Qualità, sicurezza e criteri di accettazione
Strategia di test
| Livello | Copertura minima |
|---|---|
| Unit | Intervalli temporali, tariffe, capacità, ferie, limiti, normalizzazione città, compatibilità sedi, strategie employee/freelancer |
| Contract | API, eventi e ogni adapter import/export/versione |
| Integration | PostgreSQL/datahub, transazioni, vincoli tenant e outbox |
| End-to-end | Due utenti stesso tenant, due tenant isolati, HR/progetto/forecast/actual/delivery/report |
| Golden dataset | Costi reali/standard, forecast fattibile/impossibile, correzioni retroattive |
| Non funzionali | Load, concorrenza, retry/idempotenza, backup/restore, security/IDOR |
La perturbazione, e i modi in cui un test passa per la ragione sbagliata
Ogni invariante va perturbato: si rompe di proposito il comportamento che protegge e si verifica che il test cada. Un test che resta verde con la funzione disattivata non prova niente, e costa come uno che prova qualcosa.
Fra il 17 e il 22 agosto 2026 la perturbazione ha smascherato undici test scritti male. I modi sono diversi abbastanza da valere come promemoria:
-
L'asserzione troppo larga. Il test sul consuntivo per sede verificava che esistessero righe da qualche parte, non che fossero quelle giuste. Passava con la regola spenta.
-
La fixture che non discrimina. Il test su «vale l'ultima esecuzione del mese» aveva la più recente già in fondo alla lista: prendere «l'ultima letta» invece della «più recente» dava lo stesso risultato. Bastava invertire l'ordine.
-
Il vincolo imposto due volte. Il test sulla capacità mensile del ricalcolo per progetto non cadeva, perché a tenere il numero giusto bastava già la presenza residua giornaliera. Ha dovuto guardare anche gli avvisi.
-
Il caso che non copre il caso. I test sulla riconciliazione anagrafica verificavano l'ordine di precedenza su una coppia che non lo esercitava: perturbando l'ordine dei livelli e il segno di «già abbinata», nessuno cadeva. Servivano due casi diversi che si contendono la stessa persona.
-
La scorciatoia scambiata per regola. Un controllo sulla lunghezza dei cognomi non faceva cadere niente perché il conteggio successivo scartava comunque quei casi: era un'ottimizzazione ridondante. Lasciarla senza dirlo avrebbe fatto credere a chi la legge di poter allargare la tolleranza cambiando quel numero.
-
L'helper che conta la cosa sbagliata. Un test verificava «nessuna creazione» con una funzione che contava anche gli aggiornamenti: passava mentre l'anagrafica veniva riscritta.
-
Il totale che nasconde la riga. Il test su «un import con righe escluse non si dichiara riuscito» guardava
failed > 0: ma escludendo un dipendente cadono anche le sue ore, quindi era vero comunque, e passava anche togliendo il conteggio dell'esclusione. Un totale non attribuisce: ora cerca quella riga per chiave naturale. -
Il caso che non può collidere. «Due fasce dello stesso tariffario non collidono fra loro» passava anche togliendo l'aggiornamento dell'indice in memoria: fasce diverse hanno chiavi diverse e non collidono comunque. Il caso che avrebbe discriminato — lo stesso tariffario due volte — non arriva mai alla scrittura, perché lo staging rifiuta i codici duplicati. Il test giusto fissa quella garanzia, non un caso che non esiste.
-
La fixture collassata dalla regola sbagliata. La prova che il raggruppamento dei motivi toglie i nomi propri usava nomi finti
P1,P2,P3: si collassano già per la regola sui numeri, quindi il test passava anche togliendo quella sui nomi. Servivano nomi che differiscono per lettere. -
Il test che guarda accanto al bersaglio. Il contratto verso data-service confrontava le radici dei percorsi chiesti con quelle esposte. Ma tutto il CRUD generico passa da
/tables/:tabella: una radice sola, sempre presente. Caricando i moduli di rendicontazione il verticale chiedeva due tabelle che data-service non aveva in elenco — 400 «tabella non consentita», con la rotta che esiste e il servizio che risponde — e il test restava verde. Ora legge anche i nomi delle tabelle e l'elenco che data-service consente, e la mappa delle capability: una tabella dichiarata senza capability ricade suadmin, che non produce un errore ma un permesso più stretto del dovuto. -
Il test scritto a valle della guardia. «Le righe di totale non entrano fra i dati» guardava il modello finito, dove una riga
TOTALE PROGETTIverrebbe scartata comunque perché non ha un dipendente riconoscibile: togliendo la guardia nel lettore il test restava verde. Con un nome di persona accanto — un trascinamento, e nel file vero succede — quella riga entrerebbe eccome. Il test va scritto dove sta la guardia, non dove il caso si risolve da solo per un'altra ragione.
E una regola che ne discende, dal caso del congelamento: un test di conservazione deve cambiare l'input. Verificare che una riga «conservata» sia identica dopo un ricalcolo non prova niente se ricalcolarla darebbe comunque lo stesso risultato — si sta misurando il determinismo, non la conservazione.
Il catalogo che si controlla da solo
test/catalogo.test.js legge i codici di segnalazione dal sorgente dei
moduli e verifica che ognuno abbia una voce in modules/qualita/catalogo.js. Un
elenco scritto a mano avrebbe avuto lo stesso difetto che il test chiude: si
aggiunge un avviso, ci si dimentica dell'elenco, e nessuno se ne accorge.
Ha già funzionato sei volte contro chi l'ha scritto: i quattro codici
IMPORT_*, quelli del foglio progetti, quelli dei tariffari, quelli
dell'anagrafica personale, quelli della riconciliazione e quelli della lettura
dei moduli di rendicontazione. Ogni volta bocciando
codici aggiunti lo stesso giorno, da chi li aveva appena scritti.
Invarianti da automatizzare
- Nessun record tenant-owned senza organizzazione.
- Nessuna relazione collega entità di organizzazioni diverse.
- Nessun intervallo costo/tariffa/ruolo si sovrappone quando la regola richiede unicità.
- Nessuna assegnazione sede di un dipendente si sovrappone temporalmente a un'altra.
- Ogni associazione a un progetto con sede è compatibile, per l'intero intervallo, con la città della sede della risorsa — regola dura nel forecast, avviso nel consuntivo, dove le ore sono già state lavorate e toglierle farebbe sparire un costo reale. Il consuntivo confronta con la sede storica del giorno, non con quella corrente.
- Nessun risultato forecast supera capacità o limite progetto-risorsa.
- La somma allocata è coerente tra dettaglio e aggregati.
- Un periodo consegnato non cambia, inclusi import, job e chiamate dirette. Coperto in quattro punti: import presenze, annullamento import, modifica delle associazioni, ricalcolo del consuntivo (globale e per progetto). Il ricalcolo globale di un mese passato conserva i progetti consegnati invece di rifiutare il mese, altrimenti un solo progetto che consegna bloccherebbe l'organizzazione.
- Lo stesso evento/import ripetuto non duplica dati o calcoli. Coperto su: dipendenti, cataloghi (sedi, aree, società, orari), enti, tariffari, intervalli di costo e legami ente-tariffario.
- Ciò che un import crea si può cancellare e modificare senza smontare l'anagrafica: i cataloghi nati da un import non usano
ON DELETE RESTRICT. - Nessun abbinamento fra anagrafiche diverse avviene in silenzio quando non è certo: un abbinamento sbagliato attribuisce ore e costi a un'altra persona, e i totali tornano lo stesso.
- Un run è riproducibile dagli input/versione registrati.
Definition of Done per feature
- Regole e acceptance criteria approvati.
- Migrazione forward e rollback/mitigazione documentati; schema clean-install aggiornato.
- API e UI autorizzano server-side e rispettano il tenant.
- Audit ed eventi includono correlation e organization ID.
- Test unit/integration/E2E rilevanti verdi.
- Errori utente spiegabili, metriche e log operativi presenti.
- Documentazione utente/tecnica e checklist aggiornate.
- Nessun dato personale o segreto nei log/fixture.
KPI del prodotto
- percentuale domanda progetto coperta;
- numero/ore di risorse over-allocate (target: 0 nei piani validi);
- ore scoperte e motivazioni;
- tempo medio/p95 di ricalcolo;
- tasso errori import e righe da correggere;
- modifiche retroattive per periodo e tempo di propagazione;
- percentuale mesi consegnati entro scadenza;
- incidenti cross-tenant (target: 0).
UAT MVP avanzato
Il committente deve completare almeno questi scenari:
- Due operatori della stessa organizzazione lavorano sullo stesso progetto.
- HR crea una risorsa, assegna società, sede con validità temporale, ruolo, livello, manager, costo e ferie.
- Un dipendente accede e inserisce il proprio piano ferie.
- Si configurano le sedi, un ente con cambio tariffa e un progetto a costi standard associato a una sede.
- Si verifica che una risorsa della stessa città sia associabile e una di città diversa venga rifiutata con errore esplicito.
- Si importa un nuovo formato employee/project e si approva il diff.
- Si genera un forecast senza over-allocation, con evidenza di eventuali ore scoperte.
- Si importa il consuntivo, si corregge un mese aperto e si verifica il ricalcolo futuro.
- Si consegna un mese e si prova che ogni modifica precedente sia bloccata.
- Si esportano report coerenti per dipendente e progetto.
- Si verifica che un utente di un'altra organizzazione non possa accedere agli stessi ID.
Requisiti Release 1
- Vulnerabilità critiche/alte risolte o formalmente accettate con mitigazione.
- SLO e alert concordati; runbook di recovery provato.
- Restore completo testato su ambiente isolato.
- Migrazione di onboarding ripetibile e riconciliata con conteggi/checksum.
- Prestazioni validate sui volumi target con margine concordato.
- Audit esportabile per azioni sensibili e retention definita.
- Manuali e processo supporto pronti; ownership operativa assegnata.