""" Shared test fixtures for sofiia-supervisor. Uses httpx.MockTransport to mock gateway responses — no real network calls. """ import asyncio import json import sys from pathlib import Path from typing import Any, Callable, Dict, List, Optional from unittest.mock import AsyncMock, MagicMock import pytest # ─── Path bootstrap ─────────────────────────────────────────────────────────── _svc_root = Path(__file__).parent.parent sys.path.insert(0, str(_svc_root)) # ─── Gateway mock helpers ───────────────────────────────────────────────────── class MockGatewayClient: """ Drop-in replacement for GatewayClient that intercepts call_tool and returns pre-configured responses without making HTTP requests. Usage: mock_gw = MockGatewayClient() mock_gw.register("job_orchestrator_tool", "start_task", {"job_id": "j_001", "status": "running"}) mock_gw.register("job_orchestrator_tool", "get_job", {"status": "succeeded", "result": {...}}) """ def __init__(self): self._responses: Dict[str, List[Any]] = {} # key: "tool:action" → list of responses self.calls: List[Dict] = [] # recorded calls (no payload) def register(self, tool: str, action: str, data: Any, *, error: Optional[str] = None, retryable: bool = False): """Register a response for (tool, action). Multiple registrations → FIFO queue.""" key = f"{tool}:{action}" self._responses.setdefault(key, []).append({ "data": data, "error": error, "retryable": retryable }) def _pop(self, tool: str, action: str) -> Dict: key = f"{tool}:{action}" queue = self._responses.get(key, []) if queue: resp = queue.pop(0) if not queue: # Keep last response for further calls self._responses[key] = [resp] return resp return {"data": {}, "error": None, "retryable": False} async def __aenter__(self): return self async def __aexit__(self, *args): pass async def call_tool( self, tool: str, action: str, params: Optional[Dict] = None, agent_id: str = "", workspace_id: str = "", user_id: str = "", graph_run_id: str = "", graph_node: str = "", **kwargs, ): # Record call metadata (no payload logged) self.calls.append({ "tool": tool, "action": action, "graph_run_id": graph_run_id, "graph_node": graph_node, "agent_id": agent_id, }) resp = self._pop(tool, action) from app.gateway_client import ToolCallResult if resp["error"]: return ToolCallResult( success=False, error_code="mock_error", error_message=resp["error"], retryable=resp.get("retryable", False), ) return ToolCallResult(success=True, data=resp["data"]) # ─── Fixtures ──────────────────────────────────────────────────────────────── @pytest.fixture def mock_gw_factory(): """Factory: returns a MockGatewayClient and patches app.gateway_client.GatewayClient.""" def _make(patch_target: str = "app.gateway_client.GatewayClient"): return MockGatewayClient() return _make @pytest.fixture def in_memory_backend(): from app.state_backend import MemoryStateBackend return MemoryStateBackend() def _run(coro): return asyncio.run(coro)