fix(search): cap path_prefixes at the MCP tool; widen path filter tests

Round 2 review follow-ups:
- Add Field(max_length=20) to the nc_semantic_search path_prefixes param so
  an LLM client can't build an unbounded OR-filter (mirrors the cap the
  Astrolabe PHP controller applies on the UI path).
- Note in normalize_path_prefixes that the two-pass collect-then-strip is
  deliberate (the `if path_prefix:` guard is truthy for whitespace-only
  input; the strip pass is what drops it).
- Tests: exercise build_base_filter_conditions with 3 folders (guards the
  list comprehension) and parametrize the no-path case over None, empty
  list, and blank-only inputs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Chris Coutinho
2026-06-03 13:10:14 +02:00
co-authored by Claude Opus 4.8
parent cd243ed6c3
commit ea108140ab
3 changed files with 29 additions and 6 deletions
+24 -6
View File
@@ -265,11 +265,23 @@ class TestBuildBaseFilterConditions:
assert match.text == prefix
@pytest.mark.unit
def test_no_path_condition_when_prefix_absent(self) -> None:
conditions = build_base_filter_conditions("alice", None, path_prefix=None)
@pytest.mark.parametrize(
"kwargs",
[
{"path_prefix": None},
{"path_prefixes": None},
{"path_prefixes": []},
{"path_prefix": " ", "path_prefixes": ["", " "]},
],
)
def test_no_path_condition_when_prefix_absent(self, kwargs) -> None:
# No folder filter (None, empty list, or blank-only) must add neither a
# flat file_path condition nor a nested path OR.
conditions = build_base_filter_conditions("alice", None, **kwargs)
assert not any(
isinstance(c, FieldCondition) and c.key == "file_path" for c in conditions
)
assert self._path_should_texts(conditions) is None
@staticmethod
def _path_should_texts(conditions) -> set[str] | None:
@@ -292,12 +304,18 @@ class TestBuildBaseFilterConditions:
@pytest.mark.unit
def test_multiple_path_prefixes_or_in_nested_should(self) -> None:
# Two+ folders must OR together: a single nested Filter(should=[...]) is
# appended (not two must conditions, which would AND and match nothing).
# 3+ folders must OR together: a single nested Filter(should=[...]) is
# appended (not separate must conditions, which would AND and match
# nothing). 3 folders also guards the list comprehension against an
# off-by-one.
conditions = build_base_filter_conditions(
"alice", None, path_prefixes=["/Projects", "/Archive"]
"alice", None, path_prefixes=["/Projects", "/Archive", "/Shared"]
)
assert self._path_should_texts(conditions) == {"/Projects", "/Archive"}
assert self._path_should_texts(conditions) == {
"/Projects",
"/Archive",
"/Shared",
}
# No bare file_path FieldCondition in must for the multi-folder case.
assert not any(
isinstance(c, FieldCondition) and c.key == "file_path" for c in conditions