From bd0de41590a3058bd312ea5e2b0aac3b367bc20f Mon Sep 17 00:00:00 2001 From: Talen Fisher Date: Thu, 18 Nov 2021 11:26:50 -0600 Subject: [PATCH 1/2] LAMBJ-142 Nullable Custom Resource ResponseURL --- .../CustomResourceLambdaHost.cs | 23 ++++---- src/CustomResource/CustomResourceRequest.cs | 2 +- .../CustomResourceLambdaHostTests.cs | 56 +++++++++++++++---- 3 files changed, 60 insertions(+), 21 deletions(-) diff --git a/src/CustomResource/CustomResourceLambdaHost.cs b/src/CustomResource/CustomResourceLambdaHost.cs index 66628fa4..d1c38790 100644 --- a/src/CustomResource/CustomResourceLambdaHost.cs +++ b/src/CustomResource/CustomResourceLambdaHost.cs @@ -15,7 +15,7 @@ namespace Lambdajection.CustomResource { /// public sealed class CustomResourceLambdaHost - : LambdaHostBase, TLambdaOutput, TLambdaStartup, TLambdaConfigurator, TLambdaConfigFactory> + : LambdaHostBase, CustomResourceResponse, TLambdaStartup, TLambdaConfigurator, TLambdaConfigFactory> where TLambda : class, ICustomResourceProvider where TLambdaParameter : class where TLambdaOutput : class, ICustomResourceOutputData @@ -35,13 +35,13 @@ public CustomResourceLambdaHost() /// Initializes a new instance of the class. /// /// The builder action to run on the lambda. - internal CustomResourceLambdaHost(Action, TLambdaOutput, TLambdaStartup, TLambdaConfigurator, TLambdaConfigFactory>> build) + internal CustomResourceLambdaHost(Action, CustomResourceResponse, TLambdaStartup, TLambdaConfigurator, TLambdaConfigFactory>> build) : base(build) { } /// - public override async Task InvokeLambda( + public override async Task> InvokeLambda( Stream inputStream, CancellationToken cancellationToken = default ) @@ -92,14 +92,17 @@ public override async Task InvokeLambda( }; } - await httpClient.PutJson( - requestUri: input.ResponseURL, - payload: response, - contentType: null, - cancellationToken: cancellationToken - ); + if (input.ResponseURL != null) + { + await httpClient.PutJson( + requestUri: input.ResponseURL, + payload: response, + contentType: null, + cancellationToken: cancellationToken + ); + } - return response.Data!; + return response; } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/CustomResource/CustomResourceRequest.cs b/src/CustomResource/CustomResourceRequest.cs index 1d818b5b..ac8b685d 100644 --- a/src/CustomResource/CustomResourceRequest.cs +++ b/src/CustomResource/CustomResourceRequest.cs @@ -24,7 +24,7 @@ public class CustomResourceRequest /// has been created/updated/deleted. /// /// The response URL. - public virtual Uri ResponseURL { get; set; } = new Uri("http://localhost"); + public virtual Uri? ResponseURL { get; set; } /// /// Gets or sets the Id of the CloudFormation stack that the diff --git a/tests/Unit/CustomResource/CustomResourceLambdaHostTests.cs b/tests/Unit/CustomResource/CustomResourceLambdaHostTests.cs index 5a1d9709..da53c791 100644 --- a/tests/Unit/CustomResource/CustomResourceLambdaHostTests.cs +++ b/tests/Unit/CustomResource/CustomResourceLambdaHostTests.cs @@ -196,7 +196,7 @@ [Substitute] IHttpClient httpClient await host.InvokeLambda(inputStream, cancellationToken); await httpClient.Received().PutJson( - Is(request.ResponseURL), + Is(request.ResponseURL!), Is>(response => response.Status == CustomResourceResponseStatus.Success && response.Data == data @@ -234,7 +234,7 @@ [Substitute] IHttpClient httpClient await host.InvokeLambda(inputStream, cancellationToken); await httpClient.Received().PutJson( - Is(request.ResponseURL), + Is(request.ResponseURL!), Is>(response => response.Status == CustomResourceResponseStatus.Success && response.StackId == stackId @@ -272,7 +272,7 @@ [Substitute] IHttpClient httpClient await host.InvokeLambda(inputStream, cancellationToken); await httpClient.Received().PutJson( - Is(request.ResponseURL), + Is(request.ResponseURL!), Is>(response => response.Status == CustomResourceResponseStatus.Success && response.RequestId == requestId @@ -310,7 +310,7 @@ [Substitute] IHttpClient httpClient await host.InvokeLambda(inputStream, cancellationToken); await httpClient.Received().PutJson( - Is(request.ResponseURL), + Is(request.ResponseURL!), Is>(response => response.Status == CustomResourceResponseStatus.Success && response.LogicalResourceId == logicalResourceId @@ -350,7 +350,7 @@ [Substitute] IHttpClient httpClient await host.InvokeLambda(inputStream, cancellationToken); await httpClient.Received().PutJson( - Is(request.ResponseURL), + Is(request.ResponseURL!), Is>(response => response.Status == CustomResourceResponseStatus.Success && response.PhysicalResourceId == physicalResourceId @@ -360,6 +360,42 @@ await httpClient.Received().PutJson( ); } + [Test, Auto] + public async Task ShouldNotRespondSuccessIfResponseURLIsNull( + ServiceCollection serviceCollection, + string stackId, + JsonSerializer serializer, + CustomResourceRequest request, + [Substitute] TestCustomResourceLambda lambda, + [Substitute] IHttpClient httpClient + ) + { + serviceCollection.AddSingleton(httpClient); + request.ResponseURL = null; + + var serviceProvider = serviceCollection.BuildServiceProvider(); + var cancellationToken = new CancellationToken(false); + var host = new TestCustomResourceLambdaHost(lambdaHost => + { + lambdaHost.Lambda = lambda; + lambdaHost.Scope = serviceProvider.CreateScope(); + lambdaHost.Serializer = serializer; + }); + + request.RequestType = CustomResourceRequestType.Create; + request.StackId = stackId; + + using var inputStream = await StreamUtils.CreateJsonStream(request); + await host.InvokeLambda(inputStream, cancellationToken); + + await httpClient.DidNotReceiveWithAnyArgs().PutJson( + default!, + default(CustomResourceResponse)!, + default!, + default! + ); + } + [Test, Auto] public async Task ShouldRespondFailureWithReason( string reason, @@ -393,7 +429,7 @@ [Substitute] IHttpClient httpClient await host.InvokeLambda(inputStream, cancellationToken); await httpClient.Received().PutJson( - Is(request.ResponseURL), + Is(request.ResponseURL!), Is>(response => response.Status == CustomResourceResponseStatus.Failed && response.Reason == reason @@ -506,7 +542,7 @@ [Substitute] IHttpClient httpClient await host.InvokeLambda(inputStream, cancellationToken); await httpClient.Received().PutJson( - Is(request.ResponseURL), + Is(request.ResponseURL!), Is>(response => response.Status == CustomResourceResponseStatus.Failed && response.RequestId == requestId @@ -549,7 +585,7 @@ [Substitute] IHttpClient httpClient await host.InvokeLambda(inputStream, cancellationToken); await httpClient.Received().PutJson( - Is(request.ResponseURL), + Is(request.ResponseURL!), Is>(response => response.Status == CustomResourceResponseStatus.Failed && response.StackId == stackId @@ -592,7 +628,7 @@ [Substitute] IHttpClient httpClient await host.InvokeLambda(inputStream, cancellationToken); await httpClient.Received().PutJson( - Is(request.ResponseURL), + Is(request.ResponseURL!), Is>(response => response.Status == CustomResourceResponseStatus.Failed && response.LogicalResourceId == logicalResourceId @@ -635,7 +671,7 @@ [Substitute] IHttpClient httpClient await host.InvokeLambda(inputStream, cancellationToken); await httpClient.Received().PutJson( - Is(request.ResponseURL), + Is(request.ResponseURL!), Is>(response => response.Status == CustomResourceResponseStatus.Failed && response.PhysicalResourceId == physicalResourceId From 751120297c2ef9b63928ac02ddcf63c2c082fa8f Mon Sep 17 00:00:00 2001 From: Talen Fisher Date: Thu, 18 Nov 2021 11:29:27 -0600 Subject: [PATCH 2/2] LAMBJ-142 Update Release Notes --- .github/releases/v0.9.0-beta4.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/releases/v0.9.0-beta4.md b/.github/releases/v0.9.0-beta4.md index c0b5ffeb..48975eaf 100644 --- a/.github/releases/v0.9.0-beta4.md +++ b/.github/releases/v0.9.0-beta4.md @@ -1,6 +1,8 @@ # Enhancements - CloudFormationStackEvent objects are now serializable. +- ResponseURL is now an optional argument to custom resource requests, to allow for easier testing. +- The lambda output for Custom Resources is now the full response that would've been sent to CloudFormation, rather than just the output data. # Bug Fixes