From f348bb9215a0308bdbe714c880e1839440523d5e Mon Sep 17 00:00:00 2001 From: hnnasit <84355507+hnnasit@users.noreply.github.com> Date: Wed, 21 Jun 2023 14:20:30 -0400 Subject: [PATCH] test: Remote invoke integration tests for response stream configured lambda functions (#5383) * Created base integ glass for remote invoke tests * Add integration tests for invoking response streaming lambda fns * make black * Moved tearDownClass to base class * Moved tearDownClass method to base class and removed architectures from template file * Remove the check to skip appveyor tests on master branch --- .../test_lambda_invoke_response_stream.py | 103 ++++++++++++++++++ .../remote_invoke/lambda-fns/src/index.js | 26 +++++ ...emplate-lambda-response-streaming-fns.yaml | 28 +++++ 3 files changed, 157 insertions(+) create mode 100644 tests/integration/remote/invoke/test_lambda_invoke_response_stream.py create mode 100644 tests/integration/testdata/remote_invoke/lambda-fns/src/index.js create mode 100644 tests/integration/testdata/remote_invoke/template-lambda-response-streaming-fns.yaml diff --git a/tests/integration/remote/invoke/test_lambda_invoke_response_stream.py b/tests/integration/remote/invoke/test_lambda_invoke_response_stream.py new file mode 100644 index 0000000000..5adf7bdba5 --- /dev/null +++ b/tests/integration/remote/invoke/test_lambda_invoke_response_stream.py @@ -0,0 +1,103 @@ +import json +import uuid + +from tests.integration.remote.invoke.remote_invoke_integ_base import RemoteInvokeIntegBase +from tests.testing_utils import run_command + +from pathlib import Path +import pytest + + +@pytest.mark.xdist_group(name="sam_remote_invoke_lambda_response_streaming") +class TestInvokeResponseStreamingLambdas(RemoteInvokeIntegBase): + template = Path("template-lambda-response-streaming-fns.yaml") + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.stack_name = f"{TestInvokeResponseStreamingLambdas.__name__}-{uuid.uuid4().hex}" + cls.create_resources_and_boto_clients() + + def test_invoke_empty_event_provided(self): + command_list = self.get_command_list(stack_name=self.stack_name, resource_id="NodeStreamingFunction") + + expected_streamed_responses = "LambdaFunctionStreamingResponsesTestDone!" + remote_invoke_result = run_command(command_list) + + self.assertEqual(0, remote_invoke_result.process.returncode) + remote_invoke_result_stdout = remote_invoke_result.stdout.strip().decode() + self.assertIn(expected_streamed_responses, remote_invoke_result_stdout) + + def test_invoke_with_only_event_provided(self): + command_list = self.get_command_list( + stack_name=self.stack_name, + resource_id="NodeStreamingFunction", + event='{"key1": "Hello", "key2": "serverless", "key3": "world"}', + ) + + expected_streamed_responses = "LambdaFunctionStreamingResponsesTestDone!" + remote_invoke_result = run_command(command_list) + + self.assertEqual(0, remote_invoke_result.process.returncode) + remote_invoke_result_stdout = remote_invoke_result.stdout.strip().decode() + self.assertIn(expected_streamed_responses, remote_invoke_result_stdout) + + def test_invoke_with_only_event_file_provided(self): + event_file_path = str(self.events_folder_path.joinpath("default_event.json")) + command_list = self.get_command_list( + stack_name=self.stack_name, resource_id="NodeStreamingEventValuesFunction", event_file=event_file_path + ) + + expected_streamed_responses = "Helloserverlessworld" + remote_invoke_result = run_command(command_list) + + self.assertEqual(0, remote_invoke_result.process.returncode) + remote_invoke_result_stdout = remote_invoke_result.stdout.strip().decode() + + self.assertEqual(expected_streamed_responses, remote_invoke_result_stdout) + + def test_invoke_json_output_option(self): + command_list = self.get_command_list( + stack_name=self.stack_name, + event='{"key1": "Hello", "key2": "serverless", "key3": "world"}', + resource_id="NodeStreamingEventValuesFunction", + output="json", + parameter_list=[("LogType", "None")], + ) + + remote_invoke_result = run_command(command_list) + expected_output_result = [ + {"PayloadChunk": {"Payload": "Hello"}}, + {"PayloadChunk": {"Payload": "serverless"}}, + {"PayloadChunk": {"Payload": "world"}}, + {"InvokeComplete": {}}, + ] + + self.assertEqual(0, remote_invoke_result.process.returncode) + remote_invoke_result_stdout = json.loads(remote_invoke_result.stdout.strip().decode()) + + response_event_stream = remote_invoke_result_stdout["EventStream"] + self.assertEqual(response_event_stream, expected_output_result) + + def test_invoke_different_boto_options(self): + command_list = self.get_command_list( + stack_name=self.stack_name, + event='{"key1": "Hello", "key2": "serverless", "key3": "world"}', + resource_id="NodeStreamingEventValuesFunction", + output="json", + parameter_list=[("LogType", "None"), ("InvocationType", "DryRun"), ("Qualifier", "$LATEST")], + ) + + remote_invoke_result = run_command(command_list) + expected_output_result = [ + {"PayloadChunk": {"Payload": "Hello"}}, + {"PayloadChunk": {"Payload": "serverless"}}, + {"PayloadChunk": {"Payload": "world"}}, + {"InvokeComplete": {}}, + ] + + self.assertEqual(0, remote_invoke_result.process.returncode) + remote_invoke_result_stdout = json.loads(remote_invoke_result.stdout.strip().decode()) + + response_event_stream = remote_invoke_result_stdout["EventStream"] + self.assertEqual(response_event_stream, expected_output_result) diff --git a/tests/integration/testdata/remote_invoke/lambda-fns/src/index.js b/tests/integration/testdata/remote_invoke/lambda-fns/src/index.js new file mode 100644 index 0000000000..d4cdd511a6 --- /dev/null +++ b/tests/integration/testdata/remote_invoke/lambda-fns/src/index.js @@ -0,0 +1,26 @@ +exports.handler = awslambda.streamifyResponse( + async (event, responseStream, context) => { + responseStream.write("Lambda"); + responseStream.write("Function"); + + responseStream.write("Streaming"); + await new Promise(r => setTimeout(r, 1000)); + responseStream.write("Responses"); + await new Promise(r => setTimeout(r, 1000)); + responseStream.write("Test"); + await new Promise(r => setTimeout(r, 1000)); + + responseStream.write("Done!"); + responseStream.end(); + } +); + +exports.stream_event_values = awslambda.streamifyResponse( + async (event, responseStream, context) => { + for (let k in event) { + responseStream.write(event[k]); + await new Promise(r => setTimeout(r, 1000)); + } + responseStream.end(); + } +); \ No newline at end of file diff --git a/tests/integration/testdata/remote_invoke/template-lambda-response-streaming-fns.yaml b/tests/integration/testdata/remote_invoke/template-lambda-response-streaming-fns.yaml new file mode 100644 index 0000000000..1365154a7e --- /dev/null +++ b/tests/integration/testdata/remote_invoke/template-lambda-response-streaming-fns.yaml @@ -0,0 +1,28 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 + +Description: > + Testing application for lambda functions with response streaming + +Resources: + NodeStreamingFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: ./lambda-fns/src/ + Handler: index.handler + Runtime: nodejs18.x + Timeout: 10 + FunctionUrlConfig: + AuthType: AWS_IAM + InvokeMode: RESPONSE_STREAM + + NodeStreamingEventValuesFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: ./lambda-fns/src/ + Handler: index.stream_event_values + Runtime: nodejs18.x + Timeout: 10 + FunctionUrlConfig: + AuthType: AWS_IAM + InvokeMode: RESPONSE_STREAM \ No newline at end of file