Files
CRAPP/test/README.md
T
davideandClaude Opus 5 d8f334007b Fa arrivare le push ad app chiusa e rende diagnosticabile quando non arrivano.
Il testo della notifica viaggia ora cifrato dentro la push (aes128gcm, RFC
8188/8291) invece di essere recuperato dal service worker con una fetch al
risveglio. Era quella fetch a non chiudersi in tempo: ad app chiusa il browser
tiene vivo il worker pochi secondi, showNotification non veniva mai chiamata e
non compariva niente, mentre ad app aperta con la rete calda sembrava tutto a
posto. La POST porta anche Urgency: high, che chiede la consegna immediata
invece di far accumulare i messaggi fino al risveglio del dispositivo.

Cadono i pezzi che esistevano solo per rimediare al payload vuoto: la route
push-messaggio, la coda promemoria_push con la sua scadenza a 12 ore,
messaggioPalloniOggi() e il timeout nel worker. Tutti e tre i mittenti avevano
gia il testo pronto prima di inviare.

Il worker si aggiorna da solo all'avvio e a ogni ritorno in primo piano
(mantieniWorkerPushAggiornato): nella webapp installata quello vecchio puo
sopravvivere a lungo, e senza questo un dispositivo resterebbe fermo alla
versione che va a cercare il testo in rete.

Profilo -> Opzioni ha "Mandami una notifica di prova", visibile solo a notifiche
attive: manda una push a questo dispositivo e riporta stato HTTP, corpo della
risposta e se l'endpoint risulta davvero in push_subscriptions. Senza, "non
arriva" era cieco: ogni prova richiedeva un admin, un evento nello stato giusto
e una seconda persona, e la risposta del servizio push veniva buttata via.
inviaPush torna { stato, corpo } e logga il corpo sui rifiuti.

Dalle prove sul campo: a parita di server, iPhone installato da Home riceve ad
app chiusa. Su Android installato come webapp resta da verificare: il WebAPK e
un'app Android a se, con permesso notifiche (Android 13+) e voce batteria
distinti da quelli di Chrome. Annotato nei limiti noti.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 14:24:07 +02:00

105 lines
7.0 KiB
Markdown

# Test
Suite eseguita con **bun**, senza framework né dipendenze aggiuntive: ogni file è
uno script che usa `node:assert/strict` e termina con codice diverso da zero se
un controllo fallisce.
```bash
npm run test # unit (veloce, nessuna rete, nessun database)
npm run test:integration # route server vere
npm run test:e2e # percorsi sull'app servita
npm run test:all # tutto
bun test/unit/badges.test.ts # un singolo file
```
Il runner (`test/run.ts`) esegue ogni file in un processo separato, così un test
non può inquinare gli altri.
Per le push, `unit/push-sw.test.ts` esegue il service worker reale in contesti isolati,
senza pagina né rete: verifica la notifica al risveglio e le promesse di `waitUntil`.
`unit/push-client.test.ts` verifica anche l'aggiornamento del worker all'avvio e al ritorno
in primo piano, inclusi errori offline e pulizia del listener. Sono test del codice:
la consegna effettiva a schermo bloccato richiede un telefono e il servizio push reale.
## Struttura
| Cartella | Cosa verifica | Serve rete? |
| -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- |
| `unit/` | Logica di dominio pura: badge, serie, palloni, pagelle, MVP, cacche, scout, obiettivi, notifiche, parsing CSI, dati della rosa. Più le funzioni pure isolabili nei moduli con hook/rete (validazione upload, guardie push, JWT VAPID, cattura errori, avatar) | No |
| `integration/` | Le route `/api/public/*` sul server di sviluppo: risposte, cache, validazione degli input. Più schema e permessi del Profilo Giocatore (`schema-profili`) contro il database configurato; permessi per ruolo (`permessi`), accesso alle route di notifica (`permessi-route`) e semantica degli upsert (`scritture`) sul database locale | Sì |
| `e2e/` | Percorsi completi sull'app servita: schermate, dati CSI fino alla pagina, file PWA, 404 | Sì |
| `helpers/` | Avvio del server di test e mini-harness condiviso | — |
## Database locale in Docker
Alcune cose non si possono verificare senza scrivere: le policy RLS scritte su
`auth.uid()`, i trigger, i vincoli. Contro il progetto cloud quelle scritture non
si fanno, quindi servono un database usa e getta e utenti veri.
```bash
npx supabase start # avvia lo stack in Docker (migration applicate + seed.sql)
npx supabase status # URL e chiavi locali; Studio su http://127.0.0.1:54323
npx supabase db reset # ricrea il database da zero se i dati si sporcano
npx supabase stop # spegne tutto
bun test/integration/permessi.test.ts # permessi per ruolo sulle tabelle
bun test/integration/permessi-route.test.ts # chi può far partire le notifiche
bun test/integration/scritture.test.ts # semantica degli upsert
```
`permessi-route` avvia il server di sviluppo **puntato al database locale** invece che al
progetto di `.env`: gli serve creare utenti veri per provare i tre casi (nessun token,
giocatore, amministratore).
Il primo `start` scarica le immagini (qualche minuto), i successivi partono in
una decina di secondi. Le mail finiscono in Mailpit (http://127.0.0.1:54324),
non escono dalla macchina.
I test che scrivono **non leggono `.env`**: prendono URL e chiavi da
`supabase status` (helper `test/helpers/locale.ts`) e si fermano se l'URL non è
`127.0.0.1`. È una cintura di sicurezza, non una comodità: così un `.env`
puntato alla produzione non può trasformare un test in una scrittura sul
database vero.
Ognuno ripristina lo stato che tocca in un `finally` — utenti creati, slot della
rosa, colonne modificate, e per `scritture` tutte le righe con il prefisso
`test-scritture`, che nessun dato vero può avere. Così la suite si rilancia
all'infinito senza un `db reset` in mezzo.
Due cose scoperte scrivendo questi test, utili a chi ne aggiunge:
- la **service key non è un amministratore**: per il trigger
`enforce_giocatori_squadra_update` (DD-016) `auth.uid()` è NULL, quindi ogni
UPDATE su `giocatori_squadra` fatto con la service key viene rifiutato. Per
collegare uno slot a un account serve il JWT di un utente con ruolo `admin`;
- su un UPDATE o un DELETE che non tocca nessuna riga PostgREST risponde 2xx.
Il codice di stato non basta: serve `Prefer: return=representation` e contare
le righe, oppure rileggere il dato.
## Convenzioni
- **Sul database configurato in `.env` nessun test scrive.** Integration ed e2e
fanno solo letture e verifiche di validazione: si possono lanciare anche contro
l'ambiente reale. L'unica eccezione apparente è `schema-profili`, che _tenta_
scritture da utente anonimo proprio per dimostrare che la RLS le respinge, e poi
rilegge la riga per verificare che non sia cambiata: su un UPDATE a zero righe
PostgREST risponde 2xx, quindi lo stato conta più del codice di risposta.
I test che scrivono davvero girano solo sul database locale (vedi sopra).
- I test legati alla migration M2 si **saltano da soli** dove quella migration non è
ancora applicata, indicandolo nel motivo. Per vederli tutti verdi serve un
database che la contenga: `npx supabase start` ne crea uno in locale.
- `integration` ed `e2e` avviano da soli il server di sviluppo. Per usarne uno già
attivo: `BASE_URL=http://localhost:8080 npm run test:e2e`.
- Le variabili d'ambiente vengono lette da `.env`; i nomi senza prefisso
(`SUPABASE_URL`, `SUPABASE_PUBLISHABLE_KEY`) sono derivati da quelli `VITE_*`.
I test che richiedono `SUPABASE_SERVICE_ROLE_KEY` si saltano da soli se manca.
- Il check del parsing CSI può girare contro il portale reale:
`CSI_LIVE=1 bun test/unit/csi-core.test.ts`.
## Limite noto
L'app renderizza i contenuti dopo l'idratazione: il server invia solo il guscio
(titolo, meta, splash). Gli e2e verificano quindi le risposte HTTP, i dati che
alimentano le pagine e i file della PWA, ma **non** l'interfaccia renderizzata.
Per quella servirebbe un driver browser (es. Playwright), oggi non installato.