Merge lp:~alisonken1/openlp/pjlink2_v04 into lp:openlp
- pjlink2_v04
- Merge into trunk
Status: | Merged |
---|---|
Merged at revision: | 2865 |
Proposed branch: | lp:~alisonken1/openlp/pjlink2_v04 |
Merge into: | lp:openlp |
Diff against target: |
760 lines (+316/-166) 7 files modified
openlp/core/projectors/constants.py (+10/-1) openlp/core/projectors/pjlink.py (+48/-25) openlp/core/projectors/pjlinkcommands.py (+16/-26) tests/openlp_core/projectors/test_projector_command_routing.py (+132/-0) tests/openlp_core/projectors/test_projector_constants.py (+2/-1) tests/openlp_core/projectors/test_projector_pjlink_base_02.py (+15/-11) tests/openlp_core/projectors/test_projector_pjlink_cmd_routing.py (+93/-102) |
To merge this branch: | bzr merge lp:~alisonken1/openlp/pjlink2_v04 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Tim Bentley | Approve | ||
Phill | Approve | ||
Review via email: mp+366946@code.launchpad.net |
Commit message
PJLink2 update V04
Description of the change
NOTE: Part 4 of a multi-part merge.
v[1..n] merges are to fix tests
- Fixed/Added routing tests for PJLink.get_data() method
- Move process_command() routing tests to separate module
- Refactor/add process_command() tests
- Refactor process_pjlink() command and tests
- Fix PJLink socket state check causing loss of connection (regression)
- Added 3 new status codes for initial connection processing
-------
lp:~alisonken1/openlp/pjlink2_v04 (revision 2865)
https:/
https:/
https:/
https:/
https:/
https:/
https:/
https:/
All builds passed
Raoul Snyman (raoul-snyman) wrote : | # |
Raoul Snyman (raoul-snyman) wrote : | # |
Linting passed!
Raoul Snyman (raoul-snyman) wrote : | # |
macOS tests passed!
Tim Bentley (trb143) : | # |
Preview Diff
1 | === modified file 'openlp/core/projectors/constants.py' |
2 | --- openlp/core/projectors/constants.py 2019-04-13 13:00:22 +0000 |
3 | +++ openlp/core/projectors/constants.py 2019-05-04 05:43:42 +0000 |
4 | @@ -106,6 +106,9 @@ |
5 | S_ON = 315 |
6 | S_COOLDOWN = 316 |
7 | S_INFO = 317 |
8 | +S_CONNECT = 318 # Initial connection, connected |
9 | +S_AUTHENTICATE = 319 # Initial connection, send pin hash |
10 | +S_DATA_OK = 320 # Previous command returned OK |
11 | |
12 | # Information that does not affect status |
13 | S_NETWORK_IDLE = 400 |
14 | @@ -369,11 +372,14 @@ |
15 | E_UNKNOWN_SOCKET_ERROR: 'E_UNKNOWN_SOCKET_ERROR', |
16 | E_UNSUPPORTED_SOCKET_OPERATION: 'E_UNSUPPORTED_SOCKET_OPERATION', |
17 | E_WARN: 'E_WARN', |
18 | + S_AUTHENTICATE: 'S_AUTHENTICATE', |
19 | S_BOUND: 'S_BOUND', |
20 | + S_CONNECT: 'S_CONNECT', |
21 | S_COOLDOWN: 'S_COOLDOWN', |
22 | S_CLOSING: 'S_CLOSING', |
23 | S_CONNECTED: 'S_CONNECTED', |
24 | S_CONNECTING: 'S_CONNECTING', |
25 | + S_DATA_OK: 'S_DATA_OK', |
26 | S_HOST_LOOKUP: 'S_HOST_LOOKUP', |
27 | S_INFO: 'S_INFO', |
28 | S_INITIALIZE: 'S_INITIALIZE', |
29 | @@ -387,7 +393,7 @@ |
30 | S_ON: 'S_ON', |
31 | S_STANDBY: 'S_STANDBY', |
32 | S_STATUS: 'S_STATUS', |
33 | - S_WARMUP: 'S_WARMUP', |
34 | + S_WARMUP: 'S_WARMUP' |
35 | } |
36 | |
37 | # Map status codes to message strings |
38 | @@ -459,11 +465,14 @@ |
39 | 'The requested socket operation is not supported by the local ' |
40 | 'operating system (e.g., lack of IPv6 support)'), |
41 | E_WARN: translate('OpenLP.ProjectorConstants', 'Warning condition detected'), |
42 | + S_AUTHENTICATE: translate('OpenLP.ProjectorConstants', 'Connection initializing with pin'), |
43 | S_BOUND: translate('OpenLP.ProjectorConstants', 'Socket is bount to an address or port'), |
44 | + S_CONNECT: translate('OpenLP.ProjectorConstants', 'Connection initializing'), |
45 | S_CLOSING: translate('OpenLP.ProjectorConstants', 'Socket is about to close'), |
46 | S_CONNECTED: translate('OpenLP.ProjectorConstants', 'Connected'), |
47 | S_CONNECTING: translate('OpenLP.ProjectorConstants', 'Connecting'), |
48 | S_COOLDOWN: translate('OpenLP.ProjectorConstants', 'Cooldown in progress'), |
49 | + S_DATA_OK: translate('OpenLP.ProjectorConstants', 'Command returned with OK'), |
50 | S_HOST_LOOKUP: translate('OpenLP.ProjectorConstants', 'Performing a host name lookup'), |
51 | S_INFO: translate('OpenLP.ProjectorConstants', 'Projector Information available'), |
52 | S_INITIALIZE: translate('OpenLP.ProjectorConstants', 'Initialize in progress'), |
53 | |
54 | === modified file 'openlp/core/projectors/pjlink.py' |
55 | --- openlp/core/projectors/pjlink.py 2019-04-28 19:21:23 +0000 |
56 | +++ openlp/core/projectors/pjlink.py 2019-05-04 05:43:42 +0000 |
57 | @@ -52,14 +52,15 @@ |
58 | |
59 | from PyQt5 import QtCore, QtNetwork |
60 | |
61 | +from openlp.core.common import qmd5_hash |
62 | from openlp.core.common.i18n import translate |
63 | from openlp.core.common.settings import Settings |
64 | from openlp.core.projectors.pjlinkcommands import process_command |
65 | -from openlp.core.projectors.constants import CONNECTION_ERRORS, E_CONNECTION_REFUSED, E_GENERAL, \ |
66 | +from openlp.core.projectors.constants import CONNECTION_ERRORS, E_AUTHENTICATION, E_CONNECTION_REFUSED, E_GENERAL, \ |
67 | E_NETWORK, E_NOT_CONNECTED, E_SOCKET_TIMEOUT, PJLINK_CLASS, \ |
68 | PJLINK_MAX_PACKET, PJLINK_PORT, PJLINK_PREFIX, PJLINK_SUFFIX, \ |
69 | - PJLINK_VALID_CMD, PROJECTOR_STATE, QSOCKET_STATE, S_CONNECTED, S_CONNECTING, S_NOT_CONNECTED, S_OFF, S_OK, S_ON, \ |
70 | - STATUS_CODE, STATUS_MSG |
71 | + PJLINK_VALID_CMD, PROJECTOR_STATE, QSOCKET_STATE, S_AUTHENTICATE, S_CONNECT, S_CONNECTED, S_CONNECTING, \ |
72 | + S_DATA_OK, S_NOT_CONNECTED, S_OFF, S_OK, S_ON, STATUS_CODE, STATUS_MSG |
73 | |
74 | |
75 | log = logging.getLogger(__name__) |
76 | @@ -565,21 +566,10 @@ |
77 | # V = PJLink class or version |
78 | # C = PJLink command |
79 | version, cmd = header[1], header[2:].upper() |
80 | - log.debug('({ip}) get_data() version="{version}" cmd="{cmd}"'.format(ip=self.entry.name, |
81 | - version=version, cmd=cmd)) |
82 | - # TODO: Below commented for now since it seems to cause issues with testing some invalid data. |
83 | - # Revisit after more refactoring is finished. |
84 | - ''' |
85 | - try: |
86 | - version, cmd = header[1], header[2:].upper() |
87 | - log.debug('({ip}) get_data() version="{version}" cmd="{cmd}"'.format(ip=self.entry.name, |
88 | - version=version, cmd=cmd)) |
89 | - except ValueError as e: |
90 | - self.change_status(E_INVALID_DATA) |
91 | - log.warning('({ip}) get_data(): Received data: "{data}"'.format(ip=self.entry.name, data=data_in)) |
92 | - self._trash_buffer('get_data(): Expected header + command + data') |
93 | - return self.receive_data_signal() |
94 | - ''' |
95 | + log.debug('({ip}) get_data() version="{version}" cmd="{cmd}" data="{data}"'.format(ip=self.entry.name, |
96 | + version=version, |
97 | + cmd=cmd, |
98 | + data=data)) |
99 | if cmd not in PJLINK_VALID_CMD: |
100 | self._trash_buffer('get_data(): Invalid packet - unknown command "{data}"'.format(data=cmd)) |
101 | return self.receive_data_signal() |
102 | @@ -590,7 +580,38 @@ |
103 | if not ignore_class: |
104 | log.warning('({ip}) get_data(): Projector returned class reply higher ' |
105 | 'than projector stated class'.format(ip=self.entry.name)) |
106 | - process_command(self, cmd, data) |
107 | + return self.receive_data_signal() |
108 | + |
109 | + chk = process_command(self, cmd, data) |
110 | + if chk is None: |
111 | + # Command processed normally and not initial connection, so skip other checks |
112 | + return self.receive_data_signal() |
113 | + # PJLink initial connection checks |
114 | + elif chk == S_DATA_OK: |
115 | + # Previous command returned OK |
116 | + log.debug('({ip}) OK returned - resending command'.format(ip=self.entry.name)) |
117 | + self.send_command(cmd=cmd, priority=True) |
118 | + elif chk == S_CONNECT: |
119 | + # Normal connection |
120 | + log.debug('({ip}) Connecting normal'.format(ip=self.entry.name)) |
121 | + self.change_status(S_CONNECTED) |
122 | + self.send_command(cmd='CLSS', priority=True) |
123 | + self.readyRead.connect(self.get_socket) |
124 | + elif chk == S_AUTHENTICATE: |
125 | + # Connection with pin |
126 | + log.debug('({ip}) Connecting with pin'.format(ip=self.entry.name)) |
127 | + data_hash = str(qmd5_hash(salt=chk[1].encode('utf-8'), data=self.pin.encode('utf-8')), |
128 | + encoding='ascii') |
129 | + self.change_status(S_CONNECTED) |
130 | + self.readyRead.connect(self.get_socket) |
131 | + self.send_command(cmd='CLSS', salt=data_hash, priority=True) |
132 | + elif chk == E_AUTHENTICATION: |
133 | + # Projector did not like our pin |
134 | + log.warning('({ip}) Failed authentication - disconnecting'.format(ip=self.entry.name)) |
135 | + self.disconnect_from_host() |
136 | + self.projectorAuthentication.emit(self.entry.name) |
137 | + self.change_status(status=E_AUTHENTICATION) |
138 | + |
139 | return self.receive_data_signal() |
140 | |
141 | @QtCore.pyqtSlot(QtNetwork.QAbstractSocket.SocketError) |
142 | @@ -631,7 +652,9 @@ |
143 | :param salt: Optional salt for md5 hash initial authentication |
144 | :param priority: Option to send packet now rather than queue it up |
145 | """ |
146 | - if QSOCKET_STATE[self.state()] != QSOCKET_STATE[S_CONNECTED]: |
147 | + log.debug('({ip}) send_command(cmd="{cmd}" opts="{opts}" salt="{salt}" ' |
148 | + 'priority={pri}'.format(ip=self.entry.name, cmd=cmd, opts=opts, salt=salt, pri=priority)) |
149 | + if QSOCKET_STATE[self.state()] != S_CONNECTED: |
150 | log.warning('({ip}) send_command(): Not connected - returning'.format(ip=self.entry.name)) |
151 | return self.reset_information() |
152 | if cmd not in PJLINK_VALID_CMD: |
153 | @@ -640,11 +663,11 @@ |
154 | # Just in case there's already something to send |
155 | return self._send_command() |
156 | return |
157 | - log.debug('({ip}) send_command(): Building cmd="{command}" opts="{data}"{salt}'.format(ip=self.entry.name, |
158 | - command=cmd, |
159 | - data=opts, |
160 | - salt='' if salt is None |
161 | - else ' with hash')) |
162 | + log.debug('({ip}) send_command(): Building cmd="{command}" opts="{data}" ' |
163 | + '{salt}'.format(ip=self.entry.name, |
164 | + command=cmd, |
165 | + data=opts, |
166 | + salt='' if salt is None else 'with hash')) |
167 | # Until we absolutely have to start doing version checks, use the default |
168 | # for PJLink class |
169 | header = PJLINK_HEADER.format(linkclass=PJLINK_VALID_CMD[cmd]['default']) |
170 | |
171 | === modified file 'openlp/core/projectors/pjlinkcommands.py' |
172 | --- openlp/core/projectors/pjlinkcommands.py 2019-04-28 19:22:52 +0000 |
173 | +++ openlp/core/projectors/pjlinkcommands.py 2019-05-04 05:43:42 +0000 |
174 | @@ -30,14 +30,12 @@ |
175 | import logging |
176 | import re |
177 | |
178 | -from openlp.core.common import qmd5_hash |
179 | - |
180 | from openlp.core.common.i18n import translate |
181 | from openlp.core.common.settings import Settings |
182 | |
183 | from openlp.core.projectors.constants import E_AUTHENTICATION, PJLINK_DEFAULT_CODES, PJLINK_ERRORS, \ |
184 | - PJLINK_ERST_DATA, PJLINK_ERST_STATUS, PJLINK_POWR_STATUS, S_CONNECTED, S_OFF, S_OK, S_ON, S_STANDBY, \ |
185 | - STATUS_MSG |
186 | + PJLINK_ERST_DATA, PJLINK_ERST_STATUS, PJLINK_POWR_STATUS, S_AUTHENTICATE, S_CONNECT, S_DATA_OK, S_OFF, S_OK, S_ON, \ |
187 | + S_STANDBY, STATUS_MSG |
188 | |
189 | log = logging.getLogger(__name__) |
190 | log.debug('Loading pjlinkcommands') |
191 | @@ -68,19 +66,18 @@ |
192 | elif _data == 'OK': |
193 | log.debug('({ip}) Command "{cmd}" returned OK'.format(ip=projector.entry.name, cmd=cmd)) |
194 | # A command returned successfully, so do a query on command to verify status |
195 | - return projector.send_command(cmd=cmd, priority=True) |
196 | + return S_DATA_OK |
197 | + |
198 | elif _data in PJLINK_ERRORS: |
199 | # Oops - projector error |
200 | log.error('({ip}) {cmd}: {err}'.format(ip=projector.entry.name, |
201 | cmd=cmd, |
202 | err=STATUS_MSG[PJLINK_ERRORS[_data]])) |
203 | - if PJLINK_ERRORS[_data] == E_AUTHENTICATION: |
204 | - projector.disconnect_from_host() |
205 | - projector.projectorAuthentication.emit(projector.name) |
206 | - return projector.change_status(status=E_AUTHENTICATION) |
207 | + return PJLINK_ERRORS[_data] |
208 | + |
209 | # Command checks already passed |
210 | log.debug('({ip}) Calling function for {cmd}'.format(ip=projector.entry.name, cmd=cmd)) |
211 | - pjlink_functions[cmd](projector=projector, data=data) |
212 | + return pjlink_functions[cmd](projector=projector, data=data) |
213 | |
214 | |
215 | def process_ackn(projector, data): |
216 | @@ -376,35 +373,28 @@ |
217 | if len(chk[0]) != 1: |
218 | # Invalid - after splitting, first field should be 1 character, either '0' or '1' only |
219 | log.error('({ip}) Invalid initial authentication scheme - aborting'.format(ip=projector.entry.name)) |
220 | - return projector.disconnect_from_host() |
221 | + return E_AUTHENTICATION |
222 | elif chk[0] == '0': |
223 | # Normal connection no authentication |
224 | if len(chk) > 1: |
225 | # Invalid data - there should be nothing after a normal authentication scheme |
226 | log.error('({ip}) Normal connection with extra information - aborting'.format(ip=projector.entry.name)) |
227 | - return projector.disconnect_from_host() |
228 | + return E_AUTHENTICATION |
229 | elif projector.pin: |
230 | log.error('({ip}) Normal connection but PIN set - aborting'.format(ip=projector.entry.name)) |
231 | - return projector.disconnect_from_host() |
232 | - else: |
233 | - data_hash = None |
234 | + return E_AUTHENTICATION |
235 | + log.debug('({ip}) PJLINK: Returning S_CONNECT'.format(ip=projector.entry.name)) |
236 | + return S_CONNECT |
237 | elif chk[0] == '1': |
238 | if len(chk) < 2: |
239 | # Not enough information for authenticated connection |
240 | log.error('({ip}) Authenticated connection but not enough info - aborting'.format(ip=projector.entry.name)) |
241 | - return projector.disconnect_from_host() |
242 | + return E_AUTHENTICATION |
243 | elif not projector.pin: |
244 | log.error('({ip}) Authenticate connection but no PIN - aborting'.format(ip=projector.entry.name)) |
245 | - return projector.disconnect_from_host() |
246 | - else: |
247 | - data_hash = str(qmd5_hash(salt=chk[1].encode('utf-8'), data=projector.pin.encode('utf-8')), |
248 | - encoding='ascii') |
249 | - # Passed basic checks, so start connection |
250 | - projector.readyRead.connect(projector.get_socket) |
251 | - projector.change_status(S_CONNECTED) |
252 | - log.debug('({ip}) process_pjlink(): Sending "CLSS" initial command'.format(ip=projector.entry.name)) |
253 | - # Since this is an initial connection, make it a priority just in case |
254 | - return projector.send_command(cmd="CLSS", salt=data_hash, priority=True) |
255 | + return E_AUTHENTICATION |
256 | + log.debug('({ip}) PJLINK: Returning S_AUTHENTICATE'.format(ip=projector.entry.name)) |
257 | + return S_AUTHENTICATE |
258 | |
259 | |
260 | def process_powr(projector, data): |
261 | |
262 | === added file 'tests/openlp_core/projectors/test_projector_command_routing.py' |
263 | --- tests/openlp_core/projectors/test_projector_command_routing.py 1970-01-01 00:00:00 +0000 |
264 | +++ tests/openlp_core/projectors/test_projector_command_routing.py 2019-05-04 05:43:42 +0000 |
265 | @@ -0,0 +1,132 @@ |
266 | +# -*- coding: utf-8 -*- |
267 | +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 |
268 | + |
269 | +########################################################################## |
270 | +# OpenLP - Open Source Lyrics Projection # |
271 | +# ---------------------------------------------------------------------- # |
272 | +# Copyright (c) 2008-2019 OpenLP Developers # |
273 | +# ---------------------------------------------------------------------- # |
274 | +# This program is free software: you can redistribute it and/or modify # |
275 | +# it under the terms of the GNU General Public License as published by # |
276 | +# the Free Software Foundation, either version 3 of the License, or # |
277 | +# (at your option) any later version. # |
278 | +# # |
279 | +# This program is distributed in the hope that it will be useful, # |
280 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of # |
281 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # |
282 | +# GNU General Public License for more details. # |
283 | +# # |
284 | +# You should have received a copy of the GNU General Public License # |
285 | +# along with this program. If not, see <https://www.gnu.org/licenses/>. # |
286 | +########################################################################## |
287 | +""" |
288 | +Package to test the openlp.core.projectors.pjlink command routing. |
289 | +""" |
290 | + |
291 | +from unittest import TestCase |
292 | +from unittest.mock import call, patch |
293 | + |
294 | +import openlp.core.projectors.pjlink |
295 | +from openlp.core.projectors.pjlinkcommands import process_command |
296 | +from openlp.core.projectors.constants import E_UNDEFINED, S_DATA_OK |
297 | +from openlp.core.projectors.db import Projector |
298 | +from openlp.core.projectors.pjlink import PJLink |
299 | +from tests.resources.projector.data import TEST1_DATA |
300 | + |
301 | + |
302 | +class TestPJLinkRouting(TestCase): |
303 | + """ |
304 | + Tests for the PJLink module command routing |
305 | + """ |
306 | + def setUp(self): |
307 | + """ |
308 | + Setup test environment |
309 | + """ |
310 | + self.pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True) |
311 | + |
312 | + def tearDown(self): |
313 | + """ |
314 | + Reset test environment |
315 | + """ |
316 | + del(self.pjlink) |
317 | + |
318 | + @patch.object(openlp.core.projectors.pjlinkcommands, 'log') |
319 | + def test_routing_command(self, mock_log): |
320 | + """ |
321 | + Test process_command receiving command not in function map |
322 | + """ |
323 | + # GIVEN: Test setup |
324 | + log_warning_text = [] |
325 | + log_debug_text = [call('({ip}) Processing command "CLSS" with data "1"'.format(ip=self.pjlink.name)), |
326 | + call('({ip}) Calling function for CLSS'.format(ip=self.pjlink.name)), |
327 | + call('({ip}) Setting pjlink_class for this projector to "1"'.format(ip=self.pjlink.name))] |
328 | + |
329 | + # WHEN: called with valid command and data |
330 | + chk = process_command(projector=self.pjlink, cmd='CLSS', data='1') |
331 | + |
332 | + # THEN: Appropriate log entries should have been made and methods called/not called |
333 | + mock_log.warning.assert_has_calls(log_warning_text) |
334 | + mock_log.debug.assert_has_calls(log_debug_text) |
335 | + assert (chk is None), 'process_clss() should have returned None' |
336 | + |
337 | + @patch.object(openlp.core.projectors.pjlinkcommands, 'process_clss') |
338 | + @patch.object(openlp.core.projectors.pjlinkcommands, 'pjlink_functions') |
339 | + @patch.object(openlp.core.projectors.pjlinkcommands, 'log') |
340 | + def test_routing_command_unknown(self, mock_log, mock_functions, mock_clss): |
341 | + """ |
342 | + Test process_command receiving command not in function map |
343 | + """ |
344 | + # GIVEN: Test setup |
345 | + log_warning_text = [call('({ip}) Unable to process command="CLSS" ' |
346 | + '(Future option?)'.format(ip=self.pjlink.name))] |
347 | + log_debug_text = [call('({ip}) Processing command "CLSS" with data "?"'.format(ip=self.pjlink.name))] |
348 | + # Fake CLSS command is not in list |
349 | + mock_functions.__contains__.return_value = False |
350 | + |
351 | + # WHEN: called with unknown command |
352 | + process_command(projector=self.pjlink, cmd='CLSS', data='?') |
353 | + |
354 | + # THEN: Appropriate log entries should have been made and methods called/not called |
355 | + mock_log.warning.assert_has_calls(log_warning_text) |
356 | + mock_log.debug.assert_has_calls(log_debug_text) |
357 | + assert (mock_functions.__contains__.call_count == 1), 'pjlink_functions should have been accessed only once' |
358 | + assert (not mock_clss.called), 'Should not have called process_clss' |
359 | + |
360 | + @patch.object(openlp.core.projectors.pjlink.PJLink, 'send_command') |
361 | + @patch.object(openlp.core.projectors.pjlinkcommands, 'process_clss') |
362 | + @patch.object(openlp.core.projectors.pjlinkcommands, 'log') |
363 | + def test_routing_data_ok(self, mock_log, mock_clss, mock_send): |
364 | + """ |
365 | + Test process_command calls function and sets appropriate value(s) in projector instance |
366 | + """ |
367 | + # GIVEN: Test setup |
368 | + log_debug_text = [call('({ip}) Processing command "CLSS" with data "OK"'.format(ip=self.pjlink.name)), |
369 | + call('({ip}) Command "CLSS" returned OK'.format(ip=self.pjlink.name)) |
370 | + ] |
371 | + |
372 | + # WHEN: Command called with OK |
373 | + chk = process_command(projector=self.pjlink, cmd='CLSS', data='OK') |
374 | + |
375 | + # THEN: Appropriate log entries should have been made and methods called/not called |
376 | + mock_log.debug.asset_has_calls(log_debug_text) |
377 | + mock_send.assert_not_called() |
378 | + mock_clss.assert_not_called() |
379 | + assert (chk == S_DATA_OK), 'Should have returned S_DATA_OK' |
380 | + |
381 | + @patch.object(openlp.core.projectors.pjlinkcommands, 'log') |
382 | + def test_routing_pjink_errors(self, mock_log): |
383 | + """ |
384 | + Test rouing when PJLink error received (err1, err2, err3, err4, erra) |
385 | + """ |
386 | + # GIVEN: Test setup |
387 | + log_error_text = [call('({ip}) CLSS: PJLink returned "ERR1: Undefined Command"'.format(ip=self.pjlink.name))] |
388 | + log_debug_text = [call('({ip}) Processing command "CLSS" with data "ERR1"'.format(ip=self.pjlink.name))] |
389 | + err_code = E_UNDEFINED |
390 | + |
391 | + # WHEN: routing called |
392 | + chk = process_command(projector=self.pjlink, cmd='CLSS', data='ERR1') |
393 | + |
394 | + # THEN: Appropriate log entries should have been made and methods called/not called |
395 | + mock_log.error.assert_has_calls(log_error_text) |
396 | + mock_log.debug.assert_has_calls(log_debug_text) |
397 | + assert (chk == err_code), 'Should have returned E_UNDEFINED' |
398 | |
399 | === modified file 'tests/openlp_core/projectors/test_projector_constants.py' |
400 | --- tests/openlp_core/projectors/test_projector_constants.py 2019-04-13 13:00:22 +0000 |
401 | +++ tests/openlp_core/projectors/test_projector_constants.py 2019-05-04 05:43:42 +0000 |
402 | @@ -40,11 +40,12 @@ |
403 | missing_str = [] |
404 | |
405 | # GIVEN: List of defined E_* and S_* items defined in constants |
406 | + |
407 | for item in constants.__dict__: |
408 | if item.startswith('E_') or item.startswith('S_'): |
409 | check.append(item) |
410 | |
411 | - # WHEN: Verify defined list against STATUS_STR |
412 | + # WHEN: Verify items were addeded to check |
413 | for item in check: |
414 | if constants.__dict__[item] not in STATUS_CODE: |
415 | missing_str.append(item) |
416 | |
417 | === modified file 'tests/openlp_core/projectors/test_projector_pjlink_base_02.py' |
418 | --- tests/openlp_core/projectors/test_projector_pjlink_base_02.py 2019-04-28 19:21:23 +0000 |
419 | +++ tests/openlp_core/projectors/test_projector_pjlink_base_02.py 2019-05-04 05:43:42 +0000 |
420 | @@ -454,9 +454,9 @@ |
421 | suff=PJLINK_SUFFIX) |
422 | log_error_calls = [] |
423 | log_warning_calls = [] |
424 | - log_debug_calls = [call('({ip}) send_command(): Building cmd="CLSS" opts="?"'.format(ip=self.pjlink.name)), |
425 | + log_debug_calls = [call('({ip}) send_command(): Building cmd="CLSS" opts="?" '.format(ip=self.pjlink.name)), |
426 | call('({ip}) send_command(): Adding to normal queue'.format(ip=self.pjlink.name))] |
427 | - mock_state.return_value = S_CONNECTED |
428 | + mock_state.return_value = QSOCKET_STATE[S_CONNECTED] |
429 | |
430 | # Patch here since pjlink does not have priority or send queue's until instantiated |
431 | with patch.object(self.pjlink, 'send_queue') as mock_send, \ |
432 | @@ -488,9 +488,9 @@ |
433 | suff=PJLINK_SUFFIX) |
434 | log_error_calls = [] |
435 | log_warning_calls = [] |
436 | - log_debug_calls = [call('({ip}) send_command(): Building cmd="CLSS" opts="?"'.format(ip=self.pjlink.name)), |
437 | + log_debug_calls = [call('({ip}) send_command(): Building cmd="CLSS" opts="?" '.format(ip=self.pjlink.name)), |
438 | call('({ip}) send_command(): Adding to priority queue'.format(ip=self.pjlink.name))] |
439 | - mock_state.return_value = S_CONNECTED |
440 | + mock_state.return_value = QSOCKET_STATE[S_CONNECTED] |
441 | |
442 | # Patch here since pjlink does not have priority or send queue's until instantiated |
443 | with patch.object(self.pjlink, 'send_queue') as mock_send, \ |
444 | @@ -523,8 +523,10 @@ |
445 | log_error_calls = [] |
446 | log_warning_calls = [call('({ip}) send_command(): Already in normal queue - ' |
447 | 'skipping'.format(ip=self.pjlink.name))] |
448 | - log_debug_calls = [call('({ip}) send_command(): Building cmd="CLSS" opts="?"'.format(ip=self.pjlink.name))] |
449 | - mock_state.return_value = S_CONNECTED |
450 | + log_debug_calls = [call('({ip}) send_command(cmd="CLSS" opts="?" salt="None" ' |
451 | + 'priority=False'.format(ip=self.pjlink.name)), |
452 | + call('({ip}) send_command(): Building cmd="CLSS" opts="?" '.format(ip=self.pjlink.name))] |
453 | + mock_state.return_value = QSOCKET_STATE[S_CONNECTED] |
454 | self.pjlink.send_queue = [test_command] |
455 | self.pjlink.priority_queue = [] |
456 | |
457 | @@ -555,8 +557,10 @@ |
458 | log_error_calls = [] |
459 | log_warning_calls = [call('({ip}) send_command(): Already in priority queue - ' |
460 | 'skipping'.format(ip=self.pjlink.name))] |
461 | - log_debug_calls = [call('({ip}) send_command(): Building cmd="CLSS" opts="?"'.format(ip=self.pjlink.name))] |
462 | - mock_state.return_value = S_CONNECTED |
463 | + log_debug_calls = [call('({ip}) send_command(cmd="CLSS" opts="?" salt="None" ' |
464 | + 'priority=True'.format(ip=self.pjlink.name)), |
465 | + call('({ip}) send_command(): Building cmd="CLSS" opts="?" '.format(ip=self.pjlink.name))] |
466 | + mock_state.return_value = QSOCKET_STATE[S_CONNECTED] |
467 | self.pjlink.send_queue = [] |
468 | self.pjlink.priority_queue = [test_command] |
469 | |
470 | @@ -585,7 +589,7 @@ |
471 | 'ignoring.'.format(ip=self.pjlink.name))] |
472 | log_warning_calls = [] |
473 | log_debug_calls = [] |
474 | - mock_state.return_value = S_CONNECTED |
475 | + mock_state.return_value = QSOCKET_STATE[S_CONNECTED] |
476 | self.pjlink.send_queue = [] |
477 | self.pjlink.priority_queue = [] |
478 | |
479 | @@ -617,7 +621,7 @@ |
480 | 'ignoring.'.format(ip=self.pjlink.name))] |
481 | log_warning_calls = [] |
482 | log_debug_calls = [] |
483 | - mock_state.return_value = S_CONNECTED |
484 | + mock_state.return_value = QSOCKET_STATE[S_CONNECTED] |
485 | self.pjlink.send_queue = [test_command] |
486 | self.pjlink.priority_queue = [] |
487 | |
488 | @@ -649,7 +653,7 @@ |
489 | 'ignoring.'.format(ip=self.pjlink.name))] |
490 | log_warning_calls = [] |
491 | log_debug_calls = [] |
492 | - mock_state.return_value = S_CONNECTED |
493 | + mock_state.return_value = QSOCKET_STATE[S_CONNECTED] |
494 | self.pjlink.send_queue = [] |
495 | self.pjlink.priority_queue = [test_command] |
496 | |
497 | |
498 | === modified file 'tests/openlp_core/projectors/test_projector_pjlink_cmd_routing.py' |
499 | --- tests/openlp_core/projectors/test_projector_pjlink_cmd_routing.py 2019-04-28 19:21:23 +0000 |
500 | +++ tests/openlp_core/projectors/test_projector_pjlink_cmd_routing.py 2019-05-04 05:43:42 +0000 |
501 | @@ -24,10 +24,9 @@ |
502 | """ |
503 | |
504 | from unittest import TestCase, skip |
505 | -from unittest.mock import MagicMock, call, patch |
506 | +from unittest.mock import call, patch |
507 | |
508 | import openlp.core.projectors.pjlink |
509 | -from openlp.core.projectors.pjlinkcommands import process_command |
510 | from openlp.core.projectors.constants import E_AUTHENTICATION, E_PARAMETER, E_PROJECTOR, E_UNAVAILABLE, E_UNDEFINED, \ |
511 | PJLINK_ERRORS, PJLINK_PREFIX, STATUS_MSG |
512 | from openlp.core.projectors.db import Projector |
513 | @@ -51,82 +50,93 @@ |
514 | """ |
515 | del(self.pjlink) |
516 | |
517 | - @patch.object(openlp.core.projectors.pjlink, 'log') |
518 | - def test_get_data_unknown_command(self, mock_log): |
519 | - """ |
520 | - Test not a valid command |
521 | - """ |
522 | - # GIVEN: Test object |
523 | - self.pjlink.pjlink_functions = MagicMock() |
524 | + @patch.object(openlp.core.projectors.pjlinkcommands, 'process_command') |
525 | + @patch.object(openlp.core.projectors.pjlink, 'log') |
526 | + def test_projector_get_data_invalid_version(self, mock_log, mock_process_cmd): |
527 | + """ |
528 | + Test projector received valid command invalid version |
529 | + """ |
530 | + # GIVEN: Test object |
531 | + log_warning_text = [call('({ip}) get_data() Command reply version does not match ' |
532 | + 'a valid command version'.format(ip=self.pjlink.name)), |
533 | + call('({ip}) _send_command(): Nothing to send - returning'.format(ip=self.pjlink.name))] |
534 | + log_debug_text = [call('({ip}) get_data(buffer="{pre}XCLSS=X"'.format(ip=self.pjlink.name, pre=PJLINK_PREFIX)), |
535 | + call('({ip}) get_data(): Checking new data "{pre}XCLSS=X"'.format(ip=self.pjlink.name, |
536 | + pre=PJLINK_PREFIX)), |
537 | + call('({ip}) get_data() header="{pre}XCLSS" data="X"'.format(ip=self.pjlink.name, |
538 | + pre=PJLINK_PREFIX)), |
539 | + call('({ip}) get_data() version="X" cmd="CLSS" data="X"'.format(ip=self.pjlink.name)), |
540 | + call('({ip}) Cleaning buffer - msg = "get_data() Command reply version does ' |
541 | + 'not match a valid command version"'.format(ip=self.pjlink.name)), |
542 | + call('({ip}) Finished cleaning buffer - 0 bytes dropped'.format(ip=self.pjlink.name))] |
543 | + # WHEN: get_data called with an unknown command |
544 | + self.pjlink.get_data(buff='{prefix}XCLSS=X'.format(prefix=PJLINK_PREFIX)) |
545 | + |
546 | + # THEN: Appropriate log entries should have been made and methods called/not called |
547 | + mock_log.warning.assert_has_calls(log_warning_text) |
548 | + mock_log.debug.assert_has_calls(log_debug_text) |
549 | + assert (mock_process_cmd.call_count == 0), 'process_command should not have been called' |
550 | + |
551 | + @patch.object(openlp.core.projectors.pjlinkcommands, 'process_command') |
552 | + @patch.object(openlp.core.projectors.pjlink, 'log') |
553 | + def test_projector_get_data_unknown_command(self, mock_log, mock_process_cmd): |
554 | + """ |
555 | + Test projector receiving invalid command |
556 | + """ |
557 | + # GIVEN: Test object |
558 | log_warning_text = [call('({ip}) get_data(): Invalid packet - ' |
559 | 'unknown command "UNKN"'.format(ip=self.pjlink.name)), |
560 | call('({ip}) _send_command(): Nothing to send - ' |
561 | 'returning'.format(ip=self.pjlink.name))] |
562 | - log_debug_text = [call('(___TEST_ONE___) get_data(buffer="%1UNKN=Huh?"'), |
563 | - call('(___TEST_ONE___) get_data(): Checking new data "%1UNKN=Huh?"'), |
564 | - call('(___TEST_ONE___) get_data() header="%1UNKN" data="Huh?"'), |
565 | - call('(___TEST_ONE___) get_data() version="1" cmd="UNKN"'), |
566 | - call('(___TEST_ONE___) Cleaning buffer - msg = "get_data(): ' |
567 | - 'Invalid packet - unknown command "UNKN""'), |
568 | - call('(___TEST_ONE___) Finished cleaning buffer - 0 bytes dropped')] |
569 | + log_debug_text = [call('({ip}) get_data(buffer="{pre}1UNKN=Huh?"'.format(ip=self.pjlink.name, |
570 | + pre=PJLINK_PREFIX)), |
571 | + call('({ip}) get_data(): Checking new data "{pre}1UNKN=Huh?"'.format(ip=self.pjlink.name, |
572 | + pre=PJLINK_PREFIX)), |
573 | + call('({ip}) get_data() header="{pre}1UNKN" data="Huh?"'.format(ip=self.pjlink.name, |
574 | + pre=PJLINK_PREFIX)), |
575 | + call('({ip}) get_data() version="1" cmd="UNKN" data="Huh?"'.format(ip=self.pjlink.name)), |
576 | + call('({ip}) Cleaning buffer - msg = "get_data(): Invalid packet - ' |
577 | + 'unknown command "UNKN""'.format(ip=self.pjlink.name)), |
578 | + call('({ip}) Finished cleaning buffer - 0 bytes dropped'.format(ip=self.pjlink.name))] |
579 | + |
580 | # WHEN: get_data called with an unknown command |
581 | self.pjlink.get_data(buff='{prefix}1UNKN=Huh?'.format(prefix=PJLINK_PREFIX)) |
582 | |
583 | # THEN: Appropriate log entries should have been made and methods called/not called |
584 | mock_log.warning.assert_has_calls(log_warning_text) |
585 | mock_log.debug.assert_has_calls(log_debug_text) |
586 | - assert self.pjlink.pjlink_functions.called is False, 'Should not have accessed pjlink_functions' |
587 | - |
588 | - @skip('Needs update to new setup') |
589 | - @patch("openlp.core.projectors.pjlink.log") |
590 | - def test_process_command_call_clss(self, mock_log): |
591 | - """ |
592 | - Test process_command calls proper function |
593 | - """ |
594 | - # GIVEN: Test object and mocks |
595 | - with patch.object(openlp.core.projectors.pjlink, 'log') as mock_log, \ |
596 | - patch.object(openlp.core.projectors.pjlink.PJLink, 'process_clss') as mock_process_clss: |
597 | - |
598 | - log_debug_calls = [call('({ip}) Processing command "CLSS" with data "1"'.format(ip=self.pjlink.name)), |
599 | - call('({ip}) Calling function for CLSS'.format(ip=self.pjlink.name))] |
600 | - |
601 | - # WHEN: process_command is called with valid function and data |
602 | - process_command(projector=self.pjlink, cmd='CLSS', data='1') |
603 | - |
604 | - # THEN: Appropriate log entries should have been made and methods called |
605 | - mock_log.debug.assert_has_calls(log_debug_calls) |
606 | - mock_process_clss.assert_called_once_with(data='1') |
607 | - |
608 | - @skip('Needs update to new setup') |
609 | - def test_process_command_erra(self): |
610 | - """ |
611 | - Test ERRA - Authentication Error |
612 | + assert (mock_process_cmd.call_count == 0), 'process_command should not have been called' |
613 | + |
614 | + @patch.object(openlp.core.projectors.pjlinkcommands, 'process_command') |
615 | + @patch.object(openlp.core.projectors.pjlink, 'log') |
616 | + def test_projector_get_data_version_mismatch(self, mock_log, mock_process_cmd): |
617 | + """ |
618 | + Test projector received valid command with command version higher than projector |
619 | """ |
620 | # GIVEN: Test object |
621 | - with patch.object(openlp.core.projectors.pjlink, 'log') as mock_log, \ |
622 | - patch.object(openlp.core.projectors.pjlink.PJLink, 'process_pjlink') as mock_process_pjlink, \ |
623 | - patch.object(openlp.core.projectors.pjlink.PJLink, 'change_status') as mock_change_status, \ |
624 | - patch.object(openlp.core.projectors.pjlink.PJLink, 'disconnect_from_host') as mock_disconnect, \ |
625 | - patch.object(openlp.core.projectors.pjlink.PJLink, 'projectorAuthentication') as mock_authentication: |
626 | - |
627 | - pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True) |
628 | - log_error_calls = [call('({ip}) PJLINK: {msg}'.format(ip=self.pjlink.name, |
629 | - msg=STATUS_MSG[E_AUTHENTICATION]))] |
630 | - log_debug_calls = [call('({ip}) Processing command "PJLINK" with data "ERRA"'.format(ip=self.pjlink.name))] |
631 | - |
632 | - # WHEN: process_command called with ERRA |
633 | - pjlink.process_command(cmd='PJLINK', data=PJLINK_ERRORS[E_AUTHENTICATION]) |
634 | - |
635 | - # THEN: Appropriate log entries should have been made and methods called/not called |
636 | - assert mock_disconnect.called is True, 'disconnect_from_host should have been called' |
637 | - mock_log.error.assert_has_calls(log_error_calls) |
638 | - mock_log.debug.assert_has_calls(log_debug_calls) |
639 | - mock_change_status.assert_called_once_with(status=E_AUTHENTICATION) |
640 | - mock_authentication.emit.assert_called_once_with(pjlink.name) |
641 | - mock_process_pjlink.assert_not_called() |
642 | + log_warning_text = [call('({ip}) get_data(): Projector returned class reply higher than projector ' |
643 | + 'stated class'.format(ip=self.pjlink.name)), |
644 | + call('({ip}) _send_command(): Nothing to send - returning'.format(ip=self.pjlink.name))] |
645 | + |
646 | + log_debug_text = [call('({ip}) get_data(buffer="{pre}2ACKN=X"'.format(ip=self.pjlink.name, |
647 | + pre=PJLINK_PREFIX)), |
648 | + call('({ip}) get_data(): Checking new data "{pre}2ACKN=X"'.format(ip=self.pjlink.name, |
649 | + pre=PJLINK_PREFIX)), |
650 | + call('({ip}) get_data() header="{pre}2ACKN" data="X"'.format(ip=self.pjlink.name, |
651 | + pre=PJLINK_PREFIX)), |
652 | + call('({ip}) get_data() version="2" cmd="ACKN" data="X"'.format(ip=self.pjlink.name))] |
653 | + self.pjlink.pjlink_class = '1' |
654 | + |
655 | + # WHEN: get_data called with an unknown command |
656 | + self.pjlink.get_data(buff='{prefix}2ACKN=X'.format(prefix=PJLINK_PREFIX)) |
657 | + |
658 | + # THEN: Appropriate log entries should have been made and methods called/not called |
659 | + mock_log.warning.assert_has_calls(log_warning_text) |
660 | + mock_log.debug.assert_has_calls(log_debug_text) |
661 | + assert (mock_process_cmd.call_count == 0), 'process_command should not have been called' |
662 | |
663 | @skip('Needs update to new setup') |
664 | - def test_process_command_err1(self): |
665 | + def test_routing_err1(self): |
666 | """ |
667 | Test ERR1 - Undefined projector function |
668 | """ |
669 | @@ -148,7 +158,7 @@ |
670 | mock_process_clss.assert_called_once_with(data=PJLINK_ERRORS[E_UNDEFINED]) |
671 | |
672 | @skip('Needs update to new setup') |
673 | - def test_process_command_err2(self): |
674 | + def test_routing_err2(self): |
675 | """ |
676 | Test ERR2 - Parameter Error |
677 | """ |
678 | @@ -170,7 +180,7 @@ |
679 | mock_process_clss.assert_called_once_with(data=PJLINK_ERRORS[E_PARAMETER]) |
680 | |
681 | @skip('Needs update to new setup') |
682 | - def test_process_command_err3(self): |
683 | + def test_routing_err3(self): |
684 | """ |
685 | Test ERR3 - Unavailable error |
686 | """ |
687 | @@ -192,7 +202,7 @@ |
688 | mock_process_clss.assert_called_once_with(data=PJLINK_ERRORS[E_UNAVAILABLE]) |
689 | |
690 | @skip('Needs update to new setup') |
691 | - def test_process_command_err4(self): |
692 | + def test_routing_err4(self): |
693 | """ |
694 | Test ERR3 - Unavailable error |
695 | """ |
696 | @@ -214,48 +224,29 @@ |
697 | mock_process_clss.assert_called_once_with(data=PJLINK_ERRORS[E_PROJECTOR]) |
698 | |
699 | @skip('Needs update to new setup') |
700 | - def test_process_command_future(self): |
701 | + def test_routing_erra(self): |
702 | """ |
703 | - Test command valid but no method to process yet |
704 | + Test ERRA - Authentication Error |
705 | """ |
706 | # GIVEN: Test object |
707 | with patch.object(openlp.core.projectors.pjlink, 'log') as mock_log, \ |
708 | - patch.object(openlp.core.projectors.pjlink.PJLink, 'process_clss') as mock_process_clss: |
709 | + patch.object(openlp.core.projectors.pjlink.PJLink, 'process_pjlink') as mock_process_pjlink, \ |
710 | + patch.object(openlp.core.projectors.pjlink.PJLink, 'change_status') as mock_change_status, \ |
711 | + patch.object(openlp.core.projectors.pjlink.PJLink, 'disconnect_from_host') as mock_disconnect, \ |
712 | + patch.object(openlp.core.projectors.pjlink.PJLink, 'projectorAuthentication') as mock_authentication: |
713 | |
714 | pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True) |
715 | - pjlink.pjlink_functions = MagicMock() |
716 | - log_warning_text = [call('({ip}) Unable to process command="CLSS" ' |
717 | - '(Future option?)'.format(ip=self.pjlink.name))] |
718 | - log_debug_text = [call('({ip}) Processing command "CLSS" ' |
719 | - 'with data "Huh?"'.format(ip=self.pjlink.name))] |
720 | + log_error_calls = [call('({ip}) PJLINK: {msg}'.format(ip=self.pjlink.name, |
721 | + msg=STATUS_MSG[E_AUTHENTICATION]))] |
722 | + log_debug_calls = [call('({ip}) Processing command "PJLINK" with data "ERRA"'.format(ip=self.pjlink.name))] |
723 | |
724 | - # WHEN: Processing a possible future command |
725 | - pjlink.process_command(cmd='CLSS', data="Huh?") |
726 | + # WHEN: process_command called with ERRA |
727 | + pjlink.process_command(cmd='PJLINK', data=PJLINK_ERRORS[E_AUTHENTICATION]) |
728 | |
729 | # THEN: Appropriate log entries should have been made and methods called/not called |
730 | - mock_log.debug.assert_has_calls(log_debug_text) |
731 | - mock_log.warning.assert_has_calls(log_warning_text) |
732 | - assert pjlink.pjlink_functions.called is False, 'Should not have accessed pjlink_functions' |
733 | - assert mock_process_clss.called is False, 'Should not have called process_clss' |
734 | - |
735 | - @skip('Needs update to new setup') |
736 | - def test_process_command_ok(self): |
737 | - """ |
738 | - Test command returned success |
739 | - """ |
740 | - # GIVEN: Test object and mocks |
741 | - with patch.object(openlp.core.projectors.pjlink, 'log') as mock_log, \ |
742 | - patch.object(openlp.core.projectors.pjlink.PJLink, 'send_command') as mock_send_command, \ |
743 | - patch.object(openlp.core.projectors.pjlink.PJLink, 'process_clss') as mock_process_clss: |
744 | - |
745 | - pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True) |
746 | - log_debug_calls = [call('({ip}) Processing command "CLSS" with data "OK"'.format(ip=self.pjlink.name)), |
747 | - call('({ip}) Command "CLSS" returned OK'.format(ip=self.pjlink.name))] |
748 | - |
749 | - # WHEN: process_command is called with valid function and data |
750 | - pjlink.process_command(cmd='CLSS', data='OK') |
751 | - |
752 | - # THEN: Appropriate log entries should have been made and methods called |
753 | + assert mock_disconnect.called is True, 'disconnect_from_host should have been called' |
754 | + mock_log.error.assert_has_calls(log_error_calls) |
755 | mock_log.debug.assert_has_calls(log_debug_calls) |
756 | - mock_send_command.assert_called_once_with(cmd='CLSS') |
757 | - mock_process_clss.assert_not_called() |
758 | + mock_change_status.assert_called_once_with(status=E_AUTHENTICATION) |
759 | + mock_authentication.emit.assert_called_once_with(pjlink.name) |
760 | + mock_process_pjlink.assert_not_called() |
Linux tests passed!