Newer
Older
import datetime
Eliot Berriot
committed
import json
Eliot Berriot
committed
from django.urls import reverse
from rest_framework.response import Response
Eliot Berriot
committed
import funkwhale_api
Eliot Berriot
committed
from funkwhale_api.music import models as music_models
from funkwhale_api.music import views as music_views
from funkwhale_api.subsonic import renderers, serializers
Eliot Berriot
committed
def render_json(data):
return json.loads(renderers.SubsonicJSONRenderer().render(data))
def test_render_content_json(db, api_client):
url = reverse("api:subsonic-ping")
response = api_client.get(url, {"f": "json"})
Eliot Berriot
committed
Eliot Berriot
committed
expected = {
"status": "ok",
"version": "1.16.0",
"type": "funkwhale",
"funkwhaleVersion": funkwhale_api.__version__,
Eliot Berriot
committed
}
Eliot Berriot
committed
assert response.status_code == 200
assert json.loads(response.content) == render_json(expected)
Eliot Berriot
committed
def test_exception_wrong_credentials(f, db, api_client):
url = reverse("api:subsonic-ping")
response = api_client.get(url, {"f": f, "u": "yolo"})
Eliot Berriot
committed
expected = {
"status": "failed",
"error": {"code": 40, "message": "Wrong username or password."},
Eliot Berriot
committed
}
assert response.status_code == 200
assert response.data == expected
@pytest.mark.parametrize("f", ["json"])
def test_exception_missing_credentials(f, db, api_client):
url = reverse("api:subsonic-get-artists")
response = api_client.get(url)
expected = {
"status": "failed",
"error": {"code": 10, "message": "Required parameter is missing."},
}
assert response.status_code == 200
assert response.data == expected
Eliot Berriot
committed
def test_disabled_subsonic(preferences, api_client):
preferences["subsonic__enabled"] = False
url = reverse("api:subsonic-ping")
Eliot Berriot
committed
response = api_client.get(url)
assert response.status_code == 405
def test_get_license(f, db, logged_in_api_client, mocker):
url = reverse("api:subsonic-get-license")
assert url.endswith("getLicense") is True
now = timezone.now()
mocker.patch("django.utils.timezone.now", return_value=now)
response = logged_in_api_client.get(url, {"f": f})
expected = {
"type": "funkwhale",
"funkwhaleVersion": funkwhale_api.__version__,
"license": {
"valid": "true",
"email": "valid@valid.license",
"licenseExpires": now + datetime.timedelta(days=365),
},
}
assert response.status_code == 200
assert response.data == expected
Eliot Berriot
committed
def test_ping(f, db, api_client):
url = reverse("api:subsonic-ping")
response = api_client.get(url, {"f": f})
Eliot Berriot
committed
Eliot Berriot
committed
assert response.status_code == 200
assert response.data == expected
@pytest.mark.parametrize("f", ["json"])
def test_get_artists(
f, db, logged_in_api_client, factories, mocker, queryset_equal_queries
):
url = reverse("api:subsonic-get-artists")
assert url.endswith("getArtists") is True
factories["music.Artist"].create_batch(size=3, playable=True)
playable_by = mocker.spy(music_models.ArtistQuerySet, "playable_by")
Eliot Berriot
committed
expected = {
Eliot Berriot
committed
music_models.Artist.objects.all()
).data
}
Eliot Berriot
committed
assert response.status_code == 200
assert response.data == expected
playable_by.assert_called_once_with(music_models.Artist.objects.all(), None)
Eliot Berriot
committed
@pytest.mark.parametrize("f", ["json"])
def test_get_artist(
f, db, logged_in_api_client, factories, mocker, queryset_equal_queries
):
url = reverse("api:subsonic-get-artist")
assert url.endswith("getArtist") is True
artist = factories["music.Artist"](playable=True)
factories["music.Album"].create_batch(size=3, artist=artist, playable=True)
playable_by = mocker.spy(music_models.ArtistQuerySet, "playable_by")
expected = {"artist": serializers.GetArtistSerializer(artist).data}
response = logged_in_api_client.get(url, {"id": artist.pk})
Eliot Berriot
committed
assert response.status_code == 200
assert response.data == expected
playable_by.assert_called_once_with(music_models.Artist.objects.all(), None)
Eliot Berriot
committed
@pytest.mark.parametrize("f", ["json"])
def test_get_invalid_artist(f, db, logged_in_api_client, factories):
url = reverse("api:subsonic-get-artist")
assert url.endswith("getArtist") is True
expected = {"error": {"code": 0, "message": 'For input string "asdf"'}}
response = logged_in_api_client.get(url, {"id": "asdf"})
assert response.status_code == 200
assert response.data == expected
@pytest.mark.parametrize("f", ["json"])
def test_get_artist_info2(
f, db, logged_in_api_client, factories, mocker, queryset_equal_queries
):
url = reverse("api:subsonic-get-artist-info2")
assert url.endswith("getArtistInfo2") is True
artist = factories["music.Artist"](playable=True)
playable_by = mocker.spy(music_models.ArtistQuerySet, "playable_by")
expected = {"artist-info2": {}}
response = logged_in_api_client.get(url, {"id": artist.pk})
assert response.status_code == 200
assert response.data == expected
playable_by.assert_called_once_with(music_models.Artist.objects.all(), None)
@pytest.mark.parametrize("f", ["json"])
def test_get_album(
f, db, logged_in_api_client, factories, mocker, queryset_equal_queries
):
url = reverse("api:subsonic-get-album")
assert url.endswith("getAlbum") is True
artist = factories["music.Artist"]()
album = factories["music.Album"](artist=artist)
factories["music.Track"].create_batch(size=3, album=album, playable=True)
playable_by = mocker.spy(music_models.AlbumQuerySet, "playable_by")
expected = {"album": serializers.GetAlbumSerializer(album).data}
response = logged_in_api_client.get(url, {"f": f, "id": album.pk})
Eliot Berriot
committed
assert response.status_code == 200
assert response.data == expected
playable_by.assert_called_once_with(
music_models.Album.objects.select_related("artist"), None
)
Eliot Berriot
committed
@pytest.mark.parametrize("f", ["json"])
def test_get_song(
f, db, logged_in_api_client, factories, mocker, queryset_equal_queries
):
url = reverse("api:subsonic-get-song")
assert url.endswith("getSong") is True
artist = factories["music.Artist"]()
album = factories["music.Album"](artist=artist)
track = factories["music.Track"](album=album, playable=True)
playable_by = mocker.spy(music_models.TrackQuerySet, "playable_by")
response = logged_in_api_client.get(url, {"f": f, "id": track.pk})
assert response.status_code == 200
assert response.data == {
"song": serializers.get_track_data(track.album, track, upload)
}
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):
url = reverse("api:subsonic-stream")
mocked_serve = mocker.spy(music_views, "handle_serve")
assert url.endswith("stream") is True
upload = factories["music.Upload"](playable=True)
playable_by = mocker.spy(music_models.TrackQuerySet, "playable_by")
response = logged_in_api_client.get(url, {"f": f, "id": upload.track.pk})
mocked_serve.assert_called_once_with(
upload=upload, user=logged_in_api_client.user, format=None
)
Eliot Berriot
committed
assert response.status_code == 200
playable_by.assert_called_once_with(music_models.Track.objects.all(), None)
@pytest.mark.parametrize("format,expected", [("mp3", "mp3"), ("raw", None)])
def test_stream_format(format, expected, logged_in_api_client, factories, mocker):
url = reverse("api:subsonic-stream")
mocked_serve = mocker.patch.object(
music_views, "handle_serve", return_value=Response()
)
upload = factories["music.Upload"](playable=True)
response = logged_in_api_client.get(url, {"id": upload.track.pk, "format": format})
mocked_serve.assert_called_once_with(
upload=upload, user=logged_in_api_client.user, format=expected
)
assert response.status_code == 200
@pytest.mark.parametrize("f", ["json"])
def test_star(f, db, logged_in_api_client, factories):
url = reverse("api:subsonic-star")
assert url.endswith("star") is True
track = factories["music.Track"]()
response = logged_in_api_client.get(url, {"f": f, "id": track.pk})
assert response.status_code == 200
favorite = logged_in_api_client.user.track_favorites.latest("id")
assert favorite.track == track
@pytest.mark.parametrize("f", ["json"])
def test_unstar(f, db, logged_in_api_client, factories):
url = reverse("api:subsonic-unstar")
assert url.endswith("unstar") is True
track = factories["music.Track"]()
factories["favorites.TrackFavorite"](track=track, user=logged_in_api_client.user)
response = logged_in_api_client.get(url, {"f": f, "id": track.pk})
assert response.status_code == 200
assert logged_in_api_client.user.track_favorites.count() == 0
@pytest.mark.parametrize("f", ["json"])
def test_get_starred2(f, db, logged_in_api_client, factories):
url = reverse("api:subsonic-get-starred2")
assert url.endswith("getStarred2") is True
track = factories["music.Track"]()
favorite = factories["favorites.TrackFavorite"](
track=track, user=logged_in_api_client.user
)
response = logged_in_api_client.get(url, {"f": f, "id": track.pk})
assert response.status_code == 200
assert response.data == {
"starred2": {"song": serializers.get_starred_tracks_data([favorite])}
@pytest.mark.parametrize("f", ["json"])
def test_get_random_songs(f, db, logged_in_api_client, factories, mocker):
url = reverse("api:subsonic-get-random-songs")
assert url.endswith("getRandomSongs") is True
track1 = factories["music.Track"]()
track2 = factories["music.Track"]()
factories["music.Track"]()
order_by = mocker.patch.object(
music_models.TrackQuerySet, "order_by", return_value=[track1, track2]
)
response = logged_in_api_client.get(url, {"f": f, "size": 2})
assert response.status_code == 200
assert response.data == {
"randomSongs": {
"song": serializers.GetSongSerializer([track1, track2], many=True).data
}
}
order_by.assert_called_once_with("?")
@pytest.mark.parametrize("f", ["json"])
def test_get_starred(f, db, logged_in_api_client, factories):
url = reverse("api:subsonic-get-starred")
assert url.endswith("getStarred") is True
track = factories["music.Track"]()
favorite = factories["favorites.TrackFavorite"](
track=track, user=logged_in_api_client.user
)
response = logged_in_api_client.get(url, {"f": f, "id": track.pk})
assert response.status_code == 200
assert response.data == {
"starred": {"song": serializers.get_starred_tracks_data([favorite])}
@pytest.mark.parametrize("f", ["json"])
def test_get_album_list2(
f, db, logged_in_api_client, factories, mocker, queryset_equal_queries
):
url = reverse("api:subsonic-get-album-list2")
assert url.endswith("getAlbumList2") is True
album1 = factories["music.Album"](playable=True)
album2 = factories["music.Album"](playable=True)
factories["music.Album"]()
playable_by = mocker.spy(music_models.AlbumQuerySet, "playable_by")
response = logged_in_api_client.get(url, {"f": f, "type": "newest"})
assert response.status_code == 200
assert response.data == {
"albumList2": {"album": serializers.get_album_list2_data([album2, album1])}
playable_by.assert_called_once()
@pytest.mark.parametrize("f", ["json"])
def test_get_album_list2_pagination(f, db, logged_in_api_client, factories):
url = reverse("api:subsonic-get-album-list2")
assert url.endswith("getAlbumList2") is True
album1 = factories["music.Album"](playable=True)
factories["music.Album"](playable=True)
response = logged_in_api_client.get(
url, {"f": f, "type": "newest", "size": 1, "offset": 1}
)
assert response.status_code == 200
assert response.data == {
"albumList2": {"album": serializers.get_album_list2_data([album1])}
}
@pytest.mark.parametrize("f", ["json"])
def test_search3(f, db, logged_in_api_client, factories):
url = reverse("api:subsonic-search3")
assert url.endswith("search3") is True
artist = factories["music.Artist"](name="testvalue", playable=True)
factories["music.Artist"](name="nope2", playable=True)
album = factories["music.Album"](title="testvalue", playable=True)
factories["music.Album"](title="nope2", playable=True)
track = factories["music.Track"](title="testvalue", playable=True)
factories["music.Track"](title="nope2", playable=True)
response = logged_in_api_client.get(url, {"f": f, "query": "testval"})
artist_qs = (
music_models.Artist.objects.with_albums_count()
.filter(pk=artist.pk)
.values("_albums_count", "id", "name")
)
assert response.status_code == 200
assert response.data == {
"searchResult3": {
"artist": [serializers.get_artist_data(a) for a in artist_qs],
"album": serializers.get_album_list2_data([album]),
"song": serializers.get_song_list_data([track]),
@pytest.mark.parametrize("f", ["json"])
def test_get_playlists(f, db, logged_in_api_client, factories):
url = reverse("api:subsonic-get-playlists")
assert url.endswith("getPlaylists") is True
playlist = factories["playlists.Playlist"](user=logged_in_api_client.user)
response = logged_in_api_client.get(url, {"f": f})
qs = playlist.__class__.objects.with_tracks_count()
assert response.status_code == 200
assert response.data == {
"playlists": {"playlist": [serializers.get_playlist_data(qs.first())]}
@pytest.mark.parametrize("f", ["json"])
def test_get_playlist(f, db, logged_in_api_client, factories):
url = reverse("api:subsonic-get-playlist")
assert url.endswith("getPlaylist") is True
playlist = factories["playlists.Playlist"](user=logged_in_api_client.user)
response = logged_in_api_client.get(url, {"f": f, "id": playlist.pk})
qs = playlist.__class__.objects.with_tracks_count()
assert response.status_code == 200
assert response.data == {
"playlist": serializers.get_playlist_detail_data(qs.first())
@pytest.mark.parametrize("f", ["json"])
def test_update_playlist(f, db, logged_in_api_client, factories):
url = reverse("api:subsonic-update-playlist")
assert url.endswith("updatePlaylist") is True
playlist = factories["playlists.Playlist"](user=logged_in_api_client.user)
factories["playlists.PlaylistTrack"](index=0, playlist=playlist)
response = logged_in_api_client.get(
url,
{
"f": f,
"name": "new_name",
"playlistId": playlist.pk,
"songIdToAdd": new_track.pk,
"songIndexToRemove": 0,
},
)
playlist.refresh_from_db()
assert response.status_code == 200
assert playlist.playlist_tracks.count() == 1
assert playlist.playlist_tracks.first().track_id == new_track.pk
@pytest.mark.parametrize("f", ["json"])
def test_delete_playlist(f, db, logged_in_api_client, factories):
url = reverse("api:subsonic-delete-playlist")
assert url.endswith("deletePlaylist") is True
playlist = factories["playlists.Playlist"](user=logged_in_api_client.user)
response = logged_in_api_client.get(url, {"f": f, "id": playlist.pk})
assert response.status_code == 200
with pytest.raises(playlist.__class__.DoesNotExist):
playlist.refresh_from_db()
@pytest.mark.parametrize("f", ["json"])
def test_create_playlist(f, db, logged_in_api_client, factories):
url = reverse("api:subsonic-create-playlist")
assert url.endswith("createPlaylist") is True
track1 = factories["music.Track"]()
track2 = factories["music.Track"]()
response = logged_in_api_client.get(
url, {"f": f, "name": "hello", "songId": [track1.pk, track2.pk]}
)
assert response.status_code == 200
playlist = logged_in_api_client.user.playlists.latest("id")
assert playlist.playlist_tracks.count() == 2
for i, t in enumerate([track1, track2]):
plt = playlist.playlist_tracks.get(track=t)
assert plt.index == i
qs = playlist.__class__.objects.with_tracks_count()
assert response.data == {
"playlist": serializers.get_playlist_detail_data(qs.first())
@pytest.mark.parametrize("f", ["json"])
def test_get_music_folders(f, db, logged_in_api_client, factories):
url = reverse("api:subsonic-get-music-folders")
assert url.endswith("getMusicFolders") is True
response = logged_in_api_client.get(url, {"f": f})
assert response.status_code == 200
assert response.data == {
"musicFolders": {"musicFolder": [{"id": 1, "name": "Music"}]}
@pytest.mark.parametrize("f", ["json"])
def test_get_indexes(
f, db, logged_in_api_client, factories, mocker, queryset_equal_queries
):
url = reverse("api:subsonic-get-indexes")
assert url.endswith("getIndexes") is True
factories["music.Artist"].create_batch(size=3, playable=True)
music_models.Artist.objects.all()
).data
}
playable_by = mocker.spy(music_models.ArtistQuerySet, "playable_by")
response = logged_in_api_client.get(url)
assert response.status_code == 200
assert response.data == expected
playable_by.assert_called_once_with(music_models.Artist.objects.all(), None)
def test_get_cover_art_album(factories, logged_in_api_client):
url = reverse("api:subsonic-get-cover-art")
assert url.endswith("getCoverArt") is True
album = factories["music.Album"]()
response = logged_in_api_client.get(url, {"id": "al-{}".format(album.pk)})
assert response.status_code == 200
assert response["Content-Type"] == ""
assert response["X-Accel-Redirect"] == music_views.get_file_path(
album.cover
def test_get_avatar(factories, logged_in_api_client):
user = factories["users.User"]()
url = reverse("api:subsonic-get-avatar")
assert url.endswith("getAvatar") is True
response = logged_in_api_client.get(url, {"username": user.username})
assert response.status_code == 200
assert response["Content-Type"] == ""
assert response["X-Accel-Redirect"] == music_views.get_file_path(
user.avatar
).decode("utf-8")
def test_scrobble(factories, logged_in_api_client):
upload = factories["music.Upload"]()
track = upload.track
url = reverse("api:subsonic-scrobble")
assert url.endswith("scrobble") is True
response = logged_in_api_client.get(url, {"id": track.pk, "submission": True})
assert response.status_code == 200
listening = logged_in_api_client.user.listenings.latest("id")
assert listening.track == track
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
@pytest.mark.parametrize("f", ["json"])
def test_get_user(f, db, logged_in_api_client, factories):
url = reverse("api:subsonic-get-user")
assert url.endswith("getUser") is True
response = logged_in_api_client.get(
url, {"f": f, "username": logged_in_api_client.user.username}
)
assert response.status_code == 200
assert response.data == {
"user": {
"username": logged_in_api_client.user.username,
"email": logged_in_api_client.user.email,
"scrobblingEnabled": "true",
"adminRole": "false",
"downloadRole": "true",
"uploadRole": "true",
"settingsRole": "false",
"playlistRole": "true",
"commentRole": "false",
"podcastRole": "false",
"streamRole": "true",
"jukeboxRole": "true",
"coverArtRole": "false",
"shareRole": "false",
"folder": [
f["id"] for f in serializers.get_folders(logged_in_api_client.user)
],
}
}