"""Lambda test module.""" import json from unittest import mock import pytest import index # noqa @pytest.fixture def event_fixture(): """S3 event fixture.""" return { 'Records': [ { 'EventVersion': '1.0', 'EventSubscriptionArn': 'eventsubscriptionarn', 'EventSource': 'aws:sns', 'Sns': { 'SignatureVersion': '1', 'Timestamp': '1970-01-01T00:00:00.000Z', 'Signature': 'EXAMPLE', 'SigningCertUrl': 'EXAMPLE', 'MessageId': '95df01b4-ee98-5cb9-9903-4c221d41eb5e', 'Message': 'Hello from SNS!', 'MessageAttributes': { 'Test': { 'Type': 'String', 'Value': 'TestString' }, 'TestBinary': { 'Type': 'Binary', 'Value': 'TestBinary' } }, 'Type': 'Notification', 'UnsubscribeUrl': 'EXAMPLE', 'TopicArn': 'topicarn', 'Subject': 'TestInvoke' } } ] } verification_raise_payload = ( '{"Records":[{"eventVersion":"2.0",' '"eventSource":"aws:s3",' '"awsRegion":"us-east-1",' '"eventTime":"2018-02-22T10:06:56.740Z",' '"eventName":"ObjectCreated:Put",' '"userIdentity":{"principalId":"AWS:PRINCIPALID"},' '"requestParameters":{"sourceIPAddress":"some ip"},' '"responseElements":{"x-amz-request-id":"some id",' '"x-amz-id-2":"some id"},' '"s3":{"s3SchemaVersion":"1.0",' '"configurationId":"configurationId",' '"bucket":{"name":"dev-orchdbucket",' '"ownerIdentity":{"principalId":"PRINCIPALID"},' '"arn":"arn:aws:s3:::dev-orchdbucket"},' '"object":{' '"key":"dev-statement-attachments/' 'L25824_227_1mb-%28anoter*version%29.doc",' '"size":1049999,"eTag":"etag",' '"versionId":"version id",' '"sequencer":"sequencer"}}}]}' ) verification_raise_sns_message = { 'Records': [ { 'EventSource': 'aws:sns', 'EventVersion': '1.0', 'EventSubscriptionArn': 'dev_statement_attachments_error', 'Sns': { 'Type': 'Notification', 'MessageId': 'MessageId', 'TopicArn': 'dummy:dev_statement_attachments_error', 'Subject': None, 'Message': verification_raise_payload, 'Timestamp': '2018-02-22T10:10:02.735Z', 'SignatureVersion': '1', 'Signature': 'signature', 'MessageAttributes': { 'RequestID': { 'Type': 'String', 'Value': 'value'}, 'ErrorCode': { 'Type': 'String', 'Value': '200'}}}}]} verification_failure_invalid_name_payload = ( '{"s3_event": {"Records": [' '{"eventVersion": "2.0",' '"eventSource": "aws:s3",' '"awsRegion": "us-east-1",' '"eventTime": "2018-02-22T13:35:50.737Z",' '"eventName": "ObjectCreated:Put",' '"eventTime":"2018-02-22T10:06:56.740Z",' '"eventName":"ObjectCreated:Put",' '"userIdentity":{"principalId":"AWS:PRINCIPALID"},' '"requestParameters":{"sourceIPAddress":"some ip"},' '"responseElements":{"x-amz-request-id":"some id",' '"x-amz-id-2":"some id"},' '"s3": {"s3SchemaVersion": "1.0",' '"configurationId": "configurationId",' '"bucket": {"name": "dev-orchdbucket",' '"ownerIdentity": {"principalId": "principalId"},' '"arn": "arn:aws:s3:::dev-orchdbucket"},' '"object":' '{"key": "dev-statement-attachments/L25824_227_1mb-invalid.txt",' '"size": 1049999, "eTag": "etag",' '"versionId": "versionId",' '"sequencer": "sequencer"}}}]},' '"attachment_attrs": {"account_type": "L",' '"label_id": "25824",' '"period_id": "227",' '"filename": "1mb-invalid.txt",' '"extension": "txt"},' '"status": "ERROR",' '"detailed": "Verification error:' 'dev-statement-attachments/L25824_227_1mb-invalid.txt Invalid extension.' 'Please use one of the following: pdf, png, jpg, xls, xlsx, doc, docx"}' ) verification_failure_invalid_name_sns_message = { 'Records': [ { 'EventSource': 'aws:sns', 'EventVersion': '1.0', 'EventSubscriptionArn': 'dev_statement_attachments_error', 'Sns': { 'Type': 'Notification', 'MessageId': 'MessageId', 'TopicArn': 'dummy:dev_statement_attachments_error', 'Subject': None, 'Message': verification_failure_invalid_name_payload, 'Timestamp': '2018-02-22T13:35:51.805Z', 'SignatureVersion': '1', 'Signature': 'Signature', 'MessageAttributes': {}}}]} store_success_raise_payload = ( '{"Records":[' '{"EventSource":"aws:sns",' '"EventVersion":"1.0",' '"EventSubscriptionArn":"dev_statement_attachments_success:",' '"Sns":{"Type":"Notification",' '"TopicArn":":dev_statement_attachments_success",' '"Subject":null,' '"Message":"' '{\\"s3_event\\": ' '{\\"Records\\": ' '[{\\"eventVersion\\": \\"2.0\\", ' '\\"eventSource\\": \\"aws:s3\\", ' '\\"awsRegion\\": \\"us-east-1\\", ' '\\"eventTime\\": \\"2018-02-22T13:38:06.177Z\\", ' '\\"eventName\\": \\"ObjectCreated:Put\\", ' '\\"userIdentity\\": {' '\\"principalId\\": \\"AWS:AIDAJG7JCD5AVV4V3CKH2\\"}, ' '\\"s3\\": {\\"s3SchemaVersion\\": \\"1.0\\", ' '\\"configurationId\\": \\"c6bb17cbconfigurationId\\", ' '\\"bucket\\": {\\"name\\": \\"dev-orchdbucket\\", ' '\\"ownerIdentity\\": {\\"principalId\\": \\"A2\\"}, ' '\\"arn\\": \\"arn:aws:s3:::dev-orchdbucket\\"}, ' '\\"object\\": {' '\\"key\\": \\"dev-statement-attachments/' 'L25824_227_1mb-next%28anoter*version%29.doc\\", ' '\\"size\\": 1049999, \\"eTag\\": \\"eTag\\", ' '\\"versionId\\": \\"versionId\\", ' '\\"sequencer\\": \\"sequencer\\"}}}]}, ' '\\"attachment_attrs\\": {\\"account_type\\": \\"L\\", ' '\\"label_id\\": \\"25824\\", ' '\\"period_id\\": \\"227\\", ' '\\"filename\\": \\"1mb-next(anoter*version).doc\\", ' '\\"extension\\": \\"doc\\"}}",' '"Timestamp":"2018-02-22T13:38:06.819Z",' '"SignatureVersion":"1",' '"MessageAttributes":{}}}]}') store_success_raise_sns_message = {'Records': [ {'EventSource': 'aws:sns', 'EventVersion': '1.0', 'EventSubscriptionArn': ':dev_statement_attachments_error:', 'Sns': { 'Type': 'Notification', 'MessageId': 'MessageId', 'TopicArn': ':dev_statement_attachments_error', 'Subject': None, 'Message': store_success_raise_payload, 'Timestamp': '2018-02-22T13:41:08.044Z', 'SignatureVersion': '1', 'MessageAttributes': { 'RequestID': { 'Type': 'String', 'Value': 'Value'}, 'ErrorCode': { 'Type': 'String', 'Value': '200'}}}}]} @pytest.mark.parametrize('event_json', [ store_success_raise_payload, verification_raise_payload, ]) @mock.patch('index.parse_s3_event') @mock.patch('index.email') def test_parse_dlq_message(email, parse_s3_event, event_json): """Test parse_dlq_message utility function.""" event = json.loads(event_json) error_message = 'test processing error' email.PROCESSING_ERROR = error_message file_name = 'test file name' context = {'file_name': file_name} expected = { 'error_detail': error_message, 'file_name': file_name} def parse_s3_event_test(s3_event): """Test parse_s3_event is invoked with correct data.""" assert type(s3_event) == dict assert s3_event.get('eventTime') item = s3_event expected_keys = ['s3', 'object', 'key'] for key in expected_keys: item = item.get(key) assert item return context parse_s3_event.side_effect = parse_s3_event_test result = index.parse_dlq_message(event) assert result == expected def test_parse_s3_event(): """Test parse_s3_event_records utility function.""" event = { 'eventTime': 'event time', 's3': {'object': {'key': 'test/key.ext'}}} expected_context = { 'full_file_name': 'key.ext', 'upload_timestamp': 'event time'} result = index.parse_s3_event(event) assert result == expected_context @pytest.mark.parametrize('s3_event, expected_error_detail', [ ({'Records': [{'event': 'event'}]}, '

Error detail

'), ({}, ''), ]) @mock.patch('index.parse_dlq_message') @mock.patch('index.parse_s3_event') @mock.patch('index.email') def test_prepare_message_body( email, parse_s3_event, parse_dlq_message, s3_event, expected_error_detail): """Test prepare_message_body utility funciton.""" template_str = 'Email message body' template = mock.MagicMock() template.format.return_value = template_str workflow_link = 'test.example.com' email.WORKFLOW_LINK = workflow_link upload_timestamp = '42.42.42:42:42' full_file_name = 'full_file_name' event_context = { 'upload_timestamp': upload_timestamp, 'full_file_name': full_file_name} if s3_event: message = { 's3_event': s3_event, 'detailed': 'Error detail' } parse_s3_event.return_value = event_context else: message = {} parse_dlq_message.return_value = event_context result = index.prepare_message_body(template, message) assert result == template_str if s3_event: parse_s3_event.assert_called_once_with(s3_event['Records'][0]) else: parse_s3_event.assert_not_called() parse_dlq_message.assert_called_once_with(message) template.format.assert_called_once_with( workflow_link=workflow_link, error_detail=expected_error_detail, upload_timestamp=upload_timestamp, full_file_name=full_file_name) @mock.patch('index.json') @mock.patch('index.email') @mock.patch('index.ses') @mock.patch('index.common_config') @mock.patch('index.prepare_message_body') def test_handler( prepare_message_body, common_config, ses, email, json, event_fixture): """Test index.handler.""" template = 'Test template' email_sender = 'email@sender' email_recipient = 'error@recipient' attachment_error_subject = 'Error subject' common_config.EMAIL_SENDER = email_sender common_config.ERROR_ATTACHMENT_EMAIL_RECIPIENT = email_recipient # ensure that ignore all events is not a MagicMock object. common_config.IGNORE_ALL_EVENTS = None email.ATTACHMENT_ERROR_SUBJECT = attachment_error_subject email.ATTACHMENT_ERROR_BODY = template mock_message = mock.MagicMock() json.loads.return_value = mock_message prepare_message_body.return_value = mock_message index.handler(event_fixture, None) json.loads.assert_called_once_with( event_fixture['Records'][0]['Sns']['Message']) prepare_message_body.assert_called_once_with(template, mock_message) ses.send_email.assert_called_once_with( recipient=email_recipient, sender=email_sender, subject=attachment_error_subject, message=mock_message) @pytest.mark.parametrize('event', [ store_success_raise_sns_message, verification_failure_invalid_name_sns_message, verification_raise_sns_message ]) @mock.patch('index.ses') @mock.patch('index.common_config') def test_handler_functional_no_errors_on_real_data(common_config, ses, event): """Test handler with real data, ensure no exceptions.""" # ensure that ignore all events is not a MagicMock object. common_config.IGNORE_ALL_EVENTS = None index.handler(event, None) ses.send_email.assert_has_calls([mock.ANY]) @mock.patch('index.json') @mock.patch('index.email') @mock.patch('index.ses') @mock.patch('index.common_config') @mock.patch('index.prepare_message_body') def test_handler_ignores_events( prepare_message_body, common_config, ses, email, json, event_fixture): """Test index.handler.""" template = 'Test template' email_sender = 'email@sender' email_recipient = 'error@recipient' attachment_error_subject = 'Error subject' common_config.EMAIL_SENDER = email_sender common_config.ERROR_ATTACHMENT_EMAIL_RECIPIENT = email_recipient common_config.IGNORE_ALL_EVENTS = 'True' email.ATTACHMENT_ERROR_SUBJECT = attachment_error_subject email.ATTACHMENT_ERROR_BODY = template mock_message = mock.MagicMock() json.loads.return_value = mock_message prepare_message_body.return_value = mock_message index.handler(event_fixture, None) assert json.loads.call_count == 0 assert prepare_message_body.call_count == 0 assert ses.send_email.call_count == 0