+cd ../..
+mvn clean -Pnative-image package -DskipTests
+cd infra/sam-graalvm
+sam build
+```
+
+## Deploy the sample application
+
+```shell
+sam deploy --guided --parameter-overrides BucketNameParam=my-unique-bucket-2.3.0718
+```
+
+This sample is based on Serverless Application Model (SAM). To deploy it, check out the instructions for getting started with SAM in [the examples directory](../../../README.md)
+
+## Test the application
+
+The CloudFormation custom resource will be triggered automatically during stack deployment. You can monitor the Lambda function execution in CloudWatch Logs to see the custom resource handling CREATE, UPDATE, and DELETE events for the S3 bucket.
+
+Check out [App.java](../../src/main/java/helloworld/App.java) to see how it works!
\ No newline at end of file
diff --git a/examples/powertools-examples-cloudformation/infra/sam-graalvm/template.yaml b/examples/powertools-examples-cloudformation/infra/sam-graalvm/template.yaml
new file mode 100644
index 000000000..4249aaed1
--- /dev/null
+++ b/examples/powertools-examples-cloudformation/infra/sam-graalvm/template.yaml
@@ -0,0 +1,49 @@
+AWSTemplateFormatVersion: '2010-09-09'
+Transform: AWS::Serverless-2016-10-31
+Description: >
+ powertools-examples-cloudformation-graalvm
+
+ Sample SAM Template for powertools-examples-cloudformation with GraalVM native image
+
+Globals:
+ Function:
+ Timeout: 20
+
+Parameters:
+ BucketNameParam:
+ Type: String
+
+Resources:
+ HelloWorldCustomResource:
+ Type: AWS::CloudFormation::CustomResource
+ Properties:
+ ServiceToken: !GetAtt HelloWorldFunction.Arn
+ BucketName: !Ref BucketNameParam
+
+ HelloWorldFunction:
+ Type: AWS::Serverless::Function
+ Properties:
+ CodeUri: ../../
+ Handler: helloworld.App::handleRequest
+ Runtime: provided.al2023
+ Architectures:
+ - x86_64
+ MemorySize: 512
+ Policies:
+ - Statement:
+ - Sid: bucketaccess1
+ Effect: Allow
+ Action:
+ - s3:GetLifecycleConfiguration
+ - s3:PutLifecycleConfiguration
+ - s3:CreateBucket
+ - s3:ListBucket
+ - s3:DeleteBucket
+ Resource: '*'
+ Metadata:
+ BuildMethod: makefile
+
+Outputs:
+ HelloWorldFunction:
+ Description: "Hello World Lambda Function ARN"
+ Value: !GetAtt HelloWorldFunction.Arn
\ No newline at end of file
diff --git a/examples/powertools-examples-cloudformation/pom.xml b/examples/powertools-examples-cloudformation/pom.xml
index b92f458b0..b43ac1bf3 100644
--- a/examples/powertools-examples-cloudformation/pom.xml
+++ b/examples/powertools-examples-cloudformation/pom.xml
@@ -37,9 +37,9 @@
${lambda.core.version}
- com.amazonaws
- aws-lambda-java-events
- ${lambda.events.version}
+ com.amazonaws
+ aws-lambda-java-events
+ ${lambda.events.version}
software.amazon.lambda
@@ -73,82 +73,114 @@
software.amazon.awssdk
apache-client
-
-
- commons-logging
- commons-logging
-
-
+
+
+ com.amazonaws
+ aws-lambda-java-runtime-interface-client
+ 2.8.3
-
-
- dev.aspectj
- aspectj-maven-plugin
- 1.14.1
-
- ${maven.compiler.source}
- ${maven.compiler.target}
- ${maven.compiler.target}
-
-
- software.amazon.lambda
- powertools-logging
-
-
-
-
-
-
- compile
-
-
-
-
-
- org.aspectj
- aspectjtools
- ${aspectj.version}
-
-
-
-
- org.apache.maven.plugins
- maven-shade-plugin
- 3.6.0
-
-
- package
-
- shade
-
-
- false
-
-
-
-
-
-
-
-
- org.apache.logging.log4j
- log4j-transform-maven-shade-plugin-extensions
- 0.2.0
-
-
-
-
-
- org.apache.maven.plugins
- maven-deploy-plugin
- 3.1.4
-
- true
-
-
-
+
+
+ dev.aspectj
+ aspectj-maven-plugin
+ 1.14.1
+
+ ${maven.compiler.source}
+ ${maven.compiler.target}
+ ${maven.compiler.target}
+
+
+ software.amazon.lambda
+ powertools-logging
+
+
+
+
+
+
+ compile
+
+
+
+
+
+ org.aspectj
+ aspectjtools
+ ${aspectj.version}
+
+
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ 3.6.0
+
+
+ package
+
+ shade
+
+
+ false
+
+
+
+
+
+
+
+
+ org.apache.logging.log4j
+ log4j-transform-maven-shade-plugin-extensions
+ 0.2.0
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-deploy-plugin
+ 3.1.4
+
+ true
+
+
+
+
+
+ native-image
+
+
+
+ org.graalvm.buildtools
+ native-maven-plugin
+ 0.11.0
+ true
+
+
+ build-native
+
+ build
+
+ package
+
+
+
+ hello-world
+ com.amazonaws.services.lambda.runtime.api.client.AWSLambda
+
+ --enable-url-protocols=http
+ --add-opens java.base/java.util=ALL-UNNAMED
+
+
+
+
+
+
+
diff --git a/examples/powertools-examples-cloudformation/src/main/config/bootstrap b/examples/powertools-examples-cloudformation/src/main/config/bootstrap
new file mode 100755
index 000000000..8e7928cd3
--- /dev/null
+++ b/examples/powertools-examples-cloudformation/src/main/config/bootstrap
@@ -0,0 +1,4 @@
+#!/bin/bash
+set -e
+
+./hello-world $_HANDLER
\ No newline at end of file
diff --git a/examples/powertools-examples-cloudformation/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-core/reflect-config.json b/examples/powertools-examples-cloudformation/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-core/reflect-config.json
new file mode 100644
index 000000000..2780aca09
--- /dev/null
+++ b/examples/powertools-examples-cloudformation/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-core/reflect-config.json
@@ -0,0 +1,13 @@
+[
+ {
+ "name":"com.amazonaws.services.lambda.runtime.LambdaRuntime",
+ "methods":[{"name":"","parameterTypes":[] }],
+ "fields":[{"name":"logger"}],
+ "allPublicMethods":true
+ },
+ {
+ "name":"com.amazonaws.services.lambda.runtime.LambdaRuntimeInternal",
+ "methods":[{"name":"","parameterTypes":[] }],
+ "allPublicMethods":true
+ }
+]
\ No newline at end of file
diff --git a/examples/powertools-examples-cloudformation/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-events/reflect-config.json b/examples/powertools-examples-cloudformation/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-events/reflect-config.json
new file mode 100644
index 000000000..ddda5d5f1
--- /dev/null
+++ b/examples/powertools-examples-cloudformation/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-events/reflect-config.json
@@ -0,0 +1,35 @@
+[
+ {
+ "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent",
+ "allDeclaredFields": true,
+ "allDeclaredMethods": true,
+ "allDeclaredConstructors": true
+ },
+ {
+ "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent$ProxyRequestContext",
+ "allDeclaredFields": true,
+ "allDeclaredMethods": true,
+ "allDeclaredConstructors": true
+ },
+ {
+ "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent$RequestIdentity",
+ "allDeclaredFields": true,
+ "allDeclaredMethods": true,
+ "allDeclaredConstructors": true
+ },
+ {
+ "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent",
+ "allDeclaredFields": true,
+ "allDeclaredMethods": true,
+ "allDeclaredConstructors": true
+ },
+ {
+ "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent",
+ "allDeclaredConstructors": true,
+ "allPublicConstructors": true,
+ "allDeclaredMethods": true,
+ "allPublicMethods": true,
+ "allDeclaredClasses": true,
+ "allPublicClasses": true
+ }
+]
\ No newline at end of file
diff --git a/examples/powertools-examples-cloudformation/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/jni-config.json b/examples/powertools-examples-cloudformation/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/jni-config.json
new file mode 100644
index 000000000..91be72f7a
--- /dev/null
+++ b/examples/powertools-examples-cloudformation/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/jni-config.json
@@ -0,0 +1,11 @@
+[
+ {
+ "name":"com.amazonaws.services.lambda.runtime.api.client.runtimeapi.LambdaRuntimeClientException",
+ "methods":[{"name":"","parameterTypes":["java.lang.String","int"] }]
+ },
+ {
+ "name":"com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.InvocationRequest",
+ "fields":[{"name":"id"}, {"name":"invokedFunctionArn"}, {"name":"deadlineTimeInMs"}, {"name":"xrayTraceId"}, {"name":"clientContext"}, {"name":"cognitoIdentity"}, {"name": "tenantId"}, {"name":"content"}],
+ "allPublicMethods":true
+ }
+]
\ No newline at end of file
diff --git a/examples/powertools-examples-cloudformation/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/native-image.properties b/examples/powertools-examples-cloudformation/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/native-image.properties
new file mode 100644
index 000000000..20f8b7801
--- /dev/null
+++ b/examples/powertools-examples-cloudformation/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/native-image.properties
@@ -0,0 +1 @@
+Args = --initialize-at-build-time=jdk.xml.internal.SecuritySupport
\ No newline at end of file
diff --git a/examples/powertools-examples-cloudformation/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/reflect-config.json b/examples/powertools-examples-cloudformation/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/reflect-config.json
new file mode 100644
index 000000000..10152cc64
--- /dev/null
+++ b/examples/powertools-examples-cloudformation/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/reflect-config.json
@@ -0,0 +1,34 @@
+[
+ {
+ "name":"com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.Deserializers[]"
+ },
+ {
+ "name":"com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ext.Java7SupportImpl",
+ "methods":[{"name":"","parameterTypes":[] }]
+ },
+ {
+ "name":"com.amazonaws.services.lambda.runtime.LambdaRuntime",
+ "fields":[{"name":"logger"}]
+ },
+ {
+ "name":"java.lang.Void",
+ "methods":[{"name":"","parameterTypes":[] }]
+ },
+ {
+ "name":"java.util.Collections$UnmodifiableMap",
+ "fields":[{"name":"m"}]
+ },
+ {
+ "name":"jdk.internal.module.IllegalAccessLogger",
+ "fields":[{"name":"logger"}]
+ },
+ {
+ "name":"sun.misc.Unsafe",
+ "fields":[{"name":"theUnsafe"}]
+ },
+ {
+ "name":"com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.InvocationRequest",
+ "fields":[{"name":"id"}, {"name":"invokedFunctionArn"}, {"name":"deadlineTimeInMs"}, {"name":"xrayTraceId"}, {"name":"clientContext"}, {"name":"cognitoIdentity"}, {"name": "tenantId"}, {"name":"content"}],
+ "allPublicMethods":true
+ }
+]
\ No newline at end of file
diff --git a/examples/powertools-examples-cloudformation/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/resource-config.json b/examples/powertools-examples-cloudformation/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/resource-config.json
new file mode 100644
index 000000000..1062b4249
--- /dev/null
+++ b/examples/powertools-examples-cloudformation/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/resource-config.json
@@ -0,0 +1,19 @@
+{
+ "resources": {
+ "includes": [
+ {
+ "pattern": "\\Qjni/libaws-lambda-jni.linux-aarch_64.so\\E"
+ },
+ {
+ "pattern": "\\Qjni/libaws-lambda-jni.linux-x86_64.so\\E"
+ },
+ {
+ "pattern": "\\Qjni/libaws-lambda-jni.linux_musl-aarch_64.so\\E"
+ },
+ {
+ "pattern": "\\Qjni/libaws-lambda-jni.linux_musl-x86_64.so\\E"
+ }
+ ]
+ },
+ "bundles": []
+}
\ No newline at end of file
diff --git a/examples/powertools-examples-cloudformation/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-serialization/reflect-config.json b/examples/powertools-examples-cloudformation/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-serialization/reflect-config.json
new file mode 100644
index 000000000..9890688f9
--- /dev/null
+++ b/examples/powertools-examples-cloudformation/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-serialization/reflect-config.json
@@ -0,0 +1,25 @@
+[
+ {
+ "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.Deserializers[]"
+ },
+ {
+ "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ext.Java7HandlersImpl",
+ "methods": [{ "name": "", "parameterTypes": [] }]
+ },
+ {
+ "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ext.Java7SupportImpl",
+ "methods": [{ "name": "", "parameterTypes": [] }]
+ },
+ {
+ "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.Serializers[]"
+ },
+ {
+ "name": "org.joda.time.DateTime",
+ "allDeclaredConstructors": true,
+ "allPublicConstructors": true,
+ "allDeclaredMethods": true,
+ "allPublicMethods": true,
+ "allDeclaredClasses": true,
+ "allPublicClasses": true
+ }
+]
diff --git a/examples/powertools-examples-cloudformation/src/main/resources/META-INF/native-image/helloworld/native-image.properties b/examples/powertools-examples-cloudformation/src/main/resources/META-INF/native-image/helloworld/native-image.properties
new file mode 100644
index 000000000..db5ebaa55
--- /dev/null
+++ b/examples/powertools-examples-cloudformation/src/main/resources/META-INF/native-image/helloworld/native-image.properties
@@ -0,0 +1 @@
+Args = --enable-url-protocols=http,https
\ No newline at end of file
diff --git a/examples/powertools-examples-cloudformation/src/main/resources/META-INF/native-image/helloworld/reflect-config.json b/examples/powertools-examples-cloudformation/src/main/resources/META-INF/native-image/helloworld/reflect-config.json
new file mode 100644
index 000000000..06ea9ce2f
--- /dev/null
+++ b/examples/powertools-examples-cloudformation/src/main/resources/META-INF/native-image/helloworld/reflect-config.json
@@ -0,0 +1,11 @@
+[
+ {
+ "name": "helloworld.App",
+ "allDeclaredConstructors": true,
+ "allPublicConstructors": true,
+ "allDeclaredMethods": true,
+ "allPublicMethods": true,
+ "allDeclaredClasses": true,
+ "allPublicClasses": true
+ }
+]
diff --git a/examples/powertools-examples-cloudformation/src/main/resources/META-INF/native-image/helloworld/resource-config.json b/examples/powertools-examples-cloudformation/src/main/resources/META-INF/native-image/helloworld/resource-config.json
new file mode 100644
index 000000000..be6aac3f6
--- /dev/null
+++ b/examples/powertools-examples-cloudformation/src/main/resources/META-INF/native-image/helloworld/resource-config.json
@@ -0,0 +1,7 @@
+{
+ "resources":{
+ "includes":[{
+ "pattern":"\\Qlog4j2.xml\\E"
+ }]},
+ "bundles":[]
+}
diff --git a/examples/powertools-examples-cloudformation/src/main/resources/META-INF/native-image/software.amazon.awssdk/s3/reflect-config.json b/examples/powertools-examples-cloudformation/src/main/resources/META-INF/native-image/software.amazon.awssdk/s3/reflect-config.json
new file mode 100644
index 000000000..d685b7e20
--- /dev/null
+++ b/examples/powertools-examples-cloudformation/src/main/resources/META-INF/native-image/software.amazon.awssdk/s3/reflect-config.json
@@ -0,0 +1,27 @@
+[
+ {
+ "name": "software.amazon.awssdk.services.s3.model.CreateBucketRequest",
+ "allPublicMethods": true,
+ "allPublicConstructors": true
+ },
+ {
+ "name": "software.amazon.awssdk.services.s3.model.DeleteBucketRequest",
+ "allPublicMethods": true,
+ "allPublicConstructors": true
+ },
+ {
+ "name": "software.amazon.awssdk.services.s3.model.HeadBucketRequest",
+ "allPublicMethods": true,
+ "allPublicConstructors": true
+ },
+ {
+ "name": "software.amazon.awssdk.services.s3.model.HeadBucketResponse",
+ "allPublicMethods": true,
+ "allPublicConstructors": true
+ },
+ {
+ "name": "software.amazon.awssdk.services.s3.model.NoSuchBucketException",
+ "allPublicMethods": true,
+ "allPublicConstructors": true
+ }
+]
\ No newline at end of file
diff --git a/powertools-cloudformation/pom.xml b/powertools-cloudformation/pom.xml
index 355e8a3ed..862f5832e 100644
--- a/powertools-cloudformation/pom.xml
+++ b/powertools-cloudformation/pom.xml
@@ -95,5 +95,92 @@
wiremock
test
+
+ software.amazon.lambda
+ powertools-common
+ ${project.version}
+ test-jar
+ test
+
+
+
+
+ generate-graalvm-files
+
+
+ org.mockito
+ mockito-subclass
+ test
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+
+ -Dorg.graalvm.nativeimage.imagecode=agent
+ -agentlib:native-image-agent=config-output-dir=src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-cloudformation,experimental-class-define-support
+ --add-opens java.base/java.util=ALL-UNNAMED
+ --add-opens java.base/java.lang=ALL-UNNAMED
+
+
+
+
+
+
+
+ graalvm-native
+
+
+ org.mockito
+ mockito-subclass
+ test
+
+
+
+
+
+ org.graalvm.buildtools
+ native-maven-plugin
+ 0.11.0
+ true
+
+
+ test-native
+
+ test
+
+ test
+
+
+
+ powertools-cloudformation
+
+ --add-opens java.base/java.util=ALL-UNNAMED
+ --add-opens java.base/java.lang=ALL-UNNAMED
+ --enable-url-protocols=http
+ --no-fallback
+ --verbose
+ --native-image-info
+ -H:+UnlockExperimentalVMOptions
+ -H:+ReportExceptionStackTraces
+
+
+
+
+
+
+
+
+
+
+
+
+ src/main/resources
+
+
+
diff --git a/powertools-cloudformation/src/main/java/software/amazon/lambda/powertools/cloudformation/CloudFormationResponse.java b/powertools-cloudformation/src/main/java/software/amazon/lambda/powertools/cloudformation/CloudFormationResponse.java
index 404137802..cf6fad827 100644
--- a/powertools-cloudformation/src/main/java/software/amazon/lambda/powertools/cloudformation/CloudFormationResponse.java
+++ b/powertools-cloudformation/src/main/java/software/amazon/lambda/powertools/cloudformation/CloudFormationResponse.java
@@ -46,7 +46,7 @@
*
* This class is thread-safe provided the SdkHttpClient instance used is also thread-safe.
*/
-class CloudFormationResponse {
+public class CloudFormationResponse {
private static final Logger LOG = LoggerFactory.getLogger(CloudFormationResponse.class);
private final SdkHttpClient client;
diff --git a/powertools-cloudformation/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-cloudformation/reflect-config.json b/powertools-cloudformation/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-cloudformation/reflect-config.json
new file mode 100644
index 000000000..218382888
--- /dev/null
+++ b/powertools-cloudformation/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-cloudformation/reflect-config.json
@@ -0,0 +1,432 @@
+[
+{
+ "name":"[Lcom.fasterxml.jackson.databind.deser.BeanDeserializerModifier;"
+},
+{
+ "name":"[Lcom.fasterxml.jackson.databind.deser.Deserializers;"
+},
+{
+ "name":"[Lcom.fasterxml.jackson.databind.deser.KeyDeserializers;"
+},
+{
+ "name":"[Lcom.fasterxml.jackson.databind.deser.ValueInstantiators;"
+},
+{
+ "name":"[Lcom.fasterxml.jackson.databind.ser.BeanSerializerModifier;"
+},
+{
+ "name":"[Lcom.fasterxml.jackson.databind.ser.Serializers;"
+},
+{
+ "name":"com.amazonaws.services.lambda.runtime.RequestHandler",
+ "queryAllDeclaredMethods":true,
+ "queryAllDeclaredConstructors":true
+},
+{
+ "name":"com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent",
+ "queryAllDeclaredMethods":true,
+ "queryAllDeclaredConstructors":true,
+ "methods":[{"name":"canEqual","parameterTypes":["java.lang.Object"] }, {"name":"getLogicalResourceId","parameterTypes":[] }, {"name":"getOldResourceProperties","parameterTypes":[] }, {"name":"getPhysicalResourceId","parameterTypes":[] }, {"name":"getRequestId","parameterTypes":[] }, {"name":"getRequestType","parameterTypes":[] }, {"name":"getResourceProperties","parameterTypes":[] }, {"name":"getResourceType","parameterTypes":[] }, {"name":"getResponseUrl","parameterTypes":[] }, {"name":"getServiceToken","parameterTypes":[] }, {"name":"getStackId","parameterTypes":[] }, {"name":"setLogicalResourceId","parameterTypes":["java.lang.String"] }, {"name":"setOldResourceProperties","parameterTypes":["java.util.Map"] }, {"name":"setPhysicalResourceId","parameterTypes":["java.lang.String"] }, {"name":"setRequestId","parameterTypes":["java.lang.String"] }, {"name":"setRequestType","parameterTypes":["java.lang.String"] }, {"name":"setResourceProperties","parameterTypes":["java.util.Map"] }, {"name":"setResourceType","parameterTypes":["java.lang.String"] }, {"name":"setResponseUrl","parameterTypes":["java.lang.String"] }, {"name":"setServiceToken","parameterTypes":["java.lang.String"] }, {"name":"setStackId","parameterTypes":["java.lang.String"] }, {"name":"toString","parameterTypes":[] }]
+},
+{
+ "name":"com.fasterxml.jackson.databind.ext.Java7SupportImpl",
+ "methods":[{"name":"","parameterTypes":[] }]
+},
+{
+ "name":"com.jayway.jsonpath.spi.cache.CacheProvider",
+ "fields":[{"name":"cache"}]
+},
+{
+ "name":"com.sun.crypto.provider.AESCipher$General",
+ "methods":[{"name":"","parameterTypes":[] }]
+},
+{
+ "name":"com.sun.crypto.provider.ARCFOURCipher",
+ "methods":[{"name":"","parameterTypes":[] }]
+},
+{
+ "name":"com.sun.crypto.provider.ChaCha20Cipher$ChaCha20Poly1305",
+ "methods":[{"name":"","parameterTypes":[] }]
+},
+{
+ "name":"com.sun.crypto.provider.DESCipher",
+ "methods":[{"name":"","parameterTypes":[] }]
+},
+{
+ "name":"com.sun.crypto.provider.DESedeCipher",
+ "methods":[{"name":"","parameterTypes":[] }]
+},
+{
+ "name":"com.sun.crypto.provider.GaloisCounterMode$AESGCM",
+ "methods":[{"name":"","parameterTypes":[] }]
+},
+{
+ "name":"com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl",
+ "methods":[{"name":"","parameterTypes":[] }]
+},
+{
+ "name":"com.sun.tools.attach.VirtualMachine"
+},
+{
+ "name":"java.io.FileNotFoundException",
+ "methods":[{"name":"","parameterTypes":["java.lang.String"] }]
+},
+{
+ "name":"java.io.Serializable",
+ "queryAllDeclaredMethods":true,
+ "queryAllDeclaredConstructors":true
+},
+{
+ "name":"java.lang.AutoCloseable",
+ "queryAllDeclaredMethods":true,
+ "queryAllDeclaredConstructors":true
+},
+{
+ "name":"java.lang.Boolean",
+ "methods":[{"name":"valueOf","parameterTypes":["java.lang.String"] }]
+},
+{
+ "name":"java.lang.Byte",
+ "methods":[{"name":"valueOf","parameterTypes":["java.lang.String"] }]
+},
+{
+ "name":"java.lang.Class",
+ "methods":[{"name":"getAnnotatedInterfaces","parameterTypes":[] }, {"name":"getAnnotatedSuperclass","parameterTypes":[] }, {"name":"getDeclaredMethod","parameterTypes":["java.lang.String","java.lang.Class[]"] }, {"name":"getMethod","parameterTypes":["java.lang.String","java.lang.Class[]"] }, {"name":"getModule","parameterTypes":[] }, {"name":"getNestHost","parameterTypes":[] }, {"name":"getNestMembers","parameterTypes":[] }, {"name":"getPackageName","parameterTypes":[] }, {"name":"getPermittedSubclasses","parameterTypes":[] }, {"name":"getRecordComponents","parameterTypes":[] }, {"name":"isNestmateOf","parameterTypes":["java.lang.Class"] }, {"name":"isRecord","parameterTypes":[] }, {"name":"isSealed","parameterTypes":[] }]
+},
+{
+ "name":"java.lang.ClassLoader",
+ "methods":[{"name":"getDefinedPackage","parameterTypes":["java.lang.String"] }, {"name":"getUnnamedModule","parameterTypes":[] }, {"name":"registerAsParallelCapable","parameterTypes":[] }]
+},
+{
+ "name":"java.lang.Cloneable",
+ "queryAllDeclaredMethods":true,
+ "queryAllDeclaredConstructors":true
+},
+{
+ "name":"java.lang.Double",
+ "methods":[{"name":"valueOf","parameterTypes":["java.lang.String"] }]
+},
+{
+ "name":"java.lang.Float",
+ "methods":[{"name":"valueOf","parameterTypes":["java.lang.String"] }]
+},
+{
+ "name":"java.lang.Integer",
+ "methods":[{"name":"valueOf","parameterTypes":["java.lang.String"] }]
+},
+{
+ "name":"java.lang.Long",
+ "methods":[{"name":"valueOf","parameterTypes":["java.lang.String"] }]
+},
+{
+ "name":"java.lang.Module",
+ "methods":[{"name":"addExports","parameterTypes":["java.lang.String","java.lang.Module"] }, {"name":"addOpens","parameterTypes":["java.lang.String","java.lang.Module"] }, {"name":"addReads","parameterTypes":["java.lang.Module"] }, {"name":"canRead","parameterTypes":["java.lang.Module"] }, {"name":"getClassLoader","parameterTypes":[] }, {"name":"getName","parameterTypes":[] }, {"name":"getPackages","parameterTypes":[] }, {"name":"getResourceAsStream","parameterTypes":["java.lang.String"] }, {"name":"isExported","parameterTypes":["java.lang.String"] }, {"name":"isExported","parameterTypes":["java.lang.String","java.lang.Module"] }, {"name":"isNamed","parameterTypes":[] }, {"name":"isOpen","parameterTypes":["java.lang.String","java.lang.Module"] }]
+},
+{
+ "name":"java.lang.Object",
+ "queryAllDeclaredMethods":true,
+ "queryAllDeclaredConstructors":true,
+ "methods":[{"name":"","parameterTypes":[] }, {"name":"clone","parameterTypes":[] }, {"name":"getClass","parameterTypes":[] }, {"name":"toString","parameterTypes":[] }]
+},
+{
+ "name":"java.lang.ProcessHandle",
+ "methods":[{"name":"current","parameterTypes":[] }, {"name":"pid","parameterTypes":[] }]
+},
+{
+ "name":"java.lang.Runtime",
+ "methods":[{"name":"version","parameterTypes":[] }]
+},
+{
+ "name":"java.lang.Runtime$Version",
+ "methods":[{"name":"feature","parameterTypes":[] }]
+},
+{
+ "name":"java.lang.Short",
+ "methods":[{"name":"valueOf","parameterTypes":["java.lang.String"] }]
+},
+{
+ "name":"java.lang.StackWalker"
+},
+{
+ "name":"java.lang.System",
+ "methods":[{"name":"getSecurityManager","parameterTypes":[] }]
+},
+{
+ "name":"java.lang.Thread",
+ "fields":[{"name":"threadLocalRandomProbe"}],
+ "methods":[{"name":"isVirtual","parameterTypes":[] }]
+},
+{
+ "name":"java.lang.annotation.Retention",
+ "queryAllDeclaredMethods":true,
+ "queryAllDeclaredConstructors":true
+},
+{
+ "name":"java.lang.annotation.Target",
+ "queryAllDeclaredMethods":true,
+ "queryAllDeclaredConstructors":true
+},
+{
+ "name":"java.lang.invoke.MethodHandle",
+ "methods":[{"name":"bindTo","parameterTypes":["java.lang.Object"] }, {"name":"invokeWithArguments","parameterTypes":["java.lang.Object[]"] }]
+},
+{
+ "name":"java.lang.invoke.MethodHandles",
+ "methods":[{"name":"lookup","parameterTypes":[] }, {"name":"privateLookupIn","parameterTypes":["java.lang.Class","java.lang.invoke.MethodHandles$Lookup"] }]
+},
+{
+ "name":"java.lang.invoke.MethodHandles$Lookup",
+ "methods":[{"name":"findVirtual","parameterTypes":["java.lang.Class","java.lang.String","java.lang.invoke.MethodType"] }]
+},
+{
+ "name":"java.lang.invoke.MethodType",
+ "methods":[{"name":"methodType","parameterTypes":["java.lang.Class","java.lang.Class[]"] }]
+},
+{
+ "name":"java.lang.management.ManagementFactory",
+ "methods":[{"name":"getRuntimeMXBean","parameterTypes":[] }]
+},
+{
+ "name":"java.lang.management.RuntimeMXBean",
+ "methods":[{"name":"getInputArguments","parameterTypes":[] }, {"name":"getUptime","parameterTypes":[] }]
+},
+{
+ "name":"java.lang.reflect.AccessibleObject",
+ "methods":[{"name":"setAccessible","parameterTypes":["boolean"] }]
+},
+{
+ "name":"java.lang.reflect.AnnotatedArrayType",
+ "methods":[{"name":"getAnnotatedGenericComponentType","parameterTypes":[] }]
+},
+{
+ "name":"java.lang.reflect.AnnotatedParameterizedType",
+ "methods":[{"name":"getAnnotatedActualTypeArguments","parameterTypes":[] }]
+},
+{
+ "name":"java.lang.reflect.AnnotatedType",
+ "methods":[{"name":"getType","parameterTypes":[] }]
+},
+{
+ "name":"java.lang.reflect.Executable",
+ "methods":[{"name":"getAnnotatedExceptionTypes","parameterTypes":[] }, {"name":"getAnnotatedParameterTypes","parameterTypes":[] }, {"name":"getAnnotatedReceiverType","parameterTypes":[] }, {"name":"getParameterCount","parameterTypes":[] }, {"name":"getParameters","parameterTypes":[] }]
+},
+{
+ "name":"java.lang.reflect.Method",
+ "methods":[{"name":"getAnnotatedReturnType","parameterTypes":[] }]
+},
+{
+ "name":"java.lang.reflect.Parameter",
+ "methods":[{"name":"getModifiers","parameterTypes":[] }, {"name":"getName","parameterTypes":[] }, {"name":"isNamePresent","parameterTypes":[] }]
+},
+{
+ "name":"java.security.AccessController",
+ "methods":[{"name":"doPrivileged","parameterTypes":["java.security.PrivilegedAction"] }, {"name":"doPrivileged","parameterTypes":["java.security.PrivilegedExceptionAction"] }]
+},
+{
+ "name":"java.security.AlgorithmParametersSpi"
+},
+{
+ "name":"java.security.KeyStoreSpi"
+},
+{
+ "name":"java.security.SecureRandomParameters"
+},
+{
+ "name":"java.util.HashSet",
+ "methods":[{"name":"","parameterTypes":[] }]
+},
+{
+ "name":"java.util.concurrent.Callable",
+ "queryAllDeclaredMethods":true,
+ "queryAllDeclaredConstructors":true
+},
+{
+ "name":"java.util.concurrent.Executors",
+ "methods":[{"name":"newVirtualThreadPerTaskExecutor","parameterTypes":[] }]
+},
+{
+ "name":"java.util.concurrent.ForkJoinTask",
+ "fields":[{"name":"aux"}, {"name":"status"}]
+},
+{
+ "name":"java.util.concurrent.atomic.AtomicBoolean",
+ "fields":[{"name":"value"}]
+},
+{
+ "name":"java.util.concurrent.atomic.AtomicReference",
+ "fields":[{"name":"value"}]
+},
+{
+ "name":"java.util.concurrent.atomic.Striped64",
+ "fields":[{"name":"base"}, {"name":"cellsBusy"}]
+},
+{
+ "name":"java.util.function.Consumer",
+ "queryAllPublicMethods":true
+},
+{
+ "name":"javax.security.auth.x500.X500Principal",
+ "fields":[{"name":"thisX500Name"}],
+ "methods":[{"name":"","parameterTypes":["sun.security.x509.X500Name"] }]
+},
+{
+ "name":"jdk.internal.misc.Unsafe"
+},
+{
+ "name":"kotlin.Metadata"
+},
+{
+ "name":"kotlin.jvm.JvmInline"
+},
+{
+ "name":"org.apiguardian.api.API",
+ "queryAllPublicMethods":true
+},
+{
+ "name":"org.eclipse.jetty.http.pathmap.PathSpecSet",
+ "methods":[{"name":"","parameterTypes":[] }]
+},
+{
+ "name":"org.eclipse.jetty.servlets.CrossOriginFilter",
+ "methods":[{"name":"","parameterTypes":[] }]
+},
+{
+ "name":"org.eclipse.jetty.util.AsciiLowerCaseSet",
+ "methods":[{"name":"","parameterTypes":[] }]
+},
+{
+ "name":"org.eclipse.jetty.util.TypeUtil",
+ "methods":[{"name":"getClassLoaderLocation","parameterTypes":["java.lang.Class"] }, {"name":"getCodeSourceLocation","parameterTypes":["java.lang.Class"] }, {"name":"getModuleLocation","parameterTypes":["java.lang.Class"] }, {"name":"getSystemClassLoaderLocation","parameterTypes":["java.lang.Class"] }]
+},
+{
+ "name":"software.amazon.awssdk.http.Abortable",
+ "queryAllDeclaredMethods":true,
+ "queryAllDeclaredConstructors":true,
+ "methods":[{"name":"abort","parameterTypes":[] }]
+},
+{
+ "name":"software.amazon.awssdk.http.ExecutableHttpRequest",
+ "queryAllDeclaredMethods":true,
+ "queryAllPublicMethods":true,
+ "queryAllDeclaredConstructors":true,
+ "methods":[{"name":"call","parameterTypes":[] }]
+},
+{
+ "name":"software.amazon.awssdk.http.HttpExecuteResponse",
+ "queryAllDeclaredMethods":true,
+ "queryAllDeclaredConstructors":true,
+ "methods":[{"name":"httpResponse","parameterTypes":[] }, {"name":"responseBody","parameterTypes":[] }]
+},
+{
+ "name":"software.amazon.awssdk.http.SdkHttpClient",
+ "queryAllDeclaredMethods":true,
+ "queryAllPublicMethods":true,
+ "queryAllDeclaredConstructors":true,
+ "methods":[{"name":"clientName","parameterTypes":[] }, {"name":"prepareRequest","parameterTypes":["software.amazon.awssdk.http.HttpExecuteRequest"] }]
+},
+{
+ "name":"software.amazon.awssdk.utils.SdkAutoCloseable",
+ "queryAllDeclaredMethods":true,
+ "queryAllDeclaredConstructors":true,
+ "methods":[{"name":"close","parameterTypes":[] }]
+},
+{
+ "name":"software.amazon.lambda.powertools.cloudformation.AbstractCustomResourceHandler",
+ "allDeclaredFields":true,
+ "queryAllDeclaredMethods":true,
+ "queryAllDeclaredConstructors":true,
+ "methods":[{"name":"onSendFailure","parameterTypes":["com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent","com.amazonaws.services.lambda.runtime.Context","software.amazon.lambda.powertools.cloudformation.Response","java.lang.Exception"] }]
+},
+{
+ "name":"software.amazon.lambda.powertools.cloudformation.CloudFormationResponse",
+ "queryAllDeclaredMethods":true,
+ "queryAllDeclaredConstructors":true,
+ "methods":[{"name":"headers","parameterTypes":["int"] }, {"name":"send","parameterTypes":["com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent","com.amazonaws.services.lambda.runtime.Context"] }, {"name":"send","parameterTypes":["com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent","com.amazonaws.services.lambda.runtime.Context","software.amazon.lambda.powertools.cloudformation.Response"] }]
+},
+{
+ "name":"software.amazon.lambda.powertools.cloudformation.CloudFormationResponse$ResponseBody",
+ "allDeclaredFields":true,
+ "queryAllDeclaredMethods":true,
+ "queryAllDeclaredConstructors":true,
+ "methods":[{"name":"getLogicalResourceId","parameterTypes":[] }, {"name":"getPhysicalResourceId","parameterTypes":[] }, {"name":"getReason","parameterTypes":[] }, {"name":"getRequestId","parameterTypes":[] }, {"name":"getStackId","parameterTypes":[] }, {"name":"getStatus","parameterTypes":[] }, {"name":"isNoEcho","parameterTypes":[] }]
+},
+{
+ "name":"sun.misc.SharedSecrets"
+},
+{
+ "name":"sun.reflect.ReflectionFactory",
+ "methods":[{"name":"getReflectionFactory","parameterTypes":[] }, {"name":"newConstructorForSerialization","parameterTypes":["java.lang.Class","java.lang.reflect.Constructor"] }]
+},
+{
+ "name":"sun.security.pkcs12.PKCS12KeyStore",
+ "methods":[{"name":"","parameterTypes":[] }]
+},
+{
+ "name":"sun.security.pkcs12.PKCS12KeyStore$DualFormatPKCS12",
+ "methods":[{"name":"","parameterTypes":[] }]
+},
+{
+ "name":"sun.security.provider.NativePRNG",
+ "methods":[{"name":"","parameterTypes":[] }, {"name":"","parameterTypes":["java.security.SecureRandomParameters"] }]
+},
+{
+ "name":"sun.security.provider.SHA",
+ "methods":[{"name":"","parameterTypes":[] }]
+},
+{
+ "name":"sun.security.provider.X509Factory",
+ "methods":[{"name":"","parameterTypes":[] }]
+},
+{
+ "name":"sun.security.rsa.RSAKeyFactory$Legacy",
+ "methods":[{"name":"","parameterTypes":[] }]
+},
+{
+ "name":"sun.security.ssl.SSLContextImpl$TLSContext",
+ "methods":[{"name":"","parameterTypes":[] }]
+},
+{
+ "name":"sun.security.ssl.TrustManagerFactoryImpl$PKIXFactory",
+ "methods":[{"name":"","parameterTypes":[] }]
+},
+{
+ "name":"sun.security.x509.AuthorityInfoAccessExtension",
+ "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }]
+},
+{
+ "name":"sun.security.x509.AuthorityKeyIdentifierExtension",
+ "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }]
+},
+{
+ "name":"sun.security.x509.BasicConstraintsExtension",
+ "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }]
+},
+{
+ "name":"sun.security.x509.CRLDistributionPointsExtension",
+ "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }]
+},
+{
+ "name":"sun.security.x509.CertificatePoliciesExtension",
+ "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }]
+},
+{
+ "name":"sun.security.x509.ExtendedKeyUsageExtension",
+ "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }]
+},
+{
+ "name":"sun.security.x509.KeyUsageExtension",
+ "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }]
+},
+{
+ "name":"sun.security.x509.NetscapeCertTypeExtension",
+ "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }]
+},
+{
+ "name":"sun.security.x509.PrivateKeyUsageExtension",
+ "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }]
+},
+{
+ "name":"sun.security.x509.SubjectKeyIdentifierExtension",
+ "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }]
+}
+]
diff --git a/powertools-cloudformation/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-cloudformation/resource-config.json b/powertools-cloudformation/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-cloudformation/resource-config.json
new file mode 100644
index 000000000..f3b58337b
--- /dev/null
+++ b/powertools-cloudformation/src/main/resources/META-INF/native-image/software.amazon.lambda/powertools-cloudformation/resource-config.json
@@ -0,0 +1,41 @@
+{
+ "resources":{
+ "includes":[{
+ "pattern":"\\QMETA-INF/services/java.lang.System$LoggerFinder\\E"
+ }, {
+ "pattern":"\\QMETA-INF/services/java.net.spi.InetAddressResolverProvider\\E"
+ }, {
+ "pattern":"\\QMETA-INF/services/java.net.spi.URLStreamHandlerProvider\\E"
+ }, {
+ "pattern":"\\QMETA-INF/services/java.nio.channels.spi.SelectorProvider\\E"
+ }, {
+ "pattern":"\\QMETA-INF/services/java.util.spi.ResourceBundleControlProvider\\E"
+ }, {
+ "pattern":"\\QMETA-INF/services/org.slf4j.spi.SLF4JServiceProvider\\E"
+ }, {
+ "pattern":"\\Qassets/swagger-ui/index.html\\E"
+ }, {
+ "pattern":"\\Qassets\\E"
+ }, {
+ "pattern":"\\Qhelpers.nashorn.js\\E"
+ }, {
+ "pattern":"\\Qkeystore\\E"
+ }, {
+ "pattern":"\\Qorg/apache/hc/client5/version.properties\\E"
+ }, {
+ "pattern":"\\Qorg/eclipse/jetty/http/encoding.properties\\E"
+ }, {
+ "pattern":"\\Qorg/eclipse/jetty/http/mime.properties\\E"
+ }, {
+ "pattern":"\\Qorg/eclipse/jetty/version/build.properties\\E"
+ }, {
+ "pattern":"\\Qorg/publicsuffix/list/effective_tld_names.dat\\E"
+ }]},
+ "bundles":[{
+ "name":"jakarta.servlet.LocalStrings",
+ "locales":[""]
+ }, {
+ "name":"jakarta.servlet.http.LocalStrings",
+ "locales":[""]
+ }]
+}
diff --git a/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/AbstractCustomResourceHandlerTest.java b/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/AbstractCustomResourceHandlerTest.java
index 1e399ef6f..9d0669d43 100644
--- a/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/AbstractCustomResourceHandlerTest.java
+++ b/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/AbstractCustomResourceHandlerTest.java
@@ -16,7 +16,6 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.mock;
@@ -25,14 +24,17 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import com.amazonaws.services.lambda.runtime.Context;
-import com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent;
import java.io.IOException;
+
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
+
+import com.amazonaws.services.lambda.runtime.Context;
+import com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent;
+
import software.amazon.awssdk.http.SdkHttpClient;
-import software.amazon.lambda.powertools.cloudformation.Response.Status;
+import software.amazon.lambda.powertools.common.stubs.TestLambdaContext;
public class AbstractCustomResourceHandlerTest {
@@ -68,11 +70,11 @@ void defaultAndCustomSdkHttpClients() {
}
@ParameterizedTest
- @CsvSource(value = {"Create,1,0,0", "Update,0,1,0", "Delete,0,0,1"}, delimiter = ',')
+ @CsvSource(value = { "Create,1,0,0", "Update,0,1,0", "Delete,0,0,1" }, delimiter = ',')
void eventsDelegateToCorrectHandlerMethod(String eventType, int createCount, int updateCount, int deleteCount) {
AbstractCustomResourceHandler handler = spy(new NoOpCustomResourceHandler());
- Context context = mock(Context.class);
+ Context context = new TestLambdaContext();
handler.handleRequest(eventOfType(eventType), context);
verify(handler, times(createCount)).create(any(), eq(context));
@@ -84,7 +86,7 @@ void eventsDelegateToCorrectHandlerMethod(String eventType, int createCount, int
void eventOfUnknownRequestTypeSendEmptySuccess() {
AbstractCustomResourceHandler handler = spy(new NoOpCustomResourceHandler());
- Context context = mock(Context.class);
+ Context context = new TestLambdaContext();
CloudFormationCustomResourceEvent event = eventOfType("UNKNOWN");
handler.handleRequest(event, context);
@@ -96,16 +98,9 @@ void eventOfUnknownRequestTypeSendEmptySuccess() {
@Test
void defaultStatusResponseSendsSuccess() {
- ExpectedStatusResourceHandler handler = spy(new ExpectedStatusResourceHandler(Status.SUCCESS) {
- @Override
- protected Response create(CloudFormationCustomResourceEvent event, Context context) {
- return Response.builder()
- .value("whatever")
- .build();
- }
- });
-
- Context context = mock(Context.class);
+ SuccessResponseHandler handler = spy(new SuccessResponseHandler());
+
+ Context context = new TestLambdaContext();
CloudFormationCustomResourceEvent event = eventOfType("Create");
Response response = handler.handleRequest(event, context);
@@ -116,17 +111,9 @@ protected Response create(CloudFormationCustomResourceEvent event, Context conte
@Test
void explicitResponseWithStatusSuccessSendsSuccess() {
- ExpectedStatusResourceHandler handler = spy(new ExpectedStatusResourceHandler(Status.SUCCESS) {
- @Override
- protected Response create(CloudFormationCustomResourceEvent event, Context context) {
- return Response.builder()
- .value("whatever")
- .status(Status.SUCCESS)
- .build();
- }
- });
-
- Context context = mock(Context.class);
+ ExplicitSuccessResponseHandler handler = spy(new ExplicitSuccessResponseHandler());
+
+ Context context = new TestLambdaContext();
CloudFormationCustomResourceEvent event = eventOfType("Create");
Response response = handler.handleRequest(event, context);
@@ -137,17 +124,9 @@ protected Response create(CloudFormationCustomResourceEvent event, Context conte
@Test
void explicitResponseWithStatusFailedSendsFailure() {
- ExpectedStatusResourceHandler handler = spy(new ExpectedStatusResourceHandler(Status.FAILED) {
- @Override
- protected Response create(CloudFormationCustomResourceEvent event, Context context) {
- return Response.builder()
- .value("whatever")
- .status(Status.FAILED)
- .build();
- }
- });
-
- Context context = mock(Context.class);
+ FailedResponseHandler handler = spy(new FailedResponseHandler());
+
+ Context context = new TestLambdaContext();
CloudFormationCustomResourceEvent event = eventOfType("Create");
Response response = handler.handleRequest(event, context);
@@ -158,14 +137,9 @@ protected Response create(CloudFormationCustomResourceEvent event, Context conte
@Test
void exceptionWhenGeneratingResponseSendsFailure() {
- ExpectedStatusResourceHandler handler = spy(new ExpectedStatusResourceHandler(Status.FAILED) {
- @Override
- protected Response create(CloudFormationCustomResourceEvent event, Context context) {
- throw new RuntimeException("This exception is intentional for testing");
- }
- });
-
- Context context = mock(Context.class);
+ ExceptionThrowingHandler handler = spy(new ExceptionThrowingHandler());
+
+ Context context = new TestLambdaContext();
CloudFormationCustomResourceEvent event = eventOfType("Create");
Response response = handler.handleRequest(event, context);
@@ -178,14 +152,9 @@ protected Response create(CloudFormationCustomResourceEvent event, Context conte
@Test
void exceptionWhenSendingResponseInvokesOnSendFailure() {
// a custom handler that builds response successfully but fails to send it
- FailToSendResponseHandler handler = spy(new FailToSendResponseHandler() {
- @Override
- protected Response create(CloudFormationCustomResourceEvent event, Context context) {
- return Response.builder().value("Failure happens on send").build();
- }
- });
-
- Context context = mock(Context.class);
+ SuccessfulSendHandler handler = spy(new SuccessfulSendHandler());
+
+ Context context = new TestLambdaContext();
CloudFormationCustomResourceEvent event = eventOfType("Create");
Response response = handler.handleRequest(event, context);
@@ -197,15 +166,11 @@ protected Response create(CloudFormationCustomResourceEvent event, Context conte
@Test
void bothResponseGenerationAndSendFail() {
- // a custom handler that fails to build response _and_ fails to send a FAILED response
- FailToSendResponseHandler handler = spy(new FailToSendResponseHandler() {
- @Override
- protected Response create(CloudFormationCustomResourceEvent event, Context context) {
- throw new RuntimeException("This exception is intentional for testing");
- }
- });
-
- Context context = mock(Context.class);
+ // a custom handler that fails to build response _and_ fails to send a FAILED
+ // response
+ FailedSendHandler handler = spy(new FailedSendHandler());
+
+ Context context = new TestLambdaContext();
CloudFormationCustomResourceEvent event = eventOfType("Create");
Response response = handler.handleRequest(event, context);
@@ -214,91 +179,4 @@ protected Response create(CloudFormationCustomResourceEvent event, Context conte
.onSendFailure(eq(event), eq(context), isNull(), any(IOException.class));
}
- /**
- * Bare-bones implementation that returns null for abstract methods.
- */
- static class NullCustomResourceHandler extends AbstractCustomResourceHandler {
- NullCustomResourceHandler() {
- }
-
- NullCustomResourceHandler(SdkHttpClient client) {
- super(client);
- }
-
- @Override
- protected Response create(CloudFormationCustomResourceEvent event, Context context) {
- return null;
- }
-
- @Override
- protected Response update(CloudFormationCustomResourceEvent event, Context context) {
- return null;
- }
-
- @Override
- protected Response delete(CloudFormationCustomResourceEvent event, Context context) {
- return null;
- }
- }
-
- /**
- * Uses a mocked CloudFormationResponse to avoid sending actual HTTP requests.
- */
- static class NoOpCustomResourceHandler extends NullCustomResourceHandler {
-
- NoOpCustomResourceHandler() {
- super(mock(SdkHttpClient.class));
- }
-
- @Override
- protected CloudFormationResponse buildResponseClient() {
- return mock(CloudFormationResponse.class);
- }
- }
-
- /**
- * Creates a handler that will expect the Response to be sent with an expected status. Will throw an AssertionError
- * if the method is sent with an unexpected status.
- */
- static class ExpectedStatusResourceHandler extends NoOpCustomResourceHandler {
- private final Status expectedStatus;
-
- ExpectedStatusResourceHandler(Status expectedStatus) {
- this.expectedStatus = expectedStatus;
- }
-
- @Override
- protected CloudFormationResponse buildResponseClient() {
- // create a CloudFormationResponse that fails if invoked with unexpected status
- CloudFormationResponse cfnResponse = mock(CloudFormationResponse.class);
- try {
- when(cfnResponse.send(any(), any(), argThat(resp -> resp.getStatus() != expectedStatus)))
- .thenThrow(new AssertionError("Expected response's status to be " + expectedStatus));
- } catch (IOException | CustomResourceResponseException e) {
- // this should never happen
- throw new RuntimeException("Unexpected mocking exception", e);
- }
- return cfnResponse;
- }
- }
-
- /**
- * Always fails to send the response
- */
- static class FailToSendResponseHandler extends NoOpCustomResourceHandler {
- @Override
- protected CloudFormationResponse buildResponseClient() {
- CloudFormationResponse cfnResponse = mock(CloudFormationResponse.class);
- try {
- when(cfnResponse.send(any(), any()))
- .thenThrow(new IOException("Intentional send failure"));
- when(cfnResponse.send(any(), any(), any()))
- .thenThrow(new IOException("Intentional send failure"));
- } catch (IOException | CustomResourceResponseException e) {
- // this should never happen
- throw new RuntimeException("Unexpected mocking exception", e);
- }
- return cfnResponse;
- }
- }
}
diff --git a/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/CloudFormationIntegrationTest.java b/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/CloudFormationIntegrationTest.java
index ce45d3afc..316913bf2 100644
--- a/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/CloudFormationIntegrationTest.java
+++ b/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/CloudFormationIntegrationTest.java
@@ -23,62 +23,40 @@
import static com.github.tomakehurst.wiremock.client.WireMock.verify;
import static org.assertj.core.api.Assertions.assertThat;
-import com.amazonaws.services.lambda.runtime.ClientContext;
-import com.amazonaws.services.lambda.runtime.CognitoIdentity;
-import com.amazonaws.services.lambda.runtime.Context;
-import com.amazonaws.services.lambda.runtime.LambdaLogger;
-import com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent;
-import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo;
-import com.github.tomakehurst.wiremock.junit5.WireMockTest;
import java.util.UUID;
+
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
+
+import com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent;
+import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo;
+import com.github.tomakehurst.wiremock.junit5.WireMockTest;
+
import software.amazon.lambda.powertools.cloudformation.handlers.NoPhysicalResourceIdSetHandler;
import software.amazon.lambda.powertools.cloudformation.handlers.PhysicalResourceIdSetHandler;
import software.amazon.lambda.powertools.cloudformation.handlers.RuntimeExceptionThrownHandler;
+import software.amazon.lambda.powertools.common.stubs.TestLambdaContext;
@WireMockTest
public class CloudFormationIntegrationTest {
public static final String PHYSICAL_RESOURCE_ID = UUID.randomUUID().toString();
- public static final String LOG_STREAM_NAME = "FakeLogStreamName";
-
- private static CloudFormationCustomResourceEvent updateEventWithPhysicalResourceId(int httpPort,
- String physicalResourceId) {
- CloudFormationCustomResourceEvent.CloudFormationCustomResourceEventBuilder builder = baseEvent(httpPort);
-
- builder.withPhysicalResourceId(physicalResourceId);
- builder.withRequestType("Update");
-
- return builder.build();
- }
-
- private static CloudFormationCustomResourceEvent deleteEventWithPhysicalResourceId(int httpPort,
- String physicalResourceId) {
- CloudFormationCustomResourceEvent.CloudFormationCustomResourceEventBuilder builder = baseEvent(httpPort);
-
- builder.withPhysicalResourceId(physicalResourceId);
- builder.withRequestType("Delete");
-
- return builder.build();
- }
+ public static final String LOG_STREAM_NAME = "test-log-stream";
private static CloudFormationCustomResourceEvent.CloudFormationCustomResourceEventBuilder baseEvent(int httpPort) {
- CloudFormationCustomResourceEvent.CloudFormationCustomResourceEventBuilder builder =
- CloudFormationCustomResourceEvent.builder()
- .withResponseUrl("http://localhost:" + httpPort + "/")
- .withStackId("123")
- .withRequestId("234")
- .withLogicalResourceId("345");
-
- return builder;
+ return CloudFormationCustomResourceEvent
+ .builder()
+ .withResponseUrl("http://localhost:" + httpPort + "/")
+ .withStackId("123")
+ .withRequestId("234")
+ .withLogicalResourceId("345");
}
@ParameterizedTest
- @ValueSource(strings = {"Update", "Delete"})
+ @ValueSource(strings = { "Update", "Delete" })
void physicalResourceIdTakenFromRequestForUpdateOrDeleteWhenUserSpecifiesNull(String requestType,
- WireMockRuntimeInfo wmRuntimeInfo) {
+ WireMockRuntimeInfo wmRuntimeInfo) {
stubFor(put("/").willReturn(ok()));
NoPhysicalResourceIdSetHandler handler = new NoPhysicalResourceIdSetHandler();
@@ -89,18 +67,17 @@ void physicalResourceIdTakenFromRequestForUpdateOrDeleteWhenUserSpecifiesNull(St
.withRequestType(requestType)
.build();
- handler.handleRequest(event, new FakeContext());
+ handler.handleRequest(event, new TestLambdaContext());
verify(putRequestedFor(urlPathMatching("/"))
.withRequestBody(matchingJsonPath("[?(@.Status == 'SUCCESS')]"))
- .withRequestBody(matchingJsonPath("[?(@.PhysicalResourceId == '" + PHYSICAL_RESOURCE_ID + "')]"))
- );
+ .withRequestBody(matchingJsonPath("[?(@.PhysicalResourceId == '" + PHYSICAL_RESOURCE_ID + "')]")));
}
@ParameterizedTest
- @ValueSource(strings = {"Update", "Delete"})
+ @ValueSource(strings = { "Update", "Delete" })
void physicalResourceIdDoesNotChangeWhenRuntimeExceptionThrownWhenUpdatingOrDeleting(String requestType,
- WireMockRuntimeInfo wmRuntimeInfo) {
+ WireMockRuntimeInfo wmRuntimeInfo) {
stubFor(put("/").willReturn(ok()));
RuntimeExceptionThrownHandler handler = new RuntimeExceptionThrownHandler();
@@ -111,12 +88,11 @@ void physicalResourceIdDoesNotChangeWhenRuntimeExceptionThrownWhenUpdatingOrDele
.withRequestType(requestType)
.build();
- handler.handleRequest(event, new FakeContext());
+ handler.handleRequest(event, new TestLambdaContext());
verify(putRequestedFor(urlPathMatching("/"))
.withRequestBody(matchingJsonPath("[?(@.Status == 'FAILED')]"))
- .withRequestBody(matchingJsonPath("[?(@.PhysicalResourceId == '" + PHYSICAL_RESOURCE_ID + "')]"))
- );
+ .withRequestBody(matchingJsonPath("[?(@.PhysicalResourceId == '" + PHYSICAL_RESOURCE_ID + "')]")));
}
@Test
@@ -127,16 +103,15 @@ void runtimeExceptionThrownOnCreateSendsLogStreamNameAsPhysicalResourceId(WireMo
CloudFormationCustomResourceEvent createEvent = baseEvent(wmRuntimeInfo.getHttpPort())
.withRequestType("Create")
.build();
- handler.handleRequest(createEvent, new FakeContext());
+ handler.handleRequest(createEvent, new TestLambdaContext());
verify(putRequestedFor(urlPathMatching("/"))
.withRequestBody(matchingJsonPath("[?(@.Status == 'FAILED')]"))
- .withRequestBody(matchingJsonPath("[?(@.PhysicalResourceId == '" + LOG_STREAM_NAME + "')]"))
- );
+ .withRequestBody(matchingJsonPath("[?(@.PhysicalResourceId == '" + LOG_STREAM_NAME + "')]")));
}
@ParameterizedTest
- @ValueSource(strings = {"Update", "Delete"})
+ @ValueSource(strings = { "Update", "Delete" })
void physicalResourceIdSetFromRequestOnUpdateOrDeleteWhenCustomerDoesntProvideAPhysicalResourceId(
String requestType, WireMockRuntimeInfo wmRuntimeInfo) {
stubFor(put("/").willReturn(ok()));
@@ -149,13 +124,12 @@ void physicalResourceIdSetFromRequestOnUpdateOrDeleteWhenCustomerDoesntProvideAP
.withRequestType(requestType)
.build();
- Response response = handler.handleRequest(event, new FakeContext());
+ Response response = handler.handleRequest(event, new TestLambdaContext());
assertThat(response).isNotNull();
verify(putRequestedFor(urlPathMatching("/"))
.withRequestBody(matchingJsonPath("[?(@.Status == 'SUCCESS')]"))
- .withRequestBody(matchingJsonPath("[?(@.PhysicalResourceId == '" + PHYSICAL_RESOURCE_ID + "')]"))
- );
+ .withRequestBody(matchingJsonPath("[?(@.PhysicalResourceId == '" + PHYSICAL_RESOURCE_ID + "')]")));
}
@Test
@@ -166,17 +140,16 @@ void createNewResourceBecausePhysicalResourceIdNotSetByCustomerOnCreate(WireMock
CloudFormationCustomResourceEvent createEvent = baseEvent(wmRuntimeInfo.getHttpPort())
.withRequestType("Create")
.build();
- Response response = handler.handleRequest(createEvent, new FakeContext());
+ Response response = handler.handleRequest(createEvent, new TestLambdaContext());
assertThat(response).isNotNull();
verify(putRequestedFor(urlPathMatching("/"))
.withRequestBody(matchingJsonPath("[?(@.Status == 'SUCCESS')]"))
- .withRequestBody(matchingJsonPath("[?(@.PhysicalResourceId == '" + LOG_STREAM_NAME + "')]"))
- );
+ .withRequestBody(matchingJsonPath("[?(@.PhysicalResourceId == '" + LOG_STREAM_NAME + "')]")));
}
@ParameterizedTest
- @ValueSource(strings = {"Create", "Update", "Delete"})
+ @ValueSource(strings = { "Create", "Update", "Delete" })
void physicalResourceIdReturnedFromSuccessToCloudformation(String requestType, WireMockRuntimeInfo wmRuntimeInfo) {
String physicalResourceId = UUID.randomUUID().toString();
@@ -185,17 +158,16 @@ void physicalResourceIdReturnedFromSuccessToCloudformation(String requestType, W
CloudFormationCustomResourceEvent createEvent = baseEvent(wmRuntimeInfo.getHttpPort())
.withRequestType(requestType)
.build();
- Response response = handler.handleRequest(createEvent, new FakeContext());
+ Response response = handler.handleRequest(createEvent, new TestLambdaContext());
assertThat(response).isNotNull();
verify(putRequestedFor(urlPathMatching("/"))
.withRequestBody(matchingJsonPath("[?(@.Status == 'SUCCESS')]"))
- .withRequestBody(matchingJsonPath("[?(@.PhysicalResourceId == '" + physicalResourceId + "')]"))
- );
+ .withRequestBody(matchingJsonPath("[?(@.PhysicalResourceId == '" + physicalResourceId + "')]")));
}
@ParameterizedTest
- @ValueSource(strings = {"Create", "Update", "Delete"})
+ @ValueSource(strings = { "Create", "Update", "Delete" })
void physicalResourceIdReturnedFromFailedToCloudformation(String requestType, WireMockRuntimeInfo wmRuntimeInfo) {
String physicalResourceId = UUID.randomUUID().toString();
@@ -204,69 +176,12 @@ void physicalResourceIdReturnedFromFailedToCloudformation(String requestType, Wi
CloudFormationCustomResourceEvent createEvent = baseEvent(wmRuntimeInfo.getHttpPort())
.withRequestType(requestType)
.build();
- Response response = handler.handleRequest(createEvent, new FakeContext());
+ Response response = handler.handleRequest(createEvent, new TestLambdaContext());
assertThat(response).isNotNull();
verify(putRequestedFor(urlPathMatching("/"))
.withRequestBody(matchingJsonPath("[?(@.Status == 'FAILED')]"))
- .withRequestBody(matchingJsonPath("[?(@.PhysicalResourceId == '" + physicalResourceId + "')]"))
- );
+ .withRequestBody(matchingJsonPath("[?(@.PhysicalResourceId == '" + physicalResourceId + "')]")));
}
- private static class FakeContext implements Context {
- @Override
- public String getAwsRequestId() {
- return null;
- }
-
- @Override
- public String getLogGroupName() {
- return null;
- }
-
- @Override
- public String getLogStreamName() {
- return LOG_STREAM_NAME;
- }
-
- @Override
- public String getFunctionName() {
- return null;
- }
-
- @Override
- public String getFunctionVersion() {
- return null;
- }
-
- @Override
- public String getInvokedFunctionArn() {
- return null;
- }
-
- @Override
- public CognitoIdentity getIdentity() {
- return null;
- }
-
- @Override
- public ClientContext getClientContext() {
- return null;
- }
-
- @Override
- public int getRemainingTimeInMillis() {
- return 0;
- }
-
- @Override
- public int getMemoryLimitInMB() {
- return 0;
- }
-
- @Override
- public LambdaLogger getLogger() {
- return null;
- }
- }
}
diff --git a/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/CloudFormationResponseTest.java b/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/CloudFormationResponseTest.java
index 9da18790c..0cc65f884 100644
--- a/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/CloudFormationResponseTest.java
+++ b/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/CloudFormationResponseTest.java
@@ -20,15 +20,18 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
-import com.amazonaws.services.lambda.runtime.Context;
-import com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent;
-import com.fasterxml.jackson.databind.node.ObjectNode;
import java.io.IOException;
import java.io.InputStream;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
+
import org.junit.jupiter.api.Test;
+
+import com.amazonaws.services.lambda.runtime.Context;
+import com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
import software.amazon.awssdk.http.AbortableInputStream;
import software.amazon.awssdk.http.ExecutableHttpRequest;
import software.amazon.awssdk.http.HttpExecuteRequest;
@@ -37,11 +40,13 @@
import software.amazon.awssdk.utils.IoUtils;
import software.amazon.awssdk.utils.StringInputStream;
import software.amazon.lambda.powertools.cloudformation.CloudFormationResponse.ResponseBody;
+import software.amazon.lambda.powertools.common.stubs.TestLambdaContext;
class CloudFormationResponseTest {
/**
- * Creates a mock CloudFormationCustomResourceEvent with a non-null response URL.
+ * Creates a mock CloudFormationCustomResourceEvent with a non-null response
+ * URL.
*/
static CloudFormationCustomResourceEvent mockCloudFormationCustomResourceEvent() {
CloudFormationCustomResourceEvent event = mock(CloudFormationCustomResourceEvent.class);
@@ -50,15 +55,15 @@ static CloudFormationCustomResourceEvent mockCloudFormationCustomResourceEvent()
}
/**
- * Creates a CloudFormationResponse that does not make actual HTTP requests. The HTTP response body is the request
+ * Creates a CloudFormationResponse that does not make actual HTTP requests. The
+ * HTTP response body is the request
* body.
*/
static CloudFormationResponse testableCloudFormationResponse() {
SdkHttpClient client = mock(SdkHttpClient.class);
ExecutableHttpRequest executableRequest = mock(ExecutableHttpRequest.class);
- when(client.prepareRequest(any(HttpExecuteRequest.class))).thenAnswer(args ->
- {
+ when(client.prepareRequest(any(HttpExecuteRequest.class))).thenAnswer(args -> {
HttpExecuteRequest request = args.getArgument(0, HttpExecuteRequest.class);
assertThat(request.contentStreamProvider()).isPresent();
@@ -89,7 +94,7 @@ void eventRequiredToSend() {
SdkHttpClient client = mock(SdkHttpClient.class);
CloudFormationResponse response = new CloudFormationResponse(client);
- Context context = mock(Context.class);
+ Context context = new TestLambdaContext();
assertThatThrownBy(() -> response.send(null, context))
.isInstanceOf(CustomResourceResponseException.class);
}
@@ -99,7 +104,7 @@ void contextRequiredToSend() {
SdkHttpClient client = mock(SdkHttpClient.class);
CloudFormationResponse response = new CloudFormationResponse(client);
- Context context = mock(Context.class);
+ Context context = new TestLambdaContext();
assertThatThrownBy(() -> response.send(null, context))
.isInstanceOf(CustomResourceResponseException.class);
}
@@ -110,8 +115,9 @@ void eventResponseUrlRequiredToSend() {
CloudFormationResponse response = new CloudFormationResponse(client);
CloudFormationCustomResourceEvent event = mock(CloudFormationCustomResourceEvent.class);
- Context context = mock(Context.class);
- // not a CustomResourceResponseException since the URL is not part of the response but
+ Context context = new TestLambdaContext();
+ // not a CustomResourceResponseException since the URL is not part of the
+ // response but
// rather the location the response is sent to
assertThatThrownBy(() -> response.send(event, context))
.isInstanceOf(RuntimeException.class);
@@ -122,8 +128,7 @@ void customPhysicalResponseId() {
CloudFormationCustomResourceEvent event = mockCloudFormationCustomResourceEvent();
when(event.getPhysicalResourceId()).thenReturn("This-Is-Ignored");
- Context context = mock(Context.class);
- when(context.getLogStreamName()).thenReturn("My-Log-Stream-Name");
+ Context context = new TestLambdaContext();
String customPhysicalResourceId = "Custom-Physical-Resource-ID";
ResponseBody body = new ResponseBody(
@@ -135,7 +140,7 @@ void customPhysicalResponseId() {
@Test
void responseBodyWithNullDataNode() {
CloudFormationCustomResourceEvent event = mockCloudFormationCustomResourceEvent();
- Context context = mock(Context.class);
+ Context context = new TestLambdaContext();
ResponseBody responseBody = new ResponseBody(event, Response.Status.FAILED, null, true,
"See the details in CloudWatch Log Stream: " + context.getLogStreamName());
@@ -143,7 +148,7 @@ void responseBodyWithNullDataNode() {
String expectedJson = "{" +
"\"Status\":\"FAILED\"," +
- "\"Reason\":\"See the details in CloudWatch Log Stream: null\"," +
+ "\"Reason\":\"See the details in CloudWatch Log Stream: test-log-stream\"," +
"\"PhysicalResourceId\":null," +
"\"StackId\":null," +
"\"RequestId\":null," +
@@ -157,7 +162,7 @@ void responseBodyWithNullDataNode() {
@Test
void responseBodyWithNonNullDataNode() {
CloudFormationCustomResourceEvent event = mockCloudFormationCustomResourceEvent();
- Context context = mock(Context.class);
+ Context context = new TestLambdaContext();
ObjectNode dataNode = ResponseBody.MAPPER.createObjectNode();
dataNode.put("foo", "bar");
dataNode.put("baz", 10);
@@ -168,7 +173,7 @@ void responseBodyWithNonNullDataNode() {
String expectedJson = "{" +
"\"Status\":\"FAILED\"," +
- "\"Reason\":\"See the details in CloudWatch Log Stream: null\"," +
+ "\"Reason\":\"See the details in CloudWatch Log Stream: test-log-stream\"," +
"\"PhysicalResourceId\":null," +
"\"StackId\":null," +
"\"RequestId\":null," +
@@ -182,7 +187,7 @@ void responseBodyWithNonNullDataNode() {
@Test
void defaultStatusIsSuccess() {
CloudFormationCustomResourceEvent event = mockCloudFormationCustomResourceEvent();
- Context context = mock(Context.class);
+ Context context = new TestLambdaContext();
ResponseBody body = new ResponseBody(
event, null, null, false, "See the details in CloudWatch Log Stream: " + context.getLogStreamName());
@@ -192,7 +197,7 @@ void defaultStatusIsSuccess() {
@Test
void customStatus() {
CloudFormationCustomResourceEvent event = mockCloudFormationCustomResourceEvent();
- Context context = mock(Context.class);
+ Context context = new TestLambdaContext();
ResponseBody body = new ResponseBody(
event, Response.Status.FAILED, null, false,
@@ -203,21 +208,18 @@ void customStatus() {
@Test
void reasonIncludesLogStreamName() {
CloudFormationCustomResourceEvent event = mockCloudFormationCustomResourceEvent();
-
- String logStreamName = "My-Log-Stream-Name";
- Context context = mock(Context.class);
- when(context.getLogStreamName()).thenReturn(logStreamName);
+ Context context = new TestLambdaContext();
ResponseBody body = new ResponseBody(
event, Response.Status.SUCCESS, null, false,
"See the details in CloudWatch Log Stream: " + context.getLogStreamName());
- assertThat(body.getReason()).contains(logStreamName);
+ assertThat(body.getReason()).contains(context.getLogStreamName());
}
@Test
void sendWithNoResponseData() throws Exception {
CloudFormationCustomResourceEvent event = mockCloudFormationCustomResourceEvent();
- Context context = mock(Context.class);
+ Context context = new TestLambdaContext();
CloudFormationResponse cfnResponse = testableCloudFormationResponse();
HttpExecuteResponse response = cfnResponse.send(event, context);
@@ -225,8 +227,8 @@ void sendWithNoResponseData() throws Exception {
String actualJson = responseAsString(response);
String expectedJson = "{" +
"\"Status\":\"SUCCESS\"," +
- "\"Reason\":\"See the details in CloudWatch Log Stream: null\"," +
- "\"PhysicalResourceId\":null," +
+ "\"Reason\":\"See the details in CloudWatch Log Stream: test-log-stream\"," +
+ "\"PhysicalResourceId\":\"test-log-stream\"," +
"\"StackId\":null," +
"\"RequestId\":null," +
"\"LogicalResourceId\":null," +
@@ -239,7 +241,7 @@ void sendWithNoResponseData() throws Exception {
@Test
void sendWithNonNullResponseData() throws Exception {
CloudFormationCustomResourceEvent event = mockCloudFormationCustomResourceEvent();
- Context context = mock(Context.class);
+ Context context = new TestLambdaContext();
CloudFormationResponse cfnResponse = testableCloudFormationResponse();
Map responseData = new LinkedHashMap<>();
@@ -251,8 +253,8 @@ void sendWithNonNullResponseData() throws Exception {
String actualJson = responseAsString(response);
String expectedJson = "{" +
"\"Status\":\"SUCCESS\"," +
- "\"Reason\":\"See the details in CloudWatch Log Stream: null\"," +
- "\"PhysicalResourceId\":null," +
+ "\"Reason\":\"See the details in CloudWatch Log Stream: test-log-stream\"," +
+ "\"PhysicalResourceId\":\"test-log-stream\"," +
"\"StackId\":null," +
"\"RequestId\":null," +
"\"LogicalResourceId\":null," +
@@ -265,15 +267,15 @@ void sendWithNonNullResponseData() throws Exception {
@Test
void responseBodyStreamNullResponseDefaultsToSuccessStatus() throws Exception {
CloudFormationCustomResourceEvent event = mockCloudFormationCustomResourceEvent();
- Context context = mock(Context.class);
+ Context context = new TestLambdaContext();
CloudFormationResponse cfnResponse = testableCloudFormationResponse();
StringInputStream stream = cfnResponse.responseBodyStream(event, context, null);
String expectedJson = "{" +
"\"Status\":\"SUCCESS\"," +
- "\"Reason\":\"See the details in CloudWatch Log Stream: null\"," +
- "\"PhysicalResourceId\":null," +
+ "\"Reason\":\"See the details in CloudWatch Log Stream: test-log-stream\"," +
+ "\"PhysicalResourceId\":\"test-log-stream\"," +
"\"StackId\":null," +
"\"RequestId\":null," +
"\"LogicalResourceId\":null," +
@@ -286,15 +288,15 @@ void responseBodyStreamNullResponseDefaultsToSuccessStatus() throws Exception {
@Test
void responseBodyStreamSuccessResponse() throws Exception {
CloudFormationCustomResourceEvent event = mockCloudFormationCustomResourceEvent();
- Context context = mock(Context.class);
+ Context context = new TestLambdaContext();
CloudFormationResponse cfnResponse = testableCloudFormationResponse();
StringInputStream stream = cfnResponse.responseBodyStream(event, context, Response.success(null));
String expectedJson = "{" +
"\"Status\":\"SUCCESS\"," +
- "\"Reason\":\"See the details in CloudWatch Log Stream: null\"," +
- "\"PhysicalResourceId\":null," +
+ "\"Reason\":\"See the details in CloudWatch Log Stream: test-log-stream\"," +
+ "\"PhysicalResourceId\":\"test-log-stream\"," +
"\"StackId\":null," +
"\"RequestId\":null," +
"\"LogicalResourceId\":null," +
@@ -307,15 +309,15 @@ void responseBodyStreamSuccessResponse() throws Exception {
@Test
void responseBodyStreamFailedResponse() throws Exception {
CloudFormationCustomResourceEvent event = mockCloudFormationCustomResourceEvent();
- Context context = mock(Context.class);
+ Context context = new TestLambdaContext();
CloudFormationResponse cfnResponse = testableCloudFormationResponse();
StringInputStream stream = cfnResponse.responseBodyStream(event, context, Response.failed(null));
String expectedJson = "{" +
"\"Status\":\"FAILED\"," +
- "\"Reason\":\"See the details in CloudWatch Log Stream: null\"," +
- "\"PhysicalResourceId\":null," +
+ "\"Reason\":\"See the details in CloudWatch Log Stream: test-log-stream\"," +
+ "\"PhysicalResourceId\":\"test-log-stream\"," +
"\"StackId\":null," +
"\"RequestId\":null," +
"\"LogicalResourceId\":null," +
@@ -328,17 +330,17 @@ void responseBodyStreamFailedResponse() throws Exception {
@Test
void responseBodyStreamFailedResponseWithReason() throws Exception {
CloudFormationCustomResourceEvent event = mockCloudFormationCustomResourceEvent();
- Context context = mock(Context.class);
+ Context context = new TestLambdaContext();
CloudFormationResponse cfnResponse = testableCloudFormationResponse();
String failureReason = "Failed test reason";
- Response failedResponseWithReason = Response.builder().
- status(Response.Status.FAILED).reason(failureReason).build();
+ Response failedResponseWithReason = Response.builder().status(Response.Status.FAILED).reason(failureReason)
+ .build();
StringInputStream stream = cfnResponse.responseBodyStream(event, context, failedResponseWithReason);
String expectedJson = "{" +
"\"Status\":\"FAILED\"," +
"\"Reason\":\"" + failureReason + "\"," +
- "\"PhysicalResourceId\":null," +
+ "\"PhysicalResourceId\":\"test-log-stream\"," +
"\"StackId\":null," +
"\"RequestId\":null," +
"\"LogicalResourceId\":null," +
diff --git a/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/ExceptionThrowingHandler.java b/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/ExceptionThrowingHandler.java
new file mode 100644
index 000000000..dd2d1c853
--- /dev/null
+++ b/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/ExceptionThrowingHandler.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2023 Amazon.com, Inc. or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package software.amazon.lambda.powertools.cloudformation;
+
+import com.amazonaws.services.lambda.runtime.Context;
+import com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent;
+
+import software.amazon.lambda.powertools.cloudformation.Response.Status;
+
+public class ExceptionThrowingHandler extends ExpectedStatusResourceHandler {
+ public ExceptionThrowingHandler() {
+ super(Status.FAILED);
+ }
+
+ @Override
+ protected Response create(CloudFormationCustomResourceEvent event, Context context) {
+ throw new RuntimeException("This exception is intentional for testing");
+ }
+}
diff --git a/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/ExpectedStatusResourceHandler.java b/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/ExpectedStatusResourceHandler.java
new file mode 100644
index 000000000..7992c712c
--- /dev/null
+++ b/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/ExpectedStatusResourceHandler.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2023 Amazon.com, Inc. or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package software.amazon.lambda.powertools.cloudformation;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+
+import org.mockito.ArgumentMatcher;
+
+import software.amazon.lambda.powertools.cloudformation.Response.Status;
+
+/**
+ * Creates a handler that will expect the Response to be sent with an expected
+ * status. Will throw an AssertionError
+ * if the method is sent with an unexpected status.
+ */
+public class ExpectedStatusResourceHandler extends NoOpCustomResourceHandler {
+ private final Status expectedStatus;
+
+ public ExpectedStatusResourceHandler(Status expectedStatus) {
+ this.expectedStatus = expectedStatus;
+ }
+
+ @Override
+ CloudFormationResponse buildResponseClient() {
+ // create a CloudFormationResponse that fails if invoked with unexpected status
+ CloudFormationResponse cfnResponse = mock(CloudFormationResponse.class);
+ try {
+ when(cfnResponse.send(any(), any(), org.mockito.ArgumentMatchers.argThat(new ArgumentMatcher() {
+ @Override
+ public boolean matches(Response resp) {
+ return resp != null && resp.getStatus() != expectedStatus;
+ }
+ }))).thenThrow(new AssertionError("Expected response's status to be " + expectedStatus));
+ } catch (IOException | CustomResourceResponseException e) {
+ // this should never happen
+ throw new RuntimeException("Unexpected mocking exception", e);
+ }
+ return cfnResponse;
+ }
+}
diff --git a/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/ExplicitSuccessResponseHandler.java b/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/ExplicitSuccessResponseHandler.java
new file mode 100644
index 000000000..2b11f8020
--- /dev/null
+++ b/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/ExplicitSuccessResponseHandler.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2023 Amazon.com, Inc. or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package software.amazon.lambda.powertools.cloudformation;
+
+import com.amazonaws.services.lambda.runtime.Context;
+import com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent;
+
+import software.amazon.lambda.powertools.cloudformation.Response.Status;
+
+public class ExplicitSuccessResponseHandler extends ExpectedStatusResourceHandler {
+ public ExplicitSuccessResponseHandler() {
+ super(Status.SUCCESS);
+ }
+
+ @Override
+ protected Response create(CloudFormationCustomResourceEvent event, Context context) {
+ return Response.builder().value("whatever").status(Status.SUCCESS).build();
+ }
+}
diff --git a/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/FailToSendResponseHandler.java b/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/FailToSendResponseHandler.java
new file mode 100644
index 000000000..218994c56
--- /dev/null
+++ b/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/FailToSendResponseHandler.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2023 Amazon.com, Inc. or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package software.amazon.lambda.powertools.cloudformation;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+
+/**
+ * Always fails to send the response
+ */
+public class FailToSendResponseHandler extends NoOpCustomResourceHandler {
+ @Override
+ CloudFormationResponse buildResponseClient() {
+ CloudFormationResponse cfnResponse = mock(CloudFormationResponse.class);
+ try {
+ when(cfnResponse.send(any(), any()))
+ .thenThrow(new IOException("Intentional send failure"));
+ when(cfnResponse.send(any(), any(), any()))
+ .thenThrow(new IOException("Intentional send failure"));
+ } catch (IOException | CustomResourceResponseException e) {
+ // this should never happen
+ throw new RuntimeException("Unexpected mocking exception", e);
+ }
+ return cfnResponse;
+ }
+}
diff --git a/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/FailedResponseHandler.java b/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/FailedResponseHandler.java
new file mode 100644
index 000000000..787713535
--- /dev/null
+++ b/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/FailedResponseHandler.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2023 Amazon.com, Inc. or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package software.amazon.lambda.powertools.cloudformation;
+
+import com.amazonaws.services.lambda.runtime.Context;
+import com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent;
+
+import software.amazon.lambda.powertools.cloudformation.Response.Status;
+
+public class FailedResponseHandler extends ExpectedStatusResourceHandler {
+ public FailedResponseHandler() {
+ super(Status.FAILED);
+ }
+
+ @Override
+ protected Response create(CloudFormationCustomResourceEvent event, Context context) {
+ return Response.builder().value("whatever").status(Status.FAILED).build();
+ }
+}
diff --git a/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/FailedSendHandler.java b/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/FailedSendHandler.java
new file mode 100644
index 000000000..fe88d8895
--- /dev/null
+++ b/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/FailedSendHandler.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2023 Amazon.com, Inc. or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package software.amazon.lambda.powertools.cloudformation;
+
+import com.amazonaws.services.lambda.runtime.Context;
+import com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent;
+
+public class FailedSendHandler extends FailToSendResponseHandler {
+ @Override
+ protected Response create(CloudFormationCustomResourceEvent event, Context context) {
+ throw new RuntimeException("This exception is intentional for testing");
+ }
+}
diff --git a/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/NoOpCustomResourceHandler.java b/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/NoOpCustomResourceHandler.java
new file mode 100644
index 000000000..0271c36f5
--- /dev/null
+++ b/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/NoOpCustomResourceHandler.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2023 Amazon.com, Inc. or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package software.amazon.lambda.powertools.cloudformation;
+
+import static org.mockito.Mockito.mock;
+
+import software.amazon.awssdk.http.SdkHttpClient;
+
+/**
+ * Uses a mocked CloudFormationResponse to avoid sending actual HTTP requests.
+ */
+public class NoOpCustomResourceHandler extends NullCustomResourceHandler {
+
+ public NoOpCustomResourceHandler() {
+ super(mock(SdkHttpClient.class));
+ }
+
+ @Override
+ CloudFormationResponse buildResponseClient() {
+ return mock(CloudFormationResponse.class);
+ }
+}
diff --git a/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/NullCustomResourceHandler.java b/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/NullCustomResourceHandler.java
new file mode 100644
index 000000000..a44e2d57e
--- /dev/null
+++ b/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/NullCustomResourceHandler.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2023 Amazon.com, Inc. or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package software.amazon.lambda.powertools.cloudformation;
+
+import com.amazonaws.services.lambda.runtime.Context;
+import com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent;
+
+import software.amazon.awssdk.http.SdkHttpClient;
+
+/**
+ * Bare-bones implementation that returns null for abstract methods.
+ */
+public class NullCustomResourceHandler extends AbstractCustomResourceHandler {
+ public NullCustomResourceHandler() {
+ }
+
+ public NullCustomResourceHandler(SdkHttpClient client) {
+ super(client);
+ }
+
+ @Override
+ protected Response create(CloudFormationCustomResourceEvent event, Context context) {
+ return null;
+ }
+
+ @Override
+ protected Response update(CloudFormationCustomResourceEvent event, Context context) {
+ return null;
+ }
+
+ @Override
+ protected Response delete(CloudFormationCustomResourceEvent event, Context context) {
+ return null;
+ }
+}
diff --git a/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/ResponseTest.java b/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/ResponseTest.java
index 3e2930541..726bcbeee 100644
--- a/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/ResponseTest.java
+++ b/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/ResponseTest.java
@@ -16,12 +16,14 @@
import static org.assertj.core.api.Assertions.assertThat;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import java.util.HashMap;
import java.util.Map;
+
import org.junit.jupiter.api.Test;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.PropertyNamingStrategies;
+
class ResponseTest {
@Test
@@ -81,7 +83,7 @@ void explicitReasonWithDefaultValues() {
assertThat(response.toString()).contains("Status = SUCCESS");
assertThat(response.toString()).contains("PhysicalResourceId = null");
assertThat(response.toString()).contains("NoEcho = false");
- assertThat(response.toString()).contains("Reason = "+reason);
+ assertThat(response.toString()).contains("Reason = " + reason);
}
@Test
diff --git a/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/SuccessResponseHandler.java b/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/SuccessResponseHandler.java
new file mode 100644
index 000000000..18538bc9d
--- /dev/null
+++ b/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/SuccessResponseHandler.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2023 Amazon.com, Inc. or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package software.amazon.lambda.powertools.cloudformation;
+
+import com.amazonaws.services.lambda.runtime.Context;
+import com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent;
+
+import software.amazon.lambda.powertools.cloudformation.Response.Status;
+
+public class SuccessResponseHandler extends ExpectedStatusResourceHandler {
+ public SuccessResponseHandler() {
+ super(Status.SUCCESS);
+ }
+
+ @Override
+ protected Response create(CloudFormationCustomResourceEvent event, Context context) {
+ return Response.builder().value("whatever").build();
+ }
+}
diff --git a/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/SuccessfulSendHandler.java b/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/SuccessfulSendHandler.java
new file mode 100644
index 000000000..074d1499e
--- /dev/null
+++ b/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/SuccessfulSendHandler.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2023 Amazon.com, Inc. or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package software.amazon.lambda.powertools.cloudformation;
+
+import com.amazonaws.services.lambda.runtime.Context;
+import com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent;
+
+public class SuccessfulSendHandler extends FailToSendResponseHandler {
+ @Override
+ protected Response create(CloudFormationCustomResourceEvent event, Context context) {
+ return Response.builder().value("Failure happens on send").build();
+ }
+}
diff --git a/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/handlers/NoPhysicalResourceIdSetHandler.java b/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/handlers/NoPhysicalResourceIdSetHandler.java
index e55abca03..4fb14110c 100644
--- a/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/handlers/NoPhysicalResourceIdSetHandler.java
+++ b/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/handlers/NoPhysicalResourceIdSetHandler.java
@@ -16,6 +16,7 @@
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent;
+
import software.amazon.lambda.powertools.cloudformation.AbstractCustomResourceHandler;
import software.amazon.lambda.powertools.cloudformation.Response;
diff --git a/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/handlers/PhysicalResourceIdSetHandler.java b/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/handlers/PhysicalResourceIdSetHandler.java
index c6bd56b76..a01319342 100644
--- a/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/handlers/PhysicalResourceIdSetHandler.java
+++ b/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/handlers/PhysicalResourceIdSetHandler.java
@@ -16,6 +16,7 @@
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent;
+
import software.amazon.lambda.powertools.cloudformation.AbstractCustomResourceHandler;
import software.amazon.lambda.powertools.cloudformation.Response;
diff --git a/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/handlers/RuntimeExceptionThrownHandler.java b/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/handlers/RuntimeExceptionThrownHandler.java
index d5a11e895..10e3801c2 100644
--- a/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/handlers/RuntimeExceptionThrownHandler.java
+++ b/powertools-cloudformation/src/test/java/software/amazon/lambda/powertools/cloudformation/handlers/RuntimeExceptionThrownHandler.java
@@ -16,6 +16,7 @@
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent;
+
import software.amazon.lambda.powertools.cloudformation.AbstractCustomResourceHandler;
import software.amazon.lambda.powertools.cloudformation.Response;
diff --git a/powertools-cloudformation/src/test/resources/simplelogger.properties b/powertools-cloudformation/src/test/resources/simplelogger.properties
new file mode 100644
index 000000000..e8ba3a5fa
--- /dev/null
+++ b/powertools-cloudformation/src/test/resources/simplelogger.properties
@@ -0,0 +1,7 @@
+org.slf4j.simpleLogger.logFile=target/cloudformation-test.log
+org.slf4j.simpleLogger.defaultLogLevel=warn
+org.slf4j.simpleLogger.showDateTime=true
+org.slf4j.simpleLogger.dateTimeFormat=yyyy-MM-dd HH:mm:ss:SSS
+org.slf4j.simpleLogger.showThreadName=false
+org.slf4j.simpleLogger.showLogName=true
+org.slf4j.simpleLogger.showShortLogName=false