Resolve stage: matching, version resolution, fixtures; BGG API auth

bggpipe resolve works end to end: search -> exact/fuzzy candidate
scoring -> auto/ambiguous/unmatched classification with owned-count
tie-breaks (mixed base/expansion candidates never auto-match), version
scoring from edition cues (never guessed; no cues -> version_unknown),
idempotent matches.csv appends.

Discovered mid-build: BGG now requires registered-application Bearer
tokens on the XML API (2025 policy change) and returns 401 otherwise.
Client sends Authorization from BGG_API_TOKEN and raises an actionable
BGGAuthError; CLAUDE.md and the bgg-api skill are updated to match.
Live fixture recording is blocked until registration is approved, so
tests replay hand-crafted stub fixtures via a network-refusing
transport; scripts/record_fixtures.py re-records real XML under the
same cache keys once a token exists. One live read-only smoke test is
skipped unless --run-live.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Eric Wagoner
2026-08-01 12:33:32 -04:00
parent 4e1211feb6
commit 2109e3544a
20 changed files with 880 additions and 6 deletions
+45
View File
@@ -0,0 +1,45 @@
"""Record the BGG XML fixtures the test suite replays.
Runs the real resolve logic with the cache pointed at tests/fixtures/
bgg_cache/, so exactly the responses resolve needs get recorded (live,
rate-limited). Run once; commit the fixtures. Re-running only fetches
whatever is not already recorded.
Usage: uv run python scripts/record_fixtures.py
"""
from __future__ import annotations
import os
from pathlib import Path
from bggpipe.bgg_client import BGGClient
from bggpipe.resolve import load_titles, resolve_entry
FIXTURE_CACHE = Path("tests/fixtures/bgg_cache")
def main() -> None:
if not os.environ.get("BGG_API_TOKEN"):
raise SystemExit(
"BGG_API_TOKEN is not set. The XML API requires a registered "
"application token (https://boardgamegeek.com/applications). "
"Until one exists, the committed fixtures are the hand-crafted "
"stubs from scripts/write_stub_fixtures.py; delete "
f"{FIXTURE_CACHE} and re-run this script to replace them with "
"real recordings."
)
client = BGGClient(cache_dir=FIXTURE_CACHE)
for entry in load_titles(Path("data/titles.json")):
row = resolve_entry(client, entry)
print(
f"{entry.title_raw!r}: {row.match_status} "
f"{row.bgg_name or '-'} ({row.bgg_id or '-'}) {row.version_status}"
)
print(f"\nFixtures recorded in {FIXTURE_CACHE}:")
for f in sorted(FIXTURE_CACHE.iterdir()):
print(f" {f.name}")
if __name__ == "__main__":
main()