From a43c169f0906df64b7439b74cdbffe605694f214 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 1 Sep 2021 14:49:11 -0700 Subject: [PATCH] [release/6.0] Startup analyzers work for minimal apps (#35987) * wip * more * test wip * shared tests * fix main * fb Co-authored-by: Brennan <brecon@microsoft.com> --- .../src/BuildServiceProviderAnalyzer.cs | 1 - .../Analyzers/src/StartupAnalyzer.cs | 21 +- .../Analyzers/src/UseAuthorizationAnalyzer.cs | 1 - src/Analyzers/Analyzers/src/UseMvcAnalyzer.cs | 3 +- .../test/AnalyzersDiagnosticAnalyzerRunner.cs | 10 +- ...Microsoft.AspNetCore.Analyzers.Test.csproj | 1 + .../Analyzers/test/MinimalStartupTest.cs | 363 ++++++++++++++++++ .../Analyzers/test/StartupAnalyzerTest.cs | 336 +--------------- .../Analyzers/test/StartupAnalyzerTestBase.cs | 355 +++++++++++++++++ 9 files changed, 751 insertions(+), 340 deletions(-) create mode 100644 src/Analyzers/Analyzers/test/MinimalStartupTest.cs create mode 100644 src/Analyzers/Analyzers/test/StartupAnalyzerTestBase.cs diff --git a/src/Analyzers/Analyzers/src/BuildServiceProviderAnalyzer.cs b/src/Analyzers/Analyzers/src/BuildServiceProviderAnalyzer.cs index 8ea1ffe28eb..d50ad53a500 100644 --- a/src/Analyzers/Analyzers/src/BuildServiceProviderAnalyzer.cs +++ b/src/Analyzers/Analyzers/src/BuildServiceProviderAnalyzer.cs @@ -19,7 +19,6 @@ namespace Microsoft.AspNetCore.Analyzers public void AnalyzeSymbol(SymbolAnalysisContext context) { Debug.Assert(context.Symbol.Kind == SymbolKind.NamedType); - Debug.Assert(StartupFacts.IsStartupClass(_context.StartupSymbols, (INamedTypeSymbol)context.Symbol)); var type = (INamedTypeSymbol)context.Symbol; diff --git a/src/Analyzers/Analyzers/src/StartupAnalyzer.cs b/src/Analyzers/Analyzers/src/StartupAnalyzer.cs index fa857b89661..053509d22aa 100644 --- a/src/Analyzers/Analyzers/src/StartupAnalyzer.cs +++ b/src/Analyzers/Analyzers/src/StartupAnalyzer.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Immutable; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Operations; namespace Microsoft.AspNetCore.Analyzers { @@ -35,10 +36,12 @@ namespace Microsoft.AspNetCore.Analyzers return; } + var entryPoint = context.Compilation.GetEntryPoint(context.CancellationToken); + context.RegisterSymbolStartAction(context => { var type = (INamedTypeSymbol)context.Symbol; - if (!StartupFacts.IsStartupClass(symbols, type)) + if (!StartupFacts.IsStartupClass(symbols, type) && !SymbolEqualityComparer.Default.Equals(entryPoint?.ContainingType, type)) { // Not a startup class, nothing to do. return; @@ -60,18 +63,28 @@ namespace Microsoft.AspNetCore.Analyzers } var method = (IMethodSymbol)context.OwningSymbol; - if (StartupFacts.IsConfigureServices(symbols, method)) + var isConfigureServices = StartupFacts.IsConfigureServices(symbols, method); + if (isConfigureServices) { OnConfigureServicesMethodFound(method); + } + // In the future we can consider looking at more methods, but for now limit to Main, implicit Main, and Configure* methods + var isMain = SymbolEqualityComparer.Default.Equals(entryPoint, context.OwningSymbol); + + if (isConfigureServices || isMain) + { services.AnalyzeConfigureServices(context); options.AnalyzeConfigureServices(context); } - if (StartupFacts.IsConfigure(symbols, method)) + var isConfigure = StartupFacts.IsConfigure(symbols, method); + if (isConfigure) { OnConfigureMethodFound(method); - + } + if (isConfigure || isMain) + { middleware.AnalyzeConfigureMethod(context); } }); diff --git a/src/Analyzers/Analyzers/src/UseAuthorizationAnalyzer.cs b/src/Analyzers/Analyzers/src/UseAuthorizationAnalyzer.cs index 57ef1050faa..72cc214e095 100644 --- a/src/Analyzers/Analyzers/src/UseAuthorizationAnalyzer.cs +++ b/src/Analyzers/Analyzers/src/UseAuthorizationAnalyzer.cs @@ -20,7 +20,6 @@ namespace Microsoft.AspNetCore.Analyzers public void AnalyzeSymbol(SymbolAnalysisContext context) { Debug.Assert(context.Symbol.Kind == SymbolKind.NamedType); - Debug.Assert(StartupFacts.IsStartupClass(_context.StartupSymbols, (INamedTypeSymbol)context.Symbol)); var type = (INamedTypeSymbol)context.Symbol; diff --git a/src/Analyzers/Analyzers/src/UseMvcAnalyzer.cs b/src/Analyzers/Analyzers/src/UseMvcAnalyzer.cs index f4adf8a050c..02806e52285 100644 --- a/src/Analyzers/Analyzers/src/UseMvcAnalyzer.cs +++ b/src/Analyzers/Analyzers/src/UseMvcAnalyzer.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; @@ -19,7 +19,6 @@ namespace Microsoft.AspNetCore.Analyzers public void AnalyzeSymbol(SymbolAnalysisContext context) { Debug.Assert(context.Symbol.Kind == SymbolKind.NamedType); - Debug.Assert(StartupFacts.IsStartupClass(_context.StartupSymbols, (INamedTypeSymbol)context.Symbol)); var type = (INamedTypeSymbol)context.Symbol; diff --git a/src/Analyzers/Analyzers/test/AnalyzersDiagnosticAnalyzerRunner.cs b/src/Analyzers/Analyzers/test/AnalyzersDiagnosticAnalyzerRunner.cs index 7ec7449dbc8..1602bb8556a 100644 --- a/src/Analyzers/Analyzers/test/AnalyzersDiagnosticAnalyzerRunner.cs +++ b/src/Analyzers/Analyzers/test/AnalyzersDiagnosticAnalyzerRunner.cs @@ -14,9 +14,12 @@ namespace Microsoft.AspNetCore.Analyzers { internal class AnalyzersDiagnosticAnalyzerRunner : DiagnosticAnalyzerRunner { - public AnalyzersDiagnosticAnalyzerRunner(DiagnosticAnalyzer analyzer) + private readonly OutputKind _outputKind; + + public AnalyzersDiagnosticAnalyzerRunner(DiagnosticAnalyzer analyzer, OutputKind? outputKind = null) { Analyzer = analyzer; + _outputKind = outputKind ?? OutputKind.DynamicallyLinkedLibrary; } public DiagnosticAnalyzer Analyzer { get; } @@ -51,5 +54,10 @@ namespace Microsoft.AspNetCore.Analyzers { return GetDiagnosticsAsync(new[] { project }, Analyzer, Array.Empty<string>()); } + + protected override CompilationOptions ConfigureCompilationOptions(CompilationOptions options) + { + return options.WithOutputKind(_outputKind); + } } } diff --git a/src/Analyzers/Analyzers/test/Microsoft.AspNetCore.Analyzers.Test.csproj b/src/Analyzers/Analyzers/test/Microsoft.AspNetCore.Analyzers.Test.csproj index 8f85e4be8c9..bab964826b3 100644 --- a/src/Analyzers/Analyzers/test/Microsoft.AspNetCore.Analyzers.Test.csproj +++ b/src/Analyzers/Analyzers/test/Microsoft.AspNetCore.Analyzers.Test.csproj @@ -22,6 +22,7 @@ <Reference Include="Microsoft.AspNetCore.Components.Server" /> <Reference Include="Microsoft.AspNetCore.Mvc" /> <Reference Include="Microsoft.AspNetCore.SignalR" /> + <Reference Include="Microsoft.AspNetCore" /> </ItemGroup> </Project> diff --git a/src/Analyzers/Analyzers/test/MinimalStartupTest.cs b/src/Analyzers/Analyzers/test/MinimalStartupTest.cs new file mode 100644 index 00000000000..5acd376109e --- /dev/null +++ b/src/Analyzers/Analyzers/test/MinimalStartupTest.cs @@ -0,0 +1,363 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Analyzer.Testing; +using Microsoft.CodeAnalysis; + +namespace Microsoft.AspNetCore.Analyzers +{ + public class MinimalStartupTest : StartupAnalyzerTestBase + { + public MinimalStartupTest() + { + Runner = new AnalyzersDiagnosticAnalyzerRunner(StartupAnalyzer, OutputKind.ConsoleApplication); + } + + internal override bool HasConfigure => false; + + internal override AnalyzersDiagnosticAnalyzerRunner Runner { get; } + + [Fact] + public async Task StartupAnalyzer_AuthNoRouting() + { + // Arrange + var source = TestSource.Read(@"using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.AspNetCore.Authorization; +var builder = WebApplication.CreateBuilder(args); +builder.Services.AddAuthorization(); +var app = builder.Build(); +app.UseAuthorization(); +app.Run();"); + + // Act + var diagnostics = await Runner.GetDiagnosticsAsync(source.Source); + + // Assert + var middlewareAnalysis = Assert.Single(Analyses.OfType<MiddlewareAnalysis>()); + Assert.Single(middlewareAnalysis.Middleware); + Assert.Empty(diagnostics); + } + + [Fact] + public async Task StartupAnalyzer_WorksWithNonImplicitMain() + { + // Arrange + var source = TestSource.Read(@"using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +public class Program +{ + public static void Main(string[] args) + { + var builder = WebApplication.CreateBuilder(args); + builder.Services.AddMvc(); + var app = builder.Build(); + app.UseStaticFiles(); + app.UseMiddleware<AuthorizationMiddleware>(); + /*MM*/app.UseMvc(); + app.UseRouting(); + app.UseEndpoints(endpoints => + { + }); + app.Run(); + } +}"); + + // Act + var diagnostics = await Runner.GetDiagnosticsAsync(source.Source); + + // Assert + var optionsAnalysis = Assert.Single(Analyses.OfType<OptionsAnalysis>()); + Assert.False(OptionsFacts.IsEndpointRoutingExplicitlyDisabled(optionsAnalysis)); + + var middlewareAnalysis = Assert.Single(Analyses.OfType<MiddlewareAnalysis>()); + + Assert.Collection( + middlewareAnalysis.Middleware, + item => Assert.Equal("UseStaticFiles", item.UseMethod.Name), + item => Assert.Equal("UseMiddleware", item.UseMethod.Name), + item => Assert.Equal("UseMvc", item.UseMethod.Name), + item => Assert.Equal("UseRouting", item.UseMethod.Name), + item => Assert.Equal("UseEndpoints", item.UseMethod.Name)); + + Assert.Collection( + diagnostics, + diagnostic => + { + Assert.Same(StartupAnalyzer.Diagnostics.UnsupportedUseMvcWithEndpointRouting, diagnostic.Descriptor); + AnalyzerAssert.DiagnosticLocation(source.DefaultMarkerLocation, diagnostic.Location); + Assert.Contains("inside 'Main", diagnostic.GetMessage()); + }); + } + + [Fact] + public async Task StartupAnalyzer_WorksWithOtherMethodsInProgram() + { + // Arrange + var source = TestSource.Read(@"using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +public class Program +{ + public static void Main(string[] args) + { + var builder = WebApplication.CreateBuilder(args); + builder.Services.AddMvc(); + var app = builder.Build(); + app.UseStaticFiles(); + app.UseMiddleware<AuthorizationMiddleware>(); + /*MM*/app.UseMvc(); + app.UseRouting(); + app.UseEndpoints(endpoints => + { + }); + app.Run(); + } + + private static void MethodA() + { + } + + private static void MethodB() + { + } +}"); + + // Act + var diagnostics = await Runner.GetDiagnosticsAsync(source.Source); + + // Assert + var optionsAnalysis = Assert.Single(Analyses.OfType<OptionsAnalysis>()); + Assert.False(OptionsFacts.IsEndpointRoutingExplicitlyDisabled(optionsAnalysis)); + + var middlewareAnalysis = Assert.Single(Analyses.OfType<MiddlewareAnalysis>()); + + Assert.Collection( + middlewareAnalysis.Middleware, + item => Assert.Equal("UseStaticFiles", item.UseMethod.Name), + item => Assert.Equal("UseMiddleware", item.UseMethod.Name), + item => Assert.Equal("UseMvc", item.UseMethod.Name), + item => Assert.Equal("UseRouting", item.UseMethod.Name), + item => Assert.Equal("UseEndpoints", item.UseMethod.Name)); + + Assert.Collection( + diagnostics, + diagnostic => + { + Assert.Same(StartupAnalyzer.Diagnostics.UnsupportedUseMvcWithEndpointRouting, diagnostic.Descriptor); + AnalyzerAssert.DiagnosticLocation(source.DefaultMarkerLocation, diagnostic.Location); + Assert.Contains("inside 'Main", diagnostic.GetMessage()); + }); + } + + internal override TestSource GetSource(string scenario) + { + string source = null; + switch (scenario) + { + case "StartupSignatures_Standard": //passes + source = @"using Microsoft.AspNetCore.Builder; +var builder = WebApplication.CreateBuilder(args); +var app = builder.Build(); +app.MapGet(""/"", () => ""Hello World!""); +app.Run();"; + break; + case "StartupSignatures_MoreVariety": //passes + source = @"using Microsoft.AspNetCore.Builder; +var app = WebApplication.Create(args); +app.MapGet(""/"", () => ""Hello World!""); +app.Run();"; + break; + case "MvcOptions_UseMvcWithDefaultRouteAndEndpointRoutingDisabled": //passes + source = @"using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +var builder = WebApplication.CreateBuilder(args); +builder.Services.AddMvc(options => options.EnableEndpointRouting = false); +var app = builder.Build(); +app.UseMvcWithDefaultRoute(); +app.Run();"; + break; + case "MvcOptions_UseMvcWithDefaultRouteAndAddMvcOptionsEndpointRoutingDisabled": //passes + source = @"using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +var builder = WebApplication.CreateBuilder(args); +builder.Services.AddMvc().AddMvcOptions(options => options.EnableEndpointRouting = false); +var app = builder.Build(); +app.UseMvcWithDefaultRoute(); +app.Run();"; + break; + case "MvcOptions_UseMvc": //passes (fails) + source = @"using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +var builder = WebApplication.CreateBuilder(args); +builder.Services.AddMvc(); +var app = builder.Build(); +/*MM*/app.UseMvc(); +app.Run();"; + break; + case "MvcOptions_UseMvcAndConfiguredRoutes": //passes (fails) + source = @"using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +var builder = WebApplication.CreateBuilder(args); +builder.Services.AddMvc(); +var app = builder.Build(); +/*MM*/app.UseMvc(routes => +{ + routes.MapRoute(""Name"", ""Template""); +}); +app.Run();"; + break; + case "MvcOptions_UseMvcWithDefaultRoute": //passes (fails) + source = @"using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +var builder = WebApplication.CreateBuilder(args); +builder.Services.AddMvc(); +var app = builder.Build(); +/*MM*/app.UseMvcWithDefaultRoute(); +app.Run();"; + break; + case "MvcOptions_UseMvcWithOtherMiddleware": //passes (fails) + source = @"using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.AspNetCore.Authorization; +var builder = WebApplication.CreateBuilder(args); +builder.Services.AddMvc(); +var app = builder.Build(); +app.UseStaticFiles(); +app.UseMiddleware<AuthorizationMiddleware>(); +/*MM*/app.UseMvc(); +app.UseRouting(); +app.UseEndpoints(endpoints => +{ +}); +app.Run();"; + break; + case "MvcOptions_UseMvcMultiple": //passes (fails) + source = @"using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.AspNetCore.Authorization; +var builder = WebApplication.CreateBuilder(args); +builder.Services.AddMvc(); +var app = builder.Build(); +/*MM1*/app.UseMvcWithDefaultRoute(); +app.UseStaticFiles(); +app.UseMiddleware<AuthorizationMiddleware>(); +/*MM2*/app.UseMvc(); +app.UseRouting(); +app.UseEndpoints(endpoints => +{ +}); +/*MM3*/app.UseMvc(); +app.Run();"; + break; + case "UseAuthConfiguredCorrectly": //passes + source = @"using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.AspNetCore.Authorization; +var builder = WebApplication.CreateBuilder(args); +builder.Services.AddAuthorization(); +var app = builder.Build(); +app.UseRouting(); +app.UseAuthorization(); +app.UseEndpoints(r => {}); +app.Run();"; + break; + case "UseAuthConfiguredCorrectlyChained": //passes + source = @"using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.DependencyInjection; +var builder = WebApplication.CreateBuilder(args); +builder.Services.AddAuthorization(); +var app = builder.Build(); +app.UseRouting() + .UseAuthorization() + .UseEndpoints(r => {}); +app.Run();"; + break; + case "UseAuthMultipleTimes": //passes + source = @"using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.DependencyInjection; +var builder = WebApplication.CreateBuilder(args); +builder.Services.AddAuthorization(); +var app = builder.Build(); +app.UseRouting(); +app.UseAuthorization(); +app.UseAuthorization(); +app.UseEndpoints(r => {}); +app.Run();"; + break; + case "UseAuthBeforeUseRouting": //passes (fails) + source = @"using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.DependencyInjection; +var builder = WebApplication.CreateBuilder(args); +builder.Services.AddAuthorization(); +var app = builder.Build(); +app.UseFileServer(); +/*MM*/app.UseAuthorization(); +app.UseRouting(); +app.UseEndpoints(r => {}); +app.Run();"; + break; + case "UseAuthBeforeUseRoutingChained": //passes (fails) + source = @"using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.DependencyInjection; +var builder = WebApplication.CreateBuilder(args); +builder.Services.AddAuthorization(); +var app = builder.Build(); +app.UseFileServer() + .UseAuthorization() + .UseRouting() + .UseEndpoints(r => {}); +app.Run();"; + break; + case "UseAuthAfterUseEndpoints": //passes (fails) + source = @"using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.DependencyInjection; +var builder = WebApplication.CreateBuilder(args); +builder.Services.AddAuthorization(); +var app = builder.Build(); +app.UseRouting(); +app.UseEndpoints(r => { }); +/*MM*/app.UseAuthorization(); +app.Run();"; + break; + case "UseAuthFallbackPolicy": //passes + source = @"using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +var builder = WebApplication.CreateBuilder(args); +builder.Services.AddAuthorization(); +var app = builder.Build(); +app.UseAuthorization(); +app.UseStaticFiles(); +app.UseRouting(); +app.UseAuthorization(); +app.UseEndpoints(r => { }); +app.Run();"; + break; + case "ConfigureServices_BuildServiceProvider": + source = @"using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +var builder = WebApplication.CreateBuilder(args); +/*MM1*/builder.Services.BuildServiceProvider(); +var app = builder.Build(); +app.Run();"; + break; + } + + return source is not null ? TestSource.Read(source) : null; + } + } +} diff --git a/src/Analyzers/Analyzers/test/StartupAnalyzerTest.cs b/src/Analyzers/Analyzers/test/StartupAnalyzerTest.cs index bfbd89f1c9d..b0af27ceb84 100644 --- a/src/Analyzers/Analyzers/test/StartupAnalyzerTest.cs +++ b/src/Analyzers/Analyzers/test/StartupAnalyzerTest.cs @@ -1,350 +1,24 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Collections.Concurrent; -using System.Linq; -using System.Threading.Tasks; using Microsoft.AspNetCore.Analyzer.Testing; -using Microsoft.CodeAnalysis; -using Xunit; namespace Microsoft.AspNetCore.Analyzers { - public class StartupAnalyzerTest : AnalyzerTestBase + public class StartupAnalyzerTest : StartupAnalyzerTestBase { public StartupAnalyzerTest() { - StartupAnalyzer = new StartupAnalyzer(); - Runner = new AnalyzersDiagnosticAnalyzerRunner(StartupAnalyzer); - - Analyses = new ConcurrentBag<object>(); - ConfigureServicesMethods = new ConcurrentBag<IMethodSymbol>(); - ConfigureMethods = new ConcurrentBag<IMethodSymbol>(); - StartupAnalyzer.ServicesAnalysisCompleted += (sender, analysis) => Analyses.Add(analysis); - StartupAnalyzer.OptionsAnalysisCompleted += (sender, analysis) => Analyses.Add(analysis); - StartupAnalyzer.MiddlewareAnalysisCompleted += (sender, analysis) => Analyses.Add(analysis); - StartupAnalyzer.ConfigureServicesMethodFound += (sender, method) => ConfigureServicesMethods.Add(method); - StartupAnalyzer.ConfigureMethodFound += (sender, method) => ConfigureMethods.Add(method); - } - - private StartupAnalyzer StartupAnalyzer { get; } - - private AnalyzersDiagnosticAnalyzerRunner Runner { get; } - - private ConcurrentBag<object> Analyses { get; } - - private ConcurrentBag<IMethodSymbol> ConfigureServicesMethods { get; } - - private ConcurrentBag<IMethodSymbol> ConfigureMethods { get; } - - [Fact] - public async Task StartupAnalyzer_FindsStartupMethods_StartupSignatures_Standard() - { - // Arrange - var source = Read("StartupSignatures_Standard"); - - // Act - var diagnostics = await Runner.GetDiagnosticsAsync(source.Source); - - // Assert - Assert.Empty(diagnostics); - - Assert.Collection(ConfigureServicesMethods, m => Assert.Equal("ConfigureServices", m.Name)); - Assert.Collection(ConfigureMethods, m => Assert.Equal("Configure", m.Name)); - } - - [Fact] - public async Task StartupAnalyzer_FindsStartupMethods_StartupSignatures_MoreVariety() - { - // Arrange - var source = Read("StartupSignatures_MoreVariety"); - - // Act - var diagnostics = await Runner.GetDiagnosticsAsync(source.Source); - - // Assert - Assert.Empty(diagnostics); - - Assert.Collection( - ConfigureServicesMethods.OrderBy(m => m.Name), - m => Assert.Equal("ConfigureServices", m.Name)); - - Assert.Collection( - ConfigureMethods.OrderBy(m => m.Name), - m => Assert.Equal("Configure", m.Name), - m => Assert.Equal("ConfigureProduction", m.Name)); - } - - [Fact] - public async Task StartupAnalyzer_MvcOptionsAnalysis_UseMvc_FindsEndpointRoutingDisabled() - { - // Arrange - var source = Read("MvcOptions_UseMvcWithDefaultRouteAndEndpointRoutingDisabled"); - - // Act - var diagnostics = await Runner.GetDiagnosticsAsync(source.Source); - - // Assert - var optionsAnalysis = Assert.Single(Analyses.OfType<OptionsAnalysis>()); - Assert.True(OptionsFacts.IsEndpointRoutingExplicitlyDisabled(optionsAnalysis)); - - var middlewareAnalysis = Assert.Single(Analyses.OfType<MiddlewareAnalysis>()); - var middleware = Assert.Single(middlewareAnalysis.Middleware); - Assert.Equal("UseMvcWithDefaultRoute", middleware.UseMethod.Name); - - Assert.Empty(diagnostics); - } - - [Fact] - public async Task StartupAnalyzer_MvcOptionsAnalysis_AddMvcOptions_FindsEndpointRoutingDisabled() - { - // Arrange - var source = Read("MvcOptions_UseMvcWithDefaultRouteAndAddMvcOptionsEndpointRoutingDisabled"); - - // Act - var diagnostics = await Runner.GetDiagnosticsAsync(source.Source); - - // Assert - var optionsAnalysis = Assert.Single(Analyses.OfType<OptionsAnalysis>()); - Assert.True(OptionsFacts.IsEndpointRoutingExplicitlyDisabled(optionsAnalysis)); - - var middlewareAnalysis = Assert.Single(Analyses.OfType<MiddlewareAnalysis>()); - var middleware = Assert.Single(middlewareAnalysis.Middleware); - Assert.Equal("UseMvcWithDefaultRoute", middleware.UseMethod.Name); - - Assert.Empty(diagnostics); - } - - [Theory] - [InlineData("MvcOptions_UseMvc", "UseMvc")] - [InlineData("MvcOptions_UseMvcAndConfiguredRoutes", "UseMvc")] - [InlineData("MvcOptions_UseMvcWithDefaultRoute", "UseMvcWithDefaultRoute")] - public async Task StartupAnalyzer_MvcOptionsAnalysis_FindsEndpointRoutingEnabled(string sourceFileName, string mvcMiddlewareName) - { - // Arrange - var source = Read(sourceFileName); - - // Act - var diagnostics = await Runner.GetDiagnosticsAsync(source.Source); - - // Assert - var optionsAnalysis = Assert.Single(Analyses.OfType<OptionsAnalysis>()); - Assert.False(OptionsFacts.IsEndpointRoutingExplicitlyDisabled(optionsAnalysis)); - - var middlewareAnalysis = Assert.Single(Analyses.OfType<MiddlewareAnalysis>()); - var middleware = Assert.Single(middlewareAnalysis.Middleware); - Assert.Equal(mvcMiddlewareName, middleware.UseMethod.Name); - - Assert.Collection( - diagnostics, - diagnostic => - { - Assert.Same(StartupAnalyzer.Diagnostics.UnsupportedUseMvcWithEndpointRouting, diagnostic.Descriptor); - AnalyzerAssert.DiagnosticLocation(source.DefaultMarkerLocation, diagnostic.Location); - }); - } - - [Fact] - public async Task StartupAnalyzer_MvcOptionsAnalysis_MultipleMiddleware() - { - // Arrange - var source = Read("MvcOptions_UseMvcWithOtherMiddleware"); - - // Act - var diagnostics = await Runner.GetDiagnosticsAsync(source.Source); - - // Assert - var optionsAnalysis = Assert.Single(Analyses.OfType<OptionsAnalysis>()); - Assert.False(OptionsFacts.IsEndpointRoutingExplicitlyDisabled(optionsAnalysis)); - - var middlewareAnalysis = Assert.Single(Analyses.OfType<MiddlewareAnalysis>()); - - Assert.Collection( - middlewareAnalysis.Middleware, - item => Assert.Equal("UseStaticFiles", item.UseMethod.Name), - item => Assert.Equal("UseMiddleware", item.UseMethod.Name), - item => Assert.Equal("UseMvc", item.UseMethod.Name), - item => Assert.Equal("UseRouting", item.UseMethod.Name), - item => Assert.Equal("UseEndpoints", item.UseMethod.Name)); - - Assert.Collection( - diagnostics, - diagnostic => - { - Assert.Same(StartupAnalyzer.Diagnostics.UnsupportedUseMvcWithEndpointRouting, diagnostic.Descriptor); - AnalyzerAssert.DiagnosticLocation(source.DefaultMarkerLocation, diagnostic.Location); - }); - } - - [Fact] - public async Task StartupAnalyzer_MvcOptionsAnalysis_MultipleUseMvc() - { - // Arrange - var source = Read("MvcOptions_UseMvcMultiple"); - - // Act - var diagnostics = await Runner.GetDiagnosticsAsync(source.Source); - - // Assert - var optionsAnalysis = Assert.Single(Analyses.OfType<OptionsAnalysis>()); - Assert.False(OptionsFacts.IsEndpointRoutingExplicitlyDisabled(optionsAnalysis)); - - Assert.Collection( - diagnostics, - diagnostic => - { - Assert.Same(StartupAnalyzer.Diagnostics.UnsupportedUseMvcWithEndpointRouting, diagnostic.Descriptor); - AnalyzerAssert.DiagnosticLocation(source.MarkerLocations["MM1"], diagnostic.Location); - }, - diagnostic => - { - Assert.Same(StartupAnalyzer.Diagnostics.UnsupportedUseMvcWithEndpointRouting, diagnostic.Descriptor); - AnalyzerAssert.DiagnosticLocation(source.MarkerLocations["MM2"], diagnostic.Location); - }, - diagnostic => - { - Assert.Same(StartupAnalyzer.Diagnostics.UnsupportedUseMvcWithEndpointRouting, diagnostic.Descriptor); - AnalyzerAssert.DiagnosticLocation(source.MarkerLocations["MM3"], diagnostic.Location); - }); } - [Fact] - public async Task StartupAnalyzer_ServicesAnalysis_CallBuildServiceProvider() - { - // Arrange - var source = Read("ConfigureServices_BuildServiceProvider"); - - // Act - var diagnostics = await Runner.GetDiagnosticsAsync(source.Source); + internal override bool HasConfigure => true; - // Assert - var servicesAnalysis = Assert.Single(Analyses.OfType<ServicesAnalysis>()); - Assert.NotEmpty(servicesAnalysis.Services); - Assert.Collection(diagnostics, - diagnostic => - { - Assert.Same(StartupAnalyzer.Diagnostics.BuildServiceProviderShouldNotCalledInConfigureServicesMethod, diagnostic.Descriptor); - AnalyzerAssert.DiagnosticLocation(source.MarkerLocations["MM1"], diagnostic.Location); - }); - } - - [Fact] - public async Task StartupAnalyzer_UseAuthorizationConfiguredCorrectly_ReportsNoDiagnostics() - { - // Arrange - var source = Read(nameof(TestFiles.StartupAnalyzerTest.UseAuthConfiguredCorrectly)); - - // Act - var diagnostics = await Runner.GetDiagnosticsAsync(source.Source); - - // Assert - var middlewareAnalysis = Assert.Single(Analyses.OfType<MiddlewareAnalysis>()); - Assert.NotEmpty(middlewareAnalysis.Middleware); - Assert.Empty(diagnostics); - } - - [Fact] - public async Task StartupAnalyzer_UseAuthorizationConfiguredAsAChain_ReportsNoDiagnostics() - { - // Regression test for https://github.com/dotnet/aspnetcore/issues/15203 - // Arrange - var source = Read(nameof(TestFiles.StartupAnalyzerTest.UseAuthConfiguredCorrectlyChained)); - - // Act - var diagnostics = await Runner.GetDiagnosticsAsync(source.Source); - - // Assert - var middlewareAnalysis = Assert.Single(Analyses.OfType<MiddlewareAnalysis>()); - Assert.NotEmpty(middlewareAnalysis.Middleware); - Assert.Empty(diagnostics); - } + internal override AnalyzersDiagnosticAnalyzerRunner Runner { get; } - [Fact] - public async Task StartupAnalyzer_UseAuthorizationInvokedMultipleTimesInEndpointRoutingBlock_ReportsNoDiagnostics() + internal override TestSource GetSource(string scenario) { - // Arrange - var source = Read(nameof(TestFiles.StartupAnalyzerTest.UseAuthMultipleTimes)); - - // Act - var diagnostics = await Runner.GetDiagnosticsAsync(source.Source); - - // Assert - var middlewareAnalysis = Assert.Single(Analyses.OfType<MiddlewareAnalysis>()); - Assert.NotEmpty(middlewareAnalysis.Middleware); - Assert.Empty(diagnostics); - } - - [Fact] - public async Task StartupAnalyzer_UseAuthorizationConfiguredBeforeUseRouting_ReportsDiagnostics() - { - // Arrange - var source = Read(nameof(TestFiles.StartupAnalyzerTest.UseAuthBeforeUseRouting)); - - // Act - var diagnostics = await Runner.GetDiagnosticsAsync(source.Source); - - // Assert - var middlewareAnalysis = Assert.Single(Analyses.OfType<MiddlewareAnalysis>()); - Assert.NotEmpty(middlewareAnalysis.Middleware); - Assert.Collection(diagnostics, - diagnostic => - { - Assert.Same(StartupAnalyzer.Diagnostics.IncorrectlyConfiguredAuthorizationMiddleware, diagnostic.Descriptor); - AnalyzerAssert.DiagnosticLocation(source.DefaultMarkerLocation, diagnostic.Location); - }); - } - - [Fact] - public async Task StartupAnalyzer_UseAuthorizationConfiguredBeforeUseRoutingChained_ReportsDiagnostics() - { - // This one asserts a false negative for https://github.com/dotnet/aspnetcore/issues/15203. - // We don't correctly identify chained calls, this test verifies the behavior. - // Arrange - var source = Read(nameof(TestFiles.StartupAnalyzerTest.UseAuthBeforeUseRoutingChained)); - - // Act - var diagnostics = await Runner.GetDiagnosticsAsync(source.Source); - - // Assert - var middlewareAnalysis = Assert.Single(Analyses.OfType<MiddlewareAnalysis>()); - Assert.NotEmpty(middlewareAnalysis.Middleware); - Assert.Empty(diagnostics); - } - - [Fact] - public async Task StartupAnalyzer_UseAuthorizationConfiguredAfterUseEndpoints_ReportsDiagnostics() - { - // Arrange - var source = Read(nameof(TestFiles.StartupAnalyzerTest.UseAuthAfterUseEndpoints)); - - // Act - var diagnostics = await Runner.GetDiagnosticsAsync(source.Source); - - // Assert - var middlewareAnalysis = Assert.Single(Analyses.OfType<MiddlewareAnalysis>()); - Assert.NotEmpty(middlewareAnalysis.Middleware); - Assert.Collection(diagnostics, - diagnostic => - { - Assert.Same(StartupAnalyzer.Diagnostics.IncorrectlyConfiguredAuthorizationMiddleware, diagnostic.Descriptor); - AnalyzerAssert.DiagnosticLocation(source.DefaultMarkerLocation, diagnostic.Location); - }); - } - - [Fact] - public async Task StartupAnalyzer_MultipleUseAuthorization_ReportsNoDiagnostics() - { - // Arrange - var source = Read(nameof(TestFiles.StartupAnalyzerTest.UseAuthFallbackPolicy)); - - // Act - var diagnostics = await Runner.GetDiagnosticsAsync(source.Source); - - // Assert - var middlewareAnalysis = Assert.Single(Analyses.OfType<MiddlewareAnalysis>()); - Assert.NotEmpty(middlewareAnalysis.Middleware); - Assert.Empty(diagnostics); + return Read(scenario); } } } diff --git a/src/Analyzers/Analyzers/test/StartupAnalyzerTestBase.cs b/src/Analyzers/Analyzers/test/StartupAnalyzerTestBase.cs new file mode 100644 index 00000000000..f84a7483dfb --- /dev/null +++ b/src/Analyzers/Analyzers/test/StartupAnalyzerTestBase.cs @@ -0,0 +1,355 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Concurrent; +using Microsoft.AspNetCore.Analyzer.Testing; +using Microsoft.CodeAnalysis; + +namespace Microsoft.AspNetCore.Analyzers +{ + public abstract class StartupAnalyzerTestBase : AnalyzerTestBase + { + public StartupAnalyzerTestBase() + { + StartupAnalyzer = new StartupAnalyzer(); + + Analyses = new ConcurrentBag<object>(); + ConfigureServicesMethods = new ConcurrentBag<IMethodSymbol>(); + ConfigureMethods = new ConcurrentBag<IMethodSymbol>(); + StartupAnalyzer.ServicesAnalysisCompleted += (sender, analysis) => Analyses.Add(analysis); + StartupAnalyzer.OptionsAnalysisCompleted += (sender, analysis) => Analyses.Add(analysis); + StartupAnalyzer.MiddlewareAnalysisCompleted += (sender, analysis) => Analyses.Add(analysis); + StartupAnalyzer.ConfigureServicesMethodFound += (sender, method) => ConfigureServicesMethods.Add(method); + StartupAnalyzer.ConfigureMethodFound += (sender, method) => ConfigureMethods.Add(method); + } + + internal abstract bool HasConfigure { get; } + + internal StartupAnalyzer StartupAnalyzer { get; } + + internal abstract AnalyzersDiagnosticAnalyzerRunner Runner { get; } + + internal ConcurrentBag<object> Analyses { get; } + + internal ConcurrentBag<IMethodSymbol> ConfigureServicesMethods { get; } + + internal ConcurrentBag<IMethodSymbol> ConfigureMethods { get; } + + internal abstract TestSource GetSource(string scenario); + + [Fact] + public async Task StartupAnalyzer_FindsStartupMethods_StartupSignatures_Standard() + { + // Arrange + var source = GetSource("StartupSignatures_Standard"); + + // Act + var diagnostics = await Runner.GetDiagnosticsAsync(source.Source); + + // Assert + Assert.Empty(diagnostics); + + if (HasConfigure) + { + Assert.Collection(ConfigureServicesMethods, m => Assert.Equal("ConfigureServices", m.Name)); + Assert.Collection(ConfigureMethods, m => Assert.Equal("Configure", m.Name)); + } + } + + [Fact] + public async Task StartupAnalyzer_FindsStartupMethods_StartupSignatures_MoreVariety() + { + // Arrange + var source = GetSource("StartupSignatures_MoreVariety"); + + // Act + var diagnostics = await Runner.GetDiagnosticsAsync(source.Source); + + // Assert + Assert.Empty(diagnostics); + + if (HasConfigure) + { + Assert.Collection( + ConfigureServicesMethods.OrderBy(m => m.Name), + m => Assert.Equal("ConfigureServices", m.Name)); + + Assert.Collection( + ConfigureMethods.OrderBy(m => m.Name), + m => Assert.Equal("Configure", m.Name), + m => Assert.Equal("ConfigureProduction", m.Name)); + } + } + + [Fact] + public async Task StartupAnalyzer_MvcOptionsAnalysis_UseMvc_FindsEndpointRoutingDisabled() + { + // Arrange + var source = GetSource("MvcOptions_UseMvcWithDefaultRouteAndEndpointRoutingDisabled"); + + // Act + var diagnostics = await Runner.GetDiagnosticsAsync(source.Source); + + // Assert + var optionsAnalysis = Assert.Single(Analyses.OfType<OptionsAnalysis>()); + Assert.True(OptionsFacts.IsEndpointRoutingExplicitlyDisabled(optionsAnalysis)); + + var middlewareAnalysis = Assert.Single(Analyses.OfType<MiddlewareAnalysis>()); + var middleware = Assert.Single(middlewareAnalysis.Middleware); + Assert.Equal("UseMvcWithDefaultRoute", middleware.UseMethod.Name); + + Assert.Empty(diagnostics); + } + + [Fact] + public async Task StartupAnalyzer_MvcOptionsAnalysis_AddMvcOptions_FindsEndpointRoutingDisabled() + { + // Arrange + var source = GetSource("MvcOptions_UseMvcWithDefaultRouteAndAddMvcOptionsEndpointRoutingDisabled"); + + // Act + var diagnostics = await Runner.GetDiagnosticsAsync(source.Source); + + // Assert + var optionsAnalysis = Assert.Single(Analyses.OfType<OptionsAnalysis>()); + Assert.True(OptionsFacts.IsEndpointRoutingExplicitlyDisabled(optionsAnalysis)); + + var middlewareAnalysis = Assert.Single(Analyses.OfType<MiddlewareAnalysis>()); + var middleware = Assert.Single(middlewareAnalysis.Middleware); + Assert.Equal("UseMvcWithDefaultRoute", middleware.UseMethod.Name); + + Assert.Empty(diagnostics); + } + + [Theory] + [InlineData("MvcOptions_UseMvc", "UseMvc")] + [InlineData("MvcOptions_UseMvcAndConfiguredRoutes", "UseMvc")] + [InlineData("MvcOptions_UseMvcWithDefaultRoute", "UseMvcWithDefaultRoute")] + public async Task StartupAnalyzer_MvcOptionsAnalysis_FindsEndpointRoutingEnabled(string sourceFileName, string mvcMiddlewareName) + { + // Arrange + var source = GetSource(sourceFileName); + + // Act + var diagnostics = await Runner.GetDiagnosticsAsync(source.Source); + + // Assert + var optionsAnalysis = Assert.Single(Analyses.OfType<OptionsAnalysis>()); + Assert.False(OptionsFacts.IsEndpointRoutingExplicitlyDisabled(optionsAnalysis)); + + var middlewareAnalysis = Assert.Single(Analyses.OfType<MiddlewareAnalysis>()); + var middleware = Assert.Single(middlewareAnalysis.Middleware); + Assert.Equal(mvcMiddlewareName, middleware.UseMethod.Name); + + Assert.Collection( + diagnostics, + diagnostic => + { + Assert.Same(StartupAnalyzer.Diagnostics.UnsupportedUseMvcWithEndpointRouting, diagnostic.Descriptor); + AnalyzerAssert.DiagnosticLocation(source.DefaultMarkerLocation, diagnostic.Location); + }); + } + + [Fact] + public async Task StartupAnalyzer_MvcOptionsAnalysis_MultipleMiddleware() + { + // Arrange + var source = GetSource("MvcOptions_UseMvcWithOtherMiddleware"); + + // Act + var diagnostics = await Runner.GetDiagnosticsAsync(source.Source); + + // Assert + var optionsAnalysis = Assert.Single(Analyses.OfType<OptionsAnalysis>()); + Assert.False(OptionsFacts.IsEndpointRoutingExplicitlyDisabled(optionsAnalysis)); + + var middlewareAnalysis = Assert.Single(Analyses.OfType<MiddlewareAnalysis>()); + + Assert.Collection( + middlewareAnalysis.Middleware, + item => Assert.Equal("UseStaticFiles", item.UseMethod.Name), + item => Assert.Equal("UseMiddleware", item.UseMethod.Name), + item => Assert.Equal("UseMvc", item.UseMethod.Name), + item => Assert.Equal("UseRouting", item.UseMethod.Name), + item => Assert.Equal("UseEndpoints", item.UseMethod.Name)); + + Assert.Collection( + diagnostics, + diagnostic => + { + Assert.Same(StartupAnalyzer.Diagnostics.UnsupportedUseMvcWithEndpointRouting, diagnostic.Descriptor); + AnalyzerAssert.DiagnosticLocation(source.DefaultMarkerLocation, diagnostic.Location); + }); + } + + [Fact] + public async Task StartupAnalyzer_MvcOptionsAnalysis_MultipleUseMvc() + { + // Arrange + var source = GetSource("MvcOptions_UseMvcMultiple"); + + // Act + var diagnostics = await Runner.GetDiagnosticsAsync(source.Source); + + // Assert + var optionsAnalysis = Assert.Single(Analyses.OfType<OptionsAnalysis>()); + Assert.False(OptionsFacts.IsEndpointRoutingExplicitlyDisabled(optionsAnalysis)); + + Assert.Collection( + diagnostics, + diagnostic => + { + Assert.Same(StartupAnalyzer.Diagnostics.UnsupportedUseMvcWithEndpointRouting, diagnostic.Descriptor); + AnalyzerAssert.DiagnosticLocation(source.MarkerLocations["MM1"], diagnostic.Location); + }, + diagnostic => + { + Assert.Same(StartupAnalyzer.Diagnostics.UnsupportedUseMvcWithEndpointRouting, diagnostic.Descriptor); + AnalyzerAssert.DiagnosticLocation(source.MarkerLocations["MM2"], diagnostic.Location); + }, + diagnostic => + { + Assert.Same(StartupAnalyzer.Diagnostics.UnsupportedUseMvcWithEndpointRouting, diagnostic.Descriptor); + AnalyzerAssert.DiagnosticLocation(source.MarkerLocations["MM3"], diagnostic.Location); + }); + } + + [Fact] + public async Task StartupAnalyzer_ServicesAnalysis_CallBuildServiceProvider() + { + // Arrange + var source = GetSource("ConfigureServices_BuildServiceProvider"); + + // Act + var diagnostics = await Runner.GetDiagnosticsAsync(source.Source); + + // Assert + var servicesAnalysis = Assert.Single(Analyses.OfType<ServicesAnalysis>()); + Assert.NotEmpty(servicesAnalysis.Services); + Assert.Collection(diagnostics, + diagnostic => + { + Assert.Same(StartupAnalyzer.Diagnostics.BuildServiceProviderShouldNotCalledInConfigureServicesMethod, diagnostic.Descriptor); + AnalyzerAssert.DiagnosticLocation(source.MarkerLocations["MM1"], diagnostic.Location); + }); + } + + [Fact] + public async Task StartupAnalyzer_UseAuthorizationConfiguredCorrectly_ReportsNoDiagnostics() + { + // Arrange + var source = GetSource(nameof(TestFiles.StartupAnalyzerTest.UseAuthConfiguredCorrectly)); + + // Act + var diagnostics = await Runner.GetDiagnosticsAsync(source.Source); + + // Assert + var middlewareAnalysis = Assert.Single(Analyses.OfType<MiddlewareAnalysis>()); + Assert.NotEmpty(middlewareAnalysis.Middleware); + Assert.Empty(diagnostics); + } + + [Fact] + public async Task StartupAnalyzer_UseAuthorizationConfiguredAsAChain_ReportsNoDiagnostics() + { + // Regression test for https://github.com/dotnet/aspnetcore/issues/15203 + // Arrange + var source = GetSource(nameof(TestFiles.StartupAnalyzerTest.UseAuthConfiguredCorrectlyChained)); + + // Act + var diagnostics = await Runner.GetDiagnosticsAsync(source.Source); + + // Assert + var middlewareAnalysis = Assert.Single(Analyses.OfType<MiddlewareAnalysis>()); + Assert.NotEmpty(middlewareAnalysis.Middleware); + Assert.Empty(diagnostics); + } + + [Fact] + public async Task StartupAnalyzer_UseAuthorizationInvokedMultipleTimesInEndpointRoutingBlock_ReportsNoDiagnostics() + { + // Arrange + var source = GetSource(nameof(TestFiles.StartupAnalyzerTest.UseAuthMultipleTimes)); + + // Act + var diagnostics = await Runner.GetDiagnosticsAsync(source.Source); + + // Assert + var middlewareAnalysis = Assert.Single(Analyses.OfType<MiddlewareAnalysis>()); + Assert.NotEmpty(middlewareAnalysis.Middleware); + Assert.Empty(diagnostics); + } + + [Fact] + public async Task StartupAnalyzer_UseAuthorizationConfiguredBeforeUseRouting_ReportsDiagnostics() + { + // Arrange + var source = GetSource(nameof(TestFiles.StartupAnalyzerTest.UseAuthBeforeUseRouting)); + + // Act + var diagnostics = await Runner.GetDiagnosticsAsync(source.Source); + + // Assert + var middlewareAnalysis = Assert.Single(Analyses.OfType<MiddlewareAnalysis>()); + Assert.NotEmpty(middlewareAnalysis.Middleware); + Assert.Collection(diagnostics, + diagnostic => + { + Assert.Same(StartupAnalyzer.Diagnostics.IncorrectlyConfiguredAuthorizationMiddleware, diagnostic.Descriptor); + AnalyzerAssert.DiagnosticLocation(source.DefaultMarkerLocation, diagnostic.Location); + }); + } + + [Fact] + public async Task StartupAnalyzer_UseAuthorizationConfiguredBeforeUseRoutingChained_ReportsDiagnostics() + { + // This one asserts a false negative for https://github.com/dotnet/aspnetcore/issues/15203. + // We don't correctly identify chained calls, this test verifies the behavior. + // Arrange + var source = GetSource(nameof(TestFiles.StartupAnalyzerTest.UseAuthBeforeUseRoutingChained)); + + // Act + var diagnostics = await Runner.GetDiagnosticsAsync(source.Source); + + // Assert + var middlewareAnalysis = Assert.Single(Analyses.OfType<MiddlewareAnalysis>()); + Assert.NotEmpty(middlewareAnalysis.Middleware); + Assert.Empty(diagnostics); + } + + [Fact] + public async Task StartupAnalyzer_UseAuthorizationConfiguredAfterUseEndpoints_ReportsDiagnostics() + { + // Arrange + var source = GetSource(nameof(TestFiles.StartupAnalyzerTest.UseAuthAfterUseEndpoints)); + + // Act + var diagnostics = await Runner.GetDiagnosticsAsync(source.Source); + + // Assert + var middlewareAnalysis = Assert.Single(Analyses.OfType<MiddlewareAnalysis>()); + Assert.NotEmpty(middlewareAnalysis.Middleware); + Assert.Collection(diagnostics, + diagnostic => + { + Assert.Same(StartupAnalyzer.Diagnostics.IncorrectlyConfiguredAuthorizationMiddleware, diagnostic.Descriptor); + AnalyzerAssert.DiagnosticLocation(source.DefaultMarkerLocation, diagnostic.Location); + }); + } + + [Fact] + public async Task StartupAnalyzer_MultipleUseAuthorization_ReportsNoDiagnostics() + { + // Arrange + var source = GetSource(nameof(TestFiles.StartupAnalyzerTest.UseAuthFallbackPolicy)); + + // Act + var diagnostics = await Runner.GetDiagnosticsAsync(source.Source); + + // Assert + var middlewareAnalysis = Assert.Single(Analyses.OfType<MiddlewareAnalysis>()); + Assert.NotEmpty(middlewareAnalysis.Middleware); + Assert.Empty(diagnostics); + } + } +} -- GitLab