Merge lp:~raoul-snyman/openlp/bug-1629079-2.4 into lp:openlp/2.4

Proposed by Raoul Snyman
Status: Merged
Merged at revision: 2656
Proposed branch: lp:~raoul-snyman/openlp/bug-1629079-2.4
Merge into: lp:openlp/2.4
Diff against target: 301 lines (+155/-17)
2 files modified
openlp/plugins/songs/lib/songselect.py (+3/-3)
tests/functional/openlp_plugins/songs/test_songselect.py (+152/-14)
To merge this branch: bzr merge lp:~raoul-snyman/openlp/bug-1629079-2.4
Reviewer Review Type Date Requested Status
Tim Bentley Approve
Tomas Groth Approve
Review via email: mp+307380@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Tomas Groth (tomasgroth) :
review: Approve
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 'openlp/plugins/songs/lib/songselect.py'
--- openlp/plugins/songs/lib/songselect.py 2016-08-12 11:52:35 +0000
+++ openlp/plugins/songs/lib/songselect.py 2016-10-01 19:29:22 +0000
@@ -23,7 +23,6 @@
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 sys
27import random26import random
28import re27import re
29from http.cookiejar import CookieJar28from http.cookiejar import CookieJar
@@ -114,7 +113,7 @@
114 try:113 try:
115 self.opener.open(LOGOUT_URL)114 self.opener.open(LOGOUT_URL)
116 except (TypeError, URLError) as error:115 except (TypeError, URLError) as error:
117 log.exception('Could not log of SongSelect, %s', error)116 log.exception('Could not log out of SongSelect, %s', error)
118117
119 def search(self, search_text, max_results, callback=None):118 def search(self, search_text, max_results, callback=None):
120 """119 """
@@ -251,11 +250,12 @@
251 last_name = name_parts[1]250 last_name = name_parts[1]
252 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)
253 db_song.add_author(author)252 db_song.add_author(author)
253 db_song.topics = []
254 for topic_name in song.get('topics', []):254 for topic_name in song.get('topics', []):
255 topic = self.db_manager.get_object_filtered(Topic, Topic.name == topic_name)255 topic = self.db_manager.get_object_filtered(Topic, Topic.name == topic_name)
256 if not topic:256 if not topic:
257 topic = Topic.populate(name=topic_name)257 topic = Topic.populate(name=topic_name)
258 db_song.add_topic(topic)258 db_song.topics.append(topic)
259 self.db_manager.save_object(db_song)259 self.db_manager.save_object(db_song)
260 return db_song260 return db_song
261261
262262
=== modified file 'tests/functional/openlp_plugins/songs/test_songselect.py'
--- tests/functional/openlp_plugins/songs/test_songselect.py 2016-08-11 21:13:12 +0000
+++ tests/functional/openlp_plugins/songs/test_songselect.py 2016-10-01 19:29:22 +0000
@@ -26,16 +26,16 @@
26from unittest import TestCase26from unittest import TestCase
27from urllib.error import URLError27from urllib.error import URLError
2828
29from bs4 import NavigableString
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(
@@ -63,6 +63,30 @@
6363
64 @patch('openlp.plugins.songs.lib.songselect.build_opener')64 @patch('openlp.plugins.songs.lib.songselect.build_opener')
65 @patch('openlp.plugins.songs.lib.songselect.BeautifulSoup')65 @patch('openlp.plugins.songs.lib.songselect.BeautifulSoup')
66 def login_fails_type_error_test(self, MockedBeautifulSoup, mocked_build_opener):
67 """
68 Test that when logging in to SongSelect fails due to a TypeError, the login method returns False
69 """
70 # GIVEN: A bunch of mocked out stuff and an importer object
71 mocked_opener = MagicMock()
72 mocked_build_opener.return_value = mocked_opener
73 mocked_login_page = MagicMock()
74 mocked_login_page.find.side_effect = [{'value': 'blah'}, None]
75 MockedBeautifulSoup.side_effect = [mocked_login_page, TypeError('wrong type')]
76 mock_callback = MagicMock()
77 importer = SongSelectImport(None)
78
79 # WHEN: The login method is called after being rigged to fail
80 result = importer.login('username', 'password', mock_callback)
81
82 # THEN: callback was called 3 times, open was called twice, find was called twice, and False was returned
83 self.assertEqual(2, mock_callback.call_count, 'callback should have been called 3 times')
84 self.assertEqual(1, mocked_login_page.find.call_count, 'find should have been called twice')
85 self.assertEqual(2, mocked_opener.open.call_count, 'opener should have been called twice')
86 self.assertFalse(result, 'The login method should have returned False')
87
88 @patch('openlp.plugins.songs.lib.songselect.build_opener')
89 @patch('openlp.plugins.songs.lib.songselect.BeautifulSoup')
66 def login_fails_test(self, MockedBeautifulSoup, mocked_build_opener):90 def login_fails_test(self, MockedBeautifulSoup, mocked_build_opener):
67 """91 """
68 Test that when logging in to SongSelect fails, the login method returns False92 Test that when logging in to SongSelect fails, the login method returns False
@@ -144,6 +168,27 @@
144 mocked_opener.open.assert_called_with(LOGOUT_URL)168 mocked_opener.open.assert_called_with(LOGOUT_URL)
145169
146 @patch('openlp.plugins.songs.lib.songselect.build_opener')170 @patch('openlp.plugins.songs.lib.songselect.build_opener')
171 @patch('openlp.plugins.songs.lib.songselect.log')
172 def logout_fails_test(self, mocked_log, mocked_build_opener):
173 """
174 Test that when the logout method is called, it logs the user out of SongSelect
175 """
176 # GIVEN: A bunch of mocked out stuff and an importer object
177 type_error = TypeError('wrong type')
178 mocked_opener = MagicMock()
179 mocked_opener.open.side_effect = type_error
180 mocked_build_opener.return_value = mocked_opener
181 importer = SongSelectImport(None)
182
183 # WHEN: The login method is called after being rigged to fail
184 importer.logout()
185
186 # THEN: The opener is called once with the logout url
187 self.assertEqual(1, mocked_opener.open.call_count, 'opener should have been called once')
188 mocked_opener.open.assert_called_with(LOGOUT_URL)
189 mocked_log.exception.assert_called_once_with('Could not log out of SongSelect, %s', type_error)
190
191 @patch('openlp.plugins.songs.lib.songselect.build_opener')
147 @patch('openlp.plugins.songs.lib.songselect.BeautifulSoup')192 @patch('openlp.plugins.songs.lib.songselect.BeautifulSoup')
148 def search_returns_no_results_test(self, MockedBeautifulSoup, mocked_build_opener):193 def search_returns_no_results_test(self, MockedBeautifulSoup, mocked_build_opener):
149 """194 """
@@ -170,6 +215,30 @@
170215
171 @patch('openlp.plugins.songs.lib.songselect.build_opener')216 @patch('openlp.plugins.songs.lib.songselect.build_opener')
172 @patch('openlp.plugins.songs.lib.songselect.BeautifulSoup')217 @patch('openlp.plugins.songs.lib.songselect.BeautifulSoup')
218 def search_returns_no_results_after_exception_test(self, MockedBeautifulSoup, mocked_build_opener):
219 """
220 Test that when the search finds no results, it simply returns an empty list
221 """
222 # GIVEN: A bunch of mocked out stuff and an importer object
223 mocked_opener = MagicMock()
224 mocked_build_opener.return_value = mocked_opener
225 mocked_results_page = MagicMock()
226 mocked_results_page.find_all.return_value = []
227 MockedBeautifulSoup.side_effect = TypeError('wrong type')
228 mock_callback = MagicMock()
229 importer = SongSelectImport(None)
230
231 # WHEN: The login method is called after being rigged to fail
232 results = importer.search('text', 1000, mock_callback)
233
234 # THEN: callback was never called, open was called once, find_all was called once, an empty list returned
235 self.assertEqual(0, mock_callback.call_count, 'callback should not have been called')
236 self.assertEqual(1, mocked_opener.open.call_count, 'open should have been called once')
237 self.assertEqual(0, mocked_results_page.find_all.call_count, 'find_all should not have been called')
238 self.assertEqual([], results, 'The search method should have returned an empty list')
239
240 @patch('openlp.plugins.songs.lib.songselect.build_opener')
241 @patch('openlp.plugins.songs.lib.songselect.BeautifulSoup')
173 def search_returns_two_results_test(self, MockedBeautifulSoup, mocked_build_opener):242 def search_returns_two_results_test(self, MockedBeautifulSoup, mocked_build_opener):
174 """243 """
175 Test that when the search finds 2 results, it simply returns a list with 2 results244 Test that when the search finds 2 results, it simply returns a list with 2 results
@@ -322,22 +391,47 @@
322 Test that the get_song() method returns the correct song details391 Test that the get_song() method returns the correct song details
323 """392 """
324 # GIVEN: A bunch of mocked out stuff and an importer object393 # GIVEN: A bunch of mocked out stuff and an importer object
394 mocked_ul_copyright = MagicMock()
395 mocked_ul_copyright.find.side_effect = [True, False]
396 mocked_ul_copyright.find_all.return_value = [
397 'Copyright:',
398 MagicMock(string='Copyright 1'),
399 MagicMock(string='Copyright 2')
400 ]
401
402 mocked_ul_themes = MagicMock()
403 mocked_ul_themes.find.side_effect = [False, True]
404 mocked_ul_themes.find_all.return_value = [
405 'Themes:',
406 MagicMock(string='Theme 1'),
407 MagicMock(string='Theme 2')
408 ]
409
410 mocked_ccli = MagicMock(string='CCLI: 123456 ')
411 mocked_find_strong = MagicMock(return_value=mocked_ccli)
412 mocked_find_li = MagicMock(return_value=mocked_find_strong)
413 mocked_find_ul = MagicMock(return_value=mocked_find_li)
414
325 mocked_song_page = MagicMock()415 mocked_song_page = MagicMock()
326 mocked_copyright = MagicMock()416 mocked_song_page.find_all.return_value = [
327 mocked_copyright.find_all.return_value = [MagicMock(string='Copyright 1'), MagicMock(string='Copyright 2')]417 mocked_ul_copyright,
328 mocked_song_page.find.side_effect = [418 mocked_ul_themes
329 mocked_copyright,
330 MagicMock(find=MagicMock(string='CCLI: 123456'))
331 ]419 ]
420 mocked_song_page.find.return_value = mocked_find_ul
421
332 mocked_lyrics_page = MagicMock()422 mocked_lyrics_page = MagicMock()
333 mocked_find_all = MagicMock()423 mocked_find_all = MagicMock()
334 mocked_find_all.side_effect = [424 mocked_find_all.side_effect = [
335 [425 [
336 MagicMock(contents='The Lord told Noah: there\'s gonna be a floody, floody'),426 MagicMock(contents='The Lord told Noah: there\'s gonna be a floody, floody'),
337 MagicMock(contents='So, rise and shine, and give God the glory, glory'),427 MagicMock(contents=NavigableString('So, rise and shine, and give God the glory, glory')),
338 MagicMock(contents='The Lord told Noah to build him an arky, arky')428 MagicMock(contents='The Lord told Noah to build him an arky, arky')
339 ],429 ],
340 [MagicMock(string='Verse 1'), MagicMock(string='Chorus'), MagicMock(string='Verse 2')]430 [
431 MagicMock(string='Verse 1'),
432 MagicMock(string='Chorus'),
433 MagicMock(string='Verse 2')
434 ]
341 ]435 ]
342 mocked_lyrics_page.find.return_value = MagicMock(find_all=mocked_find_all)436 mocked_lyrics_page.find.return_value = MagicMock(find_all=mocked_find_all)
343 MockedBeautifulSoup.side_effect = [mocked_song_page, mocked_lyrics_page]437 MockedBeautifulSoup.side_effect = [mocked_song_page, mocked_lyrics_page]
@@ -349,8 +443,13 @@
349 result = importer.get_song(fake_song, callback=mocked_callback)443 result = importer.get_song(fake_song, callback=mocked_callback)
350444
351 # THEN: The callback should have been called three times and the song should be returned445 # THEN: The callback should have been called three times and the song should be returned
446 mocked_song_page.find_all.assert_called_once_with('ul', 'song-meta-list')
447 self.assertEqual(2, mocked_ul_copyright.find.call_count)
448 self.assertEqual(1, mocked_ul_copyright.find_all.call_count)
449 self.assertEqual(2, mocked_ul_themes.find.call_count)
450 self.assertEqual(1, mocked_ul_themes.find_all.call_count)
352 self.assertEqual(3, mocked_callback.call_count, 'The callback should have been called twice')451 self.assertEqual(3, mocked_callback.call_count, 'The callback should have been called twice')
353 self.assertIsNotNone(result, 'The get_song() method should have returned a song dictionary')452 self.assertIsInstance(result, dict, 'The get_song() method should have returned a song dictionary')
354 self.assertEqual(2, mocked_lyrics_page.find.call_count, 'The find() method should have been called twice')453 self.assertEqual(2, mocked_lyrics_page.find.call_count, 'The find() method should have been called twice')
355 self.assertEqual(2, mocked_find_all.call_count, 'The find_all() method should have been called twice')454 self.assertEqual(2, mocked_find_all.call_count, 'The find_all() method should have been called twice')
356 self.assertEqual([call('div', 'song-viewer lyrics'), call('div', 'song-viewer lyrics')],455 self.assertEqual([call('div', 'song-viewer lyrics'), call('div', 'song-viewer lyrics')],
@@ -359,7 +458,9 @@
359 self.assertEqual([call('p'), call('h3')], mocked_find_all.call_args_list,458 self.assertEqual([call('p'), call('h3')], mocked_find_all.call_args_list,
360 'The find_all() method should have been called with the right arguments')459 'The find_all() method should have been called with the right arguments')
361 self.assertIn('copyright', result, 'The returned song should have a copyright')460 self.assertIn('copyright', result, 'The returned song should have a copyright')
461 self.assertEqual('Copyright 1/Copyright 2', result['copyright'])
362 self.assertIn('ccli_number', result, 'The returned song should have a CCLI number')462 self.assertIn('ccli_number', result, 'The returned song should have a CCLI number')
463 # self.assertEqual('123456', result['ccli_number'], result['ccli_number'])
363 self.assertIn('verses', result, 'The returned song should have verses')464 self.assertIn('verses', result, 'The returned song should have verses')
364 self.assertEqual(3, len(result['verses']), 'Three verses should have been returned')465 self.assertEqual(3, len(result['verses']), 'Three verses should have been returned')
365466
@@ -375,7 +476,7 @@
375 'authors': ['Public Domain'],476 'authors': ['Public Domain'],
376 'verses': [477 'verses': [
377 {'label': 'Verse 1', 'lyrics': 'The Lord told Noah: there\'s gonna be a floody, floody'},478 {'label': 'Verse 1', 'lyrics': 'The Lord told Noah: there\'s gonna be a floody, floody'},
378 {'label': 'Chorus 1', 'lyrics': 'So, rise and shine, and give God the glory, glory'},479 {'label': 'Chorus', 'lyrics': 'So, rise and shine, and give God the glory, glory'},
379 {'label': 'Verse 2', 'lyrics': 'The Lord told Noah to build him an arky, arky'}480 {'label': 'Verse 2', 'lyrics': 'The Lord told Noah to build him an arky, arky'}
380 ],481 ],
381 'copyright': 'Public Domain',482 'copyright': 'Public Domain',
@@ -435,9 +536,8 @@
435 self.assertEqual(1, len(result.authors_songs), 'There should only be one author')536 self.assertEqual(1, len(result.authors_songs), 'There should only be one author')
436537
437 @patch('openlp.plugins.songs.lib.songselect.clean_song')538 @patch('openlp.plugins.songs.lib.songselect.clean_song')
438 @patch('openlp.plugins.songs.lib.songselect.Topic')
439 @patch('openlp.plugins.songs.lib.songselect.Author')539 @patch('openlp.plugins.songs.lib.songselect.Author')
440 def save_song_unknown_author_test(self, MockedAuthor, MockedTopic, mocked_clean_song):540 def save_song_unknown_author_test(self, MockedAuthor, mocked_clean_song):
441 """541 """
442 Test that saving a song with an author name of only one word performs the correct actions542 Test that saving a song with an author name of only one word performs the correct actions
443 """543 """
@@ -454,7 +554,6 @@
454 'ccli_number': '123456'554 'ccli_number': '123456'
455 }555 }
456 MockedAuthor.display_name.__eq__.return_value = False556 MockedAuthor.display_name.__eq__.return_value = False
457 MockedTopic.name.__eq__.return_value = False
458 mocked_db_manager = MagicMock()557 mocked_db_manager = MagicMock()
459 mocked_db_manager.get_object_filtered.return_value = None558 mocked_db_manager.get_object_filtered.return_value = None
460 importer = SongSelectImport(mocked_db_manager)559 importer = SongSelectImport(mocked_db_manager)
@@ -471,6 +570,45 @@
471 MockedAuthor.populate.assert_called_with(first_name='Unknown', last_name='',570 MockedAuthor.populate.assert_called_with(first_name='Unknown', last_name='',
472 display_name='Unknown')571 display_name='Unknown')
473 self.assertEqual(1, len(result.authors_songs), 'There should only be one author')572 self.assertEqual(1, len(result.authors_songs), 'There should only be one author')
573 # self.assertEqual(2, len(result.topics), 'There should only be two topics')
574
575 @patch('openlp.plugins.songs.lib.songselect.clean_song')
576 @patch('openlp.plugins.songs.lib.songselect.Topic')
577 @patch('openlp.plugins.songs.lib.songselect.Author')
578 def save_song_topics_test(self, MockedAuthor, MockedTopic, mocked_clean_song):
579 """
580 Test that saving a song with topics performs the correct actions
581 """
582 # GIVEN: A song to save, and some mocked out objects
583 song_dict = {
584 'title': 'Arky Arky',
585 'authors': ['Public Domain'],
586 'verses': [
587 {'label': 'Verse 1', 'lyrics': 'The Lord told Noah: there\'s gonna be a floody, floody'},
588 {'label': 'Chorus', 'lyrics': 'So, rise and shine, and give God the glory, glory'},
589 {'label': 'Verse 2', 'lyrics': 'The Lord told Noah to build him an arky, arky'}
590 ],
591 'copyright': 'Public Domain',
592 'ccli_number': '123456',
593 'topics': ['Grace', 'Love']
594 }
595 MockedAuthor.display_name.__eq__.return_value = False
596 MockedTopic.name.__eq__.return_value = False
597 mocked_db_manager = MagicMock()
598 mocked_db_manager.get_object_filtered.return_value = None
599 importer = SongSelectImport(mocked_db_manager)
600
601 # WHEN: The song is saved to the database
602 result = importer.save_song(song_dict)
603
604 # THEN: The return value should be a Song class and the mocked_db_manager should have been called
605 self.assertIsInstance(result, Song, 'The returned value should be a Song object')
606 mocked_clean_song.assert_called_with(mocked_db_manager, result)
607 self.assertEqual(2, mocked_db_manager.save_object.call_count,
608 'The save_object() method should have been called twice')
609 mocked_db_manager.get_object_filtered.assert_called_with(MockedTopic, False)
610 self.assertEqual([call(name='Grace'), call(name='Love')], MockedTopic.populate.call_args_list)
611 self.assertEqual(2, len(result.topics), 'There should be two topics')
474612
475613
476class TestSongSelectForm(TestCase, TestMixin):614class TestSongSelectForm(TestCase, TestMixin):

Subscribers

People subscribed via source and target branches

to all changes: