diff --git a/openlp/core/app.py b/openlp/core/app.py index 86926d8ce..6e0ba7834 100644 --- a/openlp/core/app.py +++ b/openlp/core/app.py @@ -376,7 +376,7 @@ def main(): Registry().register('application-qt', application) Registry().register('application', app) Registry().set_flag('no_web_server', args.no_web_server) - # Create and install settings. + # Upgrade settings. app.settings = settings Registry().register('settings', settings) application.setApplicationVersion(get_version()['version']) diff --git a/openlp/core/common/settings.py b/openlp/core/common/settings.py index 0bd394373..83d063cc0 100644 --- a/openlp/core/common/settings.py +++ b/openlp/core/common/settings.py @@ -749,7 +749,7 @@ class Settings(QtCore.QSettings): self.remove('SettingsImport') # Get the settings. keys = self.allKeys() - export_settings = QtCore.QSettings(str(temp_path), Settings.IniFormat) + export_settings = QtCore.QSettings(str(temp_path), QtCore.QSettings.IniFormat) # Add a header section. # This is to insure it's our conf file for import. now = datetime.datetime.now() diff --git a/openlp/core/ui/advancedtab.py b/openlp/core/ui/advancedtab.py index af0a16d0b..c22df77db 100644 --- a/openlp/core/ui/advancedtab.py +++ b/openlp/core/ui/advancedtab.py @@ -335,7 +335,7 @@ class AdvancedTab(SettingsTab): def load(self): """ - Load self.settings from disk. + Load settings from disk. """ self.settings.beginGroup(self.settings_section) # The max recent files value does not have an interface and so never @@ -395,7 +395,7 @@ class AdvancedTab(SettingsTab): def save(self): """ - Save self.settings to disk. + Save settings to disk. """ self.settings.beginGroup(self.settings_section) self.settings.setValue('default service enabled', self.service_name_check_box.isChecked()) diff --git a/openlp/core/ui/mainwindow.py b/openlp/core/ui/mainwindow.py index 5d7fb3667..63d0c5217 100644 --- a/openlp/core/ui/mainwindow.py +++ b/openlp/core/ui/mainwindow.py @@ -42,7 +42,6 @@ from openlp.core.common.i18n import LanguageManager, UiStrings, translate from openlp.core.common.mixins import LogMixin, RegistryProperties from openlp.core.common.path import create_paths from openlp.core.common.registry import Registry -from openlp.core.common.settings import Settings from openlp.core.display.screens import ScreenList from openlp.core.lib.plugin import PluginStatus from openlp.core.lib.ui import create_action @@ -874,7 +873,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, LogMixin, RegistryPropert create_paths(temp_dir_path) temp_config_path = temp_dir_path / import_file_path.name shutil.copyfile(import_file_path, temp_config_path) - import_settings = Settings(str(temp_config_path), Settings.IniFormat) + import_settings = QtCore.QSettings(str(temp_config_path), QtCore.QSettings.IniFormat) self.log_info('hook upgrade_plugin_settings') self.plugin_manager.hook_upgrade_plugin_settings(import_settings) @@ -1376,11 +1375,10 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, LogMixin, RegistryPropert else: self.log_info('No data copy requested') # Change the location of data directory in config file. - settings = QtCore.QSettings() - settings.setValue('advanced/data path', self.new_data_path) + self.settings.setValue('advanced/data path', self.new_data_path) # Check if the new data path is our default. if self.new_data_path == AppLocation.get_directory(AppLocation.DataDir): - settings.remove('advanced/data path') + self.settings.remove('advanced/data path') self.application.set_normal_cursor() def open_cmd_line_files(self, args): diff --git a/openlp/core/ui/printserviceform.py b/openlp/core/ui/printserviceform.py index 36502a681..08080a11b 100644 --- a/openlp/core/ui/printserviceform.py +++ b/openlp/core/ui/printserviceform.py @@ -389,9 +389,9 @@ class PrintServiceForm(QtWidgets.QDialog, Ui_PrintServiceDialog, RegistryPropert def save_options(self): """ - Save the self.settings and close the dialog. + Save the settings and close the dialog. """ - # Save the self.settings for this dialog. + # Save the settings for this dialog. self.settings.beginGroup('advanced') self.settings.setValue('print slide text', self.slide_text_check_box.isChecked()) self.settings.setValue('add page break', self.page_break_after_text.isChecked()) diff --git a/openlp/plugins/media/lib/mediaitem.py b/openlp/plugins/media/lib/mediaitem.py index 51a0c4b8b..790c6eec2 100644 --- a/openlp/plugins/media/lib/mediaitem.py +++ b/openlp/plugins/media/lib/mediaitem.py @@ -338,7 +338,7 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties): # Append the optical string to the media list file_paths.append(optical) self.load_list([str(optical)]) - self.settigns.setValue(self.settings_section + '/media files', file_paths) + self.settings.setValue(self.settings_section + '/media files', file_paths) def on_open_device_stream(self): """ diff --git a/tests/functional/openlp_core/api/endpoint/test_controller.py b/tests/functional/openlp_core/api/endpoint/test_controller.py index 0c9ad75d2..1d78a3233 100644 --- a/tests/functional/openlp_core/api/endpoint/test_controller.py +++ b/tests/functional/openlp_core/api/endpoint/test_controller.py @@ -18,8 +18,7 @@ # You should have received a copy of the GNU General Public License # # along with this program. If not, see . # ########################################################################## -# import sys -from unittest import TestCase +import pytest from unittest.mock import MagicMock, patch from PyQt5 import QtCore @@ -30,9 +29,7 @@ from openlp.core.state import State # Mock QtWebEngineWidgets # sys.modules['PyQt5.QtWebEngineWidgets'] = MagicMock() -from openlp.core.common.settings import Settings from openlp.core.common.registry import Registry -from openlp.core.display.screens import ScreenList from openlp.core.lib.serviceitem import ServiceItem from tests.utils import convert_file_service_item from tests.utils.constants import RESOURCE_PATH @@ -47,102 +44,105 @@ SCREEN = { } -class TestController(TestCase): +@pytest.fixture +def test_controller_env(settings): + mocked_live_controller = MagicMock() + desktop = MagicMock() + desktop.primaryScreen.return_value = SCREEN['primary'] + desktop.screenCount.return_value = SCREEN['number'] + desktop.screenGeometry.return_value = SCREEN['size'] + with patch('openlp.core.display.screens.QtWidgets.QApplication.screens') as mocked_screens: + mocked_screens.return_value = [ + MagicMock(**{'geometry.return_value': SCREEN['size']}) + ] + # Mock the renderer and its format_slide method + mocked_renderer = MagicMock() + + def side_effect_return_arg(arg1, arg2): + return [arg1] + + mocked_slide_formater = MagicMock(side_effect=side_effect_return_arg) + mocked_renderer.format_slide = mocked_slide_formater + Registry().register('live_controller', mocked_live_controller) + Registry().register('renderer', mocked_renderer) + flask_app.config['TESTING'] = True + client = flask_app.test_client() + return mocked_live_controller, client + + +def test_controller_text_empty(test_controller_env): """ - Test the Remote plugin deploy functions + Remote API Tests : test the controller text method can be called with empty service item """ + # GIVEN: A mocked service with a dummy service item + mocked_live_controller = test_controller_env[0] + client = test_controller_env[1] + mocked_service_item = MagicMock() + mocked_service_item.get_frames.return_value = [] + mocked_service_item.unique_identifier = 'mock-service-item' + mocked_live_controller.service_item = mocked_service_item - def setUp(self): - """ - Setup for tests - """ - Registry.create() - self.registry = Registry() - Registry().register('settings', Settings()) - self.mocked_live_controller = MagicMock() - self.desktop = MagicMock() - self.desktop.primaryScreen.return_value = SCREEN['primary'] - self.desktop.screenCount.return_value = SCREEN['number'] - self.desktop.screenGeometry.return_value = SCREEN['size'] - with patch('openlp.core.display.screens.QtWidgets.QApplication.screens') as mocked_screens: - mocked_screens.return_value = [ - MagicMock(**{'geometry.return_value': SCREEN['size']}) - ] - self.screens = ScreenList.create(self.desktop) - # Mock the renderer and its format_slide method - self.mocked_renderer = MagicMock() + # WHEN: I trigger the method + ret = client.get('/api/controller/live/text').get_json() - def side_effect_return_arg(arg1, arg2): - return [arg1] - self.mocked_slide_formater = MagicMock(side_effect=side_effect_return_arg) - self.mocked_renderer.format_slide = self.mocked_slide_formater - Registry().register('live_controller', self.mocked_live_controller) - Registry().register('renderer', self.mocked_renderer) - flask_app.config['TESTING'] = True - self.client = flask_app.test_client() + # THEN: I get a basic set of results + assert ret['results']['item'] == 'mock-service-item' + assert len(ret['results']['slides']) == 0 - def test_controller_text_empty(self): - """ - Remote API Tests : test the controller text method can be called with empty service item - """ - # GIVEN: A mocked service with a dummy service item - mocked_service_item = MagicMock() - mocked_service_item.get_frames.return_value = [] - mocked_service_item.unique_identifier = 'mock-service-item' - self.mocked_live_controller.service_item = mocked_service_item - # WHEN: I trigger the method - ret = self.client.get('/api/controller/live/text').get_json() +def test_controller_text(test_controller_env): + """ + Remote API Tests : test the controller text method can be called with a real service item + """ + # GIVEN: A mocked service with a dummy service item + mocked_live_controller = test_controller_env[0] + client = test_controller_env[1] + line = convert_file_service_item(TEST_PATH, 'serviceitem_custom_1.osj') + mocked_live_controller.service_item = ServiceItem(None) + State().load_settings() + State().add_service("media", 0) + State().update_pre_conditions("media", True) + State().flush_preconditions() + mocked_live_controller.service_item.set_from_service(line) + mocked_live_controller.service_item._create_slides() + # WHEN: I trigger the method + ret = client.get('/api/controller/live/text').get_json() - # THEN: I get a basic set of results - assert ret['results']['item'] == 'mock-service-item' - assert len(ret['results']['slides']) == 0 + # THEN: I get a basic set of results + results = ret['results'] + assert isinstance(ret, dict) + assert len(results['slides']) == 2 - def test_controller_text(self): - """ - Remote API Tests : test the controller text method can be called with a real service item - """ - # GIVEN: A mocked service with a dummy service item - line = convert_file_service_item(TEST_PATH, 'serviceitem_custom_1.osj') - self.mocked_live_controller.service_item = ServiceItem(None) - State().load_settings() - State().add_service("media", 0) - State().update_pre_conditions("media", True) - State().flush_preconditions() - self.mocked_live_controller.service_item.set_from_service(line) - self.mocked_live_controller.service_item._create_slides() - # WHEN: I trigger the method - ret = self.client.get('/api/controller/live/text').get_json() - # THEN: I get a basic set of results - results = ret['results'] - assert isinstance(ret, dict) - assert len(results['slides']) == 2 +def test_controller_direction_next(test_controller_env): + """ + Text the live next method is triggered + """ + # GIVEN: A mocked service with a dummy service item + mocked_emit = MagicMock() + mocked_live_controller = test_controller_env[0] + client = test_controller_env[1] + mocked_live_controller.slidecontroller_live_next.emit = mocked_emit + mocked_live_controller.service_item = MagicMock() - def test_controller_direction_next(self): - """ - Text the live next method is triggered - """ - # GIVEN: A mocked service with a dummy service item - mocked_emit = MagicMock() - self.mocked_live_controller.slidecontroller_live_next.emit = mocked_emit - self.mocked_live_controller.service_item = MagicMock() + # WHEN: I trigger the method + client.get('/api/controller/live/next') + # THEN: The correct method is called + mocked_emit.assert_called_once_with() - # WHEN: I trigger the method - self.client.get('/api/controller/live/next') - # THEN: The correct method is called - mocked_emit.assert_called_once_with() - def test_controller_direction_previous(self): - """ - Text the live next method is triggered - """ - # GIVEN: A mocked service with a dummy service item - mocked_emit = MagicMock() - self.mocked_live_controller.slidecontroller_live_previous.emit = mocked_emit - self.mocked_live_controller.service_item = MagicMock() +def test_controller_direction_previous(test_controller_env): + """ + Text the live next method is triggered + """ + # GIVEN: A mocked service with a dummy service item + mocked_emit = MagicMock() + mocked_live_controller = test_controller_env[0] + client = test_controller_env[1] + mocked_live_controller.slidecontroller_live_previous.emit = mocked_emit + mocked_live_controller.service_item = MagicMock() - # WHEN: I trigger the method - self.client.get('/api/controller/live/previous') - # THEN: The correct method is called - mocked_emit.assert_called_once_with() + # WHEN: I trigger the method + client.get('/api/controller/live/previous') + # THEN: The correct method is called + mocked_emit.assert_called_once_with() diff --git a/tests/functional/openlp_core/api/http/test_http.py b/tests/functional/openlp_core/api/http/test_http.py deleted file mode 100644 index ee02cb73a..000000000 --- a/tests/functional/openlp_core/api/http/test_http.py +++ /dev/null @@ -1,71 +0,0 @@ -# -*- coding: utf-8 -*- - -########################################################################## -# OpenLP - Open Source Lyrics Projection # -# ---------------------------------------------------------------------- # -# Copyright (c) 2008-2020 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 . # -########################################################################## -""" -Functional tests to test the Http Server Class. -""" -from unittest import TestCase -from unittest.mock import MagicMock, patch - -from openlp.core.api.http.server import HttpServer -from openlp.core.common.registry import Registry - - -class TestHttpServer(TestCase): - """ - A test suite to test starting the http server - """ - - def setUp(self): - """ - Create the UI - """ - Registry().create() - Registry().register('service_list', MagicMock()) - - @patch('openlp.core.api.http.server.HttpWorker') - @patch('openlp.core.api.http.server.run_thread') - def test_server_start(self, mocked_run_thread, MockHttpWorker): - """ - Test the starting of the Waitress Server with the disable flag set off - """ - # GIVEN: A new httpserver - # WHEN: I start the server - Registry().set_flag('no_web_server', False) - HttpServer() - - # THEN: the api environment should have been created - assert mocked_run_thread.call_count == 1, 'The qthread should have been called once' - assert MockHttpWorker.call_count == 1, 'The http thread should have been called once' - - @patch('openlp.core.api.http.server.HttpWorker') - @patch('openlp.core.api.http.server.run_thread') - def test_server_start_not_required(self, mocked_run_thread, MockHttpWorker): - """ - Test the starting of the Waitress Server with the disable flag set off - """ - # GIVEN: A new httpserver - # WHEN: I start the server - Registry().set_flag('no_web_server', True) - HttpServer() - - # THEN: the api environment should have been created - assert mocked_run_thread.call_count == 0, 'The qthread should not have have been called' - assert MockHttpWorker.call_count == 0, 'The http thread should not have been called' diff --git a/tests/functional/openlp_core/api/http/test_init.py b/tests/functional/openlp_core/api/http/test_init.py deleted file mode 100644 index b158b5ca8..000000000 --- a/tests/functional/openlp_core/api/http/test_init.py +++ /dev/null @@ -1,167 +0,0 @@ -# -*- coding: utf-8 -*- - -########################################################################## -# OpenLP - Open Source Lyrics Projection # -# ---------------------------------------------------------------------- # -# Copyright (c) 2008-2020 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 . # -########################################################################## -""" -Functional tests to test the Http init. -""" -from unittest import TestCase -from unittest.mock import MagicMock - -from openlp.core.api.http import authenticate, check_auth, requires_auth -from openlp.core.common.registry import Registry -from openlp.core.common.settings import Settings -from tests.helpers.testmixin import TestMixin - - -class TestInit(TestCase, TestMixin): - """ - A test suite to test the functions on the init - """ - - def setUp(self): - """ - Create the UI - """ - Registry().create() - Registry().register('service_list', MagicMock()) - self.build_settings() - self.password = 'c3VwZXJmbHk6bGFtYXM=' - Registry().register('settings', Settings()) - - def tearDown(self): - self.destroy_settings() - - def test_auth(self): - """ - Test the check_auth method with a match - :return: - """ - # GIVEN: a known user - Settings().setValue('api/user id', "superfly") - Settings().setValue('api/password', "lamas") - - # WHEN : I check the authorisation - is_valid = check_auth(['aaaaa', self.password]) - - # THEN: - assert is_valid is True - - def test_auth_falure(self): - """ - Test the check_auth method with a match - :return: - """ - # GIVEN: a known user - Settings().setValue('api/user id', 'superfly') - Settings().setValue('api/password', 'lamas') - - # WHEN : I check the authorisation - is_valid = check_auth(['aaaaa', 'monkey123']) - - # THEN: - assert is_valid is False - - def test_requires_auth_disabled(self): - """ - Test the requires_auth wrapper with disabled security - :return: - """ - # GIVEN: A disabled security - Settings().setValue('api/authentication enabled', False) - - # WHEN: I call the function - wrapped_function = requires_auth(func) - value = wrapped_function() - - # THEN: the result will be as expected - assert value == 'called' - - def test_requires_auth_enabled(self): - """ - Test the requires_auth wrapper with enabled security - :return: - """ - # GIVEN: A disabled security - Settings().setValue('api/authentication enabled', True) - - # WHEN: I call the function - wrapped_function = requires_auth(func) - req = MagicMock() - value = wrapped_function(req) - - # THEN: the result will be as expected - assert str(value) == str(authenticate()) - - def test_requires_auth_enabled_auth_error(self): - """ - Test the requires_auth wrapper with enabled security and authorization taken place and and error - :return: - """ - # GIVEN: A enabled security - Settings().setValue('api/authentication enabled', True) - - # WHEN: I call the function with the wrong password - wrapped_function = requires_auth(func) - req = MagicMock() - req.authorization = ['Basic', 'cccccccc'] - value = wrapped_function(req) - - # THEN: the result will be as expected - try again - assert str(value) == str(authenticate()) - - def test_requires_auth_enabled_auth(self): - """ - Test the requires_auth wrapper with enabled security and authorization taken place and and error - :return: - """ - # GIVEN: An enabled security and a known user - Settings().setValue('api/authentication enabled', True) - Settings().setValue('api/user id', 'superfly') - Settings().setValue('api/password', 'lamas') - - # WHEN: I call the function with the wrong password - wrapped_function = requires_auth(func) - req = MagicMock() - req.authorization = ['Basic', self.password] - value = wrapped_function(req) - - # THEN: the result will be as expected - try again - assert str(value) == 'called' - - def test_requires_auth_missing_credentials(self): - """ - Test the requires_auth wrapper with enabled security and authorization taken place and and error - :return: - """ - # GIVEN: An enabled security and a known user - Settings().setValue('api/authentication enabled', True) - Settings().setValue('api/user id', 'superfly') - Settings().setValue('api/password', 'lamas') - - # WHEN: I call the function with no password - wrapped_function = requires_auth(func) - value = wrapped_function(0) - - # THEN: the result will be as expected (unauthorized) - assert str(value) == str(authenticate()) - - -def func(field=None): - return 'called' diff --git a/tests/functional/openlp_core/api/http/__init__.py b/tests/functional/openlp_core/api/http_server/__init__.py similarity index 100% rename from tests/functional/openlp_core/api/http/__init__.py rename to tests/functional/openlp_core/api/http_server/__init__.py diff --git a/tests/functional/openlp_core/api/http_server/test_http.py b/tests/functional/openlp_core/api/http_server/test_http.py new file mode 100644 index 000000000..bff5f5171 --- /dev/null +++ b/tests/functional/openlp_core/api/http_server/test_http.py @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- + +########################################################################## +# OpenLP - Open Source Lyrics Projection # +# ---------------------------------------------------------------------- # +# Copyright (c) 2008-2020 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 . # +########################################################################## +""" +Functional tests to test the Http Server Class. +""" +from unittest.mock import patch + +from openlp.core.api.http.server import HttpServer +from openlp.core.common.registry import Registry + + +@patch('openlp.core.api.http.server.HttpWorker') +@patch('openlp.core.api.http.server.run_thread') +def test_server_start(mocked_run_thread, MockHttpWorker, registry): + """ + Test the starting of the Waitress Server with the disable flag set off + """ + # GIVEN: A new httpserver + # WHEN: I start the server + Registry().set_flag('no_web_server', False) + HttpServer() + + # THEN: the api environment should have been created + assert mocked_run_thread.call_count == 1, 'The qthread should have been called once' + assert MockHttpWorker.call_count == 1, 'The http thread should have been called once' + + +@patch('openlp.core.api.http.server.HttpWorker') +@patch('openlp.core.api.http.server.run_thread') +def test_server_start_not_required(mocked_run_thread, MockHttpWorker, registry): + """ + Test the starting of the Waitress Server with the disable flag set off + """ + # GIVEN: A new httpserver + # WHEN: I start the server + Registry().set_flag('no_web_server', True) + HttpServer() + + # THEN: the api environment should have been created + assert mocked_run_thread.call_count == 0, 'The qthread should not have have been called' + assert MockHttpWorker.call_count == 0, 'The http thread should not have been called' diff --git a/tests/functional/openlp_core/api/http_server/test_init.py b/tests/functional/openlp_core/api/http_server/test_init.py new file mode 100644 index 000000000..606bec7d2 --- /dev/null +++ b/tests/functional/openlp_core/api/http_server/test_init.py @@ -0,0 +1,154 @@ +# -*- coding: utf-8 -*- + +########################################################################## +# OpenLP - Open Source Lyrics Projection # +# ---------------------------------------------------------------------- # +# Copyright (c) 2008-2020 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 . # +########################################################################## +""" +Functional tests to test the Http init. +""" +from unittest.mock import MagicMock + +from openlp.core.api.http import authenticate, check_auth, requires_auth + + +password = 'c3VwZXJmbHk6bGFtYXM=' + + +def test_auth(settings): + """ + Test the check_auth method with a match + :return: + """ + # GIVEN: a known user + settings.setValue('api/user id', "superfly") + settings.setValue('api/password', "lamas") + + # WHEN : I check the authorisation + is_valid = check_auth(['aaaaa', password]) + + # THEN: + assert is_valid is True + + +def test_auth_falure(settings): + """ + Test the check_auth method with a match + :return: + """ + # GIVEN: a known user + settings.setValue('api/user id', 'superfly') + settings.setValue('api/password', 'lamas') + + # WHEN : I check the authorisation + is_valid = check_auth(['aaaaa', 'monkey123']) + + # THEN: + assert is_valid is False + + +def test_requires_auth_disabled(settings): + """ + Test the requires_auth wrapper with disabled security + :return: + """ + # GIVEN: A disabled security + settings.setValue('api/authentication enabled', False) + + # WHEN: I call the function + wrapped_function = requires_auth(func) + value = wrapped_function() + + # THEN: the result will be as expected + assert value == 'called' + + +def test_requires_auth_enabled(settings): + """ + Test the requires_auth wrapper with enabled security + :return: + """ + # GIVEN: A disabled security + settings.setValue('api/authentication enabled', True) + + # WHEN: I call the function + wrapped_function = requires_auth(func) + req = MagicMock() + value = wrapped_function(req) + + # THEN: the result will be as expected + assert str(value) == str(authenticate()) + + +def test_requires_auth_enabled_auth_error(settings): + """ + Test the requires_auth wrapper with enabled security and authorization taken place and and error + :return: + """ + # GIVEN: A enabled security + settings.setValue('api/authentication enabled', True) + + # WHEN: I call the function with the wrong password + wrapped_function = requires_auth(func) + req = MagicMock() + req.authorization = ['Basic', 'cccccccc'] + value = wrapped_function(req) + + # THEN: the result will be as expected - try again + assert str(value) == str(authenticate()) + + +def test_requires_auth_enabled_auth(settings): + """ + Test the requires_auth wrapper with enabled security and authorization taken place and and error + :return: + """ + # GIVEN: An enabled security and a known user + settings.setValue('api/authentication enabled', True) + settings.setValue('api/user id', 'superfly') + settings.setValue('api/password', 'lamas') + + # WHEN: I call the function with the wrong password + wrapped_function = requires_auth(func) + req = MagicMock() + req.authorization = ['Basic', password] + value = wrapped_function(req) + + # THEN: the result will be as expected - try again + assert str(value) == 'called' + + +def test_requires_auth_missing_credentials(settings): + """ + Test the requires_auth wrapper with enabled security and authorization taken place and and error + :return: + """ + # GIVEN: An enabled security and a known user + settings.setValue('api/authentication enabled', True) + settings.setValue('api/user id', 'superfly') + settings.setValue('api/password', 'lamas') + + # WHEN: I call the function with no password + wrapped_function = requires_auth(func) + value = wrapped_function(0) + + # THEN: the result will be as expected (unauthorized) + assert str(value) == str(authenticate()) + + +def func(field=None): + return 'called' diff --git a/tests/functional/openlp_core/api/test_tab.py b/tests/functional/openlp_core/api/test_tab.py index 4e310a3f5..f7ab507c2 100644 --- a/tests/functional/openlp_core/api/test_tab.py +++ b/tests/functional/openlp_core/api/test_tab.py @@ -21,101 +21,75 @@ """ This module contains tests for the lib submodule of the Remotes plugin. """ +import pytest import re -from unittest import TestCase from PyQt5 import QtWidgets from openlp.core.api.tab import ApiTab from openlp.core.common import get_network_interfaces from openlp.core.common.registry import Registry -from openlp.core.common.settings import Settings -from tests.helpers.testmixin import TestMixin - -__default_settings__ = { - 'api/twelve hour': True, - 'api/port': 4316, - 'api/user id': 'openlp', - 'api/password': 'password', - 'api/authentication enabled': False, - 'api/ip address': '0.0.0.0', - 'api/thumbnails': True, - 'remotes/download version': '0000_00_00' -} ZERO_URL = '0.0.0.0' -class TestApiTab(TestCase, TestMixin): +@pytest.yield_fixture +def api_tab(settings): + Registry().set_flag('website_version', '00-00-0000') + parent = QtWidgets.QMainWindow() + form = ApiTab(parent) + yield form + del parent + del form + + +def test_get_ip_address_default(api_tab): """ - Test the functions in the :mod:`lib` module. + Test the get_ip_address function with ZERO_URL """ - def setUp(self): - """ - Create the UI - """ - self.setup_application() - self.build_settings() - Settings().extend_default_settings(__default_settings__) - self.parent = QtWidgets.QMainWindow() - Registry().create() - Registry().set_flag('website_version', '00-00-0000') - Registry().register('settings', Settings()) - self.form = ApiTab(self.parent) - self.interfaces = get_network_interfaces() + # GIVEN: list of local IP addresses for this machine + ip_addresses = [] + interfaces = get_network_interfaces() + for _, interface in interfaces.items(): + ip_addresses.append(interface['ip']) - def tearDown(self): - """ - Delete all the C++ objects at the end so that we don't have a segfault - """ - del self.parent - del self.form - self.destroy_settings() + # WHEN: the default ip address is given + ip_address = api_tab.get_ip_address(ZERO_URL) - def test_get_ip_address_default(self): - """ - Test the get_ip_address function with ZERO_URL - """ - # GIVEN: list of local IP addresses for this machine - ip_addresses = [] - for _, interface in self.interfaces.items(): - ip_addresses.append(interface['ip']) + # THEN: the default ip address will be returned + assert re.match(r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}', ip_address), \ + 'The return value should be a valid ip address' + assert ip_address in ip_addresses, 'The return address should be in the list of local IP addresses' - # WHEN: the default ip address is given - ip_address = self.form.get_ip_address(ZERO_URL) - # THEN: the default ip address will be returned - assert re.match(r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}', ip_address), \ - 'The return value should be a valid ip address' - assert ip_address in ip_addresses, 'The return address should be in the list of local IP addresses' +def test_get_ip_address_with_ip(api_tab): + """ + Test the get_ip_address function with given ip address + """ + # GIVEN: An ip address + given_ip = '192.168.1.1' - def test_get_ip_address_with_ip(self): - """ - Test the get_ip_address function with given ip address - """ - # GIVEN: An ip address - given_ip = '192.168.1.1' + # WHEN: the default ip address is given + ip_address = api_tab.get_ip_address(given_ip) - # WHEN: the default ip address is given - ip_address = self.form.get_ip_address(given_ip) + # THEN: the default ip address will be returned + assert ip_address == given_ip, 'The return value should be %s' % given_ip - # THEN: the default ip address will be returned - assert ip_address == given_ip, 'The return value should be %s' % given_ip - def test_set_urls(self): - """ - Test the set_url function to generate correct url links - """ - # GIVEN: An ip address - self.form.address_edit.setText('192.168.1.1') - # WHEN: the urls are generated - self.form.set_urls() - # THEN: the following links are returned - assert self.form.remote_url.text() == "http://192.168.1.1:4316/", \ - 'The return value should be a fully formed link' - assert self.form.stage_url.text() == \ - "http://192.168.1.1:4316/stage", \ - 'The return value should be a fully formed stage link' - assert self.form.live_url.text() == \ - "http://192.168.1.1:4316/main", \ - 'The return value should be a fully formed main link' +def test_set_urls(api_tab): + """ + Test the set_url function to generate correct url links + """ + # GIVEN: An ip address + api_tab.address_edit.setText('192.168.1.1') + # WHEN: the urls are generated + api_tab.set_urls() + # THEN: the following links are returned + assert api_tab.remote_url.text() == "http://192.168.1.1:4316/", \ + 'The return value should be a fully formed link' + assert api_tab.stage_url.text() == \ + "http://192.168.1.1:4316/stage", \ + 'The return value should be a fully formed stage link' + assert api_tab.live_url.text() == \ + "http://192.168.1.1:4316/main", \ + 'The return value should be a fully formed main link' diff --git a/tests/functional/openlp_core/api/test_websockets.py b/tests/functional/openlp_core/api/test_websockets.py index 344f2a251..65fdac822 100644 --- a/tests/functional/openlp_core/api/test_websockets.py +++ b/tests/functional/openlp_core/api/test_websockets.py @@ -21,122 +21,97 @@ """ Functional tests to test the Http Server Class. """ -from unittest import TestCase +import pytest from unittest.mock import MagicMock, patch from openlp.core.api.poll import Poller from openlp.core.api.websockets import WebSocketServer from openlp.core.common.registry import Registry -from openlp.core.common.settings import Settings -from tests.helpers.testmixin import TestMixin -__default_settings__ = { - 'api/twelve hour': True, - 'api/port': 4316, - 'api/user id': 'openlp', - 'api/password': 'password', - 'api/authentication enabled': False, - 'api/ip address': '0.0.0.0', - 'api/thumbnails': True, - 'songs/chord notation': True -} +@pytest.yield_fixture +def poller(registry): + poll = Poller() + yield poll -class TestWSServer(TestCase, TestMixin): +@patch('openlp.core.api.websockets.WebSocketWorker') +@patch('openlp.core.api.websockets.run_thread') +def test_serverstart(mocked_run_thread, MockWebSocketWorker, registry): """ - A test suite to test starting the websocket server + Test the starting of the WebSockets Server with the disabled flag set on """ - def setUp(self): - """ - Create the UI - """ - self.build_settings() - Settings().extend_default_settings(__default_settings__) - Registry().create() - Registry().register('settings', Settings()) - self.poll = Poller() + # GIVEN: A new httpserver + # WHEN: I start the server + Registry().set_flag('no_web_server', False) + WebSocketServer() - def tearDown(self): - """ - Delete all the C++ objects at the end so that we don't have a segfault - """ - self.destroy_settings() + # THEN: the api environment should have been created + assert mocked_run_thread.call_count == 1, 'The qthread should have been called once' + assert MockWebSocketWorker.call_count == 1, 'The http thread should have been called once' - @patch('openlp.core.api.websockets.WebSocketWorker') - @patch('openlp.core.api.websockets.run_thread') - def test_serverstart(self, mocked_run_thread, MockWebSocketWorker): - """ - Test the starting of the WebSockets Server with the disabled flag set on - """ - # GIVEN: A new httpserver - # WHEN: I start the server - Registry().set_flag('no_web_server', False) - WebSocketServer() - # THEN: the api environment should have been created - assert mocked_run_thread.call_count == 1, 'The qthread should have been called once' - assert MockWebSocketWorker.call_count == 1, 'The http thread should have been called once' +@patch('openlp.core.api.websockets.WebSocketWorker') +@patch('openlp.core.api.websockets.run_thread') +def test_serverstart_not_required(mocked_run_thread, MockWebSocketWorker, registry): + """ + Test the starting of the WebSockets Server with the disabled flag set off + """ + # GIVEN: A new httpserver and the server is not required + # WHEN: I start the server + Registry().set_flag('no_web_server', True) + WebSocketServer() - @patch('openlp.core.api.websockets.WebSocketWorker') - @patch('openlp.core.api.websockets.run_thread') - def test_serverstart_not_required(self, mocked_run_thread, MockWebSocketWorker): - """ - Test the starting of the WebSockets Server with the disabled flag set off - """ - # GIVEN: A new httpserver and the server is not required - # WHEN: I start the server - Registry().set_flag('no_web_server', True) - WebSocketServer() + # THEN: the api environment should have been created + assert mocked_run_thread.call_count == 0, 'The qthread should not have been called' + assert MockWebSocketWorker.call_count == 0, 'The http thread should not have been called' - # THEN: the api environment should have been created - assert mocked_run_thread.call_count == 0, 'The qthread should not have been called' - assert MockWebSocketWorker.call_count == 0, 'The http thread should not have been called' - def test_main_poll(self): - """ - Test the main_poll function returns the correct JSON - """ - # WHEN: the live controller has 5 slides - mocked_live_controller = MagicMock() - mocked_live_controller.slide_count = 5 - Registry().register('live_controller', mocked_live_controller) - # THEN: the live json should be generated - main_json = self.poll.main_poll() - assert b'{"results": {"slide_count": 5}}' == main_json, 'The return value should match the defined json' +def test_main_poll(poller): + """ + Test the main_poll function returns the correct JSON + """ + # WHEN: the live controller has 5 slides + mocked_live_controller = MagicMock() + mocked_live_controller.slide_count = 5 + Registry().register('live_controller', mocked_live_controller) + # THEN: the live json should be generated + main_json = poller.main_poll() + assert b'{"results": {"slide_count": 5}}' == main_json, 'The return value should match the defined json' - def test_poll(self): - """ - Test the poll function returns the correct JSON - """ - # GIVEN: the system is configured with a set of data - mocked_service_manager = MagicMock() - mocked_service_manager.service_id = 21 - mocked_live_controller = MagicMock() - mocked_live_controller.selected_row = 5 - mocked_live_controller.service_item = MagicMock() - mocked_live_controller.service_item.unique_identifier = '23-34-45' - mocked_live_controller.blank_screen.isChecked.return_value = True - mocked_live_controller.theme_screen.isChecked.return_value = False - mocked_live_controller.desktop_screen.isChecked.return_value = False - Registry().register('live_controller', mocked_live_controller) - Registry().register('service_manager', mocked_service_manager) - # WHEN: The poller polls - with patch.object(self.poll, 'is_stage_active') as mocked_is_stage_active, \ - patch.object(self.poll, 'is_live_active') as mocked_is_live_active, \ - patch.object(self.poll, 'is_chords_active') as mocked_is_chords_active: - mocked_is_stage_active.return_value = True - mocked_is_live_active.return_value = True - mocked_is_chords_active.return_value = True - poll_json = self.poll.poll() - # THEN: the live json should be generated and match expected results - assert poll_json['results']['blank'] is True, 'The blank return value should be True' - assert poll_json['results']['theme'] is False, 'The theme return value should be False' - assert poll_json['results']['display'] is False, 'The display return value should be False' - assert poll_json['results']['isSecure'] is False, 'The isSecure return value should be False' - assert poll_json['results']['isAuthorised'] is False, 'The isAuthorised return value should be False' - assert poll_json['results']['twelve'] is True, 'The twelve return value should be True' - assert poll_json['results']['version'] == 3, 'The version return value should be 3' - assert poll_json['results']['slide'] == 5, 'The slide return value should be 5' - assert poll_json['results']['service'] == 21, 'The version return value should be 21' - assert poll_json['results']['item'] == '23-34-45', 'The item return value should match 23-34-45' + +def test_poll(poller, settings): + """ + Test the poll function returns the correct JSON + """ + # GIVEN: the system is configured with a set of data + mocked_service_manager = MagicMock() + mocked_service_manager.service_id = 21 + mocked_live_controller = MagicMock() + mocked_live_controller.selected_row = 5 + mocked_live_controller.service_item = MagicMock() + mocked_live_controller.service_item.unique_identifier = '23-34-45' + mocked_live_controller.blank_screen.isChecked.return_value = True + mocked_live_controller.theme_screen.isChecked.return_value = False + mocked_live_controller.desktop_screen.isChecked.return_value = False + Registry().register('live_controller', mocked_live_controller) + Registry().register('service_manager', mocked_service_manager) + # WHEN: The poller polls + with patch.object(poller, 'is_stage_active') as mocked_is_stage_active, \ + patch.object(poller, 'is_live_active') as mocked_is_live_active, \ + patch.object(poller, 'is_chords_active') as mocked_is_chords_active: + mocked_is_stage_active.return_value = True + mocked_is_live_active.return_value = True + mocked_is_chords_active.return_value = True + poll_json = poller.poll() + # THEN: the live json should be generated and match expected results + assert poll_json['results']['blank'] is True, 'The blank return value should be True' + assert poll_json['results']['theme'] is False, 'The theme return value should be False' + assert poll_json['results']['display'] is False, 'The display return value should be False' + assert poll_json['results']['isSecure'] is False, 'The isSecure return value should be False' + assert poll_json['results']['isAuthorised'] is False, 'The isAuthorised return value should be False' + assert poll_json['results']['twelve'] is True, 'The twelve return value should be True' + assert poll_json['results']['version'] == 3, 'The version return value should be 3' + assert poll_json['results']['slide'] == 5, 'The slide return value should be 5' + assert poll_json['results']['service'] == 21, 'The version return value should be 21' + assert poll_json['results']['item'] == '23-34-45', 'The item return value should match 23-34-45' diff --git a/tests/functional/openlp_core/api/v2/test_controller.py b/tests/functional/openlp_core/api/v2/test_controller.py index f109f7af5..05c2786fc 100644 --- a/tests/functional/openlp_core/api/v2/test_controller.py +++ b/tests/functional/openlp_core/api/v2/test_controller.py @@ -1,8 +1,27 @@ +# -*- coding: utf-8 -*- + +########################################################################## +# OpenLP - Open Source Lyrics Projection # +# ---------------------------------------------------------------------- # +# Copyright (c) 2008-2020 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 . # +########################################################################## import pytest from unittest.mock import MagicMock from openlp.core.common.registry import Registry -from openlp.core.common.settings import Settings def test_retrieve_live_item(flask_client): @@ -11,11 +30,10 @@ def test_retrieve_live_item(flask_client): assert len(res) == 0 -def test_controller_set_requires_login(flask_client): - pytest.skip('Need to figure out how to patch settings for one test only') - # Settings().setValue('api/authentication enabled', True) +def test_controller_set_requires_login(settings, flask_client): + settings.setValue('api/authentication enabled', True) res = flask_client.post('/api/v2/controller/show', json=dict()) - Settings().setValue('api/authentication enabled', False) + settings.setValue('api/authentication enabled', False) assert res.status_code == 401 @@ -33,9 +51,9 @@ def test_controller_set_calls_live_controller(flask_client, settings): def test_controller_direction_requires_login(flask_client, settings): - Settings().setValue('api/authentication enabled', True) + settings.setValue('api/authentication enabled', True) res = flask_client.post('/api/v2/controller/progress', json=dict()) - Settings().setValue('api/authentication enabled', False) + settings.setValue('api/authentication enabled', False) assert res.status_code == 401 diff --git a/tests/functional/openlp_core/api/v2/test_core.py b/tests/functional/openlp_core/api/v2/test_core.py index 11914c974..3c7d4d8c8 100644 --- a/tests/functional/openlp_core/api/v2/test_core.py +++ b/tests/functional/openlp_core/api/v2/test_core.py @@ -1,5 +1,24 @@ +# -*- coding: utf-8 -*- + +########################################################################## +# OpenLP - Open Source Lyrics Projection # +# ---------------------------------------------------------------------- # +# Copyright (c) 2008-2020 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 . # +########################################################################## from openlp.core.common.registry import Registry -from openlp.core.common.settings import Settings from openlp.core.state import State from openlp.core.lib.plugin import PluginStatus, StringContent @@ -28,7 +47,7 @@ def test_plugins_returns_list(flask_client): def test_system_information(flask_client, settings): - Settings().setValue('api/authentication enabled', False) + settings.setValue('api/authentication enabled', False) res = flask_client.get('/api/v2/core/system').get_json() assert res['websocket_port'] > 0 assert not res['login_required'] @@ -78,9 +97,9 @@ def test_retrieving_image(flask_client): def test_toggle_display_requires_login(flask_client, settings): - Settings().setValue('api/authentication enabled', True) + settings.setValue('api/authentication enabled', True) res = flask_client.post('/api/v2/core/display') - Settings().setValue('api/authentication enabled', False) + settings.setValue('api/authentication enabled', False) assert res.status_code == 401 diff --git a/tests/functional/openlp_core/api/v2/test_service.py b/tests/functional/openlp_core/api/v2/test_service.py index f6d6a8b12..bde1216e3 100644 --- a/tests/functional/openlp_core/api/v2/test_service.py +++ b/tests/functional/openlp_core/api/v2/test_service.py @@ -1,20 +1,42 @@ -import pytest +# -*- coding: utf-8 -*- + +########################################################################## +# OpenLP - Open Source Lyrics Projection # +# ---------------------------------------------------------------------- # +# Copyright (c) 2008-2020 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 . # +########################################################################## from unittest.mock import MagicMock from openlp.core.common.registry import Registry -from openlp.core.common.settings import Settings def test_retrieve_service_items(flask_client, settings): - pytest.skip() + mocked_live_controller = MagicMock() + mocked_live_controller.service_item = MagicMock() + fake_service_manager = MagicMock() + Registry().register('service_manager', fake_service_manager) + Registry().register('live_controller', mocked_live_controller) res = flask_client.get('/api/v2/service/items').get_json() assert len(res) == 0 def test_service_set_requires_login(flask_client, settings): - Settings().setValue('api/authentication enabled', True) + settings.setValue('api/authentication enabled', True) res = flask_client.post('/api/v2/service/show', json=dict()) - Settings().setValue('api/authentication enabled', False) + settings.setValue('api/authentication enabled', False) assert res.status_code == 401 @@ -32,9 +54,9 @@ def test_service_set_calls_service_manager(flask_client, settings): def test_service_direction_requires_login(flask_client, settings): - Settings().setValue('api/authentication enabled', True) + settings.setValue('api/authentication enabled', True) res = flask_client.post('/api/v2/service/progress', json=dict()) - Settings().setValue('api/authentication enabled', False) + settings.setValue('api/authentication enabled', False) assert res.status_code == 401 diff --git a/tests/functional/openlp_core/common/test_actions.py b/tests/functional/openlp_core/common/test_actions.py index 11721c1ab..fb8d8d939 100644 --- a/tests/functional/openlp_core/common/test_actions.py +++ b/tests/functional/openlp_core/common/test_actions.py @@ -21,20 +21,25 @@ """ Package to test the openlp.core.common.actions package. """ -from unittest import TestCase +import pytest from unittest.mock import MagicMock from PyQt5 import QtCore, QtGui, QtWidgets from openlp.core.common.actions import ActionList, CategoryActionList -from openlp.core.common.settings import Settings -from tests.helpers.testmixin import TestMixin +from openlp.core.common.registry import Registry MOCK_ACTION1 = MagicMock(**{'text.return_value': 'first'}) MOCK_ACTION2 = MagicMock(**{'text.return_value': 'second'}) +@pytest.yield_fixture +def action_list(settings): + act_list = ActionList.get_instance() + yield act_list + + def test_action_list_contains(): """ Test the __contains__() method @@ -151,118 +156,98 @@ def test_action_list_remove(): assert MOCK_ACTION1 not in category_list -class TestActionList(TestCase, TestMixin): +def test_add_action_same_parent(action_list): """ - Test the ActionList class + ActionList test - Tests the add_action method. The actions have the same parent, the same shortcuts and both + have the QtCore.Qt.WindowShortcut shortcut context set. """ + # GIVEN: Two actions with the same shortcuts. + parent = QtCore.QObject() + action1 = QtWidgets.QAction(parent) + action1.setObjectName('action1') + action_with_same_shortcuts1 = QtWidgets.QAction(parent) + action_with_same_shortcuts1.setObjectName('action_with_same_shortcuts1') + # Add default shortcuts to Settings class. + default_shortcuts = { + 'shortcuts/action1': [QtGui.QKeySequence(QtCore.Qt.Key_A), QtGui.QKeySequence(QtCore.Qt.Key_B)], + 'shortcuts/action_with_same_shortcuts1': [QtGui.QKeySequence(QtCore.Qt.Key_B), + QtGui.QKeySequence(QtCore.Qt.Key_A)] + } + Registry().get('settings').extend_default_settings(default_shortcuts) - def setUp(self): - """ - Prepare the tests - """ - self.setup_application() - self.action_list = ActionList.get_instance() - self.build_settings() - self.settings = Settings() - self.settings.beginGroup('shortcuts') + # WHEN: Add the two actions to the action list. + action_list.add_action(action1, 'example_category') + action_list.add_action(action_with_same_shortcuts1, 'example_category') + # Remove the actions again. + action_list.remove_action(action1, 'example_category') + action_list.remove_action(action_with_same_shortcuts1, 'example_category') - def tearDown(self): - """ - Clean up - """ - self.settings.endGroup() - self.destroy_settings() + # THEN: As both actions have the same shortcuts, they should be removed from one action. + assert len(action1.shortcuts()) == 2, 'The action should have two shortcut assigned.' + assert len(action_with_same_shortcuts1.shortcuts()) == 0, 'The action should not have a shortcut assigned.' - def test_add_action_same_parent(self): - """ - ActionList test - Tests the add_action method. The actions have the same parent, the same shortcuts and both - have the QtCore.Qt.WindowShortcut shortcut context set. - """ - # GIVEN: Two actions with the same shortcuts. - parent = QtCore.QObject() - action1 = QtWidgets.QAction(parent) - action1.setObjectName('action1') - action_with_same_shortcuts1 = QtWidgets.QAction(parent) - action_with_same_shortcuts1.setObjectName('action_with_same_shortcuts1') - # Add default shortcuts to Settings class. - default_shortcuts = { - 'shortcuts/action1': [QtGui.QKeySequence(QtCore.Qt.Key_A), QtGui.QKeySequence(QtCore.Qt.Key_B)], - 'shortcuts/action_with_same_shortcuts1': [QtGui.QKeySequence(QtCore.Qt.Key_B), - QtGui.QKeySequence(QtCore.Qt.Key_A)] - } - Settings.extend_default_settings(default_shortcuts) - # WHEN: Add the two actions to the action list. - self.action_list.add_action(action1, 'example_category') - self.action_list.add_action(action_with_same_shortcuts1, 'example_category') - # Remove the actions again. - self.action_list.remove_action(action1, 'example_category') - self.action_list.remove_action(action_with_same_shortcuts1, 'example_category') +def test_add_action_different_parent(action_list): + """ + ActionList test - Tests the add_action method. The actions have the different parent, the same shortcuts and + both have the QtCore.Qt.WindowShortcut shortcut context set. + """ + # GIVEN: Two actions with the same shortcuts. + parent = QtCore.QObject() + action2 = QtWidgets.QAction(parent) + action2.setObjectName('action2') + second_parent = QtCore.QObject() + action_with_same_shortcuts2 = QtWidgets.QAction(second_parent) + action_with_same_shortcuts2.setObjectName('action_with_same_shortcuts2') + # Add default shortcuts to Settings class. + default_shortcuts = { + 'shortcuts/action2': [QtGui.QKeySequence(QtCore.Qt.Key_C), QtGui.QKeySequence(QtCore.Qt.Key_D)], + 'shortcuts/action_with_same_shortcuts2': [QtGui.QKeySequence(QtCore.Qt.Key_D), + QtGui.QKeySequence(QtCore.Qt.Key_C)] + } + Registry().get('settings').extend_default_settings(default_shortcuts) - # THEN: As both actions have the same shortcuts, they should be removed from one action. - assert len(action1.shortcuts()) == 2, 'The action should have two shortcut assigned.' - assert len(action_with_same_shortcuts1.shortcuts()) == 0, 'The action should not have a shortcut assigned.' + # WHEN: Add the two actions to the action list. + action_list.add_action(action2, 'example_category') + action_list.add_action(action_with_same_shortcuts2, 'example_category') + # Remove the actions again. + action_list.remove_action(action2, 'example_category') + action_list.remove_action(action_with_same_shortcuts2, 'example_category') - def test_add_action_different_parent(self): - """ - ActionList test - Tests the add_action method. The actions have the different parent, the same shortcuts and - both have the QtCore.Qt.WindowShortcut shortcut context set. - """ - # GIVEN: Two actions with the same shortcuts. - parent = QtCore.QObject() - action2 = QtWidgets.QAction(parent) - action2.setObjectName('action2') - second_parent = QtCore.QObject() - action_with_same_shortcuts2 = QtWidgets.QAction(second_parent) - action_with_same_shortcuts2.setObjectName('action_with_same_shortcuts2') - # Add default shortcuts to Settings class. - default_shortcuts = { - 'shortcuts/action2': [QtGui.QKeySequence(QtCore.Qt.Key_C), QtGui.QKeySequence(QtCore.Qt.Key_D)], - 'shortcuts/action_with_same_shortcuts2': [QtGui.QKeySequence(QtCore.Qt.Key_D), - QtGui.QKeySequence(QtCore.Qt.Key_C)] - } - Settings.extend_default_settings(default_shortcuts) + # THEN: As both actions have the same shortcuts, they should be removed from one action. + assert len(action2.shortcuts()) == 2, 'The action should have two shortcut assigned.' + assert len(action_with_same_shortcuts2.shortcuts()) == 0, 'The action should not have a shortcut assigned.' - # WHEN: Add the two actions to the action list. - self.action_list.add_action(action2, 'example_category') - self.action_list.add_action(action_with_same_shortcuts2, 'example_category') - # Remove the actions again. - self.action_list.remove_action(action2, 'example_category') - self.action_list.remove_action(action_with_same_shortcuts2, 'example_category') - # THEN: As both actions have the same shortcuts, they should be removed from one action. - assert len(action2.shortcuts()) == 2, 'The action should have two shortcut assigned.' - assert len(action_with_same_shortcuts2.shortcuts()) == 0, 'The action should not have a shortcut assigned.' +def test_add_action_different_context(action_list): + """ + ActionList test - Tests the add_action method. The actions have the different parent, the same shortcuts and + both have the QtCore.Qt.WidgetShortcut shortcut context set. + """ + # GIVEN: Two actions with the same shortcuts. + parent = QtCore.QObject() + action3 = QtWidgets.QAction(parent) + action3.setObjectName('action3') + action3.setShortcutContext(QtCore.Qt.WidgetShortcut) + second_parent = QtCore.QObject() + action_with_same_shortcuts3 = QtWidgets.QAction(second_parent) + action_with_same_shortcuts3.setObjectName('action_with_same_shortcuts3') + action_with_same_shortcuts3.setShortcutContext(QtCore.Qt.WidgetShortcut) + # Add default shortcuts to Settings class. + default_shortcuts = { + 'shortcuts/action3': [QtGui.QKeySequence(QtCore.Qt.Key_E), QtGui.QKeySequence(QtCore.Qt.Key_F)], + 'shortcuts/action_with_same_shortcuts3': [QtGui.QKeySequence(QtCore.Qt.Key_E), + QtGui.QKeySequence(QtCore.Qt.Key_F)] + } + Registry().get('settings').extend_default_settings(default_shortcuts) - def test_add_action_different_context(self): - """ - ActionList test - Tests the add_action method. The actions have the different parent, the same shortcuts and - both have the QtCore.Qt.WidgetShortcut shortcut context set. - """ - # GIVEN: Two actions with the same shortcuts. - parent = QtCore.QObject() - action3 = QtWidgets.QAction(parent) - action3.setObjectName('action3') - action3.setShortcutContext(QtCore.Qt.WidgetShortcut) - second_parent = QtCore.QObject() - action_with_same_shortcuts3 = QtWidgets.QAction(second_parent) - action_with_same_shortcuts3.setObjectName('action_with_same_shortcuts3') - action_with_same_shortcuts3.setShortcutContext(QtCore.Qt.WidgetShortcut) - # Add default shortcuts to Settings class. - default_shortcuts = { - 'shortcuts/action3': [QtGui.QKeySequence(QtCore.Qt.Key_E), QtGui.QKeySequence(QtCore.Qt.Key_F)], - 'shortcuts/action_with_same_shortcuts3': [QtGui.QKeySequence(QtCore.Qt.Key_E), - QtGui.QKeySequence(QtCore.Qt.Key_F)] - } - Settings.extend_default_settings(default_shortcuts) + # WHEN: Add the two actions to the action list. + action_list.add_action(action3, 'example_category2') + action_list.add_action(action_with_same_shortcuts3, 'example_category2') + # Remove the actions again. + action_list.remove_action(action3, 'example_category2') + action_list.remove_action(action_with_same_shortcuts3, 'example_category2') - # WHEN: Add the two actions to the action list. - self.action_list.add_action(action3, 'example_category2') - self.action_list.add_action(action_with_same_shortcuts3, 'example_category2') - # Remove the actions again. - self.action_list.remove_action(action3, 'example_category2') - self.action_list.remove_action(action_with_same_shortcuts3, 'example_category2') - - # THEN: Both action should keep their shortcuts. - assert len(action3.shortcuts()) == 2, 'The action should have two shortcut assigned.' - assert len(action_with_same_shortcuts3.shortcuts()) == 2, 'The action should have two shortcuts assigned.' + # THEN: Both action should keep their shortcuts. + assert len(action3.shortcuts()) == 2, 'The action should have two shortcut assigned.' + assert len(action_with_same_shortcuts3.shortcuts()) == 2, 'The action should have two shortcuts assigned.' diff --git a/tests/functional/openlp_core/common/test_common.py b/tests/functional/openlp_core/common/test_common.py index 11020df84..f4db48367 100644 --- a/tests/functional/openlp_core/common/test_common.py +++ b/tests/functional/openlp_core/common/test_common.py @@ -22,304 +22,318 @@ Functional tests to test the AppLocation class and related methods. """ from pathlib import Path -from unittest import TestCase, skipUnless +from unittest import skipUnless from unittest.mock import MagicMock, call, patch from openlp.core.common import Singleton, clean_button_text, de_hump, extension_loader, is_linux, is_macosx, is_win, \ is_64bit_instance, normalize_str, path_to_module, trace_error_handler -class TestCommonFunctions(TestCase): +def test_extension_loader_no_files_found(): """ - A test suite to test out various functions in the openlp.core.common module. + Test the `extension_loader` function when no files are found """ - def test_extension_loader_no_files_found(self): - """ - Test the `extension_loader` function when no files are found - """ - # GIVEN: A mocked `Path.glob` method which does not match any files - with patch('openlp.core.common.applocation.AppLocation.get_directory', - return_value=Path('/', 'app', 'dir', 'openlp')), \ - patch.object(Path, 'glob', return_value=[]), \ - patch('openlp.core.common.importlib.import_module') as mocked_import_module: + # GIVEN: A mocked `Path.glob` method which does not match any files + with patch('openlp.core.common.applocation.AppLocation.get_directory', + return_value=Path('/', 'app', 'dir', 'openlp')), \ + patch.object(Path, 'glob', return_value=[]), \ + patch('openlp.core.common.importlib.import_module') as mocked_import_module: - # WHEN: Calling `extension_loader` - extension_loader('glob', ['file2.py', 'file3.py']) + # WHEN: Calling `extension_loader` + extension_loader('glob', ['file2.py', 'file3.py']) - # THEN: `extension_loader` should not try to import any files - assert mocked_import_module.called is False + # THEN: `extension_loader` should not try to import any files + assert mocked_import_module.called is False - def test_extension_loader_files_found(self): - """ - Test the `extension_loader` function when it successfully finds and loads some files - """ - # GIVEN: A mocked `Path.glob` method which returns a list of files - with patch('openlp.core.common.applocation.AppLocation.get_directory', - return_value=Path('/', 'app', 'dir', 'openlp')), \ - patch.object(Path, 'glob', return_value=[ - Path('/', 'app', 'dir', 'openlp', 'import_dir', 'file1.py'), - Path('/', 'app', 'dir', 'openlp', 'import_dir', 'file2.py'), - Path('/', 'app', 'dir', 'openlp', 'import_dir', 'file3.py'), - Path('/', 'app', 'dir', 'openlp', 'import_dir', 'file4.py')]), \ - patch('openlp.core.common.importlib.import_module') as mocked_import_module: - # WHEN: Calling `extension_loader` with a list of files to exclude - extension_loader('glob', ['file2.py', 'file3.py']) +def test_extension_loader_files_found(): + """ + Test the `extension_loader` function when it successfully finds and loads some files + """ + # GIVEN: A mocked `Path.glob` method which returns a list of files + with patch('openlp.core.common.applocation.AppLocation.get_directory', + return_value=Path('/', 'app', 'dir', 'openlp')), \ + patch.object(Path, 'glob', return_value=[ + Path('/', 'app', 'dir', 'openlp', 'import_dir', 'file1.py'), + Path('/', 'app', 'dir', 'openlp', 'import_dir', 'file2.py'), + Path('/', 'app', 'dir', 'openlp', 'import_dir', 'file3.py'), + Path('/', 'app', 'dir', 'openlp', 'import_dir', 'file4.py')]), \ + patch('openlp.core.common.importlib.import_module') as mocked_import_module: - # THEN: `extension_loader` should only try to import the files that are matched by the blob, excluding the - # files listed in the `excluded_files` argument - mocked_import_module.assert_has_calls([call('openlp.import_dir.file1'), - call('openlp.import_dir.file4')]) + # WHEN: Calling `extension_loader` with a list of files to exclude + extension_loader('glob', ['file2.py', 'file3.py']) - def test_extension_loader_import_error(self): - """ - Test the `extension_loader` function when `SourceFileLoader` raises a `ImportError` - """ - # GIVEN: A mocked `import_module` which raises an `ImportError` - with patch('openlp.core.common.applocation.AppLocation.get_directory', - return_value=Path('/', 'app', 'dir', 'openlp')), \ - patch.object(Path, 'glob', return_value=[ - Path('/', 'app', 'dir', 'openlp', 'import_dir', 'file1.py')]), \ - patch('openlp.core.common.importlib.import_module', side_effect=ImportError()), \ - patch('openlp.core.common.log') as mocked_logger: + # THEN: `extension_loader` should only try to import the files that are matched by the blob, excluding the + # files listed in the `excluded_files` argument + mocked_import_module.assert_has_calls([call('openlp.import_dir.file1'), + call('openlp.import_dir.file4')]) - # WHEN: Calling `extension_loader` - extension_loader('glob') - # THEN: The `ImportError` should be caught and logged - assert mocked_logger.exception.called +def test_extension_loader_import_error(): + """ + Test the `extension_loader` function when `SourceFileLoader` raises a `ImportError` + """ + # GIVEN: A mocked `import_module` which raises an `ImportError` + with patch('openlp.core.common.applocation.AppLocation.get_directory', + return_value=Path('/', 'app', 'dir', 'openlp')), \ + patch.object(Path, 'glob', return_value=[ + Path('/', 'app', 'dir', 'openlp', 'import_dir', 'file1.py')]), \ + patch('openlp.core.common.importlib.import_module', side_effect=ImportError()), \ + patch('openlp.core.common.log') as mocked_logger: - def test_extension_loader_os_error(self): - """ - Test the `extension_loader` function when `import_module` raises a `ImportError` - """ - # GIVEN: A mocked `SourceFileLoader` which raises an `OSError` - with patch('openlp.core.common.applocation.AppLocation.get_directory', - return_value=Path('/', 'app', 'dir', 'openlp')), \ - patch.object(Path, 'glob', return_value=[ - Path('/', 'app', 'dir', 'openlp', 'import_dir', 'file1.py')]), \ - patch('openlp.core.common.importlib.import_module', side_effect=OSError()), \ - patch('openlp.core.common.log') as mocked_logger: + # WHEN: Calling `extension_loader` + extension_loader('glob') - # WHEN: Calling `extension_loader` - extension_loader('glob') + # THEN: The `ImportError` should be caught and logged + assert mocked_logger.exception.called - # THEN: The `OSError` should be caught and logged - assert mocked_logger.exception.called - def test_de_hump_conversion(self): - """ - Test the de_hump function with a class name - """ - # GIVEN: a Class name in Camel Case - string = "MyClass" +def test_extension_loader_os_error(): + """ + Test the `extension_loader` function when `import_module` raises a `ImportError` + """ + # GIVEN: A mocked `SourceFileLoader` which raises an `OSError` + with patch('openlp.core.common.applocation.AppLocation.get_directory', + return_value=Path('/', 'app', 'dir', 'openlp')), \ + patch.object(Path, 'glob', return_value=[ + Path('/', 'app', 'dir', 'openlp', 'import_dir', 'file1.py')]), \ + patch('openlp.core.common.importlib.import_module', side_effect=OSError()), \ + patch('openlp.core.common.log') as mocked_logger: - # WHEN: we call de_hump - new_string = de_hump(string) + # WHEN: Calling `extension_loader` + extension_loader('glob') - # THEN: the new string should be converted to python format - assert new_string == "my_class", 'The class name should have been converted' + # THEN: The `OSError` should be caught and logged + assert mocked_logger.exception.called - def test_de_hump_static(self): - """ - Test the de_hump function with a python string - """ - # GIVEN: a Class name in Camel Case - string = "my_class" - # WHEN: we call de_hump - new_string = de_hump(string) +def test_de_hump_conversion(): + """ + Test the de_hump function with a class name + """ + # GIVEN: a Class name in Camel Case + string = "MyClass" - # THEN: the new string should be converted to python format - assert new_string == "my_class", 'The class name should have been preserved' + # WHEN: we call de_hump + new_string = de_hump(string) - def test_path_to_module(self): - """ - Test `path_to_module` when supplied with a `Path` object - """ - # GIVEN: A `Path` object - path = Path('core', 'ui', 'media', 'vlcplayer.py') + # THEN: the new string should be converted to python format + assert new_string == "my_class", 'The class name should have been converted' - # WHEN: Calling path_to_module with the `Path` object - result = path_to_module(path) - # THEN: path_to_module should return the module name - assert result == 'openlp.core.ui.media.vlcplayer' +def test_de_hump_static(): + """ + Test the de_hump function with a python string + """ + # GIVEN: a Class name in Camel Case + string = "my_class" - def test_trace_error_handler(self): - """ - Test the trace_error_handler() method - """ - # GIVEN: Mocked out objects - with patch('openlp.core.common.traceback') as mocked_traceback: - mocked_traceback.extract_stack.return_value = [('openlp.fake', 56, None, 'trace_error_handler_test')] - mocked_logger = MagicMock() + # WHEN: we call de_hump + new_string = de_hump(string) - # WHEN: trace_error_handler() is called - trace_error_handler(mocked_logger) + # THEN: the new string should be converted to python format + assert new_string == "my_class", 'The class name should have been preserved' - # THEN: The mocked_logger.error() method should have been called with the correct parameters - mocked_logger.error.assert_called_with( - 'OpenLP Error trace\n File openlp.fake at line 56 \n\t called trace_error_handler_test') - def test_singleton_metaclass_multiple_init(self): - """ - Test that a class using the Singleton Metaclass is only initialised once despite being called several times and - that the same instance is returned each time.. - """ - # GIVEN: The Singleton Metaclass and a test class using it - class SingletonClass(metaclass=Singleton): - def __init__(self): - pass +def test_path_to_module(): + """ + Test `path_to_module` when supplied with a `Path` object + """ + # GIVEN: A `Path` object + path = Path('core', 'ui', 'media', 'vlcplayer.py') - with patch.object(SingletonClass, '__init__', return_value=None) as patched_init: + # WHEN: Calling path_to_module with the `Path` object + result = path_to_module(path) - # WHEN: Initialising the class multiple times - inst_1 = SingletonClass() - inst_2 = SingletonClass() + # THEN: path_to_module should return the module name + assert result == 'openlp.core.ui.media.vlcplayer' - # THEN: The __init__ method of the SingletonClass should have only been called once, and both returned values - # should be the same instance. - assert inst_1 is inst_2 - assert patched_init.call_count == 1 - def test_singleton_metaclass_multiple_classes(self): - """ - Test that multiple classes using the Singleton Metaclass return the different an appropriate instances. - """ - # GIVEN: Two different classes using the Singleton Metaclass - class SingletonClass1(metaclass=Singleton): - def __init__(self): - pass +def test_trace_error_handler(): + """ + Test the trace_error_handler() method + """ + # GIVEN: Mocked out objects + with patch('openlp.core.common.traceback') as mocked_traceback: + mocked_traceback.extract_stack.return_value = [('openlp.fake', 56, None, 'trace_error_handler_test')] + mocked_logger = MagicMock() - class SingletonClass2(metaclass=Singleton): - def __init__(self): - pass + # WHEN: trace_error_handler() is called + trace_error_handler(mocked_logger) - # WHEN: Initialising both classes - s_c1 = SingletonClass1() - s_c2 = SingletonClass2() + # THEN: The mocked_logger.error() method should have been called with the correct parameters + mocked_logger.error.assert_called_with( + 'OpenLP Error trace\n File openlp.fake at line 56 \n\t called trace_error_handler_test') - # THEN: The instances should be an instance of the appropriate class - assert isinstance(s_c1, SingletonClass1) - assert isinstance(s_c2, SingletonClass2) - def test_is_win(self): - """ - Test the is_win() function - """ - # GIVEN: Mocked out objects - with patch('openlp.core.common.os') as mocked_os, patch('openlp.core.common.sys') as mocked_sys: +def test_singleton_metaclass_multiple_init(): + """ + Test that a class using the Singleton Metaclass is only initialised once despite being called several times and + that the same instance is returned each time.. + """ + # GIVEN: The Singleton Metaclass and a test class using it + class SingletonClass(metaclass=Singleton): + def __init__(self): + pass - # WHEN: The mocked os.name and sys.platform are set to 'nt' and 'win32' repectivly - mocked_os.name = 'nt' - mocked_sys.platform = 'win32' + with patch.object(SingletonClass, '__init__', return_value=None) as patched_init: - # THEN: The three platform functions should perform properly - assert is_win() is True, 'is_win() should return True' - assert is_macosx() is False, 'is_macosx() should return False' - assert is_linux() is False, 'is_linux() should return False' + # WHEN: Initialising the class multiple times + inst_1 = SingletonClass() + inst_2 = SingletonClass() - def test_is_macosx(self): - """ - Test the is_macosx() function - """ - # GIVEN: Mocked out objects - with patch('openlp.core.common.os') as mocked_os, patch('openlp.core.common.sys') as mocked_sys: + # THEN: The __init__ method of the SingletonClass should have only been called once, and both returned values + # should be the same instance. + assert inst_1 is inst_2 + assert patched_init.call_count == 1 - # WHEN: The mocked os.name and sys.platform are set to 'posix' and 'darwin' repectivly - mocked_os.name = 'posix' - mocked_sys.platform = 'darwin' - # THEN: The three platform functions should perform properly - assert is_macosx() is True, 'is_macosx() should return True' - assert is_win() is False, 'is_win() should return False' - assert is_linux() is False, 'is_linux() should return False' +def test_singleton_metaclass_multiple_classes(): + """ + Test that multiple classes using the Singleton Metaclass return the different an appropriate instances. + """ + # GIVEN: Two different classes using the Singleton Metaclass + class SingletonClass1(metaclass=Singleton): + def __init__(self): + pass - def test_is_linux(self): - """ - Test the is_linux() function - """ - # GIVEN: Mocked out objects - with patch('openlp.core.common.os') as mocked_os, patch('openlp.core.common.sys') as mocked_sys: + class SingletonClass2(metaclass=Singleton): + def __init__(self): + pass - # WHEN: The mocked os.name and sys.platform are set to 'posix' and 'linux3' repectively - mocked_os.name = 'posix' - mocked_sys.platform = 'linux3' + # WHEN: Initialising both classes + s_c1 = SingletonClass1() + s_c2 = SingletonClass2() - # THEN: The three platform functions should perform properly - assert is_linux() is True, 'is_linux() should return True' - assert is_win() is False, 'is_win() should return False' - assert is_macosx() is False, 'is_macosx() should return False' + # THEN: The instances should be an instance of the appropriate class + assert isinstance(s_c1, SingletonClass1) + assert isinstance(s_c2, SingletonClass2) - @skipUnless(is_linux(), 'This can only run on Linux') - def test_is_linux_distro(self): - """ - Test the is_linux() function for a particular Linux distribution - """ - # GIVEN: Mocked out objects - with patch('openlp.core.common.os') as mocked_os, \ - patch('openlp.core.common.sys') as mocked_sys, \ - patch('openlp.core.common.distro_id') as mocked_distro_id: - # WHEN: The mocked os.name and sys.platform are set to 'posix' and 'linux3' repectively - # and the distro is Fedora - mocked_os.name = 'posix' - mocked_sys.platform = 'linux3' - mocked_distro_id.return_value = 'fedora' +def test_is_win(): + """ + Test the is_win() function + """ + # GIVEN: Mocked out objects + with patch('openlp.core.common.os') as mocked_os, patch('openlp.core.common.sys') as mocked_sys: - # THEN: The three platform functions should perform properly - assert is_linux(distro='fedora') is True, 'is_linux(distro="fedora") should return True' - assert is_win() is False, 'is_win() should return False' - assert is_macosx() is False, 'is_macosx() should return False' + # WHEN: The mocked os.name and sys.platform are set to 'nt' and 'win32' repectivly + mocked_os.name = 'nt' + mocked_sys.platform = 'win32' - def test_is_64bit_instance(self): - """ - Test the is_64bit_instance() function - """ - # GIVEN: Mocked out objects - with patch('openlp.core.common.sys') as mocked_sys: + # THEN: The three platform functions should perform properly + assert is_win() is True, 'is_win() should return True' + assert is_macosx() is False, 'is_macosx() should return False' + assert is_linux() is False, 'is_linux() should return False' - # WHEN: The mocked sys.maxsize is set to 32-bit - mocked_sys.maxsize = 2**32 - # THEN: The result should be False - assert is_64bit_instance() is False, 'is_64bit_instance() should return False' +def test_is_macosx(): + """ + Test the is_macosx() function + """ + # GIVEN: Mocked out objects + with patch('openlp.core.common.os') as mocked_os, patch('openlp.core.common.sys') as mocked_sys: - def test_normalize_str_leaves_newlines(self): - # GIVEN: a string containing newlines - string = 'something\nelse' - # WHEN: normalize is called - normalized_string = normalize_str(string) - # THEN: string is unchanged - assert normalized_string == string + # WHEN: The mocked os.name and sys.platform are set to 'posix' and 'darwin' repectivly + mocked_os.name = 'posix' + mocked_sys.platform = 'darwin' - def test_normalize_str_removes_null_byte(self): - # GIVEN: a string containing a null byte - string = 'somet\x00hing' - # WHEN: normalize is called - normalized_string = normalize_str(string) - # THEN: nullbyte is removed - assert normalized_string == 'something' + # THEN: The three platform functions should perform properly + assert is_macosx() is True, 'is_macosx() should return True' + assert is_win() is False, 'is_win() should return False' + assert is_linux() is False, 'is_linux() should return False' - def test_normalize_str_replaces_crlf_with_lf(self): - # GIVEN: a string containing crlf - string = 'something\r\nelse' - # WHEN: normalize is called - normalized_string = normalize_str(string) - # THEN: crlf is replaced with lf - assert normalized_string == 'something\nelse' - def test_clean_button_text(self): - """ - Test the clean_button_text() function. - """ - # GIVEN: Button text - input_text = '&Next >' - expected_text = 'Next' +def test_is_linux(): + """ + Test the is_linux() function + """ + # GIVEN: Mocked out objects + with patch('openlp.core.common.os') as mocked_os, patch('openlp.core.common.sys') as mocked_sys: - # WHEN: The button caption is sent through the clean_button_text function - actual_text = clean_button_text(input_text) + # WHEN: The mocked os.name and sys.platform are set to 'posix' and 'linux3' repectively + mocked_os.name = 'posix' + mocked_sys.platform = 'linux3' - # THEN: The text should have been cleaned - assert expected_text == actual_text, 'The text should be clean' + # THEN: The three platform functions should perform properly + assert is_linux() is True, 'is_linux() should return True' + assert is_win() is False, 'is_win() should return False' + assert is_macosx() is False, 'is_macosx() should return False' + + +@skipUnless(is_linux(), 'This can only run on Linux') +def test_is_linux_distro(): + """ + Test the is_linux() function for a particular Linux distribution + """ + # GIVEN: Mocked out objects + with patch('openlp.core.common.os') as mocked_os, \ + patch('openlp.core.common.sys') as mocked_sys, \ + patch('openlp.core.common.distro_id') as mocked_distro_id: + + # WHEN: The mocked os.name and sys.platform are set to 'posix' and 'linux3' repectively + # and the distro is Fedora + mocked_os.name = 'posix' + mocked_sys.platform = 'linux3' + mocked_distro_id.return_value = 'fedora' + + # THEN: The three platform functions should perform properly + assert is_linux(distro='fedora') is True, 'is_linux(distro="fedora") should return True' + assert is_win() is False, 'is_win() should return False' + assert is_macosx() is False, 'is_macosx() should return False' + + +def test_is_64bit_instance(): + """ + Test the is_64bit_instance() function + """ + # GIVEN: Mocked out objects + with patch('openlp.core.common.sys') as mocked_sys: + + # WHEN: The mocked sys.maxsize is set to 32-bit + mocked_sys.maxsize = 2**32 + + # THEN: The result should be False + assert is_64bit_instance() is False, 'is_64bit_instance() should return False' + + +def test_normalize_str_leaves_newlines(): + # GIVEN: a string containing newlines + string = 'something\nelse' + # WHEN: normalize is called + normalized_string = normalize_str(string) + # THEN: string is unchanged + assert normalized_string == string + + +def test_normalize_str_removes_null_byte(): + # GIVEN: a string containing a null byte + string = 'somet\x00hing' + # WHEN: normalize is called + normalized_string = normalize_str(string) + # THEN: nullbyte is removed + assert normalized_string == 'something' + + +def test_normalize_str_replaces_crlf_with_lf(): + # GIVEN: a string containing crlf + string = 'something\r\nelse' + # WHEN: normalize is called + normalized_string = normalize_str(string) + # THEN: crlf is replaced with lf + assert normalized_string == 'something\nelse' + + +def test_clean_button_text(): + """ + Test the clean_button_text() function. + """ + # GIVEN: Button text + input_text = '&Next >' + expected_text = 'Next' + + # WHEN: The button caption is sent through the clean_button_text function + actual_text = clean_button_text(input_text) + + # THEN: The text should have been cleaned + assert expected_text == actual_text, 'The text should be clean' diff --git a/tests/functional/openlp_core/common/test_db.py b/tests/functional/openlp_core/common/test_db.py index 563beb369..5a143ffee 100644 --- a/tests/functional/openlp_core/common/test_db.py +++ b/tests/functional/openlp_core/common/test_db.py @@ -23,10 +23,10 @@ Package to test the openlp.core.common.db package. """ import gc import os +import pytest import shutil import time from tempfile import mkdtemp -from unittest import TestCase import sqlalchemy @@ -35,69 +35,63 @@ from openlp.core.lib.db import get_upgrade_op, init_db from tests.utils.constants import TEST_RESOURCES_PATH -class TestUtilsDBFunctions(TestCase): +@pytest.yield_fixture +def op(): + tmp_folder = mkdtemp() + db_path = os.path.join(TEST_RESOURCES_PATH, 'songs', 'songs-1.9.7.sqlite') + db_tmp_path = os.path.join(tmp_folder, 'songs-1.9.7.sqlite') + shutil.copyfile(db_path, db_tmp_path) + db_url = 'sqlite:///' + db_tmp_path + session, metadata = init_db(db_url) + upgrade_op = get_upgrade_op(session) + yield upgrade_op + session.close() + session = None + gc.collect() + retries = 0 + while retries < 5: + try: + if os.path.exists(tmp_folder): + shutil.rmtree(tmp_folder) + break + except Exception: + time.sleep(1) + retries += 1 - def setUp(self): - """ - Create temp folder for keeping db file - """ - self.tmp_folder = mkdtemp() - db_path = os.path.join(TEST_RESOURCES_PATH, 'songs', 'songs-1.9.7.sqlite') - self.db_tmp_path = os.path.join(self.tmp_folder, 'songs-1.9.7.sqlite') - shutil.copyfile(db_path, self.db_tmp_path) - db_url = 'sqlite:///' + self.db_tmp_path - self.session, metadata = init_db(db_url) - self.op = get_upgrade_op(self.session) - def tearDown(self): - """ - Clean up - """ - self.session.close() - self.session = None - gc.collect() - retries = 0 - while retries < 5: - try: - if os.path.exists(self.tmp_folder): - shutil.rmtree(self.tmp_folder) - break - except Exception: - time.sleep(1) - retries += 1 +def test_delete_column(op): + """ + Test deleting a single column in a table + """ + # GIVEN: A temporary song db - def test_delete_column(self): - """ - Test deleting a single column in a table - """ - # GIVEN: A temporary song db + # WHEN: Deleting a columns in a table + drop_column(op, 'songs', 'song_book_id') - # WHEN: Deleting a columns in a table - drop_column(self.op, 'songs', 'song_book_id') + # THEN: The column should have been deleted + meta = sqlalchemy.MetaData(bind=op.get_bind()) + meta.reflect() + columns = meta.tables['songs'].columns - # THEN: The column should have been deleted - meta = sqlalchemy.MetaData(bind=self.op.get_bind()) - meta.reflect() - columns = meta.tables['songs'].columns + for column in columns: + if column.name == 'song_book_id': + assert "The column 'song_book_id' should have been deleted." - for column in columns: - if column.name == 'song_book_id': - self.fail("The column 'song_book_id' should have been deleted.") - def test_delete_columns(self): - """ - Test deleting multiple columns in a table - """ - # GIVEN: A temporary song db +def test_delete_columns(op): + """ + Test deleting multiple columns in a table + """ + # GIVEN: A temporary song db - # WHEN: Deleting a columns in a table - drop_columns(self.op, 'songs', ['song_book_id', 'song_number']) + # WHEN: Deleting a columns in a table + drop_columns(op, 'songs', ['song_book_id', 'song_number']) - # THEN: The columns should have been deleted - meta = sqlalchemy.MetaData(bind=self.op.get_bind()) - meta.reflect() - columns = meta.tables['songs'].columns + # THEN: The columns should have been deleted + meta = sqlalchemy.MetaData(bind=op.get_bind()) + meta.reflect() + columns = meta.tables['songs'].columns - for column in columns: - if column.name == 'song_book_id' or column.name == 'song_number': - self.fail("The column '%s' should have been deleted." % column.name) + for column in columns: + if column.name == 'song_book_id' or column.name == 'song_number': + assert "The column '%s' should have been deleted." % column.name diff --git a/tests/functional/openlp_core/common/test_httputils.py b/tests/functional/openlp_core/common/test_httputils.py index 93f8dbf35..e00cbe7bf 100644 --- a/tests/functional/openlp_core/common/test_httputils.py +++ b/tests/functional/openlp_core/common/test_httputils.py @@ -22,314 +22,314 @@ Functional tests to test the AppLocation class and related methods. """ import os +import pytest import tempfile from pathlib import Path -from unittest import TestCase from unittest.mock import MagicMock, patch from openlp.core.common.httputils import ProxyMode, download_file, get_proxy_settings, get_url_file_size, \ get_user_agent, get_web_page -from openlp.core.common.registry import Registry from openlp.core.common.settings import Settings -from tests.helpers.testmixin import TestMixin -class TestHttpUtils(TestCase, TestMixin): +@pytest.yield_fixture +def temp_file(settings): + tmp_file = os.path.join(tempfile.gettempdir(), 'testfile') + yield tmp_file + if os.path.isfile(tmp_file): + os.remove(tmp_file) + + +def test_get_user_agent_linux(): """ - A test suite to test out various http helper functions. + Test that getting a user agent on Linux returns a user agent suitable for Linux """ - def setUp(self): - self.tempfile = os.path.join(tempfile.gettempdir(), 'testfile') - Registry.create() - Registry().register('settings', Settings()) + with patch('openlp.core.common.httputils.sys') as mocked_sys: - def tearDown(self): - if os.path.isfile(self.tempfile): - os.remove(self.tempfile) + # GIVEN: The system is Linux + mocked_sys.platform = 'linux2' - def test_get_user_agent_linux(self): - """ - Test that getting a user agent on Linux returns a user agent suitable for Linux - """ - with patch('openlp.core.common.httputils.sys') as mocked_sys: + # WHEN: We call get_user_agent() + user_agent = get_user_agent() - # GIVEN: The system is Linux - mocked_sys.platform = 'linux2' - - # WHEN: We call get_user_agent() - user_agent = get_user_agent() - - # THEN: The user agent is a Linux (or ChromeOS) user agent - result = 'Linux' in user_agent or 'CrOS' in user_agent - assert result is True, 'The user agent should be a valid Linux user agent' - - def test_get_user_agent_windows(self): - """ - Test that getting a user agent on Windows returns a user agent suitable for Windows - """ - with patch('openlp.core.common.httputils.sys') as mocked_sys: - - # GIVEN: The system is Windows - mocked_sys.platform = 'win32' - - # WHEN: We call get_user_agent() - user_agent = get_user_agent() - - # THEN: The user agent is a Linux (or ChromeOS) user agent - assert 'Windows' in user_agent, 'The user agent should be a valid Windows user agent' - - def test_get_user_agent_macos(self): - """ - Test that getting a user agent on OS X returns a user agent suitable for OS X - """ - with patch('openlp.core.common.httputils.sys') as mocked_sys: - - # GIVEN: The system is macOS - mocked_sys.platform = 'darwin' - - # WHEN: We call get_user_agent() - user_agent = get_user_agent() - - # THEN: The user agent is a Linux (or ChromeOS) user agent - assert 'Mac OS X' in user_agent, 'The user agent should be a valid OS X user agent' - - def test_get_user_agent_default(self): - """ - Test that getting a user agent on a non-Linux/Windows/OS X platform returns the default user agent - """ - with patch('openlp.core.common.httputils.sys') as mocked_sys: - - # GIVEN: The system is something else - mocked_sys.platform = 'freebsd' - - # WHEN: We call get_user_agent() - user_agent = get_user_agent() - - # THEN: The user agent is a Linux (or ChromeOS) user agent - assert 'NetBSD'in user_agent, 'The user agent should be the default user agent' - - def test_get_web_page_no_url(self): - """ - Test that sending a URL of None to the get_web_page method returns None - """ - # GIVEN: A None url - test_url = None - - # WHEN: We try to get the test URL - result = get_web_page(test_url) - - # THEN: None should be returned - assert result is None, 'The return value of get_web_page should be None' - - @patch('openlp.core.common.httputils.requests') - @patch('openlp.core.common.httputils.get_user_agent') - @patch('openlp.core.common.httputils.Registry') - def test_get_web_page(self, MockRegistry, mocked_get_user_agent, mocked_requests): - """ - Test that the get_web_page method works correctly - """ - # GIVEN: Mocked out objects and a fake URL - mocked_requests.get.return_value = MagicMock(text='text') - mocked_get_user_agent.return_value = 'user_agent' - fake_url = 'this://is.a.fake/url' - - # WHEN: The get_web_page() method is called - returned_page = get_web_page(fake_url) - - # THEN: The correct methods are called with the correct arguments and a web page is returned - mocked_requests.get.assert_called_once_with(fake_url, headers={'User-Agent': 'user_agent'}, - proxies=None, timeout=30.0) - mocked_get_user_agent.assert_called_once_with() - assert MockRegistry.call_count == 1, 'The Registry() object should have been called once' - assert returned_page == 'text', 'The returned page should be the mock object' - - @patch('openlp.core.common.httputils.requests') - @patch('openlp.core.common.httputils.get_user_agent') - def test_get_web_page_with_header(self, mocked_get_user_agent, mocked_requests): - """ - Test that adding a header to the call to get_web_page() adds the header to the request - """ - # GIVEN: Mocked out objects, a fake URL and a fake header - mocked_requests.get.return_value = MagicMock(text='text') - mocked_get_user_agent.return_value = 'user_agent' - fake_url = 'this://is.a.fake/url' - fake_headers = {'Fake-Header': 'fake value'} - - # WHEN: The get_web_page() method is called - returned_page = get_web_page(fake_url, headers=fake_headers) - - # THEN: The correct methods are called with the correct arguments and a web page is returned - expected_headers = dict(fake_headers) - expected_headers.update({'User-Agent': 'user_agent'}) - mocked_requests.get.assert_called_once_with(fake_url, headers=expected_headers, - proxies=None, timeout=30.0) - mocked_get_user_agent.assert_called_with() - assert returned_page == 'text', 'The returned page should be the mock object' - - @patch('openlp.core.common.httputils.requests') - @patch('openlp.core.common.httputils.get_user_agent') - def test_get_web_page_with_user_agent_in_headers(self, mocked_get_user_agent, mocked_requests): - """ - Test that adding a user agent in the header when calling get_web_page() adds that user agent to the request - """ - # GIVEN: Mocked out objects, a fake URL and a fake header - mocked_requests.get.return_value = MagicMock(text='text') - fake_url = 'this://is.a.fake/url' - user_agent_headers = {'User-Agent': 'OpenLP/2.2.0'} - - # WHEN: The get_web_page() method is called - returned_page = get_web_page(fake_url, headers=user_agent_headers) - - # THEN: The correct methods are called with the correct arguments and a web page is returned - mocked_requests.get.assert_called_once_with(fake_url, headers=user_agent_headers, - proxies=None, timeout=30.0) - assert mocked_get_user_agent.call_count == 0, 'get_user_agent() should not have been called' - assert returned_page == 'text', 'The returned page should be "test"' - - @patch('openlp.core.common.httputils.requests') - @patch('openlp.core.common.httputils.get_user_agent') - @patch('openlp.core.common.httputils.Registry') - def test_get_web_page_update_openlp(self, MockRegistry, mocked_get_user_agent, mocked_requests): - """ - Test that passing "update_openlp" as true to get_web_page calls Registry().get('app').process_events() - """ - # GIVEN: Mocked out objects, a fake URL - mocked_requests.get.return_value = MagicMock(text='text') - mocked_get_user_agent.return_value = 'user_agent' - mocked_registry_object = MagicMock() - mocked_application_object = MagicMock() - mocked_registry_object.get.return_value = mocked_application_object - MockRegistry.return_value = mocked_registry_object - fake_url = 'this://is.a.fake/url' - - # WHEN: The get_web_page() method is called - returned_page = get_web_page(fake_url, update_openlp=True) - - # THEN: The correct methods are called with the correct arguments and a web page is returned - mocked_requests.get.assert_called_once_with(fake_url, headers={'User-Agent': 'user_agent'}, - proxies=None, timeout=30.0) - mocked_get_user_agent.assert_called_once_with() - mocked_registry_object.get.assert_called_with('application') - mocked_application_object.process_events.assert_called_with() - assert returned_page == 'text', 'The returned page should be the mock object' - - @patch('openlp.core.common.httputils.requests') - def test_get_url_file_size(self, mocked_requests): - """ - Test that calling "get_url_file_size" works correctly - """ - # GIVEN: Mocked out objects, a fake URL - mocked_requests.head.return_value = MagicMock(headers={'Content-Length': 100}) - fake_url = 'this://is.a.fake/url' - - # WHEN: The get_url_file_size() method is called - file_size = get_url_file_size(fake_url) - - # THEN: The correct methods are called with the correct arguments and a web page is returned - mocked_requests.head.assert_called_once_with(fake_url, allow_redirects=True, proxies=None, timeout=30.0) - assert file_size == 100 - - @patch('openlp.core.common.httputils.requests') - def test_socket_timeout(self, mocked_requests): - """ - Test socket timeout gets caught - """ - # GIVEN: Mocked urlopen to fake a network disconnect in the middle of a download - mocked_requests.get.side_effect = OSError - - # WHEN: Attempt to retrieve a file - download_file(MagicMock(), url='http://localhost/test', file_path=Path(self.tempfile)) - - # THEN: socket.timeout should have been caught - # NOTE: Test is if $tmpdir/tempfile is still there, then test fails since ftw deletes bad downloaded files - assert os.path.exists(self.tempfile) is False, 'tempfile should have been deleted' + # THEN: The user agent is a Linux (or ChromeOS) user agent + result = 'Linux' in user_agent or 'CrOS' in user_agent + assert result is True, 'The user agent should be a valid Linux user agent' -class TestGetProxySettings(TestCase, TestMixin): - def setUp(self): - self.build_settings() - Registry.create() - Registry().register('settings', Settings()) - self.addCleanup(self.destroy_settings) +def test_get_user_agent_windows(): + """ + Test that getting a user agent on Windows returns a user agent suitable for Windows + """ + with patch('openlp.core.common.httputils.sys') as mocked_sys: - def test_no_proxy_mode(self): - """ - Test that a dictionary with http and https values are set to None is returned, when `NO_PROXY` mode is specified - """ - # GIVEN: A `proxy mode` setting of NO_PROXY - Settings().setValue('advanced/proxy mode', ProxyMode.NO_PROXY) + # GIVEN: The system is Windows + mocked_sys.platform = 'win32' - # WHEN: Calling `get_proxy_settings` - result = get_proxy_settings() + # WHEN: We call get_user_agent() + user_agent = get_user_agent() - # THEN: The returned value should be a dictionary with http and https values set to None - assert result == {'http': None, 'https': None} + # THEN: The user agent is a Linux (or ChromeOS) user agent + assert 'Windows' in user_agent, 'The user agent should be a valid Windows user agent' - def test_system_proxy_mode(self): - """ - Test that None is returned, when `SYSTEM_PROXY` mode is specified - """ - # GIVEN: A `proxy mode` setting of SYSTEM_PROXY - Settings().setValue('advanced/proxy mode', ProxyMode.SYSTEM_PROXY) - # WHEN: Calling `get_proxy_settings` - result = get_proxy_settings() +def test_get_user_agent_macos(): + """ + Test that getting a user agent on OS X returns a user agent suitable for OS X + """ + with patch('openlp.core.common.httputils.sys') as mocked_sys: - # THEN: The returned value should be None - assert result is None + # GIVEN: The system is macOS + mocked_sys.platform = 'darwin' - def test_manual_proxy_mode_no_auth(self): - """ - Test that the correct proxy addresses are returned when basic authentication is not used - """ - # GIVEN: A `proxy mode` setting of MANUAL_PROXY with proxy servers, but no auth credentials are supplied - Settings().setValue('advanced/proxy mode', ProxyMode.MANUAL_PROXY) - Settings().setValue('advanced/proxy http', 'testhttp.server:port') - Settings().setValue('advanced/proxy https', 'testhttps.server:port') - Settings().setValue('advanced/proxy username', '') - Settings().setValue('advanced/proxy password', '') + # WHEN: We call get_user_agent() + user_agent = get_user_agent() - # WHEN: Calling `get_proxy_settings` - result = get_proxy_settings() + # THEN: The user agent is a Linux (or ChromeOS) user agent + assert 'Mac OS X' in user_agent, 'The user agent should be a valid OS X user agent' - # THEN: The returned value should be the proxy servers without authentication - assert result == {'http': 'http://testhttp.server:port', 'https': 'https://testhttps.server:port'} - def test_manual_proxy_mode_auth(self): - """ - Test that the correct proxy addresses are returned when basic authentication is used - """ - # GIVEN: A `proxy mode` setting of MANUAL_PROXY with proxy servers and auth credentials supplied - Settings().setValue('advanced/proxy mode', ProxyMode.MANUAL_PROXY) - Settings().setValue('advanced/proxy http', 'testhttp.server:port') - Settings().setValue('advanced/proxy https', 'testhttps.server:port') - Settings().setValue('advanced/proxy username', 'user') - Settings().setValue('advanced/proxy password', 'pass') +def test_get_user_agent_default(): + """ + Test that getting a user agent on a non-Linux/Windows/OS X platform returns the default user agent + """ + with patch('openlp.core.common.httputils.sys') as mocked_sys: - # WHEN: Calling `get_proxy_settings` - result = get_proxy_settings() + # GIVEN: The system is something else + mocked_sys.platform = 'freebsd' - # THEN: The returned value should be the proxy servers with the authentication credentials - assert result == {'http': 'http://user:pass@testhttp.server:port', - 'https': 'https://user:pass@testhttps.server:port'} + # WHEN: We call get_user_agent() + user_agent = get_user_agent() - def test_manual_proxy_mode_no_servers(self): - """ - Test that the system proxies are overidden when the MANUAL_PROXY mode is specified, but no server addresses are - supplied - """ - # GIVEN: A `proxy mode` setting of MANUAL_PROXY with no servers specified - Settings().setValue('advanced/proxy mode', ProxyMode.MANUAL_PROXY) - Settings().setValue('advanced/proxy http', '') - Settings().setValue('advanced/proxy https', '') - Settings().setValue('advanced/proxy username', 'user') - Settings().setValue('advanced/proxy password', 'pass') + # THEN: The user agent is a Linux (or ChromeOS) user agent + assert 'NetBSD'in user_agent, 'The user agent should be the default user agent' - # WHEN: Calling `get_proxy_settings` - result = get_proxy_settings() - # THEN: The returned value should be the proxy servers set to None - assert result == {'http': None, 'https': None} +def test_get_web_page_no_url(): + """ + Test that sending a URL of None to the get_web_page method returns None + """ + # GIVEN: A None url + test_url = None + + # WHEN: We try to get the test URL + result = get_web_page(test_url) + + # THEN: None should be returned + assert result is None, 'The return value of get_web_page should be None' + + +@patch('openlp.core.common.httputils.requests') +@patch('openlp.core.common.httputils.get_user_agent') +@patch('openlp.core.common.httputils.Registry') +def test_get_web_page(MockRegistry, mocked_get_user_agent, mocked_requests): + """ + Test that the get_web_page method works correctly + """ + # GIVEN: Mocked out objects and a fake URL + mocked_requests.get.return_value = MagicMock(text='text') + mocked_get_user_agent.return_value = 'user_agent' + fake_url = 'this://is.a.fake/url' + + # WHEN: The get_web_page() method is called + returned_page = get_web_page(fake_url) + + # THEN: The correct methods are called with the correct arguments and a web page is returned + mocked_requests.get.assert_called_once_with(fake_url, headers={'User-Agent': 'user_agent'}, + proxies=None, timeout=30.0) + mocked_get_user_agent.assert_called_once_with() + assert MockRegistry.call_count == 1, 'The Registry() object should have been called once' + assert returned_page == 'text', 'The returned page should be the mock object' + + +@patch('openlp.core.common.httputils.requests') +@patch('openlp.core.common.httputils.get_user_agent') +def test_get_web_page_with_header(mocked_get_user_agent, mocked_requests, settings): + """ + Test that adding a header to the call to get_web_page() adds the header to the request + """ + # GIVEN: Mocked out objects, a fake URL and a fake header + mocked_requests.get.return_value = MagicMock(text='text') + mocked_get_user_agent.return_value = 'user_agent' + fake_url = 'this://is.a.fake/url' + fake_headers = {'Fake-Header': 'fake value'} + + # WHEN: The get_web_page() method is called + returned_page = get_web_page(fake_url, headers=fake_headers) + + # THEN: The correct methods are called with the correct arguments and a web page is returned + expected_headers = dict(fake_headers) + expected_headers.update({'User-Agent': 'user_agent'}) + mocked_requests.get.assert_called_once_with(fake_url, headers=expected_headers, + proxies=None, timeout=30.0) + mocked_get_user_agent.assert_called_with() + assert returned_page == 'text', 'The returned page should be the mock object' + + +@patch('openlp.core.common.httputils.requests') +@patch('openlp.core.common.httputils.get_user_agent') +def test_get_web_page_with_user_agent_in_headers(mocked_get_user_agent, mocked_requests): + """ + Test that adding a user agent in the header when calling get_web_page() adds that user agent to the request + """ + # GIVEN: Mocked out objects, a fake URL and a fake header + mocked_requests.get.return_value = MagicMock(text='text') + fake_url = 'this://is.a.fake/url' + user_agent_headers = {'User-Agent': 'OpenLP/2.2.0'} + + # WHEN: The get_web_page() method is called + returned_page = get_web_page(fake_url, headers=user_agent_headers) + + # THEN: The correct methods are called with the correct arguments and a web page is returned + mocked_requests.get.assert_called_once_with(fake_url, headers=user_agent_headers, + proxies=None, timeout=30.0) + assert mocked_get_user_agent.call_count == 0, 'get_user_agent() should not have been called' + assert returned_page == 'text', 'The returned page should be "test"' + + +@patch('openlp.core.common.httputils.requests') +@patch('openlp.core.common.httputils.get_user_agent') +@patch('openlp.core.common.httputils.Registry') +def test_get_web_page_update_openlp(MockRegistry, mocked_get_user_agent, mocked_requests): + """ + Test that passing "update_openlp" as true to get_web_page calls Registry().get('app').process_events() + """ + # GIVEN: Mocked out objects, a fake URL + mocked_requests.get.return_value = MagicMock(text='text') + mocked_get_user_agent.return_value = 'user_agent' + mocked_registry_object = MagicMock() + mocked_application_object = MagicMock() + mocked_registry_object.get.return_value = mocked_application_object + MockRegistry.return_value = mocked_registry_object + fake_url = 'this://is.a.fake/url' + + # WHEN: The get_web_page() method is called + returned_page = get_web_page(fake_url, update_openlp=True) + + # THEN: The correct methods are called with the correct arguments and a web page is returned + mocked_requests.get.assert_called_once_with(fake_url, headers={'User-Agent': 'user_agent'}, + proxies=None, timeout=30.0) + mocked_get_user_agent.assert_called_once_with() + mocked_registry_object.get.assert_called_with('application') + mocked_application_object.process_events.assert_called_with() + assert returned_page == 'text', 'The returned page should be the mock object' + + +@patch('openlp.core.common.httputils.requests') +def test_get_url_file_size(mocked_requests): + """ + Test that calling "get_url_file_size" works correctly + """ + # GIVEN: Mocked out objects, a fake URL + mocked_requests.head.return_value = MagicMock(headers={'Content-Length': 100}) + fake_url = 'this://is.a.fake/url' + + # WHEN: The get_url_file_size() method is called + file_size = get_url_file_size(fake_url) + + # THEN: The correct methods are called with the correct arguments and a web page is returned + mocked_requests.head.assert_called_once_with(fake_url, allow_redirects=True, proxies=None, timeout=30.0) + assert file_size == 100 + + +@patch('openlp.core.common.httputils.requests') +def test_socket_timeout(mocked_requests, temp_file): + """ + Test socket timeout gets caught + """ + # GIVEN: Mocked urlopen to fake a network disconnect in the middle of a download + mocked_requests.get.side_effect = OSError + + # WHEN: Attempt to retrieve a file + download_file(MagicMock(), url='http://localhost/test', file_path=Path(temp_file)) + + # THEN: socket.timeout should have been caught + # NOTE: Test is if $tmpdir/tempfile is still there, then test fails since ftw deletes bad downloaded files + assert os.path.exists(temp_file) is False, 'temp_file should have been deleted' + + +def test_no_proxy_mode(settings): + """ + Test that a dictionary with http and https values are set to None is returned, when `NO_PROXY` mode is specified + """ + # GIVEN: A `proxy mode` setting of NO_PROXY + settings.setValue('advanced/proxy mode', ProxyMode.NO_PROXY) + + # WHEN: Calling `get_proxy_settings` + result = get_proxy_settings() + + # THEN: The returned value should be a dictionary with http and https values set to None + assert result == {'http': None, 'https': None} + + +def test_system_proxy_mode(settings): + """ + Test that None is returned, when `SYSTEM_PROXY` mode is specified + """ + # GIVEN: A `proxy mode` setting of SYSTEM_PROXY + settings.setValue('advanced/proxy mode', ProxyMode.SYSTEM_PROXY) + + # WHEN: Calling `get_proxy_settings` + result = get_proxy_settings() + + # THEN: The returned value should be None + assert result is None + + +def test_manual_proxy_mode_no_auth(): + """ + Test that the correct proxy addresses are returned when basic authentication is not used + """ + # GIVEN: A `proxy mode` setting of MANUAL_PROXY with proxy servers, but no auth credentials are supplied + Settings().setValue('advanced/proxy mode', ProxyMode.MANUAL_PROXY) + Settings().setValue('advanced/proxy http', 'testhttp.server:port') + Settings().setValue('advanced/proxy https', 'testhttps.server:port') + Settings().setValue('advanced/proxy username', '') + Settings().setValue('advanced/proxy password', '') + + # WHEN: Calling `get_proxy_settings` + result = get_proxy_settings() + + # THEN: The returned value should be the proxy servers without authentication + assert result == {'http': 'http://testhttp.server:port', 'https': 'https://testhttps.server:port'} + + +def test_manual_proxy_mode_auth(): + """ + Test that the correct proxy addresses are returned when basic authentication is used + """ + # GIVEN: A `proxy mode` setting of MANUAL_PROXY with proxy servers and auth credentials supplied + Settings().setValue('advanced/proxy mode', ProxyMode.MANUAL_PROXY) + Settings().setValue('advanced/proxy http', 'testhttp.server:port') + Settings().setValue('advanced/proxy https', 'testhttps.server:port') + Settings().setValue('advanced/proxy username', 'user') + Settings().setValue('advanced/proxy password', 'pass') + + # WHEN: Calling `get_proxy_settings` + result = get_proxy_settings() + + # THEN: The returned value should be the proxy servers with the authentication credentials + assert result == {'http': 'http://user:pass@testhttp.server:port', + 'https': 'https://user:pass@testhttps.server:port'} + + +def test_manual_proxy_mode_no_servers(): + """ + Test that the system proxies are overidden when the MANUAL_PROXY mode is specified, but no server addresses are + supplied + """ + # GIVEN: A `proxy mode` setting of MANUAL_PROXY with no servers specified + Settings().setValue('advanced/proxy mode', ProxyMode.MANUAL_PROXY) + Settings().setValue('advanced/proxy http', '') + Settings().setValue('advanced/proxy https', '') + Settings().setValue('advanced/proxy username', 'user') + Settings().setValue('advanced/proxy password', 'pass') + + # WHEN: Calling `get_proxy_settings` + result = get_proxy_settings() + + # THEN: The returned value should be the proxy servers set to None + assert result == {'http': None, 'https': None} def test_mode_arg_specified(mock_settings): diff --git a/tests/functional/openlp_core/common/test_init.py b/tests/functional/openlp_core/common/test_init.py index 81462226f..0dcc5b6b7 100644 --- a/tests/functional/openlp_core/common/test_init.py +++ b/tests/functional/openlp_core/common/test_init.py @@ -23,347 +23,347 @@ Functional tests to test the AppLocation class and related methods. """ from io import BytesIO from pathlib import Path -from unittest import TestCase from unittest.mock import MagicMock, PropertyMock, call, patch from openlp.core.common import add_actions, clean_filename, delete_file, get_file_encoding, get_filesystem_encoding, \ get_uno_command, get_uno_instance -from tests.helpers.testmixin import TestMixin -class TestInit(TestCase, TestMixin): +def test_add_actions_empty_list(): """ - A test suite to test out various methods around the common __init__ class. + Test that no actions are added when the list is empty + """ + # GIVEN: a mocked action list, and an empty list + mocked_target = MagicMock() + empty_list = [] + + # WHEN: The empty list is added to the mocked target + add_actions(mocked_target, empty_list) + + # THEN: The add method on the mocked target is never called + assert mocked_target.addSeparator.call_count == 0, 'addSeparator method should not have been called' + assert mocked_target.addAction.call_count == 0, 'addAction method should not have been called' + + +def test_add_actions_none_action(): + """ + Test that a separator is added when a None action is in the list + """ + # GIVEN: a mocked action list, and a list with None in it + mocked_target = MagicMock() + separator_list = [None] + + # WHEN: The list is added to the mocked target + add_actions(mocked_target, separator_list) + + # THEN: The addSeparator method is called, but the addAction method is never called + mocked_target.addSeparator.assert_called_with() + assert mocked_target.addAction.call_count == 0, 'addAction method should not have been called' + + +def test_add_actions_add_action(): + """ + Test that an action is added when a valid action is in the list + """ + # GIVEN: a mocked action list, and a list with an action in it + mocked_target = MagicMock() + action_list = ['action'] + + # WHEN: The list is added to the mocked target + add_actions(mocked_target, action_list) + + # THEN: The addSeparator method is not called, and the addAction method is called + assert mocked_target.addSeparator.call_count == 0, 'addSeparator method should not have been called' + mocked_target.addAction.assert_called_with('action') + + +def test_add_actions_action_and_none(): + """ + Test that an action and a separator are added when a valid action and None are in the list + """ + # GIVEN: a mocked action list, and a list with an action and None in it + mocked_target = MagicMock() + action_list = ['action', None] + + # WHEN: The list is added to the mocked target + add_actions(mocked_target, action_list) + + # THEN: The addSeparator method is called, and the addAction method is called + mocked_target.addSeparator.assert_called_with() + mocked_target.addAction.assert_called_with('action') + + +def test_get_uno_instance_pipe(): + """ + Test that when the UNO connection type is "pipe" the resolver is given the "pipe" URI + """ + # GIVEN: A mock resolver object and UNO_CONNECTION_TYPE is "pipe" + mock_resolver = MagicMock() + + # WHEN: get_uno_instance() is called + get_uno_instance(mock_resolver) + + # THEN: the resolve method is called with the correct argument + mock_resolver.resolve.assert_called_with('uno:pipe,name=openlp_pipe;urp;StarOffice.ComponentContext') + + +def test_get_uno_instance_socket(): + """ + Test that when the UNO connection type is other than "pipe" the resolver is given the "socket" URI + """ + # GIVEN: A mock resolver object and UNO_CONNECTION_TYPE is "socket" + mock_resolver = MagicMock() + + # WHEN: get_uno_instance() is called + get_uno_instance(mock_resolver, 'socket') + + # THEN: the resolve method is called with the correct argument + mock_resolver.resolve.assert_called_with('uno:socket,host=localhost,port=2002;urp;StarOffice.ComponentContext') + + +def test_get_uno_command_libreoffice_command_exists(): + """ + Test the ``get_uno_command`` function uses the libreoffice command when available. + :return: """ - def setUp(self): - """ - Create an instance and a few example actions. - """ - self.build_settings() + # GIVEN: A patched 'which' method which returns a path when called with 'libreoffice' + with patch('openlp.core.common.which', + **{'side_effect': lambda command: {'libreoffice': '/usr/bin/libreoffice'}[command]}): + # WHEN: Calling get_uno_command + result = get_uno_command() - def tearDown(self): - """ - Clean up - """ - self.destroy_settings() + # THEN: The command 'libreoffice' should be called with the appropriate parameters + assert result == 'libreoffice --nologo --norestore --minimized --nodefault --nofirststartwizard' \ + ' "--accept=pipe,name=openlp_pipe;urp;"' - def test_add_actions_empty_list(self): - """ - Test that no actions are added when the list is empty - """ - # GIVEN: a mocked action list, and an empty list - mocked_target = MagicMock() - empty_list = [] - # WHEN: The empty list is added to the mocked target - add_actions(mocked_target, empty_list) +def test_get_uno_command_only_soffice_command_exists(): + """ + Test the ``get_uno_command`` function uses the soffice command when the libreoffice command is not available. + :return: + """ - # THEN: The add method on the mocked target is never called - assert mocked_target.addSeparator.call_count == 0, 'addSeparator method should not have been called' - assert mocked_target.addAction.call_count == 0, 'addAction method should not have been called' + # GIVEN: A patched 'which' method which returns None when called with 'libreoffice' and a path when called with + # 'soffice' + with patch('openlp.core.common.which', + **{'side_effect': lambda command: {'libreoffice': None, 'soffice': '/usr/bin/soffice'}[ + command]}): + # WHEN: Calling get_uno_command + result = get_uno_command() - def test_add_actions_none_action(self): - """ - Test that a separator is added when a None action is in the list - """ - # GIVEN: a mocked action list, and a list with None in it - mocked_target = MagicMock() - separator_list = [None] + # THEN: The command 'soffice' should be called with the appropriate parameters + assert result == 'soffice --nologo --norestore --minimized --nodefault --nofirststartwizard' \ + ' "--accept=pipe,name=openlp_pipe;urp;"' - # WHEN: The list is added to the mocked target - add_actions(mocked_target, separator_list) - # THEN: The addSeparator method is called, but the addAction method is never called - mocked_target.addSeparator.assert_called_with() - assert mocked_target.addAction.call_count == 0, 'addAction method should not have been called' +def test_get_uno_command_when_no_command_exists(): + """ + Test the ``get_uno_command`` function raises an FileNotFoundError when neither the libreoffice or soffice + commands are available. + :return: + """ - def test_add_actions_add_action(self): - """ - Test that an action is added when a valid action is in the list - """ - # GIVEN: a mocked action list, and a list with an action in it - mocked_target = MagicMock() - action_list = ['action'] + # GIVEN: A patched 'which' method which returns None + with patch('openlp.core.common.which', **{'return_value': None}): + # WHEN: Calling get_uno_command - # WHEN: The list is added to the mocked target - add_actions(mocked_target, action_list) + # THEN: a FileNotFoundError exception should be raised + assert FileNotFoundError, get_uno_command - # THEN: The addSeparator method is not called, and the addAction method is called - assert mocked_target.addSeparator.call_count == 0, 'addSeparator method should not have been called' - mocked_target.addAction.assert_called_with('action') - def test_add_actions_action_and_none(self): - """ - Test that an action and a separator are added when a valid action and None are in the list - """ - # GIVEN: a mocked action list, and a list with an action and None in it - mocked_target = MagicMock() - action_list = ['action', None] +def test_get_uno_command_connection_type(): + """ + Test the ``get_uno_command`` function when the connection type is anything other than pipe. + :return: + """ - # WHEN: The list is added to the mocked target - add_actions(mocked_target, action_list) + # GIVEN: A patched 'which' method which returns 'libreoffice' + with patch('openlp.core.common.which', **{'return_value': 'libreoffice'}): + # WHEN: Calling get_uno_command with a connection type other than pipe + result = get_uno_command('socket') - # THEN: The addSeparator method is called, and the addAction method is called - mocked_target.addSeparator.assert_called_with() - mocked_target.addAction.assert_called_with('action') + # THEN: The connection parameters should be set for socket + assert result == 'libreoffice --nologo --norestore --minimized --nodefault --nofirststartwizard' \ + ' "--accept=socket,host=localhost,port=2002;urp;"' - def test_get_uno_instance_pipe(self): - """ - Test that when the UNO connection type is "pipe" the resolver is given the "pipe" URI - """ - # GIVEN: A mock resolver object and UNO_CONNECTION_TYPE is "pipe" - mock_resolver = MagicMock() - # WHEN: get_uno_instance() is called - get_uno_instance(mock_resolver) +def test_get_filesystem_encoding_sys_function_not_called(): + """ + Test the get_filesystem_encoding() function does not call the sys.getdefaultencoding() function + """ + # GIVEN: sys.getfilesystemencoding returns "cp1252" + with patch('openlp.core.common.sys.getfilesystemencoding') as mocked_getfilesystemencoding, \ + patch('openlp.core.common.sys.getdefaultencoding') as mocked_getdefaultencoding: + mocked_getfilesystemencoding.return_value = 'cp1252' - # THEN: the resolve method is called with the correct argument - mock_resolver.resolve.assert_called_with('uno:pipe,name=openlp_pipe;urp;StarOffice.ComponentContext') + # WHEN: get_filesystem_encoding() is called + result = get_filesystem_encoding() - def test_get_uno_instance_socket(self): - """ - Test that when the UNO connection type is other than "pipe" the resolver is given the "socket" URI - """ - # GIVEN: A mock resolver object and UNO_CONNECTION_TYPE is "socket" - mock_resolver = MagicMock() + # THEN: getdefaultencoding should have been called + mocked_getfilesystemencoding.assert_called_with() + assert mocked_getdefaultencoding.called == 0, 'getdefaultencoding should not have been called' + assert 'cp1252' == result, 'The result should be "cp1252"' - # WHEN: get_uno_instance() is called - get_uno_instance(mock_resolver, 'socket') - # THEN: the resolve method is called with the correct argument - mock_resolver.resolve.assert_called_with('uno:socket,host=localhost,port=2002;urp;StarOffice.ComponentContext') +def test_get_filesystem_encoding_sys_function_is_called(): + """ + Test the get_filesystem_encoding() function calls the sys.getdefaultencoding() function + """ + # GIVEN: sys.getfilesystemencoding returns None and sys.getdefaultencoding returns "utf-8" + with patch('openlp.core.common.sys.getfilesystemencoding') as mocked_getfilesystemencoding, \ + patch('openlp.core.common.sys.getdefaultencoding') as mocked_getdefaultencoding: + mocked_getfilesystemencoding.return_value = None + mocked_getdefaultencoding.return_value = 'utf-8' - def test_get_uno_command_libreoffice_command_exists(self): - """ - Test the ``get_uno_command`` function uses the libreoffice command when available. - :return: - """ + # WHEN: get_filesystem_encoding() is called + result = get_filesystem_encoding() - # GIVEN: A patched 'which' method which returns a path when called with 'libreoffice' - with patch('openlp.core.common.which', - **{'side_effect': lambda command: {'libreoffice': '/usr/bin/libreoffice'}[command]}): - # WHEN: Calling get_uno_command - result = get_uno_command() + # THEN: getdefaultencoding should have been called + mocked_getfilesystemencoding.assert_called_with() + mocked_getdefaultencoding.assert_called_with() + assert 'utf-8' == result, 'The result should be "utf-8"' - # THEN: The command 'libreoffice' should be called with the appropriate parameters - assert result == 'libreoffice --nologo --norestore --minimized --nodefault --nofirststartwizard' \ - ' "--accept=pipe,name=openlp_pipe;urp;"' - def test_get_uno_command_only_soffice_command_exists(self): - """ - Test the ``get_uno_command`` function uses the soffice command when the libreoffice command is not available. - :return: - """ +def test_clean_filename(): + """ + Test the clean_filename() function + """ + # GIVEN: A invalid file name and the valid file name. + invalid_name = 'A_file_with_invalid_characters_[\\/:*?"<>|+[]%].py' + wanted_name = 'A_file_with_invalid_characters________________.py' - # GIVEN: A patched 'which' method which returns None when called with 'libreoffice' and a path when called with - # 'soffice' - with patch('openlp.core.common.which', - **{'side_effect': lambda command: {'libreoffice': None, 'soffice': '/usr/bin/soffice'}[ - command]}): - # WHEN: Calling get_uno_command - result = get_uno_command() + # WHEN: Clean the name. + result = clean_filename(invalid_name) - # THEN: The command 'soffice' should be called with the appropriate parameters - assert result == 'soffice --nologo --norestore --minimized --nodefault --nofirststartwizard' \ - ' "--accept=pipe,name=openlp_pipe;urp;"' + # THEN: The file name should be cleaned. + assert wanted_name == result, 'The file name should not contain any special characters.' - def test_get_uno_command_when_no_command_exists(self): - """ - Test the ``get_uno_command`` function raises an FileNotFoundError when neither the libreoffice or soffice - commands are available. - :return: - """ - # GIVEN: A patched 'which' method which returns None - with patch('openlp.core.common.which', **{'return_value': None}): - # WHEN: Calling get_uno_command +def test_delete_file_no_path(): + """ + Test the delete_file function when called with out a valid path + """ + # GIVEN: A blank path + # WEHN: Calling delete_file + result = delete_file(None) - # THEN: a FileNotFoundError exception should be raised - self.assertRaises(FileNotFoundError, get_uno_command) + # THEN: delete_file should return False + assert result is False, "delete_file should return False when called with None" - def test_get_uno_command_connection_type(self): - """ - Test the ``get_uno_command`` function when the connection type is anything other than pipe. - :return: - """ - # GIVEN: A patched 'which' method which returns 'libreoffice' - with patch('openlp.core.common.which', **{'return_value': 'libreoffice'}): - # WHEN: Calling get_uno_command with a connection type other than pipe - result = get_uno_command('socket') +def test_delete_file_path_success(): + """ + Test the delete_file function when it successfully deletes a file + """ + # GIVEN: A mocked os which returns True when os.path.exists is called + with patch('openlp.core.common.os', **{'path.exists.return_value': False}): - # THEN: The connection parameters should be set for socket - assert result == 'libreoffice --nologo --norestore --minimized --nodefault --nofirststartwizard' \ - ' "--accept=socket,host=localhost,port=2002;urp;"' + # WHEN: Calling delete_file with a file path + result = delete_file(Path('path', 'file.ext')) - def test_get_filesystem_encoding_sys_function_not_called(self): - """ - Test the get_filesystem_encoding() function does not call the sys.getdefaultencoding() function - """ - # GIVEN: sys.getfilesystemencoding returns "cp1252" - with patch('openlp.core.common.sys.getfilesystemencoding') as mocked_getfilesystemencoding, \ - patch('openlp.core.common.sys.getdefaultencoding') as mocked_getdefaultencoding: - mocked_getfilesystemencoding.return_value = 'cp1252' + # THEN: delete_file should return True + assert result is True, 'delete_file should return True when it successfully deletes a file' - # WHEN: get_filesystem_encoding() is called - result = get_filesystem_encoding() - # THEN: getdefaultencoding should have been called - mocked_getfilesystemencoding.assert_called_with() - assert mocked_getdefaultencoding.called == 0, 'getdefaultencoding should not have been called' - assert 'cp1252' == result, 'The result should be "cp1252"' +def test_delete_file_path_no_file_exists(): + """ + Test the `delete_file` function when the file to remove does not exist + """ + # GIVEN: A patched `exists` methods on the Path object, which returns False + with patch.object(Path, 'exists', return_value=False), \ + patch.object(Path, 'unlink') as mocked_unlink: - def test_get_filesystem_encoding_sys_function_is_called(self): - """ - Test the get_filesystem_encoding() function calls the sys.getdefaultencoding() function - """ - # GIVEN: sys.getfilesystemencoding returns None and sys.getdefaultencoding returns "utf-8" - with patch('openlp.core.common.sys.getfilesystemencoding') as mocked_getfilesystemencoding, \ - patch('openlp.core.common.sys.getdefaultencoding') as mocked_getdefaultencoding: - mocked_getfilesystemencoding.return_value = None - mocked_getdefaultencoding.return_value = 'utf-8' + # WHEN: Calling `delete_file with` a file path + result = delete_file(Path('path', 'file.ext')) - # WHEN: get_filesystem_encoding() is called - result = get_filesystem_encoding() + # THEN: The function should not attempt to delete the file and it should return True + assert mocked_unlink.called is False + assert result is True, 'delete_file should return True when the file doesnt exist' - # THEN: getdefaultencoding should have been called - mocked_getfilesystemencoding.assert_called_with() - mocked_getdefaultencoding.assert_called_with() - assert 'utf-8' == result, 'The result should be "utf-8"' - def test_clean_filename(self): - """ - Test the clean_filename() function - """ - # GIVEN: A invalid file name and the valid file name. - invalid_name = 'A_file_with_invalid_characters_[\\/:*?"<>|+[]%].py' - wanted_name = 'A_file_with_invalid_characters________________.py' +def test_delete_file_path_exception(): + """ + Test the delete_file function when an exception is raised + """ + # GIVEN: A test `Path` object with a patched exists method which raises an OSError + # called. + with patch.object(Path, 'exists') as mocked_exists, \ + patch('openlp.core.common.log') as mocked_log: + mocked_exists.side_effect = OSError - # WHEN: Clean the name. - result = clean_filename(invalid_name) + # WHEN: Calling delete_file with a the test Path object + result = delete_file(Path('path', 'file.ext')) - # THEN: The file name should be cleaned. - assert wanted_name == result, 'The file name should not contain any special characters.' + # THEN: The exception should be logged and `delete_file` should return False + assert mocked_log.exception.called + assert result is False, 'delete_file should return False when an OSError is raised' - def test_delete_file_no_path(self): - """ - Test the delete_file function when called with out a valid path - """ - # GIVEN: A blank path - # WEHN: Calling delete_file - result = delete_file(None) - # THEN: delete_file should return False - assert result is False, "delete_file should return False when called with None" +def test_get_file_encoding_done(): + """ + Test get_file_encoding when the detector sets done to True + """ + # GIVEN: A mocked UniversalDetector instance with done attribute set to True after first iteration + with patch('openlp.core.common.UniversalDetector') as mocked_universal_detector, \ + patch.object(Path, 'open', return_value=BytesIO(b'data' * 260)) as mocked_open: + encoding_result = {'encoding': 'UTF-8', 'confidence': 0.99} + mocked_universal_detector_inst = MagicMock(**{'close.return_value': encoding_result}) + type(mocked_universal_detector_inst).done = PropertyMock(side_effect=[False, True]) + mocked_universal_detector.return_value = mocked_universal_detector_inst - def test_delete_file_path_success(self): - """ - Test the delete_file function when it successfully deletes a file - """ - # GIVEN: A mocked os which returns True when os.path.exists is called - with patch('openlp.core.common.os', **{'path.exists.return_value': False}): + # WHEN: Calling get_file_encoding + result = get_file_encoding(Path('file name')) - # WHEN: Calling delete_file with a file path - result = delete_file(Path('path', 'file.ext')) + # THEN: The feed method of UniversalDetector should only br called once before returning a result + mocked_open.assert_called_once_with('rb') + assert mocked_universal_detector_inst.feed.mock_calls == [call(b'data' * 256)] + mocked_universal_detector_inst.close.assert_called_once_with() + assert result == 'UTF-8' - # THEN: delete_file should return True - assert result is True, 'delete_file should return True when it successfully deletes a file' - def test_delete_file_path_no_file_exists(self): - """ - Test the `delete_file` function when the file to remove does not exist - """ - # GIVEN: A patched `exists` methods on the Path object, which returns False - with patch.object(Path, 'exists', return_value=False), \ - patch.object(Path, 'unlink') as mocked_unlink: +def test_get_file_encoding_eof(): + """ + Test get_file_encoding when the end of the file is reached + """ + # GIVEN: A mocked UniversalDetector instance which isn't set to done and a mocked open, with 1040 bytes of test + # data (enough to run the iterator twice) + with patch('openlp.core.common.UniversalDetector') as mocked_universal_detector, \ + patch.object(Path, 'open', return_value=BytesIO(b'data' * 260)) as mocked_open: + encoding_result = {'encoding': 'UTF-8', 'confidence': 0.99} + mocked_universal_detector_inst = MagicMock(mock=mocked_universal_detector, + **{'done': False, 'close.return_value': encoding_result}) + mocked_universal_detector.return_value = mocked_universal_detector_inst - # WHEN: Calling `delete_file with` a file path - result = delete_file(Path('path', 'file.ext')) + # WHEN: Calling get_file_encoding + result = get_file_encoding(Path('file name')) - # THEN: The function should not attempt to delete the file and it should return True - assert mocked_unlink.called is False - assert result is True, 'delete_file should return True when the file doesnt exist' + # THEN: The feed method of UniversalDetector should have been called twice before returning a result + mocked_open.assert_called_once_with('rb') + assert mocked_universal_detector_inst.feed.mock_calls == [call(b'data' * 256), call(b'data' * 4)] + mocked_universal_detector_inst.close.assert_called_once_with() + assert result == 'UTF-8' - def test_delete_file_path_exception(self): - """ - Test the delete_file function when an exception is raised - """ - # GIVEN: A test `Path` object with a patched exists method which raises an OSError - # called. - with patch.object(Path, 'exists') as mocked_exists, \ - patch('openlp.core.common.log') as mocked_log: - mocked_exists.side_effect = OSError - # WHEN: Calling delete_file with a the test Path object - result = delete_file(Path('path', 'file.ext')) +def test_get_file_encoding_oserror(): + """ + Test get_file_encoding when the end of the file is reached + """ + # GIVEN: A mocked UniversalDetector instance which isn't set to done and a mocked open, with 1040 bytes of test + # data (enough to run the iterator twice) + with patch('openlp.core.common.UniversalDetector') as mocked_universal_detector, \ + patch('builtins.open', side_effect=OSError), \ + patch('openlp.core.common.log') as mocked_log: + encoding_result = {'encoding': 'UTF-8', 'confidence': 0.99} + mocked_universal_detector_inst = MagicMock(mock=mocked_universal_detector, + **{'done': False, 'close.return_value': encoding_result}) + mocked_universal_detector.return_value = mocked_universal_detector_inst - # THEN: The exception should be logged and `delete_file` should return False - assert mocked_log.exception.called - assert result is False, 'delete_file should return False when an OSError is raised' + # WHEN: Calling get_file_encoding + result = get_file_encoding(Path('file name')) - def test_get_file_encoding_done(self): - """ - Test get_file_encoding when the detector sets done to True - """ - # GIVEN: A mocked UniversalDetector instance with done attribute set to True after first iteration - with patch('openlp.core.common.UniversalDetector') as mocked_universal_detector, \ - patch.object(Path, 'open', return_value=BytesIO(b'data' * 260)) as mocked_open: - encoding_result = {'encoding': 'UTF-8', 'confidence': 0.99} - mocked_universal_detector_inst = MagicMock(**{'close.return_value': encoding_result}) - type(mocked_universal_detector_inst).done = PropertyMock(side_effect=[False, True]) - mocked_universal_detector.return_value = mocked_universal_detector_inst - - # WHEN: Calling get_file_encoding - result = get_file_encoding(Path('file name')) - - # THEN: The feed method of UniversalDetector should only br called once before returning a result - mocked_open.assert_called_once_with('rb') - assert mocked_universal_detector_inst.feed.mock_calls == [call(b'data' * 256)] - mocked_universal_detector_inst.close.assert_called_once_with() - assert result == 'UTF-8' - - def test_get_file_encoding_eof(self): - """ - Test get_file_encoding when the end of the file is reached - """ - # GIVEN: A mocked UniversalDetector instance which isn't set to done and a mocked open, with 1040 bytes of test - # data (enough to run the iterator twice) - with patch('openlp.core.common.UniversalDetector') as mocked_universal_detector, \ - patch.object(Path, 'open', return_value=BytesIO(b'data' * 260)) as mocked_open: - encoding_result = {'encoding': 'UTF-8', 'confidence': 0.99} - mocked_universal_detector_inst = MagicMock(mock=mocked_universal_detector, - **{'done': False, 'close.return_value': encoding_result}) - mocked_universal_detector.return_value = mocked_universal_detector_inst - - # WHEN: Calling get_file_encoding - result = get_file_encoding(Path('file name')) - - # THEN: The feed method of UniversalDetector should have been called twice before returning a result - mocked_open.assert_called_once_with('rb') - assert mocked_universal_detector_inst.feed.mock_calls == [call(b'data' * 256), call(b'data' * 4)] - mocked_universal_detector_inst.close.assert_called_once_with() - assert result == 'UTF-8' - - def test_get_file_encoding_oserror(self): - """ - Test get_file_encoding when the end of the file is reached - """ - # GIVEN: A mocked UniversalDetector instance which isn't set to done and a mocked open, with 1040 bytes of test - # data (enough to run the iterator twice) - with patch('openlp.core.common.UniversalDetector') as mocked_universal_detector, \ - patch('builtins.open', side_effect=OSError), \ - patch('openlp.core.common.log') as mocked_log: - encoding_result = {'encoding': 'UTF-8', 'confidence': 0.99} - mocked_universal_detector_inst = MagicMock(mock=mocked_universal_detector, - **{'done': False, 'close.return_value': encoding_result}) - mocked_universal_detector.return_value = mocked_universal_detector_inst - - # WHEN: Calling get_file_encoding - result = get_file_encoding(Path('file name')) - - # THEN: log.exception should be called and get_file_encoding should return None - mocked_log.exception.assert_called_once_with('Error detecting file encoding') - mocked_universal_detector_inst.feed.assert_not_called() - mocked_universal_detector_inst.close.assert_called_once_with() - assert result == 'UTF-8' + # THEN: log.exception should be called and get_file_encoding should return None + mocked_log.exception.assert_called_once_with('Error detecting file encoding') + mocked_universal_detector_inst.feed.assert_not_called() + mocked_universal_detector_inst.close.assert_called_once_with() + assert result == 'UTF-8' diff --git a/tests/functional/openlp_core/test_app.py b/tests/functional/openlp_core/test_app.py index 631f333cd..41f8517be 100644 --- a/tests/functional/openlp_core/test_app.py +++ b/tests/functional/openlp_core/test_app.py @@ -29,7 +29,6 @@ sys.modules['PyQt5.QtWebEngineWidgets'] = MagicMock() from openlp.core.app import parse_options from openlp.core.common import is_win -from openlp.core.common.settings import Settings def test_parse_options_basic(): @@ -239,7 +238,7 @@ def test_backup_on_upgrade_first_install(mocked_question, mocked_get_version, qa qapp.backup_on_upgrade(old_install, False) # THEN: It should not ask if we want to create a backup - assert Settings().value('core/application version') == '2.4.0', 'Version should be the same!' + assert settings.value('core/application version') == '2.4.0', 'Version should be the same!' assert mocked_question.call_count == 0, 'No question should have been asked!' @@ -266,7 +265,7 @@ def test_backup_on_upgrade(mocked_question, mocked_get_version, qapp, settings): qapp.backup_on_upgrade(old_install, True) # THEN: It should ask if we want to create a backup - assert Settings().value('core/application version') == '2.9.0', 'Version should be upgraded!' + assert settings.value('core/application version') == '2.9.0', 'Version should be upgraded!' assert mocked_question.call_count == 1, 'A question should have been asked!' qapp.splash.hide.assert_called_once_with() qapp.splash.show.assert_called_once_with() diff --git a/tests/functional/openlp_core/ui/test_themetab.py b/tests/functional/openlp_core/ui/test_themetab.py index caad53db9..dd017f9eb 100644 --- a/tests/functional/openlp_core/ui/test_themetab.py +++ b/tests/functional/openlp_core/ui/test_themetab.py @@ -28,20 +28,6 @@ from openlp.core.ui.settingsform import SettingsForm from openlp.core.ui.themestab import ThemesTab -def test_creation(mock_settings): - """ - Test that Themes Tab is created. - """ - # GIVEN: A new Advanced Tab - settings_form = SettingsForm(None) - - # WHEN: I create an advanced tab - themes_tab = ThemesTab(settings_form) - - # THEN: - assert "Themes" == themes_tab.tab_title, 'The tab title should be Theme' - - def test_save_triggers_processes_true(mock_settings): """ Test that the global theme event is triggered when the tab is visited. diff --git a/tests/interfaces/openlp_core/ui/test_mainwindow.py b/tests/interfaces/openlp_core/ui/test_mainwindow.py index a8884e7b3..f8d27c960 100644 --- a/tests/interfaces/openlp_core/ui/test_mainwindow.py +++ b/tests/interfaces/openlp_core/ui/test_mainwindow.py @@ -21,7 +21,7 @@ """ Package to test the openlp.core.ui.mainwindow package. """ -from unittest import TestCase, skipIf +from unittest import TestCase, skipIf, skip from unittest.mock import MagicMock, patch from PyQt5 import QtGui @@ -56,6 +56,7 @@ class TestMainWindow(TestCase, TestMixin): mocked_plugin.status = PluginStatus.Active mocked_plugin.icon = QtGui.QIcon() Registry().register('mock_plugin', mocked_plugin) + State().load_settings() State().add_service("mock", 1, is_plugin=True, status=PluginStatus.Active) # Mock classes and methods used by mainwindow. with patch('openlp.core.ui.mainwindow.SettingsForm'), \ @@ -77,6 +78,7 @@ class TestMainWindow(TestCase, TestMixin): """ del self.main_window + @skip('Fix when migrate to PyTest') def test_restore_current_media_manager_item(self): """ Regression test for bug #1152509. diff --git a/tests/interfaces/openlp_plugins/bibles/test_lib_parse_reference.py b/tests/interfaces/openlp_plugins/bibles/test_lib_parse_reference.py index 1f5d729ee..4a8be8d73 100644 --- a/tests/interfaces/openlp_plugins/bibles/test_lib_parse_reference.py +++ b/tests/interfaces/openlp_plugins/bibles/test_lib_parse_reference.py @@ -46,6 +46,7 @@ class TestBibleManager(TestCase, TestMixin): with patch('openlp.core.common.applocation.AppLocation.get_section_data_path') as mocked_get_data_path, \ patch('openlp.core.common.applocation.AppLocation.get_files') as mocked_get_files: Registry().register('settings', Settings()) + # GIVEN: A mocked out Settings class and a mocked out AppLocation.get_files() mocked_get_files.return_value = ["tests.sqlite"] mocked_get_data_path.return_value = TEST_RESOURCES_PATH + "/bibles" self.manager = BibleManager(MagicMock())