"""Tests for the dynamo connector.""" from typing import Any, Dict, List from unittest.mock import AsyncMock, MagicMock, call, patch from uuid import UUID import pytest from pdp.connectors.dynamo import DynamoDbConnector TEST_TABLE_NAME = "test-table-name" TEST_HASH_KEY = "test-hash-key" TEST_RANGE_KEY = "test-range-key" def test_connector_errors_when_missing_hash_key_attr() -> None: """Test connector init throws TypeError if missing hash key attribute.""" with pytest.raises(TypeError): DynamoDbConnector(table_name=TEST_TABLE_NAME, range_key=TEST_RANGE_KEY) # type: ignore @patch("pdp.connectors.dynamo.session") @patch("pdp.connectors.dynamo.get_opts") def test_connector_uses_opts_when_present( mock_opts: MagicMock, mock_boto3_session: MagicMock, mock_dynamo_client: MagicMock, ) -> None: """Test connector instantiates with configured options.""" mock_opts.return_value = {"endpoint_url": "some url"} mock_boto3_session.Session.return_value.client.return_value = mock_dynamo_client DynamoDbConnector(table_name=TEST_TABLE_NAME, hash_key=TEST_HASH_KEY) mock_boto3_session.Session.return_value.client.assert_called_once_with( "dynamodb", endpoint_url="some url" ) @patch("pdp.connectors.dynamo.session") @patch("pdp.connectors.dynamo.get_opts") def test_connector_query_by_hash_key( mock_opts: MagicMock, mock_boto3_session: MagicMock, mock_dynamo_client: MagicMock, dynamodb_query_response: Dict[str, Any], ) -> None: """Test connector queries by hash key.""" mock_opts.return_value = {} mock_boto3_session.Session.return_value.client.return_value = mock_dynamo_client connector = DynamoDbConnector(table_name=TEST_TABLE_NAME, hash_key=TEST_HASH_KEY) result = connector.query_by_hash_key("hello") mock_boto3_session.Session.return_value.client.assert_called_once_with("dynamodb") mock_dynamo_client.query.assert_called_once_with( ExpressionAttributeValues={":hash_key": {"S": "hello"}}, KeyConditionExpression=f"{TEST_HASH_KEY} = :hash_key", TableName=TEST_TABLE_NAME, ) assert result == { "items": dynamodb_query_response["Items"], "cursor": { "cursor": None, "shorthand": None, }, } @patch("pdp.connectors.dynamo.session") @patch("pdp.connectors.dynamo.get_opts") def test_connector_query_by_hash_and_range_key( mock_opts: MagicMock, mock_boto3_session: MagicMock, mock_dynamo_client: MagicMock, dynamodb_query_response: Dict[str, Any], ) -> None: """Test connector queries by hash and range key.""" mock_opts.return_value = {} mock_boto3_session.Session.return_value.client.return_value = mock_dynamo_client connector = DynamoDbConnector( table_name=TEST_TABLE_NAME, hash_key=TEST_HASH_KEY, range_key=TEST_RANGE_KEY ) result = connector.query_by_hash_key("yo", "sup") mock_boto3_session.Session.return_value.client.assert_called_once_with("dynamodb") mock_dynamo_client.query.assert_called_once_with( ExpressionAttributeValues={ ":hash_key": {"S": "yo"}, ":range_key": {"S": "sup"}, }, KeyConditionExpression=f"{TEST_HASH_KEY} = :hash_key AND {TEST_RANGE_KEY} = :range_key", # noqa: E501 TableName=TEST_TABLE_NAME, ) assert result == { "items": dynamodb_query_response["Items"], "cursor": { "cursor": None, "shorthand": None, }, } @patch("pdp.connectors.dynamo.session") @patch("pdp.connectors.dynamo.encode_pagination_shorthand") @patch("pdp.connectors.dynamo.encode_pagination_cursor") @patch("pdp.connectors.dynamo.decode_pagination_cursor") @patch("pdp.connectors.dynamo.get_opts") def test_connector_query_handles_pagination_cursors( mock_opts: MagicMock, mock_decode_cursor: MagicMock, mock_encode_cursor: MagicMock, mock_encode_shorthand: MagicMock, mock_boto3_session: MagicMock, mock_dynamo_client: MagicMock, dynamodb_query_response: Dict[str, Any], dynamo_last_evaluated_key: Dict[str, Any], ) -> None: """Test connector queries handle pagination cursors.""" # Update the dynamodb_query_response to include pagination cursor dynamodb_query_response["LastEvaluatedKey"] = dynamo_last_evaluated_key mock_opts.return_value = {} mock_decode_cursor.return_value = dynamo_last_evaluated_key mock_encode_cursor.return_value = "well done encoder" mock_encode_shorthand.return_value = "well#done" mock_boto3_session.Session.return_value.client.return_value = mock_dynamo_client connector = DynamoDbConnector(TEST_TABLE_NAME, TEST_HASH_KEY, TEST_RANGE_KEY) result = connector.query_by_hash_key( "yo", range_key="sup", cursor="decode me pls", ) mock_boto3_session.Session.return_value.client.assert_called_once_with("dynamodb") mock_decode_cursor.assert_called_once_with("decode me pls") mock_encode_cursor.assert_called_once_with(dynamo_last_evaluated_key) mock_encode_shorthand.assert_called_once_with( dynamo_last_evaluated_key, TEST_HASH_KEY, range_key=TEST_RANGE_KEY ) mock_dynamo_client.query.assert_called_once_with( ExpressionAttributeValues={ ":hash_key": {"S": "yo"}, ":range_key": {"S": "sup"}, }, KeyConditionExpression=f"{TEST_HASH_KEY} = :hash_key AND {TEST_RANGE_KEY} = :range_key", # noqa: E501 TableName=TEST_TABLE_NAME, ExclusiveStartKey=dynamo_last_evaluated_key, ) assert result == { "items": dynamodb_query_response["Items"], "cursor": { "cursor": "well done encoder", "shorthand": "well#done", }, } @patch("pdp.connectors.dynamo.session") @patch("pdp.connectors.dynamo.get_opts") def test_connector_query_by_hash_and_missing_range_key( mock_opts: MagicMock, mock_boto3_session: MagicMock, mock_dynamo_client: MagicMock ) -> None: """Test connector throws Exception if given unexpected range key.""" mock_opts.return_value = {} mock_boto3_session.Session.return_value.client.return_value = mock_dynamo_client connector = DynamoDbConnector(table_name=TEST_TABLE_NAME, hash_key=TEST_HASH_KEY) with pytest.raises(ValueError): connector.query_by_hash_key("yo", "sup") mock_boto3_session.Session.return_value.client.assert_called_once_with("dynamodb") mock_dynamo_client.query.assert_not_called() @patch("pdp.connectors.dynamo.session") def test_connector_update_item( mock_boto3_session: MagicMock, mock_dynamo_client: MagicMock, dynamodb_update_response: MagicMock, ) -> None: """Test connector updates item.""" mock_boto3_session.Session.return_value.client.return_value = mock_dynamo_client connector = DynamoDbConnector( table_name=TEST_TABLE_NAME, hash_key=TEST_HASH_KEY, range_key=TEST_RANGE_KEY ) example_item = { "tenant_type": {"S": "account"}, "roles": {"L": [{"M": {"role": {"S": "settings_admin"}}}]}, } example_hash_value = "cool-test-hash-value" example_range_value = "cooler-range-value" update_result = connector.update_item( hash_key=example_hash_value, item=example_item, range_key=example_range_value ) mock_dynamo_client.update_item.assert_called_once_with( TableName=TEST_TABLE_NAME, Key={ TEST_HASH_KEY: {"S": example_hash_value}, TEST_RANGE_KEY: {"S": example_range_value}, }, ExpressionAttributeNames={"#tenant_type": "tenant_type", "#roles": "roles"}, ExpressionAttributeValues={ ":tenant_type": {"S": "account"}, ":roles": {"L": [{"M": {"role": {"S": "settings_admin"}}}]}, }, UpdateExpression="SET #tenant_type = :tenant_type, #roles = :roles", ReturnValues="UPDATED_NEW", ) assert update_result == dynamodb_update_response["Attributes"] @patch("pdp.connectors.dynamo.session") @patch("pdp.connectors.dynamo.get_opts") def test_connector_update_item_missing_range_key( mock_opts: MagicMock, mock_boto3_session: MagicMock, mock_dynamo_client: MagicMock ) -> None: """Test connector update item with missing range key.""" mock_opts.return_value = {} mock_boto3_session.Session.return_value.client.return_value = mock_dynamo_client connector = DynamoDbConnector( table_name=TEST_TABLE_NAME, hash_key=TEST_HASH_KEY, range_key=TEST_RANGE_KEY ) connector.update_item(hash_key="yo", range_key=None, item={}) mock_boto3_session.Session.return_value.client.assert_called_once_with("dynamodb") mock_dynamo_client.update_item.assert_called_once_with( TableName=TEST_TABLE_NAME, Key={TEST_HASH_KEY: {"S": "yo"}}, ExpressionAttributeNames={}, ExpressionAttributeValues={}, UpdateExpression="SET ", ReturnValues="UPDATED_NEW", ) @patch("pdp.connectors.dynamo.session") @patch("pdp.connectors.dynamo.get_opts") def test_connector_update_item_unexpected_range_key( mock_opts: MagicMock, mock_boto3_session: MagicMock, mock_dynamo_client: MagicMock ) -> None: """Test connector update item with unexpected range key.""" mock_opts.return_value = {} mock_boto3_session.Session.return_value.client.return_value = mock_dynamo_client connector = DynamoDbConnector( table_name=TEST_TABLE_NAME, hash_key=TEST_HASH_KEY, range_key=None ) connector.update_item(hash_key="yo", range_key="hey", item={}) mock_boto3_session.Session.return_value.client.assert_called_once_with("dynamodb") mock_dynamo_client.update_item.assert_called_once_with( TableName=TEST_TABLE_NAME, Key={TEST_HASH_KEY: {"S": "yo"}}, ExpressionAttributeNames={}, ExpressionAttributeValues={}, UpdateExpression="SET ", ReturnValues="UPDATED_NEW", ) @patch("pdp.connectors.dynamo.session") @patch("pdp.connectors.dynamo.get_opts") def test_connector_update_item_missing_hash_key( mock_opts: MagicMock, mock_boto3_session: MagicMock, mock_dynamo_client: MagicMock ) -> None: """Test connector update item with missing hash key.""" mock_opts.return_value = {} mock_boto3_session.Session.return_value.client.return_value = mock_dynamo_client connector = DynamoDbConnector( table_name=TEST_TABLE_NAME, hash_key=TEST_HASH_KEY, range_key=TEST_RANGE_KEY ) connector.update_item(hash_key="", range_key="yo", item={}) mock_boto3_session.Session.return_value.client.assert_called_once_with("dynamodb") mock_dynamo_client.update_item.assert_called_once_with( TableName=TEST_TABLE_NAME, Key={TEST_HASH_KEY: {"S": ""}, TEST_RANGE_KEY: {"S": "yo"}}, ExpressionAttributeNames={}, ExpressionAttributeValues={}, UpdateExpression="SET ", ReturnValues="UPDATED_NEW", ) @patch.object(DynamoDbConnector, "_write_items") @patch("pdp.connectors.dynamo.session") async def test_connector_put_items( mock_boto3_session: MagicMock, mock_connector__write_items: AsyncMock, ) -> None: """Test connector put_items.""" mock_dynamo_client = MagicMock() mock_boto3_session.Session.return_value.client.return_value = mock_dynamo_client expected = [{"expected": "response"}] mock_connector__write_items.return_value = expected items = [{TEST_HASH_KEY: f"unprocessed {i}"} for i in range(3)] connector = DynamoDbConnector(table_name=TEST_TABLE_NAME, hash_key=TEST_HASH_KEY) actual = await connector.put_items(items) assert actual == expected mock_connector__write_items.assert_awaited_once_with(items, is_delete=False) @patch.object(DynamoDbConnector, "_write_items") @patch("pdp.connectors.dynamo.session") async def test_connector_delete_items( mock_boto3_session: MagicMock, mock_connector__write_items: AsyncMock, ) -> None: """Test connector delete_items.""" mock_dynamo_client = MagicMock() mock_boto3_session.Session.return_value.client.return_value = mock_dynamo_client expected = [{"expected": "response"}] mock_connector__write_items.return_value = expected items = [{TEST_HASH_KEY: f"unprocessed {i}"} for i in range(3)] connector = DynamoDbConnector(table_name=TEST_TABLE_NAME, hash_key=TEST_HASH_KEY) actual = await connector.delete_items(items) assert actual == expected mock_connector__write_items.assert_awaited_once_with(items, is_delete=True) @patch("pdp.connectors.dynamo.batch_write_item") @patch("pdp.connectors.dynamo.session") async def test_connector__write_items_is_delete( mock_boto3_session: MagicMock, mock_batch_write_item: AsyncMock, ) -> None: """Test _write_items when is_delete=True.""" mock_dynamo_client = MagicMock() mock_boto3_session.Session.return_value.client.return_value = mock_dynamo_client mock_batch_write_item.side_effect = [ { TEST_TABLE_NAME: [ {"DeleteRequest": {"Key": {TEST_HASH_KEY: {"S": "unprocessed 1"}}}} ] }, {}, ] connector = DynamoDbConnector(table_name=TEST_TABLE_NAME, hash_key=TEST_HASH_KEY) unprocessed_keys = await connector._write_items( [{TEST_HASH_KEY: f"unprocessed {i}"} for i in range(27)], is_delete=True, ) assert unprocessed_keys == [{TEST_HASH_KEY: "unprocessed 1"}] assert mock_batch_write_item.call_count == 2 mock_batch_write_item.assert_has_calls( [ call( mock_dynamo_client, { TEST_TABLE_NAME: [ { "DeleteRequest": { "Key": {TEST_HASH_KEY: {"S": f"unprocessed {i}"}} } } for i in range(25) ] }, ), call( mock_dynamo_client, { TEST_TABLE_NAME: [ { "DeleteRequest": { "Key": {TEST_HASH_KEY: {"S": "unprocessed 25"}} } }, { "DeleteRequest": { "Key": {TEST_HASH_KEY: {"S": "unprocessed 26"}} } }, ] }, ), ] ) @patch("pdp.connectors.dynamo.batch_write_item") @patch("pdp.connectors.dynamo.session") async def test_connector__write_items_is_delete_exactly_one( mock_boto3_session: MagicMock, mock_batch_write_item: AsyncMock, ) -> None: """Test _write_items when is_delete=True and one item is successfully processed.""" mock_dynamo_client = MagicMock() mock_boto3_session.Session.return_value.client.return_value = mock_dynamo_client mock_batch_write_item.side_effect = [ {}, ] connector = DynamoDbConnector(table_name=TEST_TABLE_NAME, hash_key=TEST_HASH_KEY) unprocessed_keys = await connector._write_items( [{TEST_HASH_KEY: "unprocessed 1"}], is_delete=True, ) assert unprocessed_keys == [] assert mock_batch_write_item.call_count == 1 mock_batch_write_item.assert_has_calls( [ call( mock_dynamo_client, { TEST_TABLE_NAME: [ { "DeleteRequest": { "Key": {TEST_HASH_KEY: {"S": "unprocessed 1"}} } } ] }, ), ] ) @pytest.mark.parametrize("is_delete", [(True), (False)]) @patch("pdp.connectors.dynamo.batch_write_item") @patch("pdp.connectors.dynamo.session") async def test_connector__write_items_empty_list_of_keys( mock_boto3_session: MagicMock, mock_batch_write_item: AsyncMock, is_delete: bool, ) -> None: """Test _write_items when empty list of keys.""" connector = DynamoDbConnector(table_name=TEST_TABLE_NAME, hash_key=TEST_HASH_KEY) unprocessed_keys = await connector._write_items([], is_delete=is_delete) assert unprocessed_keys == [] mock_batch_write_item.assert_not_called() @patch("pdp.connectors.dynamo.batch_write_item") @patch("pdp.connectors.dynamo.session") async def test_connector__write_items_is_not_delete( mock_boto3_session: MagicMock, mock_batch_write_item: AsyncMock, ) -> None: """Test _write_items when is_delete=False.""" mock_dynamo_client = MagicMock() mock_boto3_session.Session.return_value.client.return_value = mock_dynamo_client mock_batch_write_item.side_effect = [ { TEST_TABLE_NAME: [ { "PutRequest": { "Item": { TEST_HASH_KEY: {"S": "unprocessed 1"}, "some": {"N": "2"}, } } } ] }, {}, ] connector = DynamoDbConnector(table_name=TEST_TABLE_NAME, hash_key=TEST_HASH_KEY) unprocessed_keys = await connector._write_items( [{TEST_HASH_KEY: f"unprocessed {i}", "some": i * 2} for i in range(27)], is_delete=False, ) assert unprocessed_keys == [{TEST_HASH_KEY: "unprocessed 1", "some": 2}] assert mock_batch_write_item.call_count == 2 mock_batch_write_item.assert_has_calls( [ call( mock_dynamo_client, { TEST_TABLE_NAME: [ { "PutRequest": { "Item": { TEST_HASH_KEY: {"S": f"unprocessed {i}"}, "some": {"N": f"{i * 2}"}, } } } for i in range(25) ] }, ), call( mock_dynamo_client, { TEST_TABLE_NAME: [ { "PutRequest": { "Item": { TEST_HASH_KEY: {"S": "unprocessed 25"}, "some": {"N": "50"}, } } }, { "PutRequest": { "Item": { TEST_HASH_KEY: {"S": "unprocessed 26"}, "some": {"N": "52"}, } } }, ] }, ), ] ) @patch("pdp.connectors.dynamo.batch_write_item") @patch("pdp.connectors.dynamo.session") async def test_connector__write_items_is_not_delete_exactly_one( mock_boto3_session: MagicMock, mock_batch_write_item: AsyncMock, ) -> None: """Test _write_items when is_delete=False and one item is successfully processed.""" mock_dynamo_client = MagicMock() mock_boto3_session.Session.return_value.client.return_value = mock_dynamo_client mock_batch_write_item.side_effect = [ {}, ] connector = DynamoDbConnector(table_name=TEST_TABLE_NAME, hash_key=TEST_HASH_KEY) unprocessed_items = await connector._write_items( [ { TEST_HASH_KEY: "unprocessed 1", "other": "attribute", } ], is_delete=False, ) assert unprocessed_items == [] assert mock_batch_write_item.call_count == 1 mock_batch_write_item.assert_has_calls( [ call( mock_dynamo_client, { TEST_TABLE_NAME: [ { "PutRequest": { "Item": { TEST_HASH_KEY: {"S": "unprocessed 1"}, "other": {"S": "attribute"}, } } } ] }, ), ] ) @patch("pdp.connectors.dynamo.session") @pytest.mark.parametrize( "items, is_delete, expected", [ pytest.param( [], True, {TEST_TABLE_NAME: []}, id="Empty list returns a list of empty delete requests.", ), pytest.param( [], False, {TEST_TABLE_NAME: []}, id="Empty list returns a list of empty put requests.", ), pytest.param( [{TEST_HASH_KEY: "one"}], True, { TEST_TABLE_NAME: [ {"DeleteRequest": {"Key": {TEST_HASH_KEY: {"S": "one"}}}} ] }, id="List containing one key returns a list of one delete request", ), pytest.param( [{TEST_HASH_KEY: "one", "some": "attribute"}], False, { TEST_TABLE_NAME: [ { "PutRequest": { "Item": { TEST_HASH_KEY: {"S": "one"}, "some": {"S": "attribute"}, } } } ] }, id="List containing one key returns a list of one put request", ), pytest.param( [{TEST_HASH_KEY: "one"}, {TEST_HASH_KEY: "two"}], True, { TEST_TABLE_NAME: [ {"DeleteRequest": {"Key": {TEST_HASH_KEY: {"S": "one"}}}}, {"DeleteRequest": {"Key": {TEST_HASH_KEY: {"S": "two"}}}}, ] }, id="List containing two keys returns a list of two delete requests", ), pytest.param( [{TEST_HASH_KEY: "one", "some": 1}, {TEST_HASH_KEY: "two", "some": "2"}], False, { TEST_TABLE_NAME: [ { "PutRequest": { "Item": { TEST_HASH_KEY: {"S": "one"}, "some": {"N": "1"}, } } }, { "PutRequest": { "Item": { TEST_HASH_KEY: {"S": "two"}, "some": {"S": "2"}, } } }, ] }, id="List containing two keys returns a list of two put requests", ), pytest.param( [ {TEST_HASH_KEY: "one"}, { TEST_HASH_KEY: "two", TEST_RANGE_KEY: UUID("985e9faa-1bd7-4d68-929e-298cb4c77e03"), }, ], True, { TEST_TABLE_NAME: [ {"DeleteRequest": {"Key": {TEST_HASH_KEY: {"S": "one"}}}}, { "DeleteRequest": { "Key": { TEST_HASH_KEY: {"S": "two"}, TEST_RANGE_KEY: { "S": "985e9faa-1bd7-4d68-929e-298cb4c77e03" }, } } }, ] }, id="Indifferent to receiving a mix of primary keys for delete request", ), pytest.param( [ {TEST_HASH_KEY: "one", "some": 1}, { TEST_HASH_KEY: "two", TEST_RANGE_KEY: UUID("985e9faa-1bd7-4d68-929e-298cb4c77e03"), "other": "thing", }, ], False, { TEST_TABLE_NAME: [ { "PutRequest": { "Item": { TEST_HASH_KEY: {"S": "one"}, "some": {"N": "1"}, } } }, { "PutRequest": { "Item": { TEST_HASH_KEY: {"S": "two"}, TEST_RANGE_KEY: { "S": "985e9faa-1bd7-4d68-929e-298cb4c77e03" }, "other": {"S": "thing"}, } } }, ] }, id="Indifferent to receiving a mix of items for put request", ), ], ) def test__transform_items_for_batch_write_item_requests( mock_boto3: MagicMock, items: List[Dict[str, Any]], is_delete: bool, expected: Dict[str, List[Dict[str, Any]]], ) -> None: """Test _transform_items_for_batch_write_item_requests.""" connector = DynamoDbConnector(table_name=TEST_TABLE_NAME, hash_key=TEST_HASH_KEY) actual = connector._transform_items_for_batch_write_item_requests(items, is_delete) assert actual == expected @patch("pdp.connectors.dynamo.session") @pytest.mark.parametrize( "request_items, is_delete, expected", [ pytest.param( [], True, [], id="Empty delete request items returns an empty list.", ), pytest.param( [], False, [], id="Empty put request items returns an empty list.", ), pytest.param( [{"DeleteRequest": {"Key": {TEST_HASH_KEY: {"S": "one"}}}}], True, [{TEST_HASH_KEY: "one"}], id="One unprocessed delete request yields one key", ), pytest.param( [ { "PutRequest": { "Item": { TEST_HASH_KEY: {"S": "one"}, "some": {"S": "attribute"}, } } } ], False, [{TEST_HASH_KEY: "one", "some": "attribute"}], id="One unprocessed put request yields one item", ), pytest.param( [ {"DeleteRequest": {"Key": {TEST_HASH_KEY: {"S": "one"}}}}, {"DeleteRequest": {"Key": {TEST_HASH_KEY: {"S": "two"}}}}, ], True, [{TEST_HASH_KEY: "one"}, {TEST_HASH_KEY: "two"}], id="Two unprocessed delete requests yields two keys", ), pytest.param( [ { "PutRequest": { "Item": { TEST_HASH_KEY: {"S": "one"}, "some": {"N": "1"}, } } }, { "PutRequest": { "Item": { TEST_HASH_KEY: {"S": "two"}, "some": {"S": "2"}, } } }, ], False, [{TEST_HASH_KEY: "one", "some": 1}, {TEST_HASH_KEY: "two", "some": "2"}], id="Two unprocessed put requests yields two items", ), pytest.param( [ {"DeleteRequest": {"Key": {TEST_HASH_KEY: {"S": "one"}}}}, { "DeleteRequest": { "Key": { TEST_HASH_KEY: {"S": "two"}, TEST_RANGE_KEY: { "S": "985e9faa-1bd7-4d68-929e-298cb4c77e03" }, } } }, ], True, [ {TEST_HASH_KEY: "one"}, { TEST_HASH_KEY: "two", TEST_RANGE_KEY: "985e9faa-1bd7-4d68-929e-298cb4c77e03", }, ], id="Indifferent to receiving a mix of primary keys in delete requests", ), pytest.param( [ { "PutRequest": { "Item": { TEST_HASH_KEY: {"S": "one"}, "some": {"N": "1"}, } } }, { "PutRequest": { "Item": { TEST_HASH_KEY: {"S": "two"}, TEST_RANGE_KEY: { "S": "985e9faa-1bd7-4d68-929e-298cb4c77e03" }, "other": {"S": "thing"}, } } }, ], False, [ {TEST_HASH_KEY: "one", "some": 1}, { TEST_HASH_KEY: "two", TEST_RANGE_KEY: "985e9faa-1bd7-4d68-929e-298cb4c77e03", "other": "thing", }, ], id="Indifferent to receiving a mix of put requests", ), ], ) def test__transform_batch_write_item_requests_to_items( mock_boto3: MagicMock, request_items: List[Dict[str, Any]], is_delete: bool, expected: List[Dict[str, Any]], ) -> None: """Test _transform_batch_write_item_requests_to_items.""" connector = DynamoDbConnector(table_name=TEST_TABLE_NAME, hash_key=TEST_HASH_KEY) actual = connector._transform_batch_write_item_requests_to_items( request_items, is_delete, ) assert actual == expected