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