diff --git a/src/tenantq/search.py b/src/tenantq/search.py index 41cc5c4..e77e271 100644 --- a/src/tenantq/search.py +++ b/src/tenantq/search.py @@ -44,6 +44,12 @@ def build_filter( created_before: Optional[int] = None, ) -> models.Filter: """Build a tenant-scoped filter, optionally narrowed by metadata.""" + if tenant_id is None or not str(tenant_id).strip(): + raise ValueError( + "tenant_id is required: request was not scoped to a tenant " + "(empty or whitespace-only tenant_id)" + ) + tenant_id = str(tenant_id).strip() must: List[models.FieldCondition] = [ models.FieldCondition(key=TENANT_FIELD, match=models.MatchValue(value=tenant_id)) ] diff --git a/tests/test_search.py b/tests/test_search.py index 661e0bc..7843804 100644 --- a/tests/test_search.py +++ b/tests/test_search.py @@ -38,3 +38,18 @@ 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 + + +import pytest +from tenantq.search import build_filter + + +@pytest.mark.parametrize("bad", ["", " ", "\t"]) +def test_build_filter_rejects_empty_tenant_id(bad): + with pytest.raises(ValueError, match="not scoped"): + build_filter(bad) + + +def test_build_filter_accepts_tenant_with_internal_spaces(): + f = build_filter("acme corp") + assert f.must