@@ -132,6 +132,30 @@ def test_isin_with_numbers(self) -> None:
132132 assert samples == [99 ]
133133
134134
135+ class TestNotinCheck :
136+ def test_notin_passes (self ) -> None :
137+ series = pd .Series (["a" , "b" , "c" ])
138+ fail_count , samples = apply_check (series , "notin" , ["x" , "y" , "z" ])
139+ assert fail_count == 0
140+
141+ def test_notin_fails (self ) -> None :
142+ series = pd .Series (["a" , "b" , "x" ])
143+ fail_count , samples = apply_check (series , "notin" , ["x" , "y" , "z" ])
144+ assert fail_count == 1
145+ assert samples == ["x" ]
146+
147+ def test_notin_with_numbers (self ) -> None :
148+ series = pd .Series ([1 , 2 , 3 ])
149+ fail_count , samples = apply_check (series , "notin" , [3 , 4 , 5 ])
150+ assert fail_count == 1
151+ assert samples == [3 ]
152+
153+ def test_notin_all_forbidden (self ) -> None :
154+ series = pd .Series (["x" , "y" , "z" ])
155+ fail_count , samples = apply_check (series , "notin" , ["x" , "y" , "z" ])
156+ assert fail_count == 3
157+
158+
135159class TestNotnullCheck :
136160 def test_notnull_passes (self ) -> None :
137161 series = pd .Series ([1 , 2 , 3 ])
@@ -164,6 +188,93 @@ def test_str_regex_email_pattern(self) -> None:
164188 assert samples == ["invalid" ]
165189
166190
191+ class TestStrStartswithCheck :
192+ def test_str_startswith_passes (self ) -> None :
193+ series = pd .Series (["hello" , "hi" , "hey" ])
194+ fail_count , samples = apply_check (series , "str_startswith" , "h" )
195+ assert fail_count == 0
196+
197+ def test_str_startswith_fails (self ) -> None :
198+ series = pd .Series (["hello" , "world" ])
199+ fail_count , samples = apply_check (series , "str_startswith" , "h" )
200+ assert fail_count == 1
201+ assert samples == ["world" ]
202+
203+ def test_str_startswith_prefix (self ) -> None :
204+ series = pd .Series (["pre_name" , "pre_value" , "other" ])
205+ fail_count , samples = apply_check (series , "str_startswith" , "pre_" )
206+ assert fail_count == 1
207+ assert samples == ["other" ]
208+
209+
210+ class TestStrEndswithCheck :
211+ def test_str_endswith_passes (self ) -> None :
212+ series = pd .Series (["test.py" , "main.py" ])
213+ fail_count , samples = apply_check (series , "str_endswith" , ".py" )
214+ assert fail_count == 0
215+
216+ def test_str_endswith_fails (self ) -> None :
217+ series = pd .Series (["test.py" , "readme.md" ])
218+ fail_count , samples = apply_check (series , "str_endswith" , ".py" )
219+ assert fail_count == 1
220+ assert samples == ["readme.md" ]
221+
222+
223+ class TestStrContainsCheck :
224+ def test_str_contains_passes (self ) -> None :
225+ series = pd .Series (["hello world" , "world peace" ])
226+ fail_count , samples = apply_check (series , "str_contains" , "world" )
227+ assert fail_count == 0
228+
229+ def test_str_contains_fails (self ) -> None :
230+ series = pd .Series (["hello" , "goodbye" ])
231+ fail_count , samples = apply_check (series , "str_contains" , "world" )
232+ assert fail_count == 2
233+
234+ def test_str_contains_literal_not_regex (self ) -> None :
235+ # Ensure . is treated as literal, not regex wildcard
236+ series = pd .Series (["a.b" , "axb" ])
237+ fail_count , samples = apply_check (series , "str_contains" , "." )
238+ assert fail_count == 1
239+ assert samples == ["axb" ]
240+
241+ def test_str_contains_at_symbol (self ) -> None :
242+ series = pd .Series (["user@example.com" , "no-at-here" ])
243+ fail_count , samples = apply_check (series , "str_contains" , "@" )
244+ assert fail_count == 1
245+ assert samples == ["no-at-here" ]
246+
247+
248+ class TestStrLengthCheck :
249+ def test_str_length_passes (self ) -> None :
250+ series = pd .Series (["ab" , "abc" , "abcd" ])
251+ fail_count , samples = apply_check (series , "str_length" , (2 , 4 ))
252+ assert fail_count == 0
253+
254+ def test_str_length_fails_too_short (self ) -> None :
255+ series = pd .Series (["a" , "abc" ])
256+ fail_count , samples = apply_check (series , "str_length" , (2 , 4 ))
257+ assert fail_count == 1
258+ assert samples == ["a" ]
259+
260+ def test_str_length_fails_too_long (self ) -> None :
261+ series = pd .Series (["abc" , "abcdef" ])
262+ fail_count , samples = apply_check (series , "str_length" , (2 , 4 ))
263+ assert fail_count == 1
264+ assert samples == ["abcdef" ]
265+
266+ def test_str_length_inclusive (self ) -> None :
267+ series = pd .Series (["ab" , "abcd" ])
268+ fail_count , samples = apply_check (series , "str_length" , (2 , 4 ))
269+ assert fail_count == 0
270+
271+ def test_str_length_exact (self ) -> None :
272+ # Can use same min/max for exact length
273+ series = pd .Series (["abc" , "ab" , "abcd" ])
274+ fail_count , samples = apply_check (series , "str_length" , (3 , 3 ))
275+ assert fail_count == 2
276+
277+
167278class TestMaxSamples :
168279 def test_max_samples_limits_returned_values (self ) -> None :
169280 series = pd .Series ([0 , - 1 , - 2 , - 3 , - 4 , - 5 , - 6 , - 7 , - 8 , - 9 ])
0 commit comments