diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/assets.json b/sdk/appconfiguration/azure-appconfiguration-provider/assets.json index ab147da31709..9b6a07887233 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/assets.json +++ b/sdk/appconfiguration/azure-appconfiguration-provider/assets.json @@ -2,5 +2,5 @@ "AssetsRepo": "Azure/azure-sdk-assets", "AssetsRepoPrefixPath": "python", "TagPrefix": "python/appconfiguration/azure-appconfiguration-provider", - "Tag": "python/appconfiguration/azure-appconfiguration-provider_ec20db0e42" + "Tag": "python/appconfiguration/azure-appconfiguration-provider_d81e6a8e3c" } diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/tests/aio/key_vault/test_async_secret_refresh.py b/sdk/appconfiguration/azure-appconfiguration-provider/tests/aio/key_vault/test_async_secret_refresh.py index dc7a1e8a26be..a8397565d8a1 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/tests/aio/key_vault/test_async_secret_refresh.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/tests/aio/key_vault/test_async_secret_refresh.py @@ -5,7 +5,6 @@ # -------------------------------------------------------------------------- import functools import time -import asyncio import unittest from unittest.mock import Mock, patch from devtools_testutils import EnvironmentVariableLoader @@ -63,16 +62,11 @@ async def async_callback(): # Mock the refresh method to track calls with patch.object(client, "refresh") as mock_refresh: - # Wait for the secret refresh interval to pass - await asyncio.sleep(2) - await client.refresh() # Verify refresh was called assert mock_refresh.call_count >= 1 - # Wait again to ensure multiple refreshes - await asyncio.sleep(2) await client.refresh() # Should have been called at least twice now @@ -116,15 +110,20 @@ async def test_secret_refresh_with_updated_values( kv_setting.secret_id = appconfiguration_keyvault_secret_url2 await appconfig_client.set_configuration_setting(kv_setting) - # Wait for the secret refresh interval to pass - await asyncio.sleep(2) + try: + # Expire the refresh timers to simulate time passing + client._refresh_timer._next_refresh_time = 0 + client._secret_provider.secret_refresh_timer._next_refresh_time = 0 - # Access the value again to trigger refresh - await client.refresh() + # Access the value again to trigger refresh + await client.refresh() - # Verify the value was updated - assert client["secret"] == "Very secret value 2" - assert mock_callback.call_count >= 1 + # Verify the value was updated + assert client["secret"] == "Very secret value 2" + assert mock_callback.call_count >= 1 + finally: + kv_setting.secret_id = appconfiguration_keyvault_secret_url + await appconfig_client.set_configuration_setting(kv_setting) @AppConfigProviderPreparer() @recorded_by_proxy_async diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/tests/aio/test_async_provider_feature_management.py b/sdk/appconfiguration/azure-appconfiguration-provider/tests/aio/test_async_provider_feature_management.py index a0aeb9d6ec16..1126b3de83c8 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/tests/aio/test_async_provider_feature_management.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/tests/aio/test_async_provider_feature_management.py @@ -7,7 +7,7 @@ from devtools_testutils import EnvironmentVariableLoader from devtools_testutils.aio import recorded_by_proxy_async from testcase import has_feature_flag -from asynctestcase import AppConfigTestCase, setup_configs +from asynctestcase import AppConfigTestCase from test_constants import APPCONFIGURATION_ENDPOINT_STRING, FEATURE_MANAGEMENT_KEY from azure.appconfiguration.provider import SettingSelector from azure.appconfiguration.provider.aio import load @@ -38,9 +38,6 @@ async def test_load_only_feature_flags(self, appconfiguration_endpoint_string): @AppConfigProviderPreparer() @recorded_by_proxy_async async def test_select_feature_flags(self, appconfiguration_endpoint_string): - client = self.create_appconfig_client(appconfiguration_endpoint_string) - await setup_configs(client, None, None) - credential = self.get_credential(AzureAppConfigurationClient, is_async=True) async with await load( endpoint=appconfiguration_endpoint_string, diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/tests/aio/test_async_provider_refresh.py b/sdk/appconfiguration/azure-appconfiguration-provider/tests/aio/test_async_provider_refresh.py index f08e17a92601..8901b5f2ca4e 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/tests/aio/test_async_provider_refresh.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/tests/aio/test_async_provider_refresh.py @@ -4,7 +4,6 @@ # license information. # -------------------------------------------------------------------------- import functools -import time import unittest import sys from unittest.mock import Mock @@ -63,8 +62,9 @@ async def test_refresh(self, appconfiguration_endpoint_string, appconfiguration_ await appconfig_client.set_configuration_setting(setting) await appconfig_client.set_configuration_setting(feature_flag) - # Waiting for the refresh interval to pass - time.sleep(2) + # Expire the refresh timers to simulate time passing + client._refresh_timer._next_refresh_time = 0 + client._feature_flag_refresh_timer._next_refresh_time = 0 await client.refresh() assert client["refresh_message"] == "updated value" @@ -76,8 +76,9 @@ async def test_refresh(self, appconfiguration_endpoint_string, appconfiguration_ await appconfig_client.set_configuration_setting(setting) await appconfig_client.set_configuration_setting(feature_flag) - # Waiting for the refresh interval to pass - time.sleep(2) + # Expire the refresh timers to simulate time passing + client._refresh_timer._next_refresh_time = 0 + client._feature_flag_refresh_timer._next_refresh_time = 0 await client.refresh() assert client["refresh_message"] == "original value" @@ -96,7 +97,9 @@ async def test_refresh(self, appconfiguration_endpoint_string, appconfiguration_ assert mock_callback.call_count == 2 setting.value = "original value" + feature_flag.enabled = False await appconfig_client.set_configuration_setting(setting) + await appconfig_client.set_configuration_setting(feature_flag) await client.refresh() assert client["refresh_message"] == "original value" @@ -133,8 +136,9 @@ async def test_no_refresh(self, appconfiguration_endpoint_string, appconfigurati setting.value = "updated value" await appconfig_client.set_configuration_setting(setting) - # Waiting for the refresh interval to pass - time.sleep(2) + # Expire the refresh timers to simulate time passing + client._refresh_timer._next_refresh_time = 0 + client._feature_flag_refresh_timer._next_refresh_time = 0 await client.refresh() # No Change the Watch Key wasn't updated @@ -145,15 +149,20 @@ async def test_no_refresh(self, appconfiguration_endpoint_string, appconfigurati watch_key.value = "1" await appconfig_client.set_configuration_setting(watch_key) - # Waiting for the refresh interval to pass - time.sleep(2) + # Expire the refresh timers to simulate time passing + client._refresh_timer._next_refresh_time = 0 + client._feature_flag_refresh_timer._next_refresh_time = 0 await client.refresh() assert client["refresh_message"] == "updated value" assert has_feature_flag(client, "Alpha", False) assert mock_callback.call_count == 1 - # method: refresh + # Reset modified settings + setting.value = "original value" + await appconfig_client.set_configuration_setting(setting) + await appconfig_client.delete_configuration_setting(key="watch key") + @AppConfigProviderPreparer() @recorded_by_proxy_async @pytest.mark.skipif(sys.version_info < (3, 8), reason="Python 3.7 does not support AsyncMock") @@ -181,8 +190,8 @@ async def test_empty_refresh(self, appconfiguration_endpoint_string, appconfigur static_setting.value = "updated static" await appconfig_client.set_configuration_setting(static_setting) - # Waiting for the refresh interval to pass - time.sleep(2) + # Expire the refresh timers to simulate time passing + client._refresh_timer._next_refresh_time = 0 await client.refresh() assert client["refresh_message"] == "original value" diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/tests/aio/test_async_snapshots.py b/sdk/appconfiguration/azure-appconfiguration-provider/tests/aio/test_async_snapshots.py index b4238cf1396f..1adbfcee2882 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/tests/aio/test_async_snapshots.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/tests/aio/test_async_snapshots.py @@ -4,14 +4,12 @@ # license information. # -------------------------------------------------------------------------- import functools -import time import pytest from asynctestcase import ( AppConfigTestCase, - cleanup_test_resources_async, set_test_settings_async, - create_snapshot_async, ) +import conftest from devtools_testutils import EnvironmentVariableLoader from devtools_testutils.aio import recorded_by_proxy_async from test_constants import APPCONFIGURATION_ENDPOINT_STRING @@ -152,81 +150,39 @@ async def test_load_provider_with_regular_selectors(self, appconfiguration_endpo @pytest.mark.live_test_only # Needed to fix an azure core dependency compatibility issue @AppConfigProviderPreparer() @recorded_by_proxy_async - async def test_create_snapshot_and_load_provider(self, appconfiguration_endpoint_string, **kwargs): - """Test creating a snapshot and loading provider from it.""" - # Create SDK client for setup - sdk_client = self.create_appconfig_client(appconfiguration_endpoint_string) - - # Create unique test configuration settings for the snapshot - test_settings = [ - ConfigurationSetting(key="snapshot_test_key1", value="snapshot_test_value1", label=NULL_CHAR), - ConfigurationSetting(key="snapshot_test_key2", value="snapshot_test_value2", label=NULL_CHAR), - ConfigurationSetting( - key="snapshot_test_json", - value='{"nested": "snapshot_value"}', - label=NULL_CHAR, - content_type="application/json", - ), - ConfigurationSetting(key="refresh_test_key", value="original_refresh_value", label=NULL_CHAR), - ] + async def test_create_snapshot_and_load_provider(self, appconfiguration_endpoint_string): + """Test loading provider from a pre-created snapshot.""" + snapshot_name = conftest.snapshot_names["snapshot"] - # Create feature flag settings for the snapshot - # Note: Feature flags in snapshots are NOT loaded as feature flags by the provider - test_feature_flags = [ - FeatureFlagConfigurationSetting( - feature_id="SnapshotFeature", - enabled=True, - label=NULL_CHAR, - ), - FeatureFlagConfigurationSetting( - feature_id="SnapshotFeatureDisabled", - enabled=False, - label=NULL_CHAR, - ), - ] + # Load provider using the snapshot with refresh enabled and feature flags enabled + async with await self.create_client( + endpoint=appconfiguration_endpoint_string, + selects=[ + SettingSelector(snapshot_name=snapshot_name), # Snapshot data (includes feature flags) + SettingSelector(key_filter="refresh_test_key"), # Non-snapshot key for refresh testing + ], + refresh_on=[WatchKey("refresh_test_key")], # Watch non-snapshot key for refresh + refresh_interval=1, # Short refresh interval for testing + ) as provider: - # Set the configuration settings and feature flags - await set_test_settings_async(sdk_client, test_settings + test_feature_flags) - - variables = kwargs.pop("variables", {}) - dynamic_snapshot_name_postfix = variables.setdefault("dynamic_snapshot_name_postfix", str(int(time.time()))) - - # Create a unique snapshot name with timestamp to avoid conflicts - snapshot_name = f"test-snapshot-{dynamic_snapshot_name_postfix}" - - try: - # Create the snapshot including both config settings and feature flags - await create_snapshot_async( - sdk_client, - snapshot_name, - key_filters=["snapshot_test_*", ".appconfig.featureflag/SnapshotFeature*"], - ) - - # Load provider using the snapshot with refresh enabled and feature flags enabled - async with await self.create_client( - endpoint=appconfiguration_endpoint_string, - selects=[ - SettingSelector(snapshot_name=snapshot_name), # Snapshot data (includes feature flags) - SettingSelector(key_filter="refresh_test_key"), # Non-snapshot key for refresh testing - ], - refresh_on=[WatchKey("refresh_test_key")], # Watch non-snapshot key for refresh - refresh_interval=1, # Short refresh interval for testing - ) as provider: - - # Verify all snapshot settings are loaded - assert provider["snapshot_test_key1"] == "snapshot_test_value1" - assert provider["snapshot_test_key2"] == "snapshot_test_value2" - assert provider["snapshot_test_json"]["nested"] == "snapshot_value" - assert provider["refresh_test_key"] == "original_refresh_value" - - # Verify that snapshot settings and refresh key are loaded - snapshot_keys = [key for key in provider.keys() if key.startswith("snapshot_test_")] - assert len(snapshot_keys) == 3 - - # Verify feature flags from snapshots are NOT loaded as feature flags - # (snapshots don't support feature flag loading, only regular selects do) - assert FEATURE_MANAGEMENT_KEY not in provider, "Feature flags should not be loaded from snapshots" + # Get the underlying SDK client from the provider + sdk_client = provider._replica_client_manager._original_client._client + # Verify all snapshot settings are loaded + assert provider["snapshot_test_key1"] == "snapshot_test_value1" + assert provider["snapshot_test_key2"] == "snapshot_test_value2" + assert provider["snapshot_test_json"]["nested"] == "snapshot_value" + assert provider["refresh_test_key"] == "original_refresh_value" + + # Verify that snapshot settings and refresh key are loaded + snapshot_keys = [key for key in provider.keys() if key.startswith("snapshot_test_")] + assert len(snapshot_keys) == 3 + + # Verify feature flags from snapshots are NOT loaded as feature flags + # (snapshots don't support feature flag loading, only regular selects do) + assert FEATURE_MANAGEMENT_KEY not in provider, "Feature flags should not be loaded from snapshots" + + try: # Test snapshot immutability: modify the original settings modified_settings = [ ConfigurationSetting( @@ -252,8 +208,8 @@ async def test_create_snapshot_and_load_provider(self, appconfiguration_endpoint new_key = ConfigurationSetting(key="new_key_added_after_load", value="new_value", label=NULL_CHAR) await set_test_settings_async(sdk_client, modified_settings + [new_key]) - # Wait for refresh interval to pass - time.sleep(1) + # Expire the refresh timer so refresh will check for changes + provider._refresh_timer._next_refresh_time = 0 # Refresh the existing provider (snapshots should remain immutable, but non-snapshot keys should update) await provider.refresh() @@ -269,150 +225,102 @@ async def test_create_snapshot_and_load_provider(self, appconfiguration_endpoint # Verify new keys are NOT added during refresh (only watched keys trigger full reload) assert "new_key_added_after_load" not in provider # New key should not be loaded - # Verify that loading without snapshot gets the modified values - async with await self.create_client( - endpoint=appconfiguration_endpoint_string, - selects=[SettingSelector(key_filter="snapshot_test_*")], - ) as provider_current: - - # Current values should be the modified ones - assert provider_current["snapshot_test_key1"] == "MODIFIED_VALUE1" # Modified value - assert provider_current["snapshot_test_key2"] == "MODIFIED_VALUE2" # Modified value - assert provider_current["snapshot_test_json"]["nested"] == "MODIFIED_VALUE" # Modified value - - finally: - # Clean up test resources - cleanup_settings = ( - test_settings - + test_feature_flags - + [ConfigurationSetting(key="new_key_added_after_load", value="", label=NULL_CHAR)] - ) - await cleanup_test_resources_async( - sdk_client, - settings=cleanup_settings, - snapshot_names=[snapshot_name], - ) - return variables + # Verify that loading without snapshot gets the modified values + async with await self.create_client( + endpoint=appconfiguration_endpoint_string, + selects=[SettingSelector(key_filter="snapshot_test_*")], + ) as provider_current: + + # Current values should be the modified ones + assert provider_current["snapshot_test_key1"] == "MODIFIED_VALUE1" # Modified value + assert provider_current["snapshot_test_key2"] == "MODIFIED_VALUE2" # Modified value + assert provider_current["snapshot_test_json"]["nested"] == "MODIFIED_VALUE" # Modified value + + finally: + # Restore modified settings to original values for other tests + # Must happen inside async with so sdk_client is still open + restored_settings = [ + ConfigurationSetting(key="snapshot_test_key1", value="snapshot_test_value1", label=NULL_CHAR), + ConfigurationSetting(key="snapshot_test_key2", value="snapshot_test_value2", label=NULL_CHAR), + ConfigurationSetting( + key="snapshot_test_json", + value='{"nested": "snapshot_value"}', + label=NULL_CHAR, + content_type="application/json", + ), + ConfigurationSetting(key="refresh_test_key", value="original_refresh_value", label=NULL_CHAR), + ] + await set_test_settings_async(sdk_client, restored_settings) @pytest.mark.live_test_only # Needed to fix an azure core dependency compatibility issue @AppConfigProviderPreparer() @recorded_by_proxy_async async def test_create_snapshot_and_load_provider_with_feature_flags( - self, appconfiguration_endpoint_string, **kwargs + self, appconfiguration_endpoint_string ): - """Test creating a snapshot and loading provider with feature flags from non-snapshot selectors.""" - # Create SDK client for setup - sdk_client = self.create_appconfig_client(appconfiguration_endpoint_string) - - # Create unique test configuration settings for the snapshot - test_settings = [ - ConfigurationSetting(key="ff_snapshot_test_key1", value="ff_snapshot_test_value1", label=NULL_CHAR), - ConfigurationSetting(key="ff_snapshot_test_key2", value="ff_snapshot_test_value2", label=NULL_CHAR), - ] + """Test loading provider with feature flags from a pre-created snapshot.""" + snapshot_name = conftest.snapshot_names["ff_snapshot"] - # Create feature flag settings - some for snapshot, some for regular loading - # Note: Feature flags in snapshots are NOT loaded as feature flags by the provider - snapshot_feature_flags = [ - FeatureFlagConfigurationSetting( - feature_id="SnapshotOnlyFeature", - enabled=True, - label=NULL_CHAR, - ), - ] + # Load provider using snapshot for config settings and regular selectors for feature flags + async with await self.create_client( + endpoint=appconfiguration_endpoint_string, + feature_flag_enabled=True, # Enable feature flags + feature_flag_selectors=[ + SettingSelector(snapshot_name=snapshot_name), # Load feature flags from snapshot + ], + ) as provider: - # Feature flags loaded via regular selectors (not from snapshot) - regular_feature_flags = [ - FeatureFlagConfigurationSetting( - feature_id="RegularFeature", - enabled=True, - label=NULL_CHAR, - ), - FeatureFlagConfigurationSetting( - feature_id="RegularFeatureDisabled", - enabled=False, - label=NULL_CHAR, - ), - ] + # Get the underlying SDK client from the provider + sdk_client = provider._replica_client_manager._original_client._client + + # Verify snapshot configuration settings are loaded + assert provider["ff_snapshot_test_key1"] == "ff_snapshot_test_value1" + assert provider["ff_snapshot_test_key2"] == "ff_snapshot_test_value2" + + # Verify feature flags loaded via regular selectors ARE loaded + feature_flags = provider.get(FEATURE_MANAGEMENT_KEY, {}).get(FEATURE_FLAG_KEY, []) + feature_flag_ids = {ff["id"]: ff["enabled"] for ff in feature_flags} + + # Regular feature flags should be loaded + assert "RegularFeature" not in feature_flag_ids, "RegularFeature should not be loaded via regular selector" + assert "RegularFeatureDisabled" not in feature_flag_ids, "RegularFeatureDisabled should not be loaded" + + # Snapshot-only feature flag should be loaded as a feature flag + assert "SnapshotOnlyFeature" in feature_flag_ids, "SnapshotOnlyFeature should be loaded as FF from snapshot" + + # Verify exactly 1 feature flag is loaded (the snapshot-only one) + assert len(feature_flags) == 1, f"Expected 1 feature flag, got {len(feature_flags)}" - # Set the configuration settings and feature flags - await set_test_settings_async(sdk_client, test_settings + snapshot_feature_flags + regular_feature_flags) - - variables = kwargs.pop("variables", {}) - dynamic_snapshot_name_postfix = variables.setdefault("dynamic_ff_snapshot_name_postfix", str(int(time.time()))) - - # Create a unique snapshot name with timestamp to avoid conflicts - snapshot_name = f"test-ff-snapshot-{dynamic_snapshot_name_postfix}" - - try: - # Create the snapshot including config settings and snapshot-only feature flags - await create_snapshot_async( - sdk_client, - snapshot_name, - key_filters=["ff_snapshot_test_*", ".appconfig.featureflag/SnapshotOnlyFeature"], - ) - - # Load provider using snapshot for config settings and regular selectors for feature flags - async with await self.create_client( - endpoint=appconfiguration_endpoint_string, - feature_flag_enabled=True, # Enable feature flags - feature_flag_selectors=[ - SettingSelector(snapshot_name=snapshot_name), # Load feature flags from snapshot - ], - ) as provider: - - # Verify snapshot configuration settings are loaded - assert provider["ff_snapshot_test_key1"] == "ff_snapshot_test_value1" - assert provider["ff_snapshot_test_key2"] == "ff_snapshot_test_value2" - - # Verify feature flags loaded via regular selectors ARE loaded - feature_flags = provider.get(FEATURE_MANAGEMENT_KEY, {}).get(FEATURE_FLAG_KEY, []) - feature_flag_ids = {ff["id"]: ff["enabled"] for ff in feature_flags} - - # Regular feature flags should be loaded - assert ( - "RegularFeature" not in feature_flag_ids - ), "RegularFeature should not be loaded via regular selector" - assert "RegularFeatureDisabled" not in feature_flag_ids, "RegularFeatureDisabled should not be loaded" - - # Snapshot-only feature flag should be loaded as a feature flag - assert ( - "SnapshotOnlyFeature" in feature_flag_ids - ), "SnapshotOnlyFeature should be loaded as FF from snapshot" - - # Verify exactly 1 feature flag is loaded (the snapshot-only one) - assert len(feature_flags) == 1, f"Expected 1 feature flag, got {len(feature_flags)}" - - # Modify the feature flags in the snapshot - modified_feature_flags = [ - FeatureFlagConfigurationSetting( - feature_id="SnapshotOnlyFeature", - enabled=False, # Changed from True to False - label=NULL_CHAR, - ), - ] - - await set_test_settings_async(sdk_client, modified_feature_flags) - - # Load a fresh provider without snapshot to verify current feature flag values - async with await self.create_client( - endpoint=appconfiguration_endpoint_string, - feature_flag_enabled=True, - feature_flag_selectors=[ - SettingSelector(snapshot_name=snapshot_name), # Load feature flags from snapshot - ], - ) as provider_current: - - # Current feature flag values should be the original ones from snapshot (immutable) - current_feature_flags = provider_current.get(FEATURE_MANAGEMENT_KEY, {}).get(FEATURE_FLAG_KEY, []) - current_ff_ids = {ff["id"]: ff["enabled"] for ff in current_feature_flags} - assert current_ff_ids.get("SnapshotOnlyFeature") is True # Original value from snapshot (not modified) - - finally: - # Clean up test resources - cleanup_settings = test_settings + snapshot_feature_flags + regular_feature_flags - await cleanup_test_resources_async( - sdk_client, - settings=cleanup_settings, - snapshot_names=[snapshot_name], - ) - return variables + try: + # Modify the feature flags in the snapshot + modified_feature_flags = [ + FeatureFlagConfigurationSetting( + feature_id="SnapshotOnlyFeature", + enabled=False, # Changed from True to False + label=NULL_CHAR, + ), + ] + + await set_test_settings_async(sdk_client, modified_feature_flags) + + finally: + # Restore modified feature flag to original value for other tests + # Must happen inside async with so sdk_client is still open + await set_test_settings_async( + sdk_client, + [FeatureFlagConfigurationSetting(feature_id="SnapshotOnlyFeature", enabled=True, label=NULL_CHAR)], + ) + + # Load a fresh provider to verify snapshot immutability + async with await self.create_client( + endpoint=appconfiguration_endpoint_string, + feature_flag_enabled=True, + feature_flag_selectors=[ + SettingSelector(snapshot_name=snapshot_name), # Load feature flags from snapshot + ], + ) as provider_current: + + # Current feature flag values should be the original ones from snapshot (immutable) + current_feature_flags = provider_current.get(FEATURE_MANAGEMENT_KEY, {}).get(FEATURE_FLAG_KEY, []) + current_ff_ids = {ff["id"]: ff["enabled"] for ff in current_feature_flags} + assert current_ff_ids.get("SnapshotOnlyFeature") is True # Original value from snapshot (not modified) diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/tests/aio/test_configuration_async_client_manager.py b/sdk/appconfiguration/azure-appconfiguration-provider/tests/aio/test_configuration_async_client_manager.py index bba473b36cdd..fd823bd96733 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/tests/aio/test_configuration_async_client_manager.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/tests/aio/test_configuration_async_client_manager.py @@ -3,7 +3,6 @@ # Licensed under the MIT License. See License.txt in the project root for # license information. # -------------------------------------------------------------------------- -import time from unittest.mock import patch, call, MagicMock import pytest from azure.appconfiguration.provider.aio._async_client_manager import AsyncConfigurationClientManager @@ -73,12 +72,6 @@ async def test_failover_create_client_manager_connection_string(self, mock_clien mock_update_failover_endpoints.return_value = ["https://fake.endpoint2"] manager = AsyncConfigurationClientManager(connection_string, endpoint, None, "", 0, 0, True, 0, 0, False) await manager.refresh_clients() - int = 0 - while len(manager._replica_clients) < 2: - if int > 30: - break - time.sleep(1) - int += 1 assert len(manager._replica_clients) == 2 mock_update_failover_endpoints.assert_called_once_with(endpoint, True) connection_string2 = "Endpoint=https://fake.endpoint2/;Id=fake_id;Secret=fake_secret" @@ -127,12 +120,6 @@ async def test_create_client_manager_endpoint_failover(self, mock_client, mock_u mock_update_failover_endpoints.return_value = ["https://fake.endpoint2"] manager = AsyncConfigurationClientManager(None, endpoint, "fake-credential", "", 0, 0, True, 0, 0, False) await manager.refresh_clients() - int = 0 - while len(manager._replica_clients) < 2: - if int > 30: - break - time.sleep(1) - int += 1 assert len(manager._replica_clients) == 2 mock_update_failover_endpoints.assert_called_once_with(endpoint, True) mock_client.assert_has_calls( diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/tests/asynctestcase.py b/sdk/appconfiguration/azure-appconfiguration-provider/tests/asynctestcase.py index 0262f8ef2de6..a433b81ce018 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/tests/asynctestcase.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/tests/asynctestcase.py @@ -14,14 +14,7 @@ class AppConfigTestCase(AzureRecordedTestCase): async def create_client(self, **kwargs): credential = self.get_credential(AzureAppConfigurationClient, is_async=True) - client = None - if "connection_string" in kwargs: - client = AzureAppConfigurationClient.from_connection_string(kwargs["connection_string"]) - else: - client = AzureAppConfigurationClient(kwargs["endpoint"], credential) - - await setup_configs(client, kwargs.get("keyvault_secret_url"), kwargs.get("keyvault_secret_url2")) kwargs["user_agent"] = "SDK/Integration" if "endpoint" in kwargs: diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/tests/conftest.py b/sdk/appconfiguration/azure-appconfiguration-provider/tests/conftest.py index 32d1f66fb4e0..63293c08db3f 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/tests/conftest.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/tests/conftest.py @@ -7,11 +7,42 @@ remove_batch_sanitizers, add_remove_header_sanitizer, add_uri_string_sanitizer, + is_live, ) import pytest +from azure.appconfiguration import AzureAppConfigurationClient +from azure.identity import DefaultAzureCredential +from testcase import setup_configs # autouse=True will trigger this fixture on each pytest run, even if it's not explicitly used by a test method +# Module-level storage for snapshot names created during session setup +snapshot_names = {} + + +@pytest.fixture(scope="session", autouse=True) +def setup_app_config_keys(): + """Pre-populate App Configuration with test keys and snapshots once per session (live mode only).""" + if not is_live(): + yield + return + + endpoint = os.environ.get("APPCONFIGURATION_ENDPOINT_STRING") + if not endpoint: + yield + return + + credential = DefaultAzureCredential() + client = AzureAppConfigurationClient(endpoint, credential) + keyvault_secret_url = os.environ.get("APPCONFIGURATION_KEY_VAULT_REFERENCE") + keyvault_secret_url2 = os.environ.get("APPCONFIGURATION_KEY_VAULT_REFERENCE2") + snap_name, ff_snap_name = setup_configs(client, keyvault_secret_url, keyvault_secret_url2) + + snapshot_names["snapshot"] = snap_name + snapshot_names["ff_snapshot"] = ff_snap_name + + yield + @pytest.fixture(scope="session", autouse=True) def add_sanitizers(test_proxy): diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/tests/key_vault/test_secret_refresh.py b/sdk/appconfiguration/azure-appconfiguration-provider/tests/key_vault/test_secret_refresh.py index 70b3feb81248..4934334ce27d 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/tests/key_vault/test_secret_refresh.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/tests/key_vault/test_secret_refresh.py @@ -55,16 +55,11 @@ def test_secret_refresh_timer( # Mock the refresh method to track calls with patch.object(client, "refresh") as mock_refresh: - # Wait for the secret refresh interval to pass - time.sleep(2) - client.refresh() # Verify refresh was called assert mock_refresh.call_count >= 1 - # Wait again to ensure multiple refreshes - time.sleep(2) client.refresh() # Should have been called at least twice now @@ -108,15 +103,20 @@ def test_secret_refresh_with_updated_values( kv_setting.secret_id = appconfiguration_keyvault_secret_url2 appconfig_client.set_configuration_setting(kv_setting) - # Wait for the secret refresh interval to pass - time.sleep(2) + try: + # Expire the refresh timers to simulate time passing + client._refresh_timer._next_refresh_time = 0 + client._secret_provider.secret_refresh_timer._next_refresh_time = 0 - # Access the value again to trigger refresh - client.refresh() + # Access the value again to trigger refresh + client.refresh() - # Verify the value was updated - assert client["secret"] == "Very secret value 2" - assert mock_callback.call_count >= 1 + # Verify the value was updated + assert client["secret"] == "Very secret value 2" + assert mock_callback.call_count >= 1 + finally: + kv_setting.secret_id = appconfiguration_keyvault_secret_url + appconfig_client.set_configuration_setting(kv_setting) @AppConfigProviderPreparer() @recorded_by_proxy diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/tests/test_provider_feature_management.py b/sdk/appconfiguration/azure-appconfiguration-provider/tests/test_provider_feature_management.py index 49a4aa82d920..cbe35053464f 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/tests/test_provider_feature_management.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/tests/test_provider_feature_management.py @@ -5,7 +5,7 @@ # -------------------------------------------------------------------------- import functools from devtools_testutils import EnvironmentVariableLoader, recorded_by_proxy -from testcase import AppConfigTestCase, setup_configs, has_feature_flag, get_feature_flag +from testcase import AppConfigTestCase, has_feature_flag, get_feature_flag from test_constants import APPCONFIGURATION_ENDPOINT_STRING, FEATURE_MANAGEMENT_KEY from azure.appconfiguration import AzureAppConfigurationClient from azure.appconfiguration.provider import SettingSelector, load @@ -38,9 +38,6 @@ def test_load_only_feature_flags(self, appconfiguration_endpoint_string): @AppConfigProviderPreparer() @recorded_by_proxy def test_select_feature_flags(self, appconfiguration_endpoint_string): - client = self.create_appconfig_client(appconfiguration_endpoint_string) - setup_configs(client, None, None) - credential = self.get_credential(AzureAppConfigurationClient) client = load( endpoint=appconfiguration_endpoint_string, diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/tests/test_provider_refresh.py b/sdk/appconfiguration/azure-appconfiguration-provider/tests/test_provider_refresh.py index 7a31c258586d..5b0223eecc16 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/tests/test_provider_refresh.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/tests/test_provider_refresh.py @@ -4,7 +4,6 @@ # license information. # -------------------------------------------------------------------------- import functools -import time import unittest from unittest.mock import Mock from devtools_testutils import EnvironmentVariableLoader, recorded_by_proxy @@ -53,8 +52,9 @@ def test_refresh(self, appconfiguration_endpoint_string, appconfiguration_keyvau appconfig_client.set_configuration_setting(setting) appconfig_client.set_configuration_setting(feature_flag) - # Waiting for the refresh interval to pass - time.sleep(2) + # Expire the refresh timers to simulate time passing + client._refresh_timer._next_refresh_time = 0 + client._feature_flag_refresh_timer._next_refresh_time = 0 client.refresh() assert client["refresh_message"] == "updated value" @@ -66,8 +66,9 @@ def test_refresh(self, appconfiguration_endpoint_string, appconfiguration_keyvau appconfig_client.set_configuration_setting(setting) appconfig_client.set_configuration_setting(feature_flag) - # Waiting for the refresh interval to pass - time.sleep(2) + # Expire the refresh timers to simulate time passing + client._refresh_timer._next_refresh_time = 0 + client._feature_flag_refresh_timer._next_refresh_time = 0 client.refresh() assert client["refresh_message"] == "original value" @@ -86,7 +87,9 @@ def test_refresh(self, appconfiguration_endpoint_string, appconfiguration_keyvau assert mock_callback.call_count == 2 setting.value = "original value" + feature_flag.enabled = False appconfig_client.set_configuration_setting(setting) + appconfig_client.set_configuration_setting(feature_flag) client.refresh() assert client["refresh_message"] == "original value" @@ -120,8 +123,9 @@ def test_no_refresh(self, appconfiguration_endpoint_string, appconfiguration_key setting.value = "updated value" appconfig_client.set_configuration_setting(setting) - # Waiting for the refresh interval to pass - time.sleep(2) + # Expire the refresh timers to simulate time passing + client._refresh_timer._next_refresh_time = 0 + client._feature_flag_refresh_timer._next_refresh_time = 0 client.refresh() # No Change the Watch Key wasn't updated @@ -132,15 +136,20 @@ def test_no_refresh(self, appconfiguration_endpoint_string, appconfiguration_key watch_key.value = "1" appconfig_client.set_configuration_setting(watch_key) - # Waiting for the refresh interval to pass - time.sleep(2) + # Expire the refresh timers to simulate time passing + client._refresh_timer._next_refresh_time = 0 + client._feature_flag_refresh_timer._next_refresh_time = 0 client.refresh() assert client["refresh_message"] == "updated value" assert has_feature_flag(client, "Alpha", False) assert mock_callback.call_count == 1 - # method: refresh + # Reset modified settings + setting.value = "original value" + appconfig_client.set_configuration_setting(setting) + appconfig_client.delete_configuration_setting(key="watch key") + @AppConfigProviderPreparer() @recorded_by_proxy def test_empty_refresh(self, appconfiguration_endpoint_string, appconfiguration_keyvault_secret_url): @@ -166,8 +175,8 @@ def test_empty_refresh(self, appconfiguration_endpoint_string, appconfiguration_ static_setting.value = "updated static" appconfig_client.set_configuration_setting(static_setting) - # Waiting for the refresh interval to pass - time.sleep(2) + # Expire the refresh timers to simulate time passing + client._refresh_timer._next_refresh_time = 0 client.refresh() assert client["refresh_message"] == "original value" diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/tests/test_snapshots.py b/sdk/appconfiguration/azure-appconfiguration-provider/tests/test_snapshots.py index 5e53b9a0bb1f..9eb49706a780 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/tests/test_snapshots.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/tests/test_snapshots.py @@ -4,10 +4,10 @@ # license information. # -------------------------------------------------------------------------- import functools -import time import pytest +import conftest from devtools_testutils import EnvironmentVariableLoader, recorded_by_proxy -from testcase import AppConfigTestCase, cleanup_test_resources, set_test_settings, create_snapshot +from testcase import AppConfigTestCase, set_test_settings from test_constants import APPCONFIGURATION_ENDPOINT_STRING from azure.appconfiguration.provider._models import SettingSelector from azure.appconfiguration.provider._constants import NULL_CHAR, FEATURE_MANAGEMENT_KEY, FEATURE_FLAG_KEY @@ -147,81 +147,39 @@ def test_load_provider_with_regular_selectors(self, appconfiguration_endpoint_st @pytest.mark.live_test_only # Needed to fix an azure core dependency compatibility issue @AppConfigProviderPreparer() @recorded_by_proxy - def test_create_snapshot_and_load_provider(self, appconfiguration_endpoint_string, **kwargs): - """Test creating a snapshot and loading provider from it.""" - # Create SDK client for setup - sdk_client = self.create_appconfig_client(appconfiguration_endpoint_string) - - # Create unique test configuration settings for the snapshot - test_settings = [ - ConfigurationSetting(key="snapshot_test_key1", value="snapshot_test_value1", label=NULL_CHAR), - ConfigurationSetting(key="snapshot_test_key2", value="snapshot_test_value2", label=NULL_CHAR), - ConfigurationSetting( - key="snapshot_test_json", - value='{"nested": "snapshot_value"}', - label=NULL_CHAR, - content_type="application/json", - ), - ConfigurationSetting(key="refresh_test_key", value="original_refresh_value", label=NULL_CHAR), - ] - - # Create feature flag settings for the snapshot - # Note: Feature flags in snapshots are NOT loaded as feature flags by the provider - test_feature_flags = [ - FeatureFlagConfigurationSetting( - feature_id="SnapshotFeature", - enabled=True, - label=NULL_CHAR, - ), - FeatureFlagConfigurationSetting( - feature_id="SnapshotFeatureDisabled", - enabled=False, - label=NULL_CHAR, - ), - ] - - # Set the configuration settings and feature flags - set_test_settings(sdk_client, test_settings + test_feature_flags) - - variables = kwargs.pop("variables", {}) - dynamic_snapshot_name_postfix = variables.setdefault("dynamic_snapshot_name_postfix", str(int(time.time()))) - - # Create a unique snapshot name with timestamp to avoid conflicts - snapshot_name = f"test-snapshot-{dynamic_snapshot_name_postfix}" + def test_create_snapshot_and_load_provider(self, appconfiguration_endpoint_string): + """Test loading provider from a pre-created snapshot.""" + snapshot_name = conftest.snapshot_names["snapshot"] - try: - # Create the snapshot including both config settings and feature flags - create_snapshot( - sdk_client, - snapshot_name, - key_filters=["snapshot_test_*", ".appconfig.featureflag/SnapshotFeature*"], - ) + # Load provider using the snapshot with refresh enabled and feature flags enabled + provider = self.create_client( + endpoint=appconfiguration_endpoint_string, + selects=[ + SettingSelector(snapshot_name=snapshot_name), # Snapshot data (includes feature flags) + SettingSelector(key_filter="refresh_test_key"), # Non-snapshot key for refresh testing + ], + refresh_on=[WatchKey("refresh_test_key")], # Watch non-snapshot key for refresh + refresh_interval=1, # Short refresh interval for testing + ) - # Load provider using the snapshot with refresh enabled and feature flags enabled - provider = self.create_client( - endpoint=appconfiguration_endpoint_string, - selects=[ - SettingSelector(snapshot_name=snapshot_name), # Snapshot data (includes feature flags) - SettingSelector(key_filter="refresh_test_key"), # Non-snapshot key for refresh testing - ], - refresh_on=[WatchKey("refresh_test_key")], # Watch non-snapshot key for refresh - refresh_interval=1, # Short refresh interval for testing - ) + # Get the underlying SDK client from the provider + sdk_client = provider._replica_client_manager._original_client._client - # Verify all snapshot settings are loaded - assert provider["snapshot_test_key1"] == "snapshot_test_value1" - assert provider["snapshot_test_key2"] == "snapshot_test_value2" - assert provider["snapshot_test_json"]["nested"] == "snapshot_value" - assert provider["refresh_test_key"] == "original_refresh_value" + # Verify all snapshot settings are loaded + assert provider["snapshot_test_key1"] == "snapshot_test_value1" + assert provider["snapshot_test_key2"] == "snapshot_test_value2" + assert provider["snapshot_test_json"]["nested"] == "snapshot_value" + assert provider["refresh_test_key"] == "original_refresh_value" - # Verify that snapshot settings and refresh key are loaded - snapshot_keys = [key for key in provider.keys() if key.startswith("snapshot_test_")] - assert len(snapshot_keys) == 3 + # Verify that snapshot settings and refresh key are loaded + snapshot_keys = [key for key in provider.keys() if key.startswith("snapshot_test_")] + assert len(snapshot_keys) == 3 - # Verify feature flags from snapshots are NOT loaded as feature flags - # (snapshots don't support feature flag loading, only regular selects do) - assert FEATURE_MANAGEMENT_KEY not in provider, "Feature flags should not be loaded from snapshots" + # Verify feature flags from snapshots are NOT loaded as feature flags + # (snapshots don't support feature flag loading, only regular selects do) + assert FEATURE_MANAGEMENT_KEY not in provider, "Feature flags should not be loaded from snapshots" + try: # Test snapshot immutability: modify the original settings modified_settings = [ ConfigurationSetting( @@ -247,8 +205,8 @@ def test_create_snapshot_and_load_provider(self, appconfiguration_endpoint_strin new_key = ConfigurationSetting(key="new_key_added_after_load", value="new_value", label=NULL_CHAR) set_test_settings(sdk_client, modified_settings + [new_key]) - # Wait for refresh interval to pass - time.sleep(1) + # Expire the refresh timer so refresh will check for changes + provider._refresh_timer._next_refresh_time = 0 # Refresh the existing provider (snapshots should remain immutable, but non-snapshot keys should update) provider.refresh() @@ -274,103 +232,59 @@ def test_create_snapshot_and_load_provider(self, appconfiguration_endpoint_strin assert provider_current["snapshot_test_key1"] == "MODIFIED_VALUE1" # Modified value assert provider_current["snapshot_test_key2"] == "MODIFIED_VALUE2" # Modified value assert provider_current["snapshot_test_json"]["nested"] == "MODIFIED_VALUE" # Modified value - finally: - # Clean up test resources - cleanup_settings = ( - test_settings - + test_feature_flags - + [ConfigurationSetting(key="new_key_added_after_load", value="", label=NULL_CHAR)] - ) - cleanup_test_resources( - sdk_client, - settings=cleanup_settings, - snapshot_names=[snapshot_name], - ) - return variables + # Restore modified settings to original values for other tests + restored_settings = [ + ConfigurationSetting(key="snapshot_test_key1", value="snapshot_test_value1", label=NULL_CHAR), + ConfigurationSetting(key="snapshot_test_key2", value="snapshot_test_value2", label=NULL_CHAR), + ConfigurationSetting( + key="snapshot_test_json", + value='{"nested": "snapshot_value"}', + label=NULL_CHAR, + content_type="application/json", + ), + ConfigurationSetting(key="refresh_test_key", value="original_refresh_value", label=NULL_CHAR), + ] + set_test_settings(sdk_client, restored_settings) @pytest.mark.live_test_only # Needed to fix an azure core dependency compatibility issue @AppConfigProviderPreparer() @recorded_by_proxy - def test_create_snapshot_and_load_provider_with_feature_flags(self, appconfiguration_endpoint_string, **kwargs): - """Test creating a snapshot and loading provider with feature flags from non-snapshot selectors.""" - # Create SDK client for setup - sdk_client = self.create_appconfig_client(appconfiguration_endpoint_string) - - # Create unique test configuration settings for the snapshot - test_settings = [ - ConfigurationSetting(key="ff_snapshot_test_key1", value="ff_snapshot_test_value1", label=NULL_CHAR), - ConfigurationSetting(key="ff_snapshot_test_key2", value="ff_snapshot_test_value2", label=NULL_CHAR), - ] - - # Create feature flag settings - some for snapshot, some for regular loading - # Note: Feature flags in snapshots are NOT loaded as feature flags by the provider - snapshot_feature_flags = [ - FeatureFlagConfigurationSetting( - feature_id="SnapshotOnlyFeature", - enabled=True, - label=NULL_CHAR, - ), - ] - - # Feature flags loaded via regular selectors (not from snapshot) - regular_feature_flags = [ - FeatureFlagConfigurationSetting( - feature_id="RegularFeature", - enabled=True, - label=NULL_CHAR, - ), - FeatureFlagConfigurationSetting( - feature_id="RegularFeatureDisabled", - enabled=False, - label=NULL_CHAR, - ), - ] - - # Set the configuration settings and feature flags - set_test_settings(sdk_client, test_settings + snapshot_feature_flags + regular_feature_flags) - - variables = kwargs.pop("variables", {}) - dynamic_snapshot_name_postfix = variables.setdefault("dynamic_ff_snapshot_name_postfix", str(int(time.time()))) + def test_create_snapshot_and_load_provider_with_feature_flags(self, appconfiguration_endpoint_string): + """Test loading provider with feature flags from a pre-created snapshot.""" + snapshot_name = conftest.snapshot_names["ff_snapshot"] - # Create a unique snapshot name with timestamp to avoid conflicts - snapshot_name = f"test-ff-snapshot-{dynamic_snapshot_name_postfix}" - - try: - # Create the snapshot including config settings and snapshot-only feature flags - create_snapshot( - sdk_client, - snapshot_name, - key_filters=["ff_snapshot_test_*", ".appconfig.featureflag/SnapshotOnlyFeature"], - ) + # Load provider using snapshot for config settings and regular selectors for feature flags + provider = self.create_client( + endpoint=appconfiguration_endpoint_string, + feature_flag_enabled=True, # Enable feature flags + feature_flag_selectors=[ + SettingSelector(snapshot_name=snapshot_name), # Load feature flags from snapshot + ], + ) - # Load provider using snapshot for config settings and regular selectors for feature flags - provider = self.create_client( - endpoint=appconfiguration_endpoint_string, - feature_flag_enabled=True, # Enable feature flags - feature_flag_selectors=[ - SettingSelector(snapshot_name=snapshot_name), # Load feature flags from snapshot - ], - ) + # Get the underlying SDK client from the provider + sdk_client = provider._replica_client_manager._original_client._client - # Verify snapshot configuration settings are loaded - assert provider["ff_snapshot_test_key1"] == "ff_snapshot_test_value1" - assert provider["ff_snapshot_test_key2"] == "ff_snapshot_test_value2" + # Verify snapshot configuration settings are loaded + assert provider["ff_snapshot_test_key1"] == "ff_snapshot_test_value1" + assert provider["ff_snapshot_test_key2"] == "ff_snapshot_test_value2" - # Verify feature flags loaded via regular selectors ARE loaded - feature_flags = provider.get(FEATURE_MANAGEMENT_KEY, {}).get(FEATURE_FLAG_KEY, []) - feature_flag_ids = {ff["id"]: ff["enabled"] for ff in feature_flags} + # Verify feature flags loaded via regular selectors ARE loaded + feature_flags = provider.get(FEATURE_MANAGEMENT_KEY, {}).get(FEATURE_FLAG_KEY, []) + feature_flag_ids = {ff["id"]: ff["enabled"] for ff in feature_flags} - # Regular feature flags should be loaded - assert "RegularFeature" not in feature_flag_ids, "RegularFeature should not be loaded via regular selector" - assert "RegularFeatureDisabled" not in feature_flag_ids, "RegularFeatureDisabled should not be loaded" + # Regular feature flags should be loaded + assert "RegularFeature" not in feature_flag_ids, "RegularFeature should not be loaded via regular selector" + assert "RegularFeatureDisabled" not in feature_flag_ids, "RegularFeatureDisabled should not be loaded" - # Snapshot-only feature flag should be loaded as a feature flag - assert "SnapshotOnlyFeature" in feature_flag_ids, "SnapshotOnlyFeature should be loaded as FF from snapshot" + # Snapshot-only feature flag should be loaded as a feature flag + assert "SnapshotOnlyFeature" in feature_flag_ids, "SnapshotOnlyFeature should be loaded as FF from snapshot" - # Verify exactly 1 feature flag is loaded (the snapshot-only one) - assert len(feature_flags) == 1, f"Expected 1 feature flag, got {len(feature_flags)}" + # Verify exactly 1 feature flag is loaded (the snapshot-only one) + assert len(feature_flags) == 1, f"Expected 1 feature flag, got {len(feature_flags)}" + try: # Modify the feature flags in the snapshot modified_feature_flags = [ FeatureFlagConfigurationSetting( @@ -395,13 +309,9 @@ def test_create_snapshot_and_load_provider_with_feature_flags(self, appconfigura current_feature_flags = provider_current.get(FEATURE_MANAGEMENT_KEY, {}).get(FEATURE_FLAG_KEY, []) current_ff_ids = {ff["id"]: ff["enabled"] for ff in current_feature_flags} assert current_ff_ids.get("SnapshotOnlyFeature") is True # Original value from snapshot (not modified) - finally: - # Clean up test resources - cleanup_settings = test_settings + snapshot_feature_flags + regular_feature_flags - cleanup_test_resources( + # Restore modified feature flag to original value for other tests + set_test_settings( sdk_client, - settings=cleanup_settings, - snapshot_names=[snapshot_name], + [FeatureFlagConfigurationSetting(feature_id="SnapshotOnlyFeature", enabled=True, label=NULL_CHAR)], ) - return variables diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/tests/testcase.py b/sdk/appconfiguration/azure-appconfiguration-provider/tests/testcase.py index e9707186731d..99074621b628 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/tests/testcase.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/tests/testcase.py @@ -4,13 +4,17 @@ # Licensed under the MIT License. See License.txt in the project root for # license information. # -------------------------------------------------------------------------- -from devtools_testutils import AzureRecordedTestCase +import time +from devtools_testutils import AzureRecordedTestCase, is_live from test_constants import FEATURE_MANAGEMENT_KEY, FEATURE_FLAG_KEY from azure.appconfiguration import ( AzureAppConfigurationClient, ConfigurationSetting, + ConfigurationSettingsFilter, FeatureFlagConfigurationSetting, SecretReferenceConfigurationSetting, + SnapshotComposition, + SnapshotStatus, ) from azure.appconfiguration.provider import load, AzureAppConfigurationKeyVaultOptions from azure.appconfiguration.provider._constants import NULL_CHAR @@ -20,14 +24,6 @@ class AppConfigTestCase(AzureRecordedTestCase): def create_client(self, **kwargs): credential = self.get_credential(AzureAppConfigurationClient) - client = None - - if "connection_string" in kwargs: - client = AzureAppConfigurationClient.from_connection_string(kwargs["connection_string"]) - else: - client = AzureAppConfigurationClient(kwargs["endpoint"], credential) - - setup_configs(client, kwargs.get("keyvault_secret_url"), kwargs.get("keyvault_secret_url2")) kwargs["user_agent"] = "SDK/Integration" if "endpoint" in kwargs: @@ -50,9 +46,43 @@ def create_appconfig_client(self, appconfiguration_endpoint_string): def setup_configs(client, keyvault_secret_url, keyvault_secret_url2): + """Set up all test configs and create snapshots. Returns (snapshot_name, ff_snapshot_name).""" for config in get_configs(keyvault_secret_url, keyvault_secret_url2): client.set_configuration_setting(config) + # Snapshot test settings + snapshot_settings = [ + ConfigurationSetting(key="snapshot_test_key1", value="snapshot_test_value1", label=NULL_CHAR), + ConfigurationSetting(key="snapshot_test_key2", value="snapshot_test_value2", label=NULL_CHAR), + ConfigurationSetting( + key="snapshot_test_json", + value='{"nested": "snapshot_value"}', + label=NULL_CHAR, + content_type="application/json", + ), + ConfigurationSetting(key="refresh_test_key", value="original_refresh_value", label=NULL_CHAR), + FeatureFlagConfigurationSetting(feature_id="SnapshotFeature", enabled=True, label=NULL_CHAR), + FeatureFlagConfigurationSetting(feature_id="SnapshotFeatureDisabled", enabled=False, label=NULL_CHAR), + ConfigurationSetting(key="ff_snapshot_test_key1", value="ff_snapshot_test_value1", label=NULL_CHAR), + ConfigurationSetting(key="ff_snapshot_test_key2", value="ff_snapshot_test_value2", label=NULL_CHAR), + FeatureFlagConfigurationSetting(feature_id="SnapshotOnlyFeature", enabled=True, label=NULL_CHAR), + FeatureFlagConfigurationSetting(feature_id="RegularFeature", enabled=True, label=NULL_CHAR), + FeatureFlagConfigurationSetting(feature_id="RegularFeatureDisabled", enabled=False, label=NULL_CHAR), + ] + for setting in snapshot_settings: + client.set_configuration_setting(setting) + + # Create snapshots + snapshot_name = f"test-snapshot-{int(time.time())}" + ff_snapshot_name = f"test-ff-snapshot-{int(time.time())}" + + create_snapshot(client, snapshot_name, key_filters=["snapshot_test_*", ".appconfig.featureflag/SnapshotFeature*"]) + create_snapshot( + client, ff_snapshot_name, key_filters=["ff_snapshot_test_*", ".appconfig.featureflag/SnapshotOnlyFeature"] + ) + + return snapshot_name, ff_snapshot_name + def get_configs(keyvault_secret_url, keyvault_secret_url2): configs = [] @@ -185,9 +215,6 @@ def create_snapshot(client, snapshot_name, key_filters, composition_type=None, r :param retention_period: The retention period in seconds (default: 3600, minimum valid value). :return: The created snapshot. """ - from azure.appconfiguration import SnapshotComposition, ConfigurationSettingsFilter, SnapshotStatus - from devtools_testutils import is_live - if composition_type is None: composition_type = SnapshotComposition.KEY