"""Test s3 connections.""" import json from unittest import mock from botocore.exceptions import ClientError from src.common.connectors import s3_sound_recordings import pytest from datetime import datetime from soundrecording_utils.metadata.typedload import get_loader from soundrecording_utils.metadata.types import OrchardSoundRecording @pytest.mark.parametrize( 'mock_data, latest_response, expected_result', [ # old version same as new version Track level rules ( json.dumps({'track_connection': {'tracks': [ {'primary': True, 'rules': [{'territory': 'US'}], 'product': {'label': {'type': 'Vendor', 'rules': []}}}]}} ), ( 'sr-old', json.dumps({'track_connection': {'tracks': [ {'primary': True, 'rules': [{'territory': 'US'}], 'product': {'label': {'type': 'Vendor', 'rules': []}}} ]}}).encode('UTF-8'), datetime(2021, 1, 1) ), ('sr-old', None, False, datetime(2021, 1, 1)) ), # old version same as new version Subaccount level rules ( json.dumps({'track_connection': {'tracks': [ {'primary': True, 'rules': [], 'product': { 'label': {'type': 'Subaccount', 'rules': [{'territory': 'US'}], 'vendor': {'rules': []}}}}]}} ), ( 'sr-old', json.dumps({'track_connection': {'tracks': [ {'primary': True, 'rules': [], 'product': { 'label': {'type': 'Subaccount', 'rules': [{'territory': 'US'}], 'vendor': {'rules': []}}}} ]}}).encode('UTF-8'), datetime(2021, 1, 1) ), ('sr-old', None, False, datetime(2021, 1, 1)) ), # old version same as new version Vendor level rules ( json.dumps({'track_connection': {'tracks': [ {'primary': True, 'rules': [], 'product': { 'label': {'type': 'Vendor', 'rules': [{'territory': 'US'}]}}}]}} ), ( 'sr-old', json.dumps({'track_connection': {'tracks': [ {'primary': True, 'rules': [], 'product': { 'label': {'type': 'Vendor', 'rules': [{'territory': 'US'}]}}} ]}}).encode('UTF-8'), datetime(2021, 1, 1) ), ('sr-old', None, False, datetime(2021, 1, 1)) ), # old version diff from new version Track Level ( json.dumps({'track_connection': {'tracks': [ {'primary': True, 'rules': [{'territory': 'US'}], 'product': { 'label': {'type': 'Vendor', 'rules': []}}}]}} ), ( 'sr-old', json.dumps({'track_connection': {'tracks': [ {'primary': True, 'rules': [], 'product': { 'label': {'type': 'Vendor', 'rules': []}}} ]}}).encode('UTF-8'), datetime(2021, 1, 1)), ('sr-old', 'sr-new', True, datetime(2021, 1, 1)) ), # old version diff from new version Subaccount Level ( json.dumps({'track_connection': {'tracks': [ {'primary': True, 'rules': [], 'product': { 'label': {'type': 'Subaccount', 'rules': [{'territory': 'US'}], 'vendor': {'rules': []}}}}]}} ), ( 'sr-old', json.dumps({'track_connection': {'tracks': [ {'primary': True, 'rules': [], 'product': { 'label': {'type': 'Subaccount', 'rules': [], 'vendor': {'rules': []}}}} ]}}).encode('UTF-8'), datetime(2021, 1, 1)), ('sr-old', 'sr-new', True, datetime(2021, 1, 1)) ), # old version diff from new version Vendor Level ( json.dumps({'track_connection': {'tracks': [ {'primary': True, 'rules': [], 'product': { 'label': {'type': 'Vendor', 'rules': [{'territory': 'US'}]}}}]}} ), ( 'sr-old', json.dumps({'track_connection': {'tracks': [ {'primary': True, 'rules': [], 'product': { 'label': {'type': 'Vendor', 'rules': []}}} ]}}).encode('UTF-8'), datetime(2021, 1, 1)), ('sr-old', 'sr-new', True, datetime(2021, 1, 1)) ), # is_start_absolute changed → new version ( json.dumps({'track_connection': {'tracks': [ {'primary': True, 'rules': [{'territory': 'US', 'is_start_absolute': True}], 'product': {'label': {'type': 'Vendor', 'rules': []}}}]}} ), ( 'sr-old', json.dumps({'track_connection': {'tracks': [ {'primary': True, 'rules': [{'territory': 'US', 'is_start_absolute': False}], 'product': {'label': {'type': 'Vendor', 'rules': []}}} ]}}).encode('UTF-8'), datetime(2021, 1, 1) ), ('sr-old', 'sr-new', True, datetime(2021, 1, 1)) ), # is_start_absolute unchanged → no new version ( json.dumps({'track_connection': {'tracks': [ {'primary': True, 'rules': [{'territory': 'US', 'is_start_absolute': True}], 'product': {'label': {'type': 'Vendor', 'rules': []}}}]}} ), ( 'sr-old', json.dumps({'track_connection': {'tracks': [ {'primary': True, 'rules': [{'territory': 'US', 'is_start_absolute': True}], 'product': {'label': {'type': 'Vendor', 'rules': []}}} ]}}).encode('UTF-8'), datetime(2021, 1, 1) ), ('sr-old', None, False, datetime(2021, 1, 1)) ), # is_end_absolute changed → new version ( json.dumps({'track_connection': {'tracks': [ {'primary': True, 'rules': [{'territory': 'US', 'is_end_absolute': True}], 'product': {'label': {'type': 'Vendor', 'rules': []}}}]}} ), ( 'sr-old', json.dumps({'track_connection': {'tracks': [ {'primary': True, 'rules': [{'territory': 'US', 'is_end_absolute': False}], 'product': {'label': {'type': 'Vendor', 'rules': []}}} ]}}).encode('UTF-8'), datetime(2021, 1, 1) ), ('sr-old', 'sr-new', True, datetime(2021, 1, 1)) ), # is_end_absolute unchanged → no new version ( json.dumps({'track_connection': {'tracks': [ {'primary': True, 'rules': [{'territory': 'US', 'is_end_absolute': True}], 'product': {'label': {'type': 'Vendor', 'rules': []}}}]}} ), ( 'sr-old', json.dumps({'track_connection': {'tracks': [ {'primary': True, 'rules': [{'territory': 'US', 'is_end_absolute': True}], 'product': {'label': {'type': 'Vendor', 'rules': []}}} ]}}).encode('UTF-8'), datetime(2021, 1, 1) ), ('sr-old', None, False, datetime(2021, 1, 1)) ), # no old version exists ( json.dumps({'track_connection': {'tracks': [ {'primary': True, 'rules': [{'territory': 'US'}], 'product': {'label': {'type': 'Vendor', 'rules': []}}}]}} ), (None, None, None), (None, 'sr-new', True, None) ), ] ) @mock.patch('src.common.connectors.s3_sound_recordings._config', return_value=('sr-bucket', 'versions/')) @mock.patch('src.common.connectors.s3_sound_recordings.s3_client') @mock.patch('src.common.connectors.s3_sound_recordings.get_latest_sound_recording_version') def test_write_version_changed( mock_get_latest, mock_client, mock_s3_config, mock_data, latest_response, expected_result): """Test saving new version behavior.""" sr_id = 'acb18953-e9bb-4750-973b-7f305b5d9d82' mock_get_latest.return_value = latest_response mock_client.put_object.return_value = { 'VersionId': 'sr-new', 'LastModified': datetime(2021, 1, 1)} result = s3_sound_recordings.write_sound_recording_version( sr_id, mock_data ) assert mock_get_latest.called assert mock_get_latest.call_args_list == [ mock.call(sr_id) ] if expected_result[1]: assert mock_client.put_object.called assert mock_client.put_object.call_args_list == [ mock.call( Bucket='sr-bucket', Key=f'versions/{sr_id}', Body=mock_data.encode('UTF-8') ) ] else: assert not mock_client.put_object.called assert result == expected_result @pytest.mark.parametrize( 'error_code, error_message, expected_result', [ # no exceptions ( None, None, ( 'sr-new', json.dumps({ 'track_connection': { 'tracks': [ {'primary': True, 'rules': [{'territory': 'US'}], 'product': {'label': {'type': 'Vendor', 'rules': []}}} ] } }).encode('UTF-8'), datetime(2021, 1, 1) ) ), # no such key exception ( 'NoSuchKey', 'message', (None, None, None) ), # no such bucket exception ( 'NoSuchBucket', 'message', None ), # invalid version exception ( 'InvalidArgument', 'Invalid version id specified', (None, None, None) ) ] ) @mock.patch('src.common.connectors.s3_sound_recordings._config', return_value=('sr-bucket', 'versions/')) @mock.patch('src.common.connectors.s3_sound_recordings.s3_client') def test_get_latest( mock_client, mock_s3_config, error_code, error_message, expected_result): """Test get latest version behavior.""" sr_id = 'acb18953-e9bb-4750-973b-7f305b5d9d82' if error_code: mock_client.get_object.side_effect = ClientError( {'Error': {'Code': error_code, 'Message': error_message}}, 'S3GetObject' ) else: mock_body = mock.MagicMock() mock_body.read.return_value = json.dumps( { 'track_connection': {'tracks': [ {'primary': True, 'rules': [{'territory': 'US'}], 'product': {'label': {'type': 'Vendor', 'rules': []}}} ]} }).encode('UTF-8') mock_client.get_object.return_value = { 'VersionId': 'sr-new', 'Body': mock_body, 'LastModified': datetime(2021, 1, 1), } try: result = s3_sound_recordings.get_latest_sound_recording_version(sr_id) except ClientError: assert not expected_result else: assert result == expected_result assert mock_client.get_object.called assert mock_client.get_object.call_args_list == [ mock.call( Bucket='sr-bucket', Key=f'versions/{sr_id}', ) ] _MINIMAL_RULE = { 'active': True, 'policy': 'monetize', 'service': 'tiktok', 'territory': 'US', 'start_date': '2025-01-01', 'created_date': '2025-01-01', 'last_modified_date': '2025-01-01', 'created_by': {'id': '1', 'type': 'ORCHADMIN'}, 'last_modified_by': {'id': '1', 'type': 'ORCHADMIN'}, } _MINIMAL_SR = { 'isrc': 'isrc-123', 'assets': [ {'uuid': 'ecd5647a-0655-4740-9dbf-e3c464e63ed5', 'filename': '390821c8-e8ba-4801-bd21-f61a46ece8f4', 'extension': 'wav'} ], 'track_connection': { 'tracks': [{ 'tuid': '456', 'isrc': 'isrc-123', 'name': 'Song Name', 'explicit': 'N', 'territories': ['US'], 'participations': [], 'primary': True, 'product': { 'id': '777', 'display_upc': 'upc-123', 'status': 'in_content', 'release_date': '2019-06-15', 'label': { 'name': 'Label Name', 'uuid': '94177d6a-51d4-496f-aa1a-a52b75516814', 'type': 'Vendor', 'id': {'vendor': 999, 'subaccount': 0}, 'rules': [], }, 'subgenre': { 'id': 327, 'name': 'Club/Dance', 'genre': {'id': 11, 'name': 'World Music'} } }, }] } } @pytest.mark.parametrize( 'rule_extra, expected_is_start_absolute', [ # old blob without is_start_absolute → defaults to False ({}, False), # new blob with is_start_absolute: True → loads correctly ({'is_start_absolute': True}, True), ] ) def test_fingerprint_rule_is_start_absolute_deserialization(rule_extra, expected_is_start_absolute): """Test that is_start_absolute deserializes correctly from an S3 blob.""" sr = json.loads(json.dumps(_MINIMAL_SR)) sr['track_connection']['tracks'][0]['rules'] = [{**_MINIMAL_RULE, **rule_extra}] osr = get_loader().load(sr, OrchardSoundRecording) assert osr.track_connection.tracks[0].rules[0].is_start_absolute == expected_is_start_absolute