Completa la copertura test del badge MVP e riscrive la doc del modulo badge
Aggiunge il caso positivo RLS mancante per mvp_voti in permessi.test.ts e un nuovo test end-to-end (mvp-badge.test.ts) che verifica l'intera pipeline voti reali -> mvpVintiPerGiocatore() -> statoBadge(). docs/modules/badge.md ora elenca tutti e 16 i badge con come funzionano, la copertura test badge per badge e i due problemi minori trovati in audit (badgeSbloccati() morta, categoria senza vincolo DB). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+162
-2
@@ -1,6 +1,6 @@
|
|||||||
# Modulo — Badge
|
# Modulo — Badge
|
||||||
|
|
||||||
**Stato:** implementato (v1.0), coerente con DD-007 e DD-008
|
**Stato:** implementato, coerente con DD-007 e DD-008
|
||||||
**File principali:** `src/lib/badges.ts`, `src/lib/badge-social.ts`,
|
**File principali:** `src/lib/badges.ts`, `src/lib/badge-social.ts`,
|
||||||
`src/components/crapp/CollezioneBadge.tsx`, `src/components/crapp/BadgeDrawer.tsx`,
|
`src/components/crapp/CollezioneBadge.tsx`, `src/components/crapp/BadgeDrawer.tsx`,
|
||||||
`src/components/crapp/CelebrazioneBadge.tsx`, `src/components/crapp/VotoSocial.tsx`
|
`src/components/crapp/CelebrazioneBadge.tsx`, `src/components/crapp/VotoSocial.tsx`
|
||||||
@@ -37,6 +37,20 @@ badge assegnati per voto dai compagni.
|
|||||||
database (vincolo `badge_social_no_autovoto`, migration `m12_niente_autovoto`). A
|
database (vincolo `badge_social_no_autovoto`, migration `m12_niente_autovoto`). A
|
||||||
differenza delle [Pagelle](pagelle.md), qui non c'è alcun tentativo di anonimato:
|
differenza delle [Pagelle](pagelle.md), qui non c'è alcun tentativo di anonimato:
|
||||||
`votante_id`/`votato_id` sono entrambi visibili.
|
`votante_id`/`votato_id` sono entrambi visibili.
|
||||||
|
- **Badge MVP** (`mvp`, in `badgeDefs`): l'unico badge normale la cui fonte dato arriva da
|
||||||
|
un'altra tabella di voto invece che da un contatore semplice. Pipeline completa:
|
||||||
|
1. Ogni giocatore vota l'MVP della partita su `mvp_voti` (`mvp-voti.ts`), un voto per
|
||||||
|
partita/votante (`upsert` su `match_id,votante_id`), apribile solo 2 ore dopo l'inizio
|
||||||
|
match (`votoMvpAperto()`). Autovoto impossibile per due strade indipendenti: RLS di
|
||||||
|
`m11_scritture_per_ruolo` (`votante_id` legato al proprio `auth.uid()` via
|
||||||
|
`giocatori_squadra`) e `CHECK (votante_id <> votato_id)` a database
|
||||||
|
(`mvp_no_autovoto`, migration `m12_niente_autovoto`).
|
||||||
|
2. `vincitoriMvp()`/`mvpVintiPerGiocatore()` (`mvp-voti.ts`) contano, per ogni partita, chi
|
||||||
|
ha ricevuto più voti **con un vantaggio netto** sul secondo: in caso di parità nessun MVP
|
||||||
|
è assegnato per quella partita. Il conteggio finale per giocatore è il numero di partite
|
||||||
|
vinte nettamente, non il totale dei voti ricevuti.
|
||||||
|
3. `rosa.ts` (`useRosa()`) scrive quel numero in `Giocatore.mvp`, che `badgeDefs` legge con
|
||||||
|
`valore: (g) => g.mvp` e confronta con le soglie 1/3/5 (bronzo/argento/oro).
|
||||||
- `CollezioneBadge.tsx` mostra sbloccati, in progresso, badge social vinti e un contatore di
|
- `CollezioneBadge.tsx` mostra sbloccati, in progresso, badge social vinti e un contatore di
|
||||||
badge segreti ancora da scoprire; `BadgeDrawer.tsx` il dettaglio di un singolo badge;
|
badge segreti ancora da scoprire; `BadgeDrawer.tsx` il dettaglio di un singolo badge;
|
||||||
`CelebrazioneBadge.tsx` l'overlay celebrativo alla prima visualizzazione di un badge nuovo.
|
`CelebrazioneBadge.tsx` l'overlay celebrativo alla prima visualizzazione di un badge nuovo.
|
||||||
@@ -46,6 +60,58 @@ badge assegnati per voto dai compagni.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Elenco badge
|
||||||
|
|
||||||
|
Riferimento completo per chi lavora sul codice. **In app i 5 badge segreti restano nascosti
|
||||||
|
finché non sbloccati** (fanno parte della sorpresa per i giocatori): elencarli qui, con le
|
||||||
|
condizioni esatte, è una scelta deliberata per la documentazione tecnica, non una fuga di
|
||||||
|
informazioni verso l'interfaccia.
|
||||||
|
|
||||||
|
### Badge normali (gradi bronzo/argento/oro)
|
||||||
|
|
||||||
|
Tutti calcolati come `valore(g)` confrontato con tre soglie crescenti; il grado è l'ultima
|
||||||
|
soglia raggiunta o superata (soglie inclusive), oltre l'oro resta oro.
|
||||||
|
|
||||||
|
| id | nome | come si guadagna | soglie B/A/O |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `mvp` | MVP | partite vinte nettamente al voto MVP dei compagni (`g.mvp`, vedi pipeline sopra) | 1 / 3 / 5 |
|
||||||
|
| `pagella` | Pagellone | media dei voti pagella ricevuti dai compagni a fine partita (`g.mediaVoto`) | 6.5 / 7.5 / 8.5 |
|
||||||
|
| `palloni` | Sherpa dei palloni | quante volte ti sei incaricato di portare la sacca palloni (`g.palloni`) | 3 / 6 / 10 |
|
||||||
|
| `presenze` | Presenza fissa | totale presenze a eventi/partite in stagione (`g.presenze`) | 5 / 15 / 30 |
|
||||||
|
| `serie-allenamenti` | Sempre in palestra | allenamenti consecutivi presenti, senza saltarne uno (`g.serieAllenamenti`) | 3 / 6 / 10 |
|
||||||
|
| `serie-conferme` | Risposta lampo | conferme di presenza consecutive date entro 24h dalla convocazione (`g.serieConferme`) | 3 / 8 / 15 |
|
||||||
|
|
||||||
|
### Badge segreti (booleani, nascosti finché non sbloccati)
|
||||||
|
|
||||||
|
Stesso motore dei normali ma con soglie `{bronzo:1, argento:1, oro:1}`: `valore(g)` è 0 o 1,
|
||||||
|
quindi il badge è "trovato o no", mai graduato. In UI compaiono con icona lucchetto finché non
|
||||||
|
sbloccati.
|
||||||
|
|
||||||
|
| id | nome | condizione esatta |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `s-tiebreak` | Uomo tie-break | almeno 2 MVP **e** media pagella ≥ 8 (`g.mvp >= 2 && g.mediaVoto >= 8`) |
|
||||||
|
| `s-mai-forfait` | Mai un forfait | almeno 10 conferme rapide consecutive **e** almeno 15 presenze (`g.serieConferme >= 10 && g.presenze >= 15`) |
|
||||||
|
| `s-infermeria` | Cliente VIP dell'Infermeria | almeno 3 eventi saltati per infortunio (`g.infortuni >= 3`) |
|
||||||
|
| `s-ritardi` | Aspettate, arrivo! | almeno 5 ritardi a eventi (`g.ritardi >= 5`) |
|
||||||
|
| `s-cacche` | Trono di ferro | almeno 3 partite di campionato con 3 o più cacche pre-gara dichiarate (`g.cacche >= 3`) |
|
||||||
|
|
||||||
|
### Badge social (votati dai compagni, 5 categorie per partita)
|
||||||
|
|
||||||
|
Non hanno gradi: si "vince" o non si vince una categoria in una partita. `vincitoreCategoria()`
|
||||||
|
richiede un vantaggio netto sul secondo classificato, in parità nessun vincitore.
|
||||||
|
`badgeSocialVinti()` conta quante partite ha vinto ciascun giocatore in ogni categoria (non i
|
||||||
|
voti ricevuti).
|
||||||
|
|
||||||
|
| id | nome | cosa premia |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `affidabile` | Compagno affidabile | sempre presente, sempre sul pezzo |
|
||||||
|
| `spirito` | Miglior spirito di squadra | carica il gruppo dal primo all'ultimo punto |
|
||||||
|
| `fairplay` | Fair play | rispetto per compagni, avversari e arbitro |
|
||||||
|
| `meme` | Meme della partita | la scena più memorabile della partita |
|
||||||
|
| `cuore` | Cuore del gruppo | chi tiene unita la squadra anche fuori dal campo |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Regole rispettate
|
## Regole rispettate
|
||||||
|
|
||||||
- **DD-007**: nessuna tabella `badge_sbloccati`, tutto calcolato a runtime dai dati
|
- **DD-007**: nessuna tabella `badge_sbloccati`, tutto calcolato a runtime dai dati
|
||||||
@@ -55,6 +121,98 @@ badge assegnati per voto dai compagni.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Copertura test
|
||||||
|
|
||||||
|
Verifica badge per badge (fatta rileggendo codice e test riga per riga, non solo per
|
||||||
|
categoria): nessun bug trovato nella logica di calcolo di nessuno dei 16 badge.
|
||||||
|
|
||||||
|
**Badge normali** — `badges.ts` testa la propria funzione pura (soglia → grado,
|
||||||
|
`badges.test.ts`) sull'output di altri moduli:
|
||||||
|
- `mvp`: soglie inclusive verificate (1→bronzo, 3→argento, 99→resta oro,
|
||||||
|
`badges.test.ts:44-48`), progresso a metà (`:52-56`). **Unico badge normale con integration
|
||||||
|
dedicato** (vedi sotto) perché la sua fonte, a differenza degli altri 5, passa da un'altra
|
||||||
|
tabella di voto (`mvp_voti`) invece che da un contatore già calcolato altrove.
|
||||||
|
- `pagella`: caso critico delle soglie decimali senza arrotondamento per eccesso — 6.4 →
|
||||||
|
nessun grado, 6.5 → bronzo (`badges.test.ts:65-67`); un vero 6.49 non diventa "quasi
|
||||||
|
bronzo".
|
||||||
|
- `palloni`, `presenze`, `serie-allenamenti`, `serie-conferme`: stessa funzione di soglia già
|
||||||
|
testata a fondo su `mvp`/`pagella`, coperti dagli invarianti generali
|
||||||
|
(`badges.test.ts:154-159`: soglie crescenti, testi presenti, id unici) e da
|
||||||
|
`collezioneBadge`/`prossimoTraguardo` con valori al massimo (`:119-144`).
|
||||||
|
- Nessun integration dedicato per questi 5: non toccano il database, le statistiche sorgente
|
||||||
|
(`presenze.test.ts`, `palloni-core.test.ts`, ecc.) sono già coperte nei rispettivi moduli.
|
||||||
|
|
||||||
|
**Badge segreti** — ognuno testato con la propria condizione esatta e il confine appena sotto
|
||||||
|
(`badges.test.ts:77-109`): `s-tiebreak` (mediaVoto 7.9 non basta, serve 8), `s-mai-forfait`
|
||||||
|
(unica condizione doppia, testato che **entrambe** servano), `s-infermeria`, `s-ritardi`,
|
||||||
|
`s-cacche`. Copertura unit completa; integration non necessario per lo stesso motivo dei
|
||||||
|
normali (le statistiche sorgente sono testate nei rispettivi moduli).
|
||||||
|
|
||||||
|
**Badge MVP — pipeline end-to-end** (aggiunta in una sessione dedicata a completare la
|
||||||
|
copertura di questo badge):
|
||||||
|
- Unit: `badges.test.ts` (soglie/gradi) + `mvp-voti.test.ts` (conteggio partita, vincitore con
|
||||||
|
vantaggio netto, parità che non assegna, apertura voto 2h dopo il fischio d'inizio).
|
||||||
|
- Integration (`npx supabase start` richiesto):
|
||||||
|
- `scritture.test.ts` — semantica dell'`upsert` di `mvp_voti` (un voto per
|
||||||
|
partita/votante, l'ultimo sostituisce) e rifiuto dell'autovoto a database
|
||||||
|
(`mvp_no_autovoto`).
|
||||||
|
- `permessi.test.ts` — RLS di `m11`: il proprio voto MVP si registra (caso positivo), non
|
||||||
|
si può votare a nome di un altro (caso negativo).
|
||||||
|
- `mvp-badge.test.ts` — end-to-end reale: scrive voti su `mvp_voti`, rilegge via REST come
|
||||||
|
fa `useVotiMvp()`, calcola `mvpVintiPerGiocatore()` e verifica che `statoBadge()` assegni
|
||||||
|
il grado corretto (bronzo a 1-2 vittorie nette, argento a 3), incluso un pareggio che non
|
||||||
|
deve contare come vittoria.
|
||||||
|
|
||||||
|
**Badge social** — nessuna delle 5 categorie ha logica *propria* nel codice: l'id è solo una
|
||||||
|
chiave di raggruppamento, `conteggioCategoria`/`vincitoreCategoria`/`badgeSocialVinti` sono
|
||||||
|
identici per tutte (`badge-social.ts:107-158`). Testare a fondo 2-3 categorie copre l'intero
|
||||||
|
meccanismo:
|
||||||
|
- Unit (`badge-social.test.ts`): conteggio isolato per match+categoria (`:29-32`), vantaggio
|
||||||
|
netto/parità → nessun vincitore (`:38-41`), vittorie multi-partita (`badgeSocialVinti`, g2
|
||||||
|
vince in `m1` e `m2` → `{affidabile: 2}`, `:48`), zero voti → zero badge (`:51`).
|
||||||
|
- Integration: upsert/sostituzione voto per categoria (`scritture.test.ts:170-202`), autovoto
|
||||||
|
rifiutato — doppia barriera UI + database (`scritture.test.ts:124-148`), RLS `m11` — un
|
||||||
|
giocatore firma solo il proprio voto (`permessi.test.ts:344-369`).
|
||||||
|
|
||||||
|
### Riepilogo per badge
|
||||||
|
|
||||||
|
| # | id | tipo | test unit | test integration |
|
||||||
|
| - | --- | --- | --- | --- |
|
||||||
|
| 1 | `mvp` | normale | ✅ | ✅ (`scritture`, `permessi`, `mvp-badge`) |
|
||||||
|
| 2 | `pagella` | normale | ✅ | non necessario |
|
||||||
|
| 3 | `palloni` | normale | ✅ | non necessario |
|
||||||
|
| 4 | `presenze` | normale | ✅ | non necessario |
|
||||||
|
| 5 | `serie-allenamenti` | normale | ✅ (limite noto sotto) | non necessario |
|
||||||
|
| 6 | `serie-conferme` | normale | ✅ (limite noto sotto) | non necessario |
|
||||||
|
| 7 | `s-tiebreak` | segreto | ✅ | non necessario |
|
||||||
|
| 8 | `s-mai-forfait` | segreto | ✅ | non necessario |
|
||||||
|
| 9 | `s-infermeria` | segreto | ✅ | non necessario |
|
||||||
|
| 10 | `s-ritardi` | segreto | ✅ (parziale, manca "appena sotto") | non necessario |
|
||||||
|
| 11 | `s-cacche` | segreto | ✅ (parziale, manca "appena sotto") | non necessario |
|
||||||
|
| 12 | `affidabile` | social | ✅ | ✅ |
|
||||||
|
| 13 | `spirito` | social | ✅ (meccanismo generico) | ✅ (meccanismo generico) |
|
||||||
|
| 14 | `fairplay` | social | ✅ (meccanismo generico) | ✅ (meccanismo generico) |
|
||||||
|
| 15 | `meme` | social | ✅ | ✅ |
|
||||||
|
| 16 | `cuore` | social | ✅ | ✅ (autovoto) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Problemi noti da sistemare
|
||||||
|
|
||||||
|
Trovati in audit, nessuno bloccante (nessun bug nella logica di calcolo):
|
||||||
|
|
||||||
|
- **`badgeSbloccati()` morta** (`badges.ts:283-285`): duplica esattamente
|
||||||
|
`collezioneBadge(g).sbloccati`. Zero riferimenti fuori dalla propria definizione, né in
|
||||||
|
`src/` né nei test. Da rimuovere o documentare perché esiste (es. uso futuro/esterno).
|
||||||
|
- **`categoria` senza vincolo DB** in `badge_social_voti`: la colonna è `text NOT NULL` senza
|
||||||
|
CHECK o FK verso i 5 id di `categorieSocial`
|
||||||
|
(`supabase/migrations/20260803140647_affa1c11-fa92-450f-9f00-02d87195a6d9.sql:4`). I test
|
||||||
|
stessi lo dimostrano scrivendo categorie inesistenti (`"sorriso"`/`"urlo"`,
|
||||||
|
`scritture.test.ts`). Non sfruttabile da un utente normale (l'app manda solo le 5 categorie
|
||||||
|
valide), stesso tipo di gap "solo applicativo, non a DB" del punto sotto sul votato/convocato.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Limiti noti
|
## Limiti noti
|
||||||
|
|
||||||
- **Dipendenza dal modulo [Serie](serie-presenze.md)**: i badge "Sempre in palestra",
|
- **Dipendenza dal modulo [Serie](serie-presenze.md)**: i badge "Sempre in palestra",
|
||||||
@@ -66,7 +224,7 @@ badge assegnati per voto dai compagni.
|
|||||||
"ottenuto" può sparire o apparire retroattivamente.
|
"ottenuto" può sparire o apparire retroattivamente.
|
||||||
- La policy di M11 garantisce che il voto sia firmato con il proprio `votante_id`, ma non
|
- La policy di M11 garantisce che il voto sia firmato con il proprio `votante_id`, ma non
|
||||||
che il votato sia un giocatore convocato per quella partita: quello resta un filtro solo
|
che il votato sia un giocatore convocato per quella partita: quello resta un filtro solo
|
||||||
applicativo.
|
applicativo (vale per MVP, pagelle e badge social).
|
||||||
- Notifiche "nuovo badge" solo locali al dispositivo (localStorage), si ripetono cambiando
|
- Notifiche "nuovo badge" solo locali al dispositivo (localStorage), si ripetono cambiando
|
||||||
browser o dispositivo.
|
browser o dispositivo.
|
||||||
|
|
||||||
@@ -77,3 +235,5 @@ badge assegnati per voto dai compagni.
|
|||||||
- Sincronizzare lo stato "visto" su Supabase invece che solo in localStorage.
|
- Sincronizzare lo stato "visto" su Supabase invece che solo in localStorage.
|
||||||
- Verificare sui dati di stagione che i tre badge legati alle serie si sblocchino davvero,
|
- Verificare sui dati di stagione che i tre badge legati alle serie si sblocchino davvero,
|
||||||
ora che le serie sono calcolate.
|
ora che le serie sono calcolate.
|
||||||
|
- Rimuovere `badgeSbloccati()` (codice morto) o documentarne lo scopo.
|
||||||
|
- Aggiungere un vincolo (CHECK o FK) sulla colonna `categoria` di `badge_social_voti`.
|
||||||
|
|||||||
@@ -0,0 +1,151 @@
|
|||||||
|
/**
|
||||||
|
* Badge MVP end-to-end contro il database locale: `bun test/integration/mvp-badge.test.ts`.
|
||||||
|
*
|
||||||
|
* I test unitari (`test/unit/mvp-voti.test.ts`, `test/unit/badges.test.ts`) verificano
|
||||||
|
* `mvpVintiPerGiocatore()` e `statoBadge()` come funzioni pure, con voti costruiti a mano. Qui
|
||||||
|
* invece si scrivono voti veri su `mvp_voti`, si rileggono via REST con la stessa selezione di
|
||||||
|
* `useVotiMvp()`, e si passa il risultato attraverso `mvpVintiPerGiocatore()` fino a
|
||||||
|
* `statoBadge()` sul badge `mvp`: se una colonna cambia nome o la mappatura si rompe, qui il
|
||||||
|
* grado del badge torna sbagliato anche se i test unitari restano verdi, perché quelli non
|
||||||
|
* toccano mai il database.
|
||||||
|
*
|
||||||
|
* Gira solo sullo stack locale (`npx supabase start`) e cancella le proprie righe alla fine:
|
||||||
|
* usa id con il prefisso `test-mvp-badge`, che nessun dato vero può avere.
|
||||||
|
*/
|
||||||
|
import assert from "node:assert/strict";
|
||||||
|
import { badgeDefs, statoBadge } from "@/lib/badges";
|
||||||
|
import { mvpVintiPerGiocatore, type VotoMvp } from "@/lib/mvp-voti";
|
||||||
|
import { giocatori, type Giocatore } from "@/lib/crapp-data";
|
||||||
|
import { statoLocale } from "../helpers/locale";
|
||||||
|
import { prova, riepilogo, salta } from "../helpers/prova";
|
||||||
|
|
||||||
|
const locale = statoLocale();
|
||||||
|
|
||||||
|
if (!locale) {
|
||||||
|
salta("badge MVP sul database", "stack locale non attivo (npx supabase start)");
|
||||||
|
riepilogo("mvp-badge");
|
||||||
|
} else {
|
||||||
|
const { url: URL_BASE, servizio: SERVIZIO } = locale;
|
||||||
|
console.log(`badge MVP su ${URL_BASE}`);
|
||||||
|
|
||||||
|
const PREFISSO = "test-mvp-badge";
|
||||||
|
const mvpDef = badgeDefs.find((b) => b.id === "mvp")!;
|
||||||
|
|
||||||
|
const rest = (percorso: string, init?: RequestInit) =>
|
||||||
|
fetch(`${URL_BASE}/rest/v1/${percorso}`, {
|
||||||
|
...init,
|
||||||
|
headers: {
|
||||||
|
apikey: SERVIZIO,
|
||||||
|
Authorization: `Bearer ${SERVIZIO}`,
|
||||||
|
"content-type": "application/json",
|
||||||
|
...(init?.headers ?? {}),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
async function upsert(riga: VotoMvp) {
|
||||||
|
const res = await rest("mvp_voti?on_conflict=match_id,votante_id", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { Prefer: "resolution=merge-duplicates,return=representation" },
|
||||||
|
body: JSON.stringify(riga),
|
||||||
|
});
|
||||||
|
if (!res.ok) throw new Error(`upsert su mvp_voti: ${res.status} ${await res.text()}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Rilegge esattamente come `useVotiMvp()`. */
|
||||||
|
async function leggiVoti(): Promise<VotoMvp[]> {
|
||||||
|
const res = await rest(
|
||||||
|
`mvp_voti?match_id=like.${PREFISSO}-*&select=match_id,votante_id,votato_id,votato_nome`,
|
||||||
|
);
|
||||||
|
return (await res.json()) as VotoMvp[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Un giocatore azzerato, come in `test/unit/badges.test.ts`. */
|
||||||
|
function giocatoreAzzerato(mvp: number): Giocatore {
|
||||||
|
return { ...giocatori[0]!, mvp, mediaVoto: 0, palloni: 0, presenze: 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await prova(
|
||||||
|
"il badge MVP passa da bronzo ad argento con vittorie vere lette dal database",
|
||||||
|
async () => {
|
||||||
|
// Il votato "vg1" vince nettamente le partite m1 e m2 (2 voti contro 1), pareggia in
|
||||||
|
// m3 (nessun vincitore) e perde in m4 (0 voti): dopo m1+m2 ha 2 vittorie -> bronzo,
|
||||||
|
// non ancora argento (soglia 3). Il votato "vg2" non vince mai: resta senza badge.
|
||||||
|
await upsert({
|
||||||
|
match_id: `${PREFISSO}-m1`,
|
||||||
|
votante_id: "va",
|
||||||
|
votato_id: "vg1",
|
||||||
|
votato_nome: "Uno",
|
||||||
|
});
|
||||||
|
await upsert({
|
||||||
|
match_id: `${PREFISSO}-m1`,
|
||||||
|
votante_id: "vb",
|
||||||
|
votato_id: "vg1",
|
||||||
|
votato_nome: "Uno",
|
||||||
|
});
|
||||||
|
await upsert({
|
||||||
|
match_id: `${PREFISSO}-m1`,
|
||||||
|
votante_id: "vc",
|
||||||
|
votato_id: "vg2",
|
||||||
|
votato_nome: "Due",
|
||||||
|
});
|
||||||
|
await upsert({
|
||||||
|
match_id: `${PREFISSO}-m2`,
|
||||||
|
votante_id: "va",
|
||||||
|
votato_id: "vg1",
|
||||||
|
votato_nome: "Uno",
|
||||||
|
});
|
||||||
|
await upsert({
|
||||||
|
match_id: `${PREFISSO}-m2`,
|
||||||
|
votante_id: "vb",
|
||||||
|
votato_id: "vg1",
|
||||||
|
votato_nome: "Uno",
|
||||||
|
});
|
||||||
|
await upsert({
|
||||||
|
match_id: `${PREFISSO}-m3`,
|
||||||
|
votante_id: "va",
|
||||||
|
votato_id: "vg1",
|
||||||
|
votato_nome: "Uno",
|
||||||
|
});
|
||||||
|
await upsert({
|
||||||
|
match_id: `${PREFISSO}-m3`,
|
||||||
|
votante_id: "vb",
|
||||||
|
votato_id: "vg2",
|
||||||
|
votato_nome: "Due",
|
||||||
|
});
|
||||||
|
|
||||||
|
const voti = await leggiVoti();
|
||||||
|
const vinti = mvpVintiPerGiocatore(voti);
|
||||||
|
assert.equal(vinti["vg1"], 2, "vg1 vince m1 e m2, pareggia in m3 (non conta)");
|
||||||
|
assert.equal(vinti["vg2"] ?? 0, 0, "vg2 non vince mai nettamente");
|
||||||
|
|
||||||
|
const badgeVg1 = statoBadge(mvpDef, giocatoreAzzerato(vinti["vg1"] ?? 0));
|
||||||
|
assert.equal(badgeVg1.grado, "bronzo", "2 vittorie: soglia bronzo (1) raggiunta");
|
||||||
|
assert.equal(badgeVg1.prossimo, "argento");
|
||||||
|
assert.equal(badgeVg1.prossimaSoglia, 3, "manca 1 vittoria all'argento");
|
||||||
|
|
||||||
|
const badgeVg2 = statoBadge(mvpDef, giocatoreAzzerato(vinti["vg2"] ?? 0));
|
||||||
|
assert.equal(badgeVg2.grado, null, "zero vittorie nette: nessun badge sbloccato");
|
||||||
|
|
||||||
|
// Una terza vittoria netta porta vg1 da bronzo ad argento (soglia 3).
|
||||||
|
await upsert({
|
||||||
|
match_id: `${PREFISSO}-m4`,
|
||||||
|
votante_id: "va",
|
||||||
|
votato_id: "vg1",
|
||||||
|
votato_nome: "Uno",
|
||||||
|
});
|
||||||
|
const vintiDopo = mvpVintiPerGiocatore(await leggiVoti());
|
||||||
|
assert.equal(vintiDopo["vg1"], 3);
|
||||||
|
assert.equal(
|
||||||
|
statoBadge(mvpDef, giocatoreAzzerato(vintiDopo["vg1"]!)).grado,
|
||||||
|
"argento",
|
||||||
|
"3 vittorie: soglia argento raggiunta",
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
await rest(`mvp_voti?match_id=like.${PREFISSO}-*`, { method: "DELETE" });
|
||||||
|
}
|
||||||
|
|
||||||
|
riepilogo("mvp-badge");
|
||||||
|
}
|
||||||
@@ -311,6 +311,18 @@ if (!locale) {
|
|||||||
});
|
});
|
||||||
assert.ok(!falso.ok, `non si vota a nome di un altro (${falso.status})`);
|
assert.ok(!falso.ok, `non si vota a nome di un altro (${falso.status})`);
|
||||||
|
|
||||||
|
const mioMvp = await rest("mvp_voti", tokenGiocatore, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { Prefer: "resolution=merge-duplicates,return=representation" },
|
||||||
|
body: JSON.stringify({
|
||||||
|
match_id: EVENTO,
|
||||||
|
votante_id: "g1",
|
||||||
|
votato_id: "g5",
|
||||||
|
votato_nome: "Cinque",
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
assert.equal(await righeToccate(mioMvp), 1, "il proprio voto MVP si registra");
|
||||||
|
|
||||||
const mvp = await rest("mvp_voti", tokenGiocatore, {
|
const mvp = await rest("mvp_voti", tokenGiocatore, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { Prefer: "resolution=merge-duplicates,return=representation" },
|
headers: { Prefer: "resolution=merge-duplicates,return=representation" },
|
||||||
|
|||||||
Reference in New Issue
Block a user