"""``api/chat.py`` 中 ``_ask_regulatory`` 与 RegulatoryNode.working 的接线。 历史上这里调用的是 ``regulatory_node.handle_chat_message``,但 ``RegulatoryNode`` 上从未定义该方法 —— 是从老串联架构遗留下来的死代码路径。新定位下 chat 入口 应直接调 ``working(MessageRequest)``,本测试钉死这个契约不再回退。 """ from __future__ import annotations from types import SimpleNamespace from unittest.mock import AsyncMock, MagicMock import pytest class _FakeActorRef: def __init__(self, target): self._target = target def __getattr__(self, item): return getattr(self._target, item) @pytest.mark.asyncio async def test_ask_regulatory_calls_working_and_extracts_reply(monkeypatch): from kilostar.api import chat as chat_module from kilostar.core.individual.regulatory_node.template import MessageResponse fake_resp = MessageResponse( platform="client", platform_id="chat-1", reply_message="你好" ) regulatory = MagicMock() regulatory.working = MagicMock() regulatory.working.remote = AsyncMock(return_value=fake_resp) def _fake_hook(name): assert name == "regulatory_node" return SimpleNamespace(regulatory_node=_FakeActorRef(regulatory)) monkeypatch.setattr(chat_module, "ray_actor_hook", _fake_hook) out = await chat_module._ask_regulatory( user_id="alice", chat_id="chat-1", message="hi" ) assert out == "你好" # 调用契约:MessageRequest,且 platform_id 取自 chat_id args, kwargs = regulatory.working.remote.call_args payload = args[0] if args else kwargs.get("payload") assert payload.platform == "client" assert payload.user_name == "alice" assert payload.platform_id == "chat-1" assert payload.message == "hi" @pytest.mark.asyncio async def test_ask_regulatory_returns_none_when_node_returns_none(monkeypatch): """节点降级返回 None 时,上层应静默不写回 chat history。""" from kilostar.api import chat as chat_module regulatory = MagicMock() regulatory.working = MagicMock() regulatory.working.remote = AsyncMock(return_value=None) monkeypatch.setattr( chat_module, "ray_actor_hook", lambda name: SimpleNamespace(regulatory_node=_FakeActorRef(regulatory)), ) out = await chat_module._ask_regulatory( user_id="bob", chat_id="chat-2", message="hello" ) assert out is None