// 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); });