379 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			379 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Python
		
	
	
	
| import datetime
 | |
| 
 | |
| import pytest
 | |
| from django.db.models.aggregates import Count
 | |
| 
 | |
| from funkwhale_api.music import models as music_models
 | |
| from funkwhale_api.subsonic import renderers, serializers
 | |
| 
 | |
| 
 | |
| @pytest.mark.parametrize(
 | |
|     "date, expected",
 | |
|     [
 | |
|         (datetime.datetime(2017, 1, 12, 9, 53, 12, 1890), "2017-01-12T09:53:12.000Z"),
 | |
|         (None, None),
 | |
|     ],
 | |
| )
 | |
| def test_to_subsonic_date(date, expected):
 | |
|     assert serializers.to_subsonic_date(date) == expected
 | |
| 
 | |
| 
 | |
| @pytest.mark.parametrize(
 | |
|     "input, expected",
 | |
|     [
 | |
|         ("AC/DC", "AC_DC"),
 | |
|         ("AC-DC", "AC-DC"),
 | |
|         ("A" * 100, "A" * 50),
 | |
|         ("Album (2019)", "Album (2019)"),
 | |
|         ("Haven't", "Haven_t"),
 | |
|     ],
 | |
| )
 | |
| def test_get_valid_filepart(input, expected):
 | |
|     assert serializers.get_valid_filepart(input) == expected
 | |
| 
 | |
| 
 | |
| @pytest.mark.parametrize(
 | |
|     "factory_kwargs, suffix, expected",
 | |
|     [
 | |
|         (
 | |
|             {
 | |
|                 "artist_credit__artist__name": "Hello",
 | |
|                 "album__title": "World",
 | |
|                 "title": "foo",
 | |
|                 "position": None,
 | |
|             },
 | |
|             "mp3",
 | |
|             "Hello/World/foo.mp3",
 | |
|         ),
 | |
|         (
 | |
|             {
 | |
|                 "artist_credit__artist__name": "AC/DC",
 | |
|                 "album__title": "escape/my",
 | |
|                 "title": "sla/sh",
 | |
|                 "position": 12,
 | |
|             },
 | |
|             "ogg",
 | |
|             "/".join(
 | |
|                 [
 | |
|                     serializers.get_valid_filepart("AC/DC"),
 | |
|                     serializers.get_valid_filepart("escape/my"),
 | |
|                 ]
 | |
|             )
 | |
|             + "/12 - {}.ogg".format(serializers.get_valid_filepart("sla/sh")),
 | |
|         ),
 | |
|     ],
 | |
| )
 | |
| def test_get_track_path(factory_kwargs, suffix, expected, factories):
 | |
|     track = factories["music.Track"](**factory_kwargs)
 | |
|     assert serializers.get_track_path(track, suffix) == expected
 | |
| 
 | |
| 
 | |
| def test_get_artists_serializer(factories):
 | |
|     artist1 = factories["music.Artist"](name="eliot", with_cover=True)
 | |
|     artist2 = factories["music.Artist"](name="Ellena", with_cover=True)
 | |
|     artist3 = factories["music.Artist"](name="Rilay", with_cover=True)
 | |
|     artist4 = factories["music.Artist"](
 | |
|         name="", with_cover=False
 | |
|     )  # Shouldn't be serialised
 | |
| 
 | |
|     factories["music.Album"].create_batch(size=3, artist_credit__artist=artist1)
 | |
|     factories["music.Album"].create_batch(size=2, artist_credit__artist=artist2)
 | |
| 
 | |
|     expected = {
 | |
|         "ignoredArticles": "",
 | |
|         "index": [
 | |
|             {
 | |
|                 "name": "E",
 | |
|                 "artist": [
 | |
|                     {
 | |
|                         "id": artist1.pk,
 | |
|                         "name": artist1.name,
 | |
|                         "albumCount": 3,
 | |
|                         "coverArt": f"ar-{artist1.id}",
 | |
|                         "musicBrainzId": artist1.mbid,
 | |
|                     },
 | |
|                     {
 | |
|                         "id": artist2.pk,
 | |
|                         "name": artist2.name,
 | |
|                         "albumCount": 2,
 | |
|                         "coverArt": f"ar-{artist2.id}",
 | |
|                         "musicBrainzId": artist2.mbid,
 | |
|                     },
 | |
|                 ],
 | |
|             },
 | |
|             {
 | |
|                 "name": "R",
 | |
|                 "artist": [
 | |
|                     {
 | |
|                         "id": artist3.pk,
 | |
|                         "name": artist3.name,
 | |
|                         "albumCount": 0,
 | |
|                         "coverArt": f"ar-{artist3.id}",
 | |
|                         "musicBrainzId": artist3.mbid,
 | |
|                     }
 | |
|                 ],
 | |
|             },
 | |
|         ],
 | |
|     }
 | |
| 
 | |
|     queryset = artist1.__class__.objects.filter(
 | |
|         pk__in=[artist1.pk, artist2.pk, artist3.pk, artist4.pk]
 | |
|     )
 | |
| 
 | |
|     assert serializers.GetArtistsSerializer(queryset).data == expected
 | |
| 
 | |
| 
 | |
| def test_get_artist_serializer(factories):
 | |
|     artist = factories["music.Artist"](with_cover=True)
 | |
|     album = factories["music.Album"](artist_credit__artist=artist, with_cover=True)
 | |
|     tracks = factories["music.Track"].create_batch(size=3, album=album)
 | |
| 
 | |
|     expected = {
 | |
|         "id": artist.pk,
 | |
|         "name": artist.name,
 | |
|         "albumCount": 1,
 | |
|         "coverArt": f"ar-{artist.id}",
 | |
|         "album": [
 | |
|             {
 | |
|                 "id": album.pk,
 | |
|                 "coverArt": f"al-{album.id}",
 | |
|                 "artistId": artist.pk,
 | |
|                 "name": album.title,
 | |
|                 "artist": artist.name,
 | |
|                 "songCount": len(tracks),
 | |
|                 "created": serializers.to_subsonic_date(album.creation_date),
 | |
|                 "year": album.release_date.year,
 | |
|             }
 | |
|         ],
 | |
|     }
 | |
| 
 | |
|     assert serializers.GetArtistSerializer(artist).data == expected
 | |
| 
 | |
| 
 | |
| def test_get_artist_info_2_serializer(factories):
 | |
|     content = factories["common.Content"]()
 | |
|     artist = factories["music.Artist"](with_cover=True, description=content)
 | |
| 
 | |
|     expected = {
 | |
|         "musicBrainzId": artist.mbid,
 | |
|         "mediumImageUrl": renderers.TagValue(
 | |
|             artist.attachment_cover.download_url_medium_square_crop
 | |
|         ),
 | |
|         "largeImageUrl": renderers.TagValue(
 | |
|             artist.attachment_cover.download_url_large_square_crop
 | |
|         ),
 | |
|         "biography": renderers.TagValue(artist.description.rendered),
 | |
|     }
 | |
| 
 | |
|     assert serializers.GetArtistInfo2Serializer(artist).data == expected
 | |
| 
 | |
| 
 | |
| @pytest.mark.parametrize(
 | |
|     "mimetype, extension, expected",
 | |
|     [
 | |
|         ("audio/ogg", "noop", "audio/ogg"),
 | |
|         ("", "ogg", "audio/ogg"),
 | |
|         ("", "mp3", "audio/mpeg"),
 | |
|         ("", "", "audio/mpeg"),
 | |
|     ],
 | |
| )
 | |
| def test_get_track_data_content_type(mimetype, extension, expected, factories):
 | |
|     upload = factories["music.Upload"]()
 | |
|     upload.mimetype = mimetype
 | |
|     upload.audio_file = f"test.{extension}"
 | |
| 
 | |
|     data = serializers.get_track_data(
 | |
|         album=upload.track.album, track=upload.track, upload=upload
 | |
|     )
 | |
| 
 | |
|     assert data["contentType"] == expected
 | |
| 
 | |
| 
 | |
| def test_get_album_serializer(factories):
 | |
|     artist = factories["music.Artist"]()
 | |
|     album = factories["music.Album"](artist_credit__artist=artist, with_cover=True)
 | |
|     track = factories["music.Track"](album=album, disc_number=42)
 | |
|     upload = factories["music.Upload"](track=track, bitrate=42000, duration=43, size=44)
 | |
|     tagged_item = factories["tags.TaggedItem"](content_object=album, tag__name="foo")
 | |
|     expected = {
 | |
|         "id": album.pk,
 | |
|         "artistId": artist.pk,
 | |
|         "name": album.title,
 | |
|         "artist": artist.name,
 | |
|         "songCount": 1,
 | |
|         "created": serializers.to_subsonic_date(album.creation_date),
 | |
|         "year": album.release_date.year,
 | |
|         "coverArt": f"al-{album.id}",
 | |
|         "genre": tagged_item.tag.name,
 | |
|         "genres": [{"name": tagged_item.tag.name}],
 | |
|         "mediaType": "album",
 | |
|         "musicBrainzId": album.mbid,
 | |
|         "duration": 43,
 | |
|         "playCount": album.tracks.aggregate(l=Count("listenings"))["l"] or 0,
 | |
|         "song": [
 | |
|             {
 | |
|                 "id": track.pk,
 | |
|                 "isDir": "false",
 | |
|                 "title": track.title,
 | |
|                 "coverArt": f"al-{album.id}",
 | |
|                 "album": album.title,
 | |
|                 "artist": artist.name,
 | |
|                 "track": track.position,
 | |
|                 "discNumber": track.disc_number,
 | |
|                 "year": track.album.release_date.year,
 | |
|                 "contentType": upload.mimetype,
 | |
|                 "suffix": upload.extension or "",
 | |
|                 "path": serializers.get_track_path(track, upload.extension),
 | |
|                 "bitRate": 42,
 | |
|                 "duration": 43,
 | |
|                 "size": 44,
 | |
|                 "created": serializers.to_subsonic_date(track.creation_date),
 | |
|                 "albumId": album.pk,
 | |
|                 "artistId": artist.pk,
 | |
|                 "type": "music",
 | |
|                 "mediaType": "song",
 | |
|                 "musicBrainzId": track.mbid,
 | |
|             }
 | |
|         ],
 | |
|     }
 | |
| 
 | |
|     qs = album.__class__.objects.with_duration()
 | |
| 
 | |
|     assert serializers.GetAlbumSerializer(qs.first()).data == expected
 | |
| 
 | |
| 
 | |
| def test_starred_tracks2_serializer(factories):
 | |
|     artist_credit = factories["music.ArtistCredit"]()
 | |
|     album = factories["music.Album"](artist_credit=artist_credit)
 | |
|     track = factories["music.Track"](album=album)
 | |
|     upload = factories["music.Upload"](track=track)
 | |
|     favorite = factories["favorites.TrackFavorite"](track=track)
 | |
|     expected = [serializers.get_track_data(album, track, upload)]
 | |
|     expected[0]["starred"] = serializers.to_subsonic_date(favorite.creation_date)
 | |
|     data = serializers.get_starred_tracks_data([favorite])
 | |
|     assert data == expected
 | |
| 
 | |
| 
 | |
| def test_get_album_list2_serializer(factories):
 | |
|     album1 = factories["music.Album"]().__class__.objects.with_duration().first()
 | |
|     album2 = factories["music.Album"]().__class__.objects.with_duration().last()
 | |
| 
 | |
|     qs = music_models.Album.objects.with_tracks_count().with_duration().order_by("pk")
 | |
|     expected = [
 | |
|         serializers.get_album2_data(album1),
 | |
|         serializers.get_album2_data(album2),
 | |
|     ]
 | |
|     data = serializers.get_album_list2_data(qs)
 | |
|     assert data == expected
 | |
| 
 | |
| 
 | |
| def test_playlist_serializer(factories):
 | |
|     plt = factories["playlists.PlaylistTrack"]()
 | |
|     playlist = plt.playlist
 | |
|     factories["users.User"](actor=playlist.actor)
 | |
|     qs = music_models.Album.objects.with_tracks_count().order_by("pk")
 | |
|     expected = {
 | |
|         "id": playlist.pk,
 | |
|         "name": playlist.name,
 | |
|         "owner": playlist.actor.user.username,
 | |
|         "public": "false",
 | |
|         "songCount": 1,
 | |
|         "duration": 0,
 | |
|         "created": serializers.to_subsonic_date(playlist.creation_date),
 | |
|     }
 | |
|     qs = playlist.__class__.objects.with_tracks_count()
 | |
|     data = serializers.get_playlist_data(qs.first())
 | |
|     assert data == expected
 | |
| 
 | |
| 
 | |
| def test_playlist_detail_serializer(factories):
 | |
|     plt = factories["playlists.PlaylistTrack"]()
 | |
|     upload = factories["music.Upload"](track=plt.track)
 | |
|     playlist = plt.playlist
 | |
|     factories["users.User"](actor=playlist.actor)
 | |
|     qs = music_models.Album.objects.with_tracks_count().order_by("pk")
 | |
|     expected = {
 | |
|         "id": playlist.pk,
 | |
|         "name": playlist.name,
 | |
|         "owner": playlist.actor.user.username,
 | |
|         "public": "false",
 | |
|         "songCount": 1,
 | |
|         "duration": 0,
 | |
|         "created": serializers.to_subsonic_date(playlist.creation_date),
 | |
|         "entry": [serializers.get_track_data(plt.track.album, plt.track, upload)],
 | |
|     }
 | |
|     qs = playlist.__class__.objects.with_tracks_count()
 | |
|     data = serializers.get_playlist_detail_data(qs.first())
 | |
|     assert data == expected
 | |
| 
 | |
| 
 | |
| def test_scrobble_serializer(factories):
 | |
|     upload = factories["music.Upload"]()
 | |
|     track = upload.track
 | |
|     user = factories["users.User"](with_actor=True)
 | |
|     payload = {"id": track.pk, "submission": True}
 | |
|     serializer = serializers.ScrobbleSerializer(data=payload, context={"user": user})
 | |
| 
 | |
|     assert serializer.is_valid(raise_exception=True)
 | |
| 
 | |
|     listening = serializer.save()
 | |
| 
 | |
|     assert listening.actor.user == user
 | |
|     assert listening.track == track
 | |
| 
 | |
| 
 | |
| def test_channel_serializer(factories):
 | |
|     description = factories["common.Content"]()
 | |
|     channel = factories["audio.Channel"](
 | |
|         external=True, artist__description=description, artist__with_cover=True
 | |
|     )
 | |
|     upload = factories["music.Upload"](
 | |
|         playable=True, library=channel.library, duration=42
 | |
|     )
 | |
| 
 | |
|     expected = {
 | |
|         "id": str(channel.uuid),
 | |
|         "url": channel.rss_url,
 | |
|         "title": channel.artist.name,
 | |
|         "description": description.as_plain_text,
 | |
|         "coverArt": f"at-{channel.artist.attachment_cover.uuid}",
 | |
|         "originalImageUrl": channel.artist.attachment_cover.url,
 | |
|         "status": "completed",
 | |
|         "episode": [serializers.get_channel_episode_data(upload, channel.uuid)],
 | |
|     }
 | |
|     data = serializers.get_channel_data(channel, [upload])
 | |
|     assert data == expected
 | |
| 
 | |
| 
 | |
| def test_channel_episode_serializer(factories):
 | |
|     description = factories["common.Content"]()
 | |
|     channel = factories["audio.Channel"]()
 | |
|     track = factories["music.Track"](
 | |
|         description=description, artist_credit__artist=channel.artist, with_cover=True
 | |
|     )
 | |
|     upload = factories["music.Upload"](
 | |
|         playable=True, track=track, bitrate=128000, duration=42
 | |
|     )
 | |
| 
 | |
|     expected = {
 | |
|         "id": str(upload.uuid),
 | |
|         "channelId": str(channel.uuid),
 | |
|         "streamId": upload.track.id,
 | |
|         "title": track.title,
 | |
|         "description": description.as_plain_text,
 | |
|         "coverArt": f"at-{track.attachment_cover.uuid}",
 | |
|         "isDir": "false",
 | |
|         "year": track.creation_date.year,
 | |
|         "created": track.creation_date.isoformat(),
 | |
|         "publishDate": track.creation_date.isoformat(),
 | |
|         "genre": "Podcast",
 | |
|         "size": upload.size,
 | |
|         "duration": upload.duration,
 | |
|         "bitRate": upload.bitrate / 1000,
 | |
|         "contentType": upload.mimetype,
 | |
|         "suffix": upload.extension,
 | |
|         "status": "completed",
 | |
|     }
 | |
|     data = serializers.get_channel_episode_data(upload, channel.uuid)
 | |
|     assert data == expected
 |