/ tests / unit / common / test_feature_provider.py
test_feature_provider.py
  1  """
  2  Unit tests for SamFeatureProvider.
  3  
  4  Tests cover:
  5  - Boolean flag resolution through the checker
  6  - Handling of unregistered flags (returns default with DEFAULT reason)
  7  - Error resilience during resolution
  8  - Other flag types always return default (SAM only uses boolean flags)
  9  - all_flags() delegation
 10  - load_flags_from_yaml() merging
 11  """
 12  
 13  import textwrap
 14  from pathlib import Path
 15  from unittest.mock import MagicMock
 16  
 17  import pytest
 18  import yaml
 19  
 20  from openfeature import api as openfeature_api
 21  from openfeature.flag_evaluation import Reason
 22  
 23  from solace_agent_mesh.common.features.checker import FeatureChecker
 24  from solace_agent_mesh.common.features.provider import SamFeatureProvider
 25  from solace_agent_mesh.common.features.registry import (
 26      FeatureDefinition,
 27      FeatureRegistry,
 28      ReleasePhase,
 29  )
 30  
 31  
 32  @pytest.fixture(autouse=True)
 33  def _reset_openfeature():
 34      yield
 35      openfeature_api.clear_providers()
 36  
 37  
 38  def _make_checker(*flags: tuple[str, bool]) -> FeatureChecker:
 39      reg = FeatureRegistry()
 40      for key, default in flags:
 41          reg.register(
 42              FeatureDefinition(
 43                  key=key,
 44                  name=key.replace("_", " ").title(),
 45                  release_phase=ReleasePhase.GENERAL_AVAILABILITY,
 46                  default=default,
 47                  jira="DATAGO-99999",
 48              )
 49          )
 50      return FeatureChecker(registry=reg)
 51  
 52  
 53  def _minimal_flag_yaml(**overrides) -> dict:
 54      base = {
 55          "key": "f",
 56          "name": "F",
 57          "release_phase": "general_availability",
 58          "default": False,
 59          "jira": "DATAGO-99999",
 60      }
 61      base.update(overrides)
 62      return base
 63  
 64  
 65  class TestResolveBooleanDetails:
 66      def test_returns_true_for_enabled_flag(self, monkeypatch):
 67          monkeypatch.delenv("SAM_FEATURE_F", raising=False)
 68          provider = SamFeatureProvider(_make_checker(("f", True)))
 69          result = provider.resolve_boolean_details("f", False)
 70          assert result.value is True
 71          assert result.reason == Reason.STATIC
 72  
 73      def test_returns_false_for_disabled_flag(self, monkeypatch):
 74          monkeypatch.delenv("SAM_FEATURE_F", raising=False)
 75          provider = SamFeatureProvider(_make_checker(("f", False)))
 76          result = provider.resolve_boolean_details("f", True)
 77          assert result.value is False
 78          assert result.reason == Reason.STATIC
 79  
 80      def test_env_var_override_reflected(self, monkeypatch):
 81          monkeypatch.setenv("SAM_FEATURE_F", "true")
 82          provider = SamFeatureProvider(_make_checker(("f", False)))
 83          result = provider.resolve_boolean_details("f", False)
 84          assert result.value is True
 85          assert result.reason == Reason.STATIC
 86  
 87      def test_unregistered_flag_returns_default_value(self, monkeypatch):
 88          monkeypatch.delenv("SAM_FEATURE_MISSING", raising=False)
 89          provider = SamFeatureProvider(_make_checker(("other", True)))
 90          result = provider.resolve_boolean_details("missing", True)
 91          assert result.value is True
 92          assert result.reason == Reason.DEFAULT
 93  
 94      def test_unregistered_flag_default_false(self, monkeypatch):
 95          monkeypatch.delenv("SAM_FEATURE_MISSING", raising=False)
 96          provider = SamFeatureProvider(_make_checker())
 97          result = provider.resolve_boolean_details("missing", False)
 98          assert result.value is False
 99          assert result.reason == Reason.DEFAULT
100  
101      def test_exception_during_resolution_returns_error(self, monkeypatch):
102          monkeypatch.delenv("SAM_FEATURE_F", raising=False)
103          checker = _make_checker(("f", True))
104          checker.is_enabled = MagicMock(side_effect=RuntimeError("boom"))
105          provider = SamFeatureProvider(checker)
106          result = provider.resolve_boolean_details("f", False)
107          assert result.value is False
108          assert result.reason == Reason.ERROR
109  
110  
111  class TestOtherTypeResolution:
112      def _provider(self):
113          return SamFeatureProvider(_make_checker(("f", True)))
114  
115      def test_string_returns_default(self):
116          result = self._provider().resolve_string_details("f", "default_str")
117          assert result.value == "default_str"
118          assert result.reason == Reason.DEFAULT
119  
120      def test_integer_returns_default(self):
121          result = self._provider().resolve_integer_details("f", 42)
122          assert result.value == 42
123          assert result.reason == Reason.DEFAULT
124  
125      def test_float_returns_default(self):
126          result = self._provider().resolve_float_details("f", 3.14)
127          assert result.value == 3.14
128          assert result.reason == Reason.DEFAULT
129  
130      def test_object_returns_default(self):
131          result = self._provider().resolve_object_details("f", {"a": 1})
132          assert result.value == {"a": 1}
133          assert result.reason == Reason.DEFAULT
134  
135  
136  class TestAllFlags:
137      def test_delegates_to_checker(self, monkeypatch):
138          monkeypatch.delenv("SAM_FEATURE_A", raising=False)
139          monkeypatch.delenv("SAM_FEATURE_B", raising=False)
140          provider = SamFeatureProvider(_make_checker(("a", True), ("b", False)))
141          assert provider.all_flags() == {"a": True, "b": False}
142  
143  
144  class TestLoadFlagsFromYaml:
145      def test_merges_additional_flags(self, tmp_path):
146          provider = SamFeatureProvider(_make_checker(("existing", True)))
147          p = tmp_path / "extra.yaml"
148          p.write_text(yaml.dump({"features": [_minimal_flag_yaml(key="new_flag", name="New Flag")]}))
149          provider.load_flags_from_yaml(p)
150          assert provider._checker.is_known_flag("new_flag") is True
151          assert provider._checker.is_known_flag("existing") is True
152  
153      def test_later_yaml_overrides_existing_flag(self, tmp_path, monkeypatch):
154          monkeypatch.delenv("SAM_FEATURE_F", raising=False)
155          provider = SamFeatureProvider(_make_checker(("f", False)))
156          p = tmp_path / "override.yaml"
157          p.write_text(yaml.dump({"features": [_minimal_flag_yaml(name="F Override", default=True)]}))
158          provider.load_flags_from_yaml(p)
159          assert provider._checker.is_enabled("f") is True
160  
161  
162  class TestOpenFeatureIntegration:
163      def test_registered_provider_is_used_by_api(self, monkeypatch):
164          monkeypatch.delenv("SAM_FEATURE_F", raising=False)
165          checker = _make_checker(("f", True))
166          provider = SamFeatureProvider(checker)
167          openfeature_api.set_provider(provider)
168  
169          client = openfeature_api.get_client()
170          assert client.get_boolean_value("f", False) is True
171  
172      def test_unknown_flag_falls_back_to_api_default(self, monkeypatch):
173          monkeypatch.delenv("SAM_FEATURE_NOPE", raising=False)
174          provider = SamFeatureProvider(_make_checker())
175          openfeature_api.set_provider(provider)
176  
177          client = openfeature_api.get_client()
178          assert client.get_boolean_value("nope", True) is True