Skip to content

Commit 67f5698

Browse files
authored
ESM v2: Allow target Lambda response payloads to not only be JSON (#11661)
1 parent 2ececf0 commit 67f5698

File tree

7 files changed

+137
-3
lines changed

7 files changed

+137
-3
lines changed

localstack-core/localstack/services/lambda_/event_source_mapping/pollers/sqs_poller.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ def sqs_queue_parameters(self) -> PipeSourceSqsQueueParameters:
4444
@cached_property
4545
def is_fifo_queue(self) -> bool:
4646
# Alternative heuristic: self.queue_url.endswith(".fifo"), but we need the call to get_queue_attributes for IAM
47-
return self.get_queue_attributes().get("FifoQueue") == "true"
47+
return self.get_queue_attributes().get("FifoQueue", "false").lower() == "true"
4848

4949
def get_queue_attributes(self) -> dict:
5050
"""The API call to sqs:GetQueueAttributes is required for IAM policy streamsing."""

localstack-core/localstack/services/lambda_/event_source_mapping/senders/lambda_sender.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,16 @@ def send_events(self, events: list[dict] | dict) -> dict:
5959
InvocationType=invocation_type,
6060
**optional_qualifier,
6161
)
62-
payload = json.load(invoke_result["Payload"])
62+
63+
try:
64+
payload = json.load(invoke_result["Payload"])
65+
except json.JSONDecodeError:
66+
payload = None
67+
LOG.debug(
68+
"Payload from Lambda invocation '%s' is invalid json. Setting this to 'None'",
69+
invoke_result["Payload"],
70+
)
71+
6372
if function_error := invoke_result.get("FunctionError"):
6473
LOG.debug(
6574
"Pipe target function %s failed with FunctionError %s. Payload: %s",
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
exports.handler = async (event) => {
2+
console.log(JSON.stringify(event))
3+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import json
2+
3+
4+
def handler(event, context):
5+
# Just print the event that was passed to the Lambda and return nothing
6+
print(json.dumps(event))
7+
return

tests/aws/services/lambda_/test_lambda.py

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
from localstack_snapshot.snapshots.transformer import KeyValueBasedTransformer
2222

2323
from localstack import config
24-
from localstack.aws.api.lambda_ import Architecture, InvokeMode, Runtime
24+
from localstack.aws.api.lambda_ import Architecture, InvocationType, InvokeMode, Runtime
2525
from localstack.aws.connect import ServiceLevelClientFactory
2626
from localstack.services.lambda_.provider import TAG_KEY_CUSTOM_URL
2727
from localstack.services.lambda_.runtimes import RUNTIMES_AGGREGATED
@@ -73,6 +73,7 @@
7373
)
7474
TEST_LAMBDA_PYTHON_HANDLER_ERROR = os.path.join(THIS_FOLDER, "functions/lambda_handler_error.py")
7575
TEST_LAMBDA_PYTHON_HANDLER_EXIT = os.path.join(THIS_FOLDER, "functions/lambda_handler_exit.py")
76+
TEST_LAMBDA_PYTHON_NONE = os.path.join(THIS_FOLDER, "functions/lambda_none.py")
7677
TEST_LAMBDA_AWS_PROXY = os.path.join(THIS_FOLDER, "functions/lambda_aws_proxy.py")
7778
TEST_LAMBDA_AWS_PROXY_FORMAT = os.path.join(THIS_FOLDER, "functions/lambda_aws_proxy_format.py")
7879
TEST_LAMBDA_PYTHON_S3_INTEGRATION = os.path.join(THIS_FOLDER, "functions/lambda_s3_integration.py")
@@ -81,6 +82,7 @@
8182
)
8283
TEST_LAMBDA_INTEGRATION_NODEJS = os.path.join(THIS_FOLDER, "functions/lambda_integration.js")
8384
TEST_LAMBDA_NODEJS = os.path.join(THIS_FOLDER, "functions/lambda_handler.js")
85+
TEST_LAMBDA_NODEJS_NONE = os.path.join(THIS_FOLDER, "functions/lambda_none.js")
8486
TEST_LAMBDA_NODEJS_ES6 = os.path.join(THIS_FOLDER, "functions/lambda_handler_es6.mjs")
8587
TEST_LAMBDA_NODEJS_ECHO = os.path.join(THIS_FOLDER, "functions/lambda_echo.js")
8688
TEST_LAMBDA_NODEJS_APIGW_INTEGRATION = os.path.join(THIS_FOLDER, "functions/apigw_integration.js")
@@ -1534,6 +1536,53 @@ def check_logs():
15341536

15351537
retry(check_logs, retries=15)
15361538

1539+
@pytest.mark.parametrize(
1540+
"invocation_type", [InvocationType.RequestResponse, InvocationType.Event]
1541+
)
1542+
@pytest.mark.parametrize(
1543+
["lambda_fn", "lambda_runtime"],
1544+
[
1545+
(TEST_LAMBDA_PYTHON_NONE, Runtime.python3_12),
1546+
(TEST_LAMBDA_NODEJS_NONE, Runtime.nodejs18_x),
1547+
],
1548+
ids=[
1549+
"python",
1550+
"nodejs",
1551+
],
1552+
)
1553+
@markers.aws.validated
1554+
def test_invocation_type_no_return_payload(
1555+
self,
1556+
snapshot,
1557+
create_lambda_function,
1558+
invocation_type,
1559+
aws_client,
1560+
check_lambda_logs,
1561+
lambda_fn,
1562+
lambda_runtime,
1563+
):
1564+
"""Check invocation response when Lambda does not return a payload"""
1565+
function_name = f"test-function-{short_uid()}"
1566+
create_lambda_function(
1567+
func_name=function_name,
1568+
handler_file=lambda_fn,
1569+
runtime=lambda_runtime,
1570+
)
1571+
result = aws_client.lambda_.invoke(
1572+
FunctionName=function_name, Payload=b"{}", InvocationType=invocation_type
1573+
)
1574+
result = read_streams(result)
1575+
snapshot.match("invoke-result", result)
1576+
1577+
# Assert that the function gets invoked by checking the logs.
1578+
# This also ensures that we wait until the invocation is done before deleting the function.
1579+
expected = [".*{}"]
1580+
1581+
def check_logs():
1582+
check_lambda_logs(function_name, expected_lines=expected)
1583+
1584+
retry(check_logs, retries=15)
1585+
15371586
# TODO: implement for new provider (was tested in old provider)
15381587
@pytest.mark.skip(reason="Not yet implemented")
15391588
@markers.aws.validated

tests/aws/services/lambda_/test_lambda.snapshot.json

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4409,5 +4409,59 @@
44094409
"status_code": 403
44104410
}
44114411
}
4412+
},
4413+
"tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_type_no_return_payload[python-RequestResponse]": {
4414+
"recorded-date": "09-10-2024, 16:15:57",
4415+
"recorded-content": {
4416+
"invoke-result": {
4417+
"ExecutedVersion": "$LATEST",
4418+
"Payload": "null",
4419+
"StatusCode": 200,
4420+
"ResponseMetadata": {
4421+
"HTTPHeaders": {},
4422+
"HTTPStatusCode": 200
4423+
}
4424+
}
4425+
}
4426+
},
4427+
"tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_type_no_return_payload[python-Event]": {
4428+
"recorded-date": "09-10-2024, 16:16:05",
4429+
"recorded-content": {
4430+
"invoke-result": {
4431+
"Payload": "",
4432+
"StatusCode": 202,
4433+
"ResponseMetadata": {
4434+
"HTTPHeaders": {},
4435+
"HTTPStatusCode": 202
4436+
}
4437+
}
4438+
}
4439+
},
4440+
"tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_type_no_return_payload[nodejs-RequestResponse]": {
4441+
"recorded-date": "09-10-2024, 16:16:14",
4442+
"recorded-content": {
4443+
"invoke-result": {
4444+
"ExecutedVersion": "$LATEST",
4445+
"Payload": "null",
4446+
"StatusCode": 200,
4447+
"ResponseMetadata": {
4448+
"HTTPHeaders": {},
4449+
"HTTPStatusCode": 200
4450+
}
4451+
}
4452+
}
4453+
},
4454+
"tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_type_no_return_payload[nodejs-Event]": {
4455+
"recorded-date": "09-10-2024, 16:16:28",
4456+
"recorded-content": {
4457+
"invoke-result": {
4458+
"Payload": "",
4459+
"StatusCode": 202,
4460+
"ResponseMetadata": {
4461+
"HTTPHeaders": {},
4462+
"HTTPStatusCode": 202
4463+
}
4464+
}
4465+
}
44124466
}
44134467
}

tests/aws/services/lambda_/test_lambda.validation.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,18 @@
122122
"tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_type_event_error": {
123123
"last_validated_date": "2023-09-04T20:49:02+00:00"
124124
},
125+
"tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_type_no_return_payload[nodejs-Event]": {
126+
"last_validated_date": "2024-10-09T16:16:27+00:00"
127+
},
128+
"tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_type_no_return_payload[nodejs-RequestResponse]": {
129+
"last_validated_date": "2024-10-09T16:16:13+00:00"
130+
},
131+
"tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_type_no_return_payload[python-Event]": {
132+
"last_validated_date": "2024-10-09T16:16:05+00:00"
133+
},
134+
"tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_type_no_return_payload[python-RequestResponse]": {
135+
"last_validated_date": "2024-10-09T16:15:57+00:00"
136+
},
125137
"tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_type_request_response[nodejs16.x]": {
126138
"last_validated_date": "2024-04-08T16:57:47+00:00"
127139
},

0 commit comments

Comments
 (0)