Skip to content

Commit 0df5c57

Browse files
author
Michael Brewer
authored
fix(parser): apigw wss validation check_message_id; housekeeping (#553)
1 parent f3ee8d1 commit 0df5c57

File tree

8 files changed

+66
-27
lines changed

8 files changed

+66
-27
lines changed

Diff for: aws_lambda_powertools/tracing/tracer.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -335,7 +335,7 @@ def decorate(event, context, **kwargs):
335335
# see #465
336336
@overload
337337
def capture_method(self, method: "AnyCallableT") -> "AnyCallableT":
338-
...
338+
... # pragma: no cover
339339

340340
@overload
341341
def capture_method(
@@ -344,7 +344,7 @@ def capture_method(
344344
capture_response: Optional[bool] = None,
345345
capture_error: Optional[bool] = None,
346346
) -> Callable[["AnyCallableT"], "AnyCallableT"]:
347-
...
347+
... # pragma: no cover
348348

349349
def capture_method(
350350
self,

Diff for: aws_lambda_powertools/utilities/feature_toggles/appconfig_fetcher.py

+10-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import logging
2-
from typing import Any, Dict, Optional
2+
from typing import Any, Dict, Optional, cast
33

44
from botocore.config import Config
55

@@ -56,11 +56,15 @@ def get_json_configuration(self) -> Dict[str, Any]:
5656
parsed JSON dictionary
5757
"""
5858
try:
59-
return self._conf_store.get(
60-
name=self.configuration_name,
61-
transform=TRANSFORM_TYPE,
62-
max_age=self._cache_seconds,
63-
) # parse result conf as JSON, keep in cache for self.max_age seconds
59+
# parse result conf as JSON, keep in cache for self.max_age seconds
60+
return cast(
61+
dict,
62+
self._conf_store.get(
63+
name=self.configuration_name,
64+
transform=TRANSFORM_TYPE,
65+
max_age=self._cache_seconds,
66+
),
67+
)
6468
except (GetParameterError, TransformParameterError) as exc:
6569
error_str = f"unable to get AWS AppConfig configuration file, exception={str(exc)}"
6670
self._logger.error(error_str)

Diff for: aws_lambda_powertools/utilities/idempotency/idempotency.py

+1-4
Original file line numberDiff line numberDiff line change
@@ -78,9 +78,7 @@ def idempotent(
7878
try:
7979
return idempotency_handler.handle()
8080
except IdempotencyInconsistentStateError:
81-
if i < max_handler_retries:
82-
continue
83-
else:
81+
if i == max_handler_retries:
8482
# Allow the exception to bubble up after max retries exceeded
8583
raise
8684

@@ -117,7 +115,6 @@ def __init__(
117115
self.context = context
118116
self.event = event
119117
self.lambda_handler = lambda_handler
120-
self.max_handler_retries = 2
121118

122119
def handle(self) -> Any:
123120
"""

Diff for: aws_lambda_powertools/utilities/idempotency/persistence/dynamodb.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ def _update_record(self, data_record: DataRecord):
154154
"ExpressionAttributeNames": expression_attr_names,
155155
}
156156

157-
self.table.update_item(**kwargs)
157+
self.table.update_item(**kwargs) # type: ignore
158158

159159
def _delete_record(self, data_record: DataRecord) -> None:
160160
logger.debug(f"Deleting record for idempotency key: {data_record.idempotency_key}")

Diff for: aws_lambda_powertools/utilities/parameters/dynamodb.py

+1-6
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"""
44

55

6-
from typing import Any, Dict, Optional
6+
from typing import Dict, Optional
77

88
import boto3
99
from boto3.dynamodb.conditions import Key
@@ -141,11 +141,6 @@ class DynamoDBProvider(BaseProvider):
141141
c Parameter value c
142142
"""
143143

144-
table: Any = None
145-
key_attr = None
146-
sort_attr = None
147-
value_attr = None
148-
149144
def __init__(
150145
self,
151146
table_name: str,

Diff for: aws_lambda_powertools/utilities/parser/models/apigw.py

+7-7
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,13 @@ class APIGatewayEventRequestContext(BaseModel):
6868
routeKey: Optional[str]
6969
operationName: Optional[str]
7070

71+
@root_validator
72+
def check_message_id(cls, values):
73+
message_id, event_type = values.get("messageId"), values.get("eventType")
74+
if message_id is not None and event_type != "MESSAGE":
75+
raise TypeError("messageId is available only when the `eventType` is `MESSAGE`")
76+
return values
77+
7178

7279
class APIGatewayProxyEventModel(BaseModel):
7380
version: Optional[str]
@@ -83,10 +90,3 @@ class APIGatewayProxyEventModel(BaseModel):
8390
stageVariables: Optional[Dict[str, str]]
8491
isBase64Encoded: bool
8592
body: str
86-
87-
@root_validator()
88-
def check_message_id(cls, values):
89-
message_id, event_type = values.get("messageId"), values.get("eventType")
90-
if message_id is not None and event_type != "MESSAGE":
91-
raise TypeError("messageId is available only when the `eventType` is `MESSAGE`")
92-
return values

Diff for: tests/functional/idempotency/test_idempotency.py

-1
Original file line numberDiff line numberDiff line change
@@ -395,7 +395,6 @@ def test_idempotent_lambda_expired_during_request(
395395
lambda_apigw_event,
396396
timestamp_expired,
397397
lambda_response,
398-
expected_params_update_item,
399398
hashed_idempotency_key,
400399
lambda_context,
401400
):

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

+44
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import pytest
2+
from pydantic import ValidationError
3+
14
from aws_lambda_powertools.utilities.parser import envelopes, event_parser
25
from aws_lambda_powertools.utilities.parser.models import APIGatewayProxyEventModel
36
from aws_lambda_powertools.utilities.typing import LambdaContext
@@ -100,3 +103,44 @@ def test_apigw_event():
100103
assert request_context.operationName is None
101104
assert identity.apiKey is None
102105
assert identity.apiKeyId is None
106+
107+
108+
def test_apigw_event_with_invalid_websocket_request():
109+
# GIVEN an event with an eventType != MESSAGE and has a messageId
110+
event = {
111+
"resource": "/",
112+
"path": "/",
113+
"httpMethod": "GET",
114+
"headers": {},
115+
"multiValueHeaders": {},
116+
"isBase64Encoded": False,
117+
"body": "Foo!",
118+
"requestContext": {
119+
"accountId": "1234",
120+
"apiId": "myApi",
121+
"httpMethod": "GET",
122+
"identity": {
123+
"sourceIp": "127.0.0.1",
124+
},
125+
"path": "/",
126+
"protocol": "Https",
127+
"requestId": "1234",
128+
"requestTime": "2018-09-07T16:20:46Z",
129+
"requestTimeEpoch": 1536992496000,
130+
"resourcePath": "/",
131+
"stage": "test",
132+
"eventType": "DISCONNECT",
133+
"messageId": "messageId",
134+
},
135+
}
136+
137+
# WHEN calling event_parser with APIGatewayProxyEventModel
138+
with pytest.raises(ValidationError) as err:
139+
handle_apigw_event(event, LambdaContext())
140+
141+
# THEN raise TypeError for invalid event
142+
errors = err.value.errors()
143+
assert len(errors) == 1
144+
expected_msg = "messageId is available only when the `eventType` is `MESSAGE`"
145+
assert errors[0]["msg"] == expected_msg
146+
assert expected_msg in str(err.value)

0 commit comments

Comments
 (0)