Files
leocrm/docs/test-strategy.md
T
Agent Zero 680557087e feat(F): F-TEST + F-DOC + F-UI-TRIG — Phase F complete!
- F-TEST: tests/test_phase_f_agents.py (1425 lines, 45 tests, all pass) — ReAct Loop, Permissions, Approvals, Skills, Context Builder, Data Policy, Transparency, Workstream, Budget
- F-DOC: docs/api-documentation.md (Phase F endpoints), docs/plugin-development-guide.md (Agent chapter 32), docs/test-strategy.md (Phase F test conventions)
- F-UI-TRIG: trigger_dispatcher dispatches agents on ui.*/context.* events (already implemented in F-PROACTIVE)
- Bug fix: approval.py metadata reserved attribute renamed to request_metadata
- PROGRESS.md: Phase F marked done, ~155/223 tasks done (70%)
2026-08-17 19:36:42 +02:00

20 KiB
Raw Blame History

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 testbarconftest.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-Isolationtest_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

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).

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

  1. Keine echte DB / kein echtes LLM / kein Redis: Alle externen Abhängigkeiten werden mit AsyncMock / MagicMock gemockt. Die Tests überschreiben die conftest-Fixtures db_setup und clean_tables mit No-Op-Fixtures, damit kein PostgreSQL/Redis benötigt wird.
  2. Patch-Targets am Ursprungsmodul: Funktionen, die innerhalb einer Funktion importiert werden, müssen am Ursprungsmodul gepatcht werden. Beispiel: get_provider_compliance wird in enforce_data_policy aus app.ai.llm_client importiert → Patch auf app.ai.llm_client.get_provider_compliance, nicht app.ai.data_policy.get_provider_compliance.
  3. Mock-LLM-Responses: llm_complete wird mit AsyncMock gemockt und liefert Dicts mit content, usage, cost_usd, model, raw_response (mit choices[0].message.content und message.tool_calls).
  4. Tool-Calls: Mock-Tool-Calls haben id, function.name, function.arguments (JSON-String). Tool-Handler werden als AsyncMock registriert.
  5. Keine zufälligen UUIDs in Assertions: Echte Entity-IDs aus Mocks verwenden; UUIDs nur als generierte Test-IDs.
  6. SQLAlchemy-Modelle in Mocks: Für select(model.version) in Optimistic-Lock-Tests sqlalchemy.column() verwenden, nicht Plain-Strings (sonst ArgumentError).
  7. Reservierte Attributnamen: SQLAlchemy-Modelle dürfen kein metadata-Attribut haben (reserviert in der Declarative API). ApprovalRequest nutzt request_metadata mit DB-Spaltenname metadata via mapped_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_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, ...).