feat: add BLIF Flow v2 persistence scaffolding
This commit is contained in:
86
tests/test_blif_flow_v2_persistence.py
Normal file
86
tests/test_blif_flow_v2_persistence.py
Normal file
@@ -0,0 +1,86 @@
|
||||
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=[])
|
||||
Reference in New Issue
Block a user