Files
wm-projekt/scripts/verify-migration.ts
2026-07-01 16:36:11 -05:00

165 lines
6.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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("<svg")) { emptyFlags.push(`${code}${iso2}.svg (kein <svg>, ${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<string, RawMatch[]>();
const teamsPerGroup = new Map<string, Set<string>>();
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 (1104)");
const byNum = new Map<number, RawMatch[]>();
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<string>();
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<string, string> = { 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); });