Merge lp:~alisonken1/openlp/pjlink2_v04 into lp:openlp

Proposed by Ken Roberts
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
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://ci.openlp.io/job/Branch-01-Pull/2729/ [SUCCESS]
https://ci.openlp.io/job/Branch-02a-Linux-Tests/2623/ [SUCCESS]
https://ci.openlp.io/job/Branch-02b-macOS-Tests/398/ [SUCCESS]
https://ci.openlp.io/job/Branch-03a-Build-Source/220/ [SUCCESS]
https://ci.openlp.io/job/Branch-03b-Build-macOS/199/ [SUCCESS]
https://ci.openlp.io/job/Branch-04a-Code-Lint/1682/ [SUCCESS]
https://ci.openlp.io/job/Branch-04b-Test-Coverage/1495/ [SUCCESS]
https://ci.openlp.io/job/Branch-05-AppVeyor-Tests/375/ [SUCCESS]

All builds passed

To post a comment you must log in.
Revision history for this message
Raoul Snyman (raoul-snyman) wrote :

Linux tests passed!

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

Linting passed!

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

macOS tests passed!

Revision history for this message
Phill (phill-ridout) wrote :

Looks ok to me

review: Approve
Revision history for this message
Tim Bentley (trb143) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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()