Merge lp:~bob-luursema/openlp/bug-1800761 into lp:openlp

Proposed by Bob Luursema
Status: Merged
Approved by: Raoul Snyman
Approved revision: 2850
Merged at revision: 2847
Proposed branch: lp:~bob-luursema/openlp/bug-1800761
Merge into: lp:openlp
Diff against target: 188 lines (+48/-14)
4 files modified
.bzrignore (+1/-0)
openlp/plugins/songs/forms/songselectform.py (+11/-2)
openlp/plugins/songs/lib/songselect.py (+25/-5)
tests/functional/openlp_plugins/songs/test_songselect.py (+11/-7)
To merge this branch: bzr merge lp:~bob-luursema/openlp/bug-1800761
Reviewer Review Type Date Requested Status
Tomas Groth Approve
Raoul Snyman Approve
Review via email: mp+364001@code.launchpad.net

This proposal supersedes a proposal from 2019-03-05.

Commit message

Fix bug when using SongSelect import with a free account.

Description of the change

This change checks the subscription level of the login that the user has from a piece of JavaScript on the SongSelect page. If it is a free subscription it will display a messagebox informing the user that their searches are limited to songs in the public domain (as that is what a free user can access).

To post a comment you must log in.
Revision history for this message
Bob Luursema (bob-luursema) wrote : Posted in a previous version of this proposal

I didn't yet test whether the import still works for paid users, since I don't have a paid account. Would it be good to add some unit tests for that by mocking the external call and inputting an HTML file via the mock?

Revision history for this message
Raoul Snyman (raoul-snyman) wrote : Posted in a previous version of this proposal

Linux tests passed!

Revision history for this message
Raoul Snyman (raoul-snyman) wrote : Posted in a previous version of this proposal

Linting failed, please see https://ci.openlp.io/job/MP-03-Linting/42/ for more details

Revision history for this message
Raoul Snyman (raoul-snyman) wrote : Posted in a previous version of this proposal

This looks great! One small change that I think you should make (inline in the diff), and then I think we're good to go!

review: Needs Fixing
Revision history for this message
Raoul Snyman (raoul-snyman) wrote :

Linux tests passed!

Revision history for this message
Raoul Snyman (raoul-snyman) wrote :

Linting passed!

Revision history for this message
Raoul Snyman (raoul-snyman) wrote :

macOS tests passed!

Revision history for this message
Raoul Snyman (raoul-snyman) :
review: Approve
Revision history for this message
Tomas Groth (tomasgroth) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file '.bzrignore'
2--- .bzrignore 2018-09-07 06:43:01 +0000
3+++ .bzrignore 2019-03-05 21:31:58 +0000
4@@ -7,6 +7,7 @@
5 .coverage
6 coverage
7 .directory
8+.vscode
9 dist
10 *.dll
11 documentation/build/doctrees
12
13=== modified file 'openlp/plugins/songs/forms/songselectform.py'
14--- openlp/plugins/songs/forms/songselectform.py 2019-02-14 15:09:09 +0000
15+++ openlp/plugins/songs/forms/songselectform.py 2019-03-05 21:31:58 +0000
16@@ -263,8 +263,9 @@
17 self.login_progress_bar.setVisible(True)
18 self.application.process_events()
19 # Log the user in
20- if not self.song_select_importer.login(
21- self.username_edit.text(), self.password_edit.text(), self._update_login_progress):
22+ subscription_level = self.song_select_importer.login(
23+ self.username_edit.text(), self.password_edit.text(), self._update_login_progress)
24+ if not subscription_level:
25 QtWidgets.QMessageBox.critical(
26 self,
27 translate('SongsPlugin.SongSelectForm', 'Error Logging In'),
28@@ -272,6 +273,14 @@
29 'There was a problem logging in, perhaps your username or password is incorrect?')
30 )
31 else:
32+ if subscription_level == 'Free':
33+ QtWidgets.QMessageBox.information(
34+ self,
35+ translate('SongsPlugin.SongSelectForm', 'Free user'),
36+ translate('SongsPlugin.SongSelectForm', 'You logged in with a free account, '
37+ 'the search will be limited to songs '
38+ 'in the public domain.')
39+ )
40 if self.save_password_checkbox.isChecked():
41 Settings().setValue(self.plugin.settings_section + '/songselect username', self.username_edit.text())
42 Settings().setValue(self.plugin.settings_section + '/songselect password', self.password_edit.text())
43
44=== modified file 'openlp/plugins/songs/lib/songselect.py'
45--- openlp/plugins/songs/lib/songselect.py 2019-02-14 15:09:09 +0000
46+++ openlp/plugins/songs/lib/songselect.py 2019-03-05 21:31:58 +0000
47@@ -81,7 +81,7 @@
48 :param username: SongSelect username
49 :param password: SongSelect password
50 :param callback: Method to notify of progress.
51- :return: True on success, False on failure.
52+ :return: subscription level on success, None on failure.
53 """
54 if callback:
55 callback()
56@@ -115,11 +115,31 @@
57 return False
58 if callback:
59 callback()
60+ # Page if user is in an organization
61 if posted_page.find('input', id='SearchText') is not None:
62- return True
63+ self.subscription_level = self.find_subscription_level(posted_page)
64+ return self.subscription_level
65+ # Page if user is not in an organization
66+ elif posted_page.find('div', id="select-organization") is not None:
67+ try:
68+ home_page = BeautifulSoup(self.opener.open(BASE_URL).read(), 'lxml')
69+ except (TypeError, URLError) as error:
70+ log.exception('Could not reach SongSelect, {error}'.format(error=error))
71+ self.subscription_level = self.find_subscription_level(home_page)
72+ return self.subscription_level
73 else:
74 log.debug(posted_page)
75- return False
76+ return None
77+
78+ def find_subscription_level(self, page):
79+ scripts = page.find_all('script')
80+ for tag in scripts:
81+ if tag.string:
82+ match = re.search("'Subscription': '(?P<subscription_level>[^']+)", tag.string)
83+ if match:
84+ return match.group('subscription_level')
85+ log.error('Could not determine SongSelect subscription level')
86+ return None
87
88 def logout(self):
89 """
90@@ -128,7 +148,7 @@
91 try:
92 self.opener.open(LOGOUT_URL)
93 except (TypeError, URLError) as error:
94- log.exception('Could not log of SongSelect, {error}'.format(error=error))
95+ log.exception('Could not log out of SongSelect, {error}'.format(error=error))
96
97 def search(self, search_text, max_results, callback=None):
98 """
99@@ -145,7 +165,7 @@
100 'PrimaryLanguage': '',
101 'Keys': '',
102 'Themes': '',
103- 'List': '',
104+ 'List': 'publicdomain' if self.subscription_level == 'Free' else '',
105 'Sort': '',
106 'SearchText': search_text
107 }
108
109=== modified file 'tests/functional/openlp_plugins/songs/test_songselect.py'
110--- tests/functional/openlp_plugins/songs/test_songselect.py 2019-02-14 15:09:09 +0000
111+++ tests/functional/openlp_plugins/songs/test_songselect.py 2019-03-05 21:31:58 +0000
112@@ -64,7 +64,7 @@
113 @patch('openlp.plugins.songs.lib.songselect.BeautifulSoup')
114 def test_login_fails(self, MockedBeautifulSoup, mocked_build_opener):
115 """
116- Test that when logging in to SongSelect fails, the login method returns False
117+ Test that when logging in to SongSelect fails, the login method returns None
118 """
119 # GIVEN: A bunch of mocked out stuff and an importer object
120 mocked_opener = MagicMock()
121@@ -80,12 +80,12 @@
122 # WHEN: The login method is called after being rigged to fail
123 result = importer.login('username', 'password', mock_callback)
124
125- # THEN: callback was called 3 times, open was called twice, find was called twice, and False was returned
126+ # THEN: callback was called 3 times, open was called twice, find was called twice, and None was returned
127 assert 3 == mock_callback.call_count, 'callback should have been called 3 times'
128 assert 2 == mocked_login_page.find.call_count, 'find should have been called twice'
129- assert 1 == mocked_posted_page.find.call_count, 'find should have been called once'
130+ assert 2 == mocked_posted_page.find.call_count, 'find should have been called twice'
131 assert 2 == mocked_opener.open.call_count, 'opener should have been called twice'
132- assert result is False, 'The login method should have returned False'
133+ assert result is None, 'The login method should have returned None'
134
135 @patch('openlp.plugins.songs.lib.songselect.build_opener')
136 def test_login_except(self, mocked_build_opener):
137@@ -129,7 +129,7 @@
138 assert 2 == mocked_login_page.find.call_count, 'find should have been called twice on the login page'
139 assert 1 == mocked_posted_page.find.call_count, 'find should have been called once on the posted page'
140 assert 2 == mocked_opener.open.call_count, 'opener should have been called twice'
141- assert result is True, 'The login method should have returned True'
142+ assert result is None, 'The login method should have returned the subscription level'
143
144 @patch('openlp.plugins.songs.lib.songselect.build_opener')
145 @patch('openlp.plugins.songs.lib.songselect.BeautifulSoup')
146@@ -146,7 +146,8 @@
147 mocked_login_page.find.side_effect = [{'value': 'blah'}, mocked_form]
148 mocked_posted_page = MagicMock()
149 mocked_posted_page.find.return_value = MagicMock()
150- MockedBeautifulSoup.side_effect = [mocked_login_page, mocked_posted_page]
151+ mocked_home_page = MagicMock()
152+ MockedBeautifulSoup.side_effect = [mocked_login_page, mocked_posted_page, mocked_home_page]
153 mock_callback = MagicMock()
154 importer = SongSelectImport(None)
155
156@@ -158,7 +159,7 @@
157 assert 2 == mocked_login_page.find.call_count, 'find should have been called twice on the login page'
158 assert 1 == mocked_posted_page.find.call_count, 'find should have been called once on the posted page'
159 assert 'https://profile.ccli.com/do/login', mocked_opener.open.call_args_list[1][0][0]
160- assert result is True, 'The login method should have returned True'
161+ assert result is None, 'The login method should have returned the subscription level'
162
163 @patch('openlp.plugins.songs.lib.songselect.build_opener')
164 def test_logout(self, mocked_build_opener):
165@@ -191,6 +192,7 @@
166 MockedBeautifulSoup.return_value = mocked_results_page
167 mock_callback = MagicMock()
168 importer = SongSelectImport(None)
169+ importer.subscription_level = 'premium'
170
171 # WHEN: The login method is called after being rigged to fail
172 results = importer.search('text', 1000, mock_callback)
173@@ -231,6 +233,7 @@
174 MockedBeautifulSoup.return_value = mocked_results_page
175 mock_callback = MagicMock()
176 importer = SongSelectImport(None)
177+ importer.subscription_level = 'premium'
178
179 # WHEN: The search method is called
180 results = importer.search('text', 1000, mock_callback)
181@@ -282,6 +285,7 @@
182 MockedBeautifulSoup.return_value = mocked_results_page
183 mock_callback = MagicMock()
184 importer = SongSelectImport(None)
185+ importer.subscription_level = 'premium'
186
187 # WHEN: The search method is called
188 results = importer.search('text', 2, mock_callback)