test(ebook): cover protected phrases and migrate suite to async

Add test_protected_phrases.py covering phrase-matching behavior in the
RAG engine, and update the existing ebook_search tests to use the async
SQLAlchemy engine/session (create_async_engine, AsyncSession) and async
HTTP paths.
This commit is contained in:
2026-07-24 11:38:50 -04:00
parent 56c9bb2520
commit 565119ee45
8 changed files with 1448 additions and 94 deletions
+12 -9
View File
@@ -5,7 +5,7 @@ from __future__ import annotations
from typing import TYPE_CHECKING
from fastapi.testclient import TestClient
from sqlalchemy import create_engine
from sqlalchemy.ext.asyncio import create_async_engine
from python.ebook_search.api.main import create_app
from python.ebook_search.config import EbookSearchConfig, RerankConfig
@@ -66,8 +66,8 @@ def test_is_confident_against_threshold() -> None:
def patch_app_runtime(mocker: MockerFixture):
mocker.patch(
"python.ebook_search.api.main.get_postgres_engine",
side_effect=lambda **_kwargs: create_engine("sqlite+pysqlite:///:memory:", future=True),
"python.ebook_search.api.main.get_async_postgres_engine",
side_effect=lambda **_kwargs: create_async_engine("sqlite+aiosqlite:///:memory:"),
)
mocker.patch("python.ebook_search.api.main.ensure_bm25_corpus", side_effect=lambda _session, _config: None)
@@ -75,11 +75,12 @@ def patch_app_runtime(mocker: MockerFixture):
def test_low_confidence_skips_answer_generation(mocker: MockerFixture) -> None:
called = False
def fake_search_ebooks(_engine, query, _config, *, rerank=False):
def fake_search_ebooks(_engine, _client, query, _config, *, rerank=False, phrase_matching=False):
del rerank
del phrase_matching
return SearchResponse(query=query, rank_label="Hybrid", results=make_results(1, vector_score=0.05))
def fake_answer_query(_query, _results, _config):
def fake_answer_query(_client, _query, _results, _config):
nonlocal called
called = True
return "answer"
@@ -104,14 +105,15 @@ def test_low_confidence_skips_answer_generation(mocker: MockerFixture) -> None:
def test_invalid_citation_is_flagged(mocker: MockerFixture) -> None:
def fake_search_ebooks(_engine, query, _config, *, rerank=False):
def fake_search_ebooks(_engine, _client, query, _config, *, rerank=False, phrase_matching=False):
del rerank
del phrase_matching
return SearchResponse(query=query, rank_label="Hybrid", results=make_results(2, vector_score=0.9))
mocker.patch("python.ebook_search.api.routes.search.search_ebooks", side_effect=fake_search_ebooks)
mocker.patch(
"python.ebook_search.api.routes.search.answer_query",
side_effect=lambda _query, _results, _config: "Per the text [9].",
side_effect=lambda _client, _query, _results, _config: "Per the text [9].",
)
patch_app_runtime(mocker)
app = create_app()
@@ -126,14 +128,15 @@ def test_invalid_citation_is_flagged(mocker: MockerFixture) -> None:
def test_grounded_answer_has_no_warning_badge(mocker: MockerFixture) -> None:
def fake_search_ebooks(_engine, query, _config, *, rerank=False):
def fake_search_ebooks(_engine, _client, query, _config, *, rerank=False, phrase_matching=False):
del rerank
del phrase_matching
return SearchResponse(query=query, rank_label="Hybrid", results=make_results(2, vector_score=0.9))
mocker.patch("python.ebook_search.api.routes.search.search_ebooks", side_effect=fake_search_ebooks)
mocker.patch(
"python.ebook_search.api.routes.search.answer_query",
side_effect=lambda _query, _results, _config: "Grounded in [1] and [2].",
side_effect=lambda _client, _query, _results, _config: "Grounded in [1] and [2].",
)
patch_app_runtime(mocker)
app = create_app()