Files
nomadamas__k-skill/packages/kleague-results/test/index.test.js
Jeffrey (Dongkyu) Kim c784a77454 Reject impossible K League schedule dates before querying
The kleague-results API previously accepted regex-shaped but impossible calendar dates and treated them like empty-result queries. This change adds a regression test first, then rejects invalid YYYY-MM-DD strings during normalization so bad input never reaches the fetch layer.

Constraint: Public API contract says date must be a valid Date or YYYY-MM-DD string
Rejected: Let upstream return zero matches | hides invalid input as a valid no-results response
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep string-date validation in normalizeDateInput before any schedule fetch
Tested: node --test packages/kleague-results/test/index.test.js; npm run ci; live smoke getKLeagueSummary(2026-03-22, K리그1, FC서울, standings); live smoke getMatchResults(2026-03-22, K리그2); invalid-date smoke getMatchResults(2026-13-40); LSP diagnostics on changed files; architect verification
Not-tested: Additional leap-year and month-end invalid-date permutations
2026-03-29 16:22:38 +09:00

155 lines
5.0 KiB
JavaScript

const test = require("node:test");
const assert = require("node:assert/strict");
const fs = require("node:fs");
const path = require("node:path");
const {
getKLeagueSummary,
getMatchResults,
getStandings
} = require("../src/index");
const {
normalizeLeagueId,
normalizeScheduleResponse,
normalizeStandingsResponse
} = require("../src/parse");
const fixturesDir = path.join(__dirname, "fixtures");
const schedulePayload = JSON.parse(
fs.readFileSync(path.join(fixturesDir, "schedule-kleague1-2026-03.json"), "utf8")
);
const standingsPayload = JSON.parse(
fs.readFileSync(path.join(fixturesDir, "standings-kleague1-2026.json"), "utf8")
);
test("normalizeLeagueId accepts K League numeric and Korean aliases", () => {
assert.equal(normalizeLeagueId(1), 1);
assert.equal(normalizeLeagueId("K리그1"), 1);
assert.equal(normalizeLeagueId("k league 2"), 2);
assert.equal(normalizeLeagueId("kleague2"), 2);
assert.throws(() => normalizeLeagueId("K리그3"), /leagueId/);
});
test("normalizeScheduleResponse filters a date and team alias from the official monthly payload", () => {
const result = normalizeScheduleResponse(schedulePayload, {
date: "2026-03-22",
leagueId: 1,
team: "FC서울"
});
assert.equal(result.queryDate, "2026-03-22");
assert.equal(result.leagueId, 1);
assert.equal(result.matches.length, 1);
assert.equal(result.matches[0].competitionName, "하나은행 K리그1 2026");
assert.equal(result.matches[0].round, 5);
assert.equal(result.matches[0].status.code, "FE");
assert.equal(result.matches[0].status.label, "종료");
assert.equal(result.matches[0].homeTeam.code, "K09");
assert.equal(result.matches[0].homeTeam.name, "서울");
assert.equal(result.matches[0].homeTeam.fullName, "FC서울");
assert.equal(result.matches[0].awayTeam.name, "광주");
assert.deepEqual(result.matches[0].score, { home: 5, away: 0 });
assert.equal(result.matches[0].venue.name, "서울 월드컵 경기장");
assert.equal(result.filteredTeam.normalized, "FC서울");
});
test("normalizeStandingsResponse keeps the official K League table shape", () => {
const table = normalizeStandingsResponse(standingsPayload, { leagueId: 1, year: 2026 });
const seoul = table.rows.find((row) => row.team.code === "K09");
assert.equal(table.leagueId, 1);
assert.equal(table.year, 2026);
assert.equal(table.isSplitRank, false);
assert.equal(table.rows.length, 12);
assert.equal(seoul.rank, 1);
assert.equal(seoul.team.name, "서울");
assert.equal(seoul.points, 12);
assert.equal(seoul.played, 4);
assert.deepEqual(seoul.form.slice(0, 4), ["승", "승", "승", "승"]);
});
test("public fetchers compose day results with current standings via mocked fetch", async () => {
const originalFetch = global.fetch;
const calls = [];
global.fetch = async (url, options = {}) => {
const target = String(url);
calls.push({
target,
method: options.method || "GET",
body: options.body || null,
headers: options.headers || {},
});
if (target.endsWith("/getScheduleList.do")) {
return makeResponse(schedulePayload);
}
if (target.includes("/record/teamRank.do?leagueId=1&year=2026&stadium=all&recordType=rank")) {
return makeResponse(standingsPayload);
}
throw new Error(`unexpected url: ${target}`);
};
try {
const matches = await getMatchResults("2026-03-22", { leagueId: "K리그1", team: "서울" });
assert.equal(matches.matches.length, 1);
assert.equal(matches.matches[0].homeTeam.fullName, "FC서울");
const standings = await getStandings({ leagueId: 1, year: 2026 });
assert.equal(standings.rows[0].team.name, "서울");
const summary = await getKLeagueSummary("2026-03-22", {
leagueId: 1,
team: "FC서울",
includeStandings: true
});
assert.equal(summary.matches.length, 1);
assert.equal(summary.standings.rows[0].rank, 1);
assert.equal(summary.standings.rows[0].team.fullName, "FC서울");
assert.equal(
calls.filter((call) => call.target.endsWith("/getScheduleList.do")).length,
2
);
assert.ok(
calls.some((call) => call.body && String(call.body).includes('"month":"03"')),
"expected schedule fetch to send the official month payload"
);
assert.ok(
calls.every((call) => call.headers["accept-language"]?.includes("ko-KR")),
"expected live requests to pin Korean-language payloads",
);
} finally {
global.fetch = originalFetch;
}
});
test("getMatchResults rejects impossible calendar dates before fetching", async () => {
let fetchCalled = false;
await assert.rejects(
() =>
getMatchResults("2026-13-40", {
leagueId: "K리그1",
fetchImpl: async () => {
fetchCalled = true;
return makeResponse(schedulePayload);
},
}),
/date must be a valid Date or YYYY-MM-DD string\./,
);
assert.equal(fetchCalled, false);
});
function makeResponse(body) {
return new Response(JSON.stringify(body), {
status: 200,
headers: {
"content-type": "application/json"
}
});
}