"""Tests for dynamodb module.""" import copy from unittest import mock from unittest.mock import patch import pytest from src.connectors import dynamodb @pytest.mark.parametrize( 'max_attempts, expected_max_attempts', [ (None, 10), (21, 21), ], ) @patch('boto3.resource') @patch('botocore.config.Config') def test_get_dynamodb(mocked_botocore_config, mocked_resource, max_attempts, expected_max_attempts): """Test DynamoDB resource helper uses proper configuration.""" mocked_botocore_config_instance = mock.MagicMock() mocked_botocore_config.return_value = mocked_botocore_config_instance mocked_dynamodb = mock.MagicMock() mocked_resource.return_value = mocked_dynamodb res = dynamodb.get_dynamodb(max_attempts) assert res == mocked_dynamodb mocked_botocore_config.assert_called_with(retries={'max_attempts': expected_max_attempts}) mocked_resource.assert_called_with('dynamodb', config=mocked_botocore_config_instance) @patch('src.connectors.dynamodb.get_dynamodb') def test_get_dynamodb_table(mocked_get_dynamodb): """Test that get_dynamodb_table function uses configured table name.""" test_table = 'test_table' mocked_dynamo_db = mock.MagicMock() mocked_get_dynamodb.return_value = mocked_dynamo_db assert dynamodb.get_dynamodb_table(test_table) mocked_dynamo_db.Table.assert_called_with(test_table) def test_batch_write_data(mocker, mock_attachment_items): """Test that batch writer called proper number times with proper items.""" table_name = 'test_table' mock_table = mock.MagicMock() batch_writer = mock.MagicMock() batch_context_manager = mock.MagicMock() batch_writer.__enter__.return_value = batch_context_manager mocked_get_dynamodb_table = mocker.patch('src.connectors.dynamodb.get_dynamodb_table') mocked_get_dynamodb_table.return_value = mock_table mock_table.batch_writer.return_value = batch_writer dynamodb.batch_write_data(table_name, mock_attachment_items) dynamodb.get_dynamodb_table.assert_called_with(table_name) mock_table.batch_writer.assert_called_once() expected_calls = [ mock.call(Item=mock_attachment_items[0]), mock.call(Item=mock_attachment_items[1]), ] batch_context_manager.put_item.assert_has_calls(expected_calls, any_order=True) @mock.patch('src.connectors.dynamodb.get_dynamodb_table') def test_batch_delete_data(get_dynamodb_table): """Test batch_delete_data utility funciton.""" mock_table = mock.MagicMock() batch_writer = mock.MagicMock() batch_context_manager = mock.MagicMock() batch_writer.__enter__.return_value = batch_context_manager mock_table.batch_writer.return_value = batch_writer get_dynamodb_table.return_value = mock_table table_name = 'test_table_name' key_1 = {'a': 1} key_2 = {'a': 2} key_3 = {'a': 3} keys = (key_1, key_2, key_3) expected_delete_calls = [mock.call(Key=key_1), mock.call(Key=key_2), mock.call(Key=key_3)] dynamodb.batch_delete_data(table_name, keys) get_dynamodb_table.assert_called_once_with(table_name) batch_context_manager.delete_item.assert_has_calls(expected_delete_calls) @mock.patch('src.connectors.dynamodb.get_dynamodb_table') def test_put_item(get_dynamodb_table): """Test put_item utility function.""" table_name = 'test_table' item = {'a': 1} mock_table = mock.MagicMock() get_dynamodb_table.return_value = mock_table dynamodb.put_item(table_name, item) get_dynamodb_table.assert_called_once_with(table_name) mock_table.put_item.assert_called_once_with(Item=item, ReturnValues='NONE') @mock.patch('src.connectors.dynamodb.get_dynamodb_table') def test_put_item_raises(get_dynamodb_table): """Test put_item utility function.""" table_name = 'test_table' item = {'a': 1} mock_table = mock.MagicMock() mock_table.put_item.side_effect = Exception() get_dynamodb_table.return_value = mock_table with pytest.raises(Exception): dynamodb.put_item(table_name, item) @pytest.mark.parametrize( 'scan_kwargs', [ {}, {'a': 1}, {'Select': 'COUNT', 'b': 2}, ], ) @mock.patch('src.connectors.dynamodb.time') def test_get_scan_results(time, scan_kwargs): """Test get_scan_results utility function.""" time.time.return_value = 1 table = mock.MagicMock() response = {'response': 'item'} table.scan.return_value = response result = list(dynamodb.get_scan_results(table, scan_kwargs)) assert result == [response] table.scan.assert_called_once_with(**scan_kwargs) @pytest.mark.parametrize( 'scan_kwargs', [ {}, {'a': 1}, {'Select': 'COUNT', 'b': 2}, ], ) @mock.patch('src.connectors.dynamodb.time') def test_get_scan_results_items(time, scan_kwargs): """Test get_scan_results utility function.""" time.time.return_value = 1 table = mock.MagicMock() expected_scan_kwargs = copy.deepcopy(scan_kwargs) key = {'item': 'key'} response = {'LastEvaluatedKey': key} table.scan.side_effect = (response, {}) result = list(dynamodb.get_scan_results(table, scan_kwargs)) assert result == [response, {}] expected_calls = ( mock.call(**expected_scan_kwargs), mock.call(ExclusiveStartKey=key, **expected_scan_kwargs), ) table.scan.assert_has_calls(expected_calls) def test_get_scan_results_raises(): """Test that get_scan_results raises exceptions.""" table = mock.MagicMock() table.scan.side_effect = Exception() with pytest.raises(Exception): next(dynamodb.get_scan_results(table, {})) @mock.patch('src.connectors.dynamodb.conditions') def test_update_existing_item(conditions): """Test update_existing_item utility function.""" pk = 'test' sort_key = 'second test' table = mock.MagicMock() expected_response = 'expected_response' table.update_item.return_value = expected_response update_data = {'Key': {'label_type_id_period_id': pk, 'file_name': sort_key}} resp = dynamodb.update_existing_item(table, update_data) assert resp == expected_response table.update_item.assert_has_calls([mock.ANY])