Copre la cache delle presenze, le tabelle aperte e le letture server.
Chiude i tre buchi di copertura rimasti dalla rilettura della documentazione: comportamenti che la doc descrive come regole ma che nessun test verificava. - conRisposta() esce da useSalvaPresenza e diventa una funzione pura in presenze.ts. La mutation non rilegge dopo la scrittura, quindi la cache deve imitare il database: l'istante si scrive solo se manca (??=) e il ritiro della risposta lo cancella. Erano due dettagli che serie-presenze.md chiede di preservare e che un refactor poteva perdere in silenzio, falsando la serie "Conferme 24h" fino al refresh successivo. - permessi.test.ts copre ora anche il terzo gruppo di DD-023, le tabelle lasciate aperte di proposito: scout_sessioni, scout_live, scout_partite e push_subscriptions. Non dicono che sono sicure, dicono che sono aperte per scelta: se una prende un gate nell'interfaccia, le policy devono seguirlo e questi casi vanno cambiati con loro. - lettori-server.test.ts esegue leggiEventi() e leggiGiocatoriSquadra() sul database locale, mettendo le credenziali in process.env prima della prima chiamata perché supabaseAdmin nasce pigramente. Nessuna delle due controlla l'errore di PostgREST: una colonna rinominata darebbe zero righe, zero destinatari e nessuna push, senza che niente segnali il problema. Scrivendolo è emerso che eventi_app.note è NOT NULL, mentre RigaEvento lo dichiara nullable. PROJECT_STATE: m12_niente_autovoto è applicata in produzione dal 06/09/2026, verificata con `npx supabase migration list` (23 migration, local = remote). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -394,8 +394,12 @@ if (!locale) {
|
||||
assert.equal(await righeToccate(pagella), 1, "e per cancellare il voto di un altro");
|
||||
});
|
||||
|
||||
// I turni palloni restano aperti di proposito: nell'interfaccia il turno se lo passa
|
||||
// chiunque, senza gate. Se un giorno arriva il gate, questo test va cambiato.
|
||||
// Il terzo gruppo di DD-023: tabelle lasciate aperte **di proposito**, perché
|
||||
// nell'interfaccia non hanno nessun gate — il turno palloni se lo passa chiunque, e lo
|
||||
// Scout Live lo apre chiunque, con il solo lock di sessione a tenere l'ordine.
|
||||
// Questi casi non dicono che sono sicure: dicono che sono aperte per scelta. Se un
|
||||
// giorno una di loro prende un gate nell'interfaccia, le policy devono seguirlo e
|
||||
// questi test vanno cambiati insieme.
|
||||
await prova("il turno palloni resta assegnabile da chiunque sia autenticato", async () => {
|
||||
const res = await rest("turni_palloni", tokenGiocatore, {
|
||||
method: "POST",
|
||||
@@ -404,6 +408,79 @@ if (!locale) {
|
||||
});
|
||||
assert.equal(await righeToccate(res), 1, "DD-023 lascia questa tabella invariata");
|
||||
});
|
||||
|
||||
await prova("lo Scout Live resta aperto a chiunque sia autenticato", async () => {
|
||||
const sessione = await rest("scout_sessioni", tokenGiocatore, {
|
||||
method: "POST",
|
||||
headers: { Prefer: "resolution=merge-duplicates,return=representation" },
|
||||
body: JSON.stringify({
|
||||
evento_id: EVENTO,
|
||||
giocatore_id: "g5",
|
||||
giocatore_nome: "Cinque",
|
||||
aggiornato_il: new Date().toISOString(),
|
||||
}),
|
||||
});
|
||||
assert.equal(await righeToccate(sessione), 1, "il lock lo prende chiunque");
|
||||
|
||||
const stato = await rest("scout_live", tokenGiocatore, {
|
||||
method: "POST",
|
||||
headers: { Prefer: "resolution=merge-duplicates,return=representation" },
|
||||
body: JSON.stringify({ evento_id: EVENTO, stato: { set: 1 } }),
|
||||
});
|
||||
assert.equal(await righeToccate(stato), 1, "e lo stato in corso lo scrive chiunque");
|
||||
|
||||
const archivio = await rest("scout_partite", tokenGiocatore, {
|
||||
method: "POST",
|
||||
headers: { Prefer: "return=representation" },
|
||||
body: JSON.stringify({
|
||||
id: `${PREFISSO}-match`,
|
||||
evento_id: EVENTO,
|
||||
data: "2026-01-01",
|
||||
avversario: "Prova",
|
||||
casa: true,
|
||||
set_nostri: 3,
|
||||
set_loro: 0,
|
||||
parziali: [],
|
||||
azioni: [],
|
||||
}),
|
||||
});
|
||||
assert.equal(await righeToccate(archivio), 1, "come l'archivio di fine partita");
|
||||
|
||||
const cancella = await rest(`scout_partite?id=eq.${PREFISSO}-match`, tokenGiocatore, {
|
||||
method: "DELETE",
|
||||
headers: { Prefer: "return=representation" },
|
||||
});
|
||||
assert.equal(await righeToccate(cancella), 1, "e chiunque può anche cancellarlo");
|
||||
});
|
||||
|
||||
// Le iscrizioni push non passano dalla RLS per identificare il dispositivo: la chiave è
|
||||
// l'endpoint, che il browser conosce solo per sé. Restano scrivibili da chiunque sia
|
||||
// autenticato, ed è il motivo per cui `push_subscriptions` non contiene dati personali
|
||||
// oltre all'endpoint e alle sue chiavi.
|
||||
await prova("l'iscrizione alle notifiche la registra qualsiasi autenticato", async () => {
|
||||
const endpoint = `https://esempio.test/${PREFISSO}-push`;
|
||||
const res = await rest("push_subscriptions", tokenGiocatore, {
|
||||
method: "POST",
|
||||
headers: { Prefer: "resolution=merge-duplicates,return=representation" },
|
||||
body: JSON.stringify({
|
||||
giocatore_id: "g1",
|
||||
endpoint,
|
||||
p256dh: "chiave-di-prova",
|
||||
auth: "auth-di-prova",
|
||||
}),
|
||||
});
|
||||
assert.equal(await righeToccate(res), 1, "il dispositivo si registra da solo");
|
||||
|
||||
const via = await rest(
|
||||
`push_subscriptions?endpoint=eq.${encodeURIComponent(endpoint)}`,
|
||||
tokenGiocatore,
|
||||
{
|
||||
method: "DELETE",
|
||||
headers: { Prefer: "return=representation" },
|
||||
},
|
||||
);
|
||||
assert.equal(await righeToccate(via), 1, "e si cancella quando le notifiche si spengono");
|
||||
});
|
||||
} finally {
|
||||
// Ripristino: prima le righe create (la service role passa sopra alle policy di M11),
|
||||
// poi gli slot (serve il JWT admin, il trigger rifiuta la service key), il telefono e
|
||||
@@ -414,6 +491,11 @@ if (!locale) {
|
||||
for (const tabella of ["pagelle_voti", "mvp_voti", "badge_social_voti"]) {
|
||||
await rest(`${tabella}?match_id=like.${PREFISSO}*`, SERVIZIO, { method: "DELETE" });
|
||||
}
|
||||
for (const tabella of ["scout_sessioni", "scout_live"]) {
|
||||
await rest(`${tabella}?evento_id=like.${PREFISSO}*`, SERVIZIO, { method: "DELETE" });
|
||||
}
|
||||
await rest(`scout_partite?id=like.${PREFISSO}*`, SERVIZIO, { method: "DELETE" });
|
||||
await rest(`push_subscriptions?endpoint=like.*${PREFISSO}*`, SERVIZIO, { method: "DELETE" });
|
||||
await rest(`eventi_app?id=like.${PREFISSO}*`, SERVIZIO, { method: "DELETE" });
|
||||
|
||||
for (const id of ["g1", "g2"]) {
|
||||
|
||||
Reference in New Issue
Block a user