From 6f34f8f2b86724485ff7f83dc12cc9a0642cb69a Mon Sep 17 00:00:00 2001 From: Phill Ridout Date: Thu, 7 Jun 2018 18:44:35 +0100 Subject: [PATCH 1/8] Start on implementing global OpenLP proxying --- openlp/core/common/httputils.py | 38 +++++- openlp/core/common/settings.py | 13 ++ openlp/core/ui/advancedtab.py | 55 ++++---- .../openlp_core/common/test_httputils.py | 123 +++++++++++++++++- 4 files changed, 198 insertions(+), 31 deletions(-) diff --git a/openlp/core/common/httputils.py b/openlp/core/common/httputils.py index e7d11fad5..40077f630 100644 --- a/openlp/core/common/httputils.py +++ b/openlp/core/common/httputils.py @@ -32,6 +32,7 @@ import requests from openlp.core.common import trace_error_handler from openlp.core.common.registry import Registry +from openlp.core.common.settings import ProxyMode, Settings log = logging.getLogger(__name__ + '.__init__') @@ -64,6 +65,39 @@ CONNECTION_TIMEOUT = 30 CONNECTION_RETRIES = 2 +def get_proxy_settings(mode=None): + """ + Create a dictionary containing the proxy settings. + + :param ProxyMode | None mode: Specify the source of the proxy settings + :return: A dict using the format expected by the requests library. + :rtype: dict | None + """ + settings = Settings() + if mode is None: + mode = settings.value('advanced/proxy mode') + if mode == ProxyMode.NO_PROXY: + return {'http': None, 'https': None} + elif mode == ProxyMode.SYSTEM_PROXY: + # The requests library defaults to using the proxy settings in the environment variables + return + elif mode == ProxyMode.MANUAL_PROXY: + http_addr = settings.value('advanced/proxy http') + https_addr = settings.value('advanced/proxy https') + username = settings.value('advanced/proxy username') + password = settings.value('advanced/proxy password') + basic_auth = '' + if username: + basic_auth = '{username}:{password}@'.format(username=username, password=password) + http_value = None + https_value = None + if http_addr is not None: + http_value = 'http://{basic_auth}{http_addr}'.format(basic_auth=basic_auth, http_addr=http_addr) + if https_addr is not None: + https_value = 'https://{basic_auth}{https_addr}'.format(basic_auth=basic_auth, https_addr=https_addr) + return {'http': http_value, 'https': https_value} + + def get_user_agent(): """ Return a user agent customised for the platform the user is on. @@ -75,7 +109,7 @@ def get_user_agent(): return browser_list[random_index] -def get_web_page(url, headers=None, update_openlp=False, proxies=None): +def get_web_page(url, headers=None, update_openlp=False, proxy_mode=None): """ Attempts to download the webpage at url and returns that page or None. @@ -90,6 +124,8 @@ def get_web_page(url, headers=None, update_openlp=False, proxies=None): headers = {} if 'user-agent' not in [key.lower() for key in headers.keys()]: headers['User-Agent'] = get_user_agent() + if proxy_mode is None: + proxies = get_proxy_settings(mode=proxy_mode) log.debug('Downloading URL = %s' % url) retries = 0 while retries < CONNECTION_RETRIES: diff --git a/openlp/core/common/settings.py b/openlp/core/common/settings.py index 91a587616..74d686b09 100644 --- a/openlp/core/common/settings.py +++ b/openlp/core/common/settings.py @@ -26,6 +26,7 @@ import datetime import json import logging import os +from enum import IntEnum from tempfile import gettempdir from PyQt5 import QtCore, QtGui @@ -38,6 +39,13 @@ log = logging.getLogger(__name__) __version__ = 2 + +class ProxyMode(IntEnum): + NO_PROXY = 1 + SYSTEM_PROXY = 2 + MANUAL_PROXY = 3 + + # Fix for bug #1014422. X11_BYPASS_DEFAULT = True if is_linux(): # pragma: no cover @@ -116,6 +124,11 @@ class Settings(QtCore.QSettings): 'advanced/print file meta data': False, 'advanced/print notes': False, 'advanced/print slide text': False, + 'advanced/proxy mode': ProxyMode.SYSTEM_PROXY, + 'advanced/proxy http': '', + 'advanced/proxy https': '', + 'advanced/proxy username': '', + 'advanced/proxy password': '', 'advanced/recent file count': 4, 'advanced/save current plugin': False, 'advanced/slide limits': SlideLimits.End, diff --git a/openlp/core/ui/advancedtab.py b/openlp/core/ui/advancedtab.py index ffec0e5b8..fa970f234 100644 --- a/openlp/core/ui/advancedtab.py +++ b/openlp/core/ui/advancedtab.py @@ -35,6 +35,7 @@ from openlp.core.lib import SettingsTab, build_icon from openlp.core.ui.style import HAS_DARK_STYLE from openlp.core.widgets.edits import PathEdit from openlp.core.widgets.enums import PathEditType +from openlp.core.widgets.widgets import ProxyWidget log = logging.getLogger(__name__) @@ -76,6 +77,9 @@ class AdvancedTab(SettingsTab): self.media_plugin_check_box = QtWidgets.QCheckBox(self.ui_group_box) self.media_plugin_check_box.setObjectName('media_plugin_check_box') self.ui_layout.addRow(self.media_plugin_check_box) + self.hide_mouse_check_box = QtWidgets.QCheckBox(self.ui_group_box) + self.hide_mouse_check_box.setObjectName('hide_mouse_check_box') + self.ui_layout.addWidget(self.hide_mouse_check_box) self.double_click_live_check_box = QtWidgets.QCheckBox(self.ui_group_box) self.double_click_live_check_box.setObjectName('double_click_live_check_box') self.ui_layout.addRow(self.double_click_live_check_box) @@ -116,6 +120,24 @@ class AdvancedTab(SettingsTab): self.use_dark_style_checkbox = QtWidgets.QCheckBox(self.ui_group_box) self.use_dark_style_checkbox.setObjectName('use_dark_style_checkbox') self.ui_layout.addRow(self.use_dark_style_checkbox) + # Service Item Slide Limits + self.slide_group_box = QtWidgets.QGroupBox(self.left_column) + self.slide_group_box.setObjectName('slide_group_box') + self.slide_layout = QtWidgets.QVBoxLayout(self.slide_group_box) + self.slide_layout.setObjectName('slide_layout') + self.slide_label = QtWidgets.QLabel(self.slide_group_box) + self.slide_label.setWordWrap(True) + self.slide_layout.addWidget(self.slide_label) + self.end_slide_radio_button = QtWidgets.QRadioButton(self.slide_group_box) + self.end_slide_radio_button.setObjectName('end_slide_radio_button') + self.slide_layout.addWidget(self.end_slide_radio_button) + self.wrap_slide_radio_button = QtWidgets.QRadioButton(self.slide_group_box) + self.wrap_slide_radio_button.setObjectName('wrap_slide_radio_button') + self.slide_layout.addWidget(self.wrap_slide_radio_button) + self.next_item_radio_button = QtWidgets.QRadioButton(self.slide_group_box) + self.next_item_radio_button.setObjectName('next_item_radio_button') + self.slide_layout.addWidget(self.next_item_radio_button) + self.left_layout.addWidget(self.slide_group_box) # Data Directory self.data_directory_group_box = QtWidgets.QGroupBox(self.left_column) self.data_directory_group_box.setObjectName('data_directory_group_box') @@ -142,33 +164,6 @@ class AdvancedTab(SettingsTab): self.data_directory_layout.addRow(self.data_directory_copy_check_layout) self.data_directory_layout.addRow(self.new_data_directory_has_files_label) self.left_layout.addWidget(self.data_directory_group_box) - # Hide mouse - self.hide_mouse_group_box = QtWidgets.QGroupBox(self.right_column) - self.hide_mouse_group_box.setObjectName('hide_mouse_group_box') - self.hide_mouse_layout = QtWidgets.QVBoxLayout(self.hide_mouse_group_box) - self.hide_mouse_layout.setObjectName('hide_mouse_layout') - self.hide_mouse_check_box = QtWidgets.QCheckBox(self.hide_mouse_group_box) - self.hide_mouse_check_box.setObjectName('hide_mouse_check_box') - self.hide_mouse_layout.addWidget(self.hide_mouse_check_box) - self.right_layout.addWidget(self.hide_mouse_group_box) - # Service Item Slide Limits - self.slide_group_box = QtWidgets.QGroupBox(self.right_column) - self.slide_group_box.setObjectName('slide_group_box') - self.slide_layout = QtWidgets.QVBoxLayout(self.slide_group_box) - self.slide_layout.setObjectName('slide_layout') - self.slide_label = QtWidgets.QLabel(self.slide_group_box) - self.slide_label.setWordWrap(True) - self.slide_layout.addWidget(self.slide_label) - self.end_slide_radio_button = QtWidgets.QRadioButton(self.slide_group_box) - self.end_slide_radio_button.setObjectName('end_slide_radio_button') - self.slide_layout.addWidget(self.end_slide_radio_button) - self.wrap_slide_radio_button = QtWidgets.QRadioButton(self.slide_group_box) - self.wrap_slide_radio_button.setObjectName('wrap_slide_radio_button') - self.slide_layout.addWidget(self.wrap_slide_radio_button) - self.next_item_radio_button = QtWidgets.QRadioButton(self.slide_group_box) - self.next_item_radio_button.setObjectName('next_item_radio_button') - self.slide_layout.addWidget(self.next_item_radio_button) - self.right_layout.addWidget(self.slide_group_box) # Display Workarounds self.display_workaround_group_box = QtWidgets.QGroupBox(self.right_column) self.display_workaround_group_box.setObjectName('display_workaround_group_box') @@ -223,6 +218,9 @@ class AdvancedTab(SettingsTab): self.service_name_example.setObjectName('service_name_example') self.service_name_layout.addRow(self.service_name_example_label, self.service_name_example) self.right_layout.addWidget(self.service_name_group_box) + # Proxies + self.proxy_widget = ProxyWidget(self.right_column) + self.right_layout.addWidget(self.proxy_widget) # After the last item on each side, add some spacing self.left_layout.addStretch() self.right_layout.addStretch() @@ -311,7 +309,6 @@ class AdvancedTab(SettingsTab): translate('OpenLP.AdvancedTab', 'Revert to the default service name "{name}".').format(name=UiStrings().DefaultServiceName)) self.service_name_example_label.setText(translate('OpenLP.AdvancedTab', 'Example:')) - self.hide_mouse_group_box.setTitle(translate('OpenLP.AdvancedTab', 'Mouse Cursor')) self.hide_mouse_check_box.setText(translate('OpenLP.AdvancedTab', 'Hide mouse cursor when over display window')) self.data_directory_new_label.setText(translate('OpenLP.AdvancedTab', 'Path:')) self.data_directory_cancel_button.setText(translate('OpenLP.AdvancedTab', 'Cancel')) @@ -334,6 +331,7 @@ class AdvancedTab(SettingsTab): self.wrap_slide_radio_button.setText(translate('OpenLP.GeneralTab', '&Wrap around')) self.next_item_radio_button.setText(translate('OpenLP.GeneralTab', '&Move to next/previous service item')) self.search_as_type_check_box.setText(translate('SongsPlugin.GeneralTab', 'Enable search as you type')) + self.proxy_widget.retranslate_ui() def load(self): """ @@ -436,6 +434,7 @@ class AdvancedTab(SettingsTab): if HAS_DARK_STYLE: settings.setValue('use_dark_style', self.use_dark_style_checkbox.isChecked()) settings.endGroup() + self.proxy_widget.save() def on_search_as_type_check_box_changed(self, check_state): self.is_search_as_you_type_enabled = (check_state == QtCore.Qt.Checked) diff --git a/tests/functional/openlp_core/common/test_httputils.py b/tests/functional/openlp_core/common/test_httputils.py index 7d3966279..1a979454c 100644 --- a/tests/functional/openlp_core/common/test_httputils.py +++ b/tests/functional/openlp_core/common/test_httputils.py @@ -27,13 +27,14 @@ import tempfile from unittest import TestCase from unittest.mock import MagicMock, patch -from openlp.core.common.httputils import get_user_agent, get_web_page, get_url_file_size, download_file +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.path import Path +from openlp.core.common.settings import Settings from tests.helpers.testmixin import TestMixin class TestHttpUtils(TestCase, TestMixin): - """ A test suite to test out various http helper functions. """ @@ -240,3 +241,121 @@ class TestHttpUtils(TestCase, TestMixin): # 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' + + +class TestGetProxySettings(TestCase, TestMixin): + def setUp(self): + self.build_settings() + self.addCleanup(self.destroy_settings) + + @patch('openlp.core.common.httputils.Settings') + def test_mode_arg_specified(self, MockSettings): + """ + Test that the argument is used rather than reading the 'advanced/proxy mode' setting + """ + # GIVEN: Mocked settings + mocked_settings = MagicMock() + MockSettings.return_value = mocked_settings + + # WHEN: Calling `get_proxy_settings` with the mode arg specified + get_proxy_settings(mode=ProxyMode.NO_PROXY) + + # THEN: The mode arg should have been used rather than looking it up in the settings + mocked_settings.value.assert_not_called() + + @patch('openlp.core.common.httputils.Settings') + def test_mode_incorrect_arg_specified(self, MockSettings): + """ + Test that the system settings are used when the mode arg specieied is invalid + """ + # GIVEN: Mocked settings + mocked_settings = MagicMock() + MockSettings.return_value = mocked_settings + + # WHEN: Calling `get_proxy_settings` with an invalid mode arg specified + result = get_proxy_settings(mode='qwerty') + + # THEN: An None should be returned + mocked_settings.value.assert_not_called() + assert result is None + + + 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) + + # 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(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() + + # THEN: The returned value should be None + assert result is None + + 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: 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(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') + + # 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(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', None) + Settings().setValue('advanced/proxy https', None) + 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} From dce509e909ae74055e61bfab07a727107964609f Mon Sep 17 00:00:00 2001 From: Phill Ridout Date: Thu, 7 Jun 2018 18:45:24 +0100 Subject: [PATCH 2/8] Add some forgetten files --- openlp/core/widgets/widgets.py | 134 +++++++++++++++ .../openlp_core/widgets/test_widgets.py | 155 ++++++++++++++++++ 2 files changed, 289 insertions(+) create mode 100644 openlp/core/widgets/widgets.py create mode 100644 tests/interfaces/openlp_core/widgets/test_widgets.py diff --git a/openlp/core/widgets/widgets.py b/openlp/core/widgets/widgets.py new file mode 100644 index 000000000..bb65410bd --- /dev/null +++ b/openlp/core/widgets/widgets.py @@ -0,0 +1,134 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2018 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; version 2 of the License. # +# # +# 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, write to the Free Software Foundation, Inc., 59 # +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # +############################################################################### +""" +The :mod:`~openlp.core.widgets.widgets` module contains custom widgets used in OpenLP +""" +from PyQt5 import QtWidgets + +from openlp.core.common.i18n import translate +from openlp.core.common.settings import ProxyMode, Settings + + +class ProxyWidget(QtWidgets.QGroupBox): + """ + A proxy settings widget that implements loading and saving its settings. + """ + def __init__(self, parent=None): + """ + Initialise the widget. + + :param QtWidgets.QWidget | None parent: The widgets parent + """ + super().__init__(parent) + self._setup() + + def _setup(self): + """ + A setup method seperate from __init__ to allow easier testing + """ + self.setup_ui() + self.load() + + def setup_ui(self): + """ + Create the widget layout and sub widgets + """ + self.layout = QtWidgets.QFormLayout(self) + self.radio_group = QtWidgets.QButtonGroup(self) + self.no_proxy_radio = QtWidgets.QRadioButton('', self) + self.radio_group.addButton(self.no_proxy_radio, ProxyMode.NO_PROXY) + self.layout.setWidget(0, QtWidgets.QFormLayout.SpanningRole, self.no_proxy_radio) + self.use_sysem_proxy_radio = QtWidgets.QRadioButton('', self) + self.radio_group.addButton(self.use_sysem_proxy_radio, ProxyMode.SYSTEM_PROXY) + self.layout.setWidget(1, QtWidgets.QFormLayout.SpanningRole, self.use_sysem_proxy_radio) + self.manual_proxy_radio = QtWidgets.QRadioButton('', self) + self.radio_group.addButton(self.manual_proxy_radio, ProxyMode.MANUAL_PROXY) + self.layout.setWidget(2, QtWidgets.QFormLayout.SpanningRole, self.manual_proxy_radio) + self.http_edit = QtWidgets.QLineEdit(self) + self.layout.addRow('HTTP:', self.http_edit) + self.https_edit = QtWidgets.QLineEdit(self) + self.layout.addRow('HTTPS:', self.https_edit) + self.username_edit = QtWidgets.QLineEdit(self) + self.layout.addRow('Username:', self.username_edit) + self.password_edit = QtWidgets.QLineEdit(self) + self.password_edit.setEchoMode(QtWidgets.QLineEdit.Password) + self.layout.addRow('Password:', self.password_edit) + # Signal / Slots + self.radio_group.buttonToggled.connect(self.on_radio_group_button_toggled) + + # @QtCore.pyqtSlot(int, bool) For some reason PyQt doesn't think this signature exists. + # (It does according to the docs) + def on_radio_group_button_toggled(self, button, checked): + """ + Handles the toggled signal on the radio buttons. The signal is emitted twice if a radio butting being toggled on + causes another radio button in the group to be toggled off. + + En/Disables the `Manual Proxy` line edits depending on the currently selected radio button + + :param QtWidgets.QRadioButton button: The button that has toggled + :param bool checked: The buttons new state + """ + id = self.radio_group.id(button) # The work around (see above comment) + enable_manual_edits = id == ProxyMode.MANUAL_PROXY and checked + self.http_edit.setEnabled(enable_manual_edits) + self.https_edit.setEnabled(enable_manual_edits) + self.username_edit.setEnabled(enable_manual_edits) + self.password_edit.setEnabled(enable_manual_edits) + + def retranslate_ui(self): + """ + Translate the Ui + """ + self.setTitle(translate('OpenLP.ProxyWidget', 'Proxy Server Settings')) + self.no_proxy_radio.setText(translate('OpenLP.ProxyWidget', 'No prox&y')) + self.use_sysem_proxy_radio.setText(translate('OpenLP.ProxyWidget', '&Use system proxy')) + self.manual_proxy_radio.setText(translate('OpenLP.ProxyWidget', '&Manual proxy configuration')) + proxy_example = translate('OpenLP.ProxyWidget', 'e.g. proxy_server_address:port_no') + self.layout.labelForField(self.http_edit).setText('HTTP:') + self.http_edit.setPlaceholderText(proxy_example) + self.layout.labelForField(self.https_edit).setText('HTTPS:') + self.https_edit.setPlaceholderText(proxy_example) + self.layout.labelForField(self.username_edit).setText('Username:') + self.layout.labelForField(self.password_edit).setText('Password:') + + def load(self): + """ + Load the data from the settings to the widget. + """ + settings = Settings() + checked_radio = self.radio_group.button(settings.value('advanced/proxy mode')) + checked_radio.setChecked(True) + self.http_edit.setText(settings.value('advanced/proxy http')) + self.https_edit.setText(settings.value('advanced/proxy https')) + self.username_edit.setText(settings.value('advanced/proxy username')) + self.password_edit.setText(settings.value('advanced/proxy password')) + + def save(self): + """ + Save the widget data to the settings + """ + settings = Settings() # TODO: Migrate from old system + settings.setValue('advanced/proxy mode', self.radio_group.checkedId()) + settings.setValue('advanced/proxy http', self.http_edit.text()) + settings.setValue('advanced/proxy https', self.https_edit.text()) + settings.setValue('advanced/proxy username', self.username_edit.text()) + settings.setValue('advanced/proxy password', self.password_edit.text()) diff --git a/tests/interfaces/openlp_core/widgets/test_widgets.py b/tests/interfaces/openlp_core/widgets/test_widgets.py new file mode 100644 index 000000000..8036cee3f --- /dev/null +++ b/tests/interfaces/openlp_core/widgets/test_widgets.py @@ -0,0 +1,155 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2018 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; version 2 of the License. # +# # +# 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, write to the Free Software Foundation, Inc., 59 # +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # +############################################################################### +""" +Module to test the custom widgets. +""" +from unittest import TestCase +from unittest.mock import MagicMock, call, patch + +from openlp.core.common.registry import Registry +from openlp.core.common.settings import ProxyMode +from openlp.core.widgets.widgets import ProxyWidget +from tests.helpers.testmixin import TestMixin + + +class TestProxyWidget(TestCase, TestMixin): + """ + Test the EditCustomForm. + """ + def setUp(self): + """ + Create the UI + """ + Registry.create() + self.setup_application() + + def test_radio_button_exclusivity(self): + """ + Test that only one radio button can be checked at a time, and that the line edits are only enabled when the + `manual_proxy_radio` is checked + """ + # GIVEN: An instance of the `openlp.core.common.widgets.widgets.ProxyWidget` + proxy_widget = ProxyWidget() + + # WHEN: 'Checking' the `no_proxy_radio` button + proxy_widget.no_proxy_radio.setChecked(True) + + # THEN: The other radio buttons should not be checked and the line edits should not be enabled + assert proxy_widget.use_sysem_proxy_radio.isChecked() is False + assert proxy_widget.manual_proxy_radio.isChecked() is False + assert proxy_widget.http_edit.isEnabled() is False + assert proxy_widget.https_edit.isEnabled() is False + assert proxy_widget.username_edit.isEnabled() is False + assert proxy_widget.password_edit.isEnabled() is False + + # WHEN: 'Checking' the `use_sysem_proxy_radio` button + proxy_widget.use_sysem_proxy_radio.setChecked(True) + + # THEN: The other radio buttons should not be checked and the line edits should not be enabled + assert proxy_widget.no_proxy_radio.isChecked() is False + assert proxy_widget.manual_proxy_radio.isChecked() is False + assert proxy_widget.http_edit.isEnabled() is False + assert proxy_widget.https_edit.isEnabled() is False + assert proxy_widget.username_edit.isEnabled() is False + assert proxy_widget.password_edit.isEnabled() is False + + # WHEN: 'Checking' the `manual_proxy_radio` button + proxy_widget.manual_proxy_radio.setChecked(True) + + # THEN: The other radio buttons should not be checked and the line edits should be enabled + assert proxy_widget.no_proxy_radio.isChecked() is False + assert proxy_widget.use_sysem_proxy_radio.isChecked() is False + assert proxy_widget.http_edit.isEnabled() is True + assert proxy_widget.https_edit.isEnabled() is True + assert proxy_widget.username_edit.isEnabled() is True + assert proxy_widget.password_edit.isEnabled() is True + + def test_proxy_widget_load_default_settings(self): + """ + Test that the default settings are loaded from the config correctly + """ + # GIVEN: And instance of the widget with default settings + proxy_widget = ProxyWidget() + + # WHEN: Calling the `load` method + proxy_widget.load() + + # THEN: The widget should be in its default state + assert proxy_widget.use_sysem_proxy_radio.isChecked() is True + assert proxy_widget.http_edit.text() == '' + assert proxy_widget.https_edit.text() == '' + assert proxy_widget.username_edit.text() == '' + assert proxy_widget.password_edit.text() == '' + + @patch.object(ProxyWidget, 'load') + @patch('openlp.core.widgets.widgets.Settings') + def test_proxy_widget_save_no_proxy_settings(self, settings_patcher, proxy_widget_load_patcher): + """ + Test that the settings are saved correctly + """ + # GIVEN: A Mocked settings instance of the proxy widget with some known values set + settings_instance = MagicMock() + settings_patcher.return_value = settings_instance + proxy_widget = ProxyWidget() + proxy_widget.no_proxy_radio.setChecked(True) + proxy_widget.http_edit.setText('') + proxy_widget.https_edit.setText('') + proxy_widget.username_edit.setText('') + proxy_widget.password_edit.setText('') + + # WHEN: Calling save + proxy_widget.save() + + # THEN: The settings should be set as expected + settings_instance.setValue.assert_has_calls( + [call('advanced/proxy mode', ProxyMode.NO_PROXY), + call('advanced/proxy http', ''), + call('advanced/proxy https', ''), + call('advanced/proxy username', ''), + call('advanced/proxy password', '')]) + + @patch.object(ProxyWidget, 'load') + @patch('openlp.core.widgets.widgets.Settings') + def test_proxy_widget_save_manual_settings(self, settings_patcher, proxy_widget_load_patcher): + """ + Test that the settings are saved correctly + """ + # GIVEN: A Mocked and instance of the proxy widget with some known values set + settings_instance = MagicMock() + settings_patcher.return_value = settings_instance + proxy_widget = ProxyWidget() + proxy_widget.manual_proxy_radio.setChecked(True) + proxy_widget.http_edit.setText('http_proxy_server:port') + proxy_widget.https_edit.setText('https_proxy_server:port') + proxy_widget.username_edit.setText('username') + proxy_widget.password_edit.setText('password') + + # WHEN: Calling save + proxy_widget.save() + + # THEN: The settings should be set as expected + settings_instance.setValue.assert_has_calls( + [call('advanced/proxy mode', ProxyMode.MANUAL_PROXY), + call('advanced/proxy http', 'http_proxy_server:port'), + call('advanced/proxy https', 'https_proxy_server:port'), + call('advanced/proxy username', 'username'), + call('advanced/proxy password', 'password')]) From fac5d4b79892f87bef5e754fd476163c53ad686f Mon Sep 17 00:00:00 2001 From: Phill Ridout Date: Fri, 8 Jun 2018 07:12:23 +0100 Subject: [PATCH 3/8] Fixes --- openlp/core/common/httputils.py | 4 ++-- scripts/jenkins_script.py | 1 + tests/functional/openlp_core/common/test_httputils.py | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/openlp/core/common/httputils.py b/openlp/core/common/httputils.py index 40077f630..69310c327 100644 --- a/openlp/core/common/httputils.py +++ b/openlp/core/common/httputils.py @@ -91,9 +91,9 @@ def get_proxy_settings(mode=None): basic_auth = '{username}:{password}@'.format(username=username, password=password) http_value = None https_value = None - if http_addr is not None: + if http_addr: http_value = 'http://{basic_auth}{http_addr}'.format(basic_auth=basic_auth, http_addr=http_addr) - if https_addr is not None: + if https_addr: https_value = 'https://{basic_auth}{https_addr}'.format(basic_auth=basic_auth, https_addr=https_addr) return {'http': http_value, 'https': https_value} diff --git a/scripts/jenkins_script.py b/scripts/jenkins_script.py index 5a1e67c45..9fa1bfdb3 100755 --- a/scripts/jenkins_script.py +++ b/scripts/jenkins_script.py @@ -199,6 +199,7 @@ def get_repo_name(): """ This returns the name of branch of the working directory. For example it returns *lp:~googol/openlp/render*. """ + return 'lp:~phill-ridout/openlp/proxies' # Run the bzr command. bzr = Popen(('bzr', 'info'), stdout=PIPE, stderr=PIPE) raw_output, error = bzr.communicate() diff --git a/tests/functional/openlp_core/common/test_httputils.py b/tests/functional/openlp_core/common/test_httputils.py index 1a979454c..a24f39967 100644 --- a/tests/functional/openlp_core/common/test_httputils.py +++ b/tests/functional/openlp_core/common/test_httputils.py @@ -349,8 +349,8 @@ class TestGetProxySettings(TestCase, TestMixin): """ # 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', None) - Settings().setValue('advanced/proxy https', None) + Settings().setValue('advanced/proxy http', '') + Settings().setValue('advanced/proxy https', '') Settings().setValue('advanced/proxy username', 'user') Settings().setValue('advanced/proxy password', 'pass') From 94758a9778720b9985d0f8625a56720a8057dfc8 Mon Sep 17 00:00:00 2001 From: Phill Ridout Date: Fri, 8 Jun 2018 07:21:23 +0100 Subject: [PATCH 4/8] Pep --- tests/functional/openlp_core/common/test_httputils.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/functional/openlp_core/common/test_httputils.py b/tests/functional/openlp_core/common/test_httputils.py index a24f39967..0552cce8f 100644 --- a/tests/functional/openlp_core/common/test_httputils.py +++ b/tests/functional/openlp_core/common/test_httputils.py @@ -279,7 +279,6 @@ class TestGetProxySettings(TestCase, TestMixin): mocked_settings.value.assert_not_called() assert result is None - 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 @@ -323,7 +322,6 @@ class TestGetProxySettings(TestCase, TestMixin): # 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 From 03400afc7c30ca77efe9d5a9d22bde5d398cf42d Mon Sep 17 00:00:00 2001 From: Phill Ridout Date: Fri, 8 Jun 2018 07:21:41 +0100 Subject: [PATCH 5/8] Pep --- scripts/lp-merge.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/lp-merge.py b/scripts/lp-merge.py index d017821e4..ea30a06cf 100755 --- a/scripts/lp-merge.py +++ b/scripts/lp-merge.py @@ -104,7 +104,7 @@ def get_merge_info(url): # Find the p tag that contains the commit message #
...
...

commit_message = soup.find('div', id='commit-message').find('div', id='edit-commit_message')\ - .find('div', 'yui3-editable_text-text').p + .find('div', 'yui3-editable_text-text').p merge_info['commit_message'] = commit_message.string # Find all tr-tags with this class. Makes it possible to get bug numbers. # Date: Fri, 8 Jun 2018 21:55:20 +0100 Subject: [PATCH 6/8] fix --- openlp/core/common/httputils.py | 15 ++++++++------- scripts/jenkins_script.py | 1 - 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/openlp/core/common/httputils.py b/openlp/core/common/httputils.py index 69310c327..54173a8d2 100644 --- a/openlp/core/common/httputils.py +++ b/openlp/core/common/httputils.py @@ -109,14 +109,15 @@ def get_user_agent(): return browser_list[random_index] -def get_web_page(url, headers=None, update_openlp=False, proxy_mode=None): +def get_web_page(url, headers=None, update_openlp=False, proxy=None): """ Attempts to download the webpage at url and returns that page or None. :param url: The URL to be downloaded. - :param header: An optional HTTP header to pass in the request to the web server. - :param update_openlp: Tells OpenLP to update itself if the page is successfully downloaded. - Defaults to False. + :param dict | None headers: An optional HTTP header to pass in the request to the web server. + :param update_openlp: Tells OpenLP to update itself if the page is successfully downloaded. Defaults to False. + :param dict | ProxyMode | None proxy: ProxyMode enum or a dictionary containing the proxy servers, with their types + as the key e.g. {'http': 'http://proxyserver:port', 'https': 'https://proxyserver:port'} """ if not url: return None @@ -124,13 +125,13 @@ def get_web_page(url, headers=None, update_openlp=False, proxy_mode=None): headers = {} if 'user-agent' not in [key.lower() for key in headers.keys()]: headers['User-Agent'] = get_user_agent() - if proxy_mode is None: - proxies = get_proxy_settings(mode=proxy_mode) + if not isinstance(proxy, dict): + proxy = get_proxy_settings(mode=proxy) log.debug('Downloading URL = %s' % url) retries = 0 while retries < CONNECTION_RETRIES: try: - response = requests.get(url, headers=headers, proxies=proxies, timeout=float(CONNECTION_TIMEOUT)) + response = requests.get(url, headers=headers, proxies=proxy, timeout=float(CONNECTION_TIMEOUT)) log.debug('Downloaded page {url}'.format(url=response.url)) break except OSError: diff --git a/scripts/jenkins_script.py b/scripts/jenkins_script.py index 9fa1bfdb3..5a1e67c45 100755 --- a/scripts/jenkins_script.py +++ b/scripts/jenkins_script.py @@ -199,7 +199,6 @@ def get_repo_name(): """ This returns the name of branch of the working directory. For example it returns *lp:~googol/openlp/render*. """ - return 'lp:~phill-ridout/openlp/proxies' # Run the bzr command. bzr = Popen(('bzr', 'info'), stdout=PIPE, stderr=PIPE) raw_output, error = bzr.communicate() From 70c777b7e36a634074f73ab99750acb647591eab Mon Sep 17 00:00:00 2001 From: Phill Ridout Date: Sun, 10 Jun 2018 07:38:16 +0100 Subject: [PATCH 7/8] Add translate methods --- openlp/core/widgets/widgets.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/openlp/core/widgets/widgets.py b/openlp/core/widgets/widgets.py index bb65410bd..052057eff 100644 --- a/openlp/core/widgets/widgets.py +++ b/openlp/core/widgets/widgets.py @@ -75,8 +75,6 @@ class ProxyWidget(QtWidgets.QGroupBox): # Signal / Slots self.radio_group.buttonToggled.connect(self.on_radio_group_button_toggled) - # @QtCore.pyqtSlot(int, bool) For some reason PyQt doesn't think this signature exists. - # (It does according to the docs) def on_radio_group_button_toggled(self, button, checked): """ Handles the toggled signal on the radio buttons. The signal is emitted twice if a radio butting being toggled on @@ -103,12 +101,12 @@ class ProxyWidget(QtWidgets.QGroupBox): self.use_sysem_proxy_radio.setText(translate('OpenLP.ProxyWidget', '&Use system proxy')) self.manual_proxy_radio.setText(translate('OpenLP.ProxyWidget', '&Manual proxy configuration')) proxy_example = translate('OpenLP.ProxyWidget', 'e.g. proxy_server_address:port_no') - self.layout.labelForField(self.http_edit).setText('HTTP:') + self.layout.labelForField(self.http_edit).setText(translate('OpenLP.ProxyWidget', 'HTTP:')) self.http_edit.setPlaceholderText(proxy_example) - self.layout.labelForField(self.https_edit).setText('HTTPS:') + self.layout.labelForField(self.https_edit).setText(translate('OpenLP.ProxyWidget', 'HTTPS:')) self.https_edit.setPlaceholderText(proxy_example) - self.layout.labelForField(self.username_edit).setText('Username:') - self.layout.labelForField(self.password_edit).setText('Password:') + self.layout.labelForField(self.username_edit).setText(translate('OpenLP.ProxyWidget', 'Username:')) + self.layout.labelForField(self.password_edit).setText(translate('OpenLP.ProxyWidget', 'Password:')) def load(self): """ From 2aaa0bf2870e5edab9bc314c534fa406d21a2156 Mon Sep 17 00:00:00 2001 From: Phill Ridout Date: Sun, 10 Jun 2018 07:38:42 +0100 Subject: [PATCH 8/8] break out the code using multiple when/thens --- .../openlp_core/widgets/test_widgets.py | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/tests/interfaces/openlp_core/widgets/test_widgets.py b/tests/interfaces/openlp_core/widgets/test_widgets.py index 8036cee3f..5353596fd 100644 --- a/tests/interfaces/openlp_core/widgets/test_widgets.py +++ b/tests/interfaces/openlp_core/widgets/test_widgets.py @@ -42,13 +42,14 @@ class TestProxyWidget(TestCase, TestMixin): Registry.create() self.setup_application() - def test_radio_button_exclusivity(self): + def test_radio_button_exclusivity_no_proxy(self): """ Test that only one radio button can be checked at a time, and that the line edits are only enabled when the `manual_proxy_radio` is checked """ - # GIVEN: An instance of the `openlp.core.common.widgets.widgets.ProxyWidget` + # GIVEN: An instance of the `openlp.core.common.widgets.widgets.ProxyWidget` with a radio already checked proxy_widget = ProxyWidget() + proxy_widget.manual_proxy_radio.setChecked(True) # WHEN: 'Checking' the `no_proxy_radio` button proxy_widget.no_proxy_radio.setChecked(True) @@ -61,6 +62,15 @@ class TestProxyWidget(TestCase, TestMixin): assert proxy_widget.username_edit.isEnabled() is False assert proxy_widget.password_edit.isEnabled() is False + def test_radio_button_exclusivity_system_proxy(self): + """ + Test that only one radio button can be checked at a time, and that the line edits are only enabled when the + `manual_proxy_radio` is checked + """ + # GIVEN: An instance of the `openlp.core.common.widgets.widgets.ProxyWidget` with a radio already checked + proxy_widget = ProxyWidget() + proxy_widget.manual_proxy_radio.setChecked(True) + # WHEN: 'Checking' the `use_sysem_proxy_radio` button proxy_widget.use_sysem_proxy_radio.setChecked(True) @@ -72,6 +82,15 @@ class TestProxyWidget(TestCase, TestMixin): assert proxy_widget.username_edit.isEnabled() is False assert proxy_widget.password_edit.isEnabled() is False + def test_radio_button_exclusivity_manual_proxy(self): + """ + Test that only one radio button can be checked at a time, and that the line edits are only enabled when the + `manual_proxy_radio` is checked + """ + # GIVEN: An instance of the `openlp.core.common.widgets.widgets.ProxyWidget` with a radio already checked + proxy_widget = ProxyWidget() + proxy_widget.no_proxy_radio.setChecked(True) + # WHEN: 'Checking' the `manual_proxy_radio` button proxy_widget.manual_proxy_radio.setChecked(True)