This commit is contained in:
Tim Bentley 2015-02-28 07:04:54 +00:00
commit e9be4a9143
7 changed files with 851 additions and 383 deletions

View File

@ -40,3 +40,5 @@ __pycache__
# Rejected diff's # Rejected diff's
*.rej *.rej
*.~\?~ *.~\?~
.coverage
cover

View File

@ -24,6 +24,7 @@ The bible import functions for OpenLP
""" """
import logging import logging
import os import os
import urllib.error
from PyQt4 import QtGui from PyQt4 import QtGui
@ -34,6 +35,7 @@ from openlp.core.ui.wizard import OpenLPWizard, WizardStrings
from openlp.core.utils import get_locale_key from openlp.core.utils import get_locale_key
from openlp.plugins.bibles.lib.manager import BibleFormat from openlp.plugins.bibles.lib.manager import BibleFormat
from openlp.plugins.bibles.lib.db import BiblesResourcesDB, clean_filename from openlp.plugins.bibles.lib.db import BiblesResourcesDB, clean_filename
from openlp.plugins.bibles.lib.http import CWExtract, BGExtract, BSExtract
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -90,7 +92,6 @@ class BibleImportForm(OpenLPWizard):
Perform any custom initialisation for bible importing. Perform any custom initialisation for bible importing.
""" """
self.manager.set_process_dialog(self) self.manager.set_process_dialog(self)
self.load_Web_Bibles()
self.restart() self.restart()
self.select_stack.setCurrentIndex(0) self.select_stack.setCurrentIndex(0)
@ -104,6 +105,7 @@ class BibleImportForm(OpenLPWizard):
self.csv_verses_button.clicked.connect(self.on_csv_verses_browse_button_clicked) self.csv_verses_button.clicked.connect(self.on_csv_verses_browse_button_clicked)
self.open_song_browse_button.clicked.connect(self.on_open_song_browse_button_clicked) self.open_song_browse_button.clicked.connect(self.on_open_song_browse_button_clicked)
self.zefania_browse_button.clicked.connect(self.on_zefania_browse_button_clicked) self.zefania_browse_button.clicked.connect(self.on_zefania_browse_button_clicked)
self.web_update_button.clicked.connect(self.on_web_update_button_clicked)
def add_custom_pages(self): def add_custom_pages(self):
""" """
@ -202,20 +204,33 @@ class BibleImportForm(OpenLPWizard):
self.web_bible_tab.setObjectName('WebBibleTab') self.web_bible_tab.setObjectName('WebBibleTab')
self.web_bible_layout = QtGui.QFormLayout(self.web_bible_tab) self.web_bible_layout = QtGui.QFormLayout(self.web_bible_tab)
self.web_bible_layout.setObjectName('WebBibleLayout') self.web_bible_layout.setObjectName('WebBibleLayout')
self.web_update_label = QtGui.QLabel(self.web_bible_tab)
self.web_update_label.setObjectName('WebUpdateLabel')
self.web_bible_layout.setWidget(0, QtGui.QFormLayout.LabelRole, self.web_update_label)
self.web_update_button = QtGui.QPushButton(self.web_bible_tab)
self.web_update_button.setObjectName('WebUpdateButton')
self.web_bible_layout.setWidget(0, QtGui.QFormLayout.FieldRole, self.web_update_button)
self.web_source_label = QtGui.QLabel(self.web_bible_tab) self.web_source_label = QtGui.QLabel(self.web_bible_tab)
self.web_source_label.setObjectName('WebSourceLabel') self.web_source_label.setObjectName('WebSourceLabel')
self.web_bible_layout.setWidget(0, QtGui.QFormLayout.LabelRole, self.web_source_label) self.web_bible_layout.setWidget(1, QtGui.QFormLayout.LabelRole, self.web_source_label)
self.web_source_combo_box = QtGui.QComboBox(self.web_bible_tab) self.web_source_combo_box = QtGui.QComboBox(self.web_bible_tab)
self.web_source_combo_box.setObjectName('WebSourceComboBox') self.web_source_combo_box.setObjectName('WebSourceComboBox')
self.web_source_combo_box.addItems(['', '', '']) self.web_source_combo_box.addItems(['', '', ''])
self.web_bible_layout.setWidget(0, QtGui.QFormLayout.FieldRole, self.web_source_combo_box) self.web_source_combo_box.setEnabled(False)
self.web_bible_layout.setWidget(1, QtGui.QFormLayout.FieldRole, self.web_source_combo_box)
self.web_translation_label = QtGui.QLabel(self.web_bible_tab) self.web_translation_label = QtGui.QLabel(self.web_bible_tab)
self.web_translation_label.setObjectName('web_translation_label') self.web_translation_label.setObjectName('web_translation_label')
self.web_bible_layout.setWidget(1, QtGui.QFormLayout.LabelRole, self.web_translation_label) self.web_bible_layout.setWidget(2, QtGui.QFormLayout.LabelRole, self.web_translation_label)
self.web_translation_combo_box = QtGui.QComboBox(self.web_bible_tab) self.web_translation_combo_box = QtGui.QComboBox(self.web_bible_tab)
self.web_translation_combo_box.setSizeAdjustPolicy(QtGui.QComboBox.AdjustToContents) self.web_translation_combo_box.setSizeAdjustPolicy(QtGui.QComboBox.AdjustToContents)
self.web_translation_combo_box.setObjectName('WebTranslationComboBox') self.web_translation_combo_box.setObjectName('WebTranslationComboBox')
self.web_bible_layout.setWidget(1, QtGui.QFormLayout.FieldRole, self.web_translation_combo_box) self.web_translation_combo_box.setEnabled(False)
self.web_bible_layout.setWidget(2, QtGui.QFormLayout.FieldRole, self.web_translation_combo_box)
self.web_progress_bar = QtGui.QProgressBar(self)
self.web_progress_bar.setRange(0, 3)
self.web_progress_bar.setObjectName('WebTranslationProgressBar')
self.web_progress_bar.setVisible(False)
self.web_bible_layout.setWidget(3, QtGui.QFormLayout.SpanningRole, self.web_progress_bar)
self.web_tab_widget.addTab(self.web_bible_tab, '') self.web_tab_widget.addTab(self.web_bible_tab, '')
self.web_proxy_tab = QtGui.QWidget() self.web_proxy_tab = QtGui.QWidget()
self.web_proxy_tab.setObjectName('WebProxyTab') self.web_proxy_tab.setObjectName('WebProxyTab')
@ -314,6 +329,8 @@ class BibleImportForm(OpenLPWizard):
self.open_song_file_label.setText(translate('BiblesPlugin.ImportWizardForm', 'Bible file:')) self.open_song_file_label.setText(translate('BiblesPlugin.ImportWizardForm', 'Bible file:'))
self.web_source_label.setText(translate('BiblesPlugin.ImportWizardForm', 'Location:')) self.web_source_label.setText(translate('BiblesPlugin.ImportWizardForm', 'Location:'))
self.zefania_file_label.setText(translate('BiblesPlugin.ImportWizardForm', 'Bible file:')) self.zefania_file_label.setText(translate('BiblesPlugin.ImportWizardForm', 'Bible file:'))
self.web_update_label.setText(translate('BiblesPlugin.ImportWizardForm', 'Click to download bible list'))
self.web_update_button.setText(translate('BiblesPlugin.ImportWizardForm', 'Download bible list'))
self.web_source_combo_box.setItemText(WebDownload.Crosswalk, translate('BiblesPlugin.ImportWizardForm', self.web_source_combo_box.setItemText(WebDownload.Crosswalk, translate('BiblesPlugin.ImportWizardForm',
'Crosswalk')) 'Crosswalk'))
self.web_source_combo_box.setItemText(WebDownload.BibleGateway, translate('BiblesPlugin.ImportWizardForm', self.web_source_combo_box.setItemText(WebDownload.BibleGateway, translate('BiblesPlugin.ImportWizardForm',
@ -388,9 +405,12 @@ class BibleImportForm(OpenLPWizard):
self.zefania_file_edit.setFocus() self.zefania_file_edit.setFocus()
return False return False
elif self.field('source_format') == BibleFormat.WebDownload: elif self.field('source_format') == BibleFormat.WebDownload:
# If count is 0 the bible list has not yet been downloaded
if self.web_translation_combo_box.count() == 0:
return False
else:
self.version_name_edit.setText(self.web_translation_combo_box.currentText()) self.version_name_edit.setText(self.web_translation_combo_box.currentText())
return True return True
return True
elif self.currentPage() == self.license_details_page: elif self.currentPage() == self.license_details_page:
license_version = self.field('license_version') license_version = self.field('license_version')
license_copyright = self.field('license_copyright') license_copyright = self.field('license_copyright')
@ -434,6 +454,7 @@ class BibleImportForm(OpenLPWizard):
:param index: The index of the combo box. :param index: The index of the combo box.
""" """
self.web_translation_combo_box.clear() self.web_translation_combo_box.clear()
if self.web_bible_list:
bibles = list(self.web_bible_list[index].keys()) bibles = list(self.web_bible_list[index].keys())
bibles.sort(key=get_locale_key) bibles.sort(key=get_locale_key)
self.web_translation_combo_box.addItems(bibles) self.web_translation_combo_box.addItems(bibles)
@ -475,6 +496,39 @@ class BibleImportForm(OpenLPWizard):
self.get_file_name(WizardStrings.OpenTypeFile % WizardStrings.ZEF, self.zefania_file_edit, self.get_file_name(WizardStrings.OpenTypeFile % WizardStrings.ZEF, self.zefania_file_edit,
'last directory import') 'last directory import')
def on_web_update_button_clicked(self):
"""
Download list of bibles from Crosswalk, BibleServer and BibleGateway.
"""
# Download from Crosswalk, BiblesGateway, BibleServer
self.web_bible_list = {}
self.web_source_combo_box.setEnabled(False)
self.web_translation_combo_box.setEnabled(False)
self.web_update_button.setEnabled(False)
self.web_progress_bar.setVisible(True)
self.web_progress_bar.setValue(0)
proxy_server = self.field('proxy_server')
for (download_type, extractor) in ((WebDownload.Crosswalk, CWExtract(proxy_server)),
(WebDownload.BibleGateway, BGExtract(proxy_server)),
(WebDownload.Bibleserver, BSExtract(proxy_server))):
try:
bibles = extractor.get_bibles_from_http()
except (urllib.error.URLError, ConnectionError) as err:
critical_error_message_box(translate('BiblesPlugin.ImportWizardForm', 'Error during download'),
translate('BiblesPlugin.ImportWizardForm',
'An error occurred while downloading the list of bibles from %s.'))
self.web_bible_list[download_type] = {}
for (bible_name, bible_key, language_code) in bibles:
self.web_bible_list[download_type][bible_name] = (bible_key, language_code)
self.web_progress_bar.setValue(download_type + 1)
# Update combo box if something got into the list
if self.web_bible_list:
self.on_web_source_combo_box_index_changed(0)
self.web_source_combo_box.setEnabled(True)
self.web_translation_combo_box.setEnabled(True)
self.web_update_button.setEnabled(True)
self.web_progress_bar.setVisible(False)
def register_fields(self): def register_fields(self):
""" """
Register the bible import wizard fields. Register the bible import wizard fields.
@ -520,30 +574,6 @@ class BibleImportForm(OpenLPWizard):
self.on_web_source_combo_box_index_changed(WebDownload.Crosswalk) self.on_web_source_combo_box_index_changed(WebDownload.Crosswalk)
settings.endGroup() settings.endGroup()
def load_Web_Bibles(self):
"""
Load the lists of Crosswalk, BibleGateway and Bibleserver bibles.
"""
# Load Crosswalk Bibles.
self.load_Bible_Resource(WebDownload.Crosswalk)
# Load BibleGateway Bibles.
self.load_Bible_Resource(WebDownload.BibleGateway)
# Load and Bibleserver Bibles.
self.load_Bible_Resource(WebDownload.Bibleserver)
def load_Bible_Resource(self, download_type):
"""
Loads a web bible from bible_resources.sqlite.
:param download_type: The WebDownload type e.g. bibleserver.
"""
self.web_bible_list[download_type] = {}
bibles = BiblesResourcesDB.get_webbibles(WebDownload.Names[download_type])
for bible in bibles:
version = bible['name']
name = bible['abbreviation']
self.web_bible_list[download_type][version] = name.strip()
def pre_wizard(self): def pre_wizard(self):
""" """
Prepare the UI for the import. Prepare the UI for the import.
@ -583,14 +613,15 @@ class BibleImportForm(OpenLPWizard):
self.progress_bar.setMaximum(1) self.progress_bar.setMaximum(1)
download_location = self.field('web_location') download_location = self.field('web_location')
bible_version = self.web_translation_combo_box.currentText() bible_version = self.web_translation_combo_box.currentText()
bible = self.web_bible_list[download_location][bible_version] (bible, language_id) = self.web_bible_list[download_location][bible_version]
importer = self.manager.import_bible( importer = self.manager.import_bible(
BibleFormat.WebDownload, name=license_version, BibleFormat.WebDownload, name=license_version,
download_source=WebDownload.Names[download_location], download_source=WebDownload.Names[download_location],
download_name=bible, download_name=bible,
proxy_server=self.field('proxy_server'), proxy_server=self.field('proxy_server'),
proxy_username=self.field('proxy_username'), proxy_username=self.field('proxy_username'),
proxy_password=self.field('proxy_password') proxy_password=self.field('proxy_password'),
language_id=language_id
) )
elif bible_type == BibleFormat.Zefania: elif bible_type == BibleFormat.Zefania:
# Import an Zefania bible. # Import an Zefania bible.

View File

@ -50,6 +50,38 @@ UGLY_CHARS = {
} }
VERSE_NUMBER_REGEX = re.compile(r'v(\d{1,2})(\d{3})(\d{3}) verse.*') VERSE_NUMBER_REGEX = re.compile(r'v(\d{1,2})(\d{3})(\d{3}) verse.*')
BIBLESERVER_LANGUAGE_CODE = {
'fl_1': 'de',
'fl_2': 'en',
'fl_3': 'fr',
'fl_4': 'it',
'fl_5': 'es',
'fl_6': 'pt',
'fl_7': 'ru',
'fl_8': 'sv',
'fl_9': 'no',
'fl_10': 'nl',
'fl_11': 'cs',
'fl_12': 'sk',
'fl_13': 'ro',
'fl_14': 'hr',
'fl_15': 'hu',
'fl_16': 'bg',
'fl_17': 'ar',
'fl_18': 'tr',
'fl_19': 'pl',
'fl_20': 'da',
'fl_21': 'zh'
}
CROSSWALK_LANGUAGES = {
'Portuguese': 'pt',
'German': 'de',
'Italian': 'it',
'Español': 'es',
'French': 'fr',
'Dutch': 'nl'
}
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -222,6 +254,8 @@ class BGExtract(RegistryProperties):
if not soup: if not soup:
return None return None
div = soup.find('div', 'result-text-style-normal') div = soup.find('div', 'result-text-style-normal')
if not div:
return None
self._clean_soup(div) self._clean_soup(div)
span_list = div.find_all('span', 'text') span_list = div.find_all('span', 'text')
log.debug('Span list: %s', span_list) log.debug('Span list: %s', span_list)
@ -278,6 +312,42 @@ class BGExtract(RegistryProperties):
books.append(book.contents[0]) books.append(book.contents[0])
return books return books
def get_bibles_from_http(self):
"""
Load a list of bibles from BibleGateway website.
returns a list in the form [(biblename, biblekey, language_code)]
"""
log.debug('BGExtract.get_bibles_from_http')
bible_url = 'https://legacy.biblegateway.com/versions/'
soup = get_soup_for_bible_ref(bible_url)
if not soup:
return None
bible_select = soup.find('select', {'class': 'translation-dropdown'})
if not bible_select:
log.debug('No select tags found - did site change?')
return None
option_tags = bible_select.find_all('option')
if not option_tags:
log.debug('No option tags found - did site change?')
return None
current_lang = ''
bibles = []
for ot in option_tags:
tag_class = ''
try:
tag_class = ot['class'][0]
except KeyError:
tag_class = ''
tag_text = ot.get_text()
if tag_class == 'lang':
current_lang = tag_text[tag_text.find('(') + 1:tag_text.find(')')].lower()
elif tag_class == 'spacer':
continue
else:
bibles.append((tag_text, ot['value'], current_lang))
return bibles
class BSExtract(RegistryProperties): class BSExtract(RegistryProperties):
""" """
@ -338,6 +408,43 @@ class BSExtract(RegistryProperties):
content = content.find_all('li') content = content.find_all('li')
return [book.contents[0].contents[0] for book in content if len(book.contents[0].contents)] return [book.contents[0].contents[0] for book in content if len(book.contents[0].contents)]
def get_bibles_from_http(self):
"""
Load a list of bibles from Bibleserver website.
returns a list in the form [(biblename, biblekey, language_code)]
"""
log.debug('BSExtract.get_bibles_from_http')
bible_url = 'http://www.bibleserver.com/index.php?language=2'
soup = get_soup_for_bible_ref(bible_url)
if not soup:
return None
bible_links = soup.find_all('a', {'class': 'trlCell'})
if not bible_links:
log.debug('No a tags found - did site change?')
return None
bibles = []
for link in bible_links:
bible_name = link.get_text()
# Skip any audio
if 'audio' in bible_name.lower():
continue
try:
bible_link = link['href']
bible_key = bible_link[bible_link.rfind('/') + 1:]
css_classes = link['class']
except KeyError:
log.debug('No href/class attribute found - did site change?')
language_code = ''
for css_class in css_classes:
if css_class.startswith('fl_'):
try:
language_code = BIBLESERVER_LANGUAGE_CODE[css_class]
except KeyError:
language_code = ''
bibles.append((bible_name, bible_key, language_code))
return bibles
class CWExtract(RegistryProperties): class CWExtract(RegistryProperties):
""" """
@ -408,6 +515,49 @@ class CWExtract(RegistryProperties):
books.append(book.contents[0]) books.append(book.contents[0])
return books return books
def get_bibles_from_http(self):
"""
Load a list of bibles from Crosswalk website.
returns a list in the form [(biblename, biblekey, language_code)]
"""
log.debug('CWExtract.get_bibles_from_http')
bible_url = 'http://www.biblestudytools.com/search/bible-search.part/'
soup = get_soup_for_bible_ref(bible_url)
if not soup:
return None
bible_select = soup.find('select')
if not bible_select:
log.debug('No select tags found - did site change?')
return None
option_tags = bible_select.find_all('option')
if not option_tags:
log.debug('No option tags found - did site change?')
return None
bibles = []
for ot in option_tags:
tag_text = ot.get_text().strip()
try:
tag_value = ot['value']
except KeyError:
log.exception('No value attribute found - did site change?')
return None
if not tag_value:
continue
# The names of non-english bibles has their language in parentheses at the end
if tag_text.endswith(')'):
language = tag_text[tag_text.rfind('(') + 1:-1]
if language in CROSSWALK_LANGUAGES:
language_code = CROSSWALK_LANGUAGES[language]
else:
language_code = ''
# ... except for the latin vulgate
elif 'latin' in tag_text.lower():
language_code = 'la'
else:
language_code = 'en'
bibles.append((tag_text, tag_value, language_code))
return bibles
class HTTPBible(BibleDB, RegistryProperties): class HTTPBible(BibleDB, RegistryProperties):
log.info('%s HTTPBible loaded', __name__) log.info('%s HTTPBible loaded', __name__)
@ -428,6 +578,7 @@ class HTTPBible(BibleDB, RegistryProperties):
self.proxy_server = None self.proxy_server = None
self.proxy_username = None self.proxy_username = None
self.proxy_password = None self.proxy_password = None
self.language_id = None
if 'path' in kwargs: if 'path' in kwargs:
self.path = kwargs['path'] self.path = kwargs['path']
if 'proxy_server' in kwargs: if 'proxy_server' in kwargs:
@ -436,6 +587,8 @@ class HTTPBible(BibleDB, RegistryProperties):
self.proxy_username = kwargs['proxy_username'] self.proxy_username = kwargs['proxy_username']
if 'proxy_password' in kwargs: if 'proxy_password' in kwargs:
self.proxy_password = kwargs['proxy_password'] self.proxy_password = kwargs['proxy_password']
if 'language_id' in kwargs:
self.language_id = kwargs['language_id']
def do_import(self, bible_name=None): def do_import(self, bible_name=None):
""" """
@ -468,13 +621,11 @@ class HTTPBible(BibleDB, RegistryProperties):
return False return False
self.wizard.progress_bar.setMaximum(len(books) + 2) self.wizard.progress_bar.setMaximum(len(books) + 2)
self.wizard.increment_progress_bar(translate('BiblesPlugin.HTTPBible', 'Registering Language...')) self.wizard.increment_progress_bar(translate('BiblesPlugin.HTTPBible', 'Registering Language...'))
bible = BiblesResourcesDB.get_webbible(self.download_name, self.download_source.lower()) if self.language_id:
if bible['language_id']: self.save_meta('language_id', self.language_id)
language_id = bible['language_id']
self.save_meta('language_id', language_id)
else: else:
language_id = self.get_language(bible_name) self.language_id = self.get_language(bible_name)
if not language_id: if not self.language_id:
log.error('Importing books from %s failed' % self.filename) log.error('Importing books from %s failed' % self.filename)
return False return False
for book in books: for book in books:
@ -482,7 +633,7 @@ class HTTPBible(BibleDB, RegistryProperties):
break break
self.wizard.increment_progress_bar(translate( self.wizard.increment_progress_bar(translate(
'BiblesPlugin.HTTPBible', 'Importing %s...', 'Importing <book name>...') % book) 'BiblesPlugin.HTTPBible', 'Importing %s...', 'Importing <book name>...') % book)
book_ref_id = self.get_book_ref_id_by_name(book, len(books), language_id) book_ref_id = self.get_book_ref_id_by_name(book, len(books), self.language_id)
if not book_ref_id: if not book_ref_id:
log.error('Importing books from %s - download name: "%s" failed' % log.error('Importing books from %s - download name: "%s" failed' %
(self.download_source, self.download_name)) (self.download_source, self.download_name))

View File

@ -187,6 +187,14 @@ class SongSelectForm(QtGui.QDialog, Ui_SongSelectDialog):
self.application.process_events() self.application.process_events()
# Get the full song # Get the full song
song = self.song_select_importer.get_song(song, self._update_song_progress) song = self.song_select_importer.get_song(song, self._update_song_progress)
if not song:
QtGui.QMessageBox.critical(
self, translate('SongsPlugin.SongSelectForm', 'Incomplete song'),
translate('SongsPlugin.SongSelectForm', 'This song is missing some information, like the lyrics, '
'and cannot be imported.'),
QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok), QtGui.QMessageBox.Ok)
self.stacked_widget.setCurrentIndex(1)
return
# Update the UI # Update the UI
self.title_edit.setText(song['title']) self.title_edit.setText(song['title'])
self.copyright_edit.setText(song['copyright']) self.copyright_edit.setText(song['copyright'])
@ -359,15 +367,11 @@ class SongSelectForm(QtGui.QDialog, Ui_SongSelectDialog):
Import a song from SongSelect. Import a song from SongSelect.
""" """
self.song_select_importer.save_song(self.song) self.song_select_importer.save_song(self.song)
question_dialog = QtGui.QMessageBox() if QtGui.QMessageBox.question(self, translate('SongsPlugin.SongSelectForm', 'Song Imported'),
question_dialog.setWindowTitle(translate('SongsPlugin.SongSelectForm', 'Song Imported')) translate('SongsPlugin.SongSelectForm', 'Your song has been imported, would you '
question_dialog.setText(translate('SongsPlugin.SongSelectForm', 'Your song has been imported, would you like ' 'like to import more songs?'),
'to exit now, or import more songs?')) QtGui.QMessageBox.Yes | QtGui.QMessageBox.No,
question_dialog.addButton(QtGui.QPushButton(translate('SongsPlugin.SongSelectForm', 'Import More Songs')), QtGui.QMessageBox.Yes) == QtGui.QMessageBox.Yes:
QtGui.QMessageBox.YesRole)
question_dialog.addButton(QtGui.QPushButton(translate('SongsPlugin.SongSelectForm', 'Exit Now')),
QtGui.QMessageBox.NoRole)
if question_dialog.exec_() == QtGui.QMessageBox.Yes:
self.on_back_button_clicked() self.on_back_button_clicked()
else: else:
self.application.process_events() self.application.process_events()

View File

@ -23,29 +23,31 @@
This module contains tests for the CCLI SongSelect importer. This module contains tests for the CCLI SongSelect importer.
""" """
import os import os
from unittest import TestCase from unittest import TestCase
from urllib.error import URLError from urllib.error import URLError
from tests.functional import MagicMock, patch, call
from tests.helpers.testmixin import TestMixin from PyQt4 import QtGui
from openlp.core import Registry from openlp.core import Registry
from openlp.plugins.songs.forms.songselectform import SongSelectForm from openlp.plugins.songs.forms.songselectform import SongSelectForm, SearchWorker
from openlp.plugins.songs.lib import Author, Song, VerseType 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, LOGOUT_URL, BASE_URL
from openlp.plugins.songs.lib.importers.cclifile import CCLIFileImport from openlp.plugins.songs.lib.importers.cclifile import CCLIFileImport
from tests.functional import MagicMock, patch, call
from tests.helpers.testmixin import TestMixin
class TestSongSelectImport(TestCase, TestMixin): class TestSongSelectImport(TestCase, TestMixin):
""" """
Test the :class:`~openlp.plugins.songs.lib.songselect.SongSelectImport` class Test the :class:`~openlp.plugins.songs.lib.songselect.SongSelectImport` class
""" """
def constructor_test(self): @patch('openlp.plugins.songs.lib.songselect.build_opener')
def constructor_test(self, mocked_build_opener):
""" """
Test that constructing a basic SongSelectImport object works correctly Test that constructing a basic SongSelectImport object works correctly
""" """
# GIVEN: The SongSelectImporter class and a mocked out build_opener # GIVEN: The SongSelectImporter class and a mocked out build_opener
with patch('openlp.plugins.songs.lib.songselect.build_opener') as mocked_build_opener:
# WHEN: An object is instantiated # WHEN: An object is instantiated
importer = SongSelectImport(None) importer = SongSelectImport(None)
@ -55,13 +57,13 @@ class TestSongSelectImport(TestCase, TestMixin):
self.assertIsNotNone(importer.opener, 'There should be a valid opener object') self.assertIsNotNone(importer.opener, 'There should be a valid opener object')
self.assertEqual(1, mocked_build_opener.call_count, 'The build_opener method should have been called once') self.assertEqual(1, mocked_build_opener.call_count, 'The build_opener method should have been called once')
def login_fails_test(self): @patch('openlp.plugins.songs.lib.songselect.build_opener')
@patch('openlp.plugins.songs.lib.songselect.BeautifulSoup')
def login_fails_test(self, MockedBeautifulSoup, mocked_build_opener):
""" """
Test that when logging in to SongSelect fails, the login method returns False Test that when logging in to SongSelect fails, the login method returns False
""" """
# GIVEN: A bunch of mocked out stuff and an importer object # GIVEN: A bunch of mocked out stuff and an importer object
with patch('openlp.plugins.songs.lib.songselect.build_opener') as mocked_build_opener, \
patch('openlp.plugins.songs.lib.songselect.BeautifulSoup') as MockedBeautifulSoup:
mocked_opener = MagicMock() mocked_opener = MagicMock()
mocked_build_opener.return_value = mocked_opener mocked_build_opener.return_value = mocked_opener
mocked_login_page = MagicMock() mocked_login_page = MagicMock()
@ -79,13 +81,13 @@ class TestSongSelectImport(TestCase, TestMixin):
self.assertEqual(2, mocked_opener.open.call_count, 'opener 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') self.assertFalse(result, 'The login method should have returned False')
def login_succeeds_test(self): @patch('openlp.plugins.songs.lib.songselect.build_opener')
@patch('openlp.plugins.songs.lib.songselect.BeautifulSoup')
def login_succeeds_test(self, MockedBeautifulSoup, mocked_build_opener):
""" """
Test that when logging in to SongSelect succeeds, the login method returns True Test that when logging in to SongSelect succeeds, the login method returns True
""" """
# GIVEN: A bunch of mocked out stuff and an importer object # GIVEN: A bunch of mocked out stuff and an importer object
with patch('openlp.plugins.songs.lib.songselect.build_opener') as mocked_build_opener, \
patch('openlp.plugins.songs.lib.songselect.BeautifulSoup') as MockedBeautifulSoup:
mocked_opener = MagicMock() mocked_opener = MagicMock()
mocked_build_opener.return_value = mocked_opener mocked_build_opener.return_value = mocked_opener
mocked_login_page = MagicMock() mocked_login_page = MagicMock()
@ -103,12 +105,12 @@ class TestSongSelectImport(TestCase, TestMixin):
self.assertEqual(2, mocked_opener.open.call_count, 'opener should have been called twice') self.assertEqual(2, mocked_opener.open.call_count, 'opener should have been called twice')
self.assertTrue(result, 'The login method should have returned True') self.assertTrue(result, 'The login method should have returned True')
def logout_test(self): @patch('openlp.plugins.songs.lib.songselect.build_opener')
def logout_test(self, mocked_build_opener):
""" """
Test that when the logout method is called, it logs the user out of SongSelect Test that when the logout method is called, it logs the user out of SongSelect
""" """
# GIVEN: A bunch of mocked out stuff and an importer object # GIVEN: A bunch of mocked out stuff and an importer object
with patch('openlp.plugins.songs.lib.songselect.build_opener') as mocked_build_opener:
mocked_opener = MagicMock() mocked_opener = MagicMock()
mocked_build_opener.return_value = mocked_opener mocked_build_opener.return_value = mocked_opener
importer = SongSelectImport(None) importer = SongSelectImport(None)
@ -120,13 +122,13 @@ class TestSongSelectImport(TestCase, TestMixin):
self.assertEqual(1, mocked_opener.open.call_count, 'opener should have been called once') self.assertEqual(1, mocked_opener.open.call_count, 'opener should have been called once')
mocked_opener.open.assert_called_with(LOGOUT_URL) mocked_opener.open.assert_called_with(LOGOUT_URL)
def search_returns_no_results_test(self): @patch('openlp.plugins.songs.lib.songselect.build_opener')
@patch('openlp.plugins.songs.lib.songselect.BeautifulSoup')
def search_returns_no_results_test(self, MockedBeautifulSoup, mocked_build_opener):
""" """
Test that when the search finds no results, it simply returns an empty list Test that when the search finds no results, it simply returns an empty list
""" """
# GIVEN: A bunch of mocked out stuff and an importer object # GIVEN: A bunch of mocked out stuff and an importer object
with patch('openlp.plugins.songs.lib.songselect.build_opener') as mocked_build_opener, \
patch('openlp.plugins.songs.lib.songselect.BeautifulSoup') as MockedBeautifulSoup:
mocked_opener = MagicMock() mocked_opener = MagicMock()
mocked_build_opener.return_value = mocked_opener mocked_build_opener.return_value = mocked_opener
mocked_results_page = MagicMock() mocked_results_page = MagicMock()
@ -145,13 +147,13 @@ class TestSongSelectImport(TestCase, TestMixin):
mocked_results_page.find_all.assert_called_with('li', 'result pane') mocked_results_page.find_all.assert_called_with('li', 'result pane')
self.assertEqual([], results, 'The search method should have returned an empty list') self.assertEqual([], results, 'The search method should have returned an empty list')
def search_returns_two_results_test(self): @patch('openlp.plugins.songs.lib.songselect.build_opener')
@patch('openlp.plugins.songs.lib.songselect.BeautifulSoup')
def search_returns_two_results_test(self, MockedBeautifulSoup, mocked_build_opener):
""" """
Test that when the search finds 2 results, it simply returns a list with 2 results Test that when the search finds 2 results, it simply returns a list with 2 results
""" """
# GIVEN: A bunch of mocked out stuff and an importer object # GIVEN: A bunch of mocked out stuff and an importer object
with patch('openlp.plugins.songs.lib.songselect.build_opener') as mocked_build_opener, \
patch('openlp.plugins.songs.lib.songselect.BeautifulSoup') as MockedBeautifulSoup:
# first search result # first search result
mocked_result1 = MagicMock() mocked_result1 = MagicMock()
mocked_result1.find.side_effect = [MagicMock(string='Title 1'), {'href': '/url1'}] mocked_result1.find.side_effect = [MagicMock(string='Title 1'), {'href': '/url1'}]
@ -183,13 +185,13 @@ class TestSongSelectImport(TestCase, TestMixin):
] ]
self.assertListEqual(expected_list, results, 'The search method should have returned two songs') self.assertListEqual(expected_list, results, 'The search method should have returned two songs')
def search_reaches_max_results_test(self): @patch('openlp.plugins.songs.lib.songselect.build_opener')
@patch('openlp.plugins.songs.lib.songselect.BeautifulSoup')
def search_reaches_max_results_test(self, MockedBeautifulSoup, mocked_build_opener):
""" """
Test that when the search finds MAX (2) results, it simply returns a list with those (2) Test that when the search finds MAX (2) results, it simply returns a list with those (2)
""" """
# GIVEN: A bunch of mocked out stuff and an importer object # GIVEN: A bunch of mocked out stuff and an importer object
with patch('openlp.plugins.songs.lib.songselect.build_opener') as mocked_build_opener, \
patch('openlp.plugins.songs.lib.songselect.BeautifulSoup') as MockedBeautifulSoup:
# first search result # first search result
mocked_result1 = MagicMock() mocked_result1 = MagicMock()
mocked_result1.find.side_effect = [MagicMock(string='Title 1'), {'href': '/url1'}] mocked_result1.find.side_effect = [MagicMock(string='Title 1'), {'href': '/url1'}]
@ -223,12 +225,12 @@ class TestSongSelectImport(TestCase, TestMixin):
{'title': 'Title 2', 'authors': ['Author 2-1', 'Author 2-2'], 'link': BASE_URL + '/url2'}] {'title': 'Title 2', 'authors': ['Author 2-1', 'Author 2-2'], 'link': BASE_URL + '/url2'}]
self.assertListEqual(expected_list, results, 'The search method should have returned two songs') self.assertListEqual(expected_list, results, 'The search method should have returned two songs')
def get_song_page_raises_exception_test(self): @patch('openlp.plugins.songs.lib.songselect.build_opener')
def get_song_page_raises_exception_test(self, mocked_build_opener):
""" """
Test that when BeautifulSoup gets a bad song page the get_song() method returns None Test that when BeautifulSoup gets a bad song page the get_song() method returns None
""" """
# GIVEN: A bunch of mocked out stuff and an importer object # GIVEN: A bunch of mocked out stuff and an importer object
with patch('openlp.plugins.songs.lib.songselect.build_opener') as mocked_build_opener:
mocked_opener = MagicMock() mocked_opener = MagicMock()
mocked_build_opener.return_value = mocked_opener mocked_build_opener.return_value = mocked_opener
mocked_opener.open.read.side_effect = URLError('[Errno -2] Name or service not known') mocked_opener.open.read.side_effect = URLError('[Errno -2] Name or service not known')
@ -242,13 +244,13 @@ class TestSongSelectImport(TestCase, TestMixin):
mocked_callback.assert_called_with() mocked_callback.assert_called_with()
self.assertIsNone(result, 'The get_song() method should have returned None') self.assertIsNone(result, 'The get_song() method should have returned None')
def get_song_lyrics_raise_exception_test(self): @patch('openlp.plugins.songs.lib.songselect.build_opener')
@patch('openlp.plugins.songs.lib.songselect.BeautifulSoup')
def get_song_lyrics_raise_exception_test(self, MockedBeautifulSoup, mocked_build_opener):
""" """
Test that when BeautifulSoup gets a bad lyrics page the get_song() method returns None Test that when BeautifulSoup gets a bad lyrics page the get_song() method returns None
""" """
# GIVEN: A bunch of mocked out stuff and an importer object # GIVEN: A bunch of mocked out stuff and an importer object
with patch('openlp.plugins.songs.lib.songselect.build_opener'), \
patch('openlp.plugins.songs.lib.songselect.BeautifulSoup') as MockedBeautifulSoup:
MockedBeautifulSoup.side_effect = [None, TypeError('Test Error')] MockedBeautifulSoup.side_effect = [None, TypeError('Test Error')]
mocked_callback = MagicMock() mocked_callback = MagicMock()
importer = SongSelectImport(None) importer = SongSelectImport(None)
@ -260,13 +262,13 @@ class TestSongSelectImport(TestCase, TestMixin):
self.assertEqual(2, mocked_callback.call_count, 'The callback should have been called twice') self.assertEqual(2, mocked_callback.call_count, 'The callback should have been called twice')
self.assertIsNone(result, 'The get_song() method should have returned None') self.assertIsNone(result, 'The get_song() method should have returned None')
def get_song_test(self): @patch('openlp.plugins.songs.lib.songselect.build_opener')
@patch('openlp.plugins.songs.lib.songselect.BeautifulSoup')
def get_song_test(self, MockedBeautifulSoup, mocked_build_opener):
""" """
Test that the get_song() method returns the correct song details Test that the get_song() method returns the correct song details
""" """
# GIVEN: A bunch of mocked out stuff and an importer object # GIVEN: A bunch of mocked out stuff and an importer object
with patch('openlp.plugins.songs.lib.songselect.build_opener'), \
patch('openlp.plugins.songs.lib.songselect.BeautifulSoup') as MockedBeautifulSoup:
mocked_song_page = MagicMock() mocked_song_page = MagicMock()
mocked_copyright = MagicMock() mocked_copyright = MagicMock()
mocked_copyright.find_all.return_value = [MagicMock(string='Copyright 1'), MagicMock(string='Copyright 2')] mocked_copyright.find_all.return_value = [MagicMock(string='Copyright 1'), MagicMock(string='Copyright 2')]
@ -308,13 +310,13 @@ class TestSongSelectImport(TestCase, TestMixin):
self.assertIn('verses', result, 'The returned song should have verses') self.assertIn('verses', result, 'The returned song should have verses')
self.assertEqual(3, len(result['verses']), 'Three verses should have been returned') self.assertEqual(3, len(result['verses']), 'Three verses should have been returned')
def save_song_new_author_test(self): @patch('openlp.plugins.songs.lib.songselect.clean_song')
@patch('openlp.plugins.songs.lib.songselect.Author')
def save_song_new_author_test(self, MockedAuthor, mocked_clean_song):
""" """
Test that saving a song with a new author performs the correct actions Test that saving a song with a new author performs the correct actions
""" """
# GIVEN: A song to save, and some mocked out objects # GIVEN: A song to save, and some mocked out objects
with patch('openlp.plugins.songs.lib.songselect.clean_song') as mocked_clean_song, \
patch('openlp.plugins.songs.lib.songselect.Author') as MockedAuthor:
song_dict = { song_dict = {
'title': 'Arky Arky', 'title': 'Arky Arky',
'authors': ['Public Domain'], 'authors': ['Public Domain'],
@ -344,13 +346,13 @@ class TestSongSelectImport(TestCase, TestMixin):
display_name='Public Domain') display_name='Public Domain')
self.assertEqual(1, len(result.authors_songs), 'There should only be one author') self.assertEqual(1, len(result.authors_songs), 'There should only be one author')
def save_song_existing_author_test(self): @patch('openlp.plugins.songs.lib.songselect.clean_song')
@patch('openlp.plugins.songs.lib.songselect.Author')
def save_song_existing_author_test(self, MockedAuthor, mocked_clean_song):
""" """
Test that saving a song with an existing author performs the correct actions Test that saving a song with an existing author performs the correct actions
""" """
# GIVEN: A song to save, and some mocked out objects # GIVEN: A song to save, and some mocked out objects
with patch('openlp.plugins.songs.lib.songselect.clean_song') as mocked_clean_song, \
patch('openlp.plugins.songs.lib.songselect.Author') as MockedAuthor:
song_dict = { song_dict = {
'title': 'Arky Arky', 'title': 'Arky Arky',
'authors': ['Public Domain'], 'authors': ['Public Domain'],
@ -409,14 +411,14 @@ class TestSongSelectForm(TestCase, TestMixin):
self.assertEqual(mocked_plugin, ssform.plugin, 'The correct plugin should have been assigned') self.assertEqual(mocked_plugin, ssform.plugin, 'The correct plugin should have been assigned')
self.assertEqual(mocked_db_manager, ssform.db_manager, 'The correct db_manager should have been assigned') self.assertEqual(mocked_db_manager, ssform.db_manager, 'The correct db_manager should have been assigned')
def login_fails_test(self): @patch('openlp.plugins.songs.forms.songselectform.SongSelectImport')
@patch('openlp.plugins.songs.forms.songselectform.QtGui.QMessageBox.critical')
@patch('openlp.plugins.songs.forms.songselectform.translate')
def login_fails_test(self, mocked_translate, mocked_critical, MockedSongSelectImport):
""" """
Test that when the login fails, the form returns to the correct state Test that when the login fails, the form returns to the correct state
""" """
# GIVEN: A valid SongSelectForm with a mocked out SongSelectImport, and a bunch of mocked out controls # GIVEN: A valid SongSelectForm with a mocked out SongSelectImport, and a bunch of mocked out controls
with patch('openlp.plugins.songs.forms.songselectform.SongSelectImport') as MockedSongSelectImport, \
patch('openlp.plugins.songs.forms.songselectform.QtGui.QMessageBox.critical') as mocked_critical, \
patch('openlp.plugins.songs.forms.songselectform.translate') as mocked_translate:
mocked_song_select_import = MagicMock() mocked_song_select_import = MagicMock()
mocked_song_select_import.login.return_value = False mocked_song_select_import.login.return_value = False
MockedSongSelectImport.return_value = mocked_song_select_import MockedSongSelectImport.return_value = mocked_song_select_import
@ -463,6 +465,166 @@ class TestSongSelectForm(TestCase, TestMixin):
'perhaps your username or password is ' 'perhaps your username or password is '
'incorrect?') 'incorrect?')
@patch('openlp.plugins.songs.forms.songselectform.QtGui.QMessageBox.question')
@patch('openlp.plugins.songs.forms.songselectform.translate')
def on_import_yes_clicked_test(self, mocked_translate, mocked_question):
"""
Test that when a song is imported and the user clicks the "yes" button, the UI goes back to the previous page
"""
# GIVEN: A valid SongSelectForm with a mocked out QMessageBox.question() method
mocked_translate.side_effect = lambda *args: args[1]
mocked_question.return_value = QtGui.QMessageBox.Yes
ssform = SongSelectForm(None, MagicMock(), MagicMock())
mocked_song_select_importer = MagicMock()
ssform.song_select_importer = mocked_song_select_importer
ssform.song = None
# WHEN: The import button is clicked, and the user clicks Yes
with patch.object(ssform, 'on_back_button_clicked') as mocked_on_back_button_clicked:
ssform.on_import_button_clicked()
# THEN: The on_back_button_clicked() method should have been called
mocked_song_select_importer.save_song.assert_called_with(None)
mocked_question.assert_called_with(ssform, 'Song Imported',
'Your song has been imported, would you like to import more songs?',
QtGui.QMessageBox.Yes | QtGui.QMessageBox.No, QtGui.QMessageBox.Yes)
mocked_on_back_button_clicked.assert_called_with()
@patch('openlp.plugins.songs.forms.songselectform.QtGui.QMessageBox.question')
@patch('openlp.plugins.songs.forms.songselectform.translate')
def on_import_no_clicked_test(self, mocked_translate, mocked_question):
"""
Test that when a song is imported and the user clicks the "no" button, the UI exits
"""
# GIVEN: A valid SongSelectForm with a mocked out QMessageBox.question() method
mocked_translate.side_effect = lambda *args: args[1]
mocked_question.return_value = QtGui.QMessageBox.No
ssform = SongSelectForm(None, MagicMock(), MagicMock())
mocked_song_select_importer = MagicMock()
ssform.song_select_importer = mocked_song_select_importer
ssform.song = None
# WHEN: The import button is clicked, and the user clicks Yes
with patch.object(ssform, 'done') as mocked_done:
ssform.on_import_button_clicked()
# THEN: The on_back_button_clicked() method should have been called
mocked_song_select_importer.save_song.assert_called_with(None)
mocked_question.assert_called_with(ssform, 'Song Imported',
'Your song has been imported, would you like to import more songs?',
QtGui.QMessageBox.Yes | QtGui.QMessageBox.No, QtGui.QMessageBox.Yes)
mocked_done.assert_called_with(QtGui.QDialog.Accepted)
def on_back_button_clicked_test(self):
"""
Test that when the back button is clicked, the stacked widget is set back one page
"""
# GIVEN: A SongSelect form
ssform = SongSelectForm(None, MagicMock(), MagicMock())
# WHEN: The back button is clicked
with patch.object(ssform, 'stacked_widget') as mocked_stacked_widget, \
patch.object(ssform, 'search_combobox') as mocked_search_combobox:
ssform.on_back_button_clicked()
# THEN: The stacked widget should be set back one page
mocked_stacked_widget.setCurrentIndex.assert_called_with(1)
mocked_search_combobox.setFocus.assert_called_with()
@patch('openlp.plugins.songs.forms.songselectform.QtGui.QMessageBox.information')
def on_search_show_info_test(self, mocked_information):
"""
Test that when the search_show_info signal is emitted, the on_search_show_info() method shows a dialog
"""
# GIVEN: A SongSelect form
ssform = SongSelectForm(None, MagicMock(), MagicMock())
expected_title = 'Test Title'
expected_text = 'This is a test'
# WHEN: on_search_show_info is called
ssform.on_search_show_info(expected_title, expected_text)
# THEN: An information dialog should be shown
mocked_information.assert_called_with(ssform, expected_title, expected_text)
def update_login_progress_test(self):
"""
Test the _update_login_progress() method
"""
# GIVEN: A SongSelect form
ssform = SongSelectForm(None, MagicMock(), MagicMock())
# WHEN: _update_login_progress() is called
with patch.object(ssform, 'login_progress_bar') as mocked_login_progress_bar:
mocked_login_progress_bar.value.return_value = 3
ssform._update_login_progress()
# THEN: The login progress bar should be updated
mocked_login_progress_bar.setValue.assert_called_with(4)
def update_song_progress_test(self):
"""
Test the _update_song_progress() method
"""
# GIVEN: A SongSelect form
ssform = SongSelectForm(None, MagicMock(), MagicMock())
# WHEN: _update_song_progress() is called
with patch.object(ssform, 'song_progress_bar') as mocked_song_progress_bar:
mocked_song_progress_bar.value.return_value = 2
ssform._update_song_progress()
# THEN: The song progress bar should be updated
mocked_song_progress_bar.setValue.assert_called_with(3)
def on_search_results_widget_double_clicked_test(self):
"""
Test that a song is retrieved when a song in the results list is double-clicked
"""
# GIVEN: A SongSelect form
ssform = SongSelectForm(None, MagicMock(), MagicMock())
expected_song = {'title': 'Amazing Grace'}
# WHEN: A song result is double-clicked
with patch.object(ssform, '_view_song') as mocked_view_song:
ssform.on_search_results_widget_double_clicked(expected_song)
# THEN: The song is fetched and shown to the user
mocked_view_song.assert_called_with(expected_song)
def on_view_button_clicked_test(self):
"""
Test that a song is retrieved when the view button is clicked
"""
# GIVEN: A SongSelect form
ssform = SongSelectForm(None, MagicMock(), MagicMock())
expected_song = {'title': 'Amazing Grace'}
# WHEN: A song result is double-clicked
with patch.object(ssform, '_view_song') as mocked_view_song, \
patch.object(ssform, 'search_results_widget') as mocked_search_results_widget:
mocked_search_results_widget.currentItem.return_value = expected_song
ssform.on_view_button_clicked()
# THEN: The song is fetched and shown to the user
mocked_view_song.assert_called_with(expected_song)
def on_search_results_widget_selection_changed_test(self):
"""
Test that the view button is updated when the search results list is changed
"""
# GIVEN: A SongSelect form
ssform = SongSelectForm(None, MagicMock(), MagicMock())
# WHEN: There is at least 1 item selected
with patch.object(ssform, 'search_results_widget') as mocked_search_results_widget, \
patch.object(ssform, 'view_button') as mocked_view_button:
mocked_search_results_widget.selectedItems.return_value = [1]
ssform.on_search_results_widget_selection_changed()
# THEN: The view button should be enabled
mocked_view_button.setEnabled.assert_called_with(True)
class TestSongSelectFileImport(TestCase, TestMixin): class TestSongSelectFileImport(TestCase, TestMixin):
""" """
@ -480,50 +642,43 @@ class TestSongSelectFileImport(TestCase, TestMixin):
self.authors = ['Author One', 'Author Two'] self.authors = ['Author One', 'Author Two']
self.topics = ['Adoration', 'Praise'] self.topics = ['Adoration', 'Praise']
def tearDown(self): def songselect_import_bin_file_test(self):
""" """
Test cleanups Verify import SongSelect BIN file parses file properly
"""
pass
def songselect_import_usr_file_test(self):
"""
Verify import SongSelect USR file parses file properly
""" """
# GIVEN: Text file to import and mocks # GIVEN: Text file to import and mocks
copyright = '2011 OpenLP Programmer One (Admin. by OpenLP One) | ' \ copyright_bin = '2011 OpenLP Programmer One (Admin. by OpenLP One) | ' \
'Openlp Programmer Two (Admin. by OpenLP Two)' 'Openlp Programmer Two (Admin. by OpenLP Two)'
verses = [ verses_bin = [
['v1', 'Line One Verse One\nLine Two Verse One\nLine Three Verse One\nLine Four Verse One', None], ['v1', 'Line One Verse One\nLine Two Verse One\nLine Three Verse One\nLine Four Verse One', None],
['v2', 'Line One Verse Two\nLine Two Verse Two\nLine Three Verse Two\nLine Four Verse Two', None] ['v2', 'Line One Verse Two\nLine Two Verse Two\nLine Three Verse Two\nLine Four Verse Two', None]
] ]
song_import = CCLIFileImport(manager=None, filename=['{}.bin'.format(self.file_name)])
song_import = CCLIFileImport(manager=None, filename=['{}.bin'.format(self.file_name)], )
with patch.object(song_import, 'import_wizard'), patch.object(song_import, 'finish'): with patch.object(song_import, 'import_wizard'), patch.object(song_import, 'finish'):
# WHEN: We call the song importer # WHEN: We call the song importer
song_import.do_import() song_import.do_import()
# THEN: Song values should be equal to test values in setUp # THEN: Song values should be equal to test values in setUp
self.assertEquals(song_import.title, self.title, 'Song title should match') self.assertEquals(song_import.title, self.title, 'Song title should match')
self.assertEquals(song_import.ccli_number, self.ccli_number, 'CCLI Song Number should match') self.assertEquals(song_import.ccli_number, self.ccli_number, 'CCLI Song Number should match')
self.assertEquals(song_import.authors, self.authors, 'Author(s) should match') self.assertEquals(song_import.authors, self.authors, 'Author(s) should match')
self.assertEquals(song_import.copyright, copyright, 'Copyright should match') self.assertEquals(song_import.copyright, copyright_bin, 'Copyright should match')
self.assertEquals(song_import.topics, self.topics, 'Theme(s) should match') self.assertEquals(song_import.topics, self.topics, 'Theme(s) should match')
self.assertEquals(song_import.verses, verses, 'Verses should match with test verses') self.assertEquals(song_import.verses, verses_bin, 'Verses should match with test verses')
def songselect_import_text_file_test(self): def songselect_import_text_file_test(self):
""" """
Verify import SongSelect TEXT file parses file properly Verify import SongSelect TEXT file parses file properly
""" """
# GIVEN: Text file to import and mocks # GIVEN: Text file to import and mocks
copyright = '© 2011 OpenLP Programmer One (Admin. by OpenLP One)' copyright_txt = '© 2011 OpenLP Programmer One (Admin. by OpenLP One)'
verses = [ verses_txt = [
['v1', 'Line One Verse One\r\nLine Two Verse One\r\nLine Three Verse One\r\nLine Four Verse One', None], ['v1', 'Line One Verse One\r\nLine Two Verse One\r\nLine Three Verse One\r\nLine Four Verse One', None],
['v2', 'Line One Verse Two\r\nLine Two Verse Two\r\nLine Three Verse Two\r\nLine Four Verse Two', None] ['v2', 'Line One Verse Two\r\nLine Two Verse Two\r\nLine Three Verse Two\r\nLine Four Verse Two', None]
] ]
song_import = CCLIFileImport(manager=None, filename=['{}.txt'.format(self.file_name)], ) song_import = CCLIFileImport(manager=None, filename=['{}.txt'.format(self.file_name)])
with patch.object(song_import, 'import_wizard'), patch.object(song_import, 'finish'):
with patch.object(song_import, 'import_wizard'), patch.object(song_import, 'finish'):
# WHEN: We call the song importer # WHEN: We call the song importer
song_import.do_import() song_import.do_import()
@ -531,5 +686,87 @@ class TestSongSelectFileImport(TestCase, TestMixin):
self.assertEquals(song_import.title, self.title, 'Song title should match') self.assertEquals(song_import.title, self.title, 'Song title should match')
self.assertEquals(song_import.ccli_number, self.ccli_number, 'CCLI Song Number should match') self.assertEquals(song_import.ccli_number, self.ccli_number, 'CCLI Song Number should match')
self.assertEquals(song_import.authors, self.authors, 'Author(s) should match') self.assertEquals(song_import.authors, self.authors, 'Author(s) should match')
self.assertEquals(song_import.copyright, copyright, 'Copyright should match') self.assertEquals(song_import.copyright, copyright_txt, 'Copyright should match')
self.assertEquals(song_import.verses, verses, 'Verses should match with test verses') self.assertEquals(song_import.verses, verses_txt, 'Verses should match with test verses')
class TestSearchWorker(TestCase, TestMixin):
"""
Test the SearchWorker class
"""
def constructor_test(self):
"""
Test the SearchWorker constructor
"""
# GIVEN: An importer mock object and some search text
importer = MagicMock()
search_text = 'Jesus'
# WHEN: The search worker is created
worker = SearchWorker(importer, search_text)
# THEN: The correct values should be set
self.assertIs(importer, worker.importer, 'The importer should be the right object')
self.assertEqual(search_text, worker.search_text, 'The search text should be correct')
def start_test(self):
"""
Test the start() method of the SearchWorker class
"""
# GIVEN: An importer mock object, some search text and an initialised SearchWorker
importer = MagicMock()
importer.search.return_value = ['song1', 'song2']
search_text = 'Jesus'
worker = SearchWorker(importer, search_text)
# WHEN: The start() method is called
with patch.object(worker, 'finished') as mocked_finished, patch.object(worker, 'quit') as mocked_quit:
worker.start()
# THEN: The "finished" and "quit" signals should be emitted
importer.search.assert_called_with(search_text, 1000, worker._found_song_callback)
mocked_finished.emit.assert_called_with()
mocked_quit.emit.assert_called_with()
@patch('openlp.plugins.songs.forms.songselectform.translate')
def start_over_1000_songs_test(self, mocked_translate):
"""
Test the start() method of the SearchWorker class when it finds over 1000 songs
"""
# GIVEN: An importer mock object, some search text and an initialised SearchWorker
mocked_translate.side_effect = lambda x, y: y
importer = MagicMock()
importer.search.return_value = ['song%s' % num for num in range(1050)]
search_text = 'Jesus'
worker = SearchWorker(importer, search_text)
# WHEN: The start() method is called
with patch.object(worker, 'finished') as mocked_finished, patch.object(worker, 'quit') as mocked_quit, \
patch.object(worker, 'show_info') as mocked_show_info:
worker.start()
# THEN: The "finished" and "quit" signals should be emitted
importer.search.assert_called_with(search_text, 1000, worker._found_song_callback)
mocked_show_info.emit.assert_called_with('More than 1000 results', 'Your search has returned more than 1000 '
'results, it has been stopped. Please '
'refine your search to fetch better '
'results.')
mocked_finished.emit.assert_called_with()
mocked_quit.emit.assert_called_with()
def found_song_callback_test(self):
"""
Test that when the _found_song_callback() function is called, the "found_song" signal is emitted
"""
# GIVEN: An importer mock object, some search text and an initialised SearchWorker
importer = MagicMock()
search_text = 'Jesus'
song = {'title': 'Amazing Grace'}
worker = SearchWorker(importer, search_text)
# WHEN: The start() method is called
with patch.object(worker, 'found_song') as mocked_found_song:
worker._found_song_callback(song)
# THEN: The "found_song" signal should have been emitted
mocked_found_song.emit.assert_called_with(song)

View File

@ -25,7 +25,7 @@
from unittest import TestCase from unittest import TestCase
from openlp.core.common import Registry from openlp.core.common import Registry
from openlp.plugins.bibles.lib.http import BGExtract, CWExtract from openlp.plugins.bibles.lib.http import BGExtract, CWExtract, BSExtract
from tests.interfaces import MagicMock from tests.interfaces import MagicMock
@ -116,3 +116,46 @@ class TestBibleHTTP(TestCase):
# THEN: We should get back a valid service item # THEN: We should get back a valid service item
assert len(results.verse_list) == 36, 'The book of John should not have had any verses added or removed' assert len(results.verse_list) == 36, 'The book of John should not have had any verses added or removed'
def bibleserver_get_bibles_test(self):
"""
Test getting list of bibles from BibelServer.com
"""
# GIVEN: A new Bible Server extraction class
handler = BSExtract()
# WHEN: downloading bible list from bibleserver
bibles = handler.get_bibles_from_http()
# THEN: The list should not be None, and some known bibles should be there
self.assertIsNotNone(bibles)
self.assertIn(('New Int. Readers Version', 'NIRV', 'en'), bibles)
self.assertIn(('Българската Библия', 'BLG', 'bg'), bibles)
def biblegateway_get_bibles_test(self):
"""
Test getting list of bibles from BibelGateway.com
"""
# GIVEN: A new Bible Gateway extraction class
handler = BGExtract()
# WHEN: downloading bible list from Crosswalk
bibles = handler.get_bibles_from_http()
# THEN: The list should not be None, and some known bibles should be there
self.assertIsNotNone(bibles)
self.assertIn(('Holman Christian Standard Bible', 'HCSB', 'en'), bibles)
def crosswalk_get_bibles_test(self):
"""
Test getting list of bibles from Crosswalk.com
"""
# GIVEN: A new Crosswalk extraction class
handler = CWExtract()
# WHEN: downloading bible list from Crosswalk
bibles = handler.get_bibles_from_http()
# THEN: The list should not be None, and some known bibles should be there
self.assertIsNotNone(bibles)
self.assertIn(('Giovanni Diodati 1649 (Italian)', 'gdb', 'it'), bibles)