longevity: atomicità compilazione questionario con transazione unica
This commit is contained in:
@@ -19,37 +19,40 @@ export function salvaCompilazione(
|
|||||||
liberi?: Record<string, string>;
|
liberi?: Record<string, string>;
|
||||||
}
|
}
|
||||||
): number {
|
): number {
|
||||||
// Validare PRIMA di aprire la sessione: altrimenti un id sbagliato lascia
|
// Validare PRIMA della transazione: se un test_id è sconosciuto, non apriamo
|
||||||
// in giro una sessione vuota.
|
// nemmeno una sessione. La validazione non è nel corpo della transazione.
|
||||||
for (const id of Object.keys(input.risposte)) {
|
for (const id of Object.keys(input.risposte)) {
|
||||||
if (!esisteTest(db, id)) throw new Error(`test_id non nel registro: ${id}`);
|
if (!esisteTest(db, id)) throw new Error(`test_id non nel registro: ${id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const sessioneId = apriSessione(db, {
|
// Avvolgi le tre scritture (apertura sessione, registra misure, inserisci note)
|
||||||
client_code: input.client_code,
|
// in un'unica transazione: tutto o niente, senza tracce parziali su dati sanitari.
|
||||||
data: input.data,
|
const tx = db.transaction(() => {
|
||||||
tipo: 'questionario',
|
const sessioneId = apriSessione(db, {
|
||||||
eta_alla_data: input.eta,
|
client_code: input.client_code,
|
||||||
quest_version: QUEST_VERSION,
|
data: input.data,
|
||||||
});
|
tipo: 'questionario',
|
||||||
|
eta_alla_data: input.eta,
|
||||||
|
quest_version: QUEST_VERSION,
|
||||||
|
});
|
||||||
|
|
||||||
registraMisure(
|
registraMisure(
|
||||||
db, sessioneId, 'questionario',
|
db, sessioneId, 'questionario',
|
||||||
Object.entries(input.risposte).map(([test_id, valore_num]) => ({ test_id, valore_num }))
|
Object.entries(input.risposte).map(([test_id, valore_num]) => ({ test_id, valore_num }))
|
||||||
);
|
|
||||||
|
|
||||||
if (input.liberi) {
|
|
||||||
const ins = db.prepare(
|
|
||||||
`INSERT INTO profilo_note (client_code, sessione_id, campo_id, testo) VALUES (?, ?, ?, ?)`
|
|
||||||
);
|
);
|
||||||
const tx = db.transaction(() => {
|
|
||||||
for (const [campo, testo] of Object.entries(input.liberi!)) {
|
if (input.liberi) {
|
||||||
|
const ins = db.prepare(
|
||||||
|
`INSERT INTO profilo_note (client_code, sessione_id, campo_id, testo) VALUES (?, ?, ?, ?)`
|
||||||
|
);
|
||||||
|
for (const [campo, testo] of Object.entries(input.liberi)) {
|
||||||
if (testo.trim() === '') continue;
|
if (testo.trim() === '') continue;
|
||||||
ins.run(input.client_code, sessioneId, campo, testo);
|
ins.run(input.client_code, sessioneId, campo, testo);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
tx();
|
|
||||||
}
|
|
||||||
|
|
||||||
return sessioneId;
|
return sessioneId;
|
||||||
|
});
|
||||||
|
|
||||||
|
return tx();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,4 +64,24 @@ describe('compilazione del questionario', () => {
|
|||||||
const n = db.prepare(`SELECT COUNT(*) n FROM sessioni`).get() as { n: number };
|
const n = db.prepare(`SELECT COUNT(*) n FROM sessioni`).get() as { n: number };
|
||||||
expect(n.n).toBe(0);
|
expect(n.n).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('fallimento a metà non lascia traccia: sessione, misure e note sono atomiche', () => {
|
||||||
|
const db = dbPronto();
|
||||||
|
// Passa null nel campo libero per provocare un errore NOT NULL nella tabella profilo_note.
|
||||||
|
// Questo forza il fallimento DOPO aver aperto la sessione e registrato le misure.
|
||||||
|
expect(() =>
|
||||||
|
salvaCompilazione(db, {
|
||||||
|
client_code: 'ISL-0001', data: '2026-08-21',
|
||||||
|
risposte: { q_riposato: 6 },
|
||||||
|
liberi: { farmaci: null as any }, // Tipo violato: NOT NULL
|
||||||
|
})
|
||||||
|
).toThrow();
|
||||||
|
// Verifica che la transazione sia stata rollback: niente sessione, niente misure, niente note.
|
||||||
|
const sessioni = db.prepare(`SELECT COUNT(*) n FROM sessioni`).get() as { n: number };
|
||||||
|
const misure = db.prepare(`SELECT COUNT(*) n FROM misure`).get() as { n: number };
|
||||||
|
const note = db.prepare(`SELECT COUNT(*) n FROM profilo_note`).get() as { n: number };
|
||||||
|
expect(sessioni.n).toBe(0);
|
||||||
|
expect(misure.n).toBe(0);
|
||||||
|
expect(note.n).toBe(0);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user