Merge lp:~phill-ridout/openlp/proxies into lp:openlp

Proposed by Phill
Status: Superseded
Proposed branch: lp:~phill-ridout/openlp/proxies
Merge into: lp:openlp
Diff against target: 692 lines (+491/-36)
7 files modified
openlp/core/common/httputils.py (+42/-5)
openlp/core/common/settings.py (+13/-0)
openlp/core/ui/advancedtab.py (+27/-28)
openlp/core/widgets/widgets.py (+134/-0)
scripts/lp-merge.py (+1/-1)
tests/functional/openlp_core/common/test_httputils.py (+119/-2)
tests/interfaces/openlp_core/widgets/test_widgets.py (+155/-0)
To merge this branch: bzr merge lp:~phill-ridout/openlp/proxies
Reviewer Review Type Date Requested Status
Phill Needs Fixing
Review via email: mp+347673@code.launchpad.net

This proposal has been superseded by a proposal from 2018-06-08.

Commit message

Implement a proxy configuration widget. This is just a start other tasks that still need completing (to follow) are changing the web bibles over to use this, and allow setting up of proxy from FTW (else the sample download can fail)

lp:~phill-ridout/openlp/proxies (revision )
https://ci.openlp.io/job/Branch-01-Pull/2521/ [WAITING]
[RUNNING]
[SUCCESS]
https://ci.openlp.io/job/Branch-02a-Linux-Tests/2421/ [WAITING]
[RUNNING]
[SUCCESS]
https://ci.openlp.io/job/Branch-02b-macOS-Tests/208/ [WAITING]
[FAILURE]
Stopping after failure
https://ci.openlp.io/job/Branch-03a-Build-Source/116/ [WAITING]
[SUCCESS]
https://ci.openlp.io/job/Branch-03b-Build-macOS/109/ [WAITING]
[RUNNING]
[SUCCESS]
https://ci.openlp.io/job/Branch-04a-Code-Analysis/1578/ [WAITING]
[SUCCESS]
https://ci.openlp.io/job/Branch-04b-Test-Coverage/1391/ [WAITING]
[SUCCESS]

To post a comment you must log in.
Revision history for this message
Phill (phill-ridout) wrote :

Just noticed an issue in the diff. DO NOT MERGE until I've re subbed.

review: Needs Fixing
lp:~phill-ridout/openlp/proxies updated
2826. By Phill

fix

2827. By Phill

Add translate methods

2828. By Phill

break out the code using multiple when/thens

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'openlp/core/common/httputils.py'
2--- openlp/core/common/httputils.py 2018-01-04 06:10:20 +0000
3+++ openlp/core/common/httputils.py 2018-06-08 20:55:54 +0000
4@@ -32,6 +32,7 @@
5
6 from openlp.core.common import trace_error_handler
7 from openlp.core.common.registry import Registry
8+from openlp.core.common.settings import ProxyMode, Settings
9
10 log = logging.getLogger(__name__ + '.__init__')
11
12@@ -64,6 +65,39 @@
13 CONNECTION_RETRIES = 2
14
15
16+def get_proxy_settings(mode=None):
17+ """
18+ Create a dictionary containing the proxy settings.
19+
20+ :param ProxyMode | None mode: Specify the source of the proxy settings
21+ :return: A dict using the format expected by the requests library.
22+ :rtype: dict | None
23+ """
24+ settings = Settings()
25+ if mode is None:
26+ mode = settings.value('advanced/proxy mode')
27+ if mode == ProxyMode.NO_PROXY:
28+ return {'http': None, 'https': None}
29+ elif mode == ProxyMode.SYSTEM_PROXY:
30+ # The requests library defaults to using the proxy settings in the environment variables
31+ return
32+ elif mode == ProxyMode.MANUAL_PROXY:
33+ http_addr = settings.value('advanced/proxy http')
34+ https_addr = settings.value('advanced/proxy https')
35+ username = settings.value('advanced/proxy username')
36+ password = settings.value('advanced/proxy password')
37+ basic_auth = ''
38+ if username:
39+ basic_auth = '{username}:{password}@'.format(username=username, password=password)
40+ http_value = None
41+ https_value = None
42+ if http_addr:
43+ http_value = 'http://{basic_auth}{http_addr}'.format(basic_auth=basic_auth, http_addr=http_addr)
44+ if https_addr:
45+ https_value = 'https://{basic_auth}{https_addr}'.format(basic_auth=basic_auth, https_addr=https_addr)
46+ return {'http': http_value, 'https': https_value}
47+
48+
49 def get_user_agent():
50 """
51 Return a user agent customised for the platform the user is on.
52@@ -75,14 +109,15 @@
53 return browser_list[random_index]
54
55
56-def get_web_page(url, headers=None, update_openlp=False, proxies=None):
57+def get_web_page(url, headers=None, update_openlp=False, proxy=None):
58 """
59 Attempts to download the webpage at url and returns that page or None.
60
61 :param url: The URL to be downloaded.
62- :param header: An optional HTTP header to pass in the request to the web server.
63- :param update_openlp: Tells OpenLP to update itself if the page is successfully downloaded.
64- Defaults to False.
65+ :param dict | None headers: An optional HTTP header to pass in the request to the web server.
66+ :param update_openlp: Tells OpenLP to update itself if the page is successfully downloaded. Defaults to False.
67+ :param dict | ProxyMode | None proxy: ProxyMode enum or a dictionary containing the proxy servers, with their types
68+ as the key e.g. {'http': 'http://proxyserver:port', 'https': 'https://proxyserver:port'}
69 """
70 if not url:
71 return None
72@@ -90,11 +125,13 @@
73 headers = {}
74 if 'user-agent' not in [key.lower() for key in headers.keys()]:
75 headers['User-Agent'] = get_user_agent()
76+ if not isinstance(proxy, dict):
77+ proxy = get_proxy_settings(mode=proxy)
78 log.debug('Downloading URL = %s' % url)
79 retries = 0
80 while retries < CONNECTION_RETRIES:
81 try:
82- response = requests.get(url, headers=headers, proxies=proxies, timeout=float(CONNECTION_TIMEOUT))
83+ response = requests.get(url, headers=headers, proxies=proxy, timeout=float(CONNECTION_TIMEOUT))
84 log.debug('Downloaded page {url}'.format(url=response.url))
85 break
86 except OSError:
87
88=== modified file 'openlp/core/common/settings.py'
89--- openlp/core/common/settings.py 2018-05-03 14:58:50 +0000
90+++ openlp/core/common/settings.py 2018-06-08 20:55:54 +0000
91@@ -26,6 +26,7 @@
92 import json
93 import logging
94 import os
95+from enum import IntEnum
96 from tempfile import gettempdir
97
98 from PyQt5 import QtCore, QtGui
99@@ -38,6 +39,13 @@
100
101 __version__ = 2
102
103+
104+class ProxyMode(IntEnum):
105+ NO_PROXY = 1
106+ SYSTEM_PROXY = 2
107+ MANUAL_PROXY = 3
108+
109+
110 # Fix for bug #1014422.
111 X11_BYPASS_DEFAULT = True
112 if is_linux(): # pragma: no cover
113@@ -116,6 +124,11 @@
114 'advanced/print file meta data': False,
115 'advanced/print notes': False,
116 'advanced/print slide text': False,
117+ 'advanced/proxy mode': ProxyMode.SYSTEM_PROXY,
118+ 'advanced/proxy http': '',
119+ 'advanced/proxy https': '',
120+ 'advanced/proxy username': '',
121+ 'advanced/proxy password': '',
122 'advanced/recent file count': 4,
123 'advanced/save current plugin': False,
124 'advanced/slide limits': SlideLimits.End,
125
126=== modified file 'openlp/core/ui/advancedtab.py'
127--- openlp/core/ui/advancedtab.py 2017-12-29 09:15:48 +0000
128+++ openlp/core/ui/advancedtab.py 2018-06-08 20:55:54 +0000
129@@ -35,6 +35,7 @@
130 from openlp.core.ui.style import HAS_DARK_STYLE
131 from openlp.core.widgets.edits import PathEdit
132 from openlp.core.widgets.enums import PathEditType
133+from openlp.core.widgets.widgets import ProxyWidget
134
135 log = logging.getLogger(__name__)
136
137@@ -76,6 +77,9 @@
138 self.media_plugin_check_box = QtWidgets.QCheckBox(self.ui_group_box)
139 self.media_plugin_check_box.setObjectName('media_plugin_check_box')
140 self.ui_layout.addRow(self.media_plugin_check_box)
141+ self.hide_mouse_check_box = QtWidgets.QCheckBox(self.ui_group_box)
142+ self.hide_mouse_check_box.setObjectName('hide_mouse_check_box')
143+ self.ui_layout.addWidget(self.hide_mouse_check_box)
144 self.double_click_live_check_box = QtWidgets.QCheckBox(self.ui_group_box)
145 self.double_click_live_check_box.setObjectName('double_click_live_check_box')
146 self.ui_layout.addRow(self.double_click_live_check_box)
147@@ -116,6 +120,24 @@
148 self.use_dark_style_checkbox = QtWidgets.QCheckBox(self.ui_group_box)
149 self.use_dark_style_checkbox.setObjectName('use_dark_style_checkbox')
150 self.ui_layout.addRow(self.use_dark_style_checkbox)
151+ # Service Item Slide Limits
152+ self.slide_group_box = QtWidgets.QGroupBox(self.left_column)
153+ self.slide_group_box.setObjectName('slide_group_box')
154+ self.slide_layout = QtWidgets.QVBoxLayout(self.slide_group_box)
155+ self.slide_layout.setObjectName('slide_layout')
156+ self.slide_label = QtWidgets.QLabel(self.slide_group_box)
157+ self.slide_label.setWordWrap(True)
158+ self.slide_layout.addWidget(self.slide_label)
159+ self.end_slide_radio_button = QtWidgets.QRadioButton(self.slide_group_box)
160+ self.end_slide_radio_button.setObjectName('end_slide_radio_button')
161+ self.slide_layout.addWidget(self.end_slide_radio_button)
162+ self.wrap_slide_radio_button = QtWidgets.QRadioButton(self.slide_group_box)
163+ self.wrap_slide_radio_button.setObjectName('wrap_slide_radio_button')
164+ self.slide_layout.addWidget(self.wrap_slide_radio_button)
165+ self.next_item_radio_button = QtWidgets.QRadioButton(self.slide_group_box)
166+ self.next_item_radio_button.setObjectName('next_item_radio_button')
167+ self.slide_layout.addWidget(self.next_item_radio_button)
168+ self.left_layout.addWidget(self.slide_group_box)
169 # Data Directory
170 self.data_directory_group_box = QtWidgets.QGroupBox(self.left_column)
171 self.data_directory_group_box.setObjectName('data_directory_group_box')
172@@ -142,33 +164,6 @@
173 self.data_directory_layout.addRow(self.data_directory_copy_check_layout)
174 self.data_directory_layout.addRow(self.new_data_directory_has_files_label)
175 self.left_layout.addWidget(self.data_directory_group_box)
176- # Hide mouse
177- self.hide_mouse_group_box = QtWidgets.QGroupBox(self.right_column)
178- self.hide_mouse_group_box.setObjectName('hide_mouse_group_box')
179- self.hide_mouse_layout = QtWidgets.QVBoxLayout(self.hide_mouse_group_box)
180- self.hide_mouse_layout.setObjectName('hide_mouse_layout')
181- self.hide_mouse_check_box = QtWidgets.QCheckBox(self.hide_mouse_group_box)
182- self.hide_mouse_check_box.setObjectName('hide_mouse_check_box')
183- self.hide_mouse_layout.addWidget(self.hide_mouse_check_box)
184- self.right_layout.addWidget(self.hide_mouse_group_box)
185- # Service Item Slide Limits
186- self.slide_group_box = QtWidgets.QGroupBox(self.right_column)
187- self.slide_group_box.setObjectName('slide_group_box')
188- self.slide_layout = QtWidgets.QVBoxLayout(self.slide_group_box)
189- self.slide_layout.setObjectName('slide_layout')
190- self.slide_label = QtWidgets.QLabel(self.slide_group_box)
191- self.slide_label.setWordWrap(True)
192- self.slide_layout.addWidget(self.slide_label)
193- self.end_slide_radio_button = QtWidgets.QRadioButton(self.slide_group_box)
194- self.end_slide_radio_button.setObjectName('end_slide_radio_button')
195- self.slide_layout.addWidget(self.end_slide_radio_button)
196- self.wrap_slide_radio_button = QtWidgets.QRadioButton(self.slide_group_box)
197- self.wrap_slide_radio_button.setObjectName('wrap_slide_radio_button')
198- self.slide_layout.addWidget(self.wrap_slide_radio_button)
199- self.next_item_radio_button = QtWidgets.QRadioButton(self.slide_group_box)
200- self.next_item_radio_button.setObjectName('next_item_radio_button')
201- self.slide_layout.addWidget(self.next_item_radio_button)
202- self.right_layout.addWidget(self.slide_group_box)
203 # Display Workarounds
204 self.display_workaround_group_box = QtWidgets.QGroupBox(self.right_column)
205 self.display_workaround_group_box.setObjectName('display_workaround_group_box')
206@@ -223,6 +218,9 @@
207 self.service_name_example.setObjectName('service_name_example')
208 self.service_name_layout.addRow(self.service_name_example_label, self.service_name_example)
209 self.right_layout.addWidget(self.service_name_group_box)
210+ # Proxies
211+ self.proxy_widget = ProxyWidget(self.right_column)
212+ self.right_layout.addWidget(self.proxy_widget)
213 # After the last item on each side, add some spacing
214 self.left_layout.addStretch()
215 self.right_layout.addStretch()
216@@ -311,7 +309,6 @@
217 translate('OpenLP.AdvancedTab',
218 'Revert to the default service name "{name}".').format(name=UiStrings().DefaultServiceName))
219 self.service_name_example_label.setText(translate('OpenLP.AdvancedTab', 'Example:'))
220- self.hide_mouse_group_box.setTitle(translate('OpenLP.AdvancedTab', 'Mouse Cursor'))
221 self.hide_mouse_check_box.setText(translate('OpenLP.AdvancedTab', 'Hide mouse cursor when over display window'))
222 self.data_directory_new_label.setText(translate('OpenLP.AdvancedTab', 'Path:'))
223 self.data_directory_cancel_button.setText(translate('OpenLP.AdvancedTab', 'Cancel'))
224@@ -334,6 +331,7 @@
225 self.wrap_slide_radio_button.setText(translate('OpenLP.GeneralTab', '&Wrap around'))
226 self.next_item_radio_button.setText(translate('OpenLP.GeneralTab', '&Move to next/previous service item'))
227 self.search_as_type_check_box.setText(translate('SongsPlugin.GeneralTab', 'Enable search as you type'))
228+ self.proxy_widget.retranslate_ui()
229
230 def load(self):
231 """
232@@ -436,6 +434,7 @@
233 if HAS_DARK_STYLE:
234 settings.setValue('use_dark_style', self.use_dark_style_checkbox.isChecked())
235 settings.endGroup()
236+ self.proxy_widget.save()
237
238 def on_search_as_type_check_box_changed(self, check_state):
239 self.is_search_as_you_type_enabled = (check_state == QtCore.Qt.Checked)
240
241=== added file 'openlp/core/widgets/widgets.py'
242--- openlp/core/widgets/widgets.py 1970-01-01 00:00:00 +0000
243+++ openlp/core/widgets/widgets.py 2018-06-08 20:55:54 +0000
244@@ -0,0 +1,134 @@
245+# -*- coding: utf-8 -*-
246+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
247+
248+###############################################################################
249+# OpenLP - Open Source Lyrics Projection #
250+# --------------------------------------------------------------------------- #
251+# Copyright (c) 2008-2018 OpenLP Developers #
252+# --------------------------------------------------------------------------- #
253+# This program is free software; you can redistribute it and/or modify it #
254+# under the terms of the GNU General Public License as published by the Free #
255+# Software Foundation; version 2 of the License. #
256+# #
257+# This program is distributed in the hope that it will be useful, but WITHOUT #
258+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
259+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
260+# more details. #
261+# #
262+# You should have received a copy of the GNU General Public License along #
263+# with this program; if not, write to the Free Software Foundation, Inc., 59 #
264+# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
265+###############################################################################
266+"""
267+The :mod:`~openlp.core.widgets.widgets` module contains custom widgets used in OpenLP
268+"""
269+from PyQt5 import QtWidgets
270+
271+from openlp.core.common.i18n import translate
272+from openlp.core.common.settings import ProxyMode, Settings
273+
274+
275+class ProxyWidget(QtWidgets.QGroupBox):
276+ """
277+ A proxy settings widget that implements loading and saving its settings.
278+ """
279+ def __init__(self, parent=None):
280+ """
281+ Initialise the widget.
282+
283+ :param QtWidgets.QWidget | None parent: The widgets parent
284+ """
285+ super().__init__(parent)
286+ self._setup()
287+
288+ def _setup(self):
289+ """
290+ A setup method seperate from __init__ to allow easier testing
291+ """
292+ self.setup_ui()
293+ self.load()
294+
295+ def setup_ui(self):
296+ """
297+ Create the widget layout and sub widgets
298+ """
299+ self.layout = QtWidgets.QFormLayout(self)
300+ self.radio_group = QtWidgets.QButtonGroup(self)
301+ self.no_proxy_radio = QtWidgets.QRadioButton('', self)
302+ self.radio_group.addButton(self.no_proxy_radio, ProxyMode.NO_PROXY)
303+ self.layout.setWidget(0, QtWidgets.QFormLayout.SpanningRole, self.no_proxy_radio)
304+ self.use_sysem_proxy_radio = QtWidgets.QRadioButton('', self)
305+ self.radio_group.addButton(self.use_sysem_proxy_radio, ProxyMode.SYSTEM_PROXY)
306+ self.layout.setWidget(1, QtWidgets.QFormLayout.SpanningRole, self.use_sysem_proxy_radio)
307+ self.manual_proxy_radio = QtWidgets.QRadioButton('', self)
308+ self.radio_group.addButton(self.manual_proxy_radio, ProxyMode.MANUAL_PROXY)
309+ self.layout.setWidget(2, QtWidgets.QFormLayout.SpanningRole, self.manual_proxy_radio)
310+ self.http_edit = QtWidgets.QLineEdit(self)
311+ self.layout.addRow('HTTP:', self.http_edit)
312+ self.https_edit = QtWidgets.QLineEdit(self)
313+ self.layout.addRow('HTTPS:', self.https_edit)
314+ self.username_edit = QtWidgets.QLineEdit(self)
315+ self.layout.addRow('Username:', self.username_edit)
316+ self.password_edit = QtWidgets.QLineEdit(self)
317+ self.password_edit.setEchoMode(QtWidgets.QLineEdit.Password)
318+ self.layout.addRow('Password:', self.password_edit)
319+ # Signal / Slots
320+ self.radio_group.buttonToggled.connect(self.on_radio_group_button_toggled)
321+
322+ # @QtCore.pyqtSlot(int, bool) For some reason PyQt doesn't think this signature exists.
323+ # (It does according to the docs)
324+ def on_radio_group_button_toggled(self, button, checked):
325+ """
326+ Handles the toggled signal on the radio buttons. The signal is emitted twice if a radio butting being toggled on
327+ causes another radio button in the group to be toggled off.
328+
329+ En/Disables the `Manual Proxy` line edits depending on the currently selected radio button
330+
331+ :param QtWidgets.QRadioButton button: The button that has toggled
332+ :param bool checked: The buttons new state
333+ """
334+ id = self.radio_group.id(button) # The work around (see above comment)
335+ enable_manual_edits = id == ProxyMode.MANUAL_PROXY and checked
336+ self.http_edit.setEnabled(enable_manual_edits)
337+ self.https_edit.setEnabled(enable_manual_edits)
338+ self.username_edit.setEnabled(enable_manual_edits)
339+ self.password_edit.setEnabled(enable_manual_edits)
340+
341+ def retranslate_ui(self):
342+ """
343+ Translate the Ui
344+ """
345+ self.setTitle(translate('OpenLP.ProxyWidget', 'Proxy Server Settings'))
346+ self.no_proxy_radio.setText(translate('OpenLP.ProxyWidget', 'No prox&y'))
347+ self.use_sysem_proxy_radio.setText(translate('OpenLP.ProxyWidget', '&Use system proxy'))
348+ self.manual_proxy_radio.setText(translate('OpenLP.ProxyWidget', '&Manual proxy configuration'))
349+ proxy_example = translate('OpenLP.ProxyWidget', 'e.g. proxy_server_address:port_no')
350+ self.layout.labelForField(self.http_edit).setText('HTTP:')
351+ self.http_edit.setPlaceholderText(proxy_example)
352+ self.layout.labelForField(self.https_edit).setText('HTTPS:')
353+ self.https_edit.setPlaceholderText(proxy_example)
354+ self.layout.labelForField(self.username_edit).setText('Username:')
355+ self.layout.labelForField(self.password_edit).setText('Password:')
356+
357+ def load(self):
358+ """
359+ Load the data from the settings to the widget.
360+ """
361+ settings = Settings()
362+ checked_radio = self.radio_group.button(settings.value('advanced/proxy mode'))
363+ checked_radio.setChecked(True)
364+ self.http_edit.setText(settings.value('advanced/proxy http'))
365+ self.https_edit.setText(settings.value('advanced/proxy https'))
366+ self.username_edit.setText(settings.value('advanced/proxy username'))
367+ self.password_edit.setText(settings.value('advanced/proxy password'))
368+
369+ def save(self):
370+ """
371+ Save the widget data to the settings
372+ """
373+ settings = Settings() # TODO: Migrate from old system
374+ settings.setValue('advanced/proxy mode', self.radio_group.checkedId())
375+ settings.setValue('advanced/proxy http', self.http_edit.text())
376+ settings.setValue('advanced/proxy https', self.https_edit.text())
377+ settings.setValue('advanced/proxy username', self.username_edit.text())
378+ settings.setValue('advanced/proxy password', self.password_edit.text())
379
380=== modified file 'scripts/lp-merge.py'
381--- scripts/lp-merge.py 2018-05-04 21:14:04 +0000
382+++ scripts/lp-merge.py 2018-06-08 20:55:54 +0000
383@@ -104,7 +104,7 @@
384 # Find the p tag that contains the commit message
385 # <div id="commit-message">...<div id="edit-commit_message">...<div class="yui3-editable_text-text"><p>
386 commit_message = soup.find('div', id='commit-message').find('div', id='edit-commit_message')\
387- .find('div', 'yui3-editable_text-text').p
388+ .find('div', 'yui3-editable_text-text').p
389 merge_info['commit_message'] = commit_message.string
390 # Find all tr-tags with this class. Makes it possible to get bug numbers.
391 # <tr class="bug-branch-summary"
392
393=== modified file 'tests/functional/openlp_core/common/test_httputils.py'
394--- tests/functional/openlp_core/common/test_httputils.py 2018-01-04 06:10:20 +0000
395+++ tests/functional/openlp_core/common/test_httputils.py 2018-06-08 20:55:54 +0000
396@@ -27,13 +27,14 @@
397 from unittest import TestCase
398 from unittest.mock import MagicMock, patch
399
400-from openlp.core.common.httputils import get_user_agent, get_web_page, get_url_file_size, download_file
401+from openlp.core.common.httputils import ProxyMode, download_file, get_proxy_settings, get_url_file_size, \
402+ get_user_agent, get_web_page
403 from openlp.core.common.path import Path
404+from openlp.core.common.settings import Settings
405 from tests.helpers.testmixin import TestMixin
406
407
408 class TestHttpUtils(TestCase, TestMixin):
409-
410 """
411 A test suite to test out various http helper functions.
412 """
413@@ -240,3 +241,119 @@
414 # THEN: socket.timeout should have been caught
415 # NOTE: Test is if $tmpdir/tempfile is still there, then test fails since ftw deletes bad downloaded files
416 assert os.path.exists(self.tempfile) is False, 'tempfile should have been deleted'
417+
418+
419+class TestGetProxySettings(TestCase, TestMixin):
420+ def setUp(self):
421+ self.build_settings()
422+ self.addCleanup(self.destroy_settings)
423+
424+ @patch('openlp.core.common.httputils.Settings')
425+ def test_mode_arg_specified(self, MockSettings):
426+ """
427+ Test that the argument is used rather than reading the 'advanced/proxy mode' setting
428+ """
429+ # GIVEN: Mocked settings
430+ mocked_settings = MagicMock()
431+ MockSettings.return_value = mocked_settings
432+
433+ # WHEN: Calling `get_proxy_settings` with the mode arg specified
434+ get_proxy_settings(mode=ProxyMode.NO_PROXY)
435+
436+ # THEN: The mode arg should have been used rather than looking it up in the settings
437+ mocked_settings.value.assert_not_called()
438+
439+ @patch('openlp.core.common.httputils.Settings')
440+ def test_mode_incorrect_arg_specified(self, MockSettings):
441+ """
442+ Test that the system settings are used when the mode arg specieied is invalid
443+ """
444+ # GIVEN: Mocked settings
445+ mocked_settings = MagicMock()
446+ MockSettings.return_value = mocked_settings
447+
448+ # WHEN: Calling `get_proxy_settings` with an invalid mode arg specified
449+ result = get_proxy_settings(mode='qwerty')
450+
451+ # THEN: An None should be returned
452+ mocked_settings.value.assert_not_called()
453+ assert result is None
454+
455+ def test_no_proxy_mode(self):
456+ """
457+ Test that a dictionary with http and https values are set to None is returned, when `NO_PROXY` mode is specified
458+ """
459+ # GIVEN: A `proxy mode` setting of NO_PROXY
460+ Settings().setValue('advanced/proxy mode', ProxyMode.NO_PROXY)
461+
462+ # WHEN: Calling `get_proxy_settings`
463+ result = get_proxy_settings()
464+
465+ # THEN: The returned value should be a dictionary with http and https values set to None
466+ assert result == {'http': None, 'https': None}
467+
468+ def test_system_proxy_mode(self):
469+ """
470+ Test that None is returned, when `SYSTEM_PROXY` mode is specified
471+ """
472+ # GIVEN: A `proxy mode` setting of SYSTEM_PROXY
473+ Settings().setValue('advanced/proxy mode', ProxyMode.SYSTEM_PROXY)
474+
475+ # WHEN: Calling `get_proxy_settings`
476+ result = get_proxy_settings()
477+
478+ # THEN: The returned value should be None
479+ assert result is None
480+
481+ def test_manual_proxy_mode_no_auth(self):
482+ """
483+ Test that the correct proxy addresses are returned when basic authentication is not used
484+ """
485+ # GIVEN: A `proxy mode` setting of MANUAL_PROXY with proxy servers, but no auth credentials are supplied
486+ Settings().setValue('advanced/proxy mode', ProxyMode.MANUAL_PROXY)
487+ Settings().setValue('advanced/proxy http', 'testhttp.server:port')
488+ Settings().setValue('advanced/proxy https', 'testhttps.server:port')
489+ Settings().setValue('advanced/proxy username', '')
490+ Settings().setValue('advanced/proxy password', '')
491+
492+ # WHEN: Calling `get_proxy_settings`
493+ result = get_proxy_settings()
494+
495+ # THEN: The returned value should be the proxy servers without authentication
496+ assert result == {'http': 'http://testhttp.server:port', 'https': 'https://testhttps.server:port'}
497+
498+ def test_manual_proxy_mode_auth(self):
499+ """
500+ Test that the correct proxy addresses are returned when basic authentication is used
501+ """
502+ # GIVEN: A `proxy mode` setting of MANUAL_PROXY with proxy servers and auth credentials supplied
503+ Settings().setValue('advanced/proxy mode', ProxyMode.MANUAL_PROXY)
504+ Settings().setValue('advanced/proxy http', 'testhttp.server:port')
505+ Settings().setValue('advanced/proxy https', 'testhttps.server:port')
506+ Settings().setValue('advanced/proxy username', 'user')
507+ Settings().setValue('advanced/proxy password', 'pass')
508+
509+ # WHEN: Calling `get_proxy_settings`
510+ result = get_proxy_settings()
511+
512+ # THEN: The returned value should be the proxy servers with the authentication credentials
513+ assert result == {'http': 'http://user:pass@testhttp.server:port',
514+ 'https': 'https://user:pass@testhttps.server:port'}
515+
516+ def test_manual_proxy_mode_no_servers(self):
517+ """
518+ Test that the system proxies are overidden when the MANUAL_PROXY mode is specified, but no server addresses are
519+ supplied
520+ """
521+ # GIVEN: A `proxy mode` setting of MANUAL_PROXY with no servers specified
522+ Settings().setValue('advanced/proxy mode', ProxyMode.MANUAL_PROXY)
523+ Settings().setValue('advanced/proxy http', '')
524+ Settings().setValue('advanced/proxy https', '')
525+ Settings().setValue('advanced/proxy username', 'user')
526+ Settings().setValue('advanced/proxy password', 'pass')
527+
528+ # WHEN: Calling `get_proxy_settings`
529+ result = get_proxy_settings()
530+
531+ # THEN: The returned value should be the proxy servers set to None
532+ assert result == {'http': None, 'https': None}
533
534=== added file 'tests/interfaces/openlp_core/widgets/test_widgets.py'
535--- tests/interfaces/openlp_core/widgets/test_widgets.py 1970-01-01 00:00:00 +0000
536+++ tests/interfaces/openlp_core/widgets/test_widgets.py 2018-06-08 20:55:54 +0000
537@@ -0,0 +1,155 @@
538+# -*- coding: utf-8 -*-
539+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
540+
541+###############################################################################
542+# OpenLP - Open Source Lyrics Projection #
543+# --------------------------------------------------------------------------- #
544+# Copyright (c) 2008-2018 OpenLP Developers #
545+# --------------------------------------------------------------------------- #
546+# This program is free software; you can redistribute it and/or modify it #
547+# under the terms of the GNU General Public License as published by the Free #
548+# Software Foundation; version 2 of the License. #
549+# #
550+# This program is distributed in the hope that it will be useful, but WITHOUT #
551+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
552+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
553+# more details. #
554+# #
555+# You should have received a copy of the GNU General Public License along #
556+# with this program; if not, write to the Free Software Foundation, Inc., 59 #
557+# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
558+###############################################################################
559+"""
560+Module to test the custom widgets.
561+"""
562+from unittest import TestCase
563+from unittest.mock import MagicMock, call, patch
564+
565+from openlp.core.common.registry import Registry
566+from openlp.core.common.settings import ProxyMode
567+from openlp.core.widgets.widgets import ProxyWidget
568+from tests.helpers.testmixin import TestMixin
569+
570+
571+class TestProxyWidget(TestCase, TestMixin):
572+ """
573+ Test the EditCustomForm.
574+ """
575+ def setUp(self):
576+ """
577+ Create the UI
578+ """
579+ Registry.create()
580+ self.setup_application()
581+
582+ def test_radio_button_exclusivity(self):
583+ """
584+ Test that only one radio button can be checked at a time, and that the line edits are only enabled when the
585+ `manual_proxy_radio` is checked
586+ """
587+ # GIVEN: An instance of the `openlp.core.common.widgets.widgets.ProxyWidget`
588+ proxy_widget = ProxyWidget()
589+
590+ # WHEN: 'Checking' the `no_proxy_radio` button
591+ proxy_widget.no_proxy_radio.setChecked(True)
592+
593+ # THEN: The other radio buttons should not be checked and the line edits should not be enabled
594+ assert proxy_widget.use_sysem_proxy_radio.isChecked() is False
595+ assert proxy_widget.manual_proxy_radio.isChecked() is False
596+ assert proxy_widget.http_edit.isEnabled() is False
597+ assert proxy_widget.https_edit.isEnabled() is False
598+ assert proxy_widget.username_edit.isEnabled() is False
599+ assert proxy_widget.password_edit.isEnabled() is False
600+
601+ # WHEN: 'Checking' the `use_sysem_proxy_radio` button
602+ proxy_widget.use_sysem_proxy_radio.setChecked(True)
603+
604+ # THEN: The other radio buttons should not be checked and the line edits should not be enabled
605+ assert proxy_widget.no_proxy_radio.isChecked() is False
606+ assert proxy_widget.manual_proxy_radio.isChecked() is False
607+ assert proxy_widget.http_edit.isEnabled() is False
608+ assert proxy_widget.https_edit.isEnabled() is False
609+ assert proxy_widget.username_edit.isEnabled() is False
610+ assert proxy_widget.password_edit.isEnabled() is False
611+
612+ # WHEN: 'Checking' the `manual_proxy_radio` button
613+ proxy_widget.manual_proxy_radio.setChecked(True)
614+
615+ # THEN: The other radio buttons should not be checked and the line edits should be enabled
616+ assert proxy_widget.no_proxy_radio.isChecked() is False
617+ assert proxy_widget.use_sysem_proxy_radio.isChecked() is False
618+ assert proxy_widget.http_edit.isEnabled() is True
619+ assert proxy_widget.https_edit.isEnabled() is True
620+ assert proxy_widget.username_edit.isEnabled() is True
621+ assert proxy_widget.password_edit.isEnabled() is True
622+
623+ def test_proxy_widget_load_default_settings(self):
624+ """
625+ Test that the default settings are loaded from the config correctly
626+ """
627+ # GIVEN: And instance of the widget with default settings
628+ proxy_widget = ProxyWidget()
629+
630+ # WHEN: Calling the `load` method
631+ proxy_widget.load()
632+
633+ # THEN: The widget should be in its default state
634+ assert proxy_widget.use_sysem_proxy_radio.isChecked() is True
635+ assert proxy_widget.http_edit.text() == ''
636+ assert proxy_widget.https_edit.text() == ''
637+ assert proxy_widget.username_edit.text() == ''
638+ assert proxy_widget.password_edit.text() == ''
639+
640+ @patch.object(ProxyWidget, 'load')
641+ @patch('openlp.core.widgets.widgets.Settings')
642+ def test_proxy_widget_save_no_proxy_settings(self, settings_patcher, proxy_widget_load_patcher):
643+ """
644+ Test that the settings are saved correctly
645+ """
646+ # GIVEN: A Mocked settings instance of the proxy widget with some known values set
647+ settings_instance = MagicMock()
648+ settings_patcher.return_value = settings_instance
649+ proxy_widget = ProxyWidget()
650+ proxy_widget.no_proxy_radio.setChecked(True)
651+ proxy_widget.http_edit.setText('')
652+ proxy_widget.https_edit.setText('')
653+ proxy_widget.username_edit.setText('')
654+ proxy_widget.password_edit.setText('')
655+
656+ # WHEN: Calling save
657+ proxy_widget.save()
658+
659+ # THEN: The settings should be set as expected
660+ settings_instance.setValue.assert_has_calls(
661+ [call('advanced/proxy mode', ProxyMode.NO_PROXY),
662+ call('advanced/proxy http', ''),
663+ call('advanced/proxy https', ''),
664+ call('advanced/proxy username', ''),
665+ call('advanced/proxy password', '')])
666+
667+ @patch.object(ProxyWidget, 'load')
668+ @patch('openlp.core.widgets.widgets.Settings')
669+ def test_proxy_widget_save_manual_settings(self, settings_patcher, proxy_widget_load_patcher):
670+ """
671+ Test that the settings are saved correctly
672+ """
673+ # GIVEN: A Mocked and instance of the proxy widget with some known values set
674+ settings_instance = MagicMock()
675+ settings_patcher.return_value = settings_instance
676+ proxy_widget = ProxyWidget()
677+ proxy_widget.manual_proxy_radio.setChecked(True)
678+ proxy_widget.http_edit.setText('http_proxy_server:port')
679+ proxy_widget.https_edit.setText('https_proxy_server:port')
680+ proxy_widget.username_edit.setText('username')
681+ proxy_widget.password_edit.setText('password')
682+
683+ # WHEN: Calling save
684+ proxy_widget.save()
685+
686+ # THEN: The settings should be set as expected
687+ settings_instance.setValue.assert_has_calls(
688+ [call('advanced/proxy mode', ProxyMode.MANUAL_PROXY),
689+ call('advanced/proxy http', 'http_proxy_server:port'),
690+ call('advanced/proxy https', 'https_proxy_server:port'),
691+ call('advanced/proxy username', 'username'),
692+ call('advanced/proxy password', 'password')])