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
+17 -9
View File
@@ -16,7 +16,14 @@ if TYPE_CHECKING:
from pytest_mock import MockerFixture
def test_answer_query_uses_httpx_chat_completions(mocker: MockerFixture) -> None:
def make_async_client(mocker: MockerFixture, fake_post) -> httpx.AsyncClient:
"""Build a mock async client whose post call is served by fake_post."""
client = mocker.MagicMock(spec=httpx.AsyncClient)
client.post = mocker.AsyncMock(side_effect=fake_post)
return client
async def test_answer_query_uses_httpx_chat_completions(mocker: MockerFixture) -> None:
captured: dict[str, object] = {}
def fake_post(url: str, **kwargs: object) -> httpx.Response:
@@ -28,7 +35,7 @@ def test_answer_query_uses_httpx_chat_completions(mocker: MockerFixture) -> None
request=httpx.Request("POST", url),
)
mocker.patch.object(httpx, "post", side_effect=fake_post)
client = make_async_client(mocker, fake_post)
config = EbookSearchConfig(
rerank=RerankConfig(enabled=False),
vllm_base_url="https://ollama.com/v1",
@@ -36,7 +43,8 @@ def test_answer_query_uses_httpx_chat_completions(mocker: MockerFixture) -> None
chat_model="deepseek-v4-flash",
)
answer = answer_query("question", [SearchResult(chunk_id=1, text="source", source_title="Book")], config)
results = [SearchResult(chunk_id=1, text="source", source_title="Book")]
answer = await answer_query(client, "question", results, config)
assert answer == "grounded answer"
assert captured["url"] == "https://ollama.com/v1/chat/completions"
@@ -48,7 +56,7 @@ def test_answer_query_uses_httpx_chat_completions(mocker: MockerFixture) -> None
assert payload["model"] == "deepseek-v4-flash"
def test_embed_texts_uses_httpx_embeddings(mocker: MockerFixture) -> None:
async def test_embed_texts_uses_httpx_embeddings(mocker: MockerFixture) -> None:
captured: dict[str, object] = {}
vector = [0.0] * 1024
@@ -61,14 +69,14 @@ def test_embed_texts_uses_httpx_embeddings(mocker: MockerFixture) -> None:
request=httpx.Request("POST", url),
)
mocker.patch.object(httpx, "post", side_effect=fake_post)
client = make_async_client(mocker, fake_post)
config = EbookSearchConfig(
rerank=RerankConfig(enabled=False),
embedding_base_url="http://bob:8000/v1",
embedding_model="qwen3-embedding-0.6b",
)
embeddings = embed_texts(["hello"], config)
embeddings = await embed_texts(client, ["hello"], config)
assert embeddings == [vector]
assert captured["url"] == "http://bob:8000/v1/embeddings"
@@ -78,12 +86,12 @@ def test_embed_texts_uses_httpx_embeddings(mocker: MockerFixture) -> None:
assert kwargs["json"] == {"model": "qwen3-embedding-0.6b", "input": ["hello"]}
def test_embed_texts_rejects_bad_response_shape(mocker: MockerFixture) -> None:
async def test_embed_texts_rejects_bad_response_shape(mocker: MockerFixture) -> None:
def fake_post(url: str, **_kwargs: object) -> httpx.Response:
return httpx.Response(200, json={"data": [{}]}, request=httpx.Request("POST", url))
mocker.patch.object(httpx, "post", side_effect=fake_post)
client = make_async_client(mocker, fake_post)
config = EbookSearchConfig(rerank=RerankConfig(enabled=False))
with pytest.raises(RuntimeError, match="Embedding request failed"):
embed_texts(["hello"], config)
await embed_texts(client, ["hello"], config)