Files
InsanityLab-web/.superpowers/sdd/2026-08-22-longevity-interfaccia/review-finale-report.md
T
Adriano b913a2bf90 registri dei piani in git (AI-OS#309)
Refs gitea:Adriano/AI-OS#310

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-06 10:36:26 +02:00

11 KiB
Raw Blame History

Review finale interfaccia Longevity — report

Repo: /data/Sorgenti/Clienti/InsanityLab/WebSite/insanitylab-website Branch: feat/longevity (nessun checkout, nessun push) Procedura: TDD per ogni correzione (test rosso → codice → verde), e per ogni test nuovo un tentativo di romperlo per confermare che discrimina davvero.

Stato di partenza (verificato prima di toccare codice)

$ npm test
...
 Test Files  1 failed | 39 passed (40)
      Tests  1 failed | 327 passed (328)

$ npx tsc --noEmit
(nessun output)

Il fallito era tests/modifiche-agosto.test.ts (fuori perimetro), atteso.

1. Il blocco — nessuno chiama il motore

Test scritto per primo (tests/longevity/salvataggio-e2e.test.ts): parte da salvaDalForm (la funzione che chiama l'endpoint reale) con venti risposte, arriva a refertoDi, non chiama mai calcolaSessione/salvaScore a mano.

Rosso prima del fix:

 FAIL  tests/longevity/salvataggio-e2e.test.ts > ... > venti risposte compilate producono un referto ...
AssertionError: expected 0 to be greater than 0
 ❯ tests/longevity/salvataggio-e2e.test.ts:46:26
    | expect(righeScore.n).toBeGreaterThan(0);

Fix: src/lib/longevity/vista.ts, salvaDalForm — dentro un'unica db.transaction, dopo salvaCompilazione: salvaScore(db, sessioneId, calcolaSessione(db, sessioneId)). better-sqlite3 annida automaticamente con savepoint le transazioni interne di salvaCompilazione e salvaScore.

Verde dopo il fix, suite longevity completa: 148→ poi 162 test (vedi sotto), tutti verdi.

2. Il codice cliente può ancora arrivare dal browser

Verificato il difetto del vecchio test (regex su body.client_code/corpo.client_code, ma il file usa la variabile data):

# iniettata la vulnerabilità vera in src/pages/api/longevity/questionario.ts:
#   if (typeof data.client_code === 'string') codice = data.client_code;
$ npx vitest run tests/longevity/questionario-pagina.test.ts
 Test Files  1 passed (1)
      Tests  7 passed (7)   ← suite tutta verde CON la vulnerabilità presente

Fix strutturale: src/pages/api/longevity/questionario.ts — nuovo tipo CorpoQuestionario (risposte, liberi, consensoSanitario, eta; niente client_code) e corpoDaJson() che estrae solo quei campi. Verificato che il tipo blocca davvero l'errore a compile-time:

# aggiunta `console.log(corpo.client_code)` dopo l'estrazione:
$ npx tsc --noEmit
src/pages/api/longevity/questionario.ts(73,21): error TS2339: Property 'client_code'
does not exist on type 'CorpoQuestionario'.

Nuovo test comportamentale (sostituisce quello vacuo, in tests/longevity/questionario-pagina.test.ts): chiama l'endpoint POST vero (dbs file-backed via IDENTITY_DB_PATH/LONGEVITY_DB_PATH, due clienti veri) con un client_code estraneo nel corpo, verifica che la sessione finisca sotto il cliente della sessione, non quello iniettato.

Rosso con la vulnerabilità reintrodotta:

AssertionError: expected 'ISL-0002' to be 'ISL-0001'

Verde con il fix.

3. Il consenso: tre difetti

(a) Consenso dato non testato

Verificato: cancellando registraConsenso(...) dal ramo affermativo di salvaDalForm, npx vitest run tests/longevity restava 148/148 verdi (nessun rosso).

Test aggiunto in questionario-pagina.test.ts: verifica 1 riga in profilo_note con campo_id='consenso_sanitario' e sessione_id corretto dopo un salvataggio affermativo. Rosso con la riga rimossa, verde col fix presente.

(b) Consenso fuori transazione

Test aggiunto: tests/longevity/consenso-atomico.test.ts, mocka calcolaSessione (via vi.mock) per farlo lanciare DOPO che sessione e misure sono già scritte, verifica che sessione/misure/score/consenso tornino TUTTI a zero.

Rosso contro il codice originale (pre-fix, dove salvaDalForm non chiamava nemmeno il motore):

AssertionError: expected [Function] to throw an error

(il mock di calcolaSessione non veniva mai invocato — prova indiretta che anche il punto 1 era assente). Verde con il fix (transazione unica in salvaDalForm).

(c) Versione sbagliata sul consenso

Aggiunta CONSENT_VERSION in src/lib/longevity/questionario.ts, accanto a QUEST_VERSION. registraConsenso (vista.ts) ora scrive consenso ${CONSENT_VERSION} invece di questionario ${QUEST_VERSION}.

Test aggiunto, rotto e ripristinato:

# tornato a QUEST_VERSION nella nota:
AssertionError: expected 'Consenso al trattamento dei dati sani…' to contain 'consenso v1.0'
Received: "...— questionario v1.0, registrato il 2026-08-22"

4. La prima apertura non comunica

Nuovo file tests/longevity/prima-apertura.test.ts (7 test, basati su lettura del sorgente — non esiste un harness di rendering Astro/React in questo repo e non erano ammesse dipendenze nuove), tutti scritti PRIMA del fix e verificati rossi:

$ npx vitest run tests/longevity/prima-apertura.test.ts   (prima del fix)
 Tests  7 failed (7)

Fix:

  • src/pages/longevity/io.astro — carta Fitness Age: bordo tratteggiato + "Copertura dati: X%" anche quando manca (nuovo campo fitnessAgeCopertura esposto da refertoDi, vista.ts); numero formattato con formattaNumero.
  • src/components/longevity/Radar.tsx — paragrafo di spiegazione spostato PRIMA del grafico; aggiunta frase "Questi test si fanno in sala, con il trainer: prenota la prossima sessione..." sopra l'elenco di ciò che manca.
  • src/components/longevity/MacroScore.astro — riga "Copertura dati: X%" mostrata SEMPRE (non solo quando il valore manca); valore formattato con formattaNumero.
  • src/lib/longevity/formato.ts (nuovo) — formattaNumero(n) = n.toLocaleString('it-IT'), virgola decimale. Le etichette PERFORMANCE/ENERGY/RECOVERY non sono state tradotte (nomi di prodotto).

Ogni singola assunzione (7) è stata rotta manualmente per verificare che il test corrispondente diventasse rosso, poi ripristinata:

$ npx vitest run tests/longevity/prima-apertura.test.ts   (con MacroScore.astro riportato al vecchio comportamento)
 Tests  2 failed | 5 passed (7)
$ npx vitest run tests/longevity/prima-apertura.test.ts   (con io.astro riportato al vecchio comportamento)
 Tests  3 failed | 4 passed (7)
$ npx vitest run tests/longevity/prima-apertura.test.ts   (con Radar.tsx riportato al vecchio comportamento)
 Tests  2 failed | 5 passed (7)

Tutti e 7 i sotto-controlli discriminano davvero. Ripristinato il codice fissato dopo ogni prova (diff a zero contro la versione corretta, verificato con diff).

Anche refertoDi (dato, non solo template) ha un test dedicato in referto.test.ts: fitnessAgeCopertura deve restare leggibile anche quando lo stato è insufficiente — rosso prima (undefined), verde dopo (0).

5. Chi ha compilato non può più ricompilare

Collegamento aggiunto in fondo al referto (io.astro, dentro {referto && (...)}): link a /longevity/questionario. Coperto dallo stesso file prima-apertura.test.ts (ultimo describe), rosso prima del fix, verde dopo, verificato rompendolo e ripristinandolo (vedi sopra, terzo blocco "3 failed").

6. Due controlli che elencano invece di guardare

(a) Query nelle pagine

tests/longevity/referto.test.ts, test "nessuna pagina interroga il database da sola": prima elencava due file a mano. Provato il buco: creata prova-query.astro con db.prepare('SELECT 1') dentro src/pages/longevity/, la suite restava verde. Riscritto per scandire tutte le .astro sotto src/pages/longevity/ con readdirSync ricorsivo. Riprovato lo stesso file spurio: ora il test va rosso; rimosso il file, torna verde.

(b) Giunzione identity/longevity via createIdentityDb

Come richiesto, non ho provato a chiudere il buco con un test più furbo (già tentato e aggirato due volte, secondo il commento esistente). Ho verificato empiricamente il buco reale: un file che chiama createIdentityDb() (esportata, a differenza del singleton privato getIdentityDb) e apre longevity.db con una connessione propria — mai col nome getLongevityDb/createLongevityDb, quindi senza mai scrivere "Longevity" con l'iniziale maiuscola — passa indisturbato:

$ npx vitest run tests/longevity/anagrafica.test.ts   (con il file-sonda in src/lib/longevity/)
 Test Files  1 passed (1)
      Tests  7 passed (7)

Corretto solo il commento in tests/longevity/anagrafica.test.ts per dichiarare onestamente questo buco specifico (createIdentityDb esportata per creaCliente, quindi non chiudibile come si è chiuso getIdentityDb senza cambiare quella funzione). Nessuna modifica alla logica del test.

Verifica finale

$ npm test
 Test Files  1 failed | 43 passed (44)
      Tests  1 failed | 343 passed (344)

$ npx tsc --noEmit
(nessun output — pulito)

Il solo fallito resta tests/modifiche-agosto.test.ts (fuori perimetro, atteso). 16 test nuovi rispetto al punto di partenza (328 → 344), nessuno regredito.

File toccati

 M src/components/longevity/MacroScore.astro
 M src/components/longevity/Radar.tsx
 M src/lib/longevity/questionario.ts        (+ CONSENT_VERSION)
 M src/lib/longevity/vista.ts               (+ transazione unica, fitnessAgeCopertura)
 M src/pages/api/longevity/questionario.ts  (+ CorpoQuestionario tipato)
 M src/pages/longevity/io.astro
 M tests/longevity/anagrafica.test.ts       (solo commento)
 M tests/longevity/questionario-pagina.test.ts
 M tests/longevity/referto.test.ts
?? src/lib/longevity/formato.ts
?? tests/longevity/consenso-atomico.test.ts
?? tests/longevity/formato.test.ts
?? tests/longevity/prima-apertura.test.ts
?? tests/longevity/salvataggio-e2e.test.ts
  1. Vecchio test §2 (regex body.client_code): vulnerabilità vera iniettata → suite verde (confermato vacuo).
  2. Nuovo test §2 (comportamentale): stessa vulnerabilità iniettata → rosso.
  3. Tipo CorpoQuestionario: corpo.client_code aggiunto al codice → tsc --noEmit rifiuta (TS2339).
  4. §1: registraConsenso/motore rimossi dal ramo affermativo → nuovo test rosso (consenso-atomico.test.ts non lancia neppure, prova indiretta del blocco originale).
  5. §3a: registraConsenso rimossa dal ramo affermativo → nuovo test rosso; verificato anche che la suite INTERA restasse verde senza il nuovo test (vacuità originale).
  6. §3c: CONSENT_VERSION sostituita con QUEST_VERSION nella nota → nuovo test rosso.
  7. §4 MacroScore: copertura-sempre e formattaNumero rimossi → 2 test rossi.
  8. §4 io.astro: nota Fitness Age, formattaNumero, link ricompilazione rimossi → 3 test rossi.
  9. §4/§5 Radar: spiegazione riportata sotto, invito trainer rimosso → 2 test rossi.
  10. §6a: pagina spuria con SELECT in src/pages/longevity/ → test riscritto la intercetta (prima non la vedeva).

Tutte le 10 rotture sono state ripristinate immediatamente dopo la verifica; lo stato finale del repo corrisponde al codice corretto (confermato con diff contro le copie salvate prima di ogni prova, e con la suite completa verde alla fine).