"""Tests for EPUB search health and readiness routes.""" from __future__ import annotations 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" 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(monkeypatch): monkeypatch.setattr("python.ebook_search.api.main.get_postgres_engine", fake_get_postgres_engine) monkeypatch.setattr("python.ebook_search.api.main.ensure_bm25_corpus", lambda _session, _config: None) def patch_dependencies(monkeypatch, *, database=True, embedding=True, chat=True, bm25="ok"): monkeypatch.setattr(f"{HEALTH_MODULE}.check_database", lambda _session: database) monkeypatch.setattr(f"{HEALTH_MODULE}.check_embedding_endpoint", lambda _config: embedding) monkeypatch.setattr(f"{HEALTH_MODULE}.check_chat_endpoint", lambda _config: chat) monkeypatch.setattr(f"{HEALTH_MODULE}.check_bm25_status", lambda _config: bm25) def build_client(monkeypatch, config=None): patch_app_runtime(monkeypatch) app = create_app() app.state.config = config or EbookSearchConfig(rerank=RerankConfig(enabled=False)) return TestClient(app) def test_health_returns_ok(monkeypatch) -> None: with build_client(monkeypatch) as client: response = client.get("/health") assert response.status_code == 200 assert response.json() == {"status": "ok"} def test_ready_all_dependencies_ok(monkeypatch) -> None: patch_dependencies(monkeypatch) with build_client(monkeypatch) 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(monkeypatch) -> None: patch_dependencies(monkeypatch, embedding=False) with build_client(monkeypatch) 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(monkeypatch) -> None: patch_dependencies(monkeypatch, chat=False) with build_client(monkeypatch) 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(monkeypatch) -> None: patch_dependencies(monkeypatch) config = EbookSearchConfig(rerank=RerankConfig(enabled=False), answer_enabled=False) with build_client(monkeypatch, 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(monkeypatch) -> None: patch_dependencies(monkeypatch, database=False) with build_client(monkeypatch) 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(monkeypatch) -> None: patch_dependencies(monkeypatch, bm25="missing") with build_client(monkeypatch) as client: response = client.get("/ready") assert response.status_code == 200 body = response.json() assert body["status"] == "degraded" assert body["checks"]["bm25"] == "missing"