21ec9cb4c3
- Add auth_user, admin_user, mock_minio_presigned, mock_minio_stat fixtures to conftest.py - Create test_quota.py with 4 xfail stubs (STORE-03, STORE-05, STORE-06, SC2 race) - Append test_migration_0003 to test_alembic.py (full pre-seed + post-migration assertions) - Append 3 classifier xfail stubs (DOC-03, DOC-05, D-15) - Append 6 document xfail stubs (D-05, STORE-04, SEC-04, D-16) - Append 4 topic xfail stubs (DOC-04, D-09, D-17) - Append test_settings_endpoint_removed stub (D-12) - All 19 new test IDs collect cleanly with xfail(strict=False)
124 lines
4.7 KiB
Python
124 lines
4.7 KiB
Python
"""
|
|
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
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Wave 0 xfail stub — D-12: /api/settings endpoint removed in Plan 03-04
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@pytest.mark.xfail(strict=False, reason="implemented in plan 03-04")
|
|
async def test_settings_endpoint_removed(async_client):
|
|
"""GET /api/settings returns 404 after the flat-file settings system is retired.
|
|
|
|
D-12: the /api/settings endpoint is removed entirely in Phase 3. All AI config
|
|
comes from the database (users.ai_provider / users.ai_model set by admin).
|
|
The flat-file services/storage.py load_settings()/save_settings() functions
|
|
are also deleted (CONTEXT.md D-12).
|
|
"""
|
|
assert True # scaffold
|