Files
clientflow_backend/tests/test_blif_flow_v2_data_repair_apply.py

126 lines
4.9 KiB
Python

from datetime import datetime, timezone
from inspect import getsource
from collections import Counter
import pytest
from scripts.apply_blif_flow_v2_data_repair import (
EXPECTED_REPAIR_COUNT, FROZEN_REPAIRS, RESOLUTION_CODES,
apply_transaction, assert_test_database, phase1_high_rows,
validate_frozen_repair_set, validate_target_states,
)
def planned_rows():
return [
{"task_id": task_id, "action_code": action, "classification": classification,
"opportunity_id": opportunity_id, "safety_tier": "HIGH", "auto_repair_safe": True}
for task_id, (action, classification, opportunity_id) in FROZEN_REPAIRS.items()
]
def target_rows(*, applied=False):
now = datetime.now(timezone.utc)
return [
{"id": task_id, "action_code": action, "opportunity_id": opportunity_id,
"status": "done" if applied else "pending",
"resolution_code": RESOLUTION_CODES[classification] if applied else None,
"resolved_at": now if applied else None, "resolved_by_event_id": None,
"superseded_by_task_id": None}
for task_id, (action, classification, opportunity_id) in FROZEN_REPAIRS.items()
]
def test_default_cli_is_dry_run():
source = getsource(__import__("scripts.apply_blif_flow_v2_data_repair", fromlist=["main"]).main)
assert 'add_argument("--apply", action="store_true"' in source
assert "run(apply=args.apply)" in source
@pytest.mark.parametrize("identity", [
("clientflow", "clientflow_codex_test", "off"),
("other_test", "clientflow_codex_test", "off"),
("clientflow_codex_test", "wrong_user", "off"),
])
def test_wrong_database_or_user_hard_fails(identity):
with pytest.raises(RuntimeError, match="refusing repair"):
assert_test_database(identity)
def test_exact_repair_set_is_required():
rows = planned_rows()
validate_frozen_repair_set(rows)
assert len(rows) == EXPECTED_REPAIR_COUNT
with pytest.raises(RuntimeError, match="expected 12"):
validate_frozen_repair_set(rows[:-1])
def test_changed_frozen_identity_aborts():
rows = planned_rows()
rows[0] = {**rows[0], "classification": "SUPERSEDED"}
with pytest.raises(RuntimeError, match="cohort drift"):
validate_frozen_repair_set(rows)
def test_ambiguous_and_valid_current_are_never_selected():
result = {"tasks": {"tasks": planned_rows() + [
{"task_id": "ambiguous", "classification": "AMBIGUOUS", "safety_tier": "LOW", "auto_repair_safe": False},
{"task_id": "valid", "classification": "VALID_CURRENT", "safety_tier": "HIGH", "auto_repair_safe": False},
]}}
selected = phase1_high_rows(result)
assert {row["classification"] for row in selected}.isdisjoint({"AMBIGUOUS", "VALID_CURRENT"})
def test_resolution_codes_and_deterministic_event_fields_are_written():
source = getsource(apply_transaction)
assert "resolved_by_event_id=CAST(:resolved_by_event_id AS UUID)" in source
assert RESOLUTION_CODES == {
"SATISFIED_BY_EVENT": "satisfied_by_event", "SUPERSEDED": "superseded",
"DUPLICATE": "duplicate_obligation", "PREMATURE": "premature_downstream",
}
def test_duplicate_repair_never_deletes_evidence_or_tasks():
source = getsource(apply_transaction).upper()
assert "DELETE" not in source
assert "UPDATE TASKS" in source
assert "COMMERCIAL_DOCUMENTS" not in source
assert "OPERATION_LINKS" not in source
def test_apply_is_one_atomic_transaction_with_rollback():
source = getsource(apply_transaction)
assert "transaction = conn.begin()" in source
assert "transaction.commit()" in source
assert "transaction.rollback()" in source
def test_second_apply_is_idempotent():
assert validate_target_states(target_rows(applied=False)) == "pending"
assert validate_target_states(target_rows(applied=True)) == "already_applied"
def test_partial_apply_state_aborts():
rows = target_rows(applied=True)
rows[0].update(status="pending", resolution_code=None, resolved_at=None)
with pytest.raises(RuntimeError, match="partial repair state"):
validate_target_states(rows)
def test_premature_resolution_does_not_mutate_projection_or_opportunity():
source = getsource(apply_transaction).upper()
assert "UPDATE OPPORTUNITIES" not in source
assert "UPDATE OPPORTUNITY_FLOW_STATE_V2" not in source
def test_frozen_set_contains_named_duplicate_and_instalbeira_repairs():
assert FROZEN_REPAIRS["a15b2545-591f-4d73-b8a2-3268efd01f98"][1] == "DUPLICATE"
assert FROZEN_REPAIRS["b8965dd9-f0fc-42cf-a824-f8804add9e18"][1] == "DUPLICATE"
instal = [row for row in planned_rows() if row["opportunity_id"] == "5c33db95-fab8-477a-bddd-0b9cc8f91302"]
assert Counter(row["classification"] for row in instal) == Counter({"PREMATURE": 2, "SATISFIED_BY_EVENT": 1})
def test_zero_unsafe_false_negative_categories_in_frozen_set():
assert {classification for _, classification, _ in FROZEN_REPAIRS.values()} == {
"SATISFIED_BY_EVENT", "SUPERSEDED", "DUPLICATE", "PREMATURE"}