Re-audit round 2: 5 blind reviewers, 17 fixes, +12 tests
The re-run confirmed round 1 held and then caught second-order bugs in its own fixes plus two long-standing ones everyone missed. TUI decisions after a mid-session reload were counted but never written (rows are now re-adopted into the fresh list on every save, preferring undecided slots on duplicate keys); row_ix was computed by equality so duplicate rows shared an ordinal (identity now, merges included, veto sends it); upload job keys collided for two same-version copies (completions are counted per key, so --limit or an interrupt can no longer strand the second copy); diff consumes collids on exact-version matches (a vetoed same-version second copy was silently swallowed) and splits mismatches: report-only disagreement while an unclaimed copy exists, second-copy add only when every copy is claimed. Also: XML responses are validated and written atomically before caching (a torn or truncated 200 body can never poison a re-run), JSON artifacts write atomically, thing/search parsers refuse missing ids like the collection parser, empty game names are refused by the upload queue, a never-rendering version picker fails retryably instead of terminally, the systemic-failure abort compares exception types, blocked same-title entries defer as a group so positional pairing can't misalign, truncation heads pick the earliest separator, diff messages tell the truth when a token exists without a username, and the shared-constant sweep now actually covers every module (statuses, search types, marker names, client_for, ports). pydantic declared as a direct dependency. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -382,3 +382,40 @@ def test_manual_id_unknown_to_bgg_warns_instead_of_crashing(tmp_path):
|
||||
assert session.rows[0]["match_status"] == "approved"
|
||||
assert session.rows[0]["bgg_id"] == "999999"
|
||||
assert any("no game with id 999999" in w for w in session.warnings)
|
||||
|
||||
|
||||
def test_every_tui_decision_after_external_rewrite_is_saved(tmp_path):
|
||||
# THE round-2 catch: the TUI iterates row references snapshotted before
|
||||
# any reload; after decision 1 triggers a reload, decisions 2..N used
|
||||
# to be counted but never written.
|
||||
from bggpipe.resolve import read_matches, write_matches
|
||||
|
||||
cfg = _setup(
|
||||
tmp_path,
|
||||
[
|
||||
_row(title_raw="Alpha", match_status="unmatched"),
|
||||
_row(title_raw="Beta", match_status="unmatched"),
|
||||
_row(title_raw="Gamma", match_status="unmatched"),
|
||||
],
|
||||
)
|
||||
session = ReviewSession(
|
||||
cfg,
|
||||
console=quiet_console(),
|
||||
input_fn=scripted(),
|
||||
client=unauthorized_client(tmp_path),
|
||||
)
|
||||
stale_refs = list(session.pending_rows()) # what run() iterates
|
||||
|
||||
external = read_matches(cfg.matches_path)
|
||||
external.append(_row(title_raw="Newcomer", match_status="auto", bgg_id="7"))
|
||||
write_matches(cfg.matches_path, external)
|
||||
|
||||
for ref in stale_refs: # decision 1 reloads; 2 and 3 are orphaned refs
|
||||
session.decide_reject(ref)
|
||||
|
||||
saved = {r["title_raw"]: r for r in read_matches(cfg.matches_path)}
|
||||
assert [saved[t]["match_status"] for t in ("Alpha", "Beta", "Gamma")] == (
|
||||
["rejected"] * 3
|
||||
)
|
||||
assert "Newcomer" in saved # the external row survived too
|
||||
assert session.decisions == 3
|
||||
|
||||
Reference in New Issue
Block a user