forked from openlp/openlp
378 lines
19 KiB
Python
378 lines
19 KiB
Python
# -*- coding: utf-8 -*-
|
||
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||
|
||
"""
|
||
This module contains tests for the EasyWorship song importer.
|
||
"""
|
||
|
||
import os
|
||
from unittest import TestCase
|
||
from mock import patch, MagicMock
|
||
|
||
from openlp.plugins.songs.lib.ewimport import EasyWorshipSongImport, FieldDescEntry, FieldType
|
||
|
||
TEST_PATH = os.path.abspath(
|
||
os.path.join(os.path.dirname(__file__), '..', '..', '..', 'resources', 'easyworshipsongs'))
|
||
SONG_TEST_DATA = [
|
||
{'title': 'Amazing Grace',
|
||
'authors': ['John Newton'],
|
||
'copyright': 'Public Domain',
|
||
'ccli_number': 0,
|
||
'verses':
|
||
[('Amazing grace how sweet the sound,\nThat saved a wretch like me;\n'
|
||
'I once was lost, but now am found\nWas blind, but now I see.', 'v1'),
|
||
('T\'was grace that taught my heart to fear,\nAnd grace my fears relieved;\n'
|
||
'How precious did that grace appear\nThe hour I first believed.', 'v2'),
|
||
('Through many dangers, toil and snares,\nI have already come;\n'
|
||
'\'Tis grace has brought me safe thus far,\nAnd grace will lead me home.', 'v3'),
|
||
('When we\'ve been there ten thousand years\nBright shining as the sun,\n'
|
||
'We\'ve no less days to sing God\'s praise\nThan when we\'ve first begun.', 'v4')],
|
||
'verse_order_list': []},
|
||
{'title': 'Beautiful Garden Of Prayer',
|
||
'authors': ['Eleanor Allen Schroll James H. Fillmore'],
|
||
'copyright': 'Public Domain',
|
||
'ccli_number': 0,
|
||
'verses':
|
||
[('O the beautiful garden, the garden of prayer,\nO the beautiful garden of prayer.\n'
|
||
'There my Savior awaits, and He opens the gates\nTo the beautiful garden of prayer.', 'c1'),
|
||
('There\'s a garden where Jesus is waiting,\nThere\'s a place that is wondrously fair.\n'
|
||
'For it glows with the light of His presence,\n\'Tis the beautiful garden of prayer.', 'v1'),
|
||
('There\'s a garden where Jesus is waiting,\nAnd I go with my burden and care.\n'
|
||
'Just to learn from His lips, words of comfort,\nIn the beautiful garden of prayer.', 'v2'),
|
||
('There\'s a garden where Jesus is waiting,\nAnd He bids you to come meet Him there,\n'
|
||
'Just to bow and receive a new blessing,\nIn the beautiful garden of prayer.', 'v3')],
|
||
'verse_order_list': []}]
|
||
|
||
class EasyWorshipSongImportLogger(EasyWorshipSongImport):
|
||
"""
|
||
This class logs changes in the title instance variable
|
||
"""
|
||
_title_assignment_list = []
|
||
|
||
def __init__(self, manager):
|
||
EasyWorshipSongImport.__init__(self, manager)
|
||
|
||
@property
|
||
def title(self):
|
||
return self._title_assignment_list[-1]
|
||
|
||
@title.setter
|
||
def title(self, title):
|
||
self._title_assignment_list.append(title)
|
||
|
||
class TestFieldDesc:
|
||
def __init__(self, name, field_type, size):
|
||
self.name = name
|
||
self.field_type = field_type
|
||
self.size = size
|
||
|
||
TEST_DATA_ENCODING = 'cp1252'
|
||
CODE_PAGE_MAPPINGS = [(852, 'cp1250'), (737, 'cp1253'), (775, 'cp1257'), (855, 'cp1251'), (857, 'cp1254'),
|
||
(866, 'cp1251'), (869, 'cp1253'), (862, 'cp1255'), (874, 'cp874')]
|
||
TEST_FIELD_DESCS = [TestFieldDesc('Title', FieldType.String, 50),
|
||
TestFieldDesc('Text Percentage Bottom', FieldType.Int16, 2), TestFieldDesc('RecID', FieldType.Int32, 4),
|
||
TestFieldDesc('Default Background', FieldType.Logical, 1), TestFieldDesc('Words', FieldType.Memo, 250),
|
||
TestFieldDesc('Words', FieldType.Memo, 250), TestFieldDesc('BK Bitmap', FieldType.Blob, 10),
|
||
TestFieldDesc('Last Modified', FieldType.Timestamp, 10)]
|
||
TEST_FIELDS = ['A Heart Like Thine\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0', 32868, 2147483750,
|
||
129, '{\\rtf1\\ansi\\deff0\\deftab254{\\fonttbl{\\f0\\fnil\\fcharset0 Arial;}{\\f1\\fnil\\fcharset0 Verdana;}}'
|
||
'{\\colortbl\\red0\\green0\\blue0;\\red255\\green0\\blue0;\\red0\\green128\\blue0;\\red0\\green0\\blue255;'
|
||
'\\red255\\green255\\blue0;\\red255\\green0\\blue255;\\red128\\g<EFBFBD><EFBFBD>\7\0f\r\0\0\1\0',
|
||
'{\\rtf1\\ansi\\deff0\\deftab254{\\fonttbl{\\f0\\fnil\\fcharset0 Arial;}{\\f1\\fnil\\fcharset0 Verdana;}}'
|
||
'{\\colortbl\\red0\\green0\\blue0;\\red255\\green0\\blue0;\\red0\\green128\\blue0;\\red0\\green0\\blue255;\\red255'
|
||
'\\green255\\blue0;\\red255\\green0\\blue255;\\red128\\g><3E>\6\0<EFBFBD>\6\0\0\1\0', '\0\0\0\0\0\0\0\0\0\0', 0]
|
||
GET_MEMO_FIELD_TEST_RESULTS = [
|
||
(4, '\2', {'return': '\2','read': (1, 3430), 'seek': (507136, (8, os.SEEK_CUR))}),
|
||
(4, '\3', {'return': '', 'read': (1, ), 'seek': (507136, )}),
|
||
(5, '\3', {'return': '\3', 'read': (1, 1725), 'seek': (3220111360, (41, os.SEEK_CUR), 3220111408)}),
|
||
(5, '\4', {'return': '', 'read': (), 'seek': ()})]
|
||
|
||
class TestEasyWorshipSongImport(TestCase):
|
||
"""
|
||
Test the functions in the :mod:`ewimport` module.
|
||
"""
|
||
def create_field_desc_entry_test(self):
|
||
"""
|
||
Test creating an instance of the :class`FieldDescEntry` class.
|
||
"""
|
||
# GIVEN: Set arguments
|
||
name = 'Title'
|
||
field_type = FieldType.String
|
||
size = 50
|
||
|
||
# WHEN: A FieldDescEntry object is created.
|
||
field_desc_entry = FieldDescEntry(name, field_type, size)
|
||
|
||
# THEN:
|
||
self.assertIsNotNone(field_desc_entry, 'Import should not be none')
|
||
self.assertEquals(field_desc_entry.name, name, 'FieldDescEntry.name should be the same as the name argument')
|
||
self.assertEquals(field_desc_entry.field_type, field_type,
|
||
'FieldDescEntry.type should be the same as the typeargument')
|
||
self.assertEquals(field_desc_entry.size, size, 'FieldDescEntry.size should be the same as the size argument')
|
||
|
||
def create_importer_test(self):
|
||
"""
|
||
Test creating an instance of the EasyWorship file importer
|
||
"""
|
||
# GIVEN: A mocked out SongImport class, and a mocked out "manager"
|
||
with patch('openlp.plugins.songs.lib.ewimport.SongImport'):
|
||
mocked_manager = MagicMock()
|
||
|
||
# WHEN: An importer object is created
|
||
importer = EasyWorshipSongImport(mocked_manager)
|
||
|
||
# THEN: The importer object should not be None
|
||
self.assertIsNotNone(importer, 'Import should not be none')
|
||
|
||
def find_field_exists_test(self):
|
||
"""
|
||
Test finding an existing field in a given list using the :mod:`findField`
|
||
"""
|
||
# GIVEN: A mocked out SongImport class, a mocked out "manager" and a list of field descriptions.
|
||
with patch('openlp.plugins.songs.lib.ewimport.SongImport'):
|
||
mocked_manager = MagicMock()
|
||
importer = EasyWorshipSongImport(mocked_manager)
|
||
importer.fieldDescs = TEST_FIELD_DESCS
|
||
|
||
# WHEN: Called with a field name that exists
|
||
existing_fields = ['Title', 'Text Percentage Bottom', 'RecID', 'Default Background', 'Words',
|
||
'BK Bitmap', 'Last Modified']
|
||
for field_name in existing_fields:
|
||
|
||
# THEN: The item corresponding the index returned should have the same name attribute
|
||
self.assertEquals(importer.fieldDescs[importer.findField(field_name)].name, field_name)
|
||
|
||
def find_non_existing_field_test(self):
|
||
"""
|
||
Test finding an non-existing field in a given list using the :mod:`findField`
|
||
"""
|
||
# GIVEN: A mocked out SongImport class, a mocked out "manager" and a list of field descriptions
|
||
with patch('openlp.plugins.songs.lib.ewimport.SongImport'):
|
||
mocked_manager = MagicMock()
|
||
importer = EasyWorshipSongImport(mocked_manager)
|
||
importer.fieldDescs = TEST_FIELD_DESCS
|
||
|
||
# WHEN: Called with a field name that does not exist
|
||
non_existing_fields = ['BK Gradient Shading', 'BK Gradient Variant', 'Favorite', 'Copyright']
|
||
for field_name in non_existing_fields:
|
||
|
||
# THEN: The importer object should not be None
|
||
self.assertRaises(IndexError, importer.findField, field_name)
|
||
|
||
def set_record_struct_test(self):
|
||
"""
|
||
Test the :mod:`setRecordStruct` module
|
||
"""
|
||
# GIVEN: A mocked out SongImport class, a mocked out struct class, and a mocked out "manager" and a list of
|
||
# field descriptions
|
||
with patch('openlp.plugins.songs.lib.ewimport.SongImport'), \
|
||
patch('openlp.plugins.songs.lib.ewimport.struct') as mocked_struct:
|
||
mocked_manager = MagicMock()
|
||
importer = EasyWorshipSongImport(mocked_manager)
|
||
|
||
# WHEN: setRecordStruct is called with a list of field descriptions
|
||
return_value = importer.setRecordStruct(TEST_FIELD_DESCS)
|
||
|
||
# THEN: setRecordStruct should return None and Struct should be called with a value representing
|
||
# the list of field descriptions
|
||
self.assertIsNone(return_value, 'setRecordStruct should return None')
|
||
mocked_struct.Struct.assert_called_with('>50sHIB250s250s10sQ')
|
||
|
||
def get_field_test(self):
|
||
"""
|
||
Test the :mod:`getField` module
|
||
"""
|
||
# GIVEN: A mocked out SongImport class, a mocked out "manager", an encoding and some test data and known results
|
||
with patch('openlp.plugins.songs.lib.ewimport.SongImport'):
|
||
mocked_manager = MagicMock()
|
||
importer = EasyWorshipSongImport(mocked_manager)
|
||
importer.encoding = TEST_DATA_ENCODING
|
||
importer.fields = TEST_FIELDS
|
||
importer.fieldDescs = TEST_FIELD_DESCS
|
||
field_results = [(0, 'A Heart Like Thine'), (1, 100), (2, 102), (3, True), (6, None), (7, None)]
|
||
|
||
# WHEN: Called with test data
|
||
for field_index, result in field_results:
|
||
return_value = importer.getField(field_index)
|
||
|
||
# THEN: getField should return the known results
|
||
self.assertEquals(return_value, result,
|
||
'getField should return "%s" when called with "%s"' % (result, TEST_FIELDS[field_index]))
|
||
|
||
def get_memo_field_test(self):
|
||
"""
|
||
Test the :mod:`getField` module
|
||
"""
|
||
for test_results in GET_MEMO_FIELD_TEST_RESULTS:
|
||
# GIVEN: A mocked out SongImport class, a mocked out "manager", a mocked out memo_file and an encoding
|
||
with patch('openlp.plugins.songs.lib.ewimport.SongImport'):
|
||
mocked_manager = MagicMock()
|
||
mocked_memo_file = MagicMock()
|
||
importer = EasyWorshipSongImport(mocked_manager)
|
||
importer.memoFile = mocked_memo_file
|
||
importer.encoding = TEST_DATA_ENCODING
|
||
|
||
# WHEN: Supplied with test fields and test field descriptions
|
||
importer.fields = TEST_FIELDS
|
||
importer.fieldDescs = TEST_FIELD_DESCS
|
||
field_index = test_results[0]
|
||
mocked_memo_file.read.return_value = test_results[1]
|
||
get_field_result = test_results[2]['return']
|
||
get_field_read_calls = test_results[2]['read']
|
||
get_field_seek_calls = test_results[2]['seek']
|
||
|
||
# THEN: getField should return the appropriate value with the appropriate mocked objects being called
|
||
self.assertEquals(importer.getField(field_index), get_field_result)
|
||
for call in get_field_read_calls:
|
||
mocked_memo_file.read.assert_any_call(call)
|
||
for call in get_field_seek_calls:
|
||
if isinstance(call, int):
|
||
mocked_memo_file.seek.assert_any_call(call)
|
||
else:
|
||
mocked_memo_file.seek.assert_any_call(call[0], call[1])
|
||
|
||
def do_import_source_test(self):
|
||
"""
|
||
Test the :mod:`doImport` module opens the correct files
|
||
"""
|
||
# GIVEN: A mocked out SongImport class, a mocked out "manager"
|
||
with patch('openlp.plugins.songs.lib.ewimport.SongImport'), \
|
||
patch('openlp.plugins.songs.lib.ewimport.os.path') as mocked_os_path:
|
||
mocked_manager = MagicMock()
|
||
importer = EasyWorshipSongImport(mocked_manager)
|
||
mocked_os_path.isfile.side_effect = [True, False]
|
||
|
||
# WHEN: Supplied with an import source
|
||
importer.import_source = 'Songs.DB'
|
||
|
||
# THEN: doImport should return None having called os.path.isfile
|
||
self.assertIsNone(importer.doImport(), 'doImport should return None')
|
||
mocked_os_path.isfile.assert_any_call('Songs.DB')
|
||
mocked_os_path.isfile.assert_any_call('Songs.MB')
|
||
|
||
def do_import_database_validity_test(self):
|
||
"""
|
||
Test the :mod:`doImport` module handles invalid database files correctly
|
||
"""
|
||
# GIVEN: A mocked out SongImport class, os.path and a mocked out "manager"
|
||
with patch('openlp.plugins.songs.lib.ewimport.SongImport'), \
|
||
patch('openlp.plugins.songs.lib.ewimport.os.path') as mocked_os_path:
|
||
mocked_manager = MagicMock()
|
||
importer = EasyWorshipSongImport(mocked_manager)
|
||
mocked_os_path.isfile.return_value = True
|
||
importer.import_source = 'Songs.DB'
|
||
|
||
# WHEN: DB file size is less than 0x800
|
||
mocked_os_path.getsize.return_value = 0x7FF
|
||
|
||
# THEN: doImport should return None having called os.path.isfile
|
||
self.assertIsNone(importer.doImport(), 'doImport should return None when db_size is less than 0x800')
|
||
mocked_os_path.getsize.assert_any_call('Songs.DB')
|
||
|
||
def do_import_memo_validty_test(self):
|
||
"""
|
||
Test the :mod:`doImport` module handles invalid memo files correctly
|
||
"""
|
||
# GIVEN: A mocked out SongImport class, a mocked out "manager"
|
||
with patch('openlp.plugins.songs.lib.ewimport.SongImport'), \
|
||
patch('openlp.plugins.songs.lib.ewimport.os.path') as mocked_os_path, \
|
||
patch('__builtin__.open') as mocked_open, \
|
||
patch('openlp.plugins.songs.lib.ewimport.struct') as mocked_struct:
|
||
mocked_manager = MagicMock()
|
||
importer = EasyWorshipSongImport(mocked_manager)
|
||
mocked_os_path.isfile.return_value = True
|
||
mocked_os_path.getsize.return_value = 0x800
|
||
importer.import_source = 'Songs.DB'
|
||
|
||
# WHEN: Unpacking first 35 bytes of Memo file
|
||
struct_unpack_return_values = [(0, 0x700, 2, 0, 0), (0, 0x800, 0, 0, 0), (0, 0x800, 5, 0, 0)]
|
||
mocked_struct.unpack.side_effect = struct_unpack_return_values
|
||
|
||
# THEN: doImport should return None having called closed the open files db and memo files.
|
||
for effect in struct_unpack_return_values:
|
||
self.assertIsNone(importer.doImport(), 'doImport should return None when db_size is less than 0x800')
|
||
self.assertEqual(mocked_open().close.call_count, 2,
|
||
'The open db and memo files should have been closed')
|
||
mocked_open().close.reset_mock()
|
||
self.assertIs(mocked_open().seek.called, False, 'db_file.seek should not have been called.')
|
||
|
||
def code_page_to_encoding_test(self):
|
||
"""
|
||
Test the :mod:`doImport` converts the code page to the encoding correctly
|
||
"""
|
||
# GIVEN: A mocked out SongImport class, a mocked out "manager"
|
||
with patch('openlp.plugins.songs.lib.ewimport.SongImport'), \
|
||
patch('openlp.plugins.songs.lib.ewimport.os.path') as mocked_os_path, \
|
||
patch('__builtin__.open'), patch('openlp.plugins.songs.lib.ewimport.struct') as mocked_struct, \
|
||
patch('openlp.plugins.songs.lib.ewimport.retrieve_windows_encoding') as mocked_retrieve_windows_encoding:
|
||
mocked_manager = MagicMock()
|
||
importer = EasyWorshipSongImport(mocked_manager)
|
||
mocked_os_path.isfile.return_value = True
|
||
mocked_os_path.getsize.return_value = 0x800
|
||
importer.import_source = 'Songs.DB'
|
||
|
||
# WHEN: Unpacking the code page
|
||
for code_page, encoding in CODE_PAGE_MAPPINGS:
|
||
struct_unpack_return_values = [(0, 0x800, 2, 0, 0), (code_page, )]
|
||
mocked_struct.unpack.side_effect = struct_unpack_return_values
|
||
mocked_retrieve_windows_encoding.return_value = False
|
||
|
||
# THEN: doImport should return None having called retrieve_windows_encoding with the correct encoding.
|
||
self.assertIsNone(importer.doImport(), 'doImport should return None when db_size is less than 0x800')
|
||
mocked_retrieve_windows_encoding.assert_call(encoding)
|
||
|
||
def file_import_test(self):
|
||
"""
|
||
Test the actual import of real song files and check that the imported data is correct.
|
||
"""
|
||
|
||
# GIVEN: Test files with a mocked out SongImport class, a mocked out "manager", a mocked out "import_wizard",
|
||
# and mocked out "author", "add_copyright", "add_verse", "finish" methods.
|
||
with patch('openlp.plugins.songs.lib.ewimport.SongImport'), \
|
||
patch('openlp.plugins.songs.lib.ewimport.retrieve_windows_encoding') as mocked_retrieve_windows_encoding:
|
||
mocked_retrieve_windows_encoding.return_value = 'cp1252'
|
||
mocked_manager = MagicMock()
|
||
mocked_import_wizard = MagicMock()
|
||
mocked_add_author = MagicMock()
|
||
mocked_add_verse = MagicMock()
|
||
mocked_finish = MagicMock()
|
||
mocked_title = MagicMock()
|
||
mocked_finish.return_value = True
|
||
importer = EasyWorshipSongImportLogger(mocked_manager)
|
||
importer.import_wizard = mocked_import_wizard
|
||
importer.stop_import_flag = False
|
||
importer.addAuthor = mocked_add_author
|
||
importer.addVerse = mocked_add_verse
|
||
importer.title = mocked_title
|
||
importer.finish = mocked_finish
|
||
importer.topics = []
|
||
|
||
# WHEN: Importing each file
|
||
importer.import_source = os.path.join(TEST_PATH, 'Songs.DB')
|
||
|
||
# THEN: doImport should return none, the song data should be as expected, and finish should have been
|
||
# called.
|
||
self.assertIsNone(importer.doImport(), 'doImport should return None when it has completed')
|
||
for song_data in SONG_TEST_DATA:
|
||
print mocked_title.mocked_calls()
|
||
title = song_data['title']
|
||
author_calls = song_data['authors']
|
||
song_copyright = song_data['copyright']
|
||
ccli_number = song_data['ccli_number']
|
||
add_verse_calls = song_data['verses']
|
||
verse_order_list = song_data['verse_order_list']
|
||
self.assertIn(title, importer._title_assignment_list, 'title for %s should be "%s"' % (title, title))
|
||
for author in author_calls:
|
||
mocked_add_author.assert_any_call(author)
|
||
if song_copyright:
|
||
self.assertEqual(importer.copyright, song_copyright)
|
||
if ccli_number:
|
||
self.assertEquals(importer.ccliNumber, ccli_number, 'ccliNumber for %s should be %s'
|
||
% (title, ccli_number))
|
||
for verse_text, verse_tag in add_verse_calls:
|
||
mocked_add_verse.assert_any_call(verse_text, verse_tag)
|
||
if verse_order_list:
|
||
self.assertEquals(importer.verseOrderList, verse_order_list, 'verseOrderList for %s should be %s'
|
||
% (title, verse_order_list))
|
||
mocked_finish.assert_called_with()
|