Porta la versione a 1.4.0: «Presenza modificata» + copertura test e docs

Sezione 1.4.0 del CHANGELOG, versione in package.json e package-lock.json,
PROJECT_STATE aggiornato. Il test di presenza-modificata copre ora i confini
delle 6 ore, ogni stato, allenatore inattivo, admin senza slot, doppio ruolo e
ora dell'evento non valida. Documentazione delle notifiche allineata (riga 9,
copertura test, email, DATABASE). Nessuna nuova migration: la m32 e la m33 sono
applicate in produzione (09/10/2026).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
davideandClaude Sonnet 5.5 committed 2026-10-09 11:24:11 +02:00
1 parent 986098d933
commit 3bf07049e9
8 files changed
+125 -18

No files matched your search

+4 -3
View File
@@ -9,14 +9,15 @@ schema in [docs/DATABASE.md](docs/DATABASE.md), le procedure in
## Stato generale ## Stato generale
- **Versione:** 1.3.1 (in produzione su `main` la 1.3.0, tag `v1.3.0`) — il Calendario mostra solo i prossimi - **Versione:** 1.4.0 (in produzione su `main` la 1.3.0, tag `v1.3.0`) — avviso «Presenza modificata» ad admin e
allenatori (DD-050, `m32`-`m33`). La 1.3.1: il Calendario mostra solo i prossimi
compleanni del mese; tolta la serie «Conferme 24h» con i badge «Risposta lampo» e «Mai un forfait». La 1.3.0 compleanni del mese; tolta la serie «Conferme 24h» con i badge «Risposta lampo» e «Mai un forfait». La 1.3.0
portava la notifica di compleanno, gli obiettivi cliccabili, le presenze sui convocati, gli avvisi in Home portava la notifica di compleanno, gli obiettivi cliccabili, le presenze sui convocati, gli avvisi in Home
ridisegnati e i caratteri più grandi. Dettaglio in [docs/CHANGELOG.md](docs/CHANGELOG.md). ridisegnati e i caratteri più grandi. Dettaglio in [docs/CHANGELOG.md](docs/CHANGELOG.md).
- **Backend:** Supabase proprietario (`kfkcldwncxqaixetsjes`). Lovable Cloud e il vecchio - **Backend:** Supabase proprietario (`kfkcldwncxqaixetsjes`). Lovable Cloud e il vecchio
Project Ref `hetycilxgkdmccelwerq` non si usano più. Project Ref `hetycilxgkdmccelwerq` non si usano più.
- **Migration:** 44 file in `supabase/migrations/`, fino a `m33_presenza_modificata_allenatori`. Fino alla `m32` applicate in produzione (verificato con `npx supabase migration list` il - **Migration:** 44 file in `supabase/migrations/`, fino a `m33_presenza_modificata_allenatori`. Tutte applicate in produzione (verificato con `npx supabase migration list` il
09/10/2026); la `m32` (DD-050, avviso «Presenza modificata» ai soli admin) è stata applicata quel giorno; la `m33` (anche agli allenatori) no, ancora da applicare. La `m29` toglie 09/10/2026; la `m32` è l'avviso «Presenza modificata» ai soli admin, DD-050). La `m33`, che lo estende agli allenatori, è stata applicata il 09/10/2026. La `m29` toglie
`foto_path`: il codice che non la legge più (1.2.2) va rilasciato. `foto_path`: il codice che non la legge più (1.2.2) va rilasciato.
- **Worker delle notifiche** (`mailer/`): in funzione su un Raspberry Pi con Docker dal 29/09/2026 - **Worker delle notifiche** (`mailer/`): in funzione su un Raspberry Pi con Docker dal 29/09/2026
([docs/WORKER_EMAIL.md](docs/WORKER_EMAIL.md)). Il codice con l'interruttore «Email» in Profilo ([docs/WORKER_EMAIL.md](docs/WORKER_EMAIL.md)). Il codice con l'interruttore «Email» in Profilo
+9 -4
View File
@@ -13,12 +13,17 @@ versioni `0.y.z` erano pre-release.
## [Non rilasciato] ## [Non rilasciato]
## [1.4.0] - 2026-10-09
Gli admin e gli allenatori vengono avvisati quando una presenza cambia a ridosso di una partita o di un allenamento.
Due migration, `m32` e `m33`, entrambe applicate in produzione il 09/10/2026.
### Aggiunto ### Aggiunto
- **Notifica «Presenza modificata» (DD-050)** — quando una risposta a una partita o a - **Notifica «Presenza modificata» (DD-050)** — quando una risposta a una partita o a un allenamento cambia,
un allenamento cambia, compare o viene ritirata a meno di 6 ore dall'inizio, gli admin e gli allenatori ricevono una notifica compare o viene ritirata a meno di 6 ore dall'inizio, gli admin e gli allenatori ricevono una notifica (centro
(centro notifiche, push, email) con chi, quale evento e lo stato prima e dopo. Migration `m32` (solo admin, notifiche, push, email) con chi, quale evento e lo stato prima e dopo. Migration `m32` (solo admin, applicata in
applicata in produzione il 09/10/2026) e `m33` (anche allenatori). produzione il 09/10/2026) e `m33` (aggiunge gli allenatori, applicata il 09/10/2026).
## [1.3.1] - 2026-10-06 ## [1.3.1] - 2026-10-06
+1 -1
View File
@@ -126,7 +126,7 @@ votante e votato convocati all'evento (RLS, `evento_permette_voto()`, DD-027).
`sollecita_presenze_24h`, `_12h`, `_6h`, scritti dai job (DD-040). `turno_palloni` e `sollecita_presenze_24h`, `_12h`, `_6h`, scritti dai job (DD-040). `turno_palloni` e
`sollecita_presenze` restano i tipi dei pulsanti manuali. `sollecita_presenze` restano i tipi dei pulsanti manuali.
- Il trigger delle email salta tutti i tipi `turno_palloni*`; quello delle push accoda i tipi - Il trigger delle email salta tutti i tipi `turno_palloni*`; quello delle push accoda i tipi
automatici (compleanni compresi) ma non quelli dei pulsanti. automatici (compleanni e `presenza_modificata` compresi) ma non quelli dei pulsanti. `presenza_modificata` ha `evento_id` NULL (come `admin`): la UNIQUE non si applica e ogni modifica resta una riga (M32, DD-050).
### Code di invio (`notifiche_email_coda`, `notifiche_push_coda`) ### Code di invio (`notifiche_email_coda`, `notifiche_push_coda`)
+1 -1
View File
@@ -49,7 +49,7 @@ App su Vercel ──scrive──▶ notifiche_utente ──trigger──▶ noti
## Quali notifiche e a chi ## Quali notifiche e a chi
**Ogni riga nuova di `notifiche_utente` genera una mail**: messaggio admin, sollecito presenze, sondaggio pre-partita, compleanno (DD-047: solo il titolo, senza corpo; la mail ha oggetto, link e avviso di disattivazione). **Fa eccezione il turno palloni** (DD-040, dalla **Ogni riga nuova di `notifiche_utente` genera una mail**: messaggio admin, sollecito presenze, sondaggio pre-partita, compleanno (DD-047: solo il titolo, senza corpo; la mail ha oggetto, link e avviso di disattivazione), presenza modificata (DD-050, ad admin e allenatori). **Fa eccezione il turno palloni** (DD-040, dalla
`m26`): per i tipi `turno_palloni`, `turno_palloni_12h`, `_6h`, `_3h` e `turno_palloni_revocato` il `m26`): per i tipi `turno_palloni`, `turno_palloni_12h`, `_6h`, `_3h` e `turno_palloni_revocato` il
trigger non accoda nulla, restano push e centro notifiche. Un tipo nuovo che in futuro finisse in quella tabella trigger non accoda nulla, restano push e centro notifiche. Un tipo nuovo che in futuro finisse in quella tabella
viaggerebbe per email senza altro lavoro. Le notifiche già presenti quando si applica la migration viaggerebbe per email senza altro lavoro. Le notifiche già presenti quando si applica la migration
+4 -3
View File
@@ -46,9 +46,9 @@ quelle del turno palloni**, che restano solo push e in-app: DD-040).
| 8 | **Compleanno** — auguri al festeggiato, avviso agli altri | **Automatica**, **una volta al giorno, dalle 8:00** (fuso `Europe/Rome`) per chi compie gli anni quel giorno (DD-047, `m31`): job `pg_cron` ogni ora, funzione `genera_avvisi_compleanno()`, registro `compleanni_notificati`; tipi `compleanno_auguri` e `compleanno`, push dal worker. Nessun pulsante manuale. | **Tutti i membri attivi**, allenatori compresi: il festeggiato riceve gli auguri, gli altri l'avviso. Con più compleanni lo stesso giorno, un solo messaggio a testa (agli altri con tutti i nomi, a ogni festeggiato solo i propri auguri). | sì | sì | sì | | 8 | **Compleanno** — auguri al festeggiato, avviso agli altri | **Automatica**, **una volta al giorno, dalle 8:00** (fuso `Europe/Rome`) per chi compie gli anni quel giorno (DD-047, `m31`): job `pg_cron` ogni ora, funzione `genera_avvisi_compleanno()`, registro `compleanni_notificati`; tipi `compleanno_auguri` e `compleanno`, push dal worker. Nessun pulsante manuale. | **Tutti i membri attivi**, allenatori compresi: il festeggiato riceve gli auguri, gli altri l'avviso. Con più compleanni lo stesso giorno, un solo messaggio a testa (agli altri con tutti i nomi, a ogni festeggiato solo i propri auguri). | sì | sì | sì |
| 9 | **Presenza modificata** — «Presenza modificata: _Nome Cognome_» _(DD-050, `m32` e `m33`)_ | **Automatica**, a ogni cambio di una risposta in `risposte_presenze` (nuova, modificata o ritirata) quando mancano da 0 a 6 ore all'inizio di una partita o di un allenamento: trigger sul database, tipo `presenza_modificata`, una notifica per modifica, push dal worker. | Gli **admin e gli allenatori attivi** (DD-050, `m33`; la `m32` li dava ai soli admin). Mai il giocatore a cui appartiene la presenza. | sì | sì | sì | | 9 | **Presenza modificata** — «Presenza modificata: _Nome Cognome_» _(DD-050, `m32` e `m33`)_ | **Automatica**, a ogni cambio di una risposta in `risposte_presenze` (nuova, modificata o ritirata) quando mancano da 0 a 6 ore all'inizio di una partita o di un allenamento: trigger sul database, tipo `presenza_modificata`, una notifica per modifica, push dal worker. | Gli **admin e gli allenatori attivi** (DD-050, `m33`; la `m32` li dava ai soli admin). Mai il giocatore a cui appartiene la presenza. | sì | sì | sì |
Le notifiche generate dal server (righe 1-6 e 8) hanno tutti e tre i canali (DD-038), tranne il turno palloni (riga 4), che non manda email (DD-040). La Le notifiche generate dal server (righe 1-6, 8 e 9) hanno tutti e tre i canali (DD-038), tranne il turno palloni (riga 4), che non manda email (DD-040). La
differenza sta in come parte la push: quelle dei pulsanti (3 e 6) la mandano le route dell'app, quelle differenza sta in come parte la push: quelle dei pulsanti (3 e 6) la mandano le route dell'app, quelle
automatiche (4, 5 e 8; la 1 e la 2 non esistono più) nascono nel database e la manda il worker `mailer/` (vedi «Push dei automatiche (4, 5, 8 e 9; la 1 e la 2 non esistono più) nascono nel database e la manda il worker `mailer/` (vedi «Push dei
promemoria»). Il sondaggio riscrive la notifica esistente se l'admin preme di nuovo il pulsante per promemoria»). Il sondaggio riscrive la notifica esistente se l'admin preme di nuovo il pulsante per
lo stesso evento (vedi «Centro notifiche in-app»). lo stesso evento (vedi «Centro notifiche in-app»).
@@ -161,7 +161,7 @@ descrivono come funzionavano fino alla `m28`: la funzione esiste ancora ma nessu
### Test che verificano il catalogo ### Test che verificano il catalogo
| Cosa | Test | | Cosa | Test |
| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Promemoria 24 e 3 ore (funzione ancora presente ma non pianificata, M28): destinatari (convocati, tutti i giocatori attivi se `convocati` è vuoto, mai l'allenatore), deduplica, eliminazione, coda push ed email | `test/integration/destinatari-notifiche.test.ts`, `promemoria-eventi.test.ts`, `push-promemoria.test.ts`, `notifiche-email.test.ts`; niente job pianificati: `notifiche-automatiche.test.ts` (M28) | | Promemoria 24 e 3 ore (funzione ancora presente ma non pianificata, M28): destinatari (convocati, tutti i giocatori attivi se `convocati` è vuoto, mai l'allenatore), deduplica, eliminazione, coda push ed email | `test/integration/destinatari-notifiche.test.ts`, `promemoria-eventi.test.ts`, `push-promemoria.test.ts`, `notifiche-email.test.ts`; niente job pianificati: `notifiche-automatiche.test.ts` (M28) |
| Messaggio dello staff: push a tutti i dispositivi (allenatore compreso) o a uno solo, in-app a tutta la rosa attiva, riservato agli admin | `destinatari-notifiche.test.ts`, `notifiche-utente.test.ts` | | Messaggio dello staff: push a tutti i dispositivi (allenatore compreso) o a uno solo, in-app a tutta la rosa attiva, riservato agli admin | `destinatari-notifiche.test.ts`, `notifiche-utente.test.ts` |
| Turno palloni: incaricato e chi li aveva prima, solo allenamenti e partite, riservato agli admin | `destinatari-notifiche.test.ts`, `unit/palloni-core.test.ts` | | Turno palloni: incaricato e chi li aveva prima, solo allenamenti e partite, riservato agli admin | `destinatari-notifiche.test.ts`, `unit/palloni-core.test.ts` |
@@ -169,6 +169,7 @@ descrivono come funzionavano fino alla `m28`: la funzione esiste ancora ma nessu
| Sondaggio pre-partita: push senza allenatori, avviso in-app ed email ai giocatori attivi, `upsert`, riservato agli admin | `destinatari-notifiche.test.ts`, `sondaggio-notifiche.test.ts`, `unit/cacche.test.ts` | | Sondaggio pre-partita: push senza allenatori, avviso in-app ed email ai giocatori attivi, `upsert`, riservato agli admin | `destinatari-notifiche.test.ts`, `sondaggio-notifiche.test.ts`, `unit/cacche.test.ts` |
| Promemoria con «oggi/domani», turno palloni a 3 ore e revoca, solleciti a 24 ore (manuale compreso): finestre, destinatari, una sola volta, registro (DD-040, `m26`; palloni a 3 ore: DD-042, solleciti a 24 ore: DD-045, `m30`; `m27`) | `integration/notifiche-automatiche.test.ts`, `promemoria-eventi.test.ts` (testo oggi/domani), `destinatari-notifiche.test.ts` (pulsanti), `unit/palloni-core.test.ts`, `unit/presenze.test.ts` | | Promemoria con «oggi/domani», turno palloni a 3 ore e revoca, solleciti a 24 ore (manuale compreso): finestre, destinatari, una sola volta, registro (DD-040, `m26`; palloni a 3 ore: DD-042, solleciti a 24 ore: DD-045, `m30`; `m27`) | `integration/notifiche-automatiche.test.ts`, `promemoria-eventi.test.ts` (testo oggi/domani), `destinatari-notifiche.test.ts` (pulsanti), `unit/palloni-core.test.ts`, `unit/presenze.test.ts` |
| Compleanno: ora (anche con l'ora legale), festeggiato e altri, più compleanni lo stesso giorno, una sola volta anche se eliminata, push ed email in coda, 29 febbraio, job pianificato (DD-047, `m31`) | `integration/notifiche-compleanno.test.ts` | | Compleanno: ora (anche con l'ora legale), festeggiato e altri, più compleanni lo stesso giorno, una sola volta anche se eliminata, push ed email in coda, 29 febbraio, job pianificato (DD-047, `m31`) | `integration/notifiche-compleanno.test.ts` |
| Presenza modificata: finestra di 0-6 ore, solo partite e allenamenti, admin e allenatori attivi (non gli inattivi né chi non ha slot, una sola notifica a chi ha due ruoli), mai il giocatore stesso, tutti gli stati, nessun avviso se lo stato non cambia o l'evento è cancellato, push ed email in coda (DD-050, `m32`-`m33`) | `integration/presenza-modificata.test.ts`, `unit/notifiche-utente.test.ts` (tipo) |
| Worker: esiti, backoff, tetto giornaliero, iscrizioni scadute | `unit/mailer-core.test.ts` | | Worker: esiti, backoff, tetto giornaliero, iscrizioni scadute | `unit/mailer-core.test.ts` |
| L'allenatore è fuori dalla rosa di gioco | `unit/giocatori-squadra.test.ts` (`inRosa`) | | L'allenatore è fuori dalla rosa di gioco | `unit/giocatori-squadra.test.ts` (`inRosa`) |
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "crapp", "name": "crapp",
"version": "1.3.1", "version": "1.4.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "crapp", "name": "crapp",
"version": "1.3.1", "version": "1.4.0",
"dependencies": { "dependencies": {
"@supabase/supabase-js": "^2.111.0", "@supabase/supabase-js": "^2.111.0",
"@tailwindcss/vite": "^4.2.1", "@tailwindcss/vite": "^4.2.1",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "crapp", "name": "crapp",
"version": "1.3.1", "version": "1.4.0",
"private": true, "private": true,
"sideEffects": false, "sideEffects": false,
"type": "module", "type": "module",
+103 -3
View File
@@ -12,7 +12,7 @@
* - ogni modifica è una notifica a sé (nessun raggruppamento) e ha la coda email e la coda push. * - ogni modifica è una notifica a sé (nessun raggruppamento) e ha la coda email e la coda push.
* *
* Gira solo sullo stack locale (`npx supabase start`). Usa gli slot `g4` (chi risponde), `g5` (admin), * Gira solo sullo stack locale (`npx supabase start`). Usa gli slot `g4` (chi risponde), `g5` (admin),
* `g6` (giocatore) e `g10` (allenatore) della rosa seed, ripristinati alla fine. * `g6` (giocatore), `g9` (allenatore inattivo) e `g10` (allenatore) della rosa seed, ripristinati alla fine.
*/ */
import assert from "node:assert/strict"; import assert from "node:assert/strict";
import { statoLocale } from "../helpers/locale"; import { statoLocale } from "../helpers/locale";
@@ -32,6 +32,7 @@ if (!locale) {
const ADMIN = "g5"; const ADMIN = "g5";
const GIOCATORE = "g6"; const GIOCATORE = "g6";
const ALLENATORE = "g10"; const ALLENATORE = "g10";
const INATTIVO = "g9";
const PREFISSO = "test-presenza-mod-"; const PREFISSO = "test-presenza-mod-";
const rest = (percorso: string, token: string, init?: RequestInit) => const rest = (percorso: string, token: string, init?: RequestInit) =>
@@ -137,10 +138,12 @@ if (!locale) {
try { try {
const admin = await creaUtente(`${PREFISSO}admin-${Date.now()}@example.test`); const admin = await creaUtente(`${PREFISSO}admin-${Date.now()}@example.test`);
const allenatore = await creaUtente(`${PREFISSO}allenatore-${Date.now()}@example.test`); const allenatore = await creaUtente(`${PREFISSO}allenatore-${Date.now()}@example.test`);
idUtenti.push(admin.id, allenatore.id); const inattivo = await creaUtente(`${PREFISSO}inattivo-${Date.now()}@example.test`);
const senzaSlot = await creaUtente(`${PREFISSO}senza-slot-${Date.now()}@example.test`);
idUtenti.push(admin.id, allenatore.id, inattivo.id, senzaSlot.id);
await scrivi("user_roles", "POST", { user_id: admin.id, role: "admin" }); await scrivi("user_roles", "POST", { user_id: admin.id, role: "admin" });
tokenAdmin = admin.token; tokenAdmin = admin.token;
for (const id of [RISPONDE, ADMIN, GIOCATORE, ALLENATORE]) { for (const id of [RISPONDE, ADMIN, GIOCATORE, ALLENATORE, INATTIVO]) {
const [riga] = await leggi<Record<string, unknown>>( const [riga] = await leggi<Record<string, unknown>>(
`giocatori_squadra?id=eq.${id}&select=tipo,attivo,auth_user_id`, `giocatori_squadra?id=eq.${id}&select=tipo,attivo,auth_user_id`,
); );
@@ -159,6 +162,16 @@ if (!locale) {
{ tipo: "allenatore", auth_user_id: allenatore.id }, { tipo: "allenatore", auth_user_id: allenatore.id },
tokenAdmin, tokenAdmin,
); );
// Un allenatore con slot disattivato, un admin senza nessuno slot e un admin che ha anche il
// ruolo allenatore: nessuno dei primi due può ricevere, il terzo riceve una sola notifica.
await scrivi(
`giocatori_squadra?id=eq.${INATTIVO}`,
"PATCH",
{ tipo: "allenatore", auth_user_id: inattivo.id, attivo: false },
tokenAdmin,
);
await scrivi("user_roles", "POST", { user_id: senzaSlot.id, role: "admin" });
await scrivi("user_roles", "POST", { user_id: admin.id, role: "allenatore" });
await svuota(); await svuota();
const [chi] = await leggi<{ nome: string; cognome: string }>( const [chi] = await leggi<{ nome: string; cognome: string }>(
@@ -300,6 +313,93 @@ if (!locale) {
assert.equal((await notifiche()).length, 0); assert.equal((await notifiche()).length, 0);
}); });
await prova(
"destinatari: né inattivi né senza slot, e una sola notifica a chi ha due ruoli",
async () => {
await svuota();
const id = `${PREFISSO}destinatari`;
await creaEvento(id, "partita", 4);
idEventi.push(id);
await risposta(id, "presente");
const destinatari = (await notifiche()).map((r) => r.giocatore_id);
assert.ok(!destinatari.includes(INATTIVO), "slot disattivato");
assert.deepEqual([...destinatari].sort(), await attesi());
assert.equal(
destinatari.filter((d) => d === ADMIN).length,
1,
"chi ha sia il ruolo admin sia allenatore riceve una sola notifica",
);
},
);
await prova(
"cambio di un giocatore: avvisati admin e allenatore, mai il giocatore stesso",
async () => {
await svuota();
const id = `${PREFISSO}per-conto`;
await creaEvento(id, "partita", 2);
idEventi.push(id);
await risposta(id, "presente");
await aggiorna(id, "assente");
const destinatari = new Set((await notifiche()).map((r) => r.giocatore_id));
assert.ok(destinatari.has(ALLENATORE) && destinatari.has(ADMIN));
assert.ok(!destinatari.has(RISPONDE));
},
);
await prova("tutti gli stati hanno il loro nome nel testo", async () => {
await svuota();
const id = `${PREFISSO}stati`;
await creaEvento(id, "allenamento", 1);
idEventi.push(id);
const sequenza = ["presente", "assente", "forse", "ritardo", "infortunato"];
await risposta(id, sequenza[0]!);
for (const stato of sequenza.slice(1)) await aggiorna(id, stato);
const corpi = (await notifiche())
.filter((r) => r.giocatore_id === ADMIN)
.map((r) => r.corpo.split("\n").slice(2).join("|"));
assert.deepEqual(corpi, [
"Da: nessuna risposta|A: presente",
"Da: presente|A: assente",
"Da: assente|A: forse",
"Da: forse|A: in ritardo",
"Da: in ritardo|A: infortunato",
]);
});
// Confini della finestra: la soglia è di 6 ore esatte (da 0 a 6).
for (const [nome, ore, avvisa] of [
["a 5 ore e 50 minuti", 5 + 50 / 60, true],
["a 6 ore e 10 minuti", 6 + 10 / 60, false],
["a 10 minuti", 10 / 60, true],
["iniziato da 10 minuti", -10 / 60, false],
] as const) {
await prova(`finestra: evento ${nome} ${avvisa ? "avvisa" : "non avvisa"}`, async () => {
await svuota();
const id = `${PREFISSO}soglia-${idEventi.length}`;
await creaEvento(id, "partita", ore);
idEventi.push(id);
await risposta(id, "presente");
assert.equal((await notifiche()).length > 0, avvisa);
});
}
await prova("ora dell'evento illeggibile: nessun avviso e nessun errore", async () => {
await svuota();
const id = `${PREFISSO}ora-illeggibile`;
const { data } = traOre(3);
await scrivi("eventi_app", "POST", {
id,
tipo: "partita",
titolo: "Ora strana",
data,
ora: "boh",
});
idEventi.push(id);
await risposta(id, "presente");
assert.equal((await notifiche()).length, 0);
});
await prova("la funzione non è chiamabile dai client", async () => { await prova("la funzione non è chiamabile dai client", async () => {
for (const token of [ANON, admin.token]) { for (const token of [ANON, admin.token]) {
const res = await fetch(`${URL_BASE}/rest/v1/rpc/avvisa_presenza_modificata`, { const res = await fetch(`${URL_BASE}/rest/v1/rpc/avvisa_presenza_modificata`, {