Files
miti99bot/tests/modules/lolschedule/api-client.test.js
T
tiennm99 436664c8a1 fix(lolschedule): structured error logging for cargoquery failures
Swaps the best-effort console.warn for JSON log lines emitted via
console.log so Workers Observability + wrangler tail surface the real
cause (HTTP status, API error info, or non-JSON body) when /lol_today
and /lol_week fall into the error branch.
2026-04-21 09:54:02 +07:00

120 lines
3.7 KiB
JavaScript

import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { createStore } from "../../../src/db/create-store.js";
import {
fetchMatchesInRange,
getCachedMatches,
} from "../../../src/modules/lolschedule/api-client.js";
import { makeFakeKv } from "../../fakes/fake-kv-namespace.js";
function cargoResponse(rows) {
const payload = { cargoquery: rows.map((title) => ({ title })) };
return {
ok: true,
status: 200,
text: async () => JSON.stringify(payload),
json: async () => payload,
};
}
const sampleRow = {
DateTime: "2026-04-21 09:00:00",
T1: "T1",
T2: "Gen.G",
S1: "",
S2: "",
Winner: "",
Tournament: "LCK 2026 Spring",
BO: "3",
OP: "LCK/2026_Season/Spring_Season",
};
describe("fetchMatchesInRange", () => {
afterEach(() => {
vi.restoreAllMocks();
});
it("posts cargoquery with correct where clause and parses rows", async () => {
const fetchSpy = vi.fn().mockResolvedValue(cargoResponse([sampleRow]));
global.fetch = fetchSpy;
const from = new Date("2026-04-21T00:00:00Z");
const to = new Date("2026-04-22T00:00:00Z");
const rows = await fetchMatchesInRange(from, to);
expect(rows).toEqual([sampleRow]);
expect(fetchSpy).toHaveBeenCalledOnce();
const [url, opts] = fetchSpy.mock.calls[0];
expect(url).toBe("https://lol.fandom.com/api.php");
expect(opts.method).toBe("POST");
expect(opts.headers["User-Agent"]).toMatch(/miti99bot/);
const body = opts.body.toString();
expect(body).toContain("action=cargoquery");
expect(body).toContain("MatchSchedule%3DMS"); // "MatchSchedule=MS" url-encoded
expect(body).toContain("2026-04-21+00%3A00%3A00");
expect(body).toContain("2026-04-22+00%3A00%3A00");
});
it("surfaces Leaguepedia API error field", async () => {
const errPayload = { error: { info: "Bad query", code: "x" } };
global.fetch = vi.fn().mockResolvedValue({
ok: true,
status: 200,
text: async () => JSON.stringify(errPayload),
json: async () => errPayload,
});
await expect(
fetchMatchesInRange(new Date("2026-04-21T00:00:00Z"), new Date("2026-04-22T00:00:00Z")),
).rejects.toThrow(/Bad query/);
});
});
describe("getCachedMatches", () => {
let db;
let kv;
beforeEach(() => {
kv = makeFakeKv();
db = createStore("lolschedule", { KV: kv });
});
afterEach(() => {
vi.restoreAllMocks();
});
it("caches a successful fetch and returns cached on the second call", async () => {
const fetchSpy = vi.fn().mockResolvedValue(cargoResponse([sampleRow]));
global.fetch = fetchSpy;
const from = new Date("2026-04-21T00:00:00Z");
const to = new Date("2026-04-22T00:00:00Z");
const r1 = await getCachedMatches(db, from, to, 60);
const r2 = await getCachedMatches(db, from, to, 60);
expect(r1).toEqual([sampleRow]);
expect(r2).toEqual([sampleRow]);
expect(fetchSpy).toHaveBeenCalledOnce();
});
it("falls back to stale cache when the fetch fails", async () => {
const from = new Date("2026-04-21T00:00:00Z");
const to = new Date("2026-04-22T00:00:00Z");
// Seed the cache with stale data directly.
await db.putJSON(`matches:${from.toISOString()}:${to.toISOString()}`, {
ts: Date.now() - 10 * 60 * 1000, // 10 min ago — older than the 60s TTL
rows: [sampleRow],
});
global.fetch = vi.fn().mockRejectedValue(new Error("boom"));
const out = await getCachedMatches(db, from, to, 60);
expect(out).toEqual([sampleRow]);
});
it("propagates the error when no cache is available", async () => {
global.fetch = vi.fn().mockRejectedValue(new Error("boom"));
await expect(
getCachedMatches(db, new Date("2026-04-21T00:00:00Z"), new Date("2026-04-22T00:00:00Z"), 60),
).rejects.toThrow(/boom/);
});
});