from argparse import Namespace import importlib.util from pathlib import Path import pytest PATH = Path("scripts/simulate_blif_flow_v2_authoritative.py") SPEC = importlib.util.spec_from_file_location("authoritative_simulation", PATH) sim = importlib.util.module_from_spec(SPEC) SPEC.loader.exec_module(sim) class Result: def __init__(self, row): self.row = row def mappings(self): return self def one(self): return self.row class Connection: def __init__(self, database="clientflow", readonly="on"): self.database, self.readonly, self.sql = database, readonly, [] def exec_driver_sql(self, sql): self.sql.append(sql.strip()) if "current_database" in sql: return Result({"database": self.database, "user": "audit", "transaction_read_only": self.readonly}) if "count(*) FROM opportunities" in sql: return Result({"opportunities": 328, "projections": 328}) return Result({}) def close(self): pass class Engine: def __init__(self, connection): self.connection = connection def connect(self): return self.connection def named_decisions(fail=False): result = {} for name, (oid, action, queue) in sim.NAMED.items(): result[oid] = {"effective_action": action, "operational_queue": "not_current" if queue == "suppressed" else queue, "suppress_current_card": queue == "suppressed"} if fail: result[sim.NAMED["Instalbeira"][0]]["effective_action"] = "SEND_INVOICE" return result def runtime(connection, *, fail_named=False, modes=None): empty = {"work_items": [], "waiting_items": [], "backlog_items": [], "not_current_items": []} def operations(**kwargs): assert kwargs["connection"] is connection if modes is not None: modes.append(kwargs["flow_mode"]) return empty def decisions(ids, **kwargs): assert kwargs == {"flow_mode": "authoritative", "connection": connection} return named_decisions(fail_named) return lambda: (Engine(connection), operations, decisions) def test_help_performs_zero_db_work(): called = False def loader(): nonlocal called called = True raise AssertionError("DB/application runtime must not load") with pytest.raises(SystemExit) as exc: sim.main(["--help"], runtime_loader=loader) assert exc.value.code == 0 and called is False def test_production_requires_explicit_flag(monkeypatch): monkeypatch.setenv("BLIF_FLOW_V2_MODE", "compare") with pytest.raises(RuntimeError, match="requires --production"): sim.run(Namespace(production_readonly_simulation=False), runtime_loader=runtime(Connection())) @pytest.mark.parametrize("database,readonly,mode,message", [ ("test_db", "on", "compare", "current_database"), ("clientflow", "off", "compare", "transaction_read_only"), ("clientflow", "on", None, "explicit BLIF_FLOW"), ("clientflow", "on", "off", "BLIF_FLOW_V2_MODE=compare"), ("clientflow", "on", "shadow", "BLIF_FLOW_V2_MODE=compare"), ("clientflow", "on", "authoritative", "BLIF_FLOW_V2_MODE=compare"), ]) def test_production_guards(monkeypatch, database, readonly, mode, message): if mode is None: monkeypatch.delenv("BLIF_FLOW_V2_MODE", raising=False) else: monkeypatch.setenv("BLIF_FLOW_V2_MODE", mode) with pytest.raises(RuntimeError, match=message): sim.run(Namespace(production_readonly_simulation=True), runtime_loader=runtime(Connection(database, readonly))) def test_safe_simulation_uses_explicit_modes_without_changing_config_and_has_no_db_writes(monkeypatch, tmp_path): monkeypatch.setenv("BLIF_FLOW_V2_MODE", "compare") monkeypatch.setattr(sim, "JSON_PATH", tmp_path / "report.json") monkeypatch.setattr(sim, "TEXT_PATH", tmp_path / "report.txt") connection = Connection() modes = [] report = sim.run(Namespace(production_readonly_simulation=True), runtime_loader=runtime(connection, modes=modes)) assert report["configured_mode"] == "compare" assert sim.exit_code(report) == 0 assert [sql for sql in connection.sql if sql.split(None, 1)[0].upper() in {"INSERT", "UPDATE", "DELETE", "MERGE"}] == [] assert connection.sql[0] == "BEGIN READ ONLY" and connection.sql[-1] == "ROLLBACK" assert modes == ["off", "authoritative"] def safe_report(): return sim.build_report(identity={}, configured_mode="compare", v1={"work_items": []}, v2={"work_items": []}, named_decisions=named_decisions(), missing_projections=0) def test_unsafe_false_negative_is_nonzero(): report = safe_report() report["unsafe_false_negatives"] = 1 assert sim.exit_code(report) != 0 def test_missing_projection_is_nonzero(): report = safe_report() report["missing_projections"] = 1 assert sim.exit_code(report) != 0 def test_duplicate_current_card_is_nonzero(): report = safe_report() report["duplicate_current_cards"] = 1 assert sim.exit_code(report) != 0 def test_named_case_failure_is_nonzero(): report = sim.build_report(identity={}, configured_mode="compare", v1={"work_items": []}, v2={"work_items": []}, named_decisions=named_decisions(True), missing_projections=0) assert sim.exit_code(report) != 0