From 9792bc9d3f2bff0984daafb0928b0ddd21872b95 Mon Sep 17 00:00:00 2001 From: Safia Abdalla Date: Wed, 22 Feb 2023 19:08:59 -0800 Subject: [PATCH] Add shared test infra for RDF and RDG --- .../CompileTimeGeneratorTests.cs | 84 ++++ ...s.cs => CompileTimeIncrementalityTests.cs} | 6 +- .../RequestDelegateGeneratorTestBase.cs | 48 ++- .../RequestDelegateGeneratorTests.cs | 369 +++++++----------- .../RuntimeGeneratorTests.cs | 9 + 5 files changed, 272 insertions(+), 244 deletions(-) create mode 100644 src/Http/Http.Extensions/test/RequestDelegateGenerator/CompileTimeGeneratorTests.cs rename src/Http/Http.Extensions/test/RequestDelegateGenerator/{RequestDelegateGeneratorIncrementalityTests.cs => CompileTimeIncrementalityTests.cs} (89%) create mode 100644 src/Http/Http.Extensions/test/RequestDelegateGenerator/RuntimeGeneratorTests.cs diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/CompileTimeGeneratorTests.cs b/src/Http/Http.Extensions/test/RequestDelegateGenerator/CompileTimeGeneratorTests.cs new file mode 100644 index 000000000000..1fd1deed793d --- /dev/null +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/CompileTimeGeneratorTests.cs @@ -0,0 +1,84 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +using Microsoft.CodeAnalysis; +namespace Microsoft.AspNetCore.Http.Generators.Tests; + +public class CompileTimeGeneratorTests : RequestDelegateGeneratorTests +{ + protected override bool IsGeneratorEnabled { get; } = true; + + [Fact] + public async Task MapAction_UnknownParameter_EmitsDiagnostic_NoSource() + { + // This will eventually be handled by the EndpointParameterSource.JsonBodyOrService. + // All parameters should theoretically be handleable with enough "Or"s in the future + // we'll remove this test and diagnostic. + var source = """ +app.MapGet("/", (IServiceProvider provider) => "Hello world!"); +"""; + var expectedBody = "Hello world!"; + var (generatorRunResult, compilation) = await RunGeneratorAsync(source); + + // Emits diagnostic but generates no source + var result = Assert.IsType(generatorRunResult); + var diagnostic = Assert.Single(result.Diagnostics); + Assert.Equal(DiagnosticDescriptors.GetUnableToResolveParameterDescriptor("provider").Id, diagnostic.Id); + Assert.Empty(result.GeneratedSources); + + // Falls back to runtime-generated endpoint + var endpoint = GetEndpointFromCompilation(compilation, false); + + var httpContext = CreateHttpContext(); + await endpoint.RequestDelegate(httpContext); + await VerifyResponseBodyAsync(httpContext, expectedBody); + } + + [Fact] + public async Task MapGet_WithRequestDelegate_DoesNotGenerateSources() + { + var (generatorRunResult, compilation) = await RunGeneratorAsync(""" +app.MapGet("/hello", (HttpContext context) => Task.CompletedTask); +"""); + var results = Assert.IsType(generatorRunResult); + Assert.Empty(GetStaticEndpoints(results, GeneratorSteps.EndpointModelStep)); + + var endpoint = GetEndpointFromCompilation(compilation, false); + + var httpContext = CreateHttpContext(); + await endpoint.RequestDelegate(httpContext); + await VerifyResponseBodyAsync(httpContext, ""); + } + + // Todo: Move this to a shared test that checks metadata once that is supported + // in the source generator. + [Theory] + [InlineData(@"app.MapGet(""/"", () => Console.WriteLine(""Returns void""));", null)] + [InlineData(@"app.MapGet(""/"", () => TypedResults.Ok(""Alright!""));", null)] + [InlineData(@"app.MapGet(""/"", () => Results.NotFound(""Oops!""));", null)] + [InlineData(@"app.MapGet(""/"", () => Task.FromResult(new Todo() { Name = ""Test Item""}));", "application/json")] + [InlineData(@"app.MapGet(""/"", () => ""Hello world!"");", "text/plain")] + public async Task MapAction_ProducesCorrectContentType(string source, string expectedContentType) + { + var (result, compilation) = await RunGeneratorAsync(source); + + VerifyStaticEndpointModel(result, endpointModel => + { + Assert.Equal("/", endpointModel.RoutePattern); + Assert.Equal("MapGet", endpointModel.HttpMethod); + Assert.Equal(expectedContentType, endpointModel.Response.ContentType); + }); + } + + [Fact] + public async Task MapAction_ExplicitRouteParamWithInvalidName_SimpleReturn() + { + var source = $$"""app.MapGet("/{routeValue}", ([FromRoute(Name = "invalidName" )] string parameterName) => parameterName);"""; + var (_, compilation) = await RunGeneratorAsync(source); + var endpoint = GetEndpointFromCompilation(compilation); + + var httpContext = CreateHttpContext(); + + var exception = await Assert.ThrowsAsync(() => endpoint.RequestDelegate(httpContext)); + Assert.Equal("'invalidName' is not a route parameter.", exception.Message); + } +} diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateGeneratorIncrementalityTests.cs b/src/Http/Http.Extensions/test/RequestDelegateGenerator/CompileTimeIncrementalityTests.cs similarity index 89% rename from src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateGeneratorIncrementalityTests.cs rename to src/Http/Http.Extensions/test/RequestDelegateGenerator/CompileTimeIncrementalityTests.cs index f728b5b2f2de..3264e4106184 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateGeneratorIncrementalityTests.cs +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/CompileTimeIncrementalityTests.cs @@ -5,8 +5,10 @@ namespace Microsoft.AspNetCore.Http.Generators.Tests; -public class RequestDelegateGeneratorIncrementalityTests : RequestDelegateGeneratorTestBase +public class CompileTimeIncrementalityTests : RequestDelegateGeneratorTestBase { + protected override bool IsGeneratorEnabled { get; } = true; + [Fact] public async Task MapAction_SameReturnType_DoesNotTriggerUpdate() { @@ -59,5 +61,5 @@ public async Task MapAction_ChangeBodyParamNullability_TriggersUpdate() Assert.All(outputSteps, (value) => Assert.Equal(IncrementalStepRunReason.New, value.Reason)); } - private static IEnumerable<(object Value, IncrementalStepRunReason Reason)> GetRunStepOutputs(GeneratorRunResult result) => result.TrackedOutputSteps.SelectMany(step => step.Value).SelectMany(value => value.Outputs); + private static IEnumerable<(object Value, IncrementalStepRunReason Reason)> GetRunStepOutputs(GeneratorRunResult? result) => result?.TrackedOutputSteps.SelectMany(step => step.Value).SelectMany(value => value.Outputs); } diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateGeneratorTestBase.cs b/src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateGeneratorTestBase.cs index b0d1e00a9b3d..5900a7318908 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateGeneratorTestBase.cs +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateGeneratorTestBase.cs @@ -23,21 +23,30 @@ namespace Microsoft.AspNetCore.Http.Generators.Tests; -public class RequestDelegateGeneratorTestBase : LoggedTest +public abstract class RequestDelegateGeneratorTestBase : LoggedTest { - internal static async Task<(GeneratorRunResult, Compilation)> RunGeneratorAsync(string sources, params string[] updatedSources) + protected abstract bool IsGeneratorEnabled { get; } + + internal async Task<(GeneratorRunResult?, Compilation)> RunGeneratorAsync(string sources, params string[] updatedSources) { var compilation = await CreateCompilationAsync(sources); - var generator = new RequestDelegateGenerator().AsSourceGenerator(); - // Enable the source generator in tests + // Return the compilation immediately if + // the generator is not enabled. + if (!IsGeneratorEnabled) + { + return (null, compilation); + } + + // Configure the generator driver and run + // the compilation with it if the generator + // is enabled. + var generator = new RequestDelegateGenerator().AsSourceGenerator(); GeneratorDriver driver = CSharpGeneratorDriver.Create(generators: new[] { generator }, driverOptions: new GeneratorDriverOptions(IncrementalGeneratorOutputKind.None, trackIncrementalGeneratorSteps: true)); - - // Run the source generator driver = driver.RunGeneratorsAndUpdateCompilation(compilation, out var updatedCompilation, out var _); foreach (var updatedSource in updatedSources) @@ -72,13 +81,30 @@ internal static StaticRouteHandlerModel.Endpoint[] GetStaticEndpoints(GeneratorR return Array.Empty(); } - internal static Endpoint GetEndpointFromCompilation(Compilation compilation, bool expectSourceKey = true) => - Assert.Single(GetEndpointsFromCompilation(compilation, expectSourceKey)); + internal static void VerifyStaticEndpointModel(GeneratorRunResult? result, Action runAssertions) + { + if (result.HasValue) + { + runAssertions(GetStaticEndpoint(result.Value, GeneratorSteps.EndpointModelStep)); + } + } - internal static Endpoint[] GetEndpointsFromCompilation(Compilation compilation, bool expectSourceKey = true) + internal static void VerifyStaticEndpointModels(GeneratorRunResult? result, Action runAssertions) + { + if (result.HasValue) + { + runAssertions(GetStaticEndpoints(result.Value, GeneratorSteps.EndpointModelStep)); + } + } + + internal Endpoint GetEndpointFromCompilation(Compilation compilation, bool? expectSourceKeyOverride = null) => + Assert.Single(GetEndpointsFromCompilation(compilation, expectSourceKeyOverride)); + + internal Endpoint[] GetEndpointsFromCompilation(Compilation compilation, bool? expectSourceKeyOverride = null) { var assemblyName = compilation.AssemblyName!; var symbolsName = Path.ChangeExtension(assemblyName, "pdb"); + var expectSourceKey = (expectSourceKeyOverride ?? true) && IsGeneratorEnabled; var output = new MemoryStream(); var pdb = new MemoryStream(); @@ -353,6 +379,10 @@ private static Task CreateCompilationAsync(string sources) internal async Task VerifyAgainstBaselineUsingFile(Compilation compilation, [CallerMemberName] string callerName = "") { + if (!IsGeneratorEnabled) + { + return; + } var baselineFilePath = Path.Combine("RequestDelegateGenerator", "Baselines", $"{callerName}.generated.txt"); var generatedSyntaxTree = compilation.SyntaxTrees.Last(); var generatedCode = await generatedSyntaxTree.GetTextAsync(); diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateGeneratorTests.cs b/src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateGeneratorTests.cs index 7747f5ab050f..4758be719162 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateGeneratorTests.cs +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateGeneratorTests.cs @@ -21,7 +21,7 @@ namespace Microsoft.AspNetCore.Http.Generators.Tests; -public class RequestDelegateGeneratorTests : RequestDelegateGeneratorTestBase +public abstract class RequestDelegateGeneratorTests : RequestDelegateGeneratorTestBase { [Theory] [InlineData(@"app.MapGet(""/hello"", () => ""Hello world!"");", "MapGet", "Hello world!")] @@ -35,12 +35,13 @@ public class RequestDelegateGeneratorTests : RequestDelegateGeneratorTestBase public async Task MapAction_NoParam_StringReturn(string source, string httpMethod, string expectedBody) { var (result, compilation) = await RunGeneratorAsync(source); - - var endpointModel = GetStaticEndpoint(result, GeneratorSteps.EndpointModelStep); var endpoint = GetEndpointFromCompilation(compilation); - Assert.Equal("/hello", endpointModel.RoutePattern); - Assert.Equal(httpMethod, endpointModel.HttpMethod); + VerifyStaticEndpointModel(result, (endpointModel) => + { + Assert.Equal("/hello", endpointModel.RoutePattern); + Assert.Equal(httpMethod, endpointModel.HttpMethod); + }); var httpContext = CreateHttpContext(); await endpoint.RequestDelegate(httpContext); @@ -84,15 +85,16 @@ public static object[][] MapAction_ExplicitQueryParam_StringReturn_Data public async Task MapAction_ExplicitQueryParam_StringReturn(string source, string queryValue, int expectedStatusCode, string expectedBody) { var (results, compilation) = await RunGeneratorAsync(source); - - var endpointModel = GetStaticEndpoint(results, GeneratorSteps.EndpointModelStep); var endpoint = GetEndpointFromCompilation(compilation); - Assert.Equal("/", endpointModel.RoutePattern); - Assert.Equal("MapGet", endpointModel.HttpMethod); - var p = Assert.Single(endpointModel.Parameters); - Assert.Equal(EndpointParameterSource.Query, p.Source); - Assert.Equal("queryValue", p.Name); + VerifyStaticEndpointModel(results, (endpointModel) => + { + Assert.Equal("/", endpointModel.RoutePattern); + Assert.Equal("MapGet", endpointModel.HttpMethod); + var p = Assert.Single(endpointModel.Parameters); + Assert.Equal(EndpointParameterSource.Query, p.Source); + Assert.Equal("queryValue", p.Name); + }); var httpContext = CreateHttpContext(); if (queryValue is not null) @@ -110,14 +112,16 @@ public async Task MapAction_SingleTimeOnlyParam_StringReturn() var (results, compilation) = await RunGeneratorAsync(""" app.MapGet("/hello", ([FromQuery]TimeOnly p) => p.ToString()); """); - var endpointModel = GetStaticEndpoint(results, GeneratorSteps.EndpointModelStep); var endpoint = GetEndpointFromCompilation(compilation); - Assert.Equal("/hello", endpointModel.RoutePattern); - Assert.Equal("MapGet", endpointModel.HttpMethod); - var p = Assert.Single(endpointModel.Parameters); - Assert.Equal(EndpointParameterSource.Query, p.Source); - Assert.Equal("p", p.Name); + VerifyStaticEndpointModel(results, endpointModel => + { + Assert.Equal("/hello", endpointModel.RoutePattern); + Assert.Equal("MapGet", endpointModel.HttpMethod); + var p = Assert.Single(endpointModel.Parameters); + Assert.Equal(EndpointParameterSource.Query, p.Source); + Assert.Equal("p", p.Name); + }); var httpContext = CreateHttpContext(); httpContext.Request.QueryString = new QueryString("?p=13:30"); @@ -136,14 +140,16 @@ public async Task MapAction_SingleDateLikeParam_StringReturn(string parameterTyp var (results, compilation) = await RunGeneratorAsync($$""" app.MapGet("/hello", ([FromQuery]{{parameterType}} p) => p.ToString("yyyy-MM-dd")); """); - var endpointModel = GetStaticEndpoint(results, GeneratorSteps.EndpointModelStep); var endpoint = GetEndpointFromCompilation(compilation); - Assert.Equal("/hello", endpointModel.RoutePattern); - Assert.Equal("MapGet", endpointModel.HttpMethod); - var p = Assert.Single(endpointModel.Parameters); - Assert.Equal(EndpointParameterSource.Query, p.Source); - Assert.Equal("p", p.Name); + VerifyStaticEndpointModel(results, endpointModel => + { + Assert.Equal("/hello", endpointModel.RoutePattern); + Assert.Equal("MapGet", endpointModel.HttpMethod); + var p = Assert.Single(endpointModel.Parameters); + Assert.Equal(EndpointParameterSource.Query, p.Source); + Assert.Equal("p", p.Name); + }); var httpContext = CreateHttpContext(); httpContext.Request.QueryString = new QueryString($"?p={result}"); @@ -203,13 +209,13 @@ public async Task MapAction_SingleParsable_StringReturn(string typeName, string context.Items["tryParsable"] = p; }); """); - - var endpointModel = GetStaticEndpoint(results, GeneratorSteps.EndpointModelStep); var endpoint = GetEndpointFromCompilation(compilation); var httpContext = CreateHttpContext(); - var encodedQueryStringInput = queryStringInput != null ? UrlEncoder.Default.Encode(queryStringInput) : null; - httpContext.Request.QueryString = new QueryString($"?p={encodedQueryStringInput}"); + if (queryStringInput != null) + { + httpContext.Request.QueryString = new QueryString($"?p={UrlEncoder.Default.Encode(queryStringInput)}"); + } await endpoint.RequestDelegate(httpContext); Assert.Equal(200, httpContext.Response.StatusCode); @@ -224,15 +230,16 @@ public async Task MapAction_TryParsePrecedenceCheck(string parameterType, string var (results, compilation) = await RunGeneratorAsync($$""" app.MapGet("/hello", ([FromQuery]{{parameterType}} p) => p.MagicValue); """); - - var endpointModel = GetStaticEndpoint(results, GeneratorSteps.EndpointModelStep); var endpoint = GetEndpointFromCompilation(compilation); - Assert.Equal("/hello", endpointModel.RoutePattern); - Assert.Equal("MapGet", endpointModel.HttpMethod); - var p = Assert.Single(endpointModel.Parameters); - Assert.Equal(EndpointParameterSource.Query, p.Source); - Assert.Equal("p", p.Name); + VerifyStaticEndpointModel(results, (endpointModel) => + { + Assert.Equal("/hello", endpointModel.RoutePattern); + Assert.Equal("MapGet", endpointModel.HttpMethod); + var p = Assert.Single(endpointModel.Parameters); + Assert.Equal(EndpointParameterSource.Query, p.Source); + Assert.Equal("p", p.Name); + }); var httpContext = CreateHttpContext(); httpContext.Request.QueryString = new QueryString("?p=1"); @@ -253,15 +260,16 @@ public async Task MapAction_SingleComplexTypeParam_StringReturn() var (results, compilation) = await RunGeneratorAsync(""" app.MapGet("/hello", ([FromQuery]Todo p) => p.Name!); """); - - var endpointModel = GetStaticEndpoint(results, GeneratorSteps.EndpointModelStep); var endpoint = GetEndpointFromCompilation(compilation); - Assert.Equal("/hello", endpointModel.RoutePattern); - Assert.Equal("MapGet", endpointModel.HttpMethod); - var p = Assert.Single(endpointModel.Parameters); - Assert.Equal(EndpointParameterSource.Query, p.Source); - Assert.Equal("p", p.Name); + VerifyStaticEndpointModel(results, endpointModel => + { + Assert.Equal("/hello", endpointModel.RoutePattern); + Assert.Equal("MapGet", endpointModel.HttpMethod); + var p = Assert.Single(endpointModel.Parameters); + Assert.Equal(EndpointParameterSource.Query, p.Source); + Assert.Equal("p", p.Name); + }); var httpContext = CreateHttpContext(); httpContext.Request.QueryString = new QueryString("?p=1"); @@ -283,15 +291,16 @@ public async Task MapAction_SingleEnumParam_StringReturn() var (results, compilation) = await RunGeneratorAsync(""" app.MapGet("/hello", ([FromQuery]TodoStatus p) => p.ToString()); """); - - var endpointModel = GetStaticEndpoint(results, GeneratorSteps.EndpointModelStep); var endpoint = GetEndpointFromCompilation(compilation); - Assert.Equal("/hello", endpointModel.RoutePattern); - Assert.Equal("MapGet", endpointModel.HttpMethod); - var p = Assert.Single(endpointModel.Parameters); - Assert.Equal(EndpointParameterSource.Query, p.Source); - Assert.Equal("p", p.Name); + VerifyStaticEndpointModel(results, endpointModel => + { + Assert.Equal("/hello", endpointModel.RoutePattern); + Assert.Equal("MapGet", endpointModel.HttpMethod); + var p = Assert.Single(endpointModel.Parameters); + Assert.Equal(EndpointParameterSource.Query, p.Source); + Assert.Equal("p", p.Name); + }); var httpContext = CreateHttpContext(); httpContext.Request.QueryString = new QueryString("?p=Done"); @@ -310,15 +319,16 @@ public async Task MapAction_SingleNullableStringParam_WithEmptyQueryStringValueP var (results, compilation) = await RunGeneratorAsync(""" app.MapGet("/hello", ([FromQuery]string? p) => p == string.Empty ? "No value, but not null!" : "Was null!"); """); - - var endpointModel = GetStaticEndpoint(results, GeneratorSteps.EndpointModelStep); var endpoint = GetEndpointFromCompilation(compilation); - Assert.Equal("/hello", endpointModel.RoutePattern); - Assert.Equal("MapGet", endpointModel.HttpMethod); - var p = Assert.Single(endpointModel.Parameters); - Assert.Equal(EndpointParameterSource.Query, p.Source); - Assert.Equal("p", p.Name); + VerifyStaticEndpointModel(results, endpointModel => + { + Assert.Equal("/hello", endpointModel.RoutePattern); + Assert.Equal("MapGet", endpointModel.HttpMethod); + var p = Assert.Single(endpointModel.Parameters); + Assert.Equal(EndpointParameterSource.Query, p.Source); + Assert.Equal("p", p.Name); + }); var httpContext = CreateHttpContext(); httpContext.Request.QueryString = new QueryString("?p="); @@ -334,12 +344,13 @@ public async Task MapAction_MultipleStringParam_StringReturn() var (results, compilation) = await RunGeneratorAsync(""" app.MapGet("/hello", ([FromQuery]string p1, [FromQuery]string p2) => $"{p1} {p2}"); """); - - var endpointModel = GetStaticEndpoint(results, GeneratorSteps.EndpointModelStep); var endpoint = GetEndpointFromCompilation(compilation); - Assert.Equal("/hello", endpointModel.RoutePattern); - Assert.Equal("MapGet", endpointModel.HttpMethod); + VerifyStaticEndpointModel(results, endpointModel => + { + Assert.Equal("/hello", endpointModel.RoutePattern); + Assert.Equal("MapGet", endpointModel.HttpMethod); + }); var httpContext = CreateHttpContext(); httpContext.Request.QueryString = new QueryString("?p1=Hello&p2=world!"); @@ -362,15 +373,16 @@ public async Task MapAction_SingleSpecialTypeParam_StringReturn(string parameter var (results, compilation) = await RunGeneratorAsync($""" app.MapGet("/hello", ({parameterType} p) => p == null ? "null!" : "Hello world!"); """); - - var endpointModel = GetStaticEndpoint(results, GeneratorSteps.EndpointModelStep); var endpoint = GetEndpointFromCompilation(compilation); - Assert.Equal("/hello", endpointModel.RoutePattern); - Assert.Equal("MapGet", endpointModel.HttpMethod); - var p = Assert.Single(endpointModel.Parameters); - Assert.Equal(EndpointParameterSource.SpecialType, p.Source); - Assert.Equal("p", p.Name); + VerifyStaticEndpointModel(results, endpointModel => + { + Assert.Equal("/hello", endpointModel.RoutePattern); + Assert.Equal("MapGet", endpointModel.HttpMethod); + var p = Assert.Single(endpointModel.Parameters); + Assert.Equal(EndpointParameterSource.SpecialType, p.Source); + Assert.Equal("p", p.Name); + }); var httpContext = CreateHttpContext(); await endpoint.RequestDelegate(httpContext); @@ -383,24 +395,25 @@ public async Task MapAction_MultipleSpecialTypeParam_StringReturn() var (results, compilation) = await RunGeneratorAsync(""" app.MapGet("/hello", (HttpRequest req, HttpResponse res) => req is null || res is null ? "null!" : "Hello world!"); """); - - var endpointModel = GetStaticEndpoint(results, GeneratorSteps.EndpointModelStep); var endpoint = GetEndpointFromCompilation(compilation); - Assert.Equal("/hello", endpointModel.RoutePattern); - Assert.Equal("MapGet", endpointModel.HttpMethod); - - Assert.Collection(endpointModel.Parameters, - reqParam => - { - Assert.Equal(EndpointParameterSource.SpecialType, reqParam.Source); - Assert.Equal("req", reqParam.Name); - }, - reqParam => - { - Assert.Equal(EndpointParameterSource.SpecialType, reqParam.Source); - Assert.Equal("res", reqParam.Name); - }); + VerifyStaticEndpointModel(results, endpointModel => + { + Assert.Equal("/hello", endpointModel.RoutePattern); + Assert.Equal("MapGet", endpointModel.HttpMethod); + + Assert.Collection(endpointModel.Parameters, + reqParam => + { + Assert.Equal(EndpointParameterSource.SpecialType, reqParam.Source); + Assert.Equal("req", reqParam.Name); + }, + reqParam => + { + Assert.Equal(EndpointParameterSource.SpecialType, reqParam.Source); + Assert.Equal("res", reqParam.Name); + }); + }); var httpContext = CreateHttpContext(); await endpoint.RequestDelegate(httpContext); @@ -408,22 +421,6 @@ public async Task MapAction_MultipleSpecialTypeParam_StringReturn() await VerifyAgainstBaselineUsingFile(compilation); } - [Fact] - public async Task MapGet_WithRequestDelegate_DoesNotGenerateSources() - { - var (results, compilation) = await RunGeneratorAsync(""" -app.MapGet("/hello", (HttpContext context) => Task.CompletedTask); -"""); - - Assert.Empty(GetStaticEndpoints(results, GeneratorSteps.EndpointModelStep)); - - var endpoint = GetEndpointFromCompilation(compilation, expectSourceKey: false); - - var httpContext = CreateHttpContext(); - await endpoint.RequestDelegate(httpContext); - await VerifyResponseBodyAsync(httpContext, ""); - } - [Fact] public async Task MapAction_MultilineLambda() { @@ -434,11 +431,12 @@ public async Task MapAction_MultilineLambda() }); """; var (result, compilation) = await RunGeneratorAsync(source); - - var endpointModel = GetStaticEndpoint(result, GeneratorSteps.EndpointModelStep); var endpoint = GetEndpointFromCompilation(compilation); - Assert.Equal("/hello", endpointModel.RoutePattern); + VerifyStaticEndpointModel(result, endpointModel => + { + Assert.Equal("/hello", endpointModel.RoutePattern); + }); var httpContext = CreateHttpContext(); await endpoint.RequestDelegate(httpContext); @@ -457,13 +455,13 @@ public async Task MapAction_NoParam_StringReturn_WithFilter() """; var expectedBody = "Filtered: Hello world!"; var (result, compilation) = await RunGeneratorAsync(source); - - await VerifyAgainstBaselineUsingFile(compilation); - - var endpointModel = GetStaticEndpoint(result, GeneratorSteps.EndpointModelStep); var endpoint = GetEndpointFromCompilation(compilation); - Assert.Equal("/hello", endpointModel.RoutePattern); + await VerifyAgainstBaselineUsingFile(compilation); + VerifyStaticEndpointModel(result, endpointModel => + { + Assert.Equal("/hello", endpointModel.RoutePattern); + }); var httpContext = CreateHttpContext(); await endpoint.RequestDelegate(httpContext); @@ -477,12 +475,13 @@ public async Task MapAction_NoParam_StringReturn_WithFilter() public async Task MapAction_NoParam_AnyReturn(string source, string expectedBody) { var (result, compilation) = await RunGeneratorAsync(source); - - var endpointModel = GetStaticEndpoint(result, GeneratorSteps.EndpointModelStep); var endpoint = GetEndpointFromCompilation(compilation); - Assert.Equal("/", endpointModel.RoutePattern); - Assert.Equal("MapGet", endpointModel.HttpMethod); + VerifyStaticEndpointModel(result, endpointModel => + { + Assert.Equal("/", endpointModel.RoutePattern); + Assert.Equal("MapGet", endpointModel.HttpMethod); + }); var httpContext = CreateHttpContext(); await endpoint.RequestDelegate(httpContext); @@ -505,40 +504,19 @@ public async Task MapAction_NoParam_ComplexReturn(string source) { var expectedBody = """{"id":0,"name":"Test Item","isComplete":false}"""; var (result, compilation) = await RunGeneratorAsync(source); - - var endpointModel = GetStaticEndpoint(result, GeneratorSteps.EndpointModelStep); var endpoint = GetEndpointFromCompilation(compilation); - Assert.Equal("/", endpointModel.RoutePattern); - Assert.Equal("MapGet", endpointModel.HttpMethod); + VerifyStaticEndpointModel(result, endpointModel => + { + Assert.Equal("/", endpointModel.RoutePattern); + Assert.Equal("MapGet", endpointModel.HttpMethod); + }); var httpContext = CreateHttpContext(); await endpoint.RequestDelegate(httpContext); await VerifyResponseBodyAsync(httpContext, expectedBody); } - public static IEnumerable MapAction_ProducesCorrectContentType_Data => new List() - { - new object[] { @"app.MapGet(""/"", () => Console.WriteLine(""Returns void""));", null }, - new object[] { @"app.MapGet(""/"", () => TypedResults.Ok(""Alright!""));", null }, - new object[] { @"app.MapGet(""/"", () => Results.NotFound(""Oops!""));", null }, - new object[] { @"app.MapGet(""/"", () => Task.FromResult(new Todo() { Name = ""Test Item"" }));", "application/json" }, - new object[] { @"app.MapGet(""/"", () => ""Hello world!"");", "text/plain" } - }; - - [Theory] - [MemberData(nameof(MapAction_ProducesCorrectContentType_Data))] - public async Task MapAction_ProducesCorrectContentType(string source, string expectedContentType) - { - var (result, compilation) = await RunGeneratorAsync(source); - - var endpointModel = GetStaticEndpoint(result, GeneratorSteps.EndpointModelStep); - - Assert.Equal("/", endpointModel.RoutePattern); - Assert.Equal("MapGet", endpointModel.HttpMethod); - Assert.Equal(expectedContentType, endpointModel.Response.ContentType); - } - public static IEnumerable MapAction_NoParam_TaskOfTReturn_Data => new List() { new object[] { @"app.MapGet(""/"", () => Task.FromResult(""Hello world!""));", "Hello world!" }, @@ -551,13 +529,14 @@ public async Task MapAction_ProducesCorrectContentType(string source, string exp public async Task MapAction_NoParam_TaskOfTReturn(string source, string expectedBody) { var (result, compilation) = await RunGeneratorAsync(source); - - var endpointModel = GetStaticEndpoint(result, GeneratorSteps.EndpointModelStep); var endpoint = GetEndpointFromCompilation(compilation); - Assert.Equal("/", endpointModel.RoutePattern); - Assert.Equal("MapGet", endpointModel.HttpMethod); - Assert.True(endpointModel.Response.IsAwaitable); + VerifyStaticEndpointModel(result, endpointModel => + { + Assert.Equal("/", endpointModel.RoutePattern); + Assert.Equal("MapGet", endpointModel.HttpMethod); + Assert.True(endpointModel.Response.IsAwaitable); + }); var httpContext = CreateHttpContext(); await endpoint.RequestDelegate(httpContext); @@ -576,13 +555,14 @@ public async Task MapAction_NoParam_TaskOfTReturn(string source, string expected public async Task MapAction_NoParam_ValueTaskOfTReturn(string source, string expectedBody) { var (result, compilation) = await RunGeneratorAsync(source); - - var endpointModel = GetStaticEndpoint(result, GeneratorSteps.EndpointModelStep); var endpoint = GetEndpointFromCompilation(compilation); - Assert.Equal("/", endpointModel.RoutePattern); - Assert.Equal("MapGet", endpointModel.HttpMethod); - Assert.True(endpointModel.Response.IsAwaitable); + VerifyStaticEndpointModel(result, endpointModel => + { + Assert.Equal("/", endpointModel.RoutePattern); + Assert.Equal("MapGet", endpointModel.HttpMethod); + Assert.True(endpointModel.Response.IsAwaitable); + }); var httpContext = CreateHttpContext(); await endpoint.RequestDelegate(httpContext); @@ -604,13 +584,14 @@ public async Task MapAction_NoParam_ValueTaskOfTReturn(string source, string exp public async Task MapAction_NoParam_TaskLikeOfObjectReturn(string source, string expectedBody) { var (result, compilation) = await RunGeneratorAsync(source); - - var endpointModel = GetStaticEndpoint(result, GeneratorSteps.EndpointModelStep); var endpoint = GetEndpointFromCompilation(compilation); - Assert.Equal("/", endpointModel.RoutePattern); - Assert.Equal("MapGet", endpointModel.HttpMethod); - Assert.True(endpointModel.Response.IsAwaitable); + VerifyStaticEndpointModel(result, endpointModel => + { + Assert.Equal("/", endpointModel.RoutePattern); + Assert.Equal("MapGet", endpointModel.HttpMethod); + Assert.True(endpointModel.Response.IsAwaitable); + }); var httpContext = CreateHttpContext(); await endpoint.RequestDelegate(httpContext); @@ -640,12 +621,10 @@ public async Task Multiple_MapAction_WithParams_StringReturn() app.MapGet("/zh", (HttpRequest req, HttpResponse res) => "你好世界!"); """; var (results, compilation) = await RunGeneratorAsync(source); + var endpoints = GetEndpointsFromCompilation(compilation); await VerifyAgainstBaselineUsingFile(compilation); - - var endpointModels = GetStaticEndpoints(results, GeneratorSteps.EndpointModelStep); - - Assert.Collection(endpointModels, + VerifyStaticEndpointModels(results, endpointModels => Assert.Collection(endpointModels, endpointModel => { Assert.Equal("/en", endpointModel.RoutePattern); @@ -677,9 +656,7 @@ public async Task Multiple_MapAction_WithParams_StringReturn() Assert.Equal(EndpointParameterSource.SpecialType, reqParam.Source); Assert.Equal("res", reqParam.Name); }); - }); - - var endpoints = GetEndpointsFromCompilation(compilation); + })); Assert.Equal(3, endpoints.Length); var httpContext = CreateHttpContext(); @@ -695,29 +672,6 @@ public async Task Multiple_MapAction_WithParams_StringReturn() await VerifyResponseBodyAsync(httpContext, "你好世界!"); } - [Fact] - public async Task MapAction_VariableRoutePattern_EmitsDiagnostic_NoSource() - { - var expectedBody = "Hello world!"; - var source = """ -var route = "/en"; -app.MapGet(route, () => "Hello world!"); -"""; - var (result, compilation) = await RunGeneratorAsync(source); - - // Emits diagnostic but generates no source - var diagnostic = Assert.Single(result.Diagnostics); - Assert.Equal(DiagnosticDescriptors.UnableToResolveRoutePattern.Id,diagnostic.Id); - Assert.Empty(result.GeneratedSources); - - // Falls back to runtime-generated endpoint - var endpoint = GetEndpointFromCompilation(compilation, expectSourceKey: false); - - var httpContext = CreateHttpContext(); - await endpoint.RequestDelegate(httpContext); - await VerifyResponseBodyAsync(httpContext, expectedBody); - } - public static object[][] MapAction_ExplicitBodyParam_ComplexReturn_Data { get @@ -942,19 +896,6 @@ public async Task MapAction_ExplicitRouteParam_SimpleReturn(string source, strin await VerifyResponseBodyAsync(httpContext, expectedBody, expectedStatusCode); } - [Fact] - public async Task MapAction_ExplicitRouteParamWithInvalidName_SimpleReturn() - { - var source = $$"""app.MapGet("/{routeValue}", ([FromRoute(Name = "invalidName" )] string parameterName) => parameterName);"""; - var (_, compilation) = await RunGeneratorAsync(source); - var endpoint = GetEndpointFromCompilation(compilation); - - var httpContext = CreateHttpContext(); - - var exception = await Assert.ThrowsAsync(() => endpoint.RequestDelegate(httpContext)); - Assert.Equal("'invalidName' is not a route parameter.", exception.Message); - } - public static object[][] MapAction_RouteOrQueryParam_SimpleReturn_Data { get @@ -1020,31 +961,6 @@ public async Task MapAction_RouteOrQueryParam_SimpleReturn(string source, bool h await VerifyResponseBodyAsync(httpContext, expectedBody, expectedStatusCode); } - [Fact] - public async Task MapAction_UnknownParameter_EmitsDiagnostic_NoSource() - { - // This will eventually be handled by the EndpointParameterSource.JsonBodyOrService. - // All parameters should theoretically be handleable with enough "Or"s in the future - // we'll remove this test and diagnostic. - var source = """ -app.MapGet("/", (IServiceProvider provider) => "Hello world!"); -"""; - var expectedBody = "Hello world!"; - var (result, compilation) = await RunGeneratorAsync(source); - - // Emits diagnostic but generates no source - var diagnostic = Assert.Single(result.Diagnostics); - Assert.Equal(DiagnosticDescriptors.GetUnableToResolveParameterDescriptor("provider").Id, diagnostic.Id); - Assert.Empty(result.GeneratedSources); - - // Falls back to runtime-generated endpoint - var endpoint = GetEndpointFromCompilation(compilation, expectSourceKey: false); - - var httpContext = CreateHttpContext(); - await endpoint.RequestDelegate(httpContext); - await VerifyResponseBodyAsync(httpContext, expectedBody); - } - public static object[][] MapAction_ExplicitServiceParam_SimpleReturn_Data { get @@ -1175,17 +1091,4 @@ public async Task MapAction_ExplicitSource_SimpleReturn_Snapshot() await VerifyAgainstBaselineUsingFile(compilation); } - - [Fact] - public async Task MapAction_RequestDelegateHandler_DoesNotEmit() - { - var source = """ -app.MapGet("/", (HttpContext context) => context.Response.WriteAsync("Hello world")); -"""; - var (result, _) = await RunGeneratorAsync(source); - var endpointModels = GetStaticEndpoints(result, GeneratorSteps.EndpointModelStep); - - Assert.Empty(result.GeneratedSources); - Assert.Empty(endpointModels); - } } diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/RuntimeGeneratorTests.cs b/src/Http/Http.Extensions/test/RequestDelegateGenerator/RuntimeGeneratorTests.cs new file mode 100644 index 000000000000..6bd9a4a0fd55 --- /dev/null +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/RuntimeGeneratorTests.cs @@ -0,0 +1,9 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +using Microsoft.CodeAnalysis; +namespace Microsoft.AspNetCore.Http.Generators.Tests; + +public class RuntimeGeneratorTests : RequestDelegateGeneratorTests +{ + protected override bool IsGeneratorEnabled { get; } = false; +}