From b2e4b63729ea10140f0626c03774e9617ca60612 Mon Sep 17 00:00:00 2001 From: Andreas Knuth Date: Wed, 1 Jul 2026 16:36:11 -0500 Subject: [PATCH] checks --- lib/flags.ts | 2 +- lib/team-mappings.ts | 1 + scripts/verify-migration.ts | 164 ++++++++++++++++++++++++++++++++++++ 3 files changed, 166 insertions(+), 1 deletion(-) create mode 100644 scripts/verify-migration.ts diff --git a/lib/flags.ts b/lib/flags.ts index 0e61cb3..7eb4953 100644 --- a/lib/flags.ts +++ b/lib/flags.ts @@ -14,6 +14,7 @@ export const TLA_TO_ISO2: Record = { COD: "cd", COL: "co", CPV: "cv", + CUW: "cw", CZE: "cz", ECU: "ec", EGY: "eg", @@ -48,5 +49,4 @@ export const TLA_TO_ISO2: Record = { URU: "uy", USA: "us", UZB: "uz", - CUW: "cw", }; diff --git a/lib/team-mappings.ts b/lib/team-mappings.ts index 5d8f944..f096250 100644 --- a/lib/team-mappings.ts +++ b/lib/team-mappings.ts @@ -18,6 +18,7 @@ export const TEAM_LOCALIZATION: Record = { COD: { de: "DR Kongo", en: "Congo DR" }, COL: { de: "Kolumbien", en: "Colombia" }, CPV: { de: "Kap Verde", en: "Cape Verde" }, + CUW: { de: "Curaçao", en: "Curaçao" }, CZE: { de: "Tschechien", en: "Czech Republic" }, ECU: { de: "Ecuador", en: "Ecuador" }, EGY: { de: "Ägypten", en: "Egypt" }, diff --git a/scripts/verify-migration.ts b/scripts/verify-migration.ts new file mode 100644 index 0000000..a7611f4 --- /dev/null +++ b/scripts/verify-migration.ts @@ -0,0 +1,164 @@ +// verify-migration.ts — Automatisierte FIFA-Migrations-Checks +// Ausführung: npx tsx scripts/verify-migration.ts + +import { TEAM_LOCALIZATION } from "../lib/team-mappings"; +import { TLA_TO_ISO2 } from "../lib/flags"; +import { FIFA_GROUP_MAP } from "../lib/fifa-constants"; +import * as fs from "fs"; +import * as path from "path"; + +const FIFA_BASE = "https://api.fifa.com/api/v3"; +const FIFA_SEASON = "285023"; +const FLAGS_DIR = path.join(__dirname, "..", "public", "flags"); + +interface RawMatch { + MatchNumber: number; + IdGroup: string | null; + IdStage: string; + Home: { IdTeam: string; Abbreviation: string; TeamName?: Array<{ Locale: string; Description: string }> } | null; + Away: { IdTeam: string; Abbreviation: string; TeamName?: Array<{ Locale: string; Description: string }> } | null; + HomeTeamScore: number | null; + AwayTeamScore: number | null; +} + +let failures = 0; +function pass(msg: string) { console.log(" \x1b[32m✓\x1b[0m " + msg); } +function fail(msg: string) { console.log(" \x1b[31m✗\x1b[0m " + msg); failures++; } +function warn(msg: string) { console.log(" \x1b[33m!\x1b[0m " + msg); } + +async function main() { + console.log("\n═══ FIFA-Migration Verifikation ═══\n"); + + // ── 1. Flaggen-Vollständigkeit ── + console.log("1. Flaggen-Vollständigkeit"); + const missingFlags: string[] = []; + const emptyFlags: string[] = []; + for (const code of Object.keys(TEAM_LOCALIZATION)) { + const iso2 = TLA_TO_ISO2[code]; + if (!iso2) { missingFlags.push(`${code} (kein ISO-2)`); continue; } + const filePath = path.join(FLAGS_DIR, `${iso2}.svg`); + if (!fs.existsSync(filePath)) { missingFlags.push(`${code} → ${iso2}.svg`); continue; } + const content = fs.readFileSync(filePath, "utf8"); + if (content.trim().length === 0) { emptyFlags.push(`${code} → ${iso2}.svg (leer)`); continue; } + if (!content.includes(", ${content.length} bytes)`); continue; } + } + if (missingFlags.length === 0 && emptyFlags.length === 0) { + pass(`${Object.keys(TEAM_LOCALIZATION).length} Teams, alle Flaggen ok`); + } else { + for (const f of missingFlags) fail(`Fehlende Flagge: ${f}`); + for (const f of emptyFlags) fail(`Leere/defekte Flagge: ${f}`); + } + + // ── 2. FIFA-Feed abrufen ── + console.log("\n2. FIFA-Feed-Daten abrufen"); + let rawMatches: RawMatch[] = []; + try { + const url = `${FIFA_BASE}/calendar/matches?language=de&count=500&idSeason=${FIFA_SEASON}`; + const res = await fetch(url, { headers: { "User-Agent": "wm2026-board/1.0" }, signal: AbortSignal.timeout(10000) }); + if (!res.ok) { fail(`HTTP ${res.status}`); } else { + const data = (await res.json()) as { Results: RawMatch[] }; + rawMatches = data.Results ?? []; + pass(`${rawMatches.length} Matches geladen`); + } + } catch (err) { + fail(`Feed nicht erreichbar: ${err instanceof Error ? err.message : err}`); + } + + if (rawMatches.length === 0) { + console.log(`\n\x1b[31m${failures} Fehler\x1b[0m (Feed-Fehler, Rest übersprungen)`); + process.exit(failures > 0 ? 1 : 0); + } + + // ── 3. Gruppenzuordnung ── + console.log("\n3. Gruppenzuordnung"); + const perGroup = new Map(); + const teamsPerGroup = new Map>(); + for (const m of rawMatches) { + const gid = m.IdGroup ? FIFA_GROUP_MAP[m.IdGroup] : null; + if (gid) { + if (!perGroup.has(gid)) perGroup.set(gid, []); + perGroup.get(gid)!.push(m); + if (!teamsPerGroup.has(gid)) teamsPerGroup.set(gid, new Set()); + if (m.Home?.IdTeam) teamsPerGroup.get(gid)!.add(m.Home.IdTeam); + if (m.Away?.IdTeam) teamsPerGroup.get(gid)!.add(m.Away.IdTeam); + } + } + const groupIds = ["A","B","C","D","E","F","G","H","I","J","K","L"]; + let groupOk = true; + for (const g of groupIds) { + const matches = perGroup.get(g)?.length ?? 0; + const teams = teamsPerGroup.get(g)?.size ?? 0; + if (matches !== 6) { warn(`Gruppe ${g}: ${matches} Spiele (erwartet 6)`); groupOk = false; } + if (teams !== 4) { warn(`Gruppe ${g}: ${teams} Teams (erwartet 4)`); groupOk = false; } + } + if (groupOk) { + const total = [...perGroup.values()].reduce((s, a) => s + a.length, 0); + pass(`${total} Gruppenspiele, 12 Gruppen mit je 6 Spielen + 4 Teams`); + } + + // ── 4. MatchNumber-Vollständigkeit ── + console.log("\n4. MatchNumber-Vollständigkeit (1–104)"); + const byNum = new Map(); + for (const m of rawMatches) { + if (!byNum.has(m.MatchNumber)) byNum.set(m.MatchNumber, []); + byNum.get(m.MatchNumber)!.push(m); + } + const missing: number[] = []; + const dupes: number[] = []; + for (let n = 1; n <= 104; n++) { + const entries = byNum.get(n); + if (!entries || entries.length === 0) missing.push(n); + else if (entries.length > 1) dupes.push(n); + } + if (missing.length === 0 && dupes.length === 0) { + pass("Alle 104 MatchNumbers genau einmal vorhanden"); + } else { + if (missing.length > 0) fail(`Fehlend: ${missing.join(", ")}`); + if (dupes.length > 0) fail(`Duplikate: ${dupes.join(", ")}`); + } + + // ── 5. Team-Code-Auflösung ── + console.log("\n5. Team-Code-Auflösung"); + const teamCodes = new Set(); + for (const m of rawMatches) { + for (const tb of [m.Home, m.Away]) { + if (tb?.Abbreviation) teamCodes.add(tb.Abbreviation); + } + } + const unresolved: string[] = []; + for (const code of teamCodes) { + const appCode = code; // Rohwert aus FIFA + // FIFA_CODE_OVERRIDE: CRO→HRV, POR→PRT, SUI→CHE + const overrides: Record = { CRO: "HRV", POR: "PRT", SUI: "CHE" }; + const resolved = overrides[code] ?? code; + if (!TEAM_LOCALIZATION[resolved]) unresolved.push(`${code}→${resolved}`); + if (!TLA_TO_ISO2[resolved]) unresolved.push(`${code}→${resolved} (kein ISO-2)`); + } + if (unresolved.length === 0) { + pass(`${teamCodes.size} eindeutige Team-Codes, alle in TEAM_LOCALIZATION + TLA_TO_ISO2`); + } else { + for (const u of unresolved) fail(`Nicht auflösbar: ${u}`); + } + + // ── 6. K.o.-Paarung 89/90 ── + console.log("\n6. K.o.-Paarung 89/90"); + const m89 = rawMatches.find(m => m.MatchNumber === 89); + const m90 = rawMatches.find(m => m.MatchNumber === 90); + if (m89) { + const h = m89.Home?.Abbreviation ?? "?"; + const a = m89.Away?.Abbreviation ?? "?"; + const ok = h === "PAR" && a === "FRA"; + (ok ? pass : fail)(`Spiel 89: ${h}/${a} ${ok ? "(korrekt PAR/FRA)" : "(erwartet PAR/FRA)"}`); + } else fail("Spiel 89 nicht gefunden"); + if (m90) { + const h = m90.Home?.Abbreviation ?? "?"; + const a = m90.Away?.Abbreviation ?? "?"; + const ok = h === "CAN" && a === "MAR"; + (ok ? pass : fail)(`Spiel 90: ${h}/${a} ${ok ? "(korrekt CAN/MAR)" : "(erwartet CAN/MAR)"}`); + } else fail("Spiel 90 nicht gefunden"); + + console.log(`\n═══ Ergebnis: ${failures} Fehler ═══\n`); + process.exit(failures > 0 ? 1 : 0); +} + +main().catch(err => { console.error(err); process.exit(1); });