Updated: - README.md: 23 → 25 Plugins (self_improvement, knowledge) - PROGRESS.md: Phase A-K done (261/261), Alembic 0136, 2174 Tests - PLATFORM_ROADMAP.md: Phase I, J, K marked as DONE - docs/api-documentation.md: 303 → 554+ endpoints - docs/test-strategy.md: ~500 → 2174 Tests, create_all description updated - docs/INSTALL.md: Alembic-Head 0090 → 0136 Deleted (17 obsolete files): - Root: ARCHITECTURE_PLAN.md, COMPLETE_SYSTEM_AUDIT.md, COMPLETE_VERNETZUNGS_AUDIT.md, ENTERPRISE_READINESS_PLAN.md, ROADMAP_VERIFICATION.md, SYSTEM_AUDIT.md, TEST_PLAN.md - docs/: audit-consolidated-errors.md, audit-fix-plan.md, audit-tracker.md, full-audit-errors.md, architecture-cleanup-plan.md, schema-authority.md, api-audit.md, phase-gate-review-g.md, phase-gate-review-h.md, arch-f-review.md
20 KiB
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 | 97 Testdateien, 2174 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(gleiche wie Produktion sync_plugin_schema.py — Alembic-Migrationen laufen nur in Produktion via prestart.sh) - Plugin-Aktivierung: In-Memory-Registry muss pro Fixture gesetzt werden
Bekannte Einschränkungen
-
RLS (Row Level Security) nicht testbar:
- Die Test-DB verwendet
create_allstatt 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)
- Die Test-DB verwendet
-
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)
-
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_usersdirekt aufrufen - Lösung: Seed-Daten session-scopen + clean_tables anpassen (Roadmap)
-
Keine Parallelisierung möglich:
pytest-xdistfunktioniert 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
- Test-Dateien:
tests/test_<modul>.py - Fixtures: In
tests/conftest.pydefiniert - Plugin-Aktivierung: Jede Plugin-Test-Datei muss
init_permission_registry(active_plugin_names={...})aufrufen - Entity-Typen: Verwende korrekte ENTITY_MODELS-Keys (z.B.
filenichtdms_file,mail_accountnichtmailbox) - URLs: Verwende korrekte API-Pfade (z.B.
/api/v1/entity-links/nicht/api/v1/dms/) - Dedup-Tests: Verwende unterschiedlichen Dateiinhalt pro Upload um Dedup-Logik nicht zu triggern
- 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)
- Python Compile Check
- Cross-Plugin Import Check
- Alembic Revision Graph
- Alembic Migration Test (wenn DATABASE_URL gesetzt)
- Migration Hash Check
- TypeScript Type Check
- Frontend Build
- Test Collection
- Backend Tests
- Frontend Tests
- SQL Injection Check
- npm ci strict mode
Bekannte CI-Lücken
- Smoke-Test gegen Build: Tests laufen gegen
leocrm_testDB, 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:
- Neue Plugins oder Module → Test-Kategorien und Abdeckung aktualisieren
- Security-Änderungen → Security-Lücken und Fixes dokumentieren
- Neue Test-Infrastruktur (z. B. vitest, Playwright) → Abschnitt hinzufügen
- CI-Pipeline-Änderungen → Checks und Lücken aktualisieren
- Größere Refactoring → Konventionen und Einschränkungen überprüfen
- 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 1–5 + 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 | RLS-Policies in Produktion gefixt (Migration 0136: app.tenant_id → app.current_tenant_id) |
Bekannte Test-Infrastruktur-Probleme (Phase A bestätigt)
-
Schema-Drift behoben —
conftest.pynutztBase.metadata.create_all(gleiche wie Produktionsync_plugin_schema.py). Alembic-Migrationen laufen nur in Produktion viaprestart.sh. Schema-Drifts wurden durch Migrationen 0134-0136 in Produktion gefixt. -
Test-Isolation —
test_tenant.pyhat 15 Failures im Batch (DB-Lock-Konflikte bei TRUNCATE). Einzeltests passen. Lösung: Pro-Worker Datenbank (T-PARALLEL) oder Serial-Only-Mode. -
Vitest Worker-Crashes — 7 von 96 Test-Files crashen mit „Worker exited unexpectedly". Resource-Limits im Container. Lösung:
--pool=forksoder Memory-Limit erhöhen. -
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_successFlag, 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
- Isolation & Determinismus: Jeder Test nutzt eine eigene Tenant-ID und eigene Entity-IDs. Keine zufälligen UUIDs — echte IDs aus der DB verwenden.
- Schema-Anpassung idempotent: Die Test-DB (
create_all) definiertsearch_tsvals generierte Spalte, Produktion (Migration 0001) als Plain-Column mit Trigger. Der Lifecycle-Helper konvertiert die Spalte idempotent perDO $$ ... DROP EXPRESSIONund droppt dencontacts_tsv_update-Trigger, damitremove_from_indexsearch_tsv = NULLsetzen kann. Die Schema-Änderung persistiert über Testläufe (nur Tabellen werden getruncated). - Patch-Targets: Funktionen, die innerhalb einer Funktion importiert werden (z.B.
index_entityinlifecycle.py,get_session_factoryinai_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. - Keine echten LLM/Embedding-Calls: Alle KI-Aufrufe werden mit
AsyncMockgemockt. Kein Test darf ein echtes Modell kontaktieren.
Mock-Patterns für LLM/Embedding-Calls
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_completeliefert ein Dict mitnormalized_query,facets,summary(und optionalsuggestions).
Phase F — Agent-System Test-Konventionen
Neue Test-Datei: tests/test_phase_f_agents.py (45 Tests)
| Test-Gruppe | Tests | Status |
|---|---|---|
| ReAct Loop (Multi-Step, max_steps, Timeout, Error-Recovery, Cost, Dry-Run, Audit) | 8 | ✅ |
| Agent-Permissions (Intersection, System-Admin, Visibility, Execute, Optimistic Lock) | 9 | ✅ |
| Approval Requests (Create, Approve/Reject/Expire, List-Filter) | 6 | ✅ |
| Skill Registry (Registration, get_by_names, keine Permission-Grants) | 3 | ✅ |
| Context Builder (System-Prompt, ReAct-Format, Sensitive-Fields, Tool-Descriptions) | 5 | ✅ |
| Data Policy (Sensitive-Fields, Provider-Compliance, Allowed-Categories) | 4 | ✅ |
| Transparency (AI-Generated-Marking, AI-Participant-Erkennung) | 2 | ✅ |
| Workstream (Message, Step, Result) | 3 | ✅ |
| Budget Limits (Run-Stopp bei Budget, Cost-Akkumulation) | 2 | ✅ |
Konventionen für Agent-Tests
- Keine echte DB / kein echtes LLM / kein Redis: Alle externen Abhängigkeiten werden mit
AsyncMock/MagicMockgemockt. Die Tests überschreiben dieconftest-Fixturesdb_setupundclean_tablesmit No-Op-Fixtures, damit kein PostgreSQL/Redis benötigt wird. - Patch-Targets am Ursprungsmodul: Funktionen, die innerhalb einer Funktion importiert werden, müssen am Ursprungsmodul gepatcht werden. Beispiel:
get_provider_compliancewird inenforce_data_policyausapp.ai.llm_clientimportiert → Patch aufapp.ai.llm_client.get_provider_compliance, nichtapp.ai.data_policy.get_provider_compliance. - Mock-LLM-Responses:
llm_completewird mitAsyncMockgemockt und liefert Dicts mitcontent,usage,cost_usd,model,raw_response(mitchoices[0].message.contentundmessage.tool_calls). - Tool-Calls: Mock-Tool-Calls haben
id,function.name,function.arguments(JSON-String). Tool-Handler werden alsAsyncMockregistriert. - Keine zufälligen UUIDs in Assertions: Echte Entity-IDs aus Mocks verwenden; UUIDs nur als generierte Test-IDs.
- SQLAlchemy-Modelle in Mocks: Für
select(model.version)in Optimistic-Lock-Testssqlalchemy.column()verwenden, nicht Plain-Strings (sonstArgumentError). - Reservierte Attributnamen: SQLAlchemy-Modelle dürfen kein
metadata-Attribut haben (reserviert in der Declarative API).ApprovalRequestnutztrequest_metadatamit DB-Spaltennamemetadataviamapped_column("metadata", ...).
Mock-Patterns für Agent-Tests
from unittest.mock import AsyncMock, MagicMock, patch
# LLM-Call
with patch("app.ai.agent_loop.llm_complete", new_callable=AsyncMock) as mock_llm:
mock_llm.return_value = {
"content": "Final answer",
"usage": {"total_tokens": 100},
"cost_usd": 0.001,
"model": "gpt-4o",
"raw_response": raw_response,
}
# ... Test
# Provider-Compliance (in data_policy importiert aus llm_client)
with patch("app.ai.llm_client.get_provider_compliance", new_callable=AsyncMock) as mock_compliance:
mock_compliance.return_value = {"allowed_data_classes": ["internal"]}
# ... Test
# Tool-Handler
handler = AsyncMock(return_value="result")
registry = MagicMock()
registry.get = lambda name: MagicMock(handler=handler) if name == "search" else None
generate_embedding/llm_embedliefern eine Liste von Floats (Embedding-Vektor).- Bei Fehlerpfaden:
mock_llm.side_effect = Exception("...")oderreturn_value = Nonefür Fallback-Verhalten testen. - DB-Session-Factory in AI-Tool-Handler-Tests:
patch("app.core.db.get_session_factory", return_value=sf)mitasync_sessionmaker(bind=db_session.bind, ...).