# 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 (`user` vs `user_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.id` in 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) ```typescript // 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 ```typescript // 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) ```typescript // 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 ```typescript // 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 ```yaml # .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 ```typescript // 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 ```typescript // 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 ```bash # 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 ```bash # .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 ```javascript // 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 ```bash # 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 # 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: 1. **Integration Tests** für alle kritischen User Flows (Auth, CRUD) 2. **Schema Validation** Tests für PocketBase 3. **Automated CI/CD** Pipeline mit Tests vor jedem Deploy 4. **Error Tracking** mit Sentry 5. **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_