"""Lambda test module.""" import base64 import json import pytest from neo4j import exceptions as neo4j_exceptions from neobolt import exceptions as neobolt_exceptions from splitio.api import APIException import src.app as index from src.logic.retry_exception import RetriableError @pytest.fixture def event1(): """CDC minimal event example.""" return {'xid': 1, 'ts': 1, 'xoffset': 1} @pytest.fixture def event2(): """CDC minimal event example.""" return {'xid': 1, 'ts': 1, 'xoffset': 2} @pytest.fixture def event3(): """CDC minimal event example.""" return {'xid': 2, 'ts': 1, 'xoffset': 1} def _wrap_event_to_kinesis_dict(data): return {'kinesis': {'data': base64.b64encode(json.dumps(data).encode())}} class TestHandler: """Tests for handler function.""" @pytest.fixture def process_records_mock(self, mocker): """Process records mock.""" return mocker.patch.object(index, '_process_records') @pytest.fixture def preprocess_records_mock(self, mocker): """Process records mock.""" return mocker.patch.object(index, '_preprocess_records') @pytest.fixture def unknown_event(self): """Unknown event fixture.""" return {'unknown': 'event'} @pytest.fixture def proper_event(self): """Event Expected from Kinesis.""" return {'Records': 'data'} @pytest.fixture def success_handler_response(self): """Successful lambda response.""" return {'status': 'OK'} def test_handler_successful_response( self, process_records_mock, preprocess_records_mock, proper_event, success_handler_response ): """Test handler for successful response.""" result = index.handler(proper_event, None) preprocess_records_mock.assert_called_once_with(proper_event['Records']) process_records_mock.assert_called_once_with(preprocess_records_mock.return_value) assert result == success_handler_response def test_handler_unknown_event(self, preprocess_records_mock, unknown_event, success_handler_response): """Test handler with unknown event.""" result = index.handler(unknown_event, None) assert not preprocess_records_mock.called assert result == success_handler_response def test_handler_raises_exception(self, preprocess_records_mock, proper_event): """Test handler failure.""" exception_message = 'exception message' preprocess_records_mock.side_effect = Exception(exception_message) with pytest.raises(Exception): index.handler(proper_event, None) class TestPreprocessRecords: """Tests for _preprocess_records function.""" @pytest.fixture def raw_records(self, event1, event2, event3): """Raw event records fixture.""" return ( _wrap_event_to_kinesis_dict(event1), _wrap_event_to_kinesis_dict(event2), _wrap_event_to_kinesis_dict(event3), ) @pytest.fixture def kinesis_deaggregator_mock(self, mocker): """Kinesis deaggregator mock.""" return mocker.patch.object(index, 'deaggregator') def test_correct_order(self, kinesis_deaggregator_mock, raw_records, event1, event2, event3): """Test that passed records decoded and sorted correctly.""" kinesis_deaggregator_mock.iter_deaggregate_records.return_value = [ raw_records[0], raw_records[1], raw_records[2], ] result = index._preprocess_records(raw_records) assert result == [event1, event2, event3] def test_incorrect_order(self, kinesis_deaggregator_mock, raw_records, event1, event2, event3): """Test that passed records decoded and sorted correctly.""" kinesis_deaggregator_mock.iter_deaggregate_records.return_value = [ raw_records[2], raw_records[1], raw_records[0], ] result = index._preprocess_records(raw_records) assert result == [event1, event2, event3] def test_original_order_on_sorting_failure(self, kinesis_deaggregator_mock, raw_records, event1, event2): """Test that order is the same if sorting by transaction info fails.""" event_without_transaction_info = {} wrapped_event = _wrap_event_to_kinesis_dict(event_without_transaction_info) kinesis_deaggregator_mock.iter_deaggregate_records.return_value = [ raw_records[1], wrapped_event, raw_records[0], ] result = index._preprocess_records(raw_records) assert result == [event2, event_without_transaction_info, event1] class TestProcessRecords: """Tests for _process_records function.""" @pytest.fixture def raw_record(self): """Raw records fixture.""" return {'raw': 'record'} @pytest.fixture def record_handler(self, mocker): """Record handler mock.""" return mocker.Mock() @pytest.fixture def get_record_handler_mock(self, mocker, record_handler): """Get record handler mock.""" return mocker.patch.object(index, 'get_record_handler', return_value=record_handler) @pytest.fixture def get_record_handler_no_handler_mock(self, mocker): """Get record handler mock with empty response.""" mock = mocker.patch.object(index, 'get_record_handler') record_handler = mock.return_value record_handler.__bool__ = mocker.Mock(return_value=False) return mock def test_process_records(self, raw_record, get_record_handler_mock, record_handler): """Test process record.""" index._process_records([raw_record]) get_record_handler_mock.assert_called_once_with(raw_record) record_handler.assert_called_once_with(raw_record) def test_process_records_no_handler(self, raw_record, get_record_handler_no_handler_mock): """Test process record.""" record_handler = get_record_handler_no_handler_mock.return_value index._process_records([raw_record]) get_record_handler_no_handler_mock.assert_called_once_with(raw_record) assert not record_handler.called @pytest.mark.parametrize( 'exception', [ neobolt_exceptions.ConnectionExpired, neobolt_exceptions.NotALeaderError, neobolt_exceptions.SecurityError, neobolt_exceptions.TransientError, neo4j_exceptions.DatabaseError, neo4j_exceptions.SessionExpired, neo4j_exceptions.TransientError, neo4j_exceptions.ConstraintError, ConnectionResetError, APIException, RetriableError, ], ) def test_process_records_for_exception( self, raw_record, get_record_handler_mock, sentry_sdk_capture_exception_mock, exception ): """Test _process_records raises important exceptions.""" get_record_handler_mock.return_value.side_effect = [exception('error')] with pytest.raises(exception): index._process_records([raw_record]) sentry_sdk_capture_exception_mock.assert_called_once_with() def test_process_records_for_general_exception( self, raw_record, get_record_handler_mock, sentry_sdk_capture_exception_mock ): """Test _process_records captures exceptions.""" get_record_handler_mock.return_value.side_effect = [Exception] index._process_records([raw_record]) sentry_sdk_capture_exception_mock.assert_called_once_with()