11"""Parametric FFE configuration-source coverage for a mocked FFE agentless backend.
22
3- Feature under test: server SDKs can select where UFC flag definitions come from.
4- The agentless path fetches from an HTTP backend, while explicit ``remote_config``
5- keeps the existing Agent RC path.
3+ Feature under test: server SDKs preserve legacy Remote Configuration adopters,
4+ load UFC flag definitions from the agentless HTTP backend by default only after
5+ application provider access, and honor explicit source selection and the stable
6+ provider kill switch.
67
78Test strategy: drive SDKs through public configuration-source env vars and
89OpenFeature evaluation endpoints, then use the mock FFE agentless backend for
2223from tests .parametric .conftest import APMLibrary
2324from tests .parametric .test_ffe .test_dynamic_evaluation import _set_and_wait_ffe_rc , _ffe_evaluate_with_rc_retry
2425from utils import features , scenarios
25- from utils .dd_constants import RemoteConfigApplyState
26+ from utils .dd_constants import Capabilities , RemoteConfigApplyState
2627from utils .docker_fixtures import TestAgentAPI
2728from utils .docker_fixtures ._mock_ffe_agentless_backend import (
2829 CONFIG_PATH ,
4243AGENTLESS_BASE_URL = "DD_FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_BASE_URL"
4344
4445BASE_ENVVARS = {
45- "DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED" : "true" ,
46- "DD_TELEMETRY_HEARTBEAT_INTERVAL" : "0.2" ,
46+ "DD_INSTRUMENTATION_TELEMETRY_ENABLED" : "false" ,
4747 "DD_REMOTE_CONFIG_POLL_INTERVAL_SECONDS" : "0.2" ,
4848}
4949
5050AGENTLESS_ENVVARS = {
51- "DD_FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_POLL_INTERVAL_SECONDS" : "0.2 " ,
51+ "DD_FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_POLL_INTERVAL_SECONDS" : "1 " ,
5252 "DD_FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_REQUEST_TIMEOUT_SECONDS" : "1" ,
5353}
5454
@@ -87,6 +87,11 @@ def library_env(
8787 responses = params .get ("responses" )
8888 api_key = params .get ("api_key" , TEST_API_KEY )
8989
90+ if "provider_enabled" in params :
91+ env ["DD_FEATURE_FLAGS_ENABLED" ] = str (params ["provider_enabled" ]).lower ()
92+ if "legacy_provider_enabled" in params :
93+ env ["DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED" ] = str (params ["legacy_provider_enabled" ]).lower ()
94+
9095 if responses is not None :
9196 mock_ffe_agentless_backend .set_responses (responses )
9297 elif response is not None :
@@ -141,6 +146,27 @@ def _assert_no_mock_requests(mock_ffe_agentless_backend: MockFFEAgentlessBackend
141146 time .sleep (MOCK_STATUS_INTERVAL_SECONDS )
142147
143148
149+ def _remote_config_products (test_agent : TestAgentAPI ) -> set [str ]:
150+ products : set [str ] = set ()
151+ for request in test_agent .rc_requests (post_only = True ):
152+ client = request ["body" ].get ("client" , {})
153+ products .update (client .get ("products" , []))
154+ return products
155+
156+
157+ def _assert_ffe_remote_config_activation (test_agent : TestAgentAPI ) -> None :
158+ test_agent .assert_rc_capabilities ({Capabilities .FFE_FLAG_CONFIGURATION_RULES })
159+ assert RC_PRODUCT in _remote_config_products (test_agent )
160+
161+
162+ def _assert_no_ffe_remote_config_activation (test_agent : TestAgentAPI ) -> None :
163+ for _ in range (NO_MOCK_REQUEST_ATTEMPTS ):
164+ capabilities = test_agent .wait_for_rc_capabilities ()
165+ assert Capabilities .FFE_FLAG_CONFIGURATION_RULES not in capabilities
166+ assert RC_PRODUCT not in _remote_config_products (test_agent )
167+ time .sleep (MOCK_STATUS_INTERVAL_SECONDS )
168+
169+
144170def _evaluate (test_library : APMLibrary ) -> dict [str , Any ]:
145171 return _ffe_evaluate_with_rc_retry (
146172 test_library ,
@@ -199,25 +225,51 @@ def test_remote_config_positive_ignores_agentless_env(
199225 apply_state = _set_and_wait_ffe_rc (test_agent , UFC_VALID_DATA )
200226 assert apply_state ["apply_state" ] == RemoteConfigApplyState .ACKNOWLEDGED .value
201227 assert apply_state ["product" ] == RC_PRODUCT
228+ _assert_ffe_remote_config_activation (test_agent )
202229
203230 assert test_library .ffe_start (), "failed to start FFE provider in remote_config mode"
204231 _assert_expected_value (_evaluate (test_library ))
205232
206233 _assert_no_mock_requests (mock_ffe_agentless_backend )
207234
235+ @parametrize (
236+ "library_env" ,
237+ [{"configuration_source" : "remote_config" , "provider_enabled" : False , "response" : "valid" }],
238+ indirect = True ,
239+ )
240+ def test_provider_kill_switch_stops_remote_config_subscription (
241+ self ,
242+ test_agent : TestAgentAPI ,
243+ test_library : APMLibrary ,
244+ mock_ffe_agentless_backend : MockFFEAgentlessBackendServer ,
245+ ) -> None :
246+ test_library .ffe_start ()
247+ _assert_no_ffe_remote_config_activation (test_agent )
248+ _assert_no_mock_requests (mock_ffe_agentless_backend )
249+
208250 @parametrize ("library_env" , [{"configuration_source" : "remote_config" , "response" : "valid" }], indirect = True )
209251 def test_remote_config_without_rc_does_not_fallback_to_agentless (
210- self , test_library : APMLibrary , mock_ffe_agentless_backend : MockFFEAgentlessBackendServer
252+ self ,
253+ test_agent : TestAgentAPI ,
254+ test_library : APMLibrary ,
255+ mock_ffe_agentless_backend : MockFFEAgentlessBackendServer ,
211256 ) -> None :
212- _assert_cold_not_ready ( test_library , started = test_library . ffe_start ())
257+ del test_library # fixture starts the tracer; no RC payload is delivered
213258
259+ _assert_ffe_remote_config_activation (test_agent )
214260 _assert_no_mock_requests (mock_ffe_agentless_backend )
215261
216- @parametrize ("library_env" , [{"configuration_source" : "agentless" , "response" : "valid" }], indirect = True )
217- def test_explicit_agentless_positive (
218- self , test_library : APMLibrary , mock_ffe_agentless_backend : MockFFEAgentlessBackendServer
262+ @parametrize ("library_env" , [{"configuration_source" : None , "response" : "valid" }], indirect = True )
263+ def test_default_agentless_positive (
264+ self ,
265+ test_agent : TestAgentAPI ,
266+ test_library : APMLibrary ,
267+ mock_ffe_agentless_backend : MockFFEAgentlessBackendServer ,
219268 ) -> None :
220- assert test_library .ffe_start (), "failed to start FFE provider in explicit agentless mode"
269+ _assert_no_mock_requests (mock_ffe_agentless_backend )
270+ _assert_no_ffe_remote_config_activation (test_agent )
271+
272+ assert test_library .ffe_start (), "failed to start FFE provider in default agentless mode"
221273 _assert_expected_value (_evaluate (test_library ))
222274
223275 status = _wait_for_status (
@@ -227,29 +279,111 @@ def test_explicit_agentless_positive(
227279 )
228280 assert status ["last_auth_present" ] is True
229281 assert status ["last_path" ] == CONFIG_PATH
282+ _assert_no_ffe_remote_config_activation (test_agent )
230283
231- @parametrize ("library_env" , [{"configuration_source" : None , "response" : "valid" }], indirect = True )
232- def test_default_agentless_positive (
233- self , test_library : APMLibrary , mock_ffe_agentless_backend : MockFFEAgentlessBackendServer
284+ @parametrize (
285+ "library_env" ,
286+ [{"configuration_source" : None , "legacy_provider_enabled" : True , "response" : "valid" }],
287+ indirect = True ,
288+ )
289+ def test_legacy_true_preserves_remote_config (
290+ self ,
291+ test_agent : TestAgentAPI ,
292+ test_library : APMLibrary ,
293+ mock_ffe_agentless_backend : MockFFEAgentlessBackendServer ,
234294 ) -> None :
235- assert test_library .ffe_start (), "failed to start FFE provider in default agentless mode"
295+ apply_state = _set_and_wait_ffe_rc (test_agent , UFC_VALID_DATA )
296+ assert apply_state ["apply_state" ] == RemoteConfigApplyState .ACKNOWLEDGED .value
297+ assert apply_state ["product" ] == RC_PRODUCT
298+ _assert_ffe_remote_config_activation (test_agent )
299+
300+ assert test_library .ffe_start (), "failed to start grandfathered Remote Config provider"
236301 _assert_expected_value (_evaluate (test_library ))
302+ _assert_no_mock_requests (mock_ffe_agentless_backend )
237303
238- status = _wait_for_status (
304+ @parametrize (
305+ "library_env" ,
306+ [{"configuration_source" : None , "legacy_provider_enabled" : False , "response" : "valid" }],
307+ indirect = True ,
308+ )
309+ def test_legacy_false_keeps_provider_disabled (
310+ self ,
311+ test_agent : TestAgentAPI ,
312+ test_library : APMLibrary ,
313+ mock_ffe_agentless_backend : MockFFEAgentlessBackendServer ,
314+ ) -> None :
315+ test_library .ffe_start ()
316+ _assert_no_mock_requests (mock_ffe_agentless_backend )
317+ _assert_no_ffe_remote_config_activation (test_agent )
318+
319+ @parametrize (
320+ "library_env" ,
321+ [{"configuration_source" : "agentless" , "legacy_provider_enabled" : True , "response" : "valid" }],
322+ indirect = True ,
323+ )
324+ def test_explicit_agentless_wins_over_legacy_true (
325+ self ,
326+ test_agent : TestAgentAPI ,
327+ test_library : APMLibrary ,
328+ mock_ffe_agentless_backend : MockFFEAgentlessBackendServer ,
329+ ) -> None :
330+ _assert_no_mock_requests (mock_ffe_agentless_backend )
331+ _assert_no_ffe_remote_config_activation (test_agent )
332+
333+ assert test_library .ffe_start (), "failed to start explicit agentless provider"
334+ _assert_expected_value (_evaluate (test_library ))
335+ _wait_for_status (
239336 mock_ffe_agentless_backend ,
240337 lambda current : current ["requests_total" ] > 0 and current ["last_status_code" ] == 200 ,
241- "default agentless request" ,
338+ "explicit agentless response request" ,
242339 )
243- assert status ["last_path" ] == CONFIG_PATH
340+ _assert_no_ffe_remote_config_activation (test_agent )
341+
342+ @parametrize (
343+ "library_env" ,
344+ [{"configuration_source" : "remote_config" , "legacy_provider_enabled" : False , "response" : "valid" }],
345+ indirect = True ,
346+ )
347+ def test_explicit_remote_config_wins_over_legacy_false (
348+ self ,
349+ test_agent : TestAgentAPI ,
350+ test_library : APMLibrary ,
351+ mock_ffe_agentless_backend : MockFFEAgentlessBackendServer ,
352+ ) -> None :
353+ apply_state = _set_and_wait_ffe_rc (test_agent , UFC_VALID_DATA )
354+ assert apply_state ["apply_state" ] == RemoteConfigApplyState .ACKNOWLEDGED .value
355+ assert apply_state ["product" ] == RC_PRODUCT
356+ _assert_ffe_remote_config_activation (test_agent )
357+
358+ assert test_library .ffe_start (), "failed to start explicit Remote Config provider"
359+ _assert_expected_value (_evaluate (test_library ))
360+ _assert_no_mock_requests (mock_ffe_agentless_backend )
361+
362+ @parametrize (
363+ "library_env" ,
364+ [{"configuration_source" : None , "provider_enabled" : False , "response" : "valid" }],
365+ indirect = True ,
366+ )
367+ def test_provider_kill_switch_stops_agentless_polling (
368+ self ,
369+ test_agent : TestAgentAPI ,
370+ test_library : APMLibrary ,
371+ mock_ffe_agentless_backend : MockFFEAgentlessBackendServer ,
372+ ) -> None :
373+ test_library .ffe_start ()
374+ _assert_no_mock_requests (mock_ffe_agentless_backend )
375+ _assert_no_ffe_remote_config_activation (test_agent )
244376
245377 @parametrize ("library_env" , [{"configuration_source" : "invalid" , "response" : "valid" }], indirect = True )
246378 def test_invalid_configuration_source_fails_closed (
247- self , test_library : APMLibrary , mock_ffe_agentless_backend : MockFFEAgentlessBackendServer
379+ self ,
380+ test_agent : TestAgentAPI ,
381+ test_library : APMLibrary ,
382+ mock_ffe_agentless_backend : MockFFEAgentlessBackendServer ,
248383 ) -> None :
249- started = test_library .ffe_start ()
250- if started :
251- _assert_default_or_not_ready (_evaluate (test_library ))
384+ test_library .ffe_start ()
252385 _assert_no_mock_requests (mock_ffe_agentless_backend )
386+ _assert_no_ffe_remote_config_activation (test_agent )
253387
254388
255389@scenarios .parametric
0 commit comments