Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision

Target

Select target project
  • funkwhale/funkwhale
  • Luclu7/funkwhale
  • mbothorel/funkwhale
  • EorlBruder/funkwhale
  • tcit/funkwhale
  • JocelynDelalande/funkwhale
  • eneiluj/funkwhale
  • reg/funkwhale
  • ButterflyOfFire/funkwhale
  • m4sk1n/funkwhale
  • wxcafe/funkwhale
  • andybalaam/funkwhale
  • jcgruenhage/funkwhale
  • pblayo/funkwhale
  • joshuaboniface/funkwhale
  • n3ddy/funkwhale
  • gegeweb/funkwhale
  • tohojo/funkwhale
  • emillumine/funkwhale
  • Te-k/funkwhale
  • asaintgenis/funkwhale
  • anoadragon453/funkwhale
  • Sakada/funkwhale
  • ilianaw/funkwhale
  • l4p1n/funkwhale
  • pnizet/funkwhale
  • dante383/funkwhale
  • interfect/funkwhale
  • akhardya/funkwhale
  • svfusion/funkwhale
  • noplanman/funkwhale
  • nykopol/funkwhale
  • roipoussiere/funkwhale
  • Von/funkwhale
  • aurieh/funkwhale
  • icaria36/funkwhale
  • floreal/funkwhale
  • paulwalko/funkwhale
  • comradekingu/funkwhale
  • FurryJulie/funkwhale
  • Legolars99/funkwhale
  • Vierkantor/funkwhale
  • zachhats/funkwhale
  • heyjake/funkwhale
  • sn0w/funkwhale
  • jvoisin/funkwhale
  • gordon/funkwhale
  • Alexander/funkwhale
  • bignose/funkwhale
  • qasim.ali/funkwhale
  • fakegit/funkwhale
  • Kxze/funkwhale
  • stenstad/funkwhale
  • creak/funkwhale
  • Kaze/funkwhale
  • Tixie/funkwhale
  • IISergII/funkwhale
  • lfuelling/funkwhale
  • nhaddag/funkwhale
  • yoasif/funkwhale
  • ifischer/funkwhale
  • keslerm/funkwhale
  • flupe/funkwhale
  • petitminion/funkwhale
  • ariasuni/funkwhale
  • ollie/funkwhale
  • ngaumont/funkwhale
  • techknowlogick/funkwhale
  • Shleeble/funkwhale
  • theflyingfrog/funkwhale
  • jonatron/funkwhale
  • neobrain/funkwhale
  • eorn/funkwhale
  • KokaKiwi/funkwhale
  • u1-liquid/funkwhale
  • marzzzello/funkwhale
  • sirenwatcher/funkwhale
  • newer027/funkwhale
  • codl/funkwhale
  • Zwordi/funkwhale
  • gisforgabriel/funkwhale
  • iuriatan/funkwhale
  • simon/funkwhale
  • bheesham/funkwhale
  • zeoses/funkwhale
  • accraze/funkwhale
  • meliurwen/funkwhale
  • divadsn/funkwhale
  • Etua/funkwhale
  • sdrik/funkwhale
  • Soran/funkwhale
  • kuba-orlik/funkwhale
  • cristianvogel/funkwhale
  • Forceu/funkwhale
  • jeff/funkwhale
  • der_scheibenhacker/funkwhale
  • owlnical/funkwhale
  • jovuit/funkwhale
  • SilverFox15/funkwhale
  • phw/funkwhale
  • mayhem/funkwhale
  • sridhar/funkwhale
  • stromlin/funkwhale
  • rrrnld/funkwhale
  • nitaibezerra/funkwhale
  • jaller94/funkwhale
  • pcouy/funkwhale
  • eduxstad/funkwhale
  • codingHahn/funkwhale
  • captain/funkwhale
  • polyedre/funkwhale
  • leishenailong/funkwhale
  • ccritter/funkwhale
  • lnceballosz/funkwhale
  • fpiesche/funkwhale
  • Fanyx/funkwhale
  • markusblogde/funkwhale
  • Firobe/funkwhale
  • devilcius/funkwhale
  • freaktechnik/funkwhale
  • blopware/funkwhale
  • cone/funkwhale
  • thanksd/funkwhale
  • vachan-maker/funkwhale
  • bbenti/funkwhale
  • tarator/funkwhale
  • prplecake/funkwhale
  • DMarzal/funkwhale
  • lullis/funkwhale
  • hanacgr/funkwhale
  • albjeremias/funkwhale
  • xeruf/funkwhale
  • llelite/funkwhale
  • RoiArthurB/funkwhale
  • cloo/funkwhale
  • nztvar/funkwhale
  • Keunes/funkwhale
  • petitminion/funkwhale-petitminion
  • m-idler/funkwhale
  • SkyLeite/funkwhale
140 results
Select Git revision
Show changes
Showing
with 352 additions and 44 deletions
from uuid import uuid4
import pytest
from django.conf import settings
from django.utils.timezone import now
# this test is commented since it's very slow, but it can be useful for future development
......@@ -83,7 +84,7 @@ def test_artist_credit_migration(migrator):
def test_migrate_libraries_to_playlist(migrator):
music_initial_migration = (
"music",
"0059_remove_album_artist_remove_track_artist_artistcredit_and_more",
"0060_empty_for_test",
)
music_final_migration = ("music", "0061_migrate_libraries_to_playlist")
......@@ -102,23 +103,45 @@ def test_migrate_libraries_to_playlist(migrator):
Track = music_apps.get_model("music", "Track")
Library = music_apps.get_model("music", "Library")
Upload = music_apps.get_model("music", "Upload")
Playlist = music_apps.get_model("playlists", "Playlist")
# Create data
d = settings.FEDERATION_HOSTNAME
domain = Domain.objects.create()
domain2 = Domain.objects.create(pk=2)
actor = Actor.objects.create(name="Test Actor", domain=domain)
existing_urls = Actor.objects.values_list("fid", flat=True)
print(existing_urls)
actor = Actor.objects.create(name="Test Actor", domain=domain, fid=f"http://{d}/")
target_actor = Actor.objects.create(
name="Test Actor 2", domain=domain2, fid="http://test2.com/superduniquemanonmam"
name="Test Actor 2",
domain=domain2,
fid="http://test2.com/superduniquemanonmam",
)
library = Library.objects.create(
name="This should becane playlist name",
actor=actor,
creation_date=now(),
privacy_level="everyone",
uuid=uuid4(),
description="This is a description",
)
Library.objects.create(
name="me",
actor=actor,
creation_date=now(),
privacy_level="me",
uuid=uuid4(),
description="This is a description",
fid=f"http://{d}/mylocallob",
)
library_not_local = Library.objects.create(
name="This should not becane playlist name",
fid="https://asupernotlocal.acab/federation/music/libraries/8505207e-45da-449a-9ec8-ed12a848fcea",
actor=target_actor,
creation_date=now(),
privacy_level="everyone",
uuid=uuid4(),
description="This is a description recalling to eat the rich",
)
Track.objects.create()
......@@ -133,6 +156,33 @@ def test_migrate_libraries_to_playlist(migrator):
Upload.objects.create(library=library, track=track3),
]
Upload.objects.create(library=library_not_local, track=track),
Upload.objects.create(library=library_not_local, track=track2),
Upload.objects.create(library=library_not_local, track=track3),
# Plt = music_apps.get_model("playlists", "PlaylistTrack")
# playlist = Playlist.objects.create(
# name="This should becane a library name",
# fid="https://asupernotlocal.acab/federation/music/playlist/8505207e-45da-449a-9ec8-ed12a848fcea",
# actor=actor,
# creation_date=now(),
# privacy_level="everyone",
# uuid=uuid4(),
# )
# playlist_not_local = Playlist.objects.create(
# name="This should not becane a library name",
# actor=target_actor,
# creation_date=now(),
# privacy_level="everyone",
# uuid=uuid4(),
# )
# Plt.objects.create(playlist=playlist, track=track)
# Plt.objects.create(
# playlist=playlist_not_local,
# track=track,
# fid="https://asupernotlocal.acab/federation/music/playlistttrack/8505207e-",
# )
library_follow = LibraryFollow.objects.create(
uuid=uuid4(),
target=library,
......@@ -149,9 +199,8 @@ def test_migrate_libraries_to_playlist(migrator):
new_apps = migrator.loader.project_state([music_final_migration]).apps
Playlist = new_apps.get_model("playlists", "Playlist")
PlaylistTrack = new_apps.get_model("playlists", "PlaylistTrack")
Follow = new_apps.get_model("federation", "Follow")
LibraryFollow = new_apps.get_model("federation", "LibraryFollow")
Follow = new_apps.get_model("federation", "Follow")
Library = new_apps.get_model("music", "Library")
# Assertions
......@@ -163,21 +212,43 @@ def test_migrate_libraries_to_playlist(migrator):
assert playlist.privacy_level == library.privacy_level
assert playlist.description == library.description
# Verify Playlist me creation skipped
assert not Playlist.objects.filter(name="me")
assert playlist.actor.libraries.filter(name="me").count() == 1
# Verify PlaylistTrack creation
playlist_tracks = PlaylistTrack.objects.filter(playlist=playlist).order_by("index")
assert playlist_tracks.count() == 3
for i, playlist_track in enumerate(playlist_tracks):
assert playlist_track.track.pk == uploads[i].track.pk
# Verify User Follow creation
follow = Follow.objects.get(target__pk=target_actor.pk)
# Verify playlist.library Follow creation
follow = LibraryFollow.objects.get(target__pk=playlist.library.pk)
assert follow.actor.pk == actor.pk
assert follow.approved == library_follow.approved
assert follow.target == playlist.library
# Verify LibraryFollow deletion and library creation
assert LibraryFollow.objects.count() == 0
# Verify uploads are migrated in lib.playlist_uploads
for upload in uploads:
assert upload.pk in [u.pk for u in playlist.library.playlist_uploads.all()]
assert upload.pk not in [u.pk for u in playlist.library.uploads.all()]
assert not playlist.library.uploads.all()
# Test fail but works on real db I don't get why
# no library are found in the new app
# NewAppLibrary = new_apps.get_model("music", "Library")
# assert NewAppLibrary.objects.count() == 3
# Playlist
# library = Playlist.objects.get(name="This should becane a library name").library
# assert library.name == "This should becane a library name"
# assert library.privacy_level == "me"
# # Not local
# library_not_local = Library.objects.get(fid=library_not_local.fid)
# assert not library_not_local.playlist_uploads.all()
# playlist_not_local = Playlist.objects.get(
# name="This should not becane a library name"
# )
# assert not playlist_not_local.library
......@@ -169,6 +169,7 @@ def test_upload_owner_serializer(factories, to_api_date):
"import_details": {"hello": "world"},
"source": "upload://test",
"import_reference": "ref",
"privacy_level": upload.library.privacy_level,
}
serializer = serializers.UploadForOwnerSerializer(upload)
assert serializer.data == expected
......@@ -198,6 +199,10 @@ def test_album_serializer(factories, to_api_date):
"is_local": album.is_local,
"tags": [],
"attributed_to": federation_serializers.APIActorSerializer(actor).data,
"description": None,
"tracks": [
serializers.TrackSerializer(track).data for track in album.tracks.all()
],
}
serializer = serializers.AlbumSerializer(
album.__class__.objects.with_tracks_count().get(pk=album.pk)
......@@ -230,6 +235,10 @@ def test_track_album_serializer(factories, to_api_date):
"is_local": album.is_local,
"tags": [],
"attributed_to": federation_serializers.APIActorSerializer(actor).data,
"description": None,
"tracks": [
serializers.TrackSerializer(track).data for track in album.tracks.all()
],
}
serializer = serializers.AlbumSerializer(
album.__class__.objects.with_tracks_count().get(pk=album.pk)
......@@ -271,6 +280,7 @@ def test_track_serializer(factories, to_api_date):
"cover": common_serializers.AttachmentSerializer(track.attachment_cover).data,
"downloads_count": track.downloads_count,
"is_playable": bool(track.playable_uploads),
"description": None,
}
serializer = serializers.TrackSerializer(track)
assert serializer.data == expected
......
......@@ -1528,6 +1528,7 @@ def test_fs_import_post(
prune=True,
outbox=False,
broadcast=False,
replace=False,
batch_size=1000,
verbosity=1,
)
......@@ -1572,7 +1573,7 @@ def test_can_patch_upload_list(factories, logged_in_api_client):
actor = logged_in_api_client.user.create_actor()
upload = factories["music.Upload"](library__actor=actor)
upload2 = factories["music.Upload"](library__actor=actor)
factories["music.Library"](actor=actor, privacy_level="everyone")
factories["music.Library"](actor=actor, privacy_level="everyone", name="everyone")
response = logged_in_api_client.patch(
url,
......@@ -1587,3 +1588,30 @@ def test_can_patch_upload_list(factories, logged_in_api_client):
assert response.status_code == 200
assert upload.library.privacy_level == "everyone"
def test_upload_list_wont_use_playlist_lib(factories, logged_in_api_client):
url = reverse("api:v1:uploads-bulk-update")
actor = logged_in_api_client.user.create_actor()
upload = factories["music.Upload"](library__actor=actor)
upload2 = factories["music.Upload"](library__actor=actor)
playlist = factories["playlists.Playlist"]()
lib = factories["music.Library"](
actor=actor,
privacy_level="everyone",
name="everyone",
)
playlist.library = lib
playlist.save()
response = logged_in_api_client.patch(
url,
[
{"uuid": upload.uuid, "privacy_level": "everyone"},
{"uuid": upload2.uuid, "privacy_level": "everyone"},
],
format="json",
)
upload.refresh_from_db()
upload2.refresh_from_db()
assert response.status_code == 400
......@@ -286,3 +286,32 @@ def test_playlist_playable_by_anonymous(privacy_level, expected, factories):
queryset = playlist.__class__.objects.playable_by(None).with_playable_plts(None)
match = playlist in list(queryset)
assert match is expected
def test_playlist_playable_by_library_playlist_follower(factories):
plt = factories["playlists.PlaylistTrack"]()
playlist = plt.playlist
playlist.privacy_level = "everyone"
playlist.save()
track = plt.track
upload = factories["music.Upload"](
track=track, library__privacy_level="me", import_status="finished"
)
upload.playlist_libraries.add(playlist.library)
follow = factories["federation.LibraryFollow"](
target=playlist.library, approved=True
)
# skip actortrack denormalization
assert (
plt.track.uploads.all()
.first()
.__class__.objects.playable_by(follow.actor)
.exists()
)
# doesn't skip actortrack denormalization so will fail need the library scan to be triggered
# queryset = playlist.__class__.objects.playable_by(follow.actor).with_playable_plts(
# None
# )
# assert playlist in list(queryset)
......@@ -75,7 +75,8 @@ def test_playlist_serializer(factories, to_api_date):
actor = playlist.actor
expected = {
"id": playlist.pk,
"uuid": playlist.uuid,
"fid": playlist.fid,
"name": playlist.name,
"privacy_level": playlist.privacy_level,
"is_playable": False,
......@@ -85,6 +86,9 @@ def test_playlist_serializer(factories, to_api_date):
"duration": 0,
"tracks_count": 0,
"album_covers": [],
"description": playlist.description,
"library": playlist.library.fid,
"library_followed": None,
}
serializer = serializers.PlaylistSerializer(playlist)
......
......@@ -17,6 +17,8 @@ def test_scan_playlist_page_fetches_page_and_creates_tracks(
for i in range(5)
]
for plt in tracks:
factories["music.Upload"](track=plt.track, library__actor=scan.playlist.actor)
page_conf = {
"actor": scan.playlist.actor,
"id": scan.playlist.fid,
......@@ -35,7 +37,8 @@ def test_scan_playlist_page_fetches_page_and_creates_tracks(
assert len(plts) == 3
for track in tracks[:3]:
scan.playlist.playlist_tracks.get(fid=track.fid)
plt = scan.playlist.playlist_tracks.get(fid=track.fid)
scan.playlist.library in plt.track.uploads.all()[0].playlist_libraries.all()
assert scan.status == "scanning"
assert scan.processed_files == 3
......
......@@ -24,7 +24,7 @@ def test_can_get_playlists_octet_stream(factories, logged_in_api_client):
factories["playlists.PlaylistTrack"](playlist=pl)
factories["playlists.PlaylistTrack"](playlist=pl)
url = reverse("api:v2:playlists-detail", kwargs={"pk": pl.pk})
url = reverse("api:v2:playlists-detail", kwargs={"uuid": pl.uuid})
headers = {"Accept": "application/octet-stream"}
response = logged_in_api_client.get(url, headers=headers)
el = etree.fromstring(response.content)
......@@ -36,7 +36,7 @@ def test_can_get_playlists_octet_stream(factories, logged_in_api_client):
def test_can_get_playlists_json(factories, logged_in_api_client):
logged_in_api_client.user.create_actor()
pl = factories["playlists.Playlist"]()
url = reverse("api:v2:playlists-detail", kwargs={"pk": pl.pk})
url = reverse("api:v2:playlists-detail", kwargs={"uuid": pl.uuid})
response = logged_in_api_client.get(url, format="json")
assert response.status_code == 200
assert response.data["name"] == pl.name
......@@ -105,7 +105,7 @@ def test_can_patch_playlists_octet_stream(factories, logged_in_api_client):
track = factories["music.Track"](
title="Opinel 12", artist_credit__artist=artist, album=album
)
url = reverse("api:v2:playlists-detail", kwargs={"pk": pl.pk})
url = reverse("api:v2:playlists-detail", kwargs={"uuid": pl.uuid})
data = open("./tests/playlists/test.xspf", "rb").read()
response = logged_in_api_client.patch(url, data=data, format="xspf")
pl.refresh_from_db()
......@@ -118,7 +118,7 @@ def test_can_get_playlists_track(factories, logged_in_api_client):
logged_in_api_client.user.create_actor()
pl = factories["playlists.Playlist"]()
plt = factories["playlists.PlaylistTrack"](playlist=pl)
url = reverse("api:v2:playlists-tracks", kwargs={"pk": pl.pk})
url = reverse("api:v2:playlists-tracks", kwargs={"uuid": pl.uuid})
response = logged_in_api_client.get(url)
data = json.loads(response.content.decode("utf-8"))
assert response.status_code == 200
......@@ -130,7 +130,7 @@ def test_can_get_playlists_releases(factories, logged_in_api_client):
logged_in_api_client.user.create_actor()
playlist = factories["playlists.Playlist"]()
plt = factories["playlists.PlaylistTrack"](playlist=playlist)
url = reverse("api:v2:playlists-albums", kwargs={"pk": playlist.pk})
url = reverse("api:v2:playlists-albums", kwargs={"uuid": playlist.uuid})
response = logged_in_api_client.get(url)
data = json.loads(response.content)
assert response.status_code == 200
......@@ -141,7 +141,7 @@ def test_can_get_playlists_artists(factories, logged_in_api_client):
logged_in_api_client.user.create_actor()
playlist = factories["playlists.Playlist"]()
plt = factories["playlists.PlaylistTrack"](playlist=playlist)
url = reverse("api:v2:playlists-artists", kwargs={"pk": playlist.pk})
url = reverse("api:v2:playlists-artists", kwargs={"uuid": playlist.uuid})
response = logged_in_api_client.get(url)
data = json.loads(response.content)
assert response.status_code == 200
......
......@@ -21,7 +21,7 @@ def test_serializer_includes_tracks_count(factories, logged_in_api_client):
actor = logged_in_api_client.user.create_actor()
playlist = factories["playlists.Playlist"](actor=actor)
factories["playlists.PlaylistTrack"](playlist=playlist)
url = reverse("api:v1:playlists-detail", kwargs={"pk": playlist.pk})
url = reverse("api:v1:playlists-detail", kwargs={"uuid": playlist.uuid})
response = logged_in_api_client.get(url, content_type="application/json")
assert response.data["tracks_count"] == 1
......@@ -34,7 +34,7 @@ def test_serializer_includes_tracks_count_986(factories, logged_in_api_client):
factories["music.Upload"].create_batch(
3, track=plt.track, library__privacy_level="everyone", import_status="finished"
)
url = reverse("api:v1:playlists-detail", kwargs={"pk": playlist.pk})
url = reverse("api:v1:playlists-detail", kwargs={"uuid": playlist.uuid})
response = logged_in_api_client.get(url, content_type="application/json")
assert response.data["tracks_count"] == 1
......@@ -45,7 +45,7 @@ def test_serializer_includes_is_playable(factories, logged_in_api_client):
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={"uuid": playlist.uuid})
response = logged_in_api_client.get(url, content_type="application/json")
assert response.data["is_playable"] is False
......@@ -81,7 +81,7 @@ def test_only_can_add_track_on_own_playlist_via_api(factories, logged_in_api_cli
logged_in_api_client.user.create_actor()
track = factories["music.Track"]()
playlist = factories["playlists.Playlist"]()
url = reverse("api:v1:playlists-add", kwargs={"pk": playlist.pk})
url = reverse("api:v1:playlists-add", kwargs={"uuid": playlist.uuid})
data = {"tracks": [track.pk]}
response = logged_in_api_client.post(url, data, content_type="application/json")
......@@ -96,7 +96,7 @@ def test_deleting_plt_updates_indexes(mocker, factories, logged_in_api_client):
playlist = factories["playlists.Playlist"](actor=actor)
plt0 = factories["playlists.PlaylistTrack"](index=0, playlist=playlist)
plt1 = factories["playlists.PlaylistTrack"](index=1, playlist=playlist)
url = reverse("api:v1:playlists-remove", kwargs={"pk": playlist.pk})
url = reverse("api:v1:playlists-remove", kwargs={"uuid": playlist.uuid})
response = logged_in_api_client.delete(url, {"index": 0})
......@@ -108,6 +108,40 @@ def test_deleting_plt_updates_indexes(mocker, factories, logged_in_api_client):
assert plt1.index == 0
def test_deleting_plt_updates_pl_lib(mocker, factories, logged_in_api_client):
actor = logged_in_api_client.user.create_actor()
factories["music.Track"]()
playlist = factories["playlists.Playlist"](actor=actor)
# playlist.library.uploads.add(plt0.track.uploads.all()[0])
# playlist.library.uploads.add(plt1.track.uploads.all()[0])
tracks = factories["music.Track"].create_batch(size=5)
for track in tracks:
factories["music.Upload"](track=track, library__actor=actor)
not_user_actor = factories["federation.Actor"]()
not_user_upload = factories["music.Upload"](
track=tracks[0], library__actor=not_user_actor
)
track_ids = [t.id for t in tracks]
url = reverse("api:v1:playlists-add", kwargs={"uuid": playlist.uuid})
logged_in_api_client.post(url, {"tracks": track_ids})
assert not_user_upload not in playlist.library.uploads.all()
for plt in playlist.playlist_tracks.all():
for upload in playlist.library.uploads.all():
assert upload.tracks.filter(id=plt.track.id).exists()
url = reverse("api:v1:playlists-remove", kwargs={"uuid": playlist.uuid})
logged_in_api_client.delete(url, {"index": 0})
playlist.library.refresh_from_db()
for plt in playlist.playlist_tracks.all():
for upload in playlist.library.uploads.all():
assert not upload.tracks.filter(id=plt.track.id).exists()
@pytest.mark.parametrize("level", ["instance", "me", "followers"])
def test_playlist_privacy_respected_in_list_anon(
preferences, level, factories, api_client
......@@ -124,7 +158,7 @@ def test_playlist_privacy_respected_in_list_anon(
def test_only_owner_can_edit_playlist(method, factories, logged_in_api_client):
logged_in_api_client.user.create_actor()
playlist = factories["playlists.Playlist"]()
url = reverse("api:v1:playlists-detail", kwargs={"pk": playlist.pk})
url = reverse("api:v1:playlists-detail", kwargs={"uuid": playlist.uuid})
response = getattr(logged_in_api_client, method.lower())(url)
assert response.status_code == 404
......@@ -138,7 +172,7 @@ def test_can_add_multiple_tracks_at_once_via_api(
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})
url = reverse("api:v1:playlists-add", kwargs={"uuid": playlist.uuid})
response = logged_in_api_client.post(url, {"tracks": track_ids})
assert response.status_code == 201
......@@ -149,6 +183,43 @@ def test_can_add_multiple_tracks_at_once_via_api(
assert plt.track == tracks[plt.index]
def test_add_multiple_tracks_at_once_update_pl_library(
factories, mocker, logged_in_api_client
):
actor = logged_in_api_client.user.create_actor()
playlist = factories["playlists.Playlist"](actor=actor)
tracks = factories["music.Track"].create_batch(size=5)
not_user_actor = factories["federation.Actor"]()
not_user_track = factories["music.Track"]()
not_user_upload = factories["music.Upload"](
size=5, track=not_user_track, library__actor=not_user_actor
)
for track in tracks:
factories["music.Upload"](track=track, library__actor=actor)
track_already_in_playlist = factories["music.Track"]()
upload_already_in_playlist = factories["music.Upload"](
track=track_already_in_playlist, library__actor=actor
)
upload_already_in_playlist.playlist_libraries.add(playlist.library)
track_ids = [t.id for t in tracks]
track_ids.append(not_user_track.id)
track_ids.append(track_already_in_playlist.id)
mocker.spy(playlist, "insert_many")
url = reverse("api:v1:playlists-add", kwargs={"uuid": playlist.uuid})
response = logged_in_api_client.post(url, {"tracks": track_ids})
assert response.status_code == 201
assert playlist.playlist_tracks.count() == len(track_ids)
assert playlist.playlist_tracks.filter(track=not_user_track).exists()
assert not_user_upload not in playlist.library.uploads.all()
for plt in playlist.playlist_tracks.all():
for upload in playlist.library.uploads.all():
assert upload.tracks.filter(id=plt.track.id).exists()
assert len(upload.playlist_libraries.all()) == 1
def test_honor_max_playlist_size(factories, mocker, logged_in_api_client, preferences):
actor = logged_in_api_client.user.create_actor()
preferences["playlists__max_tracks"] = 3
......@@ -158,7 +229,7 @@ def test_honor_max_playlist_size(factories, mocker, logged_in_api_client, prefer
)
track_ids = [t.id for t in tracks]
mocker.spy(playlist, "insert_many")
url = reverse("api:v1:playlists-add", kwargs={"pk": playlist.pk})
url = reverse("api:v1:playlists-add", kwargs={"uuid": playlist.uuid})
response = logged_in_api_client.post(url, {"tracks": track_ids})
assert response.status_code == 400
......@@ -168,18 +239,34 @@ def test_can_clear_playlist_from_api(factories, mocker, logged_in_api_client):
actor = logged_in_api_client.user.create_actor()
playlist = factories["playlists.Playlist"](actor=actor)
factories["playlists.PlaylistTrack"].create_batch(size=5, playlist=playlist)
url = reverse("api:v1:playlists-clear", kwargs={"pk": playlist.pk})
url = reverse("api:v1:playlists-clear", kwargs={"uuid": playlist.uuid})
response = logged_in_api_client.delete(url)
assert response.status_code == 204
assert playlist.playlist_tracks.count() == 0
def test_clear_playlist_from_api_remove_pl_lib_uploads(
factories, mocker, logged_in_api_client
):
actor = logged_in_api_client.user.create_actor()
playlist = factories["playlists.Playlist"](actor=actor)
factories["playlists.PlaylistTrack"].create_batch(size=5, playlist=playlist)
for upload in playlist.library.uploads.all():
assert upload.playlist_libraries.filter(playlist=playlist).exists()
assert upload.playlist_libraries.get(playlist=playlist).actor == actor
url = reverse("api:v1:playlists-clear", kwargs={"uuid": playlist.uuid})
response = logged_in_api_client.delete(url)
assert response.status_code == 204
assert not playlist.library.uploads.all()
def test_update_playlist_from_api(factories, mocker, logged_in_api_client):
actor = logged_in_api_client.user.create_actor()
playlist = factories["playlists.Playlist"](actor=actor)
factories["playlists.PlaylistTrack"].create_batch(size=5, playlist=playlist)
url = reverse("api:v1:playlists-detail", kwargs={"pk": playlist.pk})
url = reverse("api:v1:playlists-detail", kwargs={"uuid": playlist.uuid})
response = logged_in_api_client.patch(url, {"name": "test"})
playlist.refresh_from_db()
......@@ -192,7 +279,7 @@ def test_move_plt_updates_indexes(mocker, factories, logged_in_api_client):
playlist = factories["playlists.Playlist"](actor=actor)
plt0 = factories["playlists.PlaylistTrack"](index=0, playlist=playlist)
plt1 = factories["playlists.PlaylistTrack"](index=1, playlist=playlist)
url = reverse("api:v1:playlists-move", kwargs={"pk": playlist.pk})
url = reverse("api:v1:playlists-move", kwargs={"uuid": playlist.uuid})
response = logged_in_api_client.post(url, {"from": 1, "to": 0})
......
......@@ -18,7 +18,7 @@ from funkwhale_api.subsonic import renderers
"type": "funkwhale",
"funkwhaleVersion": funkwhale_api.__version__,
"serverVersion": funkwhale_api.__version__,
"openSubsonic": "true",
"openSubsonic": True,
"hello": "world",
},
),
......@@ -33,7 +33,7 @@ from funkwhale_api.subsonic import renderers
"type": "funkwhale",
"funkwhaleVersion": funkwhale_api.__version__,
"serverVersion": funkwhale_api.__version__,
"openSubsonic": "true",
"openSubsonic": True,
"hello": "world",
"error": {"code": 10, "message": "something went wrong"},
},
......@@ -46,7 +46,7 @@ from funkwhale_api.subsonic import renderers
"type": "funkwhale",
"funkwhaleVersion": funkwhale_api.__version__,
"serverVersion": funkwhale_api.__version__,
"openSubsonic": "true",
"openSubsonic": True,
"hello": "world",
"error": {"code": 0, "message": "something went wrong"},
},
......@@ -66,7 +66,7 @@ def test_json_renderer():
"type": "funkwhale",
"funkwhaleVersion": funkwhale_api.__version__,
"serverVersion": funkwhale_api.__version__,
"openSubsonic": "true",
"openSubsonic": True,
"hello": "world",
}
}
......
......@@ -156,6 +156,9 @@ def test_get_artist_info_2_serializer(factories):
expected = {
"musicBrainzId": artist.mbid,
"smallImageUrl": renderers.TagValue(
artist.attachment_cover.download_url_small_square_crop
),
"mediumImageUrl": renderers.TagValue(
artist.attachment_cover.download_url_medium_square_crop
),
......
......@@ -227,6 +227,62 @@ def test_get_album(
)
@pytest.mark.parametrize("f", ["json"])
def test_get_album_info_2(
f, db, logged_in_api_client, factories, mocker, queryset_equal_queries
):
url = reverse("api:subsonic:subsonic-get_album_info_2")
assert url.endswith("getAlbumInfo2") is True
artist_credit = factories["music.ArtistCredit"]()
album = (
factories["music.Album"](artist_credit=artist_credit)
.__class__.objects.with_duration()
.first()
)
factories["music.Track"].create_batch(size=3, album=album, playable=True)
playable_by = mocker.spy(music_models.AlbumQuerySet, "playable_by")
expected = {"albumInfo": serializers.GetAlbumSerializer(album).data}
response = logged_in_api_client.get(url, {"f": f, "id": album.pk})
assert response.status_code == 200
assert response.data == expected
playable_by.assert_called_once_with(
music_models.Album.objects.with_duration().prefetch_related(
"artist_credit__artist"
),
None,
)
@pytest.mark.parametrize("f", ["json"])
def test_get_album_info(
f, db, logged_in_api_client, factories, mocker, queryset_equal_queries
):
url = reverse("api:subsonic:subsonic-get_album_info")
assert url.endswith("getAlbumInfo") is True
artist_credit = factories["music.ArtistCredit"]()
album = (
factories["music.Album"](artist_credit=artist_credit)
.__class__.objects.with_duration()
.first()
)
factories["music.Track"].create_batch(size=3, album=album, playable=True)
playable_by = mocker.spy(music_models.AlbumQuerySet, "playable_by")
expected = {"albumInfo": serializers.GetAlbumSerializer(album).data}
response = logged_in_api_client.get(url, {"f": f, "id": album.pk})
assert response.status_code == 200
assert response.data == expected
playable_by.assert_called_once_with(
music_models.Album.objects.with_duration().prefetch_related(
"artist_credit__artist"
),
None,
)
@pytest.mark.parametrize("f", ["json"])
def test_get_song(
f, db, logged_in_api_client, factories, mocker, queryset_equal_queries
......@@ -247,6 +303,32 @@ def test_get_song(
playable_by.assert_called_once_with(music_models.Track.objects.all(), None)
@pytest.mark.parametrize("f", ["json"])
def test_get_top_songs(
f, db, logged_in_api_client, factories, mocker, queryset_equal_queries
):
url = reverse("api:subsonic:subsonic-get_top_songs")
assert url.endswith("getTopSongs") is True
artist_credit = factories["music.ArtistCredit"]()
album = factories["music.Album"](artist_credit=artist_credit)
track = factories["music.Track"](album=album, playable=True)
tracks = factories["music.Track"].create_batch(20, album=album, playable=True)
factories["music.Upload"](track=track)
factories["history.Listening"].create_batch(20, track=track)
factories["history.Listening"].create_batch(2, track=tracks[2])
playable_by = mocker.spy(music_models.TrackQuerySet, "playable_by")
response = logged_in_api_client.get(
url, {"f": f, "artist": artist_credit.artist.name, "count": 2}
)
assert response.status_code == 200
assert response.data["topSongs"][0] == serializers.get_track_data(
track.album, track, track.uploads.all()[0]
)
playable_by.assert_called_once_with(music_models.Track.objects.all(), None)
@pytest.mark.parametrize("f", ["json"])
def test_stream(
f, db, logged_in_api_client, factories, mocker, queryset_equal_queries, settings
......
Playlist federation spec
Playlist federation (#1458)
Quality filter for content frontend (#1469)
Support multiples artists for tracks and albums (#1568)
User follow with listening and track favorite activities (#1810)
Allow special characters in tags (#2009)
Add favorite and listening sync ith Listenbrainz (#2079)
Add cli command to prune non mbid content from db (#2083)
Add Musicbrainz genres to funkwhale tag table and allow Musicbrainz tag sync (#2143)