Spaces:
Build error
Build error
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) | |
| # --------------------------------------------------------------------------- | |
| 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) | |
| # --------------------------------------------------------------------------- | |
| 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 | |
| # --------------------------------------------------------------------------- | |
| 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 | |
| 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 | |
| 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 | |
| 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 | |
| # --------------------------------------------------------------------------- | |
| 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 | |
| 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 | |
| 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 | |
| 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" | |
| 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" | |
| 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 | |
| # --------------------------------------------------------------------------- | |
| 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 | |
| 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 | |
| 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 | |
| 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 | |