From 702003d7d37d68cb1ae1215d6d7ca2ae105a590b Mon Sep 17 00:00:00 2001 From: Idris Miles Date: Sat, 25 Jun 2022 16:06:57 +0100 Subject: [PATCH 1/2] Add test helper to generate DynamoDB event --- chalice/test.py | 25 ++++++++++++++++++++++++- docs/source/api.rst | 4 ++++ tests/unit/test_test.py | 23 +++++++++++++++++++++++ 3 files changed, 51 insertions(+), 1 deletion(-) diff --git a/chalice/test.py b/chalice/test.py index 84e2814f6..5a3354aba 100644 --- a/chalice/test.py +++ b/chalice/test.py @@ -4,7 +4,7 @@ import contextlib from types import TracebackType -from typing import Optional, Type, Generator, Dict, Any, List # noqa +from typing import Optional, Type, Generator, Dict, Any, List, Tuple # noqa from chalice import Chalice # noqa from chalice.config import Config @@ -323,6 +323,29 @@ def generate_kinesis_event(self, message_bodies, } for body in message_bodies] return {'Records': records} + def generate_dynamodb_event(self, images): + # type: (List[Tuple[Dict, Dict]]) -> Dict[str, Any] + records = [{ + "dynamodb": { + "ApproximateCreationDateTime": 1545084650.987, + "Keys": list(new_image.keys()), + "NewImage": new_image, + "OldImage": old_image, + "SequenceNumber": "12345", + "SizeBytes": 12345, + "StreamViewType": "NEW_AND_OLD_IMAGES", + }, + "awsRegion": "us-west-2", + "eventID": "da037887f71a88a1f6f4cfd149709d5a", + "eventName": "INSERT", + "eventSource": "aws:dynamodb", + "eventSourceARN": ( + "arn:aws:dynamodb:us-west-2:12345:table/MyTable/stream/" + "2015-05-11T21:21:33.291" + ) + } for new_image, old_image in images] + return {'Records': records} + class TestLambdaClient(BaseClient): def __init__(self, app, config): diff --git a/docs/source/api.rst b/docs/source/api.rst index 2dc153468..d510be333 100644 --- a/docs/source/api.rst +++ b/docs/source/api.rst @@ -1800,6 +1800,10 @@ Testing Generates a Kinesis event. + .. method:: generate_dynamodb_event(images) + + Generates a DynamoDB Stream event. + .. class:: HTTPResponse() diff --git a/tests/unit/test_test.py b/tests/unit/test_test.py index e59bf092f..2e3080ba6 100644 --- a/tests/unit/test_test.py +++ b/tests/unit/test_test.py @@ -313,6 +313,29 @@ def foo(event): assert response.payload == [b'foo', b'bar', b'baz'] +def test_can_generate_dynamodb_event(): + app = Chalice('dynamodb') + + @app.on_dynamodb_record( + stream_arn=('arn:aws:dynamodb:us-west-2:12345:table/MyTable/stream/' + '2015-05-11T21:21:33.291') + ) + def foo(event): + return [(record.new_image, record.old_image) for record in event] + + old_image = {'PK': {'S': 'foo'}, 'SK': {'S': 'bar'}} + new_image = {'PK': {'S': 'hello'}, 'SK': {'S': 'world'}} + + with Client(app) as client: + event = client.events.generate_dynamodb_event( + images=[(old_image, new_image)] + ) + response = client.lambda_.invoke('foo', event) + assert len(response.payload) == 1 + assert response.payload[0][0] == old_image + assert response.payload[0][1] == new_image + + def test_can_mix_pure_lambda_and_event_handlers(): app = Chalice('lambda-only') From e580e08fee4f8d74b81285a615945c6395910381 Mon Sep 17 00:00:00 2001 From: Idris Miles Date: Sat, 17 Sep 2022 21:56:38 +0100 Subject: [PATCH 2/2] Fixes generate_dynamodb_event to handle different view types --- chalice/test.py | 61 +++++++++++++++++++++----------- docs/source/api.rst | 2 +- tests/unit/test_test.py | 78 ++++++++++++++++++++++++++++++++++++++++- 3 files changed, 118 insertions(+), 23 deletions(-) diff --git a/chalice/test.py b/chalice/test.py index 5a3354aba..f3ce3a70c 100644 --- a/chalice/test.py +++ b/chalice/test.py @@ -16,6 +16,10 @@ class FunctionNotFoundError(Exception): pass +class InvalidDynamoStreamViewType(Exception): + pass + + class Client(object): def __init__(self, app, stage_name='dev', project_dir='.'): # type: (Chalice, str, str) -> None @@ -323,27 +327,42 @@ def generate_kinesis_event(self, message_bodies, } for body in message_bodies] return {'Records': records} - def generate_dynamodb_event(self, images): - # type: (List[Tuple[Dict, Dict]]) -> Dict[str, Any] - records = [{ - "dynamodb": { - "ApproximateCreationDateTime": 1545084650.987, - "Keys": list(new_image.keys()), - "NewImage": new_image, - "OldImage": old_image, - "SequenceNumber": "12345", - "SizeBytes": 12345, - "StreamViewType": "NEW_AND_OLD_IMAGES", - }, - "awsRegion": "us-west-2", - "eventID": "da037887f71a88a1f6f4cfd149709d5a", - "eventName": "INSERT", - "eventSource": "aws:dynamodb", - "eventSourceARN": ( - "arn:aws:dynamodb:us-west-2:12345:table/MyTable/stream/" - "2015-05-11T21:21:33.291" - ) - } for new_image, old_image in images] + def generate_dynamodb_event(self, images, view_type="NEW_AND_OLD_IMAGES"): + # type: (List[Tuple[Dict, Dict]], str) -> Dict[str, Any] + # KEYS_ONLY/NEW_IMAGE/OLD_IMAGE / NEW_AND_OLD_IMAGES + def construct_record(old_image, new_image): + record = { + "dynamodb": { + "ApproximateCreationDateTime": 1545084650.987, + "SequenceNumber": "12345", + "SizeBytes": 12345, + "StreamViewType": view_type, + }, + "awsRegion": "us-west-2", + "eventID": "da037887f71a88a1f6f4cfd149709d5a", + "eventName": "INSERT", + "eventSource": "aws:dynamodb", + "eventSourceARN": ( + "arn:aws:dynamodb:us-west-2:12345:table/MyTable/stream/" + "2015-05-11T21:21:33.291" + ) + } + + if view_type == "KEYS_ONLY": + record["dynamodb"]["Keys"] = list(new_image.keys()) + elif view_type == "NEW_IMAGE": + record["dynamodb"]["NewImage"] = new_image + elif view_type == "OLD_IMAGE": + record["dynamodb"]["OldImage"] = old_image + elif view_type == "NEW_AND_OLD_IMAGES": + record["dynamodb"]["NewImage"] = new_image + record["dynamodb"]["OldImage"] = old_image + else: + raise InvalidDynamoStreamViewType(view_type) + + return record + + records = [construct_record(old_image, new_image) for old_image, new_image in images] return {'Records': records} diff --git a/docs/source/api.rst b/docs/source/api.rst index d510be333..32b1b505b 100644 --- a/docs/source/api.rst +++ b/docs/source/api.rst @@ -1800,7 +1800,7 @@ Testing Generates a Kinesis event. - .. method:: generate_dynamodb_event(images) + .. method:: generate_dynamodb_event(images, view_type='NEW_AND_OLD_IMAGES') Generates a DynamoDB Stream event. diff --git a/tests/unit/test_test.py b/tests/unit/test_test.py index 2e3080ba6..c48100949 100644 --- a/tests/unit/test_test.py +++ b/tests/unit/test_test.py @@ -321,7 +321,7 @@ def test_can_generate_dynamodb_event(): '2015-05-11T21:21:33.291') ) def foo(event): - return [(record.new_image, record.old_image) for record in event] + return [(record.old_image, record.new_image, record.keys) for record in event] old_image = {'PK': {'S': 'foo'}, 'SK': {'S': 'bar'}} new_image = {'PK': {'S': 'hello'}, 'SK': {'S': 'world'}} @@ -334,6 +334,82 @@ def foo(event): assert len(response.payload) == 1 assert response.payload[0][0] == old_image assert response.payload[0][1] == new_image + assert response.payload[0][2] == None + + +def test_can_generate_dynamodb_event_keys_only(): + app = Chalice('dynamodb') + + @app.on_dynamodb_record( + stream_arn=('arn:aws:dynamodb:us-west-2:12345:table/MyTable/stream/' + '2015-05-11T21:21:33.291') + ) + def foo(event): + return [(record.old_image, record.new_image, record.keys) for record in event] + + old_image = {'PK': {'S': 'foo'}, 'SK': {'S': 'bar'}} + new_image = {'PK': {'S': 'hello'}, 'SK': {'S': 'world'}} + + with Client(app) as client: + event = client.events.generate_dynamodb_event( + images=[(old_image, new_image)], + view_type="KEYS_ONLY" + ) + response = client.lambda_.invoke('foo', event) + assert len(response.payload) == 1 + assert response.payload[0][0] == None + assert response.payload[0][1] == None + assert response.payload[0][2] == list(new_image.keys()) + + +def test_can_generate_dynamodb_event_old_image(): + app = Chalice('dynamodb') + + @app.on_dynamodb_record( + stream_arn=('arn:aws:dynamodb:us-west-2:12345:table/MyTable/stream/' + '2015-05-11T21:21:33.291') + ) + def foo(event): + return [(record.old_image, record.new_image, record.keys) for record in event] + + old_image = {'PK': {'S': 'foo'}, 'SK': {'S': 'bar'}} + new_image = {'PK': {'S': 'hello'}, 'SK': {'S': 'world'}} + + with Client(app) as client: + event = client.events.generate_dynamodb_event( + images=[(old_image, new_image)], + view_type="OLD_IMAGE" + ) + response = client.lambda_.invoke('foo', event) + assert len(response.payload) == 1 + assert response.payload[0][0] == old_image + assert response.payload[0][1] == None + assert response.payload[0][2] == None + + +def test_can_generate_dynamodb_event_new_image(): + app = Chalice('dynamodb') + + @app.on_dynamodb_record( + stream_arn=('arn:aws:dynamodb:us-west-2:12345:table/MyTable/stream/' + '2015-05-11T21:21:33.291') + ) + def foo(event): + return [(record.old_image, record.new_image, record.keys) for record in event] + + old_image = {'PK': {'S': 'foo'}, 'SK': {'S': 'bar'}} + new_image = {'PK': {'S': 'hello'}, 'SK': {'S': 'world'}} + + with Client(app) as client: + event = client.events.generate_dynamodb_event( + images=[(old_image, new_image)], + view_type="NEW_IMAGE" + ) + response = client.lambda_.invoke('foo', event) + assert len(response.payload) == 1 + assert response.payload[0][0] == None + assert response.payload[0][1] == new_image + assert response.payload[0][2] == None def test_can_mix_pure_lambda_and_event_handlers():