See #170: Funkwhale federation
This commit is contained in:
parent
fce4d87551
commit
9aa12db62e
20 changed files with 3719 additions and 122 deletions
|
|
@ -148,19 +148,17 @@ def test_channel_delete(logged_in_api_client, factories, mocker):
|
|||
channel = factories["audio.Channel"](attributed_to=actor)
|
||||
|
||||
url = reverse("api:v1:channels-detail", kwargs={"composite": channel.uuid})
|
||||
dispatch = mocker.patch("funkwhale_api.federation.routes.outbox.dispatch")
|
||||
on_commit = mocker.patch("funkwhale_api.common.utils.on_commit")
|
||||
response = logged_in_api_client.delete(url)
|
||||
|
||||
assert response.status_code == 204
|
||||
|
||||
on_commit.assert_called_once_with(
|
||||
views.federation_tasks.remove_actor.delay, actor_id=channel.actor.pk
|
||||
)
|
||||
with pytest.raises(channel.DoesNotExist):
|
||||
channel.refresh_from_db()
|
||||
|
||||
dispatch.assert_called_once_with(
|
||||
{"type": "Delete", "object": {"type": channel.actor.type}},
|
||||
context={"actor": channel.actor},
|
||||
)
|
||||
|
||||
|
||||
def test_channel_delete_permission(logged_in_api_client, factories):
|
||||
logged_in_api_client.user.create_actor()
|
||||
|
|
@ -218,6 +216,38 @@ def test_channel_unsubscribe(factories, logged_in_api_client):
|
|||
subscription.refresh_from_db()
|
||||
|
||||
|
||||
def test_channel_subscribe_remote(factories, logged_in_api_client, mocker):
|
||||
dispatch = mocker.patch("funkwhale_api.federation.routes.outbox.dispatch")
|
||||
actor = logged_in_api_client.user.create_actor()
|
||||
channel_actor = factories["federation.Actor"]()
|
||||
channel = factories["audio.Channel"](artist__description=None, actor=channel_actor)
|
||||
url = reverse("api:v1:channels-subscribe", kwargs={"composite": channel.uuid})
|
||||
|
||||
response = logged_in_api_client.post(url)
|
||||
|
||||
assert response.status_code == 201
|
||||
subscription = actor.emitted_follows.latest("id")
|
||||
dispatch.assert_called_once_with(
|
||||
{"type": "Follow"}, context={"follow": subscription}
|
||||
)
|
||||
|
||||
|
||||
def test_channel_unsubscribe_remote(factories, logged_in_api_client, mocker):
|
||||
dispatch = mocker.patch("funkwhale_api.federation.routes.outbox.dispatch")
|
||||
actor = logged_in_api_client.user.create_actor()
|
||||
channel_actor = factories["federation.Actor"]()
|
||||
channel = factories["audio.Channel"](actor=channel_actor)
|
||||
subscription = factories["audio.Subscription"](target=channel.actor, actor=actor)
|
||||
url = reverse("api:v1:channels-unsubscribe", kwargs={"composite": channel.uuid})
|
||||
|
||||
response = logged_in_api_client.post(url)
|
||||
|
||||
assert response.status_code == 204
|
||||
dispatch.assert_called_once_with(
|
||||
{"type": "Undo", "object": {"type": "Follow"}}, context={"follow": subscription}
|
||||
)
|
||||
|
||||
|
||||
def test_subscriptions_list(factories, logged_in_api_client):
|
||||
actor = logged_in_api_client.user.create_actor()
|
||||
channel = factories["audio.Channel"](
|
||||
|
|
|
|||
|
|
@ -167,6 +167,7 @@ def test_fetch_serializer_no_obj(factories, to_api_date):
|
|||
("music.Track", "track", "id"),
|
||||
("music.Library", "library", "uuid"),
|
||||
("music.Upload", "upload", "uuid"),
|
||||
("audio.Channel", "channel", "uuid"),
|
||||
("federation.Actor", "account", "full_username"),
|
||||
],
|
||||
)
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ from funkwhale_api.moderation import serializers as moderation_serializers
|
|||
routes.inbox_delete_library,
|
||||
),
|
||||
({"type": "Delete", "object": {"type": "Audio"}}, routes.inbox_delete_audio),
|
||||
({"type": "Delete", "object": {"type": "Album"}}, routes.inbox_delete_album),
|
||||
({"type": "Undo", "object": {"type": "Follow"}}, routes.inbox_undo_follow),
|
||||
({"type": "Update", "object": {"type": "Artist"}}, routes.inbox_update_artist),
|
||||
({"type": "Update", "object": {"type": "Album"}}, routes.inbox_update_album),
|
||||
|
|
@ -58,6 +59,7 @@ def test_inbox_routes(route, handler):
|
|||
routes.outbox_delete_library,
|
||||
),
|
||||
({"type": "Delete", "object": {"type": "Audio"}}, routes.outbox_delete_audio),
|
||||
({"type": "Delete", "object": {"type": "Album"}}, routes.outbox_delete_album),
|
||||
({"type": "Undo", "object": {"type": "Follow"}}, routes.outbox_undo_follow),
|
||||
({"type": "Update", "object": {"type": "Track"}}, routes.outbox_update_track),
|
||||
(
|
||||
|
|
@ -349,6 +351,34 @@ def test_inbox_create_audio(factories, mocker):
|
|||
assert save.call_count == 1
|
||||
|
||||
|
||||
def test_inbox_create_audio_channel(factories, mocker):
|
||||
activity = factories["federation.Activity"]()
|
||||
channel = factories["audio.Channel"]()
|
||||
album = factories["music.Album"](artist=channel.artist)
|
||||
upload = factories["music.Upload"](track__album=album, library=channel.library,)
|
||||
payload = {
|
||||
"@context": jsonld.get_default_context(),
|
||||
"type": "Create",
|
||||
"actor": channel.actor.fid,
|
||||
"object": serializers.ChannelUploadSerializer(upload).data,
|
||||
}
|
||||
upload.delete()
|
||||
init = mocker.spy(serializers.ChannelUploadSerializer, "__init__")
|
||||
save = mocker.spy(serializers.ChannelUploadSerializer, "save")
|
||||
result = routes.inbox_create_audio(
|
||||
payload,
|
||||
context={"actor": channel.actor, "raise_exception": True, "activity": activity},
|
||||
)
|
||||
assert channel.library.uploads.count() == 1
|
||||
assert result == {"object": channel.library.uploads.latest("id"), "target": channel}
|
||||
|
||||
assert init.call_count == 1
|
||||
args = init.call_args
|
||||
assert args[1]["data"] == payload["object"]
|
||||
assert args[1]["context"] == {"channel": channel}
|
||||
assert save.call_count == 1
|
||||
|
||||
|
||||
def test_inbox_delete_library(factories):
|
||||
activity = factories["federation.Activity"]()
|
||||
|
||||
|
|
@ -368,6 +398,73 @@ def test_inbox_delete_library(factories):
|
|||
library.refresh_from_db()
|
||||
|
||||
|
||||
def test_inbox_delete_album(factories):
|
||||
|
||||
album = factories["music.Album"](attributed=True)
|
||||
payload = {
|
||||
"type": "Delete",
|
||||
"actor": album.attributed_to.fid,
|
||||
"object": {"type": "Album", "id": album.fid},
|
||||
}
|
||||
|
||||
routes.inbox_delete_album(
|
||||
payload,
|
||||
context={
|
||||
"actor": album.attributed_to,
|
||||
"raise_exception": True,
|
||||
"activity": activity,
|
||||
},
|
||||
)
|
||||
|
||||
with pytest.raises(album.__class__.DoesNotExist):
|
||||
album.refresh_from_db()
|
||||
|
||||
|
||||
def test_inbox_delete_album_channel(factories):
|
||||
channel = factories["audio.Channel"]()
|
||||
album = factories["music.Album"](artist=channel.artist)
|
||||
payload = {
|
||||
"type": "Delete",
|
||||
"actor": channel.actor.fid,
|
||||
"object": {"type": "Album", "id": album.fid},
|
||||
}
|
||||
|
||||
routes.inbox_delete_album(
|
||||
payload,
|
||||
context={"actor": channel.actor, "raise_exception": True, "activity": activity},
|
||||
)
|
||||
|
||||
with pytest.raises(album.__class__.DoesNotExist):
|
||||
album.refresh_from_db()
|
||||
|
||||
|
||||
def test_outbox_delete_album(factories):
|
||||
album = factories["music.Album"](attributed=True)
|
||||
a = list(routes.outbox_delete_album({"album": album}))[0]
|
||||
expected = serializers.ActivitySerializer(
|
||||
{"type": "Delete", "object": {"type": "Album", "id": album.fid}}
|
||||
).data
|
||||
|
||||
expected["to"] = [activity.PUBLIC_ADDRESS, {"type": "instances_with_followers"}]
|
||||
|
||||
assert dict(a["payload"]) == dict(expected)
|
||||
assert a["actor"] == album.attributed_to
|
||||
|
||||
|
||||
def test_outbox_delete_album_channel(factories):
|
||||
channel = factories["audio.Channel"]()
|
||||
album = factories["music.Album"](artist=channel.artist)
|
||||
a = list(routes.outbox_delete_album({"album": album}))[0]
|
||||
expected = serializers.ActivitySerializer(
|
||||
{"type": "Delete", "object": {"type": "Album", "id": album.fid}}
|
||||
).data
|
||||
|
||||
expected["to"] = [activity.PUBLIC_ADDRESS, {"type": "instances_with_followers"}]
|
||||
|
||||
assert dict(a["payload"]) == dict(expected)
|
||||
assert a["actor"] == channel.actor
|
||||
|
||||
|
||||
def test_inbox_delete_library_impostor(factories):
|
||||
activity = factories["federation.Activity"]()
|
||||
impostor = factories["federation.Actor"]()
|
||||
|
|
@ -469,6 +566,25 @@ def test_inbox_delete_audio(factories):
|
|||
upload.refresh_from_db()
|
||||
|
||||
|
||||
def test_inbox_delete_audio_channel(factories):
|
||||
activity = factories["federation.Activity"]()
|
||||
channel = factories["audio.Channel"]()
|
||||
upload = factories["music.Upload"](track__artist=channel.artist)
|
||||
payload = {
|
||||
"type": "Delete",
|
||||
"actor": channel.actor.fid,
|
||||
"object": {"type": "Audio", "id": [upload.fid]},
|
||||
}
|
||||
|
||||
routes.inbox_delete_audio(
|
||||
payload,
|
||||
context={"actor": channel.actor, "raise_exception": True, "activity": activity},
|
||||
)
|
||||
|
||||
with pytest.raises(upload.__class__.DoesNotExist):
|
||||
upload.refresh_from_db()
|
||||
|
||||
|
||||
def test_inbox_delete_audio_impostor(factories):
|
||||
activity = factories["federation.Activity"]()
|
||||
impostor = factories["federation.Actor"]()
|
||||
|
|
|
|||
|
|
@ -795,6 +795,17 @@ def test_activity_pub_album_serializer_to_ap(factories):
|
|||
assert serializer.data == expected
|
||||
|
||||
|
||||
def test_activity_pub_album_serializer_to_ap_channel_artist(factories):
|
||||
channel = factories["audio.Channel"]()
|
||||
album = factories["music.Album"](artist=channel.artist,)
|
||||
|
||||
serializer = serializers.AlbumSerializer(album)
|
||||
|
||||
assert serializer.data["artists"] == [
|
||||
{"type": channel.actor.type, "id": channel.actor.fid}
|
||||
]
|
||||
|
||||
|
||||
def test_activity_pub_album_serializer_from_ap_create(factories, faker, now):
|
||||
actor = factories["federation.Actor"]()
|
||||
artist = factories["music.Artist"]()
|
||||
|
|
@ -836,6 +847,30 @@ def test_activity_pub_album_serializer_from_ap_create(factories, faker, now):
|
|||
]
|
||||
|
||||
|
||||
def test_activity_pub_album_serializer_from_ap_create_channel_artist(
|
||||
factories, faker, now
|
||||
):
|
||||
actor = factories["federation.Actor"]()
|
||||
channel = factories["audio.Channel"]()
|
||||
released = faker.date_object()
|
||||
payload = {
|
||||
"@context": jsonld.get_default_context(),
|
||||
"type": "Album",
|
||||
"id": "https://album.example",
|
||||
"name": faker.sentence(),
|
||||
"published": now.isoformat(),
|
||||
"released": released.isoformat(),
|
||||
"artists": [{"type": channel.actor.type, "id": channel.actor.fid}],
|
||||
"attributedTo": actor.fid,
|
||||
}
|
||||
serializer = serializers.AlbumSerializer(data=payload)
|
||||
assert serializer.is_valid(raise_exception=True) is True
|
||||
|
||||
album = serializer.save()
|
||||
|
||||
assert album.artist == channel.artist
|
||||
|
||||
|
||||
def test_activity_pub_album_serializer_from_ap_update(factories, faker):
|
||||
album = factories["music.Album"](attributed=True)
|
||||
released = faker.date_object()
|
||||
|
|
@ -1395,7 +1430,9 @@ def test_track_serializer_update_license(factories):
|
|||
|
||||
def test_channel_actor_serializer(factories):
|
||||
channel = factories["audio.Channel"](
|
||||
actor__attachment_icon=None, artist__with_cover=True
|
||||
actor__attachment_icon=None,
|
||||
artist__with_cover=True,
|
||||
artist__set_tags=["punk", "rock"],
|
||||
)
|
||||
|
||||
serializer = serializers.ActorSerializer(channel.actor)
|
||||
|
|
@ -1418,6 +1455,164 @@ def test_channel_actor_serializer(factories):
|
|||
}
|
||||
assert serializer.data["url"] == expected_url
|
||||
assert serializer.data["icon"] == expected_icon
|
||||
assert serializer.data["attributedTo"] == channel.attributed_to.fid
|
||||
assert serializer.data["category"] == channel.artist.content_category
|
||||
assert serializer.data["tag"] == [
|
||||
{"type": "Hashtag", "name": "#punk"},
|
||||
{"type": "Hashtag", "name": "#rock"},
|
||||
]
|
||||
|
||||
|
||||
def test_channel_actor_serializer_from_ap_create(mocker, factories):
|
||||
domain = factories["federation.Domain"](name="test.pod")
|
||||
attributed_to = factories["federation.Actor"](domain=domain)
|
||||
get_actor = mocker.patch.object(actors, "get_actor", return_value=attributed_to)
|
||||
actor_data = {
|
||||
"@context": jsonld.get_default_context(),
|
||||
"followers": "https://test.pod/federation/actors/mychannel/followers",
|
||||
"preferredUsername": "mychannel",
|
||||
"id": "https://test.pod/federation/actors/mychannel",
|
||||
"endpoints": {"sharedInbox": "https://test.pod/federation/shared/inbox"},
|
||||
"name": "mychannel",
|
||||
"following": "https://test.pod/federation/actors/mychannel/following",
|
||||
"outbox": "https://test.pod/federation/actors/mychannel/outbox",
|
||||
"url": [
|
||||
{
|
||||
"mediaType": "text/html",
|
||||
"href": "https://test.pod/channels/mychannel",
|
||||
"type": "Link",
|
||||
},
|
||||
{
|
||||
"mediaType": "application/rss+xml",
|
||||
"href": "https://test.pod/api/v1/channels/mychannel/rss",
|
||||
"type": "Link",
|
||||
},
|
||||
],
|
||||
"type": "Person",
|
||||
"category": "podcast",
|
||||
"attributedTo": attributed_to.fid,
|
||||
"manuallyApprovesFollowers": False,
|
||||
"inbox": "https://test.pod/federation/actors/mychannel/inbox",
|
||||
"icon": {
|
||||
"mediaType": "image/jpeg",
|
||||
"type": "Image",
|
||||
"url": "https://test.pod/media/attachments/dd/ce/b2/nosmile.jpeg",
|
||||
},
|
||||
"summary": "<p>content</p>",
|
||||
"publicKey": {
|
||||
"owner": "https://test.pod/federation/actors/mychannel",
|
||||
"publicKeyPem": "-----BEGIN RSA PUBLIC KEY-----\n+KwIDAQAB\n-----END RSA PUBLIC KEY-----\n",
|
||||
"id": "https://test.pod/federation/actors/mychannel#main-key",
|
||||
},
|
||||
"tag": [
|
||||
{"type": "Hashtag", "name": "#Indie"},
|
||||
{"type": "Hashtag", "name": "#Punk"},
|
||||
{"type": "Hashtag", "name": "#Rock"},
|
||||
],
|
||||
}
|
||||
|
||||
serializer = serializers.ActorSerializer(data=actor_data)
|
||||
assert serializer.is_valid(raise_exception=True) is True
|
||||
actor = serializer.save()
|
||||
|
||||
get_actor.assert_called_once_with(actor_data["attributedTo"])
|
||||
assert actor.preferred_username == actor_data["preferredUsername"]
|
||||
assert actor.fid == actor_data["id"]
|
||||
assert actor.name == actor_data["name"]
|
||||
assert actor.type == actor_data["type"]
|
||||
assert actor.public_key == actor_data["publicKey"]["publicKeyPem"]
|
||||
assert actor.outbox_url == actor_data["outbox"]
|
||||
assert actor.inbox_url == actor_data["inbox"]
|
||||
assert actor.shared_inbox_url == actor_data["endpoints"]["sharedInbox"]
|
||||
assert actor.channel.attributed_to == attributed_to
|
||||
assert actor.channel.rss_url == actor_data["url"][1]["href"]
|
||||
assert actor.channel.artist.attributed_to == attributed_to
|
||||
assert actor.channel.artist.content_category == actor_data["category"]
|
||||
assert actor.channel.artist.name == actor_data["name"]
|
||||
assert actor.channel.artist.get_tags() == ["Indie", "Punk", "Rock"]
|
||||
assert actor.channel.artist.description.text == actor_data["summary"]
|
||||
assert actor.channel.artist.description.content_type == "text/html"
|
||||
assert actor.channel.artist.attachment_cover.url == actor_data["icon"]["url"]
|
||||
assert (
|
||||
actor.channel.artist.attachment_cover.mimetype
|
||||
== actor_data["icon"]["mediaType"]
|
||||
)
|
||||
assert actor.channel.library.fid is not None
|
||||
assert actor.channel.library.actor == attributed_to
|
||||
assert actor.channel.library.privacy_level == "everyone"
|
||||
assert actor.channel.library.name == actor_data["name"]
|
||||
|
||||
|
||||
def test_channel_actor_serializer_from_ap_update(mocker, factories):
|
||||
domain = factories["federation.Domain"](name="test.pod")
|
||||
attributed_to = factories["federation.Actor"](domain=domain)
|
||||
actor = factories["federation.Actor"](domain=domain)
|
||||
channel = factories["audio.Channel"](actor=actor, attributed_to=attributed_to)
|
||||
get_actor = mocker.patch.object(actors, "get_actor", return_value=attributed_to)
|
||||
library = channel.library
|
||||
actor_data = {
|
||||
"@context": jsonld.get_default_context(),
|
||||
"followers": "https://test.pod/federation/actors/mychannel/followers",
|
||||
"preferredUsername": "mychannel",
|
||||
"id": actor.fid,
|
||||
"endpoints": {"sharedInbox": "https://test.pod/federation/shared/inbox"},
|
||||
"name": "mychannel",
|
||||
"following": "https://test.pod/federation/actors/mychannel/following",
|
||||
"outbox": "https://test.pod/federation/actors/mychannel/outbox",
|
||||
"url": [
|
||||
{
|
||||
"mediaType": "text/html",
|
||||
"href": "https://test.pod/channels/mychannel",
|
||||
"type": "Link",
|
||||
},
|
||||
{
|
||||
"mediaType": "application/rss+xml",
|
||||
"href": "https://test.pod/api/v1/channels/mychannel/rss",
|
||||
"type": "Link",
|
||||
},
|
||||
],
|
||||
"type": "Person",
|
||||
"category": "podcast",
|
||||
"attributedTo": attributed_to.fid,
|
||||
"manuallyApprovesFollowers": False,
|
||||
"inbox": "https://test.pod/federation/actors/mychannel/inbox",
|
||||
"icon": {
|
||||
"mediaType": "image/jpeg",
|
||||
"type": "Image",
|
||||
"url": "https://test.pod/media/attachments/dd/ce/b2/nosmile.jpeg",
|
||||
},
|
||||
"summary": "<p>content</p>",
|
||||
"publicKey": {
|
||||
"owner": "https://test.pod/federation/actors/mychannel",
|
||||
"publicKeyPem": "-----BEGIN RSA PUBLIC KEY-----\n+KwIDAQAB\n-----END RSA PUBLIC KEY-----\n",
|
||||
"id": "https://test.pod/federation/actors/mychannel#main-key",
|
||||
},
|
||||
"tag": [
|
||||
{"type": "Hashtag", "name": "#Indie"},
|
||||
{"type": "Hashtag", "name": "#Punk"},
|
||||
{"type": "Hashtag", "name": "#Rock"},
|
||||
],
|
||||
}
|
||||
|
||||
serializer = serializers.ActorSerializer(data=actor_data)
|
||||
assert serializer.is_valid(raise_exception=True) is True
|
||||
serializer.save()
|
||||
channel.refresh_from_db()
|
||||
get_actor.assert_called_once_with(actor_data["attributedTo"])
|
||||
assert channel.actor == actor
|
||||
assert channel.attributed_to == attributed_to
|
||||
assert channel.rss_url == actor_data["url"][1]["href"]
|
||||
assert channel.artist.attributed_to == attributed_to
|
||||
assert channel.artist.content_category == actor_data["category"]
|
||||
assert channel.artist.name == actor_data["name"]
|
||||
assert channel.artist.get_tags() == ["Indie", "Punk", "Rock"]
|
||||
assert channel.artist.description.text == actor_data["summary"]
|
||||
assert channel.artist.description.content_type == "text/html"
|
||||
assert channel.artist.attachment_cover.url == actor_data["icon"]["url"]
|
||||
assert channel.artist.attachment_cover.mimetype == actor_data["icon"]["mediaType"]
|
||||
assert channel.library.actor == attributed_to
|
||||
assert channel.library.privacy_level == library.privacy_level
|
||||
assert channel.library.name == library.name
|
||||
|
||||
|
||||
def test_channel_actor_outbox_serializer(factories):
|
||||
|
|
@ -1449,12 +1644,21 @@ def test_channel_actor_outbox_serializer(factories):
|
|||
def test_channel_upload_serializer(factories):
|
||||
channel = factories["audio.Channel"](library__privacy_level="everyone")
|
||||
content = factories["common.Content"]()
|
||||
cover = factories["common.Attachment"]()
|
||||
upload = factories["music.Upload"](
|
||||
playable=True,
|
||||
bitrate=543,
|
||||
size=543,
|
||||
duration=54,
|
||||
library=channel.library,
|
||||
import_status="finished",
|
||||
track__set_tags=["Punk"],
|
||||
track__attachment_cover=cover,
|
||||
track__description=content,
|
||||
track__disc_number=3,
|
||||
track__position=12,
|
||||
track__license="cc0-1.0",
|
||||
track__copyright="Copyright something",
|
||||
track__album__set_tags=["Rock"],
|
||||
track__artist__set_tags=["Indie"],
|
||||
)
|
||||
|
|
@ -1463,25 +1667,38 @@ def test_channel_upload_serializer(factories):
|
|||
"@context": jsonld.get_default_context(),
|
||||
"type": "Audio",
|
||||
"id": upload.fid,
|
||||
"name": upload.track.full_name,
|
||||
"name": upload.track.title,
|
||||
"summary": "#Indie #Punk #Rock",
|
||||
"attributedTo": channel.actor.fid,
|
||||
"published": upload.creation_date.isoformat(),
|
||||
"mediaType": "text/html",
|
||||
"content": common_utils.render_html(content.text, content.content_type),
|
||||
"to": "https://www.w3.org/ns/activitystreams#Public",
|
||||
"position": upload.track.position,
|
||||
"duration": upload.duration,
|
||||
"album": upload.track.album.fid,
|
||||
"disc": upload.track.disc_number,
|
||||
"copyright": upload.track.copyright,
|
||||
"license": upload.track.local_license["identifiers"][0],
|
||||
"url": [
|
||||
{
|
||||
"type": "Link",
|
||||
"mediaType": upload.mimetype,
|
||||
"href": utils.full_url(upload.listen_url_no_download),
|
||||
},
|
||||
{
|
||||
"type": "Link",
|
||||
"mediaType": "text/html",
|
||||
"href": utils.full_url(upload.track.get_absolute_url()),
|
||||
},
|
||||
{
|
||||
"type": "Link",
|
||||
"mediaType": upload.mimetype,
|
||||
"href": utils.full_url(upload.listen_url_no_download),
|
||||
"bitrate": upload.bitrate,
|
||||
"size": upload.size,
|
||||
},
|
||||
],
|
||||
"image": {
|
||||
"type": "Image",
|
||||
"url": upload.track.attachment_cover.download_url_original,
|
||||
"mediaType": upload.track.attachment_cover.mimetype,
|
||||
},
|
||||
"tag": [
|
||||
{"type": "Hashtag", "name": "#Indie"},
|
||||
{"type": "Hashtag", "name": "#Punk"},
|
||||
|
|
@ -1494,6 +1711,166 @@ def test_channel_upload_serializer(factories):
|
|||
assert serializer.data == expected
|
||||
|
||||
|
||||
def test_channel_upload_serializer_from_ap_create(factories, now):
|
||||
channel = factories["audio.Channel"](library__privacy_level="everyone")
|
||||
album = factories["music.Album"](artist=channel.artist)
|
||||
payload = {
|
||||
"@context": jsonld.get_default_context(),
|
||||
"type": "Audio",
|
||||
"id": "https://test.pod/uuid",
|
||||
"name": "My test track",
|
||||
"summary": "#Indie #Punk #Rock",
|
||||
"attributedTo": channel.actor.fid,
|
||||
"published": now.isoformat(),
|
||||
"mediaType": "text/html",
|
||||
"content": "<p>Hello</p>",
|
||||
"duration": 543,
|
||||
"position": 4,
|
||||
"disc": 2,
|
||||
"album": album.fid,
|
||||
"to": "https://www.w3.org/ns/activitystreams#Public",
|
||||
"copyright": "Copyright test",
|
||||
"license": "http://creativecommons.org/publicdomain/zero/1.0/",
|
||||
"url": [
|
||||
{
|
||||
"type": "Link",
|
||||
"mediaType": "text/html",
|
||||
"href": "https://test.pod/track",
|
||||
},
|
||||
{
|
||||
"type": "Link",
|
||||
"mediaType": "audio/mpeg",
|
||||
"href": "https://test.pod/file.mp3",
|
||||
"bitrate": 192000,
|
||||
"size": 15492738,
|
||||
},
|
||||
],
|
||||
"tag": [
|
||||
{"type": "Hashtag", "name": "#Indie"},
|
||||
{"type": "Hashtag", "name": "#Punk"},
|
||||
{"type": "Hashtag", "name": "#Rock"},
|
||||
],
|
||||
"image": {
|
||||
"type": "Image",
|
||||
"mediaType": "image/jpeg",
|
||||
"url": "https://image.example/image.png",
|
||||
},
|
||||
}
|
||||
|
||||
serializer = serializers.ChannelUploadSerializer(
|
||||
data=payload, context={"channel": channel}
|
||||
)
|
||||
assert serializer.is_valid(raise_exception=True) is True
|
||||
|
||||
upload = serializer.save(channel=channel)
|
||||
|
||||
assert upload.library == channel.library
|
||||
assert upload.import_status == "finished"
|
||||
assert upload.creation_date == now
|
||||
assert upload.fid == payload["id"]
|
||||
assert upload.source == payload["url"][1]["href"]
|
||||
assert upload.mimetype == payload["url"][1]["mediaType"]
|
||||
assert upload.size == payload["url"][1]["size"]
|
||||
assert upload.bitrate == payload["url"][1]["bitrate"]
|
||||
assert upload.duration == payload["duration"]
|
||||
assert upload.track.artist == channel.artist
|
||||
assert upload.track.position == payload["position"]
|
||||
assert upload.track.disc_number == payload["disc"]
|
||||
assert upload.track.attributed_to == channel.attributed_to
|
||||
assert upload.track.title == payload["name"]
|
||||
assert upload.track.creation_date == now
|
||||
assert upload.track.description.content_type == payload["mediaType"]
|
||||
assert upload.track.description.text == payload["content"]
|
||||
assert upload.track.fid == payload["id"]
|
||||
assert upload.track.license.pk == "cc0-1.0"
|
||||
assert upload.track.copyright == payload["copyright"]
|
||||
assert upload.track.get_tags() == ["Indie", "Punk", "Rock"]
|
||||
assert upload.track.attachment_cover.mimetype == payload["image"]["mediaType"]
|
||||
assert upload.track.attachment_cover.url == payload["image"]["url"]
|
||||
assert upload.track.album == album
|
||||
|
||||
|
||||
def test_channel_upload_serializer_from_ap_update(factories, now):
|
||||
channel = factories["audio.Channel"](library__privacy_level="everyone")
|
||||
album = factories["music.Album"](artist=channel.artist)
|
||||
upload = factories["music.Upload"](track__album=album, track__artist=channel.artist)
|
||||
|
||||
payload = {
|
||||
"@context": jsonld.get_default_context(),
|
||||
"type": "Audio",
|
||||
"id": upload.fid,
|
||||
"name": "Hello there",
|
||||
"attributedTo": channel.actor.fid,
|
||||
"published": now.isoformat(),
|
||||
"mediaType": "text/html",
|
||||
"content": "<p>Hello</p>",
|
||||
"duration": 543,
|
||||
"position": 4,
|
||||
"disc": 2,
|
||||
"album": album.fid,
|
||||
"to": "https://www.w3.org/ns/activitystreams#Public",
|
||||
"copyright": "Copyright test",
|
||||
"license": "http://creativecommons.org/publicdomain/zero/1.0/",
|
||||
"url": [
|
||||
{
|
||||
"type": "Link",
|
||||
"mediaType": "text/html",
|
||||
"href": "https://test.pod/track",
|
||||
},
|
||||
{
|
||||
"type": "Link",
|
||||
"mediaType": "audio/mpeg",
|
||||
"href": "https://test.pod/file.mp3",
|
||||
"bitrate": 192000,
|
||||
"size": 15492738,
|
||||
},
|
||||
],
|
||||
"tag": [
|
||||
{"type": "Hashtag", "name": "#Indie"},
|
||||
{"type": "Hashtag", "name": "#Punk"},
|
||||
{"type": "Hashtag", "name": "#Rock"},
|
||||
],
|
||||
"image": {
|
||||
"type": "Image",
|
||||
"mediaType": "image/jpeg",
|
||||
"url": "https://image.example/image.png",
|
||||
},
|
||||
}
|
||||
|
||||
serializer = serializers.ChannelUploadSerializer(
|
||||
data=payload, context={"channel": channel}
|
||||
)
|
||||
assert serializer.is_valid(raise_exception=True) is True
|
||||
|
||||
serializer.save(channel=channel)
|
||||
upload.refresh_from_db()
|
||||
|
||||
assert upload.library == channel.library
|
||||
assert upload.import_status == "finished"
|
||||
assert upload.creation_date == now
|
||||
assert upload.fid == payload["id"]
|
||||
assert upload.source == payload["url"][1]["href"]
|
||||
assert upload.mimetype == payload["url"][1]["mediaType"]
|
||||
assert upload.size == payload["url"][1]["size"]
|
||||
assert upload.bitrate == payload["url"][1]["bitrate"]
|
||||
assert upload.duration == payload["duration"]
|
||||
assert upload.track.artist == channel.artist
|
||||
assert upload.track.position == payload["position"]
|
||||
assert upload.track.disc_number == payload["disc"]
|
||||
assert upload.track.attributed_to == channel.attributed_to
|
||||
assert upload.track.title == payload["name"]
|
||||
assert upload.track.creation_date == now
|
||||
assert upload.track.description.content_type == payload["mediaType"]
|
||||
assert upload.track.description.text == payload["content"]
|
||||
assert upload.track.fid == payload["id"]
|
||||
assert upload.track.license.pk == "cc0-1.0"
|
||||
assert upload.track.copyright == payload["copyright"]
|
||||
assert upload.track.get_tags() == ["Indie", "Punk", "Rock"]
|
||||
assert upload.track.attachment_cover.mimetype == payload["image"]["mediaType"]
|
||||
assert upload.track.attachment_cover.url == payload["image"]["url"]
|
||||
assert upload.track.album == album
|
||||
|
||||
|
||||
def test_channel_create_upload_serializer(factories):
|
||||
channel = factories["audio.Channel"]()
|
||||
upload = factories["music.Upload"](
|
||||
|
|
|
|||
|
|
@ -491,6 +491,21 @@ def test_fetch_url(factory_name, serializer_class, factories, r_mock, mocker):
|
|||
assert save.call_count == 1
|
||||
|
||||
|
||||
def test_fetch_channel_actor_returns_channel(factories, r_mock):
|
||||
obj = factories["audio.Channel"]()
|
||||
fetch = factories["federation.Fetch"](url=obj.actor.fid)
|
||||
payload = serializers.ActorSerializer(obj.actor).data
|
||||
|
||||
r_mock.get(obj.fid, json=payload)
|
||||
|
||||
tasks.fetch(fetch_id=fetch.pk)
|
||||
|
||||
fetch.refresh_from_db()
|
||||
|
||||
assert fetch.status == "finished"
|
||||
assert fetch.object == obj
|
||||
|
||||
|
||||
def test_fetch_honor_instance_policy_domain(factories):
|
||||
domain = factories["moderation.InstancePolicy"](
|
||||
block_all=True, for_domain=True
|
||||
|
|
|
|||
|
|
@ -1407,7 +1407,8 @@ def test_channel_owner_can_create_album(factories, logged_in_api_client):
|
|||
assert album.description.text == "hello world"
|
||||
|
||||
|
||||
def test_channel_owner_can_delete_album(factories, logged_in_api_client):
|
||||
def test_channel_owner_can_delete_album(factories, logged_in_api_client, mocker):
|
||||
dispatch = mocker.patch("funkwhale_api.federation.routes.outbox.dispatch")
|
||||
actor = logged_in_api_client.user.create_actor()
|
||||
channel = factories["audio.Channel"](attributed_to=actor)
|
||||
album = factories["music.Album"](artist=channel.artist)
|
||||
|
|
@ -1416,6 +1417,10 @@ def test_channel_owner_can_delete_album(factories, logged_in_api_client):
|
|||
response = logged_in_api_client.delete(url)
|
||||
|
||||
assert response.status_code == 204
|
||||
|
||||
dispatch.assert_called_once_with(
|
||||
{"type": "Delete", "object": {"type": "Album"}}, context={"album": album}
|
||||
)
|
||||
with pytest.raises(album.DoesNotExist):
|
||||
album.refresh_from_db()
|
||||
|
||||
|
|
@ -1452,15 +1457,22 @@ def test_other_user_cannot_delete_album(factories, logged_in_api_client):
|
|||
album.refresh_from_db()
|
||||
|
||||
|
||||
def test_channel_owner_can_delete_track(factories, logged_in_api_client):
|
||||
def test_channel_owner_can_delete_track(factories, logged_in_api_client, mocker):
|
||||
dispatch = mocker.patch("funkwhale_api.federation.routes.outbox.dispatch")
|
||||
actor = logged_in_api_client.user.create_actor()
|
||||
channel = factories["audio.Channel"](attributed_to=actor)
|
||||
track = factories["music.Track"](artist=channel.artist)
|
||||
upload1 = factories["music.Upload"](track=track)
|
||||
upload2 = factories["music.Upload"](track=track)
|
||||
url = reverse("api:v1:tracks-detail", kwargs={"pk": track.pk})
|
||||
|
||||
response = logged_in_api_client.delete(url)
|
||||
|
||||
assert response.status_code == 204
|
||||
dispatch.assert_called_once_with(
|
||||
{"type": "Delete", "object": {"type": "Audio"}},
|
||||
context={"uploads": [upload1, upload2]},
|
||||
)
|
||||
with pytest.raises(track.DoesNotExist):
|
||||
track.refresh_from_db()
|
||||
|
||||
|
|
|
|||
|
|
@ -7,9 +7,10 @@ from funkwhale_api.users import tasks
|
|||
def test_delete_account(factories, mocker):
|
||||
user = factories["users.User"]()
|
||||
actor = user.create_actor()
|
||||
factories["federation.Follow"](target=actor, approved=True)
|
||||
library = factories["music.Library"](actor=actor)
|
||||
unrelated_library = factories["music.Library"]()
|
||||
dispatch = mocker.patch.object(routes.outbox, "dispatch")
|
||||
dispatch = mocker.spy(routes.outbox, "dispatch")
|
||||
|
||||
tasks.delete_account(user_id=user.pk)
|
||||
|
||||
|
|
@ -30,3 +31,5 @@ def test_delete_account(factories, mocker):
|
|||
assert actor.type == "Tombstone"
|
||||
assert actor.name is None
|
||||
assert actor.summary is None
|
||||
# this activity shouldn't be deleted
|
||||
assert actor.outbox_activities.filter(type="Delete").count() == 1
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue