longevity: l'endpoint del questionario apre solo longevity, e il test anti-giunzione diventa strutturale
L'endpoint /api/longevity/questionario apriva anche identity (getIdentityDb) per risolvere il client_code, violando la garanzia della spec (§4): un solo modulo, anagrafica.ts, tiene insieme le due connessioni. La logica di giunzione era gia' al posto giusto (chiamava codicePerUtente, che sta in anagrafica.ts) ma l'APERTURA delle connessioni no. - anagrafica.ts: nuova codicePerUtenteLoggato(userId), apre lei stessa identity via getIdentityDb() (e' il modulo autorizzato) e delega a codicePerUtente. L'endpoint chiama solo questa: apre esclusivamente longevity, per salvare. - tests/longevity/anagrafica.test.ts: il test anti-giunzione cercava due nomi letterali (createIdentityDb/createLongevityDb) e non vedeva getIdentityDb/ getLongevityDb, nati dopo di lui - la stessa lezione del semaforo nel Task 1 (proteggere il nome invece della cosa). Ora e' strutturale: segnala qualunque file (esclusi anagrafica.ts e db.ts) che contenga sia "Identity" sia "Longevity", in qualunque forma. Verificato con un file finto sotto src/ che apriva entrambe con i nomi nuovi: il test diventa rosso, poi torna verde dopo la cancellazione.
This commit is contained in:
@@ -4,6 +4,7 @@
|
|||||||
// risalga alla persona (richiesta del cliente, 21/08): un secondo punto di giunzione
|
// risalga alla persona (richiesta del cliente, 21/08): un secondo punto di giunzione
|
||||||
// la annullerebbe in silenzio. Un test in tests/longevity/anagrafica.test.ts lo verifica.
|
// la annullerebbe in silenzio. Un test in tests/longevity/anagrafica.test.ts lo verifica.
|
||||||
import type Database from 'better-sqlite3';
|
import type Database from 'better-sqlite3';
|
||||||
|
import { getIdentityDb } from './db';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Il prossimo codice libero: il massimo fra quelli già usati in identity.clienti E in
|
* Il prossimo codice libero: il massimo fra quelli già usati in identity.clienti E in
|
||||||
@@ -62,6 +63,17 @@ export function codicePerUtente(identity: Database.Database, userId: number): st
|
|||||||
return r?.client_code ?? null;
|
return r?.client_code ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Il codice cliente a partire dall'utente di sessione del sito: apre lei stessa la
|
||||||
|
* connessione a identity (getIdentityDb), perché QUESTO è il modulo autorizzato a farlo.
|
||||||
|
* Un endpoint che deve solo sapere "chi è" chiama questa funzione e non ha mai bisogno
|
||||||
|
* di aprire identity da sé — riceve una stringa, mai una connessione che potrebbe
|
||||||
|
* incrociarsi con quella di longevity nello stesso file.
|
||||||
|
*/
|
||||||
|
export function codicePerUtenteLoggato(userId: number): string | null {
|
||||||
|
return codicePerUtente(getIdentityDb(), userId);
|
||||||
|
}
|
||||||
|
|
||||||
/** L'eta' al momento della misura: è ciò che serve al motore, e non è un quasi-identificatore. */
|
/** L'eta' al momento della misura: è ciò che serve al motore, e non è un quasi-identificatore. */
|
||||||
export function etaAllaData(dataNascita: string, alla: string): number {
|
export function etaAllaData(dataNascita: string, alla: string): number {
|
||||||
const n = new Date(dataNascita);
|
const n = new Date(dataNascita);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { APIRoute } from 'astro';
|
import type { APIRoute } from 'astro';
|
||||||
import { getIdentityDb, getLongevityDb } from '../../../lib/longevity/db';
|
import { getLongevityDb } from '../../../lib/longevity/db';
|
||||||
import { codicePerUtente } from '../../../lib/longevity/anagrafica';
|
import { codicePerUtenteLoggato } from '../../../lib/longevity/anagrafica';
|
||||||
import { salvaDalForm } from '../../../lib/longevity/vista';
|
import { salvaDalForm } from '../../../lib/longevity/vista';
|
||||||
import { CAMPI_LIBERI } from '../../../lib/longevity/questionario';
|
import { CAMPI_LIBERI } from '../../../lib/longevity/questionario';
|
||||||
|
|
||||||
@@ -11,8 +11,10 @@ const json = (status: number, body: object) =>
|
|||||||
|
|
||||||
export const POST: APIRoute = async ({ request, locals }) => {
|
export const POST: APIRoute = async ({ request, locals }) => {
|
||||||
// Il codice cliente si ricava SEMPRE dalla sessione dell'utente loggato (spec §8), mai dal
|
// Il codice cliente si ricava SEMPRE dalla sessione dell'utente loggato (spec §8), mai dal
|
||||||
// corpo della richiesta: qui infatti il corpo non viene mai letto per questo campo.
|
// corpo della richiesta: qui infatti il corpo non viene mai letto per questo campo. La
|
||||||
const codice = codicePerUtente(getIdentityDb(), locals.user!.id);
|
// risoluzione passa da anagrafica.ts (l'unico modulo autorizzato a toccare quella
|
||||||
|
// connessione): questo file apre solo longevity, per salvare.
|
||||||
|
const codice = codicePerUtenteLoggato(locals.user!.id);
|
||||||
if (!codice) return json(403, { error: 'Nessun fascicolo cliente associato a questo utente.' });
|
if (!codice) return json(403, { error: 'Nessun fascicolo cliente associato a questo utente.' });
|
||||||
|
|
||||||
let data: Record<string, unknown>;
|
let data: Record<string, unknown>;
|
||||||
|
|||||||
@@ -88,7 +88,15 @@ describe('anagrafica pseudonimizzata', () => {
|
|||||||
expect(id.prepare(`SELECT * FROM clienti WHERE nome = 'Errato'`).get()).toBeUndefined();
|
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', () => {
|
it('nessun file in src/ (escluso anagrafica.ts e db.ts) fa riferimento sia a identity sia a longevity', () => {
|
||||||
|
// Non cerchiamo due nomi di funzione precisi (createIdentityDb/createLongevityDb): un
|
||||||
|
// domani qualcuno aggiunge getIdentityDb, o un terzo accessore con un nome diverso, e
|
||||||
|
// l'elenco invecchia senza che nessuno se ne accorga (è già successo: getIdentityDb e
|
||||||
|
// getLongevityDb sono nati DOPO questo test e sono passati indisturbati). Il verso
|
||||||
|
// giusto è strutturale: qualunque accesso alle due connessioni passa comunque per una
|
||||||
|
// funzione esportata da db.ts il cui nome contiene "Identity" o "Longevity" — quindi
|
||||||
|
// basta cercare quei due riferimenti letterali, in QUALUNQUE forma compaiano, e
|
||||||
|
// segnalare i file che li hanno tutti e due.
|
||||||
const srcDir = join(process.cwd(), 'src');
|
const srcDir = join(process.cwd(), 'src');
|
||||||
const colpevoli: string[] = [];
|
const colpevoli: string[] = [];
|
||||||
|
|
||||||
@@ -106,7 +114,7 @@ describe('anagrafica pseudonimizzata', () => {
|
|||||||
if (f === 'lib/longevity/anagrafica.ts' || f === 'lib/longevity/db.ts') continue;
|
if (f === 'lib/longevity/anagrafica.ts' || f === 'lib/longevity/db.ts') continue;
|
||||||
|
|
||||||
const src = readFileSync(path, 'utf8');
|
const src = readFileSync(path, 'utf8');
|
||||||
if (src.includes('createIdentityDb') && src.includes('createLongevityDb')) {
|
if (src.includes('Identity') && src.includes('Longevity')) {
|
||||||
colpevoli.push(f);
|
colpevoli.push(f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user