Files
CRAPP/test/unit/cacche.test.ts
T
davideandClaude Sonnet 5.5 e9013b703b Promemoria senza ritorni e tre canali per ogni notifica (DD-037, DD-038)
m23: un registro (promemoria_eventi_generati) ricorda i promemoria a 24 e 3 ore
già generati, così eliminare la notifica non li fa rigenerare dal cron né
riaccodare la mail a ogni giro.

m24: la push dei promemoria, che nascono nel database, la manda il worker
mailer/ con lo stesso modulo dell'app (coda notifiche_push_coda, esiti, backoff,
iscrizioni scadute eliminate, push scartate oltre 3 ore). Il sondaggio
pre-partita scrive anche notifiche_utente (tipo sondaggio_cacche): compare
in-app e arriva per email. L'immagine del worker si costruisce dalla radice
del repository per includere src/lib/webpush.server.ts.

Test: 9 di integrazione per m23, 17 per la coda push, 6 per la route del
sondaggio, nuovi casi unitari sul worker; prova end-to-end con un servizio
push finto. Documentazione e catalogo delle notifiche aggiornati.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-29 14:19:54 +02:00

144 lines
4.7 KiB
TypeScript

/** Check del sondaggio cacche: `bun test/unit/cacche.test.ts`. */
import assert from "node:assert/strict";
import {
avvisoSondaggio,
destinatariSondaggio,
mediaPartita,
mediaStagione,
recordStagione,
sondaggioAperto,
sondaggioTerminato,
statisticheCacche,
type RigaCacche,
} from "@/lib/cacche";
const r = (evento_id: string, giocatore_id: string, quantita: number): RigaCacche => ({
evento_id,
giocatore_id,
quantita,
});
const righe: RigaCacche[] = [
r("e1", "g1", 3),
r("e2", "g1", 4),
r("e1", "g2", 1),
r("e2", "g2", 2),
];
// --- statisticheCacche -------------------------------------------------------
const stat = statisticheCacche(righe);
assert.deepEqual(stat["g1"], { totale: 7, giornate: 2, media: 3.5, record: 4, giornateTop: 2 });
assert.deepEqual(stat["g2"], { totale: 3, giornate: 2, media: 1.5, record: 2, giornateTop: 0 });
assert.deepEqual(statisticheCacche([]), {});
// giornateTop è la soglia del badge segreto: 3 conta, 2 no.
assert.equal(statisticheCacche([r("e1", "g9", 3)])["g9"]?.giornateTop, 1);
assert.equal(statisticheCacche([r("e1", "g9", 2)])["g9"]?.giornateTop, 0);
// Uno zero dichiarato è una giornata censita, non un dato mancante.
const conZero = statisticheCacche([r("e1", "g7", 0)])["g7"]!;
assert.deepEqual([conZero.giornate, conZero.media, conZero.record], [1, 0, 0]);
// --- medie -------------------------------------------------------------------
assert.equal(mediaPartita(righe, "e1"), 2, "(3+1)/2");
assert.equal(mediaPartita(righe, "e2"), 3, "(4+2)/2");
assert.equal(mediaPartita(righe, "inesistente"), 0, "nessun dato: zero, non NaN");
assert.equal(mediaStagione(righe), 2.5);
assert.equal(mediaStagione([]), 0);
assert.equal(mediaStagione([r("e1", "g1", 1), r("e1", "g2", 2), r("e1", "g3", 2)]), 1.7);
// --- recordStagione ----------------------------------------------------------
assert.deepEqual(recordStagione(righe), r("e2", "g1", 4));
assert.equal(recordStagione([]), null);
assert.deepEqual(
recordStagione([r("e1", "g1", 4), r("e2", "g2", 4)]),
r("e1", "g1", 4),
"a parità vince chi l'ha fatto per primo",
);
// --- sondaggioAperto ---------------------------------------------------------
// Partita del 5 settembre 2026 alle 21:00.
const alle = (h: number, m = 0) => new Date(2026, 8, 5, h, m);
const ORA_PARTITA = "21:00";
assert.equal(
sondaggioAperto("2026-09-05", ORA_PARTITA, alle(7, 59)),
false,
"prima delle 8 è chiuso",
);
assert.equal(sondaggioAperto("2026-09-05", ORA_PARTITA, alle(8)), true, "alle 8 in punto apre");
assert.equal(
sondaggioAperto("2026-09-05", ORA_PARTITA, alle(20, 59)),
true,
"un minuto prima del fischio d'inizio è ancora aperto",
);
assert.equal(
sondaggioAperto("2026-09-05", ORA_PARTITA, alle(21, 0)),
false,
"al fischio d'inizio si chiude",
);
assert.equal(
sondaggioAperto("2026-09-05", ORA_PARTITA, alle(23)),
false,
"dopo l'inizio resta chiuso per il resto della giornata",
);
assert.equal(
sondaggioAperto("2026-09-06", ORA_PARTITA, alle(23)),
false,
"partita di domani: chiuso",
);
assert.equal(
sondaggioAperto("2026-09-04", ORA_PARTITA, alle(0, 1)),
false,
"partita passata: chiuso anche nei giorni successivi",
);
// --- sondaggioTerminato: distingue "non ancora aperto" da "già chiuso" -------
assert.equal(
sondaggioTerminato("2026-09-05", ORA_PARTITA, alle(7, 59)),
false,
"prima dell'apertura: non ancora aperto, non terminato",
);
assert.equal(
sondaggioTerminato("2026-09-05", ORA_PARTITA, alle(20, 59)),
false,
"in corso: non ancora terminato",
);
assert.equal(
sondaggioTerminato("2026-09-05", ORA_PARTITA, alle(21, 0)),
true,
"al fischio d'inizio: terminato",
);
assert.equal(
sondaggioTerminato("2026-09-06", ORA_PARTITA, alle(23)),
false,
"partita di domani: non ancora, non terminato",
);
assert.equal(
sondaggioTerminato("2026-09-04", ORA_PARTITA, alle(0, 1)),
true,
"partita passata: terminato",
);
// --- Avviso del sondaggio (DD-038) ---------------------------------------------
assert.deepEqual(avvisoSondaggio({ titolo: "Juve - CRAP", ora: "21:00" }), {
titolo: "💩 Sondaggio pre-partita aperto",
testo: "Juve - CRAP · ore 21:00. Quante cacche hai fatto? Rispondi prima del fischio d'inizio.",
});
// Destinatari in-app ed email: giocatori attivi, mai gli allenatori né chi non è più attivo,
// a prescindere dai dispositivi iscritti alla push.
assert.deepEqual(
destinatariSondaggio([
{ id: "g1", attivo: true, tipo: "giocatore" },
{ id: "g2", attivo: false, tipo: "giocatore" },
{ id: "g3", attivo: true, tipo: "allenatore" },
{ id: "g4", attivo: true, tipo: "giocatore" },
]),
["g1", "g4"],
"solo i giocatori attivi",
);
assert.deepEqual(destinatariSondaggio([]), [], "rosa vuota: nessun destinatario");
console.log("cacche: ok");