@@ -33,6 +33,100 @@ def test_whitespace_only_value_rejected(self) -> None:
3333class TestListEvaluator :
3434 """Tests for list evaluator runtime behavior."""
3535
36+ @pytest .mark .asyncio
37+ async def test_starts_with_matches_prefix (self ) -> None :
38+ """Test that starts_with mode triggers on prefix matches."""
39+ # Given: a starts_with evaluator config
40+ evaluator = ListEvaluator (
41+ ListEvaluatorConfig (
42+ values = ["/home/lev/agent-control" , "/tmp/cache" ],
43+ logic = "any" ,
44+ match_on = "match" ,
45+ match_mode = "starts_with" ,
46+ case_sensitive = True ,
47+ )
48+ )
49+
50+ # When: evaluating a path under an allowed prefix
51+ result = await evaluator .evaluate ("/home/lev/agent-control/server/src/app.py" )
52+
53+ # Then: the prefix match triggers
54+ assert result .matched is True
55+ assert result .metadata ["matches" ] == ["/home/lev/agent-control/server/src/app.py" ]
56+
57+ @pytest .mark .asyncio
58+ async def test_starts_with_no_match_when_prefix_absent (self ) -> None :
59+ """Test that starts_with mode does not trigger when no prefix matches."""
60+ # Given: a starts_with evaluator config
61+ evaluator = ListEvaluator (
62+ ListEvaluatorConfig (
63+ values = ["/home/lev/agent-control" , "/tmp/cache" ],
64+ logic = "any" ,
65+ match_on = "match" ,
66+ match_mode = "starts_with" ,
67+ case_sensitive = True ,
68+ )
69+ )
70+
71+ # When: evaluating a path with no configured prefix
72+ result = await evaluator .evaluate ("/var/log/system.log" )
73+
74+ # Then: the evaluator does not trigger
75+ assert result .matched is False
76+
77+ @pytest .mark .asyncio
78+ async def test_starts_with_honors_case_sensitivity (self ) -> None :
79+ """Test that starts_with mode respects case sensitivity settings."""
80+ # Given: two starts_with evaluators that differ only by case sensitivity
81+ insensitive = ListEvaluator (
82+ ListEvaluatorConfig (
83+ values = ["/HOME/LEV/AGENT-CONTROL" ],
84+ logic = "any" ,
85+ match_on = "match" ,
86+ match_mode = "starts_with" ,
87+ case_sensitive = False ,
88+ )
89+ )
90+ sensitive = ListEvaluator (
91+ ListEvaluatorConfig (
92+ values = ["/HOME/LEV/AGENT-CONTROL" ],
93+ logic = "any" ,
94+ match_on = "match" ,
95+ match_mode = "starts_with" ,
96+ case_sensitive = True ,
97+ )
98+ )
99+
100+ # When: evaluating the same lower-case path against both
101+ insensitive_result = await insensitive .evaluate ("/home/lev/agent-control/server" )
102+ sensitive_result = await sensitive .evaluate ("/home/lev/agent-control/server" )
103+
104+ # Then: only the case-insensitive evaluator matches
105+ assert insensitive_result .matched is True
106+ assert sensitive_result .matched is False
107+
108+ @pytest .mark .asyncio
109+ async def test_starts_with_supports_no_match_allowlists (self ) -> None :
110+ """Test that starts_with works with no_match for allowlist-style controls."""
111+ # Given: a starts_with evaluator configured as an allowlist
112+ evaluator = ListEvaluator (
113+ ListEvaluatorConfig (
114+ values = ["/home/lev/agent-control" , "/tmp/cache" ],
115+ logic = "any" ,
116+ match_on = "no_match" ,
117+ match_mode = "starts_with" ,
118+ case_sensitive = True ,
119+ )
120+ )
121+
122+ # When: evaluating one allowed and one disallowed path
123+ allowed_result = await evaluator .evaluate ("/home/lev/agent-control/server" )
124+ denied_result = await evaluator .evaluate ("/var/log/system.log" )
125+
126+ # Then: only the disallowed path triggers the control
127+ assert allowed_result .matched is False
128+ assert denied_result .matched is True
129+
36130 @pytest .mark .asyncio
37131 async def test_legacy_empty_string_value_is_ignored_defensively (self ) -> None :
38132 """Test that legacy invalid configs do not compile into a match-all regex."""
0 commit comments