From 4b0a56d09f91abab0c9837aad66c5db77aab1190 Mon Sep 17 00:00:00 2001 From: Yalin Li Date: Thu, 21 Mar 2024 17:55:34 -0700 Subject: [PATCH] Fix de/serialization bugs in FeatureFlagConfigurationSetting (#34852) --- .vscode/cspell.json | 6 ++ .../azure-appconfiguration/CHANGELOG.md | 10 +--- .../azure-appconfiguration/assets.json | 2 +- .../_azure_appconfiguration_client.py | 26 ++++----- .../azure/appconfiguration/_models.py | 58 +++++++++---------- .../_azure_appconfiguration_client_async.py | 26 ++++----- .../test_azure_appconfiguration_client.py | 42 ++++++++++++-- .../test_azure_appconfiguration_client_aad.py | 6 +- ...azure_appconfiguration_client_aad_async.py | 6 +- ...est_azure_appconfiguration_client_async.py | 42 ++++++++++++-- .../tests/test_consistency.py | 2 +- .../azure-appconfiguration/tests/testcase.py | 4 +- 12 files changed, 149 insertions(+), 81 deletions(-) diff --git a/.vscode/cspell.json b/.vscode/cspell.json index 71ec3978261d..c9c2a69146c3 100644 --- a/.vscode/cspell.json +++ b/.vscode/cspell.json @@ -1688,6 +1688,12 @@ "kvset" ] }, + { + "filename": "sdk/appconfiguration/azure-appconfiguration/tests/**", + "words": [ + "adfsasdfzsd" + ] + }, { "filename": "sdk/personalizer/test-resources.json", "words": [ diff --git a/sdk/appconfiguration/azure-appconfiguration/CHANGELOG.md b/sdk/appconfiguration/azure-appconfiguration/CHANGELOG.md index 65b98765ae3b..5d2525f05bc4 100644 --- a/sdk/appconfiguration/azure-appconfiguration/CHANGELOG.md +++ b/sdk/appconfiguration/azure-appconfiguration/CHANGELOG.md @@ -1,14 +1,10 @@ # Release History -## 1.6.0b2 (Unreleased) - -### Features Added - -### Breaking Changes +## 1.6.0b2 (2024-03-21) ### Bugs Fixed - -### Other Changes +- Changed invalid default value `None` to `False` for property `enabled` in `FeatureFlagConfigurationSetting`. +- Fixed the issue that `description`, `display_name` and other customer fields are missing when de/serializing `FeatureFlagConfigurationSetting` objects. ## 1.6.0b1 (2024-03-14) diff --git a/sdk/appconfiguration/azure-appconfiguration/assets.json b/sdk/appconfiguration/azure-appconfiguration/assets.json index 1493f31a8b20..64b5afd695f6 100644 --- a/sdk/appconfiguration/azure-appconfiguration/assets.json +++ b/sdk/appconfiguration/azure-appconfiguration/assets.json @@ -2,5 +2,5 @@ "AssetsRepo": "Azure/azure-sdk-assets", "AssetsRepoPrefixPath": "python", "TagPrefix": "python/appconfiguration/azure-appconfiguration", - "Tag": "python/appconfiguration/azure-appconfiguration_8137b21bd0" + "Tag": "python/appconfiguration/azure-appconfiguration_27c8f82a12" } diff --git a/sdk/appconfiguration/azure-appconfiguration/azure/appconfiguration/_azure_appconfiguration_client.py b/sdk/appconfiguration/azure-appconfiguration/azure/appconfiguration/_azure_appconfiguration_client.py index beb098e4b24c..fb68fb516357 100644 --- a/sdk/appconfiguration/azure-appconfiguration/azure/appconfiguration/_azure_appconfiguration_client.py +++ b/sdk/appconfiguration/azure-appconfiguration/azure/appconfiguration/_azure_appconfiguration_client.py @@ -6,7 +6,7 @@ import binascii import functools from datetime import datetime -from typing import Any, Dict, List, Mapping, Optional, Union, cast, overload +from typing import Any, Dict, List, Optional, Union, cast, overload from typing_extensions import Literal from azure.core import MatchConditions from azure.core.paging import ItemPaged @@ -21,7 +21,6 @@ ResourceNotModifiedError, ) from azure.core.rest import HttpRequest, HttpResponse -from azure.core.utils import CaseInsensitiveDict from ._azure_appconfiguration_error import ResourceReadOnlyError from ._azure_appconfiguration_requests import AppConfigRequestsCredentialsPolicy from ._generated import AzureAppConfiguration @@ -321,7 +320,6 @@ def add_configuration_setting(self, configuration_setting: ConfigurationSetting, added_config_setting = client.add_configuration_setting(config_setting) """ key_value = configuration_setting._to_generated() - custom_headers: Mapping[str, Any] = CaseInsensitiveDict(kwargs.get("headers")) error_map = {412: ResourceExistsError} try: key_value_added = self._impl.put_key_value( @@ -329,8 +327,8 @@ def add_configuration_setting(self, configuration_setting: ConfigurationSetting, key=key_value.key, # type: ignore label=key_value.label, if_none_match="*", - headers=custom_headers, error_map=error_map, + **kwargs, ) return ConfigurationSetting._from_generated(key_value_added) except binascii.Error as exc: @@ -358,9 +356,9 @@ def set_configuration_setting( Will use the value from param configuration_setting if not set. :return: The ConfigurationSetting returned from the service :rtype: ~azure.appconfiguration.ConfigurationSetting - :raises: :class:`~azure.core.exceptions.HttpResponseError`, \ + :raises: :class:`~azure.appconfiguration.ResourceReadOnlyError`, \ + :class:`~azure.core.exceptions.HttpResponseError`, \ :class:`~azure.core.exceptions.ClientAuthenticationError`, \ - :class:`~azure.core.exceptions.ResourceReadOnlyError`, \ :class:`~azure.core.exceptions.ResourceModifiedError`, \ :class:`~azure.core.exceptions.ResourceNotModifiedError`, \ :class:`~azure.core.exceptions.ResourceNotFoundError`, \ @@ -380,7 +378,6 @@ def set_configuration_setting( returned_config_setting = client.set_configuration_setting(config_setting) """ key_value = configuration_setting._to_generated() - custom_headers: Mapping[str, Any] = CaseInsensitiveDict(kwargs.get("headers")) error_map: Dict[int, Any] = {409: ResourceReadOnlyError} if match_condition == MatchConditions.IfNotModified: error_map.update({412: ResourceModifiedError}) @@ -398,8 +395,8 @@ def set_configuration_setting( label=key_value.label, if_match=prep_if_match(configuration_setting.etag, match_condition), if_none_match=prep_if_none_match(etag or configuration_setting.etag, match_condition), - headers=custom_headers, error_map=error_map, + **kwargs, ) return ConfigurationSetting._from_generated(key_value_set) except binascii.Error as exc: @@ -414,7 +411,7 @@ def delete_configuration_setting( # pylint:disable=delete-operation-wrong-retur etag: Optional[str] = None, match_condition: MatchConditions = MatchConditions.Unconditionally, **kwargs, - ) -> ConfigurationSetting: + ) -> Union[None, ConfigurationSetting]: """Delete a ConfigurationSetting if it exists :param key: key used to identify the ConfigurationSetting @@ -426,9 +423,9 @@ def delete_configuration_setting( # pylint:disable=delete-operation-wrong-retur :paramtype match_condition: ~azure.core.MatchConditions :return: The deleted ConfigurationSetting returned from the service, or None if it doesn't exist. :rtype: ~azure.appconfiguration.ConfigurationSetting - :raises: :class:`~azure.core.exceptions.HttpResponseError`, \ + :raises: :class:`~azure.appconfiguration.ResourceReadOnlyError`, \ + :class:`~azure.core.exceptions.HttpResponseError`, \ :class:`~azure.core.exceptions.ClientAuthenticationError`, \ - :class:`~azure.core.exceptions.ResourceReadOnlyError`, \ :class:`~azure.core.exceptions.ResourceModifiedError`, \ :class:`~azure.core.exceptions.ResourceNotModifiedError`, \ :class:`~azure.core.exceptions.ResourceNotFoundError`, \ @@ -442,7 +439,6 @@ def delete_configuration_setting( # pylint:disable=delete-operation-wrong-retur key="MyKey", label="MyLabel" ) """ - custom_headers: Mapping[str, Any] = CaseInsensitiveDict(kwargs.get("headers")) error_map: Dict[int, Any] = {409: ResourceReadOnlyError} if match_condition == MatchConditions.IfNotModified: error_map.update({412: ResourceModifiedError}) @@ -458,10 +454,12 @@ def delete_configuration_setting( # pylint:disable=delete-operation-wrong-retur key=key, label=label, if_match=prep_if_match(etag, match_condition), - headers=custom_headers, error_map=error_map, + **kwargs, ) - return ConfigurationSetting._from_generated(key_value_deleted) # type: ignore + if key_value_deleted: + return ConfigurationSetting._from_generated(key_value_deleted) + return None except binascii.Error as exc: raise binascii.Error("Connection string secret has incorrect padding") from exc diff --git a/sdk/appconfiguration/azure-appconfiguration/azure/appconfiguration/_models.py b/sdk/appconfiguration/azure-appconfiguration/azure/appconfiguration/_models.py index ad515bf392d6..b8402b4c292a 100644 --- a/sdk/appconfiguration/azure-appconfiguration/azure/appconfiguration/_models.py +++ b/sdk/appconfiguration/azure-appconfiguration/azure/appconfiguration/_models.py @@ -19,10 +19,6 @@ SnapshotStatus, ) -PolymorphicConfigurationSetting = Union[ - "ConfigurationSetting", "SecretReferenceConfigurationSetting", "FeatureFlagConfigurationSetting" -] - class ConfigurationSetting(Model): """A setting, defined by a unique combination of a key and label.""" @@ -70,25 +66,23 @@ def __init__(self, **kwargs: Any) -> None: self.tags = kwargs.get("tags", {}) @classmethod - def _from_generated(cls, key_value: KeyValue) -> PolymorphicConfigurationSetting: - if key_value is None: - return key_value + def _from_generated(cls, key_value: KeyValue) -> "ConfigurationSetting": + # pylint:disable=protected-access if key_value.content_type is not None: try: if key_value.content_type.startswith( - FeatureFlagConfigurationSetting._feature_flag_content_type # pylint:disable=protected-access + FeatureFlagConfigurationSetting._feature_flag_content_type ) and key_value.key.startswith( # type: ignore - FeatureFlagConfigurationSetting._key_prefix # pylint: disable=protected-access + FeatureFlagConfigurationSetting._key_prefix ): - return FeatureFlagConfigurationSetting._from_generated( # pylint: disable=protected-access - key_value - ) + config_setting = FeatureFlagConfigurationSetting._from_generated(key_value) + if key_value.value: + config_setting.value = key_value.value + return config_setting if key_value.content_type.startswith( - SecretReferenceConfigurationSetting._secret_reference_content_type # pylint:disable=protected-access + SecretReferenceConfigurationSetting._secret_reference_content_type ): - return SecretReferenceConfigurationSetting._from_generated( # pylint: disable=protected-access - key_value - ) + return SecretReferenceConfigurationSetting._from_generated(key_value) except (KeyError, AttributeError): pass @@ -125,7 +119,7 @@ class FeatureFlagConfigurationSetting(ConfigurationSetting): # pylint: disable= """The identity of the configuration setting.""" key: str """The key of the configuration setting.""" - enabled: Optional[bool] + enabled: bool """The value indicating whether the feature flag is enabled. A feature is OFF if enabled is false. If enabled is true, then the feature is ON if there are no conditions or if all conditions are satisfied.""" filters: Optional[List[Dict[str, Any]]] @@ -164,7 +158,7 @@ def __init__( # pylint: disable=super-init-not-called self, feature_id: str, *, - enabled: Optional[bool] = None, + enabled: bool = False, filters: Optional[List[Dict[str, Any]]] = None, **kwargs: Any, ) -> None: @@ -173,8 +167,8 @@ def __init__( # pylint: disable=super-init-not-called :type feature_id: str :keyword enabled: The value indicating whether the feature flag is enabled. A feature is OFF if enabled is false. If enabled is true, then the feature is ON - if there are no conditions or if all conditions are satisfied. - :paramtype enabled: bool or None + if there are no conditions or if all conditions are satisfied. Default value of this property is False. + :paramtype enabled: bool :keyword filters: Filters that must run on the client and be evaluated as true for the feature to be considered enabled. :paramtype filters: list[dict[str, Any]] or None @@ -207,6 +201,8 @@ def value(self) -> str: temp = json.loads(self._value) temp["id"] = self.feature_id temp["enabled"] = self.enabled + temp["display_name"] = self.display_name + temp["description"] = self.description if "conditions" not in temp.keys(): temp["conditions"] = {} temp["conditions"]["client_filters"] = self.filters @@ -221,26 +217,30 @@ def value(self, new_value: str) -> None: temp = json.loads(new_value) temp["id"] = self.feature_id self._value = json.dumps(temp) - self.enabled = temp.get("enabled", None) + self.enabled = temp.get("enabled", False) + self.display_name = temp.get("display_name", None) + self.description = temp.get("description", None) self.filters = None conditions = temp.get("conditions", None) if conditions: self.filters = conditions.get("client_filters", None) except (json.JSONDecodeError, ValueError): self._value = new_value - self.enabled = None + self.enabled = False self.filters = None @classmethod - def _from_generated(cls, key_value: KeyValue) -> Union["FeatureFlagConfigurationSetting", ConfigurationSetting]: - if key_value is None: - return key_value - enabled = None + def _from_generated(cls, key_value: KeyValue) -> "FeatureFlagConfigurationSetting": + enabled = False filters = None + display_name = None + description = None try: temp = json.loads(key_value.value) # type: ignore if isinstance(temp, dict): - enabled = temp.get("enabled") + enabled = temp.get("enabled", False) + display_name = temp.get("display_name") + description = temp.get("description") if "conditions" in temp.keys(): filters = temp["conditions"].get("client_filters") except (ValueError, json.JSONDecodeError): @@ -256,6 +256,8 @@ def _from_generated(cls, key_value: KeyValue) -> Union["FeatureFlagConfiguration etag=key_value.etag, enabled=enabled, filters=filters, + display_name=display_name, + description=description, ) def _to_generated(self) -> KeyValue: @@ -349,8 +351,6 @@ def value(self, new_value: str) -> None: @classmethod def _from_generated(cls, key_value: KeyValue) -> "SecretReferenceConfigurationSetting": - if key_value is None: - return key_value secret_uri = None try: temp = json.loads(key_value.value) # type: ignore diff --git a/sdk/appconfiguration/azure-appconfiguration/azure/appconfiguration/aio/_azure_appconfiguration_client_async.py b/sdk/appconfiguration/azure-appconfiguration/azure/appconfiguration/aio/_azure_appconfiguration_client_async.py index c98df10a329a..37cf9a052003 100644 --- a/sdk/appconfiguration/azure-appconfiguration/azure/appconfiguration/aio/_azure_appconfiguration_client_async.py +++ b/sdk/appconfiguration/azure-appconfiguration/azure/appconfiguration/aio/_azure_appconfiguration_client_async.py @@ -6,7 +6,7 @@ import binascii import functools from datetime import datetime -from typing import Any, Dict, List, Mapping, Optional, Union, cast, overload +from typing import Any, Dict, List, Optional, Union, cast, overload from typing_extensions import Literal from azure.core import MatchConditions from azure.core.async_paging import AsyncItemPaged @@ -23,7 +23,6 @@ ResourceNotModifiedError, ) from azure.core.rest import AsyncHttpResponse, HttpRequest -from azure.core.utils import CaseInsensitiveDict from ._sync_token_async import AsyncSyncTokenPolicy from .._azure_appconfiguration_error import ResourceReadOnlyError from .._azure_appconfiguration_requests import AppConfigRequestsCredentialsPolicy @@ -334,7 +333,6 @@ async def add_configuration_setting( added_config_setting = await async_client.add_configuration_setting(config_setting) """ key_value = configuration_setting._to_generated() - custom_headers: Mapping[str, Any] = CaseInsensitiveDict(kwargs.get("headers")) error_map = {412: ResourceExistsError} try: @@ -343,8 +341,8 @@ async def add_configuration_setting( key=key_value.key, # type: ignore label=key_value.label, if_none_match="*", - headers=custom_headers, error_map=error_map, + **kwargs, ) return ConfigurationSetting._from_generated(key_value_added) except binascii.Error as exc: @@ -373,9 +371,9 @@ async def set_configuration_setting( Will use the value from param configuration_setting if not set. :return: The ConfigurationSetting returned from the service :rtype: ~azure.appconfiguration.ConfigurationSetting - :raises: :class:`~azure.core.exceptions.HttpResponseError`, \ + :raises: :class:`~azure.appconfiguration.ResourceReadOnlyError`, \ + :class:`~azure.core.exceptions.HttpResponseError`, \ :class:`~azure.core.exceptions.ClientAuthenticationError`, \ - :class:`~azure.core.exceptions.ResourceReadOnlyError`, \ :class:`~azure.core.exceptions.ResourceModifiedError`, \ :class:`~azure.core.exceptions.ResourceNotModifiedError`, \ :class:`~azure.core.exceptions.ResourceNotFoundError`, \ @@ -396,7 +394,6 @@ async def set_configuration_setting( returned_config_setting = await async_client.set_configuration_setting(config_setting) """ key_value = configuration_setting._to_generated() - custom_headers: Mapping[str, Any] = CaseInsensitiveDict(kwargs.get("headers")) error_map: Dict[int, Any] = {409: ResourceReadOnlyError} if match_condition == MatchConditions.IfNotModified: error_map.update({412: ResourceModifiedError}) @@ -414,8 +411,8 @@ async def set_configuration_setting( label=key_value.label, if_match=prep_if_match(configuration_setting.etag, match_condition), if_none_match=prep_if_none_match(etag or configuration_setting.etag, match_condition), - headers=custom_headers, error_map=error_map, + **kwargs, ) return ConfigurationSetting._from_generated(key_value_set) except binascii.Error as exc: @@ -430,7 +427,7 @@ async def delete_configuration_setting( etag: Optional[str] = None, match_condition: MatchConditions = MatchConditions.Unconditionally, **kwargs, - ) -> ConfigurationSetting: + ) -> Union[None, ConfigurationSetting]: """Delete a ConfigurationSetting if it exists :param key: key used to identify the ConfigurationSetting @@ -442,9 +439,9 @@ async def delete_configuration_setting( :paramtype match_condition: ~azure.core.MatchConditions :return: The deleted ConfigurationSetting returned from the service, or None if it doesn't exist. :rtype: ~azure.appconfiguration.ConfigurationSetting - :raises: :class:`~azure.core.exceptions.HttpResponseError`, \ + :raises: :class:`~azure.appconfiguration.ResourceReadOnlyError`, \ + :class:`~azure.core.exceptions.HttpResponseError`, \ :class:`~azure.core.exceptions.ClientAuthenticationError`, \ - :class:`~azure.core.exceptions.ResourceReadOnlyError`, \ :class:`~azure.core.exceptions.ResourceModifiedError`, \ :class:`~azure.core.exceptions.ResourceNotModifiedError`, \ :class:`~azure.core.exceptions.ResourceNotFoundError`, \ @@ -459,7 +456,6 @@ async def delete_configuration_setting( key="MyKey", label="MyLabel" ) """ - custom_headers: Mapping[str, Any] = CaseInsensitiveDict(kwargs.get("headers")) error_map: Dict[int, Any] = {409: ResourceReadOnlyError} if match_condition == MatchConditions.IfNotModified: error_map.update({412: ResourceModifiedError}) @@ -475,10 +471,12 @@ async def delete_configuration_setting( key=key, label=label, if_match=prep_if_match(etag, match_condition), - headers=custom_headers, error_map=error_map, + **kwargs, ) - return ConfigurationSetting._from_generated(key_value_deleted) # type: ignore + if key_value_deleted: + return ConfigurationSetting._from_generated(key_value_deleted) + return None except binascii.Error as exc: raise binascii.Error("Connection string secret has incorrect padding") from exc diff --git a/sdk/appconfiguration/azure-appconfiguration/tests/test_azure_appconfiguration_client.py b/sdk/appconfiguration/azure-appconfiguration/tests/test_azure_appconfiguration_client.py index 88fb537cd07a..3ef2ba0118ff 100644 --- a/sdk/appconfiguration/azure-appconfiguration/tests/test_azure_appconfiguration_client.py +++ b/sdk/appconfiguration/azure-appconfiguration/tests/test_azure_appconfiguration_client.py @@ -629,15 +629,15 @@ def test_config_setting_feature_flag(self, appconfiguration_connection_string): assert temp["conditions"] == set_flag_value["conditions"] changed_flag.value = json.dumps({}) - assert changed_flag.enabled == None + assert changed_flag.enabled == False temp = json.loads(changed_flag.value) assert temp["id"] == set_flag_value["id"] - assert temp["enabled"] == None + assert temp["enabled"] == False assert temp["conditions"] != None assert temp["conditions"]["client_filters"] == None set_flag.value = "bad_value" - assert set_flag.enabled == None + assert set_flag.enabled == False assert set_flag.filters == None assert set_flag.value == "bad_value" @@ -811,6 +811,34 @@ def test_feature_filter_multiple(self, appconfiguration_connection_string): client.delete_configuration_setting(new_sent.key) + @app_config_decorator + @recorded_by_proxy + def test_feature_custom_fields(self, appconfiguration_connection_string): + client = self.create_client(appconfiguration_connection_string) + custom_fields = { + "variants": [ + {"name": "Off", "configuration_value": "Off", "status_override": "Enabled"}, + {"name": "On", "configuration_value": "On", "status_override": "Disabled"}, + ], + "allocation": { + "percentile": [{"variant": "Off", "from": 0, "to": 100}], + "user": [{"variant": "Off", "users": ["Adam"]}], + "seed": "adfsasdfzsd", + "default_when_enabled": "Off", + "default_when_disabled": "Off", + }, + } + new = FeatureFlagConfigurationSetting( + "Beta", + description="Matt's variant FF", + enabled=True, + ) + new.value = json.dumps(custom_fields) + sent = client.set_configuration_setting(new) + self._assert_same_keys(sent, new) + + client.delete_configuration_setting(new.key) + @app_config_decorator @recorded_by_proxy def test_breaking_with_feature_flag_configuration_setting(self, appconfiguration_connection_string): @@ -830,6 +858,7 @@ def test_breaking_with_feature_flag_configuration_setting(self, appconfiguration ) client.set_configuration_setting(new) client.get_configuration_setting(new.key) + client.delete_configuration_setting(new.key) new = FeatureFlagConfigurationSetting( "breaking2", @@ -846,6 +875,7 @@ def test_breaking_with_feature_flag_configuration_setting(self, appconfiguration ) client.set_configuration_setting(new) client.get_configuration_setting(new.key) + client.delete_configuration_setting(new.key) # This will show up as a Custom filter new = FeatureFlagConfigurationSetting( @@ -863,6 +893,7 @@ def test_breaking_with_feature_flag_configuration_setting(self, appconfiguration ) client.set_configuration_setting(new) client.get_configuration_setting(new.key) + client.delete_configuration_setting(new.key) new = FeatureFlagConfigurationSetting( "breaking4", @@ -873,6 +904,7 @@ def test_breaking_with_feature_flag_configuration_setting(self, appconfiguration ) client.set_configuration_setting(new) client.get_configuration_setting(new.key) + client.delete_configuration_setting(new.key) new = FeatureFlagConfigurationSetting( "breaking5", @@ -881,22 +913,24 @@ def test_breaking_with_feature_flag_configuration_setting(self, appconfiguration ) client.set_configuration_setting(new) client.get_configuration_setting(new.key) + client.delete_configuration_setting(new.key) new = FeatureFlagConfigurationSetting( "breaking6", enabled=True, filters=[{"name": FILTER_TARGETING, "parameters": "invalidformat"}] ) client.set_configuration_setting(new) client.get_configuration_setting(new.key) + client.delete_configuration_setting(new.key) new = FeatureFlagConfigurationSetting("breaking7", enabled=True, filters=[{"abc": "def"}]) client.set_configuration_setting(new) client.get_configuration_setting(new.key) + client.delete_configuration_setting(new.key) new = FeatureFlagConfigurationSetting("breaking8", enabled=True, filters=[{"abc": "def"}]) new.feature_flag_content_type = "fakeyfakey" # cspell:disable-line client.set_configuration_setting(new) client.get_configuration_setting(new.key) - client.delete_configuration_setting(new.key) @app_config_decorator diff --git a/sdk/appconfiguration/azure-appconfiguration/tests/test_azure_appconfiguration_client_aad.py b/sdk/appconfiguration/azure-appconfiguration/tests/test_azure_appconfiguration_client_aad.py index 92fd27c8bcbe..5c2dea108853 100644 --- a/sdk/appconfiguration/azure-appconfiguration/tests/test_azure_appconfiguration_client_aad.py +++ b/sdk/appconfiguration/azure-appconfiguration/tests/test_azure_appconfiguration_client_aad.py @@ -575,15 +575,15 @@ def test_config_setting_feature_flag(self, appconfiguration_endpoint_string): assert temp["conditions"] == set_flag_value["conditions"] changed_flag.value = json.dumps({}) - assert changed_flag.enabled == None + assert changed_flag.enabled == False temp = json.loads(changed_flag.value) assert temp["id"] == set_flag_value["id"] - assert temp["enabled"] == None + assert temp["enabled"] == False assert temp["conditions"] != None assert temp["conditions"]["client_filters"] == None set_flag.value = "bad_value" - assert set_flag.enabled == None + assert set_flag.enabled == False assert set_flag.filters == None assert set_flag.value == "bad_value" diff --git a/sdk/appconfiguration/azure-appconfiguration/tests/test_azure_appconfiguration_client_aad_async.py b/sdk/appconfiguration/azure-appconfiguration/tests/test_azure_appconfiguration_client_aad_async.py index 8c88162d2183..c2f17d993b8b 100644 --- a/sdk/appconfiguration/azure-appconfiguration/tests/test_azure_appconfiguration_client_aad_async.py +++ b/sdk/appconfiguration/azure-appconfiguration/tests/test_azure_appconfiguration_client_aad_async.py @@ -601,15 +601,15 @@ async def test_config_setting_feature_flag(self, appconfiguration_endpoint_strin assert temp["conditions"] == set_flag_value["conditions"] changed_flag.value = json.dumps({}) - assert changed_flag.enabled == None + assert changed_flag.enabled == False temp = json.loads(changed_flag.value) assert temp["id"] == set_flag_value["id"] - assert temp["enabled"] == None + assert temp["enabled"] == False assert temp["conditions"] != None assert temp["conditions"]["client_filters"] == None set_flag.value = "bad_value" - assert set_flag.enabled == None + assert set_flag.enabled == False assert set_flag.filters == None assert set_flag.value == "bad_value" diff --git a/sdk/appconfiguration/azure-appconfiguration/tests/test_azure_appconfiguration_client_async.py b/sdk/appconfiguration/azure-appconfiguration/tests/test_azure_appconfiguration_client_async.py index 9a9e204c1c15..01bc3dc88126 100644 --- a/sdk/appconfiguration/azure-appconfiguration/tests/test_azure_appconfiguration_client_async.py +++ b/sdk/appconfiguration/azure-appconfiguration/tests/test_azure_appconfiguration_client_async.py @@ -639,15 +639,15 @@ async def test_config_setting_feature_flag(self, appconfiguration_connection_str assert temp["conditions"] == set_flag_value["conditions"] changed_flag.value = json.dumps({}) - assert changed_flag.enabled == None + assert changed_flag.enabled == False temp = json.loads(changed_flag.value) assert temp["id"] == set_flag_value["id"] - assert temp["enabled"] == None + assert temp["enabled"] == False assert temp["conditions"] != None assert temp["conditions"]["client_filters"] == None set_flag.value = "bad_value" - assert set_flag.enabled == None + assert set_flag.enabled == False assert set_flag.filters == None assert set_flag.value == "bad_value" @@ -834,6 +834,34 @@ async def test_feature_filter_multiple(self, appconfiguration_connection_string) await client.delete_configuration_setting(new_sent.key) + @app_config_decorator_async + @recorded_by_proxy_async + async def test_feature_custom_fields(self, appconfiguration_connection_string): + custom_fields = { + "variants": [ + {"name": "Off", "configuration_value": "Off", "status_override": "Enabled"}, + {"name": "On", "configuration_value": "On", "status_override": "Disabled"}, + ], + "allocation": { + "percentile": [{"variant": "Off", "from": 0, "to": 100}], + "user": [{"variant": "Off", "users": ["Adam"]}], + "seed": "adfsasdfzsd", + "default_when_enabled": "Off", + "default_when_disabled": "Off", + }, + } + async with self.create_client(appconfiguration_connection_string) as client: + new = FeatureFlagConfigurationSetting( + "Beta", + description="Matt's variant FF", + enabled=True, + ) + new.value = json.dumps(custom_fields) + sent = await client.set_configuration_setting(new) + self._assert_same_keys(sent, new) + + await client.delete_configuration_setting(new.key) + @app_config_decorator_async @recorded_by_proxy_async async def test_breaking_with_feature_flag_configuration_setting(self, appconfiguration_connection_string): @@ -853,6 +881,7 @@ async def test_breaking_with_feature_flag_configuration_setting(self, appconfigu ) await client.set_configuration_setting(new) await client.get_configuration_setting(new.key) + await client.delete_configuration_setting(new.key) new = FeatureFlagConfigurationSetting( "breaking2", @@ -869,6 +898,7 @@ async def test_breaking_with_feature_flag_configuration_setting(self, appconfigu ) await client.set_configuration_setting(new) await client.get_configuration_setting(new.key) + await client.delete_configuration_setting(new.key) # This will show up as a Custom filter new = FeatureFlagConfigurationSetting( @@ -886,6 +916,7 @@ async def test_breaking_with_feature_flag_configuration_setting(self, appconfigu ) await client.set_configuration_setting(new) await client.get_configuration_setting(new.key) + await client.delete_configuration_setting(new.key) new = FeatureFlagConfigurationSetting( "breaking4", @@ -896,6 +927,7 @@ async def test_breaking_with_feature_flag_configuration_setting(self, appconfigu ) await client.set_configuration_setting(new) await client.get_configuration_setting(new.key) + await client.delete_configuration_setting(new.key) new = FeatureFlagConfigurationSetting( "breaking5", @@ -904,22 +936,24 @@ async def test_breaking_with_feature_flag_configuration_setting(self, appconfigu ) await client.set_configuration_setting(new) await client.get_configuration_setting(new.key) + await client.delete_configuration_setting(new.key) new = FeatureFlagConfigurationSetting( "breaking6", enabled=True, filters=[{"name": FILTER_TARGETING, "parameters": "invalidformat"}] ) await client.set_configuration_setting(new) await client.get_configuration_setting(new.key) + await client.delete_configuration_setting(new.key) new = FeatureFlagConfigurationSetting("breaking7", enabled=True, filters=[{"abc": "def"}]) await client.set_configuration_setting(new) await client.get_configuration_setting(new.key) + await client.delete_configuration_setting(new.key) new = FeatureFlagConfigurationSetting("breaking8", enabled=True, filters=[{"abc": "def"}]) new.feature_flag_content_type = "fakeyfakey" # cspell:disable-line await client.set_configuration_setting(new) await client.get_configuration_setting(new.key) - await client.delete_configuration_setting(new.key) @app_config_decorator_async diff --git a/sdk/appconfiguration/azure-appconfiguration/tests/test_consistency.py b/sdk/appconfiguration/azure-appconfiguration/tests/test_consistency.py index 808e092780ac..7b44fb16d143 100644 --- a/sdk/appconfiguration/azure-appconfiguration/tests/test_consistency.py +++ b/sdk/appconfiguration/azure-appconfiguration/tests/test_consistency.py @@ -96,7 +96,7 @@ def test_feature_flag_invalid_json_access_properties(self, appconfiguration_conn set_flag = client.set_configuration_setting(feature_flag) set_flag.value = "hello world" - assert set_flag.enabled == None + assert set_flag.enabled == False assert set_flag.filters == None client.delete_configuration_setting(feature_flag) diff --git a/sdk/appconfiguration/azure-appconfiguration/tests/testcase.py b/sdk/appconfiguration/azure-appconfiguration/tests/testcase.py index a401c8209734..ee476f24b3ba 100644 --- a/sdk/appconfiguration/azure-appconfiguration/tests/testcase.py +++ b/sdk/appconfiguration/azure-appconfiguration/tests/testcase.py @@ -87,7 +87,9 @@ def _assert_same_keys(self, key1, key2): assert key1.value == key2.value if isinstance(key1, FeatureFlagConfigurationSetting): assert key1.enabled == key2.enabled - assert len(key1.filters) == len(key2.filters) + if key1.filters and key2.filters: + assert len(key1.filters) == len(key2.filters) + assert key1.description == key2.description elif isinstance(key1, SecretReferenceConfigurationSetting): assert key1.secret_id == key2.secret_id