"""Review-TUI tests against a synthetic matches.csv, driven by scripted input. BGG responses replay from the fixture cache; nothing hits the network (the transport raises if it would).""" from __future__ import annotations import io import json from pathlib import Path import httpx from rich.console import Console from bggpipe.bgg_client import BGGClient from bggpipe.config import Config from bggpipe.resolve import read_matches, write_matches from bggpipe.review import run_review FIXTURES = Path(__file__).parent / "fixtures" / "bgg_cache" def _no_network(request: httpx.Request) -> httpx.Response: raise AssertionError(f"test hit the network: {request.url}") def fixture_client() -> BGGClient: return BGGClient(cache_dir=FIXTURES, transport=httpx.MockTransport(_no_network)) def unauthorized_client(tmp_path) -> BGGClient: """Client whose every request 401s — the no-token-yet world.""" transport = httpx.MockTransport( lambda req: httpx.Response(401, text="Unauthorized") ) return BGGClient(cache_dir=tmp_path / "empty_cache", transport=transport) def scripted(*answers): it = iter(answers) def input_fn(prompt: str) -> str: try: return next(it) except StopIteration: raise EOFError from None return input_fn def quiet_console() -> Console: return Console(file=io.StringIO(), width=100) def _row(**overrides) -> dict: row = { "title_raw": "", "bgg_id": "", "bgg_name": "", "year": "", "type": "", "match_status": "auto", "version_id": "", "version_name": "", "version_status": "version_unknown", "candidates_json": "[]", "version_candidates_json": "[]", "source_photos": "hand-typed-test-list", } row.update(overrides) return row CITADELS_CANDIDATES = json.dumps( [ { "bgg_id": 478, "name": "Citadels", "year": 2000, "type": "boardgame", "owned": 85000, "rank": 250, }, { "bgg_id": 205398, "name": "Citadels", "year": 2016, "type": "boardgame", "owned": 24000, "rank": 400, }, ] ) WINGSPAN_VERSION_CANDIDATES = json.dumps( [ { "version_id": 465063, "name": "English first edition", "year": 2019, "publishers": ["Stonemaier Games"], "languages": ["English"], "score": 5, }, { "version_id": 521212, "name": "English fourth printing", "year": 2020, "publishers": ["Stonemaier Games"], "languages": ["English"], "score": 5, }, ] ) def _cfg(tmp_path) -> Config: return Config(data_dir=tmp_path / "data") def _setup(tmp_path, rows) -> Config: cfg = _cfg(tmp_path) write_matches(cfg.matches_path, rows) return cfg def test_ambiguous_pick_approves_candidate(tmp_path): cfg = _setup( tmp_path, [ _row( title_raw="Citadels", match_status="ambiguous", candidates_json=CITADELS_CANDIDATES, ) ], ) run_review( cfg, console=quiet_console(), input_fn=scripted("2"), client=fixture_client() ) (row,) = read_matches(cfg.matches_path) assert row["match_status"] == "approved" assert row["bgg_id"] == "205398" assert row["year"] == "2016" def test_reject_and_skip(tmp_path): cfg = _setup( tmp_path, [ _row(title_raw="Blorvath", match_status="unmatched"), _row( title_raw="Citadels", match_status="ambiguous", candidates_json=CITADELS_CANDIDATES, ), ], ) run_review( cfg, console=quiet_console(), input_fn=scripted("r", "s"), client=fixture_client(), ) rows = {r["title_raw"]: r for r in read_matches(cfg.matches_path)} assert rows["Blorvath"]["match_status"] == "rejected" assert rows["Citadels"]["match_status"] == "ambiguous" # skipped, still pending def test_unmatched_manual_id_degrades_without_token(tmp_path): cfg = _setup(tmp_path, [_row(title_raw="Some Rare Game", match_status="unmatched")]) run_review( cfg, console=quiet_console(), input_fn=scripted("m 99999"), client=unauthorized_client(tmp_path), ) (row,) = read_matches(cfg.matches_path) assert row["match_status"] == "approved" assert row["bgg_id"] == "99999" assert row["bgg_name"] == "" # lookup blocked; id recorded anyway def test_unmatched_research_from_fixture_cache_with_version(tmp_path): cfg = _setup(tmp_path, [_row(title_raw="Wingspan", match_status="unmatched")]) cfg.titles_path.parent.mkdir(parents=True, exist_ok=True) cfg.titles_path.write_text( json.dumps( [ { "title_raw": "Wingspan", "publisher_hint": "Stonemaier Games", "year_hint": 2019, "language_hint": "English", "source_photos": ["x.jpg"], } ] ) ) run_review( cfg, console=quiet_console(), input_fn=scripted("f Wingspan", "1"), client=fixture_client(), ) (row,) = read_matches(cfg.matches_path) assert row["match_status"] == "approved" assert row["bgg_id"] == "266192" # cues + fixture versions -> resolved to the 2019 Stonemaier English edition assert row["version_status"] == "version_auto" assert row["version_id"] == "465063" def test_resumable_quit_midway_then_continue(tmp_path): rows = [ _row( title_raw="Citadels", match_status="ambiguous", candidates_json=CITADELS_CANDIDATES, ), _row(title_raw="Blorvath", match_status="unmatched"), ] cfg = _setup(tmp_path, rows) # first sitting: one decision, then input runs out (EOF == quit) run_review( cfg, console=quiet_console(), input_fn=scripted("1"), client=fixture_client() ) by_title = {r["title_raw"]: r for r in read_matches(cfg.matches_path)} assert by_title["Citadels"]["match_status"] == "approved" assert by_title["Blorvath"]["match_status"] == "unmatched" # untouched # second sitting resumes exactly where we left off run_review( cfg, console=quiet_console(), input_fn=scripted("r"), client=fixture_client() ) by_title = {r["title_raw"]: r for r in read_matches(cfg.matches_path)} assert by_title["Blorvath"]["match_status"] == "rejected" def test_version_pass_pick_and_unknown(tmp_path): cfg = _setup( tmp_path, [ _row( title_raw="Wingspan", match_status="auto", bgg_id="266192", version_status="version_ambiguous", version_candidates_json=WINGSPAN_VERSION_CANDIDATES, ), _row( title_raw="Catan", match_status="auto", bgg_id="13", version_status="version_ambiguous", version_candidates_json=WINGSPAN_VERSION_CANDIDATES, ), ], ) run_review( cfg, console=quiet_console(), input_fn=scripted("y", "1", "u"), client=fixture_client(), ) by_title = {r["title_raw"]: r for r in read_matches(cfg.matches_path)} assert by_title["Wingspan"]["version_status"] == "version_approved" assert by_title["Wingspan"]["version_id"] == "465063" assert by_title["Catan"]["version_status"] == "version_unknown" assert by_title["Catan"]["version_id"] == "" def test_version_pass_is_skippable(tmp_path): cfg = _setup( tmp_path, [ _row( title_raw="Wingspan", match_status="auto", bgg_id="266192", version_status="version_ambiguous", version_candidates_json=WINGSPAN_VERSION_CANDIDATES, ) ], ) run_review( cfg, console=quiet_console(), input_fn=scripted("n"), client=fixture_client() ) (row,) = read_matches(cfg.matches_path) assert row["version_status"] == "version_ambiguous" # untouched, review later