Merge lp:~raoul-snyman/openlp/bug-1629079-2.4 into lp:openlp/2.4
- bug-1629079-2.4
- Merge into 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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Tim Bentley | Approve | ||
Tomas Groth | Approve | ||
Review via email: mp+307380@code.launchpad.net |
Commit message
Description of the change
Fix bug #1629079: Attribute error when importing from SongSelect
Add this to your merge proposal:
-------
lp:~raoul-snyman/openlp/bug-1629079-2.4 (revision 2657)
[SUCCESS] https:/
[SUCCESS] https:/
[SUCCESS] https:/
[SUCCESS] https:/
[SUCCESS] https:/
[SUCCESS] https:/
[SUCCESS] https:/
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
1 | === modified file 'openlp/plugins/songs/lib/songselect.py' | |||
2 | --- openlp/plugins/songs/lib/songselect.py 2016-08-12 11:52:35 +0000 | |||
3 | +++ openlp/plugins/songs/lib/songselect.py 2016-10-01 19:29:22 +0000 | |||
4 | @@ -23,7 +23,6 @@ | |||
5 | 23 | The :mod:`~openlp.plugins.songs.lib.songselect` module contains the SongSelect importer itself. | 23 | The :mod:`~openlp.plugins.songs.lib.songselect` module contains the SongSelect importer itself. |
6 | 24 | """ | 24 | """ |
7 | 25 | import logging | 25 | import logging |
8 | 26 | import sys | ||
9 | 27 | import random | 26 | import random |
10 | 28 | import re | 27 | import re |
11 | 29 | from http.cookiejar import CookieJar | 28 | from http.cookiejar import CookieJar |
12 | @@ -114,7 +113,7 @@ | |||
13 | 114 | try: | 113 | try: |
14 | 115 | self.opener.open(LOGOUT_URL) | 114 | self.opener.open(LOGOUT_URL) |
15 | 116 | except (TypeError, URLError) as error: | 115 | except (TypeError, URLError) as error: |
17 | 117 | log.exception('Could not log of SongSelect, %s', error) | 116 | log.exception('Could not log out of SongSelect, %s', error) |
18 | 118 | 117 | ||
19 | 119 | def search(self, search_text, max_results, callback=None): | 118 | def search(self, search_text, max_results, callback=None): |
20 | 120 | """ | 119 | """ |
21 | @@ -251,11 +250,12 @@ | |||
22 | 251 | last_name = name_parts[1] | 250 | last_name = name_parts[1] |
23 | 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) |
24 | 253 | db_song.add_author(author) | 252 | db_song.add_author(author) |
25 | 253 | db_song.topics = [] | ||
26 | 254 | for topic_name in song.get('topics', []): | 254 | for topic_name in song.get('topics', []): |
27 | 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) |
28 | 256 | if not topic: | 256 | if not topic: |
29 | 257 | topic = Topic.populate(name=topic_name) | 257 | topic = Topic.populate(name=topic_name) |
31 | 258 | db_song.add_topic(topic) | 258 | db_song.topics.append(topic) |
32 | 259 | self.db_manager.save_object(db_song) | 259 | self.db_manager.save_object(db_song) |
33 | 260 | return db_song | 260 | return db_song |
34 | 261 | 261 | ||
35 | 262 | 262 | ||
36 | === modified file 'tests/functional/openlp_plugins/songs/test_songselect.py' | |||
37 | --- tests/functional/openlp_plugins/songs/test_songselect.py 2016-08-11 21:13:12 +0000 | |||
38 | +++ tests/functional/openlp_plugins/songs/test_songselect.py 2016-10-01 19:29:22 +0000 | |||
39 | @@ -26,16 +26,16 @@ | |||
40 | 26 | from unittest import TestCase | 26 | from unittest import TestCase |
41 | 27 | from urllib.error import URLError | 27 | from urllib.error import URLError |
42 | 28 | 28 | ||
43 | 29 | from bs4 import NavigableString | ||
44 | 29 | from PyQt5 import QtWidgets | 30 | from PyQt5 import QtWidgets |
45 | 30 | 31 | ||
46 | 31 | from tests.helpers.songfileimport import SongImportTestHelper | ||
47 | 32 | from openlp.core import Registry | 32 | from openlp.core import Registry |
48 | 33 | from openlp.plugins.songs.forms.songselectform import SongSelectForm, SearchWorker | 33 | from openlp.plugins.songs.forms.songselectform import SongSelectForm, SearchWorker |
49 | 34 | from openlp.plugins.songs.lib import Song | 34 | from openlp.plugins.songs.lib import Song |
50 | 35 | from openlp.plugins.songs.lib.songselect import SongSelectImport, LOGOUT_URL, BASE_URL | 35 | from openlp.plugins.songs.lib.songselect import SongSelectImport, LOGOUT_URL, BASE_URL |
51 | 36 | from openlp.plugins.songs.lib.importers.cclifile import CCLIFileImport | ||
52 | 37 | 36 | ||
53 | 38 | from tests.functional import MagicMock, patch, call | 37 | from tests.functional import MagicMock, patch, call |
54 | 38 | from tests.helpers.songfileimport import SongImportTestHelper | ||
55 | 39 | from tests.helpers.testmixin import TestMixin | 39 | from tests.helpers.testmixin import TestMixin |
56 | 40 | 40 | ||
57 | 41 | TEST_PATH = os.path.abspath( | 41 | TEST_PATH = os.path.abspath( |
58 | @@ -63,6 +63,30 @@ | |||
59 | 63 | 63 | ||
60 | 64 | @patch('openlp.plugins.songs.lib.songselect.build_opener') | 64 | @patch('openlp.plugins.songs.lib.songselect.build_opener') |
61 | 65 | @patch('openlp.plugins.songs.lib.songselect.BeautifulSoup') | 65 | @patch('openlp.plugins.songs.lib.songselect.BeautifulSoup') |
62 | 66 | def login_fails_type_error_test(self, MockedBeautifulSoup, mocked_build_opener): | ||
63 | 67 | """ | ||
64 | 68 | Test that when logging in to SongSelect fails due to a TypeError, the login method returns False | ||
65 | 69 | """ | ||
66 | 70 | # GIVEN: A bunch of mocked out stuff and an importer object | ||
67 | 71 | mocked_opener = MagicMock() | ||
68 | 72 | mocked_build_opener.return_value = mocked_opener | ||
69 | 73 | mocked_login_page = MagicMock() | ||
70 | 74 | mocked_login_page.find.side_effect = [{'value': 'blah'}, None] | ||
71 | 75 | MockedBeautifulSoup.side_effect = [mocked_login_page, TypeError('wrong type')] | ||
72 | 76 | mock_callback = MagicMock() | ||
73 | 77 | importer = SongSelectImport(None) | ||
74 | 78 | |||
75 | 79 | # WHEN: The login method is called after being rigged to fail | ||
76 | 80 | result = importer.login('username', 'password', mock_callback) | ||
77 | 81 | |||
78 | 82 | # THEN: callback was called 3 times, open was called twice, find was called twice, and False was returned | ||
79 | 83 | self.assertEqual(2, mock_callback.call_count, 'callback should have been called 3 times') | ||
80 | 84 | self.assertEqual(1, mocked_login_page.find.call_count, 'find should have been called twice') | ||
81 | 85 | self.assertEqual(2, mocked_opener.open.call_count, 'opener should have been called twice') | ||
82 | 86 | self.assertFalse(result, 'The login method should have returned False') | ||
83 | 87 | |||
84 | 88 | @patch('openlp.plugins.songs.lib.songselect.build_opener') | ||
85 | 89 | @patch('openlp.plugins.songs.lib.songselect.BeautifulSoup') | ||
86 | 66 | def login_fails_test(self, MockedBeautifulSoup, mocked_build_opener): | 90 | def login_fails_test(self, MockedBeautifulSoup, mocked_build_opener): |
87 | 67 | """ | 91 | """ |
88 | 68 | Test that when logging in to SongSelect fails, the login method returns False | 92 | Test that when logging in to SongSelect fails, the login method returns False |
89 | @@ -144,6 +168,27 @@ | |||
90 | 144 | mocked_opener.open.assert_called_with(LOGOUT_URL) | 168 | mocked_opener.open.assert_called_with(LOGOUT_URL) |
91 | 145 | 169 | ||
92 | 146 | @patch('openlp.plugins.songs.lib.songselect.build_opener') | 170 | @patch('openlp.plugins.songs.lib.songselect.build_opener') |
93 | 171 | @patch('openlp.plugins.songs.lib.songselect.log') | ||
94 | 172 | def logout_fails_test(self, mocked_log, mocked_build_opener): | ||
95 | 173 | """ | ||
96 | 174 | Test that when the logout method is called, it logs the user out of SongSelect | ||
97 | 175 | """ | ||
98 | 176 | # GIVEN: A bunch of mocked out stuff and an importer object | ||
99 | 177 | type_error = TypeError('wrong type') | ||
100 | 178 | mocked_opener = MagicMock() | ||
101 | 179 | mocked_opener.open.side_effect = type_error | ||
102 | 180 | mocked_build_opener.return_value = mocked_opener | ||
103 | 181 | importer = SongSelectImport(None) | ||
104 | 182 | |||
105 | 183 | # WHEN: The login method is called after being rigged to fail | ||
106 | 184 | importer.logout() | ||
107 | 185 | |||
108 | 186 | # THEN: The opener is called once with the logout url | ||
109 | 187 | self.assertEqual(1, mocked_opener.open.call_count, 'opener should have been called once') | ||
110 | 188 | mocked_opener.open.assert_called_with(LOGOUT_URL) | ||
111 | 189 | mocked_log.exception.assert_called_once_with('Could not log out of SongSelect, %s', type_error) | ||
112 | 190 | |||
113 | 191 | @patch('openlp.plugins.songs.lib.songselect.build_opener') | ||
114 | 147 | @patch('openlp.plugins.songs.lib.songselect.BeautifulSoup') | 192 | @patch('openlp.plugins.songs.lib.songselect.BeautifulSoup') |
115 | 148 | def search_returns_no_results_test(self, MockedBeautifulSoup, mocked_build_opener): | 193 | def search_returns_no_results_test(self, MockedBeautifulSoup, mocked_build_opener): |
116 | 149 | """ | 194 | """ |
117 | @@ -170,6 +215,30 @@ | |||
118 | 170 | 215 | ||
119 | 171 | @patch('openlp.plugins.songs.lib.songselect.build_opener') | 216 | @patch('openlp.plugins.songs.lib.songselect.build_opener') |
120 | 172 | @patch('openlp.plugins.songs.lib.songselect.BeautifulSoup') | 217 | @patch('openlp.plugins.songs.lib.songselect.BeautifulSoup') |
121 | 218 | def search_returns_no_results_after_exception_test(self, MockedBeautifulSoup, mocked_build_opener): | ||
122 | 219 | """ | ||
123 | 220 | Test that when the search finds no results, it simply returns an empty list | ||
124 | 221 | """ | ||
125 | 222 | # GIVEN: A bunch of mocked out stuff and an importer object | ||
126 | 223 | mocked_opener = MagicMock() | ||
127 | 224 | mocked_build_opener.return_value = mocked_opener | ||
128 | 225 | mocked_results_page = MagicMock() | ||
129 | 226 | mocked_results_page.find_all.return_value = [] | ||
130 | 227 | MockedBeautifulSoup.side_effect = TypeError('wrong type') | ||
131 | 228 | mock_callback = MagicMock() | ||
132 | 229 | importer = SongSelectImport(None) | ||
133 | 230 | |||
134 | 231 | # WHEN: The login method is called after being rigged to fail | ||
135 | 232 | results = importer.search('text', 1000, mock_callback) | ||
136 | 233 | |||
137 | 234 | # THEN: callback was never called, open was called once, find_all was called once, an empty list returned | ||
138 | 235 | self.assertEqual(0, mock_callback.call_count, 'callback should not have been called') | ||
139 | 236 | self.assertEqual(1, mocked_opener.open.call_count, 'open should have been called once') | ||
140 | 237 | self.assertEqual(0, mocked_results_page.find_all.call_count, 'find_all should not have been called') | ||
141 | 238 | self.assertEqual([], results, 'The search method should have returned an empty list') | ||
142 | 239 | |||
143 | 240 | @patch('openlp.plugins.songs.lib.songselect.build_opener') | ||
144 | 241 | @patch('openlp.plugins.songs.lib.songselect.BeautifulSoup') | ||
145 | 173 | def search_returns_two_results_test(self, MockedBeautifulSoup, mocked_build_opener): | 242 | def search_returns_two_results_test(self, MockedBeautifulSoup, mocked_build_opener): |
146 | 174 | """ | 243 | """ |
147 | 175 | Test that when the search finds 2 results, it simply returns a list with 2 results | 244 | Test that when the search finds 2 results, it simply returns a list with 2 results |
148 | @@ -322,22 +391,47 @@ | |||
149 | 322 | Test that the get_song() method returns the correct song details | 391 | Test that the get_song() method returns the correct song details |
150 | 323 | """ | 392 | """ |
151 | 324 | # GIVEN: A bunch of mocked out stuff and an importer object | 393 | # GIVEN: A bunch of mocked out stuff and an importer object |
152 | 394 | mocked_ul_copyright = MagicMock() | ||
153 | 395 | mocked_ul_copyright.find.side_effect = [True, False] | ||
154 | 396 | mocked_ul_copyright.find_all.return_value = [ | ||
155 | 397 | 'Copyright:', | ||
156 | 398 | MagicMock(string='Copyright 1'), | ||
157 | 399 | MagicMock(string='Copyright 2') | ||
158 | 400 | ] | ||
159 | 401 | |||
160 | 402 | mocked_ul_themes = MagicMock() | ||
161 | 403 | mocked_ul_themes.find.side_effect = [False, True] | ||
162 | 404 | mocked_ul_themes.find_all.return_value = [ | ||
163 | 405 | 'Themes:', | ||
164 | 406 | MagicMock(string='Theme 1'), | ||
165 | 407 | MagicMock(string='Theme 2') | ||
166 | 408 | ] | ||
167 | 409 | |||
168 | 410 | mocked_ccli = MagicMock(string='CCLI: 123456 ') | ||
169 | 411 | mocked_find_strong = MagicMock(return_value=mocked_ccli) | ||
170 | 412 | mocked_find_li = MagicMock(return_value=mocked_find_strong) | ||
171 | 413 | mocked_find_ul = MagicMock(return_value=mocked_find_li) | ||
172 | 414 | |||
173 | 325 | mocked_song_page = MagicMock() | 415 | mocked_song_page = MagicMock() |
179 | 326 | mocked_copyright = MagicMock() | 416 | mocked_song_page.find_all.return_value = [ |
180 | 327 | mocked_copyright.find_all.return_value = [MagicMock(string='Copyright 1'), MagicMock(string='Copyright 2')] | 417 | mocked_ul_copyright, |
181 | 328 | mocked_song_page.find.side_effect = [ | 418 | mocked_ul_themes |
177 | 329 | mocked_copyright, | ||
178 | 330 | MagicMock(find=MagicMock(string='CCLI: 123456')) | ||
182 | 331 | ] | 419 | ] |
183 | 420 | mocked_song_page.find.return_value = mocked_find_ul | ||
184 | 421 | |||
185 | 332 | mocked_lyrics_page = MagicMock() | 422 | mocked_lyrics_page = MagicMock() |
186 | 333 | mocked_find_all = MagicMock() | 423 | mocked_find_all = MagicMock() |
187 | 334 | mocked_find_all.side_effect = [ | 424 | mocked_find_all.side_effect = [ |
188 | 335 | [ | 425 | [ |
189 | 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'), |
191 | 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')), |
192 | 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') |
193 | 339 | ], | 429 | ], |
195 | 340 | [MagicMock(string='Verse 1'), MagicMock(string='Chorus'), MagicMock(string='Verse 2')] | 430 | [ |
196 | 431 | MagicMock(string='Verse 1'), | ||
197 | 432 | MagicMock(string='Chorus'), | ||
198 | 433 | MagicMock(string='Verse 2') | ||
199 | 434 | ] | ||
200 | 341 | ] | 435 | ] |
201 | 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) |
202 | 343 | MockedBeautifulSoup.side_effect = [mocked_song_page, mocked_lyrics_page] | 437 | MockedBeautifulSoup.side_effect = [mocked_song_page, mocked_lyrics_page] |
203 | @@ -349,8 +443,13 @@ | |||
204 | 349 | result = importer.get_song(fake_song, callback=mocked_callback) | 443 | result = importer.get_song(fake_song, callback=mocked_callback) |
205 | 350 | 444 | ||
206 | 351 | # THEN: The callback should have been called three times and the song should be returned | 445 | # THEN: The callback should have been called three times and the song should be returned |
207 | 446 | mocked_song_page.find_all.assert_called_once_with('ul', 'song-meta-list') | ||
208 | 447 | self.assertEqual(2, mocked_ul_copyright.find.call_count) | ||
209 | 448 | self.assertEqual(1, mocked_ul_copyright.find_all.call_count) | ||
210 | 449 | self.assertEqual(2, mocked_ul_themes.find.call_count) | ||
211 | 450 | self.assertEqual(1, mocked_ul_themes.find_all.call_count) | ||
212 | 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') |
214 | 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') |
215 | 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') |
216 | 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') |
217 | 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')], |
218 | @@ -359,7 +458,9 @@ | |||
219 | 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, |
220 | 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') |
221 | 361 | self.assertIn('copyright', result, 'The returned song should have a copyright') | 460 | self.assertIn('copyright', result, 'The returned song should have a copyright') |
222 | 461 | self.assertEqual('Copyright 1/Copyright 2', result['copyright']) | ||
223 | 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') |
224 | 463 | # self.assertEqual('123456', result['ccli_number'], result['ccli_number']) | ||
225 | 363 | self.assertIn('verses', result, 'The returned song should have verses') | 464 | self.assertIn('verses', result, 'The returned song should have verses') |
226 | 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') |
227 | 365 | 466 | ||
228 | @@ -375,7 +476,7 @@ | |||
229 | 375 | 'authors': ['Public Domain'], | 476 | 'authors': ['Public Domain'], |
230 | 376 | 'verses': [ | 477 | 'verses': [ |
231 | 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'}, |
233 | 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'}, |
234 | 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'} |
235 | 380 | ], | 481 | ], |
236 | 381 | 'copyright': 'Public Domain', | 482 | 'copyright': 'Public Domain', |
237 | @@ -435,9 +536,8 @@ | |||
238 | 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') |
239 | 436 | 537 | ||
240 | 437 | @patch('openlp.plugins.songs.lib.songselect.clean_song') | 538 | @patch('openlp.plugins.songs.lib.songselect.clean_song') |
241 | 438 | @patch('openlp.plugins.songs.lib.songselect.Topic') | ||
242 | 439 | @patch('openlp.plugins.songs.lib.songselect.Author') | 539 | @patch('openlp.plugins.songs.lib.songselect.Author') |
244 | 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): |
245 | 441 | """ | 541 | """ |
246 | 442 | Test that saving a song with an author name of only one word performs the correct actions | 542 | Test that saving a song with an author name of only one word performs the correct actions |
247 | 443 | """ | 543 | """ |
248 | @@ -454,7 +554,6 @@ | |||
249 | 454 | 'ccli_number': '123456' | 554 | 'ccli_number': '123456' |
250 | 455 | } | 555 | } |
251 | 456 | MockedAuthor.display_name.__eq__.return_value = False | 556 | MockedAuthor.display_name.__eq__.return_value = False |
252 | 457 | MockedTopic.name.__eq__.return_value = False | ||
253 | 458 | mocked_db_manager = MagicMock() | 557 | mocked_db_manager = MagicMock() |
254 | 459 | mocked_db_manager.get_object_filtered.return_value = None | 558 | mocked_db_manager.get_object_filtered.return_value = None |
255 | 460 | importer = SongSelectImport(mocked_db_manager) | 559 | importer = SongSelectImport(mocked_db_manager) |
256 | @@ -471,6 +570,45 @@ | |||
257 | 471 | MockedAuthor.populate.assert_called_with(first_name='Unknown', last_name='', | 570 | MockedAuthor.populate.assert_called_with(first_name='Unknown', last_name='', |
258 | 472 | display_name='Unknown') | 571 | display_name='Unknown') |
259 | 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') |
260 | 573 | # self.assertEqual(2, len(result.topics), 'There should only be two topics') | ||
261 | 574 | |||
262 | 575 | @patch('openlp.plugins.songs.lib.songselect.clean_song') | ||
263 | 576 | @patch('openlp.plugins.songs.lib.songselect.Topic') | ||
264 | 577 | @patch('openlp.plugins.songs.lib.songselect.Author') | ||
265 | 578 | def save_song_topics_test(self, MockedAuthor, MockedTopic, mocked_clean_song): | ||
266 | 579 | """ | ||
267 | 580 | Test that saving a song with topics performs the correct actions | ||
268 | 581 | """ | ||
269 | 582 | # GIVEN: A song to save, and some mocked out objects | ||
270 | 583 | song_dict = { | ||
271 | 584 | 'title': 'Arky Arky', | ||
272 | 585 | 'authors': ['Public Domain'], | ||
273 | 586 | 'verses': [ | ||
274 | 587 | {'label': 'Verse 1', 'lyrics': 'The Lord told Noah: there\'s gonna be a floody, floody'}, | ||
275 | 588 | {'label': 'Chorus', 'lyrics': 'So, rise and shine, and give God the glory, glory'}, | ||
276 | 589 | {'label': 'Verse 2', 'lyrics': 'The Lord told Noah to build him an arky, arky'} | ||
277 | 590 | ], | ||
278 | 591 | 'copyright': 'Public Domain', | ||
279 | 592 | 'ccli_number': '123456', | ||
280 | 593 | 'topics': ['Grace', 'Love'] | ||
281 | 594 | } | ||
282 | 595 | MockedAuthor.display_name.__eq__.return_value = False | ||
283 | 596 | MockedTopic.name.__eq__.return_value = False | ||
284 | 597 | mocked_db_manager = MagicMock() | ||
285 | 598 | mocked_db_manager.get_object_filtered.return_value = None | ||
286 | 599 | importer = SongSelectImport(mocked_db_manager) | ||
287 | 600 | |||
288 | 601 | # WHEN: The song is saved to the database | ||
289 | 602 | result = importer.save_song(song_dict) | ||
290 | 603 | |||
291 | 604 | # THEN: The return value should be a Song class and the mocked_db_manager should have been called | ||
292 | 605 | self.assertIsInstance(result, Song, 'The returned value should be a Song object') | ||
293 | 606 | mocked_clean_song.assert_called_with(mocked_db_manager, result) | ||
294 | 607 | self.assertEqual(2, mocked_db_manager.save_object.call_count, | ||
295 | 608 | 'The save_object() method should have been called twice') | ||
296 | 609 | mocked_db_manager.get_object_filtered.assert_called_with(MockedTopic, False) | ||
297 | 610 | self.assertEqual([call(name='Grace'), call(name='Love')], MockedTopic.populate.call_args_list) | ||
298 | 611 | self.assertEqual(2, len(result.topics), 'There should be two topics') | ||
299 | 474 | 612 | ||
300 | 475 | 613 | ||
301 | 476 | class TestSongSelectForm(TestCase, TestMixin): | 614 | class TestSongSelectForm(TestCase, TestMixin): |