diff --git a/openlp/plugins/songs/lib/importers/presentationmanager.py b/openlp/plugins/songs/lib/importers/presentationmanager.py index 58f4587ce..9fb630497 100644 --- a/openlp/plugins/songs/lib/importers/presentationmanager.py +++ b/openlp/plugins/songs/lib/importers/presentationmanager.py @@ -23,7 +23,7 @@ The :mod:`presentationmanager` module provides the functionality for importing Presentationmanager song files into the current database. """ - +import logging import os import re import chardet @@ -32,6 +32,8 @@ from lxml import objectify, etree from openlp.core.ui.wizard import WizardStrings from .songimport import SongImport +log = logging.getLogger(__name__) + class PresentationManagerImport(SongImport): """ @@ -62,15 +64,37 @@ class PresentationManagerImport(SongImport): 'File is not in XML-format, which is the only format supported.')) continue root = objectify.fromstring(etree.tostring(tree)) - self.process_song(root) + self.process_song(root, file_path) - def process_song(self, root): + def _get_attr(self, elem, name): + """ + Due to PresentationManager's habit of sometimes capitilising the first letter of an element, we have to do + some gymnastics. + """ + if hasattr(elem, name): + log.debug('%s: %s', name, getattr(elem, name)) + return str(getattr(elem, name)) + name = name[0].upper() + name[1:] + if hasattr(elem, name): + log.debug('%s: %s', name, getattr(elem, name)) + return str(getattr(elem, name)) + else: + return '' + + + def process_song(self, root, file_path): self.set_defaults() - self.title = str(root.attributes.title) - self.add_author(str(root.attributes.author)) - self.copyright = str(root.attributes.copyright) - self.ccli_number = str(root.attributes.ccli_number) - self.comments = str(root.attributes.comments) + attrs = None + if hasattr(root, 'attributes'): + attrs = root.attributes + elif hasattr(root, 'Attributes'): + attrs = root.Attributes + if attrs is not None: + self.title = self._get_attr(root.attributes, 'title') + self.add_author(self._get_attr(root.attributes, 'author')) + self.copyright = self._get_attr(root.attributes, 'copyright') + self.ccli_number = self._get_attr(root.attributes, 'ccli_number') + self.comments = str(root.attributes.comments) if hasattr(root.attributes, 'comments') else None verse_order_list = [] verse_count = {} duplicates = [] @@ -102,4 +126,4 @@ class PresentationManagerImport(SongImport): self.verse_order_list = verse_order_list if not self.finish(): - self.log_error(self.import_source) + self.log_error(os.path.basename(file_path)) diff --git a/openlp/plugins/songs/lib/songselect.py b/openlp/plugins/songs/lib/songselect.py index 3d43eed30..a34d89c9e 100644 --- a/openlp/plugins/songs/lib/songselect.py +++ b/openlp/plugins/songs/lib/songselect.py @@ -47,7 +47,7 @@ USER_AGENTS = [ BASE_URL = 'https://songselect.ccli.com' LOGIN_PAGE = 'https://profile.ccli.com/account/signin?appContext=SongSelect&returnUrl=' \ 'https%3a%2f%2fsongselect.ccli.com%2f' -LOGIN_URL = 'https://profile.ccli.com/' +LOGIN_URL = 'https://profile.ccli.com' LOGOUT_URL = BASE_URL + '/account/logout' SEARCH_URL = BASE_URL + '/search/results' @@ -97,14 +97,27 @@ class SongSelectImport(object): 'password': password, 'RememberMe': 'false' }) + login_form = login_page.find('form') + if login_form: + login_url = login_form.attrs['action'] + else: + login_url = '/Account/SignIn' + if not login_url.startswith('http'): + if login_url[0] != '/': + login_url = '/' + login_url + login_url = LOGIN_URL + login_url try: - posted_page = BeautifulSoup(self.opener.open(LOGIN_URL, data.encode('utf-8')).read(), 'lxml') + posted_page = BeautifulSoup(self.opener.open(login_url, data.encode('utf-8')).read(), 'lxml') except (TypeError, URLError) as error: log.exception('Could not login to SongSelect, %s', error) return False if callback: callback() - return posted_page.find('input', id='SearchText') is not None + if posted_page.find('input', id='SearchText') is not None: + return True + else: + log.debug(posted_page) + return False def logout(self): """ diff --git a/tests/functional/openlp_plugins/songs/test_songselect.py b/tests/functional/openlp_plugins/songs/test_songselect.py index cbcade105..f52ce1046 100644 --- a/tests/functional/openlp_plugins/songs/test_songselect.py +++ b/tests/functional/openlp_plugins/songs/test_songselect.py @@ -32,7 +32,7 @@ from PyQt5 import QtWidgets from openlp.core import Registry from openlp.plugins.songs.forms.songselectform import SongSelectForm, SearchWorker from openlp.plugins.songs.lib import Song -from openlp.plugins.songs.lib.songselect import SongSelectImport, LOGOUT_URL, BASE_URL +from openlp.plugins.songs.lib.songselect import SongSelectImport, LOGIN_PAGE, LOGOUT_URL, BASE_URL from tests.functional import MagicMock, patch, call from tests.helpers.songfileimport import SongImportTestHelper @@ -81,7 +81,7 @@ class TestSongSelectImport(TestCase, TestMixin): # THEN: callback was called 3 times, open was called twice, find was called twice, and False was returned self.assertEqual(2, mock_callback.call_count, 'callback should have been called 3 times') - self.assertEqual(1, mocked_login_page.find.call_count, 'find should have been called twice') + self.assertEqual(2, mocked_login_page.find.call_count, 'find should have been called twice') self.assertEqual(2, mocked_opener.open.call_count, 'opener should have been called twice') self.assertFalse(result, 'The login method should have returned False') @@ -96,7 +96,9 @@ class TestSongSelectImport(TestCase, TestMixin): mocked_build_opener.return_value = mocked_opener mocked_login_page = MagicMock() mocked_login_page.find.side_effect = [{'value': 'blah'}, None] - MockedBeautifulSoup.return_value = mocked_login_page + mocked_posted_page = MagicMock() + mocked_posted_page.find.return_value = None + MockedBeautifulSoup.side_effect = [mocked_login_page, mocked_posted_page] mock_callback = MagicMock() importer = SongSelectImport(None) @@ -105,7 +107,8 @@ class TestSongSelectImport(TestCase, TestMixin): # THEN: callback was called 3 times, open was called twice, find was called twice, and False was returned self.assertEqual(3, mock_callback.call_count, 'callback should have been called 3 times') - self.assertEqual(2, mocked_login_page.find.call_count, 'find should have been called twice') + self.assertEqual(2, mocked_login_page.find.call_count, 'find should have been called twice on the login page') + self.assertEqual(1, mocked_posted_page.find.call_count, 'find should have been called once on the posted page') self.assertEqual(2, mocked_opener.open.call_count, 'opener should have been called twice') self.assertFalse(result, 'The login method should have returned False') @@ -136,8 +139,10 @@ class TestSongSelectImport(TestCase, TestMixin): mocked_opener = MagicMock() mocked_build_opener.return_value = mocked_opener mocked_login_page = MagicMock() - mocked_login_page.find.side_effect = [{'value': 'blah'}, MagicMock()] - MockedBeautifulSoup.return_value = mocked_login_page + mocked_login_page.find.side_effect = [{'value': 'blah'}, None] + mocked_posted_page = MagicMock() + mocked_posted_page.find.return_value = MagicMock() + MockedBeautifulSoup.side_effect = [mocked_login_page, mocked_posted_page] mock_callback = MagicMock() importer = SongSelectImport(None) @@ -146,10 +151,40 @@ class TestSongSelectImport(TestCase, TestMixin): # THEN: callback was called 3 times, open was called twice, find was called twice, and True was returned self.assertEqual(3, mock_callback.call_count, 'callback should have been called 3 times') - self.assertEqual(2, mocked_login_page.find.call_count, 'find should have been called twice') + self.assertEqual(2, mocked_login_page.find.call_count, 'find should have been called twice on the login page') + self.assertEqual(1, mocked_posted_page.find.call_count, 'find should have been called once on the posted page') self.assertEqual(2, mocked_opener.open.call_count, 'opener should have been called twice') self.assertTrue(result, 'The login method should have returned True') + @patch('openlp.plugins.songs.lib.songselect.build_opener') + @patch('openlp.plugins.songs.lib.songselect.BeautifulSoup') + def login_url_from_form_test(self, MockedBeautifulSoup, mocked_build_opener): + """ + Test that the login URL is from the form + """ + # GIVEN: A bunch of mocked out stuff and an importer object + mocked_opener = MagicMock() + mocked_build_opener.return_value = mocked_opener + mocked_form = MagicMock() + mocked_form.attrs = {'action': 'do/login'} + mocked_login_page = MagicMock() + mocked_login_page.find.side_effect = [{'value': 'blah'}, mocked_form] + mocked_posted_page = MagicMock() + mocked_posted_page.find.return_value = MagicMock() + MockedBeautifulSoup.side_effect = [mocked_login_page, mocked_posted_page] + mock_callback = MagicMock() + importer = SongSelectImport(None) + + # WHEN: The login method is called after being rigged to fail + result = importer.login('username', 'password', mock_callback) + + # THEN: callback was called 3 times, open was called twice, find was called twice, and True was returned + self.assertEqual(3, mock_callback.call_count, 'callback should have been called 3 times') + self.assertEqual(2, mocked_login_page.find.call_count, 'find should have been called twice on the login page') + self.assertEqual(1, mocked_posted_page.find.call_count, 'find should have been called once on the posted page') + self.assertEqual('https://profile.ccli.com/do/login', mocked_opener.open.call_args_list[1][0][0]) + self.assertTrue(result, 'The login method should have returned True') + @patch('openlp.plugins.songs.lib.songselect.build_opener') def logout_test(self, mocked_build_opener): """ diff --git a/tests/resources/presentationmanagersongs/Agnus Dei.sng b/tests/resources/presentationmanagersongs/Agnus Dei.sng index ce4385c55..6a0f7e6f7 100644 --- a/tests/resources/presentationmanagersongs/Agnus Dei.sng +++ b/tests/resources/presentationmanagersongs/Agnus Dei.sng @@ -1,7 +1,7 @@ -Agnus Dei +Agnus Dei