diff --git a/Solutions/Menes.Hosting.AspNetCore/Menes/Internal/PocoActionResultOutputBuilder.cs b/Solutions/Menes.Hosting.AspNetCore/Menes/Internal/PocoActionResultOutputBuilder.cs index a3e92230..604e933d 100644 --- a/Solutions/Menes.Hosting.AspNetCore/Menes/Internal/PocoActionResultOutputBuilder.cs +++ b/Solutions/Menes.Hosting.AspNetCore/Menes/Internal/PocoActionResultOutputBuilder.cs @@ -46,9 +46,13 @@ protected override IActionResult FromPoco( } /// - protected override bool CanConstructFrom(OpenApiResult openApiResult, OpenApiOperation operation, ILogger logger) + protected override bool CanConstructFrom( + object result, + OpenApiOperation operation, + IEnumerable converters, + ILogger logger) { - return OpenApiActionResult.CanConstructFrom(openApiResult, operation, logger); + return OpenApiActionResult.CanConstructFrom(result, operation, logger); } } } \ No newline at end of file diff --git a/Solutions/Menes.Hosting.AspNetCore/Menes/Internal/PocoHttpResponseOutputBuilder.cs b/Solutions/Menes.Hosting.AspNetCore/Menes/Internal/PocoHttpResponseOutputBuilder.cs index 17560894..4a4ddfce 100644 --- a/Solutions/Menes.Hosting.AspNetCore/Menes/Internal/PocoHttpResponseOutputBuilder.cs +++ b/Solutions/Menes.Hosting.AspNetCore/Menes/Internal/PocoHttpResponseOutputBuilder.cs @@ -46,11 +46,12 @@ protected override IHttpResponseResult FromPoco( /// protected override bool CanConstructFrom( - OpenApiResult openApiResult, + object result, OpenApiOperation operation, + IEnumerable converters, ILogger logger) { - return OpenApiHttpResponseResult.CanConstructFrom(openApiResult, operation, logger); + return OpenApiHttpResponseResult.CanConstructFrom(result, operation, logger); } } } \ No newline at end of file diff --git a/Solutions/Menes.Hosting.AspNetCore/Microsoft/Extensions/DependencyInjection/OpenApiHttpAspNetCoreHostingServiceCollectionExtensions.cs b/Solutions/Menes.Hosting.AspNetCore/Microsoft/Extensions/DependencyInjection/OpenApiHttpAspNetCoreHostingServiceCollectionExtensions.cs index efb904ce..5f4dddf4 100644 --- a/Solutions/Menes.Hosting.AspNetCore/Microsoft/Extensions/DependencyInjection/OpenApiHttpAspNetCoreHostingServiceCollectionExtensions.cs +++ b/Solutions/Menes.Hosting.AspNetCore/Microsoft/Extensions/DependencyInjection/OpenApiHttpAspNetCoreHostingServiceCollectionExtensions.cs @@ -62,7 +62,7 @@ public static IServiceCollection AddOpenApiActionResultHosting( } /// - /// Adds / middleware-based hosting. + /// Adds middleware-based hosting. /// /// The type of the OpenApi context. /// The service collection to configure. diff --git a/Solutions/Menes.Hosting.FunctionsWorker/Menes/Hosting/AzureFunctionsWorker/HttpRequestDataParameterBuilder.cs b/Solutions/Menes.Hosting.FunctionsWorker/Menes/Hosting/AzureFunctionsWorker/HttpRequestDataParameterBuilder.cs index 8a4d3a41..78d11d1f 100644 --- a/Solutions/Menes.Hosting.FunctionsWorker/Menes/Hosting/AzureFunctionsWorker/HttpRequestDataParameterBuilder.cs +++ b/Solutions/Menes.Hosting.FunctionsWorker/Menes/Hosting/AzureFunctionsWorker/HttpRequestDataParameterBuilder.cs @@ -5,8 +5,10 @@ namespace Menes.Hosting.AzureFunctionsWorker; using System.Collections.Generic; +using System.Collections.Specialized; using System.Diagnostics.CodeAnalysis; using System.IO; +using System.Web; using Menes.Converters; @@ -33,30 +35,42 @@ public HttpRequestDataParameterBuilder( /// protected override (Stream? Body, string? ContentType) GetBodyAndContentType(HttpRequestData request) { - throw new NotImplementedException(); + string? contentType = request.Headers.TryGetValues("Content-Type", out IEnumerable? values) + ? values.FirstOrDefault() : null; + return (request.Body, contentType); } /// protected override (string Path, string Method) GetPathAndMethod(HttpRequestData request) { - throw new NotImplementedException(); + return (request.Url.AbsolutePath, request.Method); } /// protected override bool TryGetCookieValue(HttpRequestData request, string key, [NotNullWhen(true)] out string? value) { - throw new NotImplementedException(); + value = request.Cookies.FirstOrDefault(c => c.Name == key)?.Value; + return value is not null; } /// protected override bool TryGetHeaderValue(HttpRequestData request, string key, [NotNullWhen(true)] out string? value) { - throw new NotImplementedException(); + if (request.Headers.TryGetValues(key, out IEnumerable? values)) + { + value = values.First(); + return true; + } + + value = null; + return false; } /// protected override bool TryGetQueryValue(HttpRequestData request, string key, [NotNullWhen(true)] out string? value) { - throw new NotImplementedException(); + NameValueCollection queryStringCollection = HttpUtility.ParseQueryString(request.Url.Query); + value = queryStringCollection[key]; + return value is not null; } } \ No newline at end of file diff --git a/Solutions/Menes.Hosting.FunctionsWorker/Menes/Hosting/AzureFunctionsWorker/PocoHttpResponseDataOutputBuilder.cs b/Solutions/Menes.Hosting.FunctionsWorker/Menes/Hosting/AzureFunctionsWorker/PocoHttpResponseDataOutputBuilder.cs index 50da54f9..f16baa4e 100644 --- a/Solutions/Menes.Hosting.FunctionsWorker/Menes/Hosting/AzureFunctionsWorker/PocoHttpResponseDataOutputBuilder.cs +++ b/Solutions/Menes.Hosting.FunctionsWorker/Menes/Hosting/AzureFunctionsWorker/PocoHttpResponseDataOutputBuilder.cs @@ -47,10 +47,11 @@ protected override IHttpResponseDataResult FromPoco( /// protected override bool CanConstructFrom( - OpenApiResult openApiResult, + object result, OpenApiOperation operation, + IEnumerable converters, ILogger logger) { - return OpenApiHttpResponseDataResult.CanConstructFrom(openApiResult, operation, logger); + return OpenApiHttpResponseDataResult.CanConstructFrom(result, operation, logger); } } \ No newline at end of file diff --git a/Solutions/Menes.Hosting.FunctionsWorker/Microsoft/Extensions/DependencyInjection/OpenApiAzureFunctionsWorkerHostingServiceCollectionExtensions.cs b/Solutions/Menes.Hosting.FunctionsWorker/Microsoft/Extensions/DependencyInjection/OpenApiAzureFunctionsWorkerHostingServiceCollectionExtensions.cs new file mode 100644 index 00000000..1afe1759 --- /dev/null +++ b/Solutions/Menes.Hosting.FunctionsWorker/Microsoft/Extensions/DependencyInjection/OpenApiAzureFunctionsWorkerHostingServiceCollectionExtensions.cs @@ -0,0 +1,47 @@ +// +// Copyright (c) Endjin Limited. All rights reserved. +// + +namespace Microsoft.Extensions.DependencyInjection; + +using System; + +using Menes; +using Menes.Hosting.AzureFunctionsWorker; +using Menes.Internal; + +using Microsoft.Azure.Functions.Worker.Http; + +/// +/// Extensions to register open api request hosting for and . +/// +public static class OpenApiAzureFunctionsWorkerHostingServiceCollectionExtensions +{ + /// + /// Adds / middleware-based hosting. + /// + /// The type of the OpenApi context. + /// The service collection to configure. + /// A function to configure the host. + /// A function to configure the environment. + /// The configured service collection. + public static IServiceCollection AddOpenApiAzureFunctionsWorkerHosting( + this IServiceCollection services, + Action? configureHost, + Action? configureEnvironment = null) + where TContext : class, IOpenApiContext, new() + { + services.AddSingleton, PocoHttpResponseDataOutputBuilder>(); + services.AddSingleton, OpenApiResultHttpResponseDataOutputBuilder>(); + services.AddSingleton, OpenApiHttpResponseDataResultBuilder>(); + + services.AddSingleton, OpenApiContextBuilder>(); + services.AddSingleton, HttpRequestDataParameterBuilder>(); + + services.AddOpenApiHosting( + configureHost, + configureEnvironment); + + return services; + } +} \ No newline at end of file diff --git a/Solutions/Menes.Hosting/Menes/Internal/PocoOutputBuilder.cs b/Solutions/Menes.Hosting/Menes/Internal/PocoOutputBuilder.cs index 9e8ae22b..769e7f13 100644 --- a/Solutions/Menes.Hosting/Menes/Internal/PocoOutputBuilder.cs +++ b/Solutions/Menes.Hosting/Menes/Internal/PocoOutputBuilder.cs @@ -22,7 +22,7 @@ internal abstract class PocoOutputBuilder : IResponseOutputBuilder - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The open API converters to use with the builder. /// The logger for the output builder. @@ -67,7 +67,7 @@ public TResponse BuildOutput(object result, OpenApiOperation operation) /// public bool CanBuildOutput(object result, OpenApiOperation operation) { - return result is not OpenApiResult && OpenApiHttpResponseResult.CanConstructFrom(result, operation, this.logger); + return result is not OpenApiResult && this.CanConstructFrom(result, operation, this.converters, this.logger); } /// @@ -85,15 +85,17 @@ protected abstract TResponse FromPoco( ILogger logger); /// - /// Determines if the action result can be constructed from the provided result and operation definition. + /// Determines if the resposne can be constructed from the provided result and operation definition. /// - /// The . + /// The POCO result. /// The OpenAPI operation definition. + /// The OpenAPI converters to use. /// A logger for the operation. /// True if an action result can be constructed from this operation result. protected abstract bool CanConstructFrom( - OpenApiResult openApiResult, + object result, OpenApiOperation operation, + IEnumerable converters, ILogger logger); } } \ No newline at end of file diff --git a/Solutions/Menes.Specs/Bindings/MenesContainerBindings.cs b/Solutions/Menes.Specs/Bindings/MenesContainerBindings.cs index 6b8562a5..255d68b5 100644 --- a/Solutions/Menes.Specs/Bindings/MenesContainerBindings.cs +++ b/Solutions/Menes.Specs/Bindings/MenesContainerBindings.cs @@ -41,11 +41,17 @@ public static void InitializeContainer(ScenarioContext scenarioContext) serviceCollection.AddLogging(configure => configure.SetMinimumLevel(LogLevel.Debug).AddProvider(new DummyLogger())); serviceCollection.AddOpenApiAspNetPipelineHosting( null, - config => - { - config.DiscriminatedTypes.Add("registeredDiscriminatedType1", typeof(RegisteredDiscriminatedType1)); - config.DiscriminatedTypes.Add("registeredDiscriminatedType2", typeof(RegisteredDiscriminatedType2)); - }); + ConfigureOpenApi); + serviceCollection.AddOpenApiAzureFunctionsWorkerHosting( + null, + ConfigureOpenApi); + + static void ConfigureOpenApi(IOpenApiConfiguration config) + { + config.DiscriminatedTypes.Add("registeredDiscriminatedType1", typeof(RegisteredDiscriminatedType1)); + config.DiscriminatedTypes.Add("registeredDiscriminatedType2", typeof(RegisteredDiscriminatedType2)); + } + serviceCollection.AddContent(cf => { cf.RegisterTransientContent(); @@ -73,7 +79,9 @@ public void Dispose() private class Logger : ILogger, IDisposable { - public IDisposable BeginScope(TState state) => this; + public IDisposable? BeginScope(TState state) + where TState : notnull + => this; public void Dispose() { diff --git a/Solutions/Menes.Specs/Features/ParameterBuilders/HttpRequestDataParameterBuilder.feature b/Solutions/Menes.Specs/Features/ParameterBuilders/HttpRequestDataParameterBuilder.feature new file mode 100644 index 00000000..33e43401 --- /dev/null +++ b/Solutions/Menes.Specs/Features/ParameterBuilders/HttpRequestDataParameterBuilder.feature @@ -0,0 +1,41 @@ +@perScenarioContainer + +Feature: Azure Functions HttpRequestData Parameter Builder + In order to implement a web API that can run in Azure Functions with the isolated worker model + As a developer + I want Menes to be able to extract inputs from requests wrapped as HttpRequestData + +Scenario: Body + Given I have constructed the OpenAPI specification with a request body of type array, containing items of type 'integer' + When I try to parse the value '[1,2,3,4,5]' as the HttpRequestData body + Then the parameter body should be [1,2,3,4,5] of type System.String + +Scenario Outline: Cookie + Given I have constructed the OpenAPI specification with a cookie parameter with name 'openApiBoolean', type 'boolean', and format '' + When I try to parse the value '' as the cookie 'openApiBoolean' in an HttpRequestData + Then the parameter openApiBoolean should be of type System.Boolean + + Examples: + | Value | ExpectedValue | + | true | true | + | false | false | + +Scenario Outline: Header + Given I have constructed the OpenAPI specification with a header parameter with name 'openApiBoolean', type 'boolean', and format '' + When I try to parse the value '' as the header 'openApiBoolean' in an HttpRequestData + Then the parameter openApiBoolean should be of type System.Boolean + + Examples: + | Value | ExpectedValue | + | true | true | + | false | false | + +Scenario Outline: Query + Given I have constructed the OpenAPI specification with a query parameter with name 'openApiBoolean', type 'boolean', and format '' + When I try to parse the value '' as the query parameter 'openApiBoolean' in an HttpRequestData + Then the parameter openApiBoolean should be of type System.Boolean + + Examples: + | Value | ExpectedValue | + | true | true | + | false | false | diff --git a/Solutions/Menes.Specs/Menes.Specs.csproj b/Solutions/Menes.Specs/Menes.Specs.csproj index df087c8c..c807162b 100644 --- a/Solutions/Menes.Specs/Menes.Specs.csproj +++ b/Solutions/Menes.Specs/Menes.Specs.csproj @@ -2,7 +2,7 @@ - net6.0 + net7.0 enable false Menes.Specs @@ -62,32 +62,23 @@ + + Always - - - StringOutputParsing.feature - - PreserveNewest - - - $(UsingMicrosoftNETSdk) - %(RelativeDir)%(Filename).feature$(DefaultLanguageSourceExtension) - - \ No newline at end of file diff --git a/Solutions/Menes.Specs/Steps/HttpRequestDataSteps.cs b/Solutions/Menes.Specs/Steps/HttpRequestDataSteps.cs new file mode 100644 index 00000000..66a074c7 --- /dev/null +++ b/Solutions/Menes.Specs/Steps/HttpRequestDataSteps.cs @@ -0,0 +1,155 @@ +// +// Copyright (c) Endjin Limited. All rights reserved. +// + +namespace Menes.Specs.Steps; + +using System; +using System.Collections.Generic; +using System.IO; +using System.Security.Claims; +using System.Text; +using System.Threading.Tasks; + +using Corvus.Testing.SpecFlow; + +using Microsoft.Azure.Functions.Worker; +using Microsoft.Azure.Functions.Worker.Http; +using Microsoft.Extensions.DependencyInjection; + +using TechTalk.SpecFlow; + +[Binding] +public class HttpRequestDataSteps +{ + private readonly IServiceProvider serviceProvider; + private readonly FakeFunctionContext ctx; + private readonly OpenApiParameterParsingSteps openApiSteps; + private FakeHttpRequestData request; + + public HttpRequestDataSteps( + ScenarioContext scenarioContext, + OpenApiParameterParsingSteps openApiSteps) + { + this.serviceProvider = ContainerBindings.GetServiceProvider(scenarioContext); + this.ctx = new(); + this.request = new FakeHttpRequestData(this.ctx, new Uri("https://example.com/pets"), "GET"); + this.openApiSteps = openApiSteps; + } + + [When("I try to parse the value '([^']*)' as the HttpRequestData body")] + public async Task WhenITryToParseTheValueAsTheHttpRequestDataBody(string body) + { + IOpenApiParameterBuilder builder = this.serviceProvider.GetRequiredService>(); + this.openApiSteps.Matcher.FindOperationPathTemplate("/pets", "POST", out OpenApiOperationPathTemplate? operationPathTemplate); + + this.request.Headers.Add("Content-Type", "application/json"); + using (StreamWriter w = new(this.request.Body, Encoding.UTF8, leaveOpen: true)) + { + w.Write(body); + } + + this.request.Body.Position = 0; + + this.openApiSteps.Parameters = await builder.BuildParametersAsync(this.request, operationPathTemplate!).ConfigureAwait(false); + } + + [When("I try to parse the value '([^']*)' as the cookie '([^']*)' in an HttpRequestData")] + public async Task WhenITryToParseTheValueAsTheCookieInAnHttpRequestData(string cookieValue, string cookieName) + { + IOpenApiParameterBuilder builder = this.serviceProvider.GetRequiredService>(); + this.openApiSteps.Matcher.FindOperationPathTemplate("/pets", "GET", out OpenApiOperationPathTemplate? operationPathTemplate); + + this.request.WritableCookies.Add(new HttpCookie(cookieName, cookieValue)); + + this.openApiSteps.Parameters = await builder.BuildParametersAsync(this.request, operationPathTemplate!).ConfigureAwait(false); + } + + [When("I try to parse the value '([^']*)' as the header '([^']*)' in an HttpRequestData")] + public async Task WhenITryToParseTheValueAsTheHeaderInAnHttpRequestData(string headerValue, string headerName) + { + IOpenApiParameterBuilder builder = this.serviceProvider.GetRequiredService>(); + this.openApiSteps.Matcher.FindOperationPathTemplate("/pets", "GET", out OpenApiOperationPathTemplate? operationPathTemplate); + + this.request.Headers.Add(headerName, headerValue); + + this.openApiSteps.Parameters = await builder.BuildParametersAsync(this.request, operationPathTemplate!).ConfigureAwait(false); + } + + [When("I try to parse the value '([^']*)' as the query parameter '([^']*)' in an HttpRequestData")] + public async Task WhenITryToParseTheValueAsTheQueryParameterInAnHttpRequestData(string headerValue, string headerName) + { + IOpenApiParameterBuilder builder = this.serviceProvider.GetRequiredService>(); + this.openApiSteps.Matcher.FindOperationPathTemplate("/pets", "GET", out OpenApiOperationPathTemplate? operationPathTemplate); + + this.request = new FakeHttpRequestData( + this.ctx, + new Uri($"https://example.com/pets?{headerName}={headerValue}"), + "GET"); + + this.openApiSteps.Parameters = await builder.BuildParametersAsync(this.request, operationPathTemplate!).ConfigureAwait(false); + } + + private class FakeHttpRequestData : HttpRequestData + { + public FakeHttpRequestData( + FunctionContext functionContext, + Uri url, + string method) + : base(functionContext) + { + this.Url = url; + this.Method = method; + } + + public override Stream Body { get; } = new MemoryStream(); + + public override HttpHeadersCollection Headers { get; } = new(); + + public override IReadOnlyCollection Cookies => this.WritableCookies; + + public List WritableCookies { get; } = new List(); + + public override Uri Url { get; } + + public override IEnumerable Identities { get; } = new List(); + + public override string Method { get; } + + public override HttpResponseData CreateResponse() + { + throw new NotImplementedException(); + } + } + + // Even though the test doesn't really do anything with this, it has to exist for us + // to be able to construct our HttpRequestData implementation. + private class FakeFunctionContext : FunctionContext + { + public override string InvocationId => throw new NotImplementedException(); + + public override string FunctionId => throw new NotImplementedException(); + + public override TraceContext TraceContext => throw new NotImplementedException(); + + public override BindingContext BindingContext => throw new NotImplementedException(); + + public override RetryContext RetryContext => throw new NotImplementedException(); + + public override IServiceProvider InstanceServices + { + get => throw new NotImplementedException(); + set => throw new NotImplementedException(); + } + + public override FunctionDefinition FunctionDefinition => throw new NotImplementedException(); + + public override IDictionary Items + { + get => throw new NotImplementedException(); + set => throw new NotImplementedException(); + } + + public override IInvocationFeatures Features => throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/Solutions/Menes.Specs/Steps/OpenApiParameterParsingSteps.cs b/Solutions/Menes.Specs/Steps/OpenApiParameterParsingSteps.cs index 249c06b0..2666ae99 100644 --- a/Solutions/Menes.Specs/Steps/OpenApiParameterParsingSteps.cs +++ b/Solutions/Menes.Specs/Steps/OpenApiParameterParsingSteps.cs @@ -34,7 +34,6 @@ public class OpenApiParameterParsingSteps { private readonly ScenarioContext scenarioContext; private IPathMatcher? matcher; - private IDictionary? parameters; private Exception? exception; private string? responseBody; private IHeaderDictionary? responseHeaders; @@ -44,7 +43,9 @@ public OpenApiParameterParsingSteps(ScenarioContext scenarioContext) this.scenarioContext = scenarioContext; } - private IPathMatcher Matcher => this.matcher ?? throw new InvalidOperationException("Matcher not set - test must first create a fake OpenAPI spec"); + public IPathMatcher Matcher => this.matcher ?? throw new InvalidOperationException("Matcher not set - test must first create a fake OpenAPI spec"); + + public IDictionary? Parameters { get; set; } [Given("I have constructed the OpenAPI specification with a request body of type '([^']*)', and format '([^']*)'")] public void GivenIHaveConstructedTheOpenAPISpecificationWithARequestBodyOfTypeAndFormat( @@ -882,7 +883,7 @@ public async Task WhenITryToParseTheDefaultValueAsync() var context = new DefaultHttpContext(); - this.parameters = await builder.BuildParametersAsync(context.Request, operationPathTemplate!).ConfigureAwait(false); + this.Parameters = await builder.BuildParametersAsync(context.Request, operationPathTemplate!).ConfigureAwait(false); } [When("I try to parse the default value and expect an error")] @@ -909,7 +910,7 @@ public async Task WhenITryToParseTheValueAsTheRequestBody(string body) context.Request.Body = new MemoryStream(Encoding.UTF8.GetBytes(body)); context.Request.Headers.ContentType = "application/json"; - this.parameters = await builder.BuildParametersAsync(context.Request, operationPathTemplate!).ConfigureAwait(false); + this.Parameters = await builder.BuildParametersAsync(context.Request, operationPathTemplate!).ConfigureAwait(false); } [When("I try to parse the value '(.*?)' as the request body and expect an error")] @@ -938,7 +939,7 @@ public async Task WhenITryToParseThePathValue(string value, string unusedParamet DefaultHttpContext context = new(); context.Request.Path = path; - this.parameters = await builder.BuildParametersAsync(context.Request, operationPathTemplate!).ConfigureAwait(false); + this.Parameters = await builder.BuildParametersAsync(context.Request, operationPathTemplate!).ConfigureAwait(false); } [When("I try to parse the path value '(.*?)' as the parameter '([^']*)' and expect an error")] @@ -969,7 +970,7 @@ public async Task WhenITryToParseTheQueryValue( { parameterName, value }, }); - this.parameters = await builder.BuildParametersAsync(context.Request, operationPathTemplate!).ConfigureAwait(false); + this.Parameters = await builder.BuildParametersAsync(context.Request, operationPathTemplate!).ConfigureAwait(false); } [When("I try to parse the query value '([^']*)' as the parameter '([^']*)' and expect an error")] @@ -999,7 +1000,7 @@ public async Task WhenITryToParseTheHeaderValue( var context = new DefaultHttpContext(); context.Request.Headers.Add(parameterName, value); - this.parameters = await builder.BuildParametersAsync(context.Request, operationPathTemplate!).ConfigureAwait(false); + this.Parameters = await builder.BuildParametersAsync(context.Request, operationPathTemplate!).ConfigureAwait(false); } [When("I try to parse the header value '([^']*)' as the parameter '([^']*)' and expect an error")] @@ -1032,7 +1033,7 @@ public async Task WhenITryToParseTheCookieValue( { parameterName, value }, }; - this.parameters = await builder.BuildParametersAsync(context.Request, operationPathTemplate!).ConfigureAwait(false); + this.Parameters = await builder.BuildParametersAsync(context.Request, operationPathTemplate!).ConfigureAwait(false); } [When("I try to parse the cookie value '([^']*)' as the parameter '([^']*)' and expect an error")] @@ -1093,14 +1094,14 @@ public void ThenTheParameterShouldBe(string parameterName, string expectedResult { object expectedResult = GetResultFromStringAndType(expectedResultAsString, expectedType); - Assert.AreEqual(expectedResult, this.parameters![parameterName]); - Assert.AreEqual(expectedResult.GetType(), this.parameters![parameterName]!.GetType()); + Assert.AreEqual(expectedResult, this.Parameters![parameterName]); + Assert.AreEqual(expectedResult.GetType(), this.Parameters![parameterName]!.GetType()); } [Then("the parameter (.*?) should be of type '([^']*)'")] public void ThenTheParameterBodyShouldBeOfType(string parameterName, string expectedType) { - Assert.AreEqual(expectedType, this.parameters![parameterName].GetType().Name); + Assert.AreEqual(expectedType, this.Parameters![parameterName].GetType().Name); } [Then("an '(.*)' should be thrown")] @@ -1182,7 +1183,7 @@ public void Add(string key, string value) public IEnumerator> GetEnumerator() => this.cookies.GetEnumerator(); - public bool TryGetValue(string key, [MaybeNullWhen(false)] out string? value) => this.cookies.TryGetValue(key, out value); + public bool TryGetValue(string key, [NotNullWhen(true)] out string? value) => this.cookies.TryGetValue(key, out value); IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); } diff --git a/Solutions/Menes.Specs/packages.lock.json b/Solutions/Menes.Specs/packages.lock.json index 1b362e12..a02b70c2 100644 --- a/Solutions/Menes.Specs/packages.lock.json +++ b/Solutions/Menes.Specs/packages.lock.json @@ -1,7 +1,7 @@ { "version": 1, "dependencies": { - "net6.0": { + "net7.0": { "Corvus.Testing.SpecFlow.NUnit": { "type": "Direct", "requested": "[2.0.0, )", @@ -62,6 +62,21 @@ "StyleCop.Analyzers.Unstable": "1.2.0.435" } }, + "Azure.Core": { + "type": "Transitive", + "resolved": "1.10.0", + "contentHash": "iyliCDiwhYNJ5XOoyxzVl896+jvbIolYnk9SMn2JkjeXOBZItudgzdZ7lFIN4wkNl2JQBkUFE3jPYHhsmakhnw==", + "dependencies": { + "Microsoft.Bcl.AsyncInterfaces": "1.0.0", + "System.Buffers": "4.5.0", + "System.Diagnostics.DiagnosticSource": "4.6.0", + "System.Memory": "4.5.3", + "System.Memory.Data": "1.0.1", + "System.Numerics.Vectors": "4.5.0", + "System.Text.Json": "4.6.0", + "System.Threading.Tasks.Extensions": "4.5.2" + } + }, "BoDi": { "type": "Transitive", "resolved": "1.5.0", @@ -132,6 +147,22 @@ "resolved": "19.0.3", "contentHash": "kq9feqMojMj9aABrHb/ABEPaH2Y4dSclseSahAru6qxCeqVQNLLTgw/6vZMauzI1yBUL2fz03ub5yEd5btLfvg==" }, + "Microsoft.Azure.Functions.Worker.Core": { + "type": "Transitive", + "resolved": "1.8.0", + "contentHash": "8awXnik71Og7WAKUQLhPAatm236Icf9XAu0oerNCGjfAVjIF6o5U1M7id9V368ZvCsEXN3Ejq0sGRAEzjt/t6A==", + "dependencies": { + "Azure.Core": "1.10.0", + "Microsoft.Extensions.Hosting": "5.0.0", + "Microsoft.Extensions.Hosting.Abstractions": "5.0.0", + "System.Collections.Immutable": "5.0.0" + } + }, + "Microsoft.Bcl.AsyncInterfaces": { + "type": "Transitive", + "resolved": "1.0.0", + "contentHash": "K63Y4hORbBcKLWH5wnKgzyn7TOfYzevIEwIedQHBIkmkEBA9SCqgvom+XTuE+fAFGvINGkhFItaZ2dvMGdT5iw==" + }, "Microsoft.Build.Tasks.Git": { "type": "Transitive", "resolved": "1.1.1", @@ -189,6 +220,15 @@ "System.Runtime.InteropServices.RuntimeInformation": "4.0.0" } }, + "Microsoft.Extensions.Configuration": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "LN322qEKHjuVEhhXueTUe7RNePooZmS8aGid5aK2woX3NPjSnONFyKUc6+JknOS6ce6h2tCLfKPTBXE3mN/6Ag==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "5.0.0", + "Microsoft.Extensions.Primitives": "5.0.0" + } + }, "Microsoft.Extensions.Configuration.Abstractions": { "type": "Transitive", "resolved": "6.0.0", @@ -197,6 +237,66 @@ "Microsoft.Extensions.Primitives": "6.0.0" } }, + "Microsoft.Extensions.Configuration.Binder": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "Of1Irt1+NzWO+yEYkuDh5TpT4On7LKl98Q9iLqCdOZps6XXEWDj3AKtmyvzJPVXZe4apmkJJIiDL7rR1yC+hjQ==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "5.0.0" + } + }, + "Microsoft.Extensions.Configuration.CommandLine": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "OelM+VQdhZ0XMXsEQBq/bt3kFzD+EBGqR4TAgFDRAye0JfvHAaRi+3BxCRcwqUAwDhV0U0HieljBGHlTgYseRA==", + "dependencies": { + "Microsoft.Extensions.Configuration": "5.0.0", + "Microsoft.Extensions.Configuration.Abstractions": "5.0.0" + } + }, + "Microsoft.Extensions.Configuration.EnvironmentVariables": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "fqh6y6hAi0Z0fRsb4B/mP9OkKkSlifh5osa+N/YSQ+/S2a//+zYApZMUC1XeP9fdjlgZoPQoZ72Q2eLHyKLddQ==", + "dependencies": { + "Microsoft.Extensions.Configuration": "5.0.0", + "Microsoft.Extensions.Configuration.Abstractions": "5.0.0" + } + }, + "Microsoft.Extensions.Configuration.FileExtensions": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "rRdspYKA18ViPOISwAihhCMbusHsARCOtDMwa23f+BGEdIjpKPlhs3LLjmKlxfhpGXBjIsS0JpXcChjRUN+PAw==", + "dependencies": { + "Microsoft.Extensions.Configuration": "5.0.0", + "Microsoft.Extensions.Configuration.Abstractions": "5.0.0", + "Microsoft.Extensions.FileProviders.Abstractions": "5.0.0", + "Microsoft.Extensions.FileProviders.Physical": "5.0.0", + "Microsoft.Extensions.Primitives": "5.0.0" + } + }, + "Microsoft.Extensions.Configuration.Json": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "Pak8ymSUfdzPfBTLHxeOwcR32YDbuVfhnH2hkfOLnJNQd19ItlBdpMjIDY9C5O/nS2Sn9bzDMai0ZrvF7KyY/Q==", + "dependencies": { + "Microsoft.Extensions.Configuration": "5.0.0", + "Microsoft.Extensions.Configuration.Abstractions": "5.0.0", + "Microsoft.Extensions.Configuration.FileExtensions": "5.0.0", + "Microsoft.Extensions.FileProviders.Abstractions": "5.0.0" + } + }, + "Microsoft.Extensions.Configuration.UserSecrets": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "+tK3seG68106lN277YWQvqmfyI/89w0uTu/5Gz5VYSUu5TI4mqwsaWLlSmT9Bl1yW/i1Nr06gHJxqaqB5NU9Tw==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "5.0.0", + "Microsoft.Extensions.Configuration.Json": "5.0.0", + "Microsoft.Extensions.FileProviders.Abstractions": "5.0.0", + "Microsoft.Extensions.FileProviders.Physical": "5.0.0" + } + }, "Microsoft.Extensions.DependencyInjection": { "type": "Transitive", "resolved": "6.0.1", @@ -211,6 +311,67 @@ "resolved": "6.0.0", "contentHash": "xlzi2IYREJH3/m6+lUrQlujzX8wDitm4QGnUu6kUXTQAWPuZY8i+ticFJbzfqaetLA6KR/rO6Ew/HuYD+bxifg==" }, + "Microsoft.Extensions.FileProviders.Abstractions": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "iuZIiZ3mteEb+nsUqpGXKx2cGF+cv6gWPd5jqQI4hzqdiJ6I94ddLjKhQOuRW1lueHwocIw30xbSHGhQj0zjdQ==", + "dependencies": { + "Microsoft.Extensions.Primitives": "5.0.0" + } + }, + "Microsoft.Extensions.FileProviders.Physical": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "1rkd8UO2qf21biwO7X0hL9uHP7vtfmdv/NLvKgCRHkdz1XnW8zVQJXyEYiN68WYpExgtVWn55QF0qBzgfh1mGg==", + "dependencies": { + "Microsoft.Extensions.FileProviders.Abstractions": "5.0.0", + "Microsoft.Extensions.FileSystemGlobbing": "5.0.0", + "Microsoft.Extensions.Primitives": "5.0.0" + } + }, + "Microsoft.Extensions.FileSystemGlobbing": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "ArliS8lGk8sWRtrWpqI8yUVYJpRruPjCDT+EIjrgkA/AAPRctlAkRISVZ334chAKktTLzD1+PK8F5IZpGedSqA==" + }, + "Microsoft.Extensions.Hosting": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "hiokSU1TOVfcqpQAnpiOzP2rE9p+niq92g5yeAnwlbSrUlIdIS6M8emCknZvhdOagQA9x5YWNwe1n0kFUwE0NQ==", + "dependencies": { + "Microsoft.Extensions.Configuration": "5.0.0", + "Microsoft.Extensions.Configuration.Abstractions": "5.0.0", + "Microsoft.Extensions.Configuration.Binder": "5.0.0", + "Microsoft.Extensions.Configuration.CommandLine": "5.0.0", + "Microsoft.Extensions.Configuration.EnvironmentVariables": "5.0.0", + "Microsoft.Extensions.Configuration.FileExtensions": "5.0.0", + "Microsoft.Extensions.Configuration.Json": "5.0.0", + "Microsoft.Extensions.Configuration.UserSecrets": "5.0.0", + "Microsoft.Extensions.DependencyInjection": "5.0.0", + "Microsoft.Extensions.DependencyInjection.Abstractions": "5.0.0", + "Microsoft.Extensions.FileProviders.Abstractions": "5.0.0", + "Microsoft.Extensions.FileProviders.Physical": "5.0.0", + "Microsoft.Extensions.Hosting.Abstractions": "5.0.0", + "Microsoft.Extensions.Logging": "5.0.0", + "Microsoft.Extensions.Logging.Abstractions": "5.0.0", + "Microsoft.Extensions.Logging.Configuration": "5.0.0", + "Microsoft.Extensions.Logging.Console": "5.0.0", + "Microsoft.Extensions.Logging.Debug": "5.0.0", + "Microsoft.Extensions.Logging.EventLog": "5.0.0", + "Microsoft.Extensions.Logging.EventSource": "5.0.0", + "Microsoft.Extensions.Options": "5.0.0" + } + }, + "Microsoft.Extensions.Hosting.Abstractions": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "cbUOCePYBl1UhM+N2zmDSUyJ6cODulbtUd9gEzMFIK3RQDtP/gJsE08oLcBSXH3Q1RAQ0ex7OAB3HeTKB9bXpg==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "5.0.0", + "Microsoft.Extensions.DependencyInjection.Abstractions": "5.0.0", + "Microsoft.Extensions.FileProviders.Abstractions": "5.0.0" + } + }, "Microsoft.Extensions.Logging": { "type": "Transitive", "resolved": "6.0.0", @@ -228,6 +389,69 @@ "resolved": "6.0.0", "contentHash": "/HggWBbTwy8TgebGSX5DBZ24ndhzi93sHUBDvP1IxbZD7FDokYzdAr6+vbWGjw2XAfR2EJ1sfKUotpjHnFWPxA==" }, + "Microsoft.Extensions.Logging.Configuration": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "N3/d0HeMRnBekadbZlmbp+In8EvNNkQHSdbtRzjrGVckdZWpYs5GNrAfaYqVplDFW0WUedSaFJ3khB50BWYGsw==", + "dependencies": { + "Microsoft.Extensions.Configuration": "5.0.0", + "Microsoft.Extensions.Configuration.Abstractions": "5.0.0", + "Microsoft.Extensions.Configuration.Binder": "5.0.0", + "Microsoft.Extensions.DependencyInjection.Abstractions": "5.0.0", + "Microsoft.Extensions.Logging": "5.0.0", + "Microsoft.Extensions.Logging.Abstractions": "5.0.0", + "Microsoft.Extensions.Options": "5.0.0", + "Microsoft.Extensions.Options.ConfigurationExtensions": "5.0.0" + } + }, + "Microsoft.Extensions.Logging.Console": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "jH0wbWhfvXjOVmCkbra4vbiovDtTUIWLQjCeJ7Xun3h4AHvwfzm7V7wlsXKs3tNnPrsCxZ9oaV0vUAgGY1JxOA==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "5.0.0", + "Microsoft.Extensions.DependencyInjection.Abstractions": "5.0.0", + "Microsoft.Extensions.Logging": "5.0.0", + "Microsoft.Extensions.Logging.Abstractions": "5.0.0", + "Microsoft.Extensions.Logging.Configuration": "5.0.0", + "Microsoft.Extensions.Options": "5.0.0", + "Microsoft.Extensions.Options.ConfigurationExtensions": "5.0.0" + } + }, + "Microsoft.Extensions.Logging.Debug": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "9dvt0xqRrClvhaPNpfyS39WxnW9G55l5lrV5ZX7IrEgwo4VwtmJKtoPiKVYKbhAuOBGUI5WY3hWLvF+PSbJp5A==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "5.0.0", + "Microsoft.Extensions.Logging": "5.0.0", + "Microsoft.Extensions.Logging.Abstractions": "5.0.0" + } + }, + "Microsoft.Extensions.Logging.EventLog": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "CYzsgF2lqgahGl/HuErsIDaZZ9ueN+MBjGfO/0jVDLPaXLaywxlGKFpDgXMaB053DRYZwD1H2Lb1I60mTXS3jg==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "5.0.0", + "Microsoft.Extensions.Logging": "5.0.0", + "Microsoft.Extensions.Logging.Abstractions": "5.0.0", + "Microsoft.Extensions.Options": "5.0.0", + "System.Diagnostics.EventLog": "5.0.0" + } + }, + "Microsoft.Extensions.Logging.EventSource": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "hF+D6PJkrM0qXcSEGs1BwZwgP8c0BRkj26P/5wmYTcHKOp52GRey/Z/YKRmRIHIrXxj9tz/JgIjU9oWmiJ5HMw==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "5.0.0", + "Microsoft.Extensions.Logging": "5.0.0", + "Microsoft.Extensions.Logging.Abstractions": "5.0.0", + "Microsoft.Extensions.Options": "5.0.0", + "Microsoft.Extensions.Primitives": "5.0.0" + } + }, "Microsoft.Extensions.Options": { "type": "Transitive", "resolved": "6.0.0", @@ -237,6 +461,18 @@ "Microsoft.Extensions.Primitives": "6.0.0" } }, + "Microsoft.Extensions.Options.ConfigurationExtensions": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "280RxNJqOeQqq47aJLy5D9LN61CAWeuRA83gPToQ8B9jl9SNdQ5EXjlfvF66zQI5AXMl+C/3hGnbtIEN+X3mqA==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "5.0.0", + "Microsoft.Extensions.Configuration.Binder": "5.0.0", + "Microsoft.Extensions.DependencyInjection.Abstractions": "5.0.0", + "Microsoft.Extensions.Options": "5.0.0", + "Microsoft.Extensions.Primitives": "5.0.0" + } + }, "Microsoft.Extensions.Primitives": { "type": "Transitive", "resolved": "6.0.0", @@ -879,6 +1115,14 @@ "resolved": "4.5.5", "contentHash": "XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw==" }, + "System.Memory.Data": { + "type": "Transitive", + "resolved": "1.0.1", + "contentHash": "ujvrOjcni2QQbr6hG2AkUTWLb/xplrx0mt6HrdHFCzzGky2d5J6YD60TKAEf8SBk33cfSzTvFmXewAVaPY/dZg==", + "dependencies": { + "System.Text.Json": "4.6.0" + } + }, "System.Net.Http": { "type": "Transitive", "resolved": "4.3.4", @@ -923,6 +1167,11 @@ "System.Runtime.Handles": "4.3.0" } }, + "System.Numerics.Vectors": { + "type": "Transitive", + "resolved": "4.5.0", + "contentHash": "QQTlPTl06J/iiDbJCiepZ4H//BVraReU4O4EoRw1U02H5TLUIT7xn3GnDp9AXPSlJUDyFs4uWjWafNX6WrAojQ==" + }, "System.Reflection": { "type": "Transitive", "resolved": "4.3.0", @@ -1436,6 +1685,13 @@ "dependencies": { "Menes.Hosting": "[1.0.0, )" } + }, + "menes.hosting.azurefunctionsworker": { + "type": "Project", + "dependencies": { + "Menes.Hosting": "[1.0.0, )", + "Microsoft.Azure.Functions.Worker.Core": "[1.8.0, )" + } } } }