fix(build): pick a source's own fixture, not a sibling page's on the same site
build-feed globbed fixtures by site (`fandom-*`), so a second Fandom page for a game, such as Genshin's Miliastra Pass beside its event page, would have handed its fixture to the events parser. pickFixture matches the source's whole name followed by a date. Every fixture already follows that name, and the built feed is unchanged (206 events, same ids). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6e7c231160
commit
40d0dbef5a
3 files changed
+50
-9
No files matched your search
@@ -12,7 +12,7 @@
|
||||
import { ADAPTERS } from "../src/ingest/adapters/index.ts";
|
||||
import { sourceHealth } from "../src/ingest/health.ts";
|
||||
import { mergeEvents } from "../src/ingest/merge.ts";
|
||||
import { SnapshotStore, freshnessAt } from "../src/ingest/snapshots.ts";
|
||||
import { SnapshotStore, freshnessAt, pickFixture } from "../src/ingest/snapshots.ts";
|
||||
import { EventFeed, SCHEMA_VERSION, type SourceHealth } from "../src/shared/feed.ts";
|
||||
import type { GachaEvent, GameId } from "../src/shared/schema.ts";
|
||||
|
||||
@@ -22,17 +22,16 @@ const snapshots = new SnapshotStore(process.env["SNAPSHOT_DIR"] ?? "snapshots");
|
||||
/**
|
||||
* Newest fixture for one *source*, not one game.
|
||||
*
|
||||
* A game can have several sources, and fixtures are named `<site>-events-<date>`
|
||||
* against adapter ids of `<game>-<site>-events`. Globbing by game alone hands
|
||||
* one site's page to another site's parser.
|
||||
* A game can have several sources, and fixtures are named `<id less the
|
||||
* game>-<date>` (`fandom-events-2026-09-12` for `genshin-fandom-events`).
|
||||
* Globbing by game alone, or by site, hands one page to another page's parser —
|
||||
* see `pickFixture`.
|
||||
*/
|
||||
async function latestFixture(adapterId: string, game: GameId) {
|
||||
const site = adapterId.replace(`${game}-`, "").replace(/-events$/, "");
|
||||
const pattern = `fixtures/${game}/${site}-*.html`;
|
||||
const files = [...new Bun.Glob(pattern).scanSync(".")].sort();
|
||||
const file = files.at(-1);
|
||||
const files = [...new Bun.Glob(`fixtures/${game}/*.html`).scanSync(".")];
|
||||
const file = pickFixture(adapterId, game, files);
|
||||
if (file === undefined) {
|
||||
throw new Error(`no fixture found for ${adapterId} (${pattern})`);
|
||||
throw new Error(`no fixture found for ${adapterId} (fixtures/${game}/${adapterId.slice(game.length + 1)}-<date>.html)`);
|
||||
}
|
||||
return { file, html: await Bun.file(file).text() };
|
||||
}
|
||||
|
||||
@@ -404,3 +404,18 @@ export function freshnessAt(snapshot: Snapshot): string {
|
||||
? confirmed
|
||||
: snapshot.meta.contentChangedAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* The newest checked-in fixture for one *source*, from `files`, or undefined.
|
||||
*
|
||||
* Fixtures are named `<id without the game>-<YYYY-MM-DD>.html`: `fandom-events-2026-09-12`
|
||||
* for `genshin-fandom-events`. The name must be followed directly by the date —
|
||||
* a glob of `fandom-*` would hand `genshin-fandom-miliastra-pass`'s page to the
|
||||
* events parser, a second page on the same site being exactly the case that
|
||||
* shares a prefix. Sorted by name, so the newest date is last.
|
||||
*/
|
||||
export function pickFixture(adapterId: string, game: string, files: readonly string[]): string | undefined {
|
||||
const name = adapterId.slice(game.length + 1);
|
||||
const own = new RegExp(`(^|/)${name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}-\\d{4}-\\d{2}-\\d{2}\\.html$`);
|
||||
return files.filter((f) => own.test(f)).sort().at(-1);
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { pickFixture } from "../src/ingest/snapshots.ts";
|
||||
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
||||
import { statSync } from "node:fs";
|
||||
import { mkdtemp, readdir, rm, writeFile } from "node:fs/promises";
|
||||
@@ -454,3 +455,29 @@ describe("freshnessAt", () => {
|
||||
).toBe(T0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("pickFixture: a source's own fixture, never a sibling's", () => {
|
||||
const files = [
|
||||
"fixtures/genshin/fandom-events-2026-09-12.html",
|
||||
"fixtures/genshin/fandom-miliastra-pass-2026-10-16.html",
|
||||
"fixtures/genshin/game8-events-2026-08-14.html",
|
||||
];
|
||||
|
||||
test("an -events source keeps its old names", () => {
|
||||
expect(pickFixture("genshin-fandom-events", "genshin", files)).toBe(
|
||||
"fixtures/genshin/fandom-events-2026-09-12.html",
|
||||
);
|
||||
});
|
||||
|
||||
test("a second page on the same site does not take the events page's fixture, or give it its own", () => {
|
||||
expect(pickFixture("genshin-fandom-miliastra-pass", "genshin", files)).toBe(
|
||||
"fixtures/genshin/fandom-miliastra-pass-2026-10-16.html",
|
||||
);
|
||||
});
|
||||
|
||||
test("the newest date wins, and nothing matches nothing", () => {
|
||||
expect(pickFixture("genshin-game8-events", "genshin", [...files, "fixtures/genshin/game8-events-2026-09-01.html"]))
|
||||
.toBe("fixtures/genshin/game8-events-2026-09-01.html");
|
||||
expect(pickFixture("genshin-fandom-other", "genshin", files)).toBeUndefined();
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user