"""Worker test module.""" import io import json import uuid from typing import Any from unittest.mock import MagicMock, call, patch import pytest from botocore.exceptions import ClientError from src import app @patch("src.app.OwsLoggingAdapter") @patch("src.app.boto3.resource") @patch("src.app.get_asset_locations") @patch("src.app.copy_assets") def test_main_success( copy_assets_mock: MagicMock, get_asset_locations_mock: MagicMock, boto3_resource_mock: MagicMock, ows_logging_adapter_mock: MagicMock, error_report_mock_some_failures: list[dict[str, str]], asset_locations_mock: list[dict[str, str]], ) -> None: """Test main function.""" copy_assets_mock.return_value = error_report_mock_some_failures get_asset_locations_mock.return_value = asset_locations_mock logger_mock = MagicMock() ows_logging_adapter_mock.return_value = logger_mock mock_s3_client = MagicMock() boto3_resource_mock.return_value = mock_s3_client # Call main() app.main(uuid.uuid4()) get_asset_locations_mock.assert_called_once_with(mock_s3_client, logger_mock) copy_assets_mock.assert_called_once_with( mock_s3_client, asset_locations_mock, logger_mock ) boto3_resource_mock.assert_called_once_with("s3") @patch("src.app.OwsLoggingAdapter") @patch("src.app.boto3.resource") @patch("src.app.get_asset_locations") @patch("src.app.copy_assets") def test_main_error_report_exception( copy_assets_mock: MagicMock, get_asset_locations_mock: MagicMock, boto3_resource_mock: MagicMock, ows_logging_adapter_mock: MagicMock, error_report_mock_some_failures: list[dict[str, str]], asset_locations_mock: list[dict[str, str]], ) -> None: """Test main function.""" copy_assets_mock.return_value = error_report_mock_some_failures get_asset_locations_mock.return_value = asset_locations_mock logger_mock = MagicMock() exception_mock = MagicMock() logger_mock.exception = exception_mock ows_logging_adapter_mock.return_value = logger_mock mock_s3_client = MagicMock() s3_object_mock = MagicMock() client_error = ClientError( error_response={"Error": {"Code": "AccessDenied", "Message": "Access denied"}}, operation_name="Object.put", ) mock_s3_client.Object.return_value = s3_object_mock s3_object_mock.put.side_effect = client_error boto3_resource_mock.return_value = mock_s3_client # Call main() app.main(uuid.uuid4()) get_asset_locations_mock.assert_called_once_with(mock_s3_client, logger_mock) copy_assets_mock.assert_called_once_with( mock_s3_client, asset_locations_mock, logger_mock ) boto3_resource_mock.assert_called_once_with("s3") exception_mock.assert_called_once_with( "There were errors while copying assets for export test-export-123, " "but the error report for part 1 failed to write to S3.", stack_info=True, ) def test_get_asset_locations( asset_locations_object_mock: Any, asset_locations_mock: list[dict[str, str]], ) -> None: mock_body = io.BytesIO(json.dumps(asset_locations_object_mock).encode("utf-8")) mock_s3_object = MagicMock() mock_s3_object.get.return_value = { "Body": mock_body, "ContentType": "application/json", } mock_s3_resource = MagicMock() mock_s3_resource.Object.return_value = mock_s3_object logger_mock = MagicMock() result = app.get_asset_locations(mock_s3_resource, logger_mock) assert result == asset_locations_mock @pytest.mark.parametrize( "asset_locations_mock_name", [ "empty_asset_locations_object_mock", "no_key_asset_locations_object_mock", ], ) def test_get_asset_locations_empty( asset_locations_mock_name: Any, asset_locations_dict: dict[str, Any], ) -> None: asset_location_mock = asset_locations_dict[asset_locations_mock_name] mock_body = io.BytesIO(json.dumps(asset_location_mock).encode("utf-8")) mock_s3_object = MagicMock() mock_s3_object.get.return_value = { "Body": mock_body, "ContentType": "application/json", } mock_s3_resource = MagicMock() mock_s3_resource.Object.return_value = mock_s3_object logger_mock = MagicMock() error_mock = MagicMock() logger_mock.error = error_mock result = app.get_asset_locations(mock_s3_resource, logger_mock) error_mock.assert_called_once_with( "No asset locations found for export: test-export-123 " "- execution name: test-execution-1 - part: 1" ) assert result == [] def test_copy_assets_success(asset_locations_mock: list[dict[str, str]]) -> None: mock_client = MagicMock() # Specify different responses for all 11 mock asset locations mock_client.copy.side_effect = [ { "CopyObjectResult": { "ETag": '"etag1"', "LastModified": "2024-05-15T12:00:00Z", } }, { "CopyObjectResult": { "ETag": '"etag2"', "LastModified": "2024-05-15T12:01:00Z", } }, { "CopyObjectResult": { "ETag": '"etag3"', "LastModified": "2024-05-15T12:02:00Z", } }, { "CopyObjectResult": { "ETag": '"etag4"', "LastModified": "2024-05-15T12:03:00Z", } }, { "CopyObjectResult": { "ETag": '"etag5"', "LastModified": "2024-05-15T12:04:00Z", } }, { "CopyObjectResult": { "ETag": '"etag6"', "LastModified": "2024-05-15T12:05:00Z", } }, { "CopyObjectResult": { "ETag": '"etag7"', "LastModified": "2024-05-15T12:06:00Z", } }, { "CopyObjectResult": { "ETag": '"etag8"', "LastModified": "2024-05-15T12:07:00Z", } }, { "CopyObjectResult": { "ETag": '"etag9"', "LastModified": "2024-05-15T12:08:00Z", } }, { "CopyObjectResult": { "ETag": '"etag10"', "LastModified": "2024-05-15T12:09:00Z", } }, { "CopyObjectResult": { "ETag": '"etag11"', "LastModified": "2024-05-15T12:10:00Z", } }, ] mock_s3_resource = MagicMock() mock_s3_resource.meta.client = mock_client logging_mock = MagicMock() result = app.copy_assets(mock_s3_resource, asset_locations_mock, logging_mock) assert mock_client.copy.call_args_list == [ call( CopySource={ "Bucket": "source_bucket", "Key": "12345.wav", }, Bucket="test-output-bucket", Key="test-export-123/123456789/123456789_1_1.wav", ), call( CopySource={ "Bucket": "source_bucket", "Key": "123456.wav", }, Bucket="test-output-bucket", Key="test-export-123/123456789/123456789_1_2.wav", ), call( CopySource={ "Bucket": "source_bucket", "Key": "1234567.wav", }, Bucket="test-output-bucket", Key="test-export-123/123456789/123456789_1_3.wav", ), call( CopySource={ "Bucket": "source_video_bucket", "Key": "video_master/12345678.mov", }, Bucket="test-output-bucket", Key="test-export-123/123456789/123456789_1_4.mov", ), call( CopySource={ "Bucket": "source_bucket", "Key": "images/tif/123456789.tif", }, Bucket="test-output-bucket", Key="test-export-123/123456789/123456789.tif", ), call( CopySource={ "Bucket": "source_bucket", "Key": "87654.wav", }, Bucket="test-output-bucket", Key="test-export-123/987654321/987654321_1_1.wav", ), call( CopySource={ "Bucket": "source_bucket", "Key": "876543.wav", }, Bucket="test-output-bucket", Key="test-export-123/987654321/987654321_1_2.wav", ), call( CopySource={ "Bucket": "source_bucket", "Key": "8765432.wav", }, Bucket="test-output-bucket", Key="test-export-123/987654321/987654321_1_3.wav", ), call( CopySource={ "Bucket": "source_bucket", "Key": "87654321.wav", }, Bucket="test-output-bucket", Key="test-export-123/987654321/987654321_2_1.wav", ), call( CopySource={ "Bucket": "source_bucket", "Key": "876543210.wav", }, Bucket="test-output-bucket", Key="test-export-123/987654321/987654321_2_2.wav", ), call( CopySource={ "Bucket": "source_bucket", "Key": "images/tif/8765432110.tif", }, Bucket="test-output-bucket", Key="test-export-123/987654321/987654321.tif", ), ] assert result == [] def test_copy_assets_some_failure( asset_locations_mock: list[dict[str, str]], error_report_mock_some_failures: list[dict[str, str]], ) -> None: mock_client = MagicMock() # Specify different responses for all 11 mock asset locations client_error = ClientError( error_response={"Error": {"Code": "AccessDenied", "Message": "Access denied"}}, operation_name="CopyObject", ) mock_side_effect = [ { "CopyObjectResult": { "ETag": '"etag1"', "LastModified": "2024-05-15T12:00:00Z", } }, client_error, { "CopyObjectResult": { "ETag": '"etag3"', "LastModified": "2024-05-15T12:00:00Z", } }, { "CopyObjectResult": { "ETag": '"etag4"', "LastModified": "2024-05-15T12:00:00Z", } }, { "CopyObjectResult": { "ETag": '"etag5"', "LastModified": "2024-05-15T12:00:00Z", } }, { "CopyObjectResult": { "ETag": '"etag6"', "LastModified": "2024-05-15T12:00:00Z", } }, { "CopyObjectResult": { "ETag": '"etag7"', "LastModified": "2024-05-15T12:00:00Z", } }, { "CopyObjectResult": { "ETag": '"etag8"', "LastModified": "2024-05-15T12:00:00Z", } }, client_error, client_error, { "CopyObjectResult": { "ETag": '"etag11"', "LastModified": "2024-05-15T12:00:00Z", } }, ] mock_client.copy.side_effect = mock_side_effect mock_s3_resource = MagicMock() mock_s3_resource.meta.client = mock_client logging_mock = MagicMock() result = app.copy_assets(mock_s3_resource, asset_locations_mock, logging_mock) assert result == error_report_mock_some_failures def test_copy_assets_total_failure( asset_locations_mock: list[dict[str, str]], error_report_mock_total_failures: list[dict[str, str]], ) -> None: mock_client = MagicMock() client_error = ClientError( error_response={"Error": {"Code": "AccessDenied", "Message": "Access denied"}}, operation_name="CopyObject", ) mock_client.copy.side_effect = client_error mock_s3_resource = MagicMock() mock_s3_resource.meta.client = mock_client logging_mock = MagicMock() result = app.copy_assets(mock_s3_resource, asset_locations_mock, logging_mock) assert result == error_report_mock_total_failures