"""Tests for contributor Neo4j queries.""" from unittest.mock import MagicMock import pytest from contributor.queries.neo4j.contributors import ( check_vendor_access, create, get_by_uuids, get_contributors_for_global_participant, get_gp_by_id, get_gp_by_ids, has_full_catalog_access, rename, ) @pytest.fixture def lp_props(): return { "id": 42, "uuid": "550e8400-e29b-41d4-a716-446655440000", "name": "Test Artist", "spotifyId": "3TVXtAsR1Inumwj472S9r4", "appleMusicId": None, "spotifyArtistKey": None, "isni": None, "vendorId": 100, "subaccountId": None, "vendorUUID": "aaaa-bbbb-cccc-dddd", "subaccountUUID": None, "companyBrandUUID": "1111-2222-3333-4444", "parentCompanyUUID": "5555-6666-7777-8888", } @pytest.fixture def global_participant_props(): return { "id": "b1a2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", "name": "Test Artist Global", "spotifyId": "4iV5W9uYEdYUVa79Axb7Rh", "appleMusicId": "1234567890", "chartmetricId": "987654", "imageUrl": "https://example.com/artists/test-artist.jpg", "monthlyListeners": 7654321, } @pytest.fixture def expected_global_participant(): return { "id": "b1a2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", "name": "Test Artist Global", "spotify_id": "4iV5W9uYEdYUVa79Axb7Rh", "apple_music_id": "1234567890", "chartmetric_id": "987654", "image_url": "https://example.com/artists/test-artist.jpg", "monthly_listeners": 7654321, } @pytest.fixture def label_node_props(): return { "uuid": "cccc0000-dddd-4eee-8fff-aaaaaaaaaaaa", "vendorId": 100, "subaccountId": 0, } @pytest.fixture def expected_label(): return { "uuid": "cccc0000-dddd-4eee-8fff-aaaaaaaaaaaa", "vendor_id": 100, "subaccount_id": 0, } @pytest.fixture def expected_contributor(expected_label): return { "id": 42, "uuid": "550e8400-e29b-41d4-a716-446655440000", "name": "Test Artist", "spotify_id": "3TVXtAsR1Inumwj472S9r4", "apple_music_id": None, "spotify_artist_key": None, "isni": None, "company_brand_uuid": "1111-2222-3333-4444", "parent_company_uuid": "5555-6666-7777-8888", "artist_info_ids": [], "global_participant": {"id": "b1a2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d"}, "label": expected_label, } @pytest.fixture def mock_lp(lp_props): lp = MagicMock() lp.__getitem__ = lambda self, key: lp_props[key] lp.get = lambda key, default=None: lp_props.get(key, default) return lp @pytest.fixture def mock_global_participant(global_participant_props): global_participant = MagicMock() global_participant.__getitem__ = lambda self, key: global_participant_props[key] global_participant.get = lambda key, default=None: global_participant_props.get( key, default ) return global_participant @pytest.fixture def bulk_record(mock_lp, mock_global_participant, label_node_props): record = MagicMock() record.__getitem__ = lambda self, key: { "lp": mock_lp, "artistInfoIds": [], "gp": mock_global_participant, "monthlyListeners": 7654321, "label": label_node_props, }[key] return record @pytest.fixture def bulk_session(bulk_record): session = MagicMock() result = MagicMock() result.__iter__ = lambda self: iter([bulk_record]) session.run.return_value = result return session @pytest.fixture def session(mock_lp, mock_global_participant, label_node_props): """Two-query session used by create/update paths.""" session = MagicMock() lp_result = MagicMock() lp_record = MagicMock() lp_record.__getitem__ = lambda self, key: { "lp": mock_lp, "artistInfoIds": [], "gp": mock_global_participant, "monthlyListeners": 7654321, }[key] lp_result.single.return_value = lp_record label_result = MagicMock() label_record = MagicMock() label_record.__getitem__ = lambda self, key: {"label": label_node_props}[key] label_result.single.return_value = label_record session.run.side_effect = [lp_result, label_result] return session @pytest.fixture def empty_session(): session = MagicMock() result = MagicMock() result.single.return_value = None session.run.return_value = result return session @pytest.fixture def create_kwargs(): return { "vendor_id": 100, "subaccount_id": None, "name": "Test Artist", "spotify_id": "3TVXtAsR1Inumwj472S9r4", "apple_music_id": None, "spotify_artist_key": None, "isni": None, "global_participant_uuid": None, "created_by": "ows-contributor/ca0b8e48-fa8c-4352-a52e-fcab9ec4ba60", } @pytest.fixture def access_session(): session = MagicMock() result = MagicMock() record = MagicMock() record.__getitem__ = lambda self, key: True result.single.return_value = record session.run.return_value = result return session @pytest.fixture def profiles(): return [{"profile_type": "ContentApp", "profile_id": 99, "roles": []}] @pytest.fixture def full_access_session(): session = MagicMock() result = MagicMock() record = MagicMock() record.__getitem__ = lambda self, key: True result.single.return_value = record session.run.return_value = result return session @pytest.fixture def no_full_access_session(): session = MagicMock() result = MagicMock() record = MagicMock() record.__getitem__ = lambda self, key: False result.single.return_value = record session.run.return_value = result return session class TestHasFullCatalogAccess: def test_returns_true_when_profile_has_full_access( self, full_access_session, profiles ) -> None: assert has_full_catalog_access(full_access_session, profiles) is True def test_returns_false_when_no_profile_has_full_access( self, no_full_access_session, profiles ) -> None: assert has_full_catalog_access(no_full_access_session, profiles) is False def test_returns_false_when_no_record(self, empty_session, profiles) -> None: assert has_full_catalog_access(empty_session, profiles) is False class TestCheckVendorAccess: def test_returns_true_when_authorized(self, access_session) -> None: assert ( check_vendor_access(access_session, profile_ids=[7123], vendor_id=100) is True ) def test_returns_false_when_not_authorized(self, empty_session) -> None: result = MagicMock() record = MagicMock() record.__getitem__ = lambda self, key: False result.single.return_value = record empty_session.run.return_value = result assert ( check_vendor_access(empty_session, profile_ids=[9999], vendor_id=100) is False ) def test_returns_false_when_no_record(self, empty_session) -> None: assert ( check_vendor_access(empty_session, profile_ids=[], vendor_id=100) is False ) @pytest.fixture def lp_props_int_ids(lp_props): return { **lp_props, "appleMusicId": 1457615237, "spotifyArtistKey": "A1B2C-D3E4F-G5H6I-J7K8L-M9N0P", } @pytest.fixture def bulk_session_int_ids(lp_props_int_ids, label_node_props): lp = MagicMock() lp.__getitem__ = lambda self, key: lp_props_int_ids[key] lp.get = lambda key, default=None: lp_props_int_ids.get(key, default) record = MagicMock() record.__getitem__ = lambda self, key: { "lp": lp, "artistInfoIds": [], "gp": None, "monthlyListeners": None, "label": label_node_props, }[key] session = MagicMock() result = MagicMock() result.__iter__ = lambda self: iter([record]) session.run.return_value = result return session class TestGetByUuids: def test_returns_contributor(self, bulk_session, expected_contributor) -> None: results = get_by_uuids( bulk_session, uuids=["550e8400-e29b-41d4-a716-446655440000"], profile_ids=[7123], ) assert results == [expected_contributor] def test_returns_contributor_as_admin( self, bulk_session, expected_contributor ) -> None: results = get_by_uuids( bulk_session, uuids=["550e8400-e29b-41d4-a716-446655440000"], profile_ids=[], is_admin=True, ) assert results == [expected_contributor] def test_returns_multiple_contributors( self, mock_lp, mock_global_participant, label_node_props, expected_contributor ) -> None: second_record = MagicMock() second_record.__getitem__ = lambda self, key: { "lp": mock_lp, "artistInfoIds": [], "gp": mock_global_participant, "monthlyListeners": 7654321, "label": label_node_props, }[key] session = MagicMock() result = MagicMock() result.__iter__ = lambda self: iter( [ MagicMock( __getitem__=lambda s, k: { "lp": mock_lp, "artistInfoIds": [], "gp": mock_global_participant, "monthlyListeners": 7654321, "label": label_node_props, }[k] ), second_record, ] ) session.run.return_value = result results = get_by_uuids( session, uuids=[ "550e8400-e29b-41d4-a716-446655440000", "550e8400-e29b-41d4-a716-446655440000", ], profile_ids=[7123], ) assert len(results) == 2 def test_returns_none_global_participant_when_not_linked( self, mock_lp, label_node_props ) -> None: record = MagicMock() record.__getitem__ = lambda self, key: { "lp": mock_lp, "artistInfoIds": [], "gp": None, "monthlyListeners": 0, "label": label_node_props, }[key] session = MagicMock() result = MagicMock() result.__iter__ = lambda self: iter([record]) session.run.return_value = result results = get_by_uuids( session, uuids=["550e8400-e29b-41d4-a716-446655440000"], profile_ids=[7123], ) assert len(results) == 1 assert results[0]["global_participant"] is None def test_coerces_integer_id_fields_to_strings( self, bulk_session_int_ids, expected_label ) -> None: results = get_by_uuids( bulk_session_int_ids, uuids=["550e8400-e29b-41d4-a716-446655440000"], profile_ids=[7123], ) assert len(results) == 1 assert results[0]["apple_music_id"] == "1457615237" assert results[0]["spotify_artist_key"] == "A1B2C-D3E4F-G5H6I-J7K8L-M9N0P" def test_uses_owner_vendor_id_when_label_vendor_id_is_missing( self, mock_lp ) -> None: record = MagicMock() record.__getitem__ = lambda self, key: { "lp": mock_lp, "artistInfoIds": [], "gp": None, "monthlyListeners": None, "label": { "uuid": "dddd1111-eeee-4fff-8000-bbbbbbbbbbbb", "ownerVendorId": 100, "subaccountId": 9, }, }[key] session = MagicMock() result = MagicMock() result.__iter__ = lambda self: iter([record]) session.run.return_value = result results = get_by_uuids( session, uuids=["550e8400-e29b-41d4-a716-446655440000"], profile_ids=[7123], ) assert len(results) == 1 assert results[0]["label"]["vendor_id"] == 100 assert results[0]["label"]["subaccount_id"] == 9 def test_falls_back_to_lp_vendor_id_when_vendor_node_has_no_vendor_id( self, mock_lp ) -> None: """Verify coalesce(vendor.vendorId, lp.vendorId) in the Cypher query correctly surfaces lp.vendorId when the connected Vendor node has no vendorId property. The session mock simulates what the coalesced query returns in that case.""" record = MagicMock() record.__getitem__ = lambda self, key: { "lp": mock_lp, "artistInfoIds": [], "gp": None, "monthlyListeners": None, "label": { "uuid": "cccc0000-dddd-4eee-8fff-aaaaaaaaaaaa", "vendorId": 100, # coalesce fell back to lp.vendorId "ownerVendorId": 100, "subaccountId": 0, }, }[key] session = MagicMock() result = MagicMock() result.__iter__ = lambda self: iter([record]) session.run.return_value = result results = get_by_uuids( session, uuids=["550e8400-e29b-41d4-a716-446655440000"], profile_ids=[7123], ) assert len(results) == 1 assert results[0]["label"]["vendor_id"] == 100 def test_returns_empty_list_when_none_found(self, empty_session) -> None: result_mock = MagicMock() result_mock.__iter__ = lambda self: iter([]) empty_session.run.return_value = result_mock assert get_by_uuids(empty_session, uuids=["no-match"], profile_ids=[7123]) == [] class TestGetGpByIds: def test_returns_global_participant(self, expected_global_participant) -> None: mock_gp = MagicMock() mock_gp.__getitem__ = lambda self, key: { "id": "b1a2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", "name": "Test Artist Global", "spotifyId": "4iV5W9uYEdYUVa79Axb7Rh", "appleMusicId": "1234567890", "chartmetricId": "987654", "imageUrl": "https://example.com/artists/test-artist.jpg", }[key] mock_gp.get = lambda key: { "id": "b1a2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", "name": "Test Artist Global", "spotifyId": "4iV5W9uYEdYUVa79Axb7Rh", "appleMusicId": "1234567890", "chartmetricId": "987654", "imageUrl": "https://example.com/artists/test-artist.jpg", }.get(key) session = MagicMock() result = MagicMock() result.__iter__ = lambda self: iter( [ MagicMock( __getitem__=lambda s, k: { "gp": mock_gp, "monthlyListeners": 7654321, }[k] ) ] ) session.run.return_value = result results = get_gp_by_ids( session, ids=["b1a2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d"], profile_ids=[7123], ) assert len(results) == 1 assert results[0] == expected_global_participant def test_returns_multiple_global_participants( self, expected_global_participant ) -> None: mock_gp1 = MagicMock() mock_gp1.__getitem__ = lambda self, key: { "id": "b1a2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", "name": "Test Artist 1", "spotifyId": "4iV5W9uYEdYUVa79Axb7Rh", "appleMusicId": "1234567890", "chartmetricId": "987654", "imageUrl": "https://example.com/artists/artist1.jpg", }[key] mock_gp2 = MagicMock() mock_gp2.__getitem__ = lambda self, key: { "id": "c2b3d4e5-f6a7-5b8c-9d0e-1f2a3b4c5d6e", "name": "Test Artist 2", "spotifyId": "5jW5X9uYEdYUVa79Axb7Rh", "appleMusicId": "0987654321", "chartmetricId": "654321", "imageUrl": "https://example.com/artists/artist2.jpg", }[key] session = MagicMock() result = MagicMock() result.__iter__ = lambda self: iter( [ MagicMock( __getitem__=lambda s, k: { "gp": mock_gp1, "monthlyListeners": 7654321, }[k] ), MagicMock( __getitem__=lambda s, k: { "gp": mock_gp2, "monthlyListeners": 5432100, }[k] ), ] ) session.run.return_value = result results = get_gp_by_ids( session, ids=[ "b1a2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", "c2b3d4e5-f6a7-5b8c-9d0e-1f2a3b4c5d6e", ], profile_ids=[7123], ) assert len(results) == 2 def test_returns_empty_list_when_none_found(self) -> None: result_mock = MagicMock() result_mock.__iter__ = lambda self: iter([]) session = MagicMock() session.run.return_value = result_mock assert get_gp_by_ids(session, ids=["no-match"], profile_ids=[7123]) == [] class TestCreate: def test_returns_contributor( self, session, expected_contributor, create_kwargs ) -> None: result = create(session, **create_kwargs) assert result == {**expected_contributor, "global_participant": None} def test_raises_when_record_not_returned( self, empty_session, create_kwargs ) -> None: with pytest.raises(RuntimeError): create(empty_session, **create_kwargs) class TestGetGpById: def test_returns_global_participant( self, mock_global_participant, expected_global_participant ) -> None: session = MagicMock() result = MagicMock() record = MagicMock() record.__getitem__ = lambda self, key: { "gp": mock_global_participant, "monthlyListeners": 7654321, }[key] result.single.return_value = record session.run.return_value = result gp = get_gp_by_id( session, id="b1a2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", profile_ids=[7123], ) assert gp == expected_global_participant def test_returns_global_participant_as_admin( self, mock_global_participant, expected_global_participant ) -> None: session = MagicMock() result = MagicMock() record = MagicMock() record.__getitem__ = lambda self, key: { "gp": mock_global_participant, "monthlyListeners": 7654321, }[key] result.single.return_value = record session.run.return_value = result gp = get_gp_by_id( session, id="b1a2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", profile_ids=[], is_admin=True, ) assert gp == expected_global_participant def test_sets_monthly_listeners_to_none_when_null( self, mock_global_participant, expected_global_participant ) -> None: session = MagicMock() result = MagicMock() record = MagicMock() record.__getitem__ = lambda self, key: { "gp": mock_global_participant, "monthlyListeners": None, }[key] result.single.return_value = record session.run.return_value = result gp = get_gp_by_id( session, id="b1a2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", profile_ids=[7123], ) assert gp == {**expected_global_participant, "monthly_listeners": None} def test_returns_none_when_not_found(self, empty_session) -> None: assert get_gp_by_id(empty_session, id="no-match", profile_ids=[7123]) is None class TestGetContributorsForGlobalParticipant: def test_returns_contributors( self, mock_lp, mock_global_participant, expected_global_participant, label_node_props, expected_label, ) -> None: session = MagicMock() result = MagicMock() record = MagicMock() record.__getitem__ = lambda self, key: { "global_participant_id": expected_global_participant["id"], "contributors": [ { "lp": mock_lp, "label": label_node_props, "gp": mock_global_participant, "gp_monthly_listeners": 7654321, } ], }[key] result.single.return_value = record session.run.return_value = result contributors = get_contributors_for_global_participant( session, global_participant_id=expected_global_participant["id"], profile_ids=[7123], ) assert contributors is not None assert len(contributors) == 1 assert contributors[0]["uuid"] == "550e8400-e29b-41d4-a716-446655440000" assert contributors[0]["global_participant"] == { "id": expected_global_participant["id"] } assert contributors[0]["label"] == expected_label def test_filters_null_contributors_from_optional_match( self, expected_global_participant ) -> None: session = MagicMock() result = MagicMock() record = MagicMock() record.__getitem__ = lambda self, key: { "global_participant_id": expected_global_participant["id"], "contributors": [None], }[key] result.single.return_value = record session.run.return_value = result contributors = get_contributors_for_global_participant( session, global_participant_id=expected_global_participant["id"], profile_ids=[7123], ) assert contributors == [] def test_returns_none_when_global_participant_not_found( self, empty_session ) -> None: assert ( get_contributors_for_global_participant( empty_session, global_participant_id="no-match", profile_ids=[7123], ) is None ) def test_passes_is_admin_flag(self, empty_session) -> None: get_contributors_for_global_participant( empty_session, global_participant_id="b1a2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", profile_ids=[], is_admin=True, ) empty_session.run.assert_called_once() assert empty_session.run.call_args.kwargs["is_admin"] is True class TestRename: @pytest.fixture def rename_session(self, mock_lp, label_node_props): session = MagicMock() rename_result = MagicMock() rename_record = MagicMock() rename_record.__getitem__ = lambda self, key: {"node": mock_lp}[key] rename_result.single.return_value = rename_record label_result = MagicMock() label_record = MagicMock() label_record.__getitem__ = lambda self, key: {"label": label_node_props}[key] label_result.single.return_value = label_record session.run.side_effect = [rename_result, label_result] return session def test_returns_contributor_dict( self, rename_session, expected_contributor ) -> None: result = rename( rename_session, uuid="550e8400-e29b-41d4-a716-446655440000", name="New Name", updated_by="ows-contributor/test-user", profile_ids=[7123], ) expected = {**expected_contributor, "global_participant": None} assert result == expected def test_passes_params_to_session_run(self, rename_session) -> None: rename( rename_session, uuid="550e8400-e29b-41d4-a716-446655440000", name="New Name", updated_by="ows-contributor/test-user", profile_ids=[7123], is_admin=True, ) call_kwargs = rename_session.run.call_args_list[0].kwargs assert call_kwargs["uuid"] == "550e8400-e29b-41d4-a716-446655440000" assert call_kwargs["name"] == "New Name" assert call_kwargs["updated_by"] == "ows-contributor/test-user" assert call_kwargs["profile_ids"] == [7123] assert call_kwargs["is_admin"] is True def test_returns_none_when_not_found(self, empty_session) -> None: result = rename( empty_session, uuid="550e8400-e29b-41d4-a716-446655440000", name="New Name", updated_by="ows-contributor/test-user", profile_ids=[7123], ) assert result is None def test_passes_is_admin_false_by_default(self, rename_session) -> None: rename( rename_session, uuid="550e8400-e29b-41d4-a716-446655440000", name="New Name", updated_by="ows-contributor/test-user", profile_ids=[7123], ) call_kwargs = rename_session.run.call_args_list[0].kwargs assert call_kwargs["is_admin"] is False def test_fetches_label_for_returned_contributor(self, rename_session) -> None: rename( rename_session, uuid="550e8400-e29b-41d4-a716-446655440000", name="New Name", updated_by="ows-contributor/test-user", profile_ids=[7123], ) label_call_kwargs = rename_session.run.call_args_list[1].kwargs assert label_call_kwargs["uuid"] == "550e8400-e29b-41d4-a716-446655440000" assert label_call_kwargs["profile_ids"] == [7123] assert label_call_kwargs["is_admin"] is False