From bafdafea02dad778f2922dc5a942286d57e872e2 Mon Sep 17 00:00:00 2001 From: curo1305 Date: Tue, 19 May 2026 10:53:22 +0200 Subject: [PATCH] test: add unit tests for wizard model-discovery helpers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cover _fetch_local_models (LM Studio and Ollama parsing, error paths, missing base_url), _fetch_lmstudio_available_models (happy path and errors), and _load_lmstudio_model (success, API failure, exception). All mocked via monkeypatch/MagicMock — no real HTTP calls. Co-Authored-By: Claude Sonnet 4.6 --- tests/unit/test_setup_wizard.py | 93 ++++++++++++++++++++++++++++++++- 1 file changed, 92 insertions(+), 1 deletion(-) diff --git a/tests/unit/test_setup_wizard.py b/tests/unit/test_setup_wizard.py index 2c852e1..88e46c9 100644 --- a/tests/unit/test_setup_wizard.py +++ b/tests/unit/test_setup_wizard.py @@ -1,4 +1,6 @@ -"""Tests for setup wizard personalization helpers.""" +"""Tests for setup wizard personalization and model-discovery helpers.""" + +from unittest.mock import MagicMock import pytest @@ -90,3 +92,92 @@ def test_suggest_plugins_multiple_categories(monkeypatch): combined = " ".join(str(p) for p in panels) assert "email" in combined assert "ssh_tool" in combined + + +# ── _fetch_local_models ──────────────────────────────────────────────────────── + +def test_fetch_local_models_lmstudio_returns_model_ids(monkeypatch): + import pyra.setup.wizard as wiz + mock_resp = MagicMock() + mock_resp.json.return_value = {"data": [{"id": "gemma-4b"}, {"id": "llama3"}]} + mock_resp.raise_for_status = lambda: None + monkeypatch.setattr(wiz.httpx, "get", lambda *a, **kw: mock_resp) + from pyra.setup.providers import get_provider + assert wiz._fetch_local_models(get_provider("lmstudio")) == ["gemma-4b", "llama3"] + + +def test_fetch_local_models_ollama_returns_model_names(monkeypatch): + import pyra.setup.wizard as wiz + mock_resp = MagicMock() + mock_resp.json.return_value = {"models": [{"name": "llama3:latest"}, {"name": "mistral"}]} + mock_resp.raise_for_status = lambda: None + monkeypatch.setattr(wiz.httpx, "get", lambda *a, **kw: mock_resp) + from pyra.setup.providers import get_provider + assert wiz._fetch_local_models(get_provider("ollama")) == ["llama3:latest", "mistral"] + + +def test_fetch_local_models_returns_empty_on_connection_error(monkeypatch): + import pyra.setup.wizard as wiz + monkeypatch.setattr(wiz.httpx, "get", MagicMock(side_effect=Exception("conn refused"))) + from pyra.setup.providers import get_provider + assert wiz._fetch_local_models(get_provider("lmstudio")) == [] + + +def test_fetch_local_models_returns_empty_when_no_base_url(): + import pyra.setup.wizard as wiz + from pyra.setup.providers import Provider + provider = Provider( + id="test", display_name="Test", requires_key=False, + default_model="x", litellm_prefix="openai/", group="Local", + ) + assert wiz._fetch_local_models(provider) == [] + + +# ── _fetch_lmstudio_available_models ────────────────────────────────────────── + +def test_fetch_lmstudio_available_models_returns_ids(monkeypatch): + import pyra.setup.wizard as wiz + mock_resp = MagicMock() + mock_resp.json.return_value = {"data": [{"id": "model-a"}, {"id": "model-b"}]} + mock_resp.raise_for_status = lambda: None + monkeypatch.setattr(wiz.httpx, "get", lambda *a, **kw: mock_resp) + assert wiz._fetch_lmstudio_available_models() == ["model-a", "model-b"] + + +def test_fetch_lmstudio_available_models_returns_empty_on_error(monkeypatch): + import pyra.setup.wizard as wiz + monkeypatch.setattr(wiz.httpx, "get", MagicMock(side_effect=Exception("not found"))) + assert wiz._fetch_lmstudio_available_models() == [] + + +def test_fetch_lmstudio_available_models_empty_data(monkeypatch): + import pyra.setup.wizard as wiz + mock_resp = MagicMock() + mock_resp.json.return_value = {"data": []} + mock_resp.raise_for_status = lambda: None + monkeypatch.setattr(wiz.httpx, "get", lambda *a, **kw: mock_resp) + assert wiz._fetch_lmstudio_available_models() == [] + + +# ── _load_lmstudio_model ────────────────────────────────────────────────────── + +def test_load_lmstudio_model_returns_true_on_success(monkeypatch): + import pyra.setup.wizard as wiz + mock_resp = MagicMock() + mock_resp.is_success = True + monkeypatch.setattr(wiz.httpx, "post", lambda *a, **kw: mock_resp) + assert wiz._load_lmstudio_model("gemma-4b") is True + + +def test_load_lmstudio_model_returns_false_on_api_failure(monkeypatch): + import pyra.setup.wizard as wiz + mock_resp = MagicMock() + mock_resp.is_success = False + monkeypatch.setattr(wiz.httpx, "post", lambda *a, **kw: mock_resp) + assert wiz._load_lmstudio_model("gemma-4b") is False + + +def test_load_lmstudio_model_returns_false_on_exception(monkeypatch): + import pyra.setup.wizard as wiz + monkeypatch.setattr(wiz.httpx, "post", MagicMock(side_effect=Exception("timeout"))) + assert wiz._load_lmstudio_model("gemma-4b") is False