diff --git a/src/tenantq/search.py b/src/tenantq/search.py index fead624..fd616d9 100644 --- a/src/tenantq/search.py +++ b/src/tenantq/search.py @@ -112,6 +112,11 @@ def search( ) query = str(query).strip() + if not 1 <= limit <= 1000: + raise ValueError(f"limit must be within 1-1000, got {limit!r}") + if not 1 <= prefetch_limit <= 10000: + raise ValueError(f"prefetch_limit must be within 1-10000, got {prefetch_limit!r}") + qfilter = build_filter(tenant_id, category, created_after, created_before) params = models.SearchParams(hnsw_ef=settings.hnsw.hnsw_ef) diff --git a/tests/test_search.py b/tests/test_search.py index ffe10a9..8b8fa86 100644 --- a/tests/test_search.py +++ b/tests/test_search.py @@ -91,3 +91,36 @@ def test_build_filter_allows_wide_open_range(): from tenantq.search import build_filter f = build_filter("acme", created_after=100, created_before=200) assert f.must + + +@pytest.mark.parametrize("limit", [-1, 0, 10_000]) +def test_search_rejects_out_of_range_limit(settings, limit): + client = MagicMock() + embedder = MagicMock() + with pytest.raises(ValueError, match="limit must be within"): + search(client, settings, embedder, "dense query", tenant_id="acme", mode="dense", limit=limit) + client.query_points.assert_not_called() + + +@pytest.mark.parametrize("prefetch_limit", [-1, 0, 10_001]) +def test_search_rejects_out_of_range_prefetch_limit(settings, prefetch_limit): + client = MagicMock() + embedder = MagicMock() + with pytest.raises(ValueError, match="prefetch_limit must be within"): + search( + client, settings, embedder, "hybrid query", + tenant_id="acme", mode="hybrid", prefetch_limit=prefetch_limit, + ) + client.query_points.assert_not_called() + + +def test_search_accepts_max_prefetch_limit(settings): + client = MagicMock() + embedder = MagicMock() + client.query_points.return_value.points = [] + hits = search( + client, settings, embedder, "hybrid query", + tenant_id="acme", mode="hybrid", prefetch_limit=10_000, + ) + assert hits == [] + client.query_points.assert_called_once()