diff --git a/pom.xml b/pom.xml index 980d10aa7..2dcb6e2bd 100644 --- a/pom.xml +++ b/pom.xml @@ -516,6 +516,7 @@ 3.1.2 + @{argLine} --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED diff --git a/powertools-core/pom.xml b/powertools-core/pom.xml index f259b1c65..cf9ad45d1 100644 --- a/powertools-core/pom.xml +++ b/powertools-core/pom.xml @@ -82,6 +82,11 @@ assertj-core test + + org.mockito + mockito-inline + test + \ No newline at end of file diff --git a/powertools-core/src/main/java/software/amazon/lambda/powertools/core/internal/LambdaConstants.java b/powertools-core/src/main/java/software/amazon/lambda/powertools/core/internal/LambdaConstants.java index fe30b4928..bb5fc4666 100644 --- a/powertools-core/src/main/java/software/amazon/lambda/powertools/core/internal/LambdaConstants.java +++ b/powertools-core/src/main/java/software/amazon/lambda/powertools/core/internal/LambdaConstants.java @@ -18,4 +18,9 @@ public class LambdaConstants { public static final String AWS_REGION_ENV = "AWS_REGION"; public static final String AWS_LAMBDA_INITIALIZATION_TYPE = "AWS_LAMBDA_INITIALIZATION_TYPE"; public static final String ON_DEMAND = "on-demand"; + public static final String X_AMZN_TRACE_ID = "_X_AMZN_TRACE_ID"; + public static final String AWS_SAM_LOCAL = "AWS_SAM_LOCAL"; + public static final String ROOT_EQUALS = "Root="; + public static final String POWERTOOLS_SERVICE_NAME = "POWERTOOLS_SERVICE_NAME"; + public static final String SERVICE_UNDEFINED = "service_undefined"; } diff --git a/powertools-core/src/main/java/software/amazon/lambda/powertools/core/internal/LambdaHandlerProcessor.java b/powertools-core/src/main/java/software/amazon/lambda/powertools/core/internal/LambdaHandlerProcessor.java index 1cff812b8..c7f8b119f 100644 --- a/powertools-core/src/main/java/software/amazon/lambda/powertools/core/internal/LambdaHandlerProcessor.java +++ b/powertools-core/src/main/java/software/amazon/lambda/powertools/core/internal/LambdaHandlerProcessor.java @@ -27,15 +27,21 @@ import static software.amazon.lambda.powertools.core.internal.SystemWrapper.getenv; public final class LambdaHandlerProcessor { + // SERVICE_NAME cannot be final for testing purposes - private static String SERVICE_NAME = null != System.getenv("POWERTOOLS_SERVICE_NAME") - ? System.getenv("POWERTOOLS_SERVICE_NAME") : "service_undefined"; + private static String SERVICE_NAME = calculateServiceName(); + private static Boolean IS_COLD_START = null; private LambdaHandlerProcessor() { // Hide default constructor } + private static String calculateServiceName() { + return null != getenv(LambdaConstants.POWERTOOLS_SERVICE_NAME) + ? getenv(LambdaConstants.POWERTOOLS_SERVICE_NAME) : LambdaConstants.SERVICE_UNDEFINED; + } + public static boolean isHandlerMethod(final ProceedingJoinPoint pjp) { return placedOnRequestHandler(pjp) || placedOnStreamHandler(pjp); } @@ -56,23 +62,24 @@ public static boolean placedOnStreamHandler(final ProceedingJoinPoint pjp) { public static Context extractContext(final ProceedingJoinPoint pjp) { - if (isHandlerMethod(pjp)) { - if (placedOnRequestHandler(pjp)) { - return (Context) pjp.getArgs()[1]; - } - - if (placedOnStreamHandler(pjp)) { - return (Context) pjp.getArgs()[2]; - } + if (placedOnRequestHandler(pjp)) { + return (Context) pjp.getArgs()[1]; + } else if (placedOnStreamHandler(pjp)) { + return (Context) pjp.getArgs()[2]; + } else { + return null; } - - return null; } public static String serviceName() { return SERVICE_NAME; } + // Method used for testing purposes + protected static void resetServiceName() { + SERVICE_NAME = calculateServiceName(); + } + public static boolean isColdStart() { return IS_COLD_START == null; } @@ -82,13 +89,13 @@ public static void coldStartDone() { } public static boolean isSamLocal() { - return "true".equals(System.getenv("AWS_SAM_LOCAL")); + return "true".equals(getenv(LambdaConstants.AWS_SAM_LOCAL)); } public static Optional getXrayTraceId() { - final String X_AMZN_TRACE_ID = getenv("_X_AMZN_TRACE_ID"); - if(X_AMZN_TRACE_ID != null) { - return of(X_AMZN_TRACE_ID.split(";")[0].replace("Root=", "")); + final String X_AMZN_TRACE_ID = getenv(LambdaConstants.X_AMZN_TRACE_ID); + if (X_AMZN_TRACE_ID != null) { + return of(X_AMZN_TRACE_ID.split(";")[0].replace(LambdaConstants.ROOT_EQUALS, "")); } return empty(); } diff --git a/powertools-core/src/test/java/software/amazon/lambda/powertools/core/internal/LambdaHandlerProcessorTest.java b/powertools-core/src/test/java/software/amazon/lambda/powertools/core/internal/LambdaHandlerProcessorTest.java index 6ed8b4160..94ad97506 100644 --- a/powertools-core/src/test/java/software/amazon/lambda/powertools/core/internal/LambdaHandlerProcessorTest.java +++ b/powertools-core/src/test/java/software/amazon/lambda/powertools/core/internal/LambdaHandlerProcessorTest.java @@ -6,60 +6,231 @@ import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.Signature; import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; import java.io.InputStream; import java.io.OutputStream; +import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; import static org.mockito.Mockito.when; +import static software.amazon.lambda.powertools.core.internal.SystemWrapper.getenv; class LambdaHandlerProcessorTest { + private Signature signature = mock(Signature.class); + private ProceedingJoinPoint pjpMock = mock(ProceedingJoinPoint.class); + @Test void isHandlerMethod_shouldRecognizeRequestHandler() { - ProceedingJoinPoint pjpMock = mockRequestHandlerPjp(); + Object[] args = {new Object(), mock(Context.class)}; + ProceedingJoinPoint pjpMock = mockRequestHandlerPjp(RequestHandler.class, args); assertThat(LambdaHandlerProcessor.isHandlerMethod(pjpMock)).isTrue(); } @Test void isHandlerMethod_shouldRecognizeRequestStreamHandler() { - ProceedingJoinPoint pjpMock = mockRequestStreamHandlerPjp(); + Object[] args = {mock(InputStream.class), mock(OutputStream.class), mock(Context.class)}; + ProceedingJoinPoint pjpMock = mockRequestHandlerPjp(RequestStreamHandler.class, args); assertThat(LambdaHandlerProcessor.isHandlerMethod(pjpMock)).isTrue(); } + @Test + void isHandlerMethod_shouldReturnFalse() { + ProceedingJoinPoint pjpMock = mockRequestHandlerPjp(Object.class, new Object[]{}); + + boolean isHandlerMethod = LambdaHandlerProcessor.isHandlerMethod(pjpMock); + + assertThat(isHandlerMethod).isFalse(); + } + @Test void placedOnRequestHandler_shouldRecognizeRequestHandler() { - ProceedingJoinPoint pjpMock = mockRequestHandlerPjp(); + Object[] args = {new Object(), mock(Context.class)}; + ProceedingJoinPoint pjpMock = mockRequestHandlerPjp(RequestHandler.class, args); assertThat(LambdaHandlerProcessor.placedOnRequestHandler(pjpMock)).isTrue(); } @Test void placedOnStreamHandler_shouldRecognizeRequestStreamHandler() { - ProceedingJoinPoint pjpMock = mockRequestStreamHandlerPjp(); + Object[] args = {mock(InputStream.class), mock(OutputStream.class), mock(Context.class)}; + ProceedingJoinPoint pjpMock = mockRequestHandlerPjp(RequestStreamHandler.class, args); assertThat(LambdaHandlerProcessor.placedOnStreamHandler(pjpMock)).isTrue(); } - private static ProceedingJoinPoint mockRequestHandlerPjp() { - Signature signature = mock(Signature.class); - when(signature.getDeclaringType()).thenReturn(RequestHandler.class); - ProceedingJoinPoint pjpMock = mock(ProceedingJoinPoint.class); + @Test + void placedOnRequestHandler_shouldInvalidateOnWrongNoOfArgs() { + Object[] args = {new Object()}; + ProceedingJoinPoint pjpMock = mockRequestHandlerPjp(RequestHandler.class, args); + + boolean isPlacedOnRequestHandler = LambdaHandlerProcessor.placedOnRequestHandler(pjpMock); + + assertThat(isPlacedOnRequestHandler).isFalse(); + } + + @Test + void placedOnRequestHandler_shouldInvalidateOnWrongTypeOfArgs() { + Object[] args = {new Object(), new Object()}; + ProceedingJoinPoint pjpMock = mockRequestHandlerPjp(RequestHandler.class, args); + + boolean isPlacedOnRequestHandler = LambdaHandlerProcessor.placedOnRequestHandler(pjpMock); + + assertThat(isPlacedOnRequestHandler).isFalse(); + } + + @Test + void placedOnStreamHandler_shouldInvalidateOnWrongNoOfArgs() { + Object[] args = {new Object()}; + ProceedingJoinPoint pjpMock = mockRequestHandlerPjp(RequestStreamHandler.class, args); + + boolean isPlacedOnStreamHandler = LambdaHandlerProcessor.placedOnStreamHandler(pjpMock); + + assertThat(isPlacedOnStreamHandler).isFalse(); + } + + @Test + void placedOnStreamHandler_shouldInvalidateOnWrongTypeOfArgs() { + Object[] args = {new Object(), new Object(), new Object()}; + ProceedingJoinPoint pjpMock = mockRequestHandlerPjp(RequestStreamHandler.class, args); + + boolean isPlacedOnStreamHandler = LambdaHandlerProcessor.placedOnStreamHandler(pjpMock); + + assertThat(isPlacedOnStreamHandler).isFalse(); + } + + @Test + void placedOnStreamHandler_shouldInvalidateOnTypeOfArgs_invalidOutputStreamArg() { + Object[] args = {mock(InputStream.class), new Object(), mock(Context.class)}; + ProceedingJoinPoint pjpMock = mockRequestHandlerPjp(RequestStreamHandler.class, args); + + boolean isPlacedOnStreamHandler = LambdaHandlerProcessor.placedOnStreamHandler(pjpMock); + + assertThat(isPlacedOnStreamHandler).isFalse(); + } + + @Test + void placedOnStreamHandler_shouldInvalidateOnTypeOfArgs_invalidContextArg() { + Object[] args = {mock(InputStream.class), mock(OutputStream.class), new Object()}; + ProceedingJoinPoint pjpMock = mockRequestHandlerPjp(RequestStreamHandler.class, args); + + boolean isPlacedOnStreamHandler = LambdaHandlerProcessor.placedOnStreamHandler(pjpMock); + + assertThat(isPlacedOnStreamHandler).isFalse(); + } + + @Test + void getXrayTraceId_present() { + String traceID = "Root=1-5759e988-bd862e3fe1be46a994272793;Parent=53995c3f42cd8ad8;Sampled=1\""; + try (MockedStatic mockedSystemWrapper = mockStatic(SystemWrapper.class)) { + mockedSystemWrapper.when(() -> getenv(LambdaConstants.X_AMZN_TRACE_ID)).thenReturn(traceID); + + Optional xRayTraceId = LambdaHandlerProcessor.getXrayTraceId(); + + assertThat(xRayTraceId.isPresent()).isTrue(); + assertThat(traceID.split(";")[0].replace(LambdaConstants.ROOT_EQUALS, "")).isEqualTo(xRayTraceId.get()); + } + } + + @Test + void getXrayTraceId_notPresent() { + try (MockedStatic mockedSystemWrapper = mockStatic(SystemWrapper.class)) { + mockedSystemWrapper.when(() -> getenv(LambdaConstants.X_AMZN_TRACE_ID)).thenReturn(null); + + boolean isXRayTraceIdPresent = LambdaHandlerProcessor.getXrayTraceId().isPresent(); + + assertThat(isXRayTraceIdPresent).isFalse(); + } + } + + @Test + void extractContext_fromRequestHandler() { Object[] args = {new Object(), mock(Context.class)}; - when(pjpMock.getArgs()).thenReturn(args); - when(pjpMock.getSignature()).thenReturn(signature); - return pjpMock; + ProceedingJoinPoint pjpMock = mockRequestHandlerPjp(RequestHandler.class, args); + + Context context = LambdaHandlerProcessor.extractContext(pjpMock); + + assertThat(context).isNotNull(); } - private static ProceedingJoinPoint mockRequestStreamHandlerPjp() { - Signature signature = mock(Signature.class); - when(signature.getDeclaringType()).thenReturn(RequestStreamHandler.class); - ProceedingJoinPoint pjpMock = mock(ProceedingJoinPoint.class); + @Test + void extractContext_fromStreamRequestHandler() { Object[] args = {mock(InputStream.class), mock(OutputStream.class), mock(Context.class)}; - when(pjpMock.getArgs()).thenReturn(args); + ProceedingJoinPoint pjpMock = mockRequestHandlerPjp(RequestStreamHandler.class, args); + + Context context = LambdaHandlerProcessor.extractContext(pjpMock); + + assertNotNull(context); + } + + @Test + void extractContext_notKnownHandler() { + Object[] args = {new Object()}; + ProceedingJoinPoint pjpMock = mockRequestHandlerPjp(Object.class, args); + + Context context = LambdaHandlerProcessor.extractContext(pjpMock); + + assertThat(context).isNull(); + } + + @Test + void isColdStart() { + boolean isColdStart = LambdaHandlerProcessor.isColdStart(); + + assertThat(isColdStart).isTrue(); + } + + @Test + void isColdStart_coldStartDone() { + LambdaHandlerProcessor.coldStartDone(); + + boolean isColdStart = LambdaHandlerProcessor.isColdStart(); + + assertThat(isColdStart).isFalse(); + } + + @Test + void isSamLocal() { + try (MockedStatic mockedSystemWrapper = mockStatic(SystemWrapper.class)) { + mockedSystemWrapper.when(() -> getenv(LambdaConstants.AWS_SAM_LOCAL)).thenReturn("true"); + + boolean isSamLocal = LambdaHandlerProcessor.isSamLocal(); + + assertThat(isSamLocal).isTrue(); + } + } + + @Test + void serviceName() { + try (MockedStatic mockedSystemWrapper = mockStatic(SystemWrapper.class)) { + String expectedServiceName = "MyService"; + mockedSystemWrapper.when(() -> getenv(LambdaConstants.POWERTOOLS_SERVICE_NAME)).thenReturn(expectedServiceName); + + String actualServiceName = LambdaHandlerProcessor.serviceName(); + + assertThat(actualServiceName).isEqualTo(expectedServiceName); + } + } + + @Test + void serviceName_Undefined() { + LambdaHandlerProcessor.resetServiceName(); + try (MockedStatic mockedSystemWrapper = mockStatic(SystemWrapper.class)) { + mockedSystemWrapper.when(() -> getenv(LambdaConstants.POWERTOOLS_SERVICE_NAME)).thenReturn(null); + + assertThat(LambdaHandlerProcessor.serviceName()).isEqualTo(LambdaConstants.SERVICE_UNDEFINED); + } + } + + private ProceedingJoinPoint mockRequestHandlerPjp(Class handlerClass, Object[] handlerArgs) { + when(signature.getDeclaringType()).thenReturn(handlerClass); + when(pjpMock.getArgs()).thenReturn(handlerArgs); when(pjpMock.getSignature()).thenReturn(signature); return pjpMock; } diff --git a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/LoggingUtils.java b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/LoggingUtils.java index f23e274d4..9a1567d57 100644 --- a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/LoggingUtils.java +++ b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/LoggingUtils.java @@ -13,11 +13,11 @@ */ package software.amazon.lambda.powertools.logging; -import java.util.Map; - import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.logging.log4j.ThreadContext; +import java.util.Map; + import static java.util.Arrays.asList; /** diff --git a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/AbstractJacksonLayoutCopy.java b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/AbstractJacksonLayoutCopy.java index 3ceda4b79..c96d1383e 100644 --- a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/AbstractJacksonLayoutCopy.java +++ b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/AbstractJacksonLayoutCopy.java @@ -1,11 +1,12 @@ package software.amazon.lambda.powertools.logging.internal; -import java.io.IOException; -import java.io.Writer; -import java.nio.charset.Charset; -import java.util.LinkedHashMap; -import java.util.Map; - +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonRootName; +import com.fasterxml.jackson.annotation.JsonUnwrapped; +import com.fasterxml.jackson.core.JsonGenerationException; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectWriter; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.ThreadContext; @@ -25,13 +26,11 @@ import org.apache.logging.log4j.util.ReadOnlyStringMap; import org.apache.logging.log4j.util.Strings; -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonRootName; -import com.fasterxml.jackson.annotation.JsonUnwrapped; -import com.fasterxml.jackson.core.JsonGenerationException; -import com.fasterxml.jackson.databind.JsonMappingException; -import com.fasterxml.jackson.databind.ObjectWriter; +import java.io.IOException; +import java.io.Writer; +import java.nio.charset.Charset; +import java.util.LinkedHashMap; +import java.util.Map; @Deprecated abstract class AbstractJacksonLayoutCopy extends AbstractStringLayout { diff --git a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java index f522a4711..d489e093b 100644 --- a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java +++ b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java @@ -13,16 +13,6 @@ */ package software.amazon.lambda.powertools.logging.internal; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.util.Map; -import java.util.Optional; -import java.util.Random; - import com.amazonaws.services.lambda.runtime.Context; import com.fasterxml.jackson.core.JsonPointer; import com.fasterxml.jackson.core.JsonProcessingException; @@ -42,6 +32,16 @@ import software.amazon.lambda.powertools.logging.Logging; import software.amazon.lambda.powertools.logging.LoggingUtils; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.util.Map; +import java.util.Optional; +import java.util.Random; + import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Optional.empty; import static java.util.Optional.ofNullable; diff --git a/powertools-logging/src/test/java/org/apache/logging/log4j/core/layout/LambdaJsonLayoutTest.java b/powertools-logging/src/test/java/org/apache/logging/log4j/core/layout/LambdaJsonLayoutTest.java index 5fc0398d1..60f0806a9 100644 --- a/powertools-logging/src/test/java/org/apache/logging/log4j/core/layout/LambdaJsonLayoutTest.java +++ b/powertools-logging/src/test/java/org/apache/logging/log4j/core/layout/LambdaJsonLayoutTest.java @@ -13,15 +13,6 @@ */ package org.apache.logging.log4j.core.layout; -import java.io.IOException; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.nio.channels.FileChannel; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.nio.file.StandardOpenOption; -import java.util.Map; - import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestHandler; import com.fasterxml.jackson.core.JsonProcessingException; @@ -34,6 +25,15 @@ import software.amazon.lambda.powertools.logging.handlers.PowerLogToolSamplingEnabled; import software.amazon.lambda.powertools.logging.internal.LambdaLoggingAspect; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.nio.channels.FileChannel; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.util.Map; + import static java.util.Collections.emptyMap; import static org.apache.commons.lang3.reflect.FieldUtils.writeStaticField; import static org.assertj.core.api.Assertions.assertThat; diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/LoggingUtilsTest.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/LoggingUtilsTest.java index 91fea4c7a..eee8ace05 100644 --- a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/LoggingUtilsTest.java +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/LoggingUtilsTest.java @@ -13,13 +13,13 @@ */ package software.amazon.lambda.powertools.logging; -import java.util.HashMap; -import java.util.Map; - import org.apache.logging.log4j.ThreadContext; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import java.util.HashMap; +import java.util.Map; + import static org.assertj.core.api.Assertions.assertThat; diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerToolDisabledForStream.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerToolDisabledForStream.java index f0f7f676e..15b39c6c5 100644 --- a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerToolDisabledForStream.java +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerToolDisabledForStream.java @@ -13,12 +13,12 @@ */ package software.amazon.lambda.powertools.logging.handlers; -import java.io.InputStream; -import java.io.OutputStream; - import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestStreamHandler; +import java.io.InputStream; +import java.io.OutputStream; + public class PowerToolDisabledForStream implements RequestStreamHandler { @Override diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerToolLogEventEnabledWithCustomMapper.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerToolLogEventEnabledWithCustomMapper.java index d761c9ac0..f1c2f62c8 100644 --- a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerToolLogEventEnabledWithCustomMapper.java +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerToolLogEventEnabledWithCustomMapper.java @@ -1,7 +1,5 @@ package software.amazon.lambda.powertools.logging.handlers; -import java.io.IOException; - import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestHandler; import com.amazonaws.services.lambda.runtime.events.models.s3.S3EventNotification; @@ -13,6 +11,8 @@ import software.amazon.lambda.powertools.logging.Logging; import software.amazon.lambda.powertools.logging.LoggingUtils; +import java.io.IOException; + public class PowerToolLogEventEnabledWithCustomMapper implements RequestHandler { static { diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerLogToolAlbCorrelationId.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogAlbCorrelationId.java similarity index 78% rename from powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerLogToolAlbCorrelationId.java rename to powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogAlbCorrelationId.java index 125c13e26..c06f8326e 100644 --- a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerLogToolAlbCorrelationId.java +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogAlbCorrelationId.java @@ -18,14 +18,12 @@ import com.amazonaws.services.lambda.runtime.events.ApplicationLoadBalancerRequestEvent; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import software.amazon.lambda.powertools.logging.CorrelationIdPathConstants; import software.amazon.lambda.powertools.logging.Logging; -import static software.amazon.lambda.powertools.logging.CorrelationIdPathConstants.API_GATEWAY_REST; import static software.amazon.lambda.powertools.logging.CorrelationIdPathConstants.APPLICATION_LOAD_BALANCER; -public class PowerLogToolAlbCorrelationId implements RequestHandler { - private final Logger LOG = LogManager.getLogger(PowerLogToolAlbCorrelationId.class); +public class PowertoolsLogAlbCorrelationId implements RequestHandler { + private final Logger LOG = LogManager.getLogger(PowertoolsLogAlbCorrelationId.class); @Override @Logging(correlationIdPath = APPLICATION_LOAD_BALANCER) diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerLogToolEnabledWithClearState.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogEnabledWithClearState.java similarity index 85% rename from powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerLogToolEnabledWithClearState.java rename to powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogEnabledWithClearState.java index 8fef32c94..8b7d4fcaa 100644 --- a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerLogToolEnabledWithClearState.java +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogEnabledWithClearState.java @@ -20,13 +20,14 @@ import software.amazon.lambda.powertools.logging.Logging; import software.amazon.lambda.powertools.logging.LoggingUtils; -public class PowerLogToolEnabledWithClearState implements RequestHandler { - private final Logger LOG = LogManager.getLogger(PowerLogToolEnabledWithClearState.class); +public class PowertoolsLogEnabledWithClearState implements RequestHandler { public static int COUNT = 1; + private static final Logger LOG = LogManager.getLogger(PowertoolsLogEnabledWithClearState.class); + @Override @Logging(clearState = true) public Object handleRequest(Object input, Context context) { - if(COUNT == 1) { + if (COUNT == 1) { LoggingUtils.appendKey("TestKey", "TestValue"); } LOG.info("Test event"); diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogEventBridgeCorrelationId.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogEventBridgeCorrelationId.java new file mode 100644 index 000000000..f03983aff --- /dev/null +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogEventBridgeCorrelationId.java @@ -0,0 +1,37 @@ +/* + * 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.logging.handlers; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestStreamHandler; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import software.amazon.lambda.powertools.logging.Logging; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import static software.amazon.lambda.powertools.logging.CorrelationIdPathConstants.EVENT_BRIDGE; + +public class PowertoolsLogEventBridgeCorrelationId implements RequestStreamHandler { + + private final Logger LOG = LogManager.getLogger(PowertoolsLogEventBridgeCorrelationId.class); + + @Override + @Logging(correlationIdPath = EVENT_BRIDGE) + public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context) throws IOException { + LOG.info("Test event"); + } +} \ No newline at end of file diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java index e2cb58453..8e7d2d3e6 100644 --- a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java @@ -13,22 +13,6 @@ */ package software.amazon.lambda.powertools.logging.internal; -import java.io.BufferedReader; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStreamReader; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.nio.channels.FileChannel; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.nio.file.StandardOpenOption; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestHandler; import com.amazonaws.services.lambda.runtime.RequestStreamHandler; @@ -49,25 +33,35 @@ import org.mockito.MockedStatic; import software.amazon.lambda.powertools.core.internal.LambdaHandlerProcessor; import software.amazon.lambda.powertools.core.internal.SystemWrapper; -import software.amazon.lambda.powertools.logging.handlers.PowerLogToolAlbCorrelationId; import software.amazon.lambda.powertools.logging.handlers.PowerLogToolApiGatewayHttpApiCorrelationId; import software.amazon.lambda.powertools.logging.handlers.PowerLogToolApiGatewayRestApiCorrelationId; import software.amazon.lambda.powertools.logging.handlers.PowerLogToolEnabled; import software.amazon.lambda.powertools.logging.handlers.PowerLogToolEnabledForStream; -import software.amazon.lambda.powertools.logging.handlers.PowerLogToolEnabledWithClearState; import software.amazon.lambda.powertools.logging.handlers.PowerToolDisabled; import software.amazon.lambda.powertools.logging.handlers.PowerToolDisabledForStream; import software.amazon.lambda.powertools.logging.handlers.PowerToolLogEventEnabled; import software.amazon.lambda.powertools.logging.handlers.PowerToolLogEventEnabledForStream; import software.amazon.lambda.powertools.logging.handlers.PowerToolLogEventEnabledWithCustomMapper; +import software.amazon.lambda.powertools.logging.handlers.PowertoolsLogAlbCorrelationId; +import software.amazon.lambda.powertools.logging.handlers.PowertoolsLogEnabledWithClearState; +import software.amazon.lambda.powertools.logging.handlers.PowertoolsLogEventBridgeCorrelationId; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.nio.channels.FileChannel; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; -import static com.amazonaws.services.lambda.runtime.events.models.s3.S3EventNotification.RequestParametersEntity; -import static com.amazonaws.services.lambda.runtime.events.models.s3.S3EventNotification.ResponseElementsEntity; -import static com.amazonaws.services.lambda.runtime.events.models.s3.S3EventNotification.S3BucketEntity; -import static com.amazonaws.services.lambda.runtime.events.models.s3.S3EventNotification.S3Entity; -import static com.amazonaws.services.lambda.runtime.events.models.s3.S3EventNotification.S3EventNotificationRecord; -import static com.amazonaws.services.lambda.runtime.events.models.s3.S3EventNotification.S3ObjectEntity; -import static com.amazonaws.services.lambda.runtime.events.models.s3.S3EventNotification.UserIdentityEntity; import static java.util.Collections.emptyMap; import static java.util.Collections.singletonList; import static java.util.stream.Collectors.joining; @@ -284,7 +278,7 @@ void shouldLogCorrelationIdOnAPIGatewayV2HTTPEvent(APIGatewayV2HTTPEvent event) @ParameterizedTest @Event(value = "albEvent.json", type = ApplicationLoadBalancerRequestEvent.class) void shouldLogCorrelationIdOnALBEvent(ApplicationLoadBalancerRequestEvent event) { - RequestHandler handler = new PowerLogToolAlbCorrelationId(); + RequestHandler handler = new PowertoolsLogAlbCorrelationId(); handler.handleRequest(event, context); assertThat(ThreadContext.getImmutableContext()) @@ -292,9 +286,23 @@ void shouldLogCorrelationIdOnALBEvent(ApplicationLoadBalancerRequestEvent event) .containsEntry("correlation_id", event.getHeaders().get("x-amzn-trace-id")); } + @Test + void shouldLogCorrelationIdOnStreamHandler() throws IOException { + RequestStreamHandler handler = new PowertoolsLogEventBridgeCorrelationId(); + String eventId = "3"; + String event = "{\"id\":" + eventId + "}"; // CorrelationIdPathConstants.EVENT_BRIDGE + ByteArrayInputStream inputStream = new ByteArrayInputStream(event.getBytes()); + handler.handleRequest(inputStream, new ByteArrayOutputStream(), context); + + + assertThat(ThreadContext.getImmutableContext()) + .hasSize(EXPECTED_CONTEXT_SIZE + 1) + .containsEntry("correlation_id", eventId); + } + @Test void shouldLogAndClearLogContextOnEachRequest() throws IOException { - requestHandler = new PowerLogToolEnabledWithClearState(); + requestHandler = new PowertoolsLogEnabledWithClearState(); S3EventNotification s3EventNotification = s3EventNotification(); requestHandler.handleRequest(s3EventNotification, context); @@ -328,24 +336,24 @@ private void resetLogLevel(Level level) throws NoSuchMethodException, IllegalAcc } private S3EventNotification s3EventNotification() { - S3EventNotificationRecord record = new S3EventNotificationRecord("us-west-2", + S3EventNotification.S3EventNotificationRecord record = new S3EventNotification.S3EventNotificationRecord("us-west-2", "ObjectCreated:Put", "aws:s3", null, "2.1", - new RequestParametersEntity("127.0.0.1"), - new ResponseElementsEntity("C3D13FE58DE4C810", "FMyUVURIY8/IgAtTv8xRjskZQpcIZ9KG4V5Wp6S7S/JRWeUWerMUE5JgHvANOjpD"), - new S3Entity("testConfigRule", - new S3BucketEntity("mybucket", - new UserIdentityEntity("A3NL1KOZZKExample"), + new S3EventNotification.RequestParametersEntity("127.0.0.1"), + new S3EventNotification.ResponseElementsEntity("C3D13FE58DE4C810", "FMyUVURIY8/IgAtTv8xRjskZQpcIZ9KG4V5Wp6S7S/JRWeUWerMUE5JgHvANOjpD"), + new S3EventNotification.S3Entity("testConfigRule", + new S3EventNotification.S3BucketEntity("mybucket", + new S3EventNotification.UserIdentityEntity("A3NL1KOZZKExample"), "arn:aws:s3:::mybucket"), - new S3ObjectEntity("HappyFace.jpg", + new S3EventNotification.S3ObjectEntity("HappyFace.jpg", 1024L, "d41d8cd98f00b204e9800998ecf8427e", "096fKKXTRTtl3on89fVO.nfljtsv6qko", "0055AED6DCD90281E5"), "1.0"), - new UserIdentityEntity("AIDAJDPLRKLG7UEXAMPLE") + new S3EventNotification.UserIdentityEntity("AIDAJDPLRKLG7UEXAMPLE") ); return new S3EventNotification(singletonList(record)); diff --git a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/AppConfigProvider.java b/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/AppConfigProvider.java index 6e664a82e..e0255125d 100644 --- a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/AppConfigProvider.java +++ b/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/AppConfigProvider.java @@ -9,8 +9,6 @@ import software.amazon.awssdk.services.appconfigdata.model.GetLatestConfigurationRequest; import software.amazon.awssdk.services.appconfigdata.model.GetLatestConfigurationResponse; import software.amazon.awssdk.services.appconfigdata.model.StartConfigurationSessionRequest; -import software.amazon.awssdk.services.ssm.SsmClient; -import software.amazon.awssdk.services.ssm.SsmClientBuilder; import software.amazon.lambda.powertools.core.internal.LambdaConstants; import software.amazon.lambda.powertools.parameters.cache.CacheManager; import software.amazon.lambda.powertools.parameters.transform.TransformationManager; diff --git a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/DynamoDbProvider.java b/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/DynamoDbProvider.java index bf8d763b9..5144af0c2 100644 --- a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/DynamoDbProvider.java +++ b/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/DynamoDbProvider.java @@ -5,7 +5,11 @@ import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.dynamodb.DynamoDbClient; -import software.amazon.awssdk.services.dynamodb.model.*; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +import software.amazon.awssdk.services.dynamodb.model.GetItemRequest; +import software.amazon.awssdk.services.dynamodb.model.GetItemResponse; +import software.amazon.awssdk.services.dynamodb.model.QueryRequest; +import software.amazon.awssdk.services.dynamodb.model.QueryResponse; import software.amazon.lambda.powertools.parameters.cache.CacheManager; import software.amazon.lambda.powertools.parameters.exception.DynamoDbProviderSchemaException; import software.amazon.lambda.powertools.parameters.transform.TransformationManager; diff --git a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/ParamManager.java b/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/ParamManager.java index 96cbabd0e..b2e541c43 100644 --- a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/ParamManager.java +++ b/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/ParamManager.java @@ -14,14 +14,13 @@ package software.amazon.lambda.powertools.parameters; import software.amazon.awssdk.services.appconfigdata.AppConfigDataClient; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient; import software.amazon.awssdk.services.ssm.SsmClient; -import software.amazon.awssdk.services.dynamodb.DynamoDbClient; import software.amazon.lambda.powertools.parameters.cache.CacheManager; import software.amazon.lambda.powertools.parameters.transform.TransformationManager; import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; import java.util.concurrent.ConcurrentHashMap; /** @@ -164,13 +163,13 @@ public static TransformationManager getTransformationManager() { return transformationManager; } - private static T createProvider(Class providerClass) { + static T createProvider(Class providerClass) { try { Constructor constructor = providerClass.getDeclaredConstructor(CacheManager.class); T provider = constructor.newInstance(cacheManager); provider.setTransformationManager(transformationManager); return provider; - } catch (NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException e) { + } catch (ReflectiveOperationException e) { throw new RuntimeException("Unexpected error occurred. Please raise issue at " + "https://github.com/aws-powertools/powertools-lambda-java/issues", e); } diff --git a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/SSMProvider.java b/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/SSMProvider.java index bf36aa717..d6d87747b 100644 --- a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/SSMProvider.java +++ b/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/SSMProvider.java @@ -13,10 +13,6 @@ */ package software.amazon.lambda.powertools.parameters; -import java.time.temporal.ChronoUnit; -import java.util.HashMap; -import java.util.Map; - import software.amazon.awssdk.auth.credentials.EnvironmentVariableCredentialsProvider; import software.amazon.awssdk.core.SdkSystemSetting; import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient; @@ -32,6 +28,10 @@ import software.amazon.lambda.powertools.parameters.transform.TransformationManager; import software.amazon.lambda.powertools.parameters.transform.Transformer; +import java.time.temporal.ChronoUnit; +import java.util.HashMap; +import java.util.Map; + import static software.amazon.lambda.powertools.core.internal.LambdaConstants.AWS_LAMBDA_INITIALIZATION_TYPE; /** @@ -74,7 +74,7 @@ */ public class SSMProvider extends BaseProvider { - private final SsmClient client; + private SsmClient client; private boolean decrypt = false; private boolean recursive = false; @@ -92,6 +92,15 @@ public class SSMProvider extends BaseProvider { this.client = client; } + /** + * Constructor + * + * @param cacheManager handles the parameter caching + */ + SSMProvider(CacheManager cacheManager) { + super(cacheManager); + } + /** * Retrieve the parameter value from the AWS System Manager Parameter Store. * diff --git a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/SecretsProvider.java b/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/SecretsProvider.java index 9764564a9..54d0daee3 100644 --- a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/SecretsProvider.java +++ b/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/SecretsProvider.java @@ -13,10 +13,6 @@ */ package software.amazon.lambda.powertools.parameters; -import java.time.temporal.ChronoUnit; -import java.util.Base64; -import java.util.Map; - import software.amazon.awssdk.auth.credentials.EnvironmentVariableCredentialsProvider; import software.amazon.awssdk.core.SdkSystemSetting; import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient; @@ -29,6 +25,10 @@ import software.amazon.lambda.powertools.parameters.transform.TransformationManager; import software.amazon.lambda.powertools.parameters.transform.Transformer; +import java.time.temporal.ChronoUnit; +import java.util.Base64; +import java.util.Map; + import static java.nio.charset.StandardCharsets.UTF_8; import static software.amazon.lambda.powertools.core.internal.LambdaConstants.AWS_LAMBDA_INITIALIZATION_TYPE; @@ -58,7 +58,7 @@ */ public class SecretsProvider extends BaseProvider { - private final SecretsManagerClient client; + private SecretsManagerClient client; /** * Constructor with custom {@link SecretsManagerClient}.
@@ -73,6 +73,15 @@ public class SecretsProvider extends BaseProvider { this.client = client; } + /** + * Constructor + * + * @param cacheManager handles the parameter caching + */ + SecretsProvider(CacheManager cacheManager) { + super(cacheManager); + } + /** * Retrieve the parameter value from the AWS Secrets Manager. * diff --git a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/internal/LambdaParametersAspect.java b/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/internal/LambdaParametersAspect.java index ea4d465cd..8de2f3f57 100644 --- a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/internal/LambdaParametersAspect.java +++ b/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/internal/LambdaParametersAspect.java @@ -5,7 +5,9 @@ import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.FieldSignature; -import software.amazon.lambda.powertools.parameters.*; +import software.amazon.lambda.powertools.parameters.BaseProvider; +import software.amazon.lambda.powertools.parameters.Param; +import software.amazon.lambda.powertools.parameters.ParamManager; @Aspect public class LambdaParametersAspect { @@ -16,9 +18,6 @@ public void getParam(Param paramAnnotation) { @Around("getParam(paramAnnotation)") public Object injectParam(final ProceedingJoinPoint joinPoint, final Param paramAnnotation) { - if(null == paramAnnotation.provider()) { - throw new IllegalArgumentException("provider for Param annotation cannot be null!"); - } BaseProvider provider = ParamManager.getProvider(paramAnnotation.provider()); if(paramAnnotation.transformer().isInterface()) { diff --git a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/transform/Base64Transformer.java b/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/transform/Base64Transformer.java index 944f4f03c..c666edce7 100644 --- a/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/transform/Base64Transformer.java +++ b/powertools-parameters/src/main/java/software/amazon/lambda/powertools/parameters/transform/Base64Transformer.java @@ -13,10 +13,10 @@ */ package software.amazon.lambda.powertools.parameters.transform; -import java.util.Base64; - import software.amazon.lambda.powertools.parameters.exception.TransformationException; +import java.util.Base64; + import static java.nio.charset.StandardCharsets.UTF_8; /** diff --git a/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/AppConfigProviderTest.java b/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/AppConfigProviderTest.java index d72a1f042..23f6271da 100644 --- a/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/AppConfigProviderTest.java +++ b/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/AppConfigProviderTest.java @@ -7,7 +7,6 @@ import org.mockito.Mock; import org.mockito.Mockito; import software.amazon.awssdk.core.SdkBytes; -import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient; import software.amazon.awssdk.services.appconfigdata.AppConfigDataClient; import software.amazon.awssdk.services.appconfigdata.model.GetLatestConfigurationRequest; import software.amazon.awssdk.services.appconfigdata.model.GetLatestConfigurationResponse; @@ -17,34 +16,30 @@ import software.amazon.lambda.powertools.parameters.transform.TransformationManager; import static org.assertj.core.api.Assertions.assertThat; - -import java.time.Duration; -import java.util.Optional; - -import static org.mockito.ArgumentMatchers.eq; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; +import static org.assertj.core.api.Assertions.assertThatRuntimeException; import static org.mockito.MockitoAnnotations.openMocks; public class AppConfigProviderTest { + private final String environmentName = "test"; + private final String applicationName = "fakeApp"; + private final String defaultTestKey = "key1"; + @Mock AppConfigDataClient client; - - private AppConfigProvider provider; - + @Captor ArgumentCaptor startSessionRequestCaptor; - + @Captor ArgumentCaptor getLatestConfigurationRequestCaptor; - private final String environmentName = "test"; - - private final String applicationName = "fakeApp"; - - private final String defaultTestKey = "key1"; + private AppConfigProvider provider; @BeforeEach public void init() { openMocks(this); + provider = AppConfigProvider.builder() .withClient(client) .withApplication(applicationName) @@ -69,18 +64,18 @@ public void getValueRetrievesValue() { .build(); // first response returns 'value1' GetLatestConfigurationResponse firstResponse = GetLatestConfigurationResponse.builder() - .nextPollConfigurationToken("token2") - .configuration(SdkBytes.fromUtf8String("value1")) - .build(); + .nextPollConfigurationToken("token2") + .configuration(SdkBytes.fromUtf8String("value1")) + .build(); // Second response returns 'value2' GetLatestConfigurationResponse secondResponse = GetLatestConfigurationResponse.builder() - .nextPollConfigurationToken("token3") - .configuration(SdkBytes.fromUtf8String("value2")) - .build(); + .nextPollConfigurationToken("token3") + .configuration(SdkBytes.fromUtf8String("value2")) + .build(); // Third response returns nothing, which means the provider should yield the previous value again GetLatestConfigurationResponse thirdResponse = GetLatestConfigurationResponse.builder() - .nextPollConfigurationToken("token4") - .build(); + .nextPollConfigurationToken("token4") + .build(); Mockito.when(client.startConfigurationSession(startSessionRequestCaptor.capture())) .thenReturn(firstSession); Mockito.when(client.getLatestConfiguration(getLatestConfigurationRequestCaptor.capture())) @@ -103,6 +98,29 @@ public void getValueRetrievesValue() { assertThat(getLatestConfigurationRequestCaptor.getAllValues().get(2).configurationToken()).isEqualTo(secondResponse.nextPollConfigurationToken()); } + @Test + public void getValueNoValueExists() { + + // Arrange + StartConfigurationSessionResponse session = StartConfigurationSessionResponse.builder() + .initialConfigurationToken("token1") + .build(); + GetLatestConfigurationResponse response = GetLatestConfigurationResponse.builder() + .nextPollConfigurationToken("token2") + .build(); + Mockito.when(client.startConfigurationSession(startSessionRequestCaptor.capture())) + .thenReturn(session); + Mockito.when(client.getLatestConfiguration(getLatestConfigurationRequestCaptor.capture())) + .thenReturn(response); + + // Act + String returnedValue = provider.getValue(defaultTestKey); + + + // Assert + assertThat(returnedValue).isEqualTo(null); + } + /** * If we mix requests for different keys together through the same provider, retrieval should * work as expected. This means two separate configuration sessions should be established with AppConfig. @@ -145,4 +163,47 @@ public void multipleKeysRetrievalWorks() { } + @Test + public void getMultipleValuesThrowsException() { + + // Act & Assert + assertThatRuntimeException().isThrownBy(() -> provider.getMultipleValues("path")) + .withMessage("Retrieving multiple parameter values is not supported with the AWS App Config Provider"); + } + + @Test + public void testAppConfigProviderBuilderMissingCacheManager_throwsException() { + + // Act & Assert + assertThatIllegalStateException().isThrownBy(() -> AppConfigProvider.builder() + .withEnvironment(environmentName) + .withApplication(applicationName) + .withClient(client) + .build()) + .withMessage("No CacheManager provided; please provide one"); + } + + @Test + public void testAppConfigProviderBuilderMissingEnvironment_throwsException() { + + // Act & Assert + assertThatIllegalStateException().isThrownBy(() -> AppConfigProvider.builder() + .withCacheManager(new CacheManager()) + .withApplication(applicationName) + .withClient(client) + .build()) + .withMessage("No environment provided; please provide one"); + } + + @Test + public void testAppConfigProviderBuilderMissingApplication_throwsException() { + + // Act & Assert + assertThatIllegalStateException().isThrownBy(() -> AppConfigProvider.builder() + .withCacheManager(new CacheManager()) + .withEnvironment(environmentName) + .withClient(client) + .build()) + .withMessage("No application provided; please provide one"); + } } diff --git a/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/DynamoDbProviderTest.java b/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/DynamoDbProviderTest.java index 0e5b734d6..d6818a64f 100644 --- a/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/DynamoDbProviderTest.java +++ b/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/DynamoDbProviderTest.java @@ -8,15 +8,21 @@ import org.mockito.Mock; import org.mockito.Mockito; import software.amazon.awssdk.services.dynamodb.DynamoDbClient; -import software.amazon.awssdk.services.dynamodb.model.*; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +import software.amazon.awssdk.services.dynamodb.model.GetItemRequest; +import software.amazon.awssdk.services.dynamodb.model.GetItemResponse; +import software.amazon.awssdk.services.dynamodb.model.QueryRequest; +import software.amazon.awssdk.services.dynamodb.model.QueryResponse; import software.amazon.lambda.powertools.parameters.cache.CacheManager; import software.amazon.lambda.powertools.parameters.exception.DynamoDbProviderSchemaException; +import software.amazon.lambda.powertools.parameters.transform.TransformationManager; import java.util.HashMap; import java.util.Map; import java.util.UUID; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; import static org.mockito.MockitoAnnotations.openMocks; public class DynamoDbProviderTest { @@ -24,6 +30,9 @@ public class DynamoDbProviderTest { @Mock DynamoDbClient client; + @Mock + TransformationManager transformationManager; + @Captor ArgumentCaptor getItemValueCaptor; @@ -67,7 +76,7 @@ public void getValue() { @Test - public void getValueWithoutResultsReturnsNull() { + public void getValueWithNullResultsReturnsNull() { // Arrange Mockito.when(client.getItem(getItemValueCaptor.capture())).thenReturn(GetItemResponse.builder() .item(null) @@ -80,6 +89,20 @@ public void getValueWithoutResultsReturnsNull() { assertThat(value).isEqualTo(null); } + @Test + public void getValueWithoutResultsReturnsNull() { + // Arrange + Mockito.when(client.getItem(getItemValueCaptor.capture())).thenReturn(GetItemResponse.builder() + .item(new HashMap<>()) + .build()); + + // Act + String value = provider.getValue("key"); + + // Assert + assertThat(value).isEqualTo(null); + } + @Test public void getValueWithMalformedRowThrows() { // Arrange @@ -92,7 +115,7 @@ public void getValueWithMalformedRowThrows() { .build()); // Act Assertions.assertThrows(DynamoDbProviderSchemaException.class, () -> { - String value = provider.getValue(key); + provider.getValue(key); }); } @@ -144,6 +167,25 @@ public void getValuesWithoutResultsReturnsNull() { assertThat(values.size()).isEqualTo(0); } + @Test + public void getMultipleValuesMissingSortKey_throwsException() { + // Arrange + String key = "Key1"; + HashMap item = new HashMap(); + item.put("id", AttributeValue.fromS(key)); + item.put("value", AttributeValue.fromS("somevalue")); + QueryResponse response = QueryResponse.builder() + .items(item) + .build(); + Mockito.when(client.query(queryRequestCaptor.capture())).thenReturn(response); + + // Assert + Assertions.assertThrows(DynamoDbProviderSchemaException.class, () -> { + // Act + provider.getMultipleValues(key); + }); + } + @Test public void getValuesWithMalformedRowThrows() { // Arrange @@ -160,9 +202,25 @@ public void getValuesWithMalformedRowThrows() { // Assert Assertions.assertThrows(DynamoDbProviderSchemaException.class, () -> { // Act - Map values = provider.getMultipleValues(key); + provider.getMultipleValues(key); }); } + @Test + public void testDynamoDBBuilderMissingCacheManager_throwsException() { + + // Act & Assert + assertThatIllegalStateException().isThrownBy(() -> DynamoDbProvider.builder() + .withTable("table") + .build()); + } + @Test + public void testDynamoDBBuilderMissingTable_throwsException() { + + // Act & Assert + assertThatIllegalStateException().isThrownBy(() -> DynamoDbProvider.builder() + .withCacheManager(new CacheManager()) + .build()); + } } diff --git a/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/ParamManagerIntegrationTest.java b/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/ParamManagerIntegrationTest.java index fca0a9362..e1cb72be9 100644 --- a/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/ParamManagerIntegrationTest.java +++ b/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/ParamManagerIntegrationTest.java @@ -25,7 +25,11 @@ import software.amazon.awssdk.services.secretsmanager.model.GetSecretValueRequest; import software.amazon.awssdk.services.secretsmanager.model.GetSecretValueResponse; import software.amazon.awssdk.services.ssm.SsmClient; -import software.amazon.awssdk.services.ssm.model.*; +import software.amazon.awssdk.services.ssm.model.GetParameterRequest; +import software.amazon.awssdk.services.ssm.model.GetParameterResponse; +import software.amazon.awssdk.services.ssm.model.GetParametersByPathRequest; +import software.amazon.awssdk.services.ssm.model.GetParametersByPathResponse; +import software.amazon.awssdk.services.ssm.model.Parameter; import java.util.ArrayList; import java.util.List; @@ -35,7 +39,9 @@ import static org.apache.commons.lang3.reflect.FieldUtils.writeStaticField; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import static org.mockito.MockitoAnnotations.openMocks; public class ParamManagerIntegrationTest { diff --git a/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/ParamManagerTest.java b/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/ParamManagerTest.java new file mode 100644 index 000000000..ee61691c1 --- /dev/null +++ b/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/ParamManagerTest.java @@ -0,0 +1,101 @@ +/* + * 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.parameters; + +import org.junit.jupiter.api.Test; +import software.amazon.lambda.powertools.parameters.cache.CacheManager; +import software.amazon.lambda.powertools.parameters.internal.CustomProvider; +import software.amazon.lambda.powertools.parameters.transform.TransformationManager; + +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; +import static org.assertj.core.api.Assertions.assertThatRuntimeException; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +public class ParamManagerTest { + + @Test + public void testGetCacheManager() { + + // Act + CacheManager cacheManager = ParamManager.getCacheManager(); + + // Assert + assertNotNull(cacheManager); + } + + @Test + public void testGetTransformationManager() { + + // Act + TransformationManager transformationManager = ParamManager.getTransformationManager(); + + // Assert + assertNotNull(transformationManager); + } + + @Test + public void testCreateProvider() { + + // Act + CustomProvider customProvider = ParamManager.createProvider(CustomProvider.class); + + // Assert + assertNotNull(customProvider); + } + + @Test + public void testCreateUninstanciableProvider_throwsException() { + + // Act & Assert + assertThatRuntimeException().isThrownBy(() -> ParamManager.createProvider(BaseProvider.class)); + } + + @Test + public void testGetProviderWithProviderClass() { + + // Act + SecretsProvider secretsProvider = ParamManager.getProvider(SecretsProvider.class); + + // Assert + assertNotNull(secretsProvider); + } + + @Test + public void testGetProviderWithProviderClass_throwsException() { + + // Act & Assert + assertThatIllegalStateException().isThrownBy(() -> ParamManager.getProvider(null)); + } + + @Test + public void testGetSecretsProvider() { + + // Act + SecretsProvider secretsProvider = ParamManager.getSecretsProvider(); + + // Assert + assertNotNull(secretsProvider); + } + + @Test + public void testGetSSMProvider() { + + // Act + SSMProvider ssmProvider = ParamManager.getSsmProvider(); + + // Assert + assertNotNull(ssmProvider); + } + +} diff --git a/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/SSMProviderTest.java b/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/SSMProviderTest.java index da299c38d..e55f3d7e6 100644 --- a/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/SSMProviderTest.java +++ b/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/SSMProviderTest.java @@ -20,16 +20,26 @@ import org.mockito.Captor; import org.mockito.Mock; import software.amazon.awssdk.services.ssm.SsmClient; -import software.amazon.awssdk.services.ssm.model.*; +import software.amazon.awssdk.services.ssm.model.GetParameterRequest; +import software.amazon.awssdk.services.ssm.model.GetParameterResponse; +import software.amazon.awssdk.services.ssm.model.GetParametersByPathRequest; +import software.amazon.awssdk.services.ssm.model.GetParametersByPathResponse; +import software.amazon.awssdk.services.ssm.model.Parameter; import software.amazon.lambda.powertools.parameters.cache.CacheManager; +import software.amazon.lambda.powertools.parameters.transform.TransformationManager; +import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.List; import java.util.Map; import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.*; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import static org.mockito.MockitoAnnotations.openMocks; public class SSMProviderTest { @@ -37,6 +47,9 @@ public class SSMProviderTest { @Mock SsmClient client; + @Mock + TransformationManager transformationManager; + @Captor ArgumentCaptor paramCaptor; @@ -181,10 +194,24 @@ public void getMultipleWithNextToken() { assertThat(request2.nextToken()).isEqualTo("123abc"); } + @Test + public void testSecretsProviderBuilderMissingCacheManager_throwsException() { + + // Act & Assert + assertThatIllegalStateException().isThrownBy(() -> SSMProvider.builder() + .withClient(client) + .withTransformationManager(transformationManager) + .build()) + .withMessage("No CacheManager provided, please provide one"); + } + private void initMock(String expectedValue) { Parameter parameter = Parameter.builder().value(expectedValue).build(); GetParameterResponse result = GetParameterResponse.builder().parameter(parameter).build(); when(client.getParameter(paramCaptor.capture())).thenReturn(result); + provider.defaultMaxAge(1, ChronoUnit.DAYS); + provider.withMaxAge(2, ChronoUnit.DAYS); + provider.recursive(); } } diff --git a/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/SecretsProviderTest.java b/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/SecretsProviderTest.java index 611f05fa9..2ab72ffdd 100644 --- a/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/SecretsProviderTest.java +++ b/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/SecretsProviderTest.java @@ -24,10 +24,14 @@ import software.amazon.awssdk.services.secretsmanager.model.GetSecretValueRequest; import software.amazon.awssdk.services.secretsmanager.model.GetSecretValueResponse; import software.amazon.lambda.powertools.parameters.cache.CacheManager; +import software.amazon.lambda.powertools.parameters.transform.TransformationManager; +import java.time.temporal.ChronoUnit; import java.util.Base64; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; +import static org.assertj.core.api.Assertions.assertThatRuntimeException; import static org.mockito.MockitoAnnotations.openMocks; public class SecretsProviderTest { @@ -35,6 +39,9 @@ public class SecretsProviderTest { @Mock SecretsManagerClient client; + @Mock + TransformationManager transformationManager; + @Captor ArgumentCaptor paramCaptor; @@ -55,6 +62,8 @@ public void getValue() { String expectedValue = "Value1"; GetSecretValueResponse response = GetSecretValueResponse.builder().secretString(expectedValue).build(); Mockito.when(client.getSecretValue(paramCaptor.capture())).thenReturn(response); + provider.defaultMaxAge(1, ChronoUnit.DAYS); + provider.withMaxAge(2, ChronoUnit.DAYS); String value = provider.getValue(key); @@ -75,4 +84,24 @@ public void getValueBase64() { assertThat(value).isEqualTo(expectedValue); assertThat(paramCaptor.getValue().secretId()).isEqualTo(key); } + + @Test + public void getMultipleValuesThrowsException() { + + // Act & Assert + assertThatRuntimeException().isThrownBy(() -> provider.getMultipleValues("path")) + .withMessage("Impossible to get multiple values from AWS Secrets Manager"); + + } + + @Test + public void testSecretsProviderBuilderMissingCacheManager_throwsException() { + + // Act & Assert + assertThatIllegalStateException().isThrownBy(() -> SecretsProvider.builder() + .withClient(client) + .withTransformationManager(transformationManager) + .build()) + .withMessage("No CacheManager provided, please provide one"); + } } diff --git a/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/internal/LambdaParametersAspectTest.java b/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/internal/LambdaParametersAspectTest.java index 2edbc8b24..c390a051e 100644 --- a/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/internal/LambdaParametersAspectTest.java +++ b/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/internal/LambdaParametersAspectTest.java @@ -14,7 +14,10 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import static org.mockito.MockitoAnnotations.openMocks; public class LambdaParametersAspectTest { diff --git a/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/transform/TransformationManagerTest.java b/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/transform/TransformationManagerTest.java index 0fcfa8c51..6b6548071 100644 --- a/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/transform/TransformationManagerTest.java +++ b/powertools-parameters/src/test/java/software/amazon/lambda/powertools/parameters/transform/TransformationManagerTest.java @@ -15,10 +15,12 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import software.amazon.lambda.powertools.parameters.exception.TransformationException; import java.util.Base64; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatIllegalStateException; import static software.amazon.lambda.powertools.parameters.transform.Transformer.base64; import static software.amazon.lambda.powertools.parameters.transform.Transformer.json; @@ -27,6 +29,8 @@ public class TransformationManagerTest { TransformationManager manager; + Class basicTransformer = BasicTransformer.class; + @BeforeEach public void setup() { manager = new TransformationManager(); @@ -58,6 +62,14 @@ public void performBasicTransformation_notBasicTransformer_shouldThrowException( .isThrownBy(() -> manager.performBasicTransformation("value")); } + @Test + public void performBasicTransformation_abstractTransformer_throwsTransformationException() { + manager.setTransformer(basicTransformer); + + assertThatExceptionOfType(TransformationException.class) + .isThrownBy(() -> manager.performBasicTransformation("value")); + } + @Test public void performBasicTransformation_shouldPerformTransformation() { manager.setTransformer(base64); @@ -82,4 +94,12 @@ public void performComplexTransformation_shouldPerformTransformation() { assertThat(object).isNotNull(); } + + @Test + public void performComplexTransformation_throwsTransformationException() { + manager.setTransformer(basicTransformer); + + assertThatExceptionOfType(TransformationException.class) + .isThrownBy(() -> manager.performComplexTransformation("value", ObjectToDeserialize.class)); + } } diff --git a/powertools-serialization/src/main/java/software/amazon/lambda/powertools/utilities/EventDeserializer.java b/powertools-serialization/src/main/java/software/amazon/lambda/powertools/utilities/EventDeserializer.java index 9742299ee..f1b248fae 100644 --- a/powertools-serialization/src/main/java/software/amazon/lambda/powertools/utilities/EventDeserializer.java +++ b/powertools-serialization/src/main/java/software/amazon/lambda/powertools/utilities/EventDeserializer.java @@ -13,7 +13,21 @@ */ package software.amazon.lambda.powertools.utilities; -import com.amazonaws.services.lambda.runtime.events.*; +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; +import com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent; +import com.amazonaws.services.lambda.runtime.events.ActiveMQEvent; +import com.amazonaws.services.lambda.runtime.events.ApplicationLoadBalancerRequestEvent; +import com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent; +import com.amazonaws.services.lambda.runtime.events.CloudWatchLogsEvent; +import com.amazonaws.services.lambda.runtime.events.KafkaEvent; +import com.amazonaws.services.lambda.runtime.events.KinesisAnalyticsFirehoseInputPreprocessingEvent; +import com.amazonaws.services.lambda.runtime.events.KinesisAnalyticsStreamsInputPreprocessingEvent; +import com.amazonaws.services.lambda.runtime.events.KinesisEvent; +import com.amazonaws.services.lambda.runtime.events.KinesisFirehoseEvent; +import com.amazonaws.services.lambda.runtime.events.RabbitMQEvent; +import com.amazonaws.services.lambda.runtime.events.SNSEvent; +import com.amazonaws.services.lambda.runtime.events.SQSEvent; +import com.amazonaws.services.lambda.runtime.events.ScheduledEvent; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectReader; import org.slf4j.Logger; diff --git a/powertools-serialization/src/main/java/software/amazon/lambda/powertools/utilities/JsonConfig.java b/powertools-serialization/src/main/java/software/amazon/lambda/powertools/utilities/JsonConfig.java index d8af1c0cb..549418263 100644 --- a/powertools-serialization/src/main/java/software/amazon/lambda/powertools/utilities/JsonConfig.java +++ b/powertools-serialization/src/main/java/software/amazon/lambda/powertools/utilities/JsonConfig.java @@ -45,6 +45,7 @@ public static JsonConfig get() { new JsonFunction() ); private final RuntimeConfiguration configuration = new RuntimeConfiguration.Builder() + .withSilentTypeErrors(true) .withFunctionRegistry(customFunctions) .build(); private JmesPath jmesPath = new JacksonRuntime(configuration, getObjectMapper()); diff --git a/powertools-serialization/src/main/java/software/amazon/lambda/powertools/utilities/jmespath/Base64GZipFunction.java b/powertools-serialization/src/main/java/software/amazon/lambda/powertools/utilities/jmespath/Base64GZipFunction.java index 6b097af62..8628fd1d2 100644 --- a/powertools-serialization/src/main/java/software/amazon/lambda/powertools/utilities/jmespath/Base64GZipFunction.java +++ b/powertools-serialization/src/main/java/software/amazon/lambda/powertools/utilities/jmespath/Base64GZipFunction.java @@ -46,25 +46,29 @@ protected T callFunction(Adapter runtime, List> argum String decompressString = decompress(decode(encodedString.getBytes(UTF_8))); + if (decompressString == null) { + return runtime.createNull(); + } + return runtime.createString(decompressString); } public static String decompress(byte[] compressed) { - if ((compressed == null) || (compressed.length == 0)) { - return ""; + if (compressed == null || compressed.length == 0) { + return null; + } + if (!isCompressed(compressed)) { + return new String(compressed, UTF_8); } try { StringBuilder out = new StringBuilder(); - if (isCompressed(compressed)) { - GZIPInputStream gzipStream = new GZIPInputStream(new ByteArrayInputStream(compressed)); - BufferedReader bf = new BufferedReader(new InputStreamReader(gzipStream, UTF_8)); - String line; - while ((line = bf.readLine()) != null) { - out.append(line); - } - } else { - out.append(Arrays.toString(compressed)); + GZIPInputStream gzipStream = new GZIPInputStream(new ByteArrayInputStream(compressed)); + BufferedReader bf = new BufferedReader(new InputStreamReader(gzipStream, UTF_8)); + + String line; + while ((line = bf.readLine()) != null) { + out.append(line); } return out.toString(); } catch (IOException e) { diff --git a/powertools-serialization/src/test/java/software/amazon/lambda/powertools/utilities/EventDeserializerTest.java b/powertools-serialization/src/test/java/software/amazon/lambda/powertools/utilities/EventDeserializerTest.java index 90143b2a0..5055d7086 100644 --- a/powertools-serialization/src/test/java/software/amazon/lambda/powertools/utilities/EventDeserializerTest.java +++ b/powertools-serialization/src/test/java/software/amazon/lambda/powertools/utilities/EventDeserializerTest.java @@ -13,11 +13,26 @@ */ package software.amazon.lambda.powertools.utilities; -import com.amazonaws.services.lambda.runtime.events.*; +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; +import com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent; +import com.amazonaws.services.lambda.runtime.events.ActiveMQEvent; +import com.amazonaws.services.lambda.runtime.events.ApplicationLoadBalancerRequestEvent; +import com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent; +import com.amazonaws.services.lambda.runtime.events.CloudWatchLogsEvent; +import com.amazonaws.services.lambda.runtime.events.KafkaEvent; +import com.amazonaws.services.lambda.runtime.events.KinesisAnalyticsFirehoseInputPreprocessingEvent; +import com.amazonaws.services.lambda.runtime.events.KinesisAnalyticsStreamsInputPreprocessingEvent; +import com.amazonaws.services.lambda.runtime.events.KinesisEvent; +import com.amazonaws.services.lambda.runtime.events.KinesisFirehoseEvent; +import com.amazonaws.services.lambda.runtime.events.RabbitMQEvent; +import com.amazonaws.services.lambda.runtime.events.SNSEvent; +import com.amazonaws.services.lambda.runtime.events.SQSEvent; +import com.amazonaws.services.lambda.runtime.events.ScheduledEvent; import com.amazonaws.services.lambda.runtime.tests.annotations.Event; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import software.amazon.lambda.powertools.utilities.model.Basket; +import software.amazon.lambda.powertools.utilities.model.Order; import software.amazon.lambda.powertools.utilities.model.Product; import java.util.HashMap; @@ -129,7 +144,23 @@ public void testDeserializeSQSEventMessageAsObject_shouldThrowException(SQSEvent public void testDeserializeAPIGatewayEventAsList_shouldThrowException(APIGatewayProxyRequestEvent event) { assertThatThrownBy(() -> extractDataFrom(event).asListOf(Product.class)) .isInstanceOf(EventDeserializationException.class) - .hasMessageContaining("consider using 'as' instead"); + .hasMessageContaining("consider using 'as' instead") + .hasMessageContaining("Cannot load the event as a list of"); + } + + @ParameterizedTest + @Event(value = "custom_event_map.json", type = HashMap.class) + public void testDeserializeAPIGatewayMapEventAsList_shouldThrowException(Map event) { + assertThatThrownBy(() -> extractDataFrom(event).asListOf(Order.class)) + .isInstanceOf(EventDeserializationException.class) + .hasMessage("The content of this event is not a list, consider using 'as' instead"); + } + + @Test + public void testDeserializeEmptyEventAsList_shouldThrowException() { + assertThatThrownBy(() -> extractDataFrom(null).asListOf(Product.class)) + .isInstanceOf(IllegalStateException.class) + .hasMessage("Event content is null: the event may be malformed (missing fields)"); } @ParameterizedTest @@ -145,7 +176,14 @@ public void testDeserializeSQSEventBodyAsWrongObjectType_shouldThrowException(SQ public void testDeserializeAPIGatewayNoBody_shouldThrowException(APIGatewayProxyRequestEvent event) { assertThatThrownBy(() -> extractDataFrom(event).as(Product.class)) .isInstanceOf(IllegalStateException.class) - .hasMessageContaining("Event content is null"); + .hasMessage("Event content is null: the event may be malformed (missing fields)"); + } + + @Test + public void testDeserializeAPIGatewayNoBodyAsList_shouldThrowException() { + assertThatThrownBy(() -> extractDataFrom(new Object()).asListOf(Product.class)) + .isInstanceOf(EventDeserializationException.class) + .hasMessage("The content of this event is not a list, consider using 'as' instead"); } @ParameterizedTest @@ -164,9 +202,84 @@ public void testDeserializeProductAsProduct_shouldReturnProduct() { private void assertProduct(Product product) { -assertThat(product) + assertThat(product) .isEqualTo(new Product(1234, "product", 42)) .usingRecursiveComparison(); } + @ParameterizedTest + @Event(value = "scheduled_event.json", type = ScheduledEvent.class) + public void testDeserializeScheduledEventMessageAsObject_shouldReturnObject(ScheduledEvent event) { + Product product = extractDataFrom(event).as(Product.class); + assertProduct(product); + } + + @ParameterizedTest + @Event(value = "alb_event.json", type = ApplicationLoadBalancerRequestEvent.class) + public void testDeserializeALBEventMessageAsObjectShouldReturnObject(ApplicationLoadBalancerRequestEvent event) { + Product product = extractDataFrom(event).as(Product.class); + assertProduct(product); + } + + @ParameterizedTest + @Event(value = "cwl_event.json", type = CloudWatchLogsEvent.class) + public void testDeserializeCWLEventMessageAsObjectShouldReturnObject(CloudWatchLogsEvent event) { + Product product = extractDataFrom(event).as(Product.class); + assertProduct(product); + } + + @ParameterizedTest + @Event(value = "kf_event.json", type = KinesisFirehoseEvent.class) + public void testDeserializeKFEventMessageAsListShouldReturnList(KinesisFirehoseEvent event) { + List products = extractDataFrom(event).asListOf(Product.class); + assertThat(products).hasSize(1); + assertProduct(products.get(0)); + } + + @ParameterizedTest + @Event(value = "amq_event.json", type = ActiveMQEvent.class) + public void testDeserializeAMQEventMessageAsListShouldReturnList(ActiveMQEvent event) { + List products = extractDataFrom(event).asListOf(Product.class); + assertThat(products).hasSize(1); + assertProduct(products.get(0)); + } + + @ParameterizedTest + @Event(value = "rabbitmq_event.json", type = RabbitMQEvent.class) + public void testDeserializeRabbitMQEventMessageAsListShouldReturnList(RabbitMQEvent event) { + List products = extractDataFrom(event).asListOf(Product.class); + assertThat(products).hasSize(1); + assertProduct(products.get(0)); + } + + @ParameterizedTest + @Event(value = "kasip_event.json", type = KinesisAnalyticsStreamsInputPreprocessingEvent.class) + public void testDeserializeKasipEventMessageAsListShouldReturnList(KinesisAnalyticsStreamsInputPreprocessingEvent event) { + List products = extractDataFrom(event).asListOf(Product.class); + assertThat(products).hasSize(1); + assertProduct(products.get(0)); + } + + @ParameterizedTest + @Event(value = "kafip_event.json", type = KinesisAnalyticsFirehoseInputPreprocessingEvent.class) + public void testDeserializeKafipEventMessageAsListShouldReturnList(KinesisAnalyticsFirehoseInputPreprocessingEvent event) { + List products = extractDataFrom(event).asListOf(Product.class); + assertThat(products).hasSize(1); + assertProduct(products.get(0)); + } + + @ParameterizedTest + @Event(value = "apigwv2_event.json", type = APIGatewayV2HTTPEvent.class) + public void testDeserializeApiGWV2EventMessageAsObjectShouldReturnObject(APIGatewayV2HTTPEvent event) { + Product product = extractDataFrom(event).as(Product.class); + assertProduct(product); + } + + @ParameterizedTest + @Event(value = "cfcr_event.json", type = CloudFormationCustomResourceEvent.class) + public void testDeserializeCfcrEventMessageAsObjectShouldReturnObject(CloudFormationCustomResourceEvent event) { + Product product = extractDataFrom(event).as(Product.class); + assertProduct(product); + } + } diff --git a/powertools-serialization/src/test/java/software/amazon/lambda/powertools/utilities/jmespath/Base64GZipFunctionTest.java b/powertools-serialization/src/test/java/software/amazon/lambda/powertools/utilities/jmespath/Base64GZipFunctionTest.java index 8c617a634..8e574eba6 100644 --- a/powertools-serialization/src/test/java/software/amazon/lambda/powertools/utilities/jmespath/Base64GZipFunctionTest.java +++ b/powertools-serialization/src/test/java/software/amazon/lambda/powertools/utilities/jmespath/Base64GZipFunctionTest.java @@ -16,6 +16,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.JsonNodeType; import io.burt.jmespath.Expression; +import io.burt.jmespath.JmesPathType; import org.junit.jupiter.api.Test; import software.amazon.lambda.powertools.utilities.JsonConfig; @@ -25,6 +26,16 @@ public class Base64GZipFunctionTest { + @Test + public void testConstructor() { + Base64GZipFunction base64GZipFunction = new Base64GZipFunction(); + assertThat(base64GZipFunction.name()).isEqualTo("powertools_base64_gzip"); + assertThat(base64GZipFunction.argumentConstraints().expectedType().toLowerCase()).isEqualTo(JmesPathType.STRING.name().toLowerCase()); + assertThat(base64GZipFunction.argumentConstraints().minArity()).isEqualTo(1); + assertThat(base64GZipFunction.argumentConstraints().maxArity()).isEqualTo(1); + + } + @Test public void testPowertoolsGzip() throws IOException { JsonNode event = JsonConfig.get().getObjectMapper().readTree(this.getClass().getResourceAsStream("/custom_event_gzip.json")); @@ -33,4 +44,38 @@ public void testPowertoolsGzip() throws IOException { assertThat(result.getNodeType()).isEqualTo(JsonNodeType.STRING); assertThat(result.asText()).isEqualTo("{ \"id\": 43242, \"name\": \"FooBar XY\", \"price\": 258}"); } + + @Test + public void testPowertoolsGzipEmptyJsonAttribute() throws IOException { + JsonNode event = JsonConfig.get().getObjectMapper().readTree(this.getClass().getResourceAsStream("/custom_event_gzip.json")); + Expression expression = JsonConfig.get().getJmesPath().compile("basket.powertools_base64_gzip('')"); + JsonNode result = expression.search(event); + assertThat(result.getNodeType()).isEqualTo(JsonNodeType.NULL); + } + + @Test + public void testPowertoolsGzipWrongArgumentType() throws IOException { + JsonNode event = JsonConfig.get().getObjectMapper().readTree(this.getClass().getResourceAsStream("/custom_event_gzip.json")); + Expression expression = JsonConfig.get().getJmesPath().compile("basket.powertools_base64_gzip(null)"); + JsonNode result = expression.search(event); + + assertThat(result.getNodeType()).isEqualTo(JsonNodeType.NULL); + } + + @Test + public void testBase64GzipDecompressNull() { + String result = Base64GZipFunction.decompress(null); + assertThat(result).isNull(); + } + + @Test + public void testPowertoolsGzipNotCompressedJsonAttribute() throws IOException { + JsonNode event = JsonConfig.get().getObjectMapper().readTree(this.getClass().getResourceAsStream("/custom_event_gzip.json")); + Expression expression = JsonConfig.get().getJmesPath().compile("basket.powertools_base64_gzip(encodedString)"); + JsonNode result = expression.search(event); + assertThat(result.getNodeType()).isEqualTo(JsonNodeType.STRING); + assertThat(result.asText()).isEqualTo("test"); + } + + } diff --git a/powertools-serialization/src/test/java/software/amazon/lambda/powertools/utilities/model/Order.java b/powertools-serialization/src/test/java/software/amazon/lambda/powertools/utilities/model/Order.java new file mode 100644 index 000000000..eca36c222 --- /dev/null +++ b/powertools-serialization/src/test/java/software/amazon/lambda/powertools/utilities/model/Order.java @@ -0,0 +1,33 @@ +/* + * 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.utilities.model; + +import java.util.HashMap; +import java.util.Map; + +public class Order { + private Map orders = new HashMap<>(); + + public Order() { + } + + public Map getProducts() { + return orders; + } + + public void setProducts(Map products) { + this.orders = products; + } + +} diff --git a/powertools-serialization/src/test/resources/alb_event.json b/powertools-serialization/src/test/resources/alb_event.json new file mode 100644 index 000000000..d2b0d3cda --- /dev/null +++ b/powertools-serialization/src/test/resources/alb_event.json @@ -0,0 +1,28 @@ +{ + "requestContext": { + "elb": { + "targetGroupArn": "arn:aws:elasticloadbalancing:us-east-1:123456789012:targetgroup/lambda-279XGJDqGZ5rsrHC2Fjr/49e9d65c45c6791a" + } + }, + "httpMethod": "GET", + "path": "/lambda", + "queryStringParameters": { + "query": "1234ABCD" + }, + "headers": { + "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", + "accept-encoding": "gzip", + "accept-language": "en-US,en;q=0.9", + "connection": "keep-alive", + "host": "lambda-alb-123578498.us-east-1.elb.amazonaws.com", + "upgrade-insecure-requests": "1", + "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36", + "x-amzn-trace-id": "Root=1-5c536348-3d683b8b04734faae651f476", + "x-forwarded-for": "72.12.164.125", + "x-forwarded-port": "80", + "x-forwarded-proto": "http", + "x-imforwards": "20" + }, + "body": "{\"id\":1234, \"name\":\"product\", \"price\":42}", + "isBase64Encoded": false +} \ No newline at end of file diff --git a/powertools-serialization/src/test/resources/amq_event.json b/powertools-serialization/src/test/resources/amq_event.json new file mode 100644 index 000000000..00923d5e5 --- /dev/null +++ b/powertools-serialization/src/test/resources/amq_event.json @@ -0,0 +1,29 @@ +{ + "eventSource": "aws:mq", + "eventSourceArn": "arn:aws:mq:us-west-2:111122223333:broker:test:b-9bcfa592-423a-4942-879d-eb284b418fc8", + "messages": [ + { + "messageID": "ID:b-9bcfa592-423a-4942-879d-eb284b418fc8-1.mq.us-west-2.amazonaws.com-37557-1234520418293-4:1:1:1:1", + "messageType": "jms/text-message", + "deliveryMode": 1, + "replyTo": null, + "type": null, + "expiration": "60000", + "priority": 1, + "correlationId": "myJMSCoID", + "redelivered": false, + "destination": { + "physicalName": "testQueue" + }, + "data": "ewogICJpZCI6IDEyMzQsCiAgIm5hbWUiOiAicHJvZHVjdCIsCiAgInByaWNlIjogNDIKfQ==", + "timestamp": 1598827811958, + "brokerInTime": 1598827811958, + "brokerOutTime": 1598827811959, + "properties": { + "index": "1", + "doAlarm": "false", + "myCustomProperty": "value" + } + } + ] +} \ No newline at end of file diff --git a/powertools-serialization/src/test/resources/apigwv2_event.json b/powertools-serialization/src/test/resources/apigwv2_event.json new file mode 100644 index 000000000..db4fc0f95 --- /dev/null +++ b/powertools-serialization/src/test/resources/apigwv2_event.json @@ -0,0 +1,57 @@ +{ + "version": "V2", + "routeKey": "routeKey", + "rawPath": "rawPath", + "rawQueryString": "rawQueryString", + "cookies": + ["foo", "bar"] + , + "headers": { + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", + "Accept-Encoding": "gzip, deflate, sdch", + "Accept-Language": "en-US,en;q=0.8", + "Cache-Control": "max-age=0", + "CloudFront-Forwarded-Proto": "https", + "CloudFront-Is-Desktop-Viewer": "true", + "CloudFront-Is-Mobile-Viewer": "false", + "CloudFront-Is-SmartTV-Viewer": "false", + "CloudFront-Is-Tablet-Viewer": "false", + "CloudFront-Viewer-Country": "US", + "Host": "1234567890.execute-api.us-east-1.amazonaws.com", + "Upgrade-Insecure-Requests": "1", + "User-Agent": "Custom User Agent String", + "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)", + "X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==", + "X-Forwarded-For": "127.0.0.1, 127.0.0.2", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "queryStringParameters": { + "foo": "bar" + }, + "pathParameters": { + "proxy": "/path/to/resource" + }, + "stageVariables": { + "baz": "qux" + }, + "body": "{\"id\":1234, \"name\":\"product\", \"price\":42}", + "isBase64Encoded": false, + "requestContext": { + "routeKey": "routeKey", + "accountId": "123456789012", + "stage": "prod", + "apiId": "1234567890", + "domainName": "domainName", + "domainPrefix": "domainPrefix", + "time": "09/Apr/2015:12:34:56 +0000", + "timeEpoch": 1428582896000, + "http": { + "method": "POST", + "path": "/path/to/resource", + "protocol": "HTTP/1.1", + "sourceIp": "1.1.1.1", + "userAgent": "Chrome" + } + } +} diff --git a/powertools-serialization/src/test/resources/cfcr_event.json b/powertools-serialization/src/test/resources/cfcr_event.json new file mode 100644 index 000000000..58d054c06 --- /dev/null +++ b/powertools-serialization/src/test/resources/cfcr_event.json @@ -0,0 +1,20 @@ +{ + "RequestType": "requestType", + "ServiceToken": "serviceToken", + "ResponseUrl": "responseUrl", + "StackId": "stackId", + "RequestId": "requestId", + "LogicalResourceId": "logicalResourceId", + "ResourceType": "resourceType", + "ResourceProperties": { + "id": 1234, + "name": "product", + "price": 42 + }, + "OldResourceProperties": { + "id": 1234, + "name": "product", + "price": 40 + } +} + diff --git a/powertools-serialization/src/test/resources/custom_event.json b/powertools-serialization/src/test/resources/custom_event.json index 13103c434..918cad81f 100644 --- a/powertools-serialization/src/test/resources/custom_event.json +++ b/powertools-serialization/src/test/resources/custom_event.json @@ -1,6 +1,6 @@ { "basket": { - "products" : [ + "products": [ { "id": 43242, "name": "FooBar XY", diff --git a/powertools-serialization/src/test/resources/custom_event_gzip.json b/powertools-serialization/src/test/resources/custom_event_gzip.json index d212052d0..2cf088092 100644 --- a/powertools-serialization/src/test/resources/custom_event_gzip.json +++ b/powertools-serialization/src/test/resources/custom_event_gzip.json @@ -1,12 +1,13 @@ { "basket": { - "products" : [ + "products": [ { "id": 43242, "name": "FooBar XY", "price": 258 } ], - "hiddenProduct": "H4sIAAAAAAAA/6vmUlBQykxRslIwMTYyMdIBcfMSc1OBAkpu+flOiUUKEZFKYOGCosxkkLiRqQVXLQDnWo6bOAAAAA==" + "hiddenProduct": "H4sIAAAAAAAA/6vmUlBQykxRslIwMTYyMdIBcfMSc1OBAkpu+flOiUUKEZFKYOGCosxkkLiRqQVXLQDnWo6bOAAAAA==", + "encodedString": "dGVzdA==" } } \ No newline at end of file diff --git a/powertools-serialization/src/test/resources/custom_event_map.json b/powertools-serialization/src/test/resources/custom_event_map.json new file mode 100644 index 000000000..7d3f076d7 --- /dev/null +++ b/powertools-serialization/src/test/resources/custom_event_map.json @@ -0,0 +1,9 @@ +{ + "products": { + "12345": { + "id": 43242, + "name": "FooBar XY", + "price": 258 + } + } +} \ No newline at end of file diff --git a/powertools-serialization/src/test/resources/cwl_event.json b/powertools-serialization/src/test/resources/cwl_event.json new file mode 100644 index 000000000..911ab1b3a --- /dev/null +++ b/powertools-serialization/src/test/resources/cwl_event.json @@ -0,0 +1,5 @@ +{ + "awslogs": { + "data": "ewogICJpZCI6IDEyMzQsCiAgIm5hbWUiOiAicHJvZHVjdCIsCiAgInByaWNlIjogNDIKfQ==" + } +} \ No newline at end of file diff --git a/powertools-serialization/src/test/resources/kafip_event.json b/powertools-serialization/src/test/resources/kafip_event.json new file mode 100644 index 000000000..01196256c --- /dev/null +++ b/powertools-serialization/src/test/resources/kafip_event.json @@ -0,0 +1,14 @@ +{ + "invocationId": "arn:aws:iam::EXAMPLE", + "applicationArn": "arn:aws:kinesis:EXAMPLE", + "streamArn": "arn:aws:kinesis:EXAMPLE", + "records": [ + { + "kinesisFirehoseRecordMetadata": { + "approximateArrivalTimestamp": 1428537600 + }, + "recordId": "record-id", + "data": "eyJpZCI6MTIzNCwgIm5hbWUiOiJwcm9kdWN0IiwgInByaWNlIjo0Mn0=" + } + ] +} \ No newline at end of file diff --git a/powertools-serialization/src/test/resources/kasip_event.json b/powertools-serialization/src/test/resources/kasip_event.json new file mode 100644 index 000000000..78bc9a3fb --- /dev/null +++ b/powertools-serialization/src/test/resources/kasip_event.json @@ -0,0 +1,17 @@ +{ + "invocationId": "arn:aws:iam::EXAMPLE", + "applicationArn": "arn:aws:kinesis:EXAMPLE", + "streamArn": "arn:aws:kinesis:EXAMPLE", + "records": [ + { + "kinesisStreamRecordMetadata": { + "sequenceNumber": "49545115243490985018280067714973144582180062593244200961", + "partitionKey": "partitionKey-03", + "shardId": "12", + "approximateArrivalTimestamp": 1428537600 + }, + "recordId": "record-id", + "data": "eyJpZCI6MTIzNCwgIm5hbWUiOiJwcm9kdWN0IiwgInByaWNlIjo0Mn0=" + } + ] +} \ No newline at end of file diff --git a/powertools-serialization/src/test/resources/kf_event.json b/powertools-serialization/src/test/resources/kf_event.json new file mode 100644 index 000000000..e36bc4c3f --- /dev/null +++ b/powertools-serialization/src/test/resources/kf_event.json @@ -0,0 +1,12 @@ +{ + "invocationId": "invocationIdExample", + "deliveryStreamArn": "arn:aws:kinesis:EXAMPLE", + "region": "us-east-1", + "records": [ + { + "recordId": "49546986683135544286507457936321625675700192471156785154", + "approximateArrivalTimestamp": 1495072949453, + "data": "ewogICJpZCI6IDEyMzQsCiAgIm5hbWUiOiAicHJvZHVjdCIsCiAgInByaWNlIjogNDIKfQ==" + } + ] +} \ No newline at end of file diff --git a/powertools-serialization/src/test/resources/rabbitmq_event.json b/powertools-serialization/src/test/resources/rabbitmq_event.json new file mode 100644 index 000000000..698e37143 --- /dev/null +++ b/powertools-serialization/src/test/resources/rabbitmq_event.json @@ -0,0 +1,51 @@ +{ + "eventSource": "aws:rmq", + "eventSourceArn": "arn:aws:mq:us-west-2:111122223333:broker:pizzaBroker:b-9bcfa592-423a-4942-879d-eb284b418fc8", + "rmqMessagesByQueue": { + "pizzaQueue::/": [ + { + "basicProperties": { + "contentType": "text/plain", + "contentEncoding": null, + "headers": { + "header1": { + "bytes": [ + 118, + 97, + 108, + 117, + 101, + 49 + ] + }, + "header2": { + "bytes": [ + 118, + 97, + 108, + 117, + 101, + 50 + ] + }, + "numberInHeader": 10 + }, + "deliveryMode": 1, + "priority": 34, + "correlationId": null, + "replyTo": null, + "expiration": "60000", + "messageId": null, + "timestamp": "Jan 1, 1970, 12:33:41 AM", + "type": null, + "userId": "AIDACKCEVSQ6C2EXAMPLE", + "appId": null, + "clusterId": null, + "bodySize": 80 + }, + "redelivered": false, + "data": "ewogICJpZCI6IDEyMzQsCiAgIm5hbWUiOiAicHJvZHVjdCIsCiAgInByaWNlIjogNDIKfQ==" + } + ] + } +} diff --git a/powertools-serialization/src/test/resources/scheduled_event.json b/powertools-serialization/src/test/resources/scheduled_event.json new file mode 100644 index 000000000..9a65f4bd4 --- /dev/null +++ b/powertools-serialization/src/test/resources/scheduled_event.json @@ -0,0 +1,12 @@ +{ + "id": "cdc73f9d-aea9-11e3-9d5a-835b769c0d9c", + "detail-type": "Scheduled Event", + "source": "aws.events", + "account": "123456789012", + "time": "1970-01-01T00:00:00Z", + "region": "eu-central-1", + "resources": [ + "arn:aws:events:eu-central-1:123456789012:rule/my-schedule" + ], + "detail": {"id":1234, "name":"product", "price":42} +} \ No newline at end of file diff --git a/powertools-sqs/src/test/java/software/amazon/lambda/powertools/sqs/SqsUtilsBatchProcessorTest.java b/powertools-sqs/src/test/java/software/amazon/lambda/powertools/sqs/SqsUtilsBatchProcessorTest.java index 0d1c9c35a..43a089d2c 100644 --- a/powertools-sqs/src/test/java/software/amazon/lambda/powertools/sqs/SqsUtilsBatchProcessorTest.java +++ b/powertools-sqs/src/test/java/software/amazon/lambda/powertools/sqs/SqsUtilsBatchProcessorTest.java @@ -23,7 +23,6 @@ import software.amazon.awssdk.services.sqs.model.SendMessageBatchRequestEntry; import static com.amazonaws.services.lambda.runtime.events.SQSEvent.SQSMessage; -import static org.assertj.core.api.Assertions.*; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.mockito.ArgumentMatchers.any; diff --git a/powertools-validation/pom.xml b/powertools-validation/pom.xml index 76c434614..1469183ef 100644 --- a/powertools-validation/pom.xml +++ b/powertools-validation/pom.xml @@ -1,6 +1,6 @@ - 4.0.0 @@ -112,6 +112,11 @@ assertj-core test + + org.junit.jupiter + junit-jupiter-params + test + \ No newline at end of file diff --git a/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/ValidationConfig.java b/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/ValidationConfig.java index 3fd964226..b29adf8d7 100644 --- a/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/ValidationConfig.java +++ b/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/ValidationConfig.java @@ -31,19 +31,19 @@ * is just a wrapper of {@link JsonConfig}. */ public class ValidationConfig { - private ValidationConfig() { - } + private SpecVersion.VersionFlag jsonSchemaVersion = SpecVersion.VersionFlag.V7; + private JsonSchemaFactory factory = JsonSchemaFactory.getInstance(jsonSchemaVersion); - private static class ConfigHolder { - private final static ValidationConfig instance = new ValidationConfig(); + private ValidationConfig() { } public static ValidationConfig get() { return ConfigHolder.instance; } - private SpecVersion.VersionFlag jsonSchemaVersion = SpecVersion.VersionFlag.V7; - private JsonSchemaFactory factory = JsonSchemaFactory.getInstance(jsonSchemaVersion); + public SpecVersion.VersionFlag getSchemaVersion() { + return jsonSchemaVersion; + } /** * Set the version of the json schema specifications (default is V7) @@ -57,16 +57,12 @@ public void setSchemaVersion(SpecVersion.VersionFlag version) { } } - public SpecVersion.VersionFlag getSchemaVersion() { - return jsonSchemaVersion; - } - /** * Add a custom {@link io.burt.jmespath.function.Function} to JMESPath * {@link Base64Function} and {@link Base64GZipFunction} are already built-in. * * @param function the function to add - * @param Must extends {@link BaseFunction} + * @param Must extend {@link BaseFunction} */ public void addFunction(T function) { JsonConfig.get().addFunction(function); @@ -98,4 +94,8 @@ public JmesPath getJmesPath() { public ObjectMapper getObjectMapper() { return JsonConfig.get().getObjectMapper(); } + + private static class ConfigHolder { + private final static ValidationConfig instance = new ValidationConfig(); + } } diff --git a/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/ValidationUtils.java b/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/ValidationUtils.java index 9b73806a5..3c2322edc 100644 --- a/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/ValidationUtils.java +++ b/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/ValidationUtils.java @@ -13,15 +13,6 @@ */ package software.amazon.lambda.powertools.validation; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.Collections; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.stream.Collectors; - import com.amazonaws.services.lambda.runtime.serialization.PojoSerializer; import com.amazonaws.services.lambda.runtime.serialization.events.LambdaEventSerializers; import com.fasterxml.jackson.core.JsonProcessingException; @@ -33,6 +24,14 @@ import io.burt.jmespath.Expression; import software.amazon.lambda.powertools.validation.internal.ValidationAspect; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.util.Collections; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; + /** * Validation utility, used to manually validate Json against Json Schema */ @@ -79,7 +78,7 @@ public static void validate(Object obj, JsonSchema jsonSchema, String envelope) throw new ValidationException("Envelope not found in the object"); } } catch (Exception e) { - throw new ValidationException("Cannot find envelope <"+envelope+"> in the object <"+obj+">", e); + throw new ValidationException("Cannot find envelope <" + envelope + "> in the object <" + obj + ">", e); } if (subNode.getNodeType() == JsonNodeType.ARRAY) { subNode.forEach(jsonNode -> validate(jsonNode, jsonSchema)); @@ -120,7 +119,7 @@ public static void validate(Object obj, JsonSchema jsonSchema) throws Validation try { jsonNode = ValidationConfig.get().getObjectMapper().valueToTree(obj); } catch (Exception e) { - throw new ValidationException("Object <"+obj+"> is not valid against the schema provided", e); + throw new ValidationException("Object <" + obj + "> is not valid against the schema provided", e); } validate(jsonNode, jsonSchema); @@ -149,7 +148,7 @@ public static void validate(String json, JsonSchema jsonSchema) throws Validatio try { jsonNode = ValidationConfig.get().getObjectMapper().readTree(json); } catch (Exception e) { - throw new ValidationException("Json <"+json+"> is not valid against the schema provided", e); + throw new ValidationException("Json <" + json + "> is not valid against the schema provided", e); } validate(jsonNode, jsonSchema); @@ -178,7 +177,7 @@ public static void validate(Map map, JsonSchema jsonSchema) thro try { jsonNode = ValidationConfig.get().getObjectMapper().valueToTree(map); } catch (Exception e) { - throw new ValidationException("Map <"+map+"> cannot be converted to json for validation", e); + throw new ValidationException("Map <" + map + "> cannot be converted to json for validation", e); } validate(jsonNode, jsonSchema); @@ -253,12 +252,9 @@ private static JsonSchema createJsonSchema(String schema) { if (schema.startsWith(CLASSPATH)) { String filePath = schema.substring(CLASSPATH.length()); try (InputStream schemaStream = ValidationAspect.class.getResourceAsStream(filePath)) { - if (schemaStream == null) { - throw new IllegalArgumentException("'" + schema + "' is invalid, verify '" + filePath + "' is in your classpath"); - } jsonSchema = ValidationConfig.get().getFactory().getSchema(schemaStream); - } catch (IOException e) { + } catch (Exception e) { throw new IllegalArgumentException("'" + schema + "' is invalid, verify '" + filePath + "' is in your classpath"); } } else { diff --git a/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/internal/ValidationAspect.java b/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/internal/ValidationAspect.java index be19a50a0..e659abbeb 100644 --- a/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/internal/ValidationAspect.java +++ b/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/internal/ValidationAspect.java @@ -13,7 +13,26 @@ */ package software.amazon.lambda.powertools.validation.internal; -import com.amazonaws.services.lambda.runtime.events.*; +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent; +import com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent; +import com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPResponse; +import com.amazonaws.services.lambda.runtime.events.APIGatewayV2WebSocketResponse; +import com.amazonaws.services.lambda.runtime.events.ActiveMQEvent; +import com.amazonaws.services.lambda.runtime.events.ApplicationLoadBalancerRequestEvent; +import com.amazonaws.services.lambda.runtime.events.ApplicationLoadBalancerResponseEvent; +import com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent; +import com.amazonaws.services.lambda.runtime.events.CloudWatchLogsEvent; +import com.amazonaws.services.lambda.runtime.events.KafkaEvent; +import com.amazonaws.services.lambda.runtime.events.KinesisAnalyticsFirehoseInputPreprocessingEvent; +import com.amazonaws.services.lambda.runtime.events.KinesisAnalyticsInputPreprocessingResponse; +import com.amazonaws.services.lambda.runtime.events.KinesisAnalyticsStreamsInputPreprocessingEvent; +import com.amazonaws.services.lambda.runtime.events.KinesisEvent; +import com.amazonaws.services.lambda.runtime.events.KinesisFirehoseEvent; +import com.amazonaws.services.lambda.runtime.events.RabbitMQEvent; +import com.amazonaws.services.lambda.runtime.events.SNSEvent; +import com.amazonaws.services.lambda.runtime.events.SQSEvent; +import com.amazonaws.services.lambda.runtime.events.ScheduledEvent; import com.networknt.schema.JsonSchema; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; diff --git a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/ValidationUtilsTest.java b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/ValidationUtilsTest.java index d5e9332ed..6669e46b1 100644 --- a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/ValidationUtilsTest.java +++ b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/ValidationUtilsTest.java @@ -13,13 +13,17 @@ import java.util.HashMap; import java.util.Map; -import static org.assertj.core.api.Assertions.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.assertj.core.api.Assertions.assertThatNoException; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static software.amazon.lambda.powertools.validation.ValidationUtils.getJsonSchema; import static software.amazon.lambda.powertools.validation.ValidationUtils.validate; public class ValidationUtilsTest { - private JsonSchema schema = getJsonSchema("classpath:/schema_v7.json"); + private String schemaString = "classpath:/schema_v7.json"; + private JsonSchema schema = getJsonSchema(schemaString); @BeforeEach public void setup() { @@ -44,8 +48,11 @@ public void testLoadSchemaV7KO() { @Test public void testLoadMetaSchema_NoValidation() { - ValidationConfig.get().setSchemaVersion(SpecVersion.VersionFlag.V201909); - getJsonSchema("classpath:/schemas/meta_schema_V201909", false); + ValidationConfig.get().setSchemaVersion(SpecVersion.VersionFlag.V7); + + assertThatNoException().isThrownBy(() -> { + getJsonSchema("classpath:/schema_v7_ko.json", false); + }); } @Test @@ -94,7 +101,9 @@ public void testLoadSchemaNotFound() { public void testValidateJsonNodeOK() throws IOException { JsonNode node = ValidationConfig.get().getObjectMapper().readTree(this.getClass().getResourceAsStream("/json_ok.json")); - validate(node, schema); + assertThatNoException().isThrownBy(() -> { + validate(node, schemaString); + }); } @Test @@ -106,12 +115,15 @@ public void testValidateJsonNodeKO() throws IOException { @Test public void testValidateMapOK() { + Map map = new HashMap<>(); map.put("id", 43242); map.put("name", "FooBar XY"); map.put("price", 258); - validate(map, schema); + assertThatNoException().isThrownBy(() -> { + validate(map, schemaString); + }); } @Test @@ -123,11 +135,21 @@ public void testValidateMapKO() { assertThatExceptionOfType(ValidationException.class).isThrownBy(() -> validate(map, schema)); } + @Test + public void testValidateMapNotValidJsonObject() { + Map map = new HashMap<>(); + map.put("1234", new Object()); + + assertThatExceptionOfType(ValidationException.class).isThrownBy(() -> validate(map, schema)); + } + @Test public void testValidateStringOK() { String json = "{\n \"id\": 43242,\n \"name\": \"FooBar XY\",\n \"price\": 258\n}"; - validate(json, schema); + assertThatNoException().isThrownBy(() -> { + validate(json, schemaString); + }); } @Test @@ -140,14 +162,22 @@ public void testValidateStringKO() { @Test public void testValidateObjectOK() { Product product = new Product(42, "FooBar", 42); - validate(product, schema); + + assertThatNoException().isThrownBy(() -> { + validate(product, schemaString); + }); } @Test public void testValidateObjectKO() { - Product product = new Product(42, "FooBar", -12); - assertThatExceptionOfType(ValidationException.class).isThrownBy(() -> validate(product, schema)); + assertThatExceptionOfType(ValidationException.class).isThrownBy(() -> validate(new Object(), schema)); + } + + @Test + public void testValidateObjectNotValidJson() { + + assertThatExceptionOfType(ValidationException.class).isThrownBy(() -> validate(new Object(), schema)); } @Test @@ -158,7 +188,10 @@ public void testValidateSubObjectOK() { basket.add(product); basket.add(product2); MyCustomEvent event = new MyCustomEvent(basket); - validate(event, schema, "basket.products[0]"); + + assertThatNoException().isThrownBy(() -> { + validate(event, schemaString, "basket.products[0]"); + }); } @Test @@ -182,7 +215,7 @@ public void testValidateSubObjectListOK() { basket.add(product2); MyCustomEvent event = new MyCustomEvent(basket); - validate(event, schema, "basket.products[*]"); + assertThatNoException().isThrownBy(() -> validate(event, schema, "basket.products[*]")); } @Test @@ -203,7 +236,7 @@ public void testValidateSubObjectNotFound() { Basket basket = new Basket(); basket.add(product); MyCustomEvent event = new MyCustomEvent(basket); - assertThatExceptionOfType(ValidationException.class).isThrownBy(() -> validate(event, schema, "basket.product")); + assertThatExceptionOfType(ValidationException.class).isThrownBy(() -> validate(event, schema, "basket.")); } @Test @@ -226,7 +259,7 @@ public void testValidateSubObjectJsonString() { basket.setHiddenProduct("ewogICJpZCI6IDQzMjQyLAogICJuYW1lIjogIkZvb0JhciBYWSIsCiAgInByaWNlIjogMjU4Cn0="); MyCustomEvent event = new MyCustomEvent(basket); - validate(event, schema, "basket.powertools_base64(hiddenProduct)"); + assertThatNoException().isThrownBy(() -> validate(event, schema, "basket.powertools_base64(hiddenProduct)")); } @Test @@ -243,7 +276,13 @@ public void testValidateSubObjectSimpleString() { @Test public void testValidateSubObjectWithoutEnvelope() { Product product = new Product(42, "BarBazFoo", 42); - validate(product, schema, null); + assertThatNoException().isThrownBy(() -> validate(product, schema, null)); + } + + @Test + public void testValidateSubObjectWithEmptyEnvelope() { + Product product = new Product(42, "BarBazFoo", 42); + assertThatNoException().isThrownBy(() -> validate(product, schema, "")); } } diff --git a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/SQSHandler.java b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/GenericSchemaV7Handler.java similarity index 82% rename from powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/SQSHandler.java rename to powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/GenericSchemaV7Handler.java index cd6719b0f..1c64e7da1 100644 --- a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/SQSHandler.java +++ b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/GenericSchemaV7Handler.java @@ -15,14 +15,13 @@ import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestHandler; -import com.amazonaws.services.lambda.runtime.events.SQSEvent; import software.amazon.lambda.powertools.validation.Validation; -public class SQSHandler implements RequestHandler { +public class GenericSchemaV7Handler implements RequestHandler { - @Override @Validation(inboundSchema = "classpath:/schema_v7.json") - public String handleRequest(SQSEvent input, Context context) { + @Override + public String handleRequest(T input, Context context) { return "OK"; } } diff --git a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/KinesisHandler.java b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/KinesisHandler.java deleted file mode 100644 index 7132fcb9b..000000000 --- a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/KinesisHandler.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2020 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.validation.handlers; - -import com.amazonaws.services.lambda.runtime.Context; -import com.amazonaws.services.lambda.runtime.RequestHandler; -import com.amazonaws.services.lambda.runtime.events.KinesisEvent; -import software.amazon.lambda.powertools.validation.Validation; - -public class KinesisHandler implements RequestHandler { - - @Validation(inboundSchema = "classpath:/schema_v7.json") - @Override - public String handleRequest(KinesisEvent input, Context context) { - return "OK"; - } -} diff --git a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/ValidationInboundClasspathHandler.java b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/ValidationInboundClasspathHandler.java deleted file mode 100644 index 59b6cc7b5..000000000 --- a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/ValidationInboundClasspathHandler.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2020 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.validation.handlers; - -import com.amazonaws.services.lambda.runtime.Context; -import com.amazonaws.services.lambda.runtime.RequestHandler; -import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; -import software.amazon.lambda.powertools.validation.Validation; - - -public class ValidationInboundClasspathHandler implements RequestHandler { - - @Override - @Validation(inboundSchema = "classpath:/schema_v7.json") - public String handleRequest(APIGatewayProxyRequestEvent input, Context context) { - return "OK"; - } -} diff --git a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/ValidationInboundStringHandler.java b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/ValidationInboundStringHandler.java index e27f31129..2e62ba88d 100644 --- a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/ValidationInboundStringHandler.java +++ b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/ValidationInboundStringHandler.java @@ -76,7 +76,7 @@ public class ValidationInboundStringHandler implements RequestHandler provideArguments(ExtensionContext context) { + + String body = "{id"; + + final APIGatewayProxyResponseEvent apiGWProxyResponseEvent = new APIGatewayProxyResponseEvent().withBody(body); + + APIGatewayV2HTTPResponse apiGWV2HTTPResponse = new APIGatewayV2HTTPResponse(); + apiGWV2HTTPResponse.setBody(body); + + APIGatewayV2WebSocketResponse apiGWV2WebSocketResponse = new APIGatewayV2WebSocketResponse(); + apiGWV2WebSocketResponse.setBody(body); + + ApplicationLoadBalancerResponseEvent albResponseEvent = new ApplicationLoadBalancerResponseEvent(); + albResponseEvent.setBody(body); + + KinesisAnalyticsInputPreprocessingResponse kaipResponse = new KinesisAnalyticsInputPreprocessingResponse(); + List records = new ArrayList(); + ByteBuffer buffer = ByteBuffer.wrap(body.getBytes(StandardCharsets.UTF_8)); + records.add(new KinesisAnalyticsInputPreprocessingResponse.Record("1", KinesisAnalyticsInputPreprocessingResponse.Result.Ok, buffer)); + kaipResponse.setRecords(records); + + return Stream.of(apiGWProxyResponseEvent, apiGWV2HTTPResponse, apiGWV2WebSocketResponse, albResponseEvent, kaipResponse).map(Arguments::of); + } +} diff --git a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/internal/ValidationAspectTest.java b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/internal/ValidationAspectTest.java index 2c66885d6..63e93c3ac 100644 --- a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/internal/ValidationAspectTest.java +++ b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/internal/ValidationAspectTest.java @@ -14,39 +14,129 @@ package software.amazon.lambda.powertools.validation.internal; import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; import com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent; +import com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPResponse; +import com.amazonaws.services.lambda.runtime.events.ActiveMQEvent; +import com.amazonaws.services.lambda.runtime.events.ApplicationLoadBalancerRequestEvent; +import com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent; +import com.amazonaws.services.lambda.runtime.events.CloudWatchLogsEvent; +import com.amazonaws.services.lambda.runtime.events.KafkaEvent; +import com.amazonaws.services.lambda.runtime.events.KinesisAnalyticsFirehoseInputPreprocessingEvent; +import com.amazonaws.services.lambda.runtime.events.KinesisAnalyticsStreamsInputPreprocessingEvent; import com.amazonaws.services.lambda.runtime.events.KinesisEvent; +import com.amazonaws.services.lambda.runtime.events.KinesisFirehoseEvent; +import com.amazonaws.services.lambda.runtime.events.RabbitMQEvent; +import com.amazonaws.services.lambda.runtime.events.SNSEvent; import com.amazonaws.services.lambda.runtime.events.SQSEvent; +import com.amazonaws.services.lambda.runtime.events.ScheduledEvent; import com.amazonaws.services.lambda.runtime.serialization.PojoSerializer; import com.amazonaws.services.lambda.runtime.serialization.events.LambdaEventSerializers; +import com.networknt.schema.SpecVersion; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.Signature; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.ArgumentsSource; +import org.junit.jupiter.params.provider.MethodSource; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import software.amazon.lambda.powertools.validation.Validation; import software.amazon.lambda.powertools.validation.ValidationConfig; import software.amazon.lambda.powertools.validation.ValidationException; -import software.amazon.lambda.powertools.validation.handlers.*; +import software.amazon.lambda.powertools.validation.handlers.GenericSchemaV7Handler; +import software.amazon.lambda.powertools.validation.handlers.SQSWithCustomEnvelopeHandler; +import software.amazon.lambda.powertools.validation.handlers.SQSWithWrongEnvelopeHandler; +import software.amazon.lambda.powertools.validation.handlers.ValidationInboundStringHandler; import software.amazon.lambda.powertools.validation.model.MyCustomEvent; import java.io.IOException; +import java.util.stream.Stream; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.assertj.core.api.Assertions.assertThatNoException; +import static org.mockito.Mockito.when; + public class ValidationAspectTest { + @Mock + Validation validation; + @Mock + Signature signature; @Mock private Context context; + @Mock + private ProceedingJoinPoint pjp; + private ValidationAspect validationAspect = new ValidationAspect(); + + private static Stream provideEventAndEventType() { + return Stream.of( + Arguments.of("/sns_event.json", SNSEvent.class), + Arguments.of("/scheduled_event.json", ScheduledEvent.class), + Arguments.of("/alb_event.json", ApplicationLoadBalancerRequestEvent.class), + Arguments.of("/cwl_event.json", CloudWatchLogsEvent.class), + Arguments.of("/cfcr_event.json", CloudFormationCustomResourceEvent.class), + Arguments.of("/kf_event.json", KinesisFirehoseEvent.class), + Arguments.of("/kafka_event.json", KafkaEvent.class), + Arguments.of("/amq_event.json", ActiveMQEvent.class), + Arguments.of("/rabbitmq_event.json", RabbitMQEvent.class), + Arguments.of("/kafip_event.json", KinesisAnalyticsFirehoseInputPreprocessingEvent.class), + Arguments.of("/kasip_event.json", KinesisAnalyticsStreamsInputPreprocessingEvent.class), + Arguments.of("/custom_event.json", MyCustomEvent.class) + + ); + } @BeforeEach void setUp() { MockitoAnnotations.openMocks(this); } + @ParameterizedTest + @ArgumentsSource(ResponseEventsArgumentsProvider.class) + public void testValidateOutboundJsonSchema(Object object) throws Throwable { + when(validation.schemaVersion()).thenReturn(SpecVersion.VersionFlag.V7); + when(pjp.getSignature()).thenReturn(signature); + when(pjp.getSignature().getDeclaringType()).thenReturn(RequestHandler.class); + Object[] args = {new Object(), context}; + when(pjp.getArgs()).thenReturn(args); + when(pjp.proceed(args)).thenReturn(object); + when(validation.inboundSchema()).thenReturn(""); + when(validation.outboundSchema()).thenReturn("classpath:/schema_v7.json"); + + assertThatExceptionOfType(ValidationException.class).isThrownBy(() -> { + validationAspect.around(pjp, validation); + }); + } + + @Test + public void testValidateOutboundJsonSchema_APIGWV2() throws Throwable { + when(validation.schemaVersion()).thenReturn(SpecVersion.VersionFlag.V7); + when(pjp.getSignature()).thenReturn(signature); + when(pjp.getSignature().getDeclaringType()).thenReturn(RequestHandler.class); + Object[] args = {new Object(), context}; + when(pjp.getArgs()).thenReturn(args); + APIGatewayV2HTTPResponse apiGatewayV2HTTPResponse = new APIGatewayV2HTTPResponse(); + apiGatewayV2HTTPResponse.setBody("{" + + " \"id\": 1," + + " \"name\": \"Lampshade\"," + + " \"price\": 42" + + "}"); + when(pjp.proceed(args)).thenReturn(apiGatewayV2HTTPResponse); + when(validation.inboundSchema()).thenReturn(""); + when(validation.outboundSchema()).thenReturn("classpath:/schema_v7.json"); + + assertThatNoException().isThrownBy(() -> validationAspect.around(pjp, validation)); + } + @Test public void validate_inputOK_schemaInClasspath_shouldValidate() { - ValidationInboundClasspathHandler handler = new ValidationInboundClasspathHandler(); + GenericSchemaV7Handler handler = new GenericSchemaV7Handler(); APIGatewayProxyRequestEvent event = new APIGatewayProxyRequestEvent(); event.setBody("{" + " \"id\": 1," + @@ -58,7 +148,7 @@ public void validate_inputOK_schemaInClasspath_shouldValidate() { @Test public void validate_inputKO_schemaInClasspath_shouldThrowValidationException() { - ValidationInboundClasspathHandler handler = new ValidationInboundClasspathHandler(); + GenericSchemaV7Handler handler = new GenericSchemaV7Handler(); APIGatewayProxyRequestEvent event = new APIGatewayProxyRequestEvent(); event.setBody("{" + " \"id\": 1," + @@ -97,7 +187,7 @@ public void validate_SQS() { PojoSerializer pojoSerializer = LambdaEventSerializers.serializerFor(SQSEvent.class, ClassLoader.getSystemClassLoader()); SQSEvent event = pojoSerializer.fromJson(this.getClass().getResourceAsStream("/sqs.json")); - SQSHandler handler = new SQSHandler(); + GenericSchemaV7Handler handler = new GenericSchemaV7Handler(); assertThat(handler.handleRequest(event, context)).isEqualTo("OK"); } @@ -124,15 +214,16 @@ public void validate_Kinesis() { PojoSerializer pojoSerializer = LambdaEventSerializers.serializerFor(KinesisEvent.class, ClassLoader.getSystemClassLoader()); KinesisEvent event = pojoSerializer.fromJson(this.getClass().getResourceAsStream("/kinesis.json")); - KinesisHandler handler = new KinesisHandler(); + GenericSchemaV7Handler handler = new GenericSchemaV7Handler(); assertThat(handler.handleRequest(event, context)).isEqualTo("OK"); } - @Test - public void validate_CustomObject() throws IOException { - MyCustomEvent event = ValidationConfig.get().getObjectMapper().readValue(this.getClass().getResourceAsStream("/custom_event.json"), MyCustomEvent.class); + @ParameterizedTest + @MethodSource("provideEventAndEventType") + public void validateEEvent(String jsonResource, Class eventClass) throws IOException { + Object event = ValidationConfig.get().getObjectMapper().readValue(this.getClass().getResourceAsStream(jsonResource), eventClass); - MyCustomEventHandler handler = new MyCustomEventHandler(); + GenericSchemaV7Handler handler = new GenericSchemaV7Handler(); assertThat(handler.handleRequest(event, context)).isEqualTo("OK"); } } diff --git a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/model/Basket.java b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/model/Basket.java index 548ef4660..398f67265 100644 --- a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/model/Basket.java +++ b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/model/Basket.java @@ -21,6 +21,9 @@ public class Basket { private String hiddenProduct; + public Basket() { + } + public List getProducts() { return products; } @@ -29,9 +32,6 @@ public void setProducts(List products) { this.products = products; } - public Basket() { - } - public void add(Product product) { products.add(product); } diff --git a/powertools-validation/src/test/resources/alb_event.json b/powertools-validation/src/test/resources/alb_event.json new file mode 100644 index 000000000..d2b0d3cda --- /dev/null +++ b/powertools-validation/src/test/resources/alb_event.json @@ -0,0 +1,28 @@ +{ + "requestContext": { + "elb": { + "targetGroupArn": "arn:aws:elasticloadbalancing:us-east-1:123456789012:targetgroup/lambda-279XGJDqGZ5rsrHC2Fjr/49e9d65c45c6791a" + } + }, + "httpMethod": "GET", + "path": "/lambda", + "queryStringParameters": { + "query": "1234ABCD" + }, + "headers": { + "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", + "accept-encoding": "gzip", + "accept-language": "en-US,en;q=0.9", + "connection": "keep-alive", + "host": "lambda-alb-123578498.us-east-1.elb.amazonaws.com", + "upgrade-insecure-requests": "1", + "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36", + "x-amzn-trace-id": "Root=1-5c536348-3d683b8b04734faae651f476", + "x-forwarded-for": "72.12.164.125", + "x-forwarded-port": "80", + "x-forwarded-proto": "http", + "x-imforwards": "20" + }, + "body": "{\"id\":1234, \"name\":\"product\", \"price\":42}", + "isBase64Encoded": false +} \ No newline at end of file diff --git a/powertools-validation/src/test/resources/amq_event.json b/powertools-validation/src/test/resources/amq_event.json new file mode 100644 index 000000000..2f29bdc9f --- /dev/null +++ b/powertools-validation/src/test/resources/amq_event.json @@ -0,0 +1,28 @@ +{ + "eventSource": "aws:mq", + "eventSourceArn": "arn:aws:mq:us-west-2:111122223333:broker:test:b-9bcfa592-423a-4942-879d-eb284b418fc8", + "messages": [ + { + "messageID": "ID:b-9bcfa592-423a-4942-879d-eb284b418fc8-1.mq.us-west-2.amazonaws.com-37557-1234520418293-4:1:1:1:1", + "messageType": "jms/text-message", + "deliveryMode": 1, + "replyTo": null, + "type": null, + "expiration": "60000", + "priority": 1, + "redelivered": false, + "destination": { + "physicalName": "testQueue" + }, + "data": "ewogICJpZCI6IDEyMzQsCiAgIm5hbWUiOiAicHJvZHVjdCIsCiAgInByaWNlIjogNDIKfQ==", + "timestamp": 1598827811958, + "brokerInTime": 1598827811958, + "brokerOutTime": 1598827811959, + "properties": { + "index": "1", + "doAlarm": "false", + "myCustomProperty": "value" + } + } + ] +} \ No newline at end of file diff --git a/powertools-validation/src/test/resources/cfcr_event.json b/powertools-validation/src/test/resources/cfcr_event.json new file mode 100644 index 000000000..98b4a9eba --- /dev/null +++ b/powertools-validation/src/test/resources/cfcr_event.json @@ -0,0 +1,20 @@ +{ + "requestType": "requestType", + "serviceToken": "serviceToken", + "responseUrl": "responseUrl", + "stackId": "stackId", + "requestId": "requestId", + "logicalResourceId": "logicalResourceId", + "resourceType": "resourceType", + "resourceProperties": { + "id": 1234, + "name": "product", + "price": 42 + }, + "oldResourceProperties": { + "id": 1234, + "name": "product", + "price": 40 + } +} + diff --git a/powertools-validation/src/test/resources/custom_event.json b/powertools-validation/src/test/resources/custom_event.json index 13103c434..918cad81f 100644 --- a/powertools-validation/src/test/resources/custom_event.json +++ b/powertools-validation/src/test/resources/custom_event.json @@ -1,6 +1,6 @@ { "basket": { - "products" : [ + "products": [ { "id": 43242, "name": "FooBar XY", diff --git a/powertools-validation/src/test/resources/custom_event_gzip.json b/powertools-validation/src/test/resources/custom_event_gzip.json index d212052d0..165db7071 100644 --- a/powertools-validation/src/test/resources/custom_event_gzip.json +++ b/powertools-validation/src/test/resources/custom_event_gzip.json @@ -1,6 +1,6 @@ { "basket": { - "products" : [ + "products": [ { "id": 43242, "name": "FooBar XY", diff --git a/powertools-validation/src/test/resources/cwl_event.json b/powertools-validation/src/test/resources/cwl_event.json new file mode 100644 index 000000000..5dd0f6657 --- /dev/null +++ b/powertools-validation/src/test/resources/cwl_event.json @@ -0,0 +1,5 @@ +{ + "awsLogs": { + "data": "ewogICJpZCI6IDEyMzQsCiAgIm5hbWUiOiAicHJvZHVjdCIsCiAgInByaWNlIjogNDIKfQ==" + } +} \ No newline at end of file diff --git a/powertools-validation/src/test/resources/kafip_event.json b/powertools-validation/src/test/resources/kafip_event.json new file mode 100644 index 000000000..01196256c --- /dev/null +++ b/powertools-validation/src/test/resources/kafip_event.json @@ -0,0 +1,14 @@ +{ + "invocationId": "arn:aws:iam::EXAMPLE", + "applicationArn": "arn:aws:kinesis:EXAMPLE", + "streamArn": "arn:aws:kinesis:EXAMPLE", + "records": [ + { + "kinesisFirehoseRecordMetadata": { + "approximateArrivalTimestamp": 1428537600 + }, + "recordId": "record-id", + "data": "eyJpZCI6MTIzNCwgIm5hbWUiOiJwcm9kdWN0IiwgInByaWNlIjo0Mn0=" + } + ] +} \ No newline at end of file diff --git a/powertools-validation/src/test/resources/kafka_event.json b/powertools-validation/src/test/resources/kafka_event.json new file mode 100644 index 000000000..cf1bad615 --- /dev/null +++ b/powertools-validation/src/test/resources/kafka_event.json @@ -0,0 +1,27 @@ +{ + "eventSource": "aws:kafka", + "eventSourceArn": "arn:aws:kafka:us-east-1:123456789012:cluster/vpc-3432434/4834-3547-3455-9872-7929", + "bootstrapServers": "b-2.demo-cluster-1.a1bcde.c1.kafka.us-east-1.amazonaws.com:9092,b-1.demo-cluster-1.a1bcde.c1.kafka.us-east-1.amazonaws.com:9092", + "records": { + "mytopic-01": [ + { + "topic": "mytopic1", + "partition": 0, + "offset": 15, + "timestamp": 1596480920837, + "timestampType": "CREATE_TIME", + "value": "eyJpZCI6MTIzNCwgIm5hbWUiOiJwcm9kdWN0IiwgInByaWNlIjo0Mn0=" + } + ], + "mytopic-02": [ + { + "topic": "mytopic2", + "partition": 0, + "offset": 15, + "timestamp": 1596480920838, + "timestampType": "CREATE_TIME", + "value": "eyJpZCI6MTIzNDUsICJuYW1lIjoicHJvZHVjdDUiLCAicHJpY2UiOjQ1fQ==" + } + ] + } +} diff --git a/powertools-validation/src/test/resources/kasip_event.json b/powertools-validation/src/test/resources/kasip_event.json new file mode 100644 index 000000000..78bc9a3fb --- /dev/null +++ b/powertools-validation/src/test/resources/kasip_event.json @@ -0,0 +1,17 @@ +{ + "invocationId": "arn:aws:iam::EXAMPLE", + "applicationArn": "arn:aws:kinesis:EXAMPLE", + "streamArn": "arn:aws:kinesis:EXAMPLE", + "records": [ + { + "kinesisStreamRecordMetadata": { + "sequenceNumber": "49545115243490985018280067714973144582180062593244200961", + "partitionKey": "partitionKey-03", + "shardId": "12", + "approximateArrivalTimestamp": 1428537600 + }, + "recordId": "record-id", + "data": "eyJpZCI6MTIzNCwgIm5hbWUiOiJwcm9kdWN0IiwgInByaWNlIjo0Mn0=" + } + ] +} \ No newline at end of file diff --git a/powertools-validation/src/test/resources/kf_event.json b/powertools-validation/src/test/resources/kf_event.json new file mode 100644 index 000000000..e36bc4c3f --- /dev/null +++ b/powertools-validation/src/test/resources/kf_event.json @@ -0,0 +1,12 @@ +{ + "invocationId": "invocationIdExample", + "deliveryStreamArn": "arn:aws:kinesis:EXAMPLE", + "region": "us-east-1", + "records": [ + { + "recordId": "49546986683135544286507457936321625675700192471156785154", + "approximateArrivalTimestamp": 1495072949453, + "data": "ewogICJpZCI6IDEyMzQsCiAgIm5hbWUiOiAicHJvZHVjdCIsCiAgInByaWNlIjogNDIKfQ==" + } + ] +} \ No newline at end of file diff --git a/powertools-validation/src/test/resources/rabbitmq_event.json b/powertools-validation/src/test/resources/rabbitmq_event.json new file mode 100644 index 000000000..698e37143 --- /dev/null +++ b/powertools-validation/src/test/resources/rabbitmq_event.json @@ -0,0 +1,51 @@ +{ + "eventSource": "aws:rmq", + "eventSourceArn": "arn:aws:mq:us-west-2:111122223333:broker:pizzaBroker:b-9bcfa592-423a-4942-879d-eb284b418fc8", + "rmqMessagesByQueue": { + "pizzaQueue::/": [ + { + "basicProperties": { + "contentType": "text/plain", + "contentEncoding": null, + "headers": { + "header1": { + "bytes": [ + 118, + 97, + 108, + 117, + 101, + 49 + ] + }, + "header2": { + "bytes": [ + 118, + 97, + 108, + 117, + 101, + 50 + ] + }, + "numberInHeader": 10 + }, + "deliveryMode": 1, + "priority": 34, + "correlationId": null, + "replyTo": null, + "expiration": "60000", + "messageId": null, + "timestamp": "Jan 1, 1970, 12:33:41 AM", + "type": null, + "userId": "AIDACKCEVSQ6C2EXAMPLE", + "appId": null, + "clusterId": null, + "bodySize": 80 + }, + "redelivered": false, + "data": "ewogICJpZCI6IDEyMzQsCiAgIm5hbWUiOiAicHJvZHVjdCIsCiAgInByaWNlIjogNDIKfQ==" + } + ] + } +} diff --git a/powertools-validation/src/test/resources/scheduled_event.json b/powertools-validation/src/test/resources/scheduled_event.json new file mode 100644 index 000000000..12d18ba5d --- /dev/null +++ b/powertools-validation/src/test/resources/scheduled_event.json @@ -0,0 +1,14 @@ +{ + "id": "cdc73f9d-aea9-11e3-9d5a-835b769c0d9c", + "source": "aws.events", + "account": "123456789012", + "region": "eu-central-1", + "resources": [ + "arn:aws:events:eu-central-1:123456789012:rule/my-schedule" + ], + "detail": { + "id": 1234, + "name": "product", + "price": 42 + } +} \ No newline at end of file diff --git a/powertools-validation/src/test/resources/schema_v4.json b/powertools-validation/src/test/resources/schema_v4.json index ae277d476..3cd310a53 100644 --- a/powertools-validation/src/test/resources/schema_v4.json +++ b/powertools-validation/src/test/resources/schema_v4.json @@ -18,5 +18,9 @@ "exclusiveMinimum": true } }, - "required": ["id", "name", "price"] + "required": [ + "id", + "name", + "price" + ] } \ No newline at end of file diff --git a/powertools-validation/src/test/resources/sns_event.json b/powertools-validation/src/test/resources/sns_event.json new file mode 100644 index 000000000..81f6a4795 --- /dev/null +++ b/powertools-validation/src/test/resources/sns_event.json @@ -0,0 +1,26 @@ +{ + "records": [ + { + "eventSource": "aws:sns", + "eventVersion": "1.0", + "eventSubscriptionArn": "arn:aws:sns:eu-central-1:123456789012:TopicSendToMe:e3ddc7d5-2f86-40b8-a13d-3362f94fd8dd", + "sns": { + "type": "Notification", + "messageId": "dc918f50-80c6-56a2-ba33-d8a9bbf013ab", + "topicArn": "arn:aws:sns:eu-central-1:123456789012:TopicSendToMe", + "subject": "Test sns message", + "message": "{\"id\":1234, \"name\":\"product\", \"price\":42}", + "signatureVersion": "1", + "signature": "UWnPpkqPAphyr+6PXzUF9++4zJcw==", + "signingCertUrl": "https://sns.eu-central-1.amazonaws.com/SimpleNotificationService-a86cb10b4e1f29c941702d737128f7b6.pem", + "unsubscribeUrl": "https://sns.eu-central-1.amazonaws.com/?Action=Unsubscribe", + "messageAttributes": { + "name": { + "type": "String", + "value": "Bob" + } + } + } + } + ] +} \ No newline at end of file diff --git a/powertools-validation/src/test/resources/sqs.json b/powertools-validation/src/test/resources/sqs.json index 129e79bb2..10a2bbbbf 100644 --- a/powertools-validation/src/test/resources/sqs.json +++ b/powertools-validation/src/test/resources/sqs.json @@ -11,7 +11,6 @@ "ApproximateFirstReceiveTimestamp": "1601975709499" }, "messageAttributes": { - }, "md5OfBody": "0f96e88a291edb4429f2f7b9fdc3df96", "eventSource": "aws:sqs", diff --git a/powertools-validation/src/test/resources/sqs_message.json b/powertools-validation/src/test/resources/sqs_message.json index 068279b74..878b402df 100644 --- a/powertools-validation/src/test/resources/sqs_message.json +++ b/powertools-validation/src/test/resources/sqs_message.json @@ -11,7 +11,6 @@ "ApproximateFirstReceiveTimestamp": "1601975709499" }, "messageAttributes": { - }, "md5OfBody": "0f96e88a291edb4429f2f7b9fdc3df96", "eventSource": "aws:sqs",