Port some bugfixes from 2.4 to trunk

This commit is contained in:
Raoul Snyman 2017-03-09 22:42:38 -07:00
parent e5abad85a7
commit 324652c347
9 changed files with 132 additions and 29 deletions

View File

@ -319,7 +319,7 @@ class OpenLP(OpenLPMixin, QtWidgets.QApplication):
return QtWidgets.QApplication.event(self, event)
def parse_options(args):
def parse_options(args=None):
"""
Parse the command line arguments

View File

@ -221,10 +221,16 @@ class OpenLPSongImport(SongImport):
if not existing_book:
existing_book = Book.populate(name=entry.songbook.name, publisher=entry.songbook.publisher)
new_song.add_songbook_entry(existing_book, entry.entry)
elif song.book:
elif hasattr(song, 'book') and song.book:
existing_book = self.manager.get_object_filtered(Book, Book.name == song.book.name)
if not existing_book:
existing_book = Book.populate(name=song.book.name, publisher=song.book.publisher)
# Get the song_number from "songs" table "song_number" field. (This is db structure from 2.2.1)
# If there's a number, add it to the song, otherwise it will be "".
existing_number = song.song_number if hasattr(song, 'song_number') else ''
if existing_number:
new_song.add_songbook_entry(existing_book, existing_number)
else:
new_song.add_songbook_entry(existing_book, '')
# Find or create all the media files and add them to the new song object
if has_media_files and song.media_files:

View File

@ -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
@ -34,6 +34,8 @@ from openlp.core.common import translate
from openlp.core.ui.lib.wizard import WizardStrings
from .songimport import SongImport
log = logging.getLogger(__name__)
class PresentationManagerImport(SongImport):
"""
@ -65,15 +67,36 @@ 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 = []
@ -105,4 +128,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))

View File

@ -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, {error}'.format(error=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):
"""

View File

@ -22,12 +22,12 @@
import sys
from unittest import TestCase
from unittest.mock import MagicMock, patch
from openlp.core import parse_options
from tests.helpers.testmixin import TestMixin
class TestInitFunctions(TestMixin, TestCase):
class TestInitFunctions(TestCase):
def test_parse_options_basic(self):
"""
@ -116,8 +116,7 @@ class TestInitFunctions(TestMixin, TestCase):
def test_parse_options_file_and_debug(self):
"""
Test the parse options process works with a file
Test the parse options process works with a file and the debug log level
"""
# GIVEN: a a set of system arguments.
sys.argv[1:] = ['-l debug', 'dummy_temp']
@ -130,3 +129,29 @@ class TestInitFunctions(TestMixin, TestCase):
self.assertFalse(args.portable, 'The portable flag should be set to false')
self.assertEquals(args.style, None, 'There are no style flags to be processed')
self.assertEquals(args.rargs, 'dummy_temp', 'The service file should not be blank')
class TestOpenLP(TestCase):
"""
Test the OpenLP app class
"""
@patch('openlp.core.QtWidgets.QApplication.exec')
def test_exec(self, mocked_exec):
"""
Test the exec method
"""
# GIVEN: An app
app = OpenLP([])
app.shared_memory = MagicMock()
mocked_exec.return_value = False
# WHEN: exec() is called
result = app.exec()
# THEN: The right things should be called
assert app.is_event_loop_active is True
mocked_exec.assert_called_once_with()
app.shared_memory.detach.assert_called_once_with()
assert result is False
del app

View File

@ -25,11 +25,11 @@ This module contains tests for the lib submodule of the Remotes plugin.
import os
import urllib.request
from unittest import TestCase
from unittest.mock import MagicMock, patch, mock_open
from openlp.core.common import Settings, Registry
from openlp.core.ui import ServiceManager
from openlp.plugins.remotes.lib.httpserver import HttpRouter
from tests.functional import MagicMock, patch, mock_open
from tests.helpers.testmixin import TestMixin
__default_settings__ = {
@ -336,11 +336,13 @@ class TestRouter(TestCase, TestMixin):
with patch.object(self.service_manager, 'setup_ui'), \
patch.object(self.router, 'do_json_header'):
self.service_manager.bootstrap_initialise()
self.app.processEvents()
# Not sure why this is here, it doesn't make sense in the test and causes them to hang
# self.app.processEvents()
# WHEN: Remote next is received
self.router.service(action='next')
self.app.processEvents()
# Not sure why this is here, it doesn't make sense in the test and causes them to hang
# self.app.processEvents()
# THEN: service_manager.next_item() should have been called
self.assertTrue(mocked_next_item.called, 'next_item() should have been called in service_manager')
@ -357,11 +359,13 @@ class TestRouter(TestCase, TestMixin):
with patch.object(self.service_manager, 'setup_ui'), \
patch.object(self.router, 'do_json_header'):
self.service_manager.bootstrap_initialise()
self.app.processEvents()
# Not sure why this is here, it doesn't make sense in the test and causes them to hang
# self.app.processEvents()
# WHEN: Remote next is received
self.router.service(action='previous')
self.app.processEvents()
# Not sure why this is here, it doesn't make sense in the test and causes them to hang
# self.app.processEvents()
# THEN: service_manager.next_item() should have been called
self.assertTrue(mocked_previous_item.called, 'previous_item() should have been called in service_manager')

View File

@ -25,6 +25,7 @@ This module contains tests for the CCLI SongSelect importer.
"""
import os
from unittest import TestCase
from unittest.mock import MagicMock, patch, call
from urllib.error import URLError
from PyQt5 import QtWidgets
@ -32,9 +33,8 @@ 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
from tests.helpers.testmixin import TestMixin
@ -112,8 +112,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)
@ -122,10 +124,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 test_logout(self, mocked_build_opener):
"""