diff --git a/Microsoft.FeatureManagement.sln b/Microsoft.FeatureManagement.sln index d4c7ce05..90205454 100644 --- a/Microsoft.FeatureManagement.sln +++ b/Microsoft.FeatureManagement.sln @@ -19,7 +19,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConsoleApp", "examples\Cons EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TargetingConsoleApp", "examples\TargetingConsoleApp\TargetingConsoleApp.csproj", "{6558C21E-CF20-4278-AA08-EB9D1DF29D66}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RazorPages", "examples\RazorPages\RazorPages.csproj", "{BA29A1BB-81D5-4EB1-AF37-6ECF64AF27E2}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RazorPages", "examples\RazorPages\RazorPages.csproj", "{BA29A1BB-81D5-4EB1-AF37-6ECF64AF27E2}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests.FeatureManagement.AspNetCore", "tests\Tests.FeatureManagement.AspNetCore\Tests.FeatureManagement.AspNetCore.csproj", "{FC0DC3E2-5646-4AEC-A7DB-2D6167BC3BB4}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -55,6 +57,10 @@ Global {BA29A1BB-81D5-4EB1-AF37-6ECF64AF27E2}.Debug|Any CPU.Build.0 = Debug|Any CPU {BA29A1BB-81D5-4EB1-AF37-6ECF64AF27E2}.Release|Any CPU.ActiveCfg = Release|Any CPU {BA29A1BB-81D5-4EB1-AF37-6ECF64AF27E2}.Release|Any CPU.Build.0 = Release|Any CPU + {FC0DC3E2-5646-4AEC-A7DB-2D6167BC3BB4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FC0DC3E2-5646-4AEC-A7DB-2D6167BC3BB4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FC0DC3E2-5646-4AEC-A7DB-2D6167BC3BB4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FC0DC3E2-5646-4AEC-A7DB-2D6167BC3BB4}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -65,6 +71,7 @@ Global {E50FB931-7A42-440E-AC47-B8DFE5E15394} = {FB5C34DF-695C-4DF9-8AED-B3EA2516EA72} {6558C21E-CF20-4278-AA08-EB9D1DF29D66} = {FB5C34DF-695C-4DF9-8AED-B3EA2516EA72} {BA29A1BB-81D5-4EB1-AF37-6ECF64AF27E2} = {FB5C34DF-695C-4DF9-8AED-B3EA2516EA72} + {FC0DC3E2-5646-4AEC-A7DB-2D6167BC3BB4} = {8ED6FFEE-4037-49A2-9709-BC519C104A90} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {84DA6C54-F140-4518-A1B4-E4CF42117FBD} diff --git a/build/install-dotnet.ps1 b/build/install-dotnet.ps1 index 56196544..11552a78 100644 --- a/build/install-dotnet.ps1 +++ b/build/install-dotnet.ps1 @@ -1,10 +1,8 @@ -# Installs .NET Core 2.1, .NET 5 and .NET 6 for CI/CD environment +# Installs .NET 6 and .NET 7 for CI/CD environment # see: https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-install-script#examples [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; -&([scriptblock]::Create((Invoke-WebRequest -UseBasicParsing 'https://dot.net/v1/dotnet-install.ps1'))) -Version 2.1.816 +&([scriptblock]::Create((Invoke-WebRequest -UseBasicParsing 'https://dot.net/v1/dotnet-install.ps1'))) -Channel 6.0 -&([scriptblock]::Create((Invoke-WebRequest -UseBasicParsing 'https://dot.net/v1/dotnet-install.ps1'))) -Version 5.0.408 - -&([scriptblock]::Create((Invoke-WebRequest -UseBasicParsing 'https://dot.net/v1/dotnet-install.ps1'))) +&([scriptblock]::Create((Invoke-WebRequest -UseBasicParsing 'https://dot.net/v1/dotnet-install.ps1'))) -Channel 7.0 \ No newline at end of file diff --git a/examples/RazorPages/RazorPages.csproj b/examples/RazorPages/RazorPages.csproj index 2e72ac40..e6046fb7 100644 --- a/examples/RazorPages/RazorPages.csproj +++ b/examples/RazorPages/RazorPages.csproj @@ -1,7 +1,7 @@  - netcoreapp3.1 + net6.0 diff --git a/src/Microsoft.FeatureManagement.AspNetCore/AspNetCoreFeatureManagementBuilderExtensions.cs b/src/Microsoft.FeatureManagement.AspNetCore/AspNetCoreFeatureManagementBuilderExtensions.cs index 1ffea01a..742b942e 100644 --- a/src/Microsoft.FeatureManagement.AspNetCore/AspNetCoreFeatureManagementBuilderExtensions.cs +++ b/src/Microsoft.FeatureManagement.AspNetCore/AspNetCoreFeatureManagementBuilderExtensions.cs @@ -3,7 +3,6 @@ // using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.Extensions.DependencyInjection; -using Microsoft.FeatureManagement.FeatureFilters; using Microsoft.FeatureManagement.Mvc; using System; using System.Collections.Generic; diff --git a/src/Microsoft.FeatureManagement.AspNetCore/Microsoft.FeatureManagement.AspNetCore.csproj b/src/Microsoft.FeatureManagement.AspNetCore/Microsoft.FeatureManagement.AspNetCore.csproj index 612eecfc..32cf3865 100644 --- a/src/Microsoft.FeatureManagement.AspNetCore/Microsoft.FeatureManagement.AspNetCore.csproj +++ b/src/Microsoft.FeatureManagement.AspNetCore/Microsoft.FeatureManagement.AspNetCore.csproj @@ -11,7 +11,7 @@ - netstandard2.0;netcoreapp3.1;net5.0;net6.0 + net6.0;net7.0 true false ..\..\build\Microsoft.FeatureManagement.snk @@ -28,17 +28,9 @@ https://aka.ms/AzureAppConfigurationPackageIcon © Microsoft Corporation. All rights reserved. - + - - - - - - - - diff --git a/src/Microsoft.FeatureManagement/Microsoft.FeatureManagement.csproj b/src/Microsoft.FeatureManagement/Microsoft.FeatureManagement.csproj index beb3ceb4..aaf8898e 100644 --- a/src/Microsoft.FeatureManagement/Microsoft.FeatureManagement.csproj +++ b/src/Microsoft.FeatureManagement/Microsoft.FeatureManagement.csproj @@ -11,7 +11,7 @@ - netstandard2.0;netcoreapp3.1;net5.0;net6.0 + netstandard2.0;netstandard2.1 true false ..\..\build\Microsoft.FeatureManagement.snk diff --git a/tests/Tests.FeatureManagement.AspNetCore/FeatureManagementAspNetCore.cs b/tests/Tests.FeatureManagement.AspNetCore/FeatureManagementAspNetCore.cs new file mode 100644 index 00000000..62919797 --- /dev/null +++ b/tests/Tests.FeatureManagement.AspNetCore/FeatureManagementAspNetCore.cs @@ -0,0 +1,187 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.FeatureManagement; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using Xunit; + +namespace Tests.FeatureManagement.AspNetCore +{ + public class FeatureManagementAspNetCore + { + [Fact] + public async Task Integrates() + { + IConfiguration config = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build(); + + TestServer testServer = new TestServer(WebHost.CreateDefaultBuilder().ConfigureServices(services => + { + services + .AddSingleton(config) + .AddFeatureManagement() + .AddFeatureFilter(); + + services.AddMvcCore(o => + { + DisableEndpointRouting(o); + o.Filters.AddForFeature(Enum.GetName(typeof(Features), Features.ConditionalFeature)); + }); + }) + .Configure(app => + { + app.UseForFeature(Enum.GetName(typeof(Features), Features.ConditionalFeature), a => a.Use(async (ctx, next) => + { + ctx.Response.Headers[nameof(RouterMiddleware)] = bool.TrueString; + + await next(); + })); + + app.UseMvc(); + })); + + IEnumerable featureFilters = testServer.Host.Services.GetRequiredService>(); + + TestFilter testFeatureFilter = (TestFilter)featureFilters.First(f => f is TestFilter); + + testFeatureFilter.Callback = _ => Task.FromResult(true); + + HttpResponseMessage res = await testServer.CreateClient().GetAsync(""); + + Assert.True(res.Headers.Contains(nameof(MvcFilter))); + Assert.True(res.Headers.Contains(nameof(RouterMiddleware))); + + testFeatureFilter.Callback = _ => Task.FromResult(false); + + res = await testServer.CreateClient().GetAsync(""); + + Assert.False(res.Headers.Contains(nameof(MvcFilter))); + Assert.False(res.Headers.Contains(nameof(RouterMiddleware))); + } + + [Fact] + public async Task GatesFeatures() + { + IConfiguration config = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build(); + + TestServer testServer = new TestServer(WebHost.CreateDefaultBuilder().ConfigureServices(services => + { + services + .AddSingleton(config) + .AddFeatureManagement() + .AddFeatureFilter(); + + services.AddMvcCore(o => DisableEndpointRouting(o)); + }) + .Configure(app => app.UseMvc())); + + IEnumerable featureFilters = testServer.Host.Services.GetRequiredService>(); + + TestFilter testFeatureFilter = (TestFilter)featureFilters.First(f => f is TestFilter); + + // + // Enable all features + testFeatureFilter.Callback = ctx => Task.FromResult(true); + + HttpResponseMessage gateAllResponse = await testServer.CreateClient().GetAsync("gateAll"); + HttpResponseMessage gateAnyResponse = await testServer.CreateClient().GetAsync("gateAny"); + + Assert.Equal(HttpStatusCode.OK, gateAllResponse.StatusCode); + Assert.Equal(HttpStatusCode.OK, gateAnyResponse.StatusCode); + + // + // Enable 1/2 features + testFeatureFilter.Callback = ctx => Task.FromResult(ctx.FeatureName == Enum.GetName(typeof(Features), Features.ConditionalFeature)); + + gateAllResponse = await testServer.CreateClient().GetAsync("gateAll"); + gateAnyResponse = await testServer.CreateClient().GetAsync("gateAny"); + + Assert.Equal(HttpStatusCode.NotFound, gateAllResponse.StatusCode); + Assert.Equal(HttpStatusCode.OK, gateAnyResponse.StatusCode); + + // + // Enable no + testFeatureFilter.Callback = ctx => Task.FromResult(false); + + gateAllResponse = await testServer.CreateClient().GetAsync("gateAll"); + gateAnyResponse = await testServer.CreateClient().GetAsync("gateAny"); + + Assert.Equal(HttpStatusCode.NotFound, gateAllResponse.StatusCode); + Assert.Equal(HttpStatusCode.NotFound, gateAnyResponse.StatusCode); + } + + [Fact] + public async Task GatesRazorPageFeatures() + { + IConfiguration config = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build(); + + TestServer testServer = new TestServer(WebHost.CreateDefaultBuilder().ConfigureServices(services => + { + services + .AddSingleton(config) + .AddFeatureManagement() + .AddFeatureFilter(); + + services.AddRazorPages(); + + services.AddMvc(o => DisableEndpointRouting(o)); + }) + .Configure(app => + { + app.UseMvc(); + })); + + IEnumerable featureFilters = testServer.Host.Services.GetRequiredService>(); + + TestFilter testFeatureFilter = (TestFilter)featureFilters.First(f => f is TestFilter); + + // + // Enable all features + testFeatureFilter.Callback = ctx => Task.FromResult(true); + + HttpResponseMessage gateAllResponse = await testServer.CreateClient().GetAsync("RazorTestAll"); + HttpResponseMessage gateAnyResponse = await testServer.CreateClient().GetAsync("RazorTestAny"); + + Assert.Equal(HttpStatusCode.OK, gateAllResponse.StatusCode); + Assert.Equal(HttpStatusCode.OK, gateAnyResponse.StatusCode); + + // + // Enable 1/2 features + testFeatureFilter.Callback = ctx => Task.FromResult(ctx.FeatureName == Enum.GetName(typeof(Features), Features.ConditionalFeature)); + + gateAllResponse = await testServer.CreateClient().GetAsync("RazorTestAll"); + gateAnyResponse = await testServer.CreateClient().GetAsync("RazorTestAny"); + + Assert.Equal(HttpStatusCode.NotFound, gateAllResponse.StatusCode); + Assert.Equal(HttpStatusCode.OK, gateAnyResponse.StatusCode); + + // + // Enable no + testFeatureFilter.Callback = ctx => Task.FromResult(false); + + gateAllResponse = await testServer.CreateClient().GetAsync("RazorTestAll"); + gateAnyResponse = await testServer.CreateClient().GetAsync("RazorTestAny"); + + Assert.Equal(HttpStatusCode.NotFound, gateAllResponse.StatusCode); + Assert.Equal(HttpStatusCode.NotFound, gateAnyResponse.StatusCode); + } + + private static void DisableEndpointRouting(MvcOptions options) + { + // + // Endpoint routing is disabled by default in .NET Core 2.1 since it didn't exist. + options.EnableEndpointRouting = false; + } + } +} diff --git a/tests/Tests.FeatureManagement.AspNetCore/Features.cs b/tests/Tests.FeatureManagement.AspNetCore/Features.cs new file mode 100644 index 00000000..5d26be98 --- /dev/null +++ b/tests/Tests.FeatureManagement.AspNetCore/Features.cs @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +namespace Tests.FeatureManagement.AspNetCore +{ + enum Features + { + ConditionalFeature, + ConditionalFeature2 + } +} diff --git a/tests/Tests.FeatureManagement/MvcFilter.cs b/tests/Tests.FeatureManagement.AspNetCore/MvcFilter.cs similarity index 91% rename from tests/Tests.FeatureManagement/MvcFilter.cs rename to tests/Tests.FeatureManagement.AspNetCore/MvcFilter.cs index 40c63131..0635a289 100644 --- a/tests/Tests.FeatureManagement/MvcFilter.cs +++ b/tests/Tests.FeatureManagement.AspNetCore/MvcFilter.cs @@ -4,7 +4,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc.Filters; -namespace Tests.FeatureManagement +namespace Tests.FeatureManagement.AspNetCore { public class MvcFilter : IAsyncActionFilter { diff --git a/tests/Tests.FeatureManagement/Pages/RazorTestAll.cshtml b/tests/Tests.FeatureManagement.AspNetCore/Pages/RazorTestAll.cshtml similarity index 100% rename from tests/Tests.FeatureManagement/Pages/RazorTestAll.cshtml rename to tests/Tests.FeatureManagement.AspNetCore/Pages/RazorTestAll.cshtml diff --git a/tests/Tests.FeatureManagement/Pages/RazorTestAll.cshtml.cs b/tests/Tests.FeatureManagement.AspNetCore/Pages/RazorTestAll.cshtml.cs similarity index 89% rename from tests/Tests.FeatureManagement/Pages/RazorTestAll.cshtml.cs rename to tests/Tests.FeatureManagement.AspNetCore/Pages/RazorTestAll.cshtml.cs index 698ee597..5867aa30 100644 --- a/tests/Tests.FeatureManagement/Pages/RazorTestAll.cshtml.cs +++ b/tests/Tests.FeatureManagement.AspNetCore/Pages/RazorTestAll.cshtml.cs @@ -5,7 +5,7 @@ using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.FeatureManagement.Mvc; -namespace Tests.FeatureManagement.Pages +namespace Tests.FeatureManagement.AspNetCore.Pages { [FeatureGate(Features.ConditionalFeature, Features.ConditionalFeature2)] public class RazorTestAllModel : PageModel diff --git a/tests/Tests.FeatureManagement/Pages/RazorTestAny.cshtml b/tests/Tests.FeatureManagement.AspNetCore/Pages/RazorTestAny.cshtml similarity index 100% rename from tests/Tests.FeatureManagement/Pages/RazorTestAny.cshtml rename to tests/Tests.FeatureManagement.AspNetCore/Pages/RazorTestAny.cshtml diff --git a/tests/Tests.FeatureManagement/Pages/RazorTestAny.cshtml.cs b/tests/Tests.FeatureManagement.AspNetCore/Pages/RazorTestAny.cshtml.cs similarity index 90% rename from tests/Tests.FeatureManagement/Pages/RazorTestAny.cshtml.cs rename to tests/Tests.FeatureManagement.AspNetCore/Pages/RazorTestAny.cshtml.cs index 90fa47cf..54a96fae 100644 --- a/tests/Tests.FeatureManagement/Pages/RazorTestAny.cshtml.cs +++ b/tests/Tests.FeatureManagement.AspNetCore/Pages/RazorTestAny.cshtml.cs @@ -6,7 +6,7 @@ using Microsoft.FeatureManagement; using Microsoft.FeatureManagement.Mvc; -namespace Tests.FeatureManagement.Pages +namespace Tests.FeatureManagement.AspNetCore.Pages { [FeatureGate(RequirementType.Any, Features.ConditionalFeature, Features.ConditionalFeature2)] public class RazorTestAnyModel : PageModel diff --git a/tests/Tests.FeatureManagement.AspNetCore/Pages/_ViewImports.cshtml b/tests/Tests.FeatureManagement.AspNetCore/Pages/_ViewImports.cshtml new file mode 100644 index 00000000..e7acf585 --- /dev/null +++ b/tests/Tests.FeatureManagement.AspNetCore/Pages/_ViewImports.cshtml @@ -0,0 +1,3 @@ +@using Tests.FeatureManagement.AspNetCore +@namespace Tests.FeatureManagement.AspNetCore.Pages +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers diff --git a/tests/Tests.FeatureManagement/TestController.cs b/tests/Tests.FeatureManagement.AspNetCore/TestController.cs similarity index 94% rename from tests/Tests.FeatureManagement/TestController.cs rename to tests/Tests.FeatureManagement.AspNetCore/TestController.cs index 5def4cf6..2f4c8ce5 100644 --- a/tests/Tests.FeatureManagement/TestController.cs +++ b/tests/Tests.FeatureManagement.AspNetCore/TestController.cs @@ -5,7 +5,7 @@ using Microsoft.FeatureManagement; using Microsoft.FeatureManagement.Mvc; -namespace Tests.FeatureManagement +namespace Tests.FeatureManagement.AspNetCore { [Route("")] public class TestController : Controller diff --git a/tests/Tests.FeatureManagement.AspNetCore/TestFilter.cs b/tests/Tests.FeatureManagement.AspNetCore/TestFilter.cs new file mode 100644 index 00000000..fbd49122 --- /dev/null +++ b/tests/Tests.FeatureManagement.AspNetCore/TestFilter.cs @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +using Microsoft.Extensions.Configuration; +using Microsoft.FeatureManagement; +using System; +using System.Threading.Tasks; + +namespace Tests.FeatureManagement.AspNetCore +{ + class TestFilter : IFeatureFilter, IFilterParametersBinder + { + public Func ParametersBinderCallback { get; set; } + + public Func> Callback { get; set; } + + public object BindParameters(IConfiguration parameters) + { + if (ParametersBinderCallback != null) + { + return ParametersBinderCallback(parameters); + } + + return parameters; + } + + public Task EvaluateAsync(FeatureFilterEvaluationContext context) + { + return Callback?.Invoke(context) ?? Task.FromResult(false); + } + } +} diff --git a/tests/Tests.FeatureManagement.AspNetCore/Tests.FeatureManagement.AspNetCore.csproj b/tests/Tests.FeatureManagement.AspNetCore/Tests.FeatureManagement.AspNetCore.csproj new file mode 100644 index 00000000..c798e1ad --- /dev/null +++ b/tests/Tests.FeatureManagement.AspNetCore/Tests.FeatureManagement.AspNetCore.csproj @@ -0,0 +1,43 @@ + + + + net6.0;net7.0 + false + 8.0 + True + ..\..\build\Microsoft.FeatureManagement.snk + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Always + + + + diff --git a/tests/Tests.FeatureManagement.AspNetCore/appsettings.json b/tests/Tests.FeatureManagement.AspNetCore/appsettings.json new file mode 100644 index 00000000..ab9e0eb7 --- /dev/null +++ b/tests/Tests.FeatureManagement.AspNetCore/appsettings.json @@ -0,0 +1,28 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Warning" + } + }, + "AllowedHosts": "*", + + "FeatureManagement": { + "ConditionalFeature": { + "EnabledFor": [ + { + "Name": "Test", + "Parameters": { + "P1": "V1" + } + } + ] + }, + "ConditionalFeature2": { + "EnabledFor": [ + { + "Name": "Test" + } + ] + } + } +} diff --git a/tests/Tests.FeatureManagement/FeatureManagement.cs b/tests/Tests.FeatureManagement/FeatureManagement.cs index 84f18b5e..439375a6 100644 --- a/tests/Tests.FeatureManagement/FeatureManagement.cs +++ b/tests/Tests.FeatureManagement/FeatureManagement.cs @@ -1,11 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. // -using Microsoft.AspNetCore; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.FeatureManagement; @@ -14,8 +9,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using System.Net; -using System.Net.Http; using System.Text; using System.Threading.Tasks; using Xunit; @@ -24,11 +17,6 @@ namespace Tests.FeatureManagement { public class FeatureManagement { - private const string OnFeature = "OnTestFeature"; - private const string OffFeature = "OffFeature"; - private const string ConditionalFeature = "ConditionalFeature"; - private const string ContextualFeature = "ContextualFeature"; - [Fact] public async Task ReadsConfiguration() { @@ -45,9 +33,9 @@ public async Task ReadsConfiguration() IFeatureManager featureManager = serviceProvider.GetRequiredService(); - Assert.True(await featureManager.IsEnabledAsync(OnFeature)); + Assert.True(await featureManager.IsEnabledAsync(Enum.GetName(typeof(Features), Features.OnTestFeature))); - Assert.False(await featureManager.IsEnabledAsync(OffFeature)); + Assert.False(await featureManager.IsEnabledAsync(Enum.GetName(typeof(Features), Features.OffTestFeature))); IEnumerable featureFilters = serviceProvider.GetRequiredService>(); @@ -63,12 +51,12 @@ public async Task ReadsConfiguration() Assert.Equal("V1", evaluationContext.Parameters["P1"]); - Assert.Equal(ConditionalFeature, evaluationContext.FeatureName); + Assert.Equal(Enum.GetName(typeof(Features), Features.ConditionalFeature), evaluationContext.FeatureName); return Task.FromResult(true); }; - await featureManager.IsEnabledAsync(ConditionalFeature); + await featureManager.IsEnabledAsync(Enum.GetName(typeof(Features), Features.ConditionalFeature)); Assert.True(called); } @@ -97,107 +85,6 @@ public async Task ReadsOnlyFeatureManagementSection() } } - [Fact] - public async Task Integrates() - { - IConfiguration config = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build(); - - TestServer testServer = new TestServer(WebHost.CreateDefaultBuilder().ConfigureServices(services => - { - services - .AddSingleton(config) - .AddFeatureManagement() - .AddFeatureFilter(); - - services.AddMvcCore(o => - { - DisableEndpointRouting(o); - o.Filters.AddForFeature(ConditionalFeature); - }); - }) - .Configure(app => - { - - app.UseForFeature(ConditionalFeature, a => a.Use(async (ctx, next) => - { - ctx.Response.Headers[nameof(RouterMiddleware)] = bool.TrueString; - - await next(); - })); - - app.UseMvc(); - })); - - IEnumerable featureFilters = testServer.Host.Services.GetRequiredService>(); - - TestFilter testFeatureFilter = (TestFilter)featureFilters.First(f => f is TestFilter); - - testFeatureFilter.Callback = _ => Task.FromResult(true); - - HttpResponseMessage res = await testServer.CreateClient().GetAsync(""); - - Assert.True(res.Headers.Contains(nameof(MvcFilter))); - Assert.True(res.Headers.Contains(nameof(RouterMiddleware))); - - testFeatureFilter.Callback = _ => Task.FromResult(false); - - res = await testServer.CreateClient().GetAsync(""); - - Assert.False(res.Headers.Contains(nameof(MvcFilter))); - Assert.False(res.Headers.Contains(nameof(RouterMiddleware))); - } - - [Fact] - public async Task GatesFeatures() - { - IConfiguration config = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build(); - - TestServer testServer = new TestServer(WebHost.CreateDefaultBuilder().ConfigureServices(services => - { - services - .AddSingleton(config) - .AddFeatureManagement() - .AddFeatureFilter(); - - services.AddMvcCore(o => DisableEndpointRouting(o)); - }) - .Configure(app => app.UseMvc())); - - IEnumerable featureFilters = testServer.Host.Services.GetRequiredService>(); - - TestFilter testFeatureFilter = (TestFilter)featureFilters.First(f => f is TestFilter); - - // - // Enable all features - testFeatureFilter.Callback = ctx => Task.FromResult(true); - - HttpResponseMessage gateAllResponse = await testServer.CreateClient().GetAsync("gateAll"); - HttpResponseMessage gateAnyResponse = await testServer.CreateClient().GetAsync("gateAny"); - - Assert.Equal(HttpStatusCode.OK, gateAllResponse.StatusCode); - Assert.Equal(HttpStatusCode.OK, gateAnyResponse.StatusCode); - - // - // Enable 1/2 features - testFeatureFilter.Callback = ctx => Task.FromResult(ctx.FeatureName == Enum.GetName(typeof(Features), Features.ConditionalFeature)); - - gateAllResponse = await testServer.CreateClient().GetAsync("gateAll"); - gateAnyResponse = await testServer.CreateClient().GetAsync("gateAny"); - - Assert.Equal(HttpStatusCode.NotFound, gateAllResponse.StatusCode); - Assert.Equal(HttpStatusCode.OK, gateAnyResponse.StatusCode); - - // - // Enable no - testFeatureFilter.Callback = ctx => Task.FromResult(false); - - gateAllResponse = await testServer.CreateClient().GetAsync("gateAll"); - gateAnyResponse = await testServer.CreateClient().GetAsync("gateAny"); - - Assert.Equal(HttpStatusCode.NotFound, gateAllResponse.StatusCode); - Assert.Equal(HttpStatusCode.NotFound, gateAnyResponse.StatusCode); - } - [Fact] public async Task CustomFilterContextualTargetingWithNullSetting() { @@ -220,60 +107,6 @@ public async Task CustomFilterContextualTargetingWithNullSetting() Assert.True(await featureManager.IsEnabledAsync("CustomFilterFeature")); } - [Fact] - public async Task GatesRazorPageFeatures() - { - IConfiguration config = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build(); - - TestServer testServer = new TestServer(WebHost.CreateDefaultBuilder().ConfigureServices(services => - { - services - .AddSingleton(config) - .AddFeatureManagement() - .AddFeatureFilter(); - - services.AddMvc(o => DisableEndpointRouting(o)); - }) - .Configure(app => - { - app.UseMvc(); - })); - - IEnumerable featureFilters = testServer.Host.Services.GetRequiredService>(); - - TestFilter testFeatureFilter = (TestFilter)featureFilters.First(f => f is TestFilter); - - // - // Enable all features - testFeatureFilter.Callback = ctx => Task.FromResult(true); - - HttpResponseMessage gateAllResponse = await testServer.CreateClient().GetAsync("RazorTestAll"); - HttpResponseMessage gateAnyResponse = await testServer.CreateClient().GetAsync("RazorTestAny"); - - Assert.Equal(HttpStatusCode.OK, gateAllResponse.StatusCode); - Assert.Equal(HttpStatusCode.OK, gateAnyResponse.StatusCode); - - // - // Enable 1/2 features - testFeatureFilter.Callback = ctx => Task.FromResult(ctx.FeatureName == Enum.GetName(typeof(Features), Features.ConditionalFeature)); - - gateAllResponse = await testServer.CreateClient().GetAsync("RazorTestAll"); - gateAnyResponse = await testServer.CreateClient().GetAsync("RazorTestAny"); - - Assert.Equal(HttpStatusCode.NotFound, gateAllResponse.StatusCode); - Assert.Equal(HttpStatusCode.OK, gateAnyResponse.StatusCode); - - // - // Enable no - testFeatureFilter.Callback = ctx => Task.FromResult(false); - - gateAllResponse = await testServer.CreateClient().GetAsync("RazorTestAll"); - gateAnyResponse = await testServer.CreateClient().GetAsync("RazorTestAny"); - - Assert.Equal(HttpStatusCode.NotFound, gateAllResponse.StatusCode); - Assert.Equal(HttpStatusCode.NotFound, gateAnyResponse.StatusCode); - } - [Fact] public async Task TimeWindow() { @@ -484,11 +317,11 @@ public async Task UsesContext() context.AccountId = "NotEnabledAccount"; - Assert.False(await featureManager.IsEnabledAsync(ContextualFeature, context)); + Assert.False(await featureManager.IsEnabledAsync(Enum.GetName(typeof(Features), Features.ContextualFeature), context)); context.AccountId = "abc"; - Assert.True(await featureManager.IsEnabledAsync(ContextualFeature, context)); + Assert.True(await featureManager.IsEnabledAsync(Enum.GetName(typeof(Features), Features.ContextualFeature), context)); } [Fact] @@ -556,7 +389,7 @@ public async Task ThrowsExceptionForMissingFeatureFilter() IFeatureManager featureManager = serviceProvider.GetRequiredService(); - FeatureManagementException e = await Assert.ThrowsAsync(async () => await featureManager.IsEnabledAsync(ConditionalFeature)); + FeatureManagementException e = await Assert.ThrowsAsync(async () => await featureManager.IsEnabledAsync(Enum.GetName(typeof(Features), Features.ConditionalFeature))); Assert.Equal(FeatureManagementError.MissingFeatureFilter, e.Error); } @@ -582,7 +415,7 @@ public async Task SwallowsExceptionForMissingFeatureFilter() IFeatureManager featureManager = serviceProvider.GetRequiredService(); - var isEnabled = await featureManager.IsEnabledAsync(ConditionalFeature); + var isEnabled = await featureManager.IsEnabledAsync(Enum.GetName(typeof(Features), Features.ConditionalFeature)); Assert.False(isEnabled); } @@ -617,7 +450,7 @@ public async Task CustomFeatureDefinitionProvider() { FeatureDefinition testFeature = new FeatureDefinition { - Name = ConditionalFeature, + Name = Enum.GetName(typeof(Features), Features.ConditionalFeature), EnabledFor = new List() { new FeatureFilterConfiguration @@ -655,12 +488,12 @@ public async Task CustomFeatureDefinitionProvider() Assert.Equal("V1", evaluationContext.Parameters["P1"]); - Assert.Equal(ConditionalFeature, evaluationContext.FeatureName); + Assert.Equal(Enum.GetName(typeof(Features), Features.ConditionalFeature), evaluationContext.FeatureName); return Task.FromResult(true); }; - await featureManager.IsEnabledAsync(ConditionalFeature); + await featureManager.IsEnabledAsync(Enum.GetName(typeof(Features), Features.ConditionalFeature)); Assert.True(called); } @@ -702,7 +535,7 @@ public async Task ThreadsafeSnapshot() for (int i = 0; i < 1000; i++) { - tasks.Add(featureManager.IsEnabledAsync(ConditionalFeature)); + tasks.Add(featureManager.IsEnabledAsync(Enum.GetName(typeof(Features), Features.ConditionalFeature))); } Assert.True(called); @@ -870,8 +703,6 @@ public async Task RequirementTypeAllExceptions() { IConfiguration config = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build(); - string filterOneId = "1"; - var services = new ServiceCollection(); services @@ -915,7 +746,7 @@ public async Task BindsFeatureFlagSettings() { new FeatureDefinition { - Name = ConditionalFeature, + Name = Enum.GetName(typeof(Features), Features.ConditionalFeature), EnabledFor = new List() { testFilterConfiguration @@ -956,7 +787,7 @@ public async Task BindsFeatureFlagSettings() return Task.FromResult(true); }; - await featureManager.IsEnabledAsync(ConditionalFeature); + await featureManager.IsEnabledAsync(Enum.GetName(typeof(Features), Features.ConditionalFeature)); Assert.True(binderCalled); @@ -966,7 +797,7 @@ public async Task BindsFeatureFlagSettings() called = false; - await featureManager.IsEnabledAsync(ConditionalFeature); + await featureManager.IsEnabledAsync(Enum.GetName(typeof(Features), Features.ConditionalFeature)); Assert.False(binderCalled); @@ -980,20 +811,11 @@ public async Task BindsFeatureFlagSettings() called = false; - await featureManager.IsEnabledAsync(ConditionalFeature); + await featureManager.IsEnabledAsync(Enum.GetName(typeof(Features), Features.ConditionalFeature)); Assert.True(binderCalled); Assert.True(called); } - - private static void DisableEndpointRouting(MvcOptions options) - { -#if NET6_0 || NET5_0 || NETCOREAPP3_1 - // - // Endpoint routing is disabled by default in .NET Core 2.1 since it didn't exist. - options.EnableEndpointRouting = false; -#endif - } } } diff --git a/tests/Tests.FeatureManagement/Features.cs b/tests/Tests.FeatureManagement/Features.cs index 3b2177c1..93847df3 100644 --- a/tests/Tests.FeatureManagement/Features.cs +++ b/tests/Tests.FeatureManagement/Features.cs @@ -11,6 +11,7 @@ enum Features OffTestFeature, ConditionalFeature, ConditionalFeature2, + ContextualFeature, AnyFilterFeature, AllFilterFeature } diff --git a/tests/Tests.FeatureManagement/Pages/_ViewImports.cshtml b/tests/Tests.FeatureManagement/Pages/_ViewImports.cshtml deleted file mode 100644 index 9999731f..00000000 --- a/tests/Tests.FeatureManagement/Pages/_ViewImports.cshtml +++ /dev/null @@ -1,3 +0,0 @@ -@using Tests.FeatureManagement -@namespace Tests.FeatureManagement.Pages -@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers diff --git a/tests/Tests.FeatureManagement/Tests.FeatureManagement.csproj b/tests/Tests.FeatureManagement/Tests.FeatureManagement.csproj index 83063e1c..07c0ad66 100644 --- a/tests/Tests.FeatureManagement/Tests.FeatureManagement.csproj +++ b/tests/Tests.FeatureManagement/Tests.FeatureManagement.csproj @@ -1,7 +1,7 @@ - + - netcoreapp2.1;netcoreapp3.1;net5.0;net6.0 + net48;net6.0;net7.0 false 8.0 True @@ -9,38 +9,35 @@ + + - - - - - - - - - - + + - - - - - + + + - - + + - - + + + + + + Always +