diff --git a/.bzrignore b/.bzrignore index 6b7b989a6..5fd2beb79 100644 --- a/.bzrignore +++ b/.bzrignore @@ -40,3 +40,5 @@ __pycache__ # Rejected diff's *.rej *.~\?~ +.coverage +cover diff --git a/openlp/plugins/alerts/lib/alertsmanager.py b/openlp/plugins/alerts/lib/alertsmanager.py index 2e3b46f9e..d4089c6c1 100644 --- a/openlp/plugins/alerts/lib/alertsmanager.py +++ b/openlp/plugins/alerts/lib/alertsmanager.py @@ -70,7 +70,7 @@ class AlertsManager(OpenLPMixin, RegistryMixin, QtCore.QObject, RegistryProperti """ Format and request the Alert and start the timer. """ - if not self.alert_list or (self.live_controller.display.screens.display_count == 1 + if not self.alert_list or (self.live_controller.display.screens.display_count == 1 and not Settings().value('core/display on monitor')): return text = self.alert_list.pop(0) diff --git a/openlp/plugins/bibles/forms/bibleimportform.py b/openlp/plugins/bibles/forms/bibleimportform.py index 019286a68..94d8e6567 100644 --- a/openlp/plugins/bibles/forms/bibleimportform.py +++ b/openlp/plugins/bibles/forms/bibleimportform.py @@ -24,6 +24,7 @@ The bible import functions for OpenLP """ import logging import os +import urllib.error 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.plugins.bibles.lib.manager import BibleFormat from openlp.plugins.bibles.lib.db import BiblesResourcesDB, clean_filename +from openlp.plugins.bibles.lib.http import CWExtract, BGExtract, BSExtract log = logging.getLogger(__name__) @@ -90,7 +92,6 @@ class BibleImportForm(OpenLPWizard): Perform any custom initialisation for bible importing. """ self.manager.set_process_dialog(self) - self.load_Web_Bibles() self.restart() 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.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.web_update_button.clicked.connect(self.on_web_update_button_clicked) def add_custom_pages(self): """ @@ -202,20 +204,33 @@ class BibleImportForm(OpenLPWizard): self.web_bible_tab.setObjectName('WebBibleTab') self.web_bible_layout = QtGui.QFormLayout(self.web_bible_tab) 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.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.setObjectName('WebSourceComboBox') 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.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.setSizeAdjustPolicy(QtGui.QComboBox.AdjustToContents) 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_proxy_tab = QtGui.QWidget() 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.web_source_label.setText(translate('BiblesPlugin.ImportWizardForm', 'Location:')) 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', 'Crosswalk')) self.web_source_combo_box.setItemText(WebDownload.BibleGateway, translate('BiblesPlugin.ImportWizardForm', @@ -388,8 +405,11 @@ class BibleImportForm(OpenLPWizard): self.zefania_file_edit.setFocus() return False elif self.field('source_format') == BibleFormat.WebDownload: - self.version_name_edit.setText(self.web_translation_combo_box.currentText()) - return True + # 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()) return True elif self.currentPage() == self.license_details_page: license_version = self.field('license_version') @@ -434,9 +454,10 @@ class BibleImportForm(OpenLPWizard): :param index: The index of the combo box. """ self.web_translation_combo_box.clear() - bibles = list(self.web_bible_list[index].keys()) - bibles.sort(key=get_locale_key) - self.web_translation_combo_box.addItems(bibles) + if self.web_bible_list: + bibles = list(self.web_bible_list[index].keys()) + bibles.sort(key=get_locale_key) + self.web_translation_combo_box.addItems(bibles) def on_osis_browse_button_clicked(self): """ @@ -475,6 +496,39 @@ class BibleImportForm(OpenLPWizard): self.get_file_name(WizardStrings.OpenTypeFile % WizardStrings.ZEF, self.zefania_file_edit, '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): """ Register the bible import wizard fields. @@ -520,30 +574,6 @@ class BibleImportForm(OpenLPWizard): self.on_web_source_combo_box_index_changed(WebDownload.Crosswalk) 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): """ Prepare the UI for the import. @@ -583,14 +613,15 @@ class BibleImportForm(OpenLPWizard): self.progress_bar.setMaximum(1) download_location = self.field('web_location') 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( BibleFormat.WebDownload, name=license_version, download_source=WebDownload.Names[download_location], download_name=bible, proxy_server=self.field('proxy_server'), 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: # Import an Zefania bible. diff --git a/openlp/plugins/bibles/lib/http.py b/openlp/plugins/bibles/lib/http.py index 7334dd259..fbf18d98e 100644 --- a/openlp/plugins/bibles/lib/http.py +++ b/openlp/plugins/bibles/lib/http.py @@ -50,6 +50,38 @@ UGLY_CHARS = { } 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__) @@ -222,6 +254,8 @@ class BGExtract(RegistryProperties): if not soup: return None div = soup.find('div', 'result-text-style-normal') + if not div: + return None self._clean_soup(div) span_list = div.find_all('span', 'text') log.debug('Span list: %s', span_list) @@ -278,6 +312,42 @@ class BGExtract(RegistryProperties): books.append(book.contents[0]) 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): """ @@ -338,6 +408,43 @@ class BSExtract(RegistryProperties): content = content.find_all('li') 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): """ @@ -408,6 +515,49 @@ class CWExtract(RegistryProperties): books.append(book.contents[0]) 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): log.info('%s HTTPBible loaded', __name__) @@ -428,6 +578,7 @@ class HTTPBible(BibleDB, RegistryProperties): self.proxy_server = None self.proxy_username = None self.proxy_password = None + self.language_id = None if 'path' in kwargs: self.path = kwargs['path'] if 'proxy_server' in kwargs: @@ -436,6 +587,8 @@ class HTTPBible(BibleDB, RegistryProperties): self.proxy_username = kwargs['proxy_username'] if 'proxy_password' in kwargs: self.proxy_password = kwargs['proxy_password'] + if 'language_id' in kwargs: + self.language_id = kwargs['language_id'] def do_import(self, bible_name=None): """ @@ -468,13 +621,11 @@ class HTTPBible(BibleDB, RegistryProperties): return False self.wizard.progress_bar.setMaximum(len(books) + 2) self.wizard.increment_progress_bar(translate('BiblesPlugin.HTTPBible', 'Registering Language...')) - bible = BiblesResourcesDB.get_webbible(self.download_name, self.download_source.lower()) - if bible['language_id']: - language_id = bible['language_id'] - self.save_meta('language_id', language_id) + if self.language_id: + self.save_meta('language_id', self.language_id) else: - language_id = self.get_language(bible_name) - if not language_id: + self.language_id = self.get_language(bible_name) + if not self.language_id: log.error('Importing books from %s failed' % self.filename) return False for book in books: @@ -482,7 +633,7 @@ class HTTPBible(BibleDB, RegistryProperties): break self.wizard.increment_progress_bar(translate( 'BiblesPlugin.HTTPBible', 'Importing %s...', 'Importing ...') % 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: log.error('Importing books from %s - download name: "%s" failed' % (self.download_source, self.download_name)) diff --git a/openlp/plugins/songs/forms/songselectform.py b/openlp/plugins/songs/forms/songselectform.py index c208988f7..e885d0269 100755 --- a/openlp/plugins/songs/forms/songselectform.py +++ b/openlp/plugins/songs/forms/songselectform.py @@ -187,6 +187,14 @@ class SongSelectForm(QtGui.QDialog, Ui_SongSelectDialog): self.application.process_events() # Get the full song 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 self.title_edit.setText(song['title']) self.copyright_edit.setText(song['copyright']) @@ -359,15 +367,11 @@ class SongSelectForm(QtGui.QDialog, Ui_SongSelectDialog): Import a song from SongSelect. """ self.song_select_importer.save_song(self.song) - question_dialog = QtGui.QMessageBox() - question_dialog.setWindowTitle(translate('SongsPlugin.SongSelectForm', 'Song Imported')) - question_dialog.setText(translate('SongsPlugin.SongSelectForm', 'Your song has been imported, would you like ' - 'to exit now, or import more songs?')) - question_dialog.addButton(QtGui.QPushButton(translate('SongsPlugin.SongSelectForm', 'Import More Songs')), - QtGui.QMessageBox.YesRole) - question_dialog.addButton(QtGui.QPushButton(translate('SongsPlugin.SongSelectForm', 'Exit Now')), - QtGui.QMessageBox.NoRole) - if question_dialog.exec_() == QtGui.QMessageBox.Yes: + if QtGui.QMessageBox.question(self, translate('SongsPlugin.SongSelectForm', 'Song Imported'), + translate('SongsPlugin.SongSelectForm', 'Your song has been imported, would you ' + 'like to import more songs?'), + QtGui.QMessageBox.Yes | QtGui.QMessageBox.No, + QtGui.QMessageBox.Yes) == QtGui.QMessageBox.Yes: self.on_back_button_clicked() else: self.application.process_events() diff --git a/tests/functional/openlp_plugins/songs/test_songselect.py b/tests/functional/openlp_plugins/songs/test_songselect.py index 0357f4477..a086d15fd 100644 --- a/tests/functional/openlp_plugins/songs/test_songselect.py +++ b/tests/functional/openlp_plugins/songs/test_songselect.py @@ -23,361 +23,363 @@ This module contains tests for the CCLI SongSelect importer. """ import os - from unittest import TestCase 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.plugins.songs.forms.songselectform import SongSelectForm -from openlp.plugins.songs.lib import Author, Song, VerseType +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.importers.cclifile import CCLIFileImport +from tests.functional import MagicMock, patch, call +from tests.helpers.testmixin import TestMixin + class TestSongSelectImport(TestCase, TestMixin): """ 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 """ # 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 - importer = SongSelectImport(None) + # WHEN: An object is instantiated + importer = SongSelectImport(None) - # THEN: The object should have the correct properties - self.assertIsNone(importer.db_manager, 'The db_manager should be None') - self.assertIsNotNone(importer.html_parser, 'There should be a valid html_parser 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') + # THEN: The object should have the correct properties + self.assertIsNone(importer.db_manager, 'The db_manager should be None') + self.assertIsNotNone(importer.html_parser, 'There should be a valid html_parser 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') - 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 """ # 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_build_opener.return_value = mocked_opener - mocked_login_page = MagicMock() - mocked_login_page.find.return_value = {'value': 'blah'} - MockedBeautifulSoup.return_value = mocked_login_page - mock_callback = MagicMock() - importer = SongSelectImport(None) + mocked_opener = MagicMock() + mocked_build_opener.return_value = mocked_opener + mocked_login_page = MagicMock() + mocked_login_page.find.return_value = {'value': 'blah'} + MockedBeautifulSoup.return_value = mocked_login_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) + # 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 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_opener.open.call_count, 'opener should have been called twice') - self.assertFalse(result, 'The login method should have returned False') + # 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_opener.open.call_count, 'opener should have been called twice') + 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 """ # 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_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 - mock_callback = MagicMock() - importer = SongSelectImport(None) + mocked_opener = MagicMock() + 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 + mock_callback = MagicMock() + importer = SongSelectImport(None) - # WHEN: The login method is called after being rigged to fail - result = importer.login('username', 'password', mock_callback) + # 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') - self.assertEqual(2, mocked_opener.open.call_count, 'opener should have been called twice') - self.assertTrue(result, 'The login method should have returned True') + # 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_opener.open.call_count, 'opener should have been called twice') + 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 """ # 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_build_opener.return_value = mocked_opener - importer = SongSelectImport(None) + mocked_opener = MagicMock() + mocked_build_opener.return_value = mocked_opener + importer = SongSelectImport(None) - # WHEN: The login method is called after being rigged to fail - importer.logout() + # WHEN: The login method is called after being rigged to fail + importer.logout() - # THEN: The opener is called once with the logout url - self.assertEqual(1, mocked_opener.open.call_count, 'opener should have been called once') - mocked_opener.open.assert_called_with(LOGOUT_URL) + # THEN: The opener is called once with the logout url + self.assertEqual(1, mocked_opener.open.call_count, 'opener should have been called once') + 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 """ # 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_build_opener.return_value = mocked_opener - mocked_results_page = MagicMock() - mocked_results_page.find_all.return_value = [] - MockedBeautifulSoup.return_value = mocked_results_page - mock_callback = MagicMock() - importer = SongSelectImport(None) + mocked_opener = MagicMock() + mocked_build_opener.return_value = mocked_opener + mocked_results_page = MagicMock() + mocked_results_page.find_all.return_value = [] + MockedBeautifulSoup.return_value = mocked_results_page + mock_callback = MagicMock() + importer = SongSelectImport(None) - # WHEN: The login method is called after being rigged to fail - results = importer.search('text', 1000, mock_callback) + # WHEN: The login method is called after being rigged to fail + results = importer.search('text', 1000, mock_callback) - # THEN: callback was never called, open was called once, find_all was called once, an empty list returned - self.assertEqual(0, mock_callback.call_count, 'callback should not have been called') - self.assertEqual(1, mocked_opener.open.call_count, 'open should have been called once') - self.assertEqual(1, mocked_results_page.find_all.call_count, 'find_all should have been called once') - mocked_results_page.find_all.assert_called_with('li', 'result pane') - self.assertEqual([], results, 'The search method should have returned an empty list') + # THEN: callback was never called, open was called once, find_all was called once, an empty list returned + self.assertEqual(0, mock_callback.call_count, 'callback should not have been called') + self.assertEqual(1, mocked_opener.open.call_count, 'open should have been called once') + self.assertEqual(1, mocked_results_page.find_all.call_count, 'find_all should have been called once') + mocked_results_page.find_all.assert_called_with('li', 'result pane') + 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 """ # 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 - mocked_result1 = MagicMock() - mocked_result1.find.side_effect = [MagicMock(string='Title 1'), {'href': '/url1'}] - mocked_result1.find_all.return_value = [MagicMock(string='Author 1-1'), MagicMock(string='Author 1-2')] - # second search result - mocked_result2 = MagicMock() - mocked_result2.find.side_effect = [MagicMock(string='Title 2'), {'href': '/url2'}] - mocked_result2.find_all.return_value = [MagicMock(string='Author 2-1'), MagicMock(string='Author 2-2')] - # rest of the stuff - mocked_opener = MagicMock() - mocked_build_opener.return_value = mocked_opener - mocked_results_page = MagicMock() - mocked_results_page.find_all.side_effect = [[mocked_result1, mocked_result2], []] - MockedBeautifulSoup.return_value = mocked_results_page - mock_callback = MagicMock() - importer = SongSelectImport(None) + # first search result + mocked_result1 = MagicMock() + mocked_result1.find.side_effect = [MagicMock(string='Title 1'), {'href': '/url1'}] + mocked_result1.find_all.return_value = [MagicMock(string='Author 1-1'), MagicMock(string='Author 1-2')] + # second search result + mocked_result2 = MagicMock() + mocked_result2.find.side_effect = [MagicMock(string='Title 2'), {'href': '/url2'}] + mocked_result2.find_all.return_value = [MagicMock(string='Author 2-1'), MagicMock(string='Author 2-2')] + # rest of the stuff + mocked_opener = MagicMock() + mocked_build_opener.return_value = mocked_opener + mocked_results_page = MagicMock() + mocked_results_page.find_all.side_effect = [[mocked_result1, mocked_result2], []] + MockedBeautifulSoup.return_value = mocked_results_page + mock_callback = MagicMock() + importer = SongSelectImport(None) - # WHEN: The login method is called after being rigged to fail - results = importer.search('text', 1000, mock_callback) + # WHEN: The login method is called after being rigged to fail + results = importer.search('text', 1000, mock_callback) - # THEN: callback was never called, open was called once, find_all was called once, an empty list returned - self.assertEqual(2, mock_callback.call_count, 'callback should have been called twice') - self.assertEqual(2, mocked_opener.open.call_count, 'open should have been called twice') - self.assertEqual(2, mocked_results_page.find_all.call_count, 'find_all should have been called twice') - mocked_results_page.find_all.assert_called_with('li', 'result pane') - expected_list = [ - {'title': 'Title 1', 'authors': ['Author 1-1', 'Author 1-2'], 'link': BASE_URL + '/url1'}, - {'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') + # THEN: callback was never called, open was called once, find_all was called once, an empty list returned + self.assertEqual(2, mock_callback.call_count, 'callback should have been called twice') + self.assertEqual(2, mocked_opener.open.call_count, 'open should have been called twice') + self.assertEqual(2, mocked_results_page.find_all.call_count, 'find_all should have been called twice') + mocked_results_page.find_all.assert_called_with('li', 'result pane') + expected_list = [ + {'title': 'Title 1', 'authors': ['Author 1-1', 'Author 1-2'], 'link': BASE_URL + '/url1'}, + {'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') - 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) """ # 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 - mocked_result1 = MagicMock() - mocked_result1.find.side_effect = [MagicMock(string='Title 1'), {'href': '/url1'}] - mocked_result1.find_all.return_value = [MagicMock(string='Author 1-1'), MagicMock(string='Author 1-2')] - # second search result - mocked_result2 = MagicMock() - mocked_result2.find.side_effect = [MagicMock(string='Title 2'), {'href': '/url2'}] - mocked_result2.find_all.return_value = [MagicMock(string='Author 2-1'), MagicMock(string='Author 2-2')] - # third search result - mocked_result3 = MagicMock() - mocked_result3.find.side_effect = [MagicMock(string='Title 3'), {'href': '/url3'}] - mocked_result3.find_all.return_value = [MagicMock(string='Author 3-1'), MagicMock(string='Author 3-2')] - # rest of the stuff - mocked_opener = MagicMock() - mocked_build_opener.return_value = mocked_opener - mocked_results_page = MagicMock() - mocked_results_page.find_all.side_effect = [[mocked_result1, mocked_result2, mocked_result3], []] - MockedBeautifulSoup.return_value = mocked_results_page - mock_callback = MagicMock() - importer = SongSelectImport(None) + # first search result + mocked_result1 = MagicMock() + mocked_result1.find.side_effect = [MagicMock(string='Title 1'), {'href': '/url1'}] + mocked_result1.find_all.return_value = [MagicMock(string='Author 1-1'), MagicMock(string='Author 1-2')] + # second search result + mocked_result2 = MagicMock() + mocked_result2.find.side_effect = [MagicMock(string='Title 2'), {'href': '/url2'}] + mocked_result2.find_all.return_value = [MagicMock(string='Author 2-1'), MagicMock(string='Author 2-2')] + # third search result + mocked_result3 = MagicMock() + mocked_result3.find.side_effect = [MagicMock(string='Title 3'), {'href': '/url3'}] + mocked_result3.find_all.return_value = [MagicMock(string='Author 3-1'), MagicMock(string='Author 3-2')] + # rest of the stuff + mocked_opener = MagicMock() + mocked_build_opener.return_value = mocked_opener + mocked_results_page = MagicMock() + mocked_results_page.find_all.side_effect = [[mocked_result1, mocked_result2, mocked_result3], []] + MockedBeautifulSoup.return_value = mocked_results_page + mock_callback = MagicMock() + importer = SongSelectImport(None) - # WHEN: The login method is called after being rigged to fail - results = importer.search('text', 2, mock_callback) + # WHEN: The login method is called after being rigged to fail + results = importer.search('text', 2, mock_callback) - # THEN: callback was never called, open was called once, find_all was called once, an empty list returned - self.assertEqual(2, mock_callback.call_count, 'callback should have been called twice') - self.assertEqual(2, mocked_opener.open.call_count, 'open should have been called twice') - self.assertEqual(2, mocked_results_page.find_all.call_count, 'find_all should have been called twice') - mocked_results_page.find_all.assert_called_with('li', 'result pane') - expected_list = [{'title': 'Title 1', 'authors': ['Author 1-1', 'Author 1-2'], 'link': BASE_URL + '/url1'}, - {'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') + # THEN: callback was never called, open was called once, find_all was called once, an empty list returned + self.assertEqual(2, mock_callback.call_count, 'callback should have been called twice') + self.assertEqual(2, mocked_opener.open.call_count, 'open should have been called twice') + self.assertEqual(2, mocked_results_page.find_all.call_count, 'find_all should have been called twice') + mocked_results_page.find_all.assert_called_with('li', 'result pane') + expected_list = [{'title': 'Title 1', 'authors': ['Author 1-1', 'Author 1-2'], 'link': BASE_URL + '/url1'}, + {'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') - 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 """ # 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_build_opener.return_value = mocked_opener - mocked_opener.open.read.side_effect = URLError('[Errno -2] Name or service not known') - mocked_callback = MagicMock() - importer = SongSelectImport(None) + mocked_opener = MagicMock() + mocked_build_opener.return_value = mocked_opener + mocked_opener.open.read.side_effect = URLError('[Errno -2] Name or service not known') + mocked_callback = MagicMock() + importer = SongSelectImport(None) - # WHEN: get_song is called - result = importer.get_song({'link': 'link'}, callback=mocked_callback) + # WHEN: get_song is called + result = importer.get_song({'link': 'link'}, callback=mocked_callback) - # THEN: The callback should have been called once and None should be returned - mocked_callback.assert_called_with() - self.assertIsNone(result, 'The get_song() method should have returned None') + # THEN: The callback should have been called once and None should be returned + mocked_callback.assert_called_with() + 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 """ # 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')] - mocked_callback = MagicMock() - importer = SongSelectImport(None) + MockedBeautifulSoup.side_effect = [None, TypeError('Test Error')] + mocked_callback = MagicMock() + importer = SongSelectImport(None) - # WHEN: get_song is called - result = importer.get_song({'link': 'link'}, callback=mocked_callback) + # WHEN: get_song is called + result = importer.get_song({'link': 'link'}, callback=mocked_callback) - # THEN: The callback should have been called twice and None should be returned - 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') + # THEN: The callback should have been called twice and None should be returned + 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') - 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 """ # 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_copyright = MagicMock() - mocked_copyright.find_all.return_value = [MagicMock(string='Copyright 1'), MagicMock(string='Copyright 2')] - mocked_song_page.find.side_effect = [ - mocked_copyright, - MagicMock(find=MagicMock(string='CCLI: 123456')) - ] - mocked_lyrics_page = MagicMock() - mocked_find_all = MagicMock() - mocked_find_all.side_effect = [ - [ - MagicMock(contents='The Lord told Noah: there\'s gonna be a floody, floody'), - MagicMock(contents='So, rise and shine, and give God the glory, glory'), - MagicMock(contents='The Lord told Noah to build him an arky, arky') - ], - [MagicMock(string='Verse 1'), MagicMock(string='Chorus'), MagicMock(string='Verse 2')] - ] - mocked_lyrics_page.find.return_value = MagicMock(find_all=mocked_find_all) - MockedBeautifulSoup.side_effect = [mocked_song_page, mocked_lyrics_page] - mocked_callback = MagicMock() - importer = SongSelectImport(None) - fake_song = {'title': 'Title', 'authors': ['Author 1', 'Author 2'], 'link': 'url'} + mocked_song_page = MagicMock() + mocked_copyright = MagicMock() + mocked_copyright.find_all.return_value = [MagicMock(string='Copyright 1'), MagicMock(string='Copyright 2')] + mocked_song_page.find.side_effect = [ + mocked_copyright, + MagicMock(find=MagicMock(string='CCLI: 123456')) + ] + mocked_lyrics_page = MagicMock() + mocked_find_all = MagicMock() + mocked_find_all.side_effect = [ + [ + MagicMock(contents='The Lord told Noah: there\'s gonna be a floody, floody'), + MagicMock(contents='So, rise and shine, and give God the glory, glory'), + MagicMock(contents='The Lord told Noah to build him an arky, arky') + ], + [MagicMock(string='Verse 1'), MagicMock(string='Chorus'), MagicMock(string='Verse 2')] + ] + mocked_lyrics_page.find.return_value = MagicMock(find_all=mocked_find_all) + MockedBeautifulSoup.side_effect = [mocked_song_page, mocked_lyrics_page] + mocked_callback = MagicMock() + importer = SongSelectImport(None) + fake_song = {'title': 'Title', 'authors': ['Author 1', 'Author 2'], 'link': 'url'} - # WHEN: get_song is called - result = importer.get_song(fake_song, callback=mocked_callback) + # WHEN: get_song is called + result = importer.get_song(fake_song, callback=mocked_callback) - # THEN: The callback should have been called three times and the song should be returned - self.assertEqual(3, mocked_callback.call_count, 'The callback should have been called twice') - self.assertIsNotNone(result, 'The get_song() method should have returned a song dictionary') - self.assertEqual(2, mocked_lyrics_page.find.call_count, 'The find() method should have been called twice') - self.assertEqual(2, mocked_find_all.call_count, 'The find_all() method should have been called twice') - self.assertEqual([call('section', 'lyrics'), call('section', 'lyrics')], - mocked_lyrics_page.find.call_args_list, - 'The find() method should have been called with the right arguments') - self.assertEqual([call('p'), call('h3')], mocked_find_all.call_args_list, - 'The find_all() method should have been called with the right arguments') - self.assertIn('copyright', result, 'The returned song should have a copyright') - self.assertIn('ccli_number', result, 'The returned song should have a CCLI number') - self.assertIn('verses', result, 'The returned song should have verses') - self.assertEqual(3, len(result['verses']), 'Three verses should have been returned') + # THEN: The callback should have been called three times and the song should be returned + self.assertEqual(3, mocked_callback.call_count, 'The callback should have been called twice') + self.assertIsNotNone(result, 'The get_song() method should have returned a song dictionary') + self.assertEqual(2, mocked_lyrics_page.find.call_count, 'The find() method should have been called twice') + self.assertEqual(2, mocked_find_all.call_count, 'The find_all() method should have been called twice') + self.assertEqual([call('section', 'lyrics'), call('section', 'lyrics')], + mocked_lyrics_page.find.call_args_list, + 'The find() method should have been called with the right arguments') + self.assertEqual([call('p'), call('h3')], mocked_find_all.call_args_list, + 'The find_all() method should have been called with the right arguments') + self.assertIn('copyright', result, 'The returned song should have a copyright') + self.assertIn('ccli_number', result, 'The returned song should have a CCLI number') + self.assertIn('verses', result, 'The returned song should have verses') + 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 """ # 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 = { - 'title': 'Arky Arky', - 'authors': ['Public Domain'], - 'verses': [ - {'label': 'Verse 1', 'lyrics': 'The Lord told Noah: there\'s gonna be a floody, floody'}, - {'label': 'Chorus 1', 'lyrics': 'So, rise and shine, and give God the glory, glory'}, - {'label': 'Verse 2', 'lyrics': 'The Lord told Noah to build him an arky, arky'} - ], - 'copyright': 'Public Domain', - 'ccli_number': '123456' - } - MockedAuthor.display_name.__eq__.return_value = False - mocked_db_manager = MagicMock() - mocked_db_manager.get_object_filtered.return_value = None - importer = SongSelectImport(mocked_db_manager) + song_dict = { + 'title': 'Arky Arky', + 'authors': ['Public Domain'], + 'verses': [ + {'label': 'Verse 1', 'lyrics': 'The Lord told Noah: there\'s gonna be a floody, floody'}, + {'label': 'Chorus 1', 'lyrics': 'So, rise and shine, and give God the glory, glory'}, + {'label': 'Verse 2', 'lyrics': 'The Lord told Noah to build him an arky, arky'} + ], + 'copyright': 'Public Domain', + 'ccli_number': '123456' + } + MockedAuthor.display_name.__eq__.return_value = False + mocked_db_manager = MagicMock() + mocked_db_manager.get_object_filtered.return_value = None + importer = SongSelectImport(mocked_db_manager) - # WHEN: The song is saved to the database - result = importer.save_song(song_dict) + # WHEN: The song is saved to the database + result = importer.save_song(song_dict) - # THEN: The return value should be a Song class and the mocked_db_manager should have been called - self.assertIsInstance(result, Song, 'The returned value should be a Song object') - mocked_clean_song.assert_called_with(mocked_db_manager, result) - self.assertEqual(2, mocked_db_manager.save_object.call_count, - 'The save_object() method should have been called twice') - mocked_db_manager.get_object_filtered.assert_called_with(MockedAuthor, False) - MockedAuthor.populate.assert_called_with(first_name='Public', last_name='Domain', - display_name='Public Domain') - self.assertEqual(1, len(result.authors_songs), 'There should only be one author') + # THEN: The return value should be a Song class and the mocked_db_manager should have been called + self.assertIsInstance(result, Song, 'The returned value should be a Song object') + mocked_clean_song.assert_called_with(mocked_db_manager, result) + self.assertEqual(2, mocked_db_manager.save_object.call_count, + 'The save_object() method should have been called twice') + mocked_db_manager.get_object_filtered.assert_called_with(MockedAuthor, False) + MockedAuthor.populate.assert_called_with(first_name='Public', last_name='Domain', + display_name='Public Domain') + 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 """ # 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 = { - 'title': 'Arky Arky', - 'authors': ['Public Domain'], - 'verses': [ - {'label': 'Verse 1', 'lyrics': 'The Lord told Noah: there\'s gonna be a floody, floody'}, - {'label': 'Chorus 1', 'lyrics': 'So, rise and shine, and give God the glory, glory'}, - {'label': 'Verse 2', 'lyrics': 'The Lord told Noah to build him an arky, arky'} - ], - 'copyright': 'Public Domain', - 'ccli_number': '123456' - } - MockedAuthor.display_name.__eq__.return_value = False - mocked_db_manager = MagicMock() - mocked_db_manager.get_object_filtered.return_value = MagicMock() - importer = SongSelectImport(mocked_db_manager) + song_dict = { + 'title': 'Arky Arky', + 'authors': ['Public Domain'], + 'verses': [ + {'label': 'Verse 1', 'lyrics': 'The Lord told Noah: there\'s gonna be a floody, floody'}, + {'label': 'Chorus 1', 'lyrics': 'So, rise and shine, and give God the glory, glory'}, + {'label': 'Verse 2', 'lyrics': 'The Lord told Noah to build him an arky, arky'} + ], + 'copyright': 'Public Domain', + 'ccli_number': '123456' + } + MockedAuthor.display_name.__eq__.return_value = False + mocked_db_manager = MagicMock() + mocked_db_manager.get_object_filtered.return_value = MagicMock() + importer = SongSelectImport(mocked_db_manager) - # WHEN: The song is saved to the database - result = importer.save_song(song_dict) + # WHEN: The song is saved to the database + result = importer.save_song(song_dict) - # THEN: The return value should be a Song class and the mocked_db_manager should have been called - self.assertIsInstance(result, Song, 'The returned value should be a Song object') - mocked_clean_song.assert_called_with(mocked_db_manager, result) - self.assertEqual(2, mocked_db_manager.save_object.call_count, - 'The save_object() method should have been called twice') - mocked_db_manager.get_object_filtered.assert_called_with(MockedAuthor, False) - self.assertEqual(0, MockedAuthor.populate.call_count, 'A new author should not have been instantiated') - self.assertEqual(1, len(result.authors_songs), 'There should only be one author') + # THEN: The return value should be a Song class and the mocked_db_manager should have been called + self.assertIsInstance(result, Song, 'The returned value should be a Song object') + mocked_clean_song.assert_called_with(mocked_db_manager, result) + self.assertEqual(2, mocked_db_manager.save_object.call_count, + 'The save_object() method should have been called twice') + mocked_db_manager.get_object_filtered.assert_called_with(MockedAuthor, False) + self.assertEqual(0, MockedAuthor.populate.call_count, 'A new author should not have been instantiated') + self.assertEqual(1, len(result.authors_songs), 'There should only be one author') class TestSongSelectForm(TestCase, TestMixin): @@ -409,59 +411,219 @@ class TestSongSelectForm(TestCase, TestMixin): 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') - 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 """ # 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.login.return_value = False - MockedSongSelectImport.return_value = mocked_song_select_import - mocked_translate.side_effect = lambda *args: args[1] - ssform = SongSelectForm(None, MagicMock(), MagicMock()) - ssform.initialise() - with patch.object(ssform, 'username_edit') as mocked_username_edit, \ - patch.object(ssform, 'password_edit') as mocked_password_edit, \ - patch.object(ssform, 'save_password_checkbox') as mocked_save_password_checkbox, \ - patch.object(ssform, 'login_button') as mocked_login_button, \ - patch.object(ssform, 'login_spacer') as mocked_login_spacer, \ - patch.object(ssform, 'login_progress_bar') as mocked_login_progress_bar, \ - patch.object(ssform.application, 'process_events') as mocked_process_events: + mocked_song_select_import = MagicMock() + mocked_song_select_import.login.return_value = False + MockedSongSelectImport.return_value = mocked_song_select_import + mocked_translate.side_effect = lambda *args: args[1] + ssform = SongSelectForm(None, MagicMock(), MagicMock()) + ssform.initialise() + with patch.object(ssform, 'username_edit') as mocked_username_edit, \ + patch.object(ssform, 'password_edit') as mocked_password_edit, \ + patch.object(ssform, 'save_password_checkbox') as mocked_save_password_checkbox, \ + patch.object(ssform, 'login_button') as mocked_login_button, \ + patch.object(ssform, 'login_spacer') as mocked_login_spacer, \ + patch.object(ssform, 'login_progress_bar') as mocked_login_progress_bar, \ + patch.object(ssform.application, 'process_events') as mocked_process_events: - # WHEN: The login button is clicked, and the login is rigged to fail - ssform.on_login_button_clicked() + # WHEN: The login button is clicked, and the login is rigged to fail + ssform.on_login_button_clicked() - # THEN: The right things should have happened in the right order - expected_username_calls = [call(False), call(True)] - expected_password_calls = [call(False), call(True)] - expected_save_password_calls = [call(False), call(True)] - expected_login_btn_calls = [call(False), call(True)] - expected_login_spacer_calls = [call(False), call(True)] - expected_login_progress_visible_calls = [call(True), call(False)] - expected_login_progress_value_calls = [call(0), call(0)] - self.assertEqual(expected_username_calls, mocked_username_edit.setEnabled.call_args_list, - 'The username edit should be disabled then enabled') - self.assertEqual(expected_password_calls, mocked_password_edit.setEnabled.call_args_list, - 'The password edit should be disabled then enabled') - self.assertEqual(expected_save_password_calls, mocked_save_password_checkbox.setEnabled.call_args_list, - 'The save password checkbox should be disabled then enabled') - self.assertEqual(expected_login_btn_calls, mocked_login_button.setEnabled.call_args_list, - 'The login button should be disabled then enabled') - self.assertEqual(expected_login_spacer_calls, mocked_login_spacer.setVisible.call_args_list, - 'Thee login spacer should be make invisible, then visible') - self.assertEqual(expected_login_progress_visible_calls, - mocked_login_progress_bar.setVisible.call_args_list, - 'Thee login progress bar should be make visible, then invisible') - self.assertEqual(expected_login_progress_value_calls, mocked_login_progress_bar.setValue.call_args_list, - 'Thee login progress bar should have the right values set') - self.assertEqual(2, mocked_process_events.call_count, - 'The process_events() method should be called twice') - mocked_critical.assert_called_with(ssform, 'Error Logging In', 'There was a problem logging in, ' - 'perhaps your username or password is ' - 'incorrect?') + # THEN: The right things should have happened in the right order + expected_username_calls = [call(False), call(True)] + expected_password_calls = [call(False), call(True)] + expected_save_password_calls = [call(False), call(True)] + expected_login_btn_calls = [call(False), call(True)] + expected_login_spacer_calls = [call(False), call(True)] + expected_login_progress_visible_calls = [call(True), call(False)] + expected_login_progress_value_calls = [call(0), call(0)] + self.assertEqual(expected_username_calls, mocked_username_edit.setEnabled.call_args_list, + 'The username edit should be disabled then enabled') + self.assertEqual(expected_password_calls, mocked_password_edit.setEnabled.call_args_list, + 'The password edit should be disabled then enabled') + self.assertEqual(expected_save_password_calls, mocked_save_password_checkbox.setEnabled.call_args_list, + 'The save password checkbox should be disabled then enabled') + self.assertEqual(expected_login_btn_calls, mocked_login_button.setEnabled.call_args_list, + 'The login button should be disabled then enabled') + self.assertEqual(expected_login_spacer_calls, mocked_login_spacer.setVisible.call_args_list, + 'Thee login spacer should be make invisible, then visible') + self.assertEqual(expected_login_progress_visible_calls, + mocked_login_progress_bar.setVisible.call_args_list, + 'Thee login progress bar should be make visible, then invisible') + self.assertEqual(expected_login_progress_value_calls, mocked_login_progress_bar.setValue.call_args_list, + 'Thee login progress bar should have the right values set') + self.assertEqual(2, mocked_process_events.call_count, + 'The process_events() method should be called twice') + mocked_critical.assert_called_with(ssform, 'Error Logging In', 'There was a problem logging in, ' + 'perhaps your username or password is ' + '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): @@ -480,50 +642,43 @@ class TestSongSelectFileImport(TestCase, TestMixin): self.authors = ['Author One', 'Author Two'] self.topics = ['Adoration', 'Praise'] - def tearDown(self): + def songselect_import_bin_file_test(self): """ - Test cleanups - """ - pass - - def songselect_import_usr_file_test(self): - """ - Verify import SongSelect USR file parses file properly + Verify import SongSelect BIN file parses file properly """ # GIVEN: Text file to import and mocks - copyright = '2011 OpenLP Programmer One (Admin. by OpenLP One) | ' \ - 'Openlp Programmer Two (Admin. by OpenLP Two)' - verses = [ + copyright_bin = '2011 OpenLP Programmer One (Admin. by OpenLP One) | ' \ + 'Openlp Programmer Two (Admin. by OpenLP Two)' + verses_bin = [ ['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] ] + 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'): - # WHEN: We call the song importer song_import.do_import() # 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.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.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.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): """ Verify import SongSelect TEXT file parses file properly """ # GIVEN: Text file to import and mocks - copyright = '© 2011 OpenLP Programmer One (Admin. by OpenLP One)' - verses = [ + copyright_txt = '© 2011 OpenLP Programmer One (Admin. by OpenLP One)' + verses_txt = [ ['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] ] - song_import = CCLIFileImport(manager=None, filename=['{}.txt'.format(self.file_name)], ) - with patch.object(song_import, 'import_wizard'), patch.object(song_import, 'finish'): + song_import = CCLIFileImport(manager=None, filename=['{}.txt'.format(self.file_name)]) + with patch.object(song_import, 'import_wizard'), patch.object(song_import, 'finish'): # WHEN: We call the song importer 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.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.copyright, copyright, 'Copyright should match') - self.assertEquals(song_import.verses, verses, 'Verses should match with test verses') + self.assertEquals(song_import.copyright, copyright_txt, 'Copyright should match') + 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) diff --git a/tests/interfaces/openlp_plugins/bibles/test_lib_http.py b/tests/interfaces/openlp_plugins/bibles/test_lib_http.py index 673fb4455..ff9ce0382 100644 --- a/tests/interfaces/openlp_plugins/bibles/test_lib_http.py +++ b/tests/interfaces/openlp_plugins/bibles/test_lib_http.py @@ -25,7 +25,7 @@ from unittest import TestCase 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 @@ -116,3 +116,46 @@ class TestBibleHTTP(TestCase): # 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' + + 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)