theo-agent-dashboard

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

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": []}