Spaces:
Running
Running
File size: 8,363 Bytes
92bfe31 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 | """
Route-level tests for the /api/admin/model-config endpoints.
Follows the auth mock pattern from test_api.py.
"""
import os
from unittest.mock import MagicMock, patch
import pytest
from fastapi.testclient import TestClient
import main as main_module
from main import app
from services.inference_client import reset_runtime_overrides
main_module._firebase_ready = True
main_module._init_firebase_admin = lambda: None
main_module.firebase_firestore = None
main_module.firebase_auth = MagicMock()
main_module.firebase_auth.verify_id_token = MagicMock(return_value={
"uid": "test-teacher-uid",
"email": "teacher@example.com",
"role": "teacher",
})
admin_client = TestClient(app, headers={"Authorization": "Bearer admin-token"})
_RESOLVED_KEYS = {
"INFERENCE_MODEL_ID", "INFERENCE_CHAT_MODEL_ID",
"HF_QUIZ_MODEL_ID", "HF_RAG_MODEL_ID", "INFERENCE_LOCK_MODEL_ID",
}
_KNOWN_PROFILES = {"dev", "budget", "prod"}
_BASE_CONFIG_KEYS = {"profile", "overrides", "resolved"}
@pytest.fixture(autouse=True)
def _mock_firestore():
with patch("services.inference_client._save_runtime_config_to_firestore", side_effect=None):
yield
@pytest.fixture(autouse=True)
def _reset_overrides():
reset_runtime_overrides()
yield
reset_runtime_overrides()
# โโโ Auth Enforcement โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
class TestAuth:
def test_get_rejects_bad_token(self):
main_module.firebase_auth.verify_id_token = MagicMock(side_effect=Exception("bad"))
c = TestClient(app, headers={"Authorization": "Bearer bad-token"})
response = c.get("/api/admin/model-config")
main_module.firebase_auth.verify_id_token = MagicMock(return_value={
"uid": "admin-uid", "email": "admin@example.com", "role": "admin",
})
assert response.status_code in {401, 403}
def test_get_rejects_student_role(self):
main_module.firebase_auth.verify_id_token = MagicMock(return_value={
"uid": "student-uid", "email": "s@example.com", "role": "student",
})
c = TestClient(app, headers={"Authorization": "Bearer student-token"})
response = c.get("/api/admin/model-config")
main_module.firebase_auth.verify_id_token = MagicMock(return_value={
"uid": "admin-uid", "email": "admin@example.com", "role": "admin",
})
assert response.status_code == 403
# โโโ GET Model Config โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
class TestGetModelConfig:
def test_returns_base_keys(self):
response = admin_client.get("/api/admin/model-config")
assert response.status_code == 200
data = response.json()
for key in _BASE_CONFIG_KEYS:
assert key in data
def test_resolved_contains_expected_keys(self):
response = admin_client.get("/api/admin/model-config")
data = response.json()
resolved = data.get("resolved", {})
for key in _RESOLVED_KEYS:
assert key in resolved
def test_available_profiles_present(self):
response = admin_client.get("/api/admin/model-config")
data = response.json()
profiles = data.get("availableProfiles", [])
for p in _KNOWN_PROFILES:
assert p in profiles
def test_profile_descriptions_present(self):
response = admin_client.get("/api/admin/model-config")
data = response.json()
descriptions = data.get("profileDescriptions", {})
for p in _KNOWN_PROFILES:
assert p in descriptions
def test_resolved_models_are_non_empty_strings(self):
admin_client.post("/api/admin/model-config/profile", json={"profile": "dev"})
response = admin_client.get("/api/admin/model-config")
data = response.json()
resolved = data.get("resolved", {})
for key, value in resolved.items():
assert isinstance(value, str), f"{key} is not a string: {value}"
assert len(value) > 0, f"Resolved key {key} is empty"
# โโโ POST Profile Switch โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
class TestPostProfileSwitch:
def test_switch_to_dev_succeeds(self):
response = admin_client.post("/api/admin/model-config/profile", json={"profile": "dev"})
assert response.status_code == 200
assert response.json()["success"] is True
def test_switch_to_budget_succeeds(self):
response = admin_client.post("/api/admin/model-config/profile", json={"profile": "budget"})
assert response.status_code == 200
data = response.json()
assert data["success"] is True
assert data["applied"]["profile"] == "budget"
def test_switch_to_prod_succeeds(self):
response = admin_client.post("/api/admin/model-config/profile", json={"profile": "prod"})
assert response.status_code == 200
data = response.json()
assert data["success"] is True
assert data["applied"]["profile"] == "prod"
def test_switch_to_invalid_profile_returns_400(self):
response = admin_client.post("/api/admin/model-config/profile", json={"profile": "nonexistent"})
assert response.status_code == 400
def test_switch_missing_profile_field(self):
response = admin_client.post("/api/admin/model-config/profile", json={})
assert response.status_code == 422
# โโโ POST Override โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
class TestPostOverride:
def test_set_valid_override_key_succeeds(self):
response = admin_client.post(
"/api/admin/model-config/override",
json={"key": "INFERENCE_MODEL_ID", "value": "test/override-model"},
)
assert response.status_code == 200
assert response.json()["success"] is True
def test_set_invalid_override_key_returns_400(self):
response = admin_client.post(
"/api/admin/model-config/override",
json={"key": "EMBEDDING_MODEL", "value": "test/emb"},
)
assert response.status_code == 400
def test_override_is_visible_in_subsequent_get(self):
admin_client.post(
"/api/admin/model-config/override",
json={"key": "INFERENCE_MODEL_ID", "value": "custom/model-v2"},
)
response = admin_client.get("/api/admin/model-config")
data = response.json()
overrides = data.get("overrides", {})
assert "INFERENCE_MODEL_ID" in overrides
assert overrides["INFERENCE_MODEL_ID"] == "custom/model-v2"
# โโโ DELETE Reset โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
class TestDeleteReset:
def test_reset_returns_success(self):
response = admin_client.delete("/api/admin/model-config/reset")
assert response.status_code == 200
assert response.json()["success"] is True
def test_reset_clears_override(self):
admin_client.post(
"/api/admin/model-config/override",
json={"key": "INFERENCE_MODEL_ID", "value": "temp/model"},
)
response = admin_client.delete("/api/admin/model-config/reset")
assert response.status_code == 200
overrides = response.json()["current"]["overrides"]
assert overrides == {}
def test_reset_clears_profile(self):
admin_client.post("/api/admin/model-config/profile", json={"profile": "budget"})
response = admin_client.delete("/api/admin/model-config/reset")
assert response.status_code == 200
assert response.json()["current"]["profile"] == ""
# โโโ Profile after switch โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
class TestProfileAfterSwitch:
def test_switched_profile_visible_in_get(self):
admin_client.post("/api/admin/model-config/profile", json={"profile": "dev"})
response = admin_client.get("/api/admin/model-config")
assert response.json()["profile"] == "dev" |