IIIF-Studio / backend /tests /test_api_models.py
Claude
fix(sprint-f0): fondations — lazy imports, schémas conformes, pydantic-settings
193eb98 unverified
"""
Tests des endpoints /api/v1/models/* (Sprint 4 — Session B).
Stratégie :
- Appels Google AI mockés via monkeypatch sur list_all_models (nom local dans models_api_module)
- BDD SQLite en mémoire pour les endpoints qui touchent la BDD (PUT/GET model)
Vérifie :
- GET /api/v1/models → supprimé (404) — remplacé par /providers
- POST /api/v1/models/refresh → mise à jour + timestamp
- PUT /api/v1/corpora/{id}/model → création + mise à jour
- GET /api/v1/corpora/{id}/model → 200 ou 404
"""
# 1. stdlib
import uuid
from datetime import datetime, timezone
# 2. third-party
import pytest
# 3. local
import app.api.v1.models_api as models_api_module
from app.models.corpus import CorpusModel
from app.schemas.model_config import ModelInfo, ProviderType
from tests.conftest_api import async_client, db_session # noqa: F401
_NOW = datetime.now(timezone.utc)
_MOCK_MODELS = [
ModelInfo(
model_id="gemini-2.0-flash",
display_name="Gemini 2.0 Flash",
provider=ProviderType.GOOGLE_AI_STUDIO,
supports_vision=True,
input_token_limit=1_000_000,
output_token_limit=8192,
),
ModelInfo(
model_id="gemini-1.5-pro",
display_name="Gemini 1.5 Pro",
provider=ProviderType.GOOGLE_AI_STUDIO,
supports_vision=True,
input_token_limit=2_000_000,
output_token_limit=8192,
),
]
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
async def _make_corpus(db, slug="models-test"):
corpus = CorpusModel(
id=str(uuid.uuid4()), slug=slug, title="Models Test",
profile_id="medieval-illuminated", created_at=_NOW, updated_at=_NOW,
)
db.add(corpus)
await db.commit()
await db.refresh(corpus)
return corpus
# ---------------------------------------------------------------------------
# POST /api/v1/settings/api-key → supprimé (clés dans secrets HF, R06)
# ---------------------------------------------------------------------------
@pytest.mark.asyncio
async def test_settings_api_key_endpoint_removed(async_client):
"""L'endpoint /api/v1/settings/api-key ne doit plus exister (404 ou 405)."""
response = await async_client.post(
"/api/v1/settings/api-key",
json={"api_key": "AIza-test", "provider_type": "google_ai_studio"},
)
assert response.status_code in (404, 405)
# ---------------------------------------------------------------------------
# GET /api/v1/models → supprimé (remplacé par /providers)
# ---------------------------------------------------------------------------
@pytest.mark.asyncio
async def test_get_models_endpoint_removed(async_client):
"""GET /api/v1/models ne doit plus retourner de données (remplacé par /providers)."""
resp = await async_client.get("/api/v1/models")
# L'endpoint n'existe pas : FastAPI retourne 404/405 ou le catch-all renvoie 307
assert resp.status_code != 200
# ---------------------------------------------------------------------------
# POST /api/v1/models/refresh
# ---------------------------------------------------------------------------
@pytest.mark.asyncio
async def test_refresh_models_ok(async_client, monkeypatch):
monkeypatch.setattr(
"app.services.ai.model_registry.list_all_models", lambda: _MOCK_MODELS
)
response = await async_client.post("/api/v1/models/refresh")
assert response.status_code == 200
@pytest.mark.asyncio
async def test_refresh_models_has_timestamp(async_client, monkeypatch):
monkeypatch.setattr(
"app.services.ai.model_registry.list_all_models", lambda: _MOCK_MODELS
)
data = (await async_client.post("/api/v1/models/refresh")).json()
assert "refreshed_at" in data
assert data["refreshed_at"] # non-vide
@pytest.mark.asyncio
async def test_refresh_models_count(async_client, monkeypatch):
monkeypatch.setattr(
"app.services.ai.model_registry.list_all_models", lambda: _MOCK_MODELS
)
data = (await async_client.post("/api/v1/models/refresh")).json()
assert data["count"] == 2
assert len(data["models"]) == 2
@pytest.mark.asyncio
async def test_refresh_models_structure(async_client, monkeypatch):
monkeypatch.setattr(
"app.services.ai.model_registry.list_all_models", lambda: _MOCK_MODELS
)
data = (await async_client.post("/api/v1/models/refresh")).json()
assert "models" in data
assert "count" in data
assert "refreshed_at" in data
# ---------------------------------------------------------------------------
# PUT /api/v1/corpora/{id}/model
# ---------------------------------------------------------------------------
@pytest.mark.asyncio
async def test_set_model_corpus_not_found(async_client):
response = await async_client.put(
"/api/v1/corpora/nonexistent/model",
json={"model_id": "gemini-2.0-flash", "provider_type": "google_ai_studio"},
)
assert response.status_code == 404
@pytest.mark.asyncio
async def test_set_model_ok(async_client, db_session):
corpus = await _make_corpus(db_session)
response = await async_client.put(
f"/api/v1/corpora/{corpus.id}/model",
json={
"model_id": "gemini-2.0-flash",
"provider_type": "google_ai_studio",
"display_name": "Gemini 2.0 Flash",
},
)
assert response.status_code == 200
@pytest.mark.asyncio
async def test_set_model_response_fields(async_client, db_session):
corpus = await _make_corpus(db_session)
data = (await async_client.put(
f"/api/v1/corpora/{corpus.id}/model",
json={"model_id": "gemini-2.0-flash", "provider_type": "google_ai_studio"},
)).json()
assert data["corpus_id"] == corpus.id
assert data["selected_model_id"] == "gemini-2.0-flash"
assert data["provider_type"] == "google_ai_studio"
assert "updated_at" in data
@pytest.mark.asyncio
async def test_set_model_update_existing(async_client, db_session):
"""PUT sur un corpus déjà configuré → mise à jour (pas de doublon)."""
corpus = await _make_corpus(db_session)
await async_client.put(
f"/api/v1/corpora/{corpus.id}/model",
json={"model_id": "gemini-1.5-pro", "provider_type": "google_ai_studio"},
)
resp2 = await async_client.put(
f"/api/v1/corpora/{corpus.id}/model",
json={"model_id": "gemini-2.0-flash", "provider_type": "google_ai_studio"},
)
data = resp2.json()
assert data["selected_model_id"] == "gemini-2.0-flash"
@pytest.mark.asyncio
async def test_set_model_then_get(async_client, db_session):
"""Après PUT, GET retourne le même modèle."""
corpus = await _make_corpus(db_session)
await async_client.put(
f"/api/v1/corpora/{corpus.id}/model",
json={"model_id": "gemini-2.0-flash", "provider_type": "google_ai_studio"},
)
get_data = (await async_client.get(f"/api/v1/corpora/{corpus.id}/model")).json()
assert get_data["selected_model_id"] == "gemini-2.0-flash"
@pytest.mark.asyncio
async def test_set_model_display_name_fallback(async_client, db_session):
"""Sans display_name, l'id est utilisé comme display_name."""
corpus = await _make_corpus(db_session)
data = (await async_client.put(
f"/api/v1/corpora/{corpus.id}/model",
json={"model_id": "gemini-2.0-flash", "provider_type": "google_ai_studio"},
)).json()
assert data["selected_model_display_name"] == "gemini-2.0-flash"
# ---------------------------------------------------------------------------
# GET /api/v1/corpora/{id}/model
# ---------------------------------------------------------------------------
@pytest.mark.asyncio
async def test_get_model_corpus_not_found(async_client):
response = await async_client.get("/api/v1/corpora/nonexistent/model")
assert response.status_code == 404
@pytest.mark.asyncio
async def test_get_model_not_configured(async_client, db_session):
"""Corpus sans modèle configuré → 404."""
corpus = await _make_corpus(db_session)
response = await async_client.get(f"/api/v1/corpora/{corpus.id}/model")
assert response.status_code == 404
@pytest.mark.asyncio
async def test_get_model_ok(async_client, db_session):
corpus = await _make_corpus(db_session)
await async_client.put(
f"/api/v1/corpora/{corpus.id}/model",
json={"model_id": "gemini-2.0-flash", "provider_type": "google_ai_studio"},
)
response = await async_client.get(f"/api/v1/corpora/{corpus.id}/model")
assert response.status_code == 200
@pytest.mark.asyncio
async def test_get_model_fields(async_client, db_session):
corpus = await _make_corpus(db_session)
await async_client.put(
f"/api/v1/corpora/{corpus.id}/model",
json={"model_id": "gemini-1.5-pro", "provider_type": "google_ai_studio", "display_name": "Gemini 1.5 Pro"},
)
data = (await async_client.get(f"/api/v1/corpora/{corpus.id}/model")).json()
assert data["corpus_id"] == corpus.id
assert data["selected_model_id"] == "gemini-1.5-pro"
assert data["selected_model_display_name"] == "Gemini 1.5 Pro"
assert data["provider_type"] == "google_ai_studio"
assert "updated_at" in data