# Review package: 1a594fb..1efae6b ## Commits 1efae6b PDF: derivazione stato (ready/stale/generating/error/missing) ## Files changed app/src/lib/server/pdf/state.test.ts | 12 +++++++ app/src/lib/server/pdf/state.ts | 64 ++++++++++++++++++++++++++++++++++++ app/src/test-utils/env-stub.ts | 4 +++ app/vitest.config.ts | 11 +++++++ 4 files changed, 91 insertions(+) ## Diff diff --git a/app/src/lib/server/pdf/state.test.ts b/app/src/lib/server/pdf/state.test.ts new file mode 100644 index 0000000..a4eb9f4 --- /dev/null +++ b/app/src/lib/server/pdf/state.test.ts @@ -0,0 +1,12 @@ +import { describe, it, expect } from 'vitest'; +import { derivePdfState } from './state'; + +describe('derivePdfState', () => { + const H = 'abc'; + it('missing se non c\'è riga', () => expect(derivePdfState(null, H)).toBe('missing')); + it('generating se in corso', () => expect(derivePdfState({ stato: 'generating', dataHash: 'old' }, H)).toBe('generating')); + it('error se errore', () => expect(derivePdfState({ stato: 'error', dataHash: 'old' }, H)).toBe('error')); + it('ready se hash combacia', () => expect(derivePdfState({ stato: 'ready', dataHash: H }, H)).toBe('ready')); + it('stale se ready ma hash diverso', () => expect(derivePdfState({ stato: 'ready', dataHash: 'old' }, H)).toBe('stale')); + it('missing se ready ma senza hash', () => expect(derivePdfState({ stato: 'ready', dataHash: null }, H)).toBe('missing')); +}); diff --git a/app/src/lib/server/pdf/state.ts b/app/src/lib/server/pdf/state.ts new file mode 100644 index 0000000..aec929d --- /dev/null +++ b/app/src/lib/server/pdf/state.ts @@ -0,0 +1,64 @@ +import { createClient } from '@libsql/client'; +import { env } from '$env/dynamic/private'; +import { db } from '$lib/server/db'; +import { dvrPdf } from '$lib/server/db/schema'; +import { eq } from 'drizzle-orm'; +import { computeDvrFingerprint } from './fingerprint'; + +export type PdfState = 'ready' | 'stale' | 'generating' | 'error' | 'missing'; + +export function derivePdfState( + row: { stato: string | null; dataHash: string | null } | null, + currentHash: string +): PdfState { + if (!row || !row.stato) return 'missing'; + if (row.stato === 'generating') return 'generating'; + if (row.stato === 'error') return 'error'; + if (row.stato === 'ready') { + if (!row.dataHash) return 'missing'; + return row.dataHash === currentHash ? 'ready' : 'stale'; + } + return 'missing'; +} + +export type PdfStatus = { + state: PdfState; + currentHash: string; + generatoIl: string | null; + r2Key: string | null; + errorMsg: string | null; +}; + +function fpClient() { + return createClient({ + url: env.DATABASE_URL ?? 'file:../import-tools/db/dev.db', + authToken: env.DATABASE_AUTH_TOKEN + }); +} + +export async function getPdfStatus(agenziaId: number): Promise { + const client = fpClient(); + const currentHash = await computeDvrFingerprint(client, agenziaId); + const [row] = await db.select().from(dvrPdf).where(eq(dvrPdf.agenziaId, agenziaId)); + return { + state: derivePdfState(row ?? null, currentHash), + currentHash, + generatoIl: row?.generatoIl ?? null, + r2Key: row?.r2Key ?? null, + errorMsg: row?.errorMsg ?? null + }; +} + +// `_currentHash` è intenzionalmente non usato nel corpo: il data_hash viene +// persistito solo a generazione completata (Task 4), non al markGenerating. +// Il parametro è mantenuto nella firma perché l'endpoint (Task 6) lo passa. +export async function markGenerating(agenziaId: number, _currentHash: string): Promise { + const now = new Date().toISOString(); + await db + .insert(dvrPdf) + .values({ agenziaId, stato: 'generating', updatedAt: now }) + .onConflictDoUpdate({ + target: dvrPdf.agenziaId, + set: { stato: 'generating', updatedAt: now, errorMsg: null } + }); +} diff --git a/app/src/test-utils/env-stub.ts b/app/src/test-utils/env-stub.ts new file mode 100644 index 0000000..f1c21b1 --- /dev/null +++ b/app/src/test-utils/env-stub.ts @@ -0,0 +1,4 @@ +// Stub di $env/dynamic/private per Vitest (fuori da SvelteKit runtime). +// Espone le variabili d'ambiente del processo node, così i moduli che importano +// `env` da questo path possono essere caricati senza errori nei test. +export const env = process.env as Record; diff --git a/app/vitest.config.ts b/app/vitest.config.ts index b4cb01b..7edd8a0 100644 --- a/app/vitest.config.ts +++ b/app/vitest.config.ts @@ -1,8 +1,19 @@ import { defineConfig } from 'vitest/config'; +import path from 'path'; export default defineConfig({ + resolve: { + alias: { + $lib: path.resolve(__dirname, 'src/lib'), + // Stub SvelteKit runtime modules — non disponibili fuori da SvelteKit. + // I test che importano moduli con $env devono mockare le funzioni async + // che lo usano; i test sulle funzioni pure (come derivePdfState) ignorano + // questa dipendenza perché la funzione non la chiama. + '$env/dynamic/private': path.resolve(__dirname, 'src/test-utils/env-stub.ts') + } + }, test: { include: ['src/**/*.test.ts'], environment: 'node' } });