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:
2026-09-06 19:46:52 +02:00
co-authored by Claude Opus 5
parent f687322c3f
commit 7c1f08d943
6 changed files with 300 additions and 28 deletions
+57
View File
@@ -2,6 +2,7 @@
import assert from "node:assert/strict";
import type { Evento } from "@/lib/eventi";
import {
conRisposta,
contaPresenzeGiocatore,
destinatariSollecito,
serieConferme,
@@ -153,4 +154,60 @@ assert.equal(
);
assert.equal(serieConferme("g2", conConvocazione, tempi, OGGI), 0, "chi non risponde è a zero");
// --- cache locale dopo una risposta: il cronometro non riparte ----------------
// Stessa regola del database: `risposto_il` è la PRIMA risposta e un trigger la congela.
// Qui la cache deve imitarla, altrimenti la serie "Conferme 24h" mente fino al refresh.
const PRIMA = "2026-08-01T10:00:00Z";
const POI = "2026-08-08T10:00:00Z";
const dopoPrimaRisposta = conRisposta(
undefined,
{ eventoId: "e1", giocatoreId: "g1", stato: "presente" },
PRIMA,
);
assert.deepEqual(dopoPrimaRisposta, {
presenze: { e1: { g1: "presente" } },
tempi: { e1: { g1: PRIMA } },
});
const dopoRipensamento = conRisposta(
dopoPrimaRisposta,
{ eventoId: "e1", giocatoreId: "g1", stato: "assente" },
POI,
);
assert.equal(dopoRipensamento.presenze["e1"]?.["g1"], "assente", "vale l'ultima risposta");
assert.equal(
dopoRipensamento.tempi["e1"]?.["g1"],
PRIMA,
"l'istante resta quello della prima risposta: il ripensamento non fa ripartire il cronometro",
);
const dopoRitiro = conRisposta(
dopoRipensamento,
{ eventoId: "e1", giocatoreId: "g1", stato: null },
POI,
);
assert.deepEqual(dopoRitiro.presenze["e1"], {}, "ritirare la risposta la toglie");
assert.deepEqual(
dopoRitiro.tempi["e1"],
{},
"e toglie anche l'istante: chi risponde di nuovo riparte da capo",
);
assert.equal(
conRisposta(dopoRitiro, { eventoId: "e1", giocatoreId: "g1", stato: "presente" }, POI).tempi[
"e1"
]?.["g1"],
POI,
"dopo un ritiro il cronometro riparte davvero",
);
// Gli altri giocatori e gli altri eventi non vengono toccati.
const conAltri2 = conRisposta(
conRisposta(undefined, { eventoId: "e1", giocatoreId: "g1", stato: "presente" }, PRIMA),
{ eventoId: "e2", giocatoreId: "g2", stato: "forse" },
POI,
);
assert.equal(conAltri2.presenze["e1"]?.["g1"], "presente", "l'altro evento resta in cache");
assert.equal(conAltri2.tempi["e2"]?.["g2"], POI);
console.log("presenze: ok");