"""Tests for Track utils.""" from collections import ChainMap from copy import deepcopy from oto import response from backend.constants import feature_flag from backend.constants import track_field as tf from backend.models import track_sample as sample_model from backend.utils import track_utils def test_unpack_work_and_movement(): """Verify work and movement are properly extracted.""" work, movement = track_utils.unpack_work_and_movement( {tf.TRACK_NAME: ' work: movement '}) assert (work, movement) == ('work', 'movement') def test_unpack_track_work_without_movement(): """Verify work is extracted when movement is blank.""" work, movement = track_utils.unpack_work_and_movement( {tf.TRACK_NAME: 'work'}) assert (work, movement) == ('work', '') def test_unpack_track_movement_without_work(): """Verify movement is extracted when work is blank.""" work, movement = track_utils.unpack_work_and_movement( {tf.TRACK_NAME: ': movement'}) assert (work, movement) == ('', 'movement') def test_has_artist_role(track_factory): """Verify artist role was found.""" track = track_factory(performer__count=1) assert track_utils.has_role(track.to_dict(), 'performer') def test_has_writer_role(track_factory): """Verify writer role was found.""" track = track_factory(writers__count=1) assert track_utils.has_role(track.to_dict(), 'writer') def test_has_publisher_role(track_factory): """Verify publisher role was found.""" track = track_factory(publishers__count=1) assert track_utils.has_role(track.to_dict(), 'publisher') def test_not_has_artist_role(track_factory): """Verify artist role was not found.""" track = track_factory(performer__count=1) assert not track_utils.has_role(track.to_dict(), 'remixer') def test_add_track_samples_to_error(): """Verify that when original response has error it just returns it.""" track_response = response.create_fatal_response('error') result = track_utils.add_track_samples_to_response(track_response) assert result == track_response def test_add_track_samples_to_response(mocker): """Verify that when sample response is added to original response.""" original = {'tuid': '123', 'foo': 'bar'} samples = [{'sample': 1}, {'sample': 2}] track_response = response.Response(original) mocker.patch.object( sample_model, 'get_track_samples', return_value=response.Response(message=samples)) result = track_utils.add_track_samples_to_response(track_response) expected = deepcopy(original) expected[tf.TRACK_SAMPLES] = samples assert result.message == expected def test_error_track_samples_to_response(mocker): """Verify that when sample get returns an error, overall you get error.""" original = {'tuid': '123', 'foo': 'bar'} samples_error = response.create_fatal_response('error') track_response = response.Response(original) mocker.patch.object( sample_model, 'get_track_samples', return_value=samples_error) result = track_utils.add_track_samples_to_response(track_response) assert result == samples_error def test_get_track_featuring_artist_names_with_corrections(test_track_model_response): """Verify that we're able to get featuring artist names from a track.""" corrections = {'meta_language_code': 'ENG', 'artists': [ {'name': 'test primary', 'type': 'performer'}, {'name': 'test featuring', 'type': 'featuring'}]} track = test_track_model_response track[tf.ARTISTS].append({'track_artist_id': 5, 'type': 'featuring', 'name': 'Fireball'}) track = ChainMap(corrections, track) assert track_utils.get_track_featuring_artist_names(track) == ['test featuring'] def test_get_track_featuring_artist_names_no_corrections(test_track_model_response): """Verify that we're able to get featuring artist names from a track.""" corrections = {} track = test_track_model_response track[tf.ARTISTS].append({'track_artist_id': 5, 'type': 'featuring', 'name': 'Fireball'}) track = ChainMap(corrections, track) assert track_utils.get_track_featuring_artist_names(track) == ['Fireball'] def test_get_track_artists_name_ver_artists_tpl(mock_app, test_track_model_response): """Verify we get a correct name/ver/performer tuple.""" track = test_track_model_response track[tf.ARTISTS] = [ {'type': 'performer', 'track_artist_id': 111, 'name': 'An Artist'}, {'type': 'featuring', 'track_artist_id': 222, 'name': 'Some Artist'}] expected_result = ('Nine Lives vol 1 track 2', None, ('An Artist',)) assert expected_result == track_utils.get_track_artists_name_ver_artists_tpl(track) def test_get_track_artists_name_ver_artists_tpl_ff(mock_app, test_track_model_response, feature_flags): """Verify we get a correct name/ver/performer/explicit tuple.""" feature_flags[feature_flag.BYPASS_NAME_VALIDATION_IF_DIFF_EXPLICIT] = True track = test_track_model_response track[tf.ARTISTS] = [ {'type': 'performer', 'track_artist_id': 111, 'name': 'An Artist'}, {'type': 'featuring', 'track_artist_id': 222, 'name': 'Some Artist'}] expected_result = ('Nine Lives vol 1 track 2', None, ('An Artist',), 'N') assert expected_result == track_utils.get_track_artists_name_ver_artists_tpl(track) def test_unicode_normalize_compare(): a = '\\u0055\\u0065\\u0020\\u004D\\u00E0'.encode().decode('unicode-escape') b = '\\u0055\\u0065\\u0020\\u004D\\u0061\\u0300'.encode().decode('unicode-escape') c = '\\u0055\\u0065\\u0020\\u004D\\u00E0'.encode().decode('unicode-escape') d = '\\u0065\\u0020\\u004D\\u0061\\u0300'.encode().decode('unicode-escape') e = 'Bobby' f = 'Bobby' g = 'Bobby' h = 'Not Bobby' i = 'Bobby' j = None if track_utils.unicode_normalize_compare(a, b): pass if not track_utils.unicode_normalize_compare(c, d): pass if track_utils.unicode_normalize_compare(e, f): pass if not track_utils.unicode_normalize_compare(g, h): pass if not track_utils.unicode_normalize_compare(i, j): pass if not track_utils.unicode_normalize_compare(j, i): pass def test_unicode_normalize_values(): a = 'Bobby' b = '\\u0055\\u0065\\u0020\\u004D\\u00E0'.encode().decode('unicode-escape') c = 'Bobby' d = '\\u0055\\u0065\\u0020\\u004D\\u0061\\u0300'.encode().decode('unicode-escape') e = '' f = '' g = None h = None assert [a, b] == track_utils.unicode_normalize_values([c, d]) assert [e, f] == track_utils.unicode_normalize_values([g, h]) def test_get_track_artist_by_key_with_corrections(test_track_model_response): """Verify that we're able to get composers from a track.""" corrections = {'meta_language_code': 'ENG', 'artists': [ {'name': 'test primary', 'type': 'performer'}, {'name': 'test composer', 'type': 'composer'}, {'name': 'test orchestra', 'type': 'orchestra'}, {'name': 'test conductor', 'type': 'conductor'}, {'name': 'test ensemble', 'type': 'ensemble'}]} track = test_track_model_response track = ChainMap(corrections, track) assert track_utils.get_track_artists_by_key(tf.COMPOSER, track) == ['test composer'] assert track_utils.get_track_artists_by_key(tf.ORCHESTRA, track) == ['test orchestra'] assert track_utils.get_track_artists_by_key(tf.CONDUCTOR, track) == ['test conductor'] assert track_utils.get_track_artists_by_key(tf.ENSEMBLE, track) == ['test ensemble'] def test_get_track_artist_by_key_with_no_corrections(test_track_model_response): """Verify that we're able to get composers from a track.""" corrections = {} track = test_track_model_response track[tf.ARTISTS].append({'track_artist_id': 5, 'type': 'composer', 'name': 'Fireball'}) track[tf.ARTISTS].append({'track_artist_id': 6, 'type': 'orchestra', 'name': 'Pingpong'}) track[tf.ARTISTS].append({'track_artist_id': 7, 'type': 'conductor', 'name': 'Rugbyball'}) track[tf.ARTISTS].append({'track_artist_id': 8, 'type': 'ensemble', 'name': 'Wienerdawwg'}) track = ChainMap(corrections, track) assert track_utils.get_track_artists_by_key(tf.COMPOSER, track) == ['Fireball'] assert track_utils.get_track_artists_by_key(tf.ORCHESTRA, track) == ['Pingpong'] assert track_utils.get_track_artists_by_key(tf.CONDUCTOR, track) == ['Rugbyball'] assert track_utils.get_track_artists_by_key(tf.ENSEMBLE, track) == ['Wienerdawwg'] def test_get_track_artists_by_role(test_track_model_response): """Verify that we're able to filter track's artists by role type for primary_artist and other roles.""" corrections = { 'primary_artist': [{'name': 'composerPA', 'type': 'performer'}], 'composer': [{'name': 'composerEC1', 'type': 'composer'}, {'name': 'composerEC2', 'type': 'composer'}], 'orchestra': [{'name': 'orchestraEC1', 'type': 'orchestra'}, {'name': 'orchestraEC2', 'type': 'orchestra'}], 'conductor': [{'name': 'conductorEC1', 'type': 'conductor'}, {'name': 'conductorEC2', 'type': 'conductor'}], 'ensemble': [{'name': 'ensembleEC1', 'type': 'ensemble'}, {'name': 'ensembleEC2', 'type': 'ensemble'}], } track = test_track_model_response track = ChainMap(corrections, track) assert track_utils.get_track_artists_by_role(track, 'performer') == [{'name': 'composerPA', 'type': 'performer'}] assert track_utils.get_track_artists_by_role(track, 'composer') == [ {'name': 'composerEC1', 'type': 'composer'}, {'name': 'composerEC2', 'type': 'composer'}] assert track_utils.get_track_artists_by_role(track, 'orchestra') == [ {'name': 'orchestraEC1', 'type': 'orchestra'}, {'name': 'orchestraEC2', 'type': 'orchestra'}] assert track_utils.get_track_artists_by_role(track, 'conductor') == [ {'name': 'conductorEC1', 'type': 'conductor'}, {'name': 'conductorEC2', 'type': 'conductor'}] assert track_utils.get_track_artists_by_role(track, 'ensemble') == [ {'name': 'ensembleEC1', 'type': 'ensemble'}, {'name': 'ensembleEC2', 'type': 'ensemble'}] def test_get_track_artists_by_role_other_artists(test_track_model_response): """Verify that we're able to filter track's artists by role type for artists.""" track = test_track_model_response track[tf.ARTISTS].append({'track_artist_id': 5, 'type': 'composer', 'name': 'Fireball'}) track[tf.ARTISTS].append({'track_artist_id': 6, 'type': 'orchestra', 'name': 'Pingpong'}) track[tf.ARTISTS].append({'track_artist_id': 7, 'type': 'conductor', 'name': 'Rugbyball'}) track[tf.ARTISTS].append({'track_artist_id': 8, 'type': 'ensemble', 'name': 'Wienerdawwg'}) assert track_utils.get_track_artists_by_role(track, 'performer') == [ {'track_artist_id': 4, 'type': 'performer', 'name': 'Snowball'}] assert track_utils.get_track_artists_by_role(track, 'composer') == [ {'track_artist_id': 5, 'type': 'composer', 'name': 'Fireball'}] assert track_utils.get_track_artists_by_role(track, 'orchestra') == [ {'track_artist_id': 6, 'type': 'orchestra', 'name': 'Pingpong'}] assert track_utils.get_track_artists_by_role(track, 'conductor') == [ {'track_artist_id': 7, 'type': 'conductor', 'name': 'Rugbyball'}] assert track_utils.get_track_artists_by_role(track, 'ensemble') == [ {'track_artist_id': 8, 'type': 'ensemble', 'name': 'Wienerdawwg'}] # --- get_artists_with_corrections --- def test_get_artists_with_corrections_empty_corrections(all_artist_types_track): """An empty correction dict leaves all original artists intact.""" track = ChainMap({}, all_artist_types_track) result = track_utils.get_artists_with_corrections(track) assert result == all_artist_types_track['artists'] def test_get_artists_with_corrections_single_type_corrected(all_artist_types_track): """Correcting one artist type replaces only that type; all others remain original.""" correction = { 'remixer': [{'type': 'remixer', 'name': 'corrected_remixer'}], } track = ChainMap(correction, all_artist_types_track) result = track_utils.get_artists_with_corrections(track) result_by_type = {a['type']: a['name'] for a in result} assert result_by_type['remixer'] == 'corrected_remixer' # Every other type must use the original for artist_type in ['featuring', 'performer', 'producer', 'composer', 'orchestra', 'conductor', 'ensemble']: assert result_by_type[artist_type] == f'original_{artist_type}' def test_get_artists_with_corrections_all_types_corrected(all_artist_types_track): """When every artist type is corrected, no original artists appear in the result.""" correction = { 'featuring': [{'type': 'featuring', 'name': 'corrected_featuring'}], 'performer': [{'type': 'performer', 'name': 'corrected_performer'}], 'producer': [{'type': 'producer', 'name': 'corrected_producer'}], 'primary_artist': [{'type': 'performer', 'name': 'corrected_performer'}], 'remixer': [{'type': 'remixer', 'name': 'corrected_remixer'}], 'composer': [{'type': 'composer', 'name': 'corrected_composer'}], 'orchestra': [{'type': 'orchestra', 'name': 'corrected_orchestra'}], 'conductor': [{'type': 'conductor', 'name': 'corrected_conductor'}], 'ensemble': [{'type': 'ensemble', 'name': 'corrected_ensemble'}], } track = ChainMap(correction, all_artist_types_track) result = track_utils.get_artists_with_corrections(track) assert result == [a for artists in correction.values() for a in artists] def test_get_artists_with_corrections_correction_with_multiple_artists(all_artist_types_track): """A corrected type can expand to multiple artists.""" correction = { 'performer': [ {'type': 'performer', 'name': 'corrected_performer_1'}, {'type': 'performer', 'name': 'corrected_performer_2'}, ], } track = ChainMap(correction, all_artist_types_track) result = track_utils.get_artists_with_corrections(track) performer_results = [a for a in result if a['type'] == 'performer'] assert performer_results == [ {'type': 'performer', 'name': 'corrected_performer_1'}, {'type': 'performer', 'name': 'corrected_performer_2'}, ] assert not any(a['name'] == 'original_performer' for a in result) def test_get_artists_with_corrections_empty_correction_list_removes_type(all_artist_types_track): """A correction key mapped to an empty list removes that artist type entirely.""" correction = {'remixer': []} track = ChainMap(correction, all_artist_types_track) result = track_utils.get_artists_with_corrections(track) assert not any(a['type'] == 'remixer' for a in result) # All other types still present for artist_type in [ 'featuring', 'performer', 'producer', 'composer', 'orchestra', 'conductor', 'ensemble' ]: assert any(a['type'] == artist_type for a in result) def test_get_artists_with_corrections_no_artist_field(): """A track without the artist type returns the artist type correction.""" track = ChainMap( {'remixer': [{'type': 'remixer', 'name': 'corrected_remixer'}]}, { 'tuid': 99, 'artists': [{'type': 'performer', 'name': 'original_performer'}] }, ) result = track_utils.get_artists_with_corrections(track) assert result == [ {'type': 'remixer', 'name': 'corrected_remixer'}, {'type': 'performer', 'name': 'original_performer'} ] def test_is_producer_required_track(): """Test is producer required logic.""" assert track_utils.is_producer_required_track(None) is False assert track_utils.is_producer_required_track('2023-1-1') is False assert track_utils.is_producer_required_track('2024-11-5') is True assert track_utils.is_producer_required_track('2024-11-05') is True assert track_utils.is_producer_required_track('2027-02-03') is True