| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106 |
- import os
- import pytest
- from sqlalchemy import create_engine, text
- from app.core.common.identifiers import new_governance_uid
- from app.core.orchestration.migration.dual_run import DualRunMode
- from app.core.orchestration.migration.repository import PostgresMigrationStore
- pytestmark = pytest.mark.skipif(
- os.getenv("RUN_V55_PERSISTENCE_INTEGRATION") != "1",
- reason="set RUN_V55_PERSISTENCE_INTEGRATION=1",
- )
- def test_cutover_state_and_outbox_commit_atomically_and_idempotently():
- engine = create_engine(
- os.environ["DATAOPS_MCP_TEST_DATABASE_URL"], pool_pre_ping=True
- )
- store = PostgresMigrationStore(engine)
- dataflow_uid = new_governance_uid()
- correlation_id = new_governance_uid()
- idempotency_key = f"cutover:{dataflow_uid}:v55"
- state = None
- operation = None
- try:
- state = store.create_state(
- {
- "dataflow_uid": dataflow_uid,
- "environment": "test",
- "mode": DualRunMode.N8N_PRIMARY_KESTRA_SHADOW.value,
- "n8n_definition_id": f"n8n-{dataflow_uid}",
- "kestra_definition_id": f"kestra-{dataflow_uid}",
- "migration_batch": "low_risk",
- }
- )
- dual_run_id = store.record_dual_run(
- {
- "dataflow_uid": dataflow_uid,
- "environment": "test",
- "input_snapshot_hash": "a" * 64,
- "primary_execution_id": "n8n-v55-persistence",
- "shadow_execution_id": "kestra-v55-persistence",
- "primary_status": "success",
- "shadow_status": "success",
- "isolation": {"strategy": "read_only"},
- "formal_engine_changed": False,
- "correlation_id": correlation_id,
- }
- )
- report_id = store.record_reconciliation(
- {
- "state_id": state["state_id"],
- "dual_run_id": dual_run_id,
- "status": "passed",
- "policy": {},
- "safe_metrics": {"row_count_delta": 0},
- "differences": [],
- "correlation_id": correlation_id,
- }
- )
- assert dual_run_id
- assert report_id
- record = {
- "dataflow_uid": dataflow_uid,
- "environment": "test",
- "source_mode": DualRunMode.N8N_PRIMARY_KESTRA_SHADOW.value,
- "target_mode": DualRunMode.KESTRA_PRIMARY_N8N_STANDBY.value,
- "n8n_definition_id": state["n8n_definition_id"],
- "kestra_definition_id": state["kestra_definition_id"],
- "gates": {"reconciliation_passed": True},
- "idempotency_key": idempotency_key,
- "correlation_id": correlation_id,
- }
- claimed, operation = store.request_transition(record)
- replay_claimed, replay = store.request_transition(record)
- assert claimed is True
- assert replay_claimed is False
- assert replay["operation_id"] == operation["operation_id"]
- with engine.connect() as connection:
- event = connection.execute(
- text(
- "SELECT event_type, payload "
- "FROM public.outbox_events "
- "WHERE aggregate_id = :operation_id"
- ),
- {"operation_id": operation["operation_id"]},
- ).one()
- assert event[0] == "workflow.engine.cutover.requested"
- assert event[1]["idempotency_key"] == idempotency_key
- assert "password" not in repr(event[1]).lower()
- store.complete_transition(
- operation["operation_id"],
- DualRunMode.KESTRA_PRIMARY_N8N_STANDBY.value,
- {"status": "succeeded"},
- )
- current = store.get_state(dataflow_uid, "test")
- assert current["mode"] == "kestra_primary_n8n_standby"
- assert current["status"] == "stable"
- finally:
- if state:
- store.delete_test_state(state["state_id"])
- engine.dispose()
|