"""Unit tests for set_track_metadata lambda.""" from unittest.mock import patch from common.models.state_machine.label_participant import LabelParticipant from common.models.state_machine.participant import Participant from common.schemas.state_machine_schema import StateMachineSchema, TrackSchema from lambdacommon.graphql import graphql import pytest from src.constants.errors import ( KNOWN_GRAPHQL_ERRORS, OWS_PRODUCT_USER_IS_FORBIDDEN, ) from src.constants.queries import UPDATE_TRACK_METADATA from src.exceptions import SetTrackMetadataException import src.index as index # handler @patch('src.index.update_track_metadata') def test_handler_calls_update_track_metadata(mock_update, context_event): """Test handler calls update_track_metadata with correct args.""" index.handler(context_event, None) mock_update.assert_called_once() args, _ = mock_update.call_args assert args[2] == '196871691208' # upc assert args[3] == 12345678 # tuid @patch('src.index.update_track_metadata') def test_handler_returns_serialized_context(mock_update, context_event): """Test handler returns a serialized state machine context.""" result = index.handler(context_event, None) assert result['correlation_id'] == 'test-correlation-id' assert result['product']['upc'] == '196871691208' @patch('src.index.update_track_metadata') def test_handler_raises_on_forbidden_graphql_error( mock_update, context_event): """Test handler raises SetTrackMetadataException for forbidden error.""" mock_update.side_effect = graphql.GraphQLError( [{'message': OWS_PRODUCT_USER_IS_FORBIDDEN}]) with pytest.raises(SetTrackMetadataException) as exc_info: index.handler(context_event, None) assert OWS_PRODUCT_USER_IS_FORBIDDEN in str(exc_info.value) @patch('src.index.update_track_metadata') def test_handler_raises_on_known_graphql_error(mock_update, context_event): """Test handler raises SetTrackMetadataException with ISRC.""" known_error = KNOWN_GRAPHQL_ERRORS[0] mock_update.side_effect = graphql.GraphQLError([{ 'message': known_error, 'extensions': { 'code': 'GRAPHQL_VALIDATION_FAILED', 'response': {'errors': [known_error]}, }, }]) with pytest.raises(SetTrackMetadataException) as exc_info: index.handler(context_event, None) assert exc_info.value.args[0]['ISRC'] == 'QZCDB2000001' @patch('src.index.update_track_metadata') def test_handler_raises_on_unknown_graphql_error( mock_update, context_event): """Test handler raises SetTrackMetadataException for unknown errors.""" mock_update.side_effect = graphql.GraphQLError( [{'message': 'some unexpected error'}]) with pytest.raises(SetTrackMetadataException) as exc_info: index.handler(context_event, None) assert 'GraphQL error' in str(exc_info.value) @patch('src.index.update_track_metadata') def test_handler_raises_on_general_exception(mock_update, context_event): """Test handler raises SetTrackMetadataException for non-GraphQL errors.""" mock_update.side_effect = RuntimeError('something broke') with pytest.raises(SetTrackMetadataException) as exc_info: index.handler(context_event, None) assert 'Error updating track metadata' in str(exc_info.value) @patch('src.index.update_track_metadata') def test_handler_logs_warning_for_unmapped_roles( mock_update, context_event, caplog): """Test handler logs a warning when unmapped roles exist.""" context_event['track']['display_artists'][0]['roles'] = ['UNKNOWN_ROLE'] with caplog.at_level('WARNING'): index.handler(context_event, None) assert 'unmapped role' in caplog.text # get_unique_roles def test_get_unique_roles(context_event): """Test get_unique_roles returns role-to-name mapping.""" track = TrackSchema().load(context_event['track']) result = index.get_unique_roles(track) assert result == { 'PRIMARY_ARTIST': 'Artist One', 'FEATURED_ARTIST': 'Artist Two', } def test_get_unique_roles_no_display_artists(context_event): """Test get_unique_roles returns empty dict when no display artists.""" context_event['track']['display_artists'] = None track = TrackSchema().load(context_event['track']) result = index.get_unique_roles(track) assert result == {} def test_get_unique_roles_artist_with_no_roles(context_event): """Test get_unique_roles skips artists with no roles.""" context_event['track']['display_artists'][0]['roles'] = None track = TrackSchema().load(context_event['track']) result = index.get_unique_roles(track) assert 'PRIMARY_ARTIST' not in result assert 'FEATURED_ARTIST' in result # format_track_update_data def test_format_track_update_data(context_event): """Test format_track_update_data returns correct update payload.""" sm_context = StateMachineSchema().load(context_event['context']) track = TrackSchema().load(context_event['track']) result = index.format_track_update_data( sm_context, sm_context.label_participants, sm_context.product.upc, track.tuid, track, {}, ) assert result['update']['tracks'] == [12345678] body = result['update']['body'] assert body['upc'] == '196871691208' assert body['isrc'] == 'QZCDB2000001' assert body['trackName'] == 'Test Track' assert body['explicit'] == 'Y' assert body['version'] == 'Deluxe' assert body['trackType'] == 'music' assert 'participations' in body def test_format_video_track_update_data(context_event): """Test format_track_update_data returns correct update payload.""" context_event['context']['product_type'] = 'VIDEO' sm_context = StateMachineSchema().load(context_event['context']) track = TrackSchema().load(context_event['track']) result = index.format_track_update_data( sm_context, sm_context.label_participants, sm_context.product.upc, track.tuid, track, {}, ) assert result['update']['tracks'] == [12345678] body = result['update']['body'] assert body['upc'] == '196871691208' assert body['isrc'] == 'QZCDB2000001' assert body['trackName'] == 'Test Track' assert body['explicit'] == 'Y' assert body['version'] == 'Deluxe' assert body['trackType'] == 'video' assert 'participations' in body def test_format_track_update_data_filters_none_values(context_event): """Test format_track_update_data removes None values from body.""" sm_context = StateMachineSchema().load(context_event['context']) context_event['track']['explicit'] = None track = TrackSchema().load(context_event['track']) result = index.format_track_update_data( sm_context, sm_context.label_participants, sm_context.product.upc, track.tuid, track, {}, ) assert 'explicit' not in result['update']['body'] # get_track_participations def test_get_track_participations(context_event): """Test get_track_participations returns participations for matched.""" sm_context = StateMachineSchema().load(context_event['context']) track = TrackSchema().load(context_event['track']) result = index.get_track_participations( sm_context.label_participants, track, {}) assert len(result) == 2 uuids = [p['labelParticipantUuid'] for p in result] assert 'uuid-artist-one' in uuids assert 'uuid-artist-two' in uuids roles = [p['role'] for p in result] assert 'PERFORMER' in roles assert 'FEATURED_ARTIST' in roles def test_get_track_participations_no_display_artists(context_event): """Test get_track_participations returns empty list with no artists.""" sm_context = StateMachineSchema().load(context_event['context']) context_event['track']['display_artists'] = None track = TrackSchema().load(context_event['track']) result = index.get_track_participations( sm_context.label_participants, track, {}) assert result == [] def test_get_track_participations_skips_unmapped_role(context_event): """Test get_track_participations skips roles not in ARTIST_ROLE_MAP.""" sm_context = StateMachineSchema().load(context_event['context']) context_event['track']['display_artists'][0]['roles'] = ['UNKNOWN_ROLE'] track = TrackSchema().load(context_event['track']) result = index.get_track_participations( sm_context.label_participants, track, {}) uuids = [p['labelParticipantUuid'] for p in result] assert 'uuid-artist-one' not in uuids def test_get_track_participations_skips_unmatched_artist(context_event): """Test get_track_participations skips artists with no participant.""" sm_context = StateMachineSchema().load(context_event['context']) context_event['track']['display_artists'][0]['name'] = 'Unknown Artist' track = TrackSchema().load(context_event['track']) result = index.get_track_participations( sm_context.label_participants, track, {}) assert len(result) == 1 assert result[0]['labelParticipantUuid'] == 'uuid-artist-two' def test_get_track_participations_no_duplicates(context_event): """Test get_track_participations does not add duplicate participations.""" sm_context = StateMachineSchema().load(context_event['context']) context_event['track']['display_artists'][0]['roles'] = [ 'PRIMARY_ARTIST', 'PRIMARY_ARTIST'] track = TrackSchema().load(context_event['track']) result = index.get_track_participations( sm_context.label_participants, track, {}) artist_one_entries = [ p for p in result if p['labelParticipantUuid'] == 'uuid-artist-one' ] assert len(artist_one_entries) == 1 # add_artist def test_add_artist(context_event): """Test add_artist appends participation to list.""" artist = LabelParticipant( name='Artist One', label_participant_uuid='uuid-artist-one', ) participations = [] unmapped_roles = {'PRIMARY_ARTIST': 'Artist One'} index.add_artist(artist, 'PRIMARY_ARTIST', participations, unmapped_roles) assert len(participations) == 1 assert participations[0]['labelParticipantUuid'] == 'uuid-artist-one' assert participations[0]['role'] == 'PERFORMER' assert 'PRIMARY_ARTIST' not in unmapped_roles def test_add_artist_skips_unmapped_role(): """Test add_artist does nothing if role is not in ARTIST_ROLE_MAP.""" artist = LabelParticipant( name='Artist One', label_participant_uuid='uuid-artist-one', ) participations = [] index.add_artist(artist, 'UNKNOWN_ROLE', participations, {}) assert participations == [] def test_add_artist_skips_none_artist(): """Test add_artist does nothing if artist is None.""" participations = [] index.add_artist(None, 'PRIMARY_ARTIST', participations, {}) assert participations == [] def test_add_artist_no_duplicates(): """Test add_artist does not add the same participation twice.""" artist = LabelParticipant( name='Artist One', label_participant_uuid='uuid-artist-one', ) participations = [] index.add_artist(artist, 'PRIMARY_ARTIST', participations, {}) index.add_artist(artist, 'PRIMARY_ARTIST', participations, {}) assert len(participations) == 1 # retrieve_label_participant def test_retrieve_label_participant(context_event): """Test retrieve_label_participant finds participant by name.""" sm_context = StateMachineSchema().load(context_event['context']) artist = Participant(name='Artist One') result = index.retrieve_label_participant( sm_context.label_participants, artist) assert result is not None assert result.name == 'Artist One' assert result.label_participant_uuid == 'uuid-artist-one' def test_retrieve_label_participant_no_match(context_event): """Test retrieve_label_participant returns None when no match found.""" sm_context = StateMachineSchema().load(context_event['context']) artist = Participant(name='Unknown Artist') result = index.retrieve_label_participant( sm_context.label_participants, artist) assert result is None def test_retrieve_label_participant_empty_list(): """Test retrieve_label_participant returns None when list is empty.""" artist = Participant(name='Artist One') result = index.retrieve_label_participant([], artist) assert result is None def test_retrieve_label_participant_none_list(): """Test retrieve_label_participant returns None when list is None.""" artist = Participant(name='Artist One') result = index.retrieve_label_participant(None, artist) assert result is None # update_track_metadata @patch('src.index.graphql_gateway.execute') def test_update_track_metadata( mock_execute, context_event, save_tracks_response): """Test update_track_metadata sends correct payload to GraphQL.""" mock_execute.return_value = save_tracks_response sm_context = StateMachineSchema().load(context_event['context']) track = TrackSchema().load(context_event['track']) index.update_track_metadata( sm_context, sm_context.label_participants, sm_context.product.upc, track.tuid, track, {}, ) mock_execute.assert_called_once() args, _ = mock_execute.call_args assert args[0] == UPDATE_TRACK_METADATA payload = args[1]['data'] assert payload['update']['tracks'] == [12345678] assert payload['update']['body']['upc'] == '196871691208' assert payload['update']['body']['isrc'] == 'QZCDB2000001'