"""Contributors router tests.""" import pytest from fastapi.testclient import TestClient from pytest_mock import MockerFixture from contributor.api.schemas.contributors import Contributor @pytest.fixture def contributor_uuid(): return "550e8400-e29b-41d4-a716-446655440000" @pytest.fixture def unknown_uuid(): return "aaaaaaaa-bbbb-4ccc-8ddd-eeeeeeeeeeee" @pytest.fixture def oa_contributor(contributor_uuid): return { "id": 42, "uuid": contributor_uuid, "name": "Test Artist", "spotify_id": "3TVXtAsR1Inumwj472S9r4", "apple_music_id": None, "spotify_artist_key": "A1B2C-D3E4F-G5H6I-J7K8L-M9N0P", "isni": None, "company_brand_uuid": "11110000-2222-4333-8444-555555555555", "parent_company_uuid": "66660000-7777-4888-8999-aaaaaaaaaaaa", "artist_info_ids": [], "global_participant": { "id": "b1a2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", }, "label": { "uuid": "cccc0000-dddd-4eee-8fff-aaaaaaaaaaaa", "vendor_id": 100, "subaccount_id": 0, }, } @pytest.fixture def contributor(contributor_uuid): return { "id": 42, "uuid": contributor_uuid, "name": "Test Artist", "spotify_id": "3TVXtAsR1Inumwj472S9r4", "apple_music_id": None, "spotify_artist_key": None, "isni": None, "company_brand_uuid": "11110000-2222-4333-8444-555555555555", "parent_company_uuid": "66660000-7777-4888-8999-aaaaaaaaaaaa", "artist_info_ids": [], "global_participant": { "id": "b1a2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", }, "label": { "uuid": "cccc0000-dddd-4eee-8fff-aaaaaaaaaaaa", "vendor_id": 100, "subaccount_id": 0, }, } @pytest.fixture def create_request(profile_id): return { "input": { "name": "Test Artist", "spotify_id": "3TVXtAsR1Inumwj472S9r4", "apple_music_id": None, "spotify_artist_key": None, "isni": None, }, "vendor_id": profile_id, "subaccount_id": None, } @pytest.fixture(autouse=True) def mock_assert_authorization(mocker: MockerFixture) -> None: mocker.patch("contributor.logic.contributors.assert_authorization") @pytest.fixture(autouse=True) def mock_default_get_contributor(mocker: MockerFixture, contributor) -> None: mocker.patch( "contributor.logic.contributors.get_contributor", return_value=Contributor.model_validate(contributor), ) class TestGetContributor: def test_returns_contributor( self, mocker: MockerFixture, test_client: TestClient, contributor_uuid, contributor, ) -> None: mocker.patch( "contributor.logic.contributors.get_contributor", return_value=Contributor.model_validate(contributor), ) response = test_client.get(f"/contributors/{contributor_uuid}") assert response.status_code == 200 assert response.json() == contributor def test_returns_404_when_not_found( self, mocker: MockerFixture, test_client: TestClient, unknown_uuid, ) -> None: mocker.patch( "contributor.logic.contributors.get_contributor", return_value=None ) response = test_client.get(f"/contributors/{unknown_uuid}") assert response.status_code == 404 @pytest.fixture def update_request(): return { "spotify_id": "3TVXtAsR1Inumwj472S9r4", "apple_music_id": None, "spotify_artist_key": None, "isni": None, } @pytest.fixture def rename_request(): return {"name": "Renamed Artist"} class TestUpdateContributor: def test_returns_updated_contributor( self, mocker: MockerFixture, test_client: TestClient, contributor_uuid, contributor, update_request, ) -> None: mocker.patch( "contributor.logic.contributors.update_contributor", return_value=contributor, ) response = test_client.patch( f"/contributors/{contributor_uuid}", json=update_request, ) assert response.status_code == 200 assert response.json() == contributor def test_returns_404_when_not_found( self, mocker: MockerFixture, test_client: TestClient, contributor_uuid, update_request, ) -> None: mocker.patch( "contributor.logic.contributors.update_contributor", return_value=None ) response = test_client.patch( f"/contributors/{contributor_uuid}", json=update_request, ) assert response.status_code == 404 class TestRenameContributor: def test_returns_renamed_contributor( self, mocker: MockerFixture, test_client: TestClient, contributor_uuid, contributor, rename_request, ) -> None: renamed_contributor = {**contributor, "name": rename_request["name"]} mocker.patch( "contributor.logic.contributors.rename_contributor", return_value=renamed_contributor, ) response = test_client.patch( f"/contributors/{contributor_uuid}/rename", json=rename_request, ) assert response.status_code == 200 assert response.json() == renamed_contributor def test_passes_name_and_audit_fields_to_logic( self, mocker: MockerFixture, test_client: TestClient, contributor_uuid, contributor, rename_request, identity_uuid, profile_id, ) -> None: mock = mocker.patch( "contributor.logic.contributors.rename_contributor", return_value=contributor, ) test_client.patch( f"/contributors/{contributor_uuid}/rename", json=rename_request, ) assert str(mock.call_args.kwargs["contributor_uuid"]) == contributor_uuid assert mock.call_args.kwargs["name"] == rename_request["name"] assert mock.call_args.kwargs["updated_by"] == f"ows-contributor/{identity_uuid}" assert mock.call_args.kwargs["profiles"] == [ {"profile_type": "LabelProfile", "profile_id": profile_id, "roles": []} ] assert mock.call_args.kwargs["oa_user"] is False class TestMergeContributors: @pytest.fixture def merge_request(self): return { "duplicate_contributor": { "uuid": "aaaaaaaa-bbbb-4ccc-8ddd-eeeeeeeeeeee", }, } def test_returns_merged_contributor( self, mocker: MockerFixture, test_client: TestClient, contributor_uuid, contributor, merge_request, ) -> None: mocker.patch( "contributor.logic.contributors.merge_contributors", return_value=contributor, ) response = test_client.post( f"/contributors/{contributor_uuid}/merge", json=merge_request, ) assert response.status_code == 200 assert response.json() == contributor def test_passes_arguments_to_logic( self, mocker: MockerFixture, test_client: TestClient, contributor_uuid, contributor, merge_request, identity_uuid, profile_id, ) -> None: mock = mocker.patch( "contributor.logic.contributors.merge_contributors", return_value=contributor, ) test_client.post( f"/contributors/{contributor_uuid}/merge", json=merge_request, ) assert str(mock.call_args.kwargs["contributor_uuid"]) == contributor_uuid assert ( str(mock.call_args.kwargs["duplicate_uuid"]) == merge_request["duplicate_contributor"]["uuid"] ) assert mock.call_args.kwargs["updated_by"] == f"ows-contributor/{identity_uuid}" assert mock.call_args.kwargs["profiles"] == [ {"profile_type": "LabelProfile", "profile_id": profile_id, "roles": []} ] assert mock.call_args.kwargs["oa_user"] is False class TestCreateContributor: def test_returns_created_contributor( self, mocker: MockerFixture, test_client: TestClient, contributor, create_request, ) -> None: mocker.patch( "contributor.logic.contributors.check_vendor_access", return_value=True, ) mocker.patch( "contributor.logic.contributors.create_contributor", return_value=contributor, ) response = test_client.post("/contributors", json=create_request) assert response.status_code == 201 assert response.json() == contributor def test_returns_403_when_not_authorized( self, mocker: MockerFixture, test_client: TestClient, create_request, ) -> None: mocker.patch( "contributor.logic.contributors.check_vendor_access", return_value=False, ) response = test_client.post("/contributors", json=create_request) assert response.status_code == 403 class TestLoadContributors: def test_returns_contributors( self, mocker: MockerFixture, test_client: TestClient, contributor_uuid, contributor, ) -> None: mocker.patch( "contributor.logic.contributors.get_contributors", return_value=[Contributor.model_validate(contributor)], ) response = test_client.post( "/contributors/dataloader", json={"contributors": [{"uuid": contributor_uuid}]}, ) assert response.status_code == 200 assert response.json() == [contributor] def test_returns_empty_list_when_none_found( self, mocker: MockerFixture, test_client: TestClient, contributor_uuid, ) -> None: mocker.patch( "contributor.logic.contributors.get_contributors", return_value=[], ) response = test_client.post( "/contributors/dataloader", json={"contributors": [{"uuid": contributor_uuid}]}, ) assert response.status_code == 200 assert response.json() == [] def test_passes_all_uuids_to_logic( self, mocker: MockerFixture, test_client: TestClient, contributor_uuid, contributor, ) -> None: second_uuid = "aaaaaaaa-bbbb-4ccc-8ddd-eeeeeeeeeeee" mock = mocker.patch( "contributor.logic.contributors.get_contributors", return_value=[Contributor.model_validate(contributor)], ) test_client.post( "/contributors/dataloader", json={"contributors": [{"uuid": contributor_uuid}, {"uuid": second_uuid}]}, ) called_uuids = [str(u) for u in mock.call_args.kwargs["contributor_uuids"]] assert contributor_uuid in called_uuids assert second_uuid in called_uuids def test_passes_oa_user_false_to_logic_for_non_oa_user( self, mocker: MockerFixture, test_client: TestClient, contributor_uuid, contributor, ) -> None: mock = mocker.patch( "contributor.logic.contributors.get_contributors", return_value=[Contributor.model_validate(contributor)], ) test_client.post( "/contributors/dataloader", json={"contributors": [{"uuid": contributor_uuid}]}, ) assert mock.call_args.kwargs["oa_user"] is False def test_passes_oa_user_true_to_logic_for_oa_user( self, mocker: MockerFixture, test_client: TestClient, contributor_uuid, oa_contributor, ) -> None: mock = mocker.patch( "contributor.logic.contributors.get_contributors", return_value=[Contributor.model_validate(oa_contributor)], ) test_client.post( "/contributors/dataloader", json={"contributors": [{"uuid": contributor_uuid}]}, headers={"Orchard-User-Id": "oa:99999"}, ) assert mock.call_args.kwargs["oa_user"] is True @pytest.fixture def track_participations(contributor_uuid): return { "contributor": {"uuid": contributor_uuid}, "participations": [ { "contributor": {"uuid": contributor_uuid}, "participated_as": "performer", "track": {"tuid": 101}, "track_artist_id": 1001, }, { "contributor": {"uuid": contributor_uuid}, "participated_as": "featuring", "track": {"tuid": 202}, "track_artist_id": 2002, }, ], } class TestGetTrackParticipations: def test_returns_track_participations( self, mocker: MockerFixture, test_client: TestClient, contributor_uuid, track_participations, ) -> None: mocker.patch( "contributor.logic.tracks.get_track_participations", return_value=track_participations, ) response = test_client.get( f"/contributors/{contributor_uuid}/track-participations" ) assert response.status_code == 200 assert response.json() == track_participations def test_returns_404_when_not_found( self, mocker: MockerFixture, test_client: TestClient, unknown_uuid, ) -> None: mocker.patch( "contributor.logic.tracks.get_track_participations", return_value=None, ) response = test_client.get(f"/contributors/{unknown_uuid}/track-participations") assert response.status_code == 404 @pytest.fixture def product_participations(contributor_uuid): return { "contributor": {"uuid": contributor_uuid}, "participations": [ { "contributor": {"uuid": contributor_uuid}, "participated_as": "performer", "product": {"product_id": 101}, "release_artist_id": 1001, }, { "contributor": {"uuid": contributor_uuid}, "participated_as": "featuring", "product": {"product_id": 202}, "release_artist_id": 2002, }, ], } class TestGetProductParticipations: def test_returns_product_participations( self, mocker: MockerFixture, test_client: TestClient, contributor_uuid, product_participations, ) -> None: mocker.patch( "contributor.logic.products.get_product_participations", return_value=product_participations, ) response = test_client.get( f"/contributors/{contributor_uuid}/product-participations" ) assert response.status_code == 200 assert response.json() == product_participations def test_returns_404_when_not_found( self, mocker: MockerFixture, test_client: TestClient, unknown_uuid, ) -> None: mocker.patch( "contributor.logic.products.get_product_participations", return_value=None, ) response = test_client.get( f"/contributors/{unknown_uuid}/product-participations" ) assert response.status_code == 404 @pytest.fixture def sound_recording_participations(contributor_uuid): return { "contributor": {"uuid": contributor_uuid}, "participations": [ { "contributor": {"uuid": contributor_uuid}, "label_sound_recording": {"id": "0457b5bb-b032-464d-a232-7cf8100a057a"}, "role": { "apple_role_name": "Recording Engineer", "category": {"name": "Studio Personnel", "uuid": "uuid-1"}, "ddex_role_name": "RecordingEngineer", "name": "Recording Engineer", }, "sequence_number": 1, }, { "contributor": {"uuid": contributor_uuid}, "label_sound_recording": {"id": "891e83b4-0502-4230-8fa9-733ee70949ea"}, "role": { "apple_role_name": "Mixing Engineer", "category": {"name": "Studio Personnel", "uuid": "uuid-2"}, "ddex_role_name": "MixingEngineer", "name": "Mixing Engineer", }, "sequence_number": 2, }, ], } class TestGetSoundRecordingParticipations: def test_returns_sound_recording_participations( self, mocker: MockerFixture, test_client: TestClient, contributor_uuid, sound_recording_participations, ) -> None: mocker.patch( "contributor.logic.contributors.get_sound_recording_participations", return_value=sound_recording_participations, ) response = test_client.get( f"/contributors/{contributor_uuid}/sound-recording-participations" ) assert response.status_code == 200 assert response.json() == sound_recording_participations def test_returns_404_when_not_found( self, mocker: MockerFixture, test_client: TestClient, unknown_uuid, ) -> None: mocker.patch( "contributor.logic.contributors.get_sound_recording_participations", return_value=None, ) response = test_client.get( f"/contributors/{unknown_uuid}/sound-recording-participations" ) assert response.status_code == 404 @pytest.fixture def projects(contributor_uuid): return { "contributor": {"uuid": contributor_uuid}, "projects": [ {"project_id": 101}, {"project_id": 202}, ], } class TestGetProjects: def test_returns_projects( self, mocker: MockerFixture, test_client: TestClient, contributor_uuid, projects, ) -> None: mocker.patch( "contributor.logic.projects.get_projects", return_value=projects, ) response = test_client.get(f"/contributors/{contributor_uuid}/projects") assert response.status_code == 200 assert response.json() == projects def test_returns_404_when_not_found( self, mocker: MockerFixture, test_client: TestClient, unknown_uuid, ) -> None: mocker.patch( "contributor.logic.projects.get_projects", return_value=None, ) response = test_client.get(f"/contributors/{unknown_uuid}/projects") assert response.status_code == 404