Files
clientflow_backend/tests/test_blif_flow_v2_persistence.py

87 lines
3.8 KiB
Python

from datetime import datetime, timezone
from pathlib import Path
import pytest
from app.action_catalog import ACTION_CODES, FLOW_V2_BUSINESS_ACTION_CODES, TRIAGE_ACTION_CODES
from app.blif_flow_v2_projection_service import _projection_value, rebuild_blif_flow_v2_projection
def derived_row(**overrides):
row = {
"opportunity_id": "11111111-1111-1111-1111-111111111111",
"material_process_key": "odoo_sale_name:s00001",
"canonical_process_id": "11111111-1111-1111-1111-111111111111",
"raw_v2": {
"business_state": "PROFORMA_REQUIRED",
"business_next_action": "CREATE_PROFORMA",
"diagnostic_status": "clear",
"confidence": "high",
"precedence": "business_transition",
"reason": "Current order intent requires a proforma.",
},
"evidence": {
"latest_relevant_inbound": {"id": "m1", "at": "2026-08-15T00:00:00+00:00"},
"latest_relevant_outbound": None, "proforma": [], "invoice": [],
"payment": [], "odoo": [], "reconciliation": [],
},
}
row.update(overrides)
return row
def test_migration_011_is_additive_reversible_and_does_not_touch_stage():
up = Path("migrations/011_blif_flow_v2_persistence.sql").read_text()
down = Path("migrations/011_blif_flow_v2_persistence_down.sql").read_text()
assert "CREATE TABLE IF NOT EXISTS opportunity_flow_state_v2" in up
assert "CREATE TABLE IF NOT EXISTS opportunity_flow_transitions" in up
for column in ("resolution_code", "resolved_at", "resolved_by_event_id", "superseded_by_task_id"):
assert f"ADD COLUMN IF NOT EXISTS {column}" in up
assert f"DROP COLUMN IF EXISTS {column}" in down
assert "DROP TABLE IF EXISTS opportunity_flow_state_v2" in down
assert "UPDATE opportunities" not in up
assert "stage =" not in up
def test_flow_v2_actions_are_separate_from_existing_triage_and_runtime_actions():
assert FLOW_V2_BUSINESS_ACTION_CODES == {
"CREATE_PROFORMA", "CREATE_INVOICE", "VALIDATE_ODOO_ORDER", "COMPLETE_OPPORTUNITY",
}
assert "CREATE_JASMIN_QUOTE" not in FLOW_V2_BUSINESS_ACTION_CODES
assert FLOW_V2_BUSINESS_ACTION_CODES.isdisjoint(TRIAGE_ACTION_CODES)
assert FLOW_V2_BUSINESS_ACTION_CODES.isdisjoint(ACTION_CODES)
def test_projection_fingerprint_is_stable_and_excludes_derived_time():
first = _projection_value(derived_row(), datetime(2026, 8, 15, tzinfo=timezone.utc))
second = _projection_value(derived_row(), datetime(2026, 8, 16, tzinfo=timezone.utc))
assert first["source_fingerprint"] == second["source_fingerprint"]
assert first["business_next_action"] == "CREATE_PROFORMA"
assert first["evidence_refs"] == [{
"source": "message", "role": "latest_relevant_inbound", "id": "m1",
"at": "2026-08-15T00:00:00+00:00",
}]
def test_duplicate_projection_persists_canonical_material_identity():
row = derived_row(
opportunity_id="22222222-2222-2222-2222-222222222222",
canonical_process_id="11111111-1111-1111-1111-111111111111",
)
value = _projection_value(row, datetime.now(timezone.utc))
assert value["is_duplicate_representation"] is True
assert value["canonical_opportunity_id"] == "11111111-1111-1111-1111-111111111111"
assert value["material_process_key"] == "odoo_sale_name:s00001"
def test_off_mode_is_a_noop_without_deriving_or_connecting():
assert rebuild_blif_flow_v2_projection(mode="off") == {
"mode": "off", "projection_count": 0, "transitions_written": 0, "disabled": True,
}
@pytest.mark.parametrize("mode", ["compare", "authoritative", "invalid"])
def test_unimplemented_modes_fail_closed(mode):
with pytest.raises(RuntimeError, match="not implemented"):
rebuild_blif_flow_v2_projection(mode=mode, derived_rows=[])