Files
wehub-resource-sync 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
chore: import upstream snapshot with attribution
2026-07-13 13:08:55 +08:00

229 lines
8.6 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Deep coverage tests for the Ollama LLM provider.
Targets paths not exercised by test_ollama_provider.py:
- _get_auth_headers: api_key=None, settings_snapshot=None → empty dict
- _get_auth_headers: api_key from settings_snapshot is empty string → no header
- is_available: URL configured but safe_get raises unexpected exception
- is_available: safe_get returns non-200 response
- create_llm: model not found in older API format (no "models" key)
- create_llm: explicit None context window resolves to the helper default
- list_models_for_api: label ":latest" stripped correctly
- requires_auth_for_models: always returns False
"""
from unittest.mock import Mock, patch
import requests
from local_deep_research.llm.providers.implementations.ollama import (
OllamaProvider,
)
MODULE = "local_deep_research.llm.providers.implementations.ollama"
GET_SETTING = f"{MODULE}.get_setting_from_snapshot"
# ---------------------------------------------------------------------------
# _get_auth_headers edge cases
# ---------------------------------------------------------------------------
class TestGetAuthHeadersEdgeCases:
def test_both_none_returns_empty(self):
headers = OllamaProvider._get_auth_headers(
api_key=None, settings_snapshot=None
)
assert headers == {}
def test_empty_string_api_key_from_settings_returns_empty(self):
"""When settings return empty string, no Authorization header should be added."""
with patch(GET_SETTING, return_value=""):
headers = OllamaProvider._get_auth_headers(
settings_snapshot={"llm.ollama.api_key": ""}
)
assert headers == {}
def test_explicit_api_key_takes_precedence_over_snapshot(self):
"""Explicit api_key must not be overwritten by settings lookup."""
with patch(GET_SETTING, return_value="settings-key") as mock_get:
headers = OllamaProvider._get_auth_headers(
api_key="explicit-key",
settings_snapshot={"llm.ollama.api_key": "settings-key"},
)
# get_setting_from_snapshot should NOT be called because api_key provided
mock_get.assert_not_called()
assert headers["Authorization"] == "Bearer explicit-key"
# ---------------------------------------------------------------------------
# is_available additional paths
# ---------------------------------------------------------------------------
class TestIsAvailableAdditional:
def test_unexpected_exception_from_safe_get_returns_false(self):
with patch(GET_SETTING, return_value="http://localhost:11434"):
with patch(
f"{MODULE}.safe_get", side_effect=Exception("unexpected")
):
result = OllamaProvider.is_available()
assert result is False
def test_non_200_response_returns_false(self):
with patch(GET_SETTING, return_value="http://localhost:11434"):
mock_resp = Mock()
mock_resp.status_code = 503
with patch(f"{MODULE}.safe_get", return_value=mock_resp):
result = OllamaProvider.is_available()
assert result is False
def test_request_exception_returns_false(self):
with patch(GET_SETTING, return_value="http://localhost:11434"):
with patch(
f"{MODULE}.safe_get",
side_effect=requests.exceptions.RequestException("timeout"),
):
result = OllamaProvider.is_available()
assert result is False
# ---------------------------------------------------------------------------
# create_llm older API format (no "models" key in response)
# ---------------------------------------------------------------------------
class TestCreateLlmOlderApiFormat:
def test_create_llm_succeeds_without_preflight(self):
"""create_llm now skips pre-flight checks and creates ChatOllama directly."""
def _get_setting(key, default=None, *args, **kwargs):
return {
"llm.ollama.url": "http://localhost:11434",
"llm.local_context_window_size": 4096,
"llm.supports_max_tokens": True,
"llm.max_tokens": 100000,
}.get(key, default)
with patch(GET_SETTING, side_effect=_get_setting):
with patch(f"{MODULE}.ChatOllama") as mock_chat:
mock_chat.return_value = Mock()
OllamaProvider.create_llm(model_name="gemma3:12b")
mock_chat.assert_called_once()
# ---------------------------------------------------------------------------
# create_llm context_window_size is None
# ---------------------------------------------------------------------------
class TestCreateLlmRequiresModelName:
"""Direct callers of OllamaProvider.create_llm() must specify a model."""
def test_raises_when_no_model_name(self):
import pytest
def _get_setting(key, default=None, *args, **kwargs):
return {"llm.ollama.url": "http://localhost:11434"}.get(
key, default
)
with patch(GET_SETTING, side_effect=_get_setting):
with pytest.raises(ValueError, match="Ollama model not configured"):
OllamaProvider.create_llm()
def test_raises_when_model_name_empty_string(self):
import pytest
def _get_setting(key, default=None, *args, **kwargs):
return {"llm.ollama.url": "http://localhost:11434"}.get(
key, default
)
with patch(GET_SETTING, side_effect=_get_setting):
with pytest.raises(ValueError, match="Ollama model not configured"):
OllamaProvider.create_llm(model_name="")
class TestCreateLlmContextWindowNone:
def test_explicit_none_window_resolves_to_helper_default(self):
"""Explicit None resolves through the shared helper to its default,
matching what context_limit bookkeeping reports for overflow
detection. Previously num_ctx was omitted here (Ollama server
default ~2048) while context_limit claimed 8192 — overflows
between the two went undetected. No max_tokens kwarg: ChatOllama
ignores it.
Uses a real settings_snapshot (no get_setting patch) so the
explicit-None value actually reaches the shared helper's
function-local thread_settings lookup.
"""
from local_deep_research.llm.providers._helpers import (
DEFAULT_LOCAL_CONTEXT_WINDOW_SIZE,
)
snapshot = {
"llm.ollama.url": "http://localhost:11434",
"llm.local_context_window_size": None,
"llm.supports_max_tokens": True,
"llm.max_tokens": 100000,
}
with patch(f"{MODULE}.ChatOllama") as mock_chat:
mock_chat.return_value = Mock()
OllamaProvider.create_llm(
model_name="gemma3:12b", settings_snapshot=snapshot
)
call_kwargs = mock_chat.call_args[1]
assert call_kwargs["num_ctx"] == DEFAULT_LOCAL_CONTEXT_WINDOW_SIZE
assert "max_tokens" not in call_kwargs
# ---------------------------------------------------------------------------
# list_models_for_api label formatting
# ---------------------------------------------------------------------------
class TestListModelsLabelFormatting:
def test_latest_tag_stripped_from_label(self):
with patch(
"local_deep_research.utilities.llm_utils.fetch_ollama_models"
) as mock_fetch:
mock_fetch.return_value = [
{"value": "llama2:latest", "label": "llama2:latest"}
]
result = OllamaProvider.list_models_for_api(
base_url="http://localhost:11434"
)
assert len(result) == 1
# ":latest" should be stripped from the label
assert ":latest" not in result[0]["label"]
def test_colon_replaced_with_space_in_label(self):
with patch(
"local_deep_research.utilities.llm_utils.fetch_ollama_models"
) as mock_fetch:
mock_fetch.return_value = [
{"value": "model:7b", "label": "model:7b"}
]
result = OllamaProvider.list_models_for_api(
base_url="http://localhost:11434"
)
assert ":" not in result[0]["label"].replace("(Ollama)", "")
# ---------------------------------------------------------------------------
# requires_auth_for_models
# ---------------------------------------------------------------------------
class TestRequiresAuthForModels:
def test_always_returns_false(self):
assert OllamaProvider.requires_auth_for_models() is False