forked from openlp/openlp
Opensong refactors and tests
This commit is contained in:
parent
6ab2686b09
commit
46b6d041cd
@ -23,9 +23,11 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from lxml import etree, objectify
|
from lxml import etree, objectify
|
||||||
|
from zipfile import is_zipfile
|
||||||
|
|
||||||
from openlp.core.common import OpenLPMixin, languages
|
from openlp.core.common import OpenLPMixin, languages
|
||||||
from openlp.core.lib import ValidationError
|
from openlp.core.lib import ValidationError, translate
|
||||||
|
from openlp.core.lib.ui import critical_error_message_box
|
||||||
from openlp.plugins.bibles.lib.db import BibleDB, BiblesResourcesDB
|
from openlp.plugins.bibles.lib.db import BibleDB, BiblesResourcesDB
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
@ -39,6 +41,21 @@ class BibleImport(OpenLPMixin, BibleDB):
|
|||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.filename = kwargs['filename'] if 'filename' in kwargs else None
|
self.filename = kwargs['filename'] if 'filename' in kwargs else None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def is_compressed(file):
|
||||||
|
"""
|
||||||
|
Check if the supplied file is compressed
|
||||||
|
|
||||||
|
:param file: A path to the file to check
|
||||||
|
"""
|
||||||
|
if is_zipfile(file):
|
||||||
|
critical_error_message_box(
|
||||||
|
message=translate('BiblesPlugin.BibleImport',
|
||||||
|
'The file "{file}" you supplied is compressed. You must decompress it before import.'
|
||||||
|
).format(file=file))
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
def get_language_id(self, file_language=None, bible_name=None):
|
def get_language_id(self, file_language=None, bible_name=None):
|
||||||
"""
|
"""
|
||||||
Get the language_id for the language of the bible. Fallback to user input if we cannot do this pragmatically.
|
Get the language_id for the language of the bible. Fallback to user input if we cannot do this pragmatically.
|
||||||
|
@ -21,12 +21,12 @@
|
|||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from lxml import etree, objectify
|
from lxml import etree
|
||||||
|
|
||||||
from openlp.core.common import translate, trace_error_handler
|
from openlp.core.common import translate, trace_error_handler
|
||||||
|
from openlp.core.lib.exceptions import ValidationError
|
||||||
from openlp.core.lib.ui import critical_error_message_box
|
from openlp.core.lib.ui import critical_error_message_box
|
||||||
from openlp.plugins.bibles.lib.bibleimport import BibleImport
|
from openlp.plugins.bibles.lib.bibleimport import BibleImport
|
||||||
from openlp.plugins.bibles.lib.db import BibleDB, BiblesResourcesDB
|
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
@ -51,12 +51,69 @@ class OpenSongBible(BibleImport):
|
|||||||
verse_text += element.tail
|
verse_text += element.tail
|
||||||
return verse_text
|
return verse_text
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def process_chapter_no(number, previous_number):
|
||||||
|
"""
|
||||||
|
Process the chapter number
|
||||||
|
|
||||||
|
:param number: The raw data from the xml
|
||||||
|
:param previous_number: The previous chapter number
|
||||||
|
:return: Number of current chapter. (Int)
|
||||||
|
"""
|
||||||
|
if number:
|
||||||
|
return int(number.split()[-1])
|
||||||
|
return previous_number + 1
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def process_verse_no(number, previous_number):
|
||||||
|
"""
|
||||||
|
Process the verse number retrieved from the xml
|
||||||
|
|
||||||
|
:param number: The raw data from the xml
|
||||||
|
:param previous_number: The previous verse number
|
||||||
|
:return: Number of current verse. (Int)
|
||||||
|
"""
|
||||||
|
if not number:
|
||||||
|
return previous_number + 1
|
||||||
|
try:
|
||||||
|
return int(number)
|
||||||
|
except ValueError:
|
||||||
|
verse_parts = number.split('-')
|
||||||
|
if len(verse_parts) > 1:
|
||||||
|
number = int(verse_parts[0])
|
||||||
|
return number
|
||||||
|
except TypeError:
|
||||||
|
log.warning('Illegal verse number: {verse_no}'.format(verse_no=str(number)))
|
||||||
|
return previous_number + 1
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def validate_file(filename):
|
||||||
|
"""
|
||||||
|
Validate the supplied file
|
||||||
|
|
||||||
|
:param filename: The supplied file
|
||||||
|
:return: True if valid. ValidationError is raised otherwise.
|
||||||
|
"""
|
||||||
|
if BibleImport.is_compressed():
|
||||||
|
raise ValidationError(msg='Compressed file')
|
||||||
|
bible = BibleImport.parse_xml(filename, use_objectify=True)
|
||||||
|
root_tag = bible.tag.lower()
|
||||||
|
if root_tag != 'bible':
|
||||||
|
if root_tag == 'xmlbible':
|
||||||
|
# Zefania bibles have a root tag of XMLBIBLE". Sometimes these bibles are referred to as 'OpenSong'
|
||||||
|
critical_error_message_box(
|
||||||
|
message=translate('BiblesPlugin.OpenSongImport',
|
||||||
|
'Incorrect Bible file type supplied. This looks like a Zefania XML bible, '
|
||||||
|
'please use the Zefania import option.'))
|
||||||
|
raise ValidationError(msg='Invalid xml.')
|
||||||
|
return True
|
||||||
|
|
||||||
def do_import(self, bible_name=None):
|
def do_import(self, bible_name=None):
|
||||||
"""
|
"""
|
||||||
Loads a Bible from file.
|
Loads a Bible from file.
|
||||||
"""
|
"""
|
||||||
|
self.validate_file(self.filename)
|
||||||
log.debug('Starting OpenSong import from "{name}"'.format(name=self.filename))
|
log.debug('Starting OpenSong import from "{name}"'.format(name=self.filename))
|
||||||
success = True
|
|
||||||
try:
|
try:
|
||||||
bible = self.parse_xml(self.filename, use_objectify=True)
|
bible = self.parse_xml(self.filename, use_objectify=True)
|
||||||
# Check that we're not trying to import a Zefania XML bible, it is sometimes refered to as 'OpenSong'
|
# Check that we're not trying to import a Zefania XML bible, it is sometimes refered to as 'OpenSong'
|
||||||
@ -78,46 +135,21 @@ class OpenSongBible(BibleImport):
|
|||||||
for chapter in book.c:
|
for chapter in book.c:
|
||||||
if self.stop_import_flag:
|
if self.stop_import_flag:
|
||||||
break
|
break
|
||||||
number = chapter.attrib['n']
|
chapter_number = self.process_chapter_no(chapter.attrib['n'], chapter_number)
|
||||||
if number:
|
|
||||||
chapter_number = int(number.split()[-1])
|
|
||||||
else:
|
|
||||||
chapter_number += 1
|
|
||||||
verse_number = 0
|
verse_number = 0
|
||||||
for verse in chapter.v:
|
for verse in chapter.v:
|
||||||
if self.stop_import_flag:
|
if self.stop_import_flag:
|
||||||
break
|
break
|
||||||
number = verse.attrib['n']
|
verse_number = self.process_verse_no(verse.attrib['n'], verse_number)
|
||||||
if number:
|
|
||||||
try:
|
|
||||||
number = int(number)
|
|
||||||
except ValueError:
|
|
||||||
verse_parts = number.split('-')
|
|
||||||
if len(verse_parts) > 1:
|
|
||||||
number = int(verse_parts[0])
|
|
||||||
except TypeError:
|
|
||||||
log.warning('Illegal verse number: {verse:d}'.format(verse=verse.attrib['n']))
|
|
||||||
verse_number = number
|
|
||||||
else:
|
|
||||||
verse_number += 1
|
|
||||||
self.create_verse(db_book.id, chapter_number, verse_number, self.get_text(verse))
|
self.create_verse(db_book.id, chapter_number, verse_number, self.get_text(verse))
|
||||||
self.wizard.increment_progress_bar(translate('BiblesPlugin.Opensong',
|
self.wizard.increment_progress_bar(translate('BiblesPlugin.Opensong',
|
||||||
'Importing {name} {chapter}...'
|
'Importing {name} {chapter}...'
|
||||||
).format(name=db_book.name, chapter=chapter_number))
|
).format(name=db_book.name, chapter=chapter_number))
|
||||||
self.session.commit()
|
self.session.commit()
|
||||||
self.application.process_events()
|
self.application.process_events()
|
||||||
except etree.XMLSyntaxError as inst:
|
except (AttributeError, ValidationError, etree.XMLSyntaxError):
|
||||||
trace_error_handler(log)
|
|
||||||
critical_error_message_box(
|
|
||||||
message=translate('BiblesPlugin.OpenSongImport',
|
|
||||||
'Incorrect Bible file type supplied. OpenSong Bibles may be '
|
|
||||||
'compressed. You must decompress them before import.'))
|
|
||||||
log.exception(inst)
|
|
||||||
success = False
|
|
||||||
except (IOError, AttributeError):
|
|
||||||
log.exception('Loading Bible from OpenSong file failed')
|
log.exception('Loading Bible from OpenSong file failed')
|
||||||
success = False
|
trace_error_handler(log)
|
||||||
|
return False
|
||||||
if self.stop_import_flag:
|
if self.stop_import_flag:
|
||||||
return False
|
return False
|
||||||
else:
|
|
||||||
return success
|
|
||||||
|
@ -27,9 +27,13 @@ import os
|
|||||||
import json
|
import json
|
||||||
from unittest import TestCase
|
from unittest import TestCase
|
||||||
|
|
||||||
|
|
||||||
|
from lxml import objectify
|
||||||
|
|
||||||
from tests.functional import MagicMock, patch
|
from tests.functional import MagicMock, patch
|
||||||
|
from openlp.core.lib.exceptions import ValidationError
|
||||||
from openlp.plugins.bibles.lib.importers.opensong import OpenSongBible
|
from openlp.plugins.bibles.lib.importers.opensong import OpenSongBible
|
||||||
from openlp.plugins.bibles.lib.db import BibleDB
|
from openlp.plugins.bibles.lib.bibleimport import BibleImport
|
||||||
|
|
||||||
TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__),
|
TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__),
|
||||||
'..', '..', '..', 'resources', 'bibles'))
|
'..', '..', '..', 'resources', 'bibles'))
|
||||||
@ -41,14 +45,12 @@ class TestOpenSongImport(TestCase):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.registry_patcher = patch('openlp.plugins.bibles.lib.db.Registry')
|
|
||||||
self.registry_patcher.start()
|
|
||||||
self.manager_patcher = patch('openlp.plugins.bibles.lib.db.Manager')
|
self.manager_patcher = patch('openlp.plugins.bibles.lib.db.Manager')
|
||||||
|
self.addCleanup(self.manager_patcher.stop)
|
||||||
self.manager_patcher.start()
|
self.manager_patcher.start()
|
||||||
|
self.registry_patcher = patch('openlp.plugins.bibles.lib.db.Registry')
|
||||||
def tearDown(self):
|
self.addCleanup(self.registry_patcher.stop)
|
||||||
self.registry_patcher.stop()
|
self.registry_patcher.start()
|
||||||
self.manager_patcher.stop()
|
|
||||||
|
|
||||||
def test_create_importer(self):
|
def test_create_importer(self):
|
||||||
"""
|
"""
|
||||||
@ -61,7 +63,134 @@ class TestOpenSongImport(TestCase):
|
|||||||
importer = OpenSongBible(mocked_manager, path='.', name='.', filename='')
|
importer = OpenSongBible(mocked_manager, path='.', name='.', filename='')
|
||||||
|
|
||||||
# THEN: The importer should be an instance of BibleDB
|
# THEN: The importer should be an instance of BibleDB
|
||||||
self.assertIsInstance(importer, BibleDB)
|
self.assertIsInstance(importer, BibleImport)
|
||||||
|
|
||||||
|
def process_chapter_no_test(self):
|
||||||
|
"""
|
||||||
|
Test process_chapter_no when supplied with chapter number and an instance of OpenSongBible
|
||||||
|
"""
|
||||||
|
# GIVEN: The number 10 represented as a string
|
||||||
|
# WHEN: Calling process_chapter_no
|
||||||
|
result = OpenSongBible.process_chapter_no('10', 0)
|
||||||
|
|
||||||
|
# THEN: The 10 should be returned as an Int
|
||||||
|
self.assertEqual(result, 10)
|
||||||
|
|
||||||
|
def process_chapter_no_empty_attribute_test(self):
|
||||||
|
"""
|
||||||
|
Test process_chapter_no when the chapter number is an empty string. (Bug #1074727)
|
||||||
|
"""
|
||||||
|
# GIVEN: An empty string, and the previous chapter number set as 12 and an instance of OpenSongBible
|
||||||
|
# WHEN: Calling process_chapter_no
|
||||||
|
result = OpenSongBible.process_chapter_no('', 12)
|
||||||
|
|
||||||
|
# THEN: process_chapter_no should increment the previous verse number
|
||||||
|
self.assertEqual(result, 13)
|
||||||
|
|
||||||
|
def process_verse_no_valid_verse_no_test(self):
|
||||||
|
"""
|
||||||
|
Test process_verse_no when supplied with a valid verse number
|
||||||
|
"""
|
||||||
|
# GIVEN: The number 15 represented as a string and an instance of OpenSongBible
|
||||||
|
# WHEN: Calling process_verse_no
|
||||||
|
result = OpenSongBible.process_verse_no('15', 0)
|
||||||
|
|
||||||
|
# THEN: process_verse_no should return the verse number
|
||||||
|
self.assertEqual(result, 15)
|
||||||
|
|
||||||
|
def process_verse_no_verse_range_test(self):
|
||||||
|
"""
|
||||||
|
Test process_verse_no when supplied with a verse range
|
||||||
|
"""
|
||||||
|
# GIVEN: The range 24-26 represented as a string
|
||||||
|
# WHEN: Calling process_verse_no
|
||||||
|
result = OpenSongBible.process_verse_no('24-26', 0)
|
||||||
|
|
||||||
|
# THEN: process_verse_no should return the first verse number in the range
|
||||||
|
self.assertEqual(result, 24)
|
||||||
|
|
||||||
|
def process_verse_no_invalid_verse_no_test(self):
|
||||||
|
"""
|
||||||
|
Test process_verse_no when supplied with a invalid verse number
|
||||||
|
"""
|
||||||
|
# GIVEN: An non numeric string represented as a string
|
||||||
|
# WHEN: Calling process_verse_no
|
||||||
|
result = OpenSongBible.process_verse_no('invalid', 41)
|
||||||
|
|
||||||
|
# THEN: process_verse_no should increment the previous verse number
|
||||||
|
self.assertEqual(result, 42)
|
||||||
|
|
||||||
|
def process_verse_no_empty_attribute_test(self):
|
||||||
|
"""
|
||||||
|
Test process_verse_no when the verse number is an empty string. (Bug #1074727)
|
||||||
|
"""
|
||||||
|
# GIVEN: An empty string, and the previous verse number set as 14
|
||||||
|
# WHEN: Calling process_verse_no
|
||||||
|
result = OpenSongBible.process_verse_no('', 14)
|
||||||
|
|
||||||
|
# THEN: process_verse_no should increment the previous verse number
|
||||||
|
self.assertEqual(result, 15)
|
||||||
|
|
||||||
|
@patch('openlp.plugins.bibles.lib.importers.opensong.log')
|
||||||
|
def process_verse_no_invalid_type_test(self, mocked_log):
|
||||||
|
"""
|
||||||
|
Test process_verse_no when the verse number is an invalid type)
|
||||||
|
"""
|
||||||
|
# GIVEN: A mocked out log, a Tuple, and the previous verse number set as 12
|
||||||
|
# WHEN: Calling process_verse_no
|
||||||
|
result = OpenSongBible.process_verse_no((1,2,3), 12)
|
||||||
|
|
||||||
|
# THEN: process_verse_no should log the verse number it was called with increment the previous verse number
|
||||||
|
mocked_log.warning.assert_called_once_with('Illegal verse number: (1, 2, 3)')
|
||||||
|
self.assertEqual(result, 13)
|
||||||
|
|
||||||
|
@patch('openlp.plugins.bibles.lib.importers.opensong.BibleImport')
|
||||||
|
def validate_xml_bible_test(self, mocked_bible_import):
|
||||||
|
"""
|
||||||
|
Test that validate_xml returns True with valid XML
|
||||||
|
"""
|
||||||
|
# GIVEN: Some test data with an OpenSong Bible "bible" root tag
|
||||||
|
mocked_bible_import.parse_xml.return_value = objectify.fromstring('<bible></bible>')
|
||||||
|
|
||||||
|
# WHEN: Calling validate_xml
|
||||||
|
result = OpenSongBible.validate_file('file.name')
|
||||||
|
|
||||||
|
# THEN: A True should be returned
|
||||||
|
self.assertTrue(result)
|
||||||
|
|
||||||
|
@patch('openlp.plugins.bibles.lib.importers.opensong.critical_error_message_box')
|
||||||
|
@patch('openlp.plugins.bibles.lib.importers.opensong.BibleImport')
|
||||||
|
def validate_xml_zefania_root_test(self, mocked_bible_import, mocked_message_box):
|
||||||
|
"""
|
||||||
|
Test that validate_xml raises a ValidationError with a Zefinia root tag
|
||||||
|
"""
|
||||||
|
# GIVEN: Some test data with a Zefinia "XMLBIBLE" root tag
|
||||||
|
mocked_bible_import.parse_xml.return_value = objectify.fromstring('<XMLBIBLE></XMLBIBLE>')
|
||||||
|
|
||||||
|
# WHEN: Calling validate_xml
|
||||||
|
# THEN: critical_error_message_box should be called and an ValidationError should be raised
|
||||||
|
with self.assertRaises(ValidationError) as context:
|
||||||
|
OpenSongBible.validate_file('file.name')
|
||||||
|
self.assertEqual(context.exception.msg, 'Invalid xml.')
|
||||||
|
mocked_message_box.assert_called_once_with(
|
||||||
|
message='Incorrect Bible file type supplied. This looks like a Zefania XML bible, please use the '
|
||||||
|
'Zefania import option.')
|
||||||
|
|
||||||
|
@patch('openlp.plugins.bibles.lib.importers.opensong.critical_error_message_box')
|
||||||
|
@patch('openlp.plugins.bibles.lib.importers.opensong.BibleImport')
|
||||||
|
def validate_xml_invalid_root_test(self, mocked_bible_import, mocked_message_box):
|
||||||
|
"""
|
||||||
|
Test that validate_xml raises a ValidationError with an invalid root tag
|
||||||
|
"""
|
||||||
|
# GIVEN: Some test data with an invalid root tag and an instance of OpenSongBible
|
||||||
|
mocked_bible_import.parse_xml.return_value = objectify.fromstring('<song></song>')
|
||||||
|
|
||||||
|
# WHEN: Calling validate_xml
|
||||||
|
# THEN: ValidationError should be raised, and the critical error message box should not have been called
|
||||||
|
with self.assertRaises(ValidationError) as context:
|
||||||
|
OpenSongBible.validate_file('file.name')
|
||||||
|
self.assertEqual(context.exception.msg, 'Invalid xml.')
|
||||||
|
self.assertFalse(mocked_message_box.called)
|
||||||
|
|
||||||
def test_file_import(self):
|
def test_file_import(self):
|
||||||
"""
|
"""
|
||||||
@ -92,22 +221,3 @@ class TestOpenSongImport(TestCase):
|
|||||||
self.assertTrue(importer.create_verse.called)
|
self.assertTrue(importer.create_verse.called)
|
||||||
for verse_tag, verse_text in test_data['verses']:
|
for verse_tag, verse_text in test_data['verses']:
|
||||||
importer.create_verse.assert_any_call(importer.create_book().id, 1, int(verse_tag), verse_text)
|
importer.create_verse.assert_any_call(importer.create_book().id, 1, int(verse_tag), verse_text)
|
||||||
|
|
||||||
def test_zefania_import_error(self):
|
|
||||||
"""
|
|
||||||
Test that we give an error message if trying to import a zefania bible
|
|
||||||
"""
|
|
||||||
# GIVEN: A mocked out "manager" and mocked out critical_error_message_box and an import
|
|
||||||
with patch('openlp.plugins.bibles.lib.importers.opensong.critical_error_message_box') as \
|
|
||||||
mocked_critical_error_message_box:
|
|
||||||
mocked_manager = MagicMock()
|
|
||||||
importer = OpenSongBible(mocked_manager, path='.', name='.', filename='')
|
|
||||||
|
|
||||||
# WHEN: An trying to import a zefania bible
|
|
||||||
importer.filename = os.path.join(TEST_PATH, 'zefania-dk1933.xml')
|
|
||||||
importer.do_import()
|
|
||||||
|
|
||||||
# THEN: The importer should have "shown" an error message
|
|
||||||
mocked_critical_error_message_box.assert_called_with(message='Incorrect Bible file type supplied. '
|
|
||||||
'This looks like a Zefania XML bible, '
|
|
||||||
'please use the Zefania import option.')
|
|
||||||
|
Loading…
Reference in New Issue
Block a user