Files
InsanityLab-web/apps/platforms/tests/longevity/motore-caso-reale.test.ts
AdrianoDev 6301f521fd Separazione: le aree riservate diventano un'app propria (apps/platforms)
Campus, Stress Index e Longevity escono dal sito e diventano
apps.insanitylab.it, con utenti, sessioni, login e database propri.
Deciso da Adriano il 03/09/2026: una repo sola, due applicazioni
autosufficienti, cosi' che ognuna resti estraibile.

Misura che ha guidato il taglio: le tre aree condividevano col sito
SOLO l'identita' - ~170 righe fra auth.ts, middleware.ts e safe-next.ts.
Layout, librerie e dati erano gia' disgiunti.

Lato piattaforme:
- ruoli piattaforme/cliente/trainer/admin, senza quelli redazionali
- canAccessPath con default CHIUSO (nel sito era aperto: li' le rotte non
  elencate erano di chiunque fosse loggato, qui ogni sezione e' di qualcuno)
- tutta l'app protetta, si elencano le eccezioni invece dei prefissi protetti
- /api risponde 401/403, non un redirect verso una pagina di login
- tre database distinti: utenti, dati clinici senza nomi, mappatura coi nomi

Lato sito:
- ROLES tornano i tre redazionali; via le regole e il login pubblico
- il login del pannello RESPINGE un account emigrato invece di aprirgli una
  sessione: senza, landingFor lo mandava su /admin, il middleware glielo
  negava, e si formava un ciclo di redirect che sembra un guasto del sito
- l'header non mostra piu' un menu delle piattaforme: un link che esce

Verificato: sito 20 file / 146 test, piattaforme 26 file / 197 test, entrambi
i build passano, e l'app girata davvero in locale - il ruolo piattaforme entra
in Campus e viene rimbalzato da Longevity, il cliente il contrario.

Non ancora in produzione: manca il record DNS di apps.insanitylab.it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EndnceRA5WnA6rvA5iV9WL
2026-09-03 11:21:12 +00:00

82 lines
4.0 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import { createLongevityDb } from '../../src/lib/longevity/db';
import { seedRegistro, seedPesi, MODEL_VERSION } from '../../src/lib/longevity/registro';
import { apriSessione, registraMisure } from '../../src/lib/longevity/misure';
import { calcolaSessione } from '../../src/lib/longevity/motore';
/**
* Riproduce la situazione descritta nella documentazione del cliente: una persona di cui
* si conosce solo la composizione corporea, misurata dalla Wellness Tower. Un asse pieno,
* tutti gli altri scoperti. È il caso in cui un motore ingenuo mostrerebbe sei zeri.
* Dati sintetici: nessuna persona reale.
*/
describe('un profilo con una sola area misurata', () => {
it('mostra l asse coperto e dichiara insufficienti gli altri sei', () => {
const db = createLongevityDb(':memory:');
seedRegistro(db);
seedPesi(db, MODEL_VERSION);
db.prepare(`INSERT INTO soggetti (client_code, sesso) VALUES ('ISL-0001','M')`).run();
// i tre test di composizione esistono nel registro solo dopo il piano degli import:
// finche non ci sono, questo test dimostra il comportamento con zero misure fisiche
const s = apriSessione(db, { client_code: 'ISL-0001', data: '2026-08-22', tipo: 'checkup', eta_alla_data: 38 });
registraMisure(db, s, 'questionario', []);
const r = calcolaSessione(db, s);
const insufficienti = Object.values(r.assi).filter((a) => a.stato === 'insufficiente').length;
expect(insufficienti).toBe(7);
expect(r.fitnessAge).toBeNull();
});
it('nessun asse insufficiente porta con se un valore da mostrare per sbaglio', () => {
const db = createLongevityDb(':memory:');
seedRegistro(db);
seedPesi(db, MODEL_VERSION);
db.prepare(`INSERT INTO soggetti (client_code, sesso) VALUES ('ISL-0002','F')`).run();
const s = apriSessione(db, { client_code: 'ISL-0002', data: '2026-08-22', tipo: 'checkup', eta_alla_data: 35 });
registraMisure(db, s, 'questionario', []);
const r = calcolaSessione(db, s);
for (const [nome, asse] of Object.entries(r.assi)) {
if (asse.stato === 'insufficiente') {
expect((asse as { valore?: number }).valore, `${nome} espone un valore`).toBeUndefined();
}
}
});
/**
* Il caso misto: quello che la prima dashboard disegnera' davvero, non "tutto
* insufficiente". Realizza per intero la premessa del describe qui sopra — la
* composizione corporea davvero misurata dalla Wellness Tower — aggiungendo le
* due voci al registro (arrivano col piano degli import; qui bastano due righe,
* non serve aspettarle) e registrando le misure end-to-end.
*/
it('un asse valido (Composizione Corporea) e sei insufficienti, end-to-end', () => {
const db = createLongevityDb(':memory:');
seedRegistro(db);
seedPesi(db, MODEL_VERSION);
db.prepare(`INSERT INTO soggetti (client_code, sesso) VALUES ('ISL-0003','M')`).run();
db.prepare(
`INSERT INTO registro_test (test_id, etichetta, tipo_valore, curva, params, asse, sotto_dominio, attivo_da)
VALUES ('wt_grasso', 'Grasso % (Wellness Tower)', 'num', 'lin_dec', ?, 'Composizione Corporea', 'grasso', '2026-01-01')`
).run(JSON.stringify({ best: 10, worst: 40 }));
db.prepare(
`INSERT INTO registro_test (test_id, etichetta, tipo_valore, curva, params, asse, sotto_dominio, attivo_da)
VALUES ('wt_muscolo', 'Muscolo % (Wellness Tower)', 'num', 'lin_dec', ?, 'Composizione Corporea', 'muscolo', '2026-01-01')`
).run(JSON.stringify({ best: 50, worst: 30 }));
const s = apriSessione(db, { client_code: 'ISL-0003', data: '2026-08-22', tipo: 'checkup', eta_alla_data: 38 });
registraMisure(db, s, 'wellness_tower', [
{ test_id: 'wt_grasso', valore_num: 15 },
{ test_id: 'wt_muscolo', valore_num: 45 },
]);
const r = calcolaSessione(db, s);
expect(r.assi['Composizione Corporea'].stato).toBe('ok');
const insufficienti = Object.entries(r.assi)
.filter(([nome, a]) => nome !== 'Composizione Corporea' && a.stato === 'insufficiente');
expect(insufficienti.length).toBe(6);
});
});