Attachments

This commit is contained in:
Eliot Berriot 2019-11-25 09:49:06 +01:00
parent 421b441dbe
commit c84396e669
50 changed files with 879 additions and 261 deletions

View file

@ -46,3 +46,28 @@ def test_get_moderation_url(factory_name, factories, expected):
obj = factories[factory_name]()
assert obj.get_moderation_url() == expected.format(obj=obj)
def test_attachment(factories, now):
attachment = factories["common.Attachment"]()
assert attachment.uuid is not None
assert attachment.mimetype == "image/jpeg"
assert attachment.file is not None
assert attachment.url is not None
assert attachment.actor is not None
assert attachment.creation_date > now
assert attachment.last_fetch_date is None
assert attachment.size > 0
@pytest.mark.parametrize("args, expected", [([], [0]), ([True], [0]), ([False], [1])])
def test_attachment_queryset_attached(args, expected, factories, queryset_equal_list):
attachments = [
factories["music.Album"]().attachment_cover,
factories["common.Attachment"](),
]
queryset = attachments[0].__class__.objects.attached(*args).order_by("id")
expected_objs = [attachments[i] for i in expected]
assert queryset == expected_objs

View file

@ -2,11 +2,13 @@ import os
import PIL
from django.core.files.uploadedfile import SimpleUploadedFile
from django.urls import reverse
import django_filters
from funkwhale_api.common import serializers
from funkwhale_api.users import models
from funkwhale_api.federation import utils as federation_utils
class TestActionFilterSet(django_filters.FilterSet):
@ -182,3 +184,71 @@ def test_strip_exif_field():
cleaned = PIL.Image.open(field.to_internal_value(uploaded))
assert cleaned._getexif() is None
def test_attachment_serializer_existing_file(factories, to_api_date):
attachment = factories["common.Attachment"]()
expected = {
"uuid": str(attachment.uuid),
"size": attachment.size,
"mimetype": attachment.mimetype,
"creation_date": to_api_date(attachment.creation_date),
"urls": {
"source": attachment.url,
"original": federation_utils.full_url(attachment.file.url),
"medium_square_crop": federation_utils.full_url(
attachment.file.crop["200x200"].url
),
},
# XXX: BACKWARD COMPATIBILITY
"original": federation_utils.full_url(attachment.file.url),
"medium_square_crop": federation_utils.full_url(
attachment.file.crop["200x200"].url
),
"small_square_crop": federation_utils.full_url(
attachment.file.crop["200x200"].url
),
"square_crop": federation_utils.full_url(attachment.file.crop["200x200"].url),
}
serializer = serializers.AttachmentSerializer(attachment)
assert serializer.data == expected
def test_attachment_serializer_remote_file(factories, to_api_date):
attachment = factories["common.Attachment"](file=None)
proxy_url = reverse("api:v1:attachments-proxy", kwargs={"uuid": attachment.uuid})
expected = {
"uuid": str(attachment.uuid),
"size": attachment.size,
"mimetype": attachment.mimetype,
"creation_date": to_api_date(attachment.creation_date),
# everything is the same, except for the urls field because:
# - the file isn't available on the local pod
# - we need to return different URLs so that the client can trigger
# a fetch and get redirected to the desired version
#
"urls": {
"source": attachment.url,
"original": federation_utils.full_url(proxy_url + "?next=original"),
"medium_square_crop": federation_utils.full_url(
proxy_url + "?next=medium_square_crop"
),
},
# XXX: BACKWARD COMPATIBILITY
"original": federation_utils.full_url(proxy_url + "?next=original"),
"medium_square_crop": federation_utils.full_url(
proxy_url + "?next=medium_square_crop"
),
"square_crop": federation_utils.full_url(
proxy_url + "?next=medium_square_crop"
),
"small_square_crop": federation_utils.full_url(
proxy_url + "?next=medium_square_crop"
),
}
serializer = serializers.AttachmentSerializer(attachment)
assert serializer.data == expected

View file

@ -1,4 +1,5 @@
import pytest
import datetime
from funkwhale_api.common import serializers
from funkwhale_api.common import signals
@ -63,3 +64,25 @@ def test_cannot_apply_already_applied_migration(factories):
mutation = factories["common.Mutation"](payload={}, is_applied=True)
with pytest.raises(mutation.__class__.DoesNotExist):
tasks.apply_mutation(mutation_id=mutation.pk)
def test_prune_unattached_attachments(factories, settings, now):
settings.ATTACHMENTS_UNATTACHED_PRUNE_DELAY = 5
attachments = [
# attached, kept
factories["music.Album"]().attachment_cover,
# recent, kept
factories["common.Attachment"](),
# too old, pruned
factories["common.Attachment"](
creation_date=now
- datetime.timedelta(seconds=settings.ATTACHMENTS_UNATTACHED_PRUNE_DELAY)
),
]
tasks.prune_unattached_attachments()
attachments[0].refresh_from_db()
attachments[1].refresh_from_db()
with pytest.raises(attachments[2].DoesNotExist):
attachments[2].refresh_from_db()

View file

@ -1,4 +1,6 @@
import io
import pytest
from django.urls import reverse
from funkwhale_api.common import serializers
@ -181,3 +183,69 @@ def test_rate_limit(logged_in_api_client, now_time, settings, mocker):
assert response.status_code == 200
assert response.data == expected
get_status.assert_called_once_with(expected_ident, now_time)
@pytest.mark.parametrize(
"next, expected",
[
("original", "original"),
("medium_square_crop", "medium_square_crop"),
("unknown", "original"),
],
)
def test_attachment_proxy_redirects_original(
next, expected, factories, logged_in_api_client, mocker, avatar, r_mock, now
):
attachment = factories["common.Attachment"](file=None)
avatar_content = avatar.read()
fetch_remote_attachment = mocker.spy(tasks, "fetch_remote_attachment")
m = r_mock.get(attachment.url, body=io.BytesIO(avatar_content))
proxy_url = reverse("api:v1:attachments-proxy", kwargs={"uuid": attachment.uuid})
response = logged_in_api_client.get(proxy_url, {"next": next})
attachment.refresh_from_db()
urls = serializers.AttachmentSerializer(attachment).data["urls"]
assert attachment.file.read() == avatar_content
assert attachment.last_fetch_date == now
fetch_remote_attachment.assert_called_once_with(attachment)
assert len(m.request_history) == 1
assert response.status_code == 302
assert response["Location"] == urls[expected]
def test_attachment_create(logged_in_api_client, avatar):
actor = logged_in_api_client.user.create_actor()
url = reverse("api:v1:attachments-list")
content = avatar.read()
avatar.seek(0)
payload = {"file": avatar}
response = logged_in_api_client.post(url, payload)
assert response.status_code == 201
attachment = actor.attachments.latest("id")
assert attachment.file.read() == content
assert attachment.file.size == len(content)
def test_attachment_destroy(factories, logged_in_api_client):
actor = logged_in_api_client.user.create_actor()
attachment = factories["common.Attachment"](actor=actor)
url = reverse("api:v1:attachments-detail", kwargs={"uuid": attachment.uuid})
response = logged_in_api_client.delete(url)
assert response.status_code == 204
with pytest.raises(attachment.DoesNotExist):
attachment.refresh_from_db()
def test_attachment_destroy_not_owner(factories, logged_in_api_client):
logged_in_api_client.user.create_actor()
attachment = factories["common.Attachment"]()
url = reverse("api:v1:attachments-detail", kwargs={"uuid": attachment.uuid})
response = logged_in_api_client.delete(url)
assert response.status_code == 403
attachment.refresh_from_db()

View file

@ -589,7 +589,7 @@ def test_activity_pub_album_serializer_to_ap(factories):
"cover": {
"type": "Link",
"mediaType": "image/jpeg",
"href": utils.full_url(album.cover.url),
"href": utils.full_url(album.attachment_cover.file.url),
},
"musicbrainzId": album.mbid,
"published": album.creation_date.isoformat(),
@ -729,8 +729,8 @@ def test_activity_pub_track_serializer_from_ap(factories, r_mock, mocker):
assert str(track.mbid) == data["musicbrainzId"]
assert album.from_activity == activity
assert album.cover.read() == b"coucou"
assert album.cover_path.endswith(".png")
assert album.attachment_cover.file.read() == b"coucou"
assert album.attachment_cover.file.path.endswith(".png")
assert album.title == data["album"]["name"]
assert album.fid == data["album"]["id"]
assert str(album.mbid) == data["album"]["musicbrainzId"]

View file

@ -1,7 +1,8 @@
import pytest
from funkwhale_api.manage import serializers
from funkwhale_api.common import serializers as common_serializers
from funkwhale_api.federation import tasks as federation_tasks
from funkwhale_api.manage import serializers
def test_manage_upload_action_delete(factories):
@ -339,12 +340,7 @@ def test_manage_nested_album_serializer(factories, now, to_api_date):
"mbid": album.mbid,
"creation_date": to_api_date(album.creation_date),
"release_date": album.release_date.isoformat(),
"cover": {
"original": album.cover.url,
"square_crop": album.cover.crop["400x400"].url,
"medium_square_crop": album.cover.crop["200x200"].url,
"small_square_crop": album.cover.crop["50x50"].url,
},
"cover": common_serializers.AttachmentSerializer(album.attachment_cover).data,
"tracks_count": 44,
}
s = serializers.ManageNestedAlbumSerializer(album)
@ -380,12 +376,7 @@ def test_manage_album_serializer(factories, now, to_api_date):
"mbid": album.mbid,
"creation_date": to_api_date(album.creation_date),
"release_date": album.release_date.isoformat(),
"cover": {
"original": album.cover.url,
"square_crop": album.cover.crop["400x400"].url,
"medium_square_crop": album.cover.crop["200x200"].url,
"small_square_crop": album.cover.crop["50x50"].url,
},
"cover": common_serializers.AttachmentSerializer(album.attachment_cover).data,
"artist": serializers.ManageNestedArtistSerializer(album.artist).data,
"tracks": [serializers.ManageNestedTrackSerializer(track).data],
"attributed_to": serializers.ManageBaseActorSerializer(

View file

@ -192,7 +192,7 @@ def test_album_get_image_content(factories):
album.get_image(data={"content": b"test", "mimetype": "image/jpeg"})
album.refresh_from_db()
assert album.cover.read() == b"test"
assert album.attachment_cover.file.read() == b"test"
def test_library(factories):

View file

@ -132,11 +132,11 @@ def test_can_download_image_file_for_album(binary_cover, mocker, factories):
album.get_image()
album.save()
assert album.cover.file.read() == binary_cover
assert album.attachment_cover.file.read() == binary_cover
def test_album_get_image_doesnt_crash_with_empty_data(mocker, factories):
album = factories["music.Album"](mbid=None, cover=None)
album = factories["music.Album"](mbid=None, attachment_cover=None)
assert (
album.get_image(data={"content": "", "url": "", "mimetype": "image/png"})
is None

View file

@ -1,5 +1,6 @@
import pytest
from funkwhale_api.common import serializers as common_serializers
from funkwhale_api.federation import serializers as federation_serializers
from funkwhale_api.music import licenses
from funkwhale_api.music import models
@ -42,12 +43,7 @@ def test_artist_album_serializer(factories, to_api_date):
"creation_date": to_api_date(album.creation_date),
"tracks_count": 1,
"is_playable": None,
"cover": {
"original": album.cover.url,
"square_crop": album.cover.crop["400x400"].url,
"medium_square_crop": album.cover.crop["200x200"].url,
"small_square_crop": album.cover.crop["50x50"].url,
},
"cover": common_serializers.AttachmentSerializer(album.attachment_cover).data,
"release_date": to_api_date(album.release_date),
"is_local": album.is_local,
}
@ -172,12 +168,7 @@ def test_album_serializer(factories, to_api_date):
"artist": serializers.serialize_artist_simple(album.artist),
"creation_date": to_api_date(album.creation_date),
"is_playable": False,
"cover": {
"original": album.cover.url,
"square_crop": album.cover.crop["400x400"].url,
"medium_square_crop": album.cover.crop["200x200"].url,
"small_square_crop": album.cover.crop["50x50"].url,
},
"cover": common_serializers.AttachmentSerializer(album.attachment_cover).data,
"release_date": to_api_date(album.release_date),
"tracks": [serializers.serialize_album_track(t) for t in [track2, track1]],
"is_local": album.is_local,
@ -189,6 +180,15 @@ def test_album_serializer(factories, to_api_date):
assert serializer.data == expected
def test_album_serializer_empty_cover(factories, to_api_date):
# XXX: BACKWARD COMPATIBILITY
album = factories["music.Album"](attachment_cover=None)
serializer = serializers.AlbumSerializer(album)
assert serializer.data["cover"] == {}
def test_track_serializer(factories, to_api_date):
actor = factories["federation.Actor"]()
upload = factories["music.Upload"](

View file

@ -49,9 +49,7 @@ def test_library_track(spa_html, no_api_auth, client, factories, settings):
{
"tag": "meta",
"property": "og:image",
"content": utils.join_url(
settings.FUNKWHALE_URL, track.album.cover.crop["400x400"].url
),
"content": track.album.attachment_cover.download_url_medium_square_crop,
},
{
"tag": "meta",
@ -116,9 +114,7 @@ def test_library_album(spa_html, no_api_auth, client, factories, settings):
{
"tag": "meta",
"property": "og:image",
"content": utils.join_url(
settings.FUNKWHALE_URL, album.cover.crop["400x400"].url
),
"content": album.attachment_cover.download_url_medium_square_crop,
},
{
"tag": "link",
@ -166,9 +162,7 @@ def test_library_artist(spa_html, no_api_auth, client, factories, settings):
{
"tag": "meta",
"property": "og:image",
"content": utils.join_url(
settings.FUNKWHALE_URL, album.cover.crop["400x400"].url
),
"content": album.attachment_cover.download_url_medium_square_crop,
},
{
"tag": "link",
@ -217,9 +211,7 @@ def test_library_playlist(spa_html, no_api_auth, client, factories, settings):
{
"tag": "meta",
"property": "og:image",
"content": utils.join_url(
settings.FUNKWHALE_URL, track.album.cover.crop["400x400"].url
),
"content": track.album.attachment_cover.download_url_medium_square_crop,
},
{
"tag": "link",

View file

@ -285,8 +285,11 @@ def test_can_create_track_from_file_metadata_federation(factories, mocker, r_moc
assert track.fid == metadata["fid"]
assert track.creation_date == metadata["fdate"]
assert track.position == 4
assert track.album.cover.read() == b"coucou"
assert track.album.cover_path.endswith(".png")
assert track.album.attachment_cover.file.read() == b"coucou"
assert track.album.attachment_cover.file.path.endswith(".png")
assert track.album.attachment_cover.url == metadata["cover_data"]["url"]
assert track.album.attachment_cover.mimetype == metadata["cover_data"]["mimetype"]
assert track.album.fid == metadata["album"]["fid"]
assert track.album.title == metadata["album"]["title"]
assert track.album.creation_date == metadata["album"]["fdate"]
@ -312,7 +315,7 @@ def test_upload_import(now, factories, temp_signal, mocker):
update_album_cover = mocker.patch("funkwhale_api.music.tasks.update_album_cover")
get_picture = mocker.patch("funkwhale_api.music.metadata.Metadata.get_picture")
get_track_from_import_metadata = mocker.spy(tasks, "get_track_from_import_metadata")
track = factories["music.Track"](album__cover="")
track = factories["music.Track"](album__attachment_cover=None)
upload = factories["music.Upload"](
track=None, import_metadata={"funkwhale": {"track": {"uuid": str(track.uuid)}}}
)
@ -531,7 +534,7 @@ def test_upload_import_error_metadata(factories, now, temp_signal, mocker):
def test_upload_import_updates_cover_if_no_cover(factories, mocker, now):
mocked_update = mocker.patch("funkwhale_api.music.tasks.update_album_cover")
album = factories["music.Album"](cover="")
album = factories["music.Album"](attachment_cover=None)
track = factories["music.Track"](album=album)
upload = factories["music.Upload"](
track=None, import_metadata={"funkwhale": {"track": {"uuid": track.uuid}}}
@ -541,7 +544,7 @@ def test_upload_import_updates_cover_if_no_cover(factories, mocker, now):
def test_update_album_cover_mbid(factories, mocker):
album = factories["music.Album"](cover="")
album = factories["music.Album"](attachment_cover=None)
mocked_get = mocker.patch("funkwhale_api.music.models.Album.get_image")
tasks.update_album_cover(album=album)
@ -550,7 +553,7 @@ def test_update_album_cover_mbid(factories, mocker):
def test_update_album_cover_file_data(factories, mocker):
album = factories["music.Album"](cover="", mbid=None)
album = factories["music.Album"](attachment_cover=None, mbid=None)
mocked_get = mocker.patch("funkwhale_api.music.models.Album.get_image")
tasks.update_album_cover(album=album, cover_data={"hello": "world"})
@ -563,7 +566,7 @@ def test_update_album_cover_file_cover_separate_file(ext, mimetype, factories, m
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)
album = factories["music.Album"](attachment_cover=None, mbid=None)
mocked_get = mocker.patch("funkwhale_api.music.models.Album.get_image")
mocker.patch("funkwhale_api.music.metadata.Metadata.get_picture", return_value=None)

View file

@ -780,10 +780,10 @@ def test_oembed_track(factories, no_api_auth, api_client, settings):
"title": "{} by {}".format(track.title, track.artist.name),
"description": track.full_name,
"thumbnail_url": federation_utils.full_url(
track.album.cover.crop["400x400"].url
track.album.attachment_cover.file.crop["200x200"].url
),
"thumbnail_height": 400,
"thumbnail_width": 400,
"thumbnail_height": 200,
"thumbnail_width": 200,
"html": '<iframe width="600" height="150" scrolling="no" frameborder="no" src="{}"></iframe>'.format(
iframe_src
),
@ -815,9 +815,11 @@ def test_oembed_album(factories, no_api_auth, api_client, settings):
"width": 600,
"title": "{} by {}".format(album.title, album.artist.name),
"description": "{} by {}".format(album.title, album.artist.name),
"thumbnail_url": federation_utils.full_url(album.cover.crop["400x400"].url),
"thumbnail_height": 400,
"thumbnail_width": 400,
"thumbnail_url": federation_utils.full_url(
album.attachment_cover.file.crop["200x200"].url
),
"thumbnail_height": 200,
"thumbnail_width": 200,
"html": '<iframe width="600" height="400" scrolling="no" frameborder="no" src="{}"></iframe>'.format(
iframe_src
),
@ -850,9 +852,11 @@ def test_oembed_artist(factories, no_api_auth, api_client, settings):
"width": 600,
"title": artist.name,
"description": artist.name,
"thumbnail_url": federation_utils.full_url(album.cover.crop["400x400"].url),
"thumbnail_height": 400,
"thumbnail_width": 400,
"thumbnail_url": federation_utils.full_url(
album.attachment_cover.file.crop["200x200"].url
),
"thumbnail_height": 200,
"thumbnail_width": 200,
"html": '<iframe width="600" height="400" scrolling="no" frameborder="no" src="{}"></iframe>'.format(
iframe_src
),
@ -886,10 +890,10 @@ def test_oembed_playlist(factories, no_api_auth, api_client, settings):
"title": playlist.name,
"description": playlist.name,
"thumbnail_url": federation_utils.full_url(
track.album.cover.crop["400x400"].url
track.album.attachment_cover.file.crop["200x200"].url
),
"thumbnail_height": 400,
"thumbnail_width": 400,
"thumbnail_height": 200,
"thumbnail_width": 200,
"html": '<iframe width="600" height="400" scrolling="no" frameborder="no" src="{}"></iframe>'.format(
iframe_src
),

View file

@ -95,7 +95,7 @@ def test_playlist_serializer_include_covers(factories, api_request):
playlist = factories["playlists.Playlist"]()
t1 = factories["music.Track"]()
t2 = factories["music.Track"]()
t3 = factories["music.Track"](album__cover=None)
t3 = factories["music.Track"](album__attachment_cover=None)
t4 = factories["music.Track"]()
t5 = factories["music.Track"]()
t6 = factories["music.Track"]()
@ -106,11 +106,11 @@ def test_playlist_serializer_include_covers(factories, api_request):
qs = playlist.__class__.objects.with_covers().with_tracks_count()
expected = [
request.build_absolute_uri(t1.album.cover.crop["200x200"].url),
request.build_absolute_uri(t2.album.cover.crop["200x200"].url),
request.build_absolute_uri(t4.album.cover.crop["200x200"].url),
request.build_absolute_uri(t5.album.cover.crop["200x200"].url),
request.build_absolute_uri(t6.album.cover.crop["200x200"].url),
t1.album.attachment_cover.download_url_medium_square_crop,
t2.album.attachment_cover.download_url_medium_square_crop,
t4.album.attachment_cover.download_url_medium_square_crop,
t5.album.attachment_cover.download_url_medium_square_crop,
t6.album.attachment_cover.download_url_medium_square_crop,
]
serializer = serializers.PlaylistSerializer(qs.get(), context={"request": request})

View file

@ -724,7 +724,7 @@ def test_get_cover_art_album(factories, logged_in_api_client):
assert response.status_code == 200
assert response["Content-Type"] == ""
assert response["X-Accel-Redirect"] == music_views.get_file_path(
album.cover
album.attachment_cover.file
).decode("utf-8")