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:
requestAnimationFrameim Chat-Store —useSyncExternalStoreerfordert synchrone Benachrichtigung. Ein Unit-Test der Store-Emit-Funktion haette das sofort gezeigt.- Schueler in Mitarbeiter-Spaces sichtbar —
userTypes: []bedeutete "alle anzeigen" statt "keine". Ein Test mit leerer UserType-Liste haette den falschen Default aufgedeckt. - Spaces nicht geladen auf Directory-Tab —
needsSpaceswar nur fuercreateaktiviert. 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):
| Modul | Was testen | Warum |
|---|---|---|
prilog-permissions.ts | Capability-Aufloesung, Deny-by-Default bei fehlenden UserTypes | Schueler-Sichtbarkeits-Bug |
space-mode-sync.ts | Power-Level-Berechnung fuer CHAT/INFOTAFEL/DISABLED | Neuer DISABLED-Modus |
decorateSpace() | disabledTabs Parsing, Default-Werte | Neues Feature |
morning-check.router.ts | Check-Generierung mit Absences, Toggle-Logik | Anwesenheits-Korrektheit |
matrix-object-policy.ts | Bereits getestet — erweitern um Edge Cases | Bestehende Tests stabilisieren |
Web-Client (prilog-web-client) — Setup noetig:
| Modul | Was testen | Warum |
|---|---|---|
chat-store.ts | emit() ruft Listener synchron auf, applySync() merged korrekt, loadRoomFromDb() bei geschlossener DB | Alle 3 Chat-Bugs von heute |
use-visibility.ts | isVisible() mit verschiedenen UserType-Keys, fehlender Matrix | Sichtbarkeits-Logik |
space-info-panel.tsx | potentialContacts bei userTypes: [] → leere Liste | Schueler-Sichtbarkeits-Bug |
contacts-hub.tsx | Familien-Dialog filtert Schueler raus, Nachname-Sortierung | Neues Feature |
Beispiel-Test (chat-store):
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-Suite | Endpoints | Prueft |
|---|---|---|
| Berechtigungen | GET /permissions/me | Visibility-Matrix korrekt pro UserType |
| Space-Modus | PATCH /spaces/:id/mode | CHAT/INFOTAFEL/DISABLED setzen + Power-Levels |
| Space-Mitglieder | GET /spaces/:id/members | Nur UserType-passende User sichtbar |
| Token-Exchange | POST /auth/v1/exchange | Matrix-Token → Platform-JWT, Ablauf korrekt |
| Bootstrap | GET /bootstrap | Module, Branding, Context vollstaendig |
| Morgen-Check | GET/POST /morning-check | Check-Generierung, Toggle, Monats-Abfrage |
| Familien-Relationen | POST/DELETE /family-relations | Anlegen, Loeschen, keine Duplikate |
Test-Setup:
// 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.
| Flow | Schritte |
|---|---|
| Login + Chat | Einloggen → Space oeffnen → Nachricht senden → Reload → Nachricht noch da |
| Schueler-Isolation | Als Schueler einloggen → Mitarbeiter-Spaces nicht sichtbar |
| Morgen-Check | Als Lehrer → Check oeffnen → Kind als fehlend markieren → Monatsansicht pruefen |
| Space-Modus | Modus auf DISABLED → Chat verschwindet → zurueck auf CHAT → Chat da |
| Familien-Relation | Schueler auswaehlen → Elternteil hinzufuegen → nur Erwachsene in Liste |
CI/CD-Integration
Phase 1: Tests muessen laufen (sofort)
# .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 -- --runPhase 2: Branch Protection (nach ~50 Tests)
mainBranch: Tests MUESSEN grueen sein vor Merge- Kein
Bypassmehr - 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
| Phase | Aufwand | Ergebnis |
|---|---|---|
| Unit-Tests Setup + ~30 Tests | 2 Tage | Chat-Store, Permissions, Visibility abgesichert |
| Unit-Tests erweitern ~70 Tests | 3 Tage | Alle kritischen Logik-Pfade |
| Integration-Tests ~50 Tests | 3 Tage | API-Berechtigungen, Space-Modi, Bootstrap |
| E2E Setup + ~10 Flows | 5 Tage | Login-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:
emit()ist synchronpotentialContactsbei leeren UserTypes → leerloadRoomFromDbbei geschlossener DB → kein CrashisVisible()mitmitarbeiter_innen(nicht nurmitarbeiter)- 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.