theo-agent-dashboard

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

test_search.py (5837B)


      1 """Tests for the search endpoint."""
      2 
      3 from __future__ import annotations
      4 
      5 from unittest.mock import AsyncMock, patch
      6 import pytest
      7 from fastapi import FastAPI
      8 from fastapi.testclient import TestClient
      9 
     10 from app.routes.search import router as search_router
     11 
     12 def _test_app() -> FastAPI:
     13     """Build a minimal app with search router, no auth middleware."""
     14     app = FastAPI()
     15     app.include_router(search_router)
     16     return app
     17 
     18 def _mock_client(**overrides) -> AsyncMock:
     19     client = AsyncMock()
     20     client.search_messages = AsyncMock(return_value={"results": []})
     21     for name, mock in overrides.items():
     22         setattr(client, name, mock)
     23     return client
     24 
     25 
     26 class TestSearchEmpty:
     27     def test_empty_query_returns_empty(self) -> None:
     28         mock_client = _mock_client()
     29         app = _test_app()
     30         with patch("app.routes.search.get_hermes_client", return_value=mock_client):
     31             with TestClient(app) as tc:
     32                 resp = tc.get("/api/search?q=")
     33         assert resp.status_code == 200
     34         assert resp.json() == {"results": []}
     35 
     36     def test_no_query_param_returns_empty(self) -> None:
     37         mock_client = _mock_client()
     38         app = _test_app()
     39         with patch("app.routes.search.get_hermes_client", return_value=mock_client):
     40             with TestClient(app) as tc:
     41                 resp = tc.get("/api/search")
     42         assert resp.status_code == 200
     43         assert resp.json() == {"results": []}
     44 
     45     def test_whitespace_query_returns_empty(self) -> None:
     46         mock_client = _mock_client()
     47         app = _test_app()
     48         with patch("app.routes.search.get_hermes_client", return_value=mock_client):
     49             with TestClient(app) as tc:
     50                 resp = tc.get("/api/search?q=+")
     51         assert resp.status_code == 200
     52         assert resp.json() == {"results": []}
     53 
     54     def test_hermes_not_called_on_empty_query(self) -> None:
     55         """Empty queries should not call the Hermes client."""
     56         mock_client = _mock_client()
     57         app = _test_app()
     58         with patch("app.routes.search.get_hermes_client", return_value=mock_client):
     59             with TestClient(app) as tc:
     60                 tc.get("/api/search?q=")
     61         mock_client.search_messages.assert_not_awaited()
     62 
     63 
     64 class TestSearchResults:
     65     def test_search_returns_grouped_results(self) -> None:
     66         mock_client = _mock_client(
     67             search_messages=AsyncMock(return_value={
     68                 "results": [
     69                     {
     70                         "session_id": "s1",
     71                         "session_name": "Session One",
     72                         "messages": [
     73                             {"id": "m1", "content": "test message", "role": "user"},
     74                             {"id": "m2", "content": "test reply", "role": "assistant"},
     75                         ],
     76                     },
     77                     {
     78                         "session_id": "s2",
     79                         "session_name": "Session Two",
     80                         "messages": [
     81                             {"id": "m3", "content": "another test", "role": "user"},
     82                         ],
     83                     },
     84                 ],
     85             })
     86         )
     87         app = _test_app()
     88         with patch("app.routes.search.get_hermes_client", return_value=mock_client):
     89             with TestClient(app) as tc:
     90                 resp = tc.get("/api/search?q=test")
     91         assert resp.status_code == 200
     92         data = resp.json()
     93         assert "results" in data
     94         assert len(data["results"]) == 2
     95         # First group
     96         assert data["results"][0]["session_id"] == "s1"
     97         assert data["results"][0]["session_name"] == "Session One"
     98         assert len(data["results"][0]["messages"]) == 2
     99         assert data["results"][0]["messages"][0]["id"] == "m1"
    100         assert data["results"][0]["messages"][0]["content"] == "test message"
    101         assert data["results"][0]["messages"][0]["role"] == "user"
    102         assert data["results"][0]["messages"][1]["id"] == "m2"
    103         assert data["results"][0]["messages"][1]["role"] == "assistant"
    104         # Second group
    105         assert data["results"][1]["session_id"] == "s2"
    106         assert data["results"][1]["session_name"] == "Session Two"
    107         assert len(data["results"][1]["messages"]) == 1
    108         assert data["results"][1]["messages"][0]["id"] == "m3"
    109         assert data["results"][1]["messages"][0]["content"] == "another test"
    110 
    111     def test_search_with_no_results(self) -> None:
    112         mock_client = _mock_client(
    113             search_messages=AsyncMock(return_value={"results": []})
    114         )
    115         app = _test_app()
    116         with patch("app.routes.search.get_hermes_client", return_value=mock_client):
    117             with TestClient(app) as tc:
    118                 resp = tc.get("/api/search?q=nonexistent")
    119         assert resp.status_code == 200
    120         assert resp.json() == {"results": []}
    121 
    122     def test_search_api_error_returns_empty(self) -> None:
    123         mock_client = _mock_client(
    124             search_messages=AsyncMock(side_effect=Exception("timeout"))
    125         )
    126         app = _test_app()
    127         with patch("app.routes.search.get_hermes_client", return_value=mock_client):
    128             with TestClient(app) as tc:
    129                 resp = tc.get("/api/search?q=test")
    130         assert resp.status_code == 200
    131         assert resp.json() == {"results": []}
    132 
    133     def test_search_hermes_called_with_query(self) -> None:
    134         """Verify the search query is passed to Hermes."""
    135         mock_client = _mock_client(
    136             search_messages=AsyncMock(return_value={"results": []})
    137         )
    138         app = _test_app()
    139         with patch("app.routes.search.get_hermes_client", return_value=mock_client):
    140             with TestClient(app) as tc:
    141                 tc.get("/api/search?q=my%20search%20query")
    142         mock_client.search_messages.assert_awaited_once_with("my search query")