""" Settings API tests — async only (Plan 05 cutover). Settings remain flat-file backed in Phase 1 (D-03 deferred), so these tests use async_client but do not require a real database session. """ from __future__ import annotations import pytest async def test_get_settings_defaults(async_client, tmp_path, monkeypatch): # Point SETTINGS_FILE at a temp dir so tests don't clobber each other import config as cfg monkeypatch.setattr(cfg, "SETTINGS_FILE", tmp_path / "settings.json") import services.storage as st monkeypatch.setattr(st, "SETTINGS_FILE", tmp_path / "settings.json") resp = await async_client.get("/api/settings") assert resp.status_code == 200 data = resp.json() assert data["active_provider"] == "lmstudio" assert "system_prompt" in data assert "providers" in data async def test_patch_system_prompt(async_client, tmp_path, monkeypatch): import config as cfg monkeypatch.setattr(cfg, "SETTINGS_FILE", tmp_path / "settings.json") import services.storage as st monkeypatch.setattr(st, "SETTINGS_FILE", tmp_path / "settings.json") new_prompt = "Custom system prompt for testing." resp = await async_client.patch("/api/settings", json={"system_prompt": new_prompt}) assert resp.status_code == 200 resp2 = await async_client.get("/api/settings") assert resp2.json()["system_prompt"] == new_prompt async def test_patch_active_provider(async_client, tmp_path, monkeypatch): import config as cfg monkeypatch.setattr(cfg, "SETTINGS_FILE", tmp_path / "settings.json") import services.storage as st monkeypatch.setattr(st, "SETTINGS_FILE", tmp_path / "settings.json") resp = await async_client.patch("/api/settings", json={"active_provider": "ollama"}) assert resp.status_code == 200 assert resp.json()["active_provider"] == "ollama" async def test_patch_invalid_provider(async_client, tmp_path, monkeypatch): import config as cfg monkeypatch.setattr(cfg, "SETTINGS_FILE", tmp_path / "settings.json") import services.storage as st monkeypatch.setattr(st, "SETTINGS_FILE", tmp_path / "settings.json") resp = await async_client.patch("/api/settings", json={"active_provider": "unknown"}) assert resp.status_code == 400 async def test_patch_provider_config(async_client, tmp_path, monkeypatch): import config as cfg monkeypatch.setattr(cfg, "SETTINGS_FILE", tmp_path / "settings.json") import services.storage as st monkeypatch.setattr(st, "SETTINGS_FILE", tmp_path / "settings.json") resp = await async_client.patch( "/api/settings", json={ "providers": { "ollama": {"model": "mistral", "base_url": "http://host.docker.internal:11434"} } }, ) assert resp.status_code == 200 assert resp.json()["providers"]["ollama"]["model"] == "mistral" async def test_masked_api_key_not_overwritten(async_client, tmp_path, monkeypatch): """Patching with a masked key should not overwrite the real stored key.""" import config as cfg monkeypatch.setattr(cfg, "SETTINGS_FILE", tmp_path / "settings.json") import services.storage as st monkeypatch.setattr(st, "SETTINGS_FILE", tmp_path / "settings.json") # First set a real key await async_client.patch( "/api/settings", json={"providers": {"anthropic": {"api_key": "sk-ant-realkey"}}}, ) # Then patch with masked key (simulating frontend re-submitting) await async_client.patch( "/api/settings", json={"providers": {"anthropic": {"api_key": "****key"}}}, ) # The stored key should still be the real one stored = st.load_settings() assert stored["providers"]["anthropic"]["api_key"] == "sk-ant-realkey" async def test_get_default_prompt(async_client): resp = await async_client.get("/api/settings/default-prompt") assert resp.status_code == 200 assert "system_prompt" in resp.json() assert len(resp.json()["system_prompt"]) > 0