Merge lp:~raoul-snyman/openlp/bug-1608194 into lp:openlp

Proposed by Raoul Snyman
Status: Merged
Merged at revision: 2694
Proposed branch: lp:~raoul-snyman/openlp/bug-1608194
Merge into: lp:openlp
Diff against target: 1080 lines (+670/-89)
7 files modified
.bzrignore (+1/-0)
openlp/core/ui/media/systemplayer.py (+13/-13)
openlp/plugins/songs/lib/__init__.py (+8/-9)
openlp/plugins/songs/lib/songselect.py (+76/-41)
tests/functional/openlp_core_ui/test_slidecontroller.py (+2/-2)
tests/functional/openlp_core_ui_media/test_systemplayer.py (+529/-0)
tests/functional/openlp_plugins/songs/test_songselect.py (+41/-24)
To merge this branch: bzr merge lp:~raoul-snyman/openlp/bug-1608194
Reviewer Review Type Date Requested Status
Tim Bentley Approve
Review via email: mp+302867@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Tim Bentley (trb143) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file '.bzrignore'
--- .bzrignore 2016-05-17 08:48:19 +0000
+++ .bzrignore 2016-08-13 15:05:55 +0000
@@ -46,3 +46,4 @@
46coverage46coverage
47tags47tags
48output48output
49htmlcov
4950
=== modified file 'openlp/core/ui/media/systemplayer.py'
--- openlp/core/ui/media/systemplayer.py 2016-04-13 19:15:53 +0000
+++ openlp/core/ui/media/systemplayer.py 2016-08-13 15:05:55 +0000
@@ -83,17 +83,17 @@
83 elif mime_type.startswith('video/'):83 elif mime_type.startswith('video/'):
84 self._add_to_list(self.video_extensions_list, mime_type)84 self._add_to_list(self.video_extensions_list, mime_type)
8585
86 def _add_to_list(self, mime_type_list, mimetype):86 def _add_to_list(self, mime_type_list, mime_type):
87 """87 """
88 Add mimetypes to the provided list88 Add mimetypes to the provided list
89 """89 """
90 # Add all extensions which mimetypes provides us for supported types.90 # Add all extensions which mimetypes provides us for supported types.
91 extensions = mimetypes.guess_all_extensions(str(mimetype))91 extensions = mimetypes.guess_all_extensions(mime_type)
92 for extension in extensions:92 for extension in extensions:
93 ext = '*%s' % extension93 ext = '*%s' % extension
94 if ext not in mime_type_list:94 if ext not in mime_type_list:
95 mime_type_list.append(ext)95 mime_type_list.append(ext)
96 log.info('MediaPlugin: %s extensions: %s' % (mimetype, ' '.join(extensions)))96 log.info('MediaPlugin: %s extensions: %s', mime_type, ' '.join(extensions))
9797
98 def setup(self, display):98 def setup(self, display):
99 """99 """
@@ -284,25 +284,25 @@
284 :return: True if file can be played otherwise False284 :return: True if file can be played otherwise False
285 """285 """
286 thread = QtCore.QThread()286 thread = QtCore.QThread()
287 check_media_player = CheckMedia(path)287 check_media_worker = CheckMediaWorker(path)
288 check_media_player.setVolume(0)288 check_media_worker.setVolume(0)
289 check_media_player.moveToThread(thread)289 check_media_worker.moveToThread(thread)
290 check_media_player.finished.connect(thread.quit)290 check_media_worker.finished.connect(thread.quit)
291 thread.started.connect(check_media_player.play)291 thread.started.connect(check_media_worker.play)
292 thread.start()292 thread.start()
293 while thread.isRunning():293 while thread.isRunning():
294 self.application.processEvents()294 self.application.processEvents()
295 return check_media_player.result295 return check_media_worker.result
296296
297297
298class CheckMedia(QtMultimedia.QMediaPlayer):298class CheckMediaWorker(QtMultimedia.QMediaPlayer):
299 """299 """
300 Class used to check if a media file is playable300 Class used to check if a media file is playable
301 """301 """
302 finished = QtCore.pyqtSignal()302 finished = QtCore.pyqtSignal()
303303
304 def __init__(self, path):304 def __init__(self, path):
305 super(CheckMedia, self).__init__(None, QtMultimedia.QMediaPlayer.VideoSurface)305 super(CheckMediaWorker, self).__init__(None, QtMultimedia.QMediaPlayer.VideoSurface)
306 self.result = None306 self.result = None
307307
308 self.error.connect(functools.partial(self.signals, 'error'))308 self.error.connect(functools.partial(self.signals, 'error'))
309309
=== modified file 'openlp/plugins/songs/lib/__init__.py'
--- openlp/plugins/songs/lib/__init__.py 2016-05-27 08:13:14 +0000
+++ openlp/plugins/songs/lib/__init__.py 2016-08-13 15:05:55 +0000
@@ -31,9 +31,8 @@
3131
32from openlp.core.common import AppLocation, CONTROL_CHARS32from openlp.core.common import AppLocation, CONTROL_CHARS
33from openlp.core.lib import translate33from openlp.core.lib import translate
34from openlp.plugins.songs.lib.db import MediaFile, Song34from openlp.plugins.songs.lib.db import Author, MediaFile, Song, Topic
35from .db import Author35from openlp.plugins.songs.lib.ui import SongStrings
36from .ui import SongStrings
3736
38log = logging.getLogger(__name__)37log = logging.getLogger(__name__)
3938
@@ -315,8 +314,8 @@
315 ]314 ]
316 recommended_index = -1315 recommended_index = -1
317 if recommendation:316 if recommendation:
318 for index in range(len(encodings)):317 for index, encoding in enumerate(encodings):
319 if recommendation == encodings[index][0]:318 if recommendation == encoding[0]:
320 recommended_index = index319 recommended_index = index
321 break320 break
322 if recommended_index > -1:321 if recommended_index > -1:
@@ -442,7 +441,7 @@
442 # Encoded buffer.441 # Encoded buffer.
443 ebytes = bytearray()442 ebytes = bytearray()
444 for match in PATTERN.finditer(text):443 for match in PATTERN.finditer(text):
445 iinu, word, arg, hex, char, brace, tchar = match.groups()444 iinu, word, arg, hex_, char, brace, tchar = match.groups()
446 # \x (non-alpha character)445 # \x (non-alpha character)
447 if char:446 if char:
448 if char in '\\{}':447 if char in '\\{}':
@@ -450,7 +449,7 @@
450 else:449 else:
451 word = char450 word = char
452 # Flush encoded buffer to output buffer451 # Flush encoded buffer to output buffer
453 if ebytes and not hex and not tchar:452 if ebytes and not hex_ and not tchar:
454 failed = False453 failed = False
455 while True:454 while True:
456 try:455 try:
@@ -507,11 +506,11 @@
507 elif iinu:506 elif iinu:
508 ignorable = True507 ignorable = True
509 # \'xx508 # \'xx
510 elif hex:509 elif hex_:
511 if curskip > 0:510 if curskip > 0:
512 curskip -= 1511 curskip -= 1
513 elif not ignorable:512 elif not ignorable:
514 ebytes.append(int(hex, 16))513 ebytes.append(int(hex_, 16))
515 elif tchar:514 elif tchar:
516 if curskip > 0:515 if curskip > 0:
517 curskip -= 1516 curskip -= 1
518517
=== modified file 'openlp/plugins/songs/lib/songselect.py'
--- openlp/plugins/songs/lib/songselect.py 2016-05-27 08:13:14 +0000
+++ openlp/plugins/songs/lib/songselect.py 2016-08-13 15:05:55 +0000
@@ -23,7 +23,8 @@
23The :mod:`~openlp.plugins.songs.lib.songselect` module contains the SongSelect importer itself.23The :mod:`~openlp.plugins.songs.lib.songselect` module contains the SongSelect importer itself.
24"""24"""
25import logging25import logging
26import sys26import random
27import re
27from http.cookiejar import CookieJar28from http.cookiejar import CookieJar
28from urllib.parse import urlencode29from urllib.parse import urlencode
29from urllib.request import HTTPCookieProcessor, URLError, build_opener30from urllib.request import HTTPCookieProcessor, URLError, build_opener
@@ -32,14 +33,21 @@
3233
33from bs4 import BeautifulSoup, NavigableString34from bs4 import BeautifulSoup, NavigableString
3435
35from openlp.plugins.songs.lib import Song, VerseType, clean_song, Author36from openlp.plugins.songs.lib import Song, Author, Topic, VerseType, clean_song
36from openlp.plugins.songs.lib.openlyricsxml import SongXML37from openlp.plugins.songs.lib.openlyricsxml import SongXML
3738
38USER_AGENT = 'Mozilla/5.0 (Linux; U; Android 4.0.3; en-us; GT-I9000 ' \39USER_AGENTS = [
39 'Build/IML74K) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 ' \40 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) '
40 'Mobile Safari/534.30'41 'Chrome/52.0.2743.116 Safari/537.36',
41BASE_URL = 'https://mobile.songselect.com'42 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36',
42LOGIN_URL = BASE_URL + '/account/login'43 'Mozilla/5.0 (X11; Linux x86_64; rv:47.0) Gecko/20100101 Firefox/47.0',
44 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:46.0) Gecko/20100101 Firefox/46.0',
45 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:47.0) Gecko/20100101 Firefox/47.0'
46]
47BASE_URL = 'https://songselect.ccli.com'
48LOGIN_PAGE = 'https://profile.ccli.com/account/signin?appContext=SongSelect&returnUrl='\
49 'https%3a%2f%2fsongselect.ccli.com%2f'
50LOGIN_URL = 'https://profile.ccli.com/'
43LOGOUT_URL = BASE_URL + '/account/logout'51LOGOUT_URL = BASE_URL + '/account/logout'
44SEARCH_URL = BASE_URL + '/search/results'52SEARCH_URL = BASE_URL + '/search/results'
4553
@@ -60,7 +68,7 @@
60 self.db_manager = db_manager68 self.db_manager = db_manager
61 self.html_parser = HTMLParser()69 self.html_parser = HTMLParser()
62 self.opener = build_opener(HTTPCookieProcessor(CookieJar()))70 self.opener = build_opener(HTTPCookieProcessor(CookieJar()))
63 self.opener.addheaders = [('User-Agent', USER_AGENT)]71 self.opener.addheaders = [('User-Agent', random.choice(USER_AGENTS))]
64 self.run_search = True72 self.run_search = True
6573
66 def login(self, username, password, callback=None):74 def login(self, username, password, callback=None):
@@ -76,27 +84,27 @@
76 if callback:84 if callback:
77 callback()85 callback()
78 try:86 try:
79 login_page = BeautifulSoup(self.opener.open(LOGIN_URL).read(), 'lxml')87 login_page = BeautifulSoup(self.opener.open(LOGIN_PAGE).read(), 'lxml')
80 except (TypeError, URLError) as e:88 except (TypeError, URLError) as error:
81 log.exception('Could not login to SongSelect, {error}'.format(error=e))89 log.exception('Could not login to SongSelect, {error}'.format(error=error))
82 return False90 return False
83 if callback:91 if callback:
84 callback()92 callback()
85 token_input = login_page.find('input', attrs={'name': '__RequestVerificationToken'})93 token_input = login_page.find('input', attrs={'name': '__RequestVerificationToken'})
86 data = urlencode({94 data = urlencode({
87 '__RequestVerificationToken': token_input['value'],95 '__RequestVerificationToken': token_input['value'],
88 'UserName': username,96 'emailAddress': username,
89 'Password': password,97 'password': password,
90 'RememberMe': 'false'98 'RememberMe': 'false'
91 })99 })
92 try:100 try:
93 posted_page = BeautifulSoup(self.opener.open(LOGIN_URL, data.encode('utf-8')).read(), 'lxml')101 posted_page = BeautifulSoup(self.opener.open(LOGIN_URL, data.encode('utf-8')).read(), 'lxml')
94 except (TypeError, URLError) as e:102 except (TypeError, URLError) as error:
95 log.exception('Could not login to SongSelect, {error}'.format(error=e))103 log.exception('Could not login to SongSelect, {error}'.format(error=error))
96 return False104 return False
97 if callback:105 if callback:
98 callback()106 callback()
99 return not posted_page.find('input', attrs={'name': '__RequestVerificationToken'})107 return posted_page.find('input', id='SearchText') is not None
100108
101 def logout(self):109 def logout(self):
102 """110 """
@@ -104,8 +112,8 @@
104 """112 """
105 try:113 try:
106 self.opener.open(LOGOUT_URL)114 self.opener.open(LOGOUT_URL)
107 except (TypeError, URLError) as e:115 except (TypeError, URLError) as error:
108 log.exception('Could not log of SongSelect, {error}'.format(error=e))116 log.exception('Could not log of SongSelect, {error}'.format(error=error))
109117
110 def search(self, search_text, max_results, callback=None):118 def search(self, search_text, max_results, callback=None):
111 """119 """
@@ -117,7 +125,15 @@
117 :return: List of songs125 :return: List of songs
118 """126 """
119 self.run_search = True127 self.run_search = True
120 params = {'allowredirect': 'false', 'SearchTerm': search_text}128 params = {
129 'SongContent': '',
130 'PrimaryLanguage': '',
131 'Keys': '',
132 'Themes': '',
133 'List': '',
134 'Sort': '',
135 'SearchText': search_text
136 }
121 current_page = 1137 current_page = 1
122 songs = []138 songs = []
123 while self.run_search:139 while self.run_search:
@@ -125,17 +141,17 @@
125 params['page'] = current_page141 params['page'] = current_page
126 try:142 try:
127 results_page = BeautifulSoup(self.opener.open(SEARCH_URL + '?' + urlencode(params)).read(), 'lxml')143 results_page = BeautifulSoup(self.opener.open(SEARCH_URL + '?' + urlencode(params)).read(), 'lxml')
128 search_results = results_page.find_all('li', 'result pane')144 search_results = results_page.find_all('div', 'song-result')
129 except (TypeError, URLError) as e:145 except (TypeError, URLError) as error:
130 log.exception('Could not search SongSelect, {error}'.format(error=e))146 log.exception('Could not search SongSelect, {error}'.format(error=error))
131 search_results = None147 search_results = None
132 if not search_results:148 if not search_results:
133 break149 break
134 for result in search_results:150 for result in search_results:
135 song = {151 song = {
136 'title': unescape(result.find('h3').string),152 'title': unescape(result.find('p', 'song-result-title').find('a').string).strip(),
137 'authors': [unescape(author.string) for author in result.find_all('li')],153 'authors': unescape(result.find('p', 'song-result-subtitle').string).strip().split(', '),
138 'link': BASE_URL + result.find('a')['href']154 'link': BASE_URL + result.find('p', 'song-result-title').find('a')['href']
139 }155 }
140 if callback:156 if callback:
141 callback(song)157 callback(song)
@@ -157,33 +173,43 @@
157 callback()173 callback()
158 try:174 try:
159 song_page = BeautifulSoup(self.opener.open(song['link']).read(), 'lxml')175 song_page = BeautifulSoup(self.opener.open(song['link']).read(), 'lxml')
160 except (TypeError, URLError) as e:176 except (TypeError, URLError) as error:
161 log.exception('Could not get song from SongSelect, {error}'.format(error=e))177 log.exception('Could not get song from SongSelect, {error}'.format(error=error))
162 return None178 return None
163 if callback:179 if callback:
164 callback()180 callback()
165 try:181 try:
166 lyrics_page = BeautifulSoup(self.opener.open(song['link'] + '/lyrics').read(), 'lxml')182 lyrics_page = BeautifulSoup(self.opener.open(song['link'] + '/viewlyrics').read(), 'lxml')
167 except (TypeError, URLError):183 except (TypeError, URLError):
168 log.exception('Could not get lyrics from SongSelect')184 log.exception('Could not get lyrics from SongSelect')
169 return None185 return None
170 if callback:186 if callback:
171 callback()187 callback()
172 song['copyright'] = '/'.join([li.string for li in song_page.find('ul', 'copyright').find_all('li')])188 copyright_elements = []
173 song['copyright'] = unescape(song['copyright'])189 theme_elements = []
174 song['ccli_number'] = song_page.find('ul', 'info').find('li').string.split(':')[1].strip()190 copyrights_regex = re.compile(r'\bCopyrights\b')
191 themes_regex = re.compile(r'\bThemes\b')
192 for ul in song_page.find_all('ul', 'song-meta-list'):
193 if ul.find('li', string=copyrights_regex):
194 copyright_elements.extend(ul.find_all('li')[1:])
195 if ul.find('li', string=themes_regex):
196 theme_elements.extend(ul.find_all('li')[1:])
197 song['copyright'] = '/'.join([unescape(li.string).strip() for li in copyright_elements])
198 song['topics'] = [unescape(li.string).strip() for li in theme_elements]
199 song['ccli_number'] = song_page.find('div', 'song-content-data').find('ul').find('li')\
200 .find('strong').string.strip()
175 song['verses'] = []201 song['verses'] = []
176 verses = lyrics_page.find('section', 'lyrics').find_all('p')202 verses = lyrics_page.find('div', 'song-viewer lyrics').find_all('p')
177 verse_labels = lyrics_page.find('section', 'lyrics').find_all('h3')203 verse_labels = lyrics_page.find('div', 'song-viewer lyrics').find_all('h3')
178 for counter in range(len(verses)):204 for verse, label in zip(verses, verse_labels):
179 verse = {'label': verse_labels[counter].string, 'lyrics': ''}205 song_verse = {'label': unescape(label.string).strip(), 'lyrics': ''}
180 for v in verses[counter].contents:206 for v in verse.contents:
181 if isinstance(v, NavigableString):207 if isinstance(v, NavigableString):
182 verse['lyrics'] = verse['lyrics'] + v.string208 song_verse['lyrics'] += unescape(v.string).strip()
183 else:209 else:
184 verse['lyrics'] += '\n'210 song_verse['lyrics'] += '\n'
185 verse['lyrics'] = verse['lyrics'].strip(' \n\r\t')211 song_verse['lyrics'] = song_verse['lyrics'].strip(' \n\r\t')
186 song['verses'].append(unescape(verse))212 song['verses'].append(song_verse)
187 for counter, author in enumerate(song['authors']):213 for counter, author in enumerate(song['authors']):
188 song['authors'][counter] = unescape(author)214 song['authors'][counter] = unescape(author)
189 return song215 return song
@@ -199,7 +225,11 @@
199 song_xml = SongXML()225 song_xml = SongXML()
200 verse_order = []226 verse_order = []
201 for verse in song['verses']:227 for verse in song['verses']:
202 verse_type, verse_number = verse['label'].split(' ')[:2]228 if ' ' in verse['label']:
229 verse_type, verse_number = verse['label'].split(' ', 1)
230 else:
231 verse_type = verse['label']
232 verse_number = 1
203 verse_type = VerseType.from_loose_input(verse_type)233 verse_type = VerseType.from_loose_input(verse_type)
204 verse_number = int(verse_number)234 verse_number = int(verse_number)
205 song_xml.add_verse_to_lyrics(VerseType.tags[verse_type], verse_number, verse['lyrics'])235 song_xml.add_verse_to_lyrics(VerseType.tags[verse_type], verse_number, verse['lyrics'])
@@ -220,6 +250,11 @@
220 last_name = name_parts[1]250 last_name = name_parts[1]
221 author = Author.populate(first_name=first_name, last_name=last_name, display_name=author_name)251 author = Author.populate(first_name=first_name, last_name=last_name, display_name=author_name)
222 db_song.add_author(author)252 db_song.add_author(author)
253 for topic_name in song.get('topics', []):
254 topic = self.db_manager.get_object_filtered(Topic, Topic.name == topic_name)
255 if not topic:
256 topic = Topic.populate(name=topic_name)
257 db_song.topics.append(topic)
223 self.db_manager.save_object(db_song)258 self.db_manager.save_object(db_song)
224 return db_song259 return db_song
225260
226261
=== modified file 'tests/functional/openlp_core_ui/test_slidecontroller.py'
--- tests/functional/openlp_core_ui/test_slidecontroller.py 2016-07-16 16:51:08 +0000
+++ tests/functional/openlp_core_ui/test_slidecontroller.py 2016-08-13 15:05:55 +0000
@@ -243,7 +243,7 @@
243 mocked_service_item = MagicMock()243 mocked_service_item = MagicMock()
244 mocked_service_item.from_service = False244 mocked_service_item.from_service = False
245 mocked_preview_widget.current_slide_number.return_value = 1245 mocked_preview_widget.current_slide_number.return_value = 1
246 mocked_preview_widget.slide_count.return_value = 2246 mocked_preview_widget.slide_count = MagicMock(return_value=2)
247 mocked_live_controller.preview_widget = MagicMock()247 mocked_live_controller.preview_widget = MagicMock()
248 Registry.create()248 Registry.create()
249 Registry().register('live_controller', mocked_live_controller)249 Registry().register('live_controller', mocked_live_controller)
@@ -273,7 +273,7 @@
273 mocked_service_item.from_service = True273 mocked_service_item.from_service = True
274 mocked_service_item.unique_identifier = 42274 mocked_service_item.unique_identifier = 42
275 mocked_preview_widget.current_slide_number.return_value = 1275 mocked_preview_widget.current_slide_number.return_value = 1
276 mocked_preview_widget.slide_count.return_value = 2276 mocked_preview_widget.slide_count = MagicMock(return_value=2)
277 mocked_live_controller.preview_widget = MagicMock()277 mocked_live_controller.preview_widget = MagicMock()
278 Registry.create()278 Registry.create()
279 Registry().register('live_controller', mocked_live_controller)279 Registry().register('live_controller', mocked_live_controller)
280280
=== added file 'tests/functional/openlp_core_ui_media/test_systemplayer.py'
--- tests/functional/openlp_core_ui_media/test_systemplayer.py 1970-01-01 00:00:00 +0000
+++ tests/functional/openlp_core_ui_media/test_systemplayer.py 2016-08-13 15:05:55 +0000
@@ -0,0 +1,529 @@
1# -*- coding: utf-8 -*-
2# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
3
4###############################################################################
5# OpenLP - Open Source Lyrics Projection #
6# --------------------------------------------------------------------------- #
7# Copyright (c) 2008-2016 OpenLP Developers #
8# --------------------------------------------------------------------------- #
9# This program is free software; you can redistribute it and/or modify it #
10# under the terms of the GNU General Public License as published by the Free #
11# Software Foundation; version 2 of the License. #
12# #
13# This program is distributed in the hope that it will be useful, but WITHOUT #
14# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
15# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
16# more details. #
17# #
18# You should have received a copy of the GNU General Public License along #
19# with this program; if not, write to the Free Software Foundation, Inc., 59 #
20# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
21###############################################################################
22"""
23Package to test the openlp.core.ui.media.systemplayer package.
24"""
25from unittest import TestCase
26
27from PyQt5 import QtCore, QtMultimedia
28
29from openlp.core.common import Registry
30from openlp.core.ui.media import MediaState
31from openlp.core.ui.media.systemplayer import SystemPlayer, CheckMediaWorker, ADDITIONAL_EXT
32
33from tests.functional import MagicMock, call, patch
34
35
36class TestSystemPlayer(TestCase):
37 """
38 Test the system media player
39 """
40 @patch('openlp.core.ui.media.systemplayer.mimetypes')
41 @patch('openlp.core.ui.media.systemplayer.QtMultimedia.QMediaPlayer')
42 def test_constructor(self, MockQMediaPlayer, mocked_mimetypes):
43 """
44 Test the SystemPlayer constructor
45 """
46 # GIVEN: The SystemPlayer class and a mockedQMediaPlayer
47 mocked_media_player = MagicMock()
48 mocked_media_player.supportedMimeTypes.return_value = [
49 'application/postscript',
50 'audio/aiff',
51 'audio/x-aiff',
52 'text/html',
53 'video/animaflex',
54 'video/x-ms-asf'
55 ]
56 mocked_mimetypes.guess_all_extensions.side_effect = [
57 ['.aiff'],
58 ['.aiff'],
59 ['.afl'],
60 ['.asf']
61 ]
62 MockQMediaPlayer.return_value = mocked_media_player
63
64 # WHEN: An object is created from it
65 player = SystemPlayer(self)
66
67 # THEN: The correct initial values should be set up
68 self.assertEqual('system', player.name)
69 self.assertEqual('System', player.original_name)
70 self.assertEqual('&System', player.display_name)
71 self.assertEqual(self, player.parent)
72 self.assertEqual(ADDITIONAL_EXT, player.additional_extensions)
73 MockQMediaPlayer.assert_called_once_with(None, QtMultimedia.QMediaPlayer.VideoSurface)
74 mocked_mimetypes.init.assert_called_once_with()
75 mocked_media_player.service.assert_called_once_with()
76 mocked_media_player.supportedMimeTypes.assert_called_once_with()
77 self.assertEqual(['*.aiff'], player.audio_extensions_list)
78 self.assertEqual(['*.afl', '*.asf'], player.video_extensions_list)
79
80 @patch('openlp.core.ui.media.systemplayer.QtMultimediaWidgets.QVideoWidget')
81 @patch('openlp.core.ui.media.systemplayer.QtMultimedia.QMediaPlayer')
82 def test_setup(self, MockQMediaPlayer, MockQVideoWidget):
83 """
84 Test the setup() method of SystemPlayer
85 """
86 # GIVEN: A SystemPlayer instance and a mock display
87 player = SystemPlayer(self)
88 mocked_display = MagicMock()
89 mocked_display.size.return_value = [1, 2, 3, 4]
90 mocked_video_widget = MagicMock()
91 mocked_media_player = MagicMock()
92 MockQVideoWidget.return_value = mocked_video_widget
93 MockQMediaPlayer.return_value = mocked_media_player
94
95 # WHEN: setup() is run
96 player.setup(mocked_display)
97
98 # THEN: The player should have a display widget
99 MockQVideoWidget.assert_called_once_with(mocked_display)
100 self.assertEqual(mocked_video_widget, mocked_display.video_widget)
101 mocked_display.size.assert_called_once_with()
102 mocked_video_widget.resize.assert_called_once_with([1, 2, 3, 4])
103 MockQMediaPlayer.assert_called_with(mocked_display)
104 self.assertEqual(mocked_media_player, mocked_display.media_player)
105 mocked_media_player.setVideoOutput.assert_called_once_with(mocked_video_widget)
106 mocked_video_widget.raise_.assert_called_once_with()
107 mocked_video_widget.hide.assert_called_once_with()
108 self.assertTrue(player.has_own_widget)
109
110 def test_check_available(self):
111 """
112 Test the check_available() method on SystemPlayer
113 """
114 # GIVEN: A SystemPlayer instance
115 player = SystemPlayer(self)
116
117 # WHEN: check_available is run
118 result = player.check_available()
119
120 # THEN: it should be available
121 self.assertTrue(result)
122
123 def test_load_valid_media(self):
124 """
125 Test the load() method of SystemPlayer with a valid media file
126 """
127 # GIVEN: A SystemPlayer instance and a mocked display
128 player = SystemPlayer(self)
129 mocked_display = MagicMock()
130 mocked_display.controller.media_info.volume = 1
131 mocked_display.controller.media_info.file_info.absoluteFilePath.return_value = '/path/to/file'
132
133 # WHEN: The load() method is run
134 with patch.object(player, 'check_media') as mocked_check_media, \
135 patch.object(player, 'volume') as mocked_volume:
136 mocked_check_media.return_value = True
137 result = player.load(mocked_display)
138
139 # THEN: the file is sent to the video widget
140 mocked_display.controller.media_info.file_info.absoluteFilePath.assert_called_once_with()
141 mocked_check_media.assert_called_once_with('/path/to/file')
142 mocked_display.media_player.setMedia.assert_called_once_with(
143 QtMultimedia.QMediaContent(QtCore.QUrl.fromLocalFile('/path/to/file')))
144 mocked_volume.assert_called_once_with(mocked_display, 1)
145 self.assertTrue(result)
146
147 def test_load_invalid_media(self):
148 """
149 Test the load() method of SystemPlayer with an invalid media file
150 """
151 # GIVEN: A SystemPlayer instance and a mocked display
152 player = SystemPlayer(self)
153 mocked_display = MagicMock()
154 mocked_display.controller.media_info.volume = 1
155 mocked_display.controller.media_info.file_info.absoluteFilePath.return_value = '/path/to/file'
156
157 # WHEN: The load() method is run
158 with patch.object(player, 'check_media') as mocked_check_media, \
159 patch.object(player, 'volume') as mocked_volume:
160 mocked_check_media.return_value = False
161 result = player.load(mocked_display)
162
163 # THEN: stuff
164 mocked_display.controller.media_info.file_info.absoluteFilePath.assert_called_once_with()
165 mocked_check_media.assert_called_once_with('/path/to/file')
166 self.assertFalse(result)
167
168 def test_resize(self):
169 """
170 Test the resize() method of the SystemPlayer
171 """
172 # GIVEN: A SystemPlayer instance and a mocked display
173 player = SystemPlayer(self)
174 mocked_display = MagicMock()
175 mocked_display.size.return_value = [1, 2, 3, 4]
176
177 # WHEN: The resize() method is called
178 player.resize(mocked_display)
179
180 # THEN: The player is resized
181 mocked_display.size.assert_called_once_with()
182 mocked_display.video_widget.resize.assert_called_once_with([1, 2, 3, 4])
183
184 @patch('openlp.core.ui.media.systemplayer.functools')
185 def test_play_is_live(self, mocked_functools):
186 """
187 Test the play() method of the SystemPlayer on the live display
188 """
189 # GIVEN: A SystemPlayer instance and a mocked display
190 mocked_functools.partial.return_value = 'function'
191 player = SystemPlayer(self)
192 mocked_display = MagicMock()
193 mocked_display.controller.is_live = True
194 mocked_display.controller.media_info.start_time = 1
195 mocked_display.controller.media_info.volume = 1
196
197 # WHEN: play() is called
198 with patch.object(player, 'get_live_state') as mocked_get_live_state, \
199 patch.object(player, 'seek') as mocked_seek, \
200 patch.object(player, 'volume') as mocked_volume, \
201 patch.object(player, 'set_state') as mocked_set_state:
202 mocked_get_live_state.return_value = QtMultimedia.QMediaPlayer.PlayingState
203 result = player.play(mocked_display)
204
205 # THEN: the media file is played
206 mocked_get_live_state.assert_called_once_with()
207 mocked_display.media_player.play.assert_called_once_with()
208 mocked_seek.assert_called_once_with(mocked_display, 1000)
209 mocked_volume.assert_called_once_with(mocked_display, 1)
210 mocked_display.media_player.durationChanged.connect.assert_called_once_with('function')
211 mocked_set_state.assert_called_once_with(MediaState.Playing, mocked_display)
212 mocked_display.video_widget.raise_.assert_called_once_with()
213 self.assertTrue(result)
214
215 @patch('openlp.core.ui.media.systemplayer.functools')
216 def test_play_is_preview(self, mocked_functools):
217 """
218 Test the play() method of the SystemPlayer on the preview display
219 """
220 # GIVEN: A SystemPlayer instance and a mocked display
221 mocked_functools.partial.return_value = 'function'
222 player = SystemPlayer(self)
223 mocked_display = MagicMock()
224 mocked_display.controller.is_live = False
225 mocked_display.controller.media_info.start_time = 1
226 mocked_display.controller.media_info.volume = 1
227
228 # WHEN: play() is called
229 with patch.object(player, 'get_preview_state') as mocked_get_preview_state, \
230 patch.object(player, 'seek') as mocked_seek, \
231 patch.object(player, 'volume') as mocked_volume, \
232 patch.object(player, 'set_state') as mocked_set_state:
233 mocked_get_preview_state.return_value = QtMultimedia.QMediaPlayer.PlayingState
234 result = player.play(mocked_display)
235
236 # THEN: the media file is played
237 mocked_get_preview_state.assert_called_once_with()
238 mocked_display.media_player.play.assert_called_once_with()
239 mocked_seek.assert_called_once_with(mocked_display, 1000)
240 mocked_volume.assert_called_once_with(mocked_display, 1)
241 mocked_display.media_player.durationChanged.connect.assert_called_once_with('function')
242 mocked_set_state.assert_called_once_with(MediaState.Playing, mocked_display)
243 mocked_display.video_widget.raise_.assert_called_once_with()
244 self.assertTrue(result)
245
246 def test_pause_is_live(self):
247 """
248 Test the pause() method of the SystemPlayer on the live display
249 """
250 # GIVEN: A SystemPlayer instance
251 player = SystemPlayer(self)
252 mocked_display = MagicMock()
253 mocked_display.controller.is_live = True
254
255 # WHEN: The pause method is called
256 with patch.object(player, 'get_live_state') as mocked_get_live_state, \
257 patch.object(player, 'set_state') as mocked_set_state:
258 mocked_get_live_state.return_value = QtMultimedia.QMediaPlayer.PausedState
259 player.pause(mocked_display)
260
261 # THEN: The video is paused
262 mocked_display.media_player.pause.assert_called_once_with()
263 mocked_get_live_state.assert_called_once_with()
264 mocked_set_state.assert_called_once_with(MediaState.Paused, mocked_display)
265
266 def test_pause_is_preview(self):
267 """
268 Test the pause() method of the SystemPlayer on the preview display
269 """
270 # GIVEN: A SystemPlayer instance
271 player = SystemPlayer(self)
272 mocked_display = MagicMock()
273 mocked_display.controller.is_live = False
274
275 # WHEN: The pause method is called
276 with patch.object(player, 'get_preview_state') as mocked_get_preview_state, \
277 patch.object(player, 'set_state') as mocked_set_state:
278 mocked_get_preview_state.return_value = QtMultimedia.QMediaPlayer.PausedState
279 player.pause(mocked_display)
280
281 # THEN: The video is paused
282 mocked_display.media_player.pause.assert_called_once_with()
283 mocked_get_preview_state.assert_called_once_with()
284 mocked_set_state.assert_called_once_with(MediaState.Paused, mocked_display)
285
286 def test_stop(self):
287 """
288 Test the stop() method of the SystemPlayer
289 """
290 # GIVEN: A SystemPlayer instance
291 player = SystemPlayer(self)
292 mocked_display = MagicMock()
293
294 # WHEN: The stop method is called
295 with patch.object(player, 'set_visible') as mocked_set_visible, \
296 patch.object(player, 'set_state') as mocked_set_state:
297 player.stop(mocked_display)
298
299 # THEN: The video is stopped
300 mocked_display.media_player.stop.assert_called_once_with()
301 mocked_set_visible.assert_called_once_with(mocked_display, False)
302 mocked_set_state.assert_called_once_with(MediaState.Stopped, mocked_display)
303
304 def test_volume(self):
305 """
306 Test the volume() method of the SystemPlayer
307 """
308 # GIVEN: A SystemPlayer instance
309 player = SystemPlayer(self)
310 mocked_display = MagicMock()
311 mocked_display.has_audio = True
312
313 # WHEN: The stop method is called
314 player.volume(mocked_display, 2)
315
316 # THEN: The video is stopped
317 mocked_display.media_player.setVolume.assert_called_once_with(2)
318
319 def test_seek(self):
320 """
321 Test the seek() method of the SystemPlayer
322 """
323 # GIVEN: A SystemPlayer instance
324 player = SystemPlayer(self)
325 mocked_display = MagicMock()
326
327 # WHEN: The stop method is called
328 player.seek(mocked_display, 2)
329
330 # THEN: The video is stopped
331 mocked_display.media_player.setPosition.assert_called_once_with(2)
332
333 def test_reset(self):
334 """
335 Test the reset() method of the SystemPlayer
336 """
337 # GIVEN: A SystemPlayer instance
338 player = SystemPlayer(self)
339 mocked_display = MagicMock()
340
341 # WHEN: reset() is called
342 with patch.object(player, 'set_state') as mocked_set_state, \
343 patch.object(player, 'set_visible') as mocked_set_visible:
344 player.reset(mocked_display)
345
346 # THEN: The media player is reset
347 mocked_display.media_player.stop()
348 mocked_display.media_player.setMedia.assert_called_once_with(QtMultimedia.QMediaContent())
349 mocked_set_visible.assert_called_once_with(mocked_display, False)
350 mocked_display.video_widget.setVisible.assert_called_once_with(False)
351 mocked_set_state.assert_called_once_with(MediaState.Off, mocked_display)
352
353 def test_set_visible(self):
354 """
355 Test the set_visible() method on the SystemPlayer
356 """
357 # GIVEN: A SystemPlayer instance and a mocked display
358 player = SystemPlayer(self)
359 player.has_own_widget = True
360 mocked_display = MagicMock()
361
362 # WHEN: set_visible() is called
363 player.set_visible(mocked_display, True)
364
365 # THEN: The widget should be visible
366 mocked_display.video_widget.setVisible.assert_called_once_with(True)
367
368 def test_set_duration(self):
369 """
370 Test the set_duration() method of the SystemPlayer
371 """
372 # GIVEN: a mocked controller
373 mocked_controller = MagicMock()
374 mocked_controller.media_info.length = 5
375
376 # WHEN: The set_duration() is called. NB: the 10 here is ignored by the code
377 SystemPlayer.set_duration(mocked_controller, 10)
378
379 # THEN: The maximum length of the slider should be set
380 mocked_controller.seek_slider.setMaximum.assert_called_once_with(5)
381
382 def test_update_ui(self):
383 """
384 Test the update_ui() method on the SystemPlayer
385 """
386 # GIVEN: A SystemPlayer instance
387 player = SystemPlayer(self)
388 player.state = MediaState.Playing
389 mocked_display = MagicMock()
390 mocked_display.media_player.state.return_value = QtMultimedia.QMediaPlayer.PausedState
391 mocked_display.controller.media_info.end_time = 1
392 mocked_display.media_player.position.return_value = 2
393 mocked_display.controller.seek_slider.isSliderDown.return_value = False
394
395 # WHEN: update_ui() is called
396 with patch.object(player, 'stop') as mocked_stop, \
397 patch.object(player, 'set_visible') as mocked_set_visible:
398 player.update_ui(mocked_display)
399
400 # THEN: The UI is updated
401 expected_stop_calls = [call(mocked_display), call(mocked_display)]
402 expected_position_calls = [call(), call()]
403 expected_block_signals_calls = [call(True), call(False)]
404 mocked_display.media_player.state.assert_called_once_with()
405 self.assertEqual(2, mocked_stop.call_count)
406 self.assertEqual(expected_stop_calls, mocked_stop.call_args_list)
407 self.assertEqual(2, mocked_display.media_player.position.call_count)
408 self.assertEqual(expected_position_calls, mocked_display.media_player.position.call_args_list)
409 mocked_set_visible.assert_called_once_with(mocked_display, False)
410 mocked_display.controller.seek_slider.isSliderDown.assert_called_once_with()
411 self.assertEqual(expected_block_signals_calls,
412 mocked_display.controller.seek_slider.blockSignals.call_args_list)
413 mocked_display.controller.seek_slider.setSliderPosition.assert_called_once_with(2)
414
415 def test_get_media_display_css(self):
416 """
417 Test the get_media_display_css() method of the SystemPlayer
418 """
419 # GIVEN: A SystemPlayer instance
420 player = SystemPlayer(self)
421
422 # WHEN: get_media_display_css() is called
423 result = player.get_media_display_css()
424
425 # THEN: The css should be empty
426 self.assertEqual('', result)
427
428 def test_get_info(self):
429 """
430 Test the get_info() method of the SystemPlayer
431 """
432 # GIVEN: A SystemPlayer instance
433 player = SystemPlayer(self)
434
435 # WHEN: get_info() is called
436 result = player.get_info()
437
438 # THEN: The info should be correct
439 expected_info = 'This media player uses your operating system to provide media capabilities.<br/> ' \
440 '<strong>Audio</strong><br/>[]<br/><strong>Video</strong><br/>[]<br/>'
441 self.assertEqual(expected_info, result)
442
443 @patch('openlp.core.ui.media.systemplayer.CheckMediaWorker')
444 @patch('openlp.core.ui.media.systemplayer.QtCore.QThread')
445 def test_check_media(self, MockQThread, MockCheckMediaWorker):
446 """
447 Test the check_media() method of the SystemPlayer
448 """
449 # GIVEN: A SystemPlayer instance and a mocked thread
450 valid_file = '/path/to/video.ogv'
451 mocked_application = MagicMock()
452 Registry().create()
453 Registry().register('application', mocked_application)
454 player = SystemPlayer(self)
455 mocked_thread = MagicMock()
456 mocked_thread.isRunning.side_effect = [True, False]
457 mocked_thread.quit = 'quit' # actually supposed to be a slot, but it's all mocked out anyway
458 MockQThread.return_value = mocked_thread
459 mocked_check_media_worker = MagicMock()
460 mocked_check_media_worker.play = 'play'
461 mocked_check_media_worker.result = True
462 MockCheckMediaWorker.return_value = mocked_check_media_worker
463
464 # WHEN: check_media() is called with a valid media file
465 result = player.check_media(valid_file)
466
467 # THEN: It should return True
468 MockQThread.assert_called_once_with()
469 MockCheckMediaWorker.assert_called_once_with(valid_file)
470 mocked_check_media_worker.setVolume.assert_called_once_with(0)
471 mocked_check_media_worker.moveToThread.assert_called_once_with(mocked_thread)
472 mocked_check_media_worker.finished.connect.assert_called_once_with('quit')
473 mocked_thread.started.connect.assert_called_once_with('play')
474 mocked_thread.start.assert_called_once_with()
475 self.assertEqual(2, mocked_thread.isRunning.call_count)
476 mocked_application.processEvents.assert_called_once_with()
477 self.assertTrue(result)
478
479
480class TestCheckMediaWorker(TestCase):
481 """
482 Test the CheckMediaWorker class
483 """
484 def test_constructor(self):
485 """
486 Test the constructor of the CheckMediaWorker class
487 """
488 # GIVEN: A file path
489 path = 'file.ogv'
490
491 # WHEN: The CheckMediaWorker object is instantiated
492 worker = CheckMediaWorker(path)
493
494 # THEN: The correct values should be set up
495 self.assertIsNotNone(worker)
496
497 def test_signals_media(self):
498 """
499 Test the signals() signal of the CheckMediaWorker class with a "media" origin
500 """
501 # GIVEN: A CheckMediaWorker instance
502 worker = CheckMediaWorker('file.ogv')
503
504 # WHEN: signals() is called with media and BufferedMedia
505 with patch.object(worker, 'stop') as mocked_stop, \
506 patch.object(worker, 'finished') as mocked_finished:
507 worker.signals('media', worker.BufferedMedia)
508
509 # THEN: The worker should exit and the result should be True
510 mocked_stop.assert_called_once_with()
511 mocked_finished.emit.assert_called_once_with()
512 self.assertTrue(worker.result)
513
514 def test_signals_error(self):
515 """
516 Test the signals() signal of the CheckMediaWorker class with a "error" origin
517 """
518 # GIVEN: A CheckMediaWorker instance
519 worker = CheckMediaWorker('file.ogv')
520
521 # WHEN: signals() is called with error and BufferedMedia
522 with patch.object(worker, 'stop') as mocked_stop, \
523 patch.object(worker, 'finished') as mocked_finished:
524 worker.signals('error', None)
525
526 # THEN: The worker should exit and the result should be True
527 mocked_stop.assert_called_once_with()
528 mocked_finished.emit.assert_called_once_with()
529 self.assertFalse(worker.result)
0530
=== modified file 'tests/functional/openlp_plugins/songs/test_songselect.py'
--- tests/functional/openlp_plugins/songs/test_songselect.py 2016-05-31 21:40:13 +0000
+++ tests/functional/openlp_plugins/songs/test_songselect.py 2016-08-13 15:05:55 +0000
@@ -1,5 +1,6 @@
1# -*- coding: utf-8 -*-1# -*- coding: utf-8 -*-
2# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=42# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
3# pylint: disable=protected-access
34
4###############################################################################5###############################################################################
5# OpenLP - Open Source Lyrics Projection #6# OpenLP - Open Source Lyrics Projection #
@@ -28,14 +29,13 @@
2829
29from PyQt5 import QtWidgets30from PyQt5 import QtWidgets
3031
31from tests.helpers.songfileimport import SongImportTestHelper
32from openlp.core import Registry32from openlp.core import Registry
33from openlp.plugins.songs.forms.songselectform import SongSelectForm, SearchWorker33from openlp.plugins.songs.forms.songselectform import SongSelectForm, SearchWorker
34from openlp.plugins.songs.lib import Song34from openlp.plugins.songs.lib import Song
35from openlp.plugins.songs.lib.songselect import SongSelectImport, LOGOUT_URL, BASE_URL35from openlp.plugins.songs.lib.songselect import SongSelectImport, LOGOUT_URL, BASE_URL
36from openlp.plugins.songs.lib.importers.cclifile import CCLIFileImport
3736
38from tests.functional import MagicMock, patch, call37from tests.functional import MagicMock, patch, call
38from tests.helpers.songfileimport import SongImportTestHelper
39from tests.helpers.testmixin import TestMixin39from tests.helpers.testmixin import TestMixin
4040
41TEST_PATH = os.path.abspath(41TEST_PATH = os.path.abspath(
@@ -71,7 +71,7 @@
71 mocked_opener = MagicMock()71 mocked_opener = MagicMock()
72 mocked_build_opener.return_value = mocked_opener72 mocked_build_opener.return_value = mocked_opener
73 mocked_login_page = MagicMock()73 mocked_login_page = MagicMock()
74 mocked_login_page.find.return_value = {'value': 'blah'}74 mocked_login_page.find.side_effect = [{'value': 'blah'}, None]
75 MockedBeautifulSoup.return_value = mocked_login_page75 MockedBeautifulSoup.return_value = mocked_login_page
76 mock_callback = MagicMock()76 mock_callback = MagicMock()
77 importer = SongSelectImport(None)77 importer = SongSelectImport(None)
@@ -112,7 +112,7 @@
112 mocked_opener = MagicMock()112 mocked_opener = MagicMock()
113 mocked_build_opener.return_value = mocked_opener113 mocked_build_opener.return_value = mocked_opener
114 mocked_login_page = MagicMock()114 mocked_login_page = MagicMock()
115 mocked_login_page.find.side_effect = [{'value': 'blah'}, None]115 mocked_login_page.find.side_effect = [{'value': 'blah'}, MagicMock()]
116 MockedBeautifulSoup.return_value = mocked_login_page116 MockedBeautifulSoup.return_value = mocked_login_page
117 mock_callback = MagicMock()117 mock_callback = MagicMock()
118 importer = SongSelectImport(None)118 importer = SongSelectImport(None)
@@ -165,7 +165,7 @@
165 self.assertEqual(0, mock_callback.call_count, 'callback should not have been called')165 self.assertEqual(0, mock_callback.call_count, 'callback should not have been called')
166 self.assertEqual(1, mocked_opener.open.call_count, 'open should have been called once')166 self.assertEqual(1, mocked_opener.open.call_count, 'open should have been called once')
167 self.assertEqual(1, mocked_results_page.find_all.call_count, 'find_all should have been called once')167 self.assertEqual(1, mocked_results_page.find_all.call_count, 'find_all should have been called once')
168 mocked_results_page.find_all.assert_called_with('li', 'result pane')168 mocked_results_page.find_all.assert_called_with('div', 'song-result')
169 self.assertEqual([], results, 'The search method should have returned an empty list')169 self.assertEqual([], results, 'The search method should have returned an empty list')
170170
171 @patch('openlp.plugins.songs.lib.songselect.build_opener')171 @patch('openlp.plugins.songs.lib.songselect.build_opener')
@@ -177,12 +177,18 @@
177 # GIVEN: A bunch of mocked out stuff and an importer object177 # GIVEN: A bunch of mocked out stuff and an importer object
178 # first search result178 # first search result
179 mocked_result1 = MagicMock()179 mocked_result1 = MagicMock()
180 mocked_result1.find.side_effect = [MagicMock(string='Title 1'), {'href': '/url1'}]180 mocked_result1.find.side_effect = [
181 mocked_result1.find_all.return_value = [MagicMock(string='Author 1-1'), MagicMock(string='Author 1-2')]181 MagicMock(find=MagicMock(return_value=MagicMock(string='Title 1'))),
182 MagicMock(string='James, John'),
183 MagicMock(find=MagicMock(return_value={'href': '/url1'}))
184 ]
182 # second search result185 # second search result
183 mocked_result2 = MagicMock()186 mocked_result2 = MagicMock()
184 mocked_result2.find.side_effect = [MagicMock(string='Title 2'), {'href': '/url2'}]187 mocked_result2.find.side_effect = [
185 mocked_result2.find_all.return_value = [MagicMock(string='Author 2-1'), MagicMock(string='Author 2-2')]188 MagicMock(find=MagicMock(return_value=MagicMock(string='Title 2'))),
189 MagicMock(string='Philip'),
190 MagicMock(find=MagicMock(return_value={'href': '/url2'}))
191 ]
186 # rest of the stuff192 # rest of the stuff
187 mocked_opener = MagicMock()193 mocked_opener = MagicMock()
188 mocked_build_opener.return_value = mocked_opener194 mocked_build_opener.return_value = mocked_opener
@@ -199,10 +205,10 @@
199 self.assertEqual(2, mock_callback.call_count, 'callback should have been called twice')205 self.assertEqual(2, mock_callback.call_count, 'callback should have been called twice')
200 self.assertEqual(2, mocked_opener.open.call_count, 'open should have been called twice')206 self.assertEqual(2, mocked_opener.open.call_count, 'open should have been called twice')
201 self.assertEqual(2, mocked_results_page.find_all.call_count, 'find_all should have been called twice')207 self.assertEqual(2, mocked_results_page.find_all.call_count, 'find_all should have been called twice')
202 mocked_results_page.find_all.assert_called_with('li', 'result pane')208 mocked_results_page.find_all.assert_called_with('div', 'song-result')
203 expected_list = [209 expected_list = [
204 {'title': 'Title 1', 'authors': ['Author 1-1', 'Author 1-2'], 'link': BASE_URL + '/url1'},210 {'title': 'Title 1', 'authors': ['James', 'John'], 'link': BASE_URL + '/url1'},
205 {'title': 'Title 2', 'authors': ['Author 2-1', 'Author 2-2'], 'link': BASE_URL + '/url2'}211 {'title': 'Title 2', 'authors': ['Philip'], 'link': BASE_URL + '/url2'}
206 ]212 ]
207 self.assertListEqual(expected_list, results, 'The search method should have returned two songs')213 self.assertListEqual(expected_list, results, 'The search method should have returned two songs')
208214
@@ -215,16 +221,25 @@
215 # GIVEN: A bunch of mocked out stuff and an importer object221 # GIVEN: A bunch of mocked out stuff and an importer object
216 # first search result222 # first search result
217 mocked_result1 = MagicMock()223 mocked_result1 = MagicMock()
218 mocked_result1.find.side_effect = [MagicMock(string='Title 1'), {'href': '/url1'}]224 mocked_result1.find.side_effect = [
219 mocked_result1.find_all.return_value = [MagicMock(string='Author 1-1'), MagicMock(string='Author 1-2')]225 MagicMock(find=MagicMock(return_value=MagicMock(string='Title 1'))),
226 MagicMock(string='James, John'),
227 MagicMock(find=MagicMock(return_value={'href': '/url1'}))
228 ]
220 # second search result229 # second search result
221 mocked_result2 = MagicMock()230 mocked_result2 = MagicMock()
222 mocked_result2.find.side_effect = [MagicMock(string='Title 2'), {'href': '/url2'}]231 mocked_result2.find.side_effect = [
223 mocked_result2.find_all.return_value = [MagicMock(string='Author 2-1'), MagicMock(string='Author 2-2')]232 MagicMock(find=MagicMock(return_value=MagicMock(string='Title 2'))),
233 MagicMock(string='Philip'),
234 MagicMock(find=MagicMock(return_value={'href': '/url2'}))
235 ]
224 # third search result236 # third search result
225 mocked_result3 = MagicMock()237 mocked_result3 = MagicMock()
226 mocked_result3.find.side_effect = [MagicMock(string='Title 3'), {'href': '/url3'}]238 mocked_result3.find.side_effect = [
227 mocked_result3.find_all.return_value = [MagicMock(string='Author 3-1'), MagicMock(string='Author 3-2')]239 MagicMock(find=MagicMock(return_value=MagicMock(string='Title 3'))),
240 MagicMock(string='Luke, Matthew'),
241 MagicMock(find=MagicMock(return_value={'href': '/url3'}))
242 ]
228 # rest of the stuff243 # rest of the stuff
229 mocked_opener = MagicMock()244 mocked_opener = MagicMock()
230 mocked_build_opener.return_value = mocked_opener245 mocked_build_opener.return_value = mocked_opener
@@ -241,9 +256,9 @@
241 self.assertEqual(2, mock_callback.call_count, 'callback should have been called twice')256 self.assertEqual(2, mock_callback.call_count, 'callback should have been called twice')
242 self.assertEqual(2, mocked_opener.open.call_count, 'open should have been called twice')257 self.assertEqual(2, mocked_opener.open.call_count, 'open should have been called twice')
243 self.assertEqual(2, mocked_results_page.find_all.call_count, 'find_all should have been called twice')258 self.assertEqual(2, mocked_results_page.find_all.call_count, 'find_all should have been called twice')
244 mocked_results_page.find_all.assert_called_with('li', 'result pane')259 mocked_results_page.find_all.assert_called_with('div', 'song-result')
245 expected_list = [{'title': 'Title 1', 'authors': ['Author 1-1', 'Author 1-2'], 'link': BASE_URL + '/url1'},260 expected_list = [{'title': 'Title 1', 'authors': ['James', 'John'], 'link': BASE_URL + '/url1'},
246 {'title': 'Title 2', 'authors': ['Author 2-1', 'Author 2-2'], 'link': BASE_URL + '/url2'}]261 {'title': 'Title 2', 'authors': ['Philip'], 'link': BASE_URL + '/url2'}]
247 self.assertListEqual(expected_list, results, 'The search method should have returned two songs')262 self.assertListEqual(expected_list, results, 'The search method should have returned two songs')
248263
249 @patch('openlp.plugins.songs.lib.songselect.build_opener')264 @patch('openlp.plugins.songs.lib.songselect.build_opener')
@@ -337,7 +352,7 @@
337 self.assertIsNotNone(result, 'The get_song() method should have returned a song dictionary')352 self.assertIsNotNone(result, 'The get_song() method should have returned a song dictionary')
338 self.assertEqual(2, mocked_lyrics_page.find.call_count, 'The find() method should have been called twice')353 self.assertEqual(2, mocked_lyrics_page.find.call_count, 'The find() method should have been called twice')
339 self.assertEqual(2, mocked_find_all.call_count, 'The find_all() method should have been called twice')354 self.assertEqual(2, mocked_find_all.call_count, 'The find_all() method should have been called twice')
340 self.assertEqual([call('section', 'lyrics'), call('section', 'lyrics')],355 self.assertEqual([call('div', 'song-viewer lyrics'), call('div', 'song-viewer lyrics')],
341 mocked_lyrics_page.find.call_args_list,356 mocked_lyrics_page.find.call_args_list,
342 'The find() method should have been called with the right arguments')357 'The find() method should have been called with the right arguments')
343 self.assertEqual([call('p'), call('h3')], mocked_find_all.call_args_list,358 self.assertEqual([call('p'), call('h3')], mocked_find_all.call_args_list,
@@ -348,8 +363,9 @@
348 self.assertEqual(3, len(result['verses']), 'Three verses should have been returned')363 self.assertEqual(3, len(result['verses']), 'Three verses should have been returned')
349364
350 @patch('openlp.plugins.songs.lib.songselect.clean_song')365 @patch('openlp.plugins.songs.lib.songselect.clean_song')
366 @patch('openlp.plugins.songs.lib.songselect.Topic')
351 @patch('openlp.plugins.songs.lib.songselect.Author')367 @patch('openlp.plugins.songs.lib.songselect.Author')
352 def test_save_song_new_author(self, MockedAuthor, mocked_clean_song):368 def test_save_song_new_author(self, MockedAuthor, MockedTopic, mocked_clean_song):
353 """369 """
354 Test that saving a song with a new author performs the correct actions370 Test that saving a song with a new author performs the correct actions
355 """371 """
@@ -366,6 +382,7 @@
366 'ccli_number': '123456'382 'ccli_number': '123456'
367 }383 }
368 MockedAuthor.display_name.__eq__.return_value = False384 MockedAuthor.display_name.__eq__.return_value = False
385 MockedTopic.name.__eq__.return_value = False
369 mocked_db_manager = MagicMock()386 mocked_db_manager = MagicMock()
370 mocked_db_manager.get_object_filtered.return_value = None387 mocked_db_manager.get_object_filtered.return_value = None
371 importer = SongSelectImport(mocked_db_manager)388 importer = SongSelectImport(mocked_db_manager)
@@ -848,7 +865,7 @@
848865
849 # WHEN: The start() method is called866 # WHEN: The start() method is called
850 with patch.object(worker, 'found_song') as mocked_found_song:867 with patch.object(worker, 'found_song') as mocked_found_song:
851 worker._found_song_callback(song)868 worker._found_song_callback(song) # pylint: disable=protected-access
852869
853 # THEN: The "found_song" signal should have been emitted870 # THEN: The "found_song" signal should have been emitted
854 mocked_found_song.emit.assert_called_with(song)871 mocked_found_song.emit.assert_called_with(song)