/**
* Check del parsing CSI: `bun src/lib/csi-core.test.ts`.
* Con CSI_LIVE=1 verifica anche gli endpoint reali del portale.
*/
import assert from "node:assert/strict";
import {
CSI_COPPA_PROJECT_ID,
isNostraSquadra,
matchDaPartitaCsi,
parseClassifica,
parseFormazioni,
parseInfoPartita,
parsePrecedenti,
partiteDaEventi,
partiteFormatoSospetto,
partiteGiocate,
urlClassifica,
urlPartite,
urlPartitaFormazioni,
urlPartitaInfo,
urlPartitaPrecedenti,
} from "@/lib/csi-core";
const htmlClassifica = `
`;
const righe = parseClassifica(htmlClassifica);
assert.equal(righe.length, 2, "prende solo il girone che contiene la nostra squadra");
assert.deepEqual(righe[1], {
pos: 4,
squadra: "C.R.A.P. Volley",
punti: 30,
giocate: 16,
vinte: 10,
perse: 6,
setFatti: 36,
setSubiti: 28,
});
assert.equal(righe[0]!.squadra, "Paolo Poggi Volley & C.", "decodifica le entità HTML");
assert.deepEqual(parseClassifica("pagina cambiata
"), [], "portale cambiato: nessun crash");
// La Coppa ha un girone più piccolo (poche squadre) ma la stessa struttura di tabella del
// campionato: `parseClassifica()` funziona invariata, non serve un parser dedicato.
const htmlCoppa = `
`;
const righeCoppa = parseClassifica(htmlCoppa);
assert.equal(righeCoppa.length, 2, "girone di Coppa più piccolo del campionato: ok comunque");
assert.equal(righeCoppa[1]!.squadra, "C.R.A.P. Volley");
assert.ok(isNostraSquadra("CRAP Volley") && isNostraSquadra("C.R.A.P. Volley "));
assert.ok(!isNostraSquadra("CRAP Volley B"));
const eventi = [
{
id: 1,
start: "2025-11-12T22:00:00",
team1: "C.R.A.P. Volley",
team1_logo: "images/teams/logos/CRAP_Volley_3359.jpg",
team2: "AMCM",
team2_logo: "images/teams/logos/AMCM_1943.JPG",
result: "3 - 1",
partials: "25 - 2323 - 2525 - 1825 - 22",
field: "Sabin Piccolo",
project: "PVM - Campionato Open Misto Eccellenza",
group: "Girone B",
match_number: "5/XEB",
referees: "",
link: "https://livescore.csibologna.it/match_details.php?id=1",
},
{
id: 2,
start: "2025-11-19T22:00:00",
team1: "Gnu Tim SdC",
team2: "C.R.A.P. Volley",
result: "3 - 2",
partials: "25 - 2118 - 25",
field: "BVI",
project: "PVM - Campionato Open Misto Eccellenza",
},
{ id: 3, start: "2026-09-10T21:00:00", team1: "C.R.A.P. Volley", team2: "Esselle", result: "" },
{ id: 4, start: "2025-10-01T21:00:00", team1: "Esselle", team2: "AMCM", result: "3 - 0" },
];
const partite = partiteDaEventi(eventi);
assert.equal(partite.length, 3, "scarta le gare di altre squadre");
assert.equal(partite[0]!.data, "2026-09-10", "ordinate dalla più recente");
assert.equal(partite[0]!.setNostri, null, "gara futura senza risultato");
const casa = partite.find((p) => p.id === "1")!;
assert.deepEqual([casa.casa, casa.avversario, casa.setNostri, casa.setLoro], [true, "AMCM", 3, 1]);
assert.deepEqual(casa.parziali[1], [23, 25]);
assert.equal(casa.ora, "22:00");
assert.equal(
casa.logoAvversario,
"images/teams/logos/AMCM_1943.JPG",
"logo dell'avversario, non il nostro",
);
assert.equal(casa.girone, "Girone B");
assert.equal(casa.numeroGara, "5/XEB");
assert.equal(casa.link, "https://livescore.csibologna.it/match_details.php?id=1");
const trasferta = partite.find((p) => p.id === "2")!;
assert.deepEqual(
[trasferta.casa, trasferta.avversario, trasferta.setNostri, trasferta.setLoro],
[false, "Gnu Tim SdC", 2, 3],
"in trasferta il punteggio va ribaltato",
);
assert.deepEqual(trasferta.parziali[0], [21, 25], "anche i parziali");
assert.equal(partiteGiocate(partite).length, 2);
assert.deepEqual(partiteDaEventi("non è un array"), [], "risposta inattesa: nessun crash");
// --- matchDaPartitaCsi: i campi leggeri (logo, girone, n° gara, arbitro, link) arrivano
// fino alla forma usata nelle liste risultati, non solo nel tipo interno --------------------
assert.deepEqual(matchDaPartitaCsi(casa), {
id: "1",
data: "2025-11-12",
avversario: "AMCM",
logoAvversario: "images/teams/logos/AMCM_1943.JPG",
casa: true,
setNostri: 3,
setLoro: 1,
parziali: casa.parziali,
campo: "Sabin Piccolo",
girone: "Girone B",
numeroGara: "5/XEB",
arbitro: "",
link: "https://livescore.csibologna.it/match_details.php?id=1",
});
// --- partiteFormatoSospetto: distingue "nessuna gara ancora" da "formato rotto" -------------
assert.equal(
partiteFormatoSospetto(eventi, partite),
false,
"eventi grezzi presenti e riconosciuti: nessun sospetto",
);
assert.equal(
partiteFormatoSospetto([], []),
false,
"nessun evento grezzo: probabilmente solo una stagione senza gare, non un formato rotto",
);
assert.equal(
partiteFormatoSospetto("non è un array", []),
true,
"la risposta non è più un array: il formato è cambiato",
);
const eventiSenzaCampiRiconosciuti = [
{ id: 1, quando: "2025-11-12", squadraCasa: "C.R.A.P. Volley", squadraOspite: "AMCM" },
];
assert.equal(
partiteFormatoSospetto(
eventiSenzaCampiRiconosciuti,
partiteDaEventi(eventiSenzaCampiRiconosciuti),
),
true,
"eventi presenti ma con campi rinominati: nessuno riconosciuto, sospetto fondato",
);
// --- parseInfoPartita: giornata e nota libera da match-main.php -----------------------------
const htmlMatchMain = `
`;
assert.deepEqual(parseInfoPartita(htmlMatchMain), {
giornata: "2ª Giornata",
nota: "Pubblico non ammesso",
});
assert.deepEqual(
parseInfoPartita("pagina cambiata
"),
{ giornata: "", nota: "" },
"portale cambiato: nessun crash, campi vuoti",
);
// --- parseFormazioni: titolari/panchina/staff di entrambe le squadre, noi riconosciuti
// tramite isNostraSquadra() e non un ordine fisso ------------------------------------------
const htmlFormazioni = `
`;
const formazioni = parseFormazioni(htmlFormazioni)!;
assert.equal(formazioni.noi.squadra, "C.R.A.P. Volley");
assert.equal(
formazioni.avversario.squadra,
"AMCM",
"riconosciuta come avversario anche da seconda posizione",
);
assert.deepEqual(formazioni.noi.titolari, [
{ numero: "73", nome: "Bologna Mattias", ruolo: "" },
{ numero: "11", nome: "Chilese Silvia", ruolo: "Libero" },
]);
assert.deepEqual(formazioni.noi.panchina, [
{ numero: "14", nome: "Di Castelnuovo Carlo", ruolo: "" },
]);
assert.deepEqual(formazioni.noi.staff, [{ nome: "Ricci Iacopo", ruolo: "Allenatore" }]);
assert.equal(formazioni.avversario.titolari.length, 1);
assert.deepEqual(parseFormazioni("pagina cambiata
"), null, "portale cambiato: nessun crash");
assert.deepEqual(
parseFormazioni(htmlFormazioni.replace("C.R.A.P. Volley", "Un'altra squadra")),
null,
"nessuna delle due è la nostra squadra: formato non riconosciuto",
);
// --- parsePrecedenti: storico scontri diretti e probabilità di vittoria da match-stats.php --
const htmlPrecedenti = `
56.97%
43.03%
`;
assert.deepEqual(parsePrecedenti(htmlPrecedenti), {
totale: 5,
vinteNoi: 3,
vinteAvversario: 2,
casaNoi: 1,
casaAvversario: 1,
fuoriNoi: 2,
fuoriAvversario: 1,
probabilitaNoi: 56.97,
probabilitaAvversario: 43.03,
});
// Se siamo la squadra ospite invece che casa, i numeri vanno scambiati di conseguenza.
const precedentiOspiti = parsePrecedenti(
htmlPrecedenti
.replace("C.R.A.P. Volley", "__TMP__")
.replace("AMCM", "C.R.A.P. Volley")
.replace("__TMP__", "AMCM"),
)!;
assert.deepEqual(
[precedentiOspiti.vinteNoi, precedentiOspiti.vinteAvversario, precedentiOspiti.probabilitaNoi],
[2, 3, 43.03],
"noi/avversario scambiati quando siamo la squadra ospite",
);
assert.equal(parsePrecedenti("pagina cambiata
"), null, "portale cambiato: nessun crash");
// --- URL dei nuovi endpoint per-partita ------------------------------------------------------
assert.equal(
urlPartitaInfo("33694"),
"https://livescore.csibologna.it/components/match-main.php?match_id=33694",
);
assert.equal(
urlPartitaFormazioni("33694"),
"https://livescore.csibologna.it/components/match-players.php?match_id=33694",
);
assert.equal(
urlPartitaPrecedenti("33694"),
"https://livescore.csibologna.it/components/match-stats.php?match_id=33694",
);
if (process.env["CSI_LIVE"]) {
const [html, htmlCoppa, json] = await Promise.all([
fetch(urlClassifica()).then((r) => r.text()),
fetch(urlClassifica(CSI_COPPA_PROJECT_ID)).then((r) => r.text()),
fetch(urlPartite()).then((r) => r.json()),
]);
const live = parseClassifica(html);
const nostra = live.find((r) => isNostraSquadra(r.squadra));
assert.ok(nostra, "la nostra squadra è in classifica");
assert.ok(live.length >= 8 && nostra.giocate > 0);
const liveCoppa = parseClassifica(htmlCoppa);
assert.ok(
liveCoppa.some((r) => isNostraSquadra(r.squadra)),
"la nostra squadra è nel girone di Coppa",
);
const livePartite = partiteDaEventi(json);
const liveGiocate = partiteGiocate(livePartite);
assert.ok(livePartite.length > 0 && liveGiocate.length > 0);
assert.ok(
liveGiocate.every((p) => p.link.startsWith("https://")),
"ogni partita ha un referto",
);
const primaGiocata = liveGiocate[0]!;
const [liveMain, livePlayers, liveStats] = await Promise.all([
fetch(urlPartitaInfo(primaGiocata.id)).then((r) => r.text()),
fetch(urlPartitaFormazioni(primaGiocata.id)).then((r) => r.text()),
fetch(urlPartitaPrecedenti(primaGiocata.id)).then((r) => r.text()),
]);
const liveFormazioni = parseFormazioni(livePlayers);
assert.ok(liveFormazioni, "formazioni riconosciute per una gara già giocata");
assert.ok(isNostraSquadra(liveFormazioni!.noi.squadra));
const livePrecedenti = parsePrecedenti(liveStats);
assert.ok(livePrecedenti, "precedenti riconosciuti");
console.log(
`live: ${live.length} squadre, ${livePartite.length} partite, noi ${nostra.pos}°, ` +
`${parseInfoPartita(liveMain).giornata || "(senza giornata)"}, ` +
`formazione ${liveFormazioni!.noi.titolari.length} titolari`,
);
}
console.log("csi-core: ok");