Re-audit round 3: 5 blind reviewers, 15 fixes, +18 tests — converging
Round 3's two HIGHs: _fill_version resolved versions with the LAST same-title entry's cues (photo-aware lookup existed since round 1 but this caller never used it), and the round-2 diff rework let an earlier row's disagreement consume the exact-version copy a later row matched. Diff claims now settle strongest-first across all rows (exact matches, then versionless upgrades, then disagreement/second-copy), unvetoed bare duplicates stay owned per spec, and updates are withheld with a manual-fix note whenever any copy of the game already carries a version (the row edit targets by name and could hit the wrong copy). Also: entry-to-row pairing matches by photo overlap before position (titles.json order churn from reshoot filenames could swap editions); BGGQueueTimeout defers a title like a missing token; DismissStore writes atomically, mutates memory only after the write, and quarantines a torn file instead of bricking the server; version-picker page-limit exhaustion stays retryable; verify's copy-count shortfall reports once per game (the old guard was dead code); the upload log header is created atomically; transient version-lookup failures record a retryable version_error, not terminal version_unknown; extract isolates per-photo failures and salvages JSON followed by prose; a state revision counter stops stale poll responses reverting decisions; plus the shared-predicate/fsio/docstring consolidation and CLI wiring, live-diff, verify-wiring, and search-guard tests. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+44
-1
@@ -22,7 +22,9 @@ from bggpipe.upload import (
|
||||
verify_uploads,
|
||||
)
|
||||
|
||||
NOW = lambda: "2026-08-01T00:00:00+00:00" # noqa: E731
|
||||
|
||||
def NOW() -> str:
|
||||
return "2026-08-01T00:00:00+00:00"
|
||||
|
||||
|
||||
def _cfg(tmp_path: Path) -> Config:
|
||||
@@ -425,3 +427,44 @@ def test_empty_game_name_is_refused_not_uploaded(tmp_path):
|
||||
fake = FakeUploader()
|
||||
results = run_upload(cfg, uploader=fake, sleep=lambda s: None, now=NOW)
|
||||
assert results == [] and fake.calls == []
|
||||
|
||||
|
||||
def test_verify_shortfall_reported_once_per_game(tmp_path):
|
||||
# two DONE adds (different versions) of one game, one copy on BGG:
|
||||
# exactly ONE shortfall problem (the old _job_key guard was dead code
|
||||
# and double-reported)
|
||||
log = [
|
||||
_log_row(action="add", bgg_id="7", version_id="1", status="added"),
|
||||
_log_row(action="add", bgg_id="7", version_id="2", status="added"),
|
||||
]
|
||||
problems = verify_uploads(log, [_item(7, 70, version_id=1)])
|
||||
shortfalls = [p for p in problems if "add(s) logged" in p]
|
||||
assert len(shortfalls) == 1
|
||||
|
||||
|
||||
class _VerifyClient:
|
||||
def __init__(self, collection):
|
||||
self.collection = collection
|
||||
self.calls: list[dict] = []
|
||||
|
||||
def collection_full(self, username, *, refresh=False):
|
||||
self.calls.append({"username": username, "refresh": refresh})
|
||||
return self.collection
|
||||
|
||||
|
||||
def test_run_upload_verify_wiring(tmp_path, capsys):
|
||||
# verify=True must re-fetch the LIVE collection (refresh) and cross-check
|
||||
cfg = _cfg(tmp_path)
|
||||
_seed_data(tmp_path, to_add=[_add_row(bgg_id="1", name="Wingspan")])
|
||||
run_upload(cfg, uploader=FakeUploader(), sleep=lambda s: None, now=NOW)
|
||||
client = _VerifyClient([_item(1, 10, name="Wingspan")])
|
||||
run_upload(
|
||||
cfg,
|
||||
uploader=FakeUploader(),
|
||||
verify=True,
|
||||
client=client,
|
||||
sleep=lambda s: None,
|
||||
now=NOW,
|
||||
)
|
||||
assert client.calls == [{"username": "tester", "refresh": True}]
|
||||
assert "Verification OK" in capsys.readouterr().out
|
||||
|
||||
Reference in New Issue
Block a user