@mention /ping
' - } + "actor": actor.url, + "type": "Create", + "id": "http://test.federation/activity", + "object": { + "type": "Note", + "id": "http://test.federation/object", + "content": "@mention /ping
", + }, } - test_actor = actors.SYSTEM_ACTORS['test'].get_actor_instance() - expected_note = factories['federation.Note']( - id='https://test.federation/activities/note/{}'.format( - now.timestamp() - ), - content='Pong!', + test_actor = actors.SYSTEM_ACTORS["test"].get_actor_instance() + expected_note = factories["federation.Note"]( + id="https://test.federation/activities/note/{}".format(now.timestamp()), + content="Pong!", published=now.isoformat(), - inReplyTo=data['object']['id'], + inReplyTo=data["object"]["id"], cc=[], summary=None, sensitive=False, attributedTo=test_actor.url, attachment=[], to=[actor.url], - url='https://{}/activities/note/{}'.format( + url="https://{}/activities/note/{}".format( settings.FEDERATION_HOSTNAME, now.timestamp() ), - tag=[{ - 'href': actor.url, - 'name': actor.mention_username, - 'type': 'Mention', - }] + tag=[{"href": actor.url, "name": actor.mention_username, "type": "Mention"}], ) expected_activity = { - '@context': serializers.AP_CONTEXT, - 'actor': test_actor.url, - 'id': 'https://{}/activities/note/{}/activity'.format( + "@context": serializers.AP_CONTEXT, + "actor": test_actor.url, + "id": "https://{}/activities/note/{}/activity".format( settings.FEDERATION_HOSTNAME, now.timestamp() ), - 'to': actor.url, - 'type': 'Create', - 'published': now.isoformat(), - 'object': expected_note, - 'cc': [], + "to": actor.url, + "type": "Create", + "published": now.isoformat(), + "object": expected_note, + "cc": [], } - actors.SYSTEM_ACTORS['test'].post_inbox(data, actor=actor) + actors.SYSTEM_ACTORS["test"].post_inbox(data, actor=actor) deliver.assert_called_once_with( expected_activity, to=[actor.url], - on_behalf_of=actors.SYSTEM_ACTORS['test'].get_actor_instance() + on_behalf_of=actors.SYSTEM_ACTORS["test"].get_actor_instance(), ) def test_getting_actor_instance_persists_in_db(db): - test = actors.SYSTEM_ACTORS['test'].get_actor_instance() + test = actors.SYSTEM_ACTORS["test"].get_actor_instance() from_db = models.Actor.objects.get(url=test.url) for f in test._meta.fields: assert getattr(from_db, f.name) == getattr(test, f.name) -@pytest.mark.parametrize('username,domain,expected', [ - ('test', 'wrongdomain.com', False), - ('notsystem', '', False), - ('test', '', True), -]) -def test_actor_is_system( - username, domain, expected, nodb_factories, settings): +@pytest.mark.parametrize( + "username,domain,expected", + [("test", "wrongdomain.com", False), ("notsystem", "", False), ("test", "", True)], +) +def test_actor_is_system(username, domain, expected, nodb_factories, settings): if not domain: domain = settings.FEDERATION_HOSTNAME - actor = nodb_factories['federation.Actor']( - preferred_username=username, - domain=domain, + actor = nodb_factories["federation.Actor"]( + preferred_username=username, domain=domain ) assert actor.is_system is expected -@pytest.mark.parametrize('username,domain,expected', [ - ('test', 'wrongdomain.com', None), - ('notsystem', '', None), - ('test', '', actors.SYSTEM_ACTORS['test']), -]) -def test_actor_is_system( - username, domain, expected, nodb_factories, settings): +@pytest.mark.parametrize( + "username,domain,expected", + [ + ("test", "wrongdomain.com", None), + ("notsystem", "", None), + ("test", "", actors.SYSTEM_ACTORS["test"]), + ], +) +def test_actor_system_conf(username, domain, expected, nodb_factories, settings): if not domain: domain = settings.FEDERATION_HOSTNAME - actor = nodb_factories['federation.Actor']( - preferred_username=username, - domain=domain, + actor = nodb_factories["federation.Actor"]( + preferred_username=username, domain=domain ) assert actor.system_conf == expected -@pytest.mark.parametrize('value', [False, True]) -def test_library_actor_manually_approves_based_on_preference( - value, preferences): - preferences['federation__music_needs_approval'] = value - library_conf = actors.SYSTEM_ACTORS['library'] +@pytest.mark.parametrize("value", [False, True]) +def test_library_actor_manually_approves_based_on_preference(value, preferences): + preferences["federation__music_needs_approval"] = value + library_conf = actors.SYSTEM_ACTORS["library"] assert library_conf.manually_approves_followers is value def test_system_actor_handle(mocker, nodb_factories): - handler = mocker.patch( - 'funkwhale_api.federation.actors.TestActor.handle_create') - actor = nodb_factories['federation.Actor']() - activity = nodb_factories['federation.Activity']( - type='Create', actor=actor.url) - serializer = serializers.ActivitySerializer( - data=activity - ) + handler = mocker.patch("funkwhale_api.federation.actors.TestActor.handle_create") + actor = nodb_factories["federation.Actor"]() + activity = nodb_factories["federation.Activity"](type="Create", actor=actor.url) + serializer = serializers.ActivitySerializer(data=activity) assert serializer.is_valid() - actors.SYSTEM_ACTORS['test'].handle(activity, actor) + actors.SYSTEM_ACTORS["test"].handle(activity, actor) handler.assert_called_once_with(activity, actor) -def test_test_actor_handles_follow( - settings, mocker, factories): - deliver = mocker.patch( - 'funkwhale_api.federation.activity.deliver') - actor = factories['federation.Actor']() - accept_follow = mocker.patch( - 'funkwhale_api.federation.activity.accept_follow') - test_actor = actors.SYSTEM_ACTORS['test'].get_actor_instance() +def test_test_actor_handles_follow(settings, mocker, factories): + deliver = mocker.patch("funkwhale_api.federation.activity.deliver") + actor = factories["federation.Actor"]() + accept_follow = mocker.patch("funkwhale_api.federation.activity.accept_follow") + test_actor = actors.SYSTEM_ACTORS["test"].get_actor_instance() data = { - 'actor': actor.url, - 'type': 'Follow', - 'id': 'http://test.federation/user#follows/267', - 'object': test_actor.url, + "actor": actor.url, + "type": "Follow", + "id": "http://test.federation/user#follows/267", + "object": test_actor.url, } - actors.SYSTEM_ACTORS['test'].post_inbox(data, actor=actor) + actors.SYSTEM_ACTORS["test"].post_inbox(data, actor=actor) follow = models.Follow.objects.get(target=test_actor, approved=True) follow_back = models.Follow.objects.get(actor=test_actor, approved=None) accept_follow.assert_called_once_with(follow) deliver.assert_called_once_with( serializers.FollowSerializer(follow_back).data, on_behalf_of=test_actor, - to=[actor.url] + to=[actor.url], ) -def test_test_actor_handles_undo_follow( - settings, mocker, factories): - deliver = mocker.patch( - 'funkwhale_api.federation.activity.deliver') - test_actor = actors.SYSTEM_ACTORS['test'].get_actor_instance() - follow = factories['federation.Follow'](target=test_actor) - reverse_follow = factories['federation.Follow']( - actor=test_actor, target=follow.actor) +def test_test_actor_handles_undo_follow(settings, mocker, factories): + deliver = mocker.patch("funkwhale_api.federation.activity.deliver") + test_actor = actors.SYSTEM_ACTORS["test"].get_actor_instance() + follow = factories["federation.Follow"](target=test_actor) + reverse_follow = factories["federation.Follow"]( + actor=test_actor, target=follow.actor + ) follow_serializer = serializers.FollowSerializer(follow) - reverse_follow_serializer = serializers.FollowSerializer( - reverse_follow) + reverse_follow_serializer = serializers.FollowSerializer(reverse_follow) undo = { - '@context': serializers.AP_CONTEXT, - 'type': 'Undo', - 'id': follow_serializer.data['id'] + '/undo', - 'actor': follow.actor.url, - 'object': follow_serializer.data, + "@context": serializers.AP_CONTEXT, + "type": "Undo", + "id": follow_serializer.data["id"] + "/undo", + "actor": follow.actor.url, + "object": follow_serializer.data, } expected_undo = { - '@context': serializers.AP_CONTEXT, - 'type': 'Undo', - 'id': reverse_follow_serializer.data['id'] + '/undo', - 'actor': reverse_follow.actor.url, - 'object': reverse_follow_serializer.data, + "@context": serializers.AP_CONTEXT, + "type": "Undo", + "id": reverse_follow_serializer.data["id"] + "/undo", + "actor": reverse_follow.actor.url, + "object": reverse_follow_serializer.data, } - actors.SYSTEM_ACTORS['test'].post_inbox(undo, actor=follow.actor) + actors.SYSTEM_ACTORS["test"].post_inbox(undo, actor=follow.actor) deliver.assert_called_once_with( - expected_undo, - to=[follow.actor.url], - on_behalf_of=test_actor,) + expected_undo, to=[follow.actor.url], on_behalf_of=test_actor + ) assert models.Follow.objects.count() == 0 -def test_library_actor_handles_follow_manual_approval( - preferences, mocker, factories): - preferences['federation__music_needs_approval'] = True - actor = factories['federation.Actor']() +def test_library_actor_handles_follow_manual_approval(preferences, mocker, factories): + preferences["federation__music_needs_approval"] = True + actor = factories["federation.Actor"]() now = timezone.now() - mocker.patch('django.utils.timezone.now', return_value=now) - library_actor = actors.SYSTEM_ACTORS['library'].get_actor_instance() + mocker.patch("django.utils.timezone.now", return_value=now) + library_actor = actors.SYSTEM_ACTORS["library"].get_actor_instance() data = { - 'actor': actor.url, - 'type': 'Follow', - 'id': 'http://test.federation/user#follows/267', - 'object': library_actor.url, + "actor": actor.url, + "type": "Follow", + "id": "http://test.federation/user#follows/267", + "object": library_actor.url, } library_actor.system_conf.post_inbox(data, actor=actor) @@ -376,18 +338,16 @@ def test_library_actor_handles_follow_manual_approval( assert follow.approved is None -def test_library_actor_handles_follow_auto_approval( - preferences, mocker, factories): - preferences['federation__music_needs_approval'] = False - actor = factories['federation.Actor']() - accept_follow = mocker.patch( - 'funkwhale_api.federation.activity.accept_follow') - library_actor = actors.SYSTEM_ACTORS['library'].get_actor_instance() +def test_library_actor_handles_follow_auto_approval(preferences, mocker, factories): + preferences["federation__music_needs_approval"] = False + actor = factories["federation.Actor"]() + mocker.patch("funkwhale_api.federation.activity.accept_follow") + library_actor = actors.SYSTEM_ACTORS["library"].get_actor_instance() data = { - 'actor': actor.url, - 'type': 'Follow', - 'id': 'http://test.federation/user#follows/267', - 'object': library_actor.url, + "actor": actor.url, + "type": "Follow", + "id": "http://test.federation/user#follows/267", + "object": library_actor.url, } library_actor.system_conf.post_inbox(data, actor=actor) @@ -397,14 +357,11 @@ def test_library_actor_handles_follow_auto_approval( assert follow.approved is True -def test_library_actor_handles_accept( - mocker, factories): - library_actor = actors.SYSTEM_ACTORS['library'].get_actor_instance() - actor = factories['federation.Actor']() - pending_follow = factories['federation.Follow']( - actor=library_actor, - target=actor, - approved=None, +def test_library_actor_handles_accept(mocker, factories): + library_actor = actors.SYSTEM_ACTORS["library"].get_actor_instance() + actor = factories["federation.Actor"]() + pending_follow = factories["federation.Follow"]( + actor=library_actor, target=actor, approved=None ) serializer = serializers.AcceptFollowSerializer(pending_follow) library_actor.system_conf.post_inbox(serializer.data, actor=actor) @@ -418,19 +375,19 @@ def test_library_actor_handle_create_audio_no_library(mocker, factories): # when we receive inbox create audio, we should not do anything # if we don't have a configured library matching the sender mocked_create = mocker.patch( - 'funkwhale_api.federation.serializers.AudioSerializer.create' + "funkwhale_api.federation.serializers.AudioSerializer.create" ) - actor = factories['federation.Actor']() - library_actor = actors.SYSTEM_ACTORS['library'].get_actor_instance() + actor = factories["federation.Actor"]() + library_actor = actors.SYSTEM_ACTORS["library"].get_actor_instance() data = { - 'actor': actor.url, - 'type': 'Create', - 'id': 'http://test.federation/audio/create', - 'object': { - 'id': 'https://batch.import', - 'type': 'Collection', - 'totalItems': 2, - 'items': factories['federation.Audio'].create_batch(size=2) + "actor": actor.url, + "type": "Create", + "id": "http://test.federation/audio/create", + "object": { + "id": "https://batch.import", + "type": "Collection", + "totalItems": 2, + "items": factories["federation.Audio"].create_batch(size=2), }, } library_actor.system_conf.post_inbox(data, actor=actor) @@ -439,26 +396,24 @@ def test_library_actor_handle_create_audio_no_library(mocker, factories): models.LibraryTrack.objects.count() == 0 -def test_library_actor_handle_create_audio_no_library_enabled( - mocker, factories): +def test_library_actor_handle_create_audio_no_library_enabled(mocker, factories): # when we receive inbox create audio, we should not do anything # if we don't have an enabled library mocked_create = mocker.patch( - 'funkwhale_api.federation.serializers.AudioSerializer.create' + "funkwhale_api.federation.serializers.AudioSerializer.create" ) - disabled_library = factories['federation.Library']( - federation_enabled=False) + disabled_library = factories["federation.Library"](federation_enabled=False) actor = disabled_library.actor - library_actor = actors.SYSTEM_ACTORS['library'].get_actor_instance() + library_actor = actors.SYSTEM_ACTORS["library"].get_actor_instance() data = { - 'actor': actor.url, - 'type': 'Create', - 'id': 'http://test.federation/audio/create', - 'object': { - 'id': 'https://batch.import', - 'type': 'Collection', - 'totalItems': 2, - 'items': factories['federation.Audio'].create_batch(size=2) + "actor": actor.url, + "type": "Create", + "id": "http://test.federation/audio/create", + "object": { + "id": "https://batch.import", + "type": "Collection", + "totalItems": 2, + "items": factories["federation.Audio"].create_batch(size=2), }, } library_actor.system_conf.post_inbox(data, actor=actor) @@ -468,97 +423,91 @@ def test_library_actor_handle_create_audio_no_library_enabled( def test_library_actor_handle_create_audio(mocker, factories): - library_actor = actors.SYSTEM_ACTORS['library'].get_actor_instance() - remote_library = factories['federation.Library']( - federation_enabled=True - ) + library_actor = actors.SYSTEM_ACTORS["library"].get_actor_instance() + remote_library = factories["federation.Library"](federation_enabled=True) data = { - 'actor': remote_library.actor.url, - 'type': 'Create', - 'id': 'http://test.federation/audio/create', - 'object': { - 'id': 'https://batch.import', - 'type': 'Collection', - 'totalItems': 2, - 'items': factories['federation.Audio'].create_batch(size=2) + "actor": remote_library.actor.url, + "type": "Create", + "id": "http://test.federation/audio/create", + "object": { + "id": "https://batch.import", + "type": "Collection", + "totalItems": 2, + "items": factories["federation.Audio"].create_batch(size=2), }, } library_actor.system_conf.post_inbox(data, actor=remote_library.actor) - lts = list(remote_library.tracks.order_by('id')) + lts = list(remote_library.tracks.order_by("id")) assert len(lts) == 2 - for i, a in enumerate(data['object']['items']): + for i, a in enumerate(data["object"]["items"]): lt = lts[i] assert lt.pk is not None - assert lt.url == a['id'] + assert lt.url == a["id"] assert lt.library == remote_library - assert lt.audio_url == a['url']['href'] - assert lt.audio_mimetype == a['url']['mediaType'] - assert lt.metadata == a['metadata'] - assert lt.title == a['metadata']['recording']['title'] - assert lt.artist_name == a['metadata']['artist']['name'] - assert lt.album_title == a['metadata']['release']['title'] - assert lt.published_date == arrow.get(a['published']) + assert lt.audio_url == a["url"]["href"] + assert lt.audio_mimetype == a["url"]["mediaType"] + assert lt.metadata == a["metadata"] + assert lt.title == a["metadata"]["recording"]["title"] + assert lt.artist_name == a["metadata"]["artist"]["name"] + assert lt.album_title == a["metadata"]["release"]["title"] + assert lt.published_date == arrow.get(a["published"]) def test_library_actor_handle_create_audio_autoimport(mocker, factories): - mocked_import = mocker.patch( - 'funkwhale_api.common.utils.on_commit') - library_actor = actors.SYSTEM_ACTORS['library'].get_actor_instance() - remote_library = factories['federation.Library']( - federation_enabled=True, - autoimport=True, + mocked_import = mocker.patch("funkwhale_api.common.utils.on_commit") + library_actor = actors.SYSTEM_ACTORS["library"].get_actor_instance() + remote_library = factories["federation.Library"]( + federation_enabled=True, autoimport=True ) data = { - 'actor': remote_library.actor.url, - 'type': 'Create', - 'id': 'http://test.federation/audio/create', - 'object': { - 'id': 'https://batch.import', - 'type': 'Collection', - 'totalItems': 2, - 'items': factories['federation.Audio'].create_batch(size=2) + "actor": remote_library.actor.url, + "type": "Create", + "id": "http://test.federation/audio/create", + "object": { + "id": "https://batch.import", + "type": "Collection", + "totalItems": 2, + "items": factories["federation.Audio"].create_batch(size=2), }, } library_actor.system_conf.post_inbox(data, actor=remote_library.actor) - lts = list(remote_library.tracks.order_by('id')) + lts = list(remote_library.tracks.order_by("id")) assert len(lts) == 2 - for i, a in enumerate(data['object']['items']): + for i, a in enumerate(data["object"]["items"]): lt = lts[i] assert lt.pk is not None - assert lt.url == a['id'] + assert lt.url == a["id"] assert lt.library == remote_library - assert lt.audio_url == a['url']['href'] - assert lt.audio_mimetype == a['url']['mediaType'] - assert lt.metadata == a['metadata'] - assert lt.title == a['metadata']['recording']['title'] - assert lt.artist_name == a['metadata']['artist']['name'] - assert lt.album_title == a['metadata']['release']['title'] - assert lt.published_date == arrow.get(a['published']) + assert lt.audio_url == a["url"]["href"] + assert lt.audio_mimetype == a["url"]["mediaType"] + assert lt.metadata == a["metadata"] + assert lt.title == a["metadata"]["recording"]["title"] + assert lt.artist_name == a["metadata"]["artist"]["name"] + assert lt.album_title == a["metadata"]["release"]["title"] + assert lt.published_date == arrow.get(a["published"]) - batch = music_models.ImportBatch.objects.latest('id') + batch = music_models.ImportBatch.objects.latest("id") assert batch.jobs.count() == len(lts) - assert batch.source == 'federation' + assert batch.source == "federation" assert batch.submitted_by is None - for i, job in enumerate(batch.jobs.order_by('id')): + for i, job in enumerate(batch.jobs.order_by("id")): lt = lts[i] assert job.library_track == lt assert job.mbid == lt.mbid assert job.source == lt.url mocked_import.assert_any_call( - music_tasks.import_job_run.delay, - import_job_id=job.pk, - use_acoustid=False, + music_tasks.import_job_run.delay, import_job_id=job.pk, use_acoustid=False ) diff --git a/api/tests/federation/test_authentication.py b/api/tests/federation/test_authentication.py index 2f69e4d4f..95cec5d2a 100644 --- a/api/tests/federation/test_authentication.py +++ b/api/tests/federation/test_authentication.py @@ -1,38 +1,33 @@ -from funkwhale_api.federation import authentication -from funkwhale_api.federation import keys -from funkwhale_api.federation import signing +from funkwhale_api.federation import authentication, keys def test_authenticate(factories, mocker, api_request): private, public = keys.get_key_pair() - actor_url = 'https://test.federation/actor' + actor_url = "https://test.federation/actor" mocker.patch( - 'funkwhale_api.federation.actors.get_actor_data', + "funkwhale_api.federation.actors.get_actor_data", return_value={ - 'id': actor_url, - 'type': 'Person', - 'outbox': 'https://test.com', - 'inbox': 'https://test.com', - 'preferredUsername': 'test', - 'publicKey': { - 'publicKeyPem': public.decode('utf-8'), - 'owner': actor_url, - 'id': actor_url + '#main-key', - } - }) - signed_request = factories['federation.SignedRequest']( - auth__key=private, - auth__key_id=actor_url + '#main-key', - auth__headers=[ - 'date', - ] + "id": actor_url, + "type": "Person", + "outbox": "https://test.com", + "inbox": "https://test.com", + "preferredUsername": "test", + "publicKey": { + "publicKeyPem": public.decode("utf-8"), + "owner": actor_url, + "id": actor_url + "#main-key", + }, + }, + ) + signed_request = factories["federation.SignedRequest"]( + auth__key=private, auth__key_id=actor_url + "#main-key", auth__headers=["date"] ) prepared = signed_request.prepare() django_request = api_request.get( - '/', + "/", **{ - 'HTTP_DATE': prepared.headers['date'], - 'HTTP_SIGNATURE': prepared.headers['signature'], + "HTTP_DATE": prepared.headers["date"], + "HTTP_SIGNATURE": prepared.headers["signature"], } ) authenticator = authentication.SignatureAuthentication() @@ -40,5 +35,5 @@ def test_authenticate(factories, mocker, api_request): actor = django_request.actor assert user.is_anonymous is True - assert actor.public_key == public.decode('utf-8') + assert actor.public_key == public.decode("utf-8") assert actor.url == actor_url diff --git a/api/tests/federation/test_keys.py b/api/tests/federation/test_keys.py index 9dd71be09..0f6158680 100644 --- a/api/tests/federation/test_keys.py +++ b/api/tests/federation/test_keys.py @@ -3,23 +3,29 @@ import pytest from funkwhale_api.federation import keys -@pytest.mark.parametrize('raw, expected', [ - ('algorithm="test",keyId="https://test.com"', 'https://test.com'), - ('keyId="https://test.com",algorithm="test"', 'https://test.com'), -]) +@pytest.mark.parametrize( + "raw, expected", + [ + ('algorithm="test",keyId="https://test.com"', "https://test.com"), + ('keyId="https://test.com",algorithm="test"', "https://test.com"), + ], +) def test_get_key_from_header(raw, expected): r = keys.get_key_id_from_signature_header(raw) assert r == expected -@pytest.mark.parametrize('raw', [ - 'algorithm="test",keyid="badCase"', - 'algorithm="test",wrong="wrong"', - 'keyId = "wrong"', - 'keyId=\'wrong\'', - 'keyId="notanurl"', - 'keyId="wrong://test.com"', -]) +@pytest.mark.parametrize( + "raw", + [ + 'algorithm="test",keyid="badCase"', + 'algorithm="test",wrong="wrong"', + 'keyId = "wrong"', + "keyId='wrong'", + 'keyId="notanurl"', + 'keyId="wrong://test.com"', + ], +) def test_get_key_from_header_invalid(raw): with pytest.raises(ValueError): keys.get_key_id_from_signature_header(raw) diff --git a/api/tests/federation/test_library.py b/api/tests/federation/test_library.py index 7a3abf5d8..4e187e479 100644 --- a/api/tests/federation/test_library.py +++ b/api/tests/federation/test_library.py @@ -1,70 +1,64 @@ -from funkwhale_api.federation import library -from funkwhale_api.federation import serializers +from funkwhale_api.federation import library, serializers def test_library_scan_from_account_name(mocker, factories): - actor = factories['federation.Actor']( - preferred_username='library', - domain='test.library' + actor = factories["federation.Actor"]( + preferred_username="library", domain="test.library" ) - get_resource_result = {'actor_url': actor.url} + get_resource_result = {"actor_url": actor.url} get_resource = mocker.patch( - 'funkwhale_api.federation.webfinger.get_resource', - return_value=get_resource_result) + "funkwhale_api.federation.webfinger.get_resource", + return_value=get_resource_result, + ) actor_data = serializers.ActorSerializer(actor).data - actor_data['manuallyApprovesFollowers'] = False - actor_data['url'] = [{ - 'type': 'Link', - 'name': 'library', - 'mediaType': 'application/activity+json', - 'href': 'https://test.library' - }] + actor_data["manuallyApprovesFollowers"] = False + actor_data["url"] = [ + { + "type": "Link", + "name": "library", + "mediaType": "application/activity+json", + "href": "https://test.library", + } + ] get_actor_data = mocker.patch( - 'funkwhale_api.federation.actors.get_actor_data', - return_value=actor_data) + "funkwhale_api.federation.actors.get_actor_data", return_value=actor_data + ) - get_library_data_result = {'test': 'test'} + get_library_data_result = {"test": "test"} get_library_data = mocker.patch( - 'funkwhale_api.federation.library.get_library_data', - return_value=get_library_data_result) + "funkwhale_api.federation.library.get_library_data", + return_value=get_library_data_result, + ) - result = library.scan_from_account_name('library@test.actor') + result = library.scan_from_account_name("library@test.actor") - get_resource.assert_called_once_with('acct:library@test.actor') + get_resource.assert_called_once_with("acct:library@test.actor") get_actor_data.assert_called_once_with(actor.url) - get_library_data.assert_called_once_with(actor_data['url'][0]['href']) + get_library_data.assert_called_once_with(actor_data["url"][0]["href"]) assert result == { - 'webfinger': get_resource_result, - 'actor': actor_data, - 'library': get_library_data_result, - 'local': { - 'following': False, - 'awaiting_approval': False, - }, + "webfinger": get_resource_result, + "actor": actor_data, + "library": get_library_data_result, + "local": {"following": False, "awaiting_approval": False}, } def test_get_library_data(r_mock, factories): - actor = factories['federation.Actor']() - url = 'https://test.library' - conf = { - 'id': url, - 'items': [], - 'actor': actor, - 'page_size': 5, - } + actor = factories["federation.Actor"]() + url = "https://test.library" + conf = {"id": url, "items": [], "actor": actor, "page_size": 5} data = serializers.PaginatedCollectionSerializer(conf).data r_mock.get(url, json=data) result = library.get_library_data(url) - for f in ['totalItems', 'actor', 'id', 'type']: + for f in ["totalItems", "actor", "id", "type"]: assert result[f] == data[f] def test_get_library_data_requires_authentication(r_mock, factories): - url = 'https://test.library' + url = "https://test.library" r_mock.get(url, status_code=403) result = library.get_library_data(url) - assert result['errors'] == ['Permission denied while scanning library'] + assert result["errors"] == ["Permission denied while scanning library"] diff --git a/api/tests/federation/test_models.py b/api/tests/federation/test_models.py index ae158e659..61d0aea96 100644 --- a/api/tests/federation/test_models.py +++ b/api/tests/federation/test_models.py @@ -1,41 +1,31 @@ import pytest -import uuid - from django import db -from funkwhale_api.federation import models -from funkwhale_api.federation import serializers - def test_cannot_duplicate_actor(factories): - actor = factories['federation.Actor']() + actor = factories["federation.Actor"]() with pytest.raises(db.IntegrityError): - factories['federation.Actor']( - domain=actor.domain, - preferred_username=actor.preferred_username, + factories["federation.Actor"]( + domain=actor.domain, preferred_username=actor.preferred_username ) def test_cannot_duplicate_follow(factories): - follow = factories['federation.Follow']() + follow = factories["federation.Follow"]() with pytest.raises(db.IntegrityError): - factories['federation.Follow']( - target=follow.target, - actor=follow.actor, - ) + factories["federation.Follow"](target=follow.target, actor=follow.actor) def test_follow_federation_url(factories): - follow = factories['federation.Follow'](local=True) - expected = '{}#follows/{}'.format( - follow.actor.url, follow.uuid) + follow = factories["federation.Follow"](local=True) + expected = "{}#follows/{}".format(follow.actor.url, follow.uuid) assert follow.get_federation_url() == expected def test_library_model_unique_per_actor(factories): - library = factories['federation.Library']() + library = factories["federation.Library"]() with pytest.raises(db.IntegrityError): - factories['federation.Library'](actor=library.actor) + factories["federation.Library"](actor=library.actor) diff --git a/api/tests/federation/test_permissions.py b/api/tests/federation/test_permissions.py index a87f26f1b..75f76077c 100644 --- a/api/tests/federation/test_permissions.py +++ b/api/tests/federation/test_permissions.py @@ -1,60 +1,61 @@ from rest_framework.views import APIView -from funkwhale_api.federation import actors -from funkwhale_api.federation import permissions +from funkwhale_api.federation import actors, permissions -def test_library_follower( - factories, api_request, anonymous_user, preferences): - preferences['federation__music_needs_approval'] = True +def test_library_follower(factories, api_request, anonymous_user, preferences): + preferences["federation__music_needs_approval"] = True view = APIView.as_view() permission = permissions.LibraryFollower() - request = api_request.get('/') - setattr(request, 'user', anonymous_user) + request = api_request.get("/") + setattr(request, "user", anonymous_user) check = permission.has_permission(request, view) assert check is False def test_library_follower_actor_non_follower( - factories, api_request, anonymous_user, preferences): - preferences['federation__music_needs_approval'] = True - actor = factories['federation.Actor']() + factories, api_request, anonymous_user, preferences +): + preferences["federation__music_needs_approval"] = True + actor = factories["federation.Actor"]() view = APIView.as_view() permission = permissions.LibraryFollower() - request = api_request.get('/') - setattr(request, 'user', anonymous_user) - setattr(request, 'actor', actor) + request = api_request.get("/") + setattr(request, "user", anonymous_user) + setattr(request, "actor", actor) check = permission.has_permission(request, view) assert check is False def test_library_follower_actor_follower_not_approved( - factories, api_request, anonymous_user, preferences): - preferences['federation__music_needs_approval'] = True - library = actors.SYSTEM_ACTORS['library'].get_actor_instance() - follow = factories['federation.Follow'](target=library, approved=False) + factories, api_request, anonymous_user, preferences +): + preferences["federation__music_needs_approval"] = True + library = actors.SYSTEM_ACTORS["library"].get_actor_instance() + follow = factories["federation.Follow"](target=library, approved=False) view = APIView.as_view() permission = permissions.LibraryFollower() - request = api_request.get('/') - setattr(request, 'user', anonymous_user) - setattr(request, 'actor', follow.actor) + request = api_request.get("/") + setattr(request, "user", anonymous_user) + setattr(request, "actor", follow.actor) check = permission.has_permission(request, view) assert check is False def test_library_follower_actor_follower( - factories, api_request, anonymous_user, preferences): - preferences['federation__music_needs_approval'] = True - library = actors.SYSTEM_ACTORS['library'].get_actor_instance() - follow = factories['federation.Follow'](target=library, approved=True) + factories, api_request, anonymous_user, preferences +): + preferences["federation__music_needs_approval"] = True + library = actors.SYSTEM_ACTORS["library"].get_actor_instance() + follow = factories["federation.Follow"](target=library, approved=True) view = APIView.as_view() permission = permissions.LibraryFollower() - request = api_request.get('/') - setattr(request, 'user', anonymous_user) - setattr(request, 'actor', follow.actor) + request = api_request.get("/") + setattr(request, "user", anonymous_user) + setattr(request, "actor", follow.actor) check = permission.has_permission(request, view) assert check is True diff --git a/api/tests/federation/test_serializers.py b/api/tests/federation/test_serializers.py index fcf2ba1b6..e966d1711 100644 --- a/api/tests/federation/test_serializers.py +++ b/api/tests/federation/test_serializers.py @@ -1,37 +1,29 @@ import arrow import pytest - -from django.urls import reverse from django.core.paginator import Paginator -from funkwhale_api.federation import actors -from funkwhale_api.federation import keys -from funkwhale_api.federation import models -from funkwhale_api.federation import serializers -from funkwhale_api.federation import utils +from funkwhale_api.federation import actors, models, serializers, utils def test_actor_serializer_from_ap(db): payload = { - 'id': 'https://test.federation/user', - 'type': 'Person', - 'following': 'https://test.federation/user/following', - 'followers': 'https://test.federation/user/followers', - 'inbox': 'https://test.federation/user/inbox', - 'outbox': 'https://test.federation/user/outbox', - 'preferredUsername': 'user', - 'name': 'Real User', - 'summary': 'Hello world', - 'url': 'https://test.federation/@user', - 'manuallyApprovesFollowers': False, - 'publicKey': { - 'id': 'https://test.federation/user#main-key', - 'owner': 'https://test.federation/user', - 'publicKeyPem': 'yolo' - }, - 'endpoints': { - 'sharedInbox': 'https://test.federation/inbox' + "id": "https://test.federation/user", + "type": "Person", + "following": "https://test.federation/user/following", + "followers": "https://test.federation/user/followers", + "inbox": "https://test.federation/user/inbox", + "outbox": "https://test.federation/user/outbox", + "preferredUsername": "user", + "name": "Real User", + "summary": "Hello world", + "url": "https://test.federation/@user", + "manuallyApprovesFollowers": False, + "publicKey": { + "id": "https://test.federation/user#main-key", + "owner": "https://test.federation/user", + "publicKeyPem": "yolo", }, + "endpoints": {"sharedInbox": "https://test.federation/inbox"}, } serializer = serializers.ActorSerializer(data=payload) @@ -39,30 +31,30 @@ def test_actor_serializer_from_ap(db): actor = serializer.build() - assert actor.url == payload['id'] - assert actor.inbox_url == payload['inbox'] - assert actor.outbox_url == payload['outbox'] - assert actor.shared_inbox_url == payload['endpoints']['sharedInbox'] - assert actor.followers_url == payload['followers'] - assert actor.following_url == payload['following'] - assert actor.public_key == payload['publicKey']['publicKeyPem'] - assert actor.preferred_username == payload['preferredUsername'] - assert actor.name == payload['name'] - assert actor.domain == 'test.federation' - assert actor.summary == payload['summary'] - assert actor.type == 'Person' - assert actor.manually_approves_followers == payload['manuallyApprovesFollowers'] + assert actor.url == payload["id"] + assert actor.inbox_url == payload["inbox"] + assert actor.outbox_url == payload["outbox"] + assert actor.shared_inbox_url == payload["endpoints"]["sharedInbox"] + assert actor.followers_url == payload["followers"] + assert actor.following_url == payload["following"] + assert actor.public_key == payload["publicKey"]["publicKeyPem"] + assert actor.preferred_username == payload["preferredUsername"] + assert actor.name == payload["name"] + assert actor.domain == "test.federation" + assert actor.summary == payload["summary"] + assert actor.type == "Person" + assert actor.manually_approves_followers == payload["manuallyApprovesFollowers"] def test_actor_serializer_only_mandatory_field_from_ap(db): payload = { - 'id': 'https://test.federation/user', - 'type': 'Person', - 'following': 'https://test.federation/user/following', - 'followers': 'https://test.federation/user/followers', - 'inbox': 'https://test.federation/user/inbox', - 'outbox': 'https://test.federation/user/outbox', - 'preferredUsername': 'user', + "id": "https://test.federation/user", + "type": "Person", + "following": "https://test.federation/user/following", + "followers": "https://test.federation/user/followers", + "inbox": "https://test.federation/user/inbox", + "outbox": "https://test.federation/user/outbox", + "preferredUsername": "user", } serializer = serializers.ActorSerializer(data=payload) @@ -70,58 +62,55 @@ def test_actor_serializer_only_mandatory_field_from_ap(db): actor = serializer.build() - assert actor.url == payload['id'] - assert actor.inbox_url == payload['inbox'] - assert actor.outbox_url == payload['outbox'] - assert actor.followers_url == payload['followers'] - assert actor.following_url == payload['following'] - assert actor.preferred_username == payload['preferredUsername'] - assert actor.domain == 'test.federation' - assert actor.type == 'Person' + assert actor.url == payload["id"] + assert actor.inbox_url == payload["inbox"] + assert actor.outbox_url == payload["outbox"] + assert actor.followers_url == payload["followers"] + assert actor.following_url == payload["following"] + assert actor.preferred_username == payload["preferredUsername"] + assert actor.domain == "test.federation" + assert actor.type == "Person" assert actor.manually_approves_followers is None def test_actor_serializer_to_ap(): expected = { - '@context': [ - 'https://www.w3.org/ns/activitystreams', - 'https://w3id.org/security/v1', + "@context": [ + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1", {}, ], - 'id': 'https://test.federation/user', - 'type': 'Person', - 'following': 'https://test.federation/user/following', - 'followers': 'https://test.federation/user/followers', - 'inbox': 'https://test.federation/user/inbox', - 'outbox': 'https://test.federation/user/outbox', - 'preferredUsername': 'user', - 'name': 'Real User', - 'summary': 'Hello world', - 'manuallyApprovesFollowers': False, - 'publicKey': { - 'id': 'https://test.federation/user#main-key', - 'owner': 'https://test.federation/user', - 'publicKeyPem': 'yolo' - }, - 'endpoints': { - 'sharedInbox': 'https://test.federation/inbox' + "id": "https://test.federation/user", + "type": "Person", + "following": "https://test.federation/user/following", + "followers": "https://test.federation/user/followers", + "inbox": "https://test.federation/user/inbox", + "outbox": "https://test.federation/user/outbox", + "preferredUsername": "user", + "name": "Real User", + "summary": "Hello world", + "manuallyApprovesFollowers": False, + "publicKey": { + "id": "https://test.federation/user#main-key", + "owner": "https://test.federation/user", + "publicKeyPem": "yolo", }, + "endpoints": {"sharedInbox": "https://test.federation/inbox"}, } ac = models.Actor( - url=expected['id'], - inbox_url=expected['inbox'], - outbox_url=expected['outbox'], - shared_inbox_url=expected['endpoints']['sharedInbox'], - followers_url=expected['followers'], - following_url=expected['following'], - public_key=expected['publicKey']['publicKeyPem'], - preferred_username=expected['preferredUsername'], - name=expected['name'], - domain='test.federation', - summary=expected['summary'], - type='Person', + url=expected["id"], + inbox_url=expected["inbox"], + outbox_url=expected["outbox"], + shared_inbox_url=expected["endpoints"]["sharedInbox"], + followers_url=expected["followers"], + following_url=expected["following"], + public_key=expected["publicKey"]["publicKeyPem"], + preferred_username=expected["preferredUsername"], + name=expected["name"], + domain="test.federation", + summary=expected["summary"], + type="Person", manually_approves_followers=False, - ) serializer = serializers.ActorSerializer(ac) @@ -130,22 +119,20 @@ def test_actor_serializer_to_ap(): def test_webfinger_serializer(): expected = { - 'subject': 'acct:service@test.federation', - 'links': [ + "subject": "acct:service@test.federation", + "links": [ { - 'rel': 'self', - 'href': 'https://test.federation/federation/instance/actor', - 'type': 'application/activity+json', + "rel": "self", + "href": "https://test.federation/federation/instance/actor", + "type": "application/activity+json", } ], - 'aliases': [ - 'https://test.federation/federation/instance/actor', - ] + "aliases": ["https://test.federation/federation/instance/actor"], } actor = models.Actor( - url=expected['links'][0]['href'], - preferred_username='service', - domain='test.federation', + url=expected["links"][0]["href"], + preferred_username="service", + domain="test.federation", ) serializer = serializers.ActorWebfingerSerializer(actor) @@ -153,33 +140,33 @@ def test_webfinger_serializer(): def test_follow_serializer_to_ap(factories): - follow = factories['federation.Follow'](local=True) + follow = factories["federation.Follow"](local=True) serializer = serializers.FollowSerializer(follow) expected = { - '@context': [ - 'https://www.w3.org/ns/activitystreams', - 'https://w3id.org/security/v1', + "@context": [ + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1", {}, ], - 'id': follow.get_federation_url(), - 'type': 'Follow', - 'actor': follow.actor.url, - 'object': follow.target.url, + "id": follow.get_federation_url(), + "type": "Follow", + "actor": follow.actor.url, + "object": follow.target.url, } assert serializer.data == expected def test_follow_serializer_save(factories): - actor = factories['federation.Actor']() - target = factories['federation.Actor']() + actor = factories["federation.Actor"]() + target = factories["federation.Actor"]() - data = expected = { - 'id': 'https://test.follow', - 'type': 'Follow', - 'actor': actor.url, - 'object': target.url, + data = { + "id": "https://test.follow", + "type": "Follow", + "actor": actor.url, + "object": target.url, } serializer = serializers.FollowSerializer(data=data) @@ -194,39 +181,39 @@ def test_follow_serializer_save(factories): def test_follow_serializer_save_validates_on_context(factories): - actor = factories['federation.Actor']() - target = factories['federation.Actor']() - impostor = factories['federation.Actor']() + actor = factories["federation.Actor"]() + target = factories["federation.Actor"]() + impostor = factories["federation.Actor"]() - data = expected = { - 'id': 'https://test.follow', - 'type': 'Follow', - 'actor': actor.url, - 'object': target.url, + data = { + "id": "https://test.follow", + "type": "Follow", + "actor": actor.url, + "object": target.url, } serializer = serializers.FollowSerializer( - data=data, - context={'follow_actor': impostor, 'follow_target': impostor}) + data=data, context={"follow_actor": impostor, "follow_target": impostor} + ) assert serializer.is_valid() is False - assert 'actor' in serializer.errors - assert 'object' in serializer.errors + assert "actor" in serializer.errors + assert "object" in serializer.errors def test_accept_follow_serializer_representation(factories): - follow = factories['federation.Follow'](approved=None) + follow = factories["federation.Follow"](approved=None) expected = { - '@context': [ - 'https://www.w3.org/ns/activitystreams', - 'https://w3id.org/security/v1', + "@context": [ + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1", {}, ], - 'id': follow.get_federation_url() + '/accept', - 'type': 'Accept', - 'actor': follow.target.url, - 'object': serializers.FollowSerializer(follow).data, + "id": follow.get_federation_url() + "/accept", + "type": "Accept", + "actor": follow.target.url, + "object": serializers.FollowSerializer(follow).data, } serializer = serializers.AcceptFollowSerializer(follow) @@ -235,18 +222,18 @@ def test_accept_follow_serializer_representation(factories): def test_accept_follow_serializer_save(factories): - follow = factories['federation.Follow'](approved=None) + follow = factories["federation.Follow"](approved=None) data = { - '@context': [ - 'https://www.w3.org/ns/activitystreams', - 'https://w3id.org/security/v1', + "@context": [ + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1", {}, ], - 'id': follow.get_federation_url() + '/accept', - 'type': 'Accept', - 'actor': follow.target.url, - 'object': serializers.FollowSerializer(follow).data, + "id": follow.get_federation_url() + "/accept", + "type": "Accept", + "actor": follow.target.url, + "object": serializers.FollowSerializer(follow).data, } serializer = serializers.AcceptFollowSerializer(data=data) @@ -259,42 +246,42 @@ def test_accept_follow_serializer_save(factories): def test_accept_follow_serializer_validates_on_context(factories): - follow = factories['federation.Follow'](approved=None) - impostor = factories['federation.Actor']() + follow = factories["federation.Follow"](approved=None) + impostor = factories["federation.Actor"]() data = { - '@context': [ - 'https://www.w3.org/ns/activitystreams', - 'https://w3id.org/security/v1', + "@context": [ + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1", {}, ], - 'id': follow.get_federation_url() + '/accept', - 'type': 'Accept', - 'actor': impostor.url, - 'object': serializers.FollowSerializer(follow).data, + "id": follow.get_federation_url() + "/accept", + "type": "Accept", + "actor": impostor.url, + "object": serializers.FollowSerializer(follow).data, } serializer = serializers.AcceptFollowSerializer( - data=data, - context={'follow_actor': impostor, 'follow_target': impostor}) + data=data, context={"follow_actor": impostor, "follow_target": impostor} + ) assert serializer.is_valid() is False - assert 'actor' in serializer.errors['object'] - assert 'object' in serializer.errors['object'] + assert "actor" in serializer.errors["object"] + assert "object" in serializer.errors["object"] def test_undo_follow_serializer_representation(factories): - follow = factories['federation.Follow'](approved=True) + follow = factories["federation.Follow"](approved=True) expected = { - '@context': [ - 'https://www.w3.org/ns/activitystreams', - 'https://w3id.org/security/v1', + "@context": [ + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1", {}, ], - 'id': follow.get_federation_url() + '/undo', - 'type': 'Undo', - 'actor': follow.actor.url, - 'object': serializers.FollowSerializer(follow).data, + "id": follow.get_federation_url() + "/undo", + "type": "Undo", + "actor": follow.actor.url, + "object": serializers.FollowSerializer(follow).data, } serializer = serializers.UndoFollowSerializer(follow) @@ -303,18 +290,18 @@ def test_undo_follow_serializer_representation(factories): def test_undo_follow_serializer_save(factories): - follow = factories['federation.Follow'](approved=True) + follow = factories["federation.Follow"](approved=True) data = { - '@context': [ - 'https://www.w3.org/ns/activitystreams', - 'https://w3id.org/security/v1', + "@context": [ + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1", {}, ], - 'id': follow.get_federation_url() + '/undo', - 'type': 'Undo', - 'actor': follow.actor.url, - 'object': serializers.FollowSerializer(follow).data, + "id": follow.get_federation_url() + "/undo", + "type": "Undo", + "actor": follow.actor.url, + "object": serializers.FollowSerializer(follow).data, } serializer = serializers.UndoFollowSerializer(data=data) @@ -326,53 +313,53 @@ def test_undo_follow_serializer_save(factories): def test_undo_follow_serializer_validates_on_context(factories): - follow = factories['federation.Follow'](approved=True) - impostor = factories['federation.Actor']() + follow = factories["federation.Follow"](approved=True) + impostor = factories["federation.Actor"]() data = { - '@context': [ - 'https://www.w3.org/ns/activitystreams', - 'https://w3id.org/security/v1', + "@context": [ + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1", {}, ], - 'id': follow.get_federation_url() + '/undo', - 'type': 'Undo', - 'actor': impostor.url, - 'object': serializers.FollowSerializer(follow).data, + "id": follow.get_federation_url() + "/undo", + "type": "Undo", + "actor": impostor.url, + "object": serializers.FollowSerializer(follow).data, } serializer = serializers.UndoFollowSerializer( - data=data, - context={'follow_actor': impostor, 'follow_target': impostor}) + data=data, context={"follow_actor": impostor, "follow_target": impostor} + ) assert serializer.is_valid() is False - assert 'actor' in serializer.errors['object'] - assert 'object' in serializer.errors['object'] + assert "actor" in serializer.errors["object"] + assert "object" in serializer.errors["object"] def test_paginated_collection_serializer(factories): - tfs = factories['music.TrackFile'].create_batch(size=5) - actor = factories['federation.Actor'](local=True) + tfs = factories["music.TrackFile"].create_batch(size=5) + actor = factories["federation.Actor"](local=True) conf = { - 'id': 'https://test.federation/test', - 'items': tfs, - 'item_serializer': serializers.AudioSerializer, - 'actor': actor, - 'page_size': 2, + "id": "https://test.federation/test", + "items": tfs, + "item_serializer": serializers.AudioSerializer, + "actor": actor, + "page_size": 2, } expected = { - '@context': [ - 'https://www.w3.org/ns/activitystreams', - 'https://w3id.org/security/v1', + "@context": [ + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1", {}, ], - 'type': 'Collection', - 'id': conf['id'], - 'actor': actor.url, - 'totalItems': len(tfs), - 'current': conf['id'] + '?page=1', - 'last': conf['id'] + '?page=3', - 'first': conf['id'] + '?page=1', + "type": "Collection", + "id": conf["id"], + "actor": actor.url, + "totalItems": len(tfs), + "current": conf["id"] + "?page=1", + "last": conf["id"] + "?page=3", + "first": conf["id"] + "?page=1", } serializer = serializers.PaginatedCollectionSerializer(conf) @@ -382,108 +369,102 @@ def test_paginated_collection_serializer(factories): def test_paginated_collection_serializer_validation(): data = { - 'type': 'Collection', - 'id': 'https://test.federation/test', - 'totalItems': 5, - 'actor': 'http://test.actor', - 'first': 'https://test.federation/test?page=1', - 'last': 'https://test.federation/test?page=1', - 'items': [] + "type": "Collection", + "id": "https://test.federation/test", + "totalItems": 5, + "actor": "http://test.actor", + "first": "https://test.federation/test?page=1", + "last": "https://test.federation/test?page=1", + "items": [], } - serializer = serializers.PaginatedCollectionSerializer( - data=data - ) + serializer = serializers.PaginatedCollectionSerializer(data=data) assert serializer.is_valid(raise_exception=True) is True - assert serializer.validated_data['totalItems'] == 5 - assert serializer.validated_data['id'] == data['id'] - assert serializer.validated_data['actor'] == data['actor'] + assert serializer.validated_data["totalItems"] == 5 + assert serializer.validated_data["id"] == data["id"] + assert serializer.validated_data["actor"] == data["actor"] def test_collection_page_serializer_validation(): - base = 'https://test.federation/test' + base = "https://test.federation/test" data = { - 'type': 'CollectionPage', - 'id': base + '?page=2', - 'totalItems': 5, - 'actor': 'https://test.actor', - 'items': [], - 'first': 'https://test.federation/test?page=1', - 'last': 'https://test.federation/test?page=3', - 'prev': base + '?page=1', - 'next': base + '?page=3', - 'partOf': base, + "type": "CollectionPage", + "id": base + "?page=2", + "totalItems": 5, + "actor": "https://test.actor", + "items": [], + "first": "https://test.federation/test?page=1", + "last": "https://test.federation/test?page=3", + "prev": base + "?page=1", + "next": base + "?page=3", + "partOf": base, } - serializer = serializers.CollectionPageSerializer( - data=data - ) + serializer = serializers.CollectionPageSerializer(data=data) assert serializer.is_valid(raise_exception=True) is True - assert serializer.validated_data['totalItems'] == 5 - assert serializer.validated_data['id'] == data['id'] - assert serializer.validated_data['actor'] == data['actor'] - assert serializer.validated_data['items'] == [] - assert serializer.validated_data['prev'] == data['prev'] - assert serializer.validated_data['next'] == data['next'] - assert serializer.validated_data['partOf'] == data['partOf'] + assert serializer.validated_data["totalItems"] == 5 + assert serializer.validated_data["id"] == data["id"] + assert serializer.validated_data["actor"] == data["actor"] + assert serializer.validated_data["items"] == [] + assert serializer.validated_data["prev"] == data["prev"] + assert serializer.validated_data["next"] == data["next"] + assert serializer.validated_data["partOf"] == data["partOf"] def test_collection_page_serializer_can_validate_child(): data = { - 'type': 'CollectionPage', - 'id': 'https://test.page?page=2', - 'actor': 'https://test.actor', - 'first': 'https://test.page?page=1', - 'last': 'https://test.page?page=3', - 'partOf': 'https://test.page', - 'totalItems': 1, - 'items': [{'in': 'valid'}], + "type": "CollectionPage", + "id": "https://test.page?page=2", + "actor": "https://test.actor", + "first": "https://test.page?page=1", + "last": "https://test.page?page=3", + "partOf": "https://test.page", + "totalItems": 1, + "items": [{"in": "valid"}], } serializer = serializers.CollectionPageSerializer( - data=data, - context={'item_serializer': serializers.AudioSerializer} + data=data, context={"item_serializer": serializers.AudioSerializer} ) # child are validated but not included in data if not valid assert serializer.is_valid(raise_exception=True) is True - assert len(serializer.validated_data['items']) == 0 + assert len(serializer.validated_data["items"]) == 0 def test_collection_page_serializer(factories): - tfs = factories['music.TrackFile'].create_batch(size=5) - actor = factories['federation.Actor'](local=True) + tfs = factories["music.TrackFile"].create_batch(size=5) + actor = factories["federation.Actor"](local=True) conf = { - 'id': 'https://test.federation/test', - 'item_serializer': serializers.AudioSerializer, - 'actor': actor, - 'page': Paginator(tfs, 2).page(2), + "id": "https://test.federation/test", + "item_serializer": serializers.AudioSerializer, + "actor": actor, + "page": Paginator(tfs, 2).page(2), } expected = { - '@context': [ - 'https://www.w3.org/ns/activitystreams', - 'https://w3id.org/security/v1', + "@context": [ + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1", {}, ], - 'type': 'CollectionPage', - 'id': conf['id'] + '?page=2', - 'actor': actor.url, - 'totalItems': len(tfs), - 'partOf': conf['id'], - 'prev': conf['id'] + '?page=1', - 'next': conf['id'] + '?page=3', - 'first': conf['id'] + '?page=1', - 'last': conf['id'] + '?page=3', - 'items': [ - conf['item_serializer']( - i, - context={'actor': actor, 'include_ap_context': False} + "type": "CollectionPage", + "id": conf["id"] + "?page=2", + "actor": actor.url, + "totalItems": len(tfs), + "partOf": conf["id"], + "prev": conf["id"] + "?page=1", + "next": conf["id"] + "?page=3", + "first": conf["id"] + "?page=1", + "last": conf["id"] + "?page=3", + "items": [ + conf["item_serializer"]( + i, context={"actor": actor, "include_ap_context": False} ).data - for i in conf['page'].object_list - ] + for i in conf["page"].object_list + ], } serializer = serializers.CollectionPageSerializer(conf) @@ -492,35 +473,37 @@ def test_collection_page_serializer(factories): def test_activity_pub_audio_serializer_to_library_track(factories): - remote_library = factories['federation.Library']() - audio = factories['federation.Audio']() + remote_library = factories["federation.Library"]() + audio = factories["federation.Audio"]() serializer = serializers.AudioSerializer( - data=audio, context={'library': remote_library}) + data=audio, context={"library": remote_library} + ) assert serializer.is_valid(raise_exception=True) lt = serializer.save() assert lt.pk is not None - assert lt.url == audio['id'] + assert lt.url == audio["id"] assert lt.library == remote_library - assert lt.audio_url == audio['url']['href'] - assert lt.audio_mimetype == audio['url']['mediaType'] - assert lt.metadata == audio['metadata'] - assert lt.title == audio['metadata']['recording']['title'] - assert lt.artist_name == audio['metadata']['artist']['name'] - assert lt.album_title == audio['metadata']['release']['title'] - assert lt.published_date == arrow.get(audio['published']) + assert lt.audio_url == audio["url"]["href"] + assert lt.audio_mimetype == audio["url"]["mediaType"] + assert lt.metadata == audio["metadata"] + assert lt.title == audio["metadata"]["recording"]["title"] + assert lt.artist_name == audio["metadata"]["artist"]["name"] + assert lt.album_title == audio["metadata"]["release"]["title"] + assert lt.published_date == arrow.get(audio["published"]) -def test_activity_pub_audio_serializer_to_library_track_no_duplicate( - factories): - remote_library = factories['federation.Library']() - audio = factories['federation.Audio']() +def test_activity_pub_audio_serializer_to_library_track_no_duplicate(factories): + remote_library = factories["federation.Library"]() + audio = factories["federation.Audio"]() serializer1 = serializers.AudioSerializer( - data=audio, context={'library': remote_library}) + data=audio, context={"library": remote_library} + ) serializer2 = serializers.AudioSerializer( - data=audio, context={'library': remote_library}) + data=audio, context={"library": remote_library} + ) assert serializer1.is_valid() is True assert serializer2.is_valid() is True @@ -533,192 +516,168 @@ def test_activity_pub_audio_serializer_to_library_track_no_duplicate( def test_activity_pub_audio_serializer_to_ap(factories): - tf = factories['music.TrackFile']( - mimetype='audio/mp3', - bitrate=42, - duration=43, - size=44, + tf = factories["music.TrackFile"]( + mimetype="audio/mp3", bitrate=42, duration=43, size=44 ) - library = actors.SYSTEM_ACTORS['library'].get_actor_instance() + library = actors.SYSTEM_ACTORS["library"].get_actor_instance() expected = { - '@context': serializers.AP_CONTEXT, - 'type': 'Audio', - 'id': tf.get_federation_url(), - 'name': tf.track.full_name, - 'published': tf.creation_date.isoformat(), - 'updated': tf.modification_date.isoformat(), - 'metadata': { - 'artist': { - 'musicbrainz_id': tf.track.artist.mbid, - 'name': tf.track.artist.name, + "@context": serializers.AP_CONTEXT, + "type": "Audio", + "id": tf.get_federation_url(), + "name": tf.track.full_name, + "published": tf.creation_date.isoformat(), + "updated": tf.modification_date.isoformat(), + "metadata": { + "artist": { + "musicbrainz_id": tf.track.artist.mbid, + "name": tf.track.artist.name, }, - 'release': { - 'musicbrainz_id': tf.track.album.mbid, - 'title': tf.track.album.title, + "release": { + "musicbrainz_id": tf.track.album.mbid, + "title": tf.track.album.title, }, - 'recording': { - 'musicbrainz_id': tf.track.mbid, - 'title': tf.track.title, - }, - 'size': tf.size, - 'length': tf.duration, - 'bitrate': tf.bitrate, + "recording": {"musicbrainz_id": tf.track.mbid, "title": tf.track.title}, + "size": tf.size, + "length": tf.duration, + "bitrate": tf.bitrate, }, - 'url': { - 'href': utils.full_url(tf.path), - 'type': 'Link', - 'mediaType': 'audio/mp3' + "url": { + "href": utils.full_url(tf.path), + "type": "Link", + "mediaType": "audio/mp3", }, - 'attributedTo': [ - library.url - ] + "attributedTo": [library.url], } - serializer = serializers.AudioSerializer(tf, context={'actor': library}) + serializer = serializers.AudioSerializer(tf, context={"actor": library}) assert serializer.data == expected def test_activity_pub_audio_serializer_to_ap_no_mbid(factories): - tf = factories['music.TrackFile']( - mimetype='audio/mp3', + tf = factories["music.TrackFile"]( + mimetype="audio/mp3", track__mbid=None, track__album__mbid=None, track__album__artist__mbid=None, ) - library = actors.SYSTEM_ACTORS['library'].get_actor_instance() + library = actors.SYSTEM_ACTORS["library"].get_actor_instance() expected = { - '@context': serializers.AP_CONTEXT, - 'type': 'Audio', - 'id': tf.get_federation_url(), - 'name': tf.track.full_name, - 'published': tf.creation_date.isoformat(), - 'updated': tf.modification_date.isoformat(), - 'metadata': { - 'artist': { - 'name': tf.track.artist.name, - 'musicbrainz_id': None, - }, - 'release': { - 'title': tf.track.album.title, - 'musicbrainz_id': None, - }, - 'recording': { - 'title': tf.track.title, - 'musicbrainz_id': None, - }, - 'size': None, - 'length': None, - 'bitrate': None, + "@context": serializers.AP_CONTEXT, + "type": "Audio", + "id": tf.get_federation_url(), + "name": tf.track.full_name, + "published": tf.creation_date.isoformat(), + "updated": tf.modification_date.isoformat(), + "metadata": { + "artist": {"name": tf.track.artist.name, "musicbrainz_id": None}, + "release": {"title": tf.track.album.title, "musicbrainz_id": None}, + "recording": {"title": tf.track.title, "musicbrainz_id": None}, + "size": None, + "length": None, + "bitrate": None, }, - 'url': { - 'href': utils.full_url(tf.path), - 'type': 'Link', - 'mediaType': 'audio/mp3' + "url": { + "href": utils.full_url(tf.path), + "type": "Link", + "mediaType": "audio/mp3", }, - 'attributedTo': [ - library.url - ] + "attributedTo": [library.url], } - serializer = serializers.AudioSerializer(tf, context={'actor': library}) + serializer = serializers.AudioSerializer(tf, context={"actor": library}) assert serializer.data == expected def test_collection_serializer_to_ap(factories): - tf1 = factories['music.TrackFile'](mimetype='audio/mp3') - tf2 = factories['music.TrackFile'](mimetype='audio/ogg') - library = actors.SYSTEM_ACTORS['library'].get_actor_instance() + tf1 = factories["music.TrackFile"](mimetype="audio/mp3") + tf2 = factories["music.TrackFile"](mimetype="audio/ogg") + library = actors.SYSTEM_ACTORS["library"].get_actor_instance() expected = { - '@context': serializers.AP_CONTEXT, - 'id': 'https://test.id', - 'actor': library.url, - 'totalItems': 2, - 'type': 'Collection', - 'items': [ + "@context": serializers.AP_CONTEXT, + "id": "https://test.id", + "actor": library.url, + "totalItems": 2, + "type": "Collection", + "items": [ serializers.AudioSerializer( - tf1, context={'actor': library, 'include_ap_context': False} + tf1, context={"actor": library, "include_ap_context": False} ).data, serializers.AudioSerializer( - tf2, context={'actor': library, 'include_ap_context': False} + tf2, context={"actor": library, "include_ap_context": False} ).data, - ] + ], } collection = { - 'id': expected['id'], - 'actor': library, - 'items': [tf1, tf2], - 'item_serializer': serializers.AudioSerializer + "id": expected["id"], + "actor": library, + "items": [tf1, tf2], + "item_serializer": serializers.AudioSerializer, } serializer = serializers.CollectionSerializer( - collection, context={'actor': library, 'id': 'https://test.id'}) + collection, context={"actor": library, "id": "https://test.id"} + ) assert serializer.data == expected def test_api_library_create_serializer_save(factories, r_mock): - library_actor = actors.SYSTEM_ACTORS['library'].get_actor_instance() - actor = factories['federation.Actor']() - follow = factories['federation.Follow']( - target=actor, - actor=library_actor, - ) + library_actor = actors.SYSTEM_ACTORS["library"].get_actor_instance() + actor = factories["federation.Actor"]() + follow = factories["federation.Follow"](target=actor, actor=library_actor) actor_data = serializers.ActorSerializer(actor).data - actor_data['url'] = [{ - 'href': 'https://test.library', - 'name': 'library', - 'type': 'Link', - }] + actor_data["url"] = [ + {"href": "https://test.library", "name": "library", "type": "Link"} + ] library_conf = { - 'id': 'https://test.library', - 'items': range(10), - 'actor': actor, - 'page_size': 5, + "id": "https://test.library", + "items": range(10), + "actor": actor, + "page_size": 5, } library_data = serializers.PaginatedCollectionSerializer(library_conf).data r_mock.get(actor.url, json=actor_data) - r_mock.get('https://test.library', json=library_data) + r_mock.get("https://test.library", json=library_data) data = { - 'actor': actor.url, - 'autoimport': False, - 'federation_enabled': True, - 'download_files': False, + "actor": actor.url, + "autoimport": False, + "federation_enabled": True, + "download_files": False, } serializer = serializers.APILibraryCreateSerializer(data=data) assert serializer.is_valid(raise_exception=True) is True library = serializer.save() - follow = models.Follow.objects.get( - target=actor, actor=library_actor, approved=None) + follow = models.Follow.objects.get(target=actor, actor=library_actor, approved=None) - assert library.autoimport is data['autoimport'] - assert library.federation_enabled is data['federation_enabled'] - assert library.download_files is data['download_files'] + assert library.autoimport is data["autoimport"] + assert library.federation_enabled is data["federation_enabled"] + assert library.download_files is data["download_files"] assert library.tracks_count == 10 assert library.actor == actor assert library.follow == follow def test_tapi_library_track_serializer_not_imported(factories): - lt = factories['federation.LibraryTrack']() + lt = factories["federation.LibraryTrack"]() serializer = serializers.APILibraryTrackSerializer(lt) - assert serializer.get_status(lt) == 'not_imported' + assert serializer.get_status(lt) == "not_imported" def test_tapi_library_track_serializer_imported(factories): - tf = factories['music.TrackFile'](federation=True) + tf = factories["music.TrackFile"](federation=True) lt = tf.library_track serializer = serializers.APILibraryTrackSerializer(lt) - assert serializer.get_status(lt) == 'imported' + assert serializer.get_status(lt) == "imported" def test_tapi_library_track_serializer_import_pending(factories): - job = factories['music.ImportJob'](federation=True, status='pending') + job = factories["music.ImportJob"](federation=True, status="pending") lt = job.library_track serializer = serializers.APILibraryTrackSerializer(lt) - assert serializer.get_status(lt) == 'import_pending' + assert serializer.get_status(lt) == "import_pending" diff --git a/api/tests/federation/test_signing.py b/api/tests/federation/test_signing.py index 0c1ec2e0b..159f31cd9 100644 --- a/api/tests/federation/test_signing.py +++ b/api/tests/federation/test_signing.py @@ -1,43 +1,35 @@ import cryptography.exceptions -import io import pytest -import requests_http_signature -from funkwhale_api.federation import signing -from funkwhale_api.federation import keys +from funkwhale_api.federation import keys, signing def test_can_sign_and_verify_request(nodb_factories): - private, public = nodb_factories['federation.KeyPair']() - auth = nodb_factories['federation.SignatureAuth'](key=private) - request = nodb_factories['federation.SignedRequest']( - auth=auth - ) + private, public = nodb_factories["federation.KeyPair"]() + auth = nodb_factories["federation.SignatureAuth"](key=private) + request = nodb_factories["federation.SignedRequest"](auth=auth) prepared_request = request.prepare() - assert 'date' in prepared_request.headers - assert 'signature' in prepared_request.headers - assert signing.verify( - prepared_request, public) is None + assert "date" in prepared_request.headers + assert "signature" in prepared_request.headers + assert signing.verify(prepared_request, public) is None def test_can_sign_and_verify_request_digest(nodb_factories): - private, public = nodb_factories['federation.KeyPair']() - auth = nodb_factories['federation.SignatureAuth'](key=private) - request = nodb_factories['federation.SignedRequest']( - auth=auth, - method='post', - data=b'hello=world' + private, public = nodb_factories["federation.KeyPair"]() + auth = nodb_factories["federation.SignatureAuth"](key=private) + request = nodb_factories["federation.SignedRequest"]( + auth=auth, method="post", data=b"hello=world" ) prepared_request = request.prepare() - assert 'date' in prepared_request.headers - assert 'digest' in prepared_request.headers - assert 'signature' in prepared_request.headers + assert "date" in prepared_request.headers + assert "digest" in prepared_request.headers + assert "signature" in prepared_request.headers assert signing.verify(prepared_request, public) is None def test_verify_fails_with_wrong_key(nodb_factories): - wrong_private, wrong_public = nodb_factories['federation.KeyPair']() - request = nodb_factories['federation.SignedRequest']() + wrong_private, wrong_public = nodb_factories["federation.KeyPair"]() + request = nodb_factories["federation.SignedRequest"]() prepared_request = request.prepare() with pytest.raises(cryptography.exceptions.InvalidSignature): @@ -46,18 +38,15 @@ def test_verify_fails_with_wrong_key(nodb_factories): def test_can_verify_django_request(factories, fake_request): private_key, public_key = keys.get_key_pair() - signed_request = factories['federation.SignedRequest']( - auth__key=private_key, - auth__headers=[ - 'date', - ] + signed_request = factories["federation.SignedRequest"]( + auth__key=private_key, auth__headers=["date"] ) prepared = signed_request.prepare() django_request = fake_request.get( - '/', + "/", **{ - 'HTTP_DATE': prepared.headers['date'], - 'HTTP_SIGNATURE': prepared.headers['signature'], + "HTTP_DATE": prepared.headers["date"], + "HTTP_SIGNATURE": prepared.headers["signature"], } ) assert signing.verify_django(django_request, public_key) is None @@ -65,22 +54,19 @@ def test_can_verify_django_request(factories, fake_request): def test_can_verify_django_request_digest(factories, fake_request): private_key, public_key = keys.get_key_pair() - signed_request = factories['federation.SignedRequest']( + signed_request = factories["federation.SignedRequest"]( auth__key=private_key, - method='post', - data=b'hello=world', - auth__headers=[ - 'date', - 'digest', - ] + method="post", + data=b"hello=world", + auth__headers=["date", "digest"], ) prepared = signed_request.prepare() django_request = fake_request.post( - '/', + "/", **{ - 'HTTP_DATE': prepared.headers['date'], - 'HTTP_DIGEST': prepared.headers['digest'], - 'HTTP_SIGNATURE': prepared.headers['signature'], + "HTTP_DATE": prepared.headers["date"], + "HTTP_DIGEST": prepared.headers["digest"], + "HTTP_SIGNATURE": prepared.headers["signature"], } ) @@ -89,22 +75,19 @@ def test_can_verify_django_request_digest(factories, fake_request): def test_can_verify_django_request_digest_failure(factories, fake_request): private_key, public_key = keys.get_key_pair() - signed_request = factories['federation.SignedRequest']( + signed_request = factories["federation.SignedRequest"]( auth__key=private_key, - method='post', - data=b'hello=world', - auth__headers=[ - 'date', - 'digest', - ] + method="post", + data=b"hello=world", + auth__headers=["date", "digest"], ) prepared = signed_request.prepare() django_request = fake_request.post( - '/', + "/", **{ - 'HTTP_DATE': prepared.headers['date'], - 'HTTP_DIGEST': prepared.headers['digest'] + 'noop', - 'HTTP_SIGNATURE': prepared.headers['signature'], + "HTTP_DATE": prepared.headers["date"], + "HTTP_DIGEST": prepared.headers["digest"] + "noop", + "HTTP_SIGNATURE": prepared.headers["signature"], } ) @@ -114,19 +97,12 @@ def test_can_verify_django_request_digest_failure(factories, fake_request): def test_can_verify_django_request_failure(factories, fake_request): private_key, public_key = keys.get_key_pair() - signed_request = factories['federation.SignedRequest']( - auth__key=private_key, - auth__headers=[ - 'date', - ] + signed_request = factories["federation.SignedRequest"]( + auth__key=private_key, auth__headers=["date"] ) prepared = signed_request.prepare() django_request = fake_request.get( - '/', - **{ - 'HTTP_DATE': 'Wrong', - 'HTTP_SIGNATURE': prepared.headers['signature'], - } + "/", **{"HTTP_DATE": "Wrong", "HTTP_SIGNATURE": prepared.headers["signature"]} ) with pytest.raises(cryptography.exceptions.InvalidSignature): signing.verify_django(django_request, public_key) diff --git a/api/tests/federation/test_tasks.py b/api/tests/federation/test_tasks.py index 3517e8feb..bc10eae95 100644 --- a/api/tests/federation/test_tasks.py +++ b/api/tests/federation/test_tasks.py @@ -1,132 +1,119 @@ import datetime import os import pathlib -import pytest from django.core.paginator import Paginator from django.utils import timezone -from funkwhale_api.federation import serializers -from funkwhale_api.federation import tasks +from funkwhale_api.federation import serializers, tasks def test_scan_library_does_nothing_if_federation_disabled(mocker, factories): - library = factories['federation.Library'](federation_enabled=False) + library = factories["federation.Library"](federation_enabled=False) tasks.scan_library(library_id=library.pk) assert library.tracks.count() == 0 -def test_scan_library_page_does_nothing_if_federation_disabled( - mocker, factories): - library = factories['federation.Library'](federation_enabled=False) +def test_scan_library_page_does_nothing_if_federation_disabled(mocker, factories): + library = factories["federation.Library"](federation_enabled=False) tasks.scan_library_page(library_id=library.pk, page_url=None) assert library.tracks.count() == 0 -def test_scan_library_fetches_page_and_calls_scan_page( - mocker, factories, r_mock): +def test_scan_library_fetches_page_and_calls_scan_page(mocker, factories, r_mock): now = timezone.now() - library = factories['federation.Library'](federation_enabled=True) + library = factories["federation.Library"](federation_enabled=True) collection_conf = { - 'actor': library.actor, - 'id': library.url, - 'page_size': 10, - 'items': range(10), + "actor": library.actor, + "id": library.url, + "page_size": 10, + "items": range(10), } collection = serializers.PaginatedCollectionSerializer(collection_conf) - scan_page = mocker.patch( - 'funkwhale_api.federation.tasks.scan_library_page.delay') - r_mock.get(collection_conf['id'], json=collection.data) + scan_page = mocker.patch("funkwhale_api.federation.tasks.scan_library_page.delay") + r_mock.get(collection_conf["id"], json=collection.data) tasks.scan_library(library_id=library.pk) scan_page.assert_called_once_with( - library_id=library.id, - page_url=collection.data['first'], - until=None, + library_id=library.id, page_url=collection.data["first"], until=None ) library.refresh_from_db() assert library.fetched_date > now -def test_scan_page_fetches_page_and_creates_tracks( - mocker, factories, r_mock): - library = factories['federation.Library'](federation_enabled=True) - tfs = factories['music.TrackFile'].create_batch(size=5) +def test_scan_page_fetches_page_and_creates_tracks(mocker, factories, r_mock): + library = factories["federation.Library"](federation_enabled=True) + tfs = factories["music.TrackFile"].create_batch(size=5) page_conf = { - 'actor': library.actor, - 'id': library.url, - 'page': Paginator(tfs, 5).page(1), - 'item_serializer': serializers.AudioSerializer, + "actor": library.actor, + "id": library.url, + "page": Paginator(tfs, 5).page(1), + "item_serializer": serializers.AudioSerializer, } page = serializers.CollectionPageSerializer(page_conf) - r_mock.get(page.data['id'], json=page.data) + r_mock.get(page.data["id"], json=page.data) - tasks.scan_library_page(library_id=library.pk, page_url=page.data['id']) + tasks.scan_library_page(library_id=library.pk, page_url=page.data["id"]) - lts = list(library.tracks.all().order_by('-published_date')) + lts = list(library.tracks.all().order_by("-published_date")) assert len(lts) == 5 -def test_scan_page_trigger_next_page_scan_skip_if_same( - mocker, factories, r_mock): +def test_scan_page_trigger_next_page_scan_skip_if_same(mocker, factories, r_mock): patched_scan = mocker.patch( - 'funkwhale_api.federation.tasks.scan_library_page.delay' + "funkwhale_api.federation.tasks.scan_library_page.delay" ) - library = factories['federation.Library'](federation_enabled=True) - tfs = factories['music.TrackFile'].create_batch(size=1) + library = factories["federation.Library"](federation_enabled=True) + tfs = factories["music.TrackFile"].create_batch(size=1) page_conf = { - 'actor': library.actor, - 'id': library.url, - 'page': Paginator(tfs, 3).page(1), - 'item_serializer': serializers.AudioSerializer, + "actor": library.actor, + "id": library.url, + "page": Paginator(tfs, 3).page(1), + "item_serializer": serializers.AudioSerializer, } page = serializers.CollectionPageSerializer(page_conf) data = page.data - data['next'] = data['id'] - r_mock.get(page.data['id'], json=data) + data["next"] = data["id"] + r_mock.get(page.data["id"], json=data) - tasks.scan_library_page(library_id=library.pk, page_url=data['id']) + tasks.scan_library_page(library_id=library.pk, page_url=data["id"]) patched_scan.assert_not_called() -def test_scan_page_stops_once_until_is_reached( - mocker, factories, r_mock): - library = factories['federation.Library'](federation_enabled=True) - tfs = list(reversed(factories['music.TrackFile'].create_batch(size=5))) +def test_scan_page_stops_once_until_is_reached(mocker, factories, r_mock): + library = factories["federation.Library"](federation_enabled=True) + tfs = list(reversed(factories["music.TrackFile"].create_batch(size=5))) page_conf = { - 'actor': library.actor, - 'id': library.url, - 'page': Paginator(tfs, 3).page(1), - 'item_serializer': serializers.AudioSerializer, + "actor": library.actor, + "id": library.url, + "page": Paginator(tfs, 3).page(1), + "item_serializer": serializers.AudioSerializer, } page = serializers.CollectionPageSerializer(page_conf) - r_mock.get(page.data['id'], json=page.data) + r_mock.get(page.data["id"], json=page.data) tasks.scan_library_page( - library_id=library.pk, - page_url=page.data['id'], - until=tfs[1].creation_date) + library_id=library.pk, page_url=page.data["id"], until=tfs[1].creation_date + ) - lts = list(library.tracks.all().order_by('-published_date')) + lts = list(library.tracks.all().order_by("-published_date")) assert len(lts) == 2 for i, tf in enumerate(tfs[:1]): assert tf.creation_date == lts[i].published_date def test_clean_federation_music_cache_if_no_listen(preferences, factories): - preferences['federation__music_cache_duration'] = 60 - lt1 = factories['federation.LibraryTrack'](with_audio_file=True) - lt2 = factories['federation.LibraryTrack'](with_audio_file=True) - lt3 = factories['federation.LibraryTrack'](with_audio_file=True) - tf1 = factories['music.TrackFile']( - accessed_date=timezone.now(), library_track=lt1) - tf2 = factories['music.TrackFile']( - accessed_date=timezone.now()-datetime.timedelta(minutes=61), - library_track=lt2) - tf3 = factories['music.TrackFile']( - accessed_date=None, library_track=lt3) + preferences["federation__music_cache_duration"] = 60 + lt1 = factories["federation.LibraryTrack"](with_audio_file=True) + lt2 = factories["federation.LibraryTrack"](with_audio_file=True) + lt3 = factories["federation.LibraryTrack"](with_audio_file=True) + factories["music.TrackFile"](accessed_date=timezone.now(), library_track=lt1) + factories["music.TrackFile"]( + accessed_date=timezone.now() - datetime.timedelta(minutes=61), library_track=lt2 + ) + factories["music.TrackFile"](accessed_date=None, library_track=lt3) path1 = lt1.audio_file.path path2 = lt2.audio_file.path path3 = lt3.audio_file.path @@ -145,22 +132,19 @@ def test_clean_federation_music_cache_if_no_listen(preferences, factories): assert os.path.exists(path3) is False -def test_clean_federation_music_cache_orphaned( - settings, preferences, factories): - preferences['federation__music_cache_duration'] = 60 - path = os.path.join(settings.MEDIA_ROOT, 'federation_cache') - keep_path = os.path.join(os.path.join(path, '1a', 'b2'), 'keep.ogg') - remove_path = os.path.join(os.path.join(path, 'c3', 'd4'), 'remove.ogg') +def test_clean_federation_music_cache_orphaned(settings, preferences, factories): + preferences["federation__music_cache_duration"] = 60 + path = os.path.join(settings.MEDIA_ROOT, "federation_cache") + keep_path = os.path.join(os.path.join(path, "1a", "b2"), "keep.ogg") + remove_path = os.path.join(os.path.join(path, "c3", "d4"), "remove.ogg") os.makedirs(os.path.dirname(keep_path), exist_ok=True) os.makedirs(os.path.dirname(remove_path), exist_ok=True) pathlib.Path(keep_path).touch() pathlib.Path(remove_path).touch() - lt = factories['federation.LibraryTrack']( - with_audio_file=True, - audio_file__path=keep_path) - tf = factories['music.TrackFile']( - library_track=lt, - accessed_date=timezone.now()) + lt = factories["federation.LibraryTrack"]( + with_audio_file=True, audio_file__path=keep_path + ) + factories["music.TrackFile"](library_track=lt, accessed_date=timezone.now()) tasks.clean_music_cache() diff --git a/api/tests/federation/test_utils.py b/api/tests/federation/test_utils.py index dc371ad9e..dbebe0fdc 100644 --- a/api/tests/federation/test_utils.py +++ b/api/tests/federation/test_utils.py @@ -3,12 +3,15 @@ import pytest from funkwhale_api.federation import utils -@pytest.mark.parametrize('url,path,expected', [ - ('http://test.com', '/hello', 'http://test.com/hello'), - ('http://test.com/', 'hello', 'http://test.com/hello'), - ('http://test.com/', '/hello', 'http://test.com/hello'), - ('http://test.com', 'hello', 'http://test.com/hello'), -]) +@pytest.mark.parametrize( + "url,path,expected", + [ + ("http://test.com", "/hello", "http://test.com/hello"), + ("http://test.com/", "hello", "http://test.com/hello"), + ("http://test.com/", "/hello", "http://test.com/hello"), + ("http://test.com", "hello", "http://test.com/hello"), + ], +) def test_full_url(settings, url, path, expected): settings.FUNKWHALE_URL = url assert utils.full_url(path) == expected @@ -16,33 +19,34 @@ def test_full_url(settings, url, path, expected): def test_extract_headers_from_meta(): wsgi_headers = { - 'HTTP_HOST': 'nginx', - 'HTTP_X_REAL_IP': '172.20.0.4', - 'HTTP_X_FORWARDED_FOR': '188.165.228.227, 172.20.0.4', - 'HTTP_X_FORWARDED_PROTO': 'http', - 'HTTP_X_FORWARDED_HOST': 'localhost:80', - 'HTTP_X_FORWARDED_PORT': '80', - 'HTTP_CONNECTION': 'close', - 'CONTENT_LENGTH': '1155', - 'CONTENT_TYPE': 'txt/application', - 'HTTP_SIGNATURE': 'Hello', - 'HTTP_DATE': 'Sat, 31 Mar 2018 13:53:55 GMT', - 'HTTP_USER_AGENT': 'http.rb/3.0.0 (Mastodon/2.2.0; +https://mastodon.eliotberriot.com/)'} + "HTTP_HOST": "nginx", + "HTTP_X_REAL_IP": "172.20.0.4", + "HTTP_X_FORWARDED_FOR": "188.165.228.227, 172.20.0.4", + "HTTP_X_FORWARDED_PROTO": "http", + "HTTP_X_FORWARDED_HOST": "localhost:80", + "HTTP_X_FORWARDED_PORT": "80", + "HTTP_CONNECTION": "close", + "CONTENT_LENGTH": "1155", + "CONTENT_TYPE": "txt/application", + "HTTP_SIGNATURE": "Hello", + "HTTP_DATE": "Sat, 31 Mar 2018 13:53:55 GMT", + "HTTP_USER_AGENT": "http.rb/3.0.0 (Mastodon/2.2.0; +https://mastodon.eliotberriot.com/)", + } cleaned_headers = utils.clean_wsgi_headers(wsgi_headers) expected = { - 'Host': 'nginx', - 'X-Real-Ip': '172.20.0.4', - 'X-Forwarded-For': '188.165.228.227, 172.20.0.4', - 'X-Forwarded-Proto': 'http', - 'X-Forwarded-Host': 'localhost:80', - 'X-Forwarded-Port': '80', - 'Connection': 'close', - 'Content-Length': '1155', - 'Content-Type': 'txt/application', - 'Signature': 'Hello', - 'Date': 'Sat, 31 Mar 2018 13:53:55 GMT', - 'User-Agent': 'http.rb/3.0.0 (Mastodon/2.2.0; +https://mastodon.eliotberriot.com/)' + "Host": "nginx", + "X-Real-Ip": "172.20.0.4", + "X-Forwarded-For": "188.165.228.227, 172.20.0.4", + "X-Forwarded-Proto": "http", + "X-Forwarded-Host": "localhost:80", + "X-Forwarded-Port": "80", + "Connection": "close", + "Content-Length": "1155", + "Content-Type": "txt/application", + "Signature": "Hello", + "Date": "Sat, 31 Mar 2018 13:53:55 GMT", + "User-Agent": "http.rb/3.0.0 (Mastodon/2.2.0; +https://mastodon.eliotberriot.com/)", } assert cleaned_headers == expected diff --git a/api/tests/federation/test_views.py b/api/tests/federation/test_views.py index 04a419aed..9e2d66a62 100644 --- a/api/tests/federation/test_views.py +++ b/api/tests/federation/test_views.py @@ -1,327 +1,308 @@ +import pytest from django.core.paginator import Paginator from django.urls import reverse from django.utils import timezone -import pytest - -from funkwhale_api.federation import actors -from funkwhale_api.federation import activity -from funkwhale_api.federation import models -from funkwhale_api.federation import serializers -from funkwhale_api.federation import utils -from funkwhale_api.federation import views -from funkwhale_api.federation import webfinger +from funkwhale_api.federation import ( + activity, + actors, + models, + serializers, + utils, + views, + webfinger, +) -@pytest.mark.parametrize('view,permissions', [ - (views.LibraryViewSet, ['federation']), - (views.LibraryTrackViewSet, ['federation']), -]) +@pytest.mark.parametrize( + "view,permissions", + [ + (views.LibraryViewSet, ["federation"]), + (views.LibraryTrackViewSet, ["federation"]), + ], +) def test_permissions(assert_user_permission, view, permissions): assert_user_permission(view, permissions) -@pytest.mark.parametrize('system_actor', actors.SYSTEM_ACTORS.keys()) +@pytest.mark.parametrize("system_actor", actors.SYSTEM_ACTORS.keys()) def test_instance_actors(system_actor, db, api_client): actor = actors.SYSTEM_ACTORS[system_actor].get_actor_instance() - url = reverse( - 'federation:instance-actors-detail', - kwargs={'actor': system_actor}) + url = reverse("federation:instance-actors-detail", kwargs={"actor": system_actor}) response = api_client.get(url) serializer = serializers.ActorSerializer(actor) - if system_actor == 'library': - response.data.pop('url') + if system_actor == "library": + response.data.pop("url") assert response.status_code == 200 assert response.data == serializer.data -@pytest.mark.parametrize('route,kwargs', [ - ('instance-actors-outbox', {'actor': 'library'}), - ('instance-actors-inbox', {'actor': 'library'}), - ('instance-actors-detail', {'actor': 'library'}), - ('well-known-webfinger', {}), -]) +@pytest.mark.parametrize( + "route,kwargs", + [ + ("instance-actors-outbox", {"actor": "library"}), + ("instance-actors-inbox", {"actor": "library"}), + ("instance-actors-detail", {"actor": "library"}), + ("well-known-webfinger", {}), + ], +) def test_instance_endpoints_405_if_federation_disabled( - authenticated_actor, db, preferences, api_client, route, kwargs): - preferences['federation__enabled'] = False - url = reverse('federation:{}'.format(route), kwargs=kwargs) + authenticated_actor, db, preferences, api_client, route, kwargs +): + preferences["federation__enabled"] = False + url = reverse("federation:{}".format(route), kwargs=kwargs) response = api_client.get(url) assert response.status_code == 405 -def test_wellknown_webfinger_validates_resource( - db, api_client, settings, mocker): - clean = mocker.spy(webfinger, 'clean_resource') - url = reverse('federation:well-known-webfinger') - response = api_client.get(url, data={'resource': 'something'}) +def test_wellknown_webfinger_validates_resource(db, api_client, settings, mocker): + clean = mocker.spy(webfinger, "clean_resource") + url = reverse("federation:well-known-webfinger") + response = api_client.get(url, data={"resource": "something"}) - clean.assert_called_once_with('something') - assert url == '/.well-known/webfinger' + clean.assert_called_once_with("something") + assert url == "/.well-known/webfinger" assert response.status_code == 400 - assert response.data['errors']['resource'] == ( - 'Missing webfinger resource type' - ) + assert response.data["errors"]["resource"] == ("Missing webfinger resource type") -@pytest.mark.parametrize('system_actor', actors.SYSTEM_ACTORS.keys()) -def test_wellknown_webfinger_system( - system_actor, db, api_client, settings, mocker): +@pytest.mark.parametrize("system_actor", actors.SYSTEM_ACTORS.keys()) +def test_wellknown_webfinger_system(system_actor, db, api_client, settings, mocker): actor = actors.SYSTEM_ACTORS[system_actor].get_actor_instance() - url = reverse('federation:well-known-webfinger') + url = reverse("federation:well-known-webfinger") response = api_client.get( url, - data={'resource': 'acct:{}'.format(actor.webfinger_subject)}, - HTTP_ACCEPT='application/jrd+json', + data={"resource": "acct:{}".format(actor.webfinger_subject)}, + HTTP_ACCEPT="application/jrd+json", ) serializer = serializers.ActorWebfingerSerializer(actor) assert response.status_code == 200 - assert response['Content-Type'] == 'application/jrd+json' + assert response["Content-Type"] == "application/jrd+json" assert response.data == serializer.data def test_wellknown_nodeinfo(db, preferences, api_client, settings): expected = { - 'links': [ + "links": [ { - 'rel': 'http://nodeinfo.diaspora.software/ns/schema/2.0', - 'href': '{}{}'.format( - settings.FUNKWHALE_URL, - reverse('api:v1:instance:nodeinfo-2.0') - ) + "rel": "http://nodeinfo.diaspora.software/ns/schema/2.0", + "href": "{}{}".format( + settings.FUNKWHALE_URL, reverse("api:v1:instance:nodeinfo-2.0") + ), } ] } - url = reverse('federation:well-known-nodeinfo') - response = api_client.get(url, HTTP_ACCEPT='application/jrd+json') + url = reverse("federation:well-known-nodeinfo") + response = api_client.get(url, HTTP_ACCEPT="application/jrd+json") assert response.status_code == 200 - assert response['Content-Type'] == 'application/jrd+json' + assert response["Content-Type"] == "application/jrd+json" assert response.data == expected def test_wellknown_nodeinfo_disabled(db, preferences, api_client): - preferences['instance__nodeinfo_enabled'] = False - url = reverse('federation:well-known-nodeinfo') + preferences["instance__nodeinfo_enabled"] = False + url = reverse("federation:well-known-nodeinfo") response = api_client.get(url) assert response.status_code == 404 -def test_audio_file_list_requires_authenticated_actor( - db, preferences, api_client): - preferences['federation__music_needs_approval'] = True - url = reverse('federation:music:files-list') +def test_audio_file_list_requires_authenticated_actor(db, preferences, api_client): + preferences["federation__music_needs_approval"] = True + url = reverse("federation:music:files-list") response = api_client.get(url) assert response.status_code == 403 -def test_audio_file_list_actor_no_page( - db, preferences, api_client, factories): - preferences['federation__music_needs_approval'] = False - preferences['federation__collection_page_size'] = 2 - library = actors.SYSTEM_ACTORS['library'].get_actor_instance() - tfs = factories['music.TrackFile'].create_batch(size=5) +def test_audio_file_list_actor_no_page(db, preferences, api_client, factories): + preferences["federation__music_needs_approval"] = False + preferences["federation__collection_page_size"] = 2 + library = actors.SYSTEM_ACTORS["library"].get_actor_instance() + tfs = factories["music.TrackFile"].create_batch(size=5) conf = { - 'id': utils.full_url(reverse('federation:music:files-list')), - 'page_size': 2, - 'items': list(reversed(tfs)), # we order by -creation_date - 'item_serializer': serializers.AudioSerializer, - 'actor': library + "id": utils.full_url(reverse("federation:music:files-list")), + "page_size": 2, + "items": list(reversed(tfs)), # we order by -creation_date + "item_serializer": serializers.AudioSerializer, + "actor": library, } expected = serializers.PaginatedCollectionSerializer(conf).data - url = reverse('federation:music:files-list') + url = reverse("federation:music:files-list") response = api_client.get(url) assert response.status_code == 200 assert response.data == expected -def test_audio_file_list_actor_page( - db, preferences, api_client, factories): - preferences['federation__music_needs_approval'] = False - preferences['federation__collection_page_size'] = 2 - library = actors.SYSTEM_ACTORS['library'].get_actor_instance() - tfs = factories['music.TrackFile'].create_batch(size=5) +def test_audio_file_list_actor_page(db, preferences, api_client, factories): + preferences["federation__music_needs_approval"] = False + preferences["federation__collection_page_size"] = 2 + library = actors.SYSTEM_ACTORS["library"].get_actor_instance() + tfs = factories["music.TrackFile"].create_batch(size=5) conf = { - 'id': utils.full_url(reverse('federation:music:files-list')), - 'page': Paginator(list(reversed(tfs)), 2).page(2), - 'item_serializer': serializers.AudioSerializer, - 'actor': library + "id": utils.full_url(reverse("federation:music:files-list")), + "page": Paginator(list(reversed(tfs)), 2).page(2), + "item_serializer": serializers.AudioSerializer, + "actor": library, } expected = serializers.CollectionPageSerializer(conf).data - url = reverse('federation:music:files-list') - response = api_client.get(url, data={'page': 2}) + url = reverse("federation:music:files-list") + response = api_client.get(url, data={"page": 2}) assert response.status_code == 200 assert response.data == expected def test_audio_file_list_actor_page_exclude_federated_files( - db, preferences, api_client, factories): - preferences['federation__music_needs_approval'] = False - library = actors.SYSTEM_ACTORS['library'].get_actor_instance() - tfs = factories['music.TrackFile'].create_batch(size=5, federation=True) + db, preferences, api_client, factories +): + preferences["federation__music_needs_approval"] = False + factories["music.TrackFile"].create_batch(size=5, federation=True) - url = reverse('federation:music:files-list') + url = reverse("federation:music:files-list") response = api_client.get(url) assert response.status_code == 200 - assert response.data['totalItems'] == 0 + assert response.data["totalItems"] == 0 -def test_audio_file_list_actor_page_error( - db, preferences, api_client, factories): - preferences['federation__music_needs_approval'] = False - url = reverse('federation:music:files-list') - response = api_client.get(url, data={'page': 'nope'}) +def test_audio_file_list_actor_page_error(db, preferences, api_client, factories): + preferences["federation__music_needs_approval"] = False + url = reverse("federation:music:files-list") + response = api_client.get(url, data={"page": "nope"}) assert response.status_code == 400 def test_audio_file_list_actor_page_error_too_far( - db, preferences, api_client, factories): - preferences['federation__music_needs_approval'] = False - url = reverse('federation:music:files-list') - response = api_client.get(url, data={'page': 5000}) + db, preferences, api_client, factories +): + preferences["federation__music_needs_approval"] = False + url = reverse("federation:music:files-list") + response = api_client.get(url, data={"page": 5000}) assert response.status_code == 404 def test_library_actor_includes_library_link(db, preferences, api_client): - actor = actors.SYSTEM_ACTORS['library'].get_actor_instance() - url = reverse( - 'federation:instance-actors-detail', - kwargs={'actor': 'library'}) + url = reverse("federation:instance-actors-detail", kwargs={"actor": "library"}) response = api_client.get(url) expected_links = [ { - 'type': 'Link', - 'name': 'library', - 'mediaType': 'application/activity+json', - 'href': utils.full_url(reverse('federation:music:files-list')) + "type": "Link", + "name": "library", + "mediaType": "application/activity+json", + "href": utils.full_url(reverse("federation:music:files-list")), } ] assert response.status_code == 200 - assert response.data['url'] == expected_links + assert response.data["url"] == expected_links def test_can_fetch_library(superuser_api_client, mocker): - result = {'test': 'test'} + result = {"test": "test"} scan = mocker.patch( - 'funkwhale_api.federation.library.scan_from_account_name', - return_value=result) + "funkwhale_api.federation.library.scan_from_account_name", return_value=result + ) - url = reverse('api:v1:federation:libraries-fetch') - response = superuser_api_client.get( - url, data={'account': 'test@test.library'}) + url = reverse("api:v1:federation:libraries-fetch") + response = superuser_api_client.get(url, data={"account": "test@test.library"}) assert response.status_code == 200 assert response.data == result - scan.assert_called_once_with('test@test.library') + scan.assert_called_once_with("test@test.library") def test_follow_library(superuser_api_client, mocker, factories, r_mock): - library_actor = actors.SYSTEM_ACTORS['library'].get_actor_instance() - actor = factories['federation.Actor']() - follow = {'test': 'follow'} - on_commit = mocker.patch( - 'funkwhale_api.common.utils.on_commit') + library_actor = actors.SYSTEM_ACTORS["library"].get_actor_instance() + actor = factories["federation.Actor"]() + follow = {"test": "follow"} + on_commit = mocker.patch("funkwhale_api.common.utils.on_commit") actor_data = serializers.ActorSerializer(actor).data - actor_data['url'] = [{ - 'href': 'https://test.library', - 'name': 'library', - 'type': 'Link', - }] + actor_data["url"] = [ + {"href": "https://test.library", "name": "library", "type": "Link"} + ] library_conf = { - 'id': 'https://test.library', - 'items': range(10), - 'actor': actor, - 'page_size': 5, + "id": "https://test.library", + "items": range(10), + "actor": actor, + "page_size": 5, } library_data = serializers.PaginatedCollectionSerializer(library_conf).data r_mock.get(actor.url, json=actor_data) - r_mock.get('https://test.library', json=library_data) + r_mock.get("https://test.library", json=library_data) data = { - 'actor': actor.url, - 'autoimport': False, - 'federation_enabled': True, - 'download_files': False, + "actor": actor.url, + "autoimport": False, + "federation_enabled": True, + "download_files": False, } - url = reverse('api:v1:federation:libraries-list') - response = superuser_api_client.post( - url, data) + url = reverse("api:v1:federation:libraries-list") + response = superuser_api_client.post(url, data) assert response.status_code == 201 - follow = models.Follow.objects.get( - actor=library_actor, - target=actor, - approved=None, - ) + follow = models.Follow.objects.get(actor=library_actor, target=actor, approved=None) library = follow.library - assert response.data == serializers.APILibraryCreateSerializer( - library).data + assert response.data == serializers.APILibraryCreateSerializer(library).data on_commit.assert_called_once_with( activity.deliver, serializers.FollowSerializer(follow).data, on_behalf_of=library_actor, - to=[actor.url] + to=[actor.url], ) def test_can_list_system_actor_following(factories, superuser_api_client): - library_actor = actors.SYSTEM_ACTORS['library'].get_actor_instance() - follow1 = factories['federation.Follow'](actor=library_actor) - follow2 = factories['federation.Follow']() + library_actor = actors.SYSTEM_ACTORS["library"].get_actor_instance() + follow1 = factories["federation.Follow"](actor=library_actor) + factories["federation.Follow"]() - url = reverse('api:v1:federation:libraries-following') + url = reverse("api:v1:federation:libraries-following") response = superuser_api_client.get(url) assert response.status_code == 200 - assert response.data['results'] == [ - serializers.APIFollowSerializer(follow1).data - ] + assert response.data["results"] == [serializers.APIFollowSerializer(follow1).data] def test_can_list_system_actor_followers(factories, superuser_api_client): - library_actor = actors.SYSTEM_ACTORS['library'].get_actor_instance() - follow1 = factories['federation.Follow'](actor=library_actor) - follow2 = factories['federation.Follow'](target=library_actor) + library_actor = actors.SYSTEM_ACTORS["library"].get_actor_instance() + factories["federation.Follow"](actor=library_actor) + follow2 = factories["federation.Follow"](target=library_actor) - url = reverse('api:v1:federation:libraries-followers') + url = reverse("api:v1:federation:libraries-followers") response = superuser_api_client.get(url) assert response.status_code == 200 - assert response.data['results'] == [ - serializers.APIFollowSerializer(follow2).data - ] + assert response.data["results"] == [serializers.APIFollowSerializer(follow2).data] def test_can_list_libraries(factories, superuser_api_client): - library1 = factories['federation.Library']() - library2 = factories['federation.Library']() + library1 = factories["federation.Library"]() + library2 = factories["federation.Library"]() - url = reverse('api:v1:federation:libraries-list') + url = reverse("api:v1:federation:libraries-list") response = superuser_api_client.get(url) assert response.status_code == 200 - assert response.data['results'] == [ + assert response.data["results"] == [ serializers.APILibrarySerializer(library1).data, serializers.APILibrarySerializer(library2).data, ] def test_can_detail_library(factories, superuser_api_client): - library = factories['federation.Library']() + library = factories["federation.Library"]() url = reverse( - 'api:v1:federation:libraries-detail', - kwargs={'uuid': str(library.uuid)}) + "api:v1:federation:libraries-detail", kwargs={"uuid": str(library.uuid)} + ) response = superuser_api_client.get(url) assert response.status_code == 200 @@ -329,15 +310,15 @@ def test_can_detail_library(factories, superuser_api_client): def test_can_patch_library(factories, superuser_api_client): - library = factories['federation.Library']() + library = factories["federation.Library"]() data = { - 'federation_enabled': not library.federation_enabled, - 'download_files': not library.download_files, - 'autoimport': not library.autoimport, + "federation_enabled": not library.federation_enabled, + "download_files": not library.download_files, + "autoimport": not library.autoimport, } url = reverse( - 'api:v1:federation:libraries-detail', - kwargs={'uuid': str(library.uuid)}) + "api:v1:federation:libraries-detail", kwargs={"uuid": str(library.uuid)} + ) response = superuser_api_client.patch(url, data) assert response.status_code == 200 @@ -349,55 +330,49 @@ def test_can_patch_library(factories, superuser_api_client): def test_scan_library(factories, mocker, superuser_api_client): scan = mocker.patch( - 'funkwhale_api.federation.tasks.scan_library.delay', - return_value=mocker.Mock(id='id')) - library = factories['federation.Library']() + "funkwhale_api.federation.tasks.scan_library.delay", + return_value=mocker.Mock(id="id"), + ) + library = factories["federation.Library"]() now = timezone.now() - data = { - 'until': now, - } + data = {"until": now} url = reverse( - 'api:v1:federation:libraries-scan', - kwargs={'uuid': str(library.uuid)}) + "api:v1:federation:libraries-scan", kwargs={"uuid": str(library.uuid)} + ) response = superuser_api_client.post(url, data) assert response.status_code == 200 - assert response.data == {'task': 'id'} - scan.assert_called_once_with( - library_id=library.pk, - until=now - ) + assert response.data == {"task": "id"} + scan.assert_called_once_with(library_id=library.pk, until=now) def test_list_library_tracks(factories, superuser_api_client): - library = factories['federation.Library']() - lts = list(reversed(factories['federation.LibraryTrack'].create_batch( - size=5, library=library))) - factories['federation.LibraryTrack'].create_batch(size=5) - url = reverse('api:v1:federation:library-tracks-list') - response = superuser_api_client.get(url, {'library': library.uuid}) + library = factories["federation.Library"]() + lts = list( + reversed( + factories["federation.LibraryTrack"].create_batch(size=5, library=library) + ) + ) + factories["federation.LibraryTrack"].create_batch(size=5) + url = reverse("api:v1:federation:library-tracks-list") + response = superuser_api_client.get(url, {"library": library.uuid}) assert response.status_code == 200 assert response.data == { - 'results': serializers.APILibraryTrackSerializer(lts, many=True).data, - 'count': 5, - 'previous': None, - 'next': None, + "results": serializers.APILibraryTrackSerializer(lts, many=True).data, + "count": 5, + "previous": None, + "next": None, } def test_can_update_follow_status(factories, superuser_api_client, mocker): - patched_accept = mocker.patch( - 'funkwhale_api.federation.activity.accept_follow' - ) - library_actor = actors.SYSTEM_ACTORS['library'].get_actor_instance() - follow = factories['federation.Follow'](target=library_actor) + patched_accept = mocker.patch("funkwhale_api.federation.activity.accept_follow") + library_actor = actors.SYSTEM_ACTORS["library"].get_actor_instance() + follow = factories["federation.Follow"](target=library_actor) - payload = { - 'follow': follow.pk, - 'approved': True - } - url = reverse('api:v1:federation:libraries-followers') + payload = {"follow": follow.pk, "approved": True} + url = reverse("api:v1:federation:libraries-followers") response = superuser_api_client.patch(url, payload) follow.refresh_from_db() @@ -407,45 +382,33 @@ def test_can_update_follow_status(factories, superuser_api_client, mocker): def test_can_filter_pending_follows(factories, superuser_api_client): - library_actor = actors.SYSTEM_ACTORS['library'].get_actor_instance() - follow = factories['federation.Follow']( - target=library_actor, - approved=True) + library_actor = actors.SYSTEM_ACTORS["library"].get_actor_instance() + factories["federation.Follow"](target=library_actor, approved=True) - params = {'pending': True} - url = reverse('api:v1:federation:libraries-followers') + params = {"pending": True} + url = reverse("api:v1:federation:libraries-followers") response = superuser_api_client.get(url, params) assert response.status_code == 200 - assert len(response.data['results']) == 0 + assert len(response.data["results"]) == 0 -def test_library_track_action_import( - factories, superuser_api_client, mocker): - lt1 = factories['federation.LibraryTrack']() - lt2 = factories['federation.LibraryTrack'](library=lt1.library) - lt3 = factories['federation.LibraryTrack']() - lt4 = factories['federation.LibraryTrack'](library=lt3.library) - mocked_run = mocker.patch( - 'funkwhale_api.music.tasks.import_batch_run.delay') +def test_library_track_action_import(factories, superuser_api_client, mocker): + lt1 = factories["federation.LibraryTrack"]() + lt2 = factories["federation.LibraryTrack"](library=lt1.library) + lt3 = factories["federation.LibraryTrack"]() + factories["federation.LibraryTrack"](library=lt3.library) + mocked_run = mocker.patch("funkwhale_api.music.tasks.import_batch_run.delay") payload = { - 'objects': 'all', - 'action': 'import', - 'filters': { - 'library': lt1.library.uuid - } - } - url = reverse('api:v1:federation:library-tracks-action') - response = superuser_api_client.post(url, payload, format='json') - batch = superuser_api_client.user.imports.latest('id') - expected = { - 'updated': 2, - 'action': 'import', - 'result': { - 'batch': {'id': batch.pk} - } + "objects": "all", + "action": "import", + "filters": {"library": lt1.library.uuid}, } + url = reverse("api:v1:federation:library-tracks-action") + response = superuser_api_client.post(url, payload, format="json") + batch = superuser_api_client.user.imports.latest("id") + expected = {"updated": 2, "action": "import", "result": {"batch": {"id": batch.pk}}} imported_lts = [lt1, lt2] assert response.status_code == 200 diff --git a/api/tests/federation/test_webfinger.py b/api/tests/federation/test_webfinger.py index 4b8dca207..0608df3e2 100644 --- a/api/tests/federation/test_webfinger.py +++ b/api/tests/federation/test_webfinger.py @@ -1,22 +1,23 @@ import pytest - from django import forms -from django.urls import reverse from funkwhale_api.federation import webfinger def test_webfinger_clean_resource(): - t, r = webfinger.clean_resource('acct:service@test.federation') - assert t == 'acct' - assert r == 'service@test.federation' + t, r = webfinger.clean_resource("acct:service@test.federation") + assert t == "acct" + assert r == "service@test.federation" -@pytest.mark.parametrize('resource,message', [ - ('', 'Invalid resource string'), - ('service@test.com', 'Missing webfinger resource type'), - ('noop:service@test.com', 'Invalid webfinger resource type'), -]) +@pytest.mark.parametrize( + "resource,message", + [ + ("", "Invalid resource string"), + ("service@test.com", "Missing webfinger resource type"), + ("noop:service@test.com", "Invalid webfinger resource type"), + ], +) def test_webfinger_clean_resource_errors(resource, message): with pytest.raises(forms.ValidationError) as excinfo: webfinger.clean_resource(resource) @@ -25,16 +26,19 @@ def test_webfinger_clean_resource_errors(resource, message): def test_webfinger_clean_acct(settings): - username, hostname = webfinger.clean_acct('library@test.federation') - assert username == 'library' - assert hostname == 'test.federation' + username, hostname = webfinger.clean_acct("library@test.federation") + assert username == "library" + assert hostname == "test.federation" -@pytest.mark.parametrize('resource,message', [ - ('service', 'Invalid format'), - ('service@test.com', 'Invalid hostname test.com'), - ('noop@test.federation', 'Invalid account'), -]) +@pytest.mark.parametrize( + "resource,message", + [ + ("service", "Invalid format"), + ("service@test.com", "Invalid hostname test.com"), + ("noop@test.federation", "Invalid account"), + ], +) def test_webfinger_clean_acct_errors(resource, message, settings): with pytest.raises(forms.ValidationError) as excinfo: webfinger.clean_resource(resource) @@ -43,26 +47,24 @@ def test_webfinger_clean_acct_errors(resource, message, settings): def test_webfinger_get_resource(r_mock): - resource = 'acct:test@test.webfinger' + resource = "acct:test@test.webfinger" payload = { - 'subject': resource, - 'aliases': ['https://test.webfinger'], - 'links': [ + "subject": resource, + "aliases": ["https://test.webfinger"], + "links": [ { - 'rel': 'self', - 'type': 'application/activity+json', - 'href': 'https://test.webfinger/user/test' + "rel": "self", + "type": "application/activity+json", + "href": "https://test.webfinger/user/test", } - ] + ], } r_mock.get( - 'https://test.webfinger/.well-known/webfinger?resource={}'.format( - resource - ), - json=payload + "https://test.webfinger/.well-known/webfinger?resource={}".format(resource), + json=payload, ) - data = webfinger.get_resource('acct:test@test.webfinger') + data = webfinger.get_resource("acct:test@test.webfinger") - assert data['actor_url'] == 'https://test.webfinger/user/test' - assert data['subject'] == resource + assert data["actor_url"] == "https://test.webfinger/user/test" + assert data["subject"] == resource diff --git a/api/tests/history/test_activity.py b/api/tests/history/test_activity.py index 04000604b..f3ada5052 100644 --- a/api/tests/history/test_activity.py +++ b/api/tests/history/test_activity.py @@ -1,19 +1,17 @@ -from funkwhale_api.users.serializers import UserActivitySerializer +from funkwhale_api.history import activities, serializers from funkwhale_api.music.serializers import TrackActivitySerializer -from funkwhale_api.history import serializers -from funkwhale_api.history import activities +from funkwhale_api.users.serializers import UserActivitySerializer def test_get_listening_activity_url(settings, factories): - listening = factories['history.Listening']() + listening = factories["history.Listening"]() user_url = listening.user.get_activity_url() - expected = '{}/listenings/tracks/{}'.format( - user_url, listening.pk) + expected = "{}/listenings/tracks/{}".format(user_url, listening.pk) assert listening.get_activity_url() == expected def test_activity_listening_serializer(factories): - listening = factories['history.Listening']() + listening = factories["history.Listening"]() actor = UserActivitySerializer(listening.user).data field = serializers.serializers.DateTimeField() @@ -32,44 +30,30 @@ def test_activity_listening_serializer(factories): def test_track_listening_serializer_is_connected(activity_registry): - conf = activity_registry['history.Listening'] - assert conf['serializer'] == serializers.ListeningActivitySerializer + conf = activity_registry["history.Listening"] + assert conf["serializer"] == serializers.ListeningActivitySerializer -def test_track_listening_serializer_instance_activity_consumer( - activity_registry): - conf = activity_registry['history.Listening'] +def test_track_listening_serializer_instance_activity_consumer(activity_registry): + conf = activity_registry["history.Listening"] consumer = activities.broadcast_listening_to_instance_activity - assert consumer in conf['consumers'] + assert consumer in conf["consumers"] -def test_broadcast_listening_to_instance_activity( - factories, mocker): - p = mocker.patch('funkwhale_api.common.channels.group_send') - listening = factories['history.Listening']() +def test_broadcast_listening_to_instance_activity(factories, mocker): + p = mocker.patch("funkwhale_api.common.channels.group_send") + listening = factories["history.Listening"]() data = serializers.ListeningActivitySerializer(listening).data consumer = activities.broadcast_listening_to_instance_activity - message = { - "type": 'event.send', - "text": '', - "data": data - } + message = {"type": "event.send", "text": "", "data": data} consumer(data=data, obj=listening) - p.assert_called_once_with('instance_activity', message) + p.assert_called_once_with("instance_activity", message) -def test_broadcast_listening_to_instance_activity_private( - factories, mocker): - p = mocker.patch('funkwhale_api.common.channels.group_send') - listening = factories['history.Listening']( - user__privacy_level='me' - ) +def test_broadcast_listening_to_instance_activity_private(factories, mocker): + p = mocker.patch("funkwhale_api.common.channels.group_send") + listening = factories["history.Listening"](user__privacy_level="me") data = serializers.ListeningActivitySerializer(listening).data consumer = activities.broadcast_listening_to_instance_activity - message = { - "type": 'event.send', - "text": '', - "data": data - } consumer(data=data, obj=listening) p.assert_not_called() diff --git a/api/tests/history/test_history.py b/api/tests/history/test_history.py index 202725596..9cc4e3d14 100644 --- a/api/tests/history/test_history.py +++ b/api/tests/history/test_history.py @@ -1,43 +1,36 @@ -import random -import json from django.urls import reverse -from django.core.exceptions import ValidationError -from django.utils import timezone from funkwhale_api.history import models def test_can_create_listening(factories): - track = factories['music.Track']() - user = factories['users.User']() - now = timezone.now() - l = models.Listening.objects.create(user=user, track=track) + track = factories["music.Track"]() + user = factories["users.User"]() + models.Listening.objects.create(user=user, track=track) def test_logged_in_user_can_create_listening_via_api( - logged_in_client, factories, activity_muted): - track = factories['music.Track']() + logged_in_client, factories, activity_muted +): + track = factories["music.Track"]() - url = reverse('api:v1:history:listenings-list') - response = logged_in_client.post(url, { - 'track': track.pk, - }) + url = reverse("api:v1:history:listenings-list") + logged_in_client.post(url, {"track": track.pk}) - listening = models.Listening.objects.latest('id') + listening = models.Listening.objects.latest("id") assert listening.track == track assert listening.user == logged_in_client.user def test_adding_listening_calls_activity_record( - factories, logged_in_client, activity_muted): - track = factories['music.Track']() + factories, logged_in_client, activity_muted +): + track = factories["music.Track"]() - url = reverse('api:v1:history:listenings-list') - response = logged_in_client.post(url, { - 'track': track.pk, - }) + url = reverse("api:v1:history:listenings-list") + logged_in_client.post(url, {"track": track.pk}) - listening = models.Listening.objects.latest('id') + listening = models.Listening.objects.latest("id") activity_muted.assert_called_once_with(listening) diff --git a/api/tests/instance/test_nodeinfo.py b/api/tests/instance/test_nodeinfo.py index 87b888288..181ddf277 100644 --- a/api/tests/instance/test_nodeinfo.py +++ b/api/tests/instance/test_nodeinfo.py @@ -1,107 +1,79 @@ -from django.urls import reverse import funkwhale_api - from funkwhale_api.instance import nodeinfo def test_nodeinfo_dump(preferences, mocker): - preferences['instance__nodeinfo_stats_enabled'] = True + preferences["instance__nodeinfo_stats_enabled"] = True stats = { - 'users': 1, - 'tracks': 2, - 'albums': 3, - 'artists': 4, - 'track_favorites': 5, - 'music_duration': 6, - 'listenings': 7, + "users": 1, + "tracks": 2, + "albums": 3, + "artists": 4, + "track_favorites": 5, + "music_duration": 6, + "listenings": 7, } - mocker.patch('funkwhale_api.instance.stats.get', return_value=stats) + mocker.patch("funkwhale_api.instance.stats.get", return_value=stats) expected = { - 'version': '2.0', - 'software': { - 'name': 'funkwhale', - 'version': funkwhale_api.__version__ - }, - 'protocols': ['activitypub'], - 'services': { - 'inbound': [], - 'outbound': [] - }, - 'openRegistrations': preferences['users__registration_enabled'], - 'usage': { - 'users': { - 'total': stats['users'], - } - }, - 'metadata': { - 'private': preferences['instance__nodeinfo_private'], - 'shortDescription': preferences['instance__short_description'], - 'longDescription': preferences['instance__long_description'], - 'nodeName': preferences['instance__name'], - 'library': { - 'federationEnabled': preferences['federation__enabled'], - 'federationNeedsApproval': preferences['federation__music_needs_approval'], - 'anonymousCanListen': preferences['common__api_authentication_required'], - 'tracks': { - 'total': stats['tracks'], - }, - 'artists': { - 'total': stats['artists'], - }, - 'albums': { - 'total': stats['albums'], - }, - 'music': { - 'hours': stats['music_duration'] - }, + "version": "2.0", + "software": {"name": "funkwhale", "version": funkwhale_api.__version__}, + "protocols": ["activitypub"], + "services": {"inbound": [], "outbound": []}, + "openRegistrations": preferences["users__registration_enabled"], + "usage": {"users": {"total": stats["users"]}}, + "metadata": { + "private": preferences["instance__nodeinfo_private"], + "shortDescription": preferences["instance__short_description"], + "longDescription": preferences["instance__long_description"], + "nodeName": preferences["instance__name"], + "library": { + "federationEnabled": preferences["federation__enabled"], + "federationNeedsApproval": preferences[ + "federation__music_needs_approval" + ], + "anonymousCanListen": preferences[ + "common__api_authentication_required" + ], + "tracks": {"total": stats["tracks"]}, + "artists": {"total": stats["artists"]}, + "albums": {"total": stats["albums"]}, + "music": {"hours": stats["music_duration"]}, }, - 'usage': { - 'favorites': { - 'tracks': { - 'total': stats['track_favorites'], - } - }, - 'listenings': { - 'total': stats['listenings'] - } - } - } + "usage": { + "favorites": {"tracks": {"total": stats["track_favorites"]}}, + "listenings": {"total": stats["listenings"]}, + }, + }, } assert nodeinfo.get() == expected def test_nodeinfo_dump_stats_disabled(preferences, mocker): - preferences['instance__nodeinfo_stats_enabled'] = False + preferences["instance__nodeinfo_stats_enabled"] = False expected = { - 'version': '2.0', - 'software': { - 'name': 'funkwhale', - 'version': funkwhale_api.__version__ - }, - 'protocols': ['activitypub'], - 'services': { - 'inbound': [], - 'outbound': [] - }, - 'openRegistrations': preferences['users__registration_enabled'], - 'usage': { - 'users': { - 'total': 0, - } - }, - 'metadata': { - 'private': preferences['instance__nodeinfo_private'], - 'shortDescription': preferences['instance__short_description'], - 'longDescription': preferences['instance__long_description'], - 'nodeName': preferences['instance__name'], - 'library': { - 'federationEnabled': preferences['federation__enabled'], - 'federationNeedsApproval': preferences['federation__music_needs_approval'], - 'anonymousCanListen': preferences['common__api_authentication_required'], + "version": "2.0", + "software": {"name": "funkwhale", "version": funkwhale_api.__version__}, + "protocols": ["activitypub"], + "services": {"inbound": [], "outbound": []}, + "openRegistrations": preferences["users__registration_enabled"], + "usage": {"users": {"total": 0}}, + "metadata": { + "private": preferences["instance__nodeinfo_private"], + "shortDescription": preferences["instance__short_description"], + "longDescription": preferences["instance__long_description"], + "nodeName": preferences["instance__name"], + "library": { + "federationEnabled": preferences["federation__enabled"], + "federationNeedsApproval": preferences[ + "federation__music_needs_approval" + ], + "anonymousCanListen": preferences[ + "common__api_authentication_required" + ], }, - } + }, } assert nodeinfo.get() == expected diff --git a/api/tests/instance/test_preferences.py b/api/tests/instance/test_preferences.py index beb8e6d33..b465be9d3 100644 --- a/api/tests/instance/test_preferences.py +++ b/api/tests/instance/test_preferences.py @@ -1,17 +1,15 @@ import pytest - from django.urls import reverse -from dynamic_preferences.api import serializers - def test_can_list_settings_via_api(preferences, api_client): - url = reverse('api:v1:instance:settings') + url = reverse("api:v1:instance:settings") all_preferences = preferences.model.objects.all() expected_preferences = { p.preference.identifier(): p for p in all_preferences - if getattr(p.preference, 'show_in_api', False)} + if getattr(p.preference, "show_in_api", False) + } assert len(expected_preferences) > 0 @@ -20,15 +18,18 @@ def test_can_list_settings_via_api(preferences, api_client): assert len(response.data) == len(expected_preferences) for p in response.data: - i = '__'.join([p['section'], p['name']]) + i = "__".join([p["section"], p["name"]]) assert i in expected_preferences -@pytest.mark.parametrize('pref,value', [ - ('instance__name', 'My instance'), - ('instance__short_description', 'For music lovers'), - ('instance__long_description', 'For real music lovers'), -]) +@pytest.mark.parametrize( + "pref,value", + [ + ("instance__name", "My instance"), + ("instance__short_description", "For music lovers"), + ("instance__long_description", "For real music lovers"), + ], +) def test_instance_settings(pref, value, preferences): preferences[pref] = value diff --git a/api/tests/instance/test_stats.py b/api/tests/instance/test_stats.py index 6063e9300..1d8bcfc0a 100644 --- a/api/tests/instance/test_stats.py +++ b/api/tests/instance/test_stats.py @@ -1,17 +1,14 @@ -from django.urls import reverse - from funkwhale_api.instance import stats def test_get_users(mocker): - mocker.patch( - 'funkwhale_api.users.models.User.objects.count', return_value=42) + mocker.patch("funkwhale_api.users.models.User.objects.count", return_value=42) assert stats.get_users() == 42 def test_get_music_duration(factories): - factories['music.TrackFile'].create_batch(size=5, duration=360) + factories["music.TrackFile"].create_batch(size=5, duration=360) # duration is in hours assert stats.get_music_duration() == 0.5 @@ -19,56 +16,48 @@ def test_get_music_duration(factories): def test_get_listenings(mocker): mocker.patch( - 'funkwhale_api.history.models.Listening.objects.count', - return_value=42) + "funkwhale_api.history.models.Listening.objects.count", return_value=42 + ) assert stats.get_listenings() == 42 def test_get_track_favorites(mocker): mocker.patch( - 'funkwhale_api.favorites.models.TrackFavorite.objects.count', - return_value=42) + "funkwhale_api.favorites.models.TrackFavorite.objects.count", return_value=42 + ) assert stats.get_track_favorites() == 42 def test_get_tracks(mocker): - mocker.patch( - 'funkwhale_api.music.models.Track.objects.count', - return_value=42) + mocker.patch("funkwhale_api.music.models.Track.objects.count", return_value=42) assert stats.get_tracks() == 42 def test_get_albums(mocker): - mocker.patch( - 'funkwhale_api.music.models.Album.objects.count', - return_value=42) + mocker.patch("funkwhale_api.music.models.Album.objects.count", return_value=42) assert stats.get_albums() == 42 def test_get_artists(mocker): - mocker.patch( - 'funkwhale_api.music.models.Artist.objects.count', - return_value=42) + mocker.patch("funkwhale_api.music.models.Artist.objects.count", return_value=42) assert stats.get_artists() == 42 def test_get(mocker): keys = [ - 'users', - 'tracks', - 'albums', - 'artists', - 'track_favorites', - 'listenings', - 'music_duration', + "users", + "tracks", + "albums", + "artists", + "track_favorites", + "listenings", + "music_duration", ] - mocks = [ - mocker.patch.object(stats, 'get_{}'.format(k), return_value=i) + [ + mocker.patch.object(stats, "get_{}".format(k), return_value=i) for i, k in enumerate(keys) ] - expected = { - k: i for i, k in enumerate(keys) - } + expected = {k: i for i, k in enumerate(keys)} assert stats.get() == expected diff --git a/api/tests/instance/test_views.py b/api/tests/instance/test_views.py index daf54db51..6dd4f6345 100644 --- a/api/tests/instance/test_views.py +++ b/api/tests/instance/test_views.py @@ -1,62 +1,54 @@ import pytest - from django.urls import reverse from funkwhale_api.instance import views -@pytest.mark.parametrize('view,permissions', [ - (views.AdminSettings, ['settings']), -]) +@pytest.mark.parametrize("view,permissions", [(views.AdminSettings, ["settings"])]) def test_permissions(assert_user_permission, view, permissions): assert_user_permission(view, permissions) def test_nodeinfo_endpoint(db, api_client, mocker): - payload = { - 'test': 'test' - } - mocked_nodeinfo = mocker.patch( - 'funkwhale_api.instance.nodeinfo.get', return_value=payload) - url = reverse('api:v1:instance:nodeinfo-2.0') + payload = {"test": "test"} + mocker.patch("funkwhale_api.instance.nodeinfo.get", return_value=payload) + url = reverse("api:v1:instance:nodeinfo-2.0") response = api_client.get(url) - ct = 'application/json; profile=http://nodeinfo.diaspora.software/ns/schema/2.0#; charset=utf-8' # noqa + ct = "application/json; profile=http://nodeinfo.diaspora.software/ns/schema/2.0#; charset=utf-8" # noqa assert response.status_code == 200 - assert response['Content-Type'] == ct + assert response["Content-Type"] == ct assert response.data == payload def test_nodeinfo_endpoint_disabled(db, api_client, preferences): - preferences['instance__nodeinfo_enabled'] = False - url = reverse('api:v1:instance:nodeinfo-2.0') + preferences["instance__nodeinfo_enabled"] = False + url = reverse("api:v1:instance:nodeinfo-2.0") response = api_client.get(url) assert response.status_code == 404 def test_settings_only_list_public_settings(db, api_client, preferences): - url = reverse('api:v1:instance:settings') + url = reverse("api:v1:instance:settings") response = api_client.get(url) for conf in response.data: - p = preferences.model.objects.get( - section=conf['section'], name=conf['name']) + p = preferences.model.objects.get(section=conf["section"], name=conf["name"]) assert p.preference.show_in_api is True def test_admin_settings_restrict_access(db, logged_in_api_client, preferences): - url = reverse('api:v1:instance:admin-settings-list') + url = reverse("api:v1:instance:admin-settings-list") response = logged_in_api_client.get(url) assert response.status_code == 403 -def test_admin_settings_correct_permission( - db, logged_in_api_client, preferences): +def test_admin_settings_correct_permission(db, logged_in_api_client, preferences): user = logged_in_api_client.user user.permission_settings = True user.save() - url = reverse('api:v1:instance:admin-settings-list') + url = reverse("api:v1:instance:admin-settings-list") response = logged_in_api_client.get(url) assert response.status_code == 200 diff --git a/api/tests/manage/test_serializers.py b/api/tests/manage/test_serializers.py index 45167722c..893cfd86e 100644 --- a/api/tests/manage/test_serializers.py +++ b/api/tests/manage/test_serializers.py @@ -2,7 +2,7 @@ from funkwhale_api.manage import serializers def test_manage_track_file_action_delete(factories): - tfs = factories['music.TrackFile'](size=5) + tfs = factories["music.TrackFile"](size=5) s = serializers.ManageTrackFileActionSerializer(queryset=None) s.handle_delete(tfs.__class__.objects.all()) diff --git a/api/tests/manage/test_views.py b/api/tests/manage/test_views.py index db2e0980a..e2bfbf3a8 100644 --- a/api/tests/manage/test_views.py +++ b/api/tests/manage/test_views.py @@ -1,26 +1,25 @@ import pytest - from django.urls import reverse -from funkwhale_api.manage import serializers -from funkwhale_api.manage import views +from funkwhale_api.manage import serializers, views -@pytest.mark.parametrize('view,permissions,operator', [ - (views.ManageTrackFileViewSet, ['library'], 'and'), -]) +@pytest.mark.parametrize( + "view,permissions,operator", [(views.ManageTrackFileViewSet, ["library"], "and")] +) def test_permissions(assert_user_permission, view, permissions, operator): assert_user_permission(view, permissions, operator) def test_track_file_view(factories, superuser_api_client): - tfs = factories['music.TrackFile'].create_batch(size=5) - qs = tfs[0].__class__.objects.order_by('-creation_date') - url = reverse('api:v1:manage:library:track-files-list') + tfs = factories["music.TrackFile"].create_batch(size=5) + qs = tfs[0].__class__.objects.order_by("-creation_date") + url = reverse("api:v1:manage:library:track-files-list") - response = superuser_api_client.get(url, {'sort': '-creation_date'}) + response = superuser_api_client.get(url, {"sort": "-creation_date"}) expected = serializers.ManageTrackFileSerializer( - qs, many=True, context={'request': response.wsgi_request}).data + qs, many=True, context={"request": response.wsgi_request} + ).data - assert response.data['count'] == len(tfs) - assert response.data['results'] == expected + assert response.data["count"] == len(tfs) + assert response.data["results"] == expected diff --git a/api/tests/music/conftest.py b/api/tests/music/conftest.py index 4eea8effe..0cb6f4778 100644 --- a/api/tests/music/conftest.py +++ b/api/tests/music/conftest.py @@ -1,66 +1,75 @@ import pytest +_artists = {"search": {}, "get": {}} -_artists = {'search': {}, 'get': {}} - -_artists['search']['adhesive_wombat'] = { - 'artist-list': [ +_artists["search"]["adhesive_wombat"] = { + "artist-list": [ { - 'type': 'Person', - 'ext:score': '100', - 'id': '62c3befb-6366-4585-b256-809472333801', - 'disambiguation': 'George Shaw', - 'gender': 'male', - 'area': {'sort-name': 'Raleigh', 'id': '3f8828b9-ba93-4604-9b92-1f616fa1abd1', 'name': 'Raleigh'}, - 'sort-name': 'Wombat, Adhesive', - 'life-span': {'ended': 'false'}, - 'name': 'Adhesive Wombat' + "type": "Person", + "ext:score": "100", + "id": "62c3befb-6366-4585-b256-809472333801", + "disambiguation": "George Shaw", + "gender": "male", + "area": { + "sort-name": "Raleigh", + "id": "3f8828b9-ba93-4604-9b92-1f616fa1abd1", + "name": "Raleigh", + }, + "sort-name": "Wombat, Adhesive", + "life-span": {"ended": "false"}, + "name": "Adhesive Wombat", }, { - 'country': 'SE', - 'type': 'Group', - 'ext:score': '42', - 'id': '61b34e69-7573-4208-bc89-7061bca5a8fc', - 'area': {'sort-name': 'Sweden', 'id': '23d10872-f5ae-3f0c-bf55-332788a16ecb', 'name': 'Sweden'}, - 'sort-name': 'Adhesive', - 'life-span': {'end': '2002-07-12', 'begin': '1994', 'ended': 'true'}, - 'name': 'Adhesive', - 'begin-area': { - 'sort-name': 'Katrineholm', - 'id': '02390d96-b5a3-4282-a38f-e64a95d08b7f', - 'name': 'Katrineholm' + "country": "SE", + "type": "Group", + "ext:score": "42", + "id": "61b34e69-7573-4208-bc89-7061bca5a8fc", + "area": { + "sort-name": "Sweden", + "id": "23d10872-f5ae-3f0c-bf55-332788a16ecb", + "name": "Sweden", + }, + "sort-name": "Adhesive", + "life-span": {"end": "2002-07-12", "begin": "1994", "ended": "true"}, + "name": "Adhesive", + "begin-area": { + "sort-name": "Katrineholm", + "id": "02390d96-b5a3-4282-a38f-e64a95d08b7f", + "name": "Katrineholm", }, }, ] } -_artists['get']['adhesive_wombat'] = {'artist': _artists['search']['adhesive_wombat']['artist-list'][0]} +_artists["get"]["adhesive_wombat"] = { + "artist": _artists["search"]["adhesive_wombat"]["artist-list"][0] +} -_artists['get']['soad'] = { - 'artist': { - 'country': 'US', - 'isni-list': ['0000000121055332'], - 'type': 'Group', - 'area': { - 'iso-3166-1-code-list': ['US'], - 'sort-name': 'United States', - 'id': '489ce91b-6658-3307-9877-795b68554c98', - 'name': 'United States' +_artists["get"]["soad"] = { + "artist": { + "country": "US", + "isni-list": ["0000000121055332"], + "type": "Group", + "area": { + "iso-3166-1-code-list": ["US"], + "sort-name": "United States", + "id": "489ce91b-6658-3307-9877-795b68554c98", + "name": "United States", }, - 'begin-area': { - 'sort-name': 'Glendale', - 'id': '6db2e45d-d7f3-43da-ac0b-7ba5ca627373', - 'name': 'Glendale' + "begin-area": { + "sort-name": "Glendale", + "id": "6db2e45d-d7f3-43da-ac0b-7ba5ca627373", + "name": "Glendale", }, - 'id': 'cc0b7089-c08d-4c10-b6b0-873582c17fd6', - 'life-span': {'begin': '1994'}, - 'sort-name': 'System of a Down', - 'name': 'System of a Down' + "id": "cc0b7089-c08d-4c10-b6b0-873582c17fd6", + "life-span": {"begin": "1994"}, + "sort-name": "System of a Down", + "name": "System of a Down", } } -_albums = {'search': {}, 'get': {}, 'get_with_includes': {}} -_albums['search']['hypnotize'] = { - 'release-list': [ +_albums = {"search": {}, "get": {}, "get_with_includes": {}} +_albums["search"]["hypnotize"] = { + "release-list": [ { "artist-credit": [ { @@ -69,22 +78,22 @@ _albums['search']['hypnotize'] = { { "alias": "SoaD", "sort-name": "SoaD", - "type": "Search hint" + "type": "Search hint", }, { "alias": "S.O.A.D.", "sort-name": "S.O.A.D.", - "type": "Search hint" + "type": "Search hint", }, { "alias": "System Of Down", "sort-name": "System Of Down", - "type": "Search hint" - } + "type": "Search hint", + }, ], "id": "cc0b7089-c08d-4c10-b6b0-873582c17fd6", "name": "System of a Down", - "sort-name": "System of a Down" + "sort-name": "System of a Down", } } ], @@ -99,16 +108,16 @@ _albums['search']['hypnotize'] = { "catalog-number": "8-2796-93871-2", "label": { "id": "f5be9cfe-e1af-405c-a074-caeaed6797c0", - "name": "American Recordings" - } + "name": "American Recordings", + }, }, { "catalog-number": "D162990", "label": { "id": "9a7d39a4-a887-40f3-a645-a9a136d1f13f", - "name": "BMG Direct Marketing, Inc." - } - } + "name": "BMG Direct Marketing, Inc.", + }, + }, ], "medium-count": 1, "medium-list": [ @@ -117,7 +126,7 @@ _albums['search']['hypnotize'] = { "disc-list": [], "format": "CD", "track-count": 12, - "track-list": [] + "track-list": [], } ], "medium-track-count": 12, @@ -126,26 +135,21 @@ _albums['search']['hypnotize'] = { { "area": { "id": "489ce91b-6658-3307-9877-795b68554c98", - "iso-3166-1-code-list": [ - "US" - ], + "iso-3166-1-code-list": ["US"], "name": "United States", - "sort-name": "United States" + "sort-name": "United States", }, - "date": "2005" + "date": "2005", } ], "release-group": { "id": "72035143-d6ec-308b-8ee5-070b8703902a", "primary-type": "Album", - "type": "Album" + "type": "Album", }, "status": "Official", - "text-representation": { - "language": "eng", - "script": "Latn" - }, - "title": "Hypnotize" + "text-representation": {"language": "eng", "script": "Latn"}, + "title": "Hypnotize", }, { "artist-credit": [ @@ -155,22 +159,22 @@ _albums['search']['hypnotize'] = { { "alias": "SoaD", "sort-name": "SoaD", - "type": "Search hint" + "type": "Search hint", }, { "alias": "S.O.A.D.", "sort-name": "S.O.A.D.", - "type": "Search hint" + "type": "Search hint", }, { "alias": "System Of Down", "sort-name": "System Of Down", - "type": "Search hint" - } + "type": "Search hint", + }, ], "id": "cc0b7089-c08d-4c10-b6b0-873582c17fd6", "name": "System of a Down", - "sort-name": "System of a Down" + "sort-name": "System of a Down", } } ], @@ -188,7 +192,7 @@ _albums['search']['hypnotize'] = { "disc-list": [], "format": "Vinyl", "track-count": 12, - "track-list": [] + "track-list": [], } ], "medium-track-count": 12, @@ -196,167 +200,233 @@ _albums['search']['hypnotize'] = { { "area": { "id": "489ce91b-6658-3307-9877-795b68554c98", - "iso-3166-1-code-list": [ - "US" - ], + "iso-3166-1-code-list": ["US"], "name": "United States", - "sort-name": "United States" + "sort-name": "United States", }, - "date": "2005-12-20" + "date": "2005-12-20", } ], "release-group": { "id": "72035143-d6ec-308b-8ee5-070b8703902a", "primary-type": "Album", - "type": "Album" + "type": "Album", }, "status": "Official", - "text-representation": { - "language": "eng", - "script": "Latn" - }, - "title": "Hypnotize" + "text-representation": {"language": "eng", "script": "Latn"}, + "title": "Hypnotize", }, ] } -_albums['get']['hypnotize'] = {'release': _albums['search']['hypnotize']['release-list'][0]} -_albums['get_with_includes']['hypnotize'] = { - 'release': { - 'artist-credit': [ - {'artist': {'id': 'cc0b7089-c08d-4c10-b6b0-873582c17fd6', - 'name': 'System of a Down', - 'sort-name': 'System of a Down'}}], - 'artist-credit-phrase': 'System of a Down', - 'barcode': '', - 'country': 'US', - 'cover-art-archive': {'artwork': 'true', - 'back': 'false', - 'count': '1', - 'front': 'true'}, - 'date': '2005', - 'id': '47ae093f-1607-49a3-be11-a15d335ccc94', - 'medium-count': 1, - 'medium-list': [{'format': 'CD', - 'position': '1', - 'track-count': 12, - 'track-list': [{'id': '59f5cf9a-75b2-3aa3-abda-6807a87107b3', - 'length': '186000', - 'number': '1', - 'position': '1', - 'recording': {'id': '76d03fc5-758c-48d0-a354-a67de086cc68', - 'length': '186000', - 'title': 'Attack'}, - 'track_or_recording_length': '186000'}, - {'id': '3aaa28c1-12b1-3c2a-b90a-82e09e355608', - 'length': '239000', - 'number': '2', - 'position': '2', - 'recording': {'id': '327543b0-9193-48c5-83c9-01c7b36c8c0a', - 'length': '239000', - 'title': 'Dreaming'}, - 'track_or_recording_length': '239000'}, - {'id': 'a34fef19-e637-3436-b7eb-276ff2814d6f', - 'length': '147000', - 'number': '3', - 'position': '3', - 'recording': {'id': '6e27866c-07a1-425d-bb4f-9d9e728db344', - 'length': '147000', - 'title': 'Kill Rock ’n Roll'}, - 'track_or_recording_length': '147000'}, - {'id': '72a4e5c0-c150-3ba1-9ceb-3ab82648af25', - 'length': '189000', - 'number': '4', - 'position': '4', - 'recording': {'id': '7ff8a67d-c8e2-4b3a-a045-7ad3561d0605', - 'length': '189000', - 'title': 'Hypnotize'}, - 'track_or_recording_length': '189000'}, - {'id': 'a748fa6e-b3b7-3b22-89fb-a038ec92ac32', - 'length': '178000', - 'number': '5', - 'position': '5', - 'recording': {'id': '19b6eb6a-0e76-4ef7-b63f-959339dbd5d2', - 'length': '178000', - 'title': 'Stealing Society'}, - 'track_or_recording_length': '178000'}, - {'id': '5c5a8d4e-e21a-317e-a719-6e2dbdefa5d2', - 'length': '216000', - 'number': '6', - 'position': '6', - 'recording': {'id': 'c3c2afe1-ee9a-47cb-b3c6-ff8100bc19d5', - 'length': '216000', - 'title': 'Tentative'}, - 'track_or_recording_length': '216000'}, - {'id': '265718ba-787f-3193-947b-3b6fa69ffe96', - 'length': '175000', - 'number': '7', - 'position': '7', - 'recording': {'id': '96f804e1-f600-4faa-95a6-ce597e7db120', - 'length': '175000', - 'title': 'U‐Fig'}, - 'title': 'U-Fig', - 'track_or_recording_length': '175000'}, - {'id': 'cdcf8572-3060-31ca-a72c-1ded81ca1f7a', - 'length': '328000', - 'number': '8', - 'position': '8', - 'recording': {'id': '26ba38f0-b26b-48b7-8e77-226b22a55f79', - 'length': '328000', - 'title': 'Holy Mountains'}, - 'track_or_recording_length': '328000'}, - {'id': 'f9f00cb0-5635-3217-a2a0-bd61917eb0df', - 'length': '171000', - 'number': '9', - 'position': '9', - 'recording': {'id': '039f3379-3a69-4e75-a882-df1c4e1608aa', - 'length': '171000', - 'title': 'Vicinity of Obscenity'}, - 'track_or_recording_length': '171000'}, - {'id': 'cdd45914-6741-353e-bbb5-d281048ff24f', - 'length': '164000', - 'number': '10', - 'position': '10', - 'recording': {'id': 'c24d541a-a9a8-4a22-84c6-5e6419459cf8', - 'length': '164000', - 'title': 'She’s Like Heroin'}, - 'track_or_recording_length': '164000'}, - {'id': 'cfcf12ac-6831-3dd6-a2eb-9d0bfeee3f6d', - 'length': '167000', - 'number': '11', - 'position': '11', - 'recording': {'id': '0aff4799-849f-4f83-84f4-22cabbba2378', - 'length': '167000', - 'title': 'Lonely Day'}, - 'track_or_recording_length': '167000'}, - {'id': '7e38bb38-ff62-3e41-a670-b7d77f578a1f', - 'length': '220000', - 'number': '12', - 'position': '12', - 'recording': {'id': 'e1b4d90f-2f44-4fe6-a826-362d4e3d9b88', - 'length': '220000', - 'title': 'Soldier Side'}, - 'track_or_recording_length': '220000'}]}], - 'packaging': 'Digipak', - 'quality': 'normal', - 'release-event-count': 1, - 'release-event-list': [{'area': {'id': '489ce91b-6658-3307-9877-795b68554c98', - 'iso-3166-1-code-list': ['US'], - 'name': 'United States', - 'sort-name': 'United States'}, - 'date': '2005'}], - 'status': 'Official', - 'text-representation': {'language': 'eng', 'script': 'Latn'}, - 'title': 'Hypnotize'}} +_albums["get"]["hypnotize"] = { + "release": _albums["search"]["hypnotize"]["release-list"][0] +} +_albums["get_with_includes"]["hypnotize"] = { + "release": { + "artist-credit": [ + { + "artist": { + "id": "cc0b7089-c08d-4c10-b6b0-873582c17fd6", + "name": "System of a Down", + "sort-name": "System of a Down", + } + } + ], + "artist-credit-phrase": "System of a Down", + "barcode": "", + "country": "US", + "cover-art-archive": { + "artwork": "true", + "back": "false", + "count": "1", + "front": "true", + }, + "date": "2005", + "id": "47ae093f-1607-49a3-be11-a15d335ccc94", + "medium-count": 1, + "medium-list": [ + { + "format": "CD", + "position": "1", + "track-count": 12, + "track-list": [ + { + "id": "59f5cf9a-75b2-3aa3-abda-6807a87107b3", + "length": "186000", + "number": "1", + "position": "1", + "recording": { + "id": "76d03fc5-758c-48d0-a354-a67de086cc68", + "length": "186000", + "title": "Attack", + }, + "track_or_recording_length": "186000", + }, + { + "id": "3aaa28c1-12b1-3c2a-b90a-82e09e355608", + "length": "239000", + "number": "2", + "position": "2", + "recording": { + "id": "327543b0-9193-48c5-83c9-01c7b36c8c0a", + "length": "239000", + "title": "Dreaming", + }, + "track_or_recording_length": "239000", + }, + { + "id": "a34fef19-e637-3436-b7eb-276ff2814d6f", + "length": "147000", + "number": "3", + "position": "3", + "recording": { + "id": "6e27866c-07a1-425d-bb4f-9d9e728db344", + "length": "147000", + "title": "Kill Rock ’n Roll", + }, + "track_or_recording_length": "147000", + }, + { + "id": "72a4e5c0-c150-3ba1-9ceb-3ab82648af25", + "length": "189000", + "number": "4", + "position": "4", + "recording": { + "id": "7ff8a67d-c8e2-4b3a-a045-7ad3561d0605", + "length": "189000", + "title": "Hypnotize", + }, + "track_or_recording_length": "189000", + }, + { + "id": "a748fa6e-b3b7-3b22-89fb-a038ec92ac32", + "length": "178000", + "number": "5", + "position": "5", + "recording": { + "id": "19b6eb6a-0e76-4ef7-b63f-959339dbd5d2", + "length": "178000", + "title": "Stealing Society", + }, + "track_or_recording_length": "178000", + }, + { + "id": "5c5a8d4e-e21a-317e-a719-6e2dbdefa5d2", + "length": "216000", + "number": "6", + "position": "6", + "recording": { + "id": "c3c2afe1-ee9a-47cb-b3c6-ff8100bc19d5", + "length": "216000", + "title": "Tentative", + }, + "track_or_recording_length": "216000", + }, + { + "id": "265718ba-787f-3193-947b-3b6fa69ffe96", + "length": "175000", + "number": "7", + "position": "7", + "recording": { + "id": "96f804e1-f600-4faa-95a6-ce597e7db120", + "length": "175000", + "title": "U‐Fig", + }, + "title": "U-Fig", + "track_or_recording_length": "175000", + }, + { + "id": "cdcf8572-3060-31ca-a72c-1ded81ca1f7a", + "length": "328000", + "number": "8", + "position": "8", + "recording": { + "id": "26ba38f0-b26b-48b7-8e77-226b22a55f79", + "length": "328000", + "title": "Holy Mountains", + }, + "track_or_recording_length": "328000", + }, + { + "id": "f9f00cb0-5635-3217-a2a0-bd61917eb0df", + "length": "171000", + "number": "9", + "position": "9", + "recording": { + "id": "039f3379-3a69-4e75-a882-df1c4e1608aa", + "length": "171000", + "title": "Vicinity of Obscenity", + }, + "track_or_recording_length": "171000", + }, + { + "id": "cdd45914-6741-353e-bbb5-d281048ff24f", + "length": "164000", + "number": "10", + "position": "10", + "recording": { + "id": "c24d541a-a9a8-4a22-84c6-5e6419459cf8", + "length": "164000", + "title": "She’s Like Heroin", + }, + "track_or_recording_length": "164000", + }, + { + "id": "cfcf12ac-6831-3dd6-a2eb-9d0bfeee3f6d", + "length": "167000", + "number": "11", + "position": "11", + "recording": { + "id": "0aff4799-849f-4f83-84f4-22cabbba2378", + "length": "167000", + "title": "Lonely Day", + }, + "track_or_recording_length": "167000", + }, + { + "id": "7e38bb38-ff62-3e41-a670-b7d77f578a1f", + "length": "220000", + "number": "12", + "position": "12", + "recording": { + "id": "e1b4d90f-2f44-4fe6-a826-362d4e3d9b88", + "length": "220000", + "title": "Soldier Side", + }, + "track_or_recording_length": "220000", + }, + ], + } + ], + "packaging": "Digipak", + "quality": "normal", + "release-event-count": 1, + "release-event-list": [ + { + "area": { + "id": "489ce91b-6658-3307-9877-795b68554c98", + "iso-3166-1-code-list": ["US"], + "name": "United States", + "sort-name": "United States", + }, + "date": "2005", + } + ], + "status": "Official", + "text-representation": {"language": "eng", "script": "Latn"}, + "title": "Hypnotize", + } +} -_albums['get']['marsupial'] = { - 'release': { +_albums["get"]["marsupial"] = { + "release": { "artist-credit": [ { "artist": { "disambiguation": "George Shaw", "id": "62c3befb-6366-4585-b256-809472333801", "name": "Adhesive Wombat", - "sort-name": "Wombat, Adhesive" + "sort-name": "Wombat, Adhesive", } } ], @@ -366,7 +436,7 @@ _albums['get']['marsupial'] = { "artwork": "true", "back": "false", "count": "1", - "front": "true" + "front": "true", }, "date": "2013-06-05", "id": "a50d2a81-2a50-484d-9cb4-b9f6833f583e", @@ -377,28 +447,23 @@ _albums['get']['marsupial'] = { { "area": { "id": "525d4e18-3d00-31b9-a58b-a146a916de8f", - "iso-3166-1-code-list": [ - "XW" - ], + "iso-3166-1-code-list": ["XW"], "name": "[Worldwide]", - "sort-name": "[Worldwide]" + "sort-name": "[Worldwide]", }, - "date": "2013-06-05" + "date": "2013-06-05", } ], "status": "Official", - "text-representation": { - "language": "eng", - "script": "Latn" - }, - "title": "Marsupial Madness" + "text-representation": {"language": "eng", "script": "Latn"}, + "title": "Marsupial Madness", } } -_tracks = {'search': {}, 'get': {}} +_tracks = {"search": {}, "get": {}} -_tracks['search']['8bitadventures'] = { - 'recording-list': [ +_tracks["search"]["8bitadventures"] = { + "recording-list": [ { "artist-credit": [ { @@ -406,7 +471,7 @@ _tracks['search']['8bitadventures'] = { "disambiguation": "George Shaw", "id": "62c3befb-6366-4585-b256-809472333801", "name": "Adhesive Wombat", - "sort-name": "Wombat, Adhesive" + "sort-name": "Wombat, Adhesive", } } ], @@ -430,9 +495,9 @@ _tracks['search']['8bitadventures'] = { "length": "271000", "number": "1", "title": "8-Bit Adventure", - "track_or_recording_length": "271000" + "track_or_recording_length": "271000", } - ] + ], } ], "medium-track-count": 11, @@ -440,70 +505,85 @@ _tracks['search']['8bitadventures'] = { { "area": { "id": "525d4e18-3d00-31b9-a58b-a146a916de8f", - "iso-3166-1-code-list": [ - "XW" - ], + "iso-3166-1-code-list": ["XW"], "name": "[Worldwide]", - "sort-name": "[Worldwide]" + "sort-name": "[Worldwide]", }, - "date": "2013-06-05" + "date": "2013-06-05", } ], "release-group": { "id": "447b4979-2178-405c-bfe6-46bf0b09e6c7", "primary-type": "Album", - "type": "Album" + "type": "Album", }, "status": "Official", - "title": "Marsupial Madness" + "title": "Marsupial Madness", } ], "title": "8-Bit Adventure", "tag-list": [ - { - "count": "2", - "name": "techno" - }, - { - "count": "2", - "name": "good-music" - }, + {"count": "2", "name": "techno"}, + {"count": "2", "name": "good-music"}, ], - }, + } ] } -_tracks['get']['8bitadventures'] = {'recording': _tracks['search']['8bitadventures']['recording-list'][0]} -_tracks['get']['chop_suey'] = { - 'recording': { - 'id': '46c7368a-013a-47b6-97cc-e55e7ab25213', - 'length': '210240', - 'title': 'Chop Suey!', - 'work-relation-list': [{'target': 'e2ecabc4-1b9d-30b2-8f30-3596ec423dc5', - 'type': 'performance', - 'type-id': 'a3005666-a872-32c3-ad06-98af558e99b0', - 'work': {'id': 'e2ecabc4-1b9d-30b2-8f30-3596ec423dc5', - 'language': 'eng', - 'title': 'Chop Suey!'}}]}} +_tracks["get"]["8bitadventures"] = { + "recording": _tracks["search"]["8bitadventures"]["recording-list"][0] +} +_tracks["get"]["chop_suey"] = { + "recording": { + "id": "46c7368a-013a-47b6-97cc-e55e7ab25213", + "length": "210240", + "title": "Chop Suey!", + "work-relation-list": [ + { + "target": "e2ecabc4-1b9d-30b2-8f30-3596ec423dc5", + "type": "performance", + "type-id": "a3005666-a872-32c3-ad06-98af558e99b0", + "work": { + "id": "e2ecabc4-1b9d-30b2-8f30-3596ec423dc5", + "language": "eng", + "title": "Chop Suey!", + }, + } + ], + } +} -_works = {'search': {}, 'get': {}} -_works['get']['chop_suey'] = {'work': {'id': 'e2ecabc4-1b9d-30b2-8f30-3596ec423dc5', - 'language': 'eng', - 'recording-relation-list': [{'direction': 'backward', - 'recording': {'disambiguation': 'edit', - 'id': '07ca77cf-f513-4e9c-b190-d7e24bbad448', - 'length': '170893', - 'title': 'Chop Suey!'}, - 'target': '07ca77cf-f513-4e9c-b190-d7e24bbad448', - 'type': 'performance', - 'type-id': 'a3005666-a872-32c3-ad06-98af558e99b0'}, - ], - 'title': 'Chop Suey!', - 'type': 'Song', - 'url-relation-list': [{'direction': 'backward', - 'target': 'http://lyrics.wikia.com/System_Of_A_Down:Chop_Suey!', - 'type': 'lyrics', - 'type-id': 'e38e65aa-75e0-42ba-ace0-072aeb91a538'}]}} +_works = {"search": {}, "get": {}} +_works["get"]["chop_suey"] = { + "work": { + "id": "e2ecabc4-1b9d-30b2-8f30-3596ec423dc5", + "language": "eng", + "recording-relation-list": [ + { + "direction": "backward", + "recording": { + "disambiguation": "edit", + "id": "07ca77cf-f513-4e9c-b190-d7e24bbad448", + "length": "170893", + "title": "Chop Suey!", + }, + "target": "07ca77cf-f513-4e9c-b190-d7e24bbad448", + "type": "performance", + "type-id": "a3005666-a872-32c3-ad06-98af558e99b0", + } + ], + "title": "Chop Suey!", + "type": "Song", + "url-relation-list": [ + { + "direction": "backward", + "target": "http://lyrics.wikia.com/System_Of_A_Down:Chop_Suey!", + "type": "lyrics", + "type-id": "e38e65aa-75e0-42ba-ace0-072aeb91a538", + } + ], + } +} @pytest.fixture() @@ -537,7 +617,7 @@ def lyricswiki_content(): - + @@ -570,4 +650,4 @@ def binary_cover(): """ Return an album cover image in form of a binary string """ - return b'\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x02\x01\x00H\x00H\x00\x00\xff\xed\x08\xaePhotoshop 3.0\x008BIM\x03\xe9\x00\x00\x00\x00\x00x\x00\x03\x00\x00\x00H\x00H\x00\x00\x00\x00\x02\xd8\x02(\xff\xe1\xff\xe2\x02\xf9\x02F\x03G\x05(\x03\xfc\x00\x02\x00\x00\x00H\x00H\x00\x00\x00\x00\x02\xd8\x02(\x00\x01\x00\x00\x00d\x00\x00\x00\x01\x00\x03\x03\x03\x00\x00\x00\x01\'\x0f\x00\x01\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00`\x08\x00\x19\x01\x90\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x008BIM\x03\xed\x00\x00\x00\x00\x00\x10\x00H\x00\x00\x00\x01\x00\x01\x00H\x00\x00\x00\x01\x00\x018BIM\x03\xf3\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x008BIM\x04\n\x00\x00\x00\x00\x00\x01\x00\x008BIM\'\x10\x00\x00\x00\x00\x00\n\x00\x01\x00\x00\x00\x00\x00\x00\x00\x028BIM\x03\xf5\x00\x00\x00\x00\x00H\x00/ff\x00\x01\x00lff\x00\x06\x00\x00\x00\x00\x00\x01\x00/ff\x00\x01\x00\xa1\x99\x9a\x00\x06\x00\x00\x00\x00\x00\x01\x002\x00\x00\x00\x01\x00Z\x00\x00\x00\x06\x00\x00\x00\x00\x00\x01\x005\x00\x00\x00\x01\x00-\x00\x00\x00\x06\x00\x00\x00\x00\x00\x018BIM\x03\xf8\x00\x00\x00\x00\x00p\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x03\xe8\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x03\xe8\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x03\xe8\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x03\xe8\x00\x008BIM\x04\x00\x00\x00\x00\x00\x00\x02\x00\x018BIM\x04\x02\x00\x00\x00\x00\x00\x04\x00\x00\x00\x008BIM\x04\x08\x00\x00\x00\x00\x00\x10\x00\x00\x00\x01\x00\x00\x02@\x00\x00\x02@\x00\x00\x00\x008BIM\x04\t\x00\x00\x00\x00\x06\x9b\x00\x00\x00\x01\x00\x00\x00\x80\x00\x00\x00\x80\x00\x00\x01\x80\x00\x00\xc0\x00\x00\x00\x06\x7f\x00\x18\x00\x01\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x02\x01\x00H\x00H\x00\x00\xff\xfe\x00\'File written by Adobe Photoshop\xa8 4.0\x00\xff\xee\x00\x0eAdobe\x00d\x80\x00\x00\x00\x01\xff\xdb\x00\x84\x00\x0c\x08\x08\x08\t\x08\x0c\t\t\x0c\x11\x0b\n\x0b\x11\x15\x0f\x0c\x0c\x0f\x15\x18\x13\x13\x15\x13\x13\x18\x11\x0c\x0c\x0c\x0c\x0c\x0c\x11\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x01\r\x0b\x0b\r\x0e\r\x10\x0e\x0e\x10\x14\x0e\x0e\x0e\x14\x14\x0e\x0e\x0e\x0e\x14\x11\x0c\x0c\x0c\x0c\x0c\x11\x11\x0c\x0c\x0c\x0c\x0c\x0c\x11\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\xff\xc0\x00\x11\x08\x00\x80\x00\x80\x03\x01"\x00\x02\x11\x01\x03\x11\x01\xff\xdd\x00\x04\x00\x08\xff\xc4\x01?\x00\x00\x01\x05\x01\x01\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x03\x00\x01\x02\x04\x05\x06\x07\x08\t\n\x0b\x01\x00\x01\x05\x01\x01\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x01\x00\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x10\x00\x01\x04\x01\x03\x02\x04\x02\x05\x07\x06\x08\x05\x03\x0c3\x01\x00\x02\x11\x03\x04!\x121\x05AQa\x13"q\x812\x06\x14\x91\xa1\xb1B#$\x15R\xc1b34r\x82\xd1C\x07%\x92S\xf0\xe1\xf1cs5\x16\xa2\xb2\x83&D\x93TdE\xc2\xa3t6\x17\xd2U\xe2e\xf2\xb3\x84\xc3\xd3u\xe3\xf3F\'\x94\xa4\x85\xb4\x95\xc4\xd4\xe4\xf4\xa5\xb5\xc5\xd5\xe5\xf5Vfv\x86\x96\xa6\xb6\xc6\xd6\xe6\xf67GWgw\x87\x97\xa7\xb7\xc7\xd7\xe7\xf7\x11\x00\x02\x02\x01\x02\x04\x04\x03\x04\x05\x06\x07\x07\x06\x055\x01\x00\x02\x11\x03!1\x12\x04AQaq"\x13\x052\x81\x91\x14\xa1\xb1B#\xc1R\xd1\xf03$b\xe1r\x82\x92CS\x15cs4\xf1%\x06\x16\xa2\xb2\x83\x07&5\xc2\xd2D\x93T\xa3\x17dEU6te\xe2\xf2\xb3\x84\xc3\xd3u\xe3\xf3F\x94\xa4\x85\xb4\x95\xc4\xd4\xe4\xf4\xa5\xb5\xc5\xd5\xe5\xf5Vfv\x86\x96\xa6\xb6\xc6\xd6\xe6\xf6\'7GWgw\x87\x97\xa7\xb7\xc7\xff\xda\x00\x0c\x03\x01\x00\x02\x11\x03\x11\x00?\x00\xf5T\x92I%)$\x92IJI$\x92R\x92I$\x94\xa4\x92I%)$\x92IJI$\x92R\x92I$\x94\xff\x00\xff\xd0\xf5T\x92I%)$\x92IJI%\xe7\xff\x00Z\x7f\xc6\xbf\xfc\xde\xeb\xb9]\x1f\xf6_\xda~\xcd\xe9\xfe\x9b\xed\x1e\x9e\xefR\xba\xef\xfeo\xec\xf6\xed\xdb\xea\xec\xfeq%>\x80\x92\xf2\xaf\xfc}?\xf3I\xff\x00\xb3_\xfb\xe8\x97\xfe>\x9f\xf9\xa4\xff\x00\xd9\xaf\xfd\xf4IO\xaa\xa4\xbc\xab\xff\x00\x1fO\xfc\xd2\x7f\xec\xd7\xfe\xfa%\xff\x00\x8f\xa7\xfei?\xf6k\xff\x00}\x12S\xea\xa9.+\xeaW\xf8\xc8\xff\x00\x9d}V\xde\x9d\xfb;\xec~\x96;\xb2=O[\xd5\x9d\xaf\xaa\xad\x9b=\n\x7f\xd3}-\xeb\xb5IJI$\x92R\x92I$\x94\xff\x00\xff\xd1\xf5T\x92I%)$\x97\x9f\xff\x00\x8d\x7f\xad=w\xea\xf7\xec\xbf\xd8\xf9_f\xfbO\xda=o\xd1\xd7f\xefO\xec\xfe\x9f\xf3\xf5\xdb\xb7o\xabg\xd0IO\xa0/\x9f\xff\x00\xc6\x97\xfe.\xfa\x9f\xfdc\xff\x00m\xf1\xd2\xff\x00\xc7K\xeb\xdf\xfeY\xff\x00\xe0\x18\xff\x00\xfb\xce\xb9\xfe\xa9\xd53\xfa\xbe}\xbdG\xa8\xdb\xeb\xe5\xdf\xb7\xd4\xb3kY;\x1a\xda\x99\xec\xa9\xac\xaf\xf9\xb63\xf3\x12SU$\x92IJI$\x92S\xdf\xff\x00\x89O\xfcUe\x7f\xe1\x0b?\xf3\xf6*\xf6\xb5\xf3/D\xeb\xfd[\xa0\xe5?3\xa4\xdf\xf6l\x8b+59\xfb\x18\xf9a-\xb1\xcd\xdb{-g\xd3\xa9\x8bk\xff\x00\x1d/\xaf\x7f\xf9g\xff\x00\x80c\xff\x00\xef:J~\x80Iq\xff\x00\xe2\xbf\xaf\xf5n\xbd\xd023:\xb5\xff\x00i\xc8\xaf-\xf55\xfb\x18\xc8`\xae\x8b\x1a\xdd\xb42\xa6};^\xbb\x04\x94\xa4\x92I%?\xff\xd2\xf5T\x92I%)yW\xf8\xf4\xff\x00\xbcO\xfd\n\xff\x00\xddE\xea\xab\xca\xbf\xc7\xa7\xfd\xe2\x7f\xe8W\xfe\xea$\xa7\xca\x92I$\x94\xa4\x92I%)$\x92IJI$\x92S\xed_\xe2S\xff\x00\x12\xb9_\xf8~\xcf\xfc\xf3\x8a\xbd\x01y\xff\x00\xf8\x94\xff\x00\xc4\xaeW\xfe\x1f\xb3\xff\x00<\xe2\xaf@IJI$\x92S\xff\xd3\xf5T\x92I%)yW\xf8\xf4\xff\x00\xbcO\xfd\n\xff\x00\xddE\xea\xab\xca\xbf\xc7\xa7\xfd\xe2\x7f\xe8W\xfe\xea$\xa7\xca\x92I$\x94\xa4\x92I%)$\x92IJI$\x92S\xed_\xe2S\xff\x00\x12\xb9_\xf8~\xcf\xfc\xf3\x8a\xbd\x01y\xff\x00\xf8\x94\xff\x00\xc4\xaeW\xfe\x1f\xb3\xff\x00<\xe2\xaf@IJI$\x92S\xff\xd4\xf5T\x92I%)q_\xe3#\xeaWU\xfa\xd7\xfb;\xf6u\xb8\xf5}\x8f\xd6\xf5>\xd0\xe7\xb6}_Cf\xcfJ\xab\xbf\xd0\xbfr\xedRIO\x8a\x7f\xe3)\xf5\xab\xfe\xe5`\x7f\xdb\x97\x7f\xef*\xe4:\xff\x00D\xca\xe8=Z\xfe\x93\x98\xfa\xec\xc8\xc6\xd9\xbd\xd5\x12Xw\xb1\x97\xb7k\xacmO\xfa\x16\xfe\xe2\xfai|\xff\x00\xfe4\xbf\xf1w\xd4\xff\x00\xeb\x1f\xfbo\x8e\x92\x9eU$\x92IJI$\x92S\xb1\xf5_\xea\xbfP\xfa\xd1\xd4,\xc0\xc0\xb2\x9a\xad\xaa\x93{\x9dys[\xb5\xae\xae\xa2\x01\xaa\xbb\x9d\xbfu\xcd\xfc\xd5\xd3\xff\x00\xe3)\xf5\xab\xfe\xe5`\x7f\xdb\x97\x7f\xef*_\xe2S\xff\x00\x15Y_\xf8B\xcf\xfc\xfd\x8a\xbd\xad%<\xbf\xf8\xbc\xfa\xaf\xd4>\xab\xf4[\xb03\xec\xa6\xdbm\xc9u\xedu\x05\xcen\xd7WM@\x13mt\xbb~\xea]\xf9\xab\xa8I$\x94\xa4\x92I%?\xff\xd5\xf5T\x92I%)$\x92IJ\\\x7f_\xff\x00\x15\xfd\x03\xafuk\xfa\xb6fF]y\x19;7\xb6\xa7\xd6\x1861\x947kl\xa2\xd7\xfd\n\xbf}v\t$\xa7\xcf\xff\x00\xf1\x94\xfa\xab\xff\x00r\xb3\xff\x00\xed\xca\x7f\xf7\x95/\xfce>\xaa\xff\x00\xdc\xac\xff\x00\xfbr\x9f\xfd\xe5^\x80\x92J|\xff\x00\xff\x00\x19O\xaa\xbf\xf7+?\xfe\xdc\xa7\xff\x00yR\xff\x00\xc6S\xea\xaf\xfd\xca\xcf\xff\x00\xb7)\xff\x00\xdeU\xe8\t$\xa7\x97\xfa\xaf\xfe/:/\xd5~\xa1f~\x05\xd96\xdbm&\x876\xf7V\xe6\xeds\xab\xb4\x90*\xa6\x97o\xddK\x7f9u\t$\x92\x94\x92I$\xa5$\x92I)\xff\xd6\xf5T\x92I%)$\x92IJI$\x92R\x92I$\x94\xa4\x92I%)$\x92IJI$\x92R\x92I$\x94\xff\x00\xff\xd9\x008BIM\x04\x06\x00\x00\x00\x00\x00\x07\x00\x03\x00\x00\x00\x01\x01\x00\xff\xfe\x00\'File written by Adobe Photoshop\xa8 4.0\x00\xff\xee\x00\x0eAdobe\x00d\x00\x00\x00\x00\x01\xff\xdb\x00\x84\x00\n\x07\x07\x07\x08\x07\n\x08\x08\n\x0f\n\x08\n\x0f\x12\r\n\n\r\x12\x14\x10\x10\x12\x10\x10\x14\x11\x0c\x0c\x0c\x0c\x0c\x0c\x11\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x01\x0b\x0c\x0c\x15\x13\x15"\x18\x18"\x14\x0e\x0e\x0e\x14\x14\x0e\x0e\x0e\x0e\x14\x11\x0c\x0c\x0c\x0c\x0c\x11\x11\x0c\x0c\x0c\x0c\x0c\x0c\x11\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\xff\xc0\x00\x11\x08\x00\t\x00\t\x03\x01\x11\x00\x02\x11\x01\x03\x11\x01\xff\xdd\x00\x04\x00\x02\xff\xc4\x01\xa2\x00\x00\x00\x07\x01\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x04\x05\x03\x02\x06\x01\x00\x07\x08\t\n\x0b\x01\x00\x02\x02\x03\x01\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x01\x00\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x10\x00\x02\x01\x03\x03\x02\x04\x02\x06\x07\x03\x04\x02\x06\x02s\x01\x02\x03\x11\x04\x00\x05!\x121AQ\x06\x13a"q\x81\x142\x91\xa1\x07\x15\xb1B#\xc1R\xd1\xe13\x16b\xf0$r\x82\xf1%C4S\x92\xa2\xb2cs\xc25D\'\x93\xa3\xb36\x17Tdt\xc3\xd2\xe2\x08&\x83\t\n\x18\x19\x84\x94EF\xa4\xb4V\xd3U(\x1a\xf2\xe3\xf3\xc4\xd4\xe4\xf4eu\x85\x95\xa5\xb5\xc5\xd5\xe5\xf5fv\x86\x96\xa6\xb6\xc6\xd6\xe6\xf67GWgw\x87\x97\xa7\xb7\xc7\xd7\xe7\xf78HXhx\x88\x98\xa8\xb8\xc8\xd8\xe8\xf8)9IYiy\x89\x99\xa9\xb9\xc9\xd9\xe9\xf9*:JZjz\x8a\x9a\xaa\xba\xca\xda\xea\xfa\x11\x00\x02\x02\x01\x02\x03\x05\x05\x04\x05\x06\x04\x08\x03\x03m\x01\x00\x02\x11\x03\x04!\x121A\x05Q\x13a"\x06q\x81\x912\xa1\xb1\xf0\x14\xc1\xd1\xe1#B\x15Rbr\xf13$4C\x82\x16\x92S%\xa2c\xb2\xc2\x07s\xd25\xe2D\x83\x17T\x93\x08\t\n\x18\x19&6E\x1a\'dtU7\xf2\xa3\xb3\xc3()\xd3\xe3\xf3\x84\x94\xa4\xb4\xc4\xd4\xe4\xf4eu\x85\x95\xa5\xb5\xc5\xd5\xe5\xf5FVfv\x86\x96\xa6\xb6\xc6\xd6\xe6\xf6GWgw\x87\x97\xa7\xb7\xc7\xd7\xe7\xf78HXhx\x88\x98\xa8\xb8\xc8\xd8\xe8\xf89IYiy\x89\x99\xa9\xb9\xc9\xd9\xe9\xf9*:JZjz\x8a\x9a\xaa\xba\xca\xda\xea\xfa\xff\xda\x00\x0c\x03\x01\x00\x02\x11\x03\x11\x00?\x00\x91\xea\xfa\xbf\xe6D_\x99\x16\x96\x16\x16\x8c\xdeWf\x84;\x88U\xa1hY\x7f\xd3\'\x9e\xf3\xedCq\x0bz\xfe\x94^\xbc?\xdc\xdb\xff\x00\xa3\xcd\xeb\x7f\xa4\xaa\xf4\x80\x92\xf2\xaf\xfc}?\xf3I\xff\x00\xb3_\xfb\xe8\x97\xfe>\x9f\xf9\xa4\xff\x00\xd9\xaf\xfd\xf4IO\xaa\xa4\xbc\xab\xff\x00\x1fO\xfc\xd2\x7f\xec\xd7\xfe\xfa%\xff\x00\x8f\xa7\xfei?\xf6k\xff\x00}\x12S\xea\xa9.+\xeaW\xf8\xc8\xff\x00\x9d}V\xde\x9d\xfb;\xec~\x96;\xb2=O[\xd5\x9d\xaf\xaa\xad\x9b=\n\x7f\xd3}-\xeb\xb5IJI$\x92R\x92I$\x94\xff\x00\xff\xd1\xf5T\x92I%)$\x97\x9f\xff\x00\x8d\x7f\xad=w\xea\xf7\xec\xbf\xd8\xf9_f\xfbO\xda=o\xd1\xd7f\xefO\xec\xfe\x9f\xf3\xf5\xdb\xb7o\xabg\xd0IO\xa0/\x9f\xff\x00\xc6\x97\xfe.\xfa\x9f\xfdc\xff\x00m\xf1\xd2\xff\x00\xc7K\xeb\xdf\xfeY\xff\x00\xe0\x18\xff\x00\xfb\xce\xb9\xfe\xa9\xd53\xfa\xbe}\xbdG\xa8\xdb\xeb\xe5\xdf\xb7\xd4\xb3kY;\x1a\xda\x99\xec\xa9\xac\xaf\xf9\xb63\xf3\x12SU$\x92IJI$\x92S\xdf\xff\x00\x89O\xfcUe\x7f\xe1\x0b?\xf3\xf6*\xf6\xb5\xf3/D\xeb\xfd[\xa0\xe5?3\xa4\xdf\xf6l\x8b+59\xfb\x18\xf9a-\xb1\xcd\xdb{-g\xd3\xa9\x8bk\xff\x00\x1d/\xaf\x7f\xf9g\xff\x00\x80c\xff\x00\xef:J~\x80Iq\xff\x00\xe2\xbf\xaf\xf5n\xbd\xd023:\xb5\xff\x00i\xc8\xaf-\xf55\xfb\x18\xc8`\xae\x8b\x1a\xdd\xb42\xa6};^\xbb\x04\x94\xa4\x92I%?\xff\xd2\xf5T\x92I%)yW\xf8\xf4\xff\x00\xbcO\xfd\n\xff\x00\xddE\xea\xab\xca\xbf\xc7\xa7\xfd\xe2\x7f\xe8W\xfe\xea$\xa7\xca\x92I$\x94\xa4\x92I%)$\x92IJI$\x92S\xed_\xe2S\xff\x00\x12\xb9_\xf8~\xcf\xfc\xf3\x8a\xbd\x01y\xff\x00\xf8\x94\xff\x00\xc4\xaeW\xfe\x1f\xb3\xff\x00<\xe2\xaf@IJI$\x92S\xff\xd3\xf5T\x92I%)yW\xf8\xf4\xff\x00\xbcO\xfd\n\xff\x00\xddE\xea\xab\xca\xbf\xc7\xa7\xfd\xe2\x7f\xe8W\xfe\xea$\xa7\xca\x92I$\x94\xa4\x92I%)$\x92IJI$\x92S\xed_\xe2S\xff\x00\x12\xb9_\xf8~\xcf\xfc\xf3\x8a\xbd\x01y\xff\x00\xf8\x94\xff\x00\xc4\xaeW\xfe\x1f\xb3\xff\x00<\xe2\xaf@IJI$\x92S\xff\xd4\xf5T\x92I%)q_\xe3#\xeaWU\xfa\xd7\xfb;\xf6u\xb8\xf5}\x8f\xd6\xf5>\xd0\xe7\xb6}_Cf\xcfJ\xab\xbf\xd0\xbfr\xedRIO\x8a\x7f\xe3)\xf5\xab\xfe\xe5`\x7f\xdb\x97\x7f\xef*\xe4:\xff\x00D\xca\xe8=Z\xfe\x93\x98\xfa\xec\xc8\xc6\xd9\xbd\xd5\x12Xw\xb1\x97\xb7k\xacmO\xfa\x16\xfe\xe2\xfai|\xff\x00\xfe4\xbf\xf1w\xd4\xff\x00\xeb\x1f\xfbo\x8e\x92\x9eU$\x92IJI$\x92S\xb1\xf5_\xea\xbfP\xfa\xd1\xd4,\xc0\xc0\xb2\x9a\xad\xaa\x93{\x9dys[\xb5\xae\xae\xa2\x01\xaa\xbb\x9d\xbfu\xcd\xfc\xd5\xd3\xff\x00\xe3)\xf5\xab\xfe\xe5`\x7f\xdb\x97\x7f\xef*_\xe2S\xff\x00\x15Y_\xf8B\xcf\xfc\xfd\x8a\xbd\xad%<\xbf\xf8\xbc\xfa\xaf\xd4>\xab\xf4[\xb03\xec\xa6\xdbm\xc9u\xedu\x05\xcen\xd7WM@\x13mt\xbb~\xea]\xf9\xab\xa8I$\x94\xa4\x92I%?\xff\xd5\xf5T\x92I%)$\x92IJ\\\x7f_\xff\x00\x15\xfd\x03\xafuk\xfa\xb6fF]y\x19;7\xb6\xa7\xd6\x1861\x947kl\xa2\xd7\xfd\n\xbf}v\t$\xa7\xcf\xff\x00\xf1\x94\xfa\xab\xff\x00r\xb3\xff\x00\xed\xca\x7f\xf7\x95/\xfce>\xaa\xff\x00\xdc\xac\xff\x00\xfbr\x9f\xfd\xe5^\x80\x92J|\xff\x00\xff\x00\x19O\xaa\xbf\xf7+?\xfe\xdc\xa7\xff\x00yR\xff\x00\xc6S\xea\xaf\xfd\xca\xcf\xff\x00\xb7)\xff\x00\xdeU\xe8\t$\xa7\x97\xfa\xaf\xfe/:/\xd5~\xa1f~\x05\xd96\xdbm&\x876\xf7V\xe6\xeds\xab\xb4\x90*\xa6\x97o\xddK\x7f9u\t$\x92\x94\x92I$\xa5$\x92I)\xff\xd6\xf5T\x92I%)$\x92IJI$\x92R\x92I$\x94\xa4\x92I%)$\x92IJI$\x92R\x92I$\x94\xff\x00\xff\xd9\x008BIM\x04\x06\x00\x00\x00\x00\x00\x07\x00\x03\x00\x00\x00\x01\x01\x00\xff\xfe\x00'File written by Adobe Photoshop\xa8 4.0\x00\xff\xee\x00\x0eAdobe\x00d\x00\x00\x00\x00\x01\xff\xdb\x00\x84\x00\n\x07\x07\x07\x08\x07\n\x08\x08\n\x0f\n\x08\n\x0f\x12\r\n\n\r\x12\x14\x10\x10\x12\x10\x10\x14\x11\x0c\x0c\x0c\x0c\x0c\x0c\x11\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x01\x0b\x0c\x0c\x15\x13\x15\"\x18\x18\"\x14\x0e\x0e\x0e\x14\x14\x0e\x0e\x0e\x0e\x14\x11\x0c\x0c\x0c\x0c\x0c\x11\x11\x0c\x0c\x0c\x0c\x0c\x0c\x11\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\xff\xc0\x00\x11\x08\x00\t\x00\t\x03\x01\x11\x00\x02\x11\x01\x03\x11\x01\xff\xdd\x00\x04\x00\x02\xff\xc4\x01\xa2\x00\x00\x00\x07\x01\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x04\x05\x03\x02\x06\x01\x00\x07\x08\t\n\x0b\x01\x00\x02\x02\x03\x01\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x01\x00\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x10\x00\x02\x01\x03\x03\x02\x04\x02\x06\x07\x03\x04\x02\x06\x02s\x01\x02\x03\x11\x04\x00\x05!\x121AQ\x06\x13a\"q\x81\x142\x91\xa1\x07\x15\xb1B#\xc1R\xd1\xe13\x16b\xf0$r\x82\xf1%C4S\x92\xa2\xb2cs\xc25D'\x93\xa3\xb36\x17Tdt\xc3\xd2\xe2\x08&\x83\t\n\x18\x19\x84\x94EF\xa4\xb4V\xd3U(\x1a\xf2\xe3\xf3\xc4\xd4\xe4\xf4eu\x85\x95\xa5\xb5\xc5\xd5\xe5\xf5fv\x86\x96\xa6\xb6\xc6\xd6\xe6\xf67GWgw\x87\x97\xa7\xb7\xc7\xd7\xe7\xf78HXhx\x88\x98\xa8\xb8\xc8\xd8\xe8\xf8)9IYiy\x89\x99\xa9\xb9\xc9\xd9\xe9\xf9*:JZjz\x8a\x9a\xaa\xba\xca\xda\xea\xfa\x11\x00\x02\x02\x01\x02\x03\x05\x05\x04\x05\x06\x04\x08\x03\x03m\x01\x00\x02\x11\x03\x04!\x121A\x05Q\x13a\"\x06q\x81\x912\xa1\xb1\xf0\x14\xc1\xd1\xe1#B\x15Rbr\xf13$4C\x82\x16\x92S%\xa2c\xb2\xc2\x07s\xd25\xe2D\x83\x17T\x93\x08\t\n\x18\x19&6E\x1a'dtU7\xf2\xa3\xb3\xc3()\xd3\xe3\xf3\x84\x94\xa4\xb4\xc4\xd4\xe4\xf4eu\x85\x95\xa5\xb5\xc5\xd5\xe5\xf5FVfv\x86\x96\xa6\xb6\xc6\xd6\xe6\xf6GWgw\x87\x97\xa7\xb7\xc7\xd7\xe7\xf78HXhx\x88\x98\xa8\xb8\xc8\xd8\xe8\xf89IYiy\x89\x99\xa9\xb9\xc9\xd9\xe9\xf9*:JZjz\x8a\x9a\xaa\xba\xca\xda\xea\xfa\xff\xda\x00\x0c\x03\x01\x00\x02\x11\x03\x11\x00?\x00\x91\xea\xfa\xbf\xe6D_\x99\x16\x96\x16\x16\x8c\xdeWf\x84;\x88U\xa1hY\x7f\xd3'\x9e\xf3\xedCq\x0bz\xfe\x94^\xbc?\xdc\xdb\xff\x00\xa3\xcd\xeb\x7f\xa4\xaa\xf4 0 - assert type(cover_data['content']) == bytes - assert type(cover_data['description']) == str + cover_data = data.get_picture("cover_front") + assert cover_data["mimetype"].startswith("image/") + assert len(cover_data["content"]) > 0 + assert type(cover_data["content"]) == bytes + assert type(cover_data["description"]) == str -@pytest.mark.parametrize('field,value', [ - ('title', '999,999'), - ('artist', 'Nine Inch Nails'), - ('album', 'The Slip'), - ('date', datetime.date(2008, 5, 5)), - ('track_number', 1), - ('musicbrainz_albumid', uuid.UUID('12b57d46-a192-499e-a91f-7da66790a1c1')), - ('musicbrainz_recordingid', uuid.UUID('30f3f33e-8d0c-4e69-8539-cbd701d18f28')), - ('musicbrainz_artistid', uuid.UUID('b7ffd2af-418f-4be2-bdd1-22f8b48613da')), -]) +@pytest.mark.parametrize( + "field,value", + [ + ("title", "999,999"), + ("artist", "Nine Inch Nails"), + ("album", "The Slip"), + ("date", datetime.date(2008, 5, 5)), + ("track_number", 1), + ("musicbrainz_albumid", uuid.UUID("12b57d46-a192-499e-a91f-7da66790a1c1")), + ("musicbrainz_recordingid", uuid.UUID("30f3f33e-8d0c-4e69-8539-cbd701d18f28")), + ("musicbrainz_artistid", uuid.UUID("b7ffd2af-418f-4be2-bdd1-22f8b48613da")), + ], +) def test_can_get_metadata_from_flac_file(field, value): - path = os.path.join(DATA_DIR, 'sample.flac') + path = os.path.join(DATA_DIR, "sample.flac") data = metadata.Metadata(path) assert data.get(field) == value def test_can_get_metadata_from_flac_file_not_crash_if_empty(): - path = os.path.join(DATA_DIR, 'sample.flac') + path = os.path.join(DATA_DIR, "sample.flac") data = metadata.Metadata(path) with pytest.raises(metadata.TagNotFound): - data.get('test') + data.get("test") -@pytest.mark.parametrize('field_name', [ - 'musicbrainz_artistid', - 'musicbrainz_albumid', - 'musicbrainz_recordingid', -]) +@pytest.mark.parametrize( + "field_name", + ["musicbrainz_artistid", "musicbrainz_albumid", "musicbrainz_recordingid"], +) def test_mbid_clean_keeps_only_first(field_name): u1 = str(uuid.uuid4()) u2 = str(uuid.uuid4()) field = metadata.VALIDATION[field_name] - result = field.to_python('/'.join([u1, u2])) + result = field.to_python("/".join([u1, u2])) assert str(result) == u1 diff --git a/api/tests/music/test_models.py b/api/tests/music/test_models.py index 0ef54eb66..df18a0909 100644 --- a/api/tests/music/test_models.py +++ b/api/tests/music/test_models.py @@ -1,15 +1,14 @@ import os + import pytest -from funkwhale_api.music import models -from funkwhale_api.music import importers -from funkwhale_api.music import tasks +from funkwhale_api.music import importers, models, tasks DATA_DIR = os.path.dirname(os.path.abspath(__file__)) def test_can_store_release_group_id_on_album(factories): - album = factories['music.Album']() + album = factories["music.Album"]() assert album.release_group_id is not None @@ -21,7 +20,7 @@ def test_import_album_stores_release_group(factories): "disambiguation": "George Shaw", "id": "62c3befb-6366-4585-b256-809472333801", "name": "Adhesive Wombat", - "sort-name": "Wombat, Adhesive" + "sort-name": "Wombat, Adhesive", } } ], @@ -31,137 +30,134 @@ def test_import_album_stores_release_group(factories): "id": "a50d2a81-2a50-484d-9cb4-b9f6833f583e", "status": "Official", "title": "Marsupial Madness", - 'release-group': {'id': '447b4979-2178-405c-bfe6-46bf0b09e6c7'} + "release-group": {"id": "447b4979-2178-405c-bfe6-46bf0b09e6c7"}, } - artist = factories['music.Artist']( - mbid=album_data['artist-credit'][0]['artist']['id'] + artist = factories["music.Artist"]( + mbid=album_data["artist-credit"][0]["artist"]["id"] ) cleaned_data = models.Album.clean_musicbrainz_data(album_data) album = importers.load(models.Album, cleaned_data, album_data, import_hooks=[]) - assert album.release_group_id == album_data['release-group']['id'] + assert album.release_group_id == album_data["release-group"]["id"] assert album.artist == artist def test_import_track_from_release(factories, mocker): - album = factories['music.Album']( - mbid='430347cb-0879-3113-9fde-c75b658c298e') + album = factories["music.Album"](mbid="430347cb-0879-3113-9fde-c75b658c298e") album_data = { - 'release': { - 'id': album.mbid, - 'title': 'Daydream Nation', - 'status': 'Official', - 'medium-count': 1, - 'medium-list': [ + "release": { + "id": album.mbid, + "title": "Daydream Nation", + "status": "Official", + "medium-count": 1, + "medium-list": [ { - 'position': '1', - 'format': 'CD', - 'track-list': [ + "position": "1", + "format": "CD", + "track-list": [ { - 'id': '03baca8b-855a-3c05-8f3d-d3235287d84d', - 'position': '4', - 'number': '4', - 'length': '417973', - 'recording': { - 'id': '2109e376-132b-40ad-b993-2bb6812e19d4', - 'title': 'Teen Age Riot', - 'length': '417973'}, - 'track_or_recording_length': '417973' + "id": "03baca8b-855a-3c05-8f3d-d3235287d84d", + "position": "4", + "number": "4", + "length": "417973", + "recording": { + "id": "2109e376-132b-40ad-b993-2bb6812e19d4", + "title": "Teen Age Riot", + "length": "417973", + }, + "track_or_recording_length": "417973", } ], - 'track-count': 1 + "track-count": 1, } ], } } mocked_get = mocker.patch( - 'funkwhale_api.musicbrainz.api.releases.get', - return_value=album_data) - track_data = album_data['release']['medium-list'][0]['track-list'][0] + "funkwhale_api.musicbrainz.api.releases.get", return_value=album_data + ) + track_data = album_data["release"]["medium-list"][0]["track-list"][0] track = models.Track.get_or_create_from_release( - '430347cb-0879-3113-9fde-c75b658c298e', - track_data['recording']['id'], + "430347cb-0879-3113-9fde-c75b658c298e", track_data["recording"]["id"] )[0] - mocked_get.assert_called_once_with( - album.mbid, includes=models.Album.api_includes) - assert track.title == track_data['recording']['title'] - assert track.mbid == track_data['recording']['id'] + mocked_get.assert_called_once_with(album.mbid, includes=models.Album.api_includes) + assert track.title == track_data["recording"]["title"] + assert track.mbid == track_data["recording"]["id"] assert track.album == album assert track.artist == album.artist - assert track.position == int(track_data['position']) + assert track.position == int(track_data["position"]) + def test_import_job_is_bound_to_track_file(factories, mocker): - track = factories['music.Track']() - job = factories['music.ImportJob'](mbid=track.mbid) + track = factories["music.Track"]() + job = factories["music.ImportJob"](mbid=track.mbid) - mocker.patch('funkwhale_api.music.models.TrackFile.download_file') + mocker.patch("funkwhale_api.music.models.TrackFile.download_file") tasks.import_job_run(import_job_id=job.pk) job.refresh_from_db() assert job.track_file.track == track -@pytest.mark.parametrize('status', ['pending', 'errored', 'finished']) -def test_saving_job_updates_batch_status(status,factories, mocker): - batch = factories['music.ImportBatch']() +@pytest.mark.parametrize("status", ["pending", "errored", "finished"]) +def test_saving_job_updates_batch_status(status, factories, mocker): + batch = factories["music.ImportBatch"]() - assert batch.status == 'pending' + assert batch.status == "pending" - job = factories['music.ImportJob'](batch=batch, status=status) + factories["music.ImportJob"](batch=batch, status=status) batch.refresh_from_db() assert batch.status == status -@pytest.mark.parametrize('extention,mimetype', [ - ('ogg', 'audio/ogg'), - ('mp3', 'audio/mpeg'), -]) +@pytest.mark.parametrize( + "extention,mimetype", [("ogg", "audio/ogg"), ("mp3", "audio/mpeg")] +) def test_audio_track_mime_type(extention, mimetype, factories): - name = '.'.join(['test', extention]) + name = ".".join(["test", extention]) path = os.path.join(DATA_DIR, name) - tf = factories['music.TrackFile'](audio_file__from_path=path) + tf = factories["music.TrackFile"](audio_file__from_path=path) assert tf.mimetype == mimetype def test_track_file_file_name(factories): - name = 'test.mp3' + name = "test.mp3" path = os.path.join(DATA_DIR, name) - tf = factories['music.TrackFile'](audio_file__from_path=path) + tf = factories["music.TrackFile"](audio_file__from_path=path) - assert tf.filename == tf.track.full_name + '.mp3' + assert tf.filename == tf.track.full_name + ".mp3" def test_track_get_file_size(factories): - name = 'test.mp3' + name = "test.mp3" path = os.path.join(DATA_DIR, name) - tf = factories['music.TrackFile'](audio_file__from_path=path) + tf = factories["music.TrackFile"](audio_file__from_path=path) assert tf.get_file_size() == 297745 def test_track_get_file_size_federation(factories): - tf = factories['music.TrackFile']( - federation=True, - library_track__with_audio_file=True) + tf = factories["music.TrackFile"]( + federation=True, library_track__with_audio_file=True + ) assert tf.get_file_size() == tf.library_track.audio_file.size def test_track_get_file_size_in_place(factories): - name = 'test.mp3' + name = "test.mp3" path = os.path.join(DATA_DIR, name) - tf = factories['music.TrackFile']( - in_place=True, source='file://{}'.format(path)) + tf = factories["music.TrackFile"](in_place=True, source="file://{}".format(path)) assert tf.get_file_size() == 297745 def test_album_get_image_content(factories): - album = factories['music.Album']() - album.get_image(data={'content': b'test', 'mimetype':'image/jpeg'}) + album = factories["music.Album"]() + album.get_image(data={"content": b"test", "mimetype": "image/jpeg"}) album.refresh_from_db() - assert album.cover.read() == b'test' + assert album.cover.read() == b"test" diff --git a/api/tests/music/test_music.py b/api/tests/music/test_music.py index 4162912e4..387cebb2c 100644 --- a/api/tests/music/test_music.py +++ b/api/tests/music/test_music.py @@ -1,125 +1,142 @@ -import pytest -from funkwhale_api.music import models import datetime +import pytest + +from funkwhale_api.music import models + def test_can_create_artist_from_api(artists, mocker, db): mocker.patch( - 'musicbrainzngs.search_artists', - return_value=artists['search']['adhesive_wombat']) + "musicbrainzngs.search_artists", + return_value=artists["search"]["adhesive_wombat"], + ) artist = models.Artist.create_from_api(query="Adhesive wombat") - data = models.Artist.api.search(query='Adhesive wombat')['artist-list'][0] + data = models.Artist.api.search(query="Adhesive wombat")["artist-list"][0] - assert int(data['ext:score']), 100 - assert data['id'], '62c3befb-6366-4585-b256-809472333801' - assert artist.mbid, data['id'] - assert artist.name, 'Adhesive Wombat' + assert int(data["ext:score"]), 100 + assert data["id"], "62c3befb-6366-4585-b256-809472333801" + assert artist.mbid, data["id"] + assert artist.name, "Adhesive Wombat" def test_can_create_album_from_api(artists, albums, mocker, db): mocker.patch( - 'funkwhale_api.musicbrainz.api.releases.search', - return_value=albums['search']['hypnotize']) + "funkwhale_api.musicbrainz.api.releases.search", + return_value=albums["search"]["hypnotize"], + ) mocker.patch( - 'funkwhale_api.musicbrainz.api.artists.get', - return_value=artists['get']['soad']) - album = models.Album.create_from_api(query="Hypnotize", artist='system of a down', type='album') - data = models.Album.api.search(query='Hypnotize', artist='system of a down', type='album')['release-list'][0] + "funkwhale_api.musicbrainz.api.artists.get", return_value=artists["get"]["soad"] + ) + album = models.Album.create_from_api( + query="Hypnotize", artist="system of a down", type="album" + ) + data = models.Album.api.search( + query="Hypnotize", artist="system of a down", type="album" + )["release-list"][0] - assert album.mbid, data['id'] - assert album.title, 'Hypnotize' + assert album.mbid, data["id"] + assert album.title, "Hypnotize" with pytest.raises(ValueError): assert album.cover.path is not None assert album.release_date, datetime.date(2005, 1, 1) - assert album.artist.name, 'System of a Down' - assert album.artist.mbid, data['artist-credit'][0]['artist']['id'] + assert album.artist.name, "System of a Down" + assert album.artist.mbid, data["artist-credit"][0]["artist"]["id"] def test_can_create_track_from_api(artists, albums, tracks, mocker, db): mocker.patch( - 'funkwhale_api.musicbrainz.api.artists.get', - return_value=artists['get']['adhesive_wombat']) + "funkwhale_api.musicbrainz.api.artists.get", + return_value=artists["get"]["adhesive_wombat"], + ) mocker.patch( - 'funkwhale_api.musicbrainz.api.releases.get', - return_value=albums['get']['marsupial']) + "funkwhale_api.musicbrainz.api.releases.get", + return_value=albums["get"]["marsupial"], + ) mocker.patch( - 'funkwhale_api.musicbrainz.api.recordings.search', - return_value=tracks['search']['8bitadventures']) + "funkwhale_api.musicbrainz.api.recordings.search", + return_value=tracks["search"]["8bitadventures"], + ) track = models.Track.create_from_api(query="8-bit adventure") - data = models.Track.api.search(query='8-bit adventure')['recording-list'][0] - assert int(data['ext:score']) == 100 - assert data['id'] == '9968a9d6-8d92-4051-8f76-674e157b6eed' - assert track.mbid == data['id'] + data = models.Track.api.search(query="8-bit adventure")["recording-list"][0] + assert int(data["ext:score"]) == 100 + assert data["id"] == "9968a9d6-8d92-4051-8f76-674e157b6eed" + assert track.mbid == data["id"] assert track.artist.pk is not None - assert str(track.artist.mbid) == '62c3befb-6366-4585-b256-809472333801' - assert track.artist.name == 'Adhesive Wombat' - assert str(track.album.mbid) == 'a50d2a81-2a50-484d-9cb4-b9f6833f583e' - assert track.album.title == 'Marsupial Madness' + assert str(track.artist.mbid) == "62c3befb-6366-4585-b256-809472333801" + assert track.artist.name == "Adhesive Wombat" + assert str(track.album.mbid) == "a50d2a81-2a50-484d-9cb4-b9f6833f583e" + assert track.album.title == "Marsupial Madness" def test_can_create_track_from_api_with_corresponding_tags( - artists, albums, tracks, mocker, db): + artists, albums, tracks, mocker, db +): mocker.patch( - 'funkwhale_api.musicbrainz.api.artists.get', - return_value=artists['get']['adhesive_wombat']) + "funkwhale_api.musicbrainz.api.artists.get", + return_value=artists["get"]["adhesive_wombat"], + ) mocker.patch( - 'funkwhale_api.musicbrainz.api.releases.get', - return_value=albums['get']['marsupial']) + "funkwhale_api.musicbrainz.api.releases.get", + return_value=albums["get"]["marsupial"], + ) mocker.patch( - 'funkwhale_api.musicbrainz.api.recordings.get', - return_value=tracks['get']['8bitadventures']) - track = models.Track.create_from_api(id='9968a9d6-8d92-4051-8f76-674e157b6eed') - expected_tags = ['techno', 'good-music'] + "funkwhale_api.musicbrainz.api.recordings.get", + return_value=tracks["get"]["8bitadventures"], + ) + track = models.Track.create_from_api(id="9968a9d6-8d92-4051-8f76-674e157b6eed") + expected_tags = ["techno", "good-music"] track_tags = [tag.slug for tag in track.tags.all()] for tag in expected_tags: assert tag in track_tags -def test_can_get_or_create_track_from_api( - artists, albums, tracks, mocker, db): +def test_can_get_or_create_track_from_api(artists, albums, tracks, mocker, db): mocker.patch( - 'funkwhale_api.musicbrainz.api.artists.get', - return_value=artists['get']['adhesive_wombat']) + "funkwhale_api.musicbrainz.api.artists.get", + return_value=artists["get"]["adhesive_wombat"], + ) mocker.patch( - 'funkwhale_api.musicbrainz.api.releases.get', - return_value=albums['get']['marsupial']) + "funkwhale_api.musicbrainz.api.releases.get", + return_value=albums["get"]["marsupial"], + ) mocker.patch( - 'funkwhale_api.musicbrainz.api.recordings.search', - return_value=tracks['search']['8bitadventures']) + "funkwhale_api.musicbrainz.api.recordings.search", + return_value=tracks["search"]["8bitadventures"], + ) track = models.Track.create_from_api(query="8-bit adventure") - data = models.Track.api.search(query='8-bit adventure')['recording-list'][0] - assert int(data['ext:score']) == 100 - assert data['id'] == '9968a9d6-8d92-4051-8f76-674e157b6eed' - assert track.mbid == data['id'] + data = models.Track.api.search(query="8-bit adventure")["recording-list"][0] + assert int(data["ext:score"]) == 100 + assert data["id"] == "9968a9d6-8d92-4051-8f76-674e157b6eed" + assert track.mbid == data["id"] assert track.artist.pk is not None - assert str(track.artist.mbid) == '62c3befb-6366-4585-b256-809472333801' - assert track.artist.name == 'Adhesive Wombat' + assert str(track.artist.mbid) == "62c3befb-6366-4585-b256-809472333801" + assert track.artist.name == "Adhesive Wombat" - track2, created = models.Track.get_or_create_from_api(mbid=data['id']) + track2, created = models.Track.get_or_create_from_api(mbid=data["id"]) assert not created assert track == track2 def test_album_tags_deduced_from_tracks_tags(factories, django_assert_num_queries): - tag = factories['taggit.Tag']() - album = factories['music.Album']() - tracks = factories['music.Track'].create_batch( - 5, album=album, tags=[tag]) + tag = factories["taggit.Tag"]() + album = factories["music.Album"]() + factories["music.Track"].create_batch(5, album=album, tags=[tag]) - album = models.Album.objects.prefetch_related('tracks__tags').get(pk=album.pk) + album = models.Album.objects.prefetch_related("tracks__tags").get(pk=album.pk) with django_assert_num_queries(0): assert tag in album.tags def test_artist_tags_deduced_from_album_tags(factories, django_assert_num_queries): - tag = factories['taggit.Tag']() - album = factories['music.Album']() + tag = factories["taggit.Tag"]() + album = factories["music.Album"]() artist = album.artist - tracks = factories['music.Track'].create_batch( - 5, album=album, tags=[tag]) + factories["music.Track"].create_batch(5, album=album, tags=[tag]) - artist = models.Artist.objects.prefetch_related('albums__tracks__tags').get(pk=artist.pk) + artist = models.Artist.objects.prefetch_related("albums__tracks__tags").get( + pk=artist.pk + ) with django_assert_num_queries(0): assert tag in artist.tags @@ -127,10 +144,10 @@ def test_artist_tags_deduced_from_album_tags(factories, django_assert_num_querie def test_can_download_image_file_for_album(binary_cover, mocker, factories): mocker.patch( - 'funkwhale_api.musicbrainz.api.images.get_front', - return_value=binary_cover) + "funkwhale_api.musicbrainz.api.images.get_front", return_value=binary_cover + ) # client._api.get_image_front('55ea4f82-b42b-423e-a0e5-290ccdf443ed') - album = factories['music.Album'](mbid='55ea4f82-b42b-423e-a0e5-290ccdf443ed') + album = factories["music.Album"](mbid="55ea4f82-b42b-423e-a0e5-290ccdf443ed") album.get_image() album.save() diff --git a/api/tests/music/test_permissions.py b/api/tests/music/test_permissions.py index 825d1731d..5f73a361e 100644 --- a/api/tests/music/test_permissions.py +++ b/api/tests/music/test_permissions.py @@ -5,58 +5,56 @@ from funkwhale_api.music import permissions def test_list_permission_no_protect(preferences, anonymous_user, api_request): - preferences['common__api_authentication_required'] = False + preferences["common__api_authentication_required"] = False view = APIView.as_view() permission = permissions.Listen() - request = api_request.get('/') + request = api_request.get("/") assert permission.has_permission(request, view) is True -def test_list_permission_protect_authenticated( - factories, api_request, preferences): - preferences['common__api_authentication_required'] = True - user = factories['users.User']() +def test_list_permission_protect_authenticated(factories, api_request, preferences): + preferences["common__api_authentication_required"] = True + user = factories["users.User"]() view = APIView.as_view() permission = permissions.Listen() - request = api_request.get('/') - setattr(request, 'user', user) + request = api_request.get("/") + setattr(request, "user", user) assert permission.has_permission(request, view) is True def test_list_permission_protect_not_following_actor( - factories, api_request, preferences): - preferences['common__api_authentication_required'] = True - actor = factories['federation.Actor']() + factories, api_request, preferences +): + preferences["common__api_authentication_required"] = True + actor = factories["federation.Actor"]() view = APIView.as_view() permission = permissions.Listen() - request = api_request.get('/') - setattr(request, 'actor', actor) + request = api_request.get("/") + setattr(request, "actor", actor) assert permission.has_permission(request, view) is False -def test_list_permission_protect_following_actor( - factories, api_request, preferences): - preferences['common__api_authentication_required'] = True - library_actor = actors.SYSTEM_ACTORS['library'].get_actor_instance() - follow = factories['federation.Follow']( - approved=True, target=library_actor) +def test_list_permission_protect_following_actor(factories, api_request, preferences): + preferences["common__api_authentication_required"] = True + library_actor = actors.SYSTEM_ACTORS["library"].get_actor_instance() + follow = factories["federation.Follow"](approved=True, target=library_actor) view = APIView.as_view() permission = permissions.Listen() - request = api_request.get('/') - setattr(request, 'actor', follow.actor) + request = api_request.get("/") + setattr(request, "actor", follow.actor) assert permission.has_permission(request, view) is True def test_list_permission_protect_following_actor_not_approved( - factories, api_request, preferences): - preferences['common__api_authentication_required'] = True - library_actor = actors.SYSTEM_ACTORS['library'].get_actor_instance() - follow = factories['federation.Follow']( - approved=False, target=library_actor) + factories, api_request, preferences +): + preferences["common__api_authentication_required"] = True + library_actor = actors.SYSTEM_ACTORS["library"].get_actor_instance() + follow = factories["federation.Follow"](approved=False, target=library_actor) view = APIView.as_view() permission = permissions.Listen() - request = api_request.get('/') - setattr(request, 'actor', follow.actor) + request = api_request.get("/") + setattr(request, "actor", follow.actor) assert permission.has_permission(request, view) is False diff --git a/api/tests/music/test_serializers.py b/api/tests/music/test_serializers.py index fa22cecee..51ca96b5d 100644 --- a/api/tests/music/test_serializers.py +++ b/api/tests/music/test_serializers.py @@ -2,18 +2,18 @@ from funkwhale_api.music import serializers def test_artist_album_serializer(factories, to_api_date): - track = factories['music.Track']() + track = factories["music.Track"]() album = track.album album = album.__class__.objects.with_tracks_count().get(pk=album.pk) expected = { - 'id': album.id, - 'mbid': str(album.mbid), - 'title': album.title, - 'artist': album.artist.id, - 'creation_date': to_api_date(album.creation_date), - 'tracks_count': 1, - 'cover': album.cover.url, - 'release_date': to_api_date(album.release_date), + "id": album.id, + "mbid": str(album.mbid), + "title": album.title, + "artist": album.artist.id, + "creation_date": to_api_date(album.creation_date), + "tracks_count": 1, + "cover": album.cover.url, + "release_date": to_api_date(album.release_date), } serializer = serializers.ArtistAlbumSerializer(album) @@ -21,79 +21,71 @@ def test_artist_album_serializer(factories, to_api_date): def test_artist_with_albums_serializer(factories, to_api_date): - track = factories['music.Track']() + track = factories["music.Track"]() artist = track.artist artist = artist.__class__.objects.with_albums().get(pk=artist.pk) album = list(artist.albums.all())[0] expected = { - 'id': artist.id, - 'mbid': str(artist.mbid), - 'name': artist.name, - 'creation_date': to_api_date(artist.creation_date), - 'albums': [ - serializers.ArtistAlbumSerializer(album).data - ] + "id": artist.id, + "mbid": str(artist.mbid), + "name": artist.name, + "creation_date": to_api_date(artist.creation_date), + "albums": [serializers.ArtistAlbumSerializer(album).data], } serializer = serializers.ArtistWithAlbumsSerializer(artist) assert serializer.data == expected def test_album_track_serializer(factories, to_api_date): - tf = factories['music.TrackFile']() + tf = factories["music.TrackFile"]() track = tf.track expected = { - 'id': track.id, - 'artist': track.artist.id, - 'album': track.album.id, - 'mbid': str(track.mbid), - 'title': track.title, - 'position': track.position, - 'creation_date': to_api_date(track.creation_date), - 'files': [ - serializers.TrackFileSerializer(tf).data - ] + "id": track.id, + "artist": track.artist.id, + "album": track.album.id, + "mbid": str(track.mbid), + "title": track.title, + "position": track.position, + "creation_date": to_api_date(track.creation_date), + "files": [serializers.TrackFileSerializer(tf).data], } serializer = serializers.AlbumTrackSerializer(track) assert serializer.data == expected def test_track_file_serializer(factories, to_api_date): - tf = factories['music.TrackFile']() + tf = factories["music.TrackFile"]() expected = { - 'id': tf.id, - 'path': tf.path, - 'source': tf.source, - 'filename': tf.filename, - 'mimetype': tf.mimetype, - 'track': tf.track.pk, - 'duration': tf.duration, - 'mimetype': tf.mimetype, - 'bitrate': tf.bitrate, - 'size': tf.size, + "id": tf.id, + "path": tf.path, + "source": tf.source, + "filename": tf.filename, + "track": tf.track.pk, + "duration": tf.duration, + "mimetype": tf.mimetype, + "bitrate": tf.bitrate, + "size": tf.size, } serializer = serializers.TrackFileSerializer(tf) assert serializer.data == expected def test_album_serializer(factories, to_api_date): - track1 = factories['music.Track'](position=2) - track2 = factories['music.Track'](position=1, album=track1.album) + track1 = factories["music.Track"](position=2) + track2 = factories["music.Track"](position=1, album=track1.album) album = track1.album expected = { - 'id': album.id, - 'mbid': str(album.mbid), - 'title': album.title, - 'artist': serializers.ArtistSimpleSerializer(album.artist).data, - 'creation_date': to_api_date(album.creation_date), - 'cover': album.cover.url, - 'release_date': to_api_date(album.release_date), - 'tracks': serializers.AlbumTrackSerializer( - [track2, track1], - many=True - ).data + "id": album.id, + "mbid": str(album.mbid), + "title": album.title, + "artist": serializers.ArtistSimpleSerializer(album.artist).data, + "creation_date": to_api_date(album.creation_date), + "cover": album.cover.url, + "release_date": to_api_date(album.release_date), + "tracks": serializers.AlbumTrackSerializer([track2, track1], many=True).data, } serializer = serializers.AlbumSerializer(album) @@ -101,21 +93,19 @@ def test_album_serializer(factories, to_api_date): def test_track_serializer(factories, to_api_date): - tf = factories['music.TrackFile']() + tf = factories["music.TrackFile"]() track = tf.track expected = { - 'id': track.id, - 'artist': serializers.ArtistSimpleSerializer(track.artist).data, - 'album': serializers.TrackAlbumSerializer(track.album).data, - 'mbid': str(track.mbid), - 'title': track.title, - 'position': track.position, - 'creation_date': to_api_date(track.creation_date), - 'lyrics': track.get_lyrics_url(), - 'files': [ - serializers.TrackFileSerializer(tf).data - ] + "id": track.id, + "artist": serializers.ArtistSimpleSerializer(track.artist).data, + "album": serializers.TrackAlbumSerializer(track.album).data, + "mbid": str(track.mbid), + "title": track.title, + "position": track.position, + "creation_date": to_api_date(track.creation_date), + "lyrics": track.get_lyrics_url(), + "files": [serializers.TrackFileSerializer(tf).data], } serializer = serializers.TrackSerializer(track) assert serializer.data == expected diff --git a/api/tests/music/test_tasks.py b/api/tests/music/test_tasks.py index 77245e204..71d605b2b 100644 --- a/api/tests/music/test_tasks.py +++ b/api/tests/music/test_tasks.py @@ -1,181 +1,183 @@ import os + import pytest -from funkwhale_api.providers.acoustid import get_acoustid_client from funkwhale_api.music import tasks DATA_DIR = os.path.dirname(os.path.abspath(__file__)) def test_set_acoustid_on_track_file(factories, mocker, preferences): - preferences['providers_acoustid__api_key'] = 'test' - track_file = factories['music.TrackFile'](acoustid_track_id=None) - id = 'e475bf79-c1ce-4441-bed7-1e33f226c0a2' + preferences["providers_acoustid__api_key"] = "test" + track_file = factories["music.TrackFile"](acoustid_track_id=None) + id = "e475bf79-c1ce-4441-bed7-1e33f226c0a2" payload = { - 'results': [ - {'id': id, - 'recordings': [ - {'artists': [ - {'id': '9c6bddde-6228-4d9f-ad0d-03f6fcb19e13', - 'name': 'Binärpilot'}], - 'duration': 268, - 'id': 'f269d497-1cc0-4ae4-a0c4-157ec7d73fcb', - 'title': 'Bend'}], - 'score': 0.860825}], - 'status': 'ok' + "results": [ + { + "id": id, + "recordings": [ + { + "artists": [ + { + "id": "9c6bddde-6228-4d9f-ad0d-03f6fcb19e13", + "name": "Binärpilot", + } + ], + "duration": 268, + "id": "f269d497-1cc0-4ae4-a0c4-157ec7d73fcb", + "title": "Bend", + } + ], + "score": 0.860825, + } + ], + "status": "ok", } - m = mocker.patch('acoustid.match', return_value=payload) + m = mocker.patch("acoustid.match", return_value=payload) r = tasks.set_acoustid_on_track_file(track_file_id=track_file.pk) track_file.refresh_from_db() assert str(track_file.acoustid_track_id) == id assert r == id - m.assert_called_once_with('test', track_file.audio_file.path, parse=False) + m.assert_called_once_with("test", track_file.audio_file.path, parse=False) def test_set_acoustid_on_track_file_required_high_score(factories, mocker): - track_file = factories['music.TrackFile'](acoustid_track_id=None) - id = 'e475bf79-c1ce-4441-bed7-1e33f226c0a2' - payload = { - 'results': [{'score': 0.79}], - 'status': 'ok' - } - m = mocker.patch('acoustid.match', return_value=payload) - r = tasks.set_acoustid_on_track_file(track_file_id=track_file.pk) + track_file = factories["music.TrackFile"](acoustid_track_id=None) + payload = {"results": [{"score": 0.79}], "status": "ok"} + mocker.patch("acoustid.match", return_value=payload) + tasks.set_acoustid_on_track_file(track_file_id=track_file.pk) track_file.refresh_from_db() assert track_file.acoustid_track_id is None def test_import_batch_run(factories, mocker): - job = factories['music.ImportJob']() - mocked_job_run = mocker.patch( - 'funkwhale_api.music.tasks.import_job_run.delay') + job = factories["music.ImportJob"]() + mocked_job_run = mocker.patch("funkwhale_api.music.tasks.import_job_run.delay") tasks.import_batch_run(import_batch_id=job.batch.pk) mocked_job_run.assert_called_once_with(import_job_id=job.pk) -@pytest.mark.skip('Acoustid is disabled') +@pytest.mark.skip("Acoustid is disabled") def test_import_job_can_run_with_file_and_acoustid( - artists, albums, tracks, preferences, factories, mocker): - preferences['providers_acoustid__api_key'] = 'test' - path = os.path.join(DATA_DIR, 'test.ogg') - mbid = '9968a9d6-8d92-4051-8f76-674e157b6eed' + artists, albums, tracks, preferences, factories, mocker +): + preferences["providers_acoustid__api_key"] = "test" + path = os.path.join(DATA_DIR, "test.ogg") + mbid = "9968a9d6-8d92-4051-8f76-674e157b6eed" acoustid_payload = { - 'results': [ - {'id': 'e475bf79-c1ce-4441-bed7-1e33f226c0a2', - 'recordings': [ - { - 'duration': 268, - 'id': mbid}], - 'score': 0.860825}], - 'status': 'ok' + "results": [ + { + "id": "e475bf79-c1ce-4441-bed7-1e33f226c0a2", + "recordings": [{"duration": 268, "id": mbid}], + "score": 0.860825, + } + ], + "status": "ok", } mocker.patch( - 'funkwhale_api.music.utils.get_audio_file_data', - return_value={'bitrate': 42, 'length': 43}) + "funkwhale_api.music.utils.get_audio_file_data", + return_value={"bitrate": 42, "length": 43}, + ) mocker.patch( - 'funkwhale_api.musicbrainz.api.artists.get', - return_value=artists['get']['adhesive_wombat']) + "funkwhale_api.musicbrainz.api.artists.get", + return_value=artists["get"]["adhesive_wombat"], + ) mocker.patch( - 'funkwhale_api.musicbrainz.api.releases.get', - return_value=albums['get']['marsupial']) + "funkwhale_api.musicbrainz.api.releases.get", + return_value=albums["get"]["marsupial"], + ) mocker.patch( - 'funkwhale_api.musicbrainz.api.recordings.search', - return_value=tracks['search']['8bitadventures']) - mocker.patch('acoustid.match', return_value=acoustid_payload) + "funkwhale_api.musicbrainz.api.recordings.search", + return_value=tracks["search"]["8bitadventures"], + ) + mocker.patch("acoustid.match", return_value=acoustid_payload) - job = factories['music.FileImportJob'](audio_file__path=path) + job = factories["music.FileImportJob"](audio_file__path=path) f = job.audio_file tasks.import_job_run(import_job_id=job.pk) job.refresh_from_db() track_file = job.track_file - with open(path, 'rb') as f: + with open(path, "rb") as f: assert track_file.audio_file.read() == f.read() assert track_file.bitrate == 42 assert track_file.duration == 43 assert track_file.size == os.path.getsize(path) # audio file is deleted from import job once persisted to audio file assert not job.audio_file - assert job.status == 'finished' - assert job.source == 'file://' + assert job.status == "finished" + assert job.source == "file://" def test_run_import_skipping_accoustid(factories, mocker): - m = mocker.patch('funkwhale_api.music.tasks._do_import') - path = os.path.join(DATA_DIR, 'test.ogg') - job = factories['music.FileImportJob'](audio_file__path=path) + m = mocker.patch("funkwhale_api.music.tasks._do_import") + path = os.path.join(DATA_DIR, "test.ogg") + job = factories["music.FileImportJob"](audio_file__path=path) tasks.import_job_run(import_job_id=job.pk, use_acoustid=False) m.assert_called_once_with(job, False, use_acoustid=False) def test__do_import_skipping_accoustid(factories, mocker): - t = factories['music.Track']() + t = factories["music.Track"]() m = mocker.patch( - 'funkwhale_api.providers.audiofile.tasks.import_track_data_from_path', - return_value=t) - path = os.path.join(DATA_DIR, 'test.ogg') - job = factories['music.FileImportJob']( - mbid=None, - audio_file__path=path) + "funkwhale_api.providers.audiofile.tasks.import_track_data_from_path", + return_value=t, + ) + path = os.path.join(DATA_DIR, "test.ogg") + job = factories["music.FileImportJob"](mbid=None, audio_file__path=path) p = job.audio_file.path tasks._do_import(job, replace=False, use_acoustid=False) m.assert_called_once_with(p) -def test__do_import_skipping_accoustid_if_no_key( - factories, mocker, preferences): - preferences['providers_acoustid__api_key'] = '' - t = factories['music.Track']() +def test__do_import_skipping_accoustid_if_no_key(factories, mocker, preferences): + preferences["providers_acoustid__api_key"] = "" + t = factories["music.Track"]() m = mocker.patch( - 'funkwhale_api.providers.audiofile.tasks.import_track_data_from_path', - return_value=t) - path = os.path.join(DATA_DIR, 'test.ogg') - job = factories['music.FileImportJob']( - mbid=None, - audio_file__path=path) + "funkwhale_api.providers.audiofile.tasks.import_track_data_from_path", + return_value=t, + ) + path = os.path.join(DATA_DIR, "test.ogg") + job = factories["music.FileImportJob"](mbid=None, audio_file__path=path) p = job.audio_file.path tasks._do_import(job, replace=False, use_acoustid=False) m.assert_called_once_with(p) -def test_import_job_skip_if_already_exists( - artists, albums, tracks, factories, mocker): - path = os.path.join(DATA_DIR, 'test.ogg') - mbid = '9968a9d6-8d92-4051-8f76-674e157b6eed' - track_file = factories['music.TrackFile'](track__mbid=mbid) +def test_import_job_skip_if_already_exists(artists, albums, tracks, factories, mocker): + path = os.path.join(DATA_DIR, "test.ogg") + mbid = "9968a9d6-8d92-4051-8f76-674e157b6eed" + track_file = factories["music.TrackFile"](track__mbid=mbid) mocker.patch( - 'funkwhale_api.providers.audiofile.tasks.import_track_data_from_path', - return_value=track_file.track) + "funkwhale_api.providers.audiofile.tasks.import_track_data_from_path", + return_value=track_file.track, + ) - job = factories['music.FileImportJob'](audio_file__path=path) - f = job.audio_file + job = factories["music.FileImportJob"](audio_file__path=path) tasks.import_job_run(import_job_id=job.pk) job.refresh_from_db() assert job.track_file is None # audio file is deleted from import job once persisted to audio file assert not job.audio_file - assert job.status == 'skipped' + assert job.status == "skipped" def test_import_job_can_be_errored(factories, mocker, preferences): - path = os.path.join(DATA_DIR, 'test.ogg') - mbid = '9968a9d6-8d92-4051-8f76-674e157b6eed' - track_file = factories['music.TrackFile'](track__mbid=mbid) + path = os.path.join(DATA_DIR, "test.ogg") + mbid = "9968a9d6-8d92-4051-8f76-674e157b6eed" + factories["music.TrackFile"](track__mbid=mbid) class MyException(Exception): pass - mocker.patch( - 'funkwhale_api.music.tasks._do_import', - side_effect=MyException()) + mocker.patch("funkwhale_api.music.tasks._do_import", side_effect=MyException()) - job = factories['music.FileImportJob']( - audio_file__path=path, track_file=None) + job = factories["music.FileImportJob"](audio_file__path=path, track_file=None) with pytest.raises(MyException): tasks.import_job_run(import_job_id=job.pk) @@ -183,23 +185,22 @@ def test_import_job_can_be_errored(factories, mocker, preferences): job.refresh_from_db() assert job.track_file is None - assert job.status == 'errored' + assert job.status == "errored" def test__do_import_calls_update_album_cover_if_no_cover(factories, mocker): - path = os.path.join(DATA_DIR, 'test.ogg') - album = factories['music.Album'](cover='') - track = factories['music.Track'](album=album) + path = os.path.join(DATA_DIR, "test.ogg") + album = factories["music.Album"](cover="") + track = factories["music.Track"](album=album) mocker.patch( - 'funkwhale_api.providers.audiofile.tasks.import_track_data_from_path', - return_value=track) + "funkwhale_api.providers.audiofile.tasks.import_track_data_from_path", + return_value=track, + ) - mocked_update = mocker.patch( - 'funkwhale_api.music.tasks.update_album_cover') + mocked_update = mocker.patch("funkwhale_api.music.tasks.update_album_cover") - job = factories['music.FileImportJob']( - audio_file__path=path, track_file=None) + job = factories["music.FileImportJob"](audio_file__path=path, track_file=None) tasks.import_job_run(import_job_id=job.pk) @@ -207,50 +208,41 @@ def test__do_import_calls_update_album_cover_if_no_cover(factories, mocker): def test_update_album_cover_mbid(factories, mocker): - album = factories['music.Album'](cover='') + album = factories["music.Album"](cover="") - mocked_get = mocker.patch('funkwhale_api.music.models.Album.get_image') + mocked_get = mocker.patch("funkwhale_api.music.models.Album.get_image") tasks.update_album_cover(album=album, track_file=None) mocked_get.assert_called_once_with() def test_update_album_cover_file_data(factories, mocker): - path = os.path.join(DATA_DIR, 'test.mp3') - album = factories['music.Album'](cover='', mbid=None) - tf = factories['music.TrackFile'](track__album=album) + album = factories["music.Album"](cover="", mbid=None) + tf = factories["music.TrackFile"](track__album=album) - mocked_get = mocker.patch('funkwhale_api.music.models.Album.get_image') + mocked_get = mocker.patch("funkwhale_api.music.models.Album.get_image") mocker.patch( - 'funkwhale_api.music.metadata.Metadata.get_picture', - return_value={'hello': 'world'}) + "funkwhale_api.music.metadata.Metadata.get_picture", + return_value={"hello": "world"}, + ) tasks.update_album_cover(album=album, track_file=tf) - md = data = tf.get_metadata() - mocked_get.assert_called_once_with( - data={'hello': 'world'}) + tf.get_metadata() + mocked_get.assert_called_once_with(data={"hello": "world"}) -@pytest.mark.parametrize('ext,mimetype', [ - ('jpg', 'image/jpeg'), - ('png', 'image/png'), -]) -def test_update_album_cover_file_cover_separate_file( - ext, mimetype, factories, mocker): - mocker.patch('funkwhale_api.music.tasks.IMAGE_TYPES', [(ext, mimetype)]) - path = os.path.join(DATA_DIR, 'test.mp3') - image_path = os.path.join(DATA_DIR, 'cover.{}'.format(ext)) - with open(image_path, 'rb') as f: +@pytest.mark.parametrize("ext,mimetype", [("jpg", "image/jpeg"), ("png", "image/png")]) +def test_update_album_cover_file_cover_separate_file(ext, mimetype, factories, mocker): + mocker.patch("funkwhale_api.music.tasks.IMAGE_TYPES", [(ext, mimetype)]) + image_path = os.path.join(DATA_DIR, "cover.{}".format(ext)) + with open(image_path, "rb") as f: image_content = f.read() - album = factories['music.Album'](cover='', mbid=None) - tf = factories['music.TrackFile']( - track__album=album, - source='file://' + image_path) + album = factories["music.Album"](cover="", mbid=None) + tf = factories["music.TrackFile"](track__album=album, source="file://" + image_path) - mocked_get = mocker.patch('funkwhale_api.music.models.Album.get_image') - mocker.patch( - 'funkwhale_api.music.metadata.Metadata.get_picture', - return_value=None) + mocked_get = mocker.patch("funkwhale_api.music.models.Album.get_image") + mocker.patch("funkwhale_api.music.metadata.Metadata.get_picture", return_value=None) tasks.update_album_cover(album=album, track_file=tf) - md = data = tf.get_metadata() + tf.get_metadata() mocked_get.assert_called_once_with( - data={'mimetype': mimetype, 'content': image_content}) + data={"mimetype": mimetype, "content": image_content} + ) diff --git a/api/tests/music/test_utils.py b/api/tests/music/test_utils.py index 7b967dbbc..4019e47b4 100644 --- a/api/tests/music/test_utils.py +++ b/api/tests/music/test_utils.py @@ -1,4 +1,5 @@ import os + import pytest from funkwhale_api.music import utils @@ -7,35 +8,31 @@ DATA_DIR = os.path.dirname(os.path.abspath(__file__)) def test_guess_mimetype_try_using_extension(factories, mocker): - mocker.patch( - 'magic.from_buffer', return_value='audio/mpeg') - f = factories['music.TrackFile'].build( - audio_file__filename='test.ogg') + mocker.patch("magic.from_buffer", return_value="audio/mpeg") + f = factories["music.TrackFile"].build(audio_file__filename="test.ogg") - assert utils.guess_mimetype(f.audio_file) == 'audio/mpeg' + assert utils.guess_mimetype(f.audio_file) == "audio/mpeg" -@pytest.mark.parametrize('wrong', [ - 'application/octet-stream', - 'application/x-empty', -]) +@pytest.mark.parametrize("wrong", ["application/octet-stream", "application/x-empty"]) def test_guess_mimetype_try_using_extension_if_fail(wrong, factories, mocker): - mocker.patch( - 'magic.from_buffer', return_value=wrong) - f = factories['music.TrackFile'].build( - audio_file__filename='test.mp3') + mocker.patch("magic.from_buffer", return_value=wrong) + f = factories["music.TrackFile"].build(audio_file__filename="test.mp3") - assert utils.guess_mimetype(f.audio_file) == 'audio/mpeg' + assert utils.guess_mimetype(f.audio_file) == "audio/mpeg" -@pytest.mark.parametrize('name, expected', [ - ('sample.flac', {'bitrate': 1608000, 'length': 0.001}), - ('test.mp3', {'bitrate': 8000, 'length': 267.70285714285717}), - ('test.ogg', {'bitrate': 128000, 'length': 229.18304166666667}), -]) +@pytest.mark.parametrize( + "name, expected", + [ + ("sample.flac", {"bitrate": 1608000, "length": 0.001}), + ("test.mp3", {"bitrate": 8000, "length": 267.70285714285717}), + ("test.ogg", {"bitrate": 128000, "length": 229.18304166666667}), + ], +) def test_get_audio_file_data(name, expected): path = os.path.join(DATA_DIR, name) - with open(path, 'rb') as f: + with open(path, "rb") as f: result = utils.get_audio_file_data(f) assert result == expected diff --git a/api/tests/music/test_views.py b/api/tests/music/test_views.py index 91fef13f2..aa04521cb 100644 --- a/api/tests/music/test_views.py +++ b/api/tests/music/test_views.py @@ -1,36 +1,34 @@ import io -import pytest +import pytest from django.urls import reverse from django.utils import timezone -from funkwhale_api.music import serializers -from funkwhale_api.music import views from funkwhale_api.federation import actors +from funkwhale_api.music import serializers, views -@pytest.mark.parametrize('view,permissions,operator', [ - (views.ImportBatchViewSet, ['library', 'upload'], 'or'), - (views.ImportJobViewSet, ['library', 'upload'], 'or'), -]) +@pytest.mark.parametrize( + "view,permissions,operator", + [ + (views.ImportBatchViewSet, ["library", "upload"], "or"), + (views.ImportJobViewSet, ["library", "upload"], "or"), + ], +) def test_permissions(assert_user_permission, view, permissions, operator): assert_user_permission(view, permissions, operator) def test_artist_list_serializer(api_request, factories, logged_in_api_client): - track = factories['music.Track']() + track = factories["music.Track"]() artist = track.artist - request = api_request.get('/') + request = api_request.get("/") qs = artist.__class__.objects.with_albums() serializer = serializers.ArtistWithAlbumsSerializer( - qs, many=True, context={'request': request}) - expected = { - 'count': 1, - 'next': None, - 'previous': None, - 'results': serializer.data - } - url = reverse('api:v1:artists-list') + qs, many=True, context={"request": request} + ) + expected = {"count": 1, "next": None, "previous": None, "results": serializer.data} + url = reverse("api:v1:artists-list") response = logged_in_api_client.get(url) assert response.status_code == 200 @@ -38,19 +36,15 @@ def test_artist_list_serializer(api_request, factories, logged_in_api_client): def test_album_list_serializer(api_request, factories, logged_in_api_client): - track = factories['music.Track']() + track = factories["music.Track"]() album = track.album - request = api_request.get('/') + request = api_request.get("/") qs = album.__class__.objects.all() serializer = serializers.AlbumSerializer( - qs, many=True, context={'request': request}) - expected = { - 'count': 1, - 'next': None, - 'previous': None, - 'results': serializer.data - } - url = reverse('api:v1:albums-list') + qs, many=True, context={"request": request} + ) + expected = {"count": 1, "next": None, "previous": None, "results": serializer.data} + url = reverse("api:v1:albums-list") response = logged_in_api_client.get(url) assert response.status_code == 200 @@ -58,38 +52,30 @@ def test_album_list_serializer(api_request, factories, logged_in_api_client): def test_track_list_serializer(api_request, factories, logged_in_api_client): - track = factories['music.Track']() - request = api_request.get('/') + track = factories["music.Track"]() + request = api_request.get("/") qs = track.__class__.objects.all() serializer = serializers.TrackSerializer( - qs, many=True, context={'request': request}) - expected = { - 'count': 1, - 'next': None, - 'previous': None, - 'results': serializer.data - } - url = reverse('api:v1:tracks-list') + qs, many=True, context={"request": request} + ) + expected = {"count": 1, "next": None, "previous": None, "results": serializer.data} + url = reverse("api:v1:tracks-list") response = logged_in_api_client.get(url) assert response.status_code == 200 assert response.data == expected -@pytest.mark.parametrize('param,expected', [ - ('true', 'full'), - ('false', 'empty'), -]) -def test_artist_view_filter_listenable( - param, expected, factories, api_request): +@pytest.mark.parametrize("param,expected", [("true", "full"), ("false", "empty")]) +def test_artist_view_filter_listenable(param, expected, factories, api_request): artists = { - 'empty': factories['music.Artist'](), - 'full': factories['music.TrackFile']().track.artist, + "empty": factories["music.Artist"](), + "full": factories["music.TrackFile"]().track.artist, } - request = api_request.get('/', {'listenable': param}) + request = api_request.get("/", {"listenable": param}) view = views.ArtistViewSet() - view.action_map = {'get': 'list'} + view.action_map = {"get": "list"} expected = [artists[expected]] view.request = view.initialize_request(request) queryset = view.filter_queryset(view.get_queryset()) @@ -97,20 +83,16 @@ def test_artist_view_filter_listenable( assert list(queryset) == expected -@pytest.mark.parametrize('param,expected', [ - ('true', 'full'), - ('false', 'empty'), -]) -def test_album_view_filter_listenable( - param, expected, factories, api_request): +@pytest.mark.parametrize("param,expected", [("true", "full"), ("false", "empty")]) +def test_album_view_filter_listenable(param, expected, factories, api_request): artists = { - 'empty': factories['music.Album'](), - 'full': factories['music.TrackFile']().track.album, + "empty": factories["music.Album"](), + "full": factories["music.TrackFile"]().track.album, } - request = api_request.get('/', {'listenable': param}) + request = api_request.get("/", {"listenable": param}) view = views.AlbumViewSet() - view.action_map = {'get': 'list'} + view.action_map = {"get": "list"} expected = [artists[expected]] view.request = view.initialize_request(request) queryset = view.filter_queryset(view.get_queryset()) @@ -119,58 +101,53 @@ def test_album_view_filter_listenable( def test_can_serve_track_file_as_remote_library( - factories, authenticated_actor, api_client, settings, preferences): - preferences['common__api_authentication_required'] = True - library_actor = actors.SYSTEM_ACTORS['library'].get_actor_instance() - follow = factories['federation.Follow']( - approved=True, - actor=authenticated_actor, - target=library_actor) + factories, authenticated_actor, api_client, settings, preferences +): + preferences["common__api_authentication_required"] = True + library_actor = actors.SYSTEM_ACTORS["library"].get_actor_instance() + factories["federation.Follow"]( + approved=True, actor=authenticated_actor, target=library_actor + ) - track_file = factories['music.TrackFile']() + track_file = factories["music.TrackFile"]() response = api_client.get(track_file.path) assert response.status_code == 200 - assert response['X-Accel-Redirect'] == "{}{}".format( - settings.PROTECT_FILES_PATH, - track_file.audio_file.url) + assert response["X-Accel-Redirect"] == "{}{}".format( + settings.PROTECT_FILES_PATH, track_file.audio_file.url + ) def test_can_serve_track_file_as_remote_library_deny_not_following( - factories, authenticated_actor, settings, api_client, preferences): - preferences['common__api_authentication_required'] = True - track_file = factories['music.TrackFile']() + factories, authenticated_actor, settings, api_client, preferences +): + preferences["common__api_authentication_required"] = True + track_file = factories["music.TrackFile"]() response = api_client.get(track_file.path) assert response.status_code == 403 -@pytest.mark.parametrize('proxy,serve_path,expected', [ - ('apache2', '/host/music', '/host/music/hello/world.mp3'), - ('apache2', '/app/music', '/app/music/hello/world.mp3'), - ('nginx', '/host/music', '/_protected/music/hello/world.mp3'), - ('nginx', '/app/music', '/_protected/music/hello/world.mp3'), -]) +@pytest.mark.parametrize( + "proxy,serve_path,expected", + [ + ("apache2", "/host/music", "/host/music/hello/world.mp3"), + ("apache2", "/app/music", "/app/music/hello/world.mp3"), + ("nginx", "/host/music", "/_protected/music/hello/world.mp3"), + ("nginx", "/app/music", "/_protected/music/hello/world.mp3"), + ], +) def test_serve_file_in_place( - proxy, - serve_path, - expected, - factories, - api_client, - preferences, - settings): - headers = { - 'apache2': 'X-Sendfile', - 'nginx': 'X-Accel-Redirect', - } - preferences['common__api_authentication_required'] = False - settings.PROTECT_FILE_PATH = '/_protected/music' + proxy, serve_path, expected, factories, api_client, preferences, settings +): + headers = {"apache2": "X-Sendfile", "nginx": "X-Accel-Redirect"} + preferences["common__api_authentication_required"] = False + settings.PROTECT_FILE_PATH = "/_protected/music" settings.REVERSE_PROXY_TYPE = proxy - settings.MUSIC_DIRECTORY_PATH = '/app/music' + settings.MUSIC_DIRECTORY_PATH = "/app/music" settings.MUSIC_DIRECTORY_SERVE_PATH = serve_path - tf = factories['music.TrackFile']( - in_place=True, - source='file:///app/music/hello/world.mp3' + tf = factories["music.TrackFile"]( + in_place=True, source="file:///app/music/hello/world.mp3" ) response = api_client.get(tf.path) @@ -178,86 +155,76 @@ def test_serve_file_in_place( assert response[headers[proxy]] == expected -@pytest.mark.parametrize('proxy,serve_path,expected', [ - ('apache2', '/host/music', '/host/music/hello/worldéà.mp3'), - ('apache2', '/app/music', '/app/music/hello/worldéà.mp3'), - ('nginx', '/host/music', '/_protected/music/hello/worldéà.mp3'), - ('nginx', '/app/music', '/_protected/music/hello/worldéà.mp3'), -]) +@pytest.mark.parametrize( + "proxy,serve_path,expected", + [ + ("apache2", "/host/music", "/host/music/hello/worldéà.mp3"), + ("apache2", "/app/music", "/app/music/hello/worldéà.mp3"), + ("nginx", "/host/music", "/_protected/music/hello/worldéà.mp3"), + ("nginx", "/app/music", "/_protected/music/hello/worldéà.mp3"), + ], +) def test_serve_file_in_place_utf8( - proxy, - serve_path, - expected, - factories, - api_client, - settings, - preferences): - preferences['common__api_authentication_required'] = False - settings.PROTECT_FILE_PATH = '/_protected/music' + proxy, serve_path, expected, factories, api_client, settings, preferences +): + preferences["common__api_authentication_required"] = False + settings.PROTECT_FILE_PATH = "/_protected/music" settings.REVERSE_PROXY_TYPE = proxy - settings.MUSIC_DIRECTORY_PATH = '/app/music' + settings.MUSIC_DIRECTORY_PATH = "/app/music" settings.MUSIC_DIRECTORY_SERVE_PATH = serve_path - path = views.get_file_path('/app/music/hello/worldéà.mp3') + path = views.get_file_path("/app/music/hello/worldéà.mp3") - assert path == expected.encode('utf-8') + assert path == expected.encode("utf-8") -@pytest.mark.parametrize('proxy,serve_path,expected', [ - ('apache2', '/host/music', '/host/media/tracks/hello/world.mp3'), - # apache with container not supported yet - # ('apache2', '/app/music', '/app/music/tracks/hello/world.mp3'), - ('nginx', '/host/music', '/_protected/media/tracks/hello/world.mp3'), - ('nginx', '/app/music', '/_protected/media/tracks/hello/world.mp3'), -]) +@pytest.mark.parametrize( + "proxy,serve_path,expected", + [ + ("apache2", "/host/music", "/host/media/tracks/hello/world.mp3"), + # apache with container not supported yet + # ('apache2', '/app/music', '/app/music/tracks/hello/world.mp3'), + ("nginx", "/host/music", "/_protected/media/tracks/hello/world.mp3"), + ("nginx", "/app/music", "/_protected/media/tracks/hello/world.mp3"), + ], +) def test_serve_file_media( - proxy, - serve_path, - expected, - factories, - api_client, - settings, - preferences): - headers = { - 'apache2': 'X-Sendfile', - 'nginx': 'X-Accel-Redirect', - } - preferences['common__api_authentication_required'] = False - settings.MEDIA_ROOT = '/host/media' - settings.PROTECT_FILE_PATH = '/_protected/music' + proxy, serve_path, expected, factories, api_client, settings, preferences +): + headers = {"apache2": "X-Sendfile", "nginx": "X-Accel-Redirect"} + preferences["common__api_authentication_required"] = False + settings.MEDIA_ROOT = "/host/media" + settings.PROTECT_FILE_PATH = "/_protected/music" settings.REVERSE_PROXY_TYPE = proxy - settings.MUSIC_DIRECTORY_PATH = '/app/music' + settings.MUSIC_DIRECTORY_PATH = "/app/music" settings.MUSIC_DIRECTORY_SERVE_PATH = serve_path - tf = factories['music.TrackFile']() - tf.__class__.objects.filter(pk=tf.pk).update( - audio_file='tracks/hello/world.mp3') + tf = factories["music.TrackFile"]() + tf.__class__.objects.filter(pk=tf.pk).update(audio_file="tracks/hello/world.mp3") response = api_client.get(tf.path) assert response.status_code == 200 assert response[headers[proxy]] == expected -def test_can_proxy_remote_track( - factories, settings, api_client, r_mock, preferences): - preferences['common__api_authentication_required'] = False - track_file = factories['music.TrackFile'](federation=True) +def test_can_proxy_remote_track(factories, settings, api_client, r_mock, preferences): + preferences["common__api_authentication_required"] = False + track_file = factories["music.TrackFile"](federation=True) - r_mock.get(track_file.library_track.audio_url, body=io.BytesIO(b'test')) + r_mock.get(track_file.library_track.audio_url, body=io.BytesIO(b"test")) response = api_client.get(track_file.path) library_track = track_file.library_track library_track.refresh_from_db() assert response.status_code == 200 - assert response['X-Accel-Redirect'] == "{}{}".format( - settings.PROTECT_FILES_PATH, - library_track.audio_file.url) - assert library_track.audio_file.read() == b'test' + assert response["X-Accel-Redirect"] == "{}{}".format( + settings.PROTECT_FILES_PATH, library_track.audio_file.url + ) + assert library_track.audio_file.read() == b"test" -def test_serve_updates_access_date( - factories, settings, api_client, preferences): - preferences['common__api_authentication_required'] = False - track_file = factories['music.TrackFile']() +def test_serve_updates_access_date(factories, settings, api_client, preferences): + preferences["common__api_authentication_required"] = False + track_file = factories["music.TrackFile"]() now = timezone.now() assert track_file.accessed_date is None @@ -269,128 +236,118 @@ def test_serve_updates_access_date( def test_can_list_import_jobs(factories, superuser_api_client): - job = factories['music.ImportJob']() - url = reverse('api:v1:import-jobs-list') + job = factories["music.ImportJob"]() + url = reverse("api:v1:import-jobs-list") response = superuser_api_client.get(url) assert response.status_code == 200 - assert response.data['results'][0]['id'] == job.pk + assert response.data["results"][0]["id"] == job.pk def test_import_job_stats(factories, superuser_api_client): - job1 = factories['music.ImportJob'](status='pending') - job2 = factories['music.ImportJob'](status='errored') + factories["music.ImportJob"](status="pending") + factories["music.ImportJob"](status="errored") - url = reverse('api:v1:import-jobs-stats') + url = reverse("api:v1:import-jobs-stats") response = superuser_api_client.get(url) - expected = { - 'errored': 1, - 'pending': 1, - 'finished': 0, - 'skipped': 0, - 'count': 2, - } + expected = {"errored": 1, "pending": 1, "finished": 0, "skipped": 0, "count": 2} assert response.status_code == 200 assert response.data == expected def test_import_job_stats_filter(factories, superuser_api_client): - job1 = factories['music.ImportJob'](status='pending') - job2 = factories['music.ImportJob'](status='errored') + job1 = factories["music.ImportJob"](status="pending") + factories["music.ImportJob"](status="errored") - url = reverse('api:v1:import-jobs-stats') - response = superuser_api_client.get(url, {'batch': job1.batch.pk}) - expected = { - 'errored': 0, - 'pending': 1, - 'finished': 0, - 'skipped': 0, - 'count': 1, - } + url = reverse("api:v1:import-jobs-stats") + response = superuser_api_client.get(url, {"batch": job1.batch.pk}) + expected = {"errored": 0, "pending": 1, "finished": 0, "skipped": 0, "count": 1} assert response.status_code == 200 assert response.data == expected def test_import_job_run_via_api(factories, superuser_api_client, mocker): - run = mocker.patch('funkwhale_api.music.tasks.import_job_run.delay') - job1 = factories['music.ImportJob'](status='errored') - job2 = factories['music.ImportJob'](status='pending') + run = mocker.patch("funkwhale_api.music.tasks.import_job_run.delay") + job1 = factories["music.ImportJob"](status="errored") + job2 = factories["music.ImportJob"](status="pending") - url = reverse('api:v1:import-jobs-run') - response = superuser_api_client.post(url, {'jobs': [job2.pk, job1.pk]}) + url = reverse("api:v1:import-jobs-run") + response = superuser_api_client.post(url, {"jobs": [job2.pk, job1.pk]}) job1.refresh_from_db() job2.refresh_from_db() assert response.status_code == 200 - assert response.data == {'jobs': [job1.pk, job2.pk]} - assert job1.status == 'pending' - assert job2.status == 'pending' + assert response.data == {"jobs": [job1.pk, job2.pk]} + assert job1.status == "pending" + assert job2.status == "pending" run.assert_any_call(import_job_id=job1.pk) run.assert_any_call(import_job_id=job2.pk) def test_import_batch_run_via_api(factories, superuser_api_client, mocker): - run = mocker.patch('funkwhale_api.music.tasks.import_job_run.delay') + run = mocker.patch("funkwhale_api.music.tasks.import_job_run.delay") - batch = factories['music.ImportBatch']() - job1 = factories['music.ImportJob'](batch=batch, status='errored') - job2 = factories['music.ImportJob'](batch=batch, status='pending') + batch = factories["music.ImportBatch"]() + job1 = factories["music.ImportJob"](batch=batch, status="errored") + job2 = factories["music.ImportJob"](batch=batch, status="pending") - url = reverse('api:v1:import-jobs-run') - response = superuser_api_client.post(url, {'batches': [batch.pk]}) + url = reverse("api:v1:import-jobs-run") + response = superuser_api_client.post(url, {"batches": [batch.pk]}) job1.refresh_from_db() job2.refresh_from_db() assert response.status_code == 200 - assert job1.status == 'pending' - assert job2.status == 'pending' + assert job1.status == "pending" + assert job2.status == "pending" run.assert_any_call(import_job_id=job1.pk) run.assert_any_call(import_job_id=job2.pk) -def test_import_batch_and_job_run_via_api( - factories, superuser_api_client, mocker): - run = mocker.patch('funkwhale_api.music.tasks.import_job_run.delay') +def test_import_batch_and_job_run_via_api(factories, superuser_api_client, mocker): + run = mocker.patch("funkwhale_api.music.tasks.import_job_run.delay") - batch = factories['music.ImportBatch']() - job1 = factories['music.ImportJob'](batch=batch, status='errored') - job2 = factories['music.ImportJob'](status='pending') + batch = factories["music.ImportBatch"]() + job1 = factories["music.ImportJob"](batch=batch, status="errored") + job2 = factories["music.ImportJob"](status="pending") - url = reverse('api:v1:import-jobs-run') + url = reverse("api:v1:import-jobs-run") response = superuser_api_client.post( - url, {'batches': [batch.pk], 'jobs': [job2.pk]}) + url, {"batches": [batch.pk], "jobs": [job2.pk]} + ) job1.refresh_from_db() job2.refresh_from_db() assert response.status_code == 200 - assert job1.status == 'pending' - assert job2.status == 'pending' + assert job1.status == "pending" + assert job2.status == "pending" run.assert_any_call(import_job_id=job1.pk) run.assert_any_call(import_job_id=job2.pk) def test_import_job_viewset_get_queryset_upload_filters_user( - factories, logged_in_api_client): + factories, logged_in_api_client +): logged_in_api_client.user.permission_upload = True logged_in_api_client.user.save() - job = factories['music.ImportJob']() - url = reverse('api:v1:import-jobs-list') + factories["music.ImportJob"]() + url = reverse("api:v1:import-jobs-list") response = logged_in_api_client.get(url) - assert response.data['count'] == 0 + assert response.data["count"] == 0 def test_import_batch_viewset_get_queryset_upload_filters_user( - factories, logged_in_api_client): + factories, logged_in_api_client +): logged_in_api_client.user.permission_upload = True logged_in_api_client.user.save() - job = factories['music.ImportBatch']() - url = reverse('api:v1:import-batches-list') + factories["music.ImportBatch"]() + url = reverse("api:v1:import-batches-list") response = logged_in_api_client.get(url) - assert response.data['count'] == 0 + assert response.data["count"] == 0 diff --git a/api/tests/music/test_works.py b/api/tests/music/test_works.py index 13f6447be..96b537ca2 100644 --- a/api/tests/music/test_works.py +++ b/api/tests/music/test_works.py @@ -1,23 +1,18 @@ -import json -from django.urls import reverse - from funkwhale_api.music import models -from funkwhale_api.musicbrainz import api -from funkwhale_api.music import serializers def test_can_import_work(factories, mocker, works): mocker.patch( - 'funkwhale_api.musicbrainz.api.works.get', - return_value=works['get']['chop_suey']) - recording = factories['music.Track']( - mbid='07ca77cf-f513-4e9c-b190-d7e24bbad448') - mbid = 'e2ecabc4-1b9d-30b2-8f30-3596ec423dc5' + "funkwhale_api.musicbrainz.api.works.get", + return_value=works["get"]["chop_suey"], + ) + recording = factories["music.Track"](mbid="07ca77cf-f513-4e9c-b190-d7e24bbad448") + mbid = "e2ecabc4-1b9d-30b2-8f30-3596ec423dc5" work = models.Work.create_from_api(id=mbid) - assert work.title == 'Chop Suey!' - assert work.nature == 'song' - assert work.language == 'eng' + assert work.title == "Chop Suey!" + assert work.nature == "song" + assert work.language == "eng" assert work.mbid == mbid # a imported work should also be linked to corresponding recordings @@ -28,23 +23,25 @@ def test_can_import_work(factories, mocker, works): def test_can_get_work_from_recording(factories, mocker, works, tracks): mocker.patch( - 'funkwhale_api.musicbrainz.api.works.get', - return_value=works['get']['chop_suey']) + "funkwhale_api.musicbrainz.api.works.get", + return_value=works["get"]["chop_suey"], + ) mocker.patch( - 'funkwhale_api.musicbrainz.api.recordings.get', - return_value=tracks['get']['chop_suey']) - recording = factories['music.Track']( - work=None, - mbid='07ca77cf-f513-4e9c-b190-d7e24bbad448') - mbid = 'e2ecabc4-1b9d-30b2-8f30-3596ec423dc5' + "funkwhale_api.musicbrainz.api.recordings.get", + return_value=tracks["get"]["chop_suey"], + ) + recording = factories["music.Track"]( + work=None, mbid="07ca77cf-f513-4e9c-b190-d7e24bbad448" + ) + mbid = "e2ecabc4-1b9d-30b2-8f30-3596ec423dc5" - assert recording.work == None + assert recording.work is None work = recording.get_work() - assert work.title == 'Chop Suey!' - assert work.nature == 'song' - assert work.language == 'eng' + assert work.title == "Chop Suey!" + assert work.nature == "song" + assert work.language == "eng" assert work.mbid == mbid recording.refresh_from_db() @@ -53,11 +50,12 @@ def test_can_get_work_from_recording(factories, mocker, works, tracks): def test_works_import_lyrics_if_any(db, mocker, works): mocker.patch( - 'funkwhale_api.musicbrainz.api.works.get', - return_value=works['get']['chop_suey']) - mbid = 'e2ecabc4-1b9d-30b2-8f30-3596ec423dc5' + "funkwhale_api.musicbrainz.api.works.get", + return_value=works["get"]["chop_suey"], + ) + mbid = "e2ecabc4-1b9d-30b2-8f30-3596ec423dc5" work = models.Work.create_from_api(id=mbid) - lyrics = models.Lyrics.objects.latest('id') + lyrics = models.Lyrics.objects.latest("id") assert lyrics.work == work - assert lyrics.url == 'http://lyrics.wikia.com/System_Of_A_Down:Chop_Suey!' + assert lyrics.url == "http://lyrics.wikia.com/System_Of_A_Down:Chop_Suey!" diff --git a/api/tests/musicbrainz/conftest.py b/api/tests/musicbrainz/conftest.py index 505d6e553..3e3ebfa48 100644 --- a/api/tests/musicbrainz/conftest.py +++ b/api/tests/musicbrainz/conftest.py @@ -1,33 +1,28 @@ import pytest -_artists = {'search': {}, 'get': {}} -_artists['search']['lost fingers'] = { - 'artist-count': 696, - 'artist-list': [ +_artists = {"search": {}, "get": {}} +_artists["search"]["lost fingers"] = { + "artist-count": 696, + "artist-list": [ { - 'country': 'CA', - 'sort-name': 'Lost Fingers, The', - 'id': 'ac16bbc0-aded-4477-a3c3-1d81693d58c9', - 'type': 'Group', - 'life-span': { - 'ended': 'false', - 'begin': '2008' + "country": "CA", + "sort-name": "Lost Fingers, The", + "id": "ac16bbc0-aded-4477-a3c3-1d81693d58c9", + "type": "Group", + "life-span": {"ended": "false", "begin": "2008"}, + "area": { + "sort-name": "Canada", + "id": "71bbafaa-e825-3e15-8ca9-017dcad1748b", + "name": "Canada", }, - 'area': { - 'sort-name': 'Canada', - 'id': '71bbafaa-e825-3e15-8ca9-017dcad1748b', - 'name': 'Canada' - }, - 'ext:score': '100', - 'name': 'The Lost Fingers' - }, - ] + "ext:score": "100", + "name": "The Lost Fingers", + } + ], } -_artists['get']['lost fingers'] = { +_artists["get"]["lost fingers"] = { "artist": { - "life-span": { - "begin": "2008" - }, + "life-span": {"begin": "2008"}, "type": "Group", "id": "ac16bbc0-aded-4477-a3c3-1d81693d58c9", "release-group-count": 8, @@ -38,137 +33,135 @@ _artists['get']['lost fingers'] = { "first-release-date": "2010", "type": "Album", "id": "03d3f1d4-e2b0-40d3-8314-05f1896e93a0", - "primary-type": "Album" + "primary-type": "Album", }, { "title": "Gitan Kameleon", "first-release-date": "2011-11-11", "type": "Album", "id": "243c0cd2-2492-4f5d-bf37-c7c76bed05b7", - "primary-type": "Album" + "primary-type": "Album", }, { "title": "Pump Up the Jam \u2013 Do Not Cover, Pt. 3", "first-release-date": "2014-03-17", "type": "Single", "id": "4429befd-ff45-48eb-a8f4-cdf7bf007f3f", - "primary-type": "Single" + "primary-type": "Single", }, { "title": "La Marquise", "first-release-date": "2012-03-27", "type": "Album", "id": "4dab4b96-0a6b-4507-a31e-2189e3e7bad1", - "primary-type": "Album" + "primary-type": "Album", }, { "title": "Christmas Caravan", "first-release-date": "2016-11-11", "type": "Album", "id": "ca0a506d-6ba9-47c3-a712-de5ce9ae6b1f", - "primary-type": "Album" + "primary-type": "Album", }, { "title": "Rendez-vous rose", "first-release-date": "2009-06-16", "type": "Album", "id": "d002f1a8-5890-4188-be58-1caadbbd767f", - "primary-type": "Album" + "primary-type": "Album", }, { "title": "Wonders of the World", "first-release-date": "2014-05-06", "type": "Album", "id": "eeb644c2-5000-42fb-b959-e5e9cc2901c5", - "primary-type": "Album" + "primary-type": "Album", }, { "title": "Lost in the 80s", "first-release-date": "2008-05-06", "type": "Album", "id": "f04ed607-11b7-3843-957e-503ecdd485d1", - "primary-type": "Album" - } + "primary-type": "Album", + }, ], "area": { - "iso-3166-1-code-list": [ - "CA" - ], + "iso-3166-1-code-list": ["CA"], "name": "Canada", "id": "71bbafaa-e825-3e15-8ca9-017dcad1748b", - "sort-name": "Canada" + "sort-name": "Canada", }, "sort-name": "Lost Fingers, The", - "country": "CA" + "country": "CA", } } -_release_groups = {'browse': {}} -_release_groups['browse']["lost fingers"] = { +_release_groups = {"browse": {}} +_release_groups["browse"]["lost fingers"] = { "release-group-list": [ { "first-release-date": "2010", "type": "Album", "primary-type": "Album", "title": "Gypsy Kameleon", - "id": "03d3f1d4-e2b0-40d3-8314-05f1896e93a0" + "id": "03d3f1d4-e2b0-40d3-8314-05f1896e93a0", }, { "first-release-date": "2011-11-11", "type": "Album", "primary-type": "Album", "title": "Gitan Kameleon", - "id": "243c0cd2-2492-4f5d-bf37-c7c76bed05b7" + "id": "243c0cd2-2492-4f5d-bf37-c7c76bed05b7", }, { "first-release-date": "2014-03-17", "type": "Single", "primary-type": "Single", "title": "Pump Up the Jam \u2013 Do Not Cover, Pt. 3", - "id": "4429befd-ff45-48eb-a8f4-cdf7bf007f3f" + "id": "4429befd-ff45-48eb-a8f4-cdf7bf007f3f", }, { "first-release-date": "2012-03-27", "type": "Album", "primary-type": "Album", "title": "La Marquise", - "id": "4dab4b96-0a6b-4507-a31e-2189e3e7bad1" + "id": "4dab4b96-0a6b-4507-a31e-2189e3e7bad1", }, { "first-release-date": "2016-11-11", "type": "Album", "primary-type": "Album", "title": "Christmas Caravan", - "id": "ca0a506d-6ba9-47c3-a712-de5ce9ae6b1f" + "id": "ca0a506d-6ba9-47c3-a712-de5ce9ae6b1f", }, { "first-release-date": "2009-06-16", "type": "Album", "primary-type": "Album", "title": "Rendez-vous rose", - "id": "d002f1a8-5890-4188-be58-1caadbbd767f" + "id": "d002f1a8-5890-4188-be58-1caadbbd767f", }, { "first-release-date": "2014-05-06", "type": "Album", "primary-type": "Album", "title": "Wonders of the World", - "id": "eeb644c2-5000-42fb-b959-e5e9cc2901c5" + "id": "eeb644c2-5000-42fb-b959-e5e9cc2901c5", }, { "first-release-date": "2008-05-06", "type": "Album", "primary-type": "Album", "title": "Lost in the 80s", - "id": "f04ed607-11b7-3843-957e-503ecdd485d1" - } + "id": "f04ed607-11b7-3843-957e-503ecdd485d1", + }, ], - "release-group-count": 8 + "release-group-count": 8, } -_recordings = {'search': {}, 'get': {}} -_recordings['search']['brontide matador'] = { +_recordings = {"search": {}, "get": {}} +_recordings["search"]["brontide matador"] = { "recording-count": 1044, "recording-list": [ { @@ -184,9 +177,9 @@ _recordings['search']['brontide matador'] = { "name": "United Kingdom", "sort-name": "United Kingdom", "id": "8a754a16-0027-3a29-b6d7-2b40ea0481ed", - "iso-3166-1-code-list": ["GB"] + "iso-3166-1-code-list": ["GB"], }, - "date": "2011-05-30" + "date": "2011-05-30", } ], "country": "GB", @@ -196,7 +189,7 @@ _recordings['search']['brontide matador'] = { "release-group": { "type": "Album", "id": "113ab958-cfb8-4782-99af-639d4d9eae8d", - "primary-type": "Album" + "primary-type": "Album", }, "medium-list": [ { @@ -206,22 +199,24 @@ _recordings['search']['brontide matador'] = { "track_or_recording_length": "366280", "id": "fe506782-a5cb-3d89-9b3e-86287be05768", "length": "366280", - "title": "Matador", "number": "1" + "title": "Matador", + "number": "1", } ], "position": "1", - "track-count": 8 + "track-count": 8, } - ] - }, - ] + ], + } + ], } - ] + ], } -_releases = {'search': {}, 'get': {}, 'browse': {}} -_releases['search']['brontide matador'] = { - "release-count": 116, "release-list": [ +_releases = {"search": {}, "get": {}, "browse": {}} +_releases["search"]["brontide matador"] = { + "release-count": 116, + "release-list": [ { "ext:score": "100", "date": "2009-04-02", @@ -231,16 +226,16 @@ _releases['search']['brontide matador'] = { "name": "[Worldwide]", "sort-name": "[Worldwide]", "id": "525d4e18-3d00-31b9-a58b-a146a916de8f", - "iso-3166-1-code-list": ["XW"] + "iso-3166-1-code-list": ["XW"], }, - "date": "2009-04-02" + "date": "2009-04-02", } ], "label-info-list": [ { "label": { "name": "Holy Roar", - "id": "6e940f35-961d-4ac3-bc2a-569fc211c2e3" + "id": "6e940f35-961d-4ac3-bc2a-569fc211c2e3", } } ], @@ -251,7 +246,7 @@ _releases['search']['brontide matador'] = { "artist": { "name": "Brontide", "sort-name": "Brontide", - "id": "2179fbd2-3c88-4b94-a778-eb3daf1e81a1" + "id": "2179fbd2-3c88-4b94-a778-eb3daf1e81a1", } } ], @@ -265,7 +260,7 @@ _releases['search']['brontide matador'] = { "type": "EP", "secondary-type-list": ["Demo"], "id": "b9207129-2d03-4a68-8a53-3c46fe7d2810", - "primary-type": "EP" + "primary-type": "EP", }, "medium-list": [ { @@ -273,28 +268,22 @@ _releases['search']['brontide matador'] = { "format": "Digital Media", "disc-count": 0, "track-count": 3, - "track-list": [] + "track-list": [], } ], "medium-count": 1, - "text-representation": { - "script": "Latn", - "language": "eng" - } - }, - ] + "text-representation": {"script": "Latn", "language": "eng"}, + } + ], } -_releases['browse']['Lost in the 80s'] = { +_releases["browse"]["Lost in the 80s"] = { "release-count": 3, "release-list": [ { "quality": "normal", "status": "Official", - "text-representation": { - "script": "Latn", - "language": "eng" - }, + "text-representation": {"script": "Latn", "language": "eng"}, "title": "Lost in the 80s", "date": "2008-05-06", "release-event-count": 1, @@ -304,14 +293,12 @@ _releases['browse']['Lost in the 80s'] = { "release-event-list": [ { "area": { - "iso-3166-1-code-list": [ - "CA" - ], + "iso-3166-1-code-list": ["CA"], "id": "71bbafaa-e825-3e15-8ca9-017dcad1748b", "name": "Canada", - "sort-name": "Canada" + "sort-name": "Canada", }, - "date": "2008-05-06" + "date": "2008-05-06", } ], "country": "CA", @@ -319,7 +306,7 @@ _releases['browse']['Lost in the 80s'] = { "back": "false", "artwork": "false", "front": "false", - "count": "0" + "count": "0", }, "medium-list": [ { @@ -333,11 +320,11 @@ _releases['browse']['Lost in the 80s'] = { "recording": { "id": "2e0dbf37-65af-4408-8def-7b0b3cb8426b", "length": "228000", - "title": "Pump Up the Jam" + "title": "Pump Up the Jam", }, "track_or_recording_length": "228000", "position": "1", - "number": "1" + "number": "1", }, { "id": "01a8cf99-2170-3d3f-96ef-5e4ef7a015a4", @@ -345,11 +332,11 @@ _releases['browse']['Lost in the 80s'] = { "recording": { "id": "57017e2e-625d-4e7b-a445-47cdb0224dd2", "length": "231000", - "title": "You Give Love a Bad Name" + "title": "You Give Love a Bad Name", }, "track_or_recording_length": "231000", "position": "2", - "number": "2" + "number": "2", }, { "id": "375a7ce7-5a41-3fbf-9809-96d491401034", @@ -357,11 +344,11 @@ _releases['browse']['Lost in the 80s'] = { "recording": { "id": "a948672b-b42d-44a5-89b0-7e9ab6a7e11d", "length": "189000", - "title": "You Shook Me All Night Long" + "title": "You Shook Me All Night Long", }, "track_or_recording_length": "189000", "position": "3", - "number": "3" + "number": "3", }, { "id": "ed7d823e-76da-31be-82a8-770288e27d32", @@ -369,11 +356,11 @@ _releases['browse']['Lost in the 80s'] = { "recording": { "id": "6e097e31-f37b-4fae-8ad0-ada57f3091a7", "length": "253000", - "title": "Incognito" + "title": "Incognito", }, "track_or_recording_length": "253000", "position": "4", - "number": "4" + "number": "4", }, { "id": "76ac8c77-6a99-34d9-ae4d-be8f056d50e0", @@ -381,11 +368,11 @@ _releases['browse']['Lost in the 80s'] = { "recording": { "id": "faa922e6-e834-44ee-8125-79e640a690e3", "length": "221000", - "title": "Touch Me" + "title": "Touch Me", }, "track_or_recording_length": "221000", "position": "5", - "number": "5" + "number": "5", }, { "id": "d0a87409-2be6-3ab7-8526-4313e7134be1", @@ -393,11 +380,11 @@ _releases['browse']['Lost in the 80s'] = { "recording": { "id": "02da8148-60d8-4c79-ab31-8d90d233d711", "length": "228000", - "title": "Part-Time Lover" + "title": "Part-Time Lover", }, "track_or_recording_length": "228000", "position": "6", - "number": "6" + "number": "6", }, { "id": "02c5384b-5ca9-38e9-8b7c-c08dce608deb", @@ -405,11 +392,11 @@ _releases['browse']['Lost in the 80s'] = { "recording": { "id": "40085704-d6ab-44f6-a4d8-b27c9ca25b31", "length": "248000", - "title": "Fresh" + "title": "Fresh", }, "track_or_recording_length": "248000", "position": "7", - "number": "7" + "number": "7", }, { "id": "ab389542-53d5-346a-b168-1d915ecf0ef6", @@ -417,11 +404,11 @@ _releases['browse']['Lost in the 80s'] = { "recording": { "id": "77edd338-eeaf-4157-9e2a-5cc3bcee8abd", "length": "257000", - "title": "Billie Jean" + "title": "Billie Jean", }, "track_or_recording_length": "257000", "position": "8", - "number": "8" + "number": "8", }, { "id": "6d9e722b-7408-350e-bb7c-2de1e329ae84", @@ -429,11 +416,11 @@ _releases['browse']['Lost in the 80s'] = { "recording": { "id": "040aaffa-7206-40ff-9930-469413fe2420", "length": "293000", - "title": "Careless Whisper" + "title": "Careless Whisper", }, "track_or_recording_length": "293000", "position": "9", - "number": "9" + "number": "9", }, { "id": "63b4e67c-7536-3cd0-8c47-0310c1e40866", @@ -441,11 +428,11 @@ _releases['browse']['Lost in the 80s'] = { "recording": { "id": "054942f0-4c0f-4e92-a606-d590976b1cff", "length": "211000", - "title": "Tainted Love" + "title": "Tainted Love", }, "track_or_recording_length": "211000", "position": "10", - "number": "10" + "number": "10", }, { "id": "a07f4ca3-dbf0-3337-a247-afcd0509334a", @@ -453,11 +440,11 @@ _releases['browse']['Lost in the 80s'] = { "recording": { "id": "8023b5ad-649a-4c67-b7a2-e12358606f6e", "length": "245000", - "title": "Straight Up" + "title": "Straight Up", }, "track_or_recording_length": "245000", "position": "11", - "number": "11" + "number": "11", }, { "id": "73d47f16-b18d-36ff-b0bb-1fa1fd32ebf7", @@ -465,18 +452,18 @@ _releases['browse']['Lost in the 80s'] = { "recording": { "id": "95a8c8a1-fcb6-4cbb-a853-be86d816b357", "length": "322000", - "title": "Black Velvet" + "title": "Black Velvet", }, "track_or_recording_length": "322000", "position": "12", - "number": "12" - } - ] + "number": "12", + }, + ], } ], - "asin": "B0017M8YTO" - }, - ] + "asin": "B0017M8YTO", + } + ], } diff --git a/api/tests/musicbrainz/test_api.py b/api/tests/musicbrainz/test_api.py index fdd1dbdb0..0fdaf7ab6 100644 --- a/api/tests/musicbrainz/test_api.py +++ b/api/tests/musicbrainz/test_api.py @@ -1,92 +1,95 @@ -import json from django.urls import reverse -from funkwhale_api.musicbrainz import api - - def test_can_search_recording_in_musicbrainz_api( - recordings, db, mocker, logged_in_api_client): + recordings, db, mocker, logged_in_api_client +): mocker.patch( - 'funkwhale_api.musicbrainz.api.recordings.search', - return_value=recordings['search']['brontide matador']) - query = 'brontide matador' - url = reverse('api:v1:providers:musicbrainz:search-recordings') - expected = recordings['search']['brontide matador'] - response = logged_in_api_client.get(url, data={'query': query}) + "funkwhale_api.musicbrainz.api.recordings.search", + return_value=recordings["search"]["brontide matador"], + ) + query = "brontide matador" + url = reverse("api:v1:providers:musicbrainz:search-recordings") + expected = recordings["search"]["brontide matador"] + response = logged_in_api_client.get(url, data={"query": query}) assert expected == response.data -def test_can_search_release_in_musicbrainz_api(releases, db, mocker, logged_in_api_client): +def test_can_search_release_in_musicbrainz_api( + releases, db, mocker, logged_in_api_client +): mocker.patch( - 'funkwhale_api.musicbrainz.api.releases.search', - return_value=releases['search']['brontide matador']) - query = 'brontide matador' - url = reverse('api:v1:providers:musicbrainz:search-releases') - expected = releases['search']['brontide matador'] - response = logged_in_api_client.get(url, data={'query': query}) + "funkwhale_api.musicbrainz.api.releases.search", + return_value=releases["search"]["brontide matador"], + ) + query = "brontide matador" + url = reverse("api:v1:providers:musicbrainz:search-releases") + expected = releases["search"]["brontide matador"] + response = logged_in_api_client.get(url, data={"query": query}) assert expected == response.data -def test_can_search_artists_in_musicbrainz_api(artists, db, mocker, logged_in_api_client): +def test_can_search_artists_in_musicbrainz_api( + artists, db, mocker, logged_in_api_client +): mocker.patch( - 'funkwhale_api.musicbrainz.api.artists.search', - return_value=artists['search']['lost fingers']) - query = 'lost fingers' - url = reverse('api:v1:providers:musicbrainz:search-artists') - expected = artists['search']['lost fingers'] - response = logged_in_api_client.get(url, data={'query': query}) + "funkwhale_api.musicbrainz.api.artists.search", + return_value=artists["search"]["lost fingers"], + ) + query = "lost fingers" + url = reverse("api:v1:providers:musicbrainz:search-artists") + expected = artists["search"]["lost fingers"] + response = logged_in_api_client.get(url, data={"query": query}) assert expected == response.data def test_can_get_artist_in_musicbrainz_api(artists, db, mocker, logged_in_api_client): mocker.patch( - 'funkwhale_api.musicbrainz.api.artists.get', - return_value=artists['get']['lost fingers']) - uuid = 'ac16bbc0-aded-4477-a3c3-1d81693d58c9' - url = reverse('api:v1:providers:musicbrainz:artist-detail', kwargs={ - 'uuid': uuid, - }) + "funkwhale_api.musicbrainz.api.artists.get", + return_value=artists["get"]["lost fingers"], + ) + uuid = "ac16bbc0-aded-4477-a3c3-1d81693d58c9" + url = reverse("api:v1:providers:musicbrainz:artist-detail", kwargs={"uuid": uuid}) response = logged_in_api_client.get(url) - expected = artists['get']['lost fingers'] + expected = artists["get"]["lost fingers"] assert expected == response.data def test_can_broswe_release_group_using_musicbrainz_api( - release_groups, db, mocker, logged_in_api_client): + release_groups, db, mocker, logged_in_api_client +): mocker.patch( - 'funkwhale_api.musicbrainz.api.release_groups.browse', - return_value=release_groups['browse']['lost fingers']) - uuid = 'ac16bbc0-aded-4477-a3c3-1d81693d58c9' + "funkwhale_api.musicbrainz.api.release_groups.browse", + return_value=release_groups["browse"]["lost fingers"], + ) + uuid = "ac16bbc0-aded-4477-a3c3-1d81693d58c9" url = reverse( - 'api:v1:providers:musicbrainz:release-group-browse', - kwargs={ - 'artist_uuid': uuid, - } + "api:v1:providers:musicbrainz:release-group-browse", + kwargs={"artist_uuid": uuid}, ) response = logged_in_api_client.get(url) - expected = release_groups['browse']['lost fingers'] + expected = release_groups["browse"]["lost fingers"] assert expected == response.data def test_can_broswe_releases_using_musicbrainz_api( - releases, db, mocker, logged_in_api_client): + releases, db, mocker, logged_in_api_client +): mocker.patch( - 'funkwhale_api.musicbrainz.api.releases.browse', - return_value=releases['browse']['Lost in the 80s']) - uuid = 'f04ed607-11b7-3843-957e-503ecdd485d1' + "funkwhale_api.musicbrainz.api.releases.browse", + return_value=releases["browse"]["Lost in the 80s"], + ) + uuid = "f04ed607-11b7-3843-957e-503ecdd485d1" url = reverse( - 'api:v1:providers:musicbrainz:release-browse', - kwargs={ - 'release_group_uuid': uuid, - } + "api:v1:providers:musicbrainz:release-browse", + kwargs={"release_group_uuid": uuid}, ) response = logged_in_api_client.get(url) - expected = releases['browse']['Lost in the 80s'] + expected = releases["browse"]["Lost in the 80s"] assert expected == response.data diff --git a/api/tests/musicbrainz/test_cache.py b/api/tests/musicbrainz/test_cache.py index fe0d56773..3a326ff24 100644 --- a/api/tests/musicbrainz/test_cache.py +++ b/api/tests/musicbrainz/test_cache.py @@ -2,12 +2,12 @@ from funkwhale_api.musicbrainz import client def test_can_search_recording_in_musicbrainz_api(mocker): - r = {'hello': 'world'} + r = {"hello": "world"} m = mocker.patch( - 'funkwhale_api.musicbrainz.client._api.search_artists', - return_value=r) - assert client.api.artists.search('test') == r + "funkwhale_api.musicbrainz.client._api.search_artists", return_value=r + ) + assert client.api.artists.search("test") == r # now call from cache - assert client.api.artists.search('test') == r - assert client.api.artists.search('test') == r + assert client.api.artists.search("test") == r + assert client.api.artists.search("test") == r assert m.call_count == 1 diff --git a/api/tests/playlists/test_models.py b/api/tests/playlists/test_models.py index fe5dd40a8..25c40d557 100644 --- a/api/tests/playlists/test_models.py +++ b/api/tests/playlists/test_models.py @@ -1,10 +1,9 @@ import pytest - from rest_framework import exceptions def test_can_insert_plt(factories): - plt = factories['playlists.PlaylistTrack']() + plt = factories["playlists.PlaylistTrack"]() modification_date = plt.playlist.modification_date assert plt.index is None @@ -17,9 +16,8 @@ def test_can_insert_plt(factories): def test_insert_use_last_idx_by_default(factories): - playlist = factories['playlists.Playlist']() - plts = factories['playlists.PlaylistTrack'].create_batch( - size=3, playlist=playlist) + playlist = factories["playlists.Playlist"]() + plts = factories["playlists.PlaylistTrack"].create_batch(size=3, playlist=playlist) for i, plt in enumerate(plts): index = playlist.insert(plt) @@ -28,11 +26,12 @@ def test_insert_use_last_idx_by_default(factories): assert index == i assert plt.index == i + def test_can_insert_at_index(factories): - playlist = factories['playlists.Playlist']() - first = factories['playlists.PlaylistTrack'](playlist=playlist) + playlist = factories["playlists.Playlist"]() + first = factories["playlists.PlaylistTrack"](playlist=playlist) playlist.insert(first) - new_first = factories['playlists.PlaylistTrack'](playlist=playlist) + new_first = factories["playlists.PlaylistTrack"](playlist=playlist) index = playlist.insert(new_first, index=0) first.refresh_from_db() new_first.refresh_from_db() @@ -43,10 +42,10 @@ def test_can_insert_at_index(factories): def test_can_insert_and_move(factories): - playlist = factories['playlists.Playlist']() - first = factories['playlists.PlaylistTrack'](playlist=playlist, index=0) - second = factories['playlists.PlaylistTrack'](playlist=playlist, index=1) - third = factories['playlists.PlaylistTrack'](playlist=playlist, index=2) + playlist = factories["playlists.Playlist"]() + first = factories["playlists.PlaylistTrack"](playlist=playlist, index=0) + second = factories["playlists.PlaylistTrack"](playlist=playlist, index=1) + third = factories["playlists.PlaylistTrack"](playlist=playlist, index=2) playlist.insert(second, index=0) @@ -60,10 +59,10 @@ def test_can_insert_and_move(factories): def test_can_insert_and_move_last_to_0(factories): - playlist = factories['playlists.Playlist']() - first = factories['playlists.PlaylistTrack'](playlist=playlist, index=0) - second = factories['playlists.PlaylistTrack'](playlist=playlist, index=1) - third = factories['playlists.PlaylistTrack'](playlist=playlist, index=2) + playlist = factories["playlists.Playlist"]() + first = factories["playlists.PlaylistTrack"](playlist=playlist, index=0) + second = factories["playlists.PlaylistTrack"](playlist=playlist, index=1) + third = factories["playlists.PlaylistTrack"](playlist=playlist, index=2) playlist.insert(third, index=0) @@ -77,24 +76,24 @@ def test_can_insert_and_move_last_to_0(factories): def test_cannot_insert_at_wrong_index(factories): - plt = factories['playlists.PlaylistTrack']() - new = factories['playlists.PlaylistTrack'](playlist=plt.playlist) + plt = factories["playlists.PlaylistTrack"]() + new = factories["playlists.PlaylistTrack"](playlist=plt.playlist) with pytest.raises(exceptions.ValidationError): plt.playlist.insert(new, 2) def test_cannot_insert_at_negative_index(factories): - plt = factories['playlists.PlaylistTrack']() - new = factories['playlists.PlaylistTrack'](playlist=plt.playlist) + plt = factories["playlists.PlaylistTrack"]() + new = factories["playlists.PlaylistTrack"](playlist=plt.playlist) with pytest.raises(exceptions.ValidationError): plt.playlist.insert(new, -1) def test_remove_update_indexes(factories): - playlist = factories['playlists.Playlist']() - first = factories['playlists.PlaylistTrack'](playlist=playlist, index=0) - second = factories['playlists.PlaylistTrack'](playlist=playlist, index=1) - third = factories['playlists.PlaylistTrack'](playlist=playlist, index=2) + playlist = factories["playlists.Playlist"]() + first = factories["playlists.PlaylistTrack"](playlist=playlist, index=0) + second = factories["playlists.PlaylistTrack"](playlist=playlist, index=1) + third = factories["playlists.PlaylistTrack"](playlist=playlist, index=2) second.delete(update_indexes=True) @@ -106,9 +105,9 @@ def test_remove_update_indexes(factories): def test_can_insert_many(factories): - playlist = factories['playlists.Playlist']() - existing = factories['playlists.PlaylistTrack'](playlist=playlist, index=0) - tracks = factories['music.Track'].create_batch(size=3) + playlist = factories["playlists.Playlist"]() + factories["playlists.PlaylistTrack"](playlist=playlist, index=0) + tracks = factories["music.Track"].create_batch(size=3) plts = playlist.insert_many(tracks) for i, plt in enumerate(plts): assert plt.index == i + 1 @@ -117,10 +116,9 @@ def test_can_insert_many(factories): def test_insert_many_honor_max_tracks(preferences, factories): - preferences['playlists__max_tracks'] = 4 - playlist = factories['playlists.Playlist']() - plts = factories['playlists.PlaylistTrack'].create_batch( - size=2, playlist=playlist) - track = factories['music.Track']() + preferences["playlists__max_tracks"] = 4 + playlist = factories["playlists.Playlist"]() + factories["playlists.PlaylistTrack"].create_batch(size=2, playlist=playlist) + track = factories["music.Track"]() with pytest.raises(exceptions.ValidationError): playlist.insert_many([track, track, track]) diff --git a/api/tests/playlists/test_serializers.py b/api/tests/playlists/test_serializers.py index 908c1c796..677288070 100644 --- a/api/tests/playlists/test_serializers.py +++ b/api/tests/playlists/test_serializers.py @@ -1,31 +1,26 @@ -from funkwhale_api.playlists import models -from funkwhale_api.playlists import serializers +from funkwhale_api.playlists import models, serializers def test_cannot_max_500_tracks_per_playlist(factories, preferences): - preferences['playlists__max_tracks'] = 2 - playlist = factories['playlists.Playlist']() - plts = factories['playlists.PlaylistTrack'].create_batch( - size=2, playlist=playlist) - track = factories['music.Track']() - serializer = serializers.PlaylistTrackWriteSerializer(data={ - 'playlist': playlist.pk, - 'track': track.pk, - }) + preferences["playlists__max_tracks"] = 2 + playlist = factories["playlists.Playlist"]() + factories["playlists.PlaylistTrack"].create_batch(size=2, playlist=playlist) + track = factories["music.Track"]() + serializer = serializers.PlaylistTrackWriteSerializer( + data={"playlist": playlist.pk, "track": track.pk} + ) assert serializer.is_valid() is False - assert 'playlist' in serializer.errors + assert "playlist" in serializer.errors def test_create_insert_is_called_when_index_is_None(factories, mocker): - insert = mocker.spy(models.Playlist, 'insert') - playlist = factories['playlists.Playlist']() - track = factories['music.Track']() - serializer = serializers.PlaylistTrackWriteSerializer(data={ - 'playlist': playlist.pk, - 'track': track.pk, - 'index': None, - }) + insert = mocker.spy(models.Playlist, "insert") + playlist = factories["playlists.Playlist"]() + track = factories["music.Track"]() + serializer = serializers.PlaylistTrackWriteSerializer( + data={"playlist": playlist.pk, "track": track.pk, "index": None} + ) assert serializer.is_valid() is True plt = serializer.save() @@ -34,16 +29,14 @@ def test_create_insert_is_called_when_index_is_None(factories, mocker): def test_create_insert_is_called_when_index_is_provided(factories, mocker): - playlist = factories['playlists.Playlist']() - first = factories['playlists.PlaylistTrack'](playlist=playlist, index=0) - insert = mocker.spy(models.Playlist, 'insert') - factories['playlists.Playlist']() - track = factories['music.Track']() - serializer = serializers.PlaylistTrackWriteSerializer(data={ - 'playlist': playlist.pk, - 'track': track.pk, - 'index': 0, - }) + playlist = factories["playlists.Playlist"]() + first = factories["playlists.PlaylistTrack"](playlist=playlist, index=0) + insert = mocker.spy(models.Playlist, "insert") + factories["playlists.Playlist"]() + track = factories["music.Track"]() + serializer = serializers.PlaylistTrackWriteSerializer( + data={"playlist": playlist.pk, "track": track.pk, "index": 0} + ) assert serializer.is_valid() is True plt = serializer.save() @@ -54,17 +47,15 @@ def test_create_insert_is_called_when_index_is_provided(factories, mocker): def test_update_insert_is_called_when_index_is_provided(factories, mocker): - playlist = factories['playlists.Playlist']() - first = factories['playlists.PlaylistTrack'](playlist=playlist, index=0) - second = factories['playlists.PlaylistTrack'](playlist=playlist, index=1) - insert = mocker.spy(models.Playlist, 'insert') - factories['playlists.Playlist']() - track = factories['music.Track']() - serializer = serializers.PlaylistTrackWriteSerializer(second, data={ - 'playlist': playlist.pk, - 'track': second.track.pk, - 'index': 0, - }) + playlist = factories["playlists.Playlist"]() + first = factories["playlists.PlaylistTrack"](playlist=playlist, index=0) + second = factories["playlists.PlaylistTrack"](playlist=playlist, index=1) + insert = mocker.spy(models.Playlist, "insert") + factories["playlists.Playlist"]() + factories["music.Track"]() + serializer = serializers.PlaylistTrackWriteSerializer( + second, data={"playlist": playlist.pk, "track": second.track.pk, "index": 0} + ) assert serializer.is_valid() is True plt = serializer.save() diff --git a/api/tests/playlists/test_views.py b/api/tests/playlists/test_views.py index 44d060821..e7b47c7a2 100644 --- a/api/tests/playlists/test_views.py +++ b/api/tests/playlists/test_views.py @@ -1,72 +1,59 @@ -import json import pytest - from django.urls import reverse -from django.core.exceptions import ValidationError -from django.utils import timezone -from funkwhale_api.playlists import models -from funkwhale_api.playlists import serializers +from funkwhale_api.playlists import models, serializers def test_can_create_playlist_via_api(logged_in_api_client): - url = reverse('api:v1:playlists-list') - data = { - 'name': 'test', - 'privacy_level': 'everyone' - } + url = reverse("api:v1:playlists-list") + data = {"name": "test", "privacy_level": "everyone"} - response = logged_in_api_client.post(url, data) + logged_in_api_client.post(url, data) - playlist = logged_in_api_client.user.playlists.latest('id') - assert playlist.name == 'test' - assert playlist.privacy_level == 'everyone' + playlist = logged_in_api_client.user.playlists.latest("id") + assert playlist.name == "test" + assert playlist.privacy_level == "everyone" def test_serializer_includes_tracks_count(factories, logged_in_api_client): - playlist = factories['playlists.Playlist']() - plt = factories['playlists.PlaylistTrack'](playlist=playlist) + playlist = factories["playlists.Playlist"]() + factories["playlists.PlaylistTrack"](playlist=playlist) - url = reverse('api:v1:playlists-detail', kwargs={'pk': playlist.pk}) + url = reverse("api:v1:playlists-detail", kwargs={"pk": playlist.pk}) response = logged_in_api_client.get(url) - assert response.data['tracks_count'] == 1 + assert response.data["tracks_count"] == 1 def test_playlist_inherits_user_privacy(logged_in_api_client): - url = reverse('api:v1:playlists-list') + url = reverse("api:v1:playlists-list") user = logged_in_api_client.user - user.privacy_level = 'me' + user.privacy_level = "me" user.save() - data = { - 'name': 'test', - } + data = {"name": "test"} - response = logged_in_api_client.post(url, data) - playlist = user.playlists.latest('id') + logged_in_api_client.post(url, data) + playlist = user.playlists.latest("id") assert playlist.privacy_level == user.privacy_level def test_can_add_playlist_track_via_api(factories, logged_in_api_client): - tracks = factories['music.Track'].create_batch(5) - playlist = factories['playlists.Playlist'](user=logged_in_api_client.user) - url = reverse('api:v1:playlist-tracks-list') - data = { - 'playlist': playlist.pk, - 'track': tracks[0].pk - } + tracks = factories["music.Track"].create_batch(5) + playlist = factories["playlists.Playlist"](user=logged_in_api_client.user) + url = reverse("api:v1:playlist-tracks-list") + data = {"playlist": playlist.pk, "track": tracks[0].pk} response = logged_in_api_client.post(url, data) assert response.status_code == 201 - plts = logged_in_api_client.user.playlists.latest('id').playlist_tracks.all() + plts = logged_in_api_client.user.playlists.latest("id").playlist_tracks.all() assert plts.first().track == tracks[0] -@pytest.mark.parametrize('name,method', [ - ('api:v1:playlist-tracks-list', 'post'), - ('api:v1:playlists-list', 'post'), -]) +@pytest.mark.parametrize( + "name,method", + [("api:v1:playlist-tracks-list", "post"), ("api:v1:playlists-list", "post")], +) def test_url_requires_login(name, method, factories, api_client): url = reverse(name) @@ -75,29 +62,24 @@ def test_url_requires_login(name, method, factories, api_client): assert response.status_code == 401 -def test_only_can_add_track_on_own_playlist_via_api( - factories, logged_in_api_client): - track = factories['music.Track']() - playlist = factories['playlists.Playlist']() - url = reverse('api:v1:playlist-tracks-list') - data = { - 'playlist': playlist.pk, - 'track': track.pk - } +def test_only_can_add_track_on_own_playlist_via_api(factories, logged_in_api_client): + track = factories["music.Track"]() + playlist = factories["playlists.Playlist"]() + url = reverse("api:v1:playlist-tracks-list") + data = {"playlist": playlist.pk, "track": track.pk} response = logged_in_api_client.post(url, data) assert response.status_code == 400 assert playlist.playlist_tracks.count() == 0 -def test_deleting_plt_updates_indexes( - mocker, factories, logged_in_api_client): - remove = mocker.spy(models.Playlist, 'remove') - track = factories['music.Track']() - plt = factories['playlists.PlaylistTrack']( - index=0, - playlist__user=logged_in_api_client.user) - url = reverse('api:v1:playlist-tracks-detail', kwargs={'pk': plt.pk}) +def test_deleting_plt_updates_indexes(mocker, factories, logged_in_api_client): + remove = mocker.spy(models.Playlist, "remove") + factories["music.Track"]() + plt = factories["playlists.PlaylistTrack"]( + index=0, playlist__user=logged_in_api_client.user + ) + url = reverse("api:v1:playlist-tracks-detail", kwargs={"pk": plt.pk}) response = logged_in_api_client.delete(url) @@ -105,97 +87,93 @@ def test_deleting_plt_updates_indexes( remove.assert_called_once_with(plt.playlist, 0) -@pytest.mark.parametrize('level', ['instance', 'me', 'followers']) +@pytest.mark.parametrize("level", ["instance", "me", "followers"]) def test_playlist_privacy_respected_in_list_anon( - preferences, level, factories, api_client): - preferences['common__api_authentication_required'] = False - factories['playlists.Playlist'](privacy_level=level) - url = reverse('api:v1:playlists-list') + preferences, level, factories, api_client +): + preferences["common__api_authentication_required"] = False + factories["playlists.Playlist"](privacy_level=level) + url = reverse("api:v1:playlists-list") response = api_client.get(url) - assert response.data['count'] == 0 + assert response.data["count"] == 0 -@pytest.mark.parametrize('method', ['PUT', 'PATCH', 'DELETE']) +@pytest.mark.parametrize("method", ["PUT", "PATCH", "DELETE"]) def test_only_owner_can_edit_playlist(method, factories, logged_in_api_client): - playlist = factories['playlists.Playlist']() - url = reverse('api:v1:playlists-detail', kwargs={'pk': playlist.pk}) + playlist = factories["playlists.Playlist"]() + url = reverse("api:v1:playlists-detail", kwargs={"pk": playlist.pk}) response = getattr(logged_in_api_client, method.lower())(url) assert response.status_code == 404 -@pytest.mark.parametrize('method', ['PUT', 'PATCH', 'DELETE']) -def test_only_owner_can_edit_playlist_track( - method, factories, logged_in_api_client): - plt = factories['playlists.PlaylistTrack']() - url = reverse('api:v1:playlist-tracks-detail', kwargs={'pk': plt.pk}) +@pytest.mark.parametrize("method", ["PUT", "PATCH", "DELETE"]) +def test_only_owner_can_edit_playlist_track(method, factories, logged_in_api_client): + plt = factories["playlists.PlaylistTrack"]() + url = reverse("api:v1:playlist-tracks-detail", kwargs={"pk": plt.pk}) response = getattr(logged_in_api_client, method.lower())(url) assert response.status_code == 404 -@pytest.mark.parametrize('level', ['instance', 'me', 'followers']) +@pytest.mark.parametrize("level", ["instance", "me", "followers"]) def test_playlist_track_privacy_respected_in_list_anon( - level, factories, api_client, preferences): - preferences['common__api_authentication_required'] = False - factories['playlists.PlaylistTrack'](playlist__privacy_level=level) - url = reverse('api:v1:playlist-tracks-list') + level, factories, api_client, preferences +): + preferences["common__api_authentication_required"] = False + factories["playlists.PlaylistTrack"](playlist__privacy_level=level) + url = reverse("api:v1:playlist-tracks-list") response = api_client.get(url) - assert response.data['count'] == 0 + assert response.data["count"] == 0 -@pytest.mark.parametrize('level', ['instance', 'me', 'followers']) -def test_can_list_tracks_from_playlist( - level, factories, logged_in_api_client): - plt = factories['playlists.PlaylistTrack']( - playlist__user=logged_in_api_client.user) - url = reverse('api:v1:playlists-tracks', kwargs={'pk': plt.playlist.pk}) +@pytest.mark.parametrize("level", ["instance", "me", "followers"]) +def test_can_list_tracks_from_playlist(level, factories, logged_in_api_client): + plt = factories["playlists.PlaylistTrack"](playlist__user=logged_in_api_client.user) + url = reverse("api:v1:playlists-tracks", kwargs={"pk": plt.playlist.pk}) response = logged_in_api_client.get(url) serialized_plt = serializers.PlaylistTrackSerializer(plt).data - assert response.data['count'] == 1 - assert response.data['results'][0] == serialized_plt + assert response.data["count"] == 1 + assert response.data["results"][0] == serialized_plt def test_can_add_multiple_tracks_at_once_via_api( - factories, mocker, logged_in_api_client): - playlist = factories['playlists.Playlist'](user=logged_in_api_client.user) - tracks = factories['music.Track'].create_batch(size=5) + factories, mocker, logged_in_api_client +): + playlist = factories["playlists.Playlist"](user=logged_in_api_client.user) + tracks = factories["music.Track"].create_batch(size=5) track_ids = [t.id for t in tracks] - mocker.spy(playlist, 'insert_many') - url = reverse('api:v1:playlists-add', kwargs={'pk': playlist.pk}) - response = logged_in_api_client.post(url, {'tracks': track_ids}) + mocker.spy(playlist, "insert_many") + url = reverse("api:v1:playlists-add", kwargs={"pk": playlist.pk}) + response = logged_in_api_client.post(url, {"tracks": track_ids}) assert response.status_code == 201 assert playlist.playlist_tracks.count() == len(track_ids) - for plt in playlist.playlist_tracks.order_by('index'): - assert response.data['results'][plt.index]['id'] == plt.id + for plt in playlist.playlist_tracks.order_by("index"): + assert response.data["results"][plt.index]["id"] == plt.id assert plt.track == tracks[plt.index] -def test_can_clear_playlist_from_api( - factories, mocker, logged_in_api_client): - playlist = factories['playlists.Playlist'](user=logged_in_api_client.user) - plts = factories['playlists.PlaylistTrack'].create_batch( - size=5, playlist=playlist) - url = reverse('api:v1:playlists-clear', kwargs={'pk': playlist.pk}) +def test_can_clear_playlist_from_api(factories, mocker, logged_in_api_client): + playlist = factories["playlists.Playlist"](user=logged_in_api_client.user) + factories["playlists.PlaylistTrack"].create_batch(size=5, playlist=playlist) + url = reverse("api:v1:playlists-clear", kwargs={"pk": playlist.pk}) response = logged_in_api_client.delete(url) assert response.status_code == 204 assert playlist.playlist_tracks.count() == 0 -def test_update_playlist_from_api( - factories, mocker, logged_in_api_client): - playlist = factories['playlists.Playlist'](user=logged_in_api_client.user) - plts = factories['playlists.PlaylistTrack'].create_batch( - size=5, playlist=playlist) - url = reverse('api:v1:playlists-detail', kwargs={'pk': playlist.pk}) - response = logged_in_api_client.patch(url, {'name': 'test'}) +def test_update_playlist_from_api(factories, mocker, logged_in_api_client): + playlist = factories["playlists.Playlist"](user=logged_in_api_client.user) + factories["playlists.PlaylistTrack"].create_batch(size=5, playlist=playlist) + url = reverse("api:v1:playlists-detail", kwargs={"pk": playlist.pk}) + response = logged_in_api_client.patch(url, {"name": "test"}) playlist.refresh_from_db() assert response.status_code == 200 - assert response.data['user']['username'] == playlist.user.username + assert response.data["user"]["username"] == playlist.user.username diff --git a/api/tests/radios/test_api.py b/api/tests/radios/test_api.py index 66bf6052d..0ddebe387 100644 --- a/api/tests/radios/test_api.py +++ b/api/tests/radios/test_api.py @@ -1,159 +1,131 @@ -import json -import pytest - from django.urls import reverse from funkwhale_api.music.serializers import TrackSerializer -from funkwhale_api.radios import filters -from funkwhale_api.radios import serializers +from funkwhale_api.radios import filters, serializers -def test_can_list_config_options(logged_in_client): - url = reverse('api:v1:radios:radios-filters') - response = logged_in_client.get(url) +def test_can_list_config_options(logged_in_api_client): + url = reverse("api:v1:radios:radios-filters") + response = logged_in_api_client.get(url) assert response.status_code == 200 - payload = json.loads(response.content.decode('utf-8')) + payload = response.data expected = [f for f in filters.registry.values() if f.expose_in_api] assert len(payload) == len(expected) -def test_can_validate_config(logged_in_client, factories): - artist1 = factories['music.Artist']() - artist2 = factories['music.Artist']() - factories['music.Track'].create_batch(3, artist=artist1) - factories['music.Track'].create_batch(3, artist=artist2) - candidates = artist1.tracks.order_by('pk') - f = { - 'filters': [ - {'type': 'artist', 'ids': [artist1.pk]} - ] - } - url = reverse('api:v1:radios:radios-validate') - response = logged_in_client.post( - url, - json.dumps(f), - content_type="application/json") +def test_can_validate_config(logged_in_api_client, factories): + artist1 = factories["music.Artist"]() + artist2 = factories["music.Artist"]() + factories["music.Track"].create_batch(3, artist=artist1) + factories["music.Track"].create_batch(3, artist=artist2) + candidates = artist1.tracks.order_by("pk") + f = {"filters": [{"type": "artist", "ids": [artist1.pk]}]} + url = reverse("api:v1:radios:radios-validate") + response = logged_in_api_client.post(url, f, format="json") assert response.status_code == 200 - payload = json.loads(response.content.decode('utf-8')) + payload = response.data expected = { - 'count': candidates.count(), - 'sample': TrackSerializer(candidates, many=True).data + "count": candidates.count(), + "sample": TrackSerializer(candidates, many=True).data, } - assert payload['filters'][0]['candidates'] == expected - assert payload['filters'][0]['errors'] == [] + assert payload["filters"][0]["candidates"] == expected + assert payload["filters"][0]["errors"] == [] -def test_can_validate_config_with_wrong_config(logged_in_client, factories): - f = { - 'filters': [ - {'type': 'artist', 'ids': [999]} - ] - } - url = reverse('api:v1:radios:radios-validate') - response = logged_in_client.post( - url, - json.dumps(f), - content_type="application/json") +def test_can_validate_config_with_wrong_config(logged_in_api_client, factories): + f = {"filters": [{"type": "artist", "ids": [999]}]} + url = reverse("api:v1:radios:radios-validate") + response = logged_in_api_client.post(url, f, format="json") assert response.status_code == 200 - payload = json.loads(response.content.decode('utf-8')) + payload = response.data - expected = { - 'count': None, - 'sample': None - } - assert payload['filters'][0]['candidates'] == expected - assert len(payload['filters'][0]['errors']) == 1 + expected = {"count": None, "sample": None} + assert payload["filters"][0]["candidates"] == expected + assert len(payload["filters"][0]["errors"]) == 1 -def test_saving_radio_sets_user(logged_in_client, factories): - artist = factories['music.Artist']() - f = { - 'name': 'Test', - 'config': [ - {'type': 'artist', 'ids': [artist.pk]} - ] - } - url = reverse('api:v1:radios:radios-list') - response = logged_in_client.post( - url, - json.dumps(f), - content_type="application/json") +def test_saving_radio_sets_user(logged_in_api_client, factories): + artist = factories["music.Artist"]() + f = {"name": "Test", "config": [{"type": "artist", "ids": [artist.pk]}]} + url = reverse("api:v1:radios:radios-list") + response = logged_in_api_client.post(url, f, format="json") assert response.status_code == 201 - radio = logged_in_client.user.radios.latest('id') - assert radio.name == 'Test' - assert radio.user == logged_in_client.user + radio = logged_in_api_client.user.radios.latest("id") + assert radio.name == "Test" + assert radio.user == logged_in_api_client.user -def test_user_can_detail_his_radio(logged_in_client, factories): - radio = factories['radios.Radio'](user=logged_in_client.user) - url = reverse('api:v1:radios:radios-detail', kwargs={'pk': radio.pk}) - response = logged_in_client.get(url) +def test_user_can_detail_his_radio(logged_in_api_client, factories): + radio = factories["radios.Radio"](user=logged_in_api_client.user) + url = reverse("api:v1:radios:radios-detail", kwargs={"pk": radio.pk}) + response = logged_in_api_client.get(url) assert response.status_code == 200 -def test_user_can_detail_public_radio(logged_in_client, factories): - radio = factories['radios.Radio'](is_public=True) - url = reverse('api:v1:radios:radios-detail', kwargs={'pk': radio.pk}) - response = logged_in_client.get(url) +def test_user_can_detail_public_radio(logged_in_api_client, factories): + radio = factories["radios.Radio"](is_public=True) + url = reverse("api:v1:radios:radios-detail", kwargs={"pk": radio.pk}) + response = logged_in_api_client.get(url) assert response.status_code == 200 -def test_user_cannot_detail_someone_else_radio(logged_in_client, factories): - radio = factories['radios.Radio'](is_public=False) - url = reverse('api:v1:radios:radios-detail', kwargs={'pk': radio.pk}) - response = logged_in_client.get(url) +def test_user_cannot_detail_someone_else_radio(logged_in_api_client, factories): + radio = factories["radios.Radio"](is_public=False) + url = reverse("api:v1:radios:radios-detail", kwargs={"pk": radio.pk}) + response = logged_in_api_client.get(url) assert response.status_code == 404 -def test_user_can_edit_his_radio(logged_in_client, factories): - radio = factories['radios.Radio'](user=logged_in_client.user) - url = reverse('api:v1:radios:radios-detail', kwargs={'pk': radio.pk}) - response = logged_in_client.put( - url, - json.dumps({'name': 'new', 'config': []}), - content_type="application/json") +def test_user_can_edit_his_radio(logged_in_api_client, factories): + radio = factories["radios.Radio"](user=logged_in_api_client.user) + url = reverse("api:v1:radios:radios-detail", kwargs={"pk": radio.pk}) + response = logged_in_api_client.put( + url, {"name": "new", "config": []}, format="json" + ) radio.refresh_from_db() assert response.status_code == 200 - assert radio.name == 'new' + assert radio.name == "new" -def test_user_cannot_edit_someone_else_radio(logged_in_client, factories): - radio = factories['radios.Radio']() - url = reverse('api:v1:radios:radios-detail', kwargs={'pk': radio.pk}) - response = logged_in_client.put( - url, - json.dumps({'name': 'new', 'config': []}), - content_type="application/json") +def test_user_cannot_edit_someone_else_radio(logged_in_api_client, factories): + radio = factories["radios.Radio"](is_public=True) + url = reverse("api:v1:radios:radios-detail", kwargs={"pk": radio.pk}) + response = logged_in_api_client.put( + url, {"name": "new", "config": []}, format="json" + ) + + assert response.status_code == 404 + + +def test_user_cannot_delete_someone_else_radio(logged_in_api_client, factories): + radio = factories["radios.Radio"](is_public=True) + url = reverse("api:v1:radios:radios-detail", kwargs={"pk": radio.pk}) + response = logged_in_api_client.delete(url) assert response.status_code == 404 def test_clean_config_is_called_on_serializer_save(mocker, factories): - user = factories['users.User']() - artist = factories['music.Artist']() - data= { - 'name': 'Test', - 'config': [ - {'type': 'artist', 'ids': [artist.pk]} - ] - } - spied = mocker.spy(filters.registry['artist'], 'clean_config') + user = factories["users.User"]() + artist = factories["music.Artist"]() + data = {"name": "Test", "config": [{"type": "artist", "ids": [artist.pk]}]} + spied = mocker.spy(filters.registry["artist"], "clean_config") serializer = serializers.RadioSerializer(data=data) assert serializer.is_valid() instance = serializer.save(user=user) - spied.assert_called_once_with(data['config'][0]) - assert instance.config[0]['names'] == [artist.name] + spied.assert_called_once_with(data["config"][0]) + assert instance.config[0]["names"] == [artist.name] diff --git a/api/tests/radios/test_filters.py b/api/tests/radios/test_filters.py index 27166b4ab..89bb726af 100644 --- a/api/tests/radios/test_filters.py +++ b/api/tests/radios/test_filters.py @@ -1,5 +1,4 @@ import pytest - from django.core.exceptions import ValidationError from funkwhale_api.music.models import Track @@ -8,154 +7,147 @@ from funkwhale_api.radios import filters @filters.registry.register class NoopFilter(filters.RadioFilter): - code = 'noop' + code = "noop" + def get_query(self, candidates, **kwargs): return def test_most_simple_radio_does_not_filter_anything(factories): - tracks = factories['music.Track'].create_batch(3) - radio = factories['radios.Radio'](config=[{'type': 'noop'}]) + factories["music.Track"].create_batch(3) + radio = factories["radios.Radio"](config=[{"type": "noop"}]) assert radio.version == 0 assert radio.get_candidates().count() == 3 - def test_filter_can_use_custom_queryset(factories): - tracks = factories['music.Track'].create_batch(3) + tracks = factories["music.Track"].create_batch(3) candidates = Track.objects.filter(pk=tracks[0].pk) - qs = filters.run([{'type': 'noop'}], candidates=candidates) + qs = filters.run([{"type": "noop"}], candidates=candidates) assert qs.count() == 1 assert qs.first() == tracks[0] def test_filter_on_tag(factories): - tracks = factories['music.Track'].create_batch(3, tags=['metal']) - factories['music.Track'].create_batch(3, tags=['pop']) + tracks = factories["music.Track"].create_batch(3, tags=["metal"]) + factories["music.Track"].create_batch(3, tags=["pop"]) expected = tracks - f = [ - {'type': 'tag', 'names': ['metal']} - ] + f = [{"type": "tag", "names": ["metal"]}] candidates = filters.run(f) - assert list(candidates.order_by('pk')) == expected + assert list(candidates.order_by("pk")) == expected def test_filter_on_artist(factories): - artist1 = factories['music.Artist']() - artist2 = factories['music.Artist']() - factories['music.Track'].create_batch(3, artist=artist1) - factories['music.Track'].create_batch(3, artist=artist2) - expected = list(artist1.tracks.order_by('pk')) - f = [ - {'type': 'artist', 'ids': [artist1.pk]} - ] + artist1 = factories["music.Artist"]() + artist2 = factories["music.Artist"]() + factories["music.Track"].create_batch(3, artist=artist1) + factories["music.Track"].create_batch(3, artist=artist2) + expected = list(artist1.tracks.order_by("pk")) + f = [{"type": "artist", "ids": [artist1.pk]}] candidates = filters.run(f) - assert list(candidates.order_by('pk')) == expected + assert list(candidates.order_by("pk")) == expected def test_can_combine_with_or(factories): - artist1 = factories['music.Artist']() - artist2 = factories['music.Artist']() - artist3 = factories['music.Artist']() - factories['music.Track'].create_batch(3, artist=artist1) - factories['music.Track'].create_batch(3, artist=artist2) - factories['music.Track'].create_batch(3, artist=artist3) - expected = Track.objects.exclude(artist=artist3).order_by('pk') + artist1 = factories["music.Artist"]() + artist2 = factories["music.Artist"]() + artist3 = factories["music.Artist"]() + factories["music.Track"].create_batch(3, artist=artist1) + factories["music.Track"].create_batch(3, artist=artist2) + factories["music.Track"].create_batch(3, artist=artist3) + expected = Track.objects.exclude(artist=artist3).order_by("pk") f = [ - {'type': 'artist', 'ids': [artist1.pk]}, - {'type': 'artist', 'ids': [artist2.pk], 'operator': 'or'}, + {"type": "artist", "ids": [artist1.pk]}, + {"type": "artist", "ids": [artist2.pk], "operator": "or"}, ] candidates = filters.run(f) - assert list(candidates.order_by('pk')) == list(expected) + assert list(candidates.order_by("pk")) == list(expected) def test_can_combine_with_and(factories): - artist1 = factories['music.Artist']() - artist2 = factories['music.Artist']() - metal_tracks = factories['music.Track'].create_batch( - 2, artist=artist1, tags=['metal']) - factories['music.Track'].create_batch(2, artist=artist1, tags=['pop']) - factories['music.Track'].create_batch(3, artist=artist2) + artist1 = factories["music.Artist"]() + artist2 = factories["music.Artist"]() + metal_tracks = factories["music.Track"].create_batch( + 2, artist=artist1, tags=["metal"] + ) + factories["music.Track"].create_batch(2, artist=artist1, tags=["pop"]) + factories["music.Track"].create_batch(3, artist=artist2) expected = metal_tracks f = [ - {'type': 'artist', 'ids': [artist1.pk]}, - {'type': 'tag', 'names': ['metal'], 'operator': 'and'}, + {"type": "artist", "ids": [artist1.pk]}, + {"type": "tag", "names": ["metal"], "operator": "and"}, ] candidates = filters.run(f) - assert list(candidates.order_by('pk')) == list(expected) + assert list(candidates.order_by("pk")) == list(expected) def test_can_negate(factories): - artist1 = factories['music.Artist']() - artist2 = factories['music.Artist']() - factories['music.Track'].create_batch(3, artist=artist1) - factories['music.Track'].create_batch(3, artist=artist2) - expected = artist2.tracks.order_by('pk') - f = [ - {'type': 'artist', 'ids': [artist1.pk], 'not': True}, - ] + artist1 = factories["music.Artist"]() + artist2 = factories["music.Artist"]() + factories["music.Track"].create_batch(3, artist=artist1) + factories["music.Track"].create_batch(3, artist=artist2) + expected = artist2.tracks.order_by("pk") + f = [{"type": "artist", "ids": [artist1.pk], "not": True}] candidates = filters.run(f) - assert list(candidates.order_by('pk')) == list(expected) + assert list(candidates.order_by("pk")) == list(expected) def test_can_group(factories): - artist1 = factories['music.Artist']() - artist2 = factories['music.Artist']() - factories['music.Track'].create_batch(2, artist=artist1) - t1 = factories['music.Track'].create_batch( - 2, artist=artist1, tags=['metal']) - factories['music.Track'].create_batch(2, artist=artist2) - t2 = factories['music.Track'].create_batch( - 2, artist=artist2, tags=['metal']) - factories['music.Track'].create_batch(2, tags=['metal']) + artist1 = factories["music.Artist"]() + artist2 = factories["music.Artist"]() + factories["music.Track"].create_batch(2, artist=artist1) + t1 = factories["music.Track"].create_batch(2, artist=artist1, tags=["metal"]) + factories["music.Track"].create_batch(2, artist=artist2) + t2 = factories["music.Track"].create_batch(2, artist=artist2, tags=["metal"]) + factories["music.Track"].create_batch(2, tags=["metal"]) expected = t1 + t2 f = [ - {'type': 'tag', 'names': ['metal']}, - {'type': 'group', 'operator': 'and', 'filters': [ - {'type': 'artist', 'ids': [artist1.pk], 'operator': 'or'}, - {'type': 'artist', 'ids': [artist2.pk], 'operator': 'or'}, - ]} + {"type": "tag", "names": ["metal"]}, + { + "type": "group", + "operator": "and", + "filters": [ + {"type": "artist", "ids": [artist1.pk], "operator": "or"}, + {"type": "artist", "ids": [artist2.pk], "operator": "or"}, + ], + }, ] candidates = filters.run(f) - assert list(candidates.order_by('pk')) == list(expected) + assert list(candidates.order_by("pk")) == list(expected) def test_artist_filter_clean_config(factories): - artist1 = factories['music.Artist']() - artist2 = factories['music.Artist']() + artist1 = factories["music.Artist"]() + artist2 = factories["music.Artist"]() - config = filters.clean_config( - {'type': 'artist', 'ids': [artist2.pk, artist1.pk]}) + config = filters.clean_config({"type": "artist", "ids": [artist2.pk, artist1.pk]}) expected = { - 'type': 'artist', - 'ids': [artist1.pk, artist2.pk], - 'names': [artist1.name, artist2.name] + "type": "artist", + "ids": [artist1.pk, artist2.pk], + "names": [artist1.name, artist2.name], } assert filters.clean_config(config) == expected def test_can_check_artist_filter(factories): - artist = factories['music.Artist']() + artist = factories["music.Artist"]() - assert filters.validate({'type': 'artist', 'ids': [artist.pk]}) + assert filters.validate({"type": "artist", "ids": [artist.pk]}) with pytest.raises(ValidationError): - filters.validate({'type': 'artist', 'ids': [artist.pk + 1]}) + filters.validate({"type": "artist", "ids": [artist.pk + 1]}) def test_can_check_operator(): - assert filters.validate( - {'type': 'group', 'operator': 'or', 'filters': []}) - assert filters.validate( - {'type': 'group', 'operator': 'and', 'filters': []}) + assert filters.validate({"type": "group", "operator": "or", "filters": []}) + assert filters.validate({"type": "group", "operator": "and", "filters": []}) with pytest.raises(ValidationError): - assert filters.validate( - {'type': 'group', 'operator': 'nope', 'filters': []}) + assert filters.validate({"type": "group", "operator": "nope", "filters": []}) diff --git a/api/tests/radios/test_radios.py b/api/tests/radios/test_radios.py index b166b648c..e218ced90 100644 --- a/api/tests/radios/test_radios.py +++ b/api/tests/radios/test_radios.py @@ -1,15 +1,12 @@ import json import random + import pytest - -from django.urls import reverse from django.core.exceptions import ValidationError +from django.urls import reverse - -from funkwhale_api.radios import radios -from funkwhale_api.radios import models -from funkwhale_api.radios import serializers from funkwhale_api.favorites.models import TrackFavorite +from funkwhale_api.radios import models, radios, serializers def test_can_pick_track_from_choices(): @@ -51,9 +48,9 @@ def test_can_pick_by_weight(): def test_can_get_choices_for_favorites_radio(factories): - files = factories['music.TrackFile'].create_batch(10) + files = factories["music.TrackFile"].create_batch(10) tracks = [f.track for f in files] - user = factories['users.User']() + user = factories["users.User"]() for i in range(5): TrackFavorite.add(track=random.choice(tracks), user=user) @@ -71,71 +68,65 @@ def test_can_get_choices_for_favorites_radio(factories): def test_can_get_choices_for_custom_radio(factories): - artist = factories['music.Artist']() - files = factories['music.TrackFile'].create_batch( - 5, track__artist=artist) + artist = factories["music.Artist"]() + files = factories["music.TrackFile"].create_batch(5, track__artist=artist) tracks = [f.track for f in files] - wrong_files = factories['music.TrackFile'].create_batch(5) - wrong_tracks = [f.track for f in wrong_files] + factories["music.TrackFile"].create_batch(5) - session = factories['radios.CustomRadioSession']( - custom_radio__config=[{'type': 'artist', 'ids': [artist.pk]}] + session = factories["radios.CustomRadioSession"]( + custom_radio__config=[{"type": "artist", "ids": [artist.pk]}] ) choices = session.radio.get_choices() expected = [t.pk for t in tracks] - assert list(choices.values_list('id', flat=True)) == expected + assert list(choices.values_list("id", flat=True)) == expected def test_cannot_start_custom_radio_if_not_owner_or_not_public(factories): - user = factories['users.User']() - artist = factories['music.Artist']() - radio = factories['radios.Radio']( - config=[{'type': 'artist', 'ids': [artist.pk]}] - ) + user = factories["users.User"]() + artist = factories["music.Artist"]() + radio = factories["radios.Radio"](config=[{"type": "artist", "ids": [artist.pk]}]) serializer = serializers.RadioSessionSerializer( - data={ - 'radio_type': 'custom', 'custom_radio': radio.pk, 'user': user.pk} + data={"radio_type": "custom", "custom_radio": radio.pk, "user": user.pk} ) message = "You don't have access to this radio" assert not serializer.is_valid() - assert message in serializer.errors['non_field_errors'] + assert message in serializer.errors["non_field_errors"] def test_can_start_custom_radio_from_api(logged_in_client, factories): - artist = factories['music.Artist']() - radio = factories['radios.Radio']( - config=[{'type': 'artist', 'ids': [artist.pk]}], - user=logged_in_client.user + artist = factories["music.Artist"]() + radio = factories["radios.Radio"]( + config=[{"type": "artist", "ids": [artist.pk]}], user=logged_in_client.user ) - url = reverse('api:v1:radios:sessions-list') + url = reverse("api:v1:radios:sessions-list") response = logged_in_client.post( - url, {'radio_type': 'custom', 'custom_radio': radio.pk}) + url, {"radio_type": "custom", "custom_radio": radio.pk} + ) assert response.status_code == 201 - session = radio.sessions.latest('id') - assert session.radio_type == 'custom' + session = radio.sessions.latest("id") + assert session.radio_type == "custom" assert session.user == logged_in_client.user def test_can_use_radio_session_to_filter_choices(factories): - files = factories['music.TrackFile'].create_batch(30) - tracks = [f.track for f in files] - user = factories['users.User']() + factories["music.TrackFile"].create_batch(30) + user = factories["users.User"]() radio = radios.RandomRadio() session = radio.start_session(user) for i in range(30): - p = radio.pick() + radio.pick() # ensure 30 differents tracks have been suggested tracks_id = [ - session_track.track.pk - for session_track in session.session_tracks.all()] + session_track.track.pk for session_track in session.session_tracks.all() + ] assert len(set(tracks_id)) == 30 def test_can_restore_radio_from_previous_session(factories): - user = factories['users.User']() + user = factories["users.User"]() radio = radios.RandomRadio() session = radio.start_session(user) @@ -144,37 +135,37 @@ def test_can_restore_radio_from_previous_session(factories): def test_can_start_radio_for_logged_in_user(logged_in_client): - url = reverse('api:v1:radios:sessions-list') - response = logged_in_client.post(url, {'radio_type': 'random'}) - session = models.RadioSession.objects.latest('id') - assert session.radio_type == 'random' + url = reverse("api:v1:radios:sessions-list") + logged_in_client.post(url, {"radio_type": "random"}) + session = models.RadioSession.objects.latest("id") + assert session.radio_type == "random" assert session.user == logged_in_client.user def test_can_get_track_for_session_from_api(factories, logged_in_client): - files = factories['music.TrackFile'].create_batch(1) + files = factories["music.TrackFile"].create_batch(1) tracks = [f.track for f in files] - url = reverse('api:v1:radios:sessions-list') - response = logged_in_client.post(url, {'radio_type': 'random'}) - session = models.RadioSession.objects.latest('id') + url = reverse("api:v1:radios:sessions-list") + response = logged_in_client.post(url, {"radio_type": "random"}) + session = models.RadioSession.objects.latest("id") - url = reverse('api:v1:radios:tracks-list') - response = logged_in_client.post(url, {'session': session.pk}) - data = json.loads(response.content.decode('utf-8')) + url = reverse("api:v1:radios:tracks-list") + response = logged_in_client.post(url, {"session": session.pk}) + data = json.loads(response.content.decode("utf-8")) - assert data['track']['id'] == tracks[0].id - assert data['position'] == 1 + assert data["track"]["id"] == tracks[0].id + assert data["position"] == 1 - next_track = factories['music.TrackFile']().track - response = logged_in_client.post(url, {'session': session.pk}) - data = json.loads(response.content.decode('utf-8')) + next_track = factories["music.TrackFile"]().track + response = logged_in_client.post(url, {"session": session.pk}) + data = json.loads(response.content.decode("utf-8")) - assert data['track']['id'] == next_track.id - assert data['position'] == 2 + assert data["track"]["id"] == next_track.id + assert data["position"] == 2 def test_related_object_radio_validate_related_object(factories): - user = factories['users.User']() + user = factories["users.User"]() # cannot start without related object radio = radios.ArtistRadio() with pytest.raises(ValidationError): @@ -187,62 +178,58 @@ def test_related_object_radio_validate_related_object(factories): def test_can_start_artist_radio(factories): - user = factories['users.User']() - artist = factories['music.Artist']() - wrong_files = factories['music.TrackFile'].create_batch(5) - wrong_tracks = [f.track for f in wrong_files] - good_files = factories['music.TrackFile'].create_batch( - 5, track__artist=artist) + user = factories["users.User"]() + artist = factories["music.Artist"]() + factories["music.TrackFile"].create_batch(5) + good_files = factories["music.TrackFile"].create_batch(5, track__artist=artist) good_tracks = [f.track for f in good_files] radio = radios.ArtistRadio() session = radio.start_session(user, related_object=artist) - assert session.radio_type == 'artist' + assert session.radio_type == "artist" for i in range(5): assert radio.pick() in good_tracks def test_can_start_tag_radio(factories): - user = factories['users.User']() - tag = factories['taggit.Tag']() - wrong_files = factories['music.TrackFile'].create_batch(5) - wrong_tracks = [f.track for f in wrong_files] - good_files = factories['music.TrackFile'].create_batch( - 5, track__tags=[tag]) + user = factories["users.User"]() + tag = factories["taggit.Tag"]() + factories["music.TrackFile"].create_batch(5) + good_files = factories["music.TrackFile"].create_batch(5, track__tags=[tag]) good_tracks = [f.track for f in good_files] radio = radios.TagRadio() session = radio.start_session(user, related_object=tag) - assert session.radio_type == 'tag' + assert session.radio_type == "tag" for i in range(5): assert radio.pick() in good_tracks -def test_can_start_artist_radio_from_api( - logged_in_api_client, preferences, factories): - artist = factories['music.Artist']() - url = reverse('api:v1:radios:sessions-list') +def test_can_start_artist_radio_from_api(logged_in_api_client, preferences, factories): + artist = factories["music.Artist"]() + url = reverse("api:v1:radios:sessions-list") response = logged_in_api_client.post( - url, {'radio_type': 'artist', 'related_object_id': artist.id}) + url, {"radio_type": "artist", "related_object_id": artist.id} + ) assert response.status_code == 201 - session = models.RadioSession.objects.latest('id') + session = models.RadioSession.objects.latest("id") - assert session.radio_type == 'artist' + assert session.radio_type == "artist" assert session.related_object == artist def test_can_start_less_listened_radio(factories): - user = factories['users.User']() - wrong_files = factories['music.TrackFile'].create_batch(5) + user = factories["users.User"]() + wrong_files = factories["music.TrackFile"].create_batch(5) for f in wrong_files: - factories['history.Listening'](track=f.track, user=user) - good_files = factories['music.TrackFile'].create_batch(5) + factories["history.Listening"](track=f.track, user=user) + good_files = factories["music.TrackFile"].create_batch(5) good_tracks = [f.track for f in good_files] radio = radios.LessListenedRadio() - session = radio.start_session(user) + radio.start_session(user) for i in range(5): assert radio.pick() in good_tracks diff --git a/api/tests/requests/test_models.py b/api/tests/requests/test_models.py index 797656bd7..3ac8a5342 100644 --- a/api/tests/requests/test_models.py +++ b/api/tests/requests/test_models.py @@ -1,23 +1,18 @@ -import pytest - -from django.forms import ValidationError - - def test_can_bind_import_batch_to_request(factories): - request = factories['requests.ImportRequest']() + request = factories["requests.ImportRequest"]() - assert request.status == 'pending' + assert request.status == "pending" # when we create the import, we consider the request as accepted - batch = factories['music.ImportBatch'](import_request=request) + batch = factories["music.ImportBatch"](import_request=request) request.refresh_from_db() - assert request.status == 'accepted' + assert request.status == "accepted" # now, the batch is finished, therefore the request status should be # imported - batch.status = 'finished' - batch.save(update_fields=['status']) + batch.status = "finished" + batch.save(update_fields=["status"]) request.refresh_from_db() - assert request.status == 'imported' + assert request.status == "imported" diff --git a/api/tests/requests/test_views.py b/api/tests/requests/test_views.py index 6c34f9ad1..0d6433672 100644 --- a/api/tests/requests/test_views.py +++ b/api/tests/requests/test_views.py @@ -2,25 +2,25 @@ from django.urls import reverse def test_request_viewset_requires_auth(db, api_client): - url = reverse('api:v1:requests:import-requests-list') + url = reverse("api:v1:requests:import-requests-list") response = api_client.get(url) assert response.status_code == 401 def test_user_can_create_request(logged_in_api_client): - url = reverse('api:v1:requests:import-requests-list') + url = reverse("api:v1:requests:import-requests-list") user = logged_in_api_client.user data = { - 'artist_name': 'System of a Down', - 'albums': 'All please!', - 'comment': 'Please, they rock!', + "artist_name": "System of a Down", + "albums": "All please!", + "comment": "Please, they rock!", } response = logged_in_api_client.post(url, data) assert response.status_code == 201 - ir = user.import_requests.latest('id') - assert ir.status == 'pending' + ir = user.import_requests.latest("id") + assert ir.status == "pending" assert ir.creation_date is not None for field, value in data.items(): assert getattr(ir, field) == value diff --git a/api/tests/subsonic/test_authentication.py b/api/tests/subsonic/test_authentication.py index 656f8c44d..b2d2c0400 100644 --- a/api/tests/subsonic/test_authentication.py +++ b/api/tests/subsonic/test_authentication.py @@ -1,22 +1,18 @@ import binascii -import pytest +import pytest from rest_framework import exceptions from funkwhale_api.subsonic import authentication def test_auth_with_salt(api_request, factories): - salt = 'salt' - user = factories['users.User']() - user.subsonic_api_token = 'password' + salt = "salt" + user = factories["users.User"]() + user.subsonic_api_token = "password" user.save() - token = authentication.get_token(salt, 'password') - request = api_request.get('/', { - 't': token, - 's': salt, - 'u': user.username - }) + token = authentication.get_token(salt, "password") + request = api_request.get("/", {"t": token, "s": salt, "u": user.username}) authenticator = authentication.SubsonicAuthentication() u, _ = authenticator.authenticate(request) @@ -25,16 +21,20 @@ def test_auth_with_salt(api_request, factories): def test_auth_with_password_hex(api_request, factories): - salt = 'salt' - user = factories['users.User']() - user.subsonic_api_token = 'password' + user = factories["users.User"]() + user.subsonic_api_token = "password" user.save() - token = authentication.get_token(salt, 'password') - request = api_request.get('/', { - 'u': user.username, - 'p': 'enc:{}'.format(binascii.hexlify( - user.subsonic_api_token.encode('utf-8')).decode('utf-8')) - }) + request = api_request.get( + "/", + { + "u": user.username, + "p": "enc:{}".format( + binascii.hexlify(user.subsonic_api_token.encode("utf-8")).decode( + "utf-8" + ) + ), + }, + ) authenticator = authentication.SubsonicAuthentication() u, _ = authenticator.authenticate(request) @@ -43,15 +43,10 @@ def test_auth_with_password_hex(api_request, factories): def test_auth_with_password_cleartext(api_request, factories): - salt = 'salt' - user = factories['users.User']() - user.subsonic_api_token = 'password' + user = factories["users.User"]() + user.subsonic_api_token = "password" user.save() - token = authentication.get_token(salt, 'password') - request = api_request.get('/', { - 'u': user.username, - 'p': 'password', - }) + request = api_request.get("/", {"u": user.username, "p": "password"}) authenticator = authentication.SubsonicAuthentication() u, _ = authenticator.authenticate(request) @@ -60,15 +55,10 @@ def test_auth_with_password_cleartext(api_request, factories): def test_auth_with_inactive_users(api_request, factories): - salt = 'salt' - user = factories['users.User'](is_active=False) - user.subsonic_api_token = 'password' + user = factories["users.User"](is_active=False) + user.subsonic_api_token = "password" user.save() - token = authentication.get_token(salt, 'password') - request = api_request.get('/', { - 'u': user.username, - 'p': 'password', - }) + request = api_request.get("/", {"u": user.username, "p": "password"}) authenticator = authentication.SubsonicAuthentication() with pytest.raises(exceptions.AuthenticationFailed): diff --git a/api/tests/subsonic/test_renderers.py b/api/tests/subsonic/test_renderers.py index 8e2ea3f85..301fee8b5 100644 --- a/api/tests/subsonic/test_renderers.py +++ b/api/tests/subsonic/test_renderers.py @@ -5,38 +5,26 @@ from funkwhale_api.subsonic import renderers def test_json_renderer(): - data = {'hello': 'world'} + data = {"hello": "world"} expected = { - 'subsonic-response': { - 'status': 'ok', - 'version': '1.16.0', - 'hello': 'world' - } + "subsonic-response": {"status": "ok", "version": "1.16.0", "hello": "world"} } renderer = renderers.SubsonicJSONRenderer() assert json.loads(renderer.render(data)) == expected def test_xml_renderer_dict_to_xml(): - payload = { - 'hello': 'world', - 'item': [ - {'this': 1}, - {'some': 'node'}, - ] - } + payload = {"hello": "world", "item": [{"this": 1}, {"some": "node"}]} expected = """Do you want to delete the radio "{{ radio.name }}"?
-This will completely delete this radio and cannot be undone.
-Delete radio
-Do you want to delete the radio "{{ radio.name }}"?
+This will completely delete this radio and cannot be undone.
+Delete radio
+