RPGs pull real RPGGeek data; off-BGG games get facts and a cover photo
Two gaps at the edges of the library, both closed. RPGGeek items live in the same database but use their own link types — rpgdesigner, rpgpublisher, rpggenre, rpgcategory, rpgmechanic — so a board-game-only parser found none of them and both RPG entries showed just a year and a description. parse_things_full now reads both vocabularies (plus rpgproducer/rpgseries): .dungeon gains John Battle and Project Nerves, Parsely gains Jared A. Sorensen and its genres. An off-BGG game has no API to enrich it and no publisher art to fetch, so its detail page now hosts the only source it will ever have: a form for title, year, players, playing time, publishers, designers and notes, plus a cover photo upload. Both persist in data/local_games.json and data/local_art/ (committed, like every other curation store) and enrich merges them over the photo reads, so a rebuild can't erase them. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016jXZFSTZQKzAC8fqpWSz9g
This commit is contained in:
co-authored by
Claude Fable 5
parent
f5e949bd62
commit
7e95ed607d
@@ -1288,3 +1288,59 @@ def test_library_detail_serves_one_game_with_provenance(tmp_path):
|
||||
assert web.get("/api/library/nope").status_code == 404
|
||||
page = web.get("/library/game/240:24621")
|
||||
assert page.status_code == 200 and 'href="/library"' in page.text
|
||||
|
||||
|
||||
def test_local_game_notes_and_art_round_trip(tmp_path):
|
||||
"""BGG has nothing for an off-BGG game, so the owner's own words and
|
||||
photo are its only metadata — and must survive enrich rebuilding
|
||||
games.json from titles.json."""
|
||||
from bggpipe.enrich import run_enrich
|
||||
|
||||
cfg = make_cfg(tmp_path)
|
||||
rows = read_matches(cfg.matches_path)
|
||||
rows.append(
|
||||
_row(title_raw="Homebrew Game", match_status="local", source_photos="shelf.jpg")
|
||||
)
|
||||
write_matches(cfg.matches_path, rows)
|
||||
web = TestClient(create_app(cfg, client=unauthorized_client(tmp_path)))
|
||||
key = "local:homebrew game:shelf.jpg"
|
||||
|
||||
saved = web.post(
|
||||
f"/api/local-game/{key}",
|
||||
json={
|
||||
"name": "Homebrew Game",
|
||||
"year": "1998",
|
||||
"min_players": "2",
|
||||
"max_players": "6",
|
||||
"publishers": "Basement Press, Friend's Garage",
|
||||
"description": " Made by a friend. ",
|
||||
},
|
||||
).json()["saved"]
|
||||
assert saved["year"] == 1998
|
||||
assert saved["publishers"] == ["Basement Press", "Friend's Garage"]
|
||||
assert saved["description"] == "Made by a friend."
|
||||
|
||||
art = web.post(
|
||||
f"/api/local-art/{key}",
|
||||
files={"file": ("box.jpg", b"\xff\xd8jpeg", "image/jpeg")},
|
||||
).json()
|
||||
assert art["image"].startswith("/local-art/")
|
||||
assert web.get(art["image"]).status_code == 200
|
||||
|
||||
# enrich folds both into the library entry
|
||||
games = run_enrich(cfg, client=unauthorized_client(tmp_path))
|
||||
entry = games[key]
|
||||
assert entry["year"] == 1998 and entry["max_players"] == 6
|
||||
assert entry["image"] == art["image"]
|
||||
assert entry["publishers"] == ["Basement Press", "Friend's Garage"]
|
||||
|
||||
# guards: BGG-matched games and non-photos are refused
|
||||
assert web.post("/api/local-game/13", json={"name": "Catan"}).status_code == 400
|
||||
assert (
|
||||
web.post(
|
||||
f"/api/local-art/{key}",
|
||||
files={"file": ("notes.txt", b"hi", "text/plain")},
|
||||
).status_code
|
||||
== 400
|
||||
)
|
||||
assert web.post(f"/api/local-game/{key}", json={"year": "19x8"}).status_code == 400
|
||||
|
||||
Reference in New Issue
Block a user