From 4f0e7f21e48aefa99edb5ae95974a72e2d9a9a2c Mon Sep 17 00:00:00 2001 From: Habib Ur Rehman <42462183+Hbrehman@users.noreply.github.com> Date: Sun, 6 Sep 2026 17:29:35 +0500 Subject: [PATCH 1/2] fix: reject empty/whitespace search queries before embedding --- src/tenantq/search.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/tenantq/search.py b/src/tenantq/search.py index 41cc5c4..9a51c6a 100644 --- a/src/tenantq/search.py +++ b/src/tenantq/search.py @@ -91,6 +91,13 @@ def search( prefetch_limit: int = 50, ) -> List[SearchHit]: """Run a tenant-isolated search in the requested retrieval mode.""" + if query is None or not str(query).strip(): + raise ValueError( + "query is required: empty or whitespace-only query would produce " + "degenerate embeddings and arbitrary-looking hits" + ) + query = str(query).strip() + qfilter = build_filter(tenant_id, category, created_after, created_before) params = models.SearchParams(hnsw_ef=settings.hnsw.hnsw_ef) From 7f262232bca158fcc831057d31887b73be185033 Mon Sep 17 00:00:00 2001 From: Habib Ur Rehman <42462183+Hbrehman@users.noreply.github.com> Date: Sun, 6 Sep 2026 17:29:36 +0500 Subject: [PATCH 2/2] test: cover empty/whitespace search query rejection --- tests/test_search.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tests/test_search.py b/tests/test_search.py index 661e0bc..8a936f8 100644 --- a/tests/test_search.py +++ b/tests/test_search.py @@ -2,6 +2,10 @@ from __future__ import annotations +from unittest.mock import MagicMock + +import pytest + from tenantq.search import search @@ -38,3 +42,27 @@ def test_created_at_range_filter(ingested, settings, embedder, dataset): by_id = {d.id: d for d in dataset.documents} for h in hits: assert lo <= by_id[h.id].created_at <= hi + + +@pytest.mark.parametrize("bad", ["", " ", "\t", "\n"]) +def test_search_rejects_empty_or_whitespace_query(settings, bad): + client = MagicMock() + embedder = MagicMock() + with pytest.raises(ValueError, match="query is required"): + search(client, settings, embedder, bad, tenant_id="acme", mode="hybrid") + embedder.embed_dense.assert_not_called() + embedder.embed_sparse.assert_not_called() + client.query_points.assert_not_called() + + +def test_search_accepts_query_with_internal_spaces(ingested, settings, embedder): + hits = search( + ingested, + settings, + embedder, + " neural network ", + tenant_id="acme", + mode="dense", + limit=5, + ) + assert len(hits) > 0