Skip to content

Commit cce2ab4

Browse files
committed
improv: test built-in envelopes
1 parent 4dc8c47 commit cce2ab4

File tree

5 files changed

+315
-26
lines changed

5 files changed

+315
-26
lines changed

aws_lambda_powertools/utilities/validation/base.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,6 @@ def unwrap_event_from_envelope(data: Dict, envelope: str, jmespath_options: Dict
2828
try:
2929
logger.debug(f"Envelope detected: {envelope}. JMESPath options: {jmespath_options}")
3030
return jmespath.search(envelope, data, options=jmespath.Options(**jmespath_options))
31-
except (LexerError, TypeError) as e:
32-
raise InvalidEnvelopeExpressionError(e)
31+
except (LexerError, TypeError, UnicodeError) as e:
32+
message = f"Failed to unwrap event from envelope using expression. Error: {e} Exp: {envelope}, Data: {data}" # noqa: B306, E501
33+
raise InvalidEnvelopeExpressionError(message)

aws_lambda_powertools/utilities/validation/envelopes.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22

33
API_GATEWAY_REST = "powertools_json(body)"
44
API_GATEWAY_HTTP = API_GATEWAY_REST
5-
SQS = "Records[*].body"
6-
SNS = "Records[0].Sns.Message"
5+
SQS = "Records[*].powertools_json(body)"
6+
SNS = "Records[0].Sns.Message | powertools_json(@)"
77
EVENTBRIDGE = "detail"
88
CLOUDWATCH_EVENTS_SCHEDULED = EVENTBRIDGE
99
KINESIS_DATA_STREAM = "Records[*].kinesis.powertools_base64(data) | powertools_json(@)"
10-
CLOUDWATCH_LOGS = "awslogs.powertools_base64(data) | powertools_json(@)"
10+
CLOUDWATCH_LOGS = "awslogs.powertools_base64_gzip(data) | powertools_json(@).logEvents[*]"

aws_lambda_powertools/utilities/validation/jmespath_functions.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import base64
2+
import gzip
23
import json
34

45
import jmespath
@@ -12,3 +13,10 @@ def _func_powertools_json(self, value):
1213
@jmespath.functions.signature({"types": ["string"]})
1314
def _func_powertools_base64(self, value):
1415
return base64.b64decode(value).decode()
16+
17+
@jmespath.functions.signature({"types": ["string"]})
18+
def _func_powertools_base64_gzip(self, value):
19+
encoded = base64.b64decode(value)
20+
uncompressed = gzip.decompress(encoded)
21+
22+
return uncompressed.decode()

tests/functional/validator/conftest.py

Lines changed: 286 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,44 @@ def schema():
3030
}
3131

3232

33+
@pytest.fixture
34+
def schema_array():
35+
return {
36+
"$schema": "http://json-schema.org/draft-07/schema",
37+
"$id": "http://example.com/example.json",
38+
"type": "array",
39+
"title": "Sample schema",
40+
"description": "Sample JSON Schema for dummy data in an array",
41+
"examples": [[{"username": "lessa", "message": "hello world"}]],
42+
"additionalItems": True,
43+
"items": {
44+
"$id": "#/items",
45+
"anyOf": [
46+
{
47+
"$id": "#/items/anyOf/0",
48+
"type": "object",
49+
"description": "Dummy data in an array",
50+
"required": ["message", "username"],
51+
"properties": {
52+
"message": {
53+
"$id": "#/items/anyOf/0/properties/message",
54+
"type": "string",
55+
"title": "The message",
56+
"examples": ["hello world"],
57+
},
58+
"username": {
59+
"$id": "#/items/anyOf/0/properties/usernam",
60+
"type": "string",
61+
"title": "The username",
62+
"examples": ["lessa"],
63+
},
64+
},
65+
}
66+
],
67+
},
68+
}
69+
70+
3371
@pytest.fixture
3472
def raw_event():
3573
return {"message": "hello hello", "username": "blah blah"}
@@ -48,3 +86,251 @@ def wrapped_event_json_string():
4886
@pytest.fixture
4987
def wrapped_event_base64_json_string():
5088
return {"data": "eyJtZXNzYWdlIjogImhlbGxvIGhlbGxvIiwgInVzZXJuYW1lIjogImJsYWggYmxhaCJ9="}
89+
90+
91+
@pytest.fixture
92+
def apigateway_event():
93+
return {
94+
"body": '{"message": "hello world", "username": "lessa"}',
95+
"resource": "/{proxy+}",
96+
"path": "/path/to/resource",
97+
"httpMethod": "POST",
98+
"isBase64Encoded": True,
99+
"queryStringParameters": {"foo": "bar"},
100+
"multiValueQueryStringParameters": {"foo": ["bar"]},
101+
"pathParameters": {"proxy": "/path/to/resource"},
102+
"stageVariables": {"baz": "qux"},
103+
"headers": {
104+
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
105+
"Accept-Encoding": "gzip, deflate, sdch",
106+
"Accept-Language": "en-US,en;q=0.8",
107+
"Cache-Control": "max-age=0",
108+
"CloudFront-Forwarded-Proto": "https",
109+
"CloudFront-Is-Desktop-Viewer": "true",
110+
"CloudFront-Is-Mobile-Viewer": "false",
111+
"CloudFront-Is-SmartTV-Viewer": "false",
112+
"CloudFront-Is-Tablet-Viewer": "false",
113+
"CloudFront-Viewer-Country": "US",
114+
"Host": "1234567890.execute-api.us-east-1.amazonaws.com",
115+
"Upgrade-Insecure-Requests": "1",
116+
"User-Agent": "Custom User Agent String",
117+
"Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)",
118+
"X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==",
119+
"X-Forwarded-For": "127.0.0.1, 127.0.0.2",
120+
"X-Forwarded-Port": "443",
121+
"X-Forwarded-Proto": "https",
122+
},
123+
"multiValueHeaders": {
124+
"Accept": ["text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"],
125+
"Accept-Encoding": ["gzip, deflate, sdch"],
126+
"Accept-Language": ["en-US,en;q=0.8"],
127+
"Cache-Control": ["max-age=0"],
128+
"CloudFront-Forwarded-Proto": ["https"],
129+
"CloudFront-Is-Desktop-Viewer": ["true"],
130+
"CloudFront-Is-Mobile-Viewer": ["false"],
131+
"CloudFront-Is-SmartTV-Viewer": ["false"],
132+
"CloudFront-Is-Tablet-Viewer": ["false"],
133+
"CloudFront-Viewer-Country": ["US"],
134+
"Host": ["0123456789.execute-api.us-east-1.amazonaws.com"],
135+
"Upgrade-Insecure-Requests": ["1"],
136+
"User-Agent": ["Custom User Agent String"],
137+
"Via": ["1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)"],
138+
"X-Amz-Cf-Id": ["cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA=="],
139+
"X-Forwarded-For": ["127.0.0.1, 127.0.0.2"],
140+
"X-Forwarded-Port": ["443"],
141+
"X-Forwarded-Proto": ["https"],
142+
},
143+
"requestContext": {
144+
"accountId": "123456789012",
145+
"resourceId": "123456",
146+
"stage": "prod",
147+
"requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef",
148+
"requestTime": "09/Apr/2015:12:34:56 +0000",
149+
"requestTimeEpoch": 1428582896000,
150+
"path": "/prod/path/to/resource",
151+
"resourcePath": "/{proxy+}",
152+
"httpMethod": "POST",
153+
"apiId": "1234567890",
154+
"protocol": "HTTP/1.1",
155+
},
156+
}
157+
158+
159+
@pytest.fixture
160+
def sns_event():
161+
return {
162+
"Records": [
163+
{
164+
"EventSource": "aws:sns",
165+
"EventVersion": "1.0",
166+
"EventSubscriptionArn": "arn:aws:sns:us-east-1::ExampleTopic",
167+
"Sns": {
168+
"Type": "Notification",
169+
"MessageId": "95df01b4-ee98-5cb9-9903-4c221d41eb5e",
170+
"TopicArn": "arn:aws:sns:us-east-1:123456789012:ExampleTopic",
171+
"Subject": "example subject",
172+
"Message": '{"message": "hello world", "username": "lessa"}',
173+
"Timestamp": "1970-01-01T00:00:00.000Z",
174+
"SignatureVersion": "1",
175+
"Signature": "EXAMPLE",
176+
"SigningCertUrl": "EXAMPLE",
177+
"UnsubscribeUrl": "EXAMPLE",
178+
"MessageAttributes": {
179+
"Test": {"Type": "String", "Value": "TestString"},
180+
"TestBinary": {"Type": "Binary", "Value": "TestBinary"},
181+
},
182+
},
183+
}
184+
]
185+
}
186+
187+
188+
@pytest.fixture
189+
def kinesis_event():
190+
return {
191+
"Records": [
192+
{
193+
"kinesis": {
194+
"partitionKey": "partitionKey-03",
195+
"kinesisSchemaVersion": "1.0",
196+
"data": "eyJtZXNzYWdlIjogImhlbGxvIGhlbGxvIiwgInVzZXJuYW1lIjogImJsYWggYmxhaCJ9=",
197+
"sequenceNumber": "49545115243490985018280067714973144582180062593244200961",
198+
"approximateArrivalTimestamp": 1428537600.0,
199+
},
200+
"eventSource": "aws:kinesis",
201+
"eventID": "shardId-000000000000:49545115243490985018280067714973144582180062593244200961",
202+
"invokeIdentityArn": "arn:aws:iam::EXAMPLE",
203+
"eventVersion": "1.0",
204+
"eventName": "aws:kinesis:record",
205+
"eventSourceARN": "arn:aws:kinesis:EXAMPLE",
206+
"awsRegion": "us-east-1",
207+
}
208+
]
209+
}
210+
211+
212+
@pytest.fixture
213+
def eventbridge_event():
214+
return {
215+
"id": "cdc73f9d-aea9-11e3-9d5a-835b769c0d9c",
216+
"detail-type": "Scheduled Event",
217+
"source": "aws.events",
218+
"account": "123456789012",
219+
"time": "1970-01-01T00:00:00Z",
220+
"region": "us-east-1",
221+
"resources": ["arn:aws:events:us-east-1:123456789012:rule/ExampleRule"],
222+
"detail": {"message": "hello hello", "username": "blah blah"},
223+
}
224+
225+
226+
@pytest.fixture
227+
def sqs_event():
228+
return {
229+
"Records": [
230+
{
231+
"messageId": "19dd0b57-b21e-4ac1-bd88-01bbb068cb78",
232+
"receiptHandle": "MessageReceiptHandle",
233+
"body": '{"message": "hello world", "username": "lessa"}',
234+
"attributes": {
235+
"ApproximateReceiveCount": "1",
236+
"SentTimestamp": "1523232000000",
237+
"SenderId": "123456789012",
238+
"ApproximateFirstReceiveTimestamp": "1523232000001",
239+
},
240+
"messageAttributes": {},
241+
"md5OfBody": "7b270e59b47ff90a553787216d55d91d",
242+
"eventSource": "aws:sqs",
243+
"eventSourceARN": "arn:aws:sqs:us-east-1:123456789012:MyQueue",
244+
"awsRegion": "us-east-1",
245+
},
246+
]
247+
}
248+
249+
250+
@pytest.fixture
251+
def cloudwatch_logs_event():
252+
return {
253+
"awslogs": {
254+
"data": "H4sIACZAXl8C/52PzUrEMBhFX2UILpX8tPbHXWHqIOiq3Q1F0ubrWEiakqTWofTdTYYB0YWL2d5zvnuTFellBIOedoiyKH5M0iwnlKH7HZL6dDB6ngLDfLFYctUKjie9gHFaS/sAX1xNEq525QxwFXRGGMEkx4Th491rUZdV3YiIZ6Ljfd+lfSyAtZloacQgAkqSJCGhxM6t7cwwuUGPz4N0YKyvO6I9WDeMPMSo8Z4Ca/kJ6vMEYW5f1MX7W1lVxaG8vqX8hNFdjlc0iCBBSF4ERT/3Pl7RbMGMXF2KZMh/C+gDpNS7RRsp0OaRGzx0/t8e0jgmcczyLCWEePhni/23JWalzjdu0a3ZvgEaNLXeugEAAA==" # noqa: E501
255+
}
256+
}
257+
258+
259+
@pytest.fixture
260+
def cloudwatch_logs_schema():
261+
return {
262+
"$schema": "http://json-schema.org/draft-07/schema",
263+
"$id": "http://example.com/example.json",
264+
"type": "array",
265+
"title": "Sample schema",
266+
"description": "Sample JSON Schema for CloudWatch Logs logEvents using structured dummy data",
267+
"examples": [
268+
[
269+
{
270+
"id": "eventId1",
271+
"message": {"username": "lessa", "message": "hello world"},
272+
"timestamp": 1440442987000,
273+
},
274+
{
275+
"id": "eventId2",
276+
"message": {"username": "dummy", "message": "hello world"},
277+
"timestamp": 1440442987001,
278+
},
279+
]
280+
],
281+
"additionalItems": True,
282+
"items": {
283+
"$id": "#/items",
284+
"anyOf": [
285+
{
286+
"$id": "#/items/anyOf/0",
287+
"type": "object",
288+
"title": "The first anyOf schema",
289+
"description": "Actual log data found in CloudWatch Logs logEvents key",
290+
"required": ["id", "message", "timestamp"],
291+
"properties": {
292+
"id": {
293+
"$id": "#/items/anyOf/0/properties/id",
294+
"type": "string",
295+
"title": "The id schema",
296+
"description": "Unique identifier for log event",
297+
"default": "",
298+
"examples": ["eventId1"],
299+
},
300+
"message": {
301+
"$id": "#/items/anyOf/0/properties/message",
302+
"type": "object",
303+
"title": "The message schema",
304+
"description": "Log data captured in CloudWatch Logs",
305+
"default": {},
306+
"examples": [{"username": "lessa", "message": "hello world"}],
307+
"required": ["username", "message"],
308+
"properties": {
309+
"username": {
310+
"$id": "#/items/anyOf/0/properties/message/properties/username",
311+
"type": "string",
312+
"title": "The username",
313+
"examples": ["lessa"],
314+
},
315+
"message": {
316+
"$id": "#/items/anyOf/0/properties/message/properties/message",
317+
"type": "string",
318+
"title": "The message",
319+
"examples": ["hello world"],
320+
},
321+
},
322+
"additionalProperties": True,
323+
},
324+
"timestamp": {
325+
"$id": "#/items/anyOf/0/properties/timestamp",
326+
"type": "integer",
327+
"title": "The timestamp schema",
328+
"description": "Log event epoch timestamp in milliseconds",
329+
"default": 0,
330+
"examples": [1440442987000],
331+
},
332+
},
333+
}
334+
],
335+
},
336+
}

tests/functional/validator/test_validator.py

Lines changed: 15 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import pytest
22

3-
from aws_lambda_powertools.utilities.validation import exceptions, validate
3+
from aws_lambda_powertools.utilities.validation import envelopes, exceptions, validate
44

55

66
def test_validate_raw_event(schema, raw_event):
@@ -46,36 +46,30 @@ def test_validate_invalid_event(schema):
4646
validate(event=b64_event, schema=schema)
4747

4848

49-
def test_apigateway_http_envelope():
50-
raise NotImplementedError()
51-
52-
53-
def test_apigateway_rest_envelope():
54-
raise NotImplementedError()
55-
56-
57-
def test_eventbridge_envelope():
58-
raise NotImplementedError()
49+
def test_apigateway_envelope(schema, apigateway_event):
50+
# Payload v1 and v2 remains consistent where the payload is (body)
51+
validate(event=apigateway_event, schema=schema, envelope=envelopes.API_GATEWAY_REST)
52+
validate(event=apigateway_event, schema=schema, envelope=envelopes.API_GATEWAY_HTTP)
5953

6054

61-
def test_sqs_envelope():
62-
raise NotImplementedError()
55+
def test_sqs_envelope(sqs_event, schema_array):
56+
validate(event=sqs_event, schema=schema_array, envelope=envelopes.SQS)
6357

6458

65-
def test_sns_envelope():
66-
raise NotImplementedError()
59+
def test_sns_envelope(schema, sns_event):
60+
validate(event=sns_event, schema=schema, envelope=envelopes.SNS)
6761

6862

69-
def test_cloudwatch_events_schedule_envelope():
70-
raise NotImplementedError()
63+
def test_eventbridge_envelope(schema, eventbridge_event):
64+
validate(event=eventbridge_event, schema=schema, envelope=envelopes.EVENTBRIDGE)
7165

7266

73-
def test_kinesis_data_stream_envelope():
74-
raise NotImplementedError()
67+
def test_kinesis_data_stream_envelope(schema, kinesis_event):
68+
validate(event=kinesis_event, schema=schema, envelope=envelopes.KINESIS_DATA_STREAM)
7569

7670

77-
def test_cloudwatch_logs_envelope():
78-
raise NotImplementedError()
71+
def test_cloudwatch_logs_envelope(cloudwatch_logs_schema, cloudwatch_logs_event):
72+
validate(event=cloudwatch_logs_event, schema=cloudwatch_logs_schema, envelope=envelopes.CLOUDWATCH_LOGS)
7973

8074

8175
def test_validator_incoming():

0 commit comments

Comments
 (0)