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>
This commit is contained in:
1 parent
3f2ca30775
commit
cdd433cb15
3 files changed
+142
-1
No files matched your search
@@ -1309,3 +1309,10 @@ allinea una tantum `giocatori_squadra.nascita` a `profili_giocatore.data_nascita
|
|||||||
profili già esistenti, con la stessa disattivazione temporanea del trigger di sicurezza usata
|
profili già esistenti, con la stessa disattivazione temporanea del trigger di sicurezza usata
|
||||||
in M18. Da qui in poi il gap non si può ripresentare: ogni scrittura successiva passa dal
|
in M18. Da qui in poi il gap non si può ripresentare: ogni scrittura successiva passa dal
|
||||||
trigger di sync, questo backfill serve solo per lo storico antecedente a M18.
|
trigger di sync, questo backfill serve solo per lo storico antecedente a M18.
|
||||||
|
|
||||||
|
A differenza della bonifica eventi orfani (DD-029, M15→M16), qui il backfill non è stato
|
||||||
|
reso una funzione richiamabile: resta SQL diretto nella migration. La copertura di test
|
||||||
|
(`test/integration/backfill-nascita-esistenti.test.ts`) verifica comunque lo stesso
|
||||||
|
comportamento eseguendo lo stesso pattern SQL via `psql` nel container Postgres locale —
|
||||||
|
scelta necessaria perché senza una funzione RPC, PostgREST non espone un update
|
||||||
|
multi-tabella come questo.
|
||||||
+1
-1
@@ -26,7 +26,7 @@ la consegna effettiva a schermo bloccato richiede un telefono e il servizio push
|
|||||||
| Cartella | Cosa verifica | Serve rete? |
|
| 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 |
|
| `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 |
|
||||||
| `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) e la funzione di bonifica delle righe orfane (`bonifica-evento`, M16) sul database locale | Sì |
|
| `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ì |
|
| `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 | — |
|
| `helpers/` | Avvio del server di test e mini-harness condiviso | — |
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,134 @@
|
|||||||
|
/**
|
||||||
|
* 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user