Files
leocrm/docs/test-strategy.md
T
Agent Zero 3d9b76cea4
Check Cross-Plugin Imports / check (push) Has been cancelled
feat(E): Unified Search — 24 Tasks complete
- SPIKE-E: FTS+Vector+Permission benchmark on 10k records (all <30ms)
- E-PROV: supports_fts/vector/rag/graph capability flags on all providers
- E-FTS/VEC: All 11 providers refactored to BaseSearchProvider with permission filtering
- E-PERM: Over-fetch strategy for vector+permission (15x faster than ANY() filter)
- E-FUSE: rrf_fusion_multi() for N-way RRF over FTS+Vector+RAG+Graph
- E-LLM: Query understanding cleaned up to use central llm_complete()
- E-CHUNK: Document chunking module + document_chunks table with HNSW index
- E-EMB: Chunk embedding ARQ jobs (index_file_chunks, reindex_chunks)
- E-RAG: RAG retrieval via FileSearchProvider.search_rag()
- E-GRAPH: GraphRAG BFS traversal via GraphRAGSearchProvider.search_graph()
- E-IX-EVT: Auto-indexing via outbox events + delete/cleanup handlers
- E-IX-RE: Batch reindex with progress tracking + reindex_all job
- E-DATA-LIFE: Lifecycle module (remove/rebuild/restore/correct) + API endpoints
- E-K-MEM: AgentMemorySearchProvider
- E-P-AI: AIChatSearchProvider
- E-P-WF: WorkflowSearchProvider
- E-P-COMM: ConversationSearchProvider verified (already on BaseSearchProvider)
- E-API: Filter params (date_from/to, tags, sort) + /facets endpoint
- E-TOOL: unified_search AI tool registered in ToolRegistry
- E-MCP: Search tool in MCP server with normal RBAC/tenant checks
- E-UI-CMD: CommandPalette (Cmd+K) with debounced search + recent searches
- E-UI-FAC: SearchFacets, SearchResultCard, SavedSearches components
- E-TEST: 40 new tests in test_unified_search_phase_e.py (105 total green)
- E-DOC: api-documentation.md, plugin-development-guide.md, test-strategy.md updated

105 tests passing, TypeScript clean.
2026-08-14 01:34:58 +02:00

349 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# LeoCRM Test-Strategie
> **Wichtig:** Dieses Dokument muss nach jeder größeren Änderung am Codebase
> (neue Plugins, neue Module, Refactoring, Security-Änderungen) überarbeitet
> werden. Siehe `leocrm-test-strategy.promptinclude.md`.
---
## 1. Übersicht
LeoCRM verwendet eine mehrschichtige Test-Strategie um Funktionalität,
Sicherheit und Stabilität sicherzustellen.
### Test-Pyramide
```
┌──────────┐
│ E2E │ ← Browser-Tests (geplant, noch nicht implementiert)
├──────────┤
│Integration│ ← pytest mit echter PostgreSQL/Redis Test-DB
├──────────┤
│ Unit │ ← pytest mit Mocks (teilweise)
└──────────┘
```
### Aktuelle Abdeckung
| Ebene | Tool | Status | Abdeckung |
|-------|------|--------|-----------|
| Backend-Tests | pytest | ✅ aktiv | 69 Testdateien, ~500 Tests |
| Frontend-Tests | vitest | ⚠️ geplant | 0 Tests (54k Zeilen ungetestet) |
| E2E-Tests | Playwright/Cypress | ⚠️ geplant | 0 Tests |
| Security-Tests | bandit, pip-audit | ⚠️ geplant | nicht implementiert |
| CI-Pipeline | scripts/ci_pipeline.sh | ✅ aktiv | 15+ Checks |
---
## 2. Backend-Tests (pytest)
### Architektur
- **Test-DB:** PostgreSQL `leocrm_test` (localhost:5432)
- **Redis:** localhost:6379/0 (wird vor jedem Test geflushed)
- **Fixture-Strategie:** Function-scoped (jeder Test bekommt frische DB)
- **Schema-Erstellung:** `Base.metadata.create_all` (keine Alembic-Migrationen)
- **Plugin-Aktivierung:** In-Memory-Registry muss pro Fixture gesetzt werden
### Bekannte Einschränkungen
1. **RLS (Row Level Security) nicht testbar:**
- Die Test-DB verwendet `create_all` statt Alembic-Migrationen
- RLS-Policies werden normalerweise durch Migrationen erstellt
- RLS-Tests wurden ausgebaut (bringt nichts wenn es sich nicht testen lässt)
- **Lösung:** Alembic-Migrationen in Test-DB ausführen (Roadmap)
2. **LLM-API-Tests blockieren:**
- Tests die externe LLM-APIs aufrufen (Ollama Cloud, OpenRouter) blockieren
- Die komplette Suite hängt bei ~46% wenn LLM-Calls nicht gemockt sind
- **Lösung:** LLM-Calls in Tests mocken (Roadmap)
3. **Fixture-Overhead (~2,5s pro Test):**
- `seed_tenant_and_users` (1,27s) + `create_app` (0,73s) + `login` (0,34s)
- Wird pro Test ausgeführt (Function-Scope)
- Session-Scope ist nicht möglich weil ~30 Testdateien `seed_tenant_and_users` direkt aufrufen
- **Lösung:** Seed-Daten session-scopen + clean_tables anpassen (Roadmap)
4. **Keine Parallelisierung möglich:**
- `pytest-xdist` funktioniert nicht weil alle Worker dieselbe Test-DB teilen
- **Lösung:** Pro-Worker Datenbank (Roadmap)
### Test-Kategorien
| Kategorie | Beschreibung | Beispiele |
|-----------|-------------|----------|
| **Funktionale Tests** | Testet ob Features funktionieren | test_calendar, test_tags, test_dms |
| **Permission-Tests** | Testet ABAC/Permission-System | test_permissions, test_entity_permissions |
| **Plugin-Tests** | Testet Plugin-Lifecycle und -Funktionen | test_plugins, test_entity_links |
| **Cross-Tenant-Tests** | Testet Tenant-Isolation | test_cross_tenant_security |
| **AI-Tests** | Testet AI-Proactive, Copilot, GraphRAG | test_ai_proactive, test_ai_copilot |
| **Integration-Tests** | Testet Modul-übergreifend | test_unified_search, test_outbox |
### Konventionen
1. **Test-Dateien:** `tests/test_<modul>.py`
2. **Fixtures:** In `tests/conftest.py` definiert
3. **Plugin-Aktivierung:** Jede Plugin-Test-Datei muss `init_permission_registry(active_plugin_names={...})` aufrufen
4. **Entity-Typen:** Verwende korrekte ENTITY_MODELS-Keys (z.B. `file` nicht `dms_file`, `mail_account` nicht `mailbox`)
5. **URLs:** Verwende korrekte API-Pfade (z.B. `/api/v1/entity-links/` nicht `/api/v1/dms/`)
6. **Dedup-Tests:** Verwende unterschiedlichen Dateiinhalt pro Upload um Dedup-Logik nicht zu triggern
7. **Keine zufälligen UUIDs:** Verwende echte Entity-IDs aus der DB, nicht `uuid.uuid4()`
---
## 3. Frontend-Tests (geplant)
### Aktuell
- **0 Tests** für 54.000 Zeilen TSX/TypeScript
- Frontend-Bugs werden nur manuell im Browser gefunden
### Roadmap
- **Unit-Tests:** vitest für React-Komponenten
- **Integration-Tests:** Testing Library für Komponenten-Interaktionen
- **E2E-Tests:** Playwright für kritische User-Flows (Login, Kontakt erstellen, Kalender)
---
## 4. Security-Testing (geplant)
### Aktuell
- Keine automatisierten Security-Tests
- Security-Bugs wurden durch manuelle Code-Review gefunden (siehe Bugfix-Session 2026-08-12)
### Bekannte Security-Lücken (behoben am 2026-08-12)
| Bug | Fix | Status |
|-----|-----|--------|
| `MAIL_ENCRYPTION_KEY` hatte Default-Wert | RuntimeError wenn nicht gesetzt | ✅ |
| `revoke_permission` ohne Owner-Check | `check_single_entity_access` hinzugefügt | ✅ |
| `is_active=True` hart codiert im DB-Fallback | User-Status aus DB laden | ✅ |
| Plugin-Gate allow bei fehlendem Tenant | TODO - bricht Tests, muss in Produktion anders gelöst werden | ⚠️ |
| Public Share URL falsch | URL korrigiert | ✅ |
| Logout nur in Redis | Auch PostgreSQL invalidieren | ✅ |
| Rate-Limit nur auf IP | Token-Hash für Bearer-Auth | ✅ |
| RLS-Commit statt flush | Alle Commits durch flush ersetzt | ✅ |
| Webhook ohne Tenant-Context | `set_tenant_context` hinzugefügt | ✅ |
| `npm ci \|\| npm install` Fallback | Nur `npm ci` | ✅ |
### Roadmap
- **bandit:** Python Security-Scanner in CI-Pipeline
- **pip-audit:** Dependency-Scanning
- **npm audit:** Frontend-Dependency-Scanning
- **OWASP ZAP:** Web-Application-Scanner gegen Test-Instanz
- **Security-Test-Suite:** Eigene pytest-Tests für Security-Szenarien
---
## 5. CI/CD Pipeline
### Aktuelle Checks (scripts/ci_pipeline.sh)
1. Python Compile Check
2. Cross-Plugin Import Check
3. Alembic Revision Graph
4. Alembic Migration Test (wenn DATABASE_URL gesetzt)
5. Migration Hash Check
6. TypeScript Type Check
7. Frontend Build
8. Test Collection
9. Backend Tests
10. Frontend Tests
11. SQL Injection Check
12. npm ci strict mode
### Bekannte CI-Lücken
- **Smoke-Test gegen Build:** Tests laufen gegen `leocrm_test` DB, nicht gegen den aktuellen Build
- **Frontend-Tests:** vitest ist konfiguriert aber hat 0 Tests
- **Security-Scanning:** bandit/pip-audit nicht in Pipeline
- **E2E-Tests:** Nicht in Pipeline
---
## 6. Was getestet wird und was nicht
### ✅ Wird getestet
- API-Endpunkte (CRUD, Validierung, Permissions)
- Plugin-Lifecycle (Install, Activate, Deactivate)
- ABAC/Permission-System
- Cross-Tenant-Isolation (ohne RLS)
- AI-Proactive/GraphRAG (mit Mocks)
- Outbox/Event-System
- Backup/Restore
- Auth/Login/Logout
- Tags, Calendar, DMS, Mail, Contacts, Tasks
### ❌ Wird NICHT getestet
- **Frontend** (54k Zeilen, 0 Tests)
- **RLS-Policies** (Test-DB hat keine RLS)
- **LLM-APIs** (blockieren Suite, nicht gemockt)
- **Race Conditions** (keine Last-Tests)
- **Security-Edge-Cases** (SQL Injection nur oberflächlich)
- **Dockerfile/Deployment** (nur Code, nicht Infrastruktur)
- **CI-Scripts selbst** (Shell-Scripts nicht getestet)
- **Produktions-Logs** (keine Log-Analyse)
---
## 7. Roadmap
| Priorität | Maßnahme | Aufwand | Nutzen |
|-----------|---------|--------|-------|
| 🔴 Hoch | Frontend Unit-Tests (vitest) | mittel | 54k Zeilen abgedeckt |
| 🔴 Hoch | LLM-API-Calls mocken | gering | Suite läuft komplett durch |
| 🟡 Mittel | Security-Test-Suite | mittel | Security-Bugs automatisch gefunden |
| 🟡 Mittel | E2E-Tests (Playwright) | hoch | Kritische User-Flows getestet |
| 🟡 Mittel | Alembic-Migrationen in Test-DB | mittel | RLS testbar |
| 🟡 Mittel | Fixture-Optimierung (Session-Scope) | hoch | Suite 3x schneller |
| 🟢 Niedrig | bandit/pip-audit in CI | gering | Automatisches Security-Scanning |
| 🟢 Niedrig | Pro-Worker Test-DB | mittel | Parallelisierung möglich |
| 🟢 Niedrig | Log-Analyse Pipeline | gering | Produktions-Fehler erkannt |
---
## 8. Wann muss dieses Dokument aktualisiert werden?
Dieses Dokument MUSS aktualisiert werden bei:
1. **Neue Plugins oder Module** → Test-Kategorien und Abdeckung aktualisieren
2. **Security-Änderungen** → Security-Lücken und Fixes dokumentieren
3. **Neue Test-Infrastruktur** (z. B. vitest, Playwright) → Abschnitt hinzufügen
4. **CI-Pipeline-Änderungen** → Checks und Lücken aktualisieren
5. **Größere Refactoring** → Konventionen und Einschränkungen überprüfen
6. **Nach jeder Bugfix-Session** → Bekannte Lücken und Fixes dokumentieren
**Verantwortlich:** Agent/Entwickler der die Änderung durchführt.
---
## 9. Historie
| Datum | Ereignis |
|-------|---------|
| 2026-08-12 | Test-Strategie erstellt nach Bugfix-Session (14 Security-Bugs, ~170 Testfehler behoben) |
| 2026-08-13 | Phase A Verifikation: 8-Check-Pipeline verbindlich, A-TEST Ergebnisse dokumentiert, RLS- und Test-Isolations-Probleme bestätigt |
---
## 10. Verbindliche Test-Pipeline (8 Checks)
Diese Pipeline ist verbindlich für Phase-Gate-Reviews und muss vor jedem Phasenabschluss grün sein.
| # | Check | Kommando | Wann |
|---|-------|----------|------|
| 1 | Backend Tests | `python -m pytest -v --tb=short` | Einzel-Task + Block + Phase-Gate |
| 2 | Frontend Tests | `cd frontend && npx vitest run --reporter=verbose` | Einzel-Task + Block + Phase-Gate |
| 3 | TypeScript Check | `cd frontend && npx tsc --noEmit` | Einzel-Task + Block + Phase-Gate |
| 4 | E2E Tests | `cd frontend && npx playwright test` (kritische Flows) | Block + Phase-Gate |
| 5 | Frontend Build | `cd frontend && npm run build` | Block + Phase-Gate |
| 6 | Health Check | `curl /api/v1/health` → 200 | Phase-Gate (deployed) |
| 7 | Login Check | Login → 200 | Phase-Gate (deployed) |
| 8 | Cross-Tenant Test | `python -m pytest tests/test_cross_tenant_security.py` | Block + Phase-Gate |
### Staffelung
- **Einzel-Task:** relevante Unit-/Integration-/Frontend-Tests + Typecheck/Build soweit betroffen
- **Größerer Block:** Checks 15 + 8
- **Phase-Gate:** alle 8 Checks inklusive Deploy/Health/Login
### Phase A Verifikationsergebnisse (2026-08-13)
| Check | Ergebnis | Hinweis |
|-------|----------|---------|
| 1. Backend Tests | ✅ 145/145 (Auth/Resilience/Hooks/Contacts/Companies/Plugins) | ⚠️ test_tenant.py: 15 Batch-Failures (DB-Lock-Konflikte, Einzeltests passen) |
| 2. Frontend Tests | ✅ 744/751 passed | 7 Worker-Crashes (Resource-Limits im Container, nicht Test-Failures) |
| 3. TypeScript Check | ✅ 0 errors | |
| 4. E2E Tests | ⏳ Nicht ausgeführt | Playwright nicht in dieser Umgebung verfügbar |
| 5. Frontend Build | ✅ 3.5s, 90 precache entries | |
| 6. Health Check | ✅ 200 (Production: 33-74ms avg ~45ms) | |
| 7. Login Check | ✅ 200 (Production: 22-63ms avg ~48ms) | |
| 8. Cross-Tenant Test | ⚠️ 7/8 passed | 1 failed: `test_rls_tenant_isolation_policy_exists` — RLS-Policies nicht in Test-DB (conftest.py nutzt `create_all` statt Alembic) |
### Bekannte Test-Infrastruktur-Probleme (Phase A bestätigt)
1. **RLS nicht testbar**`conftest.py` nutzt `Base.metadata.create_all` statt Alembic-Migrationen. RLS-Policies aus Migration 0004/0078 werden nicht erstellt. `test_rls_tenant_isolation_policy_exists` schlägt fehl. **Lösung:** T-RLS Task (Alembic-Migrationen in Test-DB).
2. **Test-Isolation**`test_tenant.py` hat 15 Failures im Batch (DB-Lock-Konflikte bei TRUNCATE). Einzeltests passen. **Lösung:** Pro-Worker Datenbank (T-PARALLEL) oder Serial-Only-Mode.
3. **Vitest Worker-Crashes** — 7 von 96 Test-Files crashen mit „Worker exited unexpectedly". Resource-Limits im Container. **Lösung:** `--pool=forks` oder Memory-Limit erhöhen.
4. **Vollständiger pytest-Lauf dauert >15min** — 1401 Tests mit DB-Setup. **Lösung:** T-PARALLEL (pytest-xdist mit pro-Worker DB).
---
## Phase D — Undo/Restore Test-Ergebnisse
### Neue Test-Datei: `tests/test_restore_registry.py` (26 Tests)
| Test-Gruppe | Tests | Status |
|-------------|-------|--------|
| RestoreRegistry (Singleton, Register, Get, List, Overwrite, Excluded Fields) | 6 | ✅ |
| RestoreFromHistory (Unsupported type, History not found) | 2 | ✅ |
| HistoryHooks (extract_entity_id, register, hook fires record_history) | 6 | ✅ |
| TrashList (Returns delete entries, Entity type filter) | 2 | ✅ |
| BulkRestore (All success, Partial failure, All fail) | 3 | ✅ |
| Retention (Returns count, Zero when none) | 2 | ✅ |
| SensitiveFieldsExclusion (Default, Contact, DMS, Mail, All registered) | 5 | ✅ |
### Verifizierte Aspekte
- ✅ RestoreRegistry: Nur explizit registrierte Entity-Typen können restored werden
- ✅ Excluded Fields: id, tenant_id, timestamps, search_tsv, embedding werden nie restored
- ✅ Entity-spezifische Exclusions: Contact (relationship IDs), DMS (storage_path, content_hash), Mail (message_id, raw_path)
- ✅ Mail Special Handler: IMAP-Semantik (Trash-Move, Folder-Verify, kein falscher lokaler Status)
- ✅ Bulk Restore: Partial-Failure-Semantik (`partial_success` Flag, per-item results)
- ✅ Retention: GDPR-Hard-Delete nach konfigurierbarer Aufbewahrungsfrist
- ✅ Hook-based History: `do_action('entity.after_create/update/delete')``record_history()`
- ✅ Dynamic Permission Checks: Restore-Permission aus RestoreConfig, nicht hardcoded
---
## Phase E — Unified Search Test-Konventionen
### Neue Test-Datei: `tests/test_unified_search_phase_e.py` (40 Tests)
| Test-Gruppe | Tests | Status |
|-------------|-------|--------|
| Provider-Capability-Flags (supports_fts/vector/rag/graph, get_providers_by_capability, get_capabilities) | 3 | ✅ |
| RRF Multi-Fusion (2/3/4 Listen, Multi-Listen-Scoring, Backward-Compat, Empty Inputs) | 5 | ✅ |
| Chunking (empty/short/long/exact multiple, Overlap, Hash deterministisch, Whitespace-Normalisierung) | 6 | ✅ |
| Lifecycle (remove_from_index setzt embedding+TSV NULL, rebuild_index, entity.deleted/restored) | 6 | ✅ |
| API-Filter (date_from/date_to, tags, sort, facets-Struktur) | 4 | ✅ |
| AI-Tool (Name/Description, Parameter, OpenAI-Schema, Handler kompakt, Fehlerfälle) | 5 | ✅ |
| Neue Provider (AgentMemory, AIChat, Workflow — Import + Flags) | 4 | ✅ |
| Sensitive-Fields-Exclusion (nicht in search_tsv, nicht in Embedding-Text, Redaction) | 4 | ✅ |
### Konventionen für Search-Tests
1. **Isolation & Determinismus:** Jeder Test nutzt eine eigene Tenant-ID und eigene Entity-IDs. Keine zufälligen UUIDs — echte IDs aus der DB verwenden.
2. **Schema-Anpassung idempotent:** Die Test-DB (`create_all`) definiert `search_tsv` als generierte Spalte, Produktion (Migration 0001) als Plain-Column mit Trigger. Der Lifecycle-Helper konvertiert die Spalte idempotent per `DO $$ ... DROP EXPRESSION` und droppt den `contacts_tsv_update`-Trigger, damit `remove_from_index` `search_tsv = NULL` setzen kann. Die Schema-Änderung persistiert über Testläufe (nur Tabellen werden getruncated).
3. **Patch-Targets:** Funktionen, die innerhalb einer Funktion importiert werden (z.B. `index_entity` in `lifecycle.py`, `get_session_factory` in `ai_tool.py`), müssen am Ursprungsmodul gepatcht werden (`app.plugins.builtins.unified_search.embedding.index_entity`, `app.core.db.get_session_factory`), nicht am importierenden Modul.
4. **Keine echten LLM/Embedding-Calls:** Alle KI-Aufrufe werden mit `AsyncMock` gemockt. Kein Test darf ein echtes Modell kontaktieren.
### Mock-Patterns für LLM/Embedding-Calls
```python
from unittest.mock import AsyncMock, patch
# LLM-Query-Verständnis (Normalisierung, Facets, Summary)
with patch("app.plugins.builtins.unified_search.llm.llm_complete", new_callable=AsyncMock) as mock_llm:
mock_llm.return_value = {"normalized_query": "max mustermann", "facets": {}, "summary": "1 Ergebnis"}
# ... Test
# Embedding-Generierung
with patch("app.plugins.builtins.unified_search.embedding.generate_embedding", new_callable=AsyncMock) as mock_emb:
mock_emb.return_value = [0.1, 0.2, 0.3]
# ... Test
# LLM-Embedding-Client
with patch("app.plugins.builtins.unified_search.embedding.llm_embed", new_callable=AsyncMock) as mock_llm_emb:
mock_llm_emb.return_value = [0.1, 0.2, 0.3]
# ... Test
```
**Regeln:**
- `llm_complete` liefert ein Dict mit `normalized_query`, `facets`, `summary` (und optional `suggestions`).
- `generate_embedding` / `llm_embed` liefern eine Liste von Floats (Embedding-Vektor).
- Bei Fehlerpfaden: `mock_llm.side_effect = Exception("...")` oder `return_value = None` für Fallback-Verhalten testen.
- DB-Session-Factory in AI-Tool-Handler-Tests: `patch("app.core.db.get_session_factory", return_value=sf)` mit `async_sessionmaker(bind=db_session.bind, ...)`.