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: 709 lines (+508/-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 (+132/-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 (+174/-0)
To merge this branch: bzr merge lp:~phill-ridout/openlp/proxies
Reviewer Review Type Date Requested Status
Raoul Snyman Needs Fixing
Tim Bentley Needs Fixing
Phill Pending
Review via email: mp+347720@code.launchpad.net

This proposal supersedes a proposal from 2018-06-08.

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

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 : Posted in a previous version of this proposal

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

review: Needs Fixing
Revision history for this message
Tim Bentley (trb143) wrote :

Actually 3 queries!

review: Needs Fixing
Revision history for this message
Raoul Snyman (raoul-snyman) wrote :

A couple things to fix.

review: Needs Fixing
lp:~phill-ridout/openlp/proxies updated
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-10 07:27:41 +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-10 07:27:41 +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-10 07:27:41 +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-10 07:27:41 +0000
244@@ -0,0 +1,132 @@
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+ def on_radio_group_button_toggled(self, button, checked):
323+ """
324+ Handles the toggled signal on the radio buttons. The signal is emitted twice if a radio butting being toggled on
325+ causes another radio button in the group to be toggled off.
326+
327+ En/Disables the `Manual Proxy` line edits depending on the currently selected radio button
328+
329+ :param QtWidgets.QRadioButton button: The button that has toggled
330+ :param bool checked: The buttons new state
331+ """
332+ id = self.radio_group.id(button) # The work around (see above comment)
333+ enable_manual_edits = id == ProxyMode.MANUAL_PROXY and checked
334+ self.http_edit.setEnabled(enable_manual_edits)
335+ self.https_edit.setEnabled(enable_manual_edits)
336+ self.username_edit.setEnabled(enable_manual_edits)
337+ self.password_edit.setEnabled(enable_manual_edits)
338+
339+ def retranslate_ui(self):
340+ """
341+ Translate the Ui
342+ """
343+ self.setTitle(translate('OpenLP.ProxyWidget', 'Proxy Server Settings'))
344+ self.no_proxy_radio.setText(translate('OpenLP.ProxyWidget', 'No prox&y'))
345+ self.use_sysem_proxy_radio.setText(translate('OpenLP.ProxyWidget', '&Use system proxy'))
346+ self.manual_proxy_radio.setText(translate('OpenLP.ProxyWidget', '&Manual proxy configuration'))
347+ proxy_example = translate('OpenLP.ProxyWidget', 'e.g. proxy_server_address:port_no')
348+ self.layout.labelForField(self.http_edit).setText(translate('OpenLP.ProxyWidget', 'HTTP:'))
349+ self.http_edit.setPlaceholderText(proxy_example)
350+ self.layout.labelForField(self.https_edit).setText(translate('OpenLP.ProxyWidget', 'HTTPS:'))
351+ self.https_edit.setPlaceholderText(proxy_example)
352+ self.layout.labelForField(self.username_edit).setText(translate('OpenLP.ProxyWidget', 'Username:'))
353+ self.layout.labelForField(self.password_edit).setText(translate('OpenLP.ProxyWidget', 'Password:'))
354+
355+ def load(self):
356+ """
357+ Load the data from the settings to the widget.
358+ """
359+ settings = Settings()
360+ checked_radio = self.radio_group.button(settings.value('advanced/proxy mode'))
361+ checked_radio.setChecked(True)
362+ self.http_edit.setText(settings.value('advanced/proxy http'))
363+ self.https_edit.setText(settings.value('advanced/proxy https'))
364+ self.username_edit.setText(settings.value('advanced/proxy username'))
365+ self.password_edit.setText(settings.value('advanced/proxy password'))
366+
367+ def save(self):
368+ """
369+ Save the widget data to the settings
370+ """
371+ settings = Settings() # TODO: Migrate from old system
372+ settings.setValue('advanced/proxy mode', self.radio_group.checkedId())
373+ settings.setValue('advanced/proxy http', self.http_edit.text())
374+ settings.setValue('advanced/proxy https', self.https_edit.text())
375+ settings.setValue('advanced/proxy username', self.username_edit.text())
376+ settings.setValue('advanced/proxy password', self.password_edit.text())
377
378=== modified file 'scripts/lp-merge.py'
379--- scripts/lp-merge.py 2018-05-04 21:14:04 +0000
380+++ scripts/lp-merge.py 2018-06-10 07:27:41 +0000
381@@ -104,7 +104,7 @@
382 # Find the p tag that contains the commit message
383 # <div id="commit-message">...<div id="edit-commit_message">...<div class="yui3-editable_text-text"><p>
384 commit_message = soup.find('div', id='commit-message').find('div', id='edit-commit_message')\
385- .find('div', 'yui3-editable_text-text').p
386+ .find('div', 'yui3-editable_text-text').p
387 merge_info['commit_message'] = commit_message.string
388 # Find all tr-tags with this class. Makes it possible to get bug numbers.
389 # <tr class="bug-branch-summary"
390
391=== modified file 'tests/functional/openlp_core/common/test_httputils.py'
392--- tests/functional/openlp_core/common/test_httputils.py 2018-01-04 06:10:20 +0000
393+++ tests/functional/openlp_core/common/test_httputils.py 2018-06-10 07:27:41 +0000
394@@ -27,13 +27,14 @@
395 from unittest import TestCase
396 from unittest.mock import MagicMock, patch
397
398-from openlp.core.common.httputils import get_user_agent, get_web_page, get_url_file_size, download_file
399+from openlp.core.common.httputils import ProxyMode, download_file, get_proxy_settings, get_url_file_size, \
400+ get_user_agent, get_web_page
401 from openlp.core.common.path import Path
402+from openlp.core.common.settings import Settings
403 from tests.helpers.testmixin import TestMixin
404
405
406 class TestHttpUtils(TestCase, TestMixin):
407-
408 """
409 A test suite to test out various http helper functions.
410 """
411@@ -240,3 +241,119 @@
412 # THEN: socket.timeout should have been caught
413 # NOTE: Test is if $tmpdir/tempfile is still there, then test fails since ftw deletes bad downloaded files
414 assert os.path.exists(self.tempfile) is False, 'tempfile should have been deleted'
415+
416+
417+class TestGetProxySettings(TestCase, TestMixin):
418+ def setUp(self):
419+ self.build_settings()
420+ self.addCleanup(self.destroy_settings)
421+
422+ @patch('openlp.core.common.httputils.Settings')
423+ def test_mode_arg_specified(self, MockSettings):
424+ """
425+ Test that the argument is used rather than reading the 'advanced/proxy mode' setting
426+ """
427+ # GIVEN: Mocked settings
428+ mocked_settings = MagicMock()
429+ MockSettings.return_value = mocked_settings
430+
431+ # WHEN: Calling `get_proxy_settings` with the mode arg specified
432+ get_proxy_settings(mode=ProxyMode.NO_PROXY)
433+
434+ # THEN: The mode arg should have been used rather than looking it up in the settings
435+ mocked_settings.value.assert_not_called()
436+
437+ @patch('openlp.core.common.httputils.Settings')
438+ def test_mode_incorrect_arg_specified(self, MockSettings):
439+ """
440+ Test that the system settings are used when the mode arg specieied is invalid
441+ """
442+ # GIVEN: Mocked settings
443+ mocked_settings = MagicMock()
444+ MockSettings.return_value = mocked_settings
445+
446+ # WHEN: Calling `get_proxy_settings` with an invalid mode arg specified
447+ result = get_proxy_settings(mode='qwerty')
448+
449+ # THEN: An None should be returned
450+ mocked_settings.value.assert_not_called()
451+ assert result is None
452+
453+ def test_no_proxy_mode(self):
454+ """
455+ Test that a dictionary with http and https values are set to None is returned, when `NO_PROXY` mode is specified
456+ """
457+ # GIVEN: A `proxy mode` setting of NO_PROXY
458+ Settings().setValue('advanced/proxy mode', ProxyMode.NO_PROXY)
459+
460+ # WHEN: Calling `get_proxy_settings`
461+ result = get_proxy_settings()
462+
463+ # THEN: The returned value should be a dictionary with http and https values set to None
464+ assert result == {'http': None, 'https': None}
465+
466+ def test_system_proxy_mode(self):
467+ """
468+ Test that None is returned, when `SYSTEM_PROXY` mode is specified
469+ """
470+ # GIVEN: A `proxy mode` setting of SYSTEM_PROXY
471+ Settings().setValue('advanced/proxy mode', ProxyMode.SYSTEM_PROXY)
472+
473+ # WHEN: Calling `get_proxy_settings`
474+ result = get_proxy_settings()
475+
476+ # THEN: The returned value should be None
477+ assert result is None
478+
479+ def test_manual_proxy_mode_no_auth(self):
480+ """
481+ Test that the correct proxy addresses are returned when basic authentication is not used
482+ """
483+ # GIVEN: A `proxy mode` setting of MANUAL_PROXY with proxy servers, but no auth credentials are supplied
484+ Settings().setValue('advanced/proxy mode', ProxyMode.MANUAL_PROXY)
485+ Settings().setValue('advanced/proxy http', 'testhttp.server:port')
486+ Settings().setValue('advanced/proxy https', 'testhttps.server:port')
487+ Settings().setValue('advanced/proxy username', '')
488+ Settings().setValue('advanced/proxy password', '')
489+
490+ # WHEN: Calling `get_proxy_settings`
491+ result = get_proxy_settings()
492+
493+ # THEN: The returned value should be the proxy servers without authentication
494+ assert result == {'http': 'http://testhttp.server:port', 'https': 'https://testhttps.server:port'}
495+
496+ def test_manual_proxy_mode_auth(self):
497+ """
498+ Test that the correct proxy addresses are returned when basic authentication is used
499+ """
500+ # GIVEN: A `proxy mode` setting of MANUAL_PROXY with proxy servers and auth credentials supplied
501+ Settings().setValue('advanced/proxy mode', ProxyMode.MANUAL_PROXY)
502+ Settings().setValue('advanced/proxy http', 'testhttp.server:port')
503+ Settings().setValue('advanced/proxy https', 'testhttps.server:port')
504+ Settings().setValue('advanced/proxy username', 'user')
505+ Settings().setValue('advanced/proxy password', 'pass')
506+
507+ # WHEN: Calling `get_proxy_settings`
508+ result = get_proxy_settings()
509+
510+ # THEN: The returned value should be the proxy servers with the authentication credentials
511+ assert result == {'http': 'http://user:pass@testhttp.server:port',
512+ 'https': 'https://user:pass@testhttps.server:port'}
513+
514+ def test_manual_proxy_mode_no_servers(self):
515+ """
516+ Test that the system proxies are overidden when the MANUAL_PROXY mode is specified, but no server addresses are
517+ supplied
518+ """
519+ # GIVEN: A `proxy mode` setting of MANUAL_PROXY with no servers specified
520+ Settings().setValue('advanced/proxy mode', ProxyMode.MANUAL_PROXY)
521+ Settings().setValue('advanced/proxy http', '')
522+ Settings().setValue('advanced/proxy https', '')
523+ Settings().setValue('advanced/proxy username', 'user')
524+ Settings().setValue('advanced/proxy password', 'pass')
525+
526+ # WHEN: Calling `get_proxy_settings`
527+ result = get_proxy_settings()
528+
529+ # THEN: The returned value should be the proxy servers set to None
530+ assert result == {'http': None, 'https': None}
531
532=== added file 'tests/interfaces/openlp_core/widgets/test_widgets.py'
533--- tests/interfaces/openlp_core/widgets/test_widgets.py 1970-01-01 00:00:00 +0000
534+++ tests/interfaces/openlp_core/widgets/test_widgets.py 2018-06-10 07:27:41 +0000
535@@ -0,0 +1,174 @@
536+# -*- coding: utf-8 -*-
537+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
538+
539+###############################################################################
540+# OpenLP - Open Source Lyrics Projection #
541+# --------------------------------------------------------------------------- #
542+# Copyright (c) 2008-2018 OpenLP Developers #
543+# --------------------------------------------------------------------------- #
544+# This program is free software; you can redistribute it and/or modify it #
545+# under the terms of the GNU General Public License as published by the Free #
546+# Software Foundation; version 2 of the License. #
547+# #
548+# This program is distributed in the hope that it will be useful, but WITHOUT #
549+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
550+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
551+# more details. #
552+# #
553+# You should have received a copy of the GNU General Public License along #
554+# with this program; if not, write to the Free Software Foundation, Inc., 59 #
555+# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
556+###############################################################################
557+"""
558+Module to test the custom widgets.
559+"""
560+from unittest import TestCase
561+from unittest.mock import MagicMock, call, patch
562+
563+from openlp.core.common.registry import Registry
564+from openlp.core.common.settings import ProxyMode
565+from openlp.core.widgets.widgets import ProxyWidget
566+from tests.helpers.testmixin import TestMixin
567+
568+
569+class TestProxyWidget(TestCase, TestMixin):
570+ """
571+ Test the EditCustomForm.
572+ """
573+ def setUp(self):
574+ """
575+ Create the UI
576+ """
577+ Registry.create()
578+ self.setup_application()
579+
580+ def test_radio_button_exclusivity_no_proxy(self):
581+ """
582+ Test that only one radio button can be checked at a time, and that the line edits are only enabled when the
583+ `manual_proxy_radio` is checked
584+ """
585+ # GIVEN: An instance of the `openlp.core.common.widgets.widgets.ProxyWidget` with a radio already checked
586+ proxy_widget = ProxyWidget()
587+ proxy_widget.manual_proxy_radio.setChecked(True)
588+
589+ # WHEN: 'Checking' the `no_proxy_radio` button
590+ proxy_widget.no_proxy_radio.setChecked(True)
591+
592+ # THEN: The other radio buttons should not be checked and the line edits should not be enabled
593+ assert proxy_widget.use_sysem_proxy_radio.isChecked() is False
594+ assert proxy_widget.manual_proxy_radio.isChecked() is False
595+ assert proxy_widget.http_edit.isEnabled() is False
596+ assert proxy_widget.https_edit.isEnabled() is False
597+ assert proxy_widget.username_edit.isEnabled() is False
598+ assert proxy_widget.password_edit.isEnabled() is False
599+
600+ def test_radio_button_exclusivity_system_proxy(self):
601+ """
602+ Test that only one radio button can be checked at a time, and that the line edits are only enabled when the
603+ `manual_proxy_radio` is checked
604+ """
605+ # GIVEN: An instance of the `openlp.core.common.widgets.widgets.ProxyWidget` with a radio already checked
606+ proxy_widget = ProxyWidget()
607+ proxy_widget.manual_proxy_radio.setChecked(True)
608+
609+ # WHEN: 'Checking' the `use_sysem_proxy_radio` button
610+ proxy_widget.use_sysem_proxy_radio.setChecked(True)
611+
612+ # THEN: The other radio buttons should not be checked and the line edits should not be enabled
613+ assert proxy_widget.no_proxy_radio.isChecked() is False
614+ assert proxy_widget.manual_proxy_radio.isChecked() is False
615+ assert proxy_widget.http_edit.isEnabled() is False
616+ assert proxy_widget.https_edit.isEnabled() is False
617+ assert proxy_widget.username_edit.isEnabled() is False
618+ assert proxy_widget.password_edit.isEnabled() is False
619+
620+ def test_radio_button_exclusivity_manual_proxy(self):
621+ """
622+ Test that only one radio button can be checked at a time, and that the line edits are only enabled when the
623+ `manual_proxy_radio` is checked
624+ """
625+ # GIVEN: An instance of the `openlp.core.common.widgets.widgets.ProxyWidget` with a radio already checked
626+ proxy_widget = ProxyWidget()
627+ proxy_widget.no_proxy_radio.setChecked(True)
628+
629+ # WHEN: 'Checking' the `manual_proxy_radio` button
630+ proxy_widget.manual_proxy_radio.setChecked(True)
631+
632+ # THEN: The other radio buttons should not be checked and the line edits should be enabled
633+ assert proxy_widget.no_proxy_radio.isChecked() is False
634+ assert proxy_widget.use_sysem_proxy_radio.isChecked() is False
635+ assert proxy_widget.http_edit.isEnabled() is True
636+ assert proxy_widget.https_edit.isEnabled() is True
637+ assert proxy_widget.username_edit.isEnabled() is True
638+ assert proxy_widget.password_edit.isEnabled() is True
639+
640+ def test_proxy_widget_load_default_settings(self):
641+ """
642+ Test that the default settings are loaded from the config correctly
643+ """
644+ # GIVEN: And instance of the widget with default settings
645+ proxy_widget = ProxyWidget()
646+
647+ # WHEN: Calling the `load` method
648+ proxy_widget.load()
649+
650+ # THEN: The widget should be in its default state
651+ assert proxy_widget.use_sysem_proxy_radio.isChecked() is True
652+ assert proxy_widget.http_edit.text() == ''
653+ assert proxy_widget.https_edit.text() == ''
654+ assert proxy_widget.username_edit.text() == ''
655+ assert proxy_widget.password_edit.text() == ''
656+
657+ @patch.object(ProxyWidget, 'load')
658+ @patch('openlp.core.widgets.widgets.Settings')
659+ def test_proxy_widget_save_no_proxy_settings(self, settings_patcher, proxy_widget_load_patcher):
660+ """
661+ Test that the settings are saved correctly
662+ """
663+ # GIVEN: A Mocked settings instance of the proxy widget with some known values set
664+ settings_instance = MagicMock()
665+ settings_patcher.return_value = settings_instance
666+ proxy_widget = ProxyWidget()
667+ proxy_widget.no_proxy_radio.setChecked(True)
668+ proxy_widget.http_edit.setText('')
669+ proxy_widget.https_edit.setText('')
670+ proxy_widget.username_edit.setText('')
671+ proxy_widget.password_edit.setText('')
672+
673+ # WHEN: Calling save
674+ proxy_widget.save()
675+
676+ # THEN: The settings should be set as expected
677+ settings_instance.setValue.assert_has_calls(
678+ [call('advanced/proxy mode', ProxyMode.NO_PROXY),
679+ call('advanced/proxy http', ''),
680+ call('advanced/proxy https', ''),
681+ call('advanced/proxy username', ''),
682+ call('advanced/proxy password', '')])
683+
684+ @patch.object(ProxyWidget, 'load')
685+ @patch('openlp.core.widgets.widgets.Settings')
686+ def test_proxy_widget_save_manual_settings(self, settings_patcher, proxy_widget_load_patcher):
687+ """
688+ Test that the settings are saved correctly
689+ """
690+ # GIVEN: A Mocked and instance of the proxy widget with some known values set
691+ settings_instance = MagicMock()
692+ settings_patcher.return_value = settings_instance
693+ proxy_widget = ProxyWidget()
694+ proxy_widget.manual_proxy_radio.setChecked(True)
695+ proxy_widget.http_edit.setText('http_proxy_server:port')
696+ proxy_widget.https_edit.setText('https_proxy_server:port')
697+ proxy_widget.username_edit.setText('username')
698+ proxy_widget.password_edit.setText('password')
699+
700+ # WHEN: Calling save
701+ proxy_widget.save()
702+
703+ # THEN: The settings should be set as expected
704+ settings_instance.setValue.assert_has_calls(
705+ [call('advanced/proxy mode', ProxyMode.MANUAL_PROXY),
706+ call('advanced/proxy http', 'http_proxy_server:port'),
707+ call('advanced/proxy https', 'https_proxy_server:port'),
708+ call('advanced/proxy username', 'username'),
709+ call('advanced/proxy password', 'password')])