b0f30f9e4c
testIdScartatiDalleAlternative: una sezione senza scelta in `scelte` (radio assente dal form) non scarta più nulla, invece di ripiegare in silenzio sulla prima alternativa (perdeva campi come f_plank_sec senza alt_core). API checkup.ts: la nota operatore si valida (lunghezza max 5000, TESTO_MAX esportato da giornale.ts) PRIMA di salvare il check-up, non più dopo — una nota troppo lunga non faceva più fallire aggiungiVoce in silenzio ma perdeva comunque la nota col check-up già scritto. maxlength sulla textarea in checkup.astro. Suite: 314 verdi, 4 rossi di piattaforma (baseline, invariati). tsc e build puliti. Refs #13 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
157 lines
8.1 KiB
TypeScript
157 lines
8.1 KiB
TypeScript
// Test B (#13): l'API checkup.ts scarta i campi di un'alternativa non scelta e aggiunge una
|
|
// voce di giornale dalla nota operatore. File separato (stesso motivo di
|
|
// correzione-checkup-api.test.ts): l'endpoint usa il singleton getLongevityDb(), e
|
|
// LONGEVITY_DB_PATH va impostata PRIMA che lo apra — non nello stesso file di altri test che
|
|
// lo inizializzano prima. A differenza di correzione-checkup-api.test.ts (un solo test) qui
|
|
// ce ne sono diversi: il singleton si apre UNA volta per tutto il file (beforeAll/afterAll,
|
|
// non chiuso e riaperto a ogni test — getLongevityDb() lo terrebbe chiuso dopo il primo
|
|
// close()), e ogni test usa un client_code proprio per non leggere le voci di giornale di un
|
|
// altro test.
|
|
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
import { mkdtempSync, rmSync } from 'node:fs';
|
|
import { join } from 'node:path';
|
|
import { tmpdir } from 'node:os';
|
|
import { getLongevityDb } from '../../src/lib/longevity/db';
|
|
import { seedRegistro, seedPesi, MODEL_VERSION } from '../../src/lib/longevity/registro';
|
|
import { vociDelGiornale } from '../../src/lib/longevity/giornale';
|
|
import { salvaCheckup } from '../../src/lib/longevity/gestionale';
|
|
|
|
// La data d'oggi vera del sistema che esegue i test (non una finta): un checkup con questa
|
|
// data non è "nel futuro" per la validazione dell'API.
|
|
const OGGI = new Date().toISOString().slice(0, 10);
|
|
|
|
let dir: string;
|
|
let primaLg: string | undefined;
|
|
let lg: ReturnType<typeof getLongevityDb>;
|
|
|
|
beforeAll(() => {
|
|
dir = mkdtempSync(join(tmpdir(), 'lg-api-alt-test-'));
|
|
primaLg = process.env.LONGEVITY_DB_PATH;
|
|
process.env.LONGEVITY_DB_PATH = join(dir, 'longevity.db');
|
|
lg = getLongevityDb();
|
|
seedRegistro(lg);
|
|
seedPesi(lg, MODEL_VERSION);
|
|
for (const code of ['ISL-0001', 'ISL-0002', 'ISL-0003', 'ISL-0004', 'ISL-0005', 'ISL-0006']) {
|
|
lg.prepare(`INSERT INTO soggetti (client_code, sesso) VALUES (?, 'F')`).run(code);
|
|
}
|
|
});
|
|
|
|
afterAll(() => {
|
|
lg.close(); // libera l'handle sul file: senza, rmSync fallisce con EPERM su Windows
|
|
if (primaLg === undefined) delete process.env.LONGEVITY_DB_PATH; else process.env.LONGEVITY_DB_PATH = primaLg;
|
|
rmSync(dir, { recursive: true, force: true });
|
|
});
|
|
|
|
describe('API checkup.ts, sezioni ad alternative e nota operatore (#13)', () => {
|
|
it('scarta i campi di un\'alternativa non scelta, anche se compilati', async () => {
|
|
const { POST } = await import('../../src/pages/api/longevity/gestionale/checkup');
|
|
const form = new FormData();
|
|
form.set('client_code', 'ISL-0001');
|
|
form.set('data', OGGI);
|
|
form.set('alt_spinta', 'pushup'); // scelta: push-up
|
|
form.set('f_pushup_rip', '15'); // dell'alternativa scelta: deve restare
|
|
form.set('f_panca_bil_carico_kg', '60'); // di un'altra alternativa (bilanciere): va scartato
|
|
form.set('f_panca_bil_rip', '5');
|
|
const richiesta = new Request('http://localhost/api/longevity/gestionale/checkup', { method: 'POST', body: form });
|
|
|
|
const res = await POST({ request: richiesta, locals: { user: { username: 'nicola' } } } as any);
|
|
expect(res.status).toBe(303);
|
|
const loc = res.headers.get('Location')!;
|
|
expect(loc).toMatch(/salvato=/);
|
|
const sessioneId = Number(new URL(loc, 'http://localhost').searchParams.get('salvato'));
|
|
|
|
const righe = lg.prepare(`SELECT test_id FROM misure WHERE sessione_id = ?`).all(sessioneId) as { test_id: string }[];
|
|
const ids = righe.map((r) => r.test_id);
|
|
expect(ids).toContain('f_pushup_rip');
|
|
expect(ids).not.toContain('f_panca_bil_carico_kg');
|
|
expect(ids).not.toContain('f_panca_bil_rip');
|
|
});
|
|
|
|
it('senza radio inviata (nessun alt_*), non si scarta nulla: un campo di un\'alternativa non-prima resta salvato (correzione passo 3, #13)', async () => {
|
|
const { POST } = await import('../../src/pages/api/longevity/gestionale/checkup');
|
|
const form = new FormData();
|
|
form.set('client_code', 'ISL-0002');
|
|
form.set('data', OGGI);
|
|
// nessun alt_core: 'ginocchia' è la prima alternativa di "Core", f_plank_sec è 'pieno'
|
|
// (la seconda) — prima della correzione questo campo veniva scartato in silenzio.
|
|
form.set('f_plank_sec', '90');
|
|
const richiesta = new Request('http://localhost/api/longevity/gestionale/checkup', { method: 'POST', body: form });
|
|
|
|
const res = await POST({ request: richiesta, locals: { user: { username: 'nicola' } } } as any);
|
|
const sessioneId = Number(new URL(res.headers.get('Location')!, 'http://localhost').searchParams.get('salvato'));
|
|
const ids = (lg.prepare(`SELECT test_id FROM misure WHERE sessione_id = ?`).all(sessioneId) as { test_id: string }[])
|
|
.map((r) => r.test_id);
|
|
expect(ids).toContain('f_plank_sec');
|
|
});
|
|
|
|
it('nota operatore non vuota: aggiunge una voce di giornale con la data del check-up e l\'operatore come autore', async () => {
|
|
const { POST } = await import('../../src/pages/api/longevity/gestionale/checkup');
|
|
const form = new FormData();
|
|
form.set('client_code', 'ISL-0003');
|
|
form.set('data', OGGI);
|
|
form.set('f_peso_kg', '70');
|
|
form.set('nota', 'Cliente motivato, riprende dopo la pausa estiva.');
|
|
const richiesta = new Request('http://localhost/api/longevity/gestionale/checkup', { method: 'POST', body: form });
|
|
|
|
const res = await POST({ request: richiesta, locals: { user: { username: 'nicola' } } } as any);
|
|
expect(res.status).toBe(303);
|
|
|
|
const voci = vociDelGiornale(lg, 'ISL-0003');
|
|
expect(voci.length).toBe(1);
|
|
expect(voci[0].testo).toBe('Cliente motivato, riprende dopo la pausa estiva.');
|
|
expect(voci[0].data).toBe(OGGI);
|
|
expect(voci[0].autore).toBe('nicola');
|
|
});
|
|
|
|
it('nota operatore vuota (o assente): nessuna voce di giornale', async () => {
|
|
const { POST } = await import('../../src/pages/api/longevity/gestionale/checkup');
|
|
const form = new FormData();
|
|
form.set('client_code', 'ISL-0004');
|
|
form.set('data', OGGI);
|
|
form.set('f_peso_kg', '70');
|
|
form.set('nota', ' '); // solo spazi: trim -> vuoto
|
|
const richiesta = new Request('http://localhost/api/longevity/gestionale/checkup', { method: 'POST', body: form });
|
|
|
|
await POST({ request: richiesta, locals: { user: { username: 'nicola' } } } as any);
|
|
expect(vociDelGiornale(lg, 'ISL-0004').length).toBe(0);
|
|
});
|
|
|
|
it('una correzione (sessione_id nel form) non porta il campo nota: nessuna voce anche se la sessione ha una data valida', async () => {
|
|
const esito = salvaCheckup(lg, {
|
|
client_code: 'ISL-0005', data: OGGI, eta: 30, operatore: 'nicola',
|
|
misure: [{ test_id: 'f_peso_kg', valore_num: 70 }],
|
|
});
|
|
if (!esito.ok) throw new Error(esito.errore);
|
|
|
|
const { POST } = await import('../../src/pages/api/longevity/gestionale/checkup');
|
|
const form = new FormData();
|
|
form.set('client_code', 'ISL-0005');
|
|
form.set('sessione_id', String(esito.sessioneId));
|
|
form.set('data', OGGI);
|
|
form.set('f_peso_kg', '71');
|
|
// niente campo "nota": la pagina non lo mostra in correzione
|
|
const richiesta = new Request('http://localhost/api/longevity/gestionale/checkup', { method: 'POST', body: form });
|
|
|
|
await POST({ request: richiesta, locals: { user: { username: 'nicola' } } } as any);
|
|
expect(vociDelGiornale(lg, 'ISL-0005').length).toBe(0);
|
|
});
|
|
|
|
it('nota oltre 5000 caratteri: errore PRIMA di salvare, nessuna sessione né voce di giornale (correzione passo 3, #13)', async () => {
|
|
const { POST } = await import('../../src/pages/api/longevity/gestionale/checkup');
|
|
const form = new FormData();
|
|
form.set('client_code', 'ISL-0006');
|
|
form.set('data', OGGI);
|
|
form.set('f_peso_kg', '70');
|
|
form.set('nota', 'x'.repeat(5001));
|
|
const richiesta = new Request('http://localhost/api/longevity/gestionale/checkup', { method: 'POST', body: form });
|
|
|
|
const res = await POST({ request: richiesta, locals: { user: { username: 'nicola' } } } as any);
|
|
expect(res.status).toBe(303);
|
|
expect(res.headers.get('Location')).toMatch(/errore=/);
|
|
|
|
const sessioni = lg.prepare(`SELECT COUNT(*) AS n FROM sessioni WHERE client_code = ?`).get('ISL-0006') as { n: number };
|
|
expect(sessioni.n).toBe(0); // niente scritto: né la sessione né la misura f_peso_kg
|
|
expect(vociDelGiornale(lg, 'ISL-0006').length).toBe(0);
|
|
});
|
|
});
|