From 6f713e12dbf8c3ea2a8dd844eeb6edb3ebc1501f Mon Sep 17 00:00:00 2001 From: spl0k Date: Fri, 3 Nov 2017 23:15:48 +0100 Subject: [PATCH] Added tests for search and chat --- tests/__init__.py | 2 +- tests/api/__init__.py | 4 + tests/api/apitestbase.py | 8 + tests/api/test_chat.py | 46 ++++++ tests/api/test_search.py | 346 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 405 insertions(+), 1 deletion(-) create mode 100644 tests/api/test_chat.py create mode 100644 tests/api/test_search.py diff --git a/tests/__init__.py b/tests/__init__.py index 62171eb..e976340 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -18,10 +18,10 @@ from .test_frontend import FrontendTestCase def suite(): suite = unittest.TestSuite() + suite.addTest(managers.suite()) suite.addTest(base.suite()) suite.addTest(api.suite()) - suite.addTest(managers.suite()) suite.addTest(unittest.makeSuite(FrontendTestCase)) return suite diff --git a/tests/api/__init__.py b/tests/api/__init__.py index 4b7a94a..5eda1a3 100644 --- a/tests/api/__init__.py +++ b/tests/api/__init__.py @@ -14,6 +14,8 @@ from .test_response_helper import suite as rh_suite from .test_api_setup import ApiSetupTestCase from .test_system import SystemTestCase from .test_user import UserTestCase +from .test_chat import ChatTestCase +from .test_search import SearchTestCase def suite(): suite = unittest.TestSuite() @@ -22,6 +24,8 @@ def suite(): suite.addTest(unittest.makeSuite(ApiSetupTestCase)) suite.addTest(unittest.makeSuite(SystemTestCase)) suite.addTest(unittest.makeSuite(UserTestCase)) + suite.addTest(unittest.makeSuite(ChatTestCase)) + suite.addTest(unittest.makeSuite(SearchTestCase)) return suite diff --git a/tests/api/apitestbase.py b/tests/api/apitestbase.py index 1b60992..8321a5c 100644 --- a/tests/api/apitestbase.py +++ b/tests/api/apitestbase.py @@ -53,6 +53,14 @@ class ApiTestBase(unittest.TestCase): path = path_replace_regexp.sub(r'/{}\1'.format(NS), path) return xml.find(path) + def _findall(self, xml, path): + """ + Helper method that insert the namespace in XPath 'path' + """ + + path = path_replace_regexp.sub(r'/{}\1'.format(NS), path) + return xml.findall(path) + def _make_request(self, endpoint, args = {}, tag = None, error = None, skip_post = False): """ Makes both a GET and POST requests against the API, assert both get the same response. diff --git a/tests/api/test_chat.py b/tests/api/test_chat.py new file mode 100644 index 0000000..23f1dc6 --- /dev/null +++ b/tests/api/test_chat.py @@ -0,0 +1,46 @@ +#!/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. + +import unittest + +import time + +from .apitestbase import ApiTestBase + +class ChatTestCase(ApiTestBase): + def test_add_message(self): + self._make_request('addChatMessage', error = 10) + rv, child = self._make_request('getChatMessages', tag = 'chatMessages') + self.assertEqual(len(child), 0) + + self._make_request('addChatMessage', { 'message': 'Heres a message' }, skip_post = True) + rv, child = self._make_request('getChatMessages', tag = 'chatMessages') + self.assertEqual(len(child), 1) + self.assertEqual(child[0].get('username'), 'alice') + self.assertEqual(child[0].get('message'), 'Heres a message') + + def test_get_messages(self): + self._make_request('addChatMessage', { 'message': 'Hello' }, skip_post = True) + time.sleep(1) + self._make_request('addChatMessage', { 'message': 'Is someone there?' }, skip_post = True) + + rv, child = self._make_request('getChatMessages', tag = 'chatMessages') + self.assertEqual(len(child), 2) + + rv, child = self._make_request('getChatMessages', { 'since': int(time.time()) * 1000 - 500 }, tag = 'chatMessages') + self.assertEqual(len(child), 1) + self.assertEqual(child[0].get('message'), 'Is someone there?') + + self._make_request('getChatMessages', { 'since': 'invalid timestamp' }, error = 0) + +if __name__ == '__main__': + unittest.main() + diff --git a/tests/api/test_search.py b/tests/api/test_search.py new file mode 100644 index 0000000..d930da7 --- /dev/null +++ b/tests/api/test_search.py @@ -0,0 +1,346 @@ +#!/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. + +import time +import unittest + +from supysonic.db import Folder, Artist, Album, Track + +from .apitestbase import ApiTestBase + +class SearchTestCase(ApiTestBase): + def setUp(self): + super(SearchTestCase, self).setUp() + + 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(), 10) + 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 __track_as_pseudo_unique_str(self, elem): + return elem.get('artist') + elem.get('album') + elem.get('title') + + def test_search(self): + # invalid parameters + self._make_request('search', { 'count': 'string' }, error = 0) + self._make_request('search', { 'offset': 'sstring' }, error = 0) + self._make_request('search', { 'newerThan': 'ssstring' }, error = 0) + + # no search + self._make_request('search', error = 10) + + # non existent artist (but searched string present in other fields) + rv, child = self._make_request('search', { 'artist': 'One' }, tag = 'searchResult') + self.assertEqual(len(child), 0) + self.assertEqual(child.get('totalHits'), '0') + self.assertEqual(child.get('offset'), '0') + + rv, child = self._make_request('search', { 'artist': 'AAlbum' }, tag = 'searchResult') + self.assertEqual(len(child), 0) + + # non existent album (but search present in other fields) + rv, child = self._make_request('search', { 'album': 'One' }, tag = 'searchResult') + self.assertEqual(len(child), 0) + + rv, child = self._make_request('search', { 'album': 'Artist' }, tag = 'searchResult') + self.assertEqual(len(child), 0) + + # non existent title (but search present in other fields) + rv, child = self._make_request('search', { 'title': 'AAlbum' }, tag = 'searchResult') + self.assertEqual(len(child), 0) + + rv, child = self._make_request('search', { 'title': 'Artist' }, tag = 'searchResult') + self.assertEqual(len(child), 0) + + # non existent anything + rv, child = self._make_request('search', { 'any': 'Chaos' }, tag = 'searchResult') + self.assertEqual(len(child), 0) + + # artist search + rv, child = self._make_request('search', { 'artist': 'Artist' }, tag = 'searchResult') + self.assertEqual(len(child), 1) + self.assertEqual(child.get('totalHits'), '1') + self.assertEqual(child[0].get('title'), 'Artist') + + rv, child = self._make_request('search', { 'artist': 'rti' }, tag = 'searchResult') + self.assertEqual(len(child), 3) + self.assertEqual(child.get('totalHits'), '3') + + # same as above, but created in the future + future = int(time.time() * 1000 + 1000) + rv, child = self._make_request('search', { 'artist': 'rti', 'newerThan': future }, tag = 'searchResult') + self.assertEqual(len(child), 0) + + # album search + rv, child = self._make_request('search', { 'album': 'AAlbum' }, tag = 'searchResult') + self.assertEqual(len(child), 1) + self.assertEqual(child[0].get('title'), 'AAlbum') + self.assertEqual(child[0].get('artist'), 'Artist') + + rv, child = self._make_request('search', { 'album': 'lbu' }, tag = 'searchResult') + self.assertEqual(len(child), 6) + + # same as above, but created in the future + rv, child = self._make_request('search', { 'album': 'lbu', 'newerThan': future }, tag = 'searchResult') + self.assertEqual(len(child), 0) + + # song search + rv, child = self._make_request('search', { 'title': 'One' }, tag = 'searchResult') + self.assertEqual(len(child), 6) + for i in range(6): + self.assertEqual(child[i].get('title'), 'One') + + rv, child = self._make_request('search', { 'title': 'e' }, tag = 'searchResult') + self.assertEqual(len(child), 12) + + # same as above, but created in the future + rv, child = self._make_request('search', { 'title': 'e', 'newerThan': future }, tag = 'searchResult') + self.assertEqual(len(child), 0) + + # any field search + rv, child = self._make_request('search', { 'any': 'r' }, tag = 'searchResult') + self.assertEqual(len(child), 10) # root + 3 artists (*rtist) + 6 songs (Three) + + # same as above, but created in the future + rv, child = self._make_request('search', { 'any': 'r', 'newerThan': future }, tag = 'searchResult') + self.assertEqual(len(child), 0) + + # paging + songs = [] + for offset in range(0, 12, 2): + rv, child = self._make_request('search', { 'title': 'e', 'count': 2, 'offset': offset }, tag = 'searchResult') + self.assertEqual(len(child), 2) + self.assertEqual(child.get('totalHits'), '12') + self.assertEqual(child.get('offset'), str(offset)) + for song in map(self.__track_as_pseudo_unique_str, child): + self.assertNotIn(song, songs) + songs += list(map(self.__track_as_pseudo_unique_str, child)) + + def test_search2(self): + # invalid parameters + self._make_request('search2', { 'artistCount': 'string' }, error = 0) + self._make_request('search2', { 'artistOffset': 'sstring' }, error = 0) + self._make_request('search2', { 'albumCount': 'string' }, error = 0) + self._make_request('search2', { 'albumOffset': 'sstring' }, error = 0) + self._make_request('search2', { 'songCount': 'string' }, error = 0) + self._make_request('search2', { 'songOffset': 'sstring' }, error = 0) + + # no search + self._make_request('search2', error = 10) + + # non existent anything + rv, child = self._make_request('search2', { 'query': 'Chaos' }, tag = 'searchResult2') + self.assertEqual(len(child), 0) + + # artist search + rv, child = self._make_request('search2', { 'query': 'Artist' }, tag = 'searchResult2') + self.assertEqual(len(child), 1) + self.assertEqual(len(self._findall(child, './artist')), 1) + self.assertEqual(len(self._findall(child, './album')), 0) + self.assertEqual(len(self._findall(child, './song')), 0) + self.assertEqual(child[0].get('name'), 'Artist') + + rv, child = self._make_request('search2', { 'query': 'rti' }, tag = 'searchResult2') + self.assertEqual(len(child), 3) + self.assertEqual(len(self._findall(child, './artist')), 3) + self.assertEqual(len(self._findall(child, './album')), 0) + self.assertEqual(len(self._findall(child, './song')), 0) + + # album search + rv, child = self._make_request('search2', { 'query': 'AAlbum' }, tag = 'searchResult2') + self.assertEqual(len(child), 1) + self.assertEqual(len(self._findall(child, './artist')), 0) + self.assertEqual(len(self._findall(child, './album')), 1) + self.assertEqual(len(self._findall(child, './song')), 0) + self.assertEqual(child[0].get('title'), 'AAlbum') + self.assertEqual(child[0].get('artist'), 'Artist') + + rv, child = self._make_request('search2', { 'query': 'lbu' }, tag = 'searchResult2') + self.assertEqual(len(child), 6) + self.assertEqual(len(self._findall(child, './artist')), 0) + self.assertEqual(len(self._findall(child, './album')), 6) + self.assertEqual(len(self._findall(child, './song')), 0) + + # song search + rv, child = self._make_request('search2', { 'query': 'One' }, tag = 'searchResult2') + self.assertEqual(len(child), 6) + self.assertEqual(len(self._findall(child, './artist')), 0) + self.assertEqual(len(self._findall(child, './album')), 0) + self.assertEqual(len(self._findall(child, './song')), 6) + for i in range(6): + self.assertEqual(child[i].get('title'), 'One') + + rv, child = self._make_request('search2', { 'query': 'e' }, tag = 'searchResult2') + self.assertEqual(len(child), 12) + self.assertEqual(len(self._findall(child, './artist')), 0) + self.assertEqual(len(self._findall(child, './album')), 0) + self.assertEqual(len(self._findall(child, './song')), 12) + + # any field search + rv, child = self._make_request('search2', { 'query': 'r' }, tag = 'searchResult2') + self.assertEqual(len(child), 9) + self.assertEqual(len(self._findall(child, './artist')), 3) + self.assertEqual(len(self._findall(child, './album')), 0) + self.assertEqual(len(self._findall(child, './song')), 6) + + # paging + artists = [] + for offset in range(0, 4, 2): + rv, child = self._make_request('search2', { 'query': 'r', 'artistCount': 2, 'artistOffset': offset }, tag = 'searchResult2') + elems = self._findall(child, './artist') + self.assertLessEqual(len(elems), 2) + for artist in map(lambda a: a.get('name'), elems): + self.assertNotIn(artist, artists) + artists += list(map(lambda a: a.get('name'), elems)) + + songs = [] + for offset in range(0, 6, 2): + rv, child = self._make_request('search2', { 'query': 'r', 'songCount': 2, 'songOffset': offset }, tag = 'searchResult2') + elems = self._findall(child, './song') + self.assertEqual(len(elems), 2) + for song in map(self.__track_as_pseudo_unique_str, elems): + self.assertNotIn(song, songs) + songs += list(map(self.__track_as_pseudo_unique_str, elems)) + + # Almost identical as above. Test dataset (and tests) should probably be changed + # to have folders that don't share names with artists or albums + def test_search3(self): + # invalid parameters + self._make_request('search3', { 'artistCount': 'string' }, error = 0) + self._make_request('search3', { 'artistOffset': 'sstring' }, error = 0) + self._make_request('search3', { 'albumCount': 'string' }, error = 0) + self._make_request('search3', { 'albumOffset': 'sstring' }, error = 0) + self._make_request('search3', { 'songCount': 'string' }, error = 0) + self._make_request('search3', { 'songOffset': 'sstring' }, error = 0) + + # no search + self._make_request('search3', error = 10) + + # non existent anything + rv, child = self._make_request('search3', { 'query': 'Chaos' }, tag = 'searchResult3') + self.assertEqual(len(child), 0) + + # artist search + rv, child = self._make_request('search3', { 'query': 'Artist' }, tag = 'searchResult3') + self.assertEqual(len(child), 1) + self.assertEqual(len(self._findall(child, './artist')), 1) + self.assertEqual(len(self._findall(child, './album')), 0) + self.assertEqual(len(self._findall(child, './song')), 0) + self.assertEqual(child[0].get('name'), 'Artist') + + rv, child = self._make_request('search3', { 'query': 'rti' }, tag = 'searchResult3') + self.assertEqual(len(child), 3) + self.assertEqual(len(self._findall(child, './artist')), 3) + self.assertEqual(len(self._findall(child, './album')), 0) + self.assertEqual(len(self._findall(child, './song')), 0) + + # album search + rv, child = self._make_request('search3', { 'query': 'AAlbum' }, tag = 'searchResult3') + self.assertEqual(len(child), 1) + self.assertEqual(len(self._findall(child, './artist')), 0) + self.assertEqual(len(self._findall(child, './album')), 1) + self.assertEqual(len(self._findall(child, './song')), 0) + self.assertEqual(child[0].get('name'), 'AAlbum') + self.assertEqual(child[0].get('artist'), 'Artist') + + rv, child = self._make_request('search3', { 'query': 'lbu' }, tag = 'searchResult3') + self.assertEqual(len(child), 6) + self.assertEqual(len(self._findall(child, './artist')), 0) + self.assertEqual(len(self._findall(child, './album')), 6) + self.assertEqual(len(self._findall(child, './song')), 0) + + # song search + rv, child = self._make_request('search3', { 'query': 'One' }, tag = 'searchResult3') + self.assertEqual(len(child), 6) + self.assertEqual(len(self._findall(child, './artist')), 0) + self.assertEqual(len(self._findall(child, './album')), 0) + self.assertEqual(len(self._findall(child, './song')), 6) + for i in range(6): + self.assertEqual(child[i].get('title'), 'One') + + rv, child = self._make_request('search3', { 'query': 'e' }, tag = 'searchResult3') + self.assertEqual(len(child), 12) + self.assertEqual(len(self._findall(child, './artist')), 0) + self.assertEqual(len(self._findall(child, './album')), 0) + self.assertEqual(len(self._findall(child, './song')), 12) + + # any field search + rv, child = self._make_request('search3', { 'query': 'r' }, tag = 'searchResult3') + self.assertEqual(len(child), 9) + self.assertEqual(len(self._findall(child, './artist')), 3) + self.assertEqual(len(self._findall(child, './album')), 0) + self.assertEqual(len(self._findall(child, './song')), 6) + + # paging + artists = [] + for offset in range(0, 4, 2): + rv, child = self._make_request('search3', { 'query': 'r', 'artistCount': 2, 'artistOffset': offset }, tag = 'searchResult3') + elems = self._findall(child, './artist') + self.assertLessEqual(len(elems), 2) + for artist in map(lambda a: a.get('name'), elems): + self.assertNotIn(artist, artists) + artists += list(map(lambda a: a.get('name'), elems)) + + songs = [] + for offset in range(0, 6, 2): + rv, child = self._make_request('search3', { 'query': 'r', 'songCount': 2, 'songOffset': offset }, tag = 'searchResult3') + elems = self._findall(child, './song') + self.assertEqual(len(elems), 2) + for song in map(self.__track_as_pseudo_unique_str, elems): + self.assertNotIn(song, songs) + songs += list(map(self.__track_as_pseudo_unique_str, elems)) + +if __name__ == '__main__': + unittest.main() +