""" Tests for Ollama embedding provider. """ from unittest.mock import Mock, patch import requests from local_deep_research.embeddings.providers.implementations.ollama import ( OllamaEmbeddingsProvider, ) # ── helpers ────────────────────────────────────────────────────────────── _OLLAMA_MODULE = ( "local_deep_research.embeddings.providers.implementations.ollama" ) _LLM_UTILS = "local_deep_research.utilities.llm_utils" def _mock_capabilities(caps_by_model): """Return a side_effect function for _get_model_capabilities. ``caps_by_model`` maps model names to capability lists (e.g. {"nomic-embed-text:latest": ["embedding"]}). """ def _side_effect(_base_url, model_name): return caps_by_model.get(model_name) return _side_effect # ── metadata ───────────────────────────────────────────────────────────── class TestOllamaEmbeddingsProviderMetadata: """Tests for OllamaEmbeddingsProvider class metadata.""" def test_provider_name(self): """Provider name is 'Ollama'.""" assert OllamaEmbeddingsProvider.provider_name == "Ollama" def test_provider_key(self): """Provider key is 'OLLAMA'.""" assert OllamaEmbeddingsProvider.provider_key == "OLLAMA" def test_requires_api_key_false(self): """Does not require API key.""" assert OllamaEmbeddingsProvider.requires_api_key is False def test_supports_local_true(self): """Supports local execution.""" assert OllamaEmbeddingsProvider.supports_local is True def test_default_model(self): """Has a default embedding model.""" assert OllamaEmbeddingsProvider.default_model == "nomic-embed-text" # ── is_available ───────────────────────────────────────────────────────── class TestOllamaEmbeddingsIsAvailable: """Tests for is_available method.""" def test_available_when_server_responds(self): """Returns True when Ollama server responds.""" with patch(f"{_OLLAMA_MODULE}.get_ollama_base_url") as mock_get_url: mock_get_url.return_value = "http://localhost:11434" with patch("requests.get") as mock_get: mock_response = Mock() mock_response.status_code = 200 mock_get.return_value = mock_response result = OllamaEmbeddingsProvider.is_available() assert result is True def test_not_available_when_server_error(self): """Returns False when server returns error.""" with patch(f"{_OLLAMA_MODULE}.get_ollama_base_url") as mock_get_url: mock_get_url.return_value = "http://localhost:11434" with patch("requests.get") as mock_get: mock_response = Mock() mock_response.status_code = 500 mock_get.return_value = mock_response result = OllamaEmbeddingsProvider.is_available() assert result is False def test_not_available_when_connection_fails(self): """Returns False when connection fails.""" with patch(f"{_OLLAMA_MODULE}.get_ollama_base_url") as mock_get_url: mock_get_url.return_value = "http://localhost:11434" with patch("requests.get") as mock_get: mock_get.side_effect = requests.exceptions.ConnectionError() result = OllamaEmbeddingsProvider.is_available() assert result is False def test_not_available_when_timeout(self): """Returns False when request times out.""" with patch(f"{_OLLAMA_MODULE}.get_ollama_base_url") as mock_get_url: mock_get_url.return_value = "http://localhost:11434" with patch("requests.get") as mock_get: mock_get.side_effect = requests.exceptions.Timeout() result = OllamaEmbeddingsProvider.is_available() assert result is False # ── create_embeddings ──────────────────────────────────────────────────── def _make_settings_side_effect(values): """Build a side_effect for get_setting_from_snapshot that dispatches by key. ``values`` maps setting keys (e.g. "embeddings.ollama.model") to the value that lookup should return. Keys absent from ``values`` fall back to the caller-provided ``default`` argument. """ def _side_effect(key, default=None, **_kwargs): return values.get(key, default) return _side_effect class TestOllamaEmbeddingsCreate: """Tests for create_embeddings method.""" def test_create_with_default_model(self): """Creates embeddings with default model.""" with patch( f"{_OLLAMA_MODULE}.get_setting_from_snapshot" ) as mock_get_setting: mock_get_setting.side_effect = _make_settings_side_effect( { "embeddings.ollama.model": "nomic-embed-text", "embeddings.ollama.num_ctx": 8192, } ) with patch(f"{_OLLAMA_MODULE}.get_ollama_base_url") as mock_get_url: mock_get_url.return_value = "http://localhost:11434" with patch(f"{_OLLAMA_MODULE}.OllamaEmbeddings") as mock_ollama: mock_instance = Mock() mock_ollama.return_value = mock_instance result = OllamaEmbeddingsProvider.create_embeddings() assert result is mock_instance mock_ollama.assert_called_once() call_kwargs = mock_ollama.call_args[1] assert call_kwargs["model"] == "nomic-embed-text" def test_create_with_custom_model(self): """Creates embeddings with custom model.""" with patch( f"{_OLLAMA_MODULE}.get_setting_from_snapshot" ) as mock_get_setting: mock_get_setting.side_effect = _make_settings_side_effect( {"embeddings.ollama.num_ctx": 8192} ) with patch(f"{_OLLAMA_MODULE}.get_ollama_base_url") as mock_get_url: mock_get_url.return_value = "http://localhost:11434" with patch(f"{_OLLAMA_MODULE}.OllamaEmbeddings") as mock_ollama: mock_instance = Mock() mock_ollama.return_value = mock_instance OllamaEmbeddingsProvider.create_embeddings( model="mxbai-embed-large" ) call_kwargs = mock_ollama.call_args[1] assert call_kwargs["model"] == "mxbai-embed-large" def test_create_with_custom_base_url(self): """Creates embeddings with custom base URL.""" with patch( f"{_OLLAMA_MODULE}.get_setting_from_snapshot" ) as mock_get_setting: mock_get_setting.side_effect = _make_settings_side_effect( {"embeddings.ollama.num_ctx": 8192} ) with patch(f"{_OLLAMA_MODULE}.OllamaEmbeddings") as mock_ollama: mock_instance = Mock() mock_ollama.return_value = mock_instance OllamaEmbeddingsProvider.create_embeddings( model="nomic-embed-text", base_url="http://custom:8080", ) call_kwargs = mock_ollama.call_args[1] assert call_kwargs["base_url"] == "http://custom:8080" def test_create_uses_settings_snapshot(self): """Uses settings snapshot when provided.""" mock_settings = {"embeddings.ollama.model": "custom-model"} with patch( f"{_OLLAMA_MODULE}.get_setting_from_snapshot" ) as mock_get_setting: mock_get_setting.side_effect = _make_settings_side_effect( { "embeddings.ollama.model": "custom-model", "embeddings.ollama.num_ctx": 8192, } ) with patch(f"{_OLLAMA_MODULE}.get_ollama_base_url") as mock_get_url: mock_get_url.return_value = "http://localhost:11434" with patch(f"{_OLLAMA_MODULE}.OllamaEmbeddings"): OllamaEmbeddingsProvider.create_embeddings( settings_snapshot=mock_settings ) # Verify get_setting_from_snapshot was called with settings mock_get_setting.assert_called() def test_create_passes_num_ctx_when_set(self): """Forwards num_ctx to OllamaEmbeddings when configured.""" with patch( f"{_OLLAMA_MODULE}.get_setting_from_snapshot" ) as mock_get_setting: mock_get_setting.side_effect = _make_settings_side_effect( { "embeddings.ollama.model": "nomic-embed-text", "embeddings.ollama.num_ctx": 8192, } ) with patch(f"{_OLLAMA_MODULE}.get_ollama_base_url") as mock_get_url: mock_get_url.return_value = "http://localhost:11434" with patch(f"{_OLLAMA_MODULE}.OllamaEmbeddings") as mock_ollama: OllamaEmbeddingsProvider.create_embeddings() call_kwargs = mock_ollama.call_args[1] assert call_kwargs["num_ctx"] == 8192 def test_create_omits_num_ctx_when_unset(self): """Does not pass num_ctx when the setting is None.""" with patch( f"{_OLLAMA_MODULE}.get_setting_from_snapshot" ) as mock_get_setting: mock_get_setting.side_effect = _make_settings_side_effect( { "embeddings.ollama.model": "nomic-embed-text", "embeddings.ollama.num_ctx": None, } ) with patch(f"{_OLLAMA_MODULE}.get_ollama_base_url") as mock_get_url: mock_get_url.return_value = "http://localhost:11434" with patch(f"{_OLLAMA_MODULE}.OllamaEmbeddings") as mock_ollama: OllamaEmbeddingsProvider.create_embeddings() call_kwargs = mock_ollama.call_args[1] assert "num_ctx" not in call_kwargs def test_create_passes_custom_num_ctx(self): """Forwards a non-default num_ctx (proves the value is pass-through, not hardcoded).""" with patch( f"{_OLLAMA_MODULE}.get_setting_from_snapshot" ) as mock_get_setting: mock_get_setting.side_effect = _make_settings_side_effect( { "embeddings.ollama.model": "nomic-embed-text", "embeddings.ollama.num_ctx": 16384, } ) with patch(f"{_OLLAMA_MODULE}.get_ollama_base_url") as mock_get_url: mock_get_url.return_value = "http://localhost:11434" with patch(f"{_OLLAMA_MODULE}.OllamaEmbeddings") as mock_ollama: OllamaEmbeddingsProvider.create_embeddings() call_kwargs = mock_ollama.call_args[1] assert call_kwargs["num_ctx"] == 16384 def test_create_coerces_string_num_ctx_to_int(self): """Coerces string num_ctx to int (settings storage may return strings).""" with patch( f"{_OLLAMA_MODULE}.get_setting_from_snapshot" ) as mock_get_setting: mock_get_setting.side_effect = _make_settings_side_effect( { "embeddings.ollama.model": "nomic-embed-text", "embeddings.ollama.num_ctx": "8192", } ) with patch(f"{_OLLAMA_MODULE}.get_ollama_base_url") as mock_get_url: mock_get_url.return_value = "http://localhost:11434" with patch(f"{_OLLAMA_MODULE}.OllamaEmbeddings") as mock_ollama: OllamaEmbeddingsProvider.create_embeddings() call_kwargs = mock_ollama.call_args[1] assert call_kwargs["num_ctx"] == 8192 assert isinstance(call_kwargs["num_ctx"], int) def test_create_coerces_float_num_ctx_to_int(self): """Coerces float num_ctx to int (JSON deserialization may yield floats).""" with patch( f"{_OLLAMA_MODULE}.get_setting_from_snapshot" ) as mock_get_setting: mock_get_setting.side_effect = _make_settings_side_effect( { "embeddings.ollama.model": "nomic-embed-text", "embeddings.ollama.num_ctx": 8192.0, } ) with patch(f"{_OLLAMA_MODULE}.get_ollama_base_url") as mock_get_url: mock_get_url.return_value = "http://localhost:11434" with patch(f"{_OLLAMA_MODULE}.OllamaEmbeddings") as mock_ollama: OllamaEmbeddingsProvider.create_embeddings() call_kwargs = mock_ollama.call_args[1] assert call_kwargs["num_ctx"] == 8192 assert isinstance(call_kwargs["num_ctx"], int) def test_create_combines_custom_model_and_num_ctx(self): """Custom model arg and num_ctx setting both flow through.""" with patch( f"{_OLLAMA_MODULE}.get_setting_from_snapshot" ) as mock_get_setting: mock_get_setting.side_effect = _make_settings_side_effect( {"embeddings.ollama.num_ctx": 4096} ) with patch(f"{_OLLAMA_MODULE}.get_ollama_base_url") as mock_get_url: mock_get_url.return_value = "http://localhost:11434" with patch(f"{_OLLAMA_MODULE}.OllamaEmbeddings") as mock_ollama: OllamaEmbeddingsProvider.create_embeddings( model="bge-m3", ) call_kwargs = mock_ollama.call_args[1] assert call_kwargs["model"] == "bge-m3" assert call_kwargs["num_ctx"] == 4096 def test_create_uses_default_num_ctx_when_snapshot_missing_key(self): """Falls back to default=8192 when snapshot doesn't contain num_ctx.""" # _make_settings_side_effect returns the caller's `default=` for # absent keys, mirroring what the real settings system does when # neither DB nor JSON defaults have the key (shouldn't happen in # practice once the JSON ships, but verifies the explicit default # in create_embeddings is wired correctly). with patch( f"{_OLLAMA_MODULE}.get_setting_from_snapshot" ) as mock_get_setting: mock_get_setting.side_effect = _make_settings_side_effect( {"embeddings.ollama.model": "nomic-embed-text"} ) with patch(f"{_OLLAMA_MODULE}.get_ollama_base_url") as mock_get_url: mock_get_url.return_value = "http://localhost:11434" with patch(f"{_OLLAMA_MODULE}.OllamaEmbeddings") as mock_ollama: OllamaEmbeddingsProvider.create_embeddings() call_kwargs = mock_ollama.call_args[1] assert call_kwargs["num_ctx"] == 8192 # ── weakref.finalize safety net ───────────────────────────────────────── class TestOllamaEmbeddingsFinalizerSafetyNet: """The provider factory registers a ``weakref.finalize`` so callers that bypass ``LocalEmbeddingManager`` (e.g. the programmatic-API example scripts under ``examples/api_usage/``, direct test constructions) still get cleanup at GC time. The manager-driven explicit close path remains the load-bearing primary cleanup; the finalizer is the last-resort safety net only. """ def test_create_registers_finalizer_with_inner_clients(self): """Provider passes the inner sync/async ``ollama.Client`` objects to ``weakref.finalize`` — not the wrapping ``OllamaEmbeddings`` itself. Passing the instance would defeat the finalizer's purpose by keeping the instance alive through the registry's strong-ref.""" with patch( f"{_OLLAMA_MODULE}.get_setting_from_snapshot" ) as mock_get_setting: mock_get_setting.side_effect = _make_settings_side_effect( { "embeddings.ollama.model": "nomic-embed-text", "embeddings.ollama.num_ctx": 8192, } ) with patch(f"{_OLLAMA_MODULE}.get_ollama_base_url") as mock_get_url: mock_get_url.return_value = "http://localhost:11434" # A simple object (not Mock) so the auto-attr behavior of # Mock doesn't mask a missing _client/_async_client. class _FakeOllamaEmbeddings: def __init__(self): self._client = object() self._async_client = object() fake_instance = _FakeOllamaEmbeddings() with patch( f"{_OLLAMA_MODULE}.OllamaEmbeddings", return_value=fake_instance, ): with patch( f"{_OLLAMA_MODULE}.weakref.finalize" ) as mock_finalize: result = OllamaEmbeddingsProvider.create_embeddings() assert result is fake_instance mock_finalize.assert_called_once() args, _ = mock_finalize.call_args # weakref.finalize(instance, callback, sync, async) assert args[0] is fake_instance assert args[2] is fake_instance._client assert args[3] is fake_instance._async_client def test_finalizer_does_not_crash_if_inner_attrs_missing(self): """A future langchain_ollama version might rename _client / _async_client. The factory must not crash in that case — the explicit close path (or the upstream's own cleanup) remains responsible for resource release. """ with patch( f"{_OLLAMA_MODULE}.get_setting_from_snapshot" ) as mock_get_setting: mock_get_setting.side_effect = _make_settings_side_effect( { "embeddings.ollama.model": "nomic-embed-text", "embeddings.ollama.num_ctx": 8192, } ) with patch(f"{_OLLAMA_MODULE}.get_ollama_base_url") as mock_get_url: mock_get_url.return_value = "http://localhost:11434" # Slotted class with no _client / _async_client → attribute # access raises AttributeError, exercising the except branch. class _ReshapedEmbeddings: __slots__ = () with patch( f"{_OLLAMA_MODULE}.OllamaEmbeddings", return_value=_ReshapedEmbeddings(), ): # Must not raise. result = OllamaEmbeddingsProvider.create_embeddings() assert result is not None def test_real_ollama_embeddings_finalizer_closes_clients_on_gc(self): """End-to-end: construct a real ``langchain_ollama.OllamaEmbeddings`` via the provider, drop the reference, force GC, and verify both inner httpx clients are closed. This is the canary for the #3816-shaped FD leak on the embeddings side. """ import gc import weakref as _weakref with patch( f"{_OLLAMA_MODULE}.get_setting_from_snapshot" ) as mock_get_setting: mock_get_setting.side_effect = _make_settings_side_effect( { "embeddings.ollama.model": "nomic-embed-text", "embeddings.ollama.num_ctx": 8192, } ) with patch(f"{_OLLAMA_MODULE}.get_ollama_base_url") as mock_get_url: mock_get_url.return_value = "http://localhost:1" # No OllamaEmbeddings patch — uses the real class. instance = OllamaEmbeddingsProvider.create_embeddings() sync_httpx = instance._client._client async_httpx = instance._async_client._client assert sync_httpx.is_closed is False assert async_httpx.is_closed is False instance_ref = _weakref.ref(instance) # End the patches before dropping the ref so the finalizer # runs against the real ``_close_inner_ollama_clients``. del instance gc.collect() assert instance_ref() is None, ( "OllamaEmbeddings instance must be GC'able — the finalizer " "must not strong-ref it" ) assert sync_httpx.is_closed is True assert async_httpx.is_closed is True # ── _get_model_capabilities ───────────────────────────────────────────── class TestGetModelCapabilities: """Tests for _get_model_capabilities private helper.""" def test_returns_capabilities_on_success(self): """Returns capability list from /api/show response.""" mock_resp = Mock() mock_resp.status_code = 200 mock_resp.json.return_value = {"capabilities": ["embedding"]} with patch(f"{_OLLAMA_MODULE}.safe_post", return_value=mock_resp): caps = OllamaEmbeddingsProvider._get_model_capabilities( "http://localhost:11434", "nomic-embed-text" ) assert caps == ["embedding"] def test_returns_none_on_http_error(self): """Returns None when server returns non-200.""" mock_resp = Mock() mock_resp.status_code = 404 with patch(f"{_OLLAMA_MODULE}.safe_post", return_value=mock_resp): caps = OllamaEmbeddingsProvider._get_model_capabilities( "http://localhost:11434", "nonexistent-model" ) assert caps is None def test_returns_none_on_exception(self): """Returns None when request raises exception.""" with patch( f"{_OLLAMA_MODULE}.safe_post", side_effect=Exception("connection refused"), ): caps = OllamaEmbeddingsProvider._get_model_capabilities( "http://localhost:11434", "nomic-embed-text" ) assert caps is None def test_returns_none_when_capabilities_missing(self): """Returns None when response lacks capabilities field.""" mock_resp = Mock() mock_resp.status_code = 200 mock_resp.json.return_value = {"details": {"family": "nomic-bert"}} with patch(f"{_OLLAMA_MODULE}.safe_post", return_value=mock_resp): caps = OllamaEmbeddingsProvider._get_model_capabilities( "http://localhost:11434", "nomic-embed-text" ) assert caps is None def test_returns_completion_for_llm(self): """Returns completion capabilities for LLM models.""" mock_resp = Mock() mock_resp.status_code = 200 mock_resp.json.return_value = {"capabilities": ["completion", "tools"]} with patch(f"{_OLLAMA_MODULE}.safe_post", return_value=mock_resp): caps = OllamaEmbeddingsProvider._get_model_capabilities( "http://localhost:11434", "qwen3:4b" ) assert caps == ["completion", "tools"] # ── is_embedding_model ─────────────────────────────────────────────────── class TestIsEmbeddingModel: """Tests for is_embedding_model method.""" def test_returns_true_for_embedding_model(self): """Returns True when capabilities include 'embedding'.""" with patch( f"{_OLLAMA_MODULE}.get_ollama_base_url", return_value="http://localhost:11434", ): with patch.object( OllamaEmbeddingsProvider, "_get_model_capabilities", return_value=["embedding"], ): result = OllamaEmbeddingsProvider.is_embedding_model( "nomic-embed-text" ) assert result is True def test_returns_false_for_llm_model(self): """Returns False when capabilities don't include 'embedding'.""" with patch( f"{_OLLAMA_MODULE}.get_ollama_base_url", return_value="http://localhost:11434", ): with patch.object( OllamaEmbeddingsProvider, "_get_model_capabilities", return_value=["completion", "tools"], ): result = OllamaEmbeddingsProvider.is_embedding_model("qwen3:4b") assert result is False def test_returns_none_when_capabilities_unavailable(self): """Older Ollama (no capabilities in /api/show) → None, not a guess. We refuse to guess from the model name; the caller can decide whether to tag the model, hide it, or just trust the user. """ with patch( f"{_OLLAMA_MODULE}.get_ollama_base_url", return_value="http://localhost:11434", ): with patch.object( OllamaEmbeddingsProvider, "_get_model_capabilities", return_value=None, ): assert ( OllamaEmbeddingsProvider.is_embedding_model( "nomic-embed-text" ) is None ) assert ( OllamaEmbeddingsProvider.is_embedding_model( "deepseek-r1:32b" ) is None ) # ── get_available_models ───────────────────────────────────────────────── class TestOllamaEmbeddingsGetAvailableModels: """Tests for get_available_models method.""" def _setup_mocks(self, all_models, caps_by_model): """Helper: patch base_url, fetch_ollama_models, and capabilities.""" return ( patch( f"{_OLLAMA_MODULE}.get_ollama_base_url", return_value="http://localhost:11434", ), patch( f"{_LLM_UTILS}.fetch_ollama_models", return_value=all_models, ), patch.object( OllamaEmbeddingsProvider, "_get_model_capabilities", side_effect=_mock_capabilities(caps_by_model), ), ) def test_embedding_models_sorted_first(self): """Embedding models appear before LLM models.""" all_models = [ {"value": "qwen3:4b", "label": "qwen3:4b"}, { "value": "nomic-embed-text:latest", "label": "nomic-embed-text:latest", }, {"value": "deepseek-r1:32b", "label": "deepseek-r1:32b"}, ] caps = { "qwen3:4b": ["completion", "tools"], "nomic-embed-text:latest": ["embedding"], "deepseek-r1:32b": ["completion"], } p1, p2, p3 = self._setup_mocks(all_models, caps) with p1, p2, p3: result = OllamaEmbeddingsProvider.get_available_models() assert result[0]["value"] == "nomic-embed-text:latest" assert result[0]["is_embedding"] is True def test_all_models_returned_with_flag(self): """All models are returned, each with an is_embedding flag.""" all_models = [ { "value": "nomic-embed-text:latest", "label": "nomic-embed-text:latest", }, {"value": "qwen3:4b", "label": "qwen3:4b"}, ] caps = { "nomic-embed-text:latest": ["embedding"], "qwen3:4b": ["completion"], } p1, p2, p3 = self._setup_mocks(all_models, caps) with p1, p2, p3: result = OllamaEmbeddingsProvider.get_available_models() assert len(result) == 2 assert all("is_embedding" in m for m in result) embed_models = [m for m in result if m["is_embedding"]] llm_models = [m for m in result if not m["is_embedding"]] assert len(embed_models) == 1 assert len(llm_models) == 1 def test_empty_model_list(self): """Returns empty list when no models available.""" p1, p2, p3 = self._setup_mocks([], {}) with p1, p2, p3: result = OllamaEmbeddingsProvider.get_available_models() assert result == [] def test_all_llms_still_returned(self): """Even if no embedding models exist, all LLMs are returned.""" all_models = [ {"value": "qwen3:4b", "label": "qwen3:4b"}, {"value": "deepseek-r1:32b", "label": "deepseek-r1:32b"}, ] caps = { "qwen3:4b": ["completion"], "deepseek-r1:32b": ["completion"], } p1, p2, p3 = self._setup_mocks(all_models, caps) with p1, p2, p3: result = OllamaEmbeddingsProvider.get_available_models() assert len(result) == 2 assert all(m["is_embedding"] is False for m in result) def test_capabilities_unavailable_returns_untagged_models(self): """Older Ollama (no /api/show capabilities) → every model is still listed, just without an ``is_embedding`` flag. The provider doesn't guess from the name.""" all_models = [ { "value": "nomic-embed-text:latest", "label": "nomic-embed-text:latest", }, {"value": "qwen3:4b", "label": "qwen3:4b"}, ] caps = {} # No model has capabilities → all untagged p1, p2, p3 = self._setup_mocks(all_models, caps) with p1, p2, p3: result = OllamaEmbeddingsProvider.get_available_models() assert {m["value"] for m in result} == { "nomic-embed-text:latest", "qwen3:4b", } assert all("is_embedding" not in m for m in result) def test_multiple_embedding_models(self): """Multiple embedding models are all sorted first.""" all_models = [ {"value": "qwen3:4b", "label": "qwen3:4b"}, { "value": "nomic-embed-text:latest", "label": "nomic-embed-text:latest", }, { "value": "mxbai-embed-large:latest", "label": "mxbai-embed-large:latest", }, {"value": "deepseek-r1:32b", "label": "deepseek-r1:32b"}, ] caps = { "qwen3:4b": ["completion"], "nomic-embed-text:latest": ["embedding"], "mxbai-embed-large:latest": ["embedding"], "deepseek-r1:32b": ["completion"], } p1, p2, p3 = self._setup_mocks(all_models, caps) with p1, p2, p3: result = OllamaEmbeddingsProvider.get_available_models() # First two should be embedding models assert result[0]["is_embedding"] is True assert result[1]["is_embedding"] is True assert result[2]["is_embedding"] is False assert result[3]["is_embedding"] is False def test_result_preserves_value_and_label(self): """Original value and label fields are preserved.""" all_models = [ { "value": "nomic-embed-text:latest", "label": "nomic-embed-text:latest", }, ] caps = {"nomic-embed-text:latest": ["embedding"]} p1, p2, p3 = self._setup_mocks(all_models, caps) with p1, p2, p3: result = OllamaEmbeddingsProvider.get_available_models() assert result[0]["value"] == "nomic-embed-text:latest" assert result[0]["label"] == "nomic-embed-text:latest" assert result[0]["is_embedding"] is True # ── provider info & config ─────────────────────────────────────────────── class TestOllamaEmbeddingsProviderInfo: """Tests for get_provider_info method.""" def test_provider_info_structure(self): """get_provider_info returns expected structure.""" info = OllamaEmbeddingsProvider.get_provider_info() assert "name" in info assert "key" in info assert "requires_api_key" in info assert "supports_local" in info assert "default_model" in info assert info["name"] == "Ollama" assert info["key"] == "OLLAMA" assert info["requires_api_key"] is False assert info["supports_local"] is True class TestOllamaEmbeddingsValidateConfig: """Tests for validate_config method.""" def test_validate_config_when_available(self): """validate_config returns True when available.""" with patch.object( OllamaEmbeddingsProvider, "is_available", return_value=True ): is_valid, error = OllamaEmbeddingsProvider.validate_config() assert is_valid is True assert error is None def test_validate_config_when_not_available(self): """validate_config returns False when not available.""" with patch.object( OllamaEmbeddingsProvider, "is_available", return_value=False ): is_valid, error = OllamaEmbeddingsProvider.validate_config() assert is_valid is False assert error is not None assert "not available" in error