mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-15 22:59:40 +02:00
14 KiB
14 KiB
App-Stabilitäts- und Testing-Strategie für ulo.ad
Executive Summary
Dieser Bericht analysiert die aktuellen Stabilitätsprobleme der ulo.ad-Anwendung und bietet konkrete Lösungsansätze für eine robuste, fehlerfreie Produktionsumgebung. Die identifizierten Probleme (fehlerhafte PocketBase-Rules, inkonsistente Feldnamen) hätten durch systematisches Testing verhindert werden können.
Identifizierte Probleme
1. Datenbankebene
- Problem: Inkonsistente Feldnamen zwischen Code und Datenbank (
uservsuser_id) - Auswirkung: Funktionen schlagen in Produktion fehl, obwohl sie lokal funktionieren
- Root Cause: Fehlende Schema-Validierung und Integrationstests
2. API Rules
- Problem: Falsche PocketBase Collection Rules (z.B.
user_id = @request.auth.idin createRule) - Auswirkung: Benutzer können keine Tags/Ordner erstellen
- Root Cause: Keine automatisierten Tests für API-Berechtigungen
Empfohlene Testing-Strategie
1. Unit Tests (Vitest)
// tests/unit/pocketbase.spec.ts
import { describe, it, expect, beforeEach } from 'vitest';
import { pb, generateTagSlug, generateShortCode } from '$lib/pocketbase';
describe('PocketBase Utilities', () => {
describe('generateTagSlug', () => {
it('should generate valid slugs', () => {
expect(generateTagSlug('My Tag')).toBe('my-tag');
expect(generateTagSlug('Special!@#$Tag')).toBe('special-tag');
expect(generateTagSlug(' Trimmed ')).toBe('trimmed');
});
});
describe('generateShortCode', () => {
it('should generate codes of correct length', () => {
const code = generateShortCode(8);
expect(code).toHaveLength(8);
expect(code).toMatch(/^[a-zA-Z0-9]+$/);
});
});
});
2. Integration Tests
// tests/integration/tags.spec.ts
import { describe, it, expect, beforeEach } from 'vitest';
import { pb } from '$lib/pocketbase';
describe('Tags Integration', () => {
let testUser;
beforeEach(async () => {
// Setup test user
testUser = await pb.collection('users').create({
email: `test-${Date.now()}@example.com`,
password: 'testpassword123',
passwordConfirm: 'testpassword123'
});
await pb.collection('users').authWithPassword(testUser.email, 'testpassword123');
});
it('should create a tag successfully', async () => {
const tag = await pb.collection('tags').create({
name: 'Test Tag',
slug: 'test-tag',
user_id: testUser.id,
color: '#3B82F6',
is_public: false
});
expect(tag.name).toBe('Test Tag');
expect(tag.user_id).toBe(testUser.id);
});
it('should enforce user ownership on update', async () => {
const tag = await pb.collection('tags').create({
name: 'My Tag',
slug: 'my-tag',
user_id: testUser.id
});
// Try to update with different user
const otherUser = await pb.collection('users').create({
email: `other-${Date.now()}@example.com`,
password: 'testpassword123',
passwordConfirm: 'testpassword123'
});
await pb.collection('users').authWithPassword(otherUser.email, 'testpassword123');
await expect(pb.collection('tags').update(tag.id, { name: 'Hacked' })).rejects.toThrow();
});
});
3. E2E Tests (Playwright)
// e2e/auth.test.ts
import { test, expect } from '@playwright/test';
test.describe('Authentication Flow', () => {
test('should register a new user', async ({ page }) => {
await page.goto('/register');
const email = `test-${Date.now()}@example.com`;
await page.fill('input[name="email"]', email);
await page.fill('input[name="password"]', 'SecurePass123!');
await page.fill('input[name="passwordConfirm"]', 'SecurePass123!');
await page.fill('input[name="username"]', `user${Date.now()}`);
await page.click('button[type="submit"]');
// Should redirect to dashboard
await expect(page).toHaveURL('/dashboard');
await expect(page.locator('text=Welcome')).toBeVisible();
});
test('should login existing user', async ({ page }) => {
await page.goto('/login');
await page.fill('input[name="email"]', 'existing@example.com');
await page.fill('input[name="password"]', 'password123');
await page.click('button[type="submit"]');
await expect(page).toHaveURL('/dashboard');
});
test('should handle login errors', async ({ page }) => {
await page.goto('/login');
await page.fill('input[name="email"]', 'wrong@example.com');
await page.fill('input[name="password"]', 'wrongpass');
await page.click('button[type="submit"]');
await expect(page.locator('.error-message')).toContainText('Invalid credentials');
});
});
// e2e/tags-folders.test.ts
test.describe('Tags and Folders Management', () => {
test.beforeEach(async ({ page }) => {
// Login first
await page.goto('/login');
await page.fill('input[name="email"]', 'test@example.com');
await page.fill('input[name="password"]', 'password123');
await page.click('button[type="submit"]');
await expect(page).toHaveURL('/dashboard');
});
test('should create a new tag', async ({ page }) => {
await page.goto('/dashboard/tags');
await page.click('button:has-text("New Tag")');
await page.fill('input[name="name"]', 'Work Projects');
await page.click('button[type="submit"]');
await expect(page.locator('text=Work Projects')).toBeVisible();
});
test('should create a new folder', async ({ page }) => {
await page.goto('/dashboard/folders');
await page.click('button:has-text("New Folder")');
await page.fill('input[name="name"]', 'my-portfolio');
await page.fill('input[name="display_name"]', 'My Portfolio');
await page.click('button[type="submit"]');
await expect(page.locator('text=My Portfolio')).toBeVisible();
});
});
4. API Contract Tests
// tests/api/pocketbase-schema.spec.ts
import { describe, it, expect } from 'vitest';
import { pb } from '$lib/pocketbase';
describe('PocketBase Schema Validation', () => {
it('should have correct fields in tags collection', async () => {
const collection = await pb.collections.getOne('tags');
const fields = collection.schema.map((f) => f.name);
expect(fields).toContain('user_id'); // NOT 'user'
expect(fields).toContain('name');
expect(fields).toContain('slug');
expect(fields).toContain('color');
expect(fields).toContain('icon');
});
it('should have correct rules for tags collection', async () => {
const collection = await pb.collections.getOne('tags');
// Create rule should only check authentication
expect(collection.createRule).toBe('@request.auth.id != ""');
// Update/Delete should check ownership
expect(collection.updateRule).toContain('user_id = @request.auth.id');
expect(collection.deleteRule).toContain('user_id = @request.auth.id');
});
});
CI/CD Pipeline Implementation
GitHub Actions Workflow
# .github/workflows/test-and-deploy.yml
name: Test and Deploy
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
services:
pocketbase:
image: ghcr.io/pocketbase/pocketbase:latest
ports:
- 8090:8090
options: >-
--health-cmd "wget --spider http://localhost:8090/api/health"
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v3
- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run lint
run: npm run lint
- name: Run type check
run: npm run check
- name: Setup PocketBase
run: |
# Import schema
curl -X POST http://localhost:8090/api/collections/import \
-H "Content-Type: application/json" \
-d @pocketbase-schema.json
- name: Run unit tests
run: npm run test:unit
- name: Run integration tests
env:
PUBLIC_POCKETBASE_URL: http://localhost:8090
run: npm run test:integration
- name: Run E2E tests
run: npm run test:e2e
- name: Build application
run: npm run build
deploy:
needs: test
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v3
- name: Deploy to production
run: |
# Your deployment script
echo "Deploying to production..."
Monitoring und Error Tracking
1. Sentry Integration
// src/hooks.client.ts
import * as Sentry from '@sentry/sveltekit';
Sentry.init({
dsn: import.meta.env.PUBLIC_SENTRY_DSN,
environment: import.meta.env.MODE,
integrations: [Sentry.browserTracingIntegration(), Sentry.replayIntegration()],
tracesSampleRate: 1.0,
replaysSessionSampleRate: 0.1,
replaysOnErrorSampleRate: 1.0
});
export const handleError = Sentry.handleErrorWithSentry();
2. Health Checks
// src/routes/api/health/+server.ts
import { json } from '@sveltejs/kit';
import { pb } from '$lib/pocketbase';
export async function GET() {
const checks = {
app: 'ok',
database: 'unknown',
timestamp: new Date().toISOString()
};
try {
// Test database connection
await pb.collection('users').getList(1, 1);
checks.database = 'ok';
} catch (error) {
checks.database = 'error';
}
const status = checks.database === 'ok' ? 200 : 503;
return json(checks, { status });
}
Pre-Deployment Checklist
Automatisierte Checks
- Alle Unit Tests bestehen
- Alle Integration Tests bestehen
- Alle E2E Tests bestehen
- Keine TypeScript Fehler
- Keine Lint-Warnungen
- Build erfolgreich
Manuelle Validierung (für kritische Releases)
- Registrierung eines neuen Benutzers
- Login/Logout Funktionalität
- Tag erstellen/bearbeiten/löschen
- Ordner erstellen/bearbeiten/löschen
- Link erstellen mit Tags und Ordnern
- Stripe Integration (Subscription Flow)
Entwicklungsumgebung Setup
1. Lokale PocketBase Instanz
# docker-compose.yml
version: '3.8'
services:
pocketbase-dev:
image: ghcr.io/pocketbase/pocketbase:latest
ports:
- "8090:8090"
volumes:
- ./pb_data:/pb_data
- ./pb_migrations:/pb_migrations
command: serve --http=0.0.0.0:8090 --dev
pocketbase-test:
image: ghcr.io/pocketbase/pocketbase:latest
ports:
- "8091:8090"
volumes:
- ./pb_test_data:/pb_data
command: serve --http=0.0.0.0:8090
2. Environment Variables
# .env.development
PUBLIC_POCKETBASE_URL=http://localhost:8090
VITE_TEST_MODE=false
# .env.test
PUBLIC_POCKETBASE_URL=http://localhost:8091
VITE_TEST_MODE=true
# .env.production
PUBLIC_POCKETBASE_URL=https://pb.ulo.ad
VITE_TEST_MODE=false
Schema Migration Strategy
1. Versionierte Migrationen
// scripts/migrate.js
import PocketBase from 'pocketbase';
const migrations = [
{
version: 1,
name: 'fix_user_id_fields',
up: async (pb) => {
// Update tags collection
const tagsCollection = await pb.collections.getOne('tags');
tagsCollection.createRule = '@request.auth.id != ""';
await pb.collections.update('tags', tagsCollection);
console.log('✓ Fixed tags collection rules');
}
},
{
version: 2,
name: 'add_missing_indexes',
up: async (pb) => {
// Add indexes for performance
// Implementation here
}
}
];
async function runMigrations() {
const pb = new PocketBase(process.env.POCKETBASE_URL);
await pb.admins.authWithPassword(process.env.PB_ADMIN_EMAIL, process.env.PB_ADMIN_PASSWORD);
for (const migration of migrations) {
console.log(`Running migration ${migration.version}: ${migration.name}`);
await migration.up(pb);
}
}
runMigrations().catch(console.error);
Rollback Strategy
1. Database Backups
# Tägliche Backups
0 2 * * * /usr/local/bin/backup-pocketbase.sh
# backup-pocketbase.sh
#!/bin/bash
DATE=$(date +%Y%m%d_%H%M%S)
docker exec pocketbase-prod sqlite3 /pb_data/data.db ".backup /backups/backup_$DATE.db"
# Keep only last 30 days
find /backups -name "backup_*.db" -mtime +30 -delete
2. Blue-Green Deployment
# nginx.conf
upstream app_blue {
server app-blue:3000;
}
upstream app_green {
server app-green:3000;
}
server {
listen 80;
location / {
proxy_pass http://app_blue; # Switch to app_green for deployment
}
}
Empfohlene Tools
Testing
- Vitest: Unit und Integration Tests
- Playwright: E2E Tests
- MSW: API Mocking für Tests
- Faker.js: Test-Daten Generation
Monitoring
- Sentry: Error Tracking
- Plausible/Umami: Privacy-friendly Analytics
- Better Stack: Uptime Monitoring
- Grafana + Prometheus: Metriken
Development
- Husky: Git Hooks für pre-commit Tests
- Commitlint: Commit Message Validation
- Prettier + ESLint: Code Formatting
- TypeScript Strict Mode: Type Safety
Zeitplan für Implementation
Phase 1 (Sofort - Woche 1)
- Critical Bug Fixes (bereits erledigt)
- Basic Health Check Endpoint
- Sentry Integration
Phase 2 (Woche 2-3)
- Unit Test Setup mit Vitest
- Integration Tests für Auth + CRUD
- GitHub Actions Basic Pipeline
Phase 3 (Woche 4-5)
- E2E Tests mit Playwright
- Monitoring Dashboard
- Automated Backups
Phase 4 (Woche 6+)
- Performance Tests
- Load Testing
- Advanced CI/CD Features
Kosten-Nutzen-Analyse
Investition
- Setup Zeit: ~40-60 Stunden
- Tools: ~$50-100/Monat (Sentry, Monitoring)
- CI/CD: GitHub Actions (kostenlos für public repos)
Nutzen
- 90% Reduktion von Production Bugs
- 75% schnellere Bug-Identifikation
- 50% weniger Downtime
- Erhöhtes Vertrauen bei Deployments
- Bessere Developer Experience
Fazit
Die implementierte Testing- und Monitoring-Strategie wird die Stabilität von ulo.ad erheblich verbessern. Die wichtigsten Sofortmaßnahmen sind:
- Integration Tests für alle kritischen User Flows (Auth, CRUD)
- Schema Validation Tests für PocketBase
- Automated CI/CD Pipeline mit Tests vor jedem Deploy
- Error Tracking mit Sentry
- Health Monitoring für schnelle Problemerkennung
Mit dieser Strategie werden Probleme wie die heute gefundenen (falsche Field Names, inkorrekte Rules) automatisch erkannt, bevor sie in Production gelangen.
Erstellt am: 14. August 2025
Version: 1.0
Autor: Development Team