import pytest from tutor.config import Settings from tutor.services.llm.base import ChatMessage, LLMClient from tutor.services.llm.factory import GEMINI_OPENAI_BASE_URL, create_llm_client from tutor.services.llm.fake import FakeLLMClient from tutor.services.llm.openai_compat import OpenAICompatClient def _settings(**overrides: object) -> Settings: return Settings(_env_file=None, **overrides) # type: ignore[arg-type] async def test_fake_client_is_deterministic_and_records_calls() -> None: client = FakeLLMClient(responses=["first", "second"]) messages = [ChatMessage(role="user", content="hello")] first = await client.complete(messages) second = await client.complete(messages) third = await client.complete(messages) # queue exhausted -> default assert (first.text, second.text) == ("first", "second") assert third.text == "(fake) pong" assert len(client.calls) == 3 assert client.calls[0][0].content == "hello" def test_fake_satisfies_protocol() -> None: assert isinstance(FakeLLMClient(), LLMClient) def test_factory_returns_fake_by_default() -> None: client = create_llm_client(_settings()) assert isinstance(client, FakeLLMClient) def test_factory_requires_api_key_for_remote_providers() -> None: with pytest.raises(ValueError, match="LLM_API_KEY"): create_llm_client(_settings(llm_provider="gemini")) def test_factory_builds_openai_compat_client_with_gemini_preset() -> None: settings = _settings(llm_provider="gemini", llm_api_key="sk-test", llm_model="gemini-2.5-flash") client = create_llm_client(settings) assert isinstance(client, OpenAICompatClient) assert client.model == "gemini-2.5-flash" assert str(client._client.base_url) == GEMINI_OPENAI_BASE_URL def test_factory_allows_keyless_ollama() -> None: client = create_llm_client(_settings(llm_provider="ollama", llm_model="llama3")) assert isinstance(client, OpenAICompatClient)