"""Pytest 全局 fixture:把 Ray Actor 句柄、PostgresDatabase、loguru 等重副作用模块替换成可控的 stub。""" from __future__ import annotations import sys import types from typing import Any, Dict, Optional from unittest.mock import AsyncMock, MagicMock import pytest # ───────────────────────────────────────────────────────────────────────────── # Ray actor 句柄存根:测试期不真正连 Ray,由 conftest 注入名字 -> AsyncMock 句柄 # ───────────────────────────────────────────────────────────────────────────── class _FakeActorRegistry: """模拟 ``ray.get_actor`` 行为:测试可往里塞名字 -> AsyncMock。""" def __init__(self) -> None: self._actors: Dict[str, Any] = {} def register(self, name: str, handle: Any) -> None: self._actors[name] = handle def get(self, name: str, namespace: str = "kilostar"): # noqa: ARG002 if name not in self._actors: raise ValueError(f"FakeActorRegistry: actor {name!r} not registered") return self._actors[name] def clear(self) -> None: self._actors.clear() @pytest.fixture def fake_actors(monkeypatch) -> _FakeActorRegistry: """把 ``kilostar.utils.ray_hook._get_cached_actor_handle`` 的实现替换为 fake registry。 用法:: def test_xxx(fake_actors): gsm = AsyncMock() gsm.get_tool_config.remote = AsyncMock(return_value={"api_key": "k"}) fake_actors.register("global_state_machine", types.SimpleNamespace(global_state_machine=gsm)) """ registry = _FakeActorRegistry() from kilostar.utils import ray_hook ray_hook.clear_actor_cache() original = ray_hook._get_cached_actor_handle def _stub(actor_name: str): return registry.get(actor_name) _stub.cache_clear = lambda: None # type: ignore[attr-defined] monkeypatch.setattr(ray_hook, "_get_cached_actor_handle", _stub) yield registry registry.clear() monkeypatch.setattr(ray_hook, "_get_cached_actor_handle", original) ray_hook.clear_actor_cache() @pytest.fixture def gsm_handle(fake_actors) -> MagicMock: """快捷 fixture:注册一个名为 ``global_state_machine`` 的 actor,返回其内部 mock。 内部 mock 的方法默认全部是 ``AsyncMock``,调用 ``.remote(...)`` 会按 AsyncMock 规则返回。 """ gsm = MagicMock() container = types.SimpleNamespace(global_state_machine=gsm) fake_actors.register("global_state_machine", container) return gsm # ───────────────────────────────────────────────────────────────────────────── # 工具:构造一个 ``MCP_AVAILABLE`` 关闭的 mcp_helper 状态 # ───────────────────────────────────────────────────────────────────────────── @pytest.fixture def mcp_unavailable(monkeypatch): from kilostar.utils import mcp_helper monkeypatch.setattr(mcp_helper, "_MCP_AVAILABLE", False) yield