diff options
author | Safia Abdalla <safia@safia.rocks> | 2022-11-10 21:31:56 +0300 |
---|---|---|
committer | Safia Abdalla <safia@safia.rocks> | 2022-11-10 21:31:56 +0300 |
commit | 4c8ec476ba14d0ef5da55817025cc372a64fcb22 (patch) | |
tree | de7bddb7ba518d828f2cb0deedf30cb0dac0aa96 | |
parent | 1c385f463e488dc9a280fc24352740e919c6bc3f (diff) |
Support checking for required members in minimal APIssafia/asparams-required-members
-rw-r--r-- | src/Http/.vscode/launch.json | 26 | ||||
-rw-r--r-- | src/Http/.vscode/tasks.json | 41 | ||||
-rw-r--r-- | src/Http/Http.Extensions/test/RequestDelegateFactoryTests.cs | 152 |
3 files changed, 219 insertions, 0 deletions
diff --git a/src/Http/.vscode/launch.json b/src/Http/.vscode/launch.json new file mode 100644 index 0000000000..14da52882b --- /dev/null +++ b/src/Http/.vscode/launch.json @@ -0,0 +1,26 @@ +{ + "version": "0.2.0", + "configurations": [ + { + // Use IntelliSense to find out which attributes exist for C# debugging + // Use hover for the description of the existing attributes + // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md + "name": ".NET Core Launch (console)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + // If you have changed target frameworks, make sure to update the program path. + "program": "../artifacts/bin/Microsoft.AspNetCore.Http.Extensions.Tests/Debug/net7.0/Microsoft.AspNetCore.Http.Extensions.Tests.dll", + "args": [], + "cwd": "${workspaceFolder}/Http.Extensions/test", + // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console + "console": "internalConsole", + "stopAtEntry": false + }, + { + "name": ".NET Core Attach", + "type": "coreclr", + "request": "attach" + } + ] +}
\ No newline at end of file diff --git a/src/Http/.vscode/tasks.json b/src/Http/.vscode/tasks.json new file mode 100644 index 0000000000..f6ad7fd5c2 --- /dev/null +++ b/src/Http/.vscode/tasks.json @@ -0,0 +1,41 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/Http.Extensions/test/Microsoft.AspNetCore.Http.Extensions.Tests.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "publish", + "command": "dotnet", + "type": "process", + "args": [ + "publish", + "${workspaceFolder}/Http.Extensions/test/Microsoft.AspNetCore.Http.Extensions.Tests.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "watch", + "command": "dotnet", + "type": "process", + "args": [ + "watch", + "run", + "--project", + "${workspaceFolder}/Http.Extensions/test/Microsoft.AspNetCore.Http.Extensions.Tests.csproj" + ], + "problemMatcher": "$msCompile" + } + ] +}
\ No newline at end of file diff --git a/src/Http/Http.Extensions/test/RequestDelegateFactoryTests.cs b/src/Http/Http.Extensions/test/RequestDelegateFactoryTests.cs index d4a3ee1e42..17eea596ea 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateFactoryTests.cs +++ b/src/Http/Http.Extensions/test/RequestDelegateFactoryTests.cs @@ -7047,6 +7047,158 @@ public partial class RequestDelegateFactoryTests : LoggedTest Assert.Same(options.EndpointBuilder.Metadata, result.EndpointMetadata); } + private class ParameterListRequiredStringFromDifferentSources + { + public HttpContext? HttpContext { get; set; } + + [FromRoute] + public required string RequiredRouteParam { get; set; } + + [FromQuery] + public required string RequiredQueryParam { get; set; } + + [FromHeader] + public required string RequiredHeaderParam { get; set; } + } + + [Fact] + public async Task RequestDelegateFactory_AsParameters_SupportsRequiredMember() + { + // Arrange + static void TestAction([AsParameters] ParameterListRequiredStringFromDifferentSources args) { } + + var httpContext = CreateHttpContext(); + + var factoryResult = RequestDelegateFactory.Create(TestAction); + var requestDelegate = factoryResult.RequestDelegate; + + // Act + await requestDelegate(httpContext); + + // Assert that the required modifier disables optionality + // on members when they are not nullable. + Assert.Equal(400, httpContext.Response.StatusCode); + + var logs = TestSink.Writes.ToArray(); + + Assert.Equal(3, logs.Length); + + Assert.Equal(new EventId(4, "RequiredParameterNotProvided"), logs[0].EventId); + Assert.Equal(LogLevel.Debug, logs[0].LogLevel); + Assert.Equal(@"Required parameter ""string RequiredRouteParam"" was not provided from route.", logs[0].Message); + + Assert.Equal(new EventId(4, "RequiredParameterNotProvided"), logs[1].EventId); + Assert.Equal(LogLevel.Debug, logs[1].LogLevel); + Assert.Equal(@"Required parameter ""string RequiredQueryParam"" was not provided from query string.", logs[1].Message); + + Assert.Equal(new EventId(4, "RequiredParameterNotProvided"), logs[2].EventId); + Assert.Equal(LogLevel.Debug, logs[2].LogLevel); + Assert.Equal(@"Required parameter ""string RequiredHeaderParam"" was not provided from header.", logs[2].Message); + } + + private class ParameterListRequiredNullableStringFromDifferentSources + { + public HttpContext? HttpContext { get; set; } + + [FromRoute] + public required StringValues? RequiredRouteParam { get; set; } + + [FromQuery] + public required StringValues? RequiredQueryParam { get; set; } + + [FromHeader] + public required StringValues? RequiredHeaderParam { get; set; } + } + + [Fact] + public async Task RequestDelegateFactory_AsParameters_SupportsNullableRequiredMember() + { + // Arrange + static void TestAction([AsParameters] ParameterListRequiredStringFromDifferentSources args) + { + args.HttpContext!.Items.Add("RequiredRouteParam", args.RequiredRouteParam); + args.HttpContext!.Items.Add("RequiredQueryParam", args.RequiredQueryParam); + args.HttpContext!.Items.Add("RequiredHeaderParam", args.RequiredHeaderParam); + } + + var httpContext = CreateHttpContext(); + + var factoryResult = RequestDelegateFactory.Create(TestAction); + var requestDelegate = factoryResult.RequestDelegate; + + // Act + await requestDelegate(httpContext); + + // Assert that when properties are required but nullable + // we evaluate them as optional because required members + // must be initialized but they can be initialized to null + // when an NRT is required. + Assert.Equal(200, httpContext.Response.StatusCode); + + Assert.Null(httpContext.Items["RequiredRouteParam"]); + Assert.Null(httpContext.Items["RequiredQueryParam"]); + Assert.Null(httpContext.Items["RequiredHeaderParam"]); + } + +#nullable disable + private class ParameterListMixedRequiredStringsFromDifferentSources + { + public HttpContext HttpContext { get; set; } + + [FromRoute] + public required string RequiredRouteParam { get; set; } + + [FromRoute] + public string OptionalRouteParam { get; set; } + + [FromQuery] + public required string RequiredQueryParam { get; set; } + + [FromQuery] + public string OptionalQueryParam { get; set; } + + [FromHeader] + public required string RequiredHeaderParam { get; set; } + + [FromHeader] + public string OptionalHeaderParam { get; set; } + } + + [Fact] + public async Task RequestDelegateFactory_AsParameters_SupportsRequiredMember_NullabilityDisabled() + { + // Arange + static void TestAction([AsParameters] ParameterListMixedRequiredStringsFromDifferentSources args) { } + + var httpContext = CreateHttpContext(); + + var factoryResult = RequestDelegateFactory.Create(TestAction); + var requestDelegate = factoryResult.RequestDelegate; + + // Act + await requestDelegate(httpContext); + + // Assert that we only execute required parameter + // checks for members that have the required modifier + Assert.Equal(400, httpContext.Response.StatusCode); + + var logs = TestSink.Writes.ToArray(); + Assert.Equal(3, logs.Length); + + Assert.Equal(new EventId(4, "RequiredParameterNotProvided"), logs[0].EventId); + Assert.Equal(LogLevel.Debug, logs[0].LogLevel); + Assert.Equal(@"Required parameter ""string RequiredRouteParam"" was not provided from route.", logs[0].Message); + + Assert.Equal(new EventId(4, "RequiredParameterNotProvided"), logs[1].EventId); + Assert.Equal(LogLevel.Debug, logs[1].LogLevel); + Assert.Equal(@"Required parameter ""string RequiredQueryParam"" was not provided from query string.", logs[1].Message); + + Assert.Equal(new EventId(4, "RequiredParameterNotProvided"), logs[2].EventId); + Assert.Equal(LogLevel.Debug, logs[2].LogLevel); + Assert.Equal(@"Required parameter ""string RequiredHeaderParam"" was not provided from header.", logs[2].Message); + } +#nullable enable + private DefaultHttpContext CreateHttpContext() { var responseFeature = new TestHttpResponseFeature(); |