test_feature.py 5.4 KB

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