Files
wehub-resource-sync e4dcfc49aa
Tests / Import Check (Python 3.13) (push) Has been cancelled
Tests / Import Check (Python 3.14) (push) Has been cancelled
Tests / Python Tests (Python 3.11) (push) Has been cancelled
Tests / Python Tests (Python 3.12) (push) Has been cancelled
Tests / Python Tests (Python 3.14) (push) Has been cancelled
Tests / Test Summary (push) Has been cancelled
Tests / Lint and Format (push) Has been cancelled
Tests / Web Node Tests (push) Has been cancelled
Tests / Import Check (Python 3.11) (push) Has been cancelled
Tests / Import Check (Python 3.12) (push) Has been cancelled
Tests / Python Tests (Python 3.13) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:00:43 +08:00

176 lines
5.5 KiB
Python

"""Tests for the LLM client wrapper."""
from __future__ import annotations
from _pytest.monkeypatch import MonkeyPatch
import pytest
from deeptutor.services.llm.client import LLMClient
from deeptutor.services.llm.config import LLMConfig
@pytest.mark.asyncio
async def test_client_complete_uses_factory(monkeypatch: MonkeyPatch) -> None:
"""Client complete should delegate to factory.complete."""
config = LLMConfig(model="model", api_key="key", base_url="https://example.com")
client = LLMClient(config)
async def _fake_complete(**_kwargs: object) -> str:
return "ok"
monkeypatch.setattr("deeptutor.services.llm.factory.complete", _fake_complete)
result = await client.complete("hello")
assert result == "ok"
def test_client_complete_sync(monkeypatch: MonkeyPatch) -> None:
"""complete_sync should run in a fresh event loop."""
config = LLMConfig(model="model", api_key="key", base_url="https://example.com")
client = LLMClient(config)
async def _fake_complete(
_prompt: str,
_system_prompt: str | None = None,
_history: list[dict[str, str]] | None = None,
**_kwargs: object,
) -> str:
return "ok"
monkeypatch.setattr(client, "complete", _fake_complete)
assert client.complete_sync("hello") == "ok"
def test_client_reports_multimodal_image_support() -> None:
assert (
LLMClient(
LLMConfig(model="gpt-4o", api_key="key", base_url="https://example.com")
).supports_multimodal_images()
is True
)
assert (
LLMClient(
LLMConfig(model="gpt-3.5-turbo", api_key="key", base_url="https://example.com")
).supports_multimodal_images()
is False
)
@pytest.mark.asyncio
async def test_client_complete_sync_running_loop() -> None:
"""complete_sync should raise when called from a running event loop."""
config = LLMConfig(model="model", api_key="key", base_url="https://example.com")
client = LLMClient(config)
with pytest.raises(RuntimeError):
client.complete_sync("hello")
@pytest.mark.asyncio
async def test_client_get_model_func_uses_factory(monkeypatch: MonkeyPatch) -> None:
"""get_model_func should append prompt after history messages."""
config = LLMConfig(model="model", api_key="key", base_url="https://example.com")
client = LLMClient(config)
captured: dict[str, object] = {}
async def _fake_complete(**kwargs: object) -> str:
captured.update(kwargs)
return "ok"
monkeypatch.setattr("deeptutor.services.llm.factory.complete", _fake_complete)
func = client.get_model_func()
result = await func(
"hello",
system_prompt="sys",
history_messages=[{"role": "user", "content": "old"}],
)
assert result == "ok"
assert captured["prompt"] == "hello"
assert captured["system_prompt"] == "sys"
assert captured["messages"] == [
{"role": "system", "content": "sys"},
{"role": "user", "content": "old"},
{"role": "user", "content": "hello"},
]
@pytest.mark.asyncio
async def test_client_get_model_func_empty_history_uses_prompt(
monkeypatch: MonkeyPatch,
) -> None:
"""Empty history_messages must not override the current prompt."""
config = LLMConfig(model="model", api_key="key", base_url="https://example.com")
client = LLMClient(config)
captured: dict[str, object] = {}
async def _fake_complete(**kwargs: object) -> str:
captured.update(kwargs)
return "ok"
monkeypatch.setattr("deeptutor.services.llm.factory.complete", _fake_complete)
func = client.get_model_func()
result = await func("hello", system_prompt="sys", history_messages=[])
assert result == "ok"
assert captured["prompt"] == "hello"
assert captured["system_prompt"] == "sys"
assert captured["messages"] is None
@pytest.mark.asyncio
async def test_client_get_model_func_explicit_messages_override_prompt(
monkeypatch: MonkeyPatch,
) -> None:
"""Explicit messages are already complete and should pass through as-is."""
config = LLMConfig(model="model", api_key="key", base_url="https://example.com")
client = LLMClient(config)
captured: dict[str, object] = {}
async def _fake_complete(**kwargs: object) -> str:
captured.update(kwargs)
return "ok"
monkeypatch.setattr("deeptutor.services.llm.factory.complete", _fake_complete)
messages = [{"role": "user", "content": "from messages"}]
func = client.get_model_func()
result = await func("", system_prompt="sys", messages=messages)
assert result == "ok"
assert captured["messages"] == messages
@pytest.mark.asyncio
async def test_client_get_vision_model_func_uses_factory(monkeypatch: MonkeyPatch) -> None:
"""Vision model func should pass multimodal args into factory."""
config = LLMConfig(model="model", api_key="key", base_url="https://example.com")
client = LLMClient(config)
captured: dict[str, object] = {}
async def _fake_complete(**kwargs: object) -> str:
captured.update(kwargs)
return "ok"
monkeypatch.setattr("deeptutor.services.llm.factory.complete", _fake_complete)
func = client.get_vision_model_func()
result = await func(
"hello",
image_data="abc123",
messages=[{"role": "user", "content": "hi"}],
)
assert result == "ok"
assert captured["prompt"] == "hello"
assert captured["messages"] == [{"role": "user", "content": "hi"}]
assert captured["image_data"] == "abc123"