Skip to content

Commit 7d1abf0

Browse files
author
Michael Brewer
committed
tests(pydantic): Add missing coverage
Changes: * Make some methods static * Run code formatter * Some lint based changes * Add missing test cases
1 parent 6a1ea1b commit 7d1abf0

File tree

8 files changed

+115
-24
lines changed

8 files changed

+115
-24
lines changed

Diff for: aws_lambda_powertools/utilities/advanced_parser/envelopes/base.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88

99

1010
class BaseEnvelope(ABC):
11-
def _parse_user_dict_schema(self, user_event: Dict[str, Any], schema: BaseModel) -> Any:
11+
@staticmethod
12+
def _parse_user_dict_schema(user_event: Dict[str, Any], schema: BaseModel) -> Any:
1213
if user_event is None:
1314
return None
1415
logger.debug("parsing user dictionary schema")
@@ -18,7 +19,8 @@ def _parse_user_dict_schema(self, user_event: Dict[str, Any], schema: BaseModel)
1819
logger.exception("Validation exception while extracting user custom schema")
1920
raise
2021

21-
def _parse_user_json_string_schema(self, user_event: str, schema: BaseModel) -> Any:
22+
@staticmethod
23+
def _parse_user_json_string_schema(user_event: str, schema: BaseModel) -> Any:
2224
if user_event is None:
2325
return None
2426
# this is used in cases where the underlying schema is not a Dict that can be parsed as baseModel

Diff for: aws_lambda_powertools/utilities/advanced_parser/envelopes/dynamodb.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import logging
22
from typing import Any, Dict, List
3-
from typing_extensions import Literal
43

54
from pydantic import BaseModel, ValidationError
5+
from typing_extensions import Literal
66

77
from aws_lambda_powertools.utilities.advanced_parser.envelopes.base import BaseEnvelope
88
from aws_lambda_powertools.utilities.advanced_parser.schemas import DynamoDBSchema

Diff for: aws_lambda_powertools/utilities/advanced_parser/schemas/dynamodb.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@ class DynamoScheme(BaseModel):
1818
# exist in a legal schema of NEW_AND_OLD_IMAGES type
1919
@root_validator
2020
def check_one_image_exists(cls, values):
21-
newimg, oldimg = values.get("NewImage"), values.get("OldImage")
21+
new_img, old_img = values.get("NewImage"), values.get("OldImage")
2222
stream_type = values.get("StreamViewType")
23-
if stream_type == "NEW_AND_OLD_IMAGES" and not newimg and not oldimg:
23+
if stream_type == "NEW_AND_OLD_IMAGES" and not new_img and not old_img:
2424
raise TypeError("DynamoDB streams schema failed validation, missing both new & old stream images")
2525
return values
2626

Diff for: aws_lambda_powertools/utilities/advanced_parser/schemas/sqs.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -34,16 +34,16 @@ def valid_type(cls, v): # noqa: VNE001
3434
raise TypeError("data type is invalid")
3535
return v
3636

37-
# validate that dataType and value are not None and match
37+
# validate that dataType and value are not None and match
3838
@root_validator
3939
def check_str_and_binary_values(cls, values):
4040
binary_val, str_val = values.get("binaryValue", ""), values.get("stringValue", "")
41-
dataType = values.get("dataType")
41+
data_type = values.get("dataType")
4242
if not str_val and not binary_val:
4343
raise TypeError("both binaryValue and stringValue are missing")
44-
if dataType.startswith("Binary") and not binary_val:
44+
if data_type.startswith("Binary") and not binary_val:
4545
raise TypeError("binaryValue is missing")
46-
if (dataType.startswith("String") or dataType.startswith("Number")) and not str_val:
46+
if (data_type.startswith("String") or data_type.startswith("Number")) and not str_val:
4747
raise TypeError("stringValue is missing")
4848
return values
4949

Diff for: tests/functional/parser/test_dynamodb.py

+33-5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Dict, List
1+
from typing import Any, Dict, List
22

33
import pytest
44
from pydantic.error_wrappers import ValidationError
@@ -11,7 +11,7 @@
1111

1212

1313
@parser(schema=MyDynamoBusiness, envelope=Envelope.DYNAMODB_STREAM)
14-
def handle_dynamodb(event: List[Dict[str, MyDynamoBusiness]], context: LambdaContext):
14+
def handle_dynamodb(event: List[Dict[str, MyDynamoBusiness]], _: LambdaContext):
1515
assert len(event) == 2
1616
assert event[0]["OldImage"] is None
1717
assert event[0]["NewImage"].Message["S"] == "New item!"
@@ -23,7 +23,7 @@ def handle_dynamodb(event: List[Dict[str, MyDynamoBusiness]], context: LambdaCon
2323

2424

2525
@parser(schema=MyAdvancedDynamoBusiness)
26-
def handle_dynamodb_no_envelope(event: MyAdvancedDynamoBusiness, context: LambdaContext):
26+
def handle_dynamodb_no_envelope(event: MyAdvancedDynamoBusiness, _: LambdaContext):
2727
records = event.Records
2828
record = records[0]
2929
assert record.awsRegion == "us-west-2"
@@ -60,12 +60,40 @@ def test_dynamo_db_stream_trigger_event_no_envelope():
6060

6161

6262
def test_validate_event_does_not_conform_with_schema_no_envelope():
63-
event_dict = {"hello": "s"}
63+
event_dict: Any = {"hello": "s"}
6464
with pytest.raises(ValidationError):
6565
handle_dynamodb_no_envelope(event_dict, LambdaContext())
6666

6767

6868
def test_validate_event_does_not_conform_with_schema():
69-
event_dict = {"hello": "s"}
69+
event_dict: Any = {"hello": "s"}
7070
with pytest.raises(ValidationError):
7171
handle_dynamodb(event_dict, LambdaContext())
72+
73+
74+
def test_validate_event_neither_image_exists_with_schema():
75+
event_dict: Any = {
76+
"Records": [
77+
{
78+
"eventID": "1",
79+
"eventName": "INSERT",
80+
"eventVersion": "1.0",
81+
"eventSourceARN": "eventsource_arn",
82+
"awsRegion": "us-west-2",
83+
"eventSource": "aws:dynamodb",
84+
"dynamodb": {
85+
"StreamViewType": "NEW_AND_OLD_IMAGES",
86+
"SequenceNumber": "111",
87+
"SizeBytes": 26,
88+
"Keys": {"Id": {"N": "101"}},
89+
},
90+
}
91+
]
92+
}
93+
with pytest.raises(ValidationError) as exc_info:
94+
handle_dynamodb(event_dict, LambdaContext())
95+
96+
validation_error: ValidationError = exc_info.value
97+
assert len(validation_error.errors()) == 1
98+
error = validation_error.errors()[0]
99+
assert error["msg"] == "DynamoDB streams schema failed validation, missing both new & old stream images"

Diff for: tests/functional/parser/test_eventbridge.py

+24-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
from typing import Any
2+
3+
import pytest
4+
from pydantic import ValidationError
5+
16
from aws_lambda_powertools.utilities.advanced_parser.envelopes.envelopes import Envelope
27
from aws_lambda_powertools.utilities.advanced_parser.parser import parser
38
from aws_lambda_powertools.utilities.typing import LambdaContext
@@ -6,13 +11,13 @@
611

712

813
@parser(schema=MyEventbridgeBusiness, envelope=Envelope.EVENTBRIDGE)
9-
def handle_eventbridge(event: MyEventbridgeBusiness, context: LambdaContext):
14+
def handle_eventbridge(event: MyEventbridgeBusiness, _: LambdaContext):
1015
assert event.instance_id == "i-1234567890abcdef0"
1116
assert event.state == "terminated"
1217

1318

1419
@parser(schema=MyAdvancedEventbridgeBusiness)
15-
def handle_eventbridge_no_envelope(event: MyAdvancedEventbridgeBusiness, context: LambdaContext):
20+
def handle_eventbridge_no_envelope(event: MyAdvancedEventbridgeBusiness, _: LambdaContext):
1621
assert event.detail.instance_id == "i-1234567890abcdef0"
1722
assert event.detail.state == "terminated"
1823
assert event.id == "6a7e8feb-b491-4cf7-a9f1-bf3703467718"
@@ -31,6 +36,23 @@ def test_handle_eventbridge_trigger_event():
3136
handle_eventbridge(event_dict, LambdaContext())
3237

3338

39+
def test_validate_event_does_not_conform_with_user_dict_schema():
40+
event_dict: Any = {
41+
"version": "0",
42+
"id": "6a7e8feb-b491-4cf7-a9f1-bf3703467718",
43+
"detail-type": "EC2 Instance State-change Notification",
44+
"source": "aws.ec2",
45+
"account": "111122223333",
46+
"time": "2017-12-22T18:43:48Z",
47+
"region": "us-west-1",
48+
"resources": ["arn:aws:ec2:us-west-1:123456789012:instance/i-1234567890abcdef0"],
49+
"detail": {},
50+
}
51+
with pytest.raises(ValidationError) as e:
52+
handle_eventbridge(event_dict, LambdaContext())
53+
print(e.exconly())
54+
55+
3456
def test_handle_eventbridge_trigger_event_no_envelope():
3557
event_dict = load_event("eventBridgeEvent.json")
3658
handle_eventbridge_no_envelope(event_dict, LambdaContext())

Diff for: tests/functional/parser/test_sqs.py

+43-5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
from typing import List
1+
from typing import Any, List
2+
3+
import pytest
4+
from pydantic import ValidationError
25

36
from aws_lambda_powertools.utilities.advanced_parser.envelopes.envelopes import Envelope
47
from aws_lambda_powertools.utilities.advanced_parser.parser import parser
@@ -9,7 +12,7 @@
912

1013

1114
@parser(schema=str, envelope=Envelope.SQS)
12-
def handle_sqs_str_body(event: List[str], context: LambdaContext):
15+
def handle_sqs_str_body(event: List[str], _: LambdaContext):
1316
assert len(event) == 2
1417
assert event[0] == "Test message."
1518
assert event[1] == "Test message2."
@@ -21,18 +24,53 @@ def test_handle_sqs_trigger_event_str_body():
2124

2225

2326
@parser(schema=MySqsBusiness, envelope=Envelope.SQS)
24-
def handle_sqs_json_body(event: List[MySqsBusiness], context: LambdaContext):
27+
def handle_sqs_json_body(event: List[MySqsBusiness], _: LambdaContext):
2528
assert len(event) == 1
2629
assert event[0].message == "hello world"
2730
assert event[0].username == "lessa"
2831

2932

30-
def test_handle_sqs_trigger_evemt_json_body(sqs_event): # noqa: F811
33+
def test_handle_sqs_trigger_event_json_body(sqs_event): # noqa: F811
3134
handle_sqs_json_body(sqs_event, LambdaContext())
3235

3336

37+
def test_validate_event_does_not_conform_with_schema():
38+
event: Any = {"invalid": "event"}
39+
40+
with pytest.raises(ValidationError):
41+
handle_sqs_json_body(event, LambdaContext())
42+
43+
44+
def test_validate_event_does_not_conform_user_json_string_with_schema():
45+
event: Any = {
46+
"Records": [
47+
{
48+
"messageId": "059f36b4-87a3-44ab-83d2-661975830a7d",
49+
"receiptHandle": "AQEBwJnKyrHigUMZj6rYigCgxlaS3SLy0a...",
50+
"body": "Not valid json",
51+
"attributes": {
52+
"ApproximateReceiveCount": "1",
53+
"SentTimestamp": "1545082649183",
54+
"SenderId": "AIDAIENQZJOLO23YVJ4VO",
55+
"ApproximateFirstReceiveTimestamp": "1545082649185",
56+
},
57+
"messageAttributes": {
58+
"testAttr": {"stringValue": "100", "binaryValue": "base64Str", "dataType": "Number"}
59+
},
60+
"md5OfBody": "e4e68fb7bd0e697a0ae8f1bb342846b3",
61+
"eventSource": "aws:sqs",
62+
"eventSourceARN": "arn:aws:sqs:us-east-2:123456789012:my-queue",
63+
"awsRegion": "us-east-2",
64+
}
65+
]
66+
}
67+
68+
with pytest.raises(ValidationError):
69+
handle_sqs_json_body(event, LambdaContext())
70+
71+
3472
@parser(schema=MyAdvancedSqsBusiness)
35-
def handle_sqs_no_envelope(event: MyAdvancedSqsBusiness, context: LambdaContext):
73+
def handle_sqs_no_envelope(event: MyAdvancedSqsBusiness, _: LambdaContext):
3674
records = event.Records
3775
record = records[0]
3876
attributes = record.attributes

Diff for: tests/functional/parser/utils.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import json
22
import os
3+
from typing import Any
34

45

5-
def get_event_file_path(file_name: str) -> dict:
6+
def get_event_file_path(file_name: str) -> str:
67
return os.path.dirname(os.path.realpath(__file__)) + "/../../events/" + file_name
78

89

9-
def load_event(file_name: str) -> dict:
10-
full_file_name = os.path.dirname(os.path.realpath(__file__)) + "/../../events/" + file_name
10+
def load_event(file_name: str) -> Any:
11+
full_file_name = get_event_file_path(file_name)
1112
with open(full_file_name) as fp:
1213
return json.load(fp)

0 commit comments

Comments
 (0)