"""Test for Track Model.""" from collections import defaultdict from datetime import date from datetime import timedelta from unittest.mock import patch import pytest from backend.connectors import mysql from backend.constants import ownership_type from backend.constants import track_field as tf from backend.constants import track_role from backend.exceptions import IntegrityError from backend.models.track import Track from backend.models import track as track_model from backend.models.track_query import TrackQuery from tests.testutils import db from tests.testutils.seed.track_artist_seed import track_artist_seed_data from tests.testutils.seed.track_producer_natl_seed import \ track_producer_natl_seed_data from tests.testutils.seed.track_publisher_seed import track_publisher_seed_data from tests.testutils.seed.track_writer_seed import track_writer_seed_data @db.test_schema def test_updating_track_tuid_fails(test_track): """Test updating track tuid fails.""" tuid = test_track[tf.TUID] with mysql.db_session() as session: track = TrackQuery.get_by_tuid(tuid, session) with pytest.raises(ValueError): track.update(tuid=1000) @db.test_schema def test_updating_track_invalid_field_fails(test_track): """Test updating invalid field raises TypeError.""" tuid = test_track[tf.TUID] with mysql.db_session() as session: track = TrackQuery.get_by_tuid(tuid, session) with pytest.raises(TypeError): track.update(bad_field='bad bad bad') @db.test_schema def test_updating_track_product_fails(test_track): """Test updating track product_id fails.""" tuid = test_track[tf.TUID] with mysql.db_session() as session: track = TrackQuery.get_by_tuid(tuid, session) with pytest.raises(ValueError): track.update(product_id=1000) @db.test_schema_no_seed def test_track_master_rights_model(track_factory): """Test track_master_rights model.""" track = track_factory() db.merge_model_objects([track]) tuid = track.tuid with mysql.db_session() as session: track = TrackQuery.get_by_tuid(tuid, session) assert track.tuid == tuid assert track._master_rights.is_owner == 'n' assert track.ownership_rights == ownership_type.NO_RIGHTS # Set ownership rights and verify it was updated using a new session track.ownership_rights = ownership_type.ACQUIRED_RIGHTS with mysql.db_session() as session: track = TrackQuery.get_by_tuid(tuid, session) assert track._master_rights.is_owner == 'y' assert track.ownership_rights == ownership_type.ACQUIRED_RIGHTS # Set ownership rights and verify it was updated using a new session track.ownership_rights = None with mysql.db_session() as session: track = TrackQuery.get_by_tuid(tuid, session) assert track.ownership_rights is None def test_track_master_rights_model_invalid(track_factory): """Test invalid ownership_type raises exception.""" track = track_factory() with pytest.raises(ValueError): track.ownership_rights = 'bad_choice' @pytest.mark.parametrize('is_owner', ['y', 'n']) def test_track_master_rights_model_legacy_entry(track_factory, is_owner): """Test legacy choices are ignored.""" track = track_factory.create( ownership_rights='no_rights', recording_country_id=1, original_rights_holder_country_id=1) track._master_rights.rights_level = None track._master_rights.is_owner = is_owner assert track.ownership_rights is None @db.test_schema def test_track_master_rights_model_no_row(): """Test populating track_master_right works if row doesn't exist. When a track is created, track_master_rights does not have a row initially associated with the track. We want to make sure the Track model is checking if the row exists before updating/creating it. """ tuid = 3 with mysql.db_session() as session: track = TrackQuery.get_by_tuid(tuid, session) assert track.tuid == tuid assert track._master_rights is None assert track.ownership_rights is None # Set ownership rights and verify row was created using a new session track.ownership_rights = ownership_type.NO_RIGHTS with mysql.db_session() as session: track = TrackQuery.get_by_tuid(tuid, session) assert track._master_rights.is_owner == 'n' assert track.ownership_rights == ownership_type.NO_RIGHTS @pytest.mark.parametrize( ('focus_track', 'focus_track_start_date', 'focus_track_end_date', 'user_info'), [ ( 'Y', date.today(), date.today() + timedelta(days=4), {'user_id': 123, 'user_type': 'alw'} ), ( 'N', None, None, {} ) ] ) @db.test_schema def test_focus_track_update( focus_track, focus_track_start_date, focus_track_end_date, user_info): """Test populating track_master_right works if row doesn't exist. When a track is created, track_master_rights does not have a row initially associated with the track. We want to make sure the Track model is checking if the row exists before updating/creating it. """ tuid = 3 with mysql.db_session() as session: track = TrackQuery.get_by_tuid(tuid, session) assert track.tuid == tuid assert track._focus_track is None assert track.focus_track == 'N' track.update_focus_track_info( focus_track, focus_track_start_date, focus_track_end_date, user_info ) with mysql.db_session() as session: track = TrackQuery.get_by_tuid(tuid, session) assert track.focus_track == focus_track assert track.focus_track_start_date == focus_track_start_date assert track.focus_track_end_date == focus_track_end_date if focus_track == 'Y': assert track._focus_track assert track._focus_track.product_id == track.product_id assert track._focus_track.updated_by_user_type == user_info['user_type'] assert track._focus_track.updated_by_user_id == user_info['user_id'] assert track._focus_track.created_by_user_type == user_info['user_type'] assert track._focus_track.created_by_user_id == user_info['user_id'] if user_info: with mysql.db_session() as session: track = TrackQuery.get_by_tuid(tuid, session) new_user_info = {'user_id': 2123, 'user_type': 'oa'} track.update_focus_track_info( focus_track, focus_track_start_date, focus_track_end_date, new_user_info ) with mysql.db_session() as session: track = TrackQuery.get_by_tuid(tuid, session) assert track.focus_track == focus_track assert track.focus_track_start_date == focus_track_start_date assert track.focus_track_end_date == focus_track_end_date if focus_track == 'Y': assert track._focus_track assert track._focus_track.product_id == track.product_id assert track._focus_track.updated_by_user_type == new_user_info['user_type'] assert track._focus_track.updated_by_user_id == new_user_info['user_id'] assert track._focus_track.created_by_user_type == user_info['user_type'] assert track._focus_track.created_by_user_id == user_info['user_id'] @db.test_schema def test_track_producer_nationality_model(): """Test track_producer_nationality_model model.""" data = track_producer_natl_seed_data[0] tuid = data[tf.TUID] country_id = data['country_id'] with mysql.db_session() as session: track = TrackQuery.get_by_tuid(tuid, session) assert track.tuid == tuid assert track._producer_nationality assert track._producer_nationality.tuid == tuid assert track.original_rights_holder_country_id == country_id track.original_rights_holder_country_id = country_id + 1 with mysql.db_session() as session: track = TrackQuery.get_by_tuid(tuid, session) assert track.original_rights_holder_country_id == country_id + 1 @db.test_schema def test_track_producer_nationality_model_no_row(test_track): """Test populating table works if row doesn't exist.""" tuid = test_track[tf.TUID] with mysql.db_session() as session: track = TrackQuery.get_by_tuid(tuid, session) assert track.tuid == tuid assert track.original_rights_holder_country_id is None assert track._producer_nationality is None track.original_rights_holder_country_id = 1 with mysql.db_session() as session: tuid = test_track[tf.TUID] track = TrackQuery.get_by_tuid(tuid, session) assert track._producer_nationality assert track.original_rights_holder_country_id == 1 @db.test_schema def test_track_producer_nationality_model_delete(): """Test to make sure table row is deleted when value is None.""" data = track_producer_natl_seed_data[0] tuid = data[tf.TUID] country_id = data['country_id'] with mysql.db_session() as session: track = TrackQuery.get_by_tuid(tuid, session) assert track.original_rights_holder_country_id == country_id track.original_rights_holder_country_id = None with mysql.db_session() as session: track = TrackQuery.get_by_tuid(tuid, session) assert track.original_rights_holder_country_id is None assert track._producer_nationality is None @db.test_schema def test_track_artists_relationship(): """Test getting artists for tracks is successful.""" product_id = 1 # A set of tuples for quick lookup artist_set = set( (artist['tuid'], artist['artist_type'], artist['artist_name']) for artist in track_artist_seed_data) tuid_artists_count = defaultdict(int) for artist in track_artist_seed_data: tuid_artists_count[artist['tuid']] += 1 with mysql.db_session() as session: tracks = TrackQuery.get_all_by_product_id(product_id, session) for track in tracks: num_found = 0 for artist in track.artists: tup = (track.tuid, artist.artist_type, artist.artist_name) assert tup in artist_set num_found += 1 assert num_found == tuid_artists_count[track.tuid] @db.test_schema def test_track_publishers_relationship(): """Test getting publishers for tracks is successful.""" product_id = 1 # A set of tuples for quick lookup publisher_set = set( (publisher['tuid'], publisher['publisher_name']) for publisher in track_publisher_seed_data) tuid_publishers_count = defaultdict(int) for publisher in track_publisher_seed_data: tuid_publishers_count[publisher['tuid']] += 1 with mysql.db_session() as session: tracks = TrackQuery.get_all_by_product_id(product_id, session) for track in tracks: num_found = 0 for publisher in track.publishers: tup = (track.tuid, publisher.publisher_name) assert tup in publisher_set num_found += 1 assert num_found == tuid_publishers_count[track.tuid] @db.test_schema def test_track_writers_relationship(): """Test getting writers for tracks is successful.""" product_id = 1 # A set of tuples for quick lookup writer_set = set( (writer['tuid'], writer['writer_name']) for writer in track_writer_seed_data) tuid_writers_count = defaultdict(int) for writer in track_writer_seed_data: tuid_writers_count[writer['tuid']] += 1 with mysql.db_session() as session: tracks = TrackQuery.get_all_by_product_id(product_id, session) for track in tracks: num_found = 0 for writer in track.writers: tup = (track.tuid, writer.writer_name) assert tup in writer_set num_found += 1 assert num_found == tuid_writers_count[track.tuid] @db.test_schema def test_get_roles_by_type(test_track_model_response): """Test getting roles by type.""" tuid = test_track_model_response['tuid'] test_artists = test_track_model_response['artists'] test_writers = test_track_model_response['writers'] test_publishers = test_track_model_response['publishers'] # These assumptions have to be met for the test to be valid assert len(test_artists) == 1 assert len(test_writers) > 0 assert len(test_publishers) > 0 artist_role = test_artists[0]['type'] artist_id = test_artists[0]['track_artist_id'] with mysql.db_session() as session: track = TrackQuery.get_by_tuid(tuid, session) track_roles = track.get_roles_by_type(artist_role) assert len(track_roles) == len(test_artists) assert track_roles[0].track_artist_id == artist_id track_roles = track.get_roles_by_type('publisher') assert len(track_roles) == len(test_publishers) assert track_roles[0].role_type == 'publisher' track_roles = track.get_roles_by_type('writer') assert len(track_roles) == len(test_writers) assert track_roles[0].role_type == 'writer' with pytest.raises(ValueError): track.get_roles_by_type('invalid_role_type') @db.test_schema def test_add_role_and_rename(test_track_model_response): """Test adding roles by type. Also piggybacks the test for setting the role name """ tuid = test_track_model_response['tuid'] with mysql.db_session() as session: track = TrackQuery.get_by_tuid(tuid, session) new_role = track.add_role('performer', 'New Performer') assert new_role.name == 'New Performer' assert new_role.role_type == 'performer' new_role.name = 'Renamed Performer' assert new_role.name == 'Renamed Performer' new_role = track.add_role('publisher', 'New Publisher') assert new_role.name == 'New Publisher' assert new_role.role_type == 'publisher' new_role.name = 'Renamed Publisher' assert new_role.name == 'Renamed Publisher' new_role = track.add_role('writer', 'New Writer') assert new_role.name == 'New Writer' assert new_role.role_type == 'writer' new_role.name = 'Renamed Writer' assert new_role.name == 'Renamed Writer' with pytest.raises(ValueError): track.add_role('invalid_role_type', 'Some Role') @db.test_schema def test_add_role_invalid_artist_name( test_track_model_response, invalid_artist_name): """Test adding artist with invalid name.""" tuid = test_track_model_response['tuid'] with mysql.db_session() as session: track = TrackQuery.get_by_tuid(tuid, session) with pytest.raises(ValueError): track.add_role('performer', invalid_artist_name) def test_add_role_blank_artist_name(track_factory): """Test adding artist with blank name.""" track = track_factory() with pytest.raises(ValueError): track.add_role('performer', ' ') def test_add_role_artist_name_is_trimmed(track_factory): """Test adding artist with extra spaces is trimmed.""" track = track_factory() track.add_role('performer', ' Artist Name ') assert track.artists[0].name == 'Artist Name' @db.test_schema def test_remove_role(test_track_model_response): """Test removing roles.""" tuid = test_track_model_response['tuid'] with mysql.db_session() as session: track = TrackQuery.get_by_tuid(tuid, session) artist = track.artists[0] assert track.remove_role(artist.artist_type, artist) assert artist not in track.artists publisher = track.publishers[0] assert track.remove_role(track_role.PUBLISHERS[0], publisher) assert publisher not in track.publishers writer = track.writers[0] assert track.remove_role(track_role.WRITERS[0], writer) assert writer not in track.writers with pytest.raises(ValueError): track.remove_role('invalid_role_type', artist) @db.test_schema def test_update_artist_info_id_for_artist(test_track_model_response): """Test updating artist_info_id for an artist.""" tuid = test_track_model_response['tuid'] with mysql.db_session() as session: track = TrackQuery.get_by_tuid(tuid, session) artist = track.artists[0] assert not artist.artist_info_id track.update_artist_info_id_for_artist(str(artist.track_artist_id), 1) assert artist.artist_info_id == 1 @db.test_schema def test_update_artist_info_id_for_writer(test_track_model_response): """Test updating artist_info_id for a writer.""" tuid = test_track_model_response['tuid'] with mysql.db_session() as session: track = TrackQuery.get_by_tuid(tuid, session) writer = track.writers[0] assert not writer.artist_info_id track.update_artist_info_id_for_writer(str(writer.track_writer_id), 1) assert writer.artist_info_id == 1 @db.test_schema def test_sync_role_for_track_more_writer_roles(test_track): """Test syncing writers for track works with an additional writer.""" tuid = test_track[tf.TUID] new_writers = ['Writer 1', 'Writer 2'] with mysql.db_session() as session: track = TrackQuery.get_by_tuid(tuid, session) assert len(track.writers) == 1 track.sync_role('writer', new_writers) with mysql.db_session() as session: track = TrackQuery.get_by_tuid(tuid, session) assert len(track.writers) == 2 assert track.writers[0].writer_name == new_writers[0] assert track.writers[1].writer_name == new_writers[1] @db.test_schema def test_sync_role_for_track_less_writer_roles(): """Test syncing writers for track works with one less writer.""" tuid = 1 new_writers = ['Writer 1'] with mysql.db_session() as session: track = TrackQuery.get_by_tuid(tuid, session) assert len(track.writers) == 2 track.sync_role('writer', new_writers) with mysql.db_session() as session: track = TrackQuery.get_by_tuid(tuid, session) assert len(track.writers) == 1 assert track.writers[0].writer_name == new_writers[0] def test_sync_role_for_track_duplicate_roles(track_factory): """Test syncing role for track fails when duplicate names are given.""" track = track_factory( tuid=1, performer__count=2, writers__count=2, publishers__count=2) role_names = ['Lap', ' Lap '] with pytest.raises(IntegrityError): track.sync_role('performer', role_names) with pytest.raises(IntegrityError): track.sync_role('writer', role_names) with pytest.raises(IntegrityError): track.sync_role('publisher', role_names) def test_sync_role_for_track_empty_role(track_factory): """Test syncing writers for track fails when blank value is used.""" track = track_factory( tuid=1, performer__count=2, writers__count=2, publishers__count=2) role_names = ['Lap', ' '] with pytest.raises(ValueError): track.sync_role('performer', role_names) with pytest.raises(ValueError): track.sync_role('writer', role_names) with pytest.raises(ValueError): track.sync_role('publisher', role_names) def test_sync_role_for_track_invalid_artist_role(track_factory): """Test syncing artists for track fails when invalid value is used. Sync has its own checks when overwriting an existing artist name. """ track = track_factory(tuid=1, performer__count=2) artist_names = ['Lap', 'Various Artists'] with pytest.raises(ValueError): track.sync_role('performer', artist_names) @db.test_schema def test_track_to_dict_filter(test_track): """Test to_dict filtering works.""" tuid = test_track[tf.TUID] with mysql.db_session() as session: track = TrackQuery.get_by_tuid(tuid, session) fields = [tf.UPC, tf.WRITERS] track_dict = track.to_dict(filter_by_fields=fields) assert tf.TUID in track_dict for field in fields: assert field in track_dict assert len(track_dict) == len(fields) + 1 @db.test_schema def test_track_has_preview_start_time_column(): """Track model should have preview start time column.""" track_data = { 'tuid': 100, 'product_id': 1, 'upc': 1, 'volume_number': 1, 'track_number': 1, 'preview_start_time': 10 } with mysql.db_session() as session: track = Track(**track_data) session.add(track) with mysql.db_session() as session: saved_track = TrackQuery.get_by_tuid(100, session) assert saved_track.preview_start_time == 10 def test_track_duration_can_be_updated(track_factory): """Track model should have duration column that can be updated.""" track = track_factory() assert track.duration_minutes is None assert track.duration_seconds is None track.duration_minutes = 0 track.duration_seconds = 0 assert track.duration_minutes == 0 assert track.duration_seconds == 0 track.duration_minutes = 1 track.duration_seconds = 59 assert track.duration_minutes == 1 assert track.duration_seconds == 59 def test_track_duration_validators_invalid(track_factory): """Track model cannot have invalid duration values.""" track = track_factory() with pytest.raises(ValueError): track.duration_minutes = -1 with pytest.raises(ValueError): track.duration_seconds = -1 with pytest.raises(ValueError): track.duration_seconds = 60 @db.test_schema def test_to_dict_has_preview_start_time_column(): """Track.to_dict() should include preview_start_time.""" with mysql.db_session() as session: track = TrackQuery.get_by_tuid(1, session) assert 'preview_start_time' in track.to_dict() @db.test_schema def test_to_dict_has_duration_info(track_factory): """Track.to_dict() should include duration_minutes and duration_seconds.""" track = track_factory().to_dict() assert tf.DURATION_MINUTES in track assert tf.DURATION_SECONDS in track @db.test_schema_no_seed def test_track_artist_to_dict_with_tuid(track_factory): """Track Artist dict includes tuid.""" with mysql.db_session() as session: track = track_factory(performer__count=1) session.merge(track) session.commit() assert tf.TUID in track.artists[0].to_dict(include_tuid=True) @db.test_schema def test_set_artist_info_ids_ok(test_track_model_response): """Test updating artist_info_ids for track artists and writer.""" tuid = test_track_model_response['tuid'] with mysql.db_session() as session: track = TrackQuery.get_by_tuid(tuid, session) track.set_artist_info_ids({ 'artists': { ('performer', 'Snowball'): 101 }, 'writers': { ('Furball',): 201 } }) assert track.artists[0].artist_info_id == 101 assert track.writers[0].artist_info_id == 201 @patch('backend.models.track.get_session') def test_get_tracks_by_osrid(mock_neo4j_get_session, make_graph_node): """Test getting tracks by OSR ID.""" mock_neo4j_session = mock_neo4j_get_session.return_value mock_neo4j_results = mock_neo4j_session.run.return_value mock_neo4j_results.__iter__.return_value = iter([{ 'tracks': [ make_graph_node('Track', {'id': 'track1', 'isrc': 'US1A1234567'}), make_graph_node('Track', {'id': 'track2', 'isrc': 'US1B1234567'}) ], 'totalCount': 2 }]) osr_id = '123-abc' offset = 0 limit = 20 result = track_model.get_tracks_by_osrid(osr_id, offset, limit) assert mock_neo4j_session.run.called assert len(result['items']) == 2 assert result['total_records'] == 2