test_models.py (4889B)
1 """T080 — Tests for the models proxy endpoint.""" 2 3 from __future__ import annotations 4 from unittest.mock import AsyncMock, MagicMock, patch 5 import pytest 6 from fastapi import FastAPI 7 from fastapi.testclient import TestClient 8 9 10 # ── helpers ────────────────────────────────────────────────────────────── 11 12 def _mock_client(**overrides: AsyncMock) -> MagicMock: 13 """Build a mock HermesClient that returns Hermes-format model data.""" 14 client = MagicMock() 15 client.list_models = AsyncMock( 16 return_value={ 17 "object": "list", 18 "data": [ 19 {"id": "model-1", "object": "model", "created": 100, "owned_by": "hermes"}, 20 {"id": "model-2", "object": "model", "created": 100, "owned_by": "hermes"}, 21 ], 22 }, 23 ) 24 for name, mock in overrides.items(): 25 setattr(client, name, mock) 26 return client 27 28 def _test_app() -> FastAPI: 29 """Build a minimal app with models router, no auth middleware.""" 30 from app.routes.models import router as models_router 31 app = FastAPI() 32 app.include_router(models_router) 33 return app 34 35 # ── tests ──────────────────────────────────────────────────────────────── 36 37 38 class TestListModels: 39 """GET /api/models returns normalized model list from Hermes.""" 40 41 def test_returns_normalized_model_list(self) -> None: 42 """Models are normalized to {id, name} format.""" 43 mock_client = _mock_client() 44 app = _test_app() 45 with patch( 46 "app.routes.models.get_hermes_client", return_value=mock_client, 47 ): 48 with TestClient(app) as tc: 49 resp = tc.get("/api/models") 50 assert resp.status_code == 200 51 data = resp.json() 52 assert "models" in data 53 assert len(data["models"]) == 2 54 assert data["models"][0]["id"] == "model-1" 55 assert data["models"][0]["name"] == "model-1" 56 assert data["models"][1]["id"] == "model-2" 57 assert data["models"][1]["name"] == "model-2" 58 mock_client.list_models.assert_awaited_once() 59 60 def test_unwraps_hermes_list_response(self) -> None: 61 """Hermes returns {object: 'list', data: [...]} — unwrap it.""" 62 wrapped = { 63 "object": "list", 64 "data": [ 65 {"id": "model-1", "object": "model", "owned_by": "hermes"}, 66 {"id": "model-2", "object": "model", "owned_by": "hermes"}, 67 ], 68 } 69 mock_client = _mock_client(list_models=AsyncMock(return_value=wrapped)) 70 app = _test_app() 71 with patch( 72 "app.routes.models.get_hermes_client", return_value=mock_client, 73 ): 74 with TestClient(app) as tc: 75 resp = tc.get("/api/models") 76 assert resp.status_code == 200 77 data = resp.json() 78 assert len(data["models"]) == 2 79 assert data["models"][0]["id"] == "model-1" 80 assert data["models"][0]["name"] == "model-1" 81 82 def test_returns_empty_when_no_models(self) -> None: 83 """Empty response returns empty models list.""" 84 mock_client = _mock_client(list_models=AsyncMock(return_value=None)) 85 app = _test_app() 86 with patch( 87 "app.routes.models.get_hermes_client", return_value=mock_client, 88 ): 89 with TestClient(app) as tc: 90 resp = tc.get("/api/models") 91 assert resp.status_code == 200 92 assert resp.json() == {"models": []} 93 94 def test_hermes_error_returns_502(self) -> None: 95 """Hermes failure returns 502.""" 96 mock_client = _mock_client( 97 list_models=AsyncMock(side_effect=Exception("Hermes down")), 98 ) 99 app = _test_app() 100 with patch( 101 "app.routes.models.get_hermes_client", return_value=mock_client, 102 ): 103 with TestClient(app) as tc: 104 resp = tc.get("/api/models") 105 assert resp.status_code == 502 106 body = resp.json() 107 assert "detail" in body 108 assert "Hermes" in body["detail"] 109 assert "Hermes down" in body["detail"] 110 111 def test_returns_empty_when_data_not_a_list(self) -> None: 112 """When models data is not a list, return empty.""" 113 mock_client = _mock_client( 114 list_models=AsyncMock(return_value={"object": "list", "data": "not-a-list"}), 115 ) 116 app = _test_app() 117 with patch( 118 "app.routes.models.get_hermes_client", return_value=mock_client, 119 ): 120 with TestClient(app) as tc: 121 resp = tc.get("/api/models") 122 assert resp.status_code == 200 123 assert resp.json() == {"models": []}