"""The sanitising Ollama client must actually be reachable. src/ollama/provider.py exists to work around Ollama rejecting assistant messages that carry `content: null` alongside `tool_calls`. On 2026-08-11 it raised AttributeError the moment anything touched `.chat`: it fetched the parent's getter via `AsyncOpenAI.chat.fget`, and openai had made `chat` a functools.cached_property, whose getter is `.func`. Nothing caught it. The line carried a bare `# type: ignore`, so mypy stayed quiet, and the endpoints that reach this code had served no requests in 30 days, so no user hit it either. The mitigation was dead and everything looked fine. These tests exercise the path rather than the types, because the failure was a runtime attribute lookup that no annotation would have caught. """ from openai import AsyncOpenAI from src.ollama.provider import _parent_chat, _SanitizedAsyncOpenAI, get_ollama_provider class TestSanitizedClientIsReachable: def test_chat_can_be_accessed(self): """The regression: this raised AttributeError, not a type error.""" client = _SanitizedAsyncOpenAI(base_url="http://localhost:11434/v1") chat = client.chat assert chat is not None assert chat.completions is not None def test_provider_reaches_completions(self): """The full chain an agent request walks, short of the network call.""" provider = get_ollama_provider() assert provider._openai_client.chat.completions is not None def test_parent_lookup_survives_either_descriptor_shape(self): """openai has used both property and cached_property for `chat`. Whichever it is, the parent's own getter must be found — the previous code hardcoded `.fget` and broke on the switch to cached_property. """ descriptor = AsyncOpenAI.__dict__["chat"] assert hasattr(descriptor, "func") or hasattr(descriptor, "fget"), ( "AsyncOpenAI.chat exposes neither getter; _parent_chat needs updating" ) client = _SanitizedAsyncOpenAI(base_url="http://localhost:11434/v1") assert _parent_chat(client) is not None def test_parent_chat_is_not_the_override(self): """It must return openai's Chat, not recurse into the subclass property. Returning the subclass's own `chat` would be infinite recursion, and the sanitiser would wrap itself instead of the real completions resource. """ client = _SanitizedAsyncOpenAI(base_url="http://localhost:11434/v1") assert type(_parent_chat(client)).__name__ != "_SanitizedChat"