from __future__ import annotations from dataclasses import dataclass, field from typing import Dict, List, Optional from pydantic import BaseModel from sync_state_machine.common.sync_node import SyncNode from sync_state_machine.datasource.datasource import BaseDataSource from sync_state_machine.datasource.task_result import TaskResult class MockSchema(BaseModel): id: Optional[str] = None name: str = "mock" class MockSyncNode(SyncNode[MockSchema]): node_type = "mock" schema = MockSchema @dataclass class HandlerScript: create: str = "success" # success | failed | skipped | in_progress update: str = "success" delete: str = "success" poll_sequence: List[str] = field(default_factory=lambda: ["success"]) # success | failed | in_progress class ScriptedMockHandler: def __init__(self, script: HandlerScript): self._script = script self._collection = None self._poll_index = 0 @property def node_type(self) -> str: return "mock" @property def node_class(self): return MockSyncNode @property def schema(self): return MockSchema def set_collection(self, collection): self._collection = collection async def load(self): return [] async def create_all(self, nodes): return [self._build_result(self._script.create, node.node_id, "create") for node in nodes] async def update_all(self, nodes): return [ self._build_result( self._script.update, node.node_id, "update", ) for node in nodes ] async def delete_all(self, nodes): return [self._build_result(self._script.delete, node.node_id, "delete") for node in nodes] async def poll_tasks(self, task_ids: List[str]) -> Dict[str, TaskResult]: status = self._script.poll_sequence[min(self._poll_index, len(self._script.poll_sequence) - 1)] self._poll_index += 1 out: Dict[str, TaskResult] = {} for task_id in task_ids: out[task_id] = self._build_poll_result(status, task_id) return out def _build_result( self, mode: str, node_id: str, phase: str, ) -> TaskResult: if mode == "success": return TaskResult.success(node_id=node_id, data_id=f"{phase}-{node_id}") if mode == "failed": return TaskResult.failed(node_id=node_id, error=f"{phase} failed") if mode == "skipped": return TaskResult.skipped( node_id=node_id, reason=f"{phase} skipped", ) if mode == "in_progress": return TaskResult.in_progress(node_id=node_id, task_id=f"task-{phase}-{node_id}") raise ValueError(f"Unsupported mode: {mode}") def _build_poll_result(self, mode: str, task_id: str) -> TaskResult: if mode == "success": return TaskResult.success(node_id=self._node_id_from_task(task_id), data_id=f"done-{task_id}") if mode == "failed": return TaskResult.failed(node_id=self._node_id_from_task(task_id), error="poll failed") if mode == "in_progress": return TaskResult.in_progress(node_id=self._node_id_from_task(task_id), task_id=task_id) raise ValueError(f"Unsupported poll mode: {mode}") @staticmethod def _node_id_from_task(task_id: str) -> str: return task_id.split("-")[-1] class MockDataSource(BaseDataSource): pass