Skip to content

Test-Strategie — Phase 1 umgesetzt (2026-04-21)

Ausgangslage

Stand April 2026: 8 Backend-Tests, 2 Portal-Tests, 0 Web-Client-Tests. Die GitHub Actions fuer Tests sind konfiguriert aber werden bei Merges uebersprungen (Bypassed rule violations). Heute wurden drei Bugs in Production gefunden, die Tests haetten fangen koennen:

  1. requestAnimationFrame im Chat-StoreuseSyncExternalStore erfordert synchrone Benachrichtigung. Ein Unit-Test der Store-Emit-Funktion haette das sofort gezeigt.
  2. Schueler in Mitarbeiter-Spaces sichtbaruserTypes: [] bedeutete "alle anzeigen" statt "keine". Ein Test mit leerer UserType-Liste haette den falschen Default aufgedeckt.
  3. Spaces nicht geladen auf Directory-TabneedsSpaces war nur fuer create aktiviert. Ein Integrationstest der Sammelaktionen haette das fehlende Dropdown bemerkt.

Ziel

Nicht "100% Coverage", sondern: Bugs die heute in Production waren, koennen morgen nicht mehr in Production sein. Tests schuetzen die kritischen Pfade — Berechtigungen, Datensichtbarkeit, Sync-Korrektheit.


Architektur: 3 Test-Ebenen

                    ┌─────────────────────┐
                    │   E2E (spaeter)     │  ← Playwright, 5-10 Happy Paths
                    │   ~10 Tests         │  ← Login → Chat → Nachricht bleibt
                    ├─────────────────────┤
                    │   Integration       │  ← API-Endpoints mit echter DB
                    │   ~50 Tests         │  ← Berechtigungen, Sichtbarkeit
                    ├─────────────────────┤
                    │   Unit              │  ← Reine Logik, keine I/O
                    │   ~100 Tests        │  ← Store, Permissions, Filter
                    └─────────────────────┘

Ebene 1: Unit-Tests (sofort, ~2 Tage)

Reine Logik-Tests ohne Netzwerk, DB oder Browser. Schnell (~5s fuer alle), stabil, kein Setup.

Backend (prilog-backend-api):

ModulWas testenWarum
prilog-permissions.tsCapability-Aufloesung, Deny-by-Default bei fehlenden UserTypesSchueler-Sichtbarkeits-Bug
space-mode-sync.tsPower-Level-Berechnung fuer CHAT/INFOTAFEL/DISABLEDNeuer DISABLED-Modus
decorateSpace()disabledTabs Parsing, Default-WerteNeues Feature
morning-check.router.tsCheck-Generierung mit Absences, Toggle-LogikAnwesenheits-Korrektheit
matrix-object-policy.tsBereits getestet — erweitern um Edge CasesBestehende Tests stabilisieren

Web-Client (prilog-web-client) — Setup noetig:

ModulWas testenWarum
chat-store.tsemit() ruft Listener synchron auf, applySync() merged korrekt, loadRoomFromDb() bei geschlossener DBAlle 3 Chat-Bugs von heute
use-visibility.tsisVisible() mit verschiedenen UserType-Keys, fehlender MatrixSichtbarkeits-Logik
space-info-panel.tsxpotentialContacts bei userTypes: [] → leere ListeSchueler-Sichtbarkeits-Bug
contacts-hub.tsxFamilien-Dialog filtert Schueler raus, Nachname-SortierungNeues Feature

Beispiel-Test (chat-store):

typescript
import { describe, it, expect, vi } from 'vitest';

describe('chat-store emit', () => {
  it('notifies listeners synchronously', () => {
    const listener = vi.fn();
    chatStore.subscribe(listener);

    chatStore.setSyncState('syncing');

    // Listener MUSS sofort aufgerufen werden — nicht erst im naechsten Frame.
    // requestAnimationFrame wuerde hier 0 ergeben.
    expect(listener).toHaveBeenCalledTimes(1);
  });
});

describe('potentialContacts', () => {
  it('returns empty list when space has no userTypes', () => {
    const contacts = [{ userType: 'Schueler' }, { userType: 'Mitarbeiter' }];
    const spaceUserTypes = []; // kein UserType am Space

    const result = spaceUserTypes.length > 0
      ? contacts.filter(c => spaceUserTypes.some(ut => ut.label === c.userType))
      : []; // NICHT contacts!

    expect(result).toEqual([]);
  });
});

Ebene 2: Integration-Tests (Woche 2, ~3 Tage)

API-Tests die echte HTTP-Requests an Fastify senden. DB wird per Test-Transaktion isoliert (Rollback nach jedem Test).

Kritische Pfade:

Test-SuiteEndpointsPrueft
BerechtigungenGET /permissions/meVisibility-Matrix korrekt pro UserType
Space-ModusPATCH /spaces/:id/modeCHAT/INFOTAFEL/DISABLED setzen + Power-Levels
Space-MitgliederGET /spaces/:id/membersNur UserType-passende User sichtbar
Token-ExchangePOST /auth/v1/exchangeMatrix-Token → Platform-JWT, Ablauf korrekt
BootstrapGET /bootstrapModule, Branding, Context vollstaendig
Morgen-CheckGET/POST /morning-checkCheck-Generierung, Toggle, Monats-Abfrage
Familien-RelationenPOST/DELETE /family-relationsAnlegen, Loeschen, keine Duplikate

Test-Setup:

typescript
// test-utils.ts — wiederverwendbar fuer alle Integration-Tests
import { buildApp } from '../app';

export async function createTestApp() {
  const app = await buildApp({ testing: true });
  return {
    app,
    async fetch(path: string, options?: RequestInit) {
      const res = await app.inject({ method: 'GET', url: path, ...options });
      return { status: res.statusCode, body: JSON.parse(res.body) };
    },
    async cleanup() { await app.close(); },
  };
}

Ebene 3: E2E-Tests (Monat 2, ~5 Tage)

Playwright-Tests die den echten Browser steuern. Nur fuer die 5-10 wichtigsten User-Flows.

FlowSchritte
Login + ChatEinloggen → Space oeffnen → Nachricht senden → Reload → Nachricht noch da
Schueler-IsolationAls Schueler einloggen → Mitarbeiter-Spaces nicht sichtbar
Morgen-CheckAls Lehrer → Check oeffnen → Kind als fehlend markieren → Monatsansicht pruefen
Space-ModusModus auf DISABLED → Chat verschwindet → zurueck auf CHAT → Chat da
Familien-RelationSchueler auswaehlen → Elternteil hinzufuegen → nur Erwachsene in Liste

CI/CD-Integration

Phase 1: Tests muessen laufen (sofort)

yaml
# .github/workflows/test.yml — fuer alle 3 Repos
name: Test
on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 20, cache: npm }
      - run: npm ci --legacy-peer-deps
      - run: npx prisma generate  # nur Backend
      - run: npm test -- --run

Phase 2: Branch Protection (nach ~50 Tests)

  • main Branch: Tests MUESSEN grueen sein vor Merge
  • Kein Bypass mehr
  • PRs statt direkter Push auf main

Phase 3: Coverage-Gates (nach ~100 Tests)

  • Coverage-Report in CI
  • Neue Dateien muessen >80% Coverage haben
  • Bestehende Dateien: keine Regression

Aufwand

PhaseAufwandErgebnis
Unit-Tests Setup + ~30 Tests2 TageChat-Store, Permissions, Visibility abgesichert
Unit-Tests erweitern ~70 Tests3 TageAlle kritischen Logik-Pfade
Integration-Tests ~50 Tests3 TageAPI-Berechtigungen, Space-Modi, Bootstrap
E2E Setup + ~10 Flows5 TageLogin-bis-Chat, Schueler-Isolation
Gesamt~13 Tage~160 Tests, CI erzwungen

Prioritaet: Was zuerst?

Tag 1-2: Web-Client Vitest-Setup + die 5 Tests die heute's Bugs gefangen haetten:

  1. emit() ist synchron
  2. potentialContacts bei leeren UserTypes → leer
  3. loadRoomFromDb bei geschlossener DB → kein Crash
  4. isVisible() mit mitarbeiter_innen (nicht nur mitarbeiter)
  5. Familien-Dialog filtert Schueler raus

Tag 3-4: Backend-Integration fuer Berechtigungen: 6. Space-Members-Endpoint gibt nur passende UserTypes zurueck 7. Bootstrap liefert korrekte Module 8. Space-Modus DISABLED setzt Power-Levels

Danach: Systematisch erweitern, nie rueckwaerts.