test_feature.py 4.7 KB

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