"""Unit tests for Ollama backend helpers."""
import httpx
from navi.exceptions import LLMConnectionError
from navi.llm.fallback import FallbackOllamaBackend, ServerEntry
from navi.llm.ollama import _base_options, _classify_error, _resolve_think
def test_classify_read_timeout_as_connection_error():
err = _classify_error(httpx.ReadTimeout("timed out"))
assert isinstance(err, LLMConnectionError)
def test_classify_empty_timeout_message_as_connection_error():
err = _classify_error(httpx.ReadTimeout(""))
assert isinstance(err, LLMConnectionError)
assert str(err) == "ReadTimeout"
def test_fallback_client_uses_expanded_timeout(monkeypatch):
import navi.llm.fallback as fallback_mod
import navi.config as config_mod
from navi.config import Settings
captured = {}
class FakeOllamaBackend:
def __init__(self, **kwargs):
captured.update(kwargs)
monkeypatch.setattr(fallback_mod, "OllamaBackend", FakeOllamaBackend)
monkeypatch.setattr(
config_mod, "settings",
Settings(
ollama_request_timeout=30,
llm_complete_timeout=120,
llm_stream_first_chunk_timeout=180,
)
)
backend = FallbackOllamaBackend([ServerEntry(host="http://ollama.test")])
backend._get_client(ServerEntry(host="http://ollama.test", api_key="secret"))
assert captured["host"] == "http://ollama.test"
assert captured["api_key"] == "secret"
assert captured["timeout"] == 180
def test_think_resolves_from_global_setting(monkeypatch):
import navi.config as config_mod
from navi.config import Settings
monkeypatch.setattr(config_mod, "settings", Settings(ollama_think=True))
assert _resolve_think(None) is True
assert _resolve_think(False) is False
def test_base_options_do_not_include_think(monkeypatch):
import navi.llm.ollama as ollama_mod
from navi.config import Settings
monkeypatch.setattr(ollama_mod, "settings", Settings(ollama_num_ctx=1234))
opts = _base_options(0.2, top_k=20, top_p=0.8)
assert opts == {
"temperature": 0.2,
"num_ctx": 1234,
"top_k": 20,
"top_p": 0.8,
}
def test_fallback_with_single_server(monkeypatch):
import navi.llm.fallback as fallback_mod
import navi.config as config_mod
captured = {}
class FakeOllamaBackend:
def __init__(self, **kwargs):
captured.update(kwargs)
monkeypatch.setattr(fallback_mod, "OllamaBackend", FakeOllamaBackend)
backend = FallbackOllamaBackend([ServerEntry(host="http://localhost:11434", api_key="")])
client = backend._get_client(ServerEntry(host="http://localhost:11434"))
assert client is not None
def test_load_servers_missing_file_returns_empty(tmp_path):
from navi.llm.fallback import load_servers_from_file
missing = tmp_path / "no_such_file.json"
assert load_servers_from_file(str(missing)) == []
def test_load_servers_bad_json_returns_empty(tmp_path):
from navi.llm.fallback import load_servers_from_file
bad = tmp_path / "bad.json"
bad.write_text("not json", encoding="utf-8")
assert load_servers_from_file(str(bad)) == []
def test_load_servers_missing_host_skipped(tmp_path):
from navi.llm.fallback import load_servers_from_file, ServerEntry
f = tmp_path / "servers.json"
f.write_text('[{"host": "http://a"}, {"api_key": "k"}]', encoding="utf-8")
result = load_servers_from_file(str(f))
assert result == [ServerEntry(host="http://a", api_key="")]
def test_clear_blacklists_resets_state(monkeypatch):
import navi.llm.fallback as fallback_mod
fallback_mod._dead_servers["http://dead"] = 0.0
fallback_mod._dead_models[("http://dead", "model")] = 0.0
fallback_mod.clear_blacklists()
assert fallback_mod._dead_servers == {}
assert fallback_mod._dead_models == {}