"""Tests for Dynamo Audit->S3 lambda.""" import datetime import decimal import time import uuid import boto3 # noqa from moto import mock_s3 # noqa import pytest # noqa import simplejson as json import config # noqa import lambda_function # noqa @pytest.fixture def patch_datetime_utcnow(monkeypatch): """Fixture for datetime.datetime.utcnow method.""" mock_date = datetime.datetime(2017, 10, 23, 15, 9, 12, 204904) class mydatetime: """Mock datetime class for testing purposes.""" @classmethod def utcnow(cls): return mock_date monkeypatch.setattr(datetime, 'datetime', mydatetime) @pytest.fixture def dynamo_event_name(): """Fixture type and corresponding feature flag value.""" return 'tests/fixtures/dynamo_event_fixture.json' @pytest.fixture def dynamo_event(dynamo_event_name): """Fixture with valid DynamoDB event.""" with open(dynamo_event_name, 'r') as fixture: dynamo_test_event = json.load(fixture, parse_float=decimal.Decimal) return dynamo_test_event @mock_s3 def test_lambda_dynamo_audit_to_s3(dynamo_event_name): """Expect to get json file with required records.""" s3 = boto3.client('s3') s3.create_bucket(Bucket=config.S3_BUCKET_NAME) with open(dynamo_event_name, 'r') as fixture: dynamo_event = json.load(fixture) expected_content = lambda_function.parse_dynamo_events(dynamo_event) result = lambda_function.lambda_handler(dynamo_event, None) created_file = s3.get_object( Bucket=config.S3_BUCKET_NAME, Key=result['created_filename']) content = json.loads(created_file['Body'].read().decode('utf-8')) assert created_file['ResponseMetadata']['HTTPStatusCode'] == 200 assert content == expected_content def test_handler_s3_error(mocker, dynamo_event_name): """Expect to log error from S3.""" s3_error_response = { 'ResponseMetadata': {'HTTPStatusCode': 400} } s3_mock = mocker.patch.object( lambda_function.s3, 'put_object', return_value=s3_error_response) mock_sentry = mocker.patch.object( lambda_function.sentry.sentry_client, 'captureException') with open(dynamo_event_name, 'r') as fixture: dynamo_event = json.load(fixture) with pytest.raises(Exception) as e: lambda_function.lambda_handler(dynamo_event, None) assert e.value.args[0].args == (s3_error_response,) assert s3_mock.call_count == 10 assert mock_sentry.called def test_handler_sends_errors_to_sentry(mocker, dynamo_event_name): """Expect to handle exception and send it to sentry.""" mocker.patch.object( lambda_function.s3, 'put_object', side_effect=Exception) mock_sentry = mocker.patch.object( lambda_function.sentry.sentry_client, 'captureException') with open(dynamo_event_name, 'r') as fixture: dynamo_event = json.load(fixture) with pytest.raises(Exception): lambda_function.lambda_handler(dynamo_event, None) assert mock_sentry.called @pytest.mark.parametrize( 'dynamo_event_name,parsed_event_name', [ ('tests/fixtures/dynamo_event_fixture.json', 'tests/fixtures/expected_parsed_event.json'), ('tests/fixtures/dynamo_remove_event_fixture.json', 'tests/fixtures/expected_parsed_remove_event.json'), ('tests/fixtures/dynamo_lock_event_fixture.json', 'tests/fixtures/expected_parsed_lock_event.json'), ('tests/fixtures/dynamo_unlock_event_fixture.json', 'tests/fixtures/expected_parsed_unlock_event.json'), ('tests/fixtures/dynamo_conflict_created_event_fixture.json', 'tests/fixtures/expected_parsed_conflict_created_event.json'), ('tests/fixtures/dynamo_conflict_resolved_event_fixture.json', 'tests/fixtures/expected_parsed_conflict_resolved_event.json') ] ) def test_parse_dynamo_events(dynamo_event_name, parsed_event_name): """Expect to get formatted events from Dynamo event.""" with open(dynamo_event_name, 'r') as fixture: dynamo_events = json.load(fixture) result = lambda_function.parse_dynamo_events(dynamo_events) with open(parsed_event_name, 'r') as expected_data: expected_result = json.load(expected_data, parse_float=decimal.Decimal) assert result == expected_result def test_parse_single_dynamo_event(dynamo_event): """Expect to get dict with data parsed from event.""" single_event = dynamo_event['Records'][0] result = lambda_function.parse_single_dynamo_event(single_event) result_fixture_name = 'tests/fixtures/expected_parsed_event.json' with open(result_fixture_name, 'r') as expected_data: expected_result = json.load(expected_data, parse_float=decimal.Decimal) assert result == expected_result[0] def test_parse_single_dynamo_event_no_new_image(dynamo_event): """Expect to get None with data parsed from event.""" single_event = dynamo_event['Records'][0] del single_event['dynamodb']['NewImage'] result = lambda_function.parse_single_dynamo_event(single_event) assert result is None def test_make_s3_key(patch_datetime_utcnow, mocker): """Expect to get correct s3 key.""" mocker.patch.object(uuid, 'uuid4', return_value='222') expected_s3_folder = config.DEFAULT_AUDIT_S3_BUCKET_FOLDER expected_dir_name = datetime.date.today().strftime('%Y-%m-%d') expected_name = ( str(time.mktime(datetime.datetime.utcnow().timetuple())) + uuid.uuid4()) expected_filename = '.'.join([expected_name, 'json']) expected_s3_key = '/'.join( [expected_s3_folder, expected_dir_name, expected_filename]) s3_key = lambda_function.make_s3_key() assert s3_key == expected_s3_key class SentryException(Exception): """Fake Sentry exception.""" pass def test_sentry_capture_exception_failure(mocker, dynamo_event_name): """Test that an exception will be raised even if sentry fails.""" mocker.patch.object( lambda_function.s3, 'put_object', side_effect=Exception) mocker.patch.object( lambda_function.sentry.sentry_client, 'captureException', side_effect=SentryException) with open(dynamo_event_name, 'r') as fixture: dynamo_event = json.load(fixture) with pytest.raises(Exception): lambda_function.lambda_handler(dynamo_event, None)