import { describe, it, expect } from 'vitest'; import { readFileSync, readdirSync } from 'node:fs'; import { join } from 'node:path'; import { createLongevityDb, createIdentityDb } from '../../src/lib/longevity/db'; import { creaCliente, codicePerUtente, etaAllaData } from '../../src/lib/longevity/anagrafica'; describe('anagrafica pseudonimizzata', () => { it('il nome sta in identity, il sesso in longevity, e i due non si mescolano', () => { const id = createIdentityDb(':memory:'); const lg = createLongevityDb(':memory:'); const code = creaCliente(id, lg, { nome: 'Mario', cognome: 'Rossi', sesso: 'M', data_nascita: '1988-03-04' }); const inIdentity = id.prepare(`SELECT nome, cognome FROM clienti WHERE client_code = ?`).get(code) as { nome: string; cognome: string }; expect(inIdentity.nome).toBe('Mario'); const inLongevity = lg.prepare(`SELECT sesso FROM soggetti WHERE client_code = ?`).get(code) as { sesso: string }; expect(inLongevity.sesso).toBe('M'); // in longevity non deve esistere nessuna colonna che contenga il nome const dump = JSON.stringify(lg.prepare(`SELECT * FROM soggetti`).all()); expect(dump).not.toContain('Mario'); expect(dump).not.toContain('Rossi'); expect(dump).not.toContain('1988-03-04'); }); it('assegna codici progressivi e non riusa i vecchi', () => { const id = createIdentityDb(':memory:'); const lg = createLongevityDb(':memory:'); const a = creaCliente(id, lg, { nome: 'A', cognome: 'A', sesso: 'F' }); const b = creaCliente(id, lg, { nome: 'B', cognome: 'B', sesso: 'M' }); expect(a).toMatch(/^ISL-\d{4}$/); expect(b).not.toBe(a); }); it('ritrova il codice a partire dall utente del sito', () => { const id = createIdentityDb(':memory:'); const lg = createLongevityDb(':memory:'); const code = creaCliente(id, lg, { nome: 'C', cognome: 'C', sesso: 'F', user_id: 42 }); expect(codicePerUtente(id, 42)).toBe(code); expect(codicePerUtente(id, 99)).toBeNull(); }); it('calcola l eta alla data della sessione', () => { expect(etaAllaData('1988-03-04', '2026-08-21')).toBe(38); expect(etaAllaData('1988-12-31', '2026-08-21')).toBe(37); // compleanno non ancora passato }); it('un codice cancellato dall anagrafica non torna mai disponibile: le misure del vecchio non si attribuiscono al nuovo', () => { const id = createIdentityDb(':memory:'); const lg = createLongevityDb(':memory:'); const a = creaCliente(id, lg, { nome: 'A', cognome: 'A', sesso: 'F' }); const b = creaCliente(id, lg, { nome: 'B', cognome: 'B', sesso: 'M' }); // Il secondo cliente viene cancellato dall'anagrafica (disdetta, richiesta di // cancellazione, errore): le sue misure restano in longevity, pseudonimizzate. id.prepare(`DELETE FROM clienti WHERE client_code = ?`).run(b); lg.prepare(`INSERT INTO sessioni (client_code, data, tipo) VALUES (?, '2026-08-01', 'checkup')`).run(b); const c = creaCliente(id, lg, { nome: 'C', cognome: 'C', sesso: 'F' }); // Il terzo cliente riceve un codice nuovo, mai il codice riciclato del secondo. expect(c).not.toBe(b); expect(c).not.toBe(a); // Le sessioni del vecchio cliente (b) restano leggibili sotto il suo codice... const sessioniDiB = lg.prepare(`SELECT * FROM sessioni WHERE client_code = ?`).all(b); expect(sessioniDiB.length).toBe(1); // ...e non risultano in nessun modo attribuite al nuovo cliente (c). const sessioniDiC = lg.prepare(`SELECT * FROM sessioni WHERE client_code = ?`).all(c); expect(sessioniDiC.length).toBe(0); }); it('se la scrittura su longevity fallisce, quella gia fatta su identity viene annullata', () => { const id = createIdentityDb(':memory:'); const lg = createLongevityDb(':memory:'); const primaDelTentativo = (id.prepare(`SELECT COUNT(*) AS n FROM clienti`).get() as { n: number }).n; // 'Z' non è un sesso valido (CHECK IN ('M','F') su longevity.soggetti): la scrittura // su longevity fallisce per costruzione, dopo che quella su identity è già avvenuta. expect(() => creaCliente(id, lg, { nome: 'Errato', cognome: 'Errato', sesso: 'Z' as unknown as 'M' | 'F' }) ).toThrow(); const dopoIlTentativo = (id.prepare(`SELECT COUNT(*) AS n FROM clienti`).get() as { n: number }).n; expect(dopoIlTentativo).toBe(primaDelTentativo); // nessun cliente orfano rimasto in identity expect(id.prepare(`SELECT * FROM clienti WHERE nome = 'Errato'`).get()).toBeUndefined(); }); it('nessun file in src/ (escluso anagrafica.ts e db.ts) apre entrambe le connessioni', () => { const srcDir = join(process.cwd(), 'src'); const colpevoli: string[] = []; const files = readdirSync(srcDir, { recursive: true, withFileTypes: false }) as string[]; for (const f of files) { const path = join(srcDir, f); // Salta node_modules e dist if (f.includes('node_modules') || f.includes('dist')) continue; // Accetta solo .ts e .astro if (!f.endsWith('.ts') && !f.endsWith('.astro')) continue; // Esclude i moduli autorizzati if (f === 'lib/longevity/anagrafica.ts' || f === 'lib/longevity/db.ts') continue; const src = readFileSync(path, 'utf8'); if (src.includes('createIdentityDb') && src.includes('createLongevityDb')) { colpevoli.push(f); } } expect(colpevoli).toEqual([]); }); });