Merge lp:~raoul-snyman/openlp/bug-1608194-2.4 into lp:openlp/2.4
- bug-1608194-2.4
- Merge into 2.4
Proposed by
Raoul Snyman
Status: | Merged |
---|---|
Merged at revision: | 2648 |
Proposed branch: | lp:~raoul-snyman/openlp/bug-1608194-2.4 |
Merge into: | lp:openlp/2.4 |
Diff against target: |
394 lines (+115/-62) 4 files modified
openlp/plugins/songs/lib/__init__.py (+2/-3) openlp/plugins/songs/lib/db.py (+2/-2) openlp/plugins/songs/lib/songselect.py (+72/-36) tests/functional/openlp_plugins/songs/test_songselect.py (+39/-21) |
To merge this branch: | bzr merge lp:~raoul-snyman/openlp/bug-1608194-2.4 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Tim Bentley | Approve | ||
Review via email: mp+302797@code.launchpad.net |
This proposal supersedes a proposal from 2016-08-11.
Commit message
Description of the change
Fix SongSelect.
Do updated tests count?
Add this to your merge proposal:
-------
lp:~raoul-snyman/openlp/bug-1608194-2.4 (revision 2650)
[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
Tim Bentley (trb143) wrote : Posted in a previous version of this proposal | # |
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/__init__.py' | |||
2 | --- openlp/plugins/songs/lib/__init__.py 2016-04-21 19:49:22 +0000 | |||
3 | +++ openlp/plugins/songs/lib/__init__.py 2016-08-12 12:20:33 +0000 | |||
4 | @@ -32,9 +32,8 @@ | |||
5 | 32 | from openlp.core.common import AppLocation | 32 | from openlp.core.common import AppLocation |
6 | 33 | from openlp.core.lib import translate | 33 | from openlp.core.lib import translate |
7 | 34 | from openlp.core.utils import CONTROL_CHARS | 34 | from openlp.core.utils import CONTROL_CHARS |
11 | 35 | from openlp.plugins.songs.lib.db import MediaFile, Song | 35 | from openlp.plugins.songs.lib.db import Author, MediaFile, Song, Topic |
12 | 36 | from .db import Author | 36 | from openlp.plugins.songs.lib.ui import SongStrings |
10 | 37 | from .ui import SongStrings | ||
13 | 38 | 37 | ||
14 | 39 | log = logging.getLogger(__name__) | 38 | log = logging.getLogger(__name__) |
15 | 40 | 39 | ||
16 | 41 | 40 | ||
17 | === modified file 'openlp/plugins/songs/lib/db.py' | |||
18 | --- openlp/plugins/songs/lib/db.py 2016-04-27 18:45:39 +0000 | |||
19 | +++ openlp/plugins/songs/lib/db.py 2016-08-12 12:20:33 +0000 | |||
20 | @@ -135,7 +135,7 @@ | |||
21 | 135 | 135 | ||
22 | 136 | def add_author(self, author, author_type=None): | 136 | def add_author(self, author, author_type=None): |
23 | 137 | """ | 137 | """ |
25 | 138 | Add an author to the song if it not yet exists | 138 | Add an author to the song if it doesn't exist yet |
26 | 139 | 139 | ||
27 | 140 | :param author: Author object | 140 | :param author: Author object |
28 | 141 | :param author_type: AuthorType constant or None | 141 | :param author_type: AuthorType constant or None |
29 | @@ -162,7 +162,7 @@ | |||
30 | 162 | 162 | ||
31 | 163 | def add_songbook_entry(self, songbook, entry): | 163 | def add_songbook_entry(self, songbook, entry): |
32 | 164 | """ | 164 | """ |
34 | 165 | Add a Songbook Entry to the song if it not yet exists | 165 | Add a Songbook Entry to the song if it doesn't exist yet |
35 | 166 | 166 | ||
36 | 167 | :param songbook_name: Name of the Songbook. | 167 | :param songbook_name: Name of the Songbook. |
37 | 168 | :param entry: Entry in the Songbook (usually a number) | 168 | :param entry: Entry in the Songbook (usually a number) |
38 | 169 | 169 | ||
39 | === modified file 'openlp/plugins/songs/lib/songselect.py' | |||
40 | --- openlp/plugins/songs/lib/songselect.py 2016-01-09 18:01:49 +0000 | |||
41 | +++ openlp/plugins/songs/lib/songselect.py 2016-08-12 12:20:33 +0000 | |||
42 | @@ -24,6 +24,8 @@ | |||
43 | 24 | """ | 24 | """ |
44 | 25 | import logging | 25 | import logging |
45 | 26 | import sys | 26 | import sys |
46 | 27 | import random | ||
47 | 28 | import re | ||
48 | 27 | from http.cookiejar import CookieJar | 29 | from http.cookiejar import CookieJar |
49 | 28 | from urllib.parse import urlencode | 30 | from urllib.parse import urlencode |
50 | 29 | from urllib.request import HTTPCookieProcessor, URLError, build_opener | 31 | from urllib.request import HTTPCookieProcessor, URLError, build_opener |
51 | @@ -32,14 +34,21 @@ | |||
52 | 32 | 34 | ||
53 | 33 | from bs4 import BeautifulSoup, NavigableString | 35 | from bs4 import BeautifulSoup, NavigableString |
54 | 34 | 36 | ||
56 | 35 | from openlp.plugins.songs.lib import Song, VerseType, clean_song, Author | 37 | from openlp.plugins.songs.lib import Song, Author, Topic, VerseType, clean_song |
57 | 36 | from openlp.plugins.songs.lib.openlyricsxml import SongXML | 38 | from openlp.plugins.songs.lib.openlyricsxml import SongXML |
58 | 37 | 39 | ||
64 | 38 | USER_AGENT = 'Mozilla/5.0 (Linux; U; Android 4.0.3; en-us; GT-I9000 ' \ | 40 | USER_AGENTS = [ |
65 | 39 | 'Build/IML74K) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 ' \ | 41 | 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) ' |
66 | 40 | 'Mobile Safari/534.30' | 42 | 'Chrome/52.0.2743.116 Safari/537.36', |
67 | 41 | BASE_URL = 'https://mobile.songselect.com' | 43 | 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36', |
68 | 42 | LOGIN_URL = BASE_URL + '/account/login' | 44 | 'Mozilla/5.0 (X11; Linux x86_64; rv:47.0) Gecko/20100101 Firefox/47.0', |
69 | 45 | 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:46.0) Gecko/20100101 Firefox/46.0', | ||
70 | 46 | 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:47.0) Gecko/20100101 Firefox/47.0' | ||
71 | 47 | ] | ||
72 | 48 | BASE_URL = 'https://songselect.ccli.com' | ||
73 | 49 | LOGIN_PAGE = 'https://profile.ccli.com/account/signin?appContext=SongSelect&returnUrl=' \ | ||
74 | 50 | 'https%3a%2f%2fsongselect.ccli.com%2f' | ||
75 | 51 | LOGIN_URL = 'https://profile.ccli.com/' | ||
76 | 43 | LOGOUT_URL = BASE_URL + '/account/logout' | 52 | LOGOUT_URL = BASE_URL + '/account/logout' |
77 | 44 | SEARCH_URL = BASE_URL + '/search/results' | 53 | SEARCH_URL = BASE_URL + '/search/results' |
78 | 45 | 54 | ||
79 | @@ -60,7 +69,7 @@ | |||
80 | 60 | self.db_manager = db_manager | 69 | self.db_manager = db_manager |
81 | 61 | self.html_parser = HTMLParser() | 70 | self.html_parser = HTMLParser() |
82 | 62 | self.opener = build_opener(HTTPCookieProcessor(CookieJar())) | 71 | self.opener = build_opener(HTTPCookieProcessor(CookieJar())) |
84 | 63 | self.opener.addheaders = [('User-Agent', USER_AGENT)] | 72 | self.opener.addheaders = [('User-Agent', random.choice(USER_AGENTS))] |
85 | 64 | self.run_search = True | 73 | self.run_search = True |
86 | 65 | 74 | ||
87 | 66 | def login(self, username, password, callback=None): | 75 | def login(self, username, password, callback=None): |
88 | @@ -76,27 +85,27 @@ | |||
89 | 76 | if callback: | 85 | if callback: |
90 | 77 | callback() | 86 | callback() |
91 | 78 | try: | 87 | try: |
95 | 79 | login_page = BeautifulSoup(self.opener.open(LOGIN_URL).read(), 'lxml') | 88 | login_page = BeautifulSoup(self.opener.open(LOGIN_PAGE).read(), 'lxml') |
96 | 80 | except (TypeError, URLError) as e: | 89 | except (TypeError, URLError) as error: |
97 | 81 | log.exception('Could not login to SongSelect, %s', e) | 90 | log.exception('Could not login to SongSelect, %s', error) |
98 | 82 | return False | 91 | return False |
99 | 83 | if callback: | 92 | if callback: |
100 | 84 | callback() | 93 | callback() |
101 | 85 | token_input = login_page.find('input', attrs={'name': '__RequestVerificationToken'}) | 94 | token_input = login_page.find('input', attrs={'name': '__RequestVerificationToken'}) |
102 | 86 | data = urlencode({ | 95 | data = urlencode({ |
103 | 87 | '__RequestVerificationToken': token_input['value'], | 96 | '__RequestVerificationToken': token_input['value'], |
106 | 88 | 'UserName': username, | 97 | 'emailAddress': username, |
107 | 89 | 'Password': password, | 98 | 'password': password, |
108 | 90 | 'RememberMe': 'false' | 99 | 'RememberMe': 'false' |
109 | 91 | }) | 100 | }) |
110 | 92 | try: | 101 | try: |
111 | 93 | posted_page = BeautifulSoup(self.opener.open(LOGIN_URL, data.encode('utf-8')).read(), 'lxml') | 102 | posted_page = BeautifulSoup(self.opener.open(LOGIN_URL, data.encode('utf-8')).read(), 'lxml') |
114 | 94 | except (TypeError, URLError) as e: | 103 | except (TypeError, URLError) as error: |
115 | 95 | log.exception('Could not login to SongSelect, %s', e) | 104 | log.exception('Could not login to SongSelect, %s', error) |
116 | 96 | return False | 105 | return False |
117 | 97 | if callback: | 106 | if callback: |
118 | 98 | callback() | 107 | callback() |
120 | 99 | return not posted_page.find('input', attrs={'name': '__RequestVerificationToken'}) | 108 | return posted_page.find('input', id='SearchText') is not None |
121 | 100 | 109 | ||
122 | 101 | def logout(self): | 110 | def logout(self): |
123 | 102 | """ | 111 | """ |
124 | @@ -104,8 +113,8 @@ | |||
125 | 104 | """ | 113 | """ |
126 | 105 | try: | 114 | try: |
127 | 106 | self.opener.open(LOGOUT_URL) | 115 | self.opener.open(LOGOUT_URL) |
130 | 107 | except (TypeError, URLError) as e: | 116 | except (TypeError, URLError) as error: |
131 | 108 | log.exception('Could not log of SongSelect, %s', e) | 117 | log.exception('Could not log of SongSelect, %s', error) |
132 | 109 | 118 | ||
133 | 110 | def search(self, search_text, max_results, callback=None): | 119 | def search(self, search_text, max_results, callback=None): |
134 | 111 | """ | 120 | """ |
135 | @@ -117,7 +126,15 @@ | |||
136 | 117 | :return: List of songs | 126 | :return: List of songs |
137 | 118 | """ | 127 | """ |
138 | 119 | self.run_search = True | 128 | self.run_search = True |
140 | 120 | params = {'allowredirect': 'false', 'SearchTerm': search_text} | 129 | params = { |
141 | 130 | 'SongContent': '', | ||
142 | 131 | 'PrimaryLanguage': '', | ||
143 | 132 | 'Keys': '', | ||
144 | 133 | 'Themes': '', | ||
145 | 134 | 'List': '', | ||
146 | 135 | 'Sort': '', | ||
147 | 136 | 'SearchText': search_text | ||
148 | 137 | } | ||
149 | 121 | current_page = 1 | 138 | current_page = 1 |
150 | 122 | songs = [] | 139 | songs = [] |
151 | 123 | while self.run_search: | 140 | while self.run_search: |
152 | @@ -125,7 +142,7 @@ | |||
153 | 125 | params['page'] = current_page | 142 | params['page'] = current_page |
154 | 126 | try: | 143 | try: |
155 | 127 | results_page = BeautifulSoup(self.opener.open(SEARCH_URL + '?' + urlencode(params)).read(), 'lxml') | 144 | results_page = BeautifulSoup(self.opener.open(SEARCH_URL + '?' + urlencode(params)).read(), 'lxml') |
157 | 128 | search_results = results_page.find_all('li', 'result pane') | 145 | search_results = results_page.find_all('div', 'song-result') |
158 | 129 | except (TypeError, URLError) as e: | 146 | except (TypeError, URLError) as e: |
159 | 130 | log.exception('Could not search SongSelect, %s', e) | 147 | log.exception('Could not search SongSelect, %s', e) |
160 | 131 | search_results = None | 148 | search_results = None |
161 | @@ -133,9 +150,9 @@ | |||
162 | 133 | break | 150 | break |
163 | 134 | for result in search_results: | 151 | for result in search_results: |
164 | 135 | song = { | 152 | song = { |
168 | 136 | 'title': unescape(result.find('h3').string), | 153 | 'title': unescape(result.find('p', 'song-result-title').find('a').string).strip(), |
169 | 137 | 'authors': [unescape(author.string) for author in result.find_all('li')], | 154 | 'authors': unescape(result.find('p', 'song-result-subtitle').string).strip().split(', '), |
170 | 138 | 'link': BASE_URL + result.find('a')['href'] | 155 | 'link': BASE_URL + result.find('p', 'song-result-title').find('a')['href'] |
171 | 139 | } | 156 | } |
172 | 140 | if callback: | 157 | if callback: |
173 | 141 | callback(song) | 158 | callback(song) |
174 | @@ -163,27 +180,37 @@ | |||
175 | 163 | if callback: | 180 | if callback: |
176 | 164 | callback() | 181 | callback() |
177 | 165 | try: | 182 | try: |
179 | 166 | lyrics_page = BeautifulSoup(self.opener.open(song['link'] + '/lyrics').read(), 'lxml') | 183 | lyrics_page = BeautifulSoup(self.opener.open(song['link'] + '/viewlyrics').read(), 'lxml') |
180 | 167 | except (TypeError, URLError): | 184 | except (TypeError, URLError): |
181 | 168 | log.exception('Could not get lyrics from SongSelect') | 185 | log.exception('Could not get lyrics from SongSelect') |
182 | 169 | return None | 186 | return None |
183 | 170 | if callback: | 187 | if callback: |
184 | 171 | callback() | 188 | callback() |
188 | 172 | song['copyright'] = '/'.join([li.string for li in song_page.find('ul', 'copyright').find_all('li')]) | 189 | copyright_elements = [] |
189 | 173 | song['copyright'] = unescape(song['copyright']) | 190 | theme_elements = [] |
190 | 174 | song['ccli_number'] = song_page.find('ul', 'info').find('li').string.split(':')[1].strip() | 191 | copyrights_regex = re.compile(r'\bCopyrights\b') |
191 | 192 | themes_regex = re.compile(r'\bThemes\b') | ||
192 | 193 | for ul in song_page.find_all('ul', 'song-meta-list'): | ||
193 | 194 | if ul.find('li', string=copyrights_regex): | ||
194 | 195 | copyright_elements.extend(ul.find_all('li')[1:]) | ||
195 | 196 | if ul.find('li', string=themes_regex): | ||
196 | 197 | theme_elements.extend(ul.find_all('li')[1:]) | ||
197 | 198 | song['copyright'] = '/'.join([unescape(li.string).strip() for li in copyright_elements]) | ||
198 | 199 | song['topics'] = [unescape(li.string).strip() for li in theme_elements] | ||
199 | 200 | song['ccli_number'] = song_page.find('div', 'song-content-data').find('ul').find('li')\ | ||
200 | 201 | .find('strong').string.strip() | ||
201 | 175 | song['verses'] = [] | 202 | song['verses'] = [] |
207 | 176 | verses = lyrics_page.find('section', 'lyrics').find_all('p') | 203 | verses = lyrics_page.find('div', 'song-viewer lyrics').find_all('p') |
208 | 177 | verse_labels = lyrics_page.find('section', 'lyrics').find_all('h3') | 204 | verse_labels = lyrics_page.find('div', 'song-viewer lyrics').find_all('h3') |
209 | 178 | for counter in range(len(verses)): | 205 | for verse, label in zip(verses, verse_labels): |
210 | 179 | verse = {'label': verse_labels[counter].string, 'lyrics': ''} | 206 | song_verse = {'label': unescape(label.string).strip(), 'lyrics': ''} |
211 | 180 | for v in verses[counter].contents: | 207 | for v in verse.contents: |
212 | 181 | if isinstance(v, NavigableString): | 208 | if isinstance(v, NavigableString): |
214 | 182 | verse['lyrics'] = verse['lyrics'] + v.string | 209 | song_verse['lyrics'] += unescape(v.string).strip() |
215 | 183 | else: | 210 | else: |
219 | 184 | verse['lyrics'] += '\n' | 211 | song_verse['lyrics'] += '\n' |
220 | 185 | verse['lyrics'] = verse['lyrics'].strip(' \n\r\t') | 212 | song_verse['lyrics'] = song_verse['lyrics'].strip() |
221 | 186 | song['verses'].append(unescape(verse)) | 213 | song['verses'].append(song_verse) |
222 | 187 | for counter, author in enumerate(song['authors']): | 214 | for counter, author in enumerate(song['authors']): |
223 | 188 | song['authors'][counter] = unescape(author) | 215 | song['authors'][counter] = unescape(author) |
224 | 189 | return song | 216 | return song |
225 | @@ -199,7 +226,11 @@ | |||
226 | 199 | song_xml = SongXML() | 226 | song_xml = SongXML() |
227 | 200 | verse_order = [] | 227 | verse_order = [] |
228 | 201 | for verse in song['verses']: | 228 | for verse in song['verses']: |
230 | 202 | verse_type, verse_number = verse['label'].split(' ')[:2] | 229 | if ' ' in verse['label']: |
231 | 230 | verse_type, verse_number = verse['label'].split(' ', 1) | ||
232 | 231 | else: | ||
233 | 232 | verse_type = verse['label'] | ||
234 | 233 | verse_number = 1 | ||
235 | 203 | verse_type = VerseType.from_loose_input(verse_type) | 234 | verse_type = VerseType.from_loose_input(verse_type) |
236 | 204 | verse_number = int(verse_number) | 235 | verse_number = int(verse_number) |
237 | 205 | song_xml.add_verse_to_lyrics(VerseType.tags[verse_type], verse_number, verse['lyrics']) | 236 | song_xml.add_verse_to_lyrics(VerseType.tags[verse_type], verse_number, verse['lyrics']) |
238 | @@ -220,6 +251,11 @@ | |||
239 | 220 | last_name = name_parts[1] | 251 | last_name = name_parts[1] |
240 | 221 | author = Author.populate(first_name=first_name, last_name=last_name, display_name=author_name) | 252 | author = Author.populate(first_name=first_name, last_name=last_name, display_name=author_name) |
241 | 222 | db_song.add_author(author) | 253 | db_song.add_author(author) |
242 | 254 | for topic_name in song.get('topics', []): | ||
243 | 255 | topic = self.db_manager.get_object_filtered(Topic, Topic.name == topic_name) | ||
244 | 256 | if not topic: | ||
245 | 257 | topic = Topic.populate(name=topic_name) | ||
246 | 258 | db_song.add_topic(topic) | ||
247 | 223 | self.db_manager.save_object(db_song) | 259 | self.db_manager.save_object(db_song) |
248 | 224 | return db_song | 260 | return db_song |
249 | 225 | 261 | ||
250 | 226 | 262 | ||
251 | === modified file 'tests/functional/openlp_plugins/songs/test_songselect.py' | |||
252 | --- tests/functional/openlp_plugins/songs/test_songselect.py 2016-01-09 18:01:49 +0000 | |||
253 | +++ tests/functional/openlp_plugins/songs/test_songselect.py 2016-08-12 12:20:33 +0000 | |||
254 | @@ -71,7 +71,7 @@ | |||
255 | 71 | mocked_opener = MagicMock() | 71 | mocked_opener = MagicMock() |
256 | 72 | mocked_build_opener.return_value = mocked_opener | 72 | mocked_build_opener.return_value = mocked_opener |
257 | 73 | mocked_login_page = MagicMock() | 73 | mocked_login_page = MagicMock() |
259 | 74 | mocked_login_page.find.return_value = {'value': 'blah'} | 74 | mocked_login_page.find.side_effect = [{'value': 'blah'}, None] |
260 | 75 | MockedBeautifulSoup.return_value = mocked_login_page | 75 | MockedBeautifulSoup.return_value = mocked_login_page |
261 | 76 | mock_callback = MagicMock() | 76 | mock_callback = MagicMock() |
262 | 77 | importer = SongSelectImport(None) | 77 | importer = SongSelectImport(None) |
263 | @@ -112,7 +112,7 @@ | |||
264 | 112 | mocked_opener = MagicMock() | 112 | mocked_opener = MagicMock() |
265 | 113 | mocked_build_opener.return_value = mocked_opener | 113 | mocked_build_opener.return_value = mocked_opener |
266 | 114 | mocked_login_page = MagicMock() | 114 | mocked_login_page = MagicMock() |
268 | 115 | mocked_login_page.find.side_effect = [{'value': 'blah'}, None] | 115 | mocked_login_page.find.side_effect = [{'value': 'blah'}, MagicMock()] |
269 | 116 | MockedBeautifulSoup.return_value = mocked_login_page | 116 | MockedBeautifulSoup.return_value = mocked_login_page |
270 | 117 | mock_callback = MagicMock() | 117 | mock_callback = MagicMock() |
271 | 118 | importer = SongSelectImport(None) | 118 | importer = SongSelectImport(None) |
272 | @@ -165,7 +165,7 @@ | |||
273 | 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') |
274 | 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') |
275 | 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') |
277 | 168 | mocked_results_page.find_all.assert_called_with('li', 'result pane') | 168 | mocked_results_page.find_all.assert_called_with('div', 'song-result') |
278 | 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') |
279 | 170 | 170 | ||
280 | 171 | @patch('openlp.plugins.songs.lib.songselect.build_opener') | 171 | @patch('openlp.plugins.songs.lib.songselect.build_opener') |
281 | @@ -177,12 +177,18 @@ | |||
282 | 177 | # GIVEN: A bunch of mocked out stuff and an importer object | 177 | # GIVEN: A bunch of mocked out stuff and an importer object |
283 | 178 | # first search result | 178 | # first search result |
284 | 179 | mocked_result1 = MagicMock() | 179 | mocked_result1 = MagicMock() |
287 | 180 | mocked_result1.find.side_effect = [MagicMock(string='Title 1'), {'href': '/url1'}] | 180 | mocked_result1.find.side_effect = [ |
288 | 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'))), |
289 | 182 | MagicMock(string='James, John'), | ||
290 | 183 | MagicMock(find=MagicMock(return_value={'href': '/url1'})) | ||
291 | 184 | ] | ||
292 | 182 | # second search result | 185 | # second search result |
293 | 183 | mocked_result2 = MagicMock() | 186 | mocked_result2 = MagicMock() |
296 | 184 | mocked_result2.find.side_effect = [MagicMock(string='Title 2'), {'href': '/url2'}] | 187 | mocked_result2.find.side_effect = [ |
297 | 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'))), |
298 | 189 | MagicMock(string='Philip'), | ||
299 | 190 | MagicMock(find=MagicMock(return_value={'href': '/url2'})) | ||
300 | 191 | ] | ||
301 | 186 | # rest of the stuff | 192 | # rest of the stuff |
302 | 187 | mocked_opener = MagicMock() | 193 | mocked_opener = MagicMock() |
303 | 188 | mocked_build_opener.return_value = mocked_opener | 194 | mocked_build_opener.return_value = mocked_opener |
304 | @@ -196,13 +202,14 @@ | |||
305 | 196 | results = importer.search('text', 1000, mock_callback) | 202 | results = importer.search('text', 1000, mock_callback) |
306 | 197 | 203 | ||
307 | 198 | # THEN: callback was never called, open was called once, find_all was called once, an empty list returned | 204 | # THEN: callback was never called, open was called once, find_all was called once, an empty list returned |
308 | 205 | self.maxDiff = None | ||
309 | 199 | self.assertEqual(2, mock_callback.call_count, 'callback should have been called twice') | 206 | self.assertEqual(2, mock_callback.call_count, 'callback should have been called twice') |
310 | 200 | self.assertEqual(2, mocked_opener.open.call_count, 'open should have been called twice') | 207 | self.assertEqual(2, mocked_opener.open.call_count, 'open should have been called twice') |
311 | 201 | self.assertEqual(2, mocked_results_page.find_all.call_count, 'find_all should have been called twice') | 208 | self.assertEqual(2, mocked_results_page.find_all.call_count, 'find_all should have been called twice') |
313 | 202 | mocked_results_page.find_all.assert_called_with('li', 'result pane') | 209 | mocked_results_page.find_all.assert_called_with('div', 'song-result') |
314 | 203 | expected_list = [ | 210 | expected_list = [ |
317 | 204 | {'title': 'Title 1', 'authors': ['Author 1-1', 'Author 1-2'], 'link': BASE_URL + '/url1'}, | 211 | {'title': 'Title 1', 'authors': ['James', 'John'], 'link': BASE_URL + '/url1'}, |
318 | 205 | {'title': 'Title 2', 'authors': ['Author 2-1', 'Author 2-2'], 'link': BASE_URL + '/url2'} | 212 | {'title': 'Title 2', 'authors': ['Philip'], 'link': BASE_URL + '/url2'} |
319 | 206 | ] | 213 | ] |
320 | 207 | self.assertListEqual(expected_list, results, 'The search method should have returned two songs') | 214 | self.assertListEqual(expected_list, results, 'The search method should have returned two songs') |
321 | 208 | 215 | ||
322 | @@ -215,16 +222,25 @@ | |||
323 | 215 | # GIVEN: A bunch of mocked out stuff and an importer object | 222 | # GIVEN: A bunch of mocked out stuff and an importer object |
324 | 216 | # first search result | 223 | # first search result |
325 | 217 | mocked_result1 = MagicMock() | 224 | mocked_result1 = MagicMock() |
328 | 218 | mocked_result1.find.side_effect = [MagicMock(string='Title 1'), {'href': '/url1'}] | 225 | mocked_result1.find.side_effect = [ |
329 | 219 | mocked_result1.find_all.return_value = [MagicMock(string='Author 1-1'), MagicMock(string='Author 1-2')] | 226 | MagicMock(find=MagicMock(return_value=MagicMock(string='Title 1'))), |
330 | 227 | MagicMock(string='James, John'), | ||
331 | 228 | MagicMock(find=MagicMock(return_value={'href': '/url1'})) | ||
332 | 229 | ] | ||
333 | 220 | # second search result | 230 | # second search result |
334 | 221 | mocked_result2 = MagicMock() | 231 | mocked_result2 = MagicMock() |
337 | 222 | mocked_result2.find.side_effect = [MagicMock(string='Title 2'), {'href': '/url2'}] | 232 | mocked_result2.find.side_effect = [ |
338 | 223 | mocked_result2.find_all.return_value = [MagicMock(string='Author 2-1'), MagicMock(string='Author 2-2')] | 233 | MagicMock(find=MagicMock(return_value=MagicMock(string='Title 2'))), |
339 | 234 | MagicMock(string='Philip'), | ||
340 | 235 | MagicMock(find=MagicMock(return_value={'href': '/url2'})) | ||
341 | 236 | ] | ||
342 | 224 | # third search result | 237 | # third search result |
343 | 225 | mocked_result3 = MagicMock() | 238 | mocked_result3 = MagicMock() |
346 | 226 | mocked_result3.find.side_effect = [MagicMock(string='Title 3'), {'href': '/url3'}] | 239 | mocked_result3.find.side_effect = [ |
347 | 227 | mocked_result3.find_all.return_value = [MagicMock(string='Author 3-1'), MagicMock(string='Author 3-2')] | 240 | MagicMock(find=MagicMock(return_value=MagicMock(string='Title 3'))), |
348 | 241 | MagicMock(string='Luke, Matthew'), | ||
349 | 242 | MagicMock(find=MagicMock(return_value={'href': '/url3'})) | ||
350 | 243 | ] | ||
351 | 228 | # rest of the stuff | 244 | # rest of the stuff |
352 | 229 | mocked_opener = MagicMock() | 245 | mocked_opener = MagicMock() |
353 | 230 | mocked_build_opener.return_value = mocked_opener | 246 | mocked_build_opener.return_value = mocked_opener |
354 | @@ -241,9 +257,9 @@ | |||
355 | 241 | self.assertEqual(2, mock_callback.call_count, 'callback should have been called twice') | 257 | self.assertEqual(2, mock_callback.call_count, 'callback should have been called twice') |
356 | 242 | self.assertEqual(2, mocked_opener.open.call_count, 'open should have been called twice') | 258 | self.assertEqual(2, mocked_opener.open.call_count, 'open should have been called twice') |
357 | 243 | self.assertEqual(2, mocked_results_page.find_all.call_count, 'find_all should have been called twice') | 259 | self.assertEqual(2, mocked_results_page.find_all.call_count, 'find_all should have been called twice') |
361 | 244 | mocked_results_page.find_all.assert_called_with('li', 'result pane') | 260 | mocked_results_page.find_all.assert_called_with('div', 'song-result') |
362 | 245 | expected_list = [{'title': 'Title 1', 'authors': ['Author 1-1', 'Author 1-2'], 'link': BASE_URL + '/url1'}, | 261 | expected_list = [{'title': 'Title 1', 'authors': ['James', 'John'], 'link': BASE_URL + '/url1'}, |
363 | 246 | {'title': 'Title 2', 'authors': ['Author 2-1', 'Author 2-2'], 'link': BASE_URL + '/url2'}] | 262 | {'title': 'Title 2', 'authors': ['Philip'], 'link': BASE_URL + '/url2'}] |
364 | 247 | self.assertListEqual(expected_list, results, 'The search method should have returned two songs') | 263 | self.assertListEqual(expected_list, results, 'The search method should have returned two songs') |
365 | 248 | 264 | ||
366 | 249 | @patch('openlp.plugins.songs.lib.songselect.build_opener') | 265 | @patch('openlp.plugins.songs.lib.songselect.build_opener') |
367 | @@ -337,7 +353,7 @@ | |||
368 | 337 | self.assertIsNotNone(result, 'The get_song() method should have returned a song dictionary') | 353 | self.assertIsNotNone(result, 'The get_song() method should have returned a song dictionary') |
369 | 338 | self.assertEqual(2, mocked_lyrics_page.find.call_count, 'The find() method should have been called twice') | 354 | self.assertEqual(2, mocked_lyrics_page.find.call_count, 'The find() method should have been called twice') |
370 | 339 | self.assertEqual(2, mocked_find_all.call_count, 'The find_all() method should have been called twice') | 355 | self.assertEqual(2, mocked_find_all.call_count, 'The find_all() method should have been called twice') |
372 | 340 | self.assertEqual([call('section', 'lyrics'), call('section', 'lyrics')], | 356 | self.assertEqual([call('div', 'song-viewer lyrics'), call('div', 'song-viewer lyrics')], |
373 | 341 | mocked_lyrics_page.find.call_args_list, | 357 | mocked_lyrics_page.find.call_args_list, |
374 | 342 | 'The find() method should have been called with the right arguments') | 358 | 'The find() method should have been called with the right arguments') |
375 | 343 | self.assertEqual([call('p'), call('h3')], mocked_find_all.call_args_list, | 359 | self.assertEqual([call('p'), call('h3')], mocked_find_all.call_args_list, |
376 | @@ -419,8 +435,9 @@ | |||
377 | 419 | self.assertEqual(1, len(result.authors_songs), 'There should only be one author') | 435 | self.assertEqual(1, len(result.authors_songs), 'There should only be one author') |
378 | 420 | 436 | ||
379 | 421 | @patch('openlp.plugins.songs.lib.songselect.clean_song') | 437 | @patch('openlp.plugins.songs.lib.songselect.clean_song') |
380 | 438 | @patch('openlp.plugins.songs.lib.songselect.Topic') | ||
381 | 422 | @patch('openlp.plugins.songs.lib.songselect.Author') | 439 | @patch('openlp.plugins.songs.lib.songselect.Author') |
383 | 423 | def save_song_unknown_author_test(self, MockedAuthor, mocked_clean_song): | 440 | def save_song_unknown_author_test(self, MockedAuthor, MockedTopic, mocked_clean_song): |
384 | 424 | """ | 441 | """ |
385 | 425 | Test that saving a song with an author name of only one word performs the correct actions | 442 | Test that saving a song with an author name of only one word performs the correct actions |
386 | 426 | """ | 443 | """ |
387 | @@ -437,6 +454,7 @@ | |||
388 | 437 | 'ccli_number': '123456' | 454 | 'ccli_number': '123456' |
389 | 438 | } | 455 | } |
390 | 439 | MockedAuthor.display_name.__eq__.return_value = False | 456 | MockedAuthor.display_name.__eq__.return_value = False |
391 | 457 | MockedTopic.name.__eq__.return_value = False | ||
392 | 440 | mocked_db_manager = MagicMock() | 458 | mocked_db_manager = MagicMock() |
393 | 441 | mocked_db_manager.get_object_filtered.return_value = None | 459 | mocked_db_manager.get_object_filtered.return_value = None |
394 | 442 | importer = SongSelectImport(mocked_db_manager) | 460 | importer = SongSelectImport(mocked_db_manager) |
Update tests count.
You wrote the rules, I am just imposing them!