"""Test base packager behavior.""" from unittest.mock import AsyncMock from unittest.mock import MagicMock from unittest.mock import call import pytest import requests import src.exceptions from src.packagers.base import AbstractPackager @pytest.mark.asyncio async def test_package(): """Test process function.""" stereo_metadata = {'productId': 'stereo-product-id'} spatial_metadata = {'productId': 'spatial-product-id'} spatial_audio_objects = [('aaa', 'spatial-upc/bbb')] spatial_audio_details = [{'md5': 'aaa', 'filesize': 123}] spatial_artwork_details = ('md5', 100, 200) media_metadata = [{'codec': 'xyz'}] # allow for abstract class to be instantiated AbstractPackager.__abstractmethods__ = frozenset() # mock out all the functions used in package packager = AbstractPackager() packager.verify_output_dir_empty = AsyncMock(return_value=None) packager.get_product_metadata = MagicMock(side_effect=[ stereo_metadata, spatial_metadata ]) packager.get_spatial_audio_objects = \ AsyncMock(return_value=spatial_audio_objects) packager.validate_setup = MagicMock() packager.get_media_metadata = AsyncMock(return_value=media_metadata) packager.validate_spatial_audio_metadata = MagicMock() packager.get_xml = MagicMock(return_value='stereo-xml') packager.download_artwork = AsyncMock(return_value='stereo-artwork-file') packager.get_objects_details = \ AsyncMock(return_value=spatial_audio_details) packager.transform_image = MagicMock(return_value='spatial-artwork-file') packager.get_image_details = \ MagicMock(return_value=spatial_artwork_details) packager.transform_xml = MagicMock(return_value='spatial-xml') packager.commit_package = AsyncMock() # run package await packager.package('stereo-upc', 'spatial-upc', 'job-id') # assert that data passing is done as expected between functions packager.get_product_metadata.assert_has_calls([ call('stereo-upc'), call('spatial-upc') ]) packager.get_spatial_audio_objects.assert_called_with('spatial-upc') packager.validate_setup.assert_called_with( stereo_metadata, spatial_metadata, spatial_audio_objects, ) packager.get_media_metadata.assert_called_with(spatial_audio_objects) packager.validate_spatial_audio_metadata.assert_called_with(media_metadata) packager.get_xml.assert_called_with('stereo-upc', 'spatial-upc') packager.download_artwork.assert_called_with('stereo-product-id') packager.get_objects_details.assert_called_with(spatial_audio_objects) packager.transform_image.assert_called_with('stereo-artwork-file') packager.transform_xml.assert_called_with( 'stereo-xml', stereo_metadata, spatial_metadata, spatial_audio_details, spatial_artwork_details ) packager.commit_package.assert_called_with( 'spatial-upc', 'stereo-upc', spatial_audio_objects, 'spatial-xml', 'spatial-artwork-file', 'job-id' ) @pytest.mark.parametrize( 'stereo_metadata, spatial_metadata, spatial_audio_objects, exception', [ ( { 'productId': 12345, 'upc': '56789101112', 'notForDistribution': 'N', 'tracks': [ { 'volumeNumber': 1, 'trackNumber': 1, 'isrc': 'STEREO56789' }, { 'volumeNumber': 1, 'trackNumber': 2, 'isrc': 'STEREO123479' }, { 'volumeNumber': 1, 'trackNumber': 3, 'isrc': 'STEREO5678453' }, { 'volumeNumber': 1, 'trackNumber': 4, 'isrc': 'STEREO5678453' } ], 'deliveryHistory': [ { 'store': { 'storeId': 1 } } ] }, { 'productId': 23456, 'upc': '123456789', 'notForDistribution': 'N', 'tracks': [ { 'volumeNumber': 1, 'trackNumber': 1, 'isrc': 'DOLBYATMOS56789' }, { 'volumeNumber': 1, 'trackNumber': 2, 'isrc': 'DOLBYATMOS56788' } ], 'deliveryHistory': [ {} ] }, [('packager-bucket', 'xx/123456789_1_3_DOLBYATMOS56789.wav'), ('packager-bucket', 'xx/123456789_1_2_DOLBYATMOS56788.wav')], src.exceptions.InvalidNotForDistributionFlag ), ( { 'productId': 12345, 'upc': '56789101112', 'notForDistribution': 'N', 'tracks': [ { 'volumeNumber': 1, 'trackNumber': 1, 'isrc': 'STEREO56789' } ], 'deliveryHistory': [ { 'store': { 'storeId': 1 } } ] }, { 'productId': 23456, 'upc': '123456789', 'notForDistribution': 'NotforFurtherDistribution', 'tracks': [ { 'volumeNumber': 1, 'trackNumber': 1, 'isrc': 'DOLBYATMOS56789' }, { 'volumeNumber': 1, 'trackNumber': 2, 'isrc': 'DOLBYATMOS56788' } ], 'deliveryHistory': [ {} ] }, [('packager-bucket', 'xx/123456789_1_1_DOLBYATMOS56789.wav'), ('packager-bucket', 'xx/123456789_1_1_DOLBYATMOS56788.wav')], src.exceptions.InvalidNumberOfSpatialTracks ), ( { 'productId': 12345, 'upc': '56789101112', 'notForDistribution': 'N', 'tracks': [ { 'volumeNumber': 1, 'trackNumber': 1, 'isrc': 'STEREO56789' }, { 'volumeNumber': 1, 'trackNumber': 2, 'isrc': 'STEREO123479' } ], 'deliveryHistory': [ { 'store': { 'storeId': 1 } } ] }, { 'productId': 23456, 'upc': '123456789', 'notForDistribution': 'NotforFurtherDistribution', 'tracks': [ { 'volumeNumber': 1, 'trackNumber': 1, 'isrc': 'DOLBYATMOS56789' }, { 'volumeNumber': 1, 'trackNumber': 2, 'isrc': 'DOLBYATMOS56788' } ], 'deliveryHistory': [ {} ] }, [('packager-bucket', 'xx/123456789_1_1_DOLBYATMOS56789.wav')], src.exceptions.InvalidNumberOfSpatialFiles ), ( { 'productId': 12345, 'upc': '56789101112', 'notForDistribution': 'N', 'tracks': [ { 'volumeNumber': 1, 'trackNumber': 1, 'isrc': 'STEREO56789' }, { 'volumeNumber': 1, 'trackNumber': 2, 'isrc': 'STEREO123479' }, { 'volumeNumber': 1, 'trackNumber': 3, 'isrc': 'STEREO5678453' }, { 'volumeNumber': 1, 'trackNumber': 4, 'isrc': 'STEREO5678453' } ], 'deliveryHistory': [ { 'store': { 'storeId': 3 } }, { 'store': { 'storeId': 6 } } ] }, { 'productId': 23456, 'upc': '123456789', 'notForDistribution': 'NotforFurtherDistribution', 'tracks': [ { 'volumeNumber': 1, 'trackNumber': 1, 'isrc': 'DOLBYATMOS56789' }, { 'volumeNumber': 1, 'trackNumber': 2, 'isrc': 'DOLBYATMOS56788' } ], 'deliveryHistory': [ {} ] }, [('packager-bucket', 'xx/123456789_1_3_DOLBYATMOS56789.wav'), ('packager-bucket', 'xx/123456789_1_2_DOLBYATMOS56788.wav')], src.exceptions.StereoProductNotDeliveredToDMSError ), ( { 'productId': 12345, 'upc': '56789101112', 'notForDistribution': 'N', 'tracks': [ { 'volumeNumber': 1, 'trackNumber': 1, 'isrc': 'STEREO56789' }, { 'volumeNumber': 1, 'trackNumber': 2, 'isrc': 'STEREO123479' }, { 'volumeNumber': 1, 'trackNumber': 3, 'isrc': 'STEREO5678453' }, { 'volumeNumber': 2, 'trackNumber': 4, 'isrc': 'STEREO5678453' } ], 'deliveryHistory': [ { 'store': { 'storeId': 1 } } ] }, { 'productId': 23456, 'upc': '123456789', 'notForDistribution': 'NotforFurtherDistribution', 'tracks': [ { 'volumeNumber': 1, 'trackNumber': 1, 'isrc': 'DOLBYATMOS56789' }, { 'volumeNumber': 1, 'trackNumber': 2, 'isrc': 'DOLBYATMOS56784' } ], 'deliveryHistory': [ {} ] }, [('packager-bucket', 'xx/123456789_1_3_DOLBYATMOS56789.wav'), ('packager-bucket', 'xx/123456789_1_4_DOLBYATMOS56788.wav')], src.exceptions.NoStereoMatchForSpatialTrackVolumeIndex ), ( { 'productId': 12345, 'upc': '56789101112', 'notForDistribution': 'N', 'tracks': [ { 'volumeNumber': 1, 'trackNumber': 1, 'isrc': 'STEREO56789' }, { 'volumeNumber': 1, 'trackNumber': 2, 'isrc': 'STEREO123479' }, { 'volumeNumber': 1, 'trackNumber': 3, 'isrc': 'STEREO5678453' }, { 'volumeNumber': 1, 'trackNumber': 4, 'isrc': 'STEREO5678453' } ], 'deliveryHistory': [ { 'store': { 'storeId': 1 } } ] }, { 'productId': 23456, 'upc': '123456789', 'notForDistribution': 'NotforFurtherDistribution', 'tracks': [ { 'volumeNumber': 1, 'trackNumber': 1, 'isrc': 'DOLBYATMOS56789' }, { 'volumeNumber': 1, 'trackNumber': 2, 'isrc': 'DOLBYATMOS56788' } ], 'deliveryHistory': [ {} ] }, [('packager-bucket', 'xx/123456789_1_3_DOLBYATMOS56789.wav'), ('packager-bucket', 'xx/123456789_4_DOLBYATMOS56788.wav')], src.exceptions.InvalidSpatialFilename ), ( { 'productId': 12345, 'upc': '56789101112', 'notForDistribution': 'N', 'tracks': [ { 'volumeNumber': 1, 'trackNumber': 1, 'isrc': 'STEREO56789' }, { 'volumeNumber': 1, 'trackNumber': 2, 'isrc': 'STEREO123479' }, { 'volumeNumber': 1, 'trackNumber': 3, 'isrc': 'STEREO5678453' }, { 'volumeNumber': 1, 'trackNumber': 4, 'isrc': 'STEREO5678453' } ], 'deliveryHistory': [ { 'store': { 'storeId': 1 } } ] }, { 'productId': 23456, 'upc': '123456789', 'notForDistribution': 'NotforFurtherDistribution', 'tracks': [ { 'volumeNumber': 1, 'trackNumber': 1, 'isrc': 'DOLBYATMOS56784' }, { 'volumeNumber': 1, 'trackNumber': 2, 'isrc': 'DOLBYATMOS56788' } ], 'deliveryHistory': [ {} ] }, [('packager-bucket', 'xx/123456789_1_3_DOLBYATMOS56788.wav'), ('packager-bucket', 'xx/123456789_1_4_DOLBYATMOS56788.wav')], src.exceptions.DuplicateSpatialTrackIsrcError ), ( { 'productId': 12345, 'upc': '56789101112', 'notForDistribution': 'N', 'tracks': [ { 'volumeNumber': 1, 'trackNumber': 1, 'isrc': 'STEREO56789' }, { 'volumeNumber': 1, 'trackNumber': 2, 'isrc': 'STEREO123479' }, { 'volumeNumber': 1, 'trackNumber': 3, 'isrc': 'STEREO5678453' }, { 'volumeNumber': 1, 'trackNumber': 4, 'isrc': 'STEREO5678453' } ], 'deliveryHistory': [ { 'store': { 'storeId': 1 } } ] }, { 'productId': 23456, 'upc': '123456789', 'notForDistribution': 'NotforFurtherDistribution', 'tracks': [ { 'volumeNumber': 1, 'trackNumber': 1, 'isrc': 'DOLBYATMOS56789' }, { 'volumeNumber': 1, 'trackNumber': 2, 'isrc': 'DOLBYATMOS56788' } ], 'deliveryHistory': [ {} ] }, [('packager-bucket', 'xx/123456789_1_3_DOLBYATMOS56789.wav'), ('packager-bucket', 'xx/123456789_1_4_STEREO5678453.wav')], src.exceptions.StereoIsrcMatchesSpatialIsrcError ), ( { 'productId': 12345, 'upc': '56789101112', 'notForDistribution': 'N', 'tracks': [ { 'volumeNumber': 1, 'trackNumber': 1, 'isrc': 'STEREO56789' }, { 'volumeNumber': 1, 'trackNumber': 2, 'isrc': 'STEREO123479' }, { 'volumeNumber': 1, 'trackNumber': 3, 'isrc': 'STEREO5678453' }, { 'volumeNumber': 1, 'trackNumber': 4, 'isrc': 'STEREO5678453' } ], 'deliveryHistory': [ { 'store': { 'storeId': 1 } } ] }, { 'productId': 23456, 'upc': '123456789', 'notForDistribution': 'NotforFurtherDistribution', 'tracks': [ { 'volumeNumber': 1, 'trackNumber': 1, 'isrc': 'DOLBYATMOS56789' }, { 'volumeNumber': 1, 'trackNumber': 2, 'isrc': 'DOLBYATMOS56788' } ], 'deliveryHistory': [ {} ] }, [('packager-bucket', 'xx/123456789_1_1_DOLBYATMOS56789.wav'), ('packager-bucket', 'xx/123456789_1_4_DOLBYATMOS56788.wav')], None ), ( { 'productId': 12345, 'upc': '56789101112', 'notForDistribution': 'N', 'tracks': [ { 'volumeNumber': 1, 'trackNumber': 1, 'isrc': 'STEREO56789' }, { 'volumeNumber': 1, 'trackNumber': 2, 'isrc': 'STEREO123479' }, { 'volumeNumber': 1, 'trackNumber': 3, 'isrc': 'STEREO5678453' }, { 'volumeNumber': 1, 'trackNumber': 4, 'isrc': 'STEREO5678453' } ], 'deliveryHistory': [ { 'store': { 'storeId': 1 } } ] }, { 'productId': 23456, 'upc': '123456789', 'notForDistribution': 'NotforFurtherDistribution', 'tracks': [ { 'volumeNumber': 1, 'trackNumber': 1, 'isrc': 'DOLBYATMOS56789' }, { 'volumeNumber': 1, 'trackNumber': 2, 'isrc': 'DOLBYATMOS56788' } ], 'deliveryHistory': [ {} ] }, [('packager-bucket', 'xx/123456789_1_1_DOLBYATMOS56789.wav'), ('packager-bucket', 'xx/12345678_1_4_DOLBYATMOS56788.wav')], src.exceptions.InvalidSpatialFileUpc ), ( { 'productId': 12345, 'upc': '56789101112', 'notForDistribution': 'N', 'tracks': [ { 'volumeNumber': 1, 'trackNumber': 1, 'isrc': 'STEREO56789' }, { 'volumeNumber': 1, 'trackNumber': 2, 'isrc': 'STEREO123479' }, { 'volumeNumber': 1, 'trackNumber': 3, 'isrc': 'STEREO5678453' }, { 'volumeNumber': 1, 'trackNumber': 4, 'isrc': 'STEREO5678453' } ], 'deliveryHistory': [ { 'store': { 'storeId': 1 } } ] }, { 'productId': 23456, 'upc': '123456789', 'notForDistribution': 'NotforFurtherDistribution', 'tracks': [ { 'volumeNumber': 1, 'trackNumber': 1, 'isrc': 'DOLBYATMOS56789' }, { 'volumeNumber': 1, 'trackNumber': 2, 'isrc': 'DOLBYATMOS56788' } ], 'deliveryHistory': [ {} ] }, [('packager-bucket', 'xx/0123456789_1_1_DOLBYATMOS56789.wav'), ('packager-bucket', 'xx/0123456789_1_4_DOLBYATMOS56788.wav')], None ), ]) def test_validate_setup( stereo_metadata, spatial_metadata, spatial_audio_objects, exception): """Test validate setup logic.""" AbstractPackager.__abstractmethods__ = frozenset() packager = AbstractPackager() packager.dms_id = 1 packager.spatial_dms_id = 12345 if exception: with pytest.raises(exception): packager.validate_setup( stereo_metadata, spatial_metadata, spatial_audio_objects, ) else: packager.validate_setup( stereo_metadata, spatial_metadata, spatial_audio_objects, ) @pytest.mark.parametrize( 'track_data, extra_data, exception', [ ({}, {}, src.exceptions.MissingAudioTrack), ( { 'Channels': '28', 'Format': 'PCM', 'BitDepth': '24', 'SamplingRate': '48000' }, {}, src.exceptions.MissingExtraMetadata ), ( { 'Channels': '2', 'Format': 'PCM', 'BitDepth': '24', 'SamplingRate': '48000' }, { 'Metadata_Format': 'ADM, Version 0', 'AdmProfile_Format': 'Dolby Atmos Master' }, src.exceptions.InvalidAudioChannelCount ), ( { 'Channels': '28', 'Format': 'AC3', 'BitDepth': '24', 'SamplingRate': '48000' }, { 'Metadata_Format': 'ADM, Version 0', 'AdmProfile_Format': 'Dolby Atmos Master' }, src.exceptions.InvalidAudioFormat ), ( { 'Channels': '28', 'Format': 'PCM', 'BitDepth': '48', 'SamplingRate': '48000' }, { 'Metadata_Format': 'ADM, Version 0', 'AdmProfile_Format': 'Dolby Atmos Master' }, src.exceptions.InvalidAudioBitDepth ), ( { 'Channels': '28', 'Format': 'PCM', 'BitDepth': '24', 'SamplingRate': '24000' }, { 'Metadata_Format': 'ADM, Version 0', 'AdmProfile_Format': 'Dolby Atmos Master' }, src.exceptions.InvalidAudioSamplingRate ), ( { 'Channels': '28', 'Format': 'PCM', 'BitDepth': '24', 'SamplingRate': '48000' }, { 'Metadata_Format': 'JSON', 'AdmProfile_Format': 'Dolby Atmos Master' }, src.exceptions.InvalidAudioMetadataFormat ), ( { 'Channels': '28', 'Format': 'PCM', 'BitDepth': '24', 'SamplingRate': '48000' }, { 'Metadata_Format': 'ADM, Version 0', 'AdmProfile_Format': 'Sony 360' }, src.exceptions.InvalidAdmProfile ), ( { 'Channels': '28', 'Format': 'PCM', 'BitDepth': '24', 'SamplingRate': '48000' }, { 'Metadata_Format': 'ADM, Version 0', 'AdmProfile_Format': 'Dolby Atmos Master' }, None ) ] ) def test_validate_spatial_audio_metadata(track_data, extra_data, exception): """Test metadata validation logic.""" AbstractPackager.__abstractmethods__ = frozenset() packager = AbstractPackager() if track_data: track_data['@type'] = 'Audio' if extra_data: track_data['extra'] = extra_data data = [ ( None, 's3-key', {'media': {'track': [track_data]}} ) ] if exception: with pytest.raises(exception): packager.validate_spatial_audio_metadata(data) else: packager.validate_spatial_audio_metadata(data) @pytest.mark.parametrize( 'input_data, output', [ ('abc_1_2_isrc.wav', ('abc', 1, 2, 'isrc')), ('abc_01_02_isrc.wav', ('abc', 1, 2, 'isrc')), ('abc_1_2_isrc.WAV', ('abc', 1, 2, 'isrc')), ('path/abc_1_2_isrc.wav', ('abc', 1, 2, 'isrc')), ('abc_a_2_isrc.wav', src.exceptions.InvalidSpatialFilename), ('abc_1_a_isrc.wav', src.exceptions.InvalidSpatialFilename), ('abc_1_2.wav', src.exceptions.InvalidSpatialFilename), ('abc_1_2_isrc', src.exceptions.InvalidSpatialFilename) ] ) def test_extract_spatial_filename_parts(input_data, output): """Test filename processing helper.""" AbstractPackager.__abstractmethods__ = frozenset() packager = AbstractPackager() if type(output) == tuple: result = packager._extract_spatial_filename_parts(input_data) assert result == output else: with pytest.raises(output): packager._extract_spatial_filename_parts(input_data) def test_format_spatial_file_list(): """Test file data processing helper.""" input_data = [ ( 'qa-dolby-atmos-packager-alpha-input', '197190684056/197190684056_1_2_QMFMG2360978.wav', '2da8f8a950006ea6caa920bc62d607a7', 136934 ) ] output_data = { 1: { 2: { 'upc': '197190684056', 'isrc': 'QMFMG2360978', 'md5': '2da8f8a950006ea6caa920bc62d607a7', 'size': 136934, 'bucket': 'qa-dolby-atmos-packager-alpha-input', 'path': '197190684056/197190684056_1_2_QMFMG2360978.wav' } } } AbstractPackager.__abstractmethods__ = frozenset() packager = AbstractPackager() result = packager._format_spatial_file_list(input_data) assert result == output_data def test_format_spatial_files_index_error(): """Test duplicate track indexes.""" input_data = [ ( 'qa-dolby-atmos-packager-alpha-input', '197190684056/197190684056_1_2_QMFMG2360978.wav', '2da8f8a950006ea6caa920bc62d607a7', 136934 ), ( 'qa-dolby-atmos-packager-alpha-input', '197190684056/197190684056_1_2_QMFMG2360977.wav', '2da8f8a950006ea6caa920bc62d607a8', 136935 ) ] AbstractPackager.__abstractmethods__ = frozenset() packager = AbstractPackager() with pytest.raises(src.exceptions.DuplicateSpatialFileIndexes): packager._format_spatial_file_list(input_data) @pytest.mark.parametrize( 'http_code, error_message, exception', [ ( 500, '[UPC:123] Release has master DMS ID:1 restricted from sales.', src.exceptions.XMLFetchFailedReleaseCarveout ), ( 500, '[UPC:123] Label contract has master DMS ID:1 restricted from sales.', # noqa:E501 src.exceptions.XMLFetchFailedLabelCarveout ), ( 500, '[UPC:123] Label contract has master DMS ID:1 restricted from sales.', # noqa:E501 src.exceptions.XMLFetchFailedLabelCarveout ), ( 500, '[UPC:123] Label is deleted.', src.exceptions.XMLFetchFailedLabelDeleted ), ( 500, '[UPC:123] Label does not have an active contract.', src.exceptions.XMLFetchFailedLabelContractNotActive ), ( 400, '', requests.exceptions.HTTPError ) ] ) def test_get_xml_exceptionms(http_code, error_message, exception): """Test cases of fetching XML with exceptions.""" mock_response = MagicMock() mock_response.status_code = http_code mock_response.content = error_message.encode('utf-8') mock_http_error = requests.exceptions.HTTPError() mock_http_error.response = mock_response mock_response.raise_for_status.side_effect = mock_http_error mock_get_metadata = MagicMock() mock_get_metadata.return_value = (mock_response, None) AbstractPackager.__abstractmethods__ = frozenset() packager = AbstractPackager() packager.ows_metadata_connector.get_metadata = mock_get_metadata with pytest.raises(exception): packager.get_xml('abc', 'xyz')