# -*- coding: utf-8 -*- ########################################################################## # OpenLP - Open Source Lyrics Projection # # ---------------------------------------------------------------------- # # Copyright (c) 2008-2024 OpenLP Developers # # ---------------------------------------------------------------------- # # This program is free software: you can redistribute it and/or modify # # it under the terms of the GNU General Public License as published by # # the Free Software Foundation, either version 3 of the License, or # # (at your option) any later version. # # # # This program is distributed in the hope that it will be useful, # # but WITHOUT ANY WARRANTY; without even the implied warranty of # # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # # GNU General Public License for more details. # # # # You should have received a copy of the GNU General Public License # # along with this program. If not, see . # ########################################################################## """ Test the MediaShout importer """ from collections import namedtuple from unittest.mock import MagicMock, call, patch import pytest from openlp.core.common.registry import Registry from openlp.core.common.settings import Settings try: from openlp.plugins.songs.lib.importers.mediashout import MediaShoutImport CAN_RUN_TESTS = True except ImportError: class MediaShoutImport: pass CAN_RUN_TESTS = False pytestmark = pytest.mark.skipif(not CAN_RUN_TESTS, reason='Not Windows, skipping test') @pytest.fixture def importer(registry: Registry, settings: Settings): return MediaShoutImport(MagicMock(), file_path='mediashout.db') def test_constructor(importer: MediaShoutImport): """ Test the MediaShoutImport constructor """ # GIVEN: A MediaShoutImport class # WHEN: It is created # THEN: It should not be None assert importer is not None @patch('openlp.plugins.songs.lib.importers.mediashout.pyodbc') def test_do_import_fails_to_connect(mocked_pyodbc: MagicMock, importer: MediaShoutImport): """ Test that do_import exits early when unable to connect to the database """ # GIVEN: A MediaShoutImport instance and pyodbc unable to connect mocked_pyodbc.connect.side_effect = Exception('Unable to connect') # WHEN: do_import is called with patch.object(importer, 'log_error') as mocked_log_error: importer.do_import() # THEN: The songs should have been imported mocked_log_error.assert_called_once_with('mediashout.db', 'Unable to open the MediaShout database.') @patch('openlp.plugins.songs.lib.importers.mediashout.pyodbc') def test_do_import(mocked_pyodbc: MagicMock, importer: MediaShoutImport): """ Test the MediaShoutImport do_import method """ SongRecord = namedtuple('SongRecord', 'Record, Title, Author, Copyright, SongID, CCLI, Notes') VerseRecord = namedtuple('VerseRecord', 'Type, Number, Text') PlayOrderRecord = namedtuple('PlayOrderRecord', 'Type, Number, POrder') ThemeRecord = namedtuple('ThemeRecord', 'Name') GroupRecord = namedtuple('GroupRecord', 'Name') song = SongRecord(1, 'Amazing Grace', 'William Wilberforce', 'Public Domain', 1, '654321', '') verse = VerseRecord('Verse', 1, 'Amazing grace, how sweet the sound\nThat saved a wretch like me') play_order = PlayOrderRecord('Verse', 1, 1) theme = ThemeRecord('Grace') group = GroupRecord('Hymns') # GIVEN: A MediaShoutImport instance and a bunch of stuff mocked out mocked_cursor = MagicMock() mocked_cursor.fetchall.side_effect = [[song], [verse], [play_order], [theme], [group]] mocked_cursor.tables.return_value.fetchone.return_value = True mocked_connection = MagicMock() mocked_connection.cursor.return_value = mocked_cursor mocked_pyodbc.connect.return_value = mocked_connection # WHEN: do_import is called with patch.object(importer, 'import_wizard'), \ patch.object(importer, 'process_song') as mocked_process_song: importer.do_import() # THEN: The songs should have been imported expected_execute_calls = [ call('SELECT Record, Title, Author, Copyright, SongID, CCLI, Notes FROM Songs ORDER BY Title'), call('SELECT Type, Number, Text FROM Verses WHERE Record = ? ORDER BY Type, Number', 1.0), call('SELECT Type, Number, POrder FROM PlayOrder WHERE Record = ? ORDER BY POrder', 1.0), call('SELECT Name FROM Themes INNER JOIN SongThemes ON SongThemes.ThemeId = Themes.ThemeId ' 'WHERE SongThemes.Record = ?', 1.0), call('SELECT Name FROM Groups INNER JOIN SongGroups ON SongGroups.GroupId = Groups.GroupId ' 'WHERE SongGroups.Record = ?', 1.0) ] assert expected_execute_calls == mocked_cursor.execute.call_args_list mocked_process_song.assert_called_once_with(song, [verse], [play_order], [theme, group]) @patch('openlp.plugins.songs.lib.importers.mediashout.pyodbc') def test_do_import_without_topics(mocked_pyodbc: MagicMock, importer: MediaShoutImport): """ Test the MediaShoutImport do_import method with the Themes and Groups tables don't exist """ SongRecord = namedtuple('SongRecord', 'Record, Title, Author, Copyright, SongID, CCLI, Notes') VerseRecord = namedtuple('VerseRecord', 'Type, Number, Text') PlayOrderRecord = namedtuple('PlayOrderRecord', 'Type, Number, POrder') song = SongRecord(1, 'Amazing Grace', 'William Wilberforce', 'Public Domain', 1, '654321', '') verse = VerseRecord('Verse', 1, 'Amazing grace, how sweet the sound\nThat saved a wretch like me') play_order = PlayOrderRecord('Verse', 1, 1) # GIVEN: A MediaShoutImport instance and a bunch of stuff mocked out mocked_cursor = MagicMock() mocked_cursor.fetchall.side_effect = [[song], [verse], [play_order]] mocked_cursor.tables.return_value.fetchone.return_value = False mocked_connection = MagicMock() mocked_connection.cursor.return_value = mocked_cursor mocked_pyodbc.connect.return_value = mocked_connection # WHEN: do_import is called with patch.object(importer, 'import_wizard'), \ patch.object(importer, 'process_song') as mocked_process_song: importer.do_import() # THEN: The songs should have been imported expected_execute_calls = [ call('SELECT Record, Title, Author, Copyright, SongID, CCLI, Notes FROM Songs ORDER BY Title'), call('SELECT Type, Number, Text FROM Verses WHERE Record = ? ORDER BY Type, Number', 1.0), call('SELECT Type, Number, POrder FROM PlayOrder WHERE Record = ? ORDER BY POrder', 1.0) ] assert expected_execute_calls == mocked_cursor.execute.call_args_list mocked_process_song.assert_called_once_with(song, [verse], [play_order], []) @patch('openlp.plugins.songs.lib.importers.mediashout.pyodbc') def test_do_import_breaks_on_stop(mocked_pyodbc: MagicMock, importer: MediaShoutImport): """ Test the MediaShoutImport do_import stops when the user presses the cancel button """ SongRecord = namedtuple('SongRecord', 'Record, Title, Author, Copyright, SongID, CCLI, Notes') song = SongRecord(1, 'Amazing Grace', 'William Wilberforce', 'Public Domain', 1, '654321', '') # GIVEN: A MediaShoutImport instance and a bunch of stuff mocked out mocked_cursor = MagicMock() mocked_cursor.fetchall.return_value = [song] mocked_connection = MagicMock() mocked_connection.cursor.return_value = mocked_cursor mocked_pyodbc.connect.return_value = mocked_connection # WHEN: do_import is called, but cancelled with patch.object(importer, 'import_wizard'), \ patch.object(importer, 'process_song') as mocked_process_song: importer.stop_import_flag = True importer.do_import() # THEN: The songs should have been imported mocked_cursor.execute.assert_called_once_with( 'SELECT Record, Title, Author, Copyright, SongID, CCLI, Notes FROM Songs ORDER BY Title') mocked_process_song.assert_not_called() def test_process_song(importer: MediaShoutImport): """ Test the process_song method of the MediaShoutImport """ # GIVEN: An importer and a song SongRecord = namedtuple('SongRecord', 'Record, Title, Author, Copyright, SongID, CCLI, Notes') VerseRecord = namedtuple('VerseRecord', 'Type, Number, Text') PlayOrderRecord = namedtuple('PlayOrderRecord', 'Type, Number, POrder') ThemeRecord = namedtuple('ThemeRecord', 'Name') GroupRecord = namedtuple('GroupRecord', 'Name') song = SongRecord(1, 'Amazing Grace', 'William Wilberforce', 'Public Domain', 'Hymns', '654321', 'Great old hymn') verse = VerseRecord(0, 1, 'Amazing grace, how sweet the sound\nThat saved a wretch like me') play_order = PlayOrderRecord(0, 1, 1) theme = ThemeRecord('Grace') group = GroupRecord('Hymns') # WHEN: A song is processed with patch.object(importer, 'set_defaults') as mocked_set_defaults, \ patch.object(importer, 'parse_author') as mocked_parse_author, \ patch.object(importer, 'add_copyright') as mocked_add_copyright, \ patch.object(importer, 'add_verse') as mocked_add_verse, \ patch.object(importer, 'finish') as mocked_finish: importer.topics = [] importer.verse_order_list = [] importer.process_song(song, [verse], [play_order], [theme, group]) # THEN: It should be added to the database mocked_set_defaults.assert_called_once_with() assert 'Amazing Grace' == importer.title mocked_parse_author.assert_called_once_with('William Wilberforce') mocked_add_copyright.assert_called_once_with('Public Domain') assert 'Great old hymn' == importer.comments assert ['Grace', 'Hymns'] == importer.topics assert 'Hymns' == importer.song_book_name assert '' == importer.song_number mocked_add_verse.assert_called_once_with( 'Amazing grace, how sweet the sound\nThat saved a wretch like me', 'V1') assert ['V1'] == importer.verse_order_list mocked_finish.assert_called_once_with() def test_process_song_with_song_number(importer: MediaShoutImport): """ Test the process_song method with a song that has a song number """ # GIVEN: An importer and a song SongRecord = namedtuple('SongRecord', 'Record, Title, Author, Copyright, SongID, CCLI, Notes') VerseRecord = namedtuple('VerseRecord', 'Type, Number, Text') PlayOrderRecord = namedtuple('PlayOrderRecord', 'Type, Number, POrder') ThemeRecord = namedtuple('ThemeRecord', 'Name') GroupRecord = namedtuple('GroupRecord', 'Name') song = SongRecord(1, 'Amazing Grace', 'William Wilberforce', 'Public Domain', 'Hymns-2', '654321', 'Great old hymn') verse = VerseRecord(0, 1, 'Amazing grace, how sweet the sound\nThat saved a wretch like me') play_order = PlayOrderRecord(0, 1, 1) theme = ThemeRecord('Grace') group = GroupRecord('Hymns') # WHEN: A song is processed with patch.object(importer, 'set_defaults') as mocked_set_defaults, \ patch.object(importer, 'parse_author') as mocked_parse_author, \ patch.object(importer, 'add_copyright') as mocked_add_copyright, \ patch.object(importer, 'add_verse') as mocked_add_verse, \ patch.object(importer, 'finish') as mocked_finish: importer.topics = [] importer.verse_order_list = [] importer.process_song(song, [verse], [play_order], [theme, group]) # THEN: It should be added to the database mocked_set_defaults.assert_called_once_with() assert 'Amazing Grace' == importer.title mocked_parse_author.assert_called_once_with('William Wilberforce') mocked_add_copyright.assert_called_once_with('Public Domain') assert 'Great old hymn' == importer.comments assert ['Grace', 'Hymns'] == importer.topics assert 'Hymns' == importer.song_book_name assert '2' == importer.song_number mocked_add_verse.assert_called_once_with( 'Amazing grace, how sweet the sound\nThat saved a wretch like me', 'V1') assert ['V1'], importer.verse_order_list mocked_finish.assert_called_once_with()