@@ -126,6 +126,155 @@ def test_condition_when_complex():
126126 assert cond .run (State ({"foo" : "baz" , "baz" : "corge" })) == {Condition .KEY : False }
127127
128128
129+ # --- when() operator tests ---
130+
131+
132+ @pytest .mark .parametrize (
133+ "kwargs,state_dict,expected" ,
134+ [
135+ # __eq (explicit equality)
136+ ({"age__eq" : 18 }, {"age" : 18 }, True ),
137+ ({"age__eq" : 18 }, {"age" : 19 }, False ),
138+ # __ne (not equal)
139+ ({"age__ne" : 0 }, {"age" : 5 }, True ),
140+ ({"age__ne" : 0 }, {"age" : 0 }, False ),
141+ # __gt (greater than)
142+ ({"age__gt" : 18 }, {"age" : 19 }, True ),
143+ ({"age__gt" : 18 }, {"age" : 18 }, False ),
144+ ({"age__gt" : 18 }, {"age" : 17 }, False ),
145+ # __gte (greater than or equal)
146+ ({"age__gte" : 18 }, {"age" : 19 }, True ),
147+ ({"age__gte" : 18 }, {"age" : 18 }, True ),
148+ ({"age__gte" : 18 }, {"age" : 17 }, False ),
149+ # __lt (less than)
150+ ({"age__lt" : 18 }, {"age" : 17 }, True ),
151+ ({"age__lt" : 18 }, {"age" : 18 }, False ),
152+ ({"age__lt" : 18 }, {"age" : 19 }, False ),
153+ # __lte (less than or equal)
154+ ({"age__lte" : 18 }, {"age" : 17 }, True ),
155+ ({"age__lte" : 18 }, {"age" : 18 }, True ),
156+ ({"age__lte" : 18 }, {"age" : 19 }, False ),
157+ # __in (membership)
158+ ({"status__in" : ["active" , "pending" ]}, {"status" : "active" }, True ),
159+ ({"status__in" : ["active" , "pending" ]}, {"status" : "pending" }, True ),
160+ ({"status__in" : ["active" , "pending" ]}, {"status" : "banned" }, False ),
161+ # __notin (not in)
162+ ({"status__notin" : ["banned" , "suspended" ]}, {"status" : "active" }, True ),
163+ ({"status__notin" : ["banned" , "suspended" ]}, {"status" : "banned" }, False ),
164+ # __contains (collection contains value)
165+ ({"tags__contains" : "python" }, {"tags" : ["python" , "java" ]}, True ),
166+ ({"tags__contains" : "go" }, {"tags" : ["python" , "java" ]}, False ),
167+ ({"text__contains" : "hello" }, {"text" : "say hello world" }, True ),
168+ ({"text__contains" : "goodbye" }, {"text" : "say hello world" }, False ),
169+ ],
170+ ids = [
171+ "eq-match" ,
172+ "eq-no-match" ,
173+ "ne-different" ,
174+ "ne-same" ,
175+ "gt-above" ,
176+ "gt-equal" ,
177+ "gt-below" ,
178+ "gte-above" ,
179+ "gte-equal" ,
180+ "gte-below" ,
181+ "lt-below" ,
182+ "lt-equal" ,
183+ "lt-above" ,
184+ "lte-below" ,
185+ "lte-equal" ,
186+ "lte-above" ,
187+ "in-first" ,
188+ "in-second" ,
189+ "in-missing" ,
190+ "notin-absent" ,
191+ "notin-present" ,
192+ "contains-list-match" ,
193+ "contains-list-no-match" ,
194+ "contains-str-match" ,
195+ "contains-str-no-match" ,
196+ ],
197+ )
198+ def test_condition_when_operators (kwargs , state_dict , expected ):
199+ cond = Condition .when (** kwargs )
200+ assert cond .run (State (state_dict )) == {Condition .KEY : expected }
201+
202+
203+ @pytest .mark .parametrize (
204+ "kwargs,expected_reads" ,
205+ [
206+ ({"age__gte" : 18 }, ["age" ]),
207+ ({"status__in" : ["a" ]}, ["status" ]),
208+ ({"tags__contains" : "x" }, ["tags" ]),
209+ ({"age__gte" : 18 , "status" : "active" }, ["age" , "status" ]),
210+ # same key with different operators
211+ ({"age__gte" : 10 , "age__lt" : 20 }, ["age" ]),
212+ ],
213+ ids = ["gte" , "in" , "contains" , "mixed" , "same-key-two-ops" ],
214+ )
215+ def test_condition_when_operators_reads (kwargs , expected_reads ):
216+ cond = Condition .when (** kwargs )
217+ assert sorted (cond .reads ) == sorted (expected_reads )
218+
219+
220+ @pytest .mark .parametrize (
221+ "kwargs,expected_name" ,
222+ [
223+ ({"age__gte" : 18 }, "age>=18" ),
224+ ({"age__lt" : 5 }, "age<5" ),
225+ ({"age__ne" : 0 }, "age!=0" ),
226+ ({"status__in" : ["a" , "b" ]}, "status in ['a', 'b']" ),
227+ ({"status__notin" : ["x" ]}, "status not in ['x']" ),
228+ ({"tags__contains" : "py" }, "tags contains 'py'" ),
229+ # plain equality still uses old format
230+ ({"foo" : "bar" }, "foo=bar" ),
231+ ({"foo" : "bar" , "baz" : "qux" }, "baz=qux, foo=bar" ),
232+ ],
233+ ids = ["gte" , "lt" , "ne" , "in" , "notin" , "contains" , "plain-eq" , "plain-multi" ],
234+ )
235+ def test_condition_when_operators_name (kwargs , expected_name ):
236+ cond = Condition .when (** kwargs )
237+ assert cond .name == expected_name
238+
239+
240+ def test_condition_when_operators_combined ():
241+ """Test multiple operators ANDed together."""
242+ cond = Condition .when (age__gte = 18 , status = "active" , score__lt = 100 )
243+ assert cond .run (State ({"age" : 20 , "status" : "active" , "score" : 50 })) == {Condition .KEY : True }
244+ assert cond .run (State ({"age" : 17 , "status" : "active" , "score" : 50 })) == {Condition .KEY : False }
245+ assert cond .run (State ({"age" : 20 , "status" : "inactive" , "score" : 50 })) == {Condition .KEY : False }
246+ assert cond .run (State ({"age" : 20 , "status" : "active" , "score" : 100 })) == {Condition .KEY : False }
247+
248+
249+ def test_condition_when_operators_with_invert ():
250+ """Ensure operator-based conditions work with ~ (invert)."""
251+ cond = ~ Condition .when (age__gte = 18 )
252+ assert cond .run (State ({"age" : 17 })) == {Condition .KEY : True }
253+ assert cond .run (State ({"age" : 18 })) == {Condition .KEY : False }
254+
255+
256+ def test_condition_when_operators_with_or ():
257+ """Ensure operator-based conditions work with | (or)."""
258+ cond = Condition .when (age__lt = 13 ) | Condition .when (age__gte = 65 )
259+ assert cond .run (State ({"age" : 10 })) == {Condition .KEY : True }
260+ assert cond .run (State ({"age" : 70 })) == {Condition .KEY : True }
261+ assert cond .run (State ({"age" : 30 })) == {Condition .KEY : False }
262+
263+
264+ def test_condition_when_operators_with_and ():
265+ """Ensure operator-based conditions work with & (and)."""
266+ cond = Condition .when (age__gte = 18 ) & Condition .when (age__lt = 65 )
267+ assert cond .run (State ({"age" : 30 })) == {Condition .KEY : True }
268+ assert cond .run (State ({"age" : 17 })) == {Condition .KEY : False }
269+ assert cond .run (State ({"age" : 65 })) == {Condition .KEY : False }
270+
271+
272+ def test_condition_when_invalid_key ():
273+ """Empty state key before operator suffix should raise."""
274+ with pytest .raises (ValueError , match = "no state key" ):
275+ Condition .when (__gte = 18 )
276+
277+
129278def test_condition_default ():
130279 cond = default
131280 assert cond .name == "default"
0 commit comments