from __future__ import annotations import tempfile from pathlib import Path import pytest from sync_state_machine.common.collection import DataCollection from sync_state_machine.common.persistence import PersistenceBackend from sync_state_machine.common.sqlite_backend import SQLitePersistenceBackend from sync_state_machine.common.types import BindingStatus, SyncAction, SyncStatus from sync_state_machine.engine.dispatcher import StateMachineRuntime from sync_state_machine.engine.model import StateMachineConfig from tests.mocks.mock_datasource import ( HandlerScript, MockDataSource, MockSyncNode, ScriptedMockHandler, ) @pytest.fixture def datasource_bundle(): db = Path(tempfile.mkdtemp(prefix="sync_state_machine_mock_ds_")) / "state.db" persistence = SQLitePersistenceBackend(str(db)) cfg_path = Path(__file__).resolve().parents[2] / "sync_state_machine" / "config" / "node_state_machine.yaml" runtime = StateMachineRuntime(StateMachineConfig.load(cfg_path)) collection = DataCollection("mock", persistence=None, auto_persist=False) collection.set_state_machine_runtime(runtime) datasource = MockDataSource(poll_max_retries=2, poll_interval=0) datasource.set_state_machine_runtime(runtime) return datasource, collection def _new_node(node_id: str, action: SyncAction) -> MockSyncNode: data_id = "" if action == SyncAction.CREATE else "mock-data-id" return MockSyncNode( node_id=node_id, data_id=data_id, data={"id": node_id, "name": "mock"}, action=action, status=SyncStatus.PENDING, binding_status=BindingStatus.NORMAL, ) @pytest.mark.asyncio async def test_create_success_to_s11(datasource_bundle): datasource, collection = datasource_bundle node = _new_node("n1", SyncAction.CREATE) await collection.add(node) datasource.add_handler(ScriptedMockHandler(HandlerScript(create="success"))) datasource.set_collection(collection) await datasource.sync_all(data_type="mock") assert node.status == SyncStatus.SUCCESS @pytest.mark.asyncio async def test_create_skipped_is_rejected(datasource_bundle): datasource, collection = datasource_bundle node = _new_node("n2", SyncAction.CREATE) await collection.add(node) datasource.add_handler(ScriptedMockHandler(HandlerScript(create="skipped"))) datasource.set_collection(collection) await datasource.sync_all(data_type="mock") assert node.status == SyncStatus.FAILED assert node.action == SyncAction.CREATE @pytest.mark.asyncio async def test_update_skip_downgrade_to_s14(datasource_bundle): datasource, collection = datasource_bundle node = _new_node("n3", SyncAction.UPDATE) await collection.add(node) datasource.add_handler( ScriptedMockHandler(HandlerScript(update="skipped")) ) datasource.set_collection(collection) await datasource.sync_all(data_type="mock") assert node.status == SyncStatus.SKIPPED assert node.action == SyncAction.NONE @pytest.mark.asyncio async def test_update_success_writes_back_origin_data(datasource_bundle): datasource, collection = datasource_bundle node = _new_node("n3b", SyncAction.UPDATE) node.set_origin_data({"id": "n3b", "name": "before"}) node.set_data({"id": "n3b", "name": "after"}) await collection.add(node) datasource.add_handler(ScriptedMockHandler(HandlerScript(update="success"))) datasource.set_collection(collection) await datasource.sync_all(data_type="mock") assert node.status == SyncStatus.SUCCESS assert node.action == SyncAction.UPDATE assert node.get_origin_data() == node.get_data() @pytest.mark.asyncio async def test_in_progress_poll_success_to_s11(datasource_bundle): datasource, collection = datasource_bundle node = _new_node("n4", SyncAction.CREATE) await collection.add(node) datasource.add_handler( ScriptedMockHandler(HandlerScript(create="in_progress", poll_sequence=["success"])) ) datasource.set_collection(collection) await datasource.sync_all(data_type="mock", poll_async_tasks=True) assert node.status == SyncStatus.SUCCESS @pytest.mark.asyncio async def test_in_progress_poll_timeout_to_s12(datasource_bundle): datasource, collection = datasource_bundle node = _new_node("n5", SyncAction.CREATE) await collection.add(node) datasource.add_handler( ScriptedMockHandler(HandlerScript(create="in_progress", poll_sequence=["in_progress", "in_progress"])) ) datasource.set_collection(collection) await datasource.sync_all(data_type="mock", poll_async_tasks=True) assert node.status == SyncStatus.FAILED assert "timeout" in (node.error or "").lower()