test_feature_core.py
1 """ 2 Unit tests for the feature flag service module. 3 4 Each test class is isolated: the autouse fixture resets module state and 5 clears the OpenFeature global provider before and after every test so that 6 state never leaks between tests or test files. 7 8 Synthetic flag keys (never real production flag names) are used throughout 9 so that tests do not couple to specific feature flag definitions. 10 """ 11 12 import os 13 import threading 14 from unittest.mock import patch 15 16 import pytest 17 from openfeature import api as openfeature_api 18 19 from solace_agent_mesh.common.features.provider import SamFeatureProvider 20 21 from sam_test_infrastructure.feature_flags import mock_flags 22 from solace_agent_mesh.common.features.core import ( 23 _reset_for_testing, 24 get_registry, 25 has_env_override, 26 initialize, 27 is_known_flag, 28 load_flags_from_yaml, 29 ) 30 import solace_agent_mesh.common.features.core as feature_core_module 31 32 33 @pytest.fixture(autouse=True) 34 def _reset(): 35 _reset_for_testing() 36 yield 37 _reset_for_testing() 38 39 40 class TestLazyInitialization: 41 """Module does not initialize on import; first use triggers init.""" 42 43 def test_not_initialized_at_start(self): 44 assert feature_core_module._initialized is False 45 assert feature_core_module._checker is None 46 47 def test_is_known_flag_triggers_init(self): 48 is_known_flag("any_key") 49 assert feature_core_module._initialized is True 50 51 def test_get_registry_triggers_init(self): 52 get_registry() 53 assert feature_core_module._initialized is True 54 55 56 class TestIdempotentInitialization: 57 """initialize() is safe to call multiple times and from multiple threads.""" 58 59 def test_double_initialize_reuses_checker(self): 60 initialize() 61 checker_first = feature_core_module._checker 62 initialize() 63 assert feature_core_module._checker is checker_first 64 65 def test_concurrent_initialization_no_errors(self): 66 errors = [] 67 68 def _init(): 69 try: 70 initialize() 71 except Exception as exc: # pylint: disable=broad-except 72 errors.append(exc) 73 74 threads = [threading.Thread(target=_init) for _ in range(10)] 75 for t in threads: 76 t.start() 77 for t in threads: 78 t.join() 79 80 assert not errors 81 assert feature_core_module._initialized is True 82 83 def test_concurrent_init_produces_single_checker(self): 84 checkers = [] 85 86 def _capture(): 87 initialize() 88 checkers.append(feature_core_module._checker) 89 90 threads = [threading.Thread(target=_capture) for _ in range(10)] 91 for t in threads: 92 t.start() 93 for t in threads: 94 t.join() 95 96 assert all(c is checkers[0] for c in checkers) 97 98 99 class TestCommunityYamlLoading: 100 """Community features.yaml loads cleanly and produces a valid registry.""" 101 102 def test_community_yaml_loads_without_error(self): 103 initialize() 104 flags = get_registry().all() 105 assert len(flags) > 0 106 for defn in flags: 107 assert defn.key != "" 108 assert defn.jira != "" 109 110 def test_unknown_flag_returns_false(self): 111 initialize() 112 assert openfeature_api.get_client().get_boolean_value("__nonexistent_flag_xyz__", False) is False 113 114 115 class TestLoadFlagsFromYaml: 116 """load_flags_from_yaml() merges additional definitions without removing existing ones.""" 117 118 def test_extra_yaml_merged_preserves_community_flags(self, tmp_path): 119 extra_yaml = tmp_path / "extra.yaml" 120 extra_yaml.write_text( 121 "features:\n" 122 " - key: test_alpha_flag\n" 123 " name: Test Alpha\n" 124 " release_phase: experimental\n" 125 " default: true\n" 126 " jira: DATAGO-99999\n" 127 ) 128 129 initialize() 130 community_count = len(get_registry().all()) 131 load_flags_from_yaml(str(extra_yaml)) 132 133 client = openfeature_api.get_client() 134 assert is_known_flag("test_alpha_flag") 135 assert client.get_boolean_value("test_alpha_flag", False) is True 136 assert len(get_registry().all()) == community_count + 1 137 138 def test_extra_yaml_with_multiple_flags(self, tmp_path): 139 extra_yaml = tmp_path / "extra.yaml" 140 extra_yaml.write_text( 141 "features:\n" 142 " - key: test_beta_flag\n" 143 " name: Test Beta\n" 144 " release_phase: beta\n" 145 " default: false\n" 146 " jira: DATAGO-99999\n" 147 " - key: test_gamma_flag\n" 148 " name: Test Gamma\n" 149 " release_phase: early_access\n" 150 " default: true\n" 151 " jira: DATAGO-99999\n" 152 ) 153 154 initialize() 155 load_flags_from_yaml(str(extra_yaml)) 156 157 client = openfeature_api.get_client() 158 assert is_known_flag("test_beta_flag") 159 assert is_known_flag("test_gamma_flag") 160 assert client.get_boolean_value("test_beta_flag", False) is False 161 assert client.get_boolean_value("test_gamma_flag", False) is True 162 163 164 class TestOpenFeatureIntegration: 165 """OpenFeature provider is correctly registered and evaluation goes through it.""" 166 167 def test_provider_is_sam_feature_provider_after_init(self): 168 initialize() 169 provider = openfeature_api.provider_registry.get_default_provider() 170 assert isinstance(provider, SamFeatureProvider) 171 172 def test_openfeature_client_evaluates_registered_flag(self, tmp_path): 173 extra_yaml = tmp_path / "extra.yaml" 174 extra_yaml.write_text( 175 "features:\n" 176 " - key: test_of_flag\n" 177 " name: Test OF Flag\n" 178 " release_phase: general_availability\n" 179 " default: true\n" 180 " jira: DATAGO-99999\n" 181 ) 182 initialize() 183 load_flags_from_yaml(str(extra_yaml)) 184 185 assert openfeature_api.get_client().get_boolean_value("test_of_flag", False) is True 186 187 188 class TestEnvOverride: 189 """has_env_override() and env var evaluation via the OpenFeature path.""" 190 191 @pytest.fixture() 192 def _registered_key(self, tmp_path): 193 yaml_path = tmp_path / "test_flags.yaml" 194 yaml_path.write_text( 195 "features:\n" 196 " - key: test_env_flag\n" 197 " name: Test Env Flag\n" 198 " release_phase: general_availability\n" 199 " default: false\n" 200 " jira: DATAGO-99999\n" 201 ) 202 initialize() 203 load_flags_from_yaml(str(yaml_path)) 204 return "test_env_flag" 205 206 def test_no_env_var_returns_false(self, _registered_key): 207 assert has_env_override(_registered_key) is False 208 209 def test_env_var_present_returns_true(self, _registered_key): 210 with patch.dict(os.environ, {"SAM_FEATURE_TEST_ENV_FLAG": "true"}): 211 assert has_env_override(_registered_key) is True 212 213 def test_env_var_overrides_default_to_enabled(self, _registered_key): 214 client = openfeature_api.get_client() 215 with patch.dict(os.environ, {"SAM_FEATURE_TEST_ENV_FLAG": "true"}): 216 assert client.get_boolean_value(_registered_key, False) is True 217 218 def test_flag_disabled_without_env_var(self, _registered_key): 219 assert openfeature_api.get_client().get_boolean_value(_registered_key, False) is False 220 221 222 class TestEnterpriseIntegration: 223 """initialize() loads enterprise flags when the enterprise package is available.""" 224 225 def test_enterprise_flags_loaded_during_initialize(self, monkeypatch, tmp_path): 226 import sys 227 import types 228 229 yaml_path = tmp_path / "enterprise.yaml" 230 yaml_path.write_text( 231 "features:\n" 232 " - key: test_enterprise_flag\n" 233 " name: Test Enterprise Flag\n" 234 " release_phase: early_access\n" 235 " default: false\n" 236 " jira: DATAGO-99999\n" 237 ) 238 239 def _fake_register(): 240 load_flags_from_yaml(str(yaml_path)) 241 242 fake_module = types.SimpleNamespace( 243 _register_enterprise_feature_flags=_fake_register 244 ) 245 monkeypatch.setitem( 246 sys.modules, 247 "solace_agent_mesh_enterprise.init_enterprise", 248 fake_module, 249 ) 250 251 initialize() 252 253 assert is_known_flag("test_enterprise_flag") 254 255 def test_enterprise_flags_registered_after_provider(self, monkeypatch, tmp_path): 256 import sys 257 import types 258 from openfeature import api as openfeature_api 259 from solace_agent_mesh.common.features.provider import SamFeatureProvider 260 261 provider_at_call_time = [] 262 263 def _fake_register(): 264 provider_at_call_time.append( 265 openfeature_api.provider_registry.get_default_provider() 266 ) 267 268 fake_module = types.SimpleNamespace( 269 _register_enterprise_feature_flags=_fake_register 270 ) 271 monkeypatch.setitem( 272 sys.modules, 273 "solace_agent_mesh_enterprise.init_enterprise", 274 fake_module, 275 ) 276 277 initialize() 278 279 assert len(provider_at_call_time) == 1 280 assert isinstance(provider_at_call_time[0], SamFeatureProvider) 281 282 def test_initialize_succeeds_when_enterprise_not_installed(self, monkeypatch): 283 import sys 284 285 monkeypatch.setitem( 286 sys.modules, "solace_agent_mesh_enterprise.init_enterprise", None 287 ) 288 289 initialize() 290 291 assert feature_core_module._initialized is True 292 assert len(get_registry().all()) > 0 293 294 295 class TestResetForTesting: 296 """_reset_for_testing() clears module state and allows full re-initialisation.""" 297 298 def test_reset_clears_state(self): 299 initialize() 300 _reset_for_testing() 301 assert feature_core_module._initialized is False 302 assert feature_core_module._checker is None 303 304 def test_reset_clears_openfeature_provider(self): 305 initialize() 306 _reset_for_testing() 307 provider = openfeature_api.provider_registry.get_default_provider() 308 assert not isinstance(provider, SamFeatureProvider) 309 310 def test_can_reinitialize_after_reset(self): 311 initialize() 312 _reset_for_testing() 313 initialize() 314 assert feature_core_module._initialized is True 315 assert len(get_registry().all()) > 0 316 317 318 class TestMockFlags: 319 """mock_flags() context manager sets env-var overrides for the test scope.""" 320 321 def _register_flag(self, tmp_path, key: str, default: bool): 322 """Write a single-flag YAML and load it into the initialised registry.""" 323 yaml_path = tmp_path / f"{key}.yaml" 324 yaml_path.write_text( 325 "features:\n" 326 f" - key: {key}\n" 327 f" name: Test {key}\n" 328 " release_phase: beta\n" 329 f" default: {str(default).lower()}\n" 330 " jira: DATAGO-99999\n" 331 ) 332 initialize() 333 load_flags_from_yaml(str(yaml_path)) 334 335 def test_false_default_overridden_to_true(self, tmp_path): 336 self._register_flag(tmp_path, "test_mock_flag_a", False) 337 client = openfeature_api.get_client() 338 339 assert client.get_boolean_value("test_mock_flag_a", False) is False 340 with mock_flags(test_mock_flag_a=True): 341 assert client.get_boolean_value("test_mock_flag_a", False) is True 342 assert client.get_boolean_value("test_mock_flag_a", False) is False 343 344 def test_true_default_overridden_to_false(self, tmp_path): 345 self._register_flag(tmp_path, "test_mock_flag_b", True) 346 client = openfeature_api.get_client() 347 348 assert client.get_boolean_value("test_mock_flag_b", True) is True 349 with mock_flags(test_mock_flag_b=False): 350 assert client.get_boolean_value("test_mock_flag_b", True) is False 351 assert client.get_boolean_value("test_mock_flag_b", True) is True 352 353 def test_unspecified_flags_unaffected(self, tmp_path): 354 self._register_flag(tmp_path, "test_mock_flag_c", False) 355 self._register_flag(tmp_path, "test_mock_flag_d", False) 356 client = openfeature_api.get_client() 357 358 with mock_flags(test_mock_flag_c=True): 359 assert client.get_boolean_value("test_mock_flag_c", False) is True 360 assert client.get_boolean_value("test_mock_flag_d", False) is False 361 362 def test_env_var_absent_after_context_exits(self, tmp_path): 363 self._register_flag(tmp_path, "test_mock_flag_e", False) 364 env_key = "SAM_FEATURE_TEST_MOCK_FLAG_E" 365 366 assert env_key not in os.environ 367 with mock_flags(test_mock_flag_e=True): 368 assert os.environ.get(env_key) == "true" 369 assert env_key not in os.environ 370 371 def test_pre_existing_env_var_restored_after_context(self, tmp_path): 372 self._register_flag(tmp_path, "test_mock_flag_f", False) 373 env_key = "SAM_FEATURE_TEST_MOCK_FLAG_F" 374 375 os.environ[env_key] = "true" 376 try: 377 with mock_flags(test_mock_flag_f=False): 378 assert os.environ.get(env_key) == "false" 379 assert os.environ.get(env_key) == "true" 380 finally: 381 os.environ.pop(env_key, None) 382 383 def test_multiple_flags_controlled_independently(self, tmp_path): 384 self._register_flag(tmp_path, "test_mock_flag_g", False) 385 self._register_flag(tmp_path, "test_mock_flag_h", True) 386 client = openfeature_api.get_client() 387 388 with mock_flags(test_mock_flag_g=True, test_mock_flag_h=False): 389 assert client.get_boolean_value("test_mock_flag_g", False) is True 390 assert client.get_boolean_value("test_mock_flag_h", True) is False