File size: 9,318 Bytes
ed52286
2d76892
ed52286
 
2d76892
ed52286
 
 
2d76892
ed52286
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
844e230
ed52286
 
 
844e230
 
ed52286
 
844e230
ed52286
844e230
ed52286
 
 
2d76892
ed52286
 
 
2d76892
 
 
 
 
ed52286
 
 
 
 
 
 
 
 
193eb98
ed52286
 
 
 
 
 
 
 
193eb98
ed52286
 
 
 
 
 
 
 
 
193eb98
ed52286
 
 
 
 
 
 
 
 
193eb98
ed52286
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
"""
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