ADR-0006: Test Strategy — Pyramid, Golden-Set e CI Gating
Status
Accepted — 13 maggio 2026. Strategia di test per il progetto Akira, da implementare in W4 (settimana di setup pre-Fase 1) e validare prima del primo merge su main.
Context
Akira non e un CRUD generico: opera su tre domini ad alto rischio operativo e contrattuale, ognuno dei quali deve essere coperto da test con criteri diversi.
- Rating engine — calcola denaro reale. Un bug di arrotondamento o billing_increment errato significa fatturazione sbagliata, recupero credito impossibile o cliente in default. Il modulo gestisce ~2-5 milioni di CDR/mese e deve produrre output bit-exact rispetto al sistema legacy Stealth durante la fase di parallel run.
- Signaling (Kamailio + RTPengine + FreeSWITCH) — vincolato da SLA contrattuali (ASR/PDD/MOS). Una regressione in routing o LCR puo causare drop di chiamate, fallimento failover, perdita di traffico premium. Test richiedono SIPp scenari realistici, non solo unit test.
- Fraud detection — esposizione finanziaria diretta (IRSF, Wangiri, SIM Box). False positives bloccano clienti legittimi (MSRN ranges, viaggiatori); false negatives generano perdite cash su numeri premium internazionali. Serve un dataset golden con casi noti e regressione settimanale.
Vincoli del team:
- Team unipersonale (Massimo) + agenti AI. Non c'e bandwidth per QA manuale ricorrente: tutto cio che non e automatizzato non viene testato.
- Stack omogeneo Python 3.12 (backend, sipp-orch, packages) + TypeScript 5 (frontend, e2e). Questo abilita tooling uniforme e ridotto context-switch.
- CI/CD su GitHub Actions con deploy Ansible verso staging/prod (vedi ADR-0001).
- Budget Anthropic API limitato: i test che esercitano AgentCore devono usare mock
respx/pytest-httpxdi default; le chiamate reali sono gated a job nightly opt-in.
Senza una strategia di test esplicita rischiamo: (a) regressioni rating in produzione, (b) deploy che rompono Kamailio dialplan senza accorgersene, (c) drift OpenAPI tra backend e frontend, (d) prompt injection non scoperte fino al primo incidente reale.
Decision
Test pyramid a 4 livelli
| Livello | Conteggio target | Wall-clock target | Tooling principale |
|---|---|---|---|
| Unit | ~2000 test | < 60 s | pytest, vitest |
| Integration | ~300 test | < 5 min | testcontainers |
| Contract+SIPp | ~30 + 6 scenari | < 8 min | schemathesis, SIPp |
| E2E | ~50 flussi | < 15 min | Playwright |
La pyramid e bilanciata: la base massimizza feedback rapido per il dev, il top copre flussi business-critical.
Tooling
Backend (Python):
pytest+pytest-asyncio(modalitaauto) +pytest-xdist(-n auto).testcontainersper integration (Postgres+Timescale, Redis, Qdrant, NATS).polyfactoryper factory di Pydantic/SQLAlchemy models.hypothesisper property-based su rating e LCR.respxper mocking httpx (Anthropic, Revolut, IMAP REST).schemathesisper contract test su OpenAPI.
Frontend (TypeScript):
vitest+@testing-library/reactper unit/component.msw(Mock Service Worker) per mock fetch in jsdom.Playwrightper E2E cross-browser (Chromium + Firefox a campione).axe-coreintegrato in Playwright per accessibility smoke (WCAG AA su 5 route critiche).
Cross-protocol:
- SIPp XML scenari (vedi
apps/sipp-orch/scenarios/qa/) per testare INVITE/REGISTER/BYE contro Kamailio in staging. - k6 per load test API REST (sustained 200 RPS su
/cdr,/balance).
Coverage targets per modulo
Vincolanti come gate in pr.yml (overall) e in nightly.yml (per modulo). Riferimento: docs/conventions/definition-of-done.md.
| Modulo | Linee | Branch |
|---|---|---|
apps/backend/services/rating | ≥ 95% | ≥ 90% |
apps/backend/services/routing | ≥ 90% | ≥ 85% |
apps/backend/services/fraud | ≥ 90% | ≥ 85% |
apps/backend/services/billing | ≥ 90% | ≥ 85% |
| Altri servizi backend | ≥ 80% | ≥ 75% |
packages/* | ≥ 85% | ≥ 80% |
| Frontend componenti business | ≥ 75% | - |
Golden-set rating
Export anonimizzato di 10.000 CDR Stealth (numeri E.164 hashati SHA-256, importi preservati, billing_increment originale) versionato in tests/fixtures/golden/. Ogni run del rating Akira deve produrre output bit-exact rispetto al golden. Il test tests/golden/test_rating_golden.py e gating in nightly.yml e in pr.yml quando il path services/rating/** e modificato.
Dataset QA dedicati
- Fraud sintetico (
fraud_dataset_builder.py): 6080 CDR seedati a 42 con casi noti (Wangiri, IRSF, SIM Box, MSRN legittimi, Ping, Robocalling). Outputgolden_fraud_dataset.parquet. Target: precision ≥ 0.95, recall ≥ 0.90. - Prompt injection regression (
prompt_injection.jsonl): 30 payload classificati per categoria (jailbreak, sql_injection, impersonation, prompt_structure_injection, credential_extraction, legitimate, destructive_legitimate, vague_destructive). Gating: 100% delle entryexpected_action=refuseddevono restituire rifiuto. - Email corpus eterogeneo: 50 EML (plain, HTML, multipart, allegati PDF/XLSX, headers Gmail/M365/Postfix/Exchange) per testare l'ingest IMAP+OAuth2 (ADR-0009).
CI workflows
| Workflow | Trigger | Job principali |
|---|---|---|
pr.yml | pull_request -> main | lint, unit (BE+FE), contract OpenAPI diff |
main.yml | push main | integration testcontainers, schemathesis, build+deploy staging, E2E |
security.yml | pr + push | Trivy, Bandit, Semgrep, Gitleaks |
nightly.yml | cron 02:00 UTC | regression full + mutation testing rating + notify Telegram |
weekly.yml | cron lun 03:00 UTC | performance SIPp 1h + k6 sustained |
Branch protection: pr.yml + security.yml obbligatori per merge su main.
Consequences
Positive
- Catch precoce di regressioni rating/routing/fraud prima che raggiungano staging.
- Golden-set abilita parallel run con Stealth senza paura di drift.
- Pyramid bilanciata mantiene loop di sviluppo < 60 s sul caso comune (unit only).
- CI gating rende impossibile il merge di codice che rompe coverage minimo o contract OpenAPI.
- Dataset versionati permettono benchmark longitudinali (precision/recall fraud nel tempo).
Negative / Trade-offs
- Setup tooling settimana W4 (pre-dev) bloccante: nessun PR Fase 1 puo essere aperto prima che
pr.ymlsia green su un commit smoke. - Golden-set Stealth richiede export anonimato firmato Massimo (BLOCCANTE prima Fase 1). Senza questo, il modulo rating va a produzione cieco.
- testcontainers boot time: Postgres+Timescale+Redis+Qdrant devono bootare < 30 s su runner GitHub
ubuntu-latest. Da validare prima del primo PR; se > 30 s, fallback aservices:containerizzati nativi GHA. - CI/CD overhead +5 min per PR (lint+unit+contract). Accettabile in rapporto al ROI (catch precoce ~10x cheaper di hotfix in prod).
- Mutation testing rating (
mutmutocosmic-ray) e costoso (~20 min). Solo nightly, mai su PR.
Neutral
- Skill set richiesto al team: Python+TS gia presenti; SIPp e nuovo ma documentato e bash-friendly.
- Dipendenza GitHub Actions: in caso di outage GH, fallback locale via
actaccettato.
Implementation hints
- W4 day 1: creare
tests/skeleton +pyproject.tomlcon[tool.pytest.ini_options]e[tool.coverage]. Validareuv run pytest tests/unit -n auto< 60 s su skeleton vuoto. - W4 day 2: validare testcontainers boot time. Script
scripts/bench_testcontainers.pydeve loggare wall-clock di ogni container. - W4 day 3: importare golden-set Stealth (richiede sign-off Massimo su anonimizzazione). Salvare in
tests/fixtures/golden/cdr_stealth_v1.parquet.gz+VERSIONfile. - W4 day 4: scrivere primi 5 test rating parametrici + fixture testcontainers; aprire PR smoke per validare
pr.ymlend-to-end. - W4 day 5: setup pre-commit + dependabot. Validare che il pre-commit non rompa l'editing AI-assisted.
- Continuous: ogni nuovo modulo MVP deve includere il proprio set di unit + almeno 1 integration test nel PR introduttivo. DoD bloccante.
Alternatives considered
A. Test-light + heavy manual QA
Rejected: team unipersonale, non sostenibile. Manual QA non scala oltre 2-3 release/mese.
B. Full E2E senza unit/integration
Rejected: feedback loop > 15 min per ogni cambio, debug impossibile, costo CI runner alto.
C. Solo unit + smoke E2E (no integration)
Rejected: il rischio principale di Akira e l'interazione DB+Redis+Kamailio (race condition rating, lock contention balance). Integration con testcontainers e non negoziabile.
D. Pact (consumer-driven contract) invece di Schemathesis
Rejected per ora: Pact richiede broker dedicato e disciplina su entrambi i lati. Con team unipersonale, schemathesis su OpenAPI generato da FastAPI da copertura sufficiente. Rivalutare in v2 quando ci sara un consumer esterno (es. portale cliente).
E. Cypress invece di Playwright
Rejected: Playwright ha primitivi auto-wait, parallelizzazione nativa, e supporto WebKit/Firefox out-of-the-box, tutti rilevanti per il mockup Akira.
References
- ADR-0001 — Stack Python/FastAPI + Next.js
- ADR-0007 — CDR pipeline NATS JetStream (impatto su test integration)
docs/conventions/definition-of-done.md— coverage targets canonicitests/README.md— guida operativa per devtests/fixtures/golden/README.md— procedura import golden-set Stealth