From e13722eae9248de2f4b6956af3d533c375265a9e Mon Sep 17 00:00:00 2001 From: Davide Grilli Date: Tue, 8 Sep 2026 11:14:57 +0200 Subject: [PATCH] Rende dinamica anche la scadenza di "Tutti rispondono alle convocazioni" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit La scadenza era una data fissa (2026-09-30), stesso problema già risolto per "presenze del mese" e "evento di squadra al mese": ora segue l'ultimo giorno del mese corrente. Aggiunti unit test e integration test end-to-end contro il database locale (isolando gli eventi di test, perché questo obiettivo aggrega su tutti gli eventi indipendentemente dal mese). Co-Authored-By: Claude Sonnet 5 --- docs/modules/obiettivi-squadra.md | 16 +++++-- src/lib/obiettivi.ts | 2 +- test/integration/obiettivi.test.ts | 76 +++++++++++++++++++++++++++++- test/unit/obiettivi.test.ts | 7 +++ 4 files changed, 93 insertions(+), 8 deletions(-) diff --git a/docs/modules/obiettivi-squadra.md b/docs/modules/obiettivi-squadra.md index 57466af..65c6c2e 100644 --- a/docs/modules/obiettivi-squadra.md +++ b/docs/modules/obiettivi-squadra.md @@ -59,8 +59,14 @@ smart (`notifiche-smart.ts`). "90% presenze del mese" e "1 evento di squadra al mese" si azzerano automaticamente a ogni cambio mese: il mese di riferimento è calcolato dalla data corrente (fuso Europe/Rome), non più una costante fissa. Per il primo, titolo ("90% di presenze ad agosto" / "a settembre" / ...) e -scadenza (ultimo giorno del mese) seguono di conseguenza. Per i test, `obiettiviSquadra`/ -`obiettiviOrdinati` accettano un terzo parametro opzionale `oggi: Date` per iniettare una data -deterministica. Coperti sia da unit test (`test/unit/obiettivi.test.ts`, funzione pura) sia da -un integration test end-to-end (`test/integration/obiettivi.test.ts`, scrive/rilegge righe vere -su `eventi_app` e `risposte_presenze` sullo stack Supabase locale). +scadenza (ultimo giorno del mese) seguono di conseguenza. + +"Tutti rispondono alle convocazioni" non si azzera (aggrega su tutti gli eventi in programma, +non solo quelli del mese corrente), ma la sua `scadenza` mostrata in interfaccia era anch'essa +una data fissa (`"2026-09-30"`): ora è anch'essa l'ultimo giorno del mese corrente. + +Per i test, `obiettiviSquadra`/`obiettiviOrdinati` accettano un terzo parametro opzionale +`oggi: Date` per iniettare una data deterministica. Coperti sia da unit test +(`test/unit/obiettivi.test.ts`, funzione pura) sia da un integration test end-to-end +(`test/integration/obiettivi.test.ts`, scrive/rilegge righe vere su `eventi_app` e +`risposte_presenze` sullo stack Supabase locale). diff --git a/src/lib/obiettivi.ts b/src/lib/obiettivi.ts index 9e33e65..fe36c54 100644 --- a/src/lib/obiettivi.ts +++ b/src/lib/obiettivi.ts @@ -102,7 +102,7 @@ export function obiettiviSquadra( valore: percentualeRisposte(ctx, rosa.length), target: 90, unita: "%", - scadenza: "2026-09-30", + scadenza: fineMese(oggi), emoji: "⚡", impatto: "Bastano pochi tap per far quadrare i conti a chi organizza.", }, diff --git a/test/integration/obiettivi.test.ts b/test/integration/obiettivi.test.ts index a3a1c68..930ae53 100644 --- a/test/integration/obiettivi.test.ts +++ b/test/integration/obiettivi.test.ts @@ -1,6 +1,7 @@ /** - * Obiettivi mensili di squadra ("presenze del mese" e "evento di squadra al mese") - * end-to-end contro il database locale: `bun test/integration/obiettivi.test.ts`. + * Obiettivi di squadra con scadenza/mese dinamici ("presenze del mese", "evento di squadra al + * mese" e "tutti rispondono alle convocazioni") end-to-end contro il database locale: + * `bun test/integration/obiettivi.test.ts`. * * I test unitari (`test/unit/obiettivi.test.ts`) verificano `obiettiviSquadra()` come funzione * pura, con un `ContestoObiettivi` costruito a mano. Qui invece si scrivono righe vere su @@ -207,6 +208,77 @@ if (!locale) { ); }, ); + + await prova( + "o2 legge dal database risposte reali ed esclude i compleanni, con scadenza dinamica", + async () => { + // o2 (percentualeRisposte) aggrega su TUTTI gli eventi non-compleanno, senza filtro di + // mese: per un'asserzione deterministica isoliamo dal risultato reale solo i due eventi + // di questo test, invece di dipendere dal numero di eventi già presenti nel database + // (seed incluso). + const partitaId = `${PREFISSO}-o2-partita`; + const compleannoId = `${PREFISSO}-o2-compleanno`; + + const inseritaPartita = await rest("eventi_app", { + method: "POST", + body: JSON.stringify({ + id: partitaId, + tipo: "partita", + titolo: "Test obiettivi o2", + data: `${MESE_TEST}-05`, + }), + }); + if (!inseritaPartita.ok) { + throw new Error(`inserimento evento fallito: ${await inseritaPartita.text()}`); + } + const inseritoCompleanno = await rest("eventi_app", { + method: "POST", + body: JSON.stringify({ + id: compleannoId, + tipo: "compleanno", + titolo: "Test obiettivi o2 - compleanno", + data: `${MESE_TEST}-06`, + }), + }); + if (!inseritoCompleanno.ok) { + throw new Error(`inserimento evento fallito: ${await inseritoCompleanno.text()}`); + } + + // Solo 12 giocatori su tutta la rosa rispondono alla partita; nessuno "risponde" al + // compleanno, perché non richiede risposta. + const rispondenti = giocatori.slice(0, 12); + const righe = rispondenti.map((g, i) => ({ + evento_id: partitaId, + giocatore_id: g.id, + stato: i % 2 === 0 ? "presente" : "forse", + })); + const inseriteRisposte = await rest("risposte_presenze", { + method: "POST", + body: JSON.stringify(righe), + }); + if (!inseriteRisposte.ok) { + throw new Error(`inserimento presenze fallito: ${await inseriteRisposte.text()}`); + } + + const eventiReali = (await leggiEventi()).filter( + (e) => e.id === partitaId || e.id === compleannoId, + ); + const presenzeReali = await leggiPresenze(partitaId); + + const o2 = obiettiviSquadra( + giocatori, + { eventi: eventiReali, presenze: presenzeReali, pagelle: [] }, + OGGI, + ).find((o) => o.id === "o2")!; + + assert.equal( + o2.valore, + Math.round((12 / giocatori.length) * 100), + "12 risposte reali su un solo evento che le richiede (il compleanno è escluso)", + ); + assert.equal(o2.scadenza, "2099-03-31", "scadenza o2 = ultimo giorno del mese iniettato"); + }, + ); } finally { await rest(`risposte_presenze?evento_id=like.${PREFISSO}*`, { method: "DELETE" }); await rest(`eventi_app?id=like.${PREFISSO}*`, { method: "DELETE" }); diff --git a/test/unit/obiettivi.test.ts b/test/unit/obiettivi.test.ts index 612a11e..efa824e 100644 --- a/test/unit/obiettivi.test.ts +++ b/test/unit/obiettivi.test.ts @@ -127,6 +127,13 @@ assert.equal(trova(obiettiviSquadra(giocatori, soloCompleanni, OGGI_AGOSTO), "o2 const o1Settembre = trova(obiettiviSquadra(giocatori, contestoVuoto, OGGI_SETTEMBRE), "o1"); assert.ok(o1Settembre.titolo.includes("settembre"), "titolo o1 riflette il mese iniettato (settembre)"); assert.equal(o1Settembre.scadenza, "2026-09-30", "scadenza = ultimo giorno di settembre (30 gg)"); + + // La scadenza di o2 ("Tutti rispondono alle convocazioni") era una data fissa + // ("2026-09-30"): ora segue lo stesso mese dinamico di o1. + const o2Agosto = trova(obiettiviSquadra(giocatori, contestoVuoto, OGGI_AGOSTO), "o2"); + assert.equal(o2Agosto.scadenza, "2026-08-31", "scadenza o2 = ultimo giorno del mese iniettato"); + const o2Settembre = trova(obiettiviSquadra(giocatori, contestoVuoto, OGGI_SETTEMBRE), "o2"); + assert.equal(o2Settembre.scadenza, "2026-09-30", "scadenza o2 cambia con il mese iniettato"); } // --- o6: evento di squadra al mese, si azzera come o1 ------------------------