Files
CRAPP/test/integration/backfill-nascita-esistenti.test.ts
davideandClaude Sonnet 5 cdd433cb15 Copre con un test il backfill di M19 (nascita dei profili preesistenti
M19 era stato applicato in produzione senza copertura automatica, solo
verificato a mano. Il test simula un profilo compilato prima di M18 ed
esegue lo stesso SQL del backfill via psql, perché PostgREST non espone
un update multi-tabella come RPC.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-15 13:22:19 +02:00

135 lines
6.0 KiB
TypeScript

/**
* Backfill di M19 (`m19_backfill_nascita_da_profili_esistenti`):
* `bun test/integration/backfill-nascita-esistenti.test.ts`.
*
* M18 ha aggiunto `giocatori_squadra.nascita`, sincronizzata da un trigger su
* `profili_giocatore` — ma solo per le scritture *future*. In produzione questo ha lasciato
* scoperti i profili già compilati **prima** di M18: la nascita restava vuota finché
* `m19_backfill_nascita_da_profili_esistenti` non ha rieseguito una tantum lo stesso
* allineamento (vedi DD-031, aggiornamento del 15/09/2026).
*
* A differenza della bonifica eventi orfani (M15→M16, `bonifica_dati_evento_orfani()`),
* questo backfill non è stato reso una funzione richiamabile: qui si verifica lo stesso
* pattern SQL (`UPDATE giocatori_squadra ... FROM profili_giocatore`, con
* `enforce_giocatori_squadra_update` temporaneamente disattivato) eseguendolo via `psql`
* dentro il container Postgres locale — PostgREST non espone un update multi-tabella come
* RPC, quindi qui serve accesso diretto al database, non solo alla REST.
*
* Simula un profilo "storico" (inserito con il trigger di sync disattivato, come se
* esistesse da prima di M18) e verifica che il backfill lo recuperi. Gira solo sullo stack
* locale con Docker attivo: usa lo slot `g9` della rosa seed, ripristinato alla fine.
*/
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { spawnSync } from "node:child_process";
import { statoLocale } from "../helpers/locale";
import { prova, riepilogo, salta } from "../helpers/prova";
const locale = statoLocale();
if (!locale) {
salta("backfill nascita profili esistenti", "stack locale non attivo (npx supabase start)");
riepilogo("backfill-nascita-esistenti");
} else {
const { url: URL_BASE, anon: ANON, servizio: SERVIZIO } = locale;
const projectId = /project_id\s*=\s*"([^"]+)"/.exec(
readFileSync(new URL("../../supabase/config.toml", import.meta.url), "utf8"),
)?.[1];
const container = projectId ? `supabase_db_${projectId}` : null;
const dockerDisponibile =
!!container && spawnSync("docker", ["inspect", container], { stdio: "ignore" }).status === 0;
if (!dockerDisponibile) {
salta("backfill nascita profili esistenti", "container Postgres locale non trovato");
riepilogo("backfill-nascita-esistenti");
} else {
console.log(`backfill nascita profili esistenti su ${URL_BASE} (container ${container})`);
function eseguiSql(sql: string): string {
const esito = spawnSync(
"docker",
["exec", "-i", container!, "psql", "-U", "postgres", "-d", "postgres"],
{
input: sql,
encoding: "utf8",
},
);
if (esito.status !== 0) throw new Error(`psql fallito: ${esito.stderr || esito.stdout}`);
return esito.stdout;
}
const rest = (percorso: string, token: string, init?: RequestInit) =>
fetch(`${URL_BASE}/rest/v1/${percorso}`, {
...init,
headers: {
apikey: token === SERVIZIO ? SERVIZIO : ANON,
Authorization: `Bearer ${token}`,
"content-type": "application/json",
...(init?.headers ?? {}),
},
});
async function leggiNascita(): Promise<string | null> {
const res = await rest("giocatori_squadra?id=eq.g9&select=nascita", SERVIZIO);
const righe = (await res.json()) as Array<{ nascita: string | null }>;
return righe[0]?.nascita ?? null;
}
try {
await prova(
"il backfill recupera la nascita di un profilo compilato prima di M18",
async () => {
// g9 è uno dei 17 storici: il backfill hardcoded di M18 le ha già dato una nascita
// pubblica. Per simulare lo stato "prima di M19" — un profilo compilato ma non
// ancora sincronizzato — va prima azzerata, poi inserito il profilo con il trigger
// di sync disattivato (come se esistesse da prima che M18 lo introducesse).
eseguiSql(`
ALTER TABLE public.giocatori_squadra DISABLE TRIGGER enforce_giocatori_squadra_update;
UPDATE public.giocatori_squadra SET nascita = NULL WHERE id = 'g9';
ALTER TABLE public.giocatori_squadra ENABLE TRIGGER enforce_giocatori_squadra_update;
ALTER TABLE public.profili_giocatore DISABLE TRIGGER sincronizza_nascita_pubblica;
INSERT INTO public.profili_giocatore (giocatore_id, data_nascita)
VALUES ('g9', '1988-07-21');
ALTER TABLE public.profili_giocatore ENABLE TRIGGER sincronizza_nascita_pubblica;
`);
assert.equal(
await leggiNascita(),
null,
"prima del backfill la nascita pubblica è ancora vuota",
);
// Stesso corpo esatto di m19_backfill_nascita_da_profili_esistenti.sql.
eseguiSql(`
ALTER TABLE public.giocatori_squadra DISABLE TRIGGER enforce_giocatori_squadra_update;
UPDATE public.giocatori_squadra gs
SET nascita = pg.data_nascita
FROM public.profili_giocatore pg
WHERE pg.giocatore_id = gs.id;
ALTER TABLE public.giocatori_squadra ENABLE TRIGGER enforce_giocatori_squadra_update;
`);
assert.equal(
await leggiNascita(),
"1988-07-21",
"dopo il backfill la nascita del profilo preesistente è recuperata",
);
},
);
} finally {
// g9 (Camilla Esposito) è uno dei 17 storici: il backfill di M18 le aveva già dato
// '2003-04-04' (src/lib/crapp-data.ts) prima che questo test la sovrascrivesse — va
// ripristinato quel valore, non svuotato, per non alterare lo stato del seed.
eseguiSql(`
ALTER TABLE public.giocatori_squadra DISABLE TRIGGER enforce_giocatori_squadra_update;
DELETE FROM public.profili_giocatore WHERE giocatore_id = 'g9';
UPDATE public.giocatori_squadra SET nascita = '2003-04-04' WHERE id = 'g9';
ALTER TABLE public.giocatori_squadra ENABLE TRIGGER enforce_giocatori_squadra_update;
`);
riepilogo("backfill-nascita-esistenti");
}
}
}