Files
clientflow_backend/tests/test_blif_flow_v2_authoritative_simulation.py

139 lines
5.3 KiB
Python

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