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:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user