From 3a6e57f4cd62700f5db69969af9889be9ce38a15 Mon Sep 17 00:00:00 2001 From: AMER JUSUPOVIC Date: Tue, 25 Jun 2024 14:20:15 -0700 Subject: [PATCH 1/6] adding new private methods for dotnet versus microsoft schema, fixing tests --- .../FeatureManagementConstants.cs | 5 + .../FeatureManagementKeyValueAdapter.cs | 141 +++++-- .../FeatureManagementTests.cs | 366 ++++++++---------- .../JsonContentTypeTests.cs | 6 +- 4 files changed, 280 insertions(+), 238 deletions(-) diff --git a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementConstants.cs b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementConstants.cs index 9568a2cb..c6d86d84 100644 --- a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementConstants.cs +++ b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementConstants.cs @@ -44,5 +44,10 @@ internal class FeatureManagementConstants public const string ETag = "ETag"; public const string FeatureFlagId = "FeatureFlagId"; public const string FeatureFlagReference = "FeatureFlagReference"; + + // Dotnet schema keys + public const string DotnetSchemaSectionName = "FeatureManagement"; + public const string DotnetSchemaEnabledFor = "EnabledFor"; + public const string DotnetSchemaRequirementType = "RequirementType"; } } diff --git a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs index 04f9841d..8d45771d 100644 --- a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs +++ b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs @@ -18,6 +18,7 @@ internal class FeatureManagementKeyValueAdapter : IKeyValueAdapter { private FeatureFilterTracing _featureFilterTracing; private int _featureFlagIndex = 0; + private bool _isMicrosoftSchema; public FeatureManagementKeyValueAdapter(FeatureFilterTracing featureFilterTracing) { @@ -26,13 +27,110 @@ public FeatureManagementKeyValueAdapter(FeatureFilterTracing featureFilterTracin public Task>> ProcessKeyValue(ConfigurationSetting setting, Uri endpoint, Logger logger, CancellationToken cancellationToken) { + _isMicrosoftSchema = false; + FeatureFlag featureFlag = ParseFeatureFlag(setting.Key, setting.Value); var keyValues = new List>(); + if (_isMicrosoftSchema) + { + keyValues = ProcessMicrosoftSchemaFeatureFlag(featureFlag, setting, endpoint); + } + else + { + keyValues = ProcessDotnetSchemaFeatureFlag(featureFlag, setting, endpoint); + } + + return Task.FromResult>>(keyValues); + } + + public bool CanProcess(ConfigurationSetting setting) + { + string contentType = setting?.ContentType?.Split(';')[0].Trim(); + + return string.Equals(contentType, FeatureManagementConstants.ContentType) || + setting.Key.StartsWith(FeatureManagementConstants.FeatureFlagMarker); + } + + public void InvalidateCache(ConfigurationSetting setting = null) + { + return; + } + + public bool NeedsRefresh() + { + return false; + } + + public void OnChangeDetected(ConfigurationSetting setting = null) + { + return; + } + + public void OnConfigUpdated() + { + _featureFlagIndex = 0; + + return; + } + + private List> ProcessDotnetSchemaFeatureFlag(FeatureFlag featureFlag, ConfigurationSetting setting, Uri endpoint) + { + var keyValues = new List>(); + + if (!string.IsNullOrEmpty(featureFlag.Id)) + { + string featureFlagPath = $"{FeatureManagementConstants.DotnetSchemaSectionName}:{featureFlag.Id}"; + + if (featureFlag.Enabled) + { + if (featureFlag.Conditions?.ClientFilters == null || !featureFlag.Conditions.ClientFilters.Any()) + { + keyValues.Add(new KeyValuePair(featureFlagPath, true.ToString())); + } + else + { + for (int i = 0; i < featureFlag.Conditions.ClientFilters.Count; i++) + { + ClientFilter clientFilter = featureFlag.Conditions.ClientFilters[i]; + + _featureFilterTracing.UpdateFeatureFilterTracing(clientFilter.Name); + + keyValues.Add(new KeyValuePair($"{featureFlagPath}:{FeatureManagementConstants.DotnetSchemaEnabledFor}:{i}:Name", clientFilter.Name)); + + foreach (KeyValuePair kvp in new JsonFlattener().FlattenJson(clientFilter.Parameters)) + { + keyValues.Add(new KeyValuePair($"{featureFlagPath}:{FeatureManagementConstants.DotnetSchemaEnabledFor}:{i}:Parameters:{kvp.Key}", kvp.Value)); + } + } + + // + // process RequirementType only when filters are not empty + if (featureFlag.Conditions.RequirementType != null) + { + keyValues.Add(new KeyValuePair( + $"{featureFlagPath}:{FeatureManagementConstants.DotnetSchemaRequirementType}", + featureFlag.Conditions.RequirementType)); + } + } + } + else + { + keyValues.Add(new KeyValuePair($"{featureFlagPath}", false.ToString())); + } + } + + return keyValues; + } + + private List> ProcessMicrosoftSchemaFeatureFlag(FeatureFlag featureFlag, ConfigurationSetting setting, Uri endpoint) + { + var keyValues = new List>(); + if (string.IsNullOrEmpty(featureFlag.Id)) { - return Task.FromResult>>(keyValues); + return keyValues; } string featureFlagPath = $"{FeatureManagementConstants.FeatureManagementSectionName}:{FeatureManagementConstants.FeatureFlagsSectionName}:{_featureFlagIndex}"; @@ -53,7 +151,7 @@ public Task>> ProcessKeyValue(Configura { ClientFilter clientFilter = featureFlag.Conditions.ClientFilters[i]; - _featureFilterTracing.UpdateFeatureFilterTracing(clientFilter.Name); + _featureFilterTracing.UpdateFeatureFilterTracing(clientFilter.Name); string clientFiltersPath = $"{featureFlagPath}:{FeatureManagementConstants.Conditions}:{FeatureManagementConstants.ClientFilters}:{i}"; @@ -70,7 +168,7 @@ public Task>> ProcessKeyValue(Configura if (featureFlag.Conditions.RequirementType != null) { keyValues.Add(new KeyValuePair( - $"{featureFlagPath}:{FeatureManagementConstants.Conditions}:{FeatureManagementConstants.RequirementType}", + $"{featureFlagPath}:{FeatureManagementConstants.Conditions}:{FeatureManagementConstants.RequirementType}", featureFlag.Conditions.RequirementType)); } } @@ -219,37 +317,12 @@ public Task>> ProcessKeyValue(Configura } } - return Task.FromResult>>(keyValues); + return keyValues; } - public bool CanProcess(ConfigurationSetting setting) + private bool IsMicrosoftSchema(FeatureFlag featureFlag) { - string contentType = setting?.ContentType?.Split(';')[0].Trim(); - - return string.Equals(contentType, FeatureManagementConstants.ContentType) || - setting.Key.StartsWith(FeatureManagementConstants.FeatureFlagMarker); - } - - public void InvalidateCache(ConfigurationSetting setting = null) - { - return; - } - - public bool NeedsRefresh() - { - return false; - } - - public void OnChangeDetected(ConfigurationSetting setting = null) - { - return; - } - - public void OnConfigUpdated() - { - _featureFlagIndex = 0; - - return; + return featureFlag.Allocation != null || featureFlag.Variants != null || featureFlag.Telemetry != null; } private FormatException CreateFeatureFlagFormatException(string jsonPropertyName, string settingKey, string foundJsonValueKind, string expectedJsonValueKind) @@ -346,6 +419,8 @@ private FeatureFlag ParseFeatureFlag(string settingKey, string settingValue) case FeatureManagementConstants.Allocation: { + _isMicrosoftSchema = true; + if (reader.Read() && reader.TokenType == JsonTokenType.StartObject) { featureFlag.Allocation = ParseFeatureAllocation(ref reader, settingKey); @@ -364,6 +439,8 @@ private FeatureFlag ParseFeatureFlag(string settingKey, string settingValue) case FeatureManagementConstants.Variants: { + _isMicrosoftSchema = true; + if (reader.Read() && reader.TokenType == JsonTokenType.StartArray) { List variants = new List(); @@ -409,6 +486,8 @@ private FeatureFlag ParseFeatureFlag(string settingKey, string settingValue) case FeatureManagementConstants.Telemetry: { + _isMicrosoftSchema = true; + if (reader.Read() && reader.TokenType == JsonTokenType.StartObject) { featureFlag.Telemetry = ParseFeatureTelemetry(ref reader, settingKey); diff --git a/tests/Tests.AzureAppConfiguration/FeatureManagementTests.cs b/tests/Tests.AzureAppConfiguration/FeatureManagementTests.cs index 12a18a97..3f5696c5 100644 --- a/tests/Tests.AzureAppConfiguration/FeatureManagementTests.cs +++ b/tests/Tests.AzureAppConfiguration/FeatureManagementTests.cs @@ -645,18 +645,16 @@ public void UsesFeatureFlags() }) .Build(); - Assert.Equal("Beta", config["feature_management:feature_flags:0:id"]); - Assert.Equal("True", config["feature_management:feature_flags:0:enabled"]); - Assert.Equal("Browser", config["feature_management:feature_flags:0:conditions:client_filters:0:name"]); - Assert.Equal("Firefox", config["feature_management:feature_flags:0:conditions:client_filters:0:parameters:AllowedBrowsers:0"]); - Assert.Equal("Safari", config["feature_management:feature_flags:0:conditions:client_filters:0:parameters:AllowedBrowsers:1"]); - Assert.Equal("RollOut", config["feature_management:feature_flags:0:conditions:client_filters:1:name"]); - Assert.Equal("20", config["feature_management:feature_flags:0:conditions:client_filters:1:parameters:Percentage"]); - Assert.Equal("US", config["feature_management:feature_flags:0:conditions:client_filters:1:parameters:Region"]); - Assert.Equal("SuperUsers", config["feature_management:feature_flags:0:conditions:client_filters:2:name"]); - Assert.Equal("TimeWindow", config["feature_management:feature_flags:0:conditions:client_filters:3:name"]); - Assert.Equal("/Date(1578643200000)/", config["feature_management:feature_flags:0:conditions:client_filters:3:parameters:Start"]); - Assert.Equal("/Date(1578686400000)/", config["feature_management:feature_flags:0:conditions:client_filters:3:parameters:End"]); + Assert.Equal("Browser", config["FeatureManagement:Beta:EnabledFor:0:Name"]); + Assert.Equal("Firefox", config["FeatureManagement:Beta:EnabledFor:0:Parameters:AllowedBrowsers:0"]); + Assert.Equal("Safari", config["FeatureManagement:Beta:EnabledFor:0:Parameters:AllowedBrowsers:1"]); + Assert.Equal("RollOut", config["FeatureManagement:Beta:EnabledFor:1:Name"]); + Assert.Equal("20", config["FeatureManagement:Beta:EnabledFor:1:Parameters:Percentage"]); + Assert.Equal("US", config["FeatureManagement:Beta:EnabledFor:1:Parameters:Region"]); + Assert.Equal("SuperUsers", config["FeatureManagement:Beta:EnabledFor:2:Name"]); + Assert.Equal("TimeWindow", config["FeatureManagement:Beta:EnabledFor:3:Name"]); + Assert.Equal("/Date(1578643200000)/", config["FeatureManagement:Beta:EnabledFor:3:Parameters:Start"]); + Assert.Equal("/Date(1578686400000)/", config["FeatureManagement:Beta:EnabledFor:3:Parameters:End"]); } [Fact] @@ -681,18 +679,16 @@ public async Task WatchesFeatureFlags() }) .Build(); - Assert.Equal("Beta", config["feature_management:feature_flags:0:id"]); - Assert.Equal("True", config["feature_management:feature_flags:0:enabled"]); - Assert.Equal("Browser", config["feature_management:feature_flags:0:conditions:client_filters:0:name"]); - Assert.Equal("Firefox", config["feature_management:feature_flags:0:conditions:client_filters:0:parameters:AllowedBrowsers:0"]); - Assert.Equal("Safari", config["feature_management:feature_flags:0:conditions:client_filters:0:parameters:AllowedBrowsers:1"]); - Assert.Equal("RollOut", config["feature_management:feature_flags:0:conditions:client_filters:1:name"]); - Assert.Equal("20", config["feature_management:feature_flags:0:conditions:client_filters:1:parameters:Percentage"]); - Assert.Equal("US", config["feature_management:feature_flags:0:conditions:client_filters:1:parameters:Region"]); - Assert.Equal("SuperUsers", config["feature_management:feature_flags:0:conditions:client_filters:2:name"]); - Assert.Equal("TimeWindow", config["feature_management:feature_flags:0:conditions:client_filters:3:name"]); - Assert.Equal("/Date(1578643200000)/", config["feature_management:feature_flags:0:conditions:client_filters:3:parameters:Start"]); - Assert.Equal("/Date(1578686400000)/", config["feature_management:feature_flags:0:conditions:client_filters:3:parameters:End"]); + Assert.Equal("Browser", config["FeatureManagement:Beta:EnabledFor:0:Name"]); + Assert.Equal("Firefox", config["FeatureManagement:Beta:EnabledFor:0:Parameters:AllowedBrowsers:0"]); + Assert.Equal("Safari", config["FeatureManagement:Beta:EnabledFor:0:Parameters:AllowedBrowsers:1"]); + Assert.Equal("RollOut", config["FeatureManagement:Beta:EnabledFor:1:Name"]); + Assert.Equal("20", config["FeatureManagement:Beta:EnabledFor:1:Parameters:Percentage"]); + Assert.Equal("US", config["FeatureManagement:Beta:EnabledFor:1:Parameters:Region"]); + Assert.Equal("SuperUsers", config["FeatureManagement:Beta:EnabledFor:2:Name"]); + Assert.Equal("TimeWindow", config["FeatureManagement:Beta:EnabledFor:3:Name"]); + Assert.Equal("/Date(1578643200000)/", config["FeatureManagement:Beta:EnabledFor:3:Parameters:Start"]); + Assert.Equal("/Date(1578686400000)/", config["FeatureManagement:Beta:EnabledFor:3:Parameters:End"]); featureFlags[0] = ConfigurationModelFactory.ConfigurationSetting( key: FeatureManagementConstants.FeatureFlagMarker + "myFeature", @@ -724,12 +720,10 @@ public async Task WatchesFeatureFlags() Thread.Sleep(RefreshInterval); await refresher.RefreshAsync(); - Assert.Equal("Beta", config["feature_management:feature_flags:0:id"]); - Assert.Equal("True", config["feature_management:feature_flags:0:enabled"]); - Assert.Equal("Browser", config["feature_management:feature_flags:0:conditions:client_filters:0:name"]); - Assert.Equal("Chrome", config["feature_management:feature_flags:0:conditions:client_filters:0:parameters:AllowedBrowsers:0"]); - Assert.Equal("Edge", config["feature_management:feature_flags:0:conditions:client_filters:0:parameters:AllowedBrowsers:1"]); - Assert.Equal("SuperUsers", config["feature_management:feature_flags:1:conditions:client_filters:0:name"]); + Assert.Equal("Browser", config["FeatureManagement:Beta:EnabledFor:0:Name"]); + Assert.Equal("Chrome", config["FeatureManagement:Beta:EnabledFor:0:Parameters:AllowedBrowsers:0"]); + Assert.Equal("Edge", config["FeatureManagement:Beta:EnabledFor:0:Parameters:AllowedBrowsers:1"]); + Assert.Equal("SuperUsers", config["FeatureManagement:MyFeature2:EnabledFor:0:Name"]); } [Fact] @@ -756,18 +750,16 @@ public async Task WatchesFeatureFlagsUsingCacheExpirationInterval() }) .Build(); - Assert.Equal("Beta", config["feature_management:feature_flags:0:id"]); - Assert.Equal("True", config["feature_management:feature_flags:0:enabled"]); - Assert.Equal("Browser", config["feature_management:feature_flags:0:conditions:client_filters:0:name"]); - Assert.Equal("Firefox", config["feature_management:feature_flags:0:conditions:client_filters:0:parameters:AllowedBrowsers:0"]); - Assert.Equal("Safari", config["feature_management:feature_flags:0:conditions:client_filters:0:parameters:AllowedBrowsers:1"]); - Assert.Equal("RollOut", config["feature_management:feature_flags:0:conditions:client_filters:1:name"]); - Assert.Equal("20", config["feature_management:feature_flags:0:conditions:client_filters:1:parameters:Percentage"]); - Assert.Equal("US", config["feature_management:feature_flags:0:conditions:client_filters:1:parameters:Region"]); - Assert.Equal("SuperUsers", config["feature_management:feature_flags:0:conditions:client_filters:2:name"]); - Assert.Equal("TimeWindow", config["feature_management:feature_flags:0:conditions:client_filters:3:name"]); - Assert.Equal("/Date(1578643200000)/", config["feature_management:feature_flags:0:conditions:client_filters:3:parameters:Start"]); - Assert.Equal("/Date(1578686400000)/", config["feature_management:feature_flags:0:conditions:client_filters:3:parameters:End"]); + Assert.Equal("Browser", config["FeatureManagement:Beta:EnabledFor:0:Name"]); + Assert.Equal("Firefox", config["FeatureManagement:Beta:EnabledFor:0:Parameters:AllowedBrowsers:0"]); + Assert.Equal("Safari", config["FeatureManagement:Beta:EnabledFor:0:Parameters:AllowedBrowsers:1"]); + Assert.Equal("RollOut", config["FeatureManagement:Beta:EnabledFor:1:Name"]); + Assert.Equal("20", config["FeatureManagement:Beta:EnabledFor:1:Parameters:Percentage"]); + Assert.Equal("US", config["FeatureManagement:Beta:EnabledFor:1:Parameters:Region"]); + Assert.Equal("SuperUsers", config["FeatureManagement:Beta:EnabledFor:2:Name"]); + Assert.Equal("TimeWindow", config["FeatureManagement:Beta:EnabledFor:3:Name"]); + Assert.Equal("/Date(1578643200000)/", config["FeatureManagement:Beta:EnabledFor:3:Parameters:Start"]); + Assert.Equal("/Date(1578686400000)/", config["FeatureManagement:Beta:EnabledFor:3:Parameters:End"]); featureFlags[0] = ConfigurationModelFactory.ConfigurationSetting( key: FeatureManagementConstants.FeatureFlagMarker + "myFeature", @@ -799,12 +791,10 @@ public async Task WatchesFeatureFlagsUsingCacheExpirationInterval() Thread.Sleep(cacheExpirationInterval); await refresher.RefreshAsync(); - Assert.Equal("Beta", config["feature_management:feature_flags:0:id"]); - Assert.Equal("True", config["feature_management:feature_flags:0:enabled"]); - Assert.Equal("Browser", config["feature_management:feature_flags:0:conditions:client_filters:0:name"]); - Assert.Equal("Chrome", config["feature_management:feature_flags:0:conditions:client_filters:0:parameters:AllowedBrowsers:0"]); - Assert.Equal("Edge", config["feature_management:feature_flags:0:conditions:client_filters:0:parameters:AllowedBrowsers:1"]); - Assert.Equal("SuperUsers", config["feature_management:feature_flags:1:conditions:client_filters:0:name"]); + Assert.Equal("Browser", config["FeatureManagement:Beta:EnabledFor:0:Name"]); + Assert.Equal("Chrome", config["FeatureManagement:Beta:EnabledFor:0:Parameters:AllowedBrowsers:0"]); + Assert.Equal("Edge", config["FeatureManagement:Beta:EnabledFor:0:Parameters:AllowedBrowsers:1"]); + Assert.Equal("SuperUsers", config["FeatureManagement:MyFeature2:EnabledFor:0:Name"]); } [Fact] @@ -829,18 +819,16 @@ public async Task SkipRefreshIfRefreshIntervalHasNotElapsed() }) .Build(); - Assert.Equal("Beta", config["feature_management:feature_flags:0:id"]); - Assert.Equal("True", config["feature_management:feature_flags:0:enabled"]); - Assert.Equal("Browser", config["feature_management:feature_flags:0:conditions:client_filters:0:name"]); - Assert.Equal("Firefox", config["feature_management:feature_flags:0:conditions:client_filters:0:parameters:AllowedBrowsers:0"]); - Assert.Equal("Safari", config["feature_management:feature_flags:0:conditions:client_filters:0:parameters:AllowedBrowsers:1"]); - Assert.Equal("RollOut", config["feature_management:feature_flags:0:conditions:client_filters:1:name"]); - Assert.Equal("20", config["feature_management:feature_flags:0:conditions:client_filters:1:parameters:Percentage"]); - Assert.Equal("US", config["feature_management:feature_flags:0:conditions:client_filters:1:parameters:Region"]); - Assert.Equal("SuperUsers", config["feature_management:feature_flags:0:conditions:client_filters:2:name"]); - Assert.Equal("TimeWindow", config["feature_management:feature_flags:0:conditions:client_filters:3:name"]); - Assert.Equal("/Date(1578643200000)/", config["feature_management:feature_flags:0:conditions:client_filters:3:parameters:Start"]); - Assert.Equal("/Date(1578686400000)/", config["feature_management:feature_flags:0:conditions:client_filters:3:parameters:End"]); + Assert.Equal("Browser", config["FeatureManagement:Beta:EnabledFor:0:Name"]); + Assert.Equal("Firefox", config["FeatureManagement:Beta:EnabledFor:0:Parameters:AllowedBrowsers:0"]); + Assert.Equal("Safari", config["FeatureManagement:Beta:EnabledFor:0:Parameters:AllowedBrowsers:1"]); + Assert.Equal("RollOut", config["FeatureManagement:Beta:EnabledFor:1:Name"]); + Assert.Equal("20", config["FeatureManagement:Beta:EnabledFor:1:Parameters:Percentage"]); + Assert.Equal("US", config["FeatureManagement:Beta:EnabledFor:1:Parameters:Region"]); + Assert.Equal("SuperUsers", config["FeatureManagement:Beta:EnabledFor:2:Name"]); + Assert.Equal("TimeWindow", config["FeatureManagement:Beta:EnabledFor:3:Name"]); + Assert.Equal("/Date(1578643200000)/", config["FeatureManagement:Beta:EnabledFor:3:Parameters:Start"]); + Assert.Equal("/Date(1578686400000)/", config["FeatureManagement:Beta:EnabledFor:3:Parameters:End"]); featureFlags[0] = ConfigurationModelFactory.ConfigurationSetting( key: FeatureManagementConstants.FeatureFlagMarker + "myFeature", @@ -870,12 +858,10 @@ public async Task SkipRefreshIfRefreshIntervalHasNotElapsed() await refresher.RefreshAsync(); - Assert.Equal("Beta", config["feature_management:feature_flags:0:id"]); - Assert.Equal("True", config["feature_management:feature_flags:0:enabled"]); - Assert.Equal("Browser", config["feature_management:feature_flags:0:conditions:client_filters:0:name"]); - Assert.Equal("Firefox", config["feature_management:feature_flags:0:conditions:client_filters:0:parameters:AllowedBrowsers:0"]); - Assert.Equal("Safari", config["feature_management:feature_flags:0:conditions:client_filters:0:parameters:AllowedBrowsers:1"]); - Assert.Null(config["feature_management:feature_flags:1:conditions:client_filters:0:name"]); + Assert.Equal("Browser", config["FeatureManagement:Beta:EnabledFor:0:Name"]); + Assert.Equal("Firefox", config["FeatureManagement:Beta:EnabledFor:0:Parameters:AllowedBrowsers:0"]); + Assert.Equal("Safari", config["FeatureManagement:Beta:EnabledFor:0:Parameters:AllowedBrowsers:1"]); + Assert.Null(config["FeatureManagement:MyFeature2:EnabledFor:0:Name"]); } [Fact] @@ -900,18 +886,16 @@ public async Task SkipRefreshIfCacheNotExpired() }) .Build(); - Assert.Equal("Beta", config["feature_management:feature_flags:0:id"]); - Assert.Equal("True", config["feature_management:feature_flags:0:enabled"]); - Assert.Equal("Browser", config["feature_management:feature_flags:0:conditions:client_filters:0:name"]); - Assert.Equal("Firefox", config["feature_management:feature_flags:0:conditions:client_filters:0:parameters:AllowedBrowsers:0"]); - Assert.Equal("Safari", config["feature_management:feature_flags:0:conditions:client_filters:0:parameters:AllowedBrowsers:1"]); - Assert.Equal("RollOut", config["feature_management:feature_flags:0:conditions:client_filters:1:name"]); - Assert.Equal("20", config["feature_management:feature_flags:0:conditions:client_filters:1:parameters:Percentage"]); - Assert.Equal("US", config["feature_management:feature_flags:0:conditions:client_filters:1:parameters:Region"]); - Assert.Equal("SuperUsers", config["feature_management:feature_flags:0:conditions:client_filters:2:name"]); - Assert.Equal("TimeWindow", config["feature_management:feature_flags:0:conditions:client_filters:3:name"]); - Assert.Equal("/Date(1578643200000)/", config["feature_management:feature_flags:0:conditions:client_filters:3:parameters:Start"]); - Assert.Equal("/Date(1578686400000)/", config["feature_management:feature_flags:0:conditions:client_filters:3:parameters:End"]); + Assert.Equal("Browser", config["FeatureManagement:Beta:EnabledFor:0:Name"]); + Assert.Equal("Firefox", config["FeatureManagement:Beta:EnabledFor:0:Parameters:AllowedBrowsers:0"]); + Assert.Equal("Safari", config["FeatureManagement:Beta:EnabledFor:0:Parameters:AllowedBrowsers:1"]); + Assert.Equal("RollOut", config["FeatureManagement:Beta:EnabledFor:1:Name"]); + Assert.Equal("20", config["FeatureManagement:Beta:EnabledFor:1:Parameters:Percentage"]); + Assert.Equal("US", config["FeatureManagement:Beta:EnabledFor:1:Parameters:Region"]); + Assert.Equal("SuperUsers", config["FeatureManagement:Beta:EnabledFor:2:Name"]); + Assert.Equal("TimeWindow", config["FeatureManagement:Beta:EnabledFor:3:Name"]); + Assert.Equal("/Date(1578643200000)/", config["FeatureManagement:Beta:EnabledFor:3:Parameters:Start"]); + Assert.Equal("/Date(1578686400000)/", config["FeatureManagement:Beta:EnabledFor:3:Parameters:End"]); featureFlags[0] = ConfigurationModelFactory.ConfigurationSetting( key: FeatureManagementConstants.FeatureFlagMarker + "myFeature", @@ -941,12 +925,10 @@ public async Task SkipRefreshIfCacheNotExpired() await refresher.RefreshAsync(); - Assert.Equal("Beta", config["feature_management:feature_flags:0:id"]); - Assert.Equal("True", config["feature_management:feature_flags:0:enabled"]); - Assert.Equal("Browser", config["feature_management:feature_flags:0:conditions:client_filters:0:name"]); - Assert.Equal("Firefox", config["feature_management:feature_flags:0:conditions:client_filters:0:parameters:AllowedBrowsers:0"]); - Assert.Equal("Safari", config["feature_management:feature_flags:0:conditions:client_filters:0:parameters:AllowedBrowsers:1"]); - Assert.Null(config["feature_management:feature_flags:1:conditions:client_filters:0:name"]); + Assert.Equal("Browser", config["FeatureManagement:Beta:EnabledFor:0:Name"]); + Assert.Equal("Firefox", config["FeatureManagement:Beta:EnabledFor:0:Parameters:AllowedBrowsers:0"]); + Assert.Equal("Safari", config["FeatureManagement:Beta:EnabledFor:0:Parameters:AllowedBrowsers:1"]); + Assert.Null(config["FeatureManagement:MyFeature2:EnabledFor:0:Name"]); } [Fact] @@ -1055,15 +1037,15 @@ public void SelectFeatureFlags() }) .Build(); - Assert.Equal("True", config["feature_management:feature_flags:0:enabled"]); - Assert.Equal("False", config["feature_management:feature_flags:1:enabled"]); + Assert.Equal("True", config["FeatureManagement:App1_Feature1"]); + Assert.Equal("False", config["FeatureManagement:App1_Feature2"]); // Verify that the feature flag that did not start with the specified prefix was not loaded - Assert.Null(config["feature_management:feature_flags:2"]); + Assert.Null(config["FeatureManagement:Feature1"]); // Verify that the feature flag that did not match the specified label was not loaded - Assert.Null(config["feature_management:feature_flags:3"]); - Assert.Null(config["feature_management:feature_flags:4"]); + Assert.Null(config["FeatureManagement:App2_Feature1"]); + Assert.Null(config["FeatureManagement:App2_Feature2"]); } [Fact] @@ -1091,13 +1073,13 @@ public void TestNullAndMissingValuesForConditions() }) .Build(); - Assert.Equal("Filter", config["feature_management:feature_flags:0:conditions:client_filters:0:name"]); - Assert.Null(config["feature_management:feature_flags:0:conditions:client_filters:0:parameters"]); - Assert.Null(config["feature_management:feature_flags:1:conditions"]); - Assert.Null(config["feature_management:feature_flags:2:conditions"]); - Assert.Null(config["feature_management:feature_flags:3:conditions"]); - Assert.Null(config["feature_management:feature_flags:4:conditions"]); - Assert.Null(config["feature_management:feature_flags:5:conditions"]); + Assert.Null(config["FeatureManagement:NullConditions:EnabledFor"]); + Assert.Equal("Filter", config["FeatureManagement:NullParameters:EnabledFor:0:Name"]); + Assert.Null(config["FeatureManagement:NullParameters:EnabledFor:0:Parameters"]); + Assert.Null(config["FeatureManagement:NullClientFilters:EnabledFor"]); + Assert.Null(config["FeatureManagement:NoConditions:EnabledFor"]); + Assert.Null(config["FeatureManagement:EmptyConditions:EnabledFor"]); + Assert.Null(config["FeatureManagement:EmptyClientFilter:EnabledFor"]); } [Fact] @@ -1146,29 +1128,42 @@ public void AlternateValidFeatureFlagFormats() { var mockResponse = new Mock(); var mockClient = new Mock(MockBehavior.Strict); - var refreshInterval = TimeSpan.FromSeconds(1); + var cacheExpiration = TimeSpan.FromSeconds(1); mockClient.Setup(c => c.GetConfigurationSettingsAsync(It.IsAny(), It.IsAny())) - .Returns(new MockAsyncPageable(_validFormatFeatureFlagCollection)); + .Returns((Func)GetTestKeys); + + MockAsyncPageable GetTestKeys(SettingSelector selector, CancellationToken ct) + { + var copy = new List(); + var newSetting = _validFormatFeatureFlagCollection.FirstOrDefault(s => s.Key == selector.KeyFilter); + if (newSetting != null) + copy.Add(TestHelpers.CloneSetting(newSetting)); + return new MockAsyncPageable(copy); + }; var testClient = mockClient.Object; - var config = new ConfigurationBuilder() - .AddAzureAppConfiguration(options => + foreach (ConfigurationSetting setting in _validFormatFeatureFlagCollection) { - options.ClientManager = TestHelpers.CreateMockedConfigurationClientManager(testClient); - options.UseFeatureFlags(ff => + string flagKey = setting.Key.Substring(FeatureManagementConstants.FeatureFlagMarker.Length); + + var config = new ConfigurationBuilder() + .AddAzureAppConfiguration(options => { - ff.SetRefreshInterval(refreshInterval); - ff.Select(KeyFilter.Any); - }); - }) - .Build(); + options.Select("_"); + options.ClientManager = TestHelpers.CreateMockedConfigurationClientManager(testClient); + options.UseFeatureFlags(ff => + { + ff.CacheExpirationInterval = cacheExpiration; + ff.Select(flagKey); + }); + }) + .Build(); - // None of the feature flags should throw an exception, and the flag should be loaded like normal - Assert.Equal("True", config[$"feature_management:feature_flags:0:enabled"]); - Assert.Equal("True", config[$"feature_management:feature_flags:1:enabled"]); - Assert.Equal("True", config[$"feature_management:feature_flags:2:enabled"]); + // None of the feature flags should throw an exception, and the flag should be loaded like normal + Assert.Equal("True", config[$"FeatureManagement:{flagKey}"]); + } } [Fact] @@ -1203,17 +1198,13 @@ public void MultipleSelectsInSameUseFeatureFlags() }) .Build(); - Assert.Equal("True", config["feature_management:feature_flags:0:enabled"]); - Assert.Equal("App1_Feature1", config["feature_management:feature_flags:0:id"]); - Assert.Equal("False", config["feature_management:feature_flags:1:enabled"]); - Assert.Equal("App1_Feature2", config["feature_management:feature_flags:1:id"]); - Assert.Equal("False", config["feature_management:feature_flags:2:enabled"]); - Assert.Equal("App2_Feature1", config["feature_management:feature_flags:2:id"]); - Assert.Equal("True", config["feature_management:feature_flags:3:enabled"]); - Assert.Equal("App2_Feature2", config["feature_management:feature_flags:3:id"]); + Assert.Equal("True", config["FeatureManagement:App1_Feature1"]); + Assert.Equal("False", config["FeatureManagement:App1_Feature2"]); + Assert.Equal("False", config["FeatureManagement:App2_Feature1"]); + Assert.Equal("True", config["FeatureManagement:App2_Feature2"]); // Verify that the feature flag that did not start with the specified prefix was not loaded - Assert.Null(config["feature_management:feature_flags:4"]); + Assert.Null(config["FeatureManagement:Feature1"]); } [Fact] @@ -1248,8 +1239,7 @@ public void KeepSelectorPrecedenceAfterDedup() }) .Build(); // label: App1_Label has higher precedence - Assert.Equal("Feature1", config["feature_management:feature_flags:0:id"]); - Assert.Equal("True", config["feature_management:feature_flags:0:enabled"]); + Assert.Equal("True", config["FeatureManagement:Feature1"]); } [Fact] @@ -1321,17 +1311,13 @@ public void MultipleCallsToUseFeatureFlags() }) .Build(); - Assert.Equal("True", config["feature_management:feature_flags:0:enabled"]); - Assert.Equal("App1_Feature1", config["feature_management:feature_flags:0:id"]); - Assert.Equal("False", config["feature_management:feature_flags:1:enabled"]); - Assert.Equal("App1_Feature2", config["feature_management:feature_flags:1:id"]); - Assert.Equal("False", config["feature_management:feature_flags:2:enabled"]); - Assert.Equal("App2_Feature1", config["feature_management:feature_flags:2:id"]); - Assert.Equal("True", config["feature_management:feature_flags:3:enabled"]); - Assert.Equal("App2_Feature2", config["feature_management:feature_flags:3:id"]); + Assert.Equal("True", config["FeatureManagement:App1_Feature1"]); + Assert.Equal("False", config["FeatureManagement:App1_Feature2"]); + Assert.Equal("False", config["FeatureManagement:App2_Feature1"]); + Assert.Equal("True", config["FeatureManagement:App2_Feature2"]); - // Verify that the feature flag Feature1 did not start with the specified prefix was not loaded - Assert.Null(config["feature_management:feature_flags:4"]); + // Verify that the feature flag that did not start with the specified prefix was not loaded + Assert.Null(config["FeatureManagement:Feature1"]); } [Fact] @@ -1369,18 +1355,13 @@ public void MultipleCallsToUseFeatureFlagsWithSelectAndLabel() .Build(); // Loaded from prefix1 and label1 - Assert.Equal("True", config["feature_management:feature_flags:0:enabled"]); - Assert.Equal("App1_Feature1", config["feature_management:feature_flags:0:id"]); - Assert.Equal("False", config["feature_management:feature_flags:1:enabled"]); - Assert.Equal("App1_Feature2", config["feature_management:feature_flags:1:id"]); + Assert.Equal("True", config["FeatureManagement:App1_Feature1"]); + Assert.Equal("False", config["FeatureManagement:App1_Feature2"]); // Loaded from label2 - Assert.Equal("False", config["feature_management:feature_flags:2:enabled"]); - Assert.Equal("App2_Feature1", config["feature_management:feature_flags:2:id"]); - Assert.Equal("True", config["feature_management:feature_flags:3:enabled"]); - Assert.Equal("App2_Feature2", config["feature_management:feature_flags:3:id"]); - Assert.Equal("True", config["feature_management:feature_flags:4:enabled"]); - Assert.Equal("Feature1", config["feature_management:feature_flags:4:id"]); + Assert.Equal("False", config["FeatureManagement:App2_Feature1"]); + Assert.Equal("True", config["FeatureManagement:App2_Feature2"]); + Assert.Equal("True", config["FeatureManagement:Feature1"]); } [Fact] @@ -1424,14 +1405,10 @@ public async Task DifferentCacheExpirationsForMultipleFeatureFlagRegistrations() }) .Build(); - Assert.Equal("True", config["feature_management:feature_flags:0:enabled"]); - Assert.Equal("App1_Feature1", config["feature_management:feature_flags:0:id"]); - Assert.Equal("False", config["feature_management:feature_flags:1:enabled"]); - Assert.Equal("App1_Feature2", config["feature_management:feature_flags:1:id"]); - Assert.Equal("False", config["feature_management:feature_flags:2:enabled"]); - Assert.Equal("App2_Feature1", config["feature_management:feature_flags:2:id"]); - Assert.Equal("True", config["feature_management:feature_flags:3:enabled"]); - Assert.Equal("App2_Feature2", config["feature_management:feature_flags:3:id"]); + Assert.Equal("True", config["FeatureManagement:App1_Feature1"]); + Assert.Equal("False", config["FeatureManagement:App1_Feature2"]); + Assert.Equal("False", config["FeatureManagement:App2_Feature1"]); + Assert.Equal("True", config["FeatureManagement:App2_Feature2"]); // update the value of App1_Feature1 feature flag with label1 featureFlagCollection[0] = ConfigurationModelFactory.ConfigurationSetting( @@ -1476,22 +1453,19 @@ public async Task DifferentCacheExpirationsForMultipleFeatureFlagRegistrations() Thread.Sleep(refreshInterval1); await refresher.RefreshAsync(); - Assert.Equal("Browser", config["feature_management:feature_flags:0:conditions:client_filters:0:name"]); - Assert.Equal("Chrome", config["feature_management:feature_flags:0:conditions:client_filters:0:parameters:AllowedBrowsers:0"]); - Assert.Equal("Edge", config["feature_management:feature_flags:0:conditions:client_filters:0:parameters:AllowedBrowsers:1"]); - Assert.Equal("False", config["feature_management:feature_flags:1:enabled"]); - Assert.Equal("App1_Feature2", config["feature_management:feature_flags:1:id"]); - Assert.Equal("False", config["feature_management:feature_flags:2:enabled"]); - Assert.Equal("App2_Feature1", config["feature_management:feature_flags:2:id"]); - Assert.Equal("True", config["feature_management:feature_flags:3:enabled"]); - Assert.Equal("App2_Feature2", config["feature_management:feature_flags:3:id"]); + Assert.Equal("Browser", config["FeatureManagement:App1_Feature1:EnabledFor:0:Name"]); + Assert.Equal("Chrome", config["FeatureManagement:App1_Feature1:EnabledFor:0:Parameters:AllowedBrowsers:0"]); + Assert.Equal("Edge", config["FeatureManagement:App1_Feature1:EnabledFor:0:Parameters:AllowedBrowsers:1"]); + Assert.Equal("False", config["FeatureManagement:App1_Feature2"]); + Assert.Equal("False", config["FeatureManagement:App2_Feature1"]); + Assert.Equal("True", config["FeatureManagement:App2_Feature2"]); - // even though App2_Feature3 feature flag has been added, its value should not be loaded in config because label2 refresh interval has not elapsed - Assert.Null(config["feature_management:feature_flags:4"]); + // even though App2_Feature3 feature flag has been added, its value should not be loaded in config because label2 cache has not expired + Assert.Null(config["FeatureManagement:App2_Feature3"]); } [Fact] - public async Task OverwrittenCacheExpirationForSameFeatureFlagRegistrations() + public async Task OverwrittenRefreshIntervalForSameFeatureFlagRegistrations() { var mockResponse = new Mock(); var mockClient = new Mock(MockBehavior.Strict); @@ -1524,16 +1498,11 @@ public async Task OverwrittenCacheExpirationForSameFeatureFlagRegistrations() }) .Build(); - Assert.Equal("True", config["feature_management:feature_flags:0:enabled"]); - Assert.Equal("App1_Feature1", config["feature_management:feature_flags:0:id"]); - Assert.Equal("False", config["feature_management:feature_flags:1:enabled"]); - Assert.Equal("App1_Feature2", config["feature_management:feature_flags:1:id"]); - Assert.Equal("True", config["feature_management:feature_flags:2:enabled"]); - Assert.Equal("Feature1", config["feature_management:feature_flags:2:id"]); - Assert.Equal("False", config["feature_management:feature_flags:3:enabled"]); - Assert.Equal("App2_Feature1", config["feature_management:feature_flags:3:id"]); - Assert.Equal("True", config["feature_management:feature_flags:4:enabled"]); - Assert.Equal("App2_Feature2", config["feature_management:feature_flags:4:id"]); + Assert.Equal("True", config["FeatureManagement:App1_Feature1"]); + Assert.Equal("False", config["FeatureManagement:App1_Feature2"]); + Assert.Equal("False", config["FeatureManagement:App2_Feature1"]); + Assert.Equal("True", config["FeatureManagement:App2_Feature2"]); + Assert.Equal("True", config["FeatureManagement:Feature1"]); // update the value of App1_Feature1 feature flag with label1 featureFlagCollection[0] = ConfigurationModelFactory.ConfigurationSetting( @@ -1563,16 +1532,11 @@ public async Task OverwrittenCacheExpirationForSameFeatureFlagRegistrations() // The refresh interval time for feature flags was overwritten by second call to UseFeatureFlags. // Sleeping for refreshInterval1 time should not update feature flags. - Assert.Equal("True", config["feature_management:feature_flags:0:enabled"]); - Assert.Equal("App1_Feature1", config["feature_management:feature_flags:0:id"]); - Assert.Equal("False", config["feature_management:feature_flags:1:enabled"]); - Assert.Equal("App1_Feature2", config["feature_management:feature_flags:1:id"]); - Assert.Equal("True", config["feature_management:feature_flags:2:enabled"]); - Assert.Equal("Feature1", config["feature_management:feature_flags:2:id"]); - Assert.Equal("False", config["feature_management:feature_flags:3:enabled"]); - Assert.Equal("App2_Feature1", config["feature_management:feature_flags:3:id"]); - Assert.Equal("True", config["feature_management:feature_flags:4:enabled"]); - Assert.Equal("App2_Feature2", config["feature_management:feature_flags:4:id"]); + Assert.Equal("True", config["FeatureManagement:App1_Feature1"]); + Assert.Equal("False", config["FeatureManagement:App1_Feature2"]); + Assert.Equal("False", config["FeatureManagement:App2_Feature1"]); + Assert.Equal("True", config["FeatureManagement:App2_Feature2"]); + Assert.Equal("True", config["FeatureManagement:Feature1"]); } [Fact] @@ -1606,8 +1570,7 @@ public async Task SelectAndRefreshSingleFeatureFlag() }) .Build(); - Assert.Equal("False", config["feature_management:feature_flags:0:enabled"]); - Assert.Equal("Feature1", config["feature_management:feature_flags:0:id"]); + Assert.Equal("False", config["FeatureManagement:Feature1"]); // update the value of Feature1 feature flag with App1_Label featureFlagCollection[2] = ConfigurationModelFactory.ConfigurationSetting( @@ -1636,9 +1599,9 @@ public async Task SelectAndRefreshSingleFeatureFlag() Thread.Sleep(RefreshInterval); await refresher.RefreshAsync(); - Assert.Equal("Browser", config["feature_management:feature_flags:0:conditions:client_filters:0:name"]); - Assert.Equal("Chrome", config["feature_management:feature_flags:0:conditions:client_filters:0:parameters:AllowedBrowsers:0"]); - Assert.Equal("Edge", config["feature_management:feature_flags:0:conditions:client_filters:0:parameters:AllowedBrowsers:1"]); + Assert.Equal("Browser", config["FeatureManagement:Feature1:EnabledFor:0:Name"]); + Assert.Equal("Chrome", config["FeatureManagement:Feature1:EnabledFor:0:Parameters:AllowedBrowsers:0"]); + Assert.Equal("Edge", config["FeatureManagement:Feature1:EnabledFor:0:Parameters:AllowedBrowsers:1"]); } [Fact] @@ -1678,8 +1641,7 @@ public async Task ValidateCorrectFeatureFlagLoggedIfModifiedOrRemovedDuringRefre }) .Build(); - Assert.Equal("SuperUsers", config["feature_management:feature_flags:0:conditions:client_filters:0:name"]); - Assert.Equal("MyFeature2", config["feature_management:feature_flags:0:id"]); + Assert.Equal("SuperUsers", config["FeatureManagement:MyFeature2:EnabledFor:0:Name"]); featureFlags[0] = ConfigurationModelFactory.ConfigurationSetting( key: FeatureManagementConstants.FeatureFlagMarker + "myFeature1", @@ -1704,8 +1666,7 @@ public async Task ValidateCorrectFeatureFlagLoggedIfModifiedOrRemovedDuringRefre Thread.Sleep(RefreshInterval); await refresher.TryRefreshAsync(); - Assert.Equal("AllUsers", config["feature_management:feature_flags:0:conditions:client_filters:0:name"]); - Assert.Equal("MyFeature", config["feature_management:feature_flags:0:id"]); + Assert.Equal("AllUsers", config["FeatureManagement:MyFeature:EnabledFor:0:Name"]); Assert.Contains(LogHelper.BuildFeatureFlagReadMessage("myFeature1", null, TestHelpers.PrimaryConfigStoreEndpoint.ToString().TrimEnd('/')), verboseInvocation); Assert.Contains(LogHelper.BuildFeatureFlagUpdatedMessage("myFeature1"), informationalInvocation); @@ -1713,7 +1674,7 @@ public async Task ValidateCorrectFeatureFlagLoggedIfModifiedOrRemovedDuringRefre Thread.Sleep(RefreshInterval); await refresher.TryRefreshAsync(); - Assert.Null(config["feature_management:feature_flags:0:conditions:client_filters:0:name"]); + Assert.Null(config["FeatureManagement:MyFeature:EnabledFor:0:Name"]); Assert.Contains(LogHelper.BuildFeatureFlagReadMessage("myFeature1", null, TestHelpers.PrimaryConfigStoreEndpoint.ToString().TrimEnd('/')), verboseInvocation); Assert.Contains(LogHelper.BuildFeatureFlagUpdatedMessage("myFeature1"), informationalInvocation); } @@ -1761,12 +1722,12 @@ public async Task ValidateFeatureFlagsUnchangedLogged() }) .Build(); - Assert.Equal("SuperUsers", config["feature_management:feature_flags:0:conditions:client_filters:0:name"]); + Assert.Equal("SuperUsers", config["FeatureManagement:MyFeature2:EnabledFor:0:Name"]); FirstKeyValue.Value = "newValue1"; Thread.Sleep(RefreshInterval); await refresher.TryRefreshAsync(); - Assert.Equal("SuperUsers", config["feature_management:feature_flags:0:conditions:client_filters:0:name"]); + Assert.Equal("SuperUsers", config["FeatureManagement:MyFeature2:EnabledFor:0:Name"]); Assert.Contains(LogHelper.BuildFeatureFlagsUnchangedMessage(TestHelpers.PrimaryConfigStoreEndpoint.ToString().TrimEnd('/')), verboseInvocation); } @@ -1852,8 +1813,7 @@ public async Task MapTransformFeatureFlagWithRefresh() .Build(); Assert.Equal("TestValue1", config["TestKey1"]); - Assert.Equal("NoUsers", config["feature_management:feature_flags:0:conditions:client_filters:0:name"]); - Assert.Equal("MyFeature", config["feature_management:feature_flags:0:id"]); + Assert.Equal("NoUsers", config["FeatureManagement:MyFeature:EnabledFor:0:Name"]); FirstKeyValue.Value = "newValue1"; featureFlags[0] = ConfigurationModelFactory.ConfigurationSetting( @@ -1881,7 +1841,7 @@ public async Task MapTransformFeatureFlagWithRefresh() await refresher.TryRefreshAsync(); Assert.Equal("newValue1", config["TestKey1"]); - Assert.Equal("NoUsers", config["feature_management:feature_flags:0:conditions:client_filters:0:name"]); + Assert.Equal("NoUsers", config["FeatureManagement:MyFeature:EnabledFor:0:Name"]); } [Fact] @@ -2052,13 +2012,11 @@ public void WithRequirementType() .Build(); Assert.Null(config["feature_management:feature_flags:0:requirement_type"]); - Assert.Equal("MyFeature2", config["feature_management:feature_flags:0:id"]); - Assert.Null(config["feature_management:feature_flags:1:requirement_type"]); - Assert.Equal("Feature_NoFilters", config["feature_management:feature_flags:1:id"]); - Assert.Equal("All", config["feature_management:feature_flags:2:conditions:requirement_type"]); - Assert.Equal("Feature_RequireAll", config["feature_management:feature_flags:2:id"]); - Assert.Equal("Any", config["feature_management:feature_flags:3:conditions:requirement_type"]); - Assert.Equal("Feature_RequireAny", config["feature_management:feature_flags:3:id"]); + Assert.Equal("Feature_NoFilters", config["feature_management:feature_flags:0:id"]); + Assert.Equal("All", config["feature_management:feature_flags:1:conditions:requirement_type"]); + Assert.Equal("Feature_RequireAll", config["feature_management:feature_flags:1:id"]); + Assert.Equal("Any", config["feature_management:feature_flags:2:conditions:requirement_type"]); + Assert.Equal("Feature_RequireAny", config["feature_management:feature_flags:2:id"]); } [Fact] diff --git a/tests/Tests.AzureAppConfiguration/JsonContentTypeTests.cs b/tests/Tests.AzureAppConfiguration/JsonContentTypeTests.cs index 7f36dfdb..91cb03a8 100644 --- a/tests/Tests.AzureAppConfiguration/JsonContentTypeTests.cs +++ b/tests/Tests.AzureAppConfiguration/JsonContentTypeTests.cs @@ -216,9 +216,9 @@ public void JsonContentTypeTests_DontFlattenFeatureFlagAsJsonObject() .AddAzureAppConfiguration(options => options.ClientManager = mockClientManager) .Build(); - Assert.Equal("Browser", config["feature_management:feature_flags:0:conditions:client_filters:0:name"]); - Assert.Equal("Firefox", config["feature_management:feature_flags:0:conditions:client_filters:0:parameters:AllowedBrowsers:0"]); - Assert.Equal("Safari", config["feature_management:feature_flags:0:conditions:client_filters:0:parameters:AllowedBrowsers:1"]); + Assert.Equal("Browser", config["FeatureManagement:Beta:EnabledFor:0:Name"]); + Assert.Equal("Firefox", config["FeatureManagement:Beta:EnabledFor:0:Parameters:AllowedBrowsers:0"]); + Assert.Equal("Safari", config["FeatureManagement:Beta:EnabledFor:0:Parameters:AllowedBrowsers:1"]); } [Fact] From 0a8d11b93997126bb9fa919220a1eb7fd9bc2bce Mon Sep 17 00:00:00 2001 From: AMER JUSUPOVIC Date: Tue, 25 Jun 2024 14:23:21 -0700 Subject: [PATCH 2/6] remove unused method --- .../FeatureManagement/FeatureManagementKeyValueAdapter.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs index 8d45771d..14154366 100644 --- a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs +++ b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs @@ -320,11 +320,6 @@ private List> ProcessMicrosoftSchemaFeatureFlag(Fea return keyValues; } - private bool IsMicrosoftSchema(FeatureFlag featureFlag) - { - return featureFlag.Allocation != null || featureFlag.Variants != null || featureFlag.Telemetry != null; - } - private FormatException CreateFeatureFlagFormatException(string jsonPropertyName, string settingKey, string foundJsonValueKind, string expectedJsonValueKind) { return new FormatException(string.Format( From 8e96d6df9bcd5c4269aff94256a0f50baaeedb91 Mon Sep 17 00:00:00 2001 From: AMER JUSUPOVIC Date: Mon, 8 Jul 2024 10:02:47 -0700 Subject: [PATCH 3/6] remove schema warning, PR comments --- .../AzureAppConfigurationProvider.cs | 12 +----------- .../Constants/LoggingConstants.cs | 3 --- .../FeatureManagementKeyValueAdapter.cs | 11 ++++------- .../LogHelper.cs | 6 ------ 4 files changed, 5 insertions(+), 27 deletions(-) diff --git a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureAppConfigurationProvider.cs b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureAppConfigurationProvider.cs index 354a450f..a8755ce8 100644 --- a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureAppConfigurationProvider.cs +++ b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureAppConfigurationProvider.cs @@ -1218,21 +1218,11 @@ private void EnsureFeatureManagementVersionInspected() { if (!_isFeatureManagementVersionInspected) { - const string FeatureManagementMinimumVersion = "3.2.0"; - _isFeatureManagementVersionInspected = true; if (_requestTracingEnabled && _requestTracingOptions != null) { - string featureManagementVersion = TracingUtils.GetAssemblyVersion(RequestTracingConstants.FeatureManagementAssemblyName); - - // If the version is less than 3.2.0, log the schema version warning - if (featureManagementVersion != null && Version.Parse(featureManagementVersion) < Version.Parse(FeatureManagementMinimumVersion)) - { - _logger.LogWarning(LogHelper.BuildFeatureManagementMicrosoftSchemaVersionWarningMessage()); - } - - _requestTracingOptions.FeatureManagementVersion = featureManagementVersion; + _requestTracingOptions.FeatureManagementVersion = TracingUtils.GetAssemblyVersion(RequestTracingConstants.FeatureManagementAssemblyName); _requestTracingOptions.FeatureManagementAspNetCoreVersion = TracingUtils.GetAssemblyVersion(RequestTracingConstants.FeatureManagementAspNetCoreAssemblyName); } diff --git a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/Constants/LoggingConstants.cs b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/Constants/LoggingConstants.cs index e202d79b..86576a48 100644 --- a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/Constants/LoggingConstants.cs +++ b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/Constants/LoggingConstants.cs @@ -33,8 +33,5 @@ internal class LoggingConstants public const string RefreshSkippedNoClientAvailable = "Refresh skipped because no endpoint is accessible."; public const string RefreshFailedToGetSettingsFromEndpoint = "Failed to get configuration settings from endpoint"; public const string FailingOverToEndpoint = "Failing over to endpoint"; - public const string FeatureManagementMicrosoftSchemaVersionWarning = "Your application may be using an older version of " + - "Microsoft.FeatureManagement library that isn't compatible with Microsoft.Extensions.Configuration.AzureAppConfiguration. Please update " + - "the Microsoft.FeatureManagement package to version 3.2.0 or later."; } } diff --git a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs index 14154366..cf1c22c4 100644 --- a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs +++ b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs @@ -53,11 +53,6 @@ public bool CanProcess(ConfigurationSetting setting) setting.Key.StartsWith(FeatureManagementConstants.FeatureFlagMarker); } - public void InvalidateCache(ConfigurationSetting setting = null) - { - return; - } - public bool NeedsRefresh() { return false; @@ -97,11 +92,13 @@ private List> ProcessDotnetSchemaFeatureFlag(Featur _featureFilterTracing.UpdateFeatureFilterTracing(clientFilter.Name); - keyValues.Add(new KeyValuePair($"{featureFlagPath}:{FeatureManagementConstants.DotnetSchemaEnabledFor}:{i}:Name", clientFilter.Name)); + string clientFiltersPath = $"{featureFlagPath}:{FeatureManagementConstants.DotnetSchemaEnabledFor}:{i}"; + + keyValues.Add(new KeyValuePair($"{clientFiltersPath}:Name", clientFilter.Name)); foreach (KeyValuePair kvp in new JsonFlattener().FlattenJson(clientFilter.Parameters)) { - keyValues.Add(new KeyValuePair($"{featureFlagPath}:{FeatureManagementConstants.DotnetSchemaEnabledFor}:{i}:Parameters:{kvp.Key}", kvp.Value)); + keyValues.Add(new KeyValuePair($"{clientFiltersPath}:Parameters:{kvp.Key}", kvp.Value)); } } diff --git a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/LogHelper.cs b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/LogHelper.cs index acb93fb9..11442dcb 100644 --- a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/LogHelper.cs +++ b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/LogHelper.cs @@ -88,13 +88,7 @@ public static string BuildLastEndpointFailedMessage(string endpoint) public static string BuildFallbackClientLookupFailMessage(string exceptionMessage) { return $"{LoggingConstants.FallbackClientLookupError}\n{exceptionMessage}"; - } - - public static string BuildFeatureManagementMicrosoftSchemaVersionWarningMessage() - { - return LoggingConstants.FeatureManagementMicrosoftSchemaVersionWarning; } - public static string BuildRefreshFailedDueToFormattingErrorMessage(string exceptionMessage) { return $"{LoggingConstants.RefreshFailedDueToFormattingError}\n{exceptionMessage}"; From 661aa62ccfb9efd6996d33c0aa887645f4fcfc27 Mon Sep 17 00:00:00 2001 From: AMER JUSUPOVIC Date: Mon, 8 Jul 2024 15:38:38 -0700 Subject: [PATCH 4/6] unify process feature flag approaches with id, fix schema check --- .../FeatureManagementKeyValueAdapter.cs | 76 +++++++++---------- 1 file changed, 35 insertions(+), 41 deletions(-) diff --git a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs index cf1c22c4..5c48a08e 100644 --- a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs +++ b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs @@ -18,7 +18,6 @@ internal class FeatureManagementKeyValueAdapter : IKeyValueAdapter { private FeatureFilterTracing _featureFilterTracing; private int _featureFlagIndex = 0; - private bool _isMicrosoftSchema; public FeatureManagementKeyValueAdapter(FeatureFilterTracing featureFilterTracing) { @@ -27,13 +26,12 @@ public FeatureManagementKeyValueAdapter(FeatureFilterTracing featureFilterTracin public Task>> ProcessKeyValue(ConfigurationSetting setting, Uri endpoint, Logger logger, CancellationToken cancellationToken) { - _isMicrosoftSchema = false; - FeatureFlag featureFlag = ParseFeatureFlag(setting.Key, setting.Value); var keyValues = new List>(); - if (_isMicrosoftSchema) + // Check if we need to process the feature flag using the microsoft schema + if (featureFlag.Variants != null || featureFlag.Allocation != null || featureFlag.Telemetry != null) { keyValues = ProcessMicrosoftSchemaFeatureFlag(featureFlag, setting, endpoint); } @@ -74,50 +72,52 @@ private List> ProcessDotnetSchemaFeatureFlag(Featur { var keyValues = new List>(); - if (!string.IsNullOrEmpty(featureFlag.Id)) + if (string.IsNullOrEmpty(featureFlag.Id)) { - string featureFlagPath = $"{FeatureManagementConstants.DotnetSchemaSectionName}:{featureFlag.Id}"; + return keyValues; + } + + string featureFlagPath = $"{FeatureManagementConstants.DotnetSchemaSectionName}:{featureFlag.Id}"; - if (featureFlag.Enabled) + if (featureFlag.Enabled) + { + if (featureFlag.Conditions?.ClientFilters == null || !featureFlag.Conditions.ClientFilters.Any()) { - if (featureFlag.Conditions?.ClientFilters == null || !featureFlag.Conditions.ClientFilters.Any()) - { - keyValues.Add(new KeyValuePair(featureFlagPath, true.ToString())); - } - else + keyValues.Add(new KeyValuePair(featureFlagPath, true.ToString())); + } + else + { + for (int i = 0; i < featureFlag.Conditions.ClientFilters.Count; i++) { - for (int i = 0; i < featureFlag.Conditions.ClientFilters.Count; i++) - { - ClientFilter clientFilter = featureFlag.Conditions.ClientFilters[i]; - - _featureFilterTracing.UpdateFeatureFilterTracing(clientFilter.Name); + ClientFilter clientFilter = featureFlag.Conditions.ClientFilters[i]; - string clientFiltersPath = $"{featureFlagPath}:{FeatureManagementConstants.DotnetSchemaEnabledFor}:{i}"; + _featureFilterTracing.UpdateFeatureFilterTracing(clientFilter.Name); - keyValues.Add(new KeyValuePair($"{clientFiltersPath}:Name", clientFilter.Name)); + string clientFiltersPath = $"{featureFlagPath}:{FeatureManagementConstants.DotnetSchemaEnabledFor}:{i}"; - foreach (KeyValuePair kvp in new JsonFlattener().FlattenJson(clientFilter.Parameters)) - { - keyValues.Add(new KeyValuePair($"{clientFiltersPath}:Parameters:{kvp.Key}", kvp.Value)); - } - } + keyValues.Add(new KeyValuePair($"{clientFiltersPath}:Name", clientFilter.Name)); - // - // process RequirementType only when filters are not empty - if (featureFlag.Conditions.RequirementType != null) + foreach (KeyValuePair kvp in new JsonFlattener().FlattenJson(clientFilter.Parameters)) { - keyValues.Add(new KeyValuePair( - $"{featureFlagPath}:{FeatureManagementConstants.DotnetSchemaRequirementType}", - featureFlag.Conditions.RequirementType)); + keyValues.Add(new KeyValuePair($"{clientFiltersPath}:Parameters:{kvp.Key}", kvp.Value)); } } - } - else - { - keyValues.Add(new KeyValuePair($"{featureFlagPath}", false.ToString())); + + // + // process RequirementType only when filters are not empty + if (featureFlag.Conditions.RequirementType != null) + { + keyValues.Add(new KeyValuePair( + $"{featureFlagPath}:{FeatureManagementConstants.DotnetSchemaRequirementType}", + featureFlag.Conditions.RequirementType)); + } } } - + else + { + keyValues.Add(new KeyValuePair($"{featureFlagPath}", false.ToString())); + } + return keyValues; } @@ -411,8 +411,6 @@ private FeatureFlag ParseFeatureFlag(string settingKey, string settingValue) case FeatureManagementConstants.Allocation: { - _isMicrosoftSchema = true; - if (reader.Read() && reader.TokenType == JsonTokenType.StartObject) { featureFlag.Allocation = ParseFeatureAllocation(ref reader, settingKey); @@ -431,8 +429,6 @@ private FeatureFlag ParseFeatureFlag(string settingKey, string settingValue) case FeatureManagementConstants.Variants: { - _isMicrosoftSchema = true; - if (reader.Read() && reader.TokenType == JsonTokenType.StartArray) { List variants = new List(); @@ -478,8 +474,6 @@ private FeatureFlag ParseFeatureFlag(string settingKey, string settingValue) case FeatureManagementConstants.Telemetry: { - _isMicrosoftSchema = true; - if (reader.Read() && reader.TokenType == JsonTokenType.StartObject) { featureFlag.Telemetry = ParseFeatureTelemetry(ref reader, settingKey); From a602820c4cea8ea9e972316dedeb63d629765e28 Mon Sep 17 00:00:00 2001 From: AMER JUSUPOVIC Date: Mon, 8 Jul 2024 15:43:56 -0700 Subject: [PATCH 5/6] fix tests to match changes --- tests/Tests.AzureAppConfiguration/FeatureManagementTests.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tests/Tests.AzureAppConfiguration/FeatureManagementTests.cs b/tests/Tests.AzureAppConfiguration/FeatureManagementTests.cs index 3f5696c5..8e871ef2 100644 --- a/tests/Tests.AzureAppConfiguration/FeatureManagementTests.cs +++ b/tests/Tests.AzureAppConfiguration/FeatureManagementTests.cs @@ -1923,10 +1923,7 @@ public void WithVariants() Assert.Equal("Other", config["feature_management:feature_flags:2:variants:2:configuration_value"]); Assert.Equal("NumberVariant", config["feature_management:feature_flags:2:allocation:default_when_enabled"]); - Assert.Equal("VariantsFeature4", config["feature_management:feature_flags:3:id"]); - Assert.Equal("True", config["feature_management:feature_flags:3:enabled"]); - Assert.Null(config["feature_management:feature_flags:3:variants"]); - Assert.Null(config["feature_management:feature_flags:3:allocation"]); + Assert.Equal("True", config["FeatureManagement:VariantsFeature4"]); } [Fact] From d27c7c261d495e7f6a09831db80785f80405e49b Mon Sep 17 00:00:00 2001 From: AMER JUSUPOVIC Date: Tue, 9 Jul 2024 12:24:23 -0700 Subject: [PATCH 6/6] check for empty variants when deciding schema --- .../FeatureManagement/FeatureManagementKeyValueAdapter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs index 5c48a08e..b7639bb8 100644 --- a/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs +++ b/src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureManagementKeyValueAdapter.cs @@ -31,7 +31,7 @@ public Task>> ProcessKeyValue(Configura var keyValues = new List>(); // Check if we need to process the feature flag using the microsoft schema - if (featureFlag.Variants != null || featureFlag.Allocation != null || featureFlag.Telemetry != null) + if ((featureFlag.Variants != null && featureFlag.Variants.Any()) || featureFlag.Allocation != null || featureFlag.Telemetry != null) { keyValues = ProcessMicrosoftSchemaFeatureFlag(featureFlag, setting, endpoint); }