"""Tests for the mutating commands: refute and correct, including the cascade invalidation behaviour and the signed-log side effects.""" from __future__ import annotations from helpers import ( ACTIVE_STATUSES, CORRECTED_STATUSES, INVALIDATED_STATUSES, REFUTED_STATUSES, find_dependency_pair, get_claim, get_claims, op_count, run_cli, run_ok, ) # --------------------------------------------------------------------------- # # refute # --------------------------------------------------------------------------- # def test_refute_marks_claim_refuted(home): cid = str(get_claims(home)[0]["id"]) run_ok(home, "refute", cid, "--reason", "the user says this is wrong") assert get_claim(home, cid).get("status") in REFUTED_STATUSES def test_refute_cascades_to_downstream_claims(home): claims = get_claims(home, "--all") pair = find_dependency_pair(claims) assert pair is not None, ( "the seed dataset must contain at least one claim derived from " "another claim, so the cascade can be demonstrated" ) upstream, downstream = pair run_ok(home, "refute", str(upstream["id"]), "--reason", "cascade test") up_status = get_claim(home, str(upstream["id"])).get("status") down_status = get_claim(home, str(downstream["id"])).get("status") assert up_status in REFUTED_STATUSES assert down_status in INVALIDATED_STATUSES | REFUTED_STATUSES, ( f"downstream claim should be invalidated by the cascade, " f"got status {down_status!r}" ) def test_refuted_claim_hidden_from_default_listing(home): cid = str(get_claims(home)[0]["id"]) run_ok(home, "refute", cid, "--reason", "hide me") assert cid not in {str(c["id"]) for c in get_claims(home)}, ( "default listing shows only active claims" ) assert cid in {str(c["id"]) for c in get_claims(home, "--all")}, ( "--all listing keeps the full history visible" ) def test_refute_reports_affected_claims(home): claims = get_claims(home, "--all") pair = find_dependency_pair(claims) assert pair is not None upstream, downstream = pair out = run_ok(home, "refute", str(upstream["id"]), "--reason", "report test") assert out.strip(), "refute should report its effects" # The transcript should reference the downstream effect in some form: # either by id prefix or by an explicit invalidation count/word. lowered = out.lower() assert ( str(downstream["id"])[:8] in out or "invalidat" in lowered or "downstream" in lowered or "cascade" in lowered ), f"refute output should describe the cascade, got:\n{out}" def test_refute_unknown_claim_fails(home): code, _out, _err = run_cli(home, "refute", "no-such-claim", "--reason", "x") assert code != 0 def test_refute_is_idempotent_or_rejected_second_time(home): """Refuting twice must not corrupt state: either it is accepted again (idempotent) or rejected — but the claim stays refuted either way.""" cid = str(get_claims(home)[0]["id"]) run_ok(home, "refute", cid, "--reason", "first") run_cli(home, "refute", cid, "--reason", "second") # exit code unconstrained assert get_claim(home, cid).get("status") in REFUTED_STATUSES # --------------------------------------------------------------------------- # # correct # --------------------------------------------------------------------------- # def test_correct_supersedes_original(home): cid = str(get_claims(home)[0]["id"]) run_ok(home, "correct", cid, "--value", "TestValue-001", "--reason", "fix") status = get_claim(home, cid).get("status") assert status in CORRECTED_STATUSES | REFUTED_STATUSES, ( f"original claim should be superseded after correction, got {status!r}" ) assert status not in ACTIVE_STATUSES def test_corrected_value_surfaces_in_graph(home): cid = str(get_claims(home)[0]["id"]) marker = "Corrected-By-Test-9381" run_ok(home, "correct", cid, "--value", marker, "--reason", "user correction") raw = run_ok(home, "claims", "--json", "--all") assert marker in raw, ( "the corrected value should surface in a successor claim in the graph" ) def test_correct_unknown_claim_fails(home): code, _out, _err = run_cli( home, "correct", "no-such-claim", "--value", "v", "--reason", "x" ) assert code != 0 # --------------------------------------------------------------------------- # # log side effects # --------------------------------------------------------------------------- # def test_refute_appends_to_signed_log(home): before = op_count(home) cid = str(get_claims(home)[0]["id"]) run_ok(home, "refute", cid, "--reason", "log growth check") after = op_count(home) assert after > before, "a refutation must be appended as a signed operation" def test_correct_appends_to_signed_log(home): before = op_count(home) cid = str(get_claims(home)[0]["id"]) run_ok(home, "correct", cid, "--value", "LogCheck", "--reason", "log growth") after = op_count(home) assert after > before, "a correction must be appended as a signed operation" def test_log_still_audits_clean_after_mutations(home): cid = str(get_claims(home)[0]["id"]) run_ok(home, "refute", cid, "--reason", "audit-after-mutation") code, out, err = run_cli(home, "audit") assert code == 0, f"audit must pass after legitimate mutations: {err or out}"