7a0da7932b
OSV-Scanner (Scheduled) / scan-scheduled (push) Failing after 0s
Create Release / test-gate (push) Has been cancelled
Create Release / release-gate (push) Has been cancelled
Create Release / ci-gate (push) Has been cancelled
Create Release / version-check (push) Has been cancelled
Create Release / e2e-test-gate (push) Has been cancelled
Create Release / responsive-test-gate (push) Has been cancelled
Create Release / compat-test-gate (push) Has been cancelled
Create Release / compose-integration-gate (push) Has been cancelled
Create Release / vulture-gate (push) Has been cancelled
Create Release / build (push) Has been cancelled
Create Release / provenance (push) Has been cancelled
Create Release / prerelease-docker (push) Has been cancelled
Create Release / publish-docker (push) Has been cancelled
Create Release / create-release (push) Has been cancelled
Create Release / cleanup-changelog (push) Has been cancelled
Create Release / trigger-pypi (push) Has been cancelled
Create Release / monitor-pypi (push) Has been cancelled
Create Release / Clean up orphan prerelease tags and signatures (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Has been cancelled
Docker Tests (Consolidated) / Accessibility Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Unit Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Example Tests (push) Has been cancelled
Docker Tests (Consolidated) / Production Image Smoke Test (push) Has been cancelled
Docker Tests (Consolidated) / Infrastructure Tests (push) Has been cancelled
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Has been cancelled
Backwards Compatibility / Verify Encryption Constants (push) Has been cancelled
Backwards Compatibility / PyPI Version Compatibility (push) Has been cancelled
Backwards Compatibility / Database Migration Tests (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Docker Tests (Consolidated) / detect-changes (push) Has been cancelled
Docker Tests (Consolidated) / Build Test Image (push) Has been cancelled
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Has been cancelled
170 lines
5.6 KiB
Python
170 lines
5.6 KiB
Python
"""
|
|
Coverage tests for local_deep_research/utilities/llm_utils.py.
|
|
|
|
Focuses on paths not exercised by existing test files:
|
|
- _close_base_llm: close() delegation, Ollama httpx client closing,
|
|
non-Ollama _client skip, missing _client attr, missing httpx client
|
|
- fetch_ollama_models: verifies safe_get (not requests.get) is used
|
|
"""
|
|
|
|
from unittest.mock import Mock, patch
|
|
|
|
from local_deep_research.utilities.llm_utils import (
|
|
_close_base_llm,
|
|
fetch_ollama_models,
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# _close_base_llm
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestCloseBaseLlmDelegation:
|
|
"""When the LLM object has its own close() method defined on the type."""
|
|
|
|
def test_delegates_to_close_method(self):
|
|
"""Should call llm.close() when the type defines close."""
|
|
|
|
class ClosableLLM:
|
|
def close(self):
|
|
pass
|
|
|
|
llm = ClosableLLM()
|
|
llm.close = Mock()
|
|
_close_base_llm(llm)
|
|
llm.close.assert_called_once()
|
|
|
|
def test_does_not_inspect_client_after_close(self):
|
|
"""After calling close(), should return immediately without
|
|
inspecting _client."""
|
|
|
|
class ClosableLLM:
|
|
def close(self):
|
|
pass
|
|
|
|
llm = ClosableLLM()
|
|
llm.close = Mock()
|
|
llm._client = Mock()
|
|
_close_base_llm(llm)
|
|
llm.close.assert_called_once()
|
|
llm._client.close.assert_not_called()
|
|
|
|
|
|
class TestCloseBaseLlmOllamaClient:
|
|
"""When the LLM has an Ollama-module _client with an httpx _client."""
|
|
|
|
def test_closes_httpx_client_on_ollama(self):
|
|
"""Should close the nested httpx client on an Ollama _client."""
|
|
httpx_client = Mock()
|
|
httpx_client.close = Mock()
|
|
|
|
ollama_client = Mock()
|
|
ollama_client._client = httpx_client
|
|
type(ollama_client).__module__ = "ollama._client"
|
|
|
|
llm = Mock(spec=[])
|
|
llm._client = ollama_client
|
|
|
|
_close_base_llm(llm)
|
|
httpx_client.close.assert_called_once()
|
|
|
|
def test_skips_non_ollama_client_module(self):
|
|
"""Should NOT close _client._client when module is not ollama."""
|
|
httpx_client = Mock()
|
|
httpx_client.close = Mock()
|
|
|
|
non_ollama_client = Mock()
|
|
non_ollama_client._client = httpx_client
|
|
type(non_ollama_client).__module__ = "openai._http_client"
|
|
|
|
llm = Mock(spec=[])
|
|
llm._client = non_ollama_client
|
|
|
|
_close_base_llm(llm)
|
|
httpx_client.close.assert_not_called()
|
|
|
|
def test_no_client_attr_is_noop(self):
|
|
"""Should do nothing when llm has no _client attribute."""
|
|
llm = Mock(spec=[])
|
|
assert (
|
|
not hasattr(llm, "_client") or getattr(llm, "_client", None) is None
|
|
)
|
|
_close_base_llm(llm)
|
|
|
|
def test_ollama_client_without_httpx_client(self):
|
|
"""Should handle Ollama _client that has no nested _client (httpx)."""
|
|
ollama_client = Mock(spec=[])
|
|
type(ollama_client).__module__ = "ollama._client"
|
|
|
|
llm = Mock(spec=[])
|
|
llm._client = ollama_client
|
|
_close_base_llm(llm)
|
|
|
|
def test_ollama_httpx_client_without_close(self):
|
|
"""Should handle Ollama httpx _client that lacks close()."""
|
|
httpx_client = Mock(spec=[])
|
|
|
|
ollama_client = Mock()
|
|
ollama_client._client = httpx_client
|
|
type(ollama_client).__module__ = "ollama.core"
|
|
|
|
llm = Mock(spec=[])
|
|
llm._client = ollama_client
|
|
_close_base_llm(llm)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# fetch_ollama_models - verifies safe_get usage
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestFetchOllamaModelsUsesSafeGet:
|
|
"""Verify that fetch_ollama_models calls safe_get, not requests.get."""
|
|
|
|
def test_calls_safe_get_not_requests_get(self):
|
|
"""safe_get should be called; requests.get should NOT."""
|
|
mock_safe_get = Mock()
|
|
mock_response = Mock()
|
|
mock_response.status_code = 200
|
|
mock_response.json.return_value = {"models": [{"name": "llama3"}]}
|
|
mock_safe_get.return_value = mock_response
|
|
|
|
with patch(
|
|
"local_deep_research.utilities.llm_utils.safe_get",
|
|
mock_safe_get,
|
|
create=True,
|
|
):
|
|
with patch("local_deep_research.security.safe_get", mock_safe_get):
|
|
result = fetch_ollama_models("http://localhost:11434")
|
|
|
|
mock_safe_get.assert_called()
|
|
call_args = mock_safe_get.call_args
|
|
assert "localhost:11434/api/tags" in call_args.args[0]
|
|
assert call_args.kwargs["allow_localhost"] is True
|
|
assert call_args.kwargs["allow_private_ips"] is True
|
|
|
|
assert len(result) == 1
|
|
assert result[0]["value"] == "llama3"
|
|
|
|
def test_safe_get_exception_returns_empty_list(self):
|
|
"""When safe_get raises, should return [] gracefully."""
|
|
with patch(
|
|
"local_deep_research.security.safe_get",
|
|
side_effect=RuntimeError("SSRF blocked"),
|
|
):
|
|
result = fetch_ollama_models("http://evil.com:11434")
|
|
assert result == []
|
|
|
|
def test_safe_get_non_200_returns_empty(self):
|
|
"""Non-200 response from safe_get should yield empty list."""
|
|
mock_response = Mock()
|
|
mock_response.status_code = 503
|
|
|
|
with patch(
|
|
"local_deep_research.security.safe_get",
|
|
return_value=mock_response,
|
|
):
|
|
result = fetch_ollama_models("http://localhost:11434")
|
|
assert result == []
|