115 lines
3.3 KiB
Python
115 lines
3.3 KiB
Python
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
from types import SimpleNamespace
|
|
from typing import Any
|
|
|
|
import pytest
|
|
|
|
from src.services import chat_manager as chat_module
|
|
from src.services.chat_manager import ChatManager
|
|
|
|
|
|
class _FakeResponse:
|
|
def __init__(self, content: str = "老闆,系統目前穩定。") -> None:
|
|
self._content = content
|
|
|
|
def raise_for_status(self) -> None:
|
|
return None
|
|
|
|
def json(self) -> dict[str, Any]:
|
|
return {
|
|
"message": {"content": self._content},
|
|
"prompt_eval_count": 11,
|
|
"eval_count": 13,
|
|
}
|
|
|
|
|
|
class _FakeAsyncClient:
|
|
posted: list[tuple[str, dict[str, Any]]] = []
|
|
|
|
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
self.args = args
|
|
self.kwargs = kwargs
|
|
|
|
async def __aenter__(self) -> _FakeAsyncClient:
|
|
return self
|
|
|
|
async def __aexit__(self, *args: Any) -> None:
|
|
return None
|
|
|
|
async def post(self, url: str, *, json: dict[str, Any]) -> _FakeResponse:
|
|
self.posted.append((url, json))
|
|
return _FakeResponse()
|
|
|
|
|
|
def _settings() -> SimpleNamespace:
|
|
return SimpleNamespace(OPENCLAW_DEFAULT_MODEL="qwen3:14b")
|
|
|
|
|
|
def _fake_ollama_order(_workload_type: str) -> tuple[SimpleNamespace, ...]:
|
|
return (
|
|
SimpleNamespace(url="http://gcp-a:11435", provider_name="ollama_gcp_a"),
|
|
SimpleNamespace(url="http://gcp-b:11436", provider_name="ollama_gcp_b"),
|
|
SimpleNamespace(url="http://local-111:11434", provider_name="ollama_local"),
|
|
)
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def _reset_fake_client() -> None:
|
|
_FakeAsyncClient.posted = []
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_openclaw_chat_uses_ollama_interactive_lane(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
monkeypatch.setattr(chat_module.httpx, "AsyncClient", _FakeAsyncClient)
|
|
monkeypatch.setattr(chat_module, "get_settings", _settings)
|
|
monkeypatch.setattr(
|
|
chat_module,
|
|
"resolve_ollama_order",
|
|
_fake_ollama_order,
|
|
)
|
|
|
|
result = await ChatManager()._call_openclaw("system context", "幫我看狀態")
|
|
|
|
assert result is not None
|
|
assert "qwen3:14b" in result
|
|
assert "免費" in result
|
|
|
|
assert len(_FakeAsyncClient.posted) == 1
|
|
url, payload = _FakeAsyncClient.posted[0]
|
|
assert url == "http://gcp-a:11435/api/chat"
|
|
assert payload["model"] == "qwen3:14b"
|
|
assert payload["messages"][0]["role"] == "system"
|
|
assert payload["messages"][1] == {"role": "user", "content": "幫我看狀態"}
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_nemoclaw_chat_uses_resolved_interactive_lane(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
monkeypatch.setattr(chat_module.httpx, "AsyncClient", _FakeAsyncClient)
|
|
monkeypatch.setattr(
|
|
chat_module,
|
|
"resolve_ollama_order",
|
|
_fake_ollama_order,
|
|
)
|
|
|
|
result = await ChatManager()._call_nemotron("system context", "補充觀點")
|
|
|
|
assert result is not None
|
|
url, payload = _FakeAsyncClient.posted[0]
|
|
assert url == "http://gcp-a:11435/api/chat"
|
|
assert payload["model"] == "deepseek-r1:14b"
|
|
assert payload["think"] is False
|
|
|
|
|
|
def test_chat_manager_has_no_direct_gemini_generation_path() -> None:
|
|
source_path = Path(chat_module.__file__).resolve()
|
|
source = source_path.read_text(encoding="utf-8")
|
|
|
|
assert "generativelanguage.googleapis.com" not in source
|
|
assert "GEMINI_API_KEY" not in source
|