#!/usr/bin/env python # -*- coding: utf-8 -*- # vim:fenc=utf-8 # # This file is part of Supysonic. # Supysonic is a Python implementation of the Subsonic server API. # # Copyright (C) 2017 Alban 'spl0k' FĂ©ron # # Distributed under terms of the GNU AGPLv3 license. from lxml import etree import time import uuid from supysonic.db import Folder, Artist, Album, Track from .apitestbase import ApiTestBase class BrowseTestCase(ApiTestBase): def setUp(self): super(BrowseTestCase, self).setUp() empty = Folder() empty.root = True empty.name = 'Empty root' empty.path = '/tmp' self.store.add(empty) root = Folder() root.root = True root.name = 'Root folder' root.path = 'tests/assets' self.store.add(root) for letter in 'ABC': folder = Folder() folder.name = letter + 'rtist' folder.path = 'tests/assets/{}rtist'.format(letter) folder.parent = root artist = Artist() artist.name = letter + 'rtist' for lether in 'AB': afolder = Folder() afolder.name = letter + lether + 'lbum' afolder.path = 'tests/assets/{0}rtist/{0}{1}lbum'.format(letter, lether) afolder.parent = folder album = Album() album.name = letter + lether + 'lbum' album.artist = artist for num, song in enumerate([ 'One', 'Two', 'Three' ]): track = Track() track.disc = 1 track.number = num track.title = song track.duration = 2 track.album = album track.artist = artist track.bitrate = 320 track.path = 'tests/assets/{0}rtist/{0}{1}lbum/{2}'.format(letter, lether, song) track.content_type = 'audio/mpeg' track.last_modification = 0 track.root_folder = root track.folder = afolder self.store.add(track) self.store.commit() self.assertEqual(self.store.find(Folder).count(), 11) self.assertEqual(self.store.find(Folder, Folder.root == True).count(), 2) self.assertEqual(self.store.find(Artist).count(), 3) self.assertEqual(self.store.find(Album).count(), 6) self.assertEqual(self.store.find(Track).count(), 18) def test_get_music_folders(self): # Do not validate against the XSD here, this is the only place where the API should return ids as ints # all our ids are uuids :/ rv = self.client.get('/rest/getMusicFolders.view', query_string = { 'u': 'alice', 'p': 'Alic3', 'c': 'tests' }) self.assertEqual(rv.status_code, 200) xml = etree.fromstring(rv.data) NS = '{http://subsonic.org/restapi}' self.assertEqual(xml.tag, NS + 'subsonic-response') self.assertEqual(xml.get('status'), 'ok') self.assertEqual(len(xml), 1) self.assertEqual(xml[0].tag, NS + 'musicFolders') self.assertEqual(len(xml[0]), 2) self.assertSequenceEqual(sorted(self._xpath(xml, './musicFolders/musicFolder/@name')), [ 'Empty root', 'Root folder' ]) def test_get_indexes(self): self._make_request('getIndexes', { 'musicFolderId': 'abcdef' }, error = 0) self._make_request('getIndexes', { 'musicFolderId': str(uuid.uuid4()) }, error = 70) self._make_request('getIndexes', { 'ifModifiedSince': 'quoi' }, error = 0) rv, child = self._make_request('getIndexes', { 'ifModifiedSince': int(time.time() * 1000 + 1000) }, tag = 'indexes') self.assertEqual(len(child), 0) fid = self.store.find(Folder, Folder.name == 'Empty root').one().id rv, child = self._make_request('getIndexes', { 'musicFolderId': str(fid) }, tag = 'indexes') self.assertEqual(len(child), 0) rv, child = self._make_request('getIndexes', tag = 'indexes') self.assertEqual(len(child), 3) for i, letter in enumerate([ 'A', 'B', 'C' ]): self.assertEqual(child[i].get('name'), letter) self.assertEqual(len(child[i]), 1) self.assertEqual(child[i][0].get('name'), letter + 'rtist') def test_get_music_directory(self): self._make_request('getMusicDirectory', error = 10) self._make_request('getMusicDirectory', { 'id': 'id' }, error = 0) self._make_request('getMusicDirectory', { 'id': str(uuid.uuid4()) }, error = 70) # should test with folders with both children folders and tracks. this code would break in that case for f in self.store.find(Folder): rv, child = self._make_request('getMusicDirectory', { 'id': str(f.id) }, tag = 'directory') self.assertEqual(child.get('id'), str(f.id)) self.assertEqual(child.get('name'), f.name) self.assertEqual(len(child), f.children.count() + f.tracks.count()) for dbc, xmlc in zip(sorted(f.children, key = lambda c: c.name), sorted(child, key = lambda c: c.get('title'))): self.assertEqual(dbc.name, xmlc.get('title')) self.assertEqual(xmlc.get('artist'), f.name) self.assertEqual(xmlc.get('parent'), str(f.id)) for t, xmlc in zip(sorted(f.tracks, key = lambda t: t.title), sorted(child, key = lambda c: c.get('title'))): self.assertEqual(t.title, xmlc.get('title')) self.assertEqual(xmlc.get('parent'), str(f.id)) def test_get_artists(self): # same as getIndexes standard case # dataset should be improved to have a different directory structure than /root/Artist/Album/Track rv, child = self._make_request('getArtists', tag = 'artists') self.assertEqual(len(child), 3) for i, letter in enumerate([ 'A', 'B', 'C' ]): self.assertEqual(child[i].get('name'), letter) self.assertEqual(len(child[i]), 1) self.assertEqual(child[i][0].get('name'), letter + 'rtist') def test_get_artist(self): # dataset should be improved to have tracks by a different artist than the album's artist self._make_request('getArtist', error = 10) self._make_request('getArtist', { 'id': 'artist' }, error = 0) self._make_request('getArtist', { 'id': str(uuid.uuid4()) }, error = 70) for ar in self.store.find(Artist): rv, child = self._make_request('getArtist', { 'id': str(ar.id) }, tag = 'artist') self.assertEqual(child.get('id'), str(ar.id)) self.assertEqual(child.get('albumCount'), str(len(child))) self.assertEqual(len(child), ar.albums.count()) for dal, xal in zip(sorted(ar.albums, key = lambda a: a.name), sorted(child, key = lambda c: c.get('name'))): self.assertEqual(dal.name, xal.get('name')) self.assertEqual(xal.get('artist'), ar.name) # could break with a better dataset self.assertEqual(xal.get('artistId'), str(ar.id)) # see above def test_get_album(self): self._make_request('getAlbum', error = 10) self._make_request('getAlbum', { 'id': 'nastynasty' }, error = 0) self._make_request('getAlbum', { 'id': str(uuid.uuid4()) }, error = 70) a = self.store.find(Album)[0] rv, child = self._make_request('getAlbum', { 'id': str(a.id) }, tag = 'album') self.assertEqual(child.get('id'), str(a.id)) self.assertEqual(child.get('songCount'), str(len(child))) self.assertEqual(len(child), a.tracks.count()) for dal, xal in zip(sorted(a.tracks, key = lambda t: t.title), sorted(child, key = lambda c: c.get('title'))): self.assertEqual(dal.title, xal.get('title')) self.assertEqual(xal.get('album'), a.name) self.assertEqual(xal.get('albumId'), str(a.id)) def test_get_song(self): self._make_request('getSong', error = 10) self._make_request('getSong', { 'id': 'nastynasty' }, error = 0) self._make_request('getSong', { 'id': str(uuid.uuid4()) }, error = 70) s = self.store.find(Track)[0] self._make_request('getSong', { 'id': str(s.id) }, tag = 'song') def test_get_videos(self): self._make_request('getVideos', error = 0) if __name__ == '__main__': unittest.main()