Skip to content

Commit b5a16c4

Browse files
author
Davide Melfi
committed
test: Add round-trip tests for 11 response event types
Add ResponseEventSerializationRoundTripTest covering all 11 response event types in aws-lambda-java-events. 9 pass cleanly, 2 are known failures (IamPolicyResponse, IamPolicyResponseV1 — getPolicyDocument() returns Map<String,Object> instead of the PolicyDocument POJO, making round-trip impossible by design since these are serialize-only types). Also update SerializationRoundTripTest comment for APIGatewayV2CustomAuthorizerEvent to clarify the date format change is a lossy transformation, not a bug. Total: 69 tests (34 registered + 22 unregistered + 11 response + 2 LambdaEventAssertTest), all green. Coverage now 66/68 event classes (97%).
1 parent f031481 commit b5a16c4

12 files changed

+220
-4
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/* Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. */
2+
package com.amazonaws.services.lambda.runtime.tests;
3+
4+
import com.amazonaws.services.lambda.runtime.events.*;
5+
import org.junit.jupiter.params.ParameterizedTest;
6+
import org.junit.jupiter.params.provider.Arguments;
7+
import org.junit.jupiter.params.provider.MethodSource;
8+
9+
import java.util.stream.Stream;
10+
11+
import static org.junit.jupiter.api.Assertions.*;
12+
13+
/**
14+
* Verifies serialization round-trip fidelity for Lambda response event types.
15+
*
16+
* <p>Response events are POJOs that Lambda functions return to the RIC, which
17+
* serializes them to JSON via {@code EventHandlerLoader.getSerializer()}.
18+
* None of these types are registered in {@code SUPPORTED_EVENTS}, so they
19+
* go through the bare Jackson path ({@code JacksonFactory.getInstance()
20+
* .getSerializer(type)}).</p>
21+
*
22+
* <p>Although the RIC only calls {@code toJson()} on response types (never
23+
* {@code fromJson()}), the round-trip test is a stricter check: if a response
24+
* type survives JSON &rarr; POJO &rarr; JSON &rarr; POJO &rarr; JSON, it
25+
* certainly survives the production POJO &rarr; JSON path.</p>
26+
*
27+
* @see SerializationRoundTripTest for registered input events
28+
* @see UnregisteredEventSerializationRoundTripTest for unregistered input events
29+
*/
30+
@SuppressWarnings("deprecation") // APIGatewayV2ProxyResponseEvent is deprecated
31+
public class ResponseEventSerializationRoundTripTest {
32+
33+
@ParameterizedTest(name = "{0}")
34+
@MethodSource("passingCases")
35+
void roundTrip(String displayName, String fixture, Class<?> eventClass) {
36+
LambdaEventAssert.assertSerializationRoundTrip(fixture, eventClass);
37+
}
38+
39+
@ParameterizedTest(name = "{0} (known failure)")
40+
@MethodSource("knownFailureCases")
41+
void roundTripKnownFailures(String displayName, String fixture, Class<?> eventClass) {
42+
assertThrows(Throwable.class,
43+
() -> LambdaEventAssert.assertSerializationRoundTrip(fixture, eventClass),
44+
displayName + " was expected to fail but passed — move it to passingCases()");
45+
}
46+
47+
private static Stream<Arguments> passingCases() {
48+
return Stream.of(
49+
// API Gateway responses
50+
args(APIGatewayProxyResponseEvent.class, "response/apigw_proxy_response.json"),
51+
args(APIGatewayV2HTTPResponse.class, "response/apigw_v2_http_response.json"),
52+
args(APIGatewayV2WebSocketResponse.class, "response/apigw_v2_websocket_response.json"),
53+
args(APIGatewayV2ProxyResponseEvent.class, "response/apigw_v2_websocket_response.json"),
54+
// ALB response
55+
args(ApplicationLoadBalancerResponseEvent.class, "response/alb_response.json"),
56+
// S3 Batch response
57+
args(S3BatchResponse.class, "response/s3_batch_response.json"),
58+
// SQS Batch response
59+
args(SQSBatchResponse.class, "response/sqs_batch_response.json"),
60+
// Simple IAM Policy response (HTTP API)
61+
args(SimpleIAMPolicyResponse.class, "response/simple_iam_policy_response.json"),
62+
// MSK Firehose response
63+
args(MSKFirehoseResponse.class, "response/msk_firehose_response.json"));
64+
}
65+
66+
private static Stream<Arguments> knownFailureCases() {
67+
return Stream.of(
68+
// IamPolicyResponse: getPolicyDocument() returns Map<String,Object> instead
69+
// of the PolicyDocument POJO — it manually converts Statement objects to Maps
70+
// with capitalized keys ("Version", "Statement", "Effect", "Action",
71+
// "Resource", "Condition") and converts Resource lists to String[]. On
72+
// deserialization, Jackson tries to populate the PolicyDocument POJO from
73+
// this Map structure, which fails because the shapes don't match.
74+
// Same issue affects IamPolicyResponseV1.
75+
args(IamPolicyResponse.class, "response/iam_policy_response.json"),
76+
args(IamPolicyResponseV1.class, "response/iam_policy_response_v1.json"));
77+
}
78+
79+
private static Arguments args(Class<?> clazz, String fixture) {
80+
return Arguments.of(clazz.getSimpleName(), fixture, clazz);
81+
}
82+
}

aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/SerializationRoundTripTest.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -86,12 +86,11 @@ private static Stream<Arguments> knownFailureCases() {
8686
// 1. getTime() parses the raw string "12/Mar/2020:19:03:58 +0000" into a
8787
// DateTime via dd/MMM/yyyy formatter. Jackson serializes as ISO-8601, but
8888
// the formatter cannot parse ISO-8601 back on the second round-trip.
89-
// Additionally, getTime() calls fmt.parseDateTime(time) unconditionally —
90-
// when time is null (e.g. omitted from fixture) it throws NPE during
91-
// serialization, so the field cannot simply be removed from the fixture.
89+
// The time field is effectively mandatory (getTime() throws NPE if null),
90+
// and the date format change is inherent to how the serialization works.
9291
// 2. getTimeEpoch() converts long to Instant; Jackson serializes as decimal
9392
// seconds (e.g. 1583348638.390000000) instead of the original long.
94-
// Both transformations are non-reversible; coercion captured in EventLoaderTest.
93+
// Both transformations are lossy; coercion captured in EventLoaderTest.
9594
args(APIGatewayV2CustomAuthorizerEvent.class, "apigw_auth_v2.json"),
9695
// ActiveMQEvent has one serialization issue:
9796
// Destination.physicalName (camelCase) vs JSON "physicalname" (lowercase) —
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"statusCode": 200,
3+
"statusDescription": "200 OK",
4+
"headers": {
5+
"Content-Type": "text/html"
6+
},
7+
"multiValueHeaders": {
8+
"Set-Cookie": [
9+
"cookie1=value1",
10+
"cookie2=value2"
11+
]
12+
},
13+
"body": "<html><body>Hello</body></html>",
14+
"isBase64Encoded": false
15+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"statusCode": 200,
3+
"headers": {
4+
"Content-Type": "application/json",
5+
"X-Custom-Header": "custom-value"
6+
},
7+
"multiValueHeaders": {
8+
"Set-Cookie": [
9+
"cookie1=value1",
10+
"cookie2=value2"
11+
]
12+
},
13+
"body": "{\"message\":\"Hello from Lambda\"}",
14+
"isBase64Encoded": false
15+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"statusCode": 200,
3+
"headers": {
4+
"Content-Type": "application/json"
5+
},
6+
"multiValueHeaders": {
7+
"Set-Cookie": [
8+
"cookie1=value1"
9+
]
10+
},
11+
"cookies": [
12+
"session=abc123; Secure; HttpOnly"
13+
],
14+
"body": "{\"message\":\"OK\"}",
15+
"isBase64Encoded": false
16+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"statusCode": 200,
3+
"headers": {
4+
"Content-Type": "application/json"
5+
},
6+
"multiValueHeaders": {
7+
"X-Custom": [
8+
"val1",
9+
"val2"
10+
]
11+
},
12+
"body": "{\"action\":\"sendmessage\",\"data\":\"hello\"}",
13+
"isBase64Encoded": false
14+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"principalId": "user123",
3+
"policyDocument": {
4+
"Version": "2012-10-17",
5+
"Statement": [
6+
{
7+
"Action": "execute-api:Invoke",
8+
"Effect": "Allow",
9+
"Resource": [
10+
"arn:aws:execute-api:us-east-1:123456789012:api-id/stage/GET/resource"
11+
]
12+
}
13+
]
14+
},
15+
"context": {
16+
"org": "my-org",
17+
"role": "admin"
18+
}
19+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"principalId": "user456",
3+
"policyDocument": {
4+
"Version": "2012-10-17",
5+
"Statement": [
6+
{
7+
"Action": "execute-api:Invoke",
8+
"Effect": "Allow",
9+
"Resource": [
10+
"arn:aws:execute-api:us-east-1:123456789012:api-id/stage/GET/*"
11+
]
12+
}
13+
]
14+
},
15+
"context": {
16+
"stringKey": "value",
17+
"numberKey": 123,
18+
"booleanKey": true
19+
},
20+
"usageIdentifierKey": "usage-plan-key-id"
21+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"records": [
3+
{
4+
"recordId": "record-1",
5+
"result": "Ok",
6+
"kafkaRecordValue": "dHJhbnNmb3JtZWQgZGF0YQ=="
7+
}
8+
]
9+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"invocationSchemaVersion": "1.0",
3+
"treatMissingKeysAs": "PermanentFailure",
4+
"invocationId": "YXNkbGZqYWRmaiBhc2RmdW9hZHNmZGpmaGFzbGtkaGZza2RmaAo",
5+
"results": [
6+
{
7+
"taskId": "dGFza2lkZ29lc2hlcmUK",
8+
"resultCode": "Succeeded",
9+
"resultString": "Successfully processed"
10+
}
11+
]
12+
}

0 commit comments

Comments
 (0)