import pytest from faker import Faker from dmp.artists.enums import ParticipantType from dmp.artists.handlers import GetParticipantsHandler, GetParticipantsRequest from dmp.artists.models import VirtualParticipant from dmp.fandata.models import FansByArtistAccountDbt, GlobalFansByArtistDbt from dmp.rosters.models import ( ArtistRosterMainRep, CustomList, GlobalParticipantAccountDbt, ) from tests.unit.types import CreateReportingModel class TestGetParticipantsHandler: @pytest.mark.db def test_get_participants_empty(self, handler: GetParticipantsHandler) -> None: response = handler.handle( GetParticipantsRequest( vendor_id=1, subaccount_id=1, limit=50, offset=0, ) ) assert response.total == 0 assert response.items == [] @pytest.mark.db def test_get_participants_complex( self, handler: GetParticipantsHandler, create_reporting_model: CreateReportingModel, faker: Faker, ) -> None: vendor_id = 100 subaccount_id = 200 global_participant_id_1 = faker.uuid4() global_participant_id_2 = faker.uuid4() create_reporting_model( ArtistRosterMainRep, vendor_id=vendor_id, subaccount_id=subaccount_id, global_participant_id=global_participant_id_1, ) create_reporting_model( GlobalFansByArtistDbt, global_participant_id=global_participant_id_1, global_participant_name="Global Artist", ) create_reporting_model( GlobalParticipantAccountDbt, id=global_participant_id_1, spotify_id="spotify:artist:global1", vendor_id=vendor_id, subaccount_id=subaccount_id, ) create_reporting_model( FansByArtistAccountDbt, global_participant_id=global_participant_id_2, global_participant_name="Account Artist", vendor_id=vendor_id, subaccount_id=subaccount_id, ) create_reporting_model( GlobalParticipantAccountDbt, id=global_participant_id_2, spotify_id="spotify:artist:account1", vendor_id=vendor_id, subaccount_id=subaccount_id, ) virtual_participant = create_reporting_model( VirtualParticipant, name="Virtual Artist", spotify_id="spotify:artist:virtual1", ) create_reporting_model( CustomList, vendor_id=vendor_id, subaccount_id=subaccount_id, virtual_participant_id=virtual_participant.id, ) response = handler.handle( GetParticipantsRequest( vendor_id=vendor_id, subaccount_id=subaccount_id, limit=50, offset=0, ) ) assert response.total == 3 assert len(response.items) == 3 global_artist = next( p for p in response.items if p.spotify_id == "spotify:artist:global1" ) assert global_artist.name == "Global Artist" assert global_artist.global_participant_id == global_participant_id_1 assert global_artist.virtual_participant_id is None assert len(global_artist.labels) == 1 account_artist = next( p for p in response.items if p.spotify_id == "spotify:artist:account1" ) assert account_artist.name == "Account Artist" assert account_artist.global_participant_id == global_participant_id_2 assert account_artist.virtual_participant_id is None assert len(account_artist.labels) == 1 virtual_artist = next( p for p in response.items if p.virtual_participant_id == virtual_participant.id ) assert virtual_artist.name == "Virtual Artist" assert virtual_artist.global_participant_id is None assert virtual_artist.spotify_id == "spotify:artist:virtual1" assert len(virtual_artist.labels) == 1 @pytest.mark.db def test_subaccount_zero_filters_by_vendor_only( self, handler: GetParticipantsHandler, create_reporting_model: CreateReportingModel, faker: Faker, ) -> None: subaccount_id = 0 requested_vendor_id = 100 other_vendor_id = 999 requested_gp_id = faker.uuid4() other_gp_id = faker.uuid4() create_reporting_model( ArtistRosterMainRep, vendor_id=requested_vendor_id, subaccount_id=subaccount_id, global_participant_id=requested_gp_id, ) create_reporting_model( GlobalFansByArtistDbt, global_participant_id=requested_gp_id, global_participant_name="Requested Vendor Artist", ) create_reporting_model( GlobalParticipantAccountDbt, id=requested_gp_id, spotify_id="spotify:artist:requested", vendor_id=requested_vendor_id, subaccount_id=subaccount_id, ) create_reporting_model( ArtistRosterMainRep, vendor_id=other_vendor_id, subaccount_id=subaccount_id, global_participant_id=other_gp_id, ) create_reporting_model( GlobalFansByArtistDbt, global_participant_id=other_gp_id, global_participant_name="Other Vendor Artist", ) create_reporting_model( GlobalParticipantAccountDbt, id=other_gp_id, spotify_id="spotify:artist:other", vendor_id=other_vendor_id, subaccount_id=subaccount_id, ) response = handler.handle( GetParticipantsRequest( vendor_id=requested_vendor_id, subaccount_id=subaccount_id, limit=50, offset=0, ) ) assert response.total == 1 assert len(response.items) == 1 assert response.items[0].global_participant_id == requested_gp_id assert response.items[0].name == "Requested Vendor Artist" @pytest.mark.db def test_virtual_participant_without_custom_list_not_included( self, handler: GetParticipantsHandler, create_reporting_model: CreateReportingModel, ) -> None: vendor_id = 100 subaccount_id = 200 create_reporting_model( VirtualParticipant, name="Orphan Virtual Artist", spotify_id="spotify:artist:orphan", ) response = handler.handle( GetParticipantsRequest( vendor_id=vendor_id, subaccount_id=subaccount_id, limit=50, offset=0, ) ) assert response.total == 0 assert response.items == [] @pytest.mark.db def test_virtual_participant_without_spotify_id_but_with_custom_list( self, handler: GetParticipantsHandler, create_reporting_model: CreateReportingModel, ) -> None: vendor_id = 100 subaccount_id = 200 virtual_participant = create_reporting_model( VirtualParticipant, name="No Spotify Virtual Artist", spotify_id=None, ) create_reporting_model( CustomList, vendor_id=vendor_id, subaccount_id=subaccount_id, virtual_participant_id=virtual_participant.id, ) response = handler.handle( GetParticipantsRequest( vendor_id=vendor_id, subaccount_id=subaccount_id, limit=50, offset=0, ) ) assert response.total == 1 assert len(response.items) == 1 participant = response.items[0] assert participant.name == "No Spotify Virtual Artist" assert participant.global_participant_id is None assert participant.spotify_id is None assert participant.virtual_participant_id == virtual_participant.id assert len(participant.labels) == 1 @pytest.mark.db def test_virtual_participant_with_different_vendor_not_included( self, handler: GetParticipantsHandler, create_reporting_model: CreateReportingModel, ) -> None: vendor_id = 100 subaccount_id = 200 different_vendor_id = 999 virtual_participant = create_reporting_model( VirtualParticipant, name="Different Vendor Artist", spotify_id="spotify:artist:different", ) create_reporting_model( CustomList, vendor_id=different_vendor_id, subaccount_id=subaccount_id, virtual_participant_id=virtual_participant.id, ) response = handler.handle( GetParticipantsRequest( vendor_id=vendor_id, subaccount_id=subaccount_id, limit=50, offset=0, ) ) assert response.total == 0 assert response.items == [] @pytest.mark.db def test_virtual_participant_multiple_custom_lists_same_vendor( self, handler: GetParticipantsHandler, create_reporting_model: CreateReportingModel, ) -> None: vendor_id = 100 subaccount_id_1 = 200 subaccount_id_2 = 201 virtual_participant = create_reporting_model( VirtualParticipant, name="Multi List Artist", spotify_id="spotify:artist:multi", ) create_reporting_model( CustomList, vendor_id=vendor_id, subaccount_id=subaccount_id_1, virtual_participant_id=virtual_participant.id, ) create_reporting_model( CustomList, vendor_id=vendor_id, subaccount_id=subaccount_id_2, virtual_participant_id=virtual_participant.id, ) response = handler.handle( GetParticipantsRequest( vendor_id=vendor_id, subaccount_id=None, limit=50, offset=0, ) ) assert response.total == 1 assert len(response.items) == 1 participant = response.items[0] assert participant.name == "Multi List Artist" assert participant.global_participant_id is None assert participant.virtual_participant_id == virtual_participant.id assert len(participant.labels) == 2 label_tuples = { (label.vendor_id, label.subaccount_id) for label in participant.labels } assert label_tuples == { (vendor_id, subaccount_id_1), (vendor_id, subaccount_id_2), } @pytest.mark.db def test_get_all_participants_without_filters( self, handler: GetParticipantsHandler, create_reporting_model: CreateReportingModel, faker: Faker, ) -> None: vendor_id_1 = 100 vendor_id_2 = 200 subaccount_id_1 = 300 subaccount_id_2 = 400 global_participant_id = faker.uuid4() create_reporting_model( ArtistRosterMainRep, vendor_id=vendor_id_1, subaccount_id=subaccount_id_1, global_participant_id=global_participant_id, ) create_reporting_model( GlobalFansByArtistDbt, global_participant_id=global_participant_id, global_participant_name="Global Artist", ) create_reporting_model( GlobalParticipantAccountDbt, id=global_participant_id, spotify_id="spotify:artist:global1", vendor_id=vendor_id_1, subaccount_id=subaccount_id_1, ) virtual_participant = create_reporting_model( VirtualParticipant, name="Virtual Artist", spotify_id="spotify:artist:virtual1", ) create_reporting_model( CustomList, vendor_id=vendor_id_2, subaccount_id=subaccount_id_2, virtual_participant_id=virtual_participant.id, ) response = handler.handle( GetParticipantsRequest( vendor_id=None, subaccount_id=None, limit=50, offset=0, ) ) assert response.total >= 2 assert len(response.items) >= 2 participant_names = {p.name for p in response.items} assert "Global Artist" in participant_names assert "Virtual Artist" in participant_names @pytest.mark.db("reporting") def test_virtual_participant_merged_with_global_by_spotify_id( self, handler: GetParticipantsHandler, create_reporting_model: CreateReportingModel, faker: Faker, ) -> None: vendor_id = 100 subaccount_id = 200 virtual_vendor_id = 300 virtual_subaccount_id = 400 shared_spotify_id = "6vWDO969PvNqNYHIOW5v0m" global_participant_id = faker.uuid4() # Create global participant with spotify_id create_reporting_model( ArtistRosterMainRep, vendor_id=vendor_id, subaccount_id=subaccount_id, global_participant_id=global_participant_id, ) create_reporting_model( GlobalFansByArtistDbt, global_participant_id=global_participant_id, global_participant_name="Released Artist", ) create_reporting_model( GlobalParticipantAccountDbt, id=global_participant_id, spotify_id=shared_spotify_id, vendor_id=vendor_id, subaccount_id=subaccount_id, ) # Create virtual participant with same spotify_id virtual_participant = create_reporting_model( VirtualParticipant, name="Released Artist (pending)", spotify_id=shared_spotify_id, ) create_reporting_model( CustomList, vendor_id=virtual_vendor_id, subaccount_id=virtual_subaccount_id, virtual_participant_id=virtual_participant.id, ) response = handler.handle( GetParticipantsRequest( vendor_id=None, subaccount_id=None, limit=50, offset=0, ) ) # Should merge into one record assert response.total == 1 assert len(response.items) == 1 participant = response.items[0] assert participant.global_participant_id == global_participant_id assert participant.virtual_participant_id == virtual_participant.id assert participant.spotify_id == shared_spotify_id # Name comes from global participant (authoritative) assert participant.name == "Released Artist" # Labels merged from both global and virtual sides assert len(participant.labels) == 2 label_tuples = { (label.vendor_id, label.subaccount_id) for label in participant.labels } assert label_tuples == { (vendor_id, subaccount_id), (virtual_vendor_id, virtual_subaccount_id), } @pytest.mark.db("reporting") def test_two_virtual_participants_same_spotify_id_merged_with_global( self, handler: GetParticipantsHandler, create_reporting_model: CreateReportingModel, faker: Faker, ) -> None: vendor_id = 100 subaccount_id = 200 shared_spotify_id = "6vWDO969PvNqNYHIOW5v0m" global_participant_id = faker.uuid4() # Create global participant create_reporting_model( ArtistRosterMainRep, vendor_id=vendor_id, subaccount_id=subaccount_id, global_participant_id=global_participant_id, ) create_reporting_model( GlobalFansByArtistDbt, global_participant_id=global_participant_id, global_participant_name="Shared Artist", ) create_reporting_model( GlobalParticipantAccountDbt, id=global_participant_id, spotify_id=shared_spotify_id, vendor_id=vendor_id, subaccount_id=subaccount_id, ) # Create two virtual participants with same spotify_id vp1 = create_reporting_model( VirtualParticipant, name="Pending Artist Copy 1", spotify_id=shared_spotify_id, ) create_reporting_model( CustomList, vendor_id=vendor_id, subaccount_id=300, virtual_participant_id=vp1.id, ) vp2 = create_reporting_model( VirtualParticipant, name="Pending Artist Copy 2", spotify_id=shared_spotify_id, ) create_reporting_model( CustomList, vendor_id=vendor_id, subaccount_id=400, virtual_participant_id=vp2.id, ) response = handler.handle( GetParticipantsRequest( vendor_id=vendor_id, subaccount_id=None, limit=50, offset=0, ) ) # Two records: each virtual participant gets its own row, both with same global_participant_id merged_items = [p for p in response.items if p.spotify_id == shared_spotify_id] assert len(merged_items) == 2 virtual_ids = {p.virtual_participant_id for p in merged_items} assert virtual_ids == {vp1.id, vp2.id} for item in merged_items: assert item.global_participant_id == global_participant_id assert item.spotify_id == shared_spotify_id @pytest.mark.db("reporting") def test_virtual_participant_with_spotify_id_no_global_match( self, handler: GetParticipantsHandler, create_reporting_model: CreateReportingModel, ) -> None: vendor_id = 100 subaccount_id = 200 virtual_participant = create_reporting_model( VirtualParticipant, name="Unreleased Artist", spotify_id="4iV5W9uYEdYUVa79Axb7Rh", ) create_reporting_model( CustomList, vendor_id=vendor_id, subaccount_id=subaccount_id, virtual_participant_id=virtual_participant.id, ) response = handler.handle( GetParticipantsRequest( vendor_id=vendor_id, subaccount_id=subaccount_id, limit=50, offset=0, ) ) assert response.total == 1 assert len(response.items) == 1 participant = response.items[0] assert participant.global_participant_id is None assert participant.virtual_participant_id == virtual_participant.id assert participant.spotify_id == "4iV5W9uYEdYUVa79Axb7Rh" assert participant.name == "Unreleased Artist" @pytest.mark.db("reporting") def test_merged_participant_count_not_double_counted( self, handler: GetParticipantsHandler, create_reporting_model: CreateReportingModel, faker: Faker, ) -> None: vendor_id = 100 subaccount_id = 200 shared_spotify_id = "6vWDO969PvNqNYHIOW5v0m" global_participant_id = faker.uuid4() create_reporting_model( ArtistRosterMainRep, vendor_id=vendor_id, subaccount_id=subaccount_id, global_participant_id=global_participant_id, ) create_reporting_model( GlobalFansByArtistDbt, global_participant_id=global_participant_id, global_participant_name="Merged Artist", ) create_reporting_model( GlobalParticipantAccountDbt, id=global_participant_id, spotify_id=shared_spotify_id, vendor_id=vendor_id, subaccount_id=subaccount_id, ) virtual_participant = create_reporting_model( VirtualParticipant, name="Merged Artist (pending)", spotify_id=shared_spotify_id, ) create_reporting_model( CustomList, vendor_id=vendor_id, subaccount_id=subaccount_id, virtual_participant_id=virtual_participant.id, ) response = handler.handle( GetParticipantsRequest( vendor_id=vendor_id, subaccount_id=subaccount_id, limit=50, offset=0, ) ) # Merged = 1 record, not 2 assert response.total == 1 assert len(response.items) == 1 assert response.items[0].global_participant_id == global_participant_id assert response.items[0].virtual_participant_id == virtual_participant.id @pytest.mark.db("reporting") def test_filter_type_artist( self, handler: GetParticipantsHandler, create_reporting_model: CreateReportingModel, faker: Faker, ) -> None: vendor_id = 100 subaccount_id = 200 global_participant_id = faker.uuid4() create_reporting_model( ArtistRosterMainRep, vendor_id=vendor_id, subaccount_id=subaccount_id, global_participant_id=global_participant_id, ) create_reporting_model( GlobalFansByArtistDbt, global_participant_id=global_participant_id, global_participant_name="Global Artist", ) create_reporting_model( GlobalParticipantAccountDbt, id=global_participant_id, spotify_id="06HL4z0CvFAxyc27GXpf02", vendor_id=vendor_id, subaccount_id=subaccount_id, ) virtual_participant = create_reporting_model( VirtualParticipant, name="Custom List Only", spotify_id=None, ) create_reporting_model( CustomList, vendor_id=vendor_id, subaccount_id=subaccount_id, virtual_participant_id=virtual_participant.id, ) response = handler.handle( GetParticipantsRequest( vendor_id=vendor_id, subaccount_id=subaccount_id, search=None, limit=50, offset=0, participant_type=ParticipantType.ARTIST, ) ) assert response.total == 1 assert len(response.items) == 1 assert response.items[0].global_participant_id == global_participant_id assert response.items[0].name == "Global Artist" @pytest.mark.db("reporting") def test_filter_type_custom_list( self, handler: GetParticipantsHandler, create_reporting_model: CreateReportingModel, faker: Faker, ) -> None: vendor_id = 100 subaccount_id = 200 global_participant_id = faker.uuid4() create_reporting_model( ArtistRosterMainRep, vendor_id=vendor_id, subaccount_id=subaccount_id, global_participant_id=global_participant_id, ) create_reporting_model( GlobalFansByArtistDbt, global_participant_id=global_participant_id, global_participant_name="Global Artist", ) create_reporting_model( GlobalParticipantAccountDbt, id=global_participant_id, spotify_id="06HL4z0CvFAxyc27GXpf02", vendor_id=vendor_id, subaccount_id=subaccount_id, ) virtual_participant = create_reporting_model( VirtualParticipant, name="Custom List Only", spotify_id=None, ) create_reporting_model( CustomList, vendor_id=vendor_id, subaccount_id=subaccount_id, virtual_participant_id=virtual_participant.id, ) response = handler.handle( GetParticipantsRequest( vendor_id=vendor_id, subaccount_id=subaccount_id, search=None, limit=50, offset=0, participant_type=ParticipantType.CUSTOM_LIST, ) ) assert response.total == 1 assert len(response.items) == 1 assert response.items[0].virtual_participant_id == virtual_participant.id assert response.items[0].global_participant_id is None assert response.items[0].name == "Custom List Only" @pytest.mark.db("reporting") def test_merged_record_is_artist_not_custom_list( self, handler: GetParticipantsHandler, create_reporting_model: CreateReportingModel, faker: Faker, ) -> None: vendor_id = 100 subaccount_id = 200 shared_spotify_id = "6vWDO969PvNqNYHIOW5v0m" global_participant_id = faker.uuid4() create_reporting_model( ArtistRosterMainRep, vendor_id=vendor_id, subaccount_id=subaccount_id, global_participant_id=global_participant_id, ) create_reporting_model( GlobalFansByArtistDbt, global_participant_id=global_participant_id, global_participant_name="Merged Artist", ) create_reporting_model( GlobalParticipantAccountDbt, id=global_participant_id, spotify_id=shared_spotify_id, vendor_id=vendor_id, subaccount_id=subaccount_id, ) virtual_participant = create_reporting_model( VirtualParticipant, name="Merged Artist (pending)", spotify_id=shared_spotify_id, ) create_reporting_model( CustomList, vendor_id=vendor_id, subaccount_id=subaccount_id, virtual_participant_id=virtual_participant.id, ) artist_response = handler.handle( GetParticipantsRequest( vendor_id=vendor_id, subaccount_id=subaccount_id, search=None, limit=50, offset=0, participant_type=ParticipantType.ARTIST, ) ) assert artist_response.total == 1 assert artist_response.items[0].global_participant_id == global_participant_id assert artist_response.items[0].virtual_participant_id == virtual_participant.id custom_list_response = handler.handle( GetParticipantsRequest( vendor_id=vendor_id, subaccount_id=subaccount_id, search=None, limit=50, offset=0, participant_type=ParticipantType.CUSTOM_LIST, ) ) assert custom_list_response.total == 0 assert custom_list_response.items == [] @pytest.mark.db("reporting") def test_filter_virtual_participant_ids( self, handler: GetParticipantsHandler, create_reporting_model: CreateReportingModel, ) -> None: vendor_id = 100 subaccount_id = 200 vp1 = create_reporting_model( VirtualParticipant, name="Target VP", spotify_id=None, ) create_reporting_model( CustomList, vendor_id=vendor_id, subaccount_id=subaccount_id, virtual_participant_id=vp1.id, ) vp2 = create_reporting_model( VirtualParticipant, name="Other VP", spotify_id=None, ) create_reporting_model( CustomList, vendor_id=vendor_id, subaccount_id=subaccount_id, virtual_participant_id=vp2.id, ) response = handler.handle( GetParticipantsRequest( vendor_id=vendor_id, subaccount_id=subaccount_id, search=None, limit=50, offset=0, virtual_participant_ids=[vp1.id], ) ) assert response.total == 1 assert len(response.items) == 1 assert response.items[0].virtual_participant_id == vp1.id assert response.items[0].name == "Target VP" @pytest.mark.db("reporting") def test_filter_virtual_participant_ids_with_type_custom_list( self, handler: GetParticipantsHandler, create_reporting_model: CreateReportingModel, faker: Faker, ) -> None: vendor_id = 100 subaccount_id = 200 global_participant_id = faker.uuid4() create_reporting_model( ArtistRosterMainRep, vendor_id=vendor_id, subaccount_id=subaccount_id, global_participant_id=global_participant_id, ) create_reporting_model( GlobalFansByArtistDbt, global_participant_id=global_participant_id, global_participant_name="Global Artist", ) create_reporting_model( GlobalParticipantAccountDbt, id=global_participant_id, spotify_id="06HL4z0CvFAxyc27GXpf02", vendor_id=vendor_id, subaccount_id=subaccount_id, ) vp1 = create_reporting_model( VirtualParticipant, name="Target VP", spotify_id=None, ) create_reporting_model( CustomList, vendor_id=vendor_id, subaccount_id=subaccount_id, virtual_participant_id=vp1.id, ) vp2 = create_reporting_model( VirtualParticipant, name="Other VP", spotify_id=None, ) create_reporting_model( CustomList, vendor_id=vendor_id, subaccount_id=subaccount_id, virtual_participant_id=vp2.id, ) response = handler.handle( GetParticipantsRequest( vendor_id=vendor_id, subaccount_id=subaccount_id, search=None, limit=50, offset=0, participant_type=ParticipantType.CUSTOM_LIST, virtual_participant_ids=[vp1.id], ) ) assert response.total == 1 assert len(response.items) == 1 assert response.items[0].virtual_participant_id == vp1.id