test_operators.py 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. from typing import Any
  2. import pytest
  3. from pydantic import ValidationError
  4. from flagpole.operators import (
  5. ConditionTypeMismatchException,
  6. ContainsOperator,
  7. EqualsOperator,
  8. InOperator,
  9. NotContainsOperator,
  10. NotEqualsOperator,
  11. NotInOperator,
  12. Operator,
  13. OperatorKind,
  14. create_case_insensitive_set_from_list,
  15. )
  16. from sentry.testutils.cases import TestCase
  17. from sentry.utils import json
  18. class TestCreateCaseInsensitiveSetFromList(TestCase):
  19. def test_empty_set(self):
  20. assert create_case_insensitive_set_from_list([]) == set()
  21. def test_returns_int_set(self):
  22. assert create_case_insensitive_set_from_list([1, 2, 3]) == {1, 2, 3}
  23. def test_returns_float_set(self):
  24. assert create_case_insensitive_set_from_list([1.1, 2.2, 3.3]) == {1.1, 2.2, 3.3}
  25. def test_returns_lowercase_string_set(self):
  26. assert create_case_insensitive_set_from_list(["AbC", "DEF"]) == {"abc", "def"}
  27. def assert_valid_types(operator: type[Operator], expected_types: list[Any]):
  28. for value in expected_types:
  29. operator_dict = dict(value=value)
  30. json_operator = json.dumps(operator_dict)
  31. try:
  32. parsed_operator = operator.parse_raw(json_operator)
  33. except ValidationError as exc:
  34. raise AssertionError(
  35. f"Expected value `{value}` to be a valid value for operator '{operator}'"
  36. ) from exc
  37. assert parsed_operator.value == value
  38. def assert_invalid_types(operator: type[Operator], invalid_types: list[Any]):
  39. for value in invalid_types:
  40. json_dict = dict(value=value)
  41. operator_json = json.dumps(json_dict)
  42. try:
  43. operator.parse_raw(operator_json)
  44. except ValidationError:
  45. continue
  46. raise AssertionError(
  47. f"Expected validation error for value: `{value}` for operator `{operator}`"
  48. )
  49. class TestInOperators(TestCase):
  50. def test_invalid_values(self):
  51. with pytest.raises(ValidationError):
  52. InOperator(value="bar")
  53. with pytest.raises(ValidationError):
  54. InOperator(value=1234)
  55. def test_is_in(self):
  56. values = ["bar", "baz"]
  57. operator = InOperator(kind=OperatorKind.IN, value=values)
  58. assert operator.match(condition_property="bar", segment_name="test")
  59. not_operator = NotInOperator(kind=OperatorKind.NOT_IN, value=values)
  60. assert not not_operator.match(condition_property="bar", segment_name="test")
  61. int_values = [1, 2]
  62. operator = InOperator(kind=OperatorKind.IN, value=int_values)
  63. # Validation check to ensure no type coercion occurs
  64. assert operator.value == int_values
  65. assert operator.match(condition_property=2, segment_name="test")
  66. assert not operator.match(condition_property=3, segment_name="test")
  67. def test_is_in_numeric_string(self):
  68. values = ["123", "456"]
  69. operator = InOperator(kind=OperatorKind.IN, value=values)
  70. assert operator.value == values
  71. assert not operator.match(condition_property=123, segment_name="test")
  72. assert operator.match(condition_property="123", segment_name="test")
  73. def test_is_not_in(self):
  74. values = ["bar", "baz"]
  75. operator = InOperator(kind=OperatorKind.IN, value=values)
  76. assert not operator.match(condition_property="foo", segment_name="test")
  77. not_operator = NotInOperator(kind=OperatorKind.NOT_IN, value=values)
  78. assert not_operator.match(condition_property="foo", segment_name="test")
  79. def test_is_in_case_insensitivity(self):
  80. values = ["bAr", "baz"]
  81. operator = InOperator(kind=OperatorKind.IN, value=values)
  82. assert operator.match(condition_property="BaR", segment_name="test")
  83. not_operator = NotInOperator(kind=OperatorKind.NOT_IN, value=values)
  84. assert not not_operator.match(condition_property="BaR", segment_name="test")
  85. def test_invalid_property_value(self):
  86. values = ["bar", "baz"]
  87. operator = InOperator(kind=OperatorKind.IN, value=values)
  88. with pytest.raises(ConditionTypeMismatchException):
  89. operator.match(condition_property=[], segment_name="test")
  90. not_operator = NotInOperator(kind=OperatorKind.NOT_IN, value=values)
  91. with pytest.raises(ConditionTypeMismatchException):
  92. not_operator.match(condition_property=[], segment_name="test")
  93. def test_valid_json_and_reparse(self):
  94. values = [["foo", "bar"], [1, 2], [1.1, 2.2], []]
  95. assert_valid_types(operator=InOperator, expected_types=values)
  96. assert_valid_types(operator=NotInOperator, expected_types=values)
  97. def test_invalid_value_type_parsing(self):
  98. values = ["abc", 1, 2.2, True, None, ["a", 1], [True], [[]], [1, 2.2], [1.1, "2.2"]]
  99. assert_invalid_types(operator=InOperator, invalid_types=values)
  100. assert_invalid_types(operator=NotInOperator, invalid_types=values)
  101. class TestContainsOperators(TestCase):
  102. def test_does_contain(self):
  103. operator = ContainsOperator(kind=OperatorKind.CONTAINS, value="bar")
  104. assert operator.match(condition_property=["foo", "bar"], segment_name="test")
  105. not_operator = NotContainsOperator(kind=OperatorKind.NOT_CONTAINS, value="bar")
  106. assert not not_operator.match(condition_property=["foo", "bar"], segment_name="test")
  107. operator = ContainsOperator(kind=OperatorKind.CONTAINS, value=1)
  108. assert operator.match(condition_property=[1, 2], segment_name="test")
  109. assert not operator.match(condition_property=[3, 4], segment_name="test")
  110. def test_does_not_contain(self):
  111. values = "baz"
  112. operator = ContainsOperator(kind=OperatorKind.CONTAINS, value=values)
  113. assert not operator.match(condition_property=["foo", "bar"], segment_name="test")
  114. not_operator = NotContainsOperator(kind=OperatorKind.NOT_CONTAINS, value=values)
  115. assert not_operator.match(condition_property=["foo", "bar"], segment_name="test")
  116. def test_invalid_property_provided(self):
  117. values = "baz"
  118. with pytest.raises(ConditionTypeMismatchException):
  119. operator = ContainsOperator(kind=OperatorKind.CONTAINS, value=values)
  120. assert not operator.match(condition_property="oops", segment_name="test")
  121. with pytest.raises(ConditionTypeMismatchException):
  122. not_operator = NotContainsOperator(kind=OperatorKind.NOT_CONTAINS, value=values)
  123. assert not_operator.match(condition_property="oops", segment_name="test")
  124. def test_valid_json_parsing_with_types(self):
  125. values = [1, 2.2, "abc"]
  126. assert_valid_types(operator=ContainsOperator, expected_types=values)
  127. assert_valid_types(operator=NotContainsOperator, expected_types=values)
  128. def test_invalid_value_type_parsing(self):
  129. values: list[Any] = [
  130. None,
  131. [],
  132. dict(foo="bar"),
  133. [[]],
  134. ]
  135. assert_invalid_types(operator=ContainsOperator, invalid_types=values)
  136. assert_invalid_types(operator=NotContainsOperator, invalid_types=values)
  137. class TestEqualsOperators(TestCase):
  138. def test_is_equal_string(self):
  139. value = "foo"
  140. operator = EqualsOperator(kind=OperatorKind.EQUALS, value=value)
  141. assert operator.match(condition_property="foo", segment_name="test")
  142. not_operator = NotEqualsOperator(kind=OperatorKind.NOT_EQUALS, value=value)
  143. assert not not_operator.match(condition_property="foo", segment_name="test")
  144. def test_is_not_equals(self):
  145. values = "bar"
  146. operator = EqualsOperator(kind=OperatorKind.EQUALS, value=values)
  147. assert not operator.match(condition_property="foo", segment_name="test")
  148. not_operator = NotEqualsOperator(kind=OperatorKind.NOT_EQUALS, value=values)
  149. assert not_operator.match(condition_property="foo", segment_name="test")
  150. def test_is_equal_case_insensitive(self):
  151. values = "bAr"
  152. operator = EqualsOperator(kind=OperatorKind.EQUALS, value=values)
  153. assert operator.match(condition_property="BaR", segment_name="test")
  154. not_operator = NotEqualsOperator(kind=OperatorKind.NOT_EQUALS, value=values)
  155. assert not not_operator.match(condition_property="BaR", segment_name="test")
  156. def test_equality_type_mismatch_strings(self):
  157. values = ["foo", "bar"]
  158. operator = EqualsOperator(kind=OperatorKind.EQUALS, value=values)
  159. with pytest.raises(ConditionTypeMismatchException):
  160. operator.match(condition_property="foo", segment_name="test")
  161. not_operator = NotEqualsOperator(kind=OperatorKind.NOT_EQUALS, value=values)
  162. with pytest.raises(ConditionTypeMismatchException):
  163. not_operator.match(condition_property="foo", segment_name="test")
  164. def test_valid_json_parsing_with_types(self):
  165. values = [1, 2.2, "abc", True, False, [], ["foo"], [1], [1.1]]
  166. assert_valid_types(operator=EqualsOperator, expected_types=values)
  167. assert_valid_types(operator=NotEqualsOperator, expected_types=values)
  168. def test_invalid_value_type_parsing(self):
  169. values = [None, dict(foo="bar")]
  170. assert_invalid_types(operator=EqualsOperator, invalid_types=values)
  171. assert_invalid_types(operator=NotEqualsOperator, invalid_types=values)