Aggiunge il controllo dei tipi e copre con un test lo script di backup

- npm run typecheck (tsc --noEmit) entra nella checklist di fine lavoro:
  bun esegue i test senza controllare i tipi, ed è così che si erano
  accumulati errori di tipo con la suite verde.
- La logica pura del download dello storage passa in storage-core.ts,
  testata con dati finti (nessuna lettura del .env reale).
- Il download dello storage esce con errore se anche un solo file non
  si scarica, così backup.sh non dichiara completo un backup parziale.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
davideandClaude Opus 5.5 committed 2026-09-24 12:25:35 +02:00
1 parent 431530af2a
commit c6437a605c
10 files changed
+140 -26

No files matched your search

+7 -6
View File
@@ -13,7 +13,8 @@ bun test/unit/badges.test.ts # un singolo file
```
Il runner (`test/run.ts`) esegue ogni file in un processo separato, così un test
non può inquinare gli altri.
non può inquinare gli altri. bun esegue il TypeScript senza controllarne i tipi: un
test può risultare verde anche con errori di tipo, che trova solo `npm run typecheck`.
Per le push, `unit/push-sw.test.ts` esegue il service worker reale in contesti isolati,
senza pagina né rete: verifica la notifica al risveglio e le promesse di `waitUntil`.
@@ -23,12 +24,12 @@ la consegna effettiva a schermo bloccato richiede un telefono e il servizio push
## Struttura
| Cartella | Cosa verifica | Serve rete? |
| -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- |
| `unit/` | Logica di dominio pura: badge, serie, palloni, pagelle, MVP, cacche, scout, obiettivi, notifiche, parsing CSI, dati della rosa. Più le funzioni pure isolabili nei moduli con hook/rete (validazione upload, guardie push, JWT VAPID, cattura errori, avatar, guardia admin delle route) | No |
| Cartella | Cosa verifica | Serve rete? |
| -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- |
| `unit/` | Logica di dominio pura: badge, serie, palloni, pagelle, MVP, cacche, scout, obiettivi, notifiche, parsing CSI, dati della rosa. Più le funzioni pure isolabili nei moduli con hook/rete (validazione upload, guardie push, JWT VAPID, cattura errori, avatar, guardia admin delle route, lettura del `.env` ed elenco dei bucket dello script di backup) | No |
| `integration/` | Le route `/api/public/*` sul server di sviluppo: risposte, cache, validazione degli input. Più schema e permessi del Profilo Giocatore (`schema-profili`) contro il database configurato; permessi per ruolo, incluse le policy di M11, le deroghe dell'amministratore e le tabelle lasciate aperte di proposito (`permessi`), accesso alle route di notifica (`permessi-route`), semantica degli upsert e vincoli (`scritture`), le letture lato server delle route push (`lettori-server`) e la pulizia a cascata dei dati collegati alla cancellazione di un evento (`pulizia-evento`, M14) la funzione di bonifica delle righe orfane (`bonifica-evento`, M16), il centro notifiche in-app (`notifiche-utente`, M17), la nascita pubblica sincronizzata dal profilo (`nascita-pubblica`, M18) e il suo backfill per i profili preesistenti (`backfill-nascita-esistenti`, M19 — unico test che esegue SQL diretto via `docker exec` nel container Postgres, perché PostgREST non espone quell'update multi-tabella come RPC) sul database locale | Sì |
| `e2e/` | Percorsi completi sull'app servita: schermate, dati CSI fino alla pagina, file PWA, 404 | Sì |
| `helpers/` | Avvio del server di test e mini-harness condiviso | — |
| `e2e/` | Percorsi completi sull'app servita: schermate, dati CSI fino alla pagina, file PWA, 404 | Sì |
| `helpers/` | Avvio del server di test e mini-harness condiviso | — |
## Database locale in Docker
+70
View File
@@ -0,0 +1,70 @@
/** Logica pura del backup storage: `bun test/unit/backup-storage.test.ts`. Solo dati finti. */
import assert from "node:assert/strict";
import { elencaFile, leggiEnv, type Voce } from "../../backups/storage-core.ts";
import { prova, riepilogo } from "../helpers/prova";
// --- leggiEnv -----------------------------------------------------------------
await prova("toglie virgolette doppie e singole", () => {
const env = leggiEnv(`A="uno"\nB='due'\nC=tre`);
assert.deepEqual(env, { A: "uno", B: "due", C: "tre" });
});
await prova("lascia le virgolette che non fanno coppia", () => {
const env = leggiEnv(`A="aperta\nB=chiusa"\nC="mista'`);
assert.deepEqual(env, { A: '"aperta', B: 'chiusa"', C: `"mista'` });
});
await prova("conserva gli = dentro il valore", () => {
assert.equal(leggiEnv(`CHIAVE="abc=="`)["CHIAVE"], "abc==");
});
await prova("ignora righe vuote, commenti e spazi ai bordi", () => {
const env = leggiEnv(`# commento\n\n A=1 \r\nnon una variabile\n`);
assert.deepEqual(env, { A: "1" });
});
await prova("valore vuoto resta stringa vuota", () => {
assert.deepEqual(leggiEnv(`A=\nB=""`), { A: "", B: "" });
});
// --- elencaFile ---------------------------------------------------------------
const cartella = (name: string): Voce => ({ name, id: null });
const file = (name: string): Voce => ({ name, id: `id-${name}` });
function bucketFinto(albero: Record<string, Voce[]>) {
const chiesti: string[] = [];
const lista = async (prefisso: string) => {
chiesti.push(prefisso);
return albero[prefisso] ?? [];
};
return { lista, chiesti };
}
await prova("scende nelle sottocartelle e restituisce percorsi completi", async () => {
const { lista } = bucketFinto({
"": [cartella("g1"), file("radice.jpg")],
g1: [file("avatar.jpg"), cartella("doc")],
"g1/doc": [file("fronte.png")],
});
assert.deepEqual(await elencaFile(lista), ["g1/avatar.jpg", "g1/doc/fronte.png", "radice.jpg"]);
});
await prova("interroga ogni cartella una volta sola", async () => {
const { lista, chiesti } = bucketFinto({ "": [cartella("a"), cartella("b")], a: [file("x")] });
await elencaFile(lista);
assert.deepEqual(chiesti, ["", "a", "b"]);
});
await prova("bucket vuoto: nessun file", async () => {
assert.deepEqual(await elencaFile(bucketFinto({}).lista), []);
});
await prova("un errore di elenco interrompe il backup invece di saltare file", async () => {
const lista = async (prefisso: string) => {
if (prefisso === "g1") throw new Error("rete giù");
return [cartella("g1")];
};
await assert.rejects(elencaFile(lista), /rete giù/);
});
riepilogo("backup-storage");