app.js: default_stt/llm/tts_provider und default_language von 'select' auf 'combo' umgestellt, damit neben den vordefinierten Optionen jederzeit ein beliebiger eigener Wert eingegeben werden kann (Zukunftssicherheit). combo-Renderer unterstützt jetzt auch meta.labels (Sprachkürzel → Klarname). tests/test_admin_settings.py: 28 Tests für GET/PUT/DELETE /api/admin/config — Auth, bekannte Werte, Custom-Values, Persistenz, Runtime-Wirkung. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
191 lines
6.8 KiB
Python
191 lines
6.8 KiB
Python
"""Tests für den Admin-Einstellungen-Tab (Provider & Sprache + Laufzeit-Config).
|
|
|
|
Getestet: GET /api/admin/config, PUT /api/admin/config/{key},
|
|
DELETE /api/admin/config/{key} — Auth, Custom-Values, Persistenz.
|
|
"""
|
|
|
|
import pytest
|
|
from fastapi.testclient import TestClient
|
|
|
|
import app.dependencies as deps
|
|
from app.main import app
|
|
from app.config import settings
|
|
from app.runtime_config import RUNTIME_SETTABLE, invalidate_cache
|
|
|
|
client = TestClient(app)
|
|
ADMIN = "test-admin-key"
|
|
ADM_HDR = {"X-Admin-Key": ADMIN}
|
|
|
|
PROVIDER_KEYS = [
|
|
"default_stt_provider",
|
|
"default_llm_provider",
|
|
"default_tts_provider",
|
|
"default_language",
|
|
]
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def _setup(monkeypatch):
|
|
monkeypatch.setattr(settings, "admin_api_key", ADMIN)
|
|
monkeypatch.setattr(settings, "auth_enabled", False)
|
|
invalidate_cache()
|
|
yield
|
|
invalidate_cache()
|
|
|
|
|
|
# ── GET /api/admin/config ────────────────────────────────────────────────────
|
|
|
|
def test_get_config_returns_all_keys():
|
|
resp = client.get("/api/admin/config", headers=ADM_HDR)
|
|
assert resp.status_code == 200
|
|
keys = {e["key"] for e in resp.json()}
|
|
assert keys == set(RUNTIME_SETTABLE.keys())
|
|
|
|
|
|
def test_get_config_requires_admin_key():
|
|
resp = client.get("/api/admin/config")
|
|
assert resp.status_code == 401
|
|
|
|
|
|
def test_get_config_provider_keys_present():
|
|
resp = client.get("/api/admin/config", headers=ADM_HDR)
|
|
keys = {e["key"] for e in resp.json()}
|
|
for k in PROVIDER_KEYS:
|
|
assert k in keys, f"Schlüssel {k!r} fehlt in /api/admin/config"
|
|
|
|
|
|
def test_get_config_entry_structure():
|
|
resp = client.get("/api/admin/config", headers=ADM_HDR)
|
|
entry = next(e for e in resp.json() if e["key"] == "default_stt_provider")
|
|
assert "label" in entry
|
|
assert "hint" in entry
|
|
assert "effective_value" in entry
|
|
assert "base_value" in entry
|
|
assert "is_overridden" in entry
|
|
assert entry["is_overridden"] is False
|
|
|
|
|
|
def test_get_config_not_overridden_initially():
|
|
resp = client.get("/api/admin/config", headers=ADM_HDR)
|
|
for entry in resp.json():
|
|
if entry["key"] in PROVIDER_KEYS:
|
|
assert not entry["is_overridden"], (
|
|
f"{entry['key']} sollte initial nicht überschrieben sein"
|
|
)
|
|
|
|
|
|
# ── PUT /api/admin/config/{key} ──────────────────────────────────────────────
|
|
|
|
@pytest.mark.parametrize("key,value", [
|
|
("default_stt_provider", "openrouter"),
|
|
("default_stt_provider", "faster-whisper"),
|
|
("default_llm_provider", "openrouter"),
|
|
("default_llm_provider", "local-openai-compatible"),
|
|
("default_tts_provider", "openrouter"),
|
|
("default_tts_provider", "piper"),
|
|
("default_tts_provider", "chatterbox"),
|
|
("default_language", "de"),
|
|
("default_language", "en"),
|
|
])
|
|
def test_put_known_value_accepted(key, value):
|
|
resp = client.put(f"/api/admin/config/{key}", headers=ADM_HDR, json={"value": value})
|
|
assert resp.status_code == 200
|
|
assert resp.json()["value"] == value
|
|
|
|
|
|
@pytest.mark.parametrize("key,custom_value", [
|
|
("default_stt_provider", "my-custom-stt"),
|
|
("default_llm_provider", "local-openai-compatible-v2"),
|
|
("default_tts_provider", "future-tts-engine"),
|
|
("default_language", "pl"),
|
|
])
|
|
def test_put_custom_value_accepted(key, custom_value):
|
|
"""Eigener Wert (nicht in der Dropdown-Liste) muss akzeptiert werden."""
|
|
resp = client.put(f"/api/admin/config/{key}", headers=ADM_HDR, json={"value": custom_value})
|
|
assert resp.status_code == 200
|
|
assert resp.json()["value"] == custom_value
|
|
|
|
|
|
def test_put_custom_value_reflected_in_get():
|
|
"""Gespeicherter Custom-Wert erscheint in effective_value."""
|
|
client.put("/api/admin/config/default_stt_provider", headers=ADM_HDR,
|
|
json={"value": "my-new-stt"})
|
|
invalidate_cache()
|
|
resp = client.get("/api/admin/config", headers=ADM_HDR)
|
|
entry = next(e for e in resp.json() if e["key"] == "default_stt_provider")
|
|
assert entry["effective_value"] == "my-new-stt"
|
|
assert entry["is_overridden"] is True
|
|
|
|
|
|
def test_put_unknown_key_returns_400():
|
|
resp = client.put("/api/admin/config/nonexistent_key", headers=ADM_HDR,
|
|
json={"value": "x"})
|
|
assert resp.status_code == 400
|
|
|
|
|
|
def test_put_trims_whitespace():
|
|
resp = client.put("/api/admin/config/default_stt_provider", headers=ADM_HDR,
|
|
json={"value": " openrouter "})
|
|
assert resp.status_code == 200
|
|
assert resp.json()["value"] == "openrouter"
|
|
|
|
|
|
def test_put_requires_admin_key():
|
|
resp = client.put("/api/admin/config/default_stt_provider", json={"value": "x"})
|
|
assert resp.status_code == 401
|
|
|
|
|
|
# ── DELETE /api/admin/config/{key} ──────────────────────────────────────────
|
|
|
|
def test_delete_removes_override():
|
|
client.put("/api/admin/config/default_tts_provider", headers=ADM_HDR,
|
|
json={"value": "future-engine"})
|
|
invalidate_cache()
|
|
|
|
del_resp = client.delete("/api/admin/config/default_tts_provider", headers=ADM_HDR)
|
|
assert del_resp.status_code == 200
|
|
assert del_resp.json()["deleted"] == "default_tts_provider"
|
|
|
|
invalidate_cache()
|
|
cfg = {e["key"]: e for e in
|
|
client.get("/api/admin/config", headers=ADM_HDR).json()}
|
|
assert not cfg["default_tts_provider"]["is_overridden"]
|
|
|
|
|
|
def test_delete_nonexistent_override_returns_404():
|
|
resp = client.delete("/api/admin/config/default_tts_provider", headers=ADM_HDR)
|
|
assert resp.status_code == 404
|
|
|
|
|
|
def test_delete_unknown_key_returns_400():
|
|
resp = client.delete("/api/admin/config/does_not_exist", headers=ADM_HDR)
|
|
assert resp.status_code == 400
|
|
|
|
|
|
def test_delete_requires_admin_key():
|
|
resp = client.delete("/api/admin/config/default_tts_provider")
|
|
assert resp.status_code == 401
|
|
|
|
|
|
# ── Persistenz: Override wirkt sich auf Runtime aus ──────────────────────────
|
|
|
|
def test_override_reflected_in_runtime_settings():
|
|
from app.runtime_config import runtime_settings
|
|
client.put("/api/admin/config/default_language", headers=ADM_HDR,
|
|
json={"value": "fr"})
|
|
invalidate_cache()
|
|
assert runtime_settings.default_language == "fr"
|
|
|
|
|
|
def test_delete_restores_base_value():
|
|
from app.runtime_config import runtime_settings
|
|
base = str(settings.default_language)
|
|
|
|
client.put("/api/admin/config/default_language", headers=ADM_HDR,
|
|
json={"value": "it"})
|
|
invalidate_cache()
|
|
assert runtime_settings.default_language == "it"
|
|
|
|
client.delete("/api/admin/config/default_language", headers=ADM_HDR)
|
|
invalidate_cache()
|
|
assert str(runtime_settings.default_language) == base
|