dbc6b5b53b
Move ebook search tests into tests/ebook_search and standardize mocking on pytest-mock.
123 lines
4.1 KiB
Python
123 lines
4.1 KiB
Python
"""Tests for EPUB search health and readiness routes."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import TYPE_CHECKING
|
|
|
|
from fastapi.testclient import TestClient
|
|
from sqlalchemy import create_engine
|
|
|
|
from python.ebook_search.api.main import create_app
|
|
from python.ebook_search.config import EbookSearchConfig, RerankConfig
|
|
|
|
HEALTH_MODULE = "python.ebook_search.api.routes.health"
|
|
|
|
if TYPE_CHECKING:
|
|
from pytest_mock import MockerFixture
|
|
|
|
|
|
def fake_get_postgres_engine(**_kwargs):
|
|
"""Return an in-memory engine for route tests."""
|
|
return create_engine("sqlite+pysqlite:///:memory:", future=True)
|
|
|
|
|
|
def patch_app_runtime(mocker: MockerFixture):
|
|
mocker.patch("python.ebook_search.api.main.get_postgres_engine", side_effect=fake_get_postgres_engine)
|
|
mocker.patch("python.ebook_search.api.main.ensure_bm25_corpus", side_effect=lambda _session, _config: None)
|
|
|
|
|
|
def patch_dependencies(mocker: MockerFixture, *, database=True, embedding=True, chat=True, bm25="ok"):
|
|
mocker.patch(f"{HEALTH_MODULE}.check_database", side_effect=lambda _session: database)
|
|
mocker.patch(f"{HEALTH_MODULE}.check_embedding_endpoint", side_effect=lambda _config: embedding)
|
|
mocker.patch(f"{HEALTH_MODULE}.check_chat_endpoint", side_effect=lambda _config: chat)
|
|
mocker.patch(f"{HEALTH_MODULE}.check_bm25_status", side_effect=lambda _config: bm25)
|
|
|
|
|
|
def build_client(mocker: MockerFixture, config=None):
|
|
resolved = config or EbookSearchConfig(rerank=RerankConfig(enabled=False))
|
|
mocker.patch("python.ebook_search.api.main.load_config", side_effect=lambda: resolved)
|
|
patch_app_runtime(mocker)
|
|
app = create_app()
|
|
return TestClient(app)
|
|
|
|
|
|
def test_health_returns_ok(mocker: MockerFixture) -> None:
|
|
with build_client(mocker) as client:
|
|
response = client.get("/health")
|
|
|
|
assert response.status_code == 200
|
|
assert response.json() == {"status": "ok"}
|
|
|
|
|
|
def test_ready_all_dependencies_ok(mocker: MockerFixture) -> None:
|
|
patch_dependencies(mocker)
|
|
|
|
with build_client(mocker) as client:
|
|
response = client.get("/ready")
|
|
|
|
assert response.status_code == 200
|
|
body = response.json()
|
|
assert body["status"] == "ready"
|
|
assert body["checks"] == {"database": "ok", "embedding": "ok", "chat": "ok", "bm25": "ok"}
|
|
|
|
|
|
def test_ready_embedding_down_is_degraded(mocker: MockerFixture) -> None:
|
|
patch_dependencies(mocker, embedding=False)
|
|
|
|
with build_client(mocker) as client:
|
|
response = client.get("/ready")
|
|
|
|
assert response.status_code == 200
|
|
body = response.json()
|
|
assert body["status"] == "degraded"
|
|
assert body["checks"]["embedding"] == "fail"
|
|
|
|
|
|
def test_ready_chat_down_is_degraded(mocker: MockerFixture) -> None:
|
|
patch_dependencies(mocker, chat=False)
|
|
|
|
with build_client(mocker) as client:
|
|
response = client.get("/ready")
|
|
|
|
assert response.status_code == 200
|
|
body = response.json()
|
|
assert body["status"] == "degraded"
|
|
assert body["checks"]["chat"] == "fail"
|
|
|
|
|
|
def test_ready_chat_disabled_when_answers_off(mocker: MockerFixture) -> None:
|
|
patch_dependencies(mocker)
|
|
config = EbookSearchConfig(rerank=RerankConfig(enabled=False), answer_enabled=False)
|
|
|
|
with build_client(mocker, config) as client:
|
|
response = client.get("/ready")
|
|
|
|
assert response.status_code == 200
|
|
body = response.json()
|
|
assert body["status"] == "ready"
|
|
assert body["checks"]["chat"] == "disabled"
|
|
|
|
|
|
def test_ready_database_down_is_unavailable(mocker: MockerFixture) -> None:
|
|
patch_dependencies(mocker, database=False)
|
|
|
|
with build_client(mocker) as client:
|
|
response = client.get("/ready")
|
|
|
|
assert response.status_code == 503
|
|
body = response.json()
|
|
assert body["status"] == "unavailable"
|
|
assert body["checks"]["database"] == "fail"
|
|
|
|
|
|
def test_ready_bm25_missing_is_degraded(mocker: MockerFixture) -> None:
|
|
patch_dependencies(mocker, bm25="missing")
|
|
|
|
with build_client(mocker) as client:
|
|
response = client.get("/ready")
|
|
|
|
assert response.status_code == 200
|
|
body = response.json()
|
|
assert body["status"] == "degraded"
|
|
assert body["checks"]["bm25"] == "missing"
|