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