from __future__ import annotations from pathlib import Path import pytest from sync_state_machine.common.types import BindingStatus, SyncAction, SyncStatus from sync_state_machine.engine import StateMachineConfig, StateMachineRuntime from sync_state_machine.engine.events import e10_bind_core from sync_state_machine.engine.invariants import InvariantViolation from tests.mocks.mock_datasource import MockSyncNode CFG_PATH = Path(__file__).resolve().parents[2] / "sync_state_machine" / "config" / "node_state_machine.yaml" def _new_node(node_id: str) -> MockSyncNode: return MockSyncNode( node_id=node_id, data_id="seed-id", data={"id": node_id, "name": "mock"}, action=SyncAction.NONE, status=SyncStatus.PENDING, binding_status=BindingStatus.UNCHECKED, ) def test_event_path_calls_check_invariants(monkeypatch: pytest.MonkeyPatch) -> None: runtime = StateMachineRuntime(StateMachineConfig.load(CFG_PATH)) called = {"value": False} def _fake_check_invariants(snapshot): called["value"] = True return [] monkeypatch.setattr(runtime, "check_invariants", _fake_check_invariants) node = _new_node("inv-call") decision = e10_bind_core( runtime, node=node, has_binding_record=False, ) assert decision.to_state == "S02" assert called["value"] is True def test_event_path_raises_on_invariant_violation(monkeypatch: pytest.MonkeyPatch) -> None: runtime = StateMachineRuntime(StateMachineConfig.load(CFG_PATH)) def _fake_violation(snapshot): return [InvariantViolation(invariant_id="INV-X", message="boom")] monkeypatch.setattr(runtime, "check_invariants", _fake_violation) node = _new_node("inv-fail") with pytest.raises(RuntimeError, match="Invariant violation"): e10_bind_core( runtime, node=node, has_binding_record=False, )