From 8f0a0720446fc35a0ba7b246ae3c42cfe4fe9e7b Mon Sep 17 00:00:00 2001 From: Santiago Garcia Arango Date: Thu, 27 Apr 2023 13:52:32 -0500 Subject: [PATCH] Include UnitTests for the Lambda Function --- important_commands.sh | 14 +++- lambda/__init__.py | 0 lambda/requirements-dev.txt | 2 + lambda/src/__init__.py | 0 lambda/src/lambda_function.py | 5 +- lambda/tests/__init__.py | 0 lambda/tests/test_event_01_good_single.json | 21 +++++ lambda/tests/test_event_02_good_multiple.json | 38 +++++++++ lambda/tests/test_event_03_bad.json | 21 +++++ lambda/tests/test_lambda_function.py | 80 +++++++++++++++++++ 10 files changed, 176 insertions(+), 5 deletions(-) create mode 100644 lambda/__init__.py create mode 100644 lambda/requirements-dev.txt create mode 100644 lambda/src/__init__.py create mode 100644 lambda/tests/__init__.py create mode 100644 lambda/tests/test_event_01_good_single.json create mode 100644 lambda/tests/test_event_02_good_multiple.json create mode 100644 lambda/tests/test_event_03_bad.json create mode 100644 lambda/tests/test_lambda_function.py diff --git a/important_commands.sh b/important_commands.sh index 0e78030..2e074aa 100644 --- a/important_commands.sh +++ b/important_commands.sh @@ -32,6 +32,8 @@ pip install aws-cdk-lib # Configure AWS credentials (follow steps) aws configure +# --> Alternative 1: Environment variables added to terminal session +# --> Alternative 2: AWS Cloud9 with the right permissions # Bootstrap CDK (provision initial resources to work with CDK.. S3, roles, etc) #! Change "ACCOUNT-NUMBER" and "REGION" to your needed values @@ -56,13 +58,19 @@ pip install -r requirements.txt || pip3 install -r requirements.txt # PART 3: Main CDK and Python commands (most used) ################################################################################ -cdk bootstrap -source .venv/bin/activate || echo "Make sure that virtual env exists" +# Activate Python virtual environment and check dependencies installed +# --> On the root of the repository (same context as this file), run: +source ./cdk/.venv/bin/activate || echo "Make sure that virtual env exists" +pip install -r ./cdk/requirements.txt +pip install -r ./lambda/requirements.txt +pip install -r ./lambda/requirements-dev.txt -# Test Lambda Python Stack +# Test Lambda Python Stack (optional) +# --> On the root of the repository (same context as this file), run: python -m pytest # CDK commands +# --> On the "./cdk" folder, run: cdk bootstrap cdk synthesize cdk diff diff --git a/lambda/__init__.py b/lambda/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lambda/requirements-dev.txt b/lambda/requirements-dev.txt new file mode 100644 index 0000000..94bf5e8 --- /dev/null +++ b/lambda/requirements-dev.txt @@ -0,0 +1,2 @@ +pytest==6.2.5 +moto==4.1.7 diff --git a/lambda/src/__init__.py b/lambda/src/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lambda/src/lambda_function.py b/lambda/src/lambda_function.py index 57c137b..3a8c47a 100644 --- a/lambda/src/lambda_function.py +++ b/lambda/src/lambda_function.py @@ -38,7 +38,7 @@ def process_messages(event: SQSEvent): # Note: if input does not contain "Message" key, this will raise an error message = json.loads(payload)["Message"] logger.info("Message: {}".format(message)) - + return True @logger.inject_lambda_context(log_event=True) @event_source(data_class=SQSEvent) @@ -47,7 +47,8 @@ def handler(event: SQSEvent, context: LambdaContext) -> str: logger.debug("Starting messages processing") tracer.put_metadata(key="details", value="messages processing handler") try: - process_messages(event) + result = process_messages(event) + logger.debug(result) except Exception as e: logger.exception("Error processing the messages") raise RuntimeError("Processing failed for the input messages") from e diff --git a/lambda/tests/__init__.py b/lambda/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lambda/tests/test_event_01_good_single.json b/lambda/tests/test_event_01_good_single.json new file mode 100644 index 0000000..d634b42 --- /dev/null +++ b/lambda/tests/test_event_01_good_single.json @@ -0,0 +1,21 @@ +{ + "Records": [ + { + "messageId": "4332b17c-b7cd-4aef-8dba-7cb7240e3d0e", + "receiptHandle": "AWEBZGy/Ajve1xtBoqIJhPmO0ANH9FWhcpjt7xevdsSw5+BK0OIljV8Jq/6SNgQ9boZKcTdGkK2aaiSo9iCAgsnSyUJ3u3FenB5rOnlOTf3ctMHHEJ4tGrwSR6lx8be8/CggdbtuCxjzZsPU2IUEIo4URuA4Kum54DELIWYaBNTBbfKlvlyKeRilpTh3a3I3rmy2ctn5HkKjGVmIv1WyIPNga3POZ28pxAEcQsVEFuU0fU/Bf9H5sDuj0i47JxzCBuimyFQEN1cMYwE5W7jz54hCEN3wC1blt9/M0U+jWqsC3psmlPb+qkj50ZriJb8bNoIHMutp+ERFy5CGrBJIiVIt8xvsJmkz9LuUhKDOTmwSN9yg9p+jrTD5L6B8yH8/WSu+4XMgK0QDv/BE/5eeLZcv6Q==", + "body": "{\n \"Message\": \"Test Message OK: good example message\",\n \"Details\": \"Test details\" \n}", + "attributes": { + "ApproximateReceiveCount": "1", + "AWSTraceHeader": "Root=1-64495159-17ceb00068aada396a66bd23", + "SentTimestamp": "1682526553465", + "SenderId": "AROASECKEBOYZPRE774YO:BackplaneAssumeRoleSession", + "ApproximateFirstReceiveTimestamp": "1682526553466" + }, + "messageAttributes": {}, + "md5OfBody": "d46c3294a7ebf36b9a9a99f46b768a9d", + "eventSource": "aws:sqs", + "eventSourceARN": "arn:aws:sqs:us-east-1:12345678901:apigw-sqs-lambda-queue", + "awsRegion": "us-east-1" + } + ] +} diff --git a/lambda/tests/test_event_02_good_multiple.json b/lambda/tests/test_event_02_good_multiple.json new file mode 100644 index 0000000..fd42e11 --- /dev/null +++ b/lambda/tests/test_event_02_good_multiple.json @@ -0,0 +1,38 @@ +{ + "Records": [ + { + "messageId": "4332b17c-b7cd-4aef-8dba-7cb7240e3d0e", + "receiptHandle": "AWEBZGy/Ajve1xtBoqIJhPmO0ANH9FWhcpjt7xevdsSw5+BK0OIljV8Jq/6SNgQ9boZKcTdGkK2aaiSo9iCAgsnSyUJ3u3FenB5rOnlOTf3ctMHHEJ4tGrwSR6lx8be8/CggdbtuCxjzZsPU2IUEIo4URuA4Kum54DELIWYaBNTBbfKlvlyKeRilpTh3a3I3rmy2ctn5HkKjGVmIv1WyIPNga3POZ28pxAEcQsVEFuU0fU/Bf9H5sDuj0i47JxzCBuimyFQEN1cMYwE5W7jz54hCEN3wC1blt9/M0U+jWqsC3psmlPb+qkj50ZriJb8bNoIHMutp+ERFy5CGrBJIiVIt8xvsJmkz9LuUhKDOTmwSN9yg9p+jrTD5L6B8yH8/WSu+4XMgK0QDv/BE/5eeLZcv6Q==", + "body": "{\n \"Message\": \"Test Message OK: good example message 1\",\n \"Details\": \"Test details\" \n}", + "attributes": { + "ApproximateReceiveCount": "1", + "AWSTraceHeader": "Root=1-64495159-17ceb00068aada396a66bd23", + "SentTimestamp": "1682526553465", + "SenderId": "AROASECKEBOYZPRE774YO:BackplaneAssumeRoleSession", + "ApproximateFirstReceiveTimestamp": "1682526553466" + }, + "messageAttributes": {}, + "md5OfBody": "d46c3294a7ebf36b9a9a99f46b768a9d", + "eventSource": "aws:sqs", + "eventSourceARN": "arn:aws:sqs:us-east-1:12345678901:apigw-sqs-lambda-queue", + "awsRegion": "us-east-1" + }, + { + "messageId": "4332b17c-b7cd-4aef-8dba-7cb7240e3d0e", + "receiptHandle": "AWEBZGy/Ajve1xtBoqIJhPmO0ANH9FWhcpjt7xevdsSw5+BK0OIljV8Jq/6SNgQ9boZKcTdGkK2aaiSo9iCAgsnSyUJ3u3FenB5rOnlOTf3ctMHHEJ4tGrwSR6lx8be8/CggdbtuCxjzZsPU2IUEIo4URuA4Kum54DELIWYaBNTBbfKlvlyKeRilpTh3a3I3rmy2ctn5HkKjGVmIv1WyIPNga3POZ28pxAEcQsVEFuU0fU/Bf9H5sDuj0i47JxzCBuimyFQEN1cMYwE5W7jz54hCEN3wC1blt9/M0U+jWqsC3psmlPb+qkj50ZriJb8bNoIHMutp+ERFy5CGrBJIiVIt8xvsJmkz9LuUhKDOTmwSN9yg9p+jrTD5L6B8yH8/WSu+4XMgK0QDv/BE/5eeLZcv6Q==", + "body": "{\n \"Message\": \"Test Message OK: good example message 2\",\n \"Details\": \"Test details\" \n}", + "attributes": { + "ApproximateReceiveCount": "1", + "AWSTraceHeader": "Root=1-64495159-17ceb00068aada396a66bd23", + "SentTimestamp": "1682526553465", + "SenderId": "AROASECKEBOYZPRE774YO:BackplaneAssumeRoleSession", + "ApproximateFirstReceiveTimestamp": "1682526553466" + }, + "messageAttributes": {}, + "md5OfBody": "d46c3294a7ebf36b9a9a99f46b768a9d", + "eventSource": "aws:sqs", + "eventSourceARN": "arn:aws:sqs:us-east-1:12345678901:apigw-sqs-lambda-queue", + "awsRegion": "us-east-1" + } + ] +} diff --git a/lambda/tests/test_event_03_bad.json b/lambda/tests/test_event_03_bad.json new file mode 100644 index 0000000..498eeee --- /dev/null +++ b/lambda/tests/test_event_03_bad.json @@ -0,0 +1,21 @@ +{ + "Records": [ + { + "messageId": "4332b17c-b7cd-4aef-8dba-7cb7240e3d0e", + "receiptHandle": "AWCBZGy/Ajve1xtBoqIJhPmO0ANH9FWhcpjt7xevdsSw5+BK0OIljV8Jq/6SNgQ9boZKcTdGkK2aaiSo9iCAgsnSyUJ3u3FenB5rOnlOTf3ctMHHEJ4tGrwSR6lx8be8/CggdbtuCxjzZsPU2IUEIo4URuA4Kum54DELIWYaBNTBbfKlvlyKeRilpTh3a3I3rmy2ctn5HkKjGVmIv1WyIPNga3POZ28pxAEcQsVEFuU0fU/Bf9H5sDuj0i47JxzCBuimyFQEN1cMYwE5W7jz54hCEN3wC1blt9/M0U+jWqsC3psmlPb+qkj50ZriJb8bNoIHMutp+ERFy5CGrBJIiVIt8xvsJmkz9LuUhKDOTmwSN9yg9p+jrTD5L6B8yH8/WSu+4XMgK0QDv/BE/5eeLZcv6Q==", + "body": "{\n \"MessageIntentionalWrongKey\": \"Test Message NOT OK: good example message with wrong keys\"\n}", + "attributes": { + "ApproximateReceiveCount": "1", + "AWSTraceHeader": "Root=1-64495159-17ceb00068aada396a66bd23", + "SentTimestamp": "1682526553465", + "SenderId": "AROASECKEBOYZPRE774YO:BackplaneAssumeRoleSession", + "ApproximateFirstReceiveTimestamp": "1682526553466" + }, + "messageAttributes": {}, + "md5OfBody": "d46c3294a7ebf36b9a9a99f46b768a9d", + "eventSource": "aws:sqs", + "eventSourceARN": "arn:aws:sqs:us-east-1:12345678901:apigw-sqs-lambda-queue", + "awsRegion": "us-east-1" + } + ] +} diff --git a/lambda/tests/test_lambda_function.py b/lambda/tests/test_lambda_function.py new file mode 100644 index 0000000..bdfa687 --- /dev/null +++ b/lambda/tests/test_lambda_function.py @@ -0,0 +1,80 @@ +# Built-in imports +import os, sys +import json +import unittest + +# External imports +from aws_lambda_powertools.utilities.data_classes import event_source, SQSEvent +from moto import mock_sts + +# Add path to find lambda directory for own imports +sys.path.append(os.path.abspath(os.path.dirname(os.path.dirname(__file__)))) + +# Own imports +import src.lambda_function as _lambda # noqa: E402 + + +class TestLambdaFunction(unittest.TestCase): + """ + TestCase for unit testing the inner Lambda Function functionalities. + """ + + def load_test_event(self, test_event_name: str) -> dict: + """ + Load test event from given file. + """ + path_to_event = os.path.join( + os.path.dirname(__file__), + test_event_name, + ) + with open(path_to_event) as file: + test_event = json.load(file) + return test_event + + # The "mock_sts" decorator allows to "mock/fake" the sts API calls for tests + @mock_sts() + def test_process_messages_success_single(self): + """ + Test successful process_messages call for a single message. + """ + # Load pre-configured event for current test case + event = self.load_test_event("test_event_01_good_single.json") + + # Middleware to load event with correct SQSEvent data class + event_sqs = SQSEvent(event) + result = _lambda.process_messages(event_sqs) + + self.assertEqual(result, True) + + @mock_sts() + def test_process_messages_success_multiple(self): + """ + Test successful process_messages call for multiple messages. + """ + # Load pre-configured event for current test case + event = self.load_test_event("test_event_02_good_multiple.json") + + # Middleware to load event with correct SQSEvent data class + event_sqs = SQSEvent(event) + result = _lambda.process_messages(event_sqs) + + self.assertEqual(result, True) + + @mock_sts() + def test_process_messages_error(self): + """ + Test errors on process_messages call due to wrong message format. + """ + # Load pre-configured event for current test case + event = self.load_test_event("test_event_03_bad.json") + + # Middleware to load event with correct SQSEvent data class + event_sqs = SQSEvent(event) + + # Expected an exception intentionally, otherwise fails + with self.assertRaises(Exception): + _lambda.process_messages(event_sqs) + + +if __name__ == "__main__": + unittest.main()