diff --git a/schemas/FeatureManagement.Dotnet.v1.0.0.schema.json b/schemas/FeatureManagement.Dotnet.v1.0.0.schema.json index 8ea0f613..3b879f0b 100644 --- a/schemas/FeatureManagement.Dotnet.v1.0.0.schema.json +++ b/schemas/FeatureManagement.Dotnet.v1.0.0.schema.json @@ -3,100 +3,110 @@ "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "title": "A .NET Feature Management Configuration", - "required": [], - "patternProperties": { - "^[^:]*$": { - "description": "Declares a feature flag.", - "anyOf": [ - { - "type": "boolean", - "title": "On/Off Feature Flag", - "description": "A feature flag that always returns the same value." - }, - { - "type": "object", - "title": "Conditional Feature Flag", - "description": "A feature flag which value is dynamic based on a set of feature filters", - "required": [ - "EnabledFor" - ], - "properties": { - "RequirementType": { - "type": "string", - "title": "Requirement Type", - "description": "Determines whether any or all registered feature filters must be enabled for the feature to be considered enabled.", - "enum": [ - "Any", - "All" - ], - "default": "Any" + "required": [ + "FeatureManagement" + ], + "properties":{ + "FeatureManagement": { + "type": "object", + "title": "Feature Management", + "description": "Declares feature management configuration.", + "required": [], + "patternProperties": { + "^[^:]*$": { + "description": "Declares a feature flag.", + "anyOf": [ + { + "type": "boolean", + "title": "On/Off Feature Flag", + "description": "A feature flag that always returns the same value." }, - "EnabledFor": { - "oneOf": [ - { - "type": "array", - "title": "Feature Filter Collection", - "description": "Feature filters that are evaluated to conditionally enable the flag.", - "items": { - "type": "object", - "title": "Feature Filter Declaration", - "required": [ - "Name" - ], - "properties": { - "Name": { - "type": "string", - "title": "Feature Filter Name", - "description": "The name used to refer to and require a feature filter.", - "default": "", - "examples": [ - "Percentage", - "TimeWindow" - ], - "pattern": "^[^:]*$" - }, - "Parameters": { + { + "type": "object", + "title": "Conditional Feature Flag", + "description": "A feature flag which value is dynamic based on a set of feature filters", + "required": [ + "EnabledFor" + ], + "properties": { + "RequirementType": { + "type": "string", + "title": "Requirement Type", + "description": "Determines whether any or all registered feature filters must be enabled for the feature to be considered enabled.", + "enum": [ + "Any", + "All" + ], + "default": "Any" + }, + "EnabledFor": { + "oneOf": [ + { + "type": "array", + "title": "Feature Filter Collection", + "description": "Feature filters that are evaluated to conditionally enable the flag.", + "items": { "type": "object", - "title": "Feature Filter Parameters", - "description": "Custom parameters for a given feature filter. A feature filter can require any set of parameters of any type.", - "required": [], - "patternProperties": { - "^.*$": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - }, - { - "type": "object" - }, - { - "type": "number" - }, - { - "type": "array" - }, - { - "type": "boolean" + "title": "Feature Filter Declaration", + "required": [ + "Name" + ], + "properties": { + "Name": { + "type": "string", + "title": "Feature Filter Name", + "description": "The name used to refer to and require a feature filter.", + "default": "", + "examples": [ + "Percentage", + "TimeWindow" + ], + "pattern": "^[^:]*$" + }, + "Parameters": { + "type": "object", + "title": "Feature Filter Parameters", + "description": "Custom parameters for a given feature filter. A feature filter can require any set of parameters of any type.", + "required": [], + "patternProperties": { + "^.*$": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + }, + { + "type": "object" + }, + { + "type": "number" + }, + { + "type": "array" + }, + { + "type": "boolean" + } + ] } - ] + } } } } + }, + { + "type": "boolean" } - } + ] }, - { - "type": "boolean" - } - ] - }, - "additionalProperties": false - } + "additionalProperties": false + } + } + ] } - ] + } } } -} \ No newline at end of file +} diff --git a/src/Microsoft.FeatureManagement/ConfigurationFeatureDefinitionProvider.cs b/src/Microsoft.FeatureManagement/ConfigurationFeatureDefinitionProvider.cs index de180158..c86113e0 100644 --- a/src/Microsoft.FeatureManagement/ConfigurationFeatureDefinitionProvider.cs +++ b/src/Microsoft.FeatureManagement/ConfigurationFeatureDefinitionProvider.cs @@ -26,9 +26,7 @@ public sealed class ConfigurationFeatureDefinitionProvider : IFeatureDefinitionP private readonly ConcurrentDictionary _definitions; private IDisposable _changeSubscription; private int _stale = 0; - private long _initialized = 0; - private bool _microsoftFeatureFlagSchemaEnabled; - private readonly object _lock = new object(); + private readonly bool _microsoftFeatureManagementSchemaEnabled; /// /// Creates a configuration feature definition provider. @@ -42,6 +40,19 @@ public ConfigurationFeatureDefinitionProvider(IConfiguration configuration) _changeSubscription = ChangeToken.OnChange( () => _configuration.GetReloadToken(), () => _stale = 1); + + IConfiguration MicrosoftFeatureManagementConfigurationSection = _configuration + .GetChildren() + .FirstOrDefault(section => + string.Equals( + section.Key, + MicrosoftFeatureManagementFields.FeatureManagementSectionName, + StringComparison.OrdinalIgnoreCase)); + + if (MicrosoftFeatureManagementConfigurationSection != null) + { + _microsoftFeatureManagementSchemaEnabled = true; + } } /// @@ -81,8 +92,6 @@ public Task GetFeatureDefinitionAsync(string featureName) throw new ArgumentException($"The value '{ConfigurationPath.KeyDelimiter}' is not allowed in the feature name.", nameof(featureName)); } - EnsureInit(); - if (Interlocked.Exchange(ref _stale, 0) != 0) { _definitions.Clear(); @@ -106,8 +115,6 @@ public Task GetFeatureDefinitionAsync(string featureName) public async IAsyncEnumerable GetAllFeatureDefinitionsAsync() #pragma warning restore CS1998 { - EnsureInit(); - if (Interlocked.Exchange(ref _stale, 0) != 0) { _definitions.Clear(); @@ -130,38 +137,6 @@ public async IAsyncEnumerable GetAllFeatureDefinitionsAsync() } } - private void EnsureInit() - { - if (_initialized == 0) - { - IConfiguration featureManagementConfigurationSection = _configuration - .GetChildren() - .FirstOrDefault(section => - string.Equals( - section.Key, - ConfigurationFields.FeatureManagementSectionName, - StringComparison.OrdinalIgnoreCase)); - - if (featureManagementConfigurationSection == null && RootConfigurationFallbackEnabled) - { - featureManagementConfigurationSection = _configuration; - } - - bool hasMicrosoftFeatureFlagSchema = featureManagementConfigurationSection != null && - HasMicrosoftFeatureFlagSchema(featureManagementConfigurationSection); - - lock (_lock) - { - if (Interlocked.Read(ref _initialized) == 0) - { - _microsoftFeatureFlagSchemaEnabled = hasMicrosoftFeatureFlagSchema; - - Interlocked.Exchange(ref _initialized, 1); - } - } - } - } - private FeatureDefinition ReadFeatureDefinition(string featureName) { IConfigurationSection configuration = GetFeatureDefinitionSections() @@ -177,7 +152,7 @@ private FeatureDefinition ReadFeatureDefinition(string featureName) private FeatureDefinition ReadFeatureDefinition(IConfigurationSection configurationSection) { - if (_microsoftFeatureFlagSchemaEnabled) + if (_microsoftFeatureManagementSchemaEnabled) { return ParseMicrosoftFeatureDefinition(configurationSection); } @@ -313,20 +288,9 @@ private FeatureDefinition ParseMicrosoftFeatureDefinition(IConfigurationSection RequirementType requirementType = RequirementType.Any; - IConfigurationSection conditions = configurationSection.GetSection(MicrosoftFeatureFlagFields.Conditions); + IConfigurationSection conditions = configurationSection.GetSection(MicrosoftFeatureManagementFields.Conditions); - string rawRequirementType = conditions[MicrosoftFeatureFlagFields.RequirementType]; - - // - // If requirement type is specified, parse it and set the requirementType variable - if (!string.IsNullOrEmpty(rawRequirementType) && !Enum.TryParse(rawRequirementType, ignoreCase: true, out requirementType)) - { - throw new FeatureManagementException( - FeatureManagementError.InvalidConfigurationSetting, - $"Invalid value '{rawRequirementType}' for field '{MicrosoftFeatureFlagFields.RequirementType}' of feature '{featureName}'."); - } - - string rawEnabled = configurationSection[MicrosoftFeatureFlagFields.Enabled]; + string rawEnabled = configurationSection[MicrosoftFeatureManagementFields.Enabled]; bool enabled = false; @@ -334,12 +298,23 @@ private FeatureDefinition ParseMicrosoftFeatureDefinition(IConfigurationSection { throw new FeatureManagementException( FeatureManagementError.InvalidConfigurationSetting, - $"Invalid value '{rawEnabled}' for field '{MicrosoftFeatureFlagFields.Enabled}' of feature '{featureName}'."); + $"Invalid value '{rawEnabled}' for field '{MicrosoftFeatureManagementFields.Enabled}' of feature '{featureName}'."); } if (enabled) { - IEnumerable filterSections = conditions.GetSection(MicrosoftFeatureFlagFields.ClientFilters).GetChildren(); + string rawRequirementType = conditions[MicrosoftFeatureManagementFields.RequirementType]; + + // + // If requirement type is specified, parse it and set the requirementType variable + if (!string.IsNullOrEmpty(rawRequirementType) && !Enum.TryParse(rawRequirementType, ignoreCase: true, out requirementType)) + { + throw new FeatureManagementException( + FeatureManagementError.InvalidConfigurationSetting, + $"Invalid value '{rawRequirementType}' for field '{MicrosoftFeatureManagementFields.RequirementType}' of feature '{featureName}'."); + } + + IEnumerable filterSections = conditions.GetSection(MicrosoftFeatureManagementFields.ClientFilters).GetChildren(); if (filterSections.Any()) { @@ -348,12 +323,12 @@ private FeatureDefinition ParseMicrosoftFeatureDefinition(IConfigurationSection // // Arrays in json such as "myKey": [ "some", "values" ] // Are accessed through the configuration system by using the array index as the property name, e.g. "myKey": { "0": "some", "1": "values" } - if (int.TryParse(section.Key, out int _) && !string.IsNullOrEmpty(section[MicrosoftFeatureFlagFields.Name])) + if (int.TryParse(section.Key, out int _) && !string.IsNullOrEmpty(section[MicrosoftFeatureManagementFields.Name])) { enabledFor.Add(new FeatureFilterConfiguration() { - Name = section[MicrosoftFeatureFlagFields.Name], - Parameters = new ConfigurationWrapper(section.GetSection(MicrosoftFeatureFlagFields.Parameters)) + Name = section[MicrosoftFeatureManagementFields.Name], + Parameters = new ConfigurationWrapper(section.GetSection(MicrosoftFeatureManagementFields.Parameters)) }); } } @@ -377,9 +352,9 @@ private FeatureDefinition ParseMicrosoftFeatureDefinition(IConfigurationSection private string GetFeatureName(IConfigurationSection section) { - if (_microsoftFeatureFlagSchemaEnabled) + if (_microsoftFeatureManagementSchemaEnabled) { - return section[MicrosoftFeatureFlagFields.Id]; + return section[MicrosoftFeatureManagementFields.Id]; } return section.Key; @@ -399,56 +374,33 @@ private IEnumerable GetFeatureDefinitionSections() .FirstOrDefault(section => string.Equals( section.Key, - ConfigurationFields.FeatureManagementSectionName, + _microsoftFeatureManagementSchemaEnabled ? + MicrosoftFeatureManagementFields.FeatureManagementSectionName : + ConfigurationFields.FeatureManagementSectionName, StringComparison.OrdinalIgnoreCase)); if (featureManagementConfigurationSection == null) { - if (RootConfigurationFallbackEnabled) + if (RootConfigurationFallbackEnabled && !_microsoftFeatureManagementSchemaEnabled) { featureManagementConfigurationSection = _configuration; } else { - Logger?.LogDebug($"No configuration section named '{ConfigurationFields.FeatureManagementSectionName}' was found."); + Logger?.LogDebug($"No feature management configuration section was found."); return Enumerable.Empty(); } } - if (_microsoftFeatureFlagSchemaEnabled) + if (_microsoftFeatureManagementSchemaEnabled) { - IConfigurationSection featureFlagsSection = featureManagementConfigurationSection.GetSection(MicrosoftFeatureFlagFields.FeatureFlagsSectionName); + IConfigurationSection featureFlagsSection = featureManagementConfigurationSection.GetSection(MicrosoftFeatureManagementFields.FeatureFlagsSectionName); return featureFlagsSection.GetChildren(); } return featureManagementConfigurationSection.GetChildren(); } - - private static bool HasMicrosoftFeatureFlagSchema(IConfiguration featureManagementConfiguration) - { - IConfigurationSection featureFlagsConfigurationSection = featureManagementConfiguration - .GetChildren() - .FirstOrDefault(section => - string.Equals( - section.Key, - MicrosoftFeatureFlagFields.FeatureFlagsSectionName, - StringComparison.OrdinalIgnoreCase)); - - if (featureFlagsConfigurationSection != null) - { - if (!string.IsNullOrEmpty(featureFlagsConfigurationSection.Value)) - { - return false; - } - - IEnumerable featureFlagsChildren = featureFlagsConfigurationSection.GetChildren(); - - return featureFlagsChildren.Any() && featureFlagsChildren.All(section => int.TryParse(section.Key, out int _)); - } - - return false; - } } } diff --git a/src/Microsoft.FeatureManagement/MicrosoftFeatureFlagFields.cs b/src/Microsoft.FeatureManagement/MicrosoftFeatureManagementFields.cs similarity index 58% rename from src/Microsoft.FeatureManagement/MicrosoftFeatureFlagFields.cs rename to src/Microsoft.FeatureManagement/MicrosoftFeatureManagementFields.cs index 21286e5c..50351526 100644 --- a/src/Microsoft.FeatureManagement/MicrosoftFeatureFlagFields.cs +++ b/src/Microsoft.FeatureManagement/MicrosoftFeatureManagementFields.cs @@ -5,13 +5,14 @@ namespace Microsoft.FeatureManagement { // - // Microsoft feature flag schema: https://github.com/Azure/AppConfiguration/blob/main/docs/FeatureManagement/FeatureFlag.v1.1.0.schema.json - internal static class MicrosoftFeatureFlagFields + // Microsoft Feature Management schema: https://github.com/Azure/AppConfiguration/blob/main/docs/FeatureManagement/FeatureManagement.v1.0.0.schema.json + internal static class MicrosoftFeatureManagementFields { - public const string FeatureFlagsSectionName = "FeatureFlags"; + public const string FeatureManagementSectionName = "feature_management"; + public const string FeatureFlagsSectionName = "feature_flags"; // - // Feature flag keywords + // Microsoft feature flag keywords public const string Id = "id"; public const string Enabled = "enabled"; public const string Conditions = "conditions"; diff --git a/tests/Tests.FeatureManagement/FeatureManagement.cs b/tests/Tests.FeatureManagement/FeatureManagement.cs index 8a360c89..8f965fd9 100644 --- a/tests/Tests.FeatureManagement/FeatureManagement.cs +++ b/tests/Tests.FeatureManagement/FeatureManagement.cs @@ -106,202 +106,6 @@ public async Task ReadsTopLevelConfiguration() Assert.True(await featureManager.IsEnabledAsync(feature)); } - [Fact] - public async Task ReadsMicrosoftFeatureFlagSchema() - { - string json = @" - { - ""AllowedHosts"": ""*"", - ""FeatureManagement"": { - ""MyFeature"": true, - ""FeatureFlags"": [ - { - ""id"": ""Alpha"", - ""enabled"": true, - ""conditions"": { - ""client_filters"": [] - } - }, - { - ""id"": ""Beta"", - ""enabled"": true, - ""conditions"": { - ""client_filters"": [ - { - ""name"": ""Percentage"", - ""parameters"": { - ""Value"": 100 - } - }, - { - ""name"": ""Targeting"", - ""parameters"": { - ""Audience"": { - ""Users"": [""Jeff""], - ""Groups"": [], - ""DefaultRolloutPercentage"": 0 - } - } - } - ], - ""requirement_type"" : ""all"" - } - }, - { - ""id"": ""Sigma"", - ""enabled"": false, - ""conditions"": { - ""client_filters"": [ - { - ""name"": ""Percentage"", - ""parameters"": { - ""Value"": 100 - } - } - ] - } - }, - { - ""id"": ""Omega"", - ""enabled"": true, - ""conditions"": { - ""client_filters"": [ - { - ""name"": ""Percentage"", - ""parameters"": { - ""Value"": 100 - } - }, - { - ""name"": ""Percentage"", - ""parameters"": { - ""Value"": 0 - } - } - ] - } - } - ] - } - }"; - - var stream = new MemoryStream(Encoding.UTF8.GetBytes(json)); - - IConfiguration config = new ConfigurationBuilder().AddJsonStream(stream).Build(); - - var services = new ServiceCollection(); - - services.AddSingleton(config) - .AddFeatureManagement(); - - ServiceProvider serviceProvider = services.BuildServiceProvider(); - - IFeatureManager featureManager = serviceProvider.GetRequiredService(); - - Assert.False(await featureManager.IsEnabledAsync("MyFeature")); - - Assert.True(await featureManager.IsEnabledAsync("Alpha")); - - Assert.True(await featureManager.IsEnabledAsync("Beta", new TargetingContext - { - UserId = "Jeff" - })); - - Assert.False(await featureManager.IsEnabledAsync("Beta", new TargetingContext - { - UserId = "Sam" - })); - - Assert.False(await featureManager.IsEnabledAsync("Sigma")); - - Assert.True(await featureManager.IsEnabledAsync("Omega")); - - json = @" - { - ""AllowedHosts"": ""*"", - ""FeatureManagement"": { - ""MyFeature"": true, - ""FeatureFlags"": [ - { - ""id"": ""Alpha"", - ""enabled"": true - } - ] - } - }"; - - stream = new MemoryStream(Encoding.UTF8.GetBytes(json)); - - config = new ConfigurationBuilder().AddJsonStream(stream).Build(); - - services = new ServiceCollection(); - - services.AddFeatureManagement(config.GetSection("FeatureManagement")); - - serviceProvider = services.BuildServiceProvider(); - - featureManager = serviceProvider.GetRequiredService(); - - Assert.False(await featureManager.IsEnabledAsync("MyFeature")); - - Assert.True(await featureManager.IsEnabledAsync("Alpha")); - - json = @" - { - ""AllowedHosts"": ""*"", - ""FeatureManagement"": { - ""MyFeature"": true, - ""FeatureFlags"": true - } - }"; - - stream = new MemoryStream(Encoding.UTF8.GetBytes(json)); - - config = new ConfigurationBuilder().AddJsonStream(stream).Build(); - - services = new ServiceCollection(); - - services.AddFeatureManagement(config.GetSection("FeatureManagement")); - - serviceProvider = services.BuildServiceProvider(); - - featureManager = serviceProvider.GetRequiredService(); - - Assert.True(await featureManager.IsEnabledAsync("MyFeature")); - - Assert.True(await featureManager.IsEnabledAsync("FeatureFlags")); - - json = @" - { - ""AllowedHosts"": ""*"", - ""FeatureManagement"": { - ""MyFeature"": true, - ""FeatureFlags"": { - ""EnabledFor"": [ - { - ""Name"": ""AlwaysOn"" - } - ] - } - } - }"; - - stream = new MemoryStream(Encoding.UTF8.GetBytes(json)); - - config = new ConfigurationBuilder().AddJsonStream(stream).Build(); - - services = new ServiceCollection(); - - services.AddFeatureManagement(config.GetSection("FeatureManagement")); - - serviceProvider = services.BuildServiceProvider(); - - featureManager = serviceProvider.GetRequiredService(); - - Assert.True(await featureManager.IsEnabledAsync("MyFeature")); - - Assert.True(await featureManager.IsEnabledAsync("FeatureFlags")); - } [Fact] public void AddsScopedFeatureManagement() diff --git a/tests/Tests.FeatureManagement/MicrosoftFeatureManagement.json b/tests/Tests.FeatureManagement/MicrosoftFeatureManagement.json new file mode 100644 index 00000000..b3c8db10 --- /dev/null +++ b/tests/Tests.FeatureManagement/MicrosoftFeatureManagement.json @@ -0,0 +1,77 @@ +{ + "feature_management": { + "feature_flags": [ + { + "id": "OnTestFeature", + "enabled": true + }, + { + "id": "OffTestFeature", + "enabled": false, + "conditions": { + "client_filters": [ + { + "name": "AlwaysOn" + } + ] + } + }, + { + "id": "ConditionalFeature", + "enabled": true, + "conditions": { + "client_filters": [ + { + "name": "Test", + "parameters": { + "P1": "V1" + } + } + ] + } + }, + { + "id": "AnyFilterFeature", + "enabled": true, + "conditions": { + "requirement_type": "any", + "client_filters": [ + { + "name": "Test", + "parameters": { + "Id": "1" + } + }, + { + "name": "Test", + "parameters": { + "Id": "2" + } + } + ] + } + }, + { + "id": "AllFilterFeature", + "enabled": true, + "conditions": { + "requirement_type": "all", + "client_filters": [ + { + "name": "Test", + "parameters": { + "Id": "1" + } + }, + { + "name": "Test", + "parameters": { + "Id": "2" + } + } + ] + } + } + ] + } +} diff --git a/tests/Tests.FeatureManagement/MicrosoftFeatureManagementSchema.cs b/tests/Tests.FeatureManagement/MicrosoftFeatureManagementSchema.cs new file mode 100644 index 00000000..869d3399 --- /dev/null +++ b/tests/Tests.FeatureManagement/MicrosoftFeatureManagementSchema.cs @@ -0,0 +1,109 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.FeatureManagement; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace Tests.FeatureManagement +{ + public class MicrosoftFeatureFlagSchemaTest + { + [Fact] + public async Task ReadsFeatureDefinition() + { + IConfiguration config = new ConfigurationBuilder().AddJsonFile("MicrosoftFeatureManagement.json").Build(); + + var featureDefinitionProvider = new ConfigurationFeatureDefinitionProvider(config); + + FeatureDefinition featureDefinition = await featureDefinitionProvider.GetFeatureDefinitionAsync(Features.OnTestFeature); + + Assert.NotNull(featureDefinition); + + Assert.NotEmpty(featureDefinition.EnabledFor); + + FeatureFilterConfiguration filterConfig = featureDefinition.EnabledFor.First(); + + Assert.Equal("AlwaysOn", filterConfig.Name); + + Assert.Equal(RequirementType.Any, featureDefinition.RequirementType); + + featureDefinition = await featureDefinitionProvider.GetFeatureDefinitionAsync(Features.OffTestFeature); + + Assert.NotNull(featureDefinition); + + Assert.Empty(featureDefinition.EnabledFor); + + featureDefinition = await featureDefinitionProvider.GetFeatureDefinitionAsync(Features.AnyFilterFeature); + + Assert.NotNull(featureDefinition); + + Assert.NotEmpty(featureDefinition.EnabledFor); + + Assert.Equal(RequirementType.Any, featureDefinition.RequirementType); + + featureDefinition = await featureDefinitionProvider.GetFeatureDefinitionAsync(Features.AllFilterFeature); + + Assert.NotNull(featureDefinition); + + Assert.NotEmpty(featureDefinition.EnabledFor); + + Assert.Equal(RequirementType.All, featureDefinition.RequirementType); + + featureDefinition = await featureDefinitionProvider.GetFeatureDefinitionAsync(Features.ConditionalFeature); + + Assert.NotNull(featureDefinition); + + Assert.NotEmpty(featureDefinition.EnabledFor); + + filterConfig = featureDefinition.EnabledFor.First(); + + Assert.Equal("Test", filterConfig.Name); + + Assert.Equal("V1", filterConfig.Parameters["P1"]); + } + + [Fact] + public async Task ReadsMicrosoftFeatureManagementSchemaIfAny() + { + string json = @" + { + ""AllowedHosts"": ""*"", + ""feature_management"": { + ""feature_flags"": [ + { + ""id"": ""FeatureX"", + ""enabled"": true + } + ] + }, + ""FeatureManagement"": { + ""FeatureY"": true + } + }"; + + var stream = new MemoryStream(Encoding.UTF8.GetBytes(json)); + + IConfiguration config = new ConfigurationBuilder().AddJsonStream(stream).Build(); + + var services = new ServiceCollection(); + + services.AddSingleton(config) + .AddFeatureManagement(); + + ServiceProvider serviceProvider = services.BuildServiceProvider(); + + IFeatureManager featureManager = serviceProvider.GetRequiredService(); + + Assert.True(await featureManager.IsEnabledAsync("FeatureX")); + + Assert.False(await featureManager.IsEnabledAsync("FeatureY")); + } + } +} + diff --git a/tests/Tests.FeatureManagement/Tests.FeatureManagement.csproj b/tests/Tests.FeatureManagement/Tests.FeatureManagement.csproj index 60ba9ba4..ebf99ad6 100644 --- a/tests/Tests.FeatureManagement/Tests.FeatureManagement.csproj +++ b/tests/Tests.FeatureManagement/Tests.FeatureManagement.csproj @@ -46,6 +46,9 @@ Always + + Always +