import json import httpx import pytest from app.ollama_client import ModelError, chat def _client(handler): return httpx.Client(transport=httpx.MockTransport(handler), base_url="http://ollama") def test_success_returns_content_and_sends_payload(): seen = {} def handler(request): seen["body"] = json.loads(request.content) return httpx.Response(200, json={"message": {"content": "You stand on the wharf."}}) out = chat("qwen3.5:latest", [{"role": "user", "content": "hi"}], {"seed": 7}, think=False, client=_client(handler)) assert out == "You stand on the wharf." assert seen["body"]["model"] == "qwen3.5:latest" assert seen["body"]["stream"] is False assert seen["body"]["options"]["seed"] == 7 assert seen["body"]["think"] is False def test_think_omitted_when_none(): seen = {} def handler(request): seen["body"] = json.loads(request.content) return httpx.Response(200, json={"message": {"content": "ok"}}) chat("m", [{"role": "user", "content": "x"}], {}, client=_client(handler)) assert "think" not in seen["body"] def test_retries_once_then_succeeds(): calls = {"n": 0} def handler(request): calls["n"] += 1 if calls["n"] == 1: raise httpx.ConnectError("boom") return httpx.Response(200, json={"message": {"content": "recovered"}}) out = chat("m", [{"role": "user", "content": "x"}], {}, client=_client(handler)) assert out == "recovered" assert calls["n"] == 2 def test_two_transport_failures_raise_modelerror(): def handler(request): raise httpx.ConnectError("down") with pytest.raises(ModelError): chat("m", [{"role": "user", "content": "x"}], {}, client=_client(handler)) def test_timeout_exhausted_raises_modelerror(): def handler(request): raise httpx.TimeoutException("slow") with pytest.raises(ModelError): chat("m", [{"role": "user", "content": "x"}], {}, client=_client(handler)) def test_non_2xx_retried_then_modelerror(): def handler(request): return httpx.Response(500, json={"error": "nope"}) with pytest.raises(ModelError): chat("m", [{"role": "user", "content": "x"}], {}, client=_client(handler)) def test_empty_content_retried_then_modelerror(): def handler(request): return httpx.Response(200, json={"message": {"content": " "}}) with pytest.raises(ModelError): chat("m", [{"role": "user", "content": "x"}], {}, client=_client(handler)) def test_non_json_200_retried_then_modelerror(): """A 200 response with a non-JSON body (e.g. a proxy error page) must degrade to ModelError after the one retry, not escape as a bare JSONDecodeError/ValueError (charter §12/§13). """ def handler(request): return httpx.Response(200, text="not json") with pytest.raises(ModelError): chat("m", [{"role": "user", "content": "x"}], {}, client=_client(handler))