680557087e
- 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%)
402 lines
20 KiB
Markdown
402 lines
20 KiB
Markdown
# 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 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 | 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`).
|
||
|
||
## 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
|
||
|
||
```python
|
||
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, ...)`.
|