test_feature.py 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. from datetime import datetime, timezone
  2. import pytest
  3. from flagpole import ContextBuilder, EvaluationContext, Feature, InvalidFeatureFlagConfiguration
  4. from flagpole.operators import OperatorKind
  5. from sentry.testutils.helpers import override_options
  6. @pytest.fixture(autouse=True)
  7. def run_before_each():
  8. with override_options({"flagpole.enable-orjson": 0.0}):
  9. yield
  10. class TestParseFeatureConfig:
  11. def get_is_true_context_builder(self, is_true_value: bool):
  12. return ContextBuilder().add_context_transformer(lambda _data: dict(is_true=is_true_value))
  13. def test_valid_without_created_at(self):
  14. feature = Feature.from_feature_config_json("foo", '{"owner": "test", "segments":[]}')
  15. assert feature.name == "foo"
  16. assert isinstance(feature.created_at, datetime)
  17. assert feature.segments == []
  18. def test_feature_with_empty_segments(self):
  19. feature = Feature.from_feature_config_json(
  20. "foobar",
  21. """
  22. {
  23. "created_at": "2023-10-12T00:00:00.000Z",
  24. "owner": "test-owner",
  25. "segments": []
  26. }
  27. """,
  28. )
  29. assert feature.name == "foobar"
  30. assert feature.created_at == datetime(2023, 10, 12, tzinfo=timezone.utc)
  31. assert feature.owner == "test-owner"
  32. assert feature.segments == []
  33. assert not feature.match(EvaluationContext(dict()))
  34. def test_valid_with_all_nesting(self):
  35. feature = Feature.from_feature_config_json(
  36. "foobar",
  37. """
  38. {
  39. "created_at": "2023-10-12T00:00:00.000Z",
  40. "owner": "test-owner",
  41. "segments": [{
  42. "name": "segment1",
  43. "rollout": 100,
  44. "conditions": [{
  45. "property": "test_property",
  46. "operator": {
  47. "kind": "in",
  48. "value": ["foobar"]
  49. }
  50. }]
  51. }]
  52. }
  53. """,
  54. )
  55. assert feature.name == "foobar"
  56. assert len(feature.segments) == 1
  57. assert feature.segments[0].name == "segment1"
  58. assert feature.segments[0].rollout == 100
  59. assert len(feature.segments[0].conditions) == 1
  60. condition = feature.segments[0].conditions[0]
  61. assert condition.property == "test_property"
  62. assert condition.operator
  63. assert condition.operator.kind == OperatorKind.IN
  64. assert condition.operator.value == ["foobar"]
  65. assert feature.match(EvaluationContext(dict(test_property="foobar")))
  66. assert not feature.match(EvaluationContext(dict(test_property="barfoo")))
  67. def test_invalid_json(self):
  68. with pytest.raises(InvalidFeatureFlagConfiguration):
  69. Feature.from_feature_config_json("foobar", "{")
  70. def test_empty_string_name(self):
  71. with pytest.raises(InvalidFeatureFlagConfiguration) as exception:
  72. Feature.from_feature_config_json("", '{"segments":[]}')
  73. assert "Provided JSON is not a valid feature" in str(exception)
  74. def test_missing_segments(self):
  75. with pytest.raises(InvalidFeatureFlagConfiguration) as exception:
  76. Feature.from_feature_config_json("foo", "{}")
  77. assert "Provided JSON is not a valid feature" in str(exception)
  78. def test_enabled_feature(self):
  79. feature = Feature.from_feature_config_json(
  80. "foo",
  81. """
  82. {
  83. "owner": "test-user",
  84. "segments": [{
  85. "name": "always_pass_segment",
  86. "rollout": 100,
  87. "conditions": [{
  88. "name": "Always true",
  89. "property": "is_true",
  90. "operator": {
  91. "kind": "equals",
  92. "value": true
  93. }
  94. }]
  95. }]
  96. }
  97. """,
  98. )
  99. context_builder = self.get_is_true_context_builder(is_true_value=True)
  100. assert feature.match(context_builder.build())
  101. def test_disabled_feature(self):
  102. feature = Feature.from_feature_config_json(
  103. "foo",
  104. """
  105. {
  106. "owner": "test-user",
  107. "enabled": false,
  108. "segments": [{
  109. "name": "always_pass_segment",
  110. "rollout": 100,
  111. "conditions": [{
  112. "name": "Always true",
  113. "property": "is_true",
  114. "operator": {
  115. "kind": "equals",
  116. "value": true
  117. }
  118. }]
  119. }]
  120. }
  121. """,
  122. )
  123. context_builder = self.get_is_true_context_builder(is_true_value=True)
  124. assert not feature.match(context_builder.build())