2017-10-26 19:41:16 +00:00
|
|
|
# This file is part of Supysonic.
|
|
|
|
# Supysonic is a Python implementation of the Subsonic server API.
|
|
|
|
#
|
2018-03-04 20:49:56 +00:00
|
|
|
# Copyright (C) 2017-2018 Alban 'spl0k' Féron
|
2017-10-26 19:41:16 +00:00
|
|
|
#
|
|
|
|
# Distributed under terms of the GNU AGPLv3 license.
|
|
|
|
|
2017-12-14 21:28:49 +00:00
|
|
|
import re
|
2017-10-26 19:41:16 +00:00
|
|
|
import unittest
|
|
|
|
import uuid
|
|
|
|
|
2017-12-14 21:28:49 +00:00
|
|
|
from collections import namedtuple
|
2018-08-11 14:16:34 +00:00
|
|
|
from pony.orm import db_session
|
2017-12-14 21:28:49 +00:00
|
|
|
|
2017-10-26 19:41:16 +00:00
|
|
|
from supysonic import db
|
|
|
|
|
2019-06-29 15:25:44 +00:00
|
|
|
date_regex = re.compile(r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$")
|
|
|
|
|
2017-10-26 19:41:16 +00:00
|
|
|
|
|
|
|
class DbTestCase(unittest.TestCase):
|
|
|
|
def setUp(self):
|
2019-06-29 15:25:44 +00:00
|
|
|
db.init_database("sqlite:")
|
2017-10-26 19:41:16 +00:00
|
|
|
|
2018-01-16 22:16:13 +00:00
|
|
|
try:
|
|
|
|
self.assertRegex
|
|
|
|
except AttributeError:
|
|
|
|
self.assertRegex = self.assertRegexpMatches
|
|
|
|
|
2017-10-26 19:41:16 +00:00
|
|
|
def tearDown(self):
|
2017-12-19 22:16:55 +00:00
|
|
|
db.release_database()
|
2017-10-26 19:41:16 +00:00
|
|
|
|
|
|
|
def create_some_folders(self):
|
2019-06-29 15:25:44 +00:00
|
|
|
root_folder = db.Folder(root=True, name="Root folder", path="tests")
|
2017-12-14 21:28:49 +00:00
|
|
|
|
2020-11-29 16:24:28 +00:00
|
|
|
db.Folder(
|
2019-06-29 15:25:44 +00:00
|
|
|
root=False,
|
|
|
|
name="Child folder",
|
|
|
|
path="tests/assets",
|
|
|
|
cover_art="cover.jpg",
|
|
|
|
parent=root_folder,
|
2017-12-14 21:28:49 +00:00
|
|
|
)
|
2017-10-26 19:41:16 +00:00
|
|
|
|
2020-11-29 16:24:28 +00:00
|
|
|
db.Folder(
|
2019-06-29 15:25:44 +00:00
|
|
|
root=False,
|
|
|
|
name="Child folder (No Art)",
|
|
|
|
path="tests/formats",
|
|
|
|
parent=root_folder,
|
2018-10-09 17:04:17 +00:00
|
|
|
)
|
|
|
|
|
2019-10-01 14:55:23 +00:00
|
|
|
# Folder IDs don't get populated until we query the db.
|
|
|
|
return (
|
|
|
|
db.Folder.get(name="Root folder"),
|
|
|
|
db.Folder.get(name="Child folder"),
|
2020-11-08 14:39:09 +00:00
|
|
|
db.Folder.get(name="Child Folder (No Art)"),
|
2019-10-01 14:55:23 +00:00
|
|
|
)
|
2017-10-26 19:41:16 +00:00
|
|
|
|
2019-06-29 15:25:44 +00:00
|
|
|
def create_some_tracks(self, artist=None, album=None):
|
2018-10-09 17:04:17 +00:00
|
|
|
root, child, child_2 = self.create_some_folders()
|
2017-10-26 21:16:20 +00:00
|
|
|
|
|
|
|
if not artist:
|
2019-06-29 15:25:44 +00:00
|
|
|
artist = db.Artist(name="Test artist")
|
2017-10-26 21:16:20 +00:00
|
|
|
|
|
|
|
if not album:
|
2019-06-29 15:25:44 +00:00
|
|
|
album = db.Album(artist=artist, name="Test Album")
|
2017-12-14 21:28:49 +00:00
|
|
|
|
|
|
|
track1 = db.Track(
|
2019-06-29 15:25:44 +00:00
|
|
|
title="Track Title",
|
|
|
|
album=album,
|
|
|
|
artist=artist,
|
|
|
|
disc=1,
|
|
|
|
number=1,
|
|
|
|
duration=3,
|
|
|
|
has_art=True,
|
|
|
|
bitrate=320,
|
|
|
|
path="tests/assets/formats/silence.ogg",
|
|
|
|
last_modification=1234,
|
|
|
|
root_folder=root,
|
|
|
|
folder=child,
|
2017-12-14 21:28:49 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
track2 = db.Track(
|
2019-06-29 15:25:44 +00:00
|
|
|
title="One Awesome Song",
|
|
|
|
album=album,
|
|
|
|
artist=artist,
|
|
|
|
disc=1,
|
|
|
|
number=2,
|
|
|
|
duration=5,
|
|
|
|
bitrate=96,
|
|
|
|
path="tests/assets/23bytes",
|
|
|
|
last_modification=1234,
|
|
|
|
root_folder=root,
|
|
|
|
folder=child,
|
2017-12-14 21:28:49 +00:00
|
|
|
)
|
2017-10-26 21:16:20 +00:00
|
|
|
|
|
|
|
return track1, track2
|
|
|
|
|
2019-06-29 15:25:44 +00:00
|
|
|
def create_track_in(self, folder, root, artist=None, album=None, has_art=True):
|
|
|
|
artist = artist or db.Artist(name="Snazzy Artist")
|
|
|
|
album = album or db.Album(artist=artist, name="Rockin' Album")
|
2018-10-09 17:04:17 +00:00
|
|
|
return db.Track(
|
2019-06-29 15:25:44 +00:00
|
|
|
title="Nifty Number",
|
|
|
|
album=album,
|
|
|
|
artist=artist,
|
|
|
|
disc=1,
|
|
|
|
number=1,
|
|
|
|
duration=5,
|
|
|
|
has_art=has_art,
|
|
|
|
bitrate=96,
|
|
|
|
path="tests/assets/formats/silence.flac",
|
|
|
|
last_modification=1234,
|
|
|
|
root_folder=root,
|
|
|
|
folder=folder,
|
2018-10-09 17:04:17 +00:00
|
|
|
)
|
|
|
|
|
2019-06-29 15:25:44 +00:00
|
|
|
def create_user(self, name="Test User"):
|
|
|
|
return db.User(name=name, password="secret", salt="ABC+")
|
2017-12-14 21:28:49 +00:00
|
|
|
|
2017-10-27 19:42:36 +00:00
|
|
|
def create_playlist(self):
|
|
|
|
|
2019-06-29 15:25:44 +00:00
|
|
|
playlist = db.Playlist(user=self.create_user(), name="Playlist!")
|
2017-10-27 19:42:36 +00:00
|
|
|
|
|
|
|
return playlist
|
|
|
|
|
2017-12-14 21:28:49 +00:00
|
|
|
@db_session
|
2017-10-26 19:41:16 +00:00
|
|
|
def test_folder_base(self):
|
2018-10-09 17:04:17 +00:00
|
|
|
root_folder, child_folder, child_noart = self.create_some_folders()
|
|
|
|
track_embededart = self.create_track_in(child_noart, root_folder)
|
2017-10-26 19:41:16 +00:00
|
|
|
|
2019-06-29 15:25:44 +00:00
|
|
|
MockUser = namedtuple("User", ["id"])
|
2017-10-26 19:41:16 +00:00
|
|
|
user = MockUser(uuid.uuid4())
|
|
|
|
|
|
|
|
root = root_folder.as_subsonic_child(user)
|
|
|
|
self.assertIsInstance(root, dict)
|
2019-06-29 15:25:44 +00:00
|
|
|
self.assertIn("id", root)
|
|
|
|
self.assertIn("isDir", root)
|
|
|
|
self.assertIn("title", root)
|
|
|
|
self.assertIn("album", root)
|
|
|
|
self.assertIn("created", root)
|
|
|
|
self.assertTrue(root["isDir"])
|
|
|
|
self.assertEqual(root["title"], "Root folder")
|
|
|
|
self.assertEqual(root["album"], "Root folder")
|
|
|
|
self.assertRegex(root["created"], date_regex)
|
2017-10-26 19:41:16 +00:00
|
|
|
|
|
|
|
child = child_folder.as_subsonic_child(user)
|
2019-06-29 15:25:44 +00:00
|
|
|
self.assertIn("parent", child)
|
|
|
|
self.assertIn("artist", child)
|
|
|
|
self.assertIn("coverArt", child)
|
|
|
|
self.assertEqual(child["parent"], str(root_folder.id))
|
|
|
|
self.assertEqual(child["artist"], root_folder.name)
|
|
|
|
self.assertEqual(child["coverArt"], child["id"])
|
2017-12-14 21:28:49 +00:00
|
|
|
|
2018-10-09 17:04:17 +00:00
|
|
|
noart = child_noart.as_subsonic_child(user)
|
2019-06-29 15:25:44 +00:00
|
|
|
self.assertIn("coverArt", noart)
|
|
|
|
self.assertEqual(noart["coverArt"], str(track_embededart.id))
|
2018-10-09 17:04:17 +00:00
|
|
|
|
2017-12-14 21:28:49 +00:00
|
|
|
@db_session
|
2017-10-26 19:41:16 +00:00
|
|
|
def test_folder_annotation(self):
|
2018-10-09 17:04:17 +00:00
|
|
|
root_folder, child_folder, _ = self.create_some_folders()
|
2017-10-26 19:41:16 +00:00
|
|
|
|
2017-12-14 21:28:49 +00:00
|
|
|
user = self.create_user()
|
2020-11-29 16:24:28 +00:00
|
|
|
db.StarredFolder(user=user, starred=root_folder)
|
|
|
|
db.RatingFolder(user=user, rated=root_folder, rating=2)
|
2019-06-29 15:25:44 +00:00
|
|
|
other = self.create_user("Other")
|
2020-11-29 16:24:28 +00:00
|
|
|
db.RatingFolder(user=other, rated=root_folder, rating=5)
|
2017-10-26 19:41:16 +00:00
|
|
|
|
|
|
|
root = root_folder.as_subsonic_child(user)
|
2019-06-29 15:25:44 +00:00
|
|
|
self.assertIn("starred", root)
|
|
|
|
self.assertIn("userRating", root)
|
|
|
|
self.assertIn("averageRating", root)
|
|
|
|
self.assertRegex(root["starred"], date_regex)
|
|
|
|
self.assertEqual(root["userRating"], 2)
|
|
|
|
self.assertEqual(root["averageRating"], 3.5)
|
2017-10-26 19:41:16 +00:00
|
|
|
|
|
|
|
child = child_folder.as_subsonic_child(user)
|
2019-06-29 15:25:44 +00:00
|
|
|
self.assertNotIn("starred", child)
|
|
|
|
self.assertNotIn("userRating", child)
|
2017-10-26 19:41:16 +00:00
|
|
|
|
2017-12-14 21:28:49 +00:00
|
|
|
@db_session
|
2017-10-26 21:16:20 +00:00
|
|
|
def test_artist(self):
|
2019-06-29 15:25:44 +00:00
|
|
|
artist = db.Artist(name="Test Artist")
|
2017-10-26 21:16:20 +00:00
|
|
|
|
2017-12-14 21:28:49 +00:00
|
|
|
user = self.create_user()
|
2020-11-29 16:24:28 +00:00
|
|
|
db.StarredArtist(user=user, starred=artist)
|
2017-10-26 21:16:20 +00:00
|
|
|
|
|
|
|
artist_dict = artist.as_subsonic_artist(user)
|
|
|
|
self.assertIsInstance(artist_dict, dict)
|
2019-06-29 15:25:44 +00:00
|
|
|
self.assertIn("id", artist_dict)
|
|
|
|
self.assertIn("name", artist_dict)
|
|
|
|
self.assertIn("albumCount", artist_dict)
|
|
|
|
self.assertIn("starred", artist_dict)
|
|
|
|
self.assertEqual(artist_dict["name"], "Test Artist")
|
|
|
|
self.assertEqual(artist_dict["albumCount"], 0)
|
|
|
|
self.assertRegex(artist_dict["starred"], date_regex)
|
2017-12-14 21:28:49 +00:00
|
|
|
|
2019-06-29 15:25:44 +00:00
|
|
|
db.Album(name="Test Artist", artist=artist) # self-titled
|
|
|
|
db.Album(name="The Album After The First One", artist=artist)
|
2017-10-26 21:16:20 +00:00
|
|
|
|
|
|
|
artist_dict = artist.as_subsonic_artist(user)
|
2019-06-29 15:25:44 +00:00
|
|
|
self.assertEqual(artist_dict["albumCount"], 2)
|
2017-10-26 21:16:20 +00:00
|
|
|
|
2017-12-14 21:28:49 +00:00
|
|
|
@db_session
|
2017-10-26 21:16:20 +00:00
|
|
|
def test_album(self):
|
2019-06-29 15:25:44 +00:00
|
|
|
artist = db.Artist(name="Test Artist")
|
|
|
|
album = db.Album(artist=artist, name="Test Album")
|
2017-12-14 21:28:49 +00:00
|
|
|
|
|
|
|
user = self.create_user()
|
2020-11-29 16:24:28 +00:00
|
|
|
db.StarredAlbum(user=user, starred=album)
|
2017-10-26 21:16:20 +00:00
|
|
|
|
|
|
|
# No tracks, shouldn't be stored under normal circumstances
|
|
|
|
self.assertRaises(ValueError, album.as_subsonic_album, user)
|
|
|
|
|
2018-10-12 23:07:48 +00:00
|
|
|
root_folder, folder_art, folder_noart = self.create_some_folders()
|
2019-06-29 15:25:44 +00:00
|
|
|
track1 = self.create_track_in(
|
2019-10-01 14:55:23 +00:00
|
|
|
folder_noart, root_folder, artist=artist, album=album
|
2019-06-29 15:25:44 +00:00
|
|
|
)
|
2017-10-26 21:16:20 +00:00
|
|
|
|
|
|
|
album_dict = album.as_subsonic_album(user)
|
|
|
|
self.assertIsInstance(album_dict, dict)
|
2019-06-29 15:25:44 +00:00
|
|
|
self.assertIn("id", album_dict)
|
|
|
|
self.assertIn("name", album_dict)
|
|
|
|
self.assertIn("artist", album_dict)
|
|
|
|
self.assertIn("artistId", album_dict)
|
|
|
|
self.assertIn("songCount", album_dict)
|
|
|
|
self.assertIn("duration", album_dict)
|
|
|
|
self.assertIn("created", album_dict)
|
|
|
|
self.assertIn("starred", album_dict)
|
|
|
|
self.assertIn("coverArt", album_dict)
|
|
|
|
self.assertEqual(album_dict["name"], album.name)
|
|
|
|
self.assertEqual(album_dict["artist"], artist.name)
|
|
|
|
self.assertEqual(album_dict["artistId"], str(artist.id))
|
|
|
|
self.assertEqual(album_dict["songCount"], 1)
|
|
|
|
self.assertEqual(album_dict["duration"], 5)
|
|
|
|
self.assertEqual(album_dict["coverArt"], str(track1.id))
|
|
|
|
self.assertRegex(album_dict["created"], date_regex)
|
|
|
|
self.assertRegex(album_dict["starred"], date_regex)
|
2017-12-14 21:28:49 +00:00
|
|
|
|
|
|
|
@db_session
|
2017-10-26 21:16:20 +00:00
|
|
|
def test_track(self):
|
|
|
|
track1, track2 = self.create_some_tracks()
|
2017-10-27 19:42:36 +00:00
|
|
|
|
|
|
|
# Assuming SQLite doesn't enforce foreign key constraints
|
2019-06-29 15:25:44 +00:00
|
|
|
MockUser = namedtuple("User", ["id"])
|
2017-10-27 19:42:36 +00:00
|
|
|
user = MockUser(uuid.uuid4())
|
|
|
|
|
|
|
|
track1_dict = track1.as_subsonic_child(user, None)
|
|
|
|
self.assertIsInstance(track1_dict, dict)
|
2019-06-29 15:25:44 +00:00
|
|
|
self.assertIn("id", track1_dict)
|
|
|
|
self.assertIn("parent", track1_dict)
|
|
|
|
self.assertIn("isDir", track1_dict)
|
|
|
|
self.assertIn("title", track1_dict)
|
|
|
|
self.assertFalse(track1_dict["isDir"])
|
|
|
|
self.assertIn("coverArt", track1_dict)
|
|
|
|
self.assertEqual(track1_dict["coverArt"], track1_dict["id"])
|
2018-10-09 17:04:17 +00:00
|
|
|
|
|
|
|
track2_dict = track2.as_subsonic_child(user, None)
|
2019-06-29 15:25:44 +00:00
|
|
|
self.assertEqual(track2_dict["coverArt"], track2_dict["parent"])
|
2017-10-27 19:42:36 +00:00
|
|
|
# ... we'll test the rest against the API XSD.
|
|
|
|
|
2017-12-14 21:28:49 +00:00
|
|
|
@db_session
|
2017-10-27 19:42:36 +00:00
|
|
|
def test_user(self):
|
2017-12-14 21:28:49 +00:00
|
|
|
user = self.create_user()
|
2017-10-27 19:42:36 +00:00
|
|
|
|
|
|
|
user_dict = user.as_subsonic_user()
|
|
|
|
self.assertIsInstance(user_dict, dict)
|
|
|
|
|
2017-12-14 21:28:49 +00:00
|
|
|
@db_session
|
2017-10-27 19:42:36 +00:00
|
|
|
def test_chat(self):
|
2017-12-14 21:28:49 +00:00
|
|
|
user = self.create_user()
|
2017-10-27 19:42:36 +00:00
|
|
|
|
2019-06-29 15:25:44 +00:00
|
|
|
line = db.ChatMessage(user=user, message="Hello world!")
|
2017-10-27 19:42:36 +00:00
|
|
|
|
|
|
|
line_dict = line.responsize()
|
|
|
|
self.assertIsInstance(line_dict, dict)
|
2019-06-29 15:25:44 +00:00
|
|
|
self.assertIn("username", line_dict)
|
|
|
|
self.assertEqual(line_dict["username"], user.name)
|
2017-10-27 19:42:36 +00:00
|
|
|
|
2017-12-14 21:28:49 +00:00
|
|
|
@db_session
|
2017-10-27 19:42:36 +00:00
|
|
|
def test_playlist(self):
|
|
|
|
playlist = self.create_playlist()
|
|
|
|
playlist_dict = playlist.as_subsonic_playlist(playlist.user)
|
|
|
|
self.assertIsInstance(playlist_dict, dict)
|
|
|
|
|
2017-12-14 21:28:49 +00:00
|
|
|
@db_session
|
2017-10-27 19:42:36 +00:00
|
|
|
def test_playlist_tracks(self):
|
|
|
|
playlist = self.create_playlist()
|
|
|
|
track1, track2 = self.create_some_tracks()
|
|
|
|
|
|
|
|
playlist.add(track1)
|
|
|
|
playlist.add(track2)
|
2019-06-29 15:25:44 +00:00
|
|
|
self.assertSequenceEqual(playlist.get_tracks(), [track1, track2])
|
2017-10-27 19:42:36 +00:00
|
|
|
|
|
|
|
playlist.add(track2.id)
|
|
|
|
playlist.add(track1.id)
|
2019-06-29 15:25:44 +00:00
|
|
|
self.assertSequenceEqual(
|
|
|
|
playlist.get_tracks(), [track1, track2, track2, track1]
|
|
|
|
)
|
2017-10-27 19:42:36 +00:00
|
|
|
|
|
|
|
playlist.clear()
|
|
|
|
self.assertSequenceEqual(playlist.get_tracks(), [])
|
|
|
|
|
|
|
|
playlist.add(str(track1.id))
|
2019-06-29 15:25:44 +00:00
|
|
|
self.assertSequenceEqual(playlist.get_tracks(), [track1])
|
2017-10-27 19:42:36 +00:00
|
|
|
|
2019-06-29 15:25:44 +00:00
|
|
|
self.assertRaises(ValueError, playlist.add, "some string")
|
2017-10-27 19:42:36 +00:00
|
|
|
self.assertRaises(NameError, playlist.add, 2345)
|
|
|
|
|
2017-12-14 21:28:49 +00:00
|
|
|
@db_session
|
2017-10-27 19:42:36 +00:00
|
|
|
def test_playlist_remove_tracks(self):
|
|
|
|
playlist = self.create_playlist()
|
|
|
|
track1, track2 = self.create_some_tracks()
|
|
|
|
|
|
|
|
playlist.add(track1)
|
|
|
|
playlist.add(track2)
|
2019-06-29 15:25:44 +00:00
|
|
|
playlist.remove_at_indexes([0, 2])
|
|
|
|
self.assertSequenceEqual(playlist.get_tracks(), [track2])
|
2017-10-27 19:42:36 +00:00
|
|
|
|
|
|
|
playlist.add(track1)
|
|
|
|
playlist.add(track2)
|
|
|
|
playlist.add(track2)
|
2019-06-29 15:25:44 +00:00
|
|
|
playlist.remove_at_indexes([2, 1])
|
|
|
|
self.assertSequenceEqual(playlist.get_tracks(), [track2, track2])
|
2017-10-27 19:42:36 +00:00
|
|
|
|
|
|
|
playlist.add(track1)
|
2019-06-29 15:25:44 +00:00
|
|
|
playlist.remove_at_indexes([1, 1])
|
|
|
|
self.assertSequenceEqual(playlist.get_tracks(), [track2, track1])
|
2017-10-27 19:42:36 +00:00
|
|
|
|
2017-12-14 21:28:49 +00:00
|
|
|
@db_session
|
2017-10-27 19:42:36 +00:00
|
|
|
def test_playlist_fixing(self):
|
|
|
|
playlist = self.create_playlist()
|
|
|
|
track1, track2 = self.create_some_tracks()
|
|
|
|
|
|
|
|
playlist.add(track1)
|
|
|
|
playlist.add(uuid.uuid4())
|
|
|
|
playlist.add(track2)
|
2019-06-29 15:25:44 +00:00
|
|
|
self.assertSequenceEqual(playlist.get_tracks(), [track1, track2])
|
2017-10-27 19:42:36 +00:00
|
|
|
|
2017-12-14 21:28:49 +00:00
|
|
|
track2.delete()
|
2019-06-29 15:25:44 +00:00
|
|
|
self.assertSequenceEqual(playlist.get_tracks(), [track1])
|
2017-10-27 19:42:36 +00:00
|
|
|
|
2019-06-29 15:25:44 +00:00
|
|
|
playlist.tracks = "{0},{0},some random garbage,{0}".format(track1.id)
|
|
|
|
self.assertSequenceEqual(playlist.get_tracks(), [track1, track1, track1])
|
2017-10-26 21:16:20 +00:00
|
|
|
|
|
|
|
|
2019-06-29 15:25:44 +00:00
|
|
|
if __name__ == "__main__":
|
|
|
|
unittest.main()
|