Skip to content

Commit 94a55dd

Browse files
authored
Merge pull request #203 from thorinaboenke/fix/conditional_string_comparison
fix only_if type conversion bug
2 parents 96ac5dd + 4f2e514 commit 94a55dd

2 files changed

Lines changed: 247 additions & 3 deletions

File tree

src/attackmate/executors/features/conditional.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def compare_value(cls, name: ast.Constant | ast.Name):
3636
if isinstance(name, ast.Name):
3737
return name.id
3838
elif isinstance(name, ast.Constant):
39-
return int(name.value)
39+
return name.value
4040
else:
4141
raise ConditionalError('part is neither a name nor constant')
4242

test/units/test_conditionals.py

Lines changed: 246 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,10 @@ def test_compare(self):
5151
assert Conditional.test('1 > 10') is False
5252
assert Conditional.test('test is test')
5353
assert Conditional.test('apple is not banana')
54-
assert Conditional.test('1 is True')
55-
assert Conditional.test('0 is True') is False
54+
assert Conditional.test('1 is True') is False
55+
assert Conditional.test('1 == True')
56+
assert Conditional.test('0 == True') is False
57+
assert Conditional.test('2 == True') is False
5658

5759
def test_exceptions(self):
5860
with pytest.raises(ConditionalError, match='Unknown Condition:'):
@@ -98,3 +100,245 @@ def test_regex(self):
98100
assert Conditional.test('hello !~ h[^aeiou]llo') is True
99101
assert Conditional.test('hello123 !~ h.*\\d+') is False
100102
assert Conditional.test('hello !~ ^h.*o$') is False
103+
104+
105+
class TestConditionalStringComparisons:
106+
"""Test conditional evaluations with string values"""
107+
108+
def test_string_equality_with_quotes(self):
109+
"""Test string equality: VAR == "hello" """
110+
# When variables are substituted, 'hello' becomes the Name 'hello'
111+
# and "hello" is a Constant string
112+
assert Conditional.test('hello == "hello"') is True
113+
assert Conditional.test('hello == "world"') is False
114+
115+
def test_string_equality_without_quotes(self):
116+
"""Test string equality: VAR == hello (both Names)"""
117+
assert Conditional.test('hello == hello') is True
118+
assert Conditional.test('hello == world') is False
119+
120+
def test_string_inequality(self):
121+
"""Test string inequality: VAR != "hello" """
122+
assert Conditional.test('hello != "world"') is True
123+
assert Conditional.test('hello != "hello"') is False
124+
125+
def test_string_comparison_less_than(self):
126+
"""Test string comparison: VAR < "world" """
127+
assert Conditional.test('"apple" < "banana"') is True
128+
assert Conditional.test('"zebra" < "apple"') is False
129+
130+
def test_string_comparison_greater_than(self):
131+
"""Test string comparison: VAR > "world" """
132+
assert Conditional.test('"zebra" > "apple"') is True
133+
assert Conditional.test('"apple" > "zebra"') is False
134+
135+
def test_string_comparison_less_than_or_equal(self):
136+
"""Test string comparison: VAR <= "world" """
137+
assert Conditional.test('"apple" <= "banana"') is True
138+
assert Conditional.test('"apple" <= "apple"') is True
139+
assert Conditional.test('"zebra" <= "apple"') is False
140+
141+
def test_string_comparison_greater_than_or_equal(self):
142+
"""Test string comparison: VAR >= "world" """
143+
assert Conditional.test('"zebra" >= "apple"') is True
144+
assert Conditional.test('"zebra" >= "zebra"') is True
145+
assert Conditional.test('"apple" >= "zebra"') is False
146+
147+
148+
class TestConditionalIntegerComparisons:
149+
"""Test conditional evaluations with integer values"""
150+
151+
def test_integer_equality(self):
152+
"""Test integer equality: VAR == 123"""
153+
assert Conditional.test('123 == 123') is True
154+
assert Conditional.test('123 == 456') is False
155+
156+
def test_integer_inequality(self):
157+
"""Test integer inequality: VAR != 123"""
158+
assert Conditional.test('123 != 456') is True
159+
assert Conditional.test('123 != 123') is False
160+
161+
def test_integer_less_than(self):
162+
"""Test integer comparison: VAR < 100"""
163+
assert Conditional.test('50 < 100') is True
164+
assert Conditional.test('150 < 100') is False
165+
166+
def test_integer_greater_than(self):
167+
"""Test integer comparison: VAR > 100"""
168+
assert Conditional.test('150 > 100') is True
169+
assert Conditional.test('50 > 100') is False
170+
171+
def test_integer_less_than_or_equal(self):
172+
"""Test integer comparison: VAR <= 100"""
173+
assert Conditional.test('50 <= 100') is True
174+
assert Conditional.test('100 <= 100') is True
175+
assert Conditional.test('150 <= 100') is False
176+
177+
def test_integer_greater_than_or_equal(self):
178+
"""Test integer comparison: VAR >= 100"""
179+
assert Conditional.test('150 >= 100') is True
180+
assert Conditional.test('100 >= 100') is True
181+
assert Conditional.test('50 >= 100') is False
182+
183+
def test_integer_boolean_equality(self):
184+
"""Test integer-boolean equality (Python's behavior: 1 == True, 0 == False)"""
185+
assert Conditional.test('1 == True') is True
186+
assert Conditional.test('0 == False') is True
187+
assert Conditional.test('2 == True') is False
188+
189+
190+
class TestConditionalMixedTypes:
191+
"""Test conditional evaluations with mixed types"""
192+
193+
def test_string_number_inequality(self):
194+
"""Test that string "123" is not equal to integer 123"""
195+
assert Conditional.test('"123" == 123') is False
196+
assert Conditional.test('"123" != 123') is True
197+
198+
def test_numeric_string_equality(self):
199+
"""Test numeric string equality"""
200+
assert Conditional.test('"123" == "123"') is True
201+
assert Conditional.test('"123" != "456"') is True
202+
203+
204+
class TestConditionalRegex:
205+
"""Test conditional regex matching"""
206+
207+
def test_regex_match(self):
208+
"""Test regex match: string =~ pattern"""
209+
assert Conditional.test('hello =~ h.*o') is True
210+
assert Conditional.test('hello =~ ^hell') is True
211+
assert Conditional.test('hello =~ world') is False
212+
213+
def test_regex_not_match(self):
214+
"""Test regex not match: string !~ pattern"""
215+
assert Conditional.test('hello !~ world') is True
216+
assert Conditional.test('hello !~ ^hell') is False
217+
218+
def test_regex_with_numbers(self):
219+
"""Test regex with numeric strings"""
220+
assert Conditional.test('123 =~ ^[0-9]+$') is True
221+
assert Conditional.test('hello123 =~ [0-9]+') is True
222+
assert Conditional.test('hello =~ ^[0-9]+$') is False
223+
224+
225+
class TestConditionalBoolean:
226+
"""Test conditional boolean evaluations"""
227+
228+
def test_truthy_variable(self):
229+
"""Test truthy variable evaluation (Names are truthy)"""
230+
assert Conditional.test('true') is True
231+
assert Conditional.test('True') is True
232+
233+
def test_not_operator(self):
234+
"""Test not operator"""
235+
assert Conditional.test('not False') is True
236+
assert Conditional.test('not True') is False
237+
238+
def test_constant_true(self):
239+
"""Test constant True evaluation"""
240+
assert Conditional.test('True') is True
241+
242+
def test_constant_false(self):
243+
"""Test constant False evaluation"""
244+
assert Conditional.test('False') is False
245+
246+
def test_empty_or_none(self):
247+
"""Test empty/None conditions"""
248+
assert Conditional.test(None) is False
249+
assert Conditional.test('') is False
250+
251+
252+
class TestConditionalIsOperator:
253+
"""Test conditional 'is' and 'is not' operators"""
254+
255+
def test_is_operator_with_none(self):
256+
"""Test 'is' operator with None"""
257+
assert Conditional.test('None is None') is True
258+
259+
def test_is_operator_with_names(self):
260+
"""Test 'is' operator with variable names"""
261+
# Note: Names (identifiers) will be compared as strings, not identity
262+
assert Conditional.test('test is test') is True
263+
assert Conditional.test('apple is banana') is False
264+
265+
def test_is_not_operator(self):
266+
"""Test 'is not' operator"""
267+
assert Conditional.test('apple is not banana') is True
268+
assert Conditional.test('None is not None') is False
269+
270+
def test_is_with_different_types(self):
271+
"""Test 'is' with integers (note: small integers are cached in Python)"""
272+
# In Python, 1 is True returns False (different objects)
273+
assert Conditional.test('1 is True') is False # Different ocjects
274+
assert Conditional.test('1 == True')
275+
276+
277+
class TestConditionalEdgeCases:
278+
"""Test edge cases and error conditions"""
279+
280+
def test_empty_string_equality(self):
281+
"""Test empty string comparisons"""
282+
assert Conditional.test('"" == ""') is True
283+
assert Conditional.test('"hello" == ""') is False
284+
285+
def test_whitespace_in_strings(self):
286+
"""Test strings with whitespace"""
287+
assert Conditional.test('"hello world" == "hello world"') is True
288+
assert Conditional.test('"hello" == "hello "') is False
289+
290+
def test_special_characters_in_strings(self):
291+
"""Test strings with special characters"""
292+
assert Conditional.test('"hello@world" == "hello@world"') is True
293+
assert Conditional.test('"a-b-c" == "a-b-c"') is True
294+
295+
296+
class TestConditionalRealWorldScenarios:
297+
"""Test real-world scenarios from the bug report"""
298+
299+
def test_scenario_int_variable(self):
300+
"""Test scenario with VAR_INT == '123'
301+
302+
In the real system, after variable substitution:
303+
- $VAR_INT becomes the value "123"
304+
- The condition "$VAR_INT == 123" becomes "123 == 123" (both integers)
305+
- The condition '$VAR_INT == "123"' becomes '123 == "123"' (int vs string)
306+
"""
307+
# After variable substitution, this becomes:
308+
assert Conditional.test('123 == "123"') is False # Different types
309+
assert Conditional.test('123 == 123') is True # Same type
310+
assert Conditional.test('"123" == "123"') is True # Both strings
311+
312+
def test_scenario_str_variable_no_crash(self):
313+
"""Test scenario with VAR_STR == 'hello' - should NOT crash
314+
315+
This is the main bug fix test. Before the fix, comparing a string
316+
variable to a string literal would crash with:
317+
ValueError: invalid literal for int() with base 10: 'hello'
318+
"""
319+
# This should not crash anymore - the key test for the bug fix
320+
assert Conditional.test('hello == "hello"') is True
321+
assert Conditional.test('"hello" == hello') is True
322+
assert Conditional.test('"hello" == "hello"') is True
323+
324+
def test_numeric_string_in_variable(self):
325+
"""Test when a variable contains a numeric string"""
326+
# Variable contains "123" as a string
327+
assert Conditional.test('"123" == "123"') is True
328+
assert Conditional.test('"123" != 123') is True # String vs int
329+
330+
def test_original_crash_scenario(self):
331+
"""Test the exact scenario that caused crash
332+
333+
From demo_str.yml:
334+
only_if: $VAR_STR == "hello"
335+
336+
After substitution becomes: hello == "hello"
337+
This would crash with the old code that forced int() conversion
338+
"""
339+
# This exact expression caused the crash
340+
try:
341+
result = Conditional.test('hello == "hello"')
342+
assert result is True
343+
except ValueError as e:
344+
pytest.fail(f'Should not raise ValueError: {e}')

0 commit comments

Comments
 (0)