← Back to team overview

openlp-core team mailing list archive

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

 

Ken Roberts has proposed merging lp:~alisonken1/openlp/pjlink2_v04 into lp:openlp.

Commit message:
PJLink2 update V04

Requested reviews:
  OpenLP Core (openlp-core)

For more details, see:
https://code.launchpad.net/~alisonken1/openlp/pjlink2_v04/+merge/366946

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

-- 
Your team OpenLP Core is requested to review the proposed merge of lp:~alisonken1/openlp/pjlink2_v04 into lp:openlp.
=== modified file 'openlp/core/projectors/constants.py'
--- openlp/core/projectors/constants.py	2019-04-13 13:00:22 +0000
+++ openlp/core/projectors/constants.py	2019-05-04 05:43:42 +0000
@@ -106,6 +106,9 @@
 S_ON = 315
 S_COOLDOWN = 316
 S_INFO = 317
+S_CONNECT = 318  # Initial connection, connected
+S_AUTHENTICATE = 319  # Initial connection, send pin hash
+S_DATA_OK = 320  # Previous command returned OK
 
 # Information that does not affect status
 S_NETWORK_IDLE = 400
@@ -369,11 +372,14 @@
     E_UNKNOWN_SOCKET_ERROR: 'E_UNKNOWN_SOCKET_ERROR',
     E_UNSUPPORTED_SOCKET_OPERATION: 'E_UNSUPPORTED_SOCKET_OPERATION',
     E_WARN: 'E_WARN',
+    S_AUTHENTICATE: 'S_AUTHENTICATE',
     S_BOUND: 'S_BOUND',
+    S_CONNECT: 'S_CONNECT',
     S_COOLDOWN: 'S_COOLDOWN',
     S_CLOSING: 'S_CLOSING',
     S_CONNECTED: 'S_CONNECTED',
     S_CONNECTING: 'S_CONNECTING',
+    S_DATA_OK: 'S_DATA_OK',
     S_HOST_LOOKUP: 'S_HOST_LOOKUP',
     S_INFO: 'S_INFO',
     S_INITIALIZE: 'S_INITIALIZE',
@@ -387,7 +393,7 @@
     S_ON: 'S_ON',
     S_STANDBY: 'S_STANDBY',
     S_STATUS: 'S_STATUS',
-    S_WARMUP: 'S_WARMUP',
+    S_WARMUP: 'S_WARMUP'
 }
 
 # Map status codes to message strings
@@ -459,11 +465,14 @@
                                               'The requested socket operation is not supported by the local '
                                               'operating system (e.g., lack of IPv6 support)'),
     E_WARN: translate('OpenLP.ProjectorConstants', 'Warning condition detected'),
+    S_AUTHENTICATE: translate('OpenLP.ProjectorConstants', 'Connection initializing with pin'),
     S_BOUND: translate('OpenLP.ProjectorConstants', 'Socket is bount to an address or port'),
+    S_CONNECT: translate('OpenLP.ProjectorConstants', 'Connection initializing'),
     S_CLOSING: translate('OpenLP.ProjectorConstants', 'Socket is about to close'),
     S_CONNECTED: translate('OpenLP.ProjectorConstants', 'Connected'),
     S_CONNECTING: translate('OpenLP.ProjectorConstants', 'Connecting'),
     S_COOLDOWN: translate('OpenLP.ProjectorConstants', 'Cooldown in progress'),
+    S_DATA_OK: translate('OpenLP.ProjectorConstants', 'Command returned with OK'),
     S_HOST_LOOKUP: translate('OpenLP.ProjectorConstants', 'Performing a host name lookup'),
     S_INFO: translate('OpenLP.ProjectorConstants', 'Projector Information available'),
     S_INITIALIZE: translate('OpenLP.ProjectorConstants', 'Initialize in progress'),

=== modified file 'openlp/core/projectors/pjlink.py'
--- openlp/core/projectors/pjlink.py	2019-04-28 19:21:23 +0000
+++ openlp/core/projectors/pjlink.py	2019-05-04 05:43:42 +0000
@@ -52,14 +52,15 @@
 
 from PyQt5 import QtCore, QtNetwork
 
+from openlp.core.common import qmd5_hash
 from openlp.core.common.i18n import translate
 from openlp.core.common.settings import Settings
 from openlp.core.projectors.pjlinkcommands import process_command
-from openlp.core.projectors.constants import CONNECTION_ERRORS, E_CONNECTION_REFUSED, E_GENERAL, \
+from openlp.core.projectors.constants import CONNECTION_ERRORS, E_AUTHENTICATION, E_CONNECTION_REFUSED, E_GENERAL, \
     E_NETWORK, E_NOT_CONNECTED, E_SOCKET_TIMEOUT, PJLINK_CLASS, \
     PJLINK_MAX_PACKET, PJLINK_PORT, PJLINK_PREFIX, PJLINK_SUFFIX, \
-    PJLINK_VALID_CMD, PROJECTOR_STATE, QSOCKET_STATE, S_CONNECTED, S_CONNECTING, S_NOT_CONNECTED, S_OFF, S_OK, S_ON, \
-    STATUS_CODE, STATUS_MSG
+    PJLINK_VALID_CMD, PROJECTOR_STATE, QSOCKET_STATE, S_AUTHENTICATE, S_CONNECT, S_CONNECTED, S_CONNECTING, \
+    S_DATA_OK, S_NOT_CONNECTED, S_OFF, S_OK, S_ON, STATUS_CODE, STATUS_MSG
 
 
 log = logging.getLogger(__name__)
@@ -565,21 +566,10 @@
         #       V = PJLink class or version
         #       C = PJLink command
         version, cmd = header[1], header[2:].upper()
-        log.debug('({ip}) get_data() version="{version}" cmd="{cmd}"'.format(ip=self.entry.name,
-                                                                             version=version, cmd=cmd))
-        # TODO: Below commented for now since it seems to cause issues with testing some invalid data.
-        #       Revisit after more refactoring is finished.
-        '''
-        try:
-            version, cmd = header[1], header[2:].upper()
-            log.debug('({ip}) get_data() version="{version}" cmd="{cmd}"'.format(ip=self.entry.name,
-                                                                                 version=version, cmd=cmd))
-        except ValueError as e:
-            self.change_status(E_INVALID_DATA)
-            log.warning('({ip}) get_data(): Received data: "{data}"'.format(ip=self.entry.name, data=data_in))
-            self._trash_buffer('get_data(): Expected header + command + data')
-            return self.receive_data_signal()
-        '''
+        log.debug('({ip}) get_data() version="{version}" cmd="{cmd}" data="{data}"'.format(ip=self.entry.name,
+                                                                                           version=version,
+                                                                                           cmd=cmd,
+                                                                                           data=data))
         if cmd not in PJLINK_VALID_CMD:
             self._trash_buffer('get_data(): Invalid packet - unknown command "{data}"'.format(data=cmd))
             return self.receive_data_signal()
@@ -590,7 +580,38 @@
             if not ignore_class:
                 log.warning('({ip}) get_data(): Projector returned class reply higher '
                             'than projector stated class'.format(ip=self.entry.name))
-        process_command(self, cmd, data)
+                return self.receive_data_signal()
+
+        chk = process_command(self, cmd, data)
+        if chk is None:
+            # Command processed normally and not initial connection, so skip other checks
+            return self.receive_data_signal()
+        # PJLink initial connection checks
+        elif chk == S_DATA_OK:
+            # Previous command returned OK
+            log.debug('({ip}) OK returned - resending command'.format(ip=self.entry.name))
+            self.send_command(cmd=cmd, priority=True)
+        elif chk == S_CONNECT:
+            # Normal connection
+            log.debug('({ip}) Connecting normal'.format(ip=self.entry.name))
+            self.change_status(S_CONNECTED)
+            self.send_command(cmd='CLSS', priority=True)
+            self.readyRead.connect(self.get_socket)
+        elif chk == S_AUTHENTICATE:
+            # Connection with pin
+            log.debug('({ip}) Connecting with pin'.format(ip=self.entry.name))
+            data_hash = str(qmd5_hash(salt=chk[1].encode('utf-8'), data=self.pin.encode('utf-8')),
+                            encoding='ascii')
+            self.change_status(S_CONNECTED)
+            self.readyRead.connect(self.get_socket)
+            self.send_command(cmd='CLSS', salt=data_hash, priority=True)
+        elif chk == E_AUTHENTICATION:
+            # Projector did not like our pin
+            log.warning('({ip}) Failed authentication - disconnecting'.format(ip=self.entry.name))
+            self.disconnect_from_host()
+            self.projectorAuthentication.emit(self.entry.name)
+            self.change_status(status=E_AUTHENTICATION)
+
         return self.receive_data_signal()
 
     @QtCore.pyqtSlot(QtNetwork.QAbstractSocket.SocketError)
@@ -631,7 +652,9 @@
         :param salt: Optional  salt for md5 hash initial authentication
         :param priority: Option to send packet now rather than queue it up
         """
-        if QSOCKET_STATE[self.state()] != QSOCKET_STATE[S_CONNECTED]:
+        log.debug('({ip}) send_command(cmd="{cmd}" opts="{opts}" salt="{salt}" '
+                  'priority={pri}'.format(ip=self.entry.name, cmd=cmd, opts=opts, salt=salt, pri=priority))
+        if QSOCKET_STATE[self.state()] != S_CONNECTED:
             log.warning('({ip}) send_command(): Not connected - returning'.format(ip=self.entry.name))
             return self.reset_information()
         if cmd not in PJLINK_VALID_CMD:
@@ -640,11 +663,11 @@
                 # Just in case there's already something to send
                 return self._send_command()
             return
-        log.debug('({ip}) send_command(): Building cmd="{command}" opts="{data}"{salt}'.format(ip=self.entry.name,
-                                                                                               command=cmd,
-                                                                                               data=opts,
-                                                                                               salt='' if salt is None
-                                                                                               else ' with hash'))
+        log.debug('({ip}) send_command(): Building cmd="{command}" opts="{data}" '
+                  '{salt}'.format(ip=self.entry.name,
+                                  command=cmd,
+                                  data=opts,
+                                  salt='' if salt is None else 'with hash'))
         # Until we absolutely have to start doing version checks, use the default
         # for PJLink class
         header = PJLINK_HEADER.format(linkclass=PJLINK_VALID_CMD[cmd]['default'])

=== modified file 'openlp/core/projectors/pjlinkcommands.py'
--- openlp/core/projectors/pjlinkcommands.py	2019-04-28 19:22:52 +0000
+++ openlp/core/projectors/pjlinkcommands.py	2019-05-04 05:43:42 +0000
@@ -30,14 +30,12 @@
 import logging
 import re
 
-from openlp.core.common import qmd5_hash
-
 from openlp.core.common.i18n import translate
 from openlp.core.common.settings import Settings
 
 from openlp.core.projectors.constants import E_AUTHENTICATION, PJLINK_DEFAULT_CODES, PJLINK_ERRORS, \
-    PJLINK_ERST_DATA, PJLINK_ERST_STATUS, PJLINK_POWR_STATUS, S_CONNECTED, S_OFF, S_OK, S_ON, S_STANDBY, \
-    STATUS_MSG
+    PJLINK_ERST_DATA, PJLINK_ERST_STATUS, PJLINK_POWR_STATUS, S_AUTHENTICATE, S_CONNECT, S_DATA_OK, S_OFF, S_OK, S_ON, \
+    S_STANDBY, STATUS_MSG
 
 log = logging.getLogger(__name__)
 log.debug('Loading pjlinkcommands')
@@ -68,19 +66,18 @@
     elif _data == 'OK':
         log.debug('({ip}) Command "{cmd}" returned OK'.format(ip=projector.entry.name, cmd=cmd))
         # A command returned successfully, so do a query on command to verify status
-        return projector.send_command(cmd=cmd, priority=True)
+        return S_DATA_OK
+
     elif _data in PJLINK_ERRORS:
         # Oops - projector error
         log.error('({ip}) {cmd}: {err}'.format(ip=projector.entry.name,
                                                cmd=cmd,
                                                err=STATUS_MSG[PJLINK_ERRORS[_data]]))
-        if PJLINK_ERRORS[_data] == E_AUTHENTICATION:
-            projector.disconnect_from_host()
-            projector.projectorAuthentication.emit(projector.name)
-            return projector.change_status(status=E_AUTHENTICATION)
+        return PJLINK_ERRORS[_data]
+
     # Command checks already passed
     log.debug('({ip}) Calling function for {cmd}'.format(ip=projector.entry.name, cmd=cmd))
-    pjlink_functions[cmd](projector=projector, data=data)
+    return pjlink_functions[cmd](projector=projector, data=data)
 
 
 def process_ackn(projector, data):
@@ -376,35 +373,28 @@
     if len(chk[0]) != 1:
         # Invalid - after splitting, first field should be 1 character, either '0' or '1' only
         log.error('({ip}) Invalid initial authentication scheme - aborting'.format(ip=projector.entry.name))
-        return projector.disconnect_from_host()
+        return E_AUTHENTICATION
     elif chk[0] == '0':
         # Normal connection no authentication
         if len(chk) > 1:
             # Invalid data - there should be nothing after a normal authentication scheme
             log.error('({ip}) Normal connection with extra information - aborting'.format(ip=projector.entry.name))
-            return projector.disconnect_from_host()
+            return E_AUTHENTICATION
         elif projector.pin:
             log.error('({ip}) Normal connection but PIN set - aborting'.format(ip=projector.entry.name))
-            return projector.disconnect_from_host()
-        else:
-            data_hash = None
+            return E_AUTHENTICATION
+        log.debug('({ip}) PJLINK: Returning S_CONNECT'.format(ip=projector.entry.name))
+        return S_CONNECT
     elif chk[0] == '1':
         if len(chk) < 2:
             # Not enough information for authenticated connection
             log.error('({ip}) Authenticated connection but not enough info - aborting'.format(ip=projector.entry.name))
-            return projector.disconnect_from_host()
+            return E_AUTHENTICATION
         elif not projector.pin:
             log.error('({ip}) Authenticate connection but no PIN - aborting'.format(ip=projector.entry.name))
-            return projector.disconnect_from_host()
-        else:
-            data_hash = str(qmd5_hash(salt=chk[1].encode('utf-8'), data=projector.pin.encode('utf-8')),
-                            encoding='ascii')
-    # Passed basic checks, so start connection
-    projector.readyRead.connect(projector.get_socket)
-    projector.change_status(S_CONNECTED)
-    log.debug('({ip}) process_pjlink(): Sending "CLSS" initial command'.format(ip=projector.entry.name))
-    # Since this is an initial connection, make it a priority just in case
-    return projector.send_command(cmd="CLSS", salt=data_hash, priority=True)
+            return E_AUTHENTICATION
+        log.debug('({ip}) PJLINK: Returning S_AUTHENTICATE'.format(ip=projector.entry.name))
+        return S_AUTHENTICATE
 
 
 def process_powr(projector, data):

=== added file 'tests/openlp_core/projectors/test_projector_command_routing.py'
--- tests/openlp_core/projectors/test_projector_command_routing.py	1970-01-01 00:00:00 +0000
+++ tests/openlp_core/projectors/test_projector_command_routing.py	2019-05-04 05:43:42 +0000
@@ -0,0 +1,132 @@
+# -*- coding: utf-8 -*-
+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
+
+##########################################################################
+# OpenLP - Open Source Lyrics Projection                                 #
+# ---------------------------------------------------------------------- #
+# Copyright (c) 2008-2019 OpenLP Developers                              #
+# ---------------------------------------------------------------------- #
+# This program is free software: you can redistribute it and/or modify   #
+# it under the terms of the GNU General Public License as published by   #
+# the Free Software Foundation, either version 3 of the License, or      #
+# (at your option) any later version.                                    #
+#                                                                        #
+# This program is distributed in the hope that it will be useful,        #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of         #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the          #
+# GNU General Public License for more details.                           #
+#                                                                        #
+# You should have received a copy of the GNU General Public License      #
+# along with this program.  If not, see <https://www.gnu.org/licenses/>. #
+##########################################################################
+"""
+Package to test the openlp.core.projectors.pjlink command routing.
+"""
+
+from unittest import TestCase
+from unittest.mock import call, patch
+
+import openlp.core.projectors.pjlink
+from openlp.core.projectors.pjlinkcommands import process_command
+from openlp.core.projectors.constants import E_UNDEFINED, S_DATA_OK
+from openlp.core.projectors.db import Projector
+from openlp.core.projectors.pjlink import PJLink
+from tests.resources.projector.data import TEST1_DATA
+
+
+class TestPJLinkRouting(TestCase):
+    """
+    Tests for the PJLink module command routing
+    """
+    def setUp(self):
+        """
+        Setup test environment
+        """
+        self.pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True)
+
+    def tearDown(self):
+        """
+        Reset test environment
+        """
+        del(self.pjlink)
+
+    @patch.object(openlp.core.projectors.pjlinkcommands, 'log')
+    def test_routing_command(self, mock_log):
+        """
+        Test process_command receiving command not in function map
+        """
+        # GIVEN: Test setup
+        log_warning_text = []
+        log_debug_text = [call('({ip}) Processing command "CLSS" with data "1"'.format(ip=self.pjlink.name)),
+                          call('({ip}) Calling function for CLSS'.format(ip=self.pjlink.name)),
+                          call('({ip}) Setting pjlink_class for this projector to "1"'.format(ip=self.pjlink.name))]
+
+        # WHEN: called with valid command and data
+        chk = process_command(projector=self.pjlink, cmd='CLSS', data='1')
+
+        # THEN: Appropriate log entries should have been made and methods called/not called
+        mock_log.warning.assert_has_calls(log_warning_text)
+        mock_log.debug.assert_has_calls(log_debug_text)
+        assert (chk is None), 'process_clss() should have returned None'
+
+    @patch.object(openlp.core.projectors.pjlinkcommands, 'process_clss')
+    @patch.object(openlp.core.projectors.pjlinkcommands, 'pjlink_functions')
+    @patch.object(openlp.core.projectors.pjlinkcommands, 'log')
+    def test_routing_command_unknown(self, mock_log, mock_functions, mock_clss):
+        """
+        Test process_command receiving command not in function map
+        """
+        # GIVEN: Test setup
+        log_warning_text = [call('({ip}) Unable to process command="CLSS" '
+                                 '(Future option?)'.format(ip=self.pjlink.name))]
+        log_debug_text = [call('({ip}) Processing command "CLSS" with data "?"'.format(ip=self.pjlink.name))]
+        # Fake CLSS command is not in list
+        mock_functions.__contains__.return_value = False
+
+        # WHEN: called with unknown command
+        process_command(projector=self.pjlink, cmd='CLSS', data='?')
+
+        # THEN: Appropriate log entries should have been made and methods called/not called
+        mock_log.warning.assert_has_calls(log_warning_text)
+        mock_log.debug.assert_has_calls(log_debug_text)
+        assert (mock_functions.__contains__.call_count == 1), 'pjlink_functions should have been accessed only once'
+        assert (not mock_clss.called), 'Should not have called process_clss'
+
+    @patch.object(openlp.core.projectors.pjlink.PJLink, 'send_command')
+    @patch.object(openlp.core.projectors.pjlinkcommands, 'process_clss')
+    @patch.object(openlp.core.projectors.pjlinkcommands, 'log')
+    def test_routing_data_ok(self, mock_log, mock_clss, mock_send):
+        """
+        Test process_command calls function and sets appropriate value(s) in projector instance
+        """
+        # GIVEN: Test setup
+        log_debug_text = [call('({ip}) Processing command "CLSS" with data "OK"'.format(ip=self.pjlink.name)),
+                          call('({ip}) Command "CLSS" returned OK'.format(ip=self.pjlink.name))
+                          ]
+
+        # WHEN: Command called with OK
+        chk = process_command(projector=self.pjlink, cmd='CLSS', data='OK')
+
+        # THEN: Appropriate log entries should have been made and methods called/not called
+        mock_log.debug.asset_has_calls(log_debug_text)
+        mock_send.assert_not_called()
+        mock_clss.assert_not_called()
+        assert (chk == S_DATA_OK), 'Should have returned S_DATA_OK'
+
+    @patch.object(openlp.core.projectors.pjlinkcommands, 'log')
+    def test_routing_pjink_errors(self, mock_log):
+        """
+        Test rouing when PJLink error received (err1, err2, err3, err4, erra)
+        """
+        # GIVEN: Test setup
+        log_error_text = [call('({ip}) CLSS: PJLink returned "ERR1: Undefined Command"'.format(ip=self.pjlink.name))]
+        log_debug_text = [call('({ip}) Processing command "CLSS" with data "ERR1"'.format(ip=self.pjlink.name))]
+        err_code = E_UNDEFINED
+
+        # WHEN: routing called
+        chk = process_command(projector=self.pjlink, cmd='CLSS', data='ERR1')
+
+        # THEN: Appropriate log entries should have been made and methods called/not called
+        mock_log.error.assert_has_calls(log_error_text)
+        mock_log.debug.assert_has_calls(log_debug_text)
+        assert (chk == err_code), 'Should have returned E_UNDEFINED'

=== modified file 'tests/openlp_core/projectors/test_projector_constants.py'
--- tests/openlp_core/projectors/test_projector_constants.py	2019-04-13 13:00:22 +0000
+++ tests/openlp_core/projectors/test_projector_constants.py	2019-05-04 05:43:42 +0000
@@ -40,11 +40,12 @@
         missing_str = []
 
         # GIVEN: List of defined E_* and S_* items defined in constants
+
         for item in constants.__dict__:
             if item.startswith('E_') or item.startswith('S_'):
                 check.append(item)
 
-        # WHEN: Verify defined list against STATUS_STR
+        # WHEN: Verify items were addeded to check
         for item in check:
             if constants.__dict__[item] not in STATUS_CODE:
                 missing_str.append(item)

=== modified file 'tests/openlp_core/projectors/test_projector_pjlink_base_02.py'
--- tests/openlp_core/projectors/test_projector_pjlink_base_02.py	2019-04-28 19:21:23 +0000
+++ tests/openlp_core/projectors/test_projector_pjlink_base_02.py	2019-05-04 05:43:42 +0000
@@ -454,9 +454,9 @@
                                                            suff=PJLINK_SUFFIX)
         log_error_calls = []
         log_warning_calls = []
-        log_debug_calls = [call('({ip}) send_command(): Building cmd="CLSS" opts="?"'.format(ip=self.pjlink.name)),
+        log_debug_calls = [call('({ip}) send_command(): Building cmd="CLSS" opts="?" '.format(ip=self.pjlink.name)),
                            call('({ip}) send_command(): Adding to normal queue'.format(ip=self.pjlink.name))]
-        mock_state.return_value = S_CONNECTED
+        mock_state.return_value = QSOCKET_STATE[S_CONNECTED]
 
         # Patch here since pjlink does not have priority or send queue's until instantiated
         with patch.object(self.pjlink, 'send_queue') as mock_send, \
@@ -488,9 +488,9 @@
                                                            suff=PJLINK_SUFFIX)
         log_error_calls = []
         log_warning_calls = []
-        log_debug_calls = [call('({ip}) send_command(): Building cmd="CLSS" opts="?"'.format(ip=self.pjlink.name)),
+        log_debug_calls = [call('({ip}) send_command(): Building cmd="CLSS" opts="?" '.format(ip=self.pjlink.name)),
                            call('({ip}) send_command(): Adding to priority queue'.format(ip=self.pjlink.name))]
-        mock_state.return_value = S_CONNECTED
+        mock_state.return_value = QSOCKET_STATE[S_CONNECTED]
 
         # Patch here since pjlink does not have priority or send queue's until instantiated
         with patch.object(self.pjlink, 'send_queue') as mock_send, \
@@ -523,8 +523,10 @@
         log_error_calls = []
         log_warning_calls = [call('({ip}) send_command(): Already in normal queue - '
                                   'skipping'.format(ip=self.pjlink.name))]
-        log_debug_calls = [call('({ip}) send_command(): Building cmd="CLSS" opts="?"'.format(ip=self.pjlink.name))]
-        mock_state.return_value = S_CONNECTED
+        log_debug_calls = [call('({ip}) send_command(cmd="CLSS" opts="?" salt="None" '
+                                'priority=False'.format(ip=self.pjlink.name)),
+                           call('({ip}) send_command(): Building cmd="CLSS" opts="?" '.format(ip=self.pjlink.name))]
+        mock_state.return_value = QSOCKET_STATE[S_CONNECTED]
         self.pjlink.send_queue = [test_command]
         self.pjlink.priority_queue = []
 
@@ -555,8 +557,10 @@
         log_error_calls = []
         log_warning_calls = [call('({ip}) send_command(): Already in priority queue - '
                                   'skipping'.format(ip=self.pjlink.name))]
-        log_debug_calls = [call('({ip}) send_command(): Building cmd="CLSS" opts="?"'.format(ip=self.pjlink.name))]
-        mock_state.return_value = S_CONNECTED
+        log_debug_calls = [call('({ip}) send_command(cmd="CLSS" opts="?" salt="None" '
+                                'priority=True'.format(ip=self.pjlink.name)),
+                           call('({ip}) send_command(): Building cmd="CLSS" opts="?" '.format(ip=self.pjlink.name))]
+        mock_state.return_value = QSOCKET_STATE[S_CONNECTED]
         self.pjlink.send_queue = []
         self.pjlink.priority_queue = [test_command]
 
@@ -585,7 +589,7 @@
                                 'ignoring.'.format(ip=self.pjlink.name))]
         log_warning_calls = []
         log_debug_calls = []
-        mock_state.return_value = S_CONNECTED
+        mock_state.return_value = QSOCKET_STATE[S_CONNECTED]
         self.pjlink.send_queue = []
         self.pjlink.priority_queue = []
 
@@ -617,7 +621,7 @@
                                 'ignoring.'.format(ip=self.pjlink.name))]
         log_warning_calls = []
         log_debug_calls = []
-        mock_state.return_value = S_CONNECTED
+        mock_state.return_value = QSOCKET_STATE[S_CONNECTED]
         self.pjlink.send_queue = [test_command]
         self.pjlink.priority_queue = []
 
@@ -649,7 +653,7 @@
                                 'ignoring.'.format(ip=self.pjlink.name))]
         log_warning_calls = []
         log_debug_calls = []
-        mock_state.return_value = S_CONNECTED
+        mock_state.return_value = QSOCKET_STATE[S_CONNECTED]
         self.pjlink.send_queue = []
         self.pjlink.priority_queue = [test_command]
 

=== modified file 'tests/openlp_core/projectors/test_projector_pjlink_cmd_routing.py'
--- tests/openlp_core/projectors/test_projector_pjlink_cmd_routing.py	2019-04-28 19:21:23 +0000
+++ tests/openlp_core/projectors/test_projector_pjlink_cmd_routing.py	2019-05-04 05:43:42 +0000
@@ -24,10 +24,9 @@
 """
 
 from unittest import TestCase, skip
-from unittest.mock import MagicMock, call, patch
+from unittest.mock import call, patch
 
 import openlp.core.projectors.pjlink
-from openlp.core.projectors.pjlinkcommands import process_command
 from openlp.core.projectors.constants import E_AUTHENTICATION, E_PARAMETER, E_PROJECTOR, E_UNAVAILABLE, E_UNDEFINED, \
     PJLINK_ERRORS, PJLINK_PREFIX, STATUS_MSG
 from openlp.core.projectors.db import Projector
@@ -51,82 +50,93 @@
         """
         del(self.pjlink)
 
-    @patch.object(openlp.core.projectors.pjlink, 'log')
-    def test_get_data_unknown_command(self, mock_log):
-        """
-        Test not a valid command
-        """
-        # GIVEN: Test object
-        self.pjlink.pjlink_functions = MagicMock()
+    @patch.object(openlp.core.projectors.pjlinkcommands, 'process_command')
+    @patch.object(openlp.core.projectors.pjlink, 'log')
+    def test_projector_get_data_invalid_version(self, mock_log, mock_process_cmd):
+        """
+        Test projector received valid command invalid version
+        """
+        # GIVEN: Test object
+        log_warning_text = [call('({ip}) get_data() Command reply version does not match '
+                                 'a valid command version'.format(ip=self.pjlink.name)),
+                            call('({ip}) _send_command(): Nothing to send - returning'.format(ip=self.pjlink.name))]
+        log_debug_text = [call('({ip}) get_data(buffer="{pre}XCLSS=X"'.format(ip=self.pjlink.name, pre=PJLINK_PREFIX)),
+                          call('({ip}) get_data(): Checking new data "{pre}XCLSS=X"'.format(ip=self.pjlink.name,
+                                                                                            pre=PJLINK_PREFIX)),
+                          call('({ip}) get_data() header="{pre}XCLSS" data="X"'.format(ip=self.pjlink.name,
+                                                                                       pre=PJLINK_PREFIX)),
+                          call('({ip}) get_data() version="X" cmd="CLSS" data="X"'.format(ip=self.pjlink.name)),
+                          call('({ip}) Cleaning buffer - msg = "get_data() Command reply version does '
+                               'not match a valid command version"'.format(ip=self.pjlink.name)),
+                          call('({ip}) Finished cleaning buffer - 0 bytes dropped'.format(ip=self.pjlink.name))]
+        # WHEN: get_data called with an unknown command
+        self.pjlink.get_data(buff='{prefix}XCLSS=X'.format(prefix=PJLINK_PREFIX))
+
+        # THEN: Appropriate log entries should have been made and methods called/not called
+        mock_log.warning.assert_has_calls(log_warning_text)
+        mock_log.debug.assert_has_calls(log_debug_text)
+        assert (mock_process_cmd.call_count == 0), 'process_command should not have been called'
+
+    @patch.object(openlp.core.projectors.pjlinkcommands, 'process_command')
+    @patch.object(openlp.core.projectors.pjlink, 'log')
+    def test_projector_get_data_unknown_command(self, mock_log, mock_process_cmd):
+        """
+        Test projector receiving invalid command
+        """
+        # GIVEN: Test object
         log_warning_text = [call('({ip}) get_data(): Invalid packet - '
                                  'unknown command "UNKN"'.format(ip=self.pjlink.name)),
                             call('({ip}) _send_command(): Nothing to send - '
                                  'returning'.format(ip=self.pjlink.name))]
-        log_debug_text = [call('(___TEST_ONE___) get_data(buffer="%1UNKN=Huh?"'),
-                          call('(___TEST_ONE___) get_data(): Checking new data "%1UNKN=Huh?"'),
-                          call('(___TEST_ONE___) get_data() header="%1UNKN" data="Huh?"'),
-                          call('(___TEST_ONE___) get_data() version="1" cmd="UNKN"'),
-                          call('(___TEST_ONE___) Cleaning buffer - msg = "get_data(): '
-                               'Invalid packet - unknown command "UNKN""'),
-                          call('(___TEST_ONE___) Finished cleaning buffer - 0 bytes dropped')]
+        log_debug_text = [call('({ip}) get_data(buffer="{pre}1UNKN=Huh?"'.format(ip=self.pjlink.name,
+                                                                                 pre=PJLINK_PREFIX)),
+                          call('({ip}) get_data(): Checking new data "{pre}1UNKN=Huh?"'.format(ip=self.pjlink.name,
+                                                                                               pre=PJLINK_PREFIX)),
+                          call('({ip}) get_data() header="{pre}1UNKN" data="Huh?"'.format(ip=self.pjlink.name,
+                                                                                          pre=PJLINK_PREFIX)),
+                          call('({ip}) get_data() version="1" cmd="UNKN" data="Huh?"'.format(ip=self.pjlink.name)),
+                          call('({ip}) Cleaning buffer - msg = "get_data(): Invalid packet - '
+                               'unknown command "UNKN""'.format(ip=self.pjlink.name)),
+                          call('({ip}) Finished cleaning buffer - 0 bytes dropped'.format(ip=self.pjlink.name))]
+
         # WHEN: get_data called with an unknown command
         self.pjlink.get_data(buff='{prefix}1UNKN=Huh?'.format(prefix=PJLINK_PREFIX))
 
         # THEN: Appropriate log entries should have been made and methods called/not called
         mock_log.warning.assert_has_calls(log_warning_text)
         mock_log.debug.assert_has_calls(log_debug_text)
-        assert self.pjlink.pjlink_functions.called is False, 'Should not have accessed pjlink_functions'
-
-    @skip('Needs update to new setup')
-    @patch("openlp.core.projectors.pjlink.log")
-    def test_process_command_call_clss(self, mock_log):
-        """
-        Test process_command calls proper function
-        """
-        # GIVEN: Test object and mocks
-        with patch.object(openlp.core.projectors.pjlink, 'log') as mock_log, \
-                patch.object(openlp.core.projectors.pjlink.PJLink, 'process_clss') as mock_process_clss:
-
-            log_debug_calls = [call('({ip}) Processing command "CLSS" with data "1"'.format(ip=self.pjlink.name)),
-                               call('({ip}) Calling function for CLSS'.format(ip=self.pjlink.name))]
-
-            # WHEN: process_command is called with valid function and data
-            process_command(projector=self.pjlink, cmd='CLSS', data='1')
-
-            # THEN: Appropriate log entries should have been made and methods called
-            mock_log.debug.assert_has_calls(log_debug_calls)
-            mock_process_clss.assert_called_once_with(data='1')
-
-    @skip('Needs update to new setup')
-    def test_process_command_erra(self):
-        """
-        Test ERRA - Authentication Error
+        assert (mock_process_cmd.call_count == 0), 'process_command should not have been called'
+
+    @patch.object(openlp.core.projectors.pjlinkcommands, 'process_command')
+    @patch.object(openlp.core.projectors.pjlink, 'log')
+    def test_projector_get_data_version_mismatch(self, mock_log, mock_process_cmd):
+        """
+        Test projector received valid command with command version higher than projector
         """
         # GIVEN: Test object
-        with patch.object(openlp.core.projectors.pjlink, 'log') as mock_log, \
-                patch.object(openlp.core.projectors.pjlink.PJLink, 'process_pjlink') as mock_process_pjlink, \
-                patch.object(openlp.core.projectors.pjlink.PJLink, 'change_status') as mock_change_status, \
-                patch.object(openlp.core.projectors.pjlink.PJLink, 'disconnect_from_host') as mock_disconnect, \
-                patch.object(openlp.core.projectors.pjlink.PJLink, 'projectorAuthentication') as mock_authentication:
-
-            pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True)
-            log_error_calls = [call('({ip}) PJLINK: {msg}'.format(ip=self.pjlink.name,
-                                                                  msg=STATUS_MSG[E_AUTHENTICATION]))]
-            log_debug_calls = [call('({ip}) Processing command "PJLINK" with data "ERRA"'.format(ip=self.pjlink.name))]
-
-            # WHEN: process_command called with ERRA
-            pjlink.process_command(cmd='PJLINK', data=PJLINK_ERRORS[E_AUTHENTICATION])
-
-            # THEN: Appropriate log entries should have been made and methods called/not called
-            assert mock_disconnect.called is True, 'disconnect_from_host should have been called'
-            mock_log.error.assert_has_calls(log_error_calls)
-            mock_log.debug.assert_has_calls(log_debug_calls)
-            mock_change_status.assert_called_once_with(status=E_AUTHENTICATION)
-            mock_authentication.emit.assert_called_once_with(pjlink.name)
-            mock_process_pjlink.assert_not_called()
+        log_warning_text = [call('({ip}) get_data(): Projector returned class reply higher than projector '
+                                 'stated class'.format(ip=self.pjlink.name)),
+                            call('({ip}) _send_command(): Nothing to send - returning'.format(ip=self.pjlink.name))]
+
+        log_debug_text = [call('({ip}) get_data(buffer="{pre}2ACKN=X"'.format(ip=self.pjlink.name,
+                                                                              pre=PJLINK_PREFIX)),
+                          call('({ip}) get_data(): Checking new data "{pre}2ACKN=X"'.format(ip=self.pjlink.name,
+                                                                                            pre=PJLINK_PREFIX)),
+                          call('({ip}) get_data() header="{pre}2ACKN" data="X"'.format(ip=self.pjlink.name,
+                                                                                       pre=PJLINK_PREFIX)),
+                          call('({ip}) get_data() version="2" cmd="ACKN" data="X"'.format(ip=self.pjlink.name))]
+        self.pjlink.pjlink_class = '1'
+
+        # WHEN: get_data called with an unknown command
+        self.pjlink.get_data(buff='{prefix}2ACKN=X'.format(prefix=PJLINK_PREFIX))
+
+        # THEN: Appropriate log entries should have been made and methods called/not called
+        mock_log.warning.assert_has_calls(log_warning_text)
+        mock_log.debug.assert_has_calls(log_debug_text)
+        assert (mock_process_cmd.call_count == 0), 'process_command should not have been called'
 
     @skip('Needs update to new setup')
-    def test_process_command_err1(self):
+    def test_routing_err1(self):
         """
         Test ERR1 - Undefined projector function
         """
@@ -148,7 +158,7 @@
             mock_process_clss.assert_called_once_with(data=PJLINK_ERRORS[E_UNDEFINED])
 
     @skip('Needs update to new setup')
-    def test_process_command_err2(self):
+    def test_routing_err2(self):
         """
         Test ERR2 - Parameter Error
         """
@@ -170,7 +180,7 @@
             mock_process_clss.assert_called_once_with(data=PJLINK_ERRORS[E_PARAMETER])
 
     @skip('Needs update to new setup')
-    def test_process_command_err3(self):
+    def test_routing_err3(self):
         """
         Test ERR3 - Unavailable error
         """
@@ -192,7 +202,7 @@
             mock_process_clss.assert_called_once_with(data=PJLINK_ERRORS[E_UNAVAILABLE])
 
     @skip('Needs update to new setup')
-    def test_process_command_err4(self):
+    def test_routing_err4(self):
         """
         Test ERR3 - Unavailable error
         """
@@ -214,48 +224,29 @@
             mock_process_clss.assert_called_once_with(data=PJLINK_ERRORS[E_PROJECTOR])
 
     @skip('Needs update to new setup')
-    def test_process_command_future(self):
+    def test_routing_erra(self):
         """
-        Test command valid but no method to process yet
+        Test ERRA - Authentication Error
         """
         # GIVEN: Test object
         with patch.object(openlp.core.projectors.pjlink, 'log') as mock_log, \
-                patch.object(openlp.core.projectors.pjlink.PJLink, 'process_clss') as mock_process_clss:
+                patch.object(openlp.core.projectors.pjlink.PJLink, 'process_pjlink') as mock_process_pjlink, \
+                patch.object(openlp.core.projectors.pjlink.PJLink, 'change_status') as mock_change_status, \
+                patch.object(openlp.core.projectors.pjlink.PJLink, 'disconnect_from_host') as mock_disconnect, \
+                patch.object(openlp.core.projectors.pjlink.PJLink, 'projectorAuthentication') as mock_authentication:
 
             pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True)
-            pjlink.pjlink_functions = MagicMock()
-            log_warning_text = [call('({ip}) Unable to process command="CLSS" '
-                                     '(Future option?)'.format(ip=self.pjlink.name))]
-            log_debug_text = [call('({ip}) Processing command "CLSS" '
-                                   'with data "Huh?"'.format(ip=self.pjlink.name))]
+            log_error_calls = [call('({ip}) PJLINK: {msg}'.format(ip=self.pjlink.name,
+                                                                  msg=STATUS_MSG[E_AUTHENTICATION]))]
+            log_debug_calls = [call('({ip}) Processing command "PJLINK" with data "ERRA"'.format(ip=self.pjlink.name))]
 
-            # WHEN: Processing a possible future command
-            pjlink.process_command(cmd='CLSS', data="Huh?")
+            # WHEN: process_command called with ERRA
+            pjlink.process_command(cmd='PJLINK', data=PJLINK_ERRORS[E_AUTHENTICATION])
 
             # THEN: Appropriate log entries should have been made and methods called/not called
-            mock_log.debug.assert_has_calls(log_debug_text)
-            mock_log.warning.assert_has_calls(log_warning_text)
-            assert pjlink.pjlink_functions.called is False, 'Should not have accessed pjlink_functions'
-            assert mock_process_clss.called is False, 'Should not have called process_clss'
-
-    @skip('Needs update to new setup')
-    def test_process_command_ok(self):
-        """
-        Test command returned success
-        """
-        # GIVEN: Test object and mocks
-        with patch.object(openlp.core.projectors.pjlink, 'log') as mock_log, \
-                patch.object(openlp.core.projectors.pjlink.PJLink, 'send_command') as mock_send_command, \
-                patch.object(openlp.core.projectors.pjlink.PJLink, 'process_clss') as mock_process_clss:
-
-            pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True)
-            log_debug_calls = [call('({ip}) Processing command "CLSS" with data "OK"'.format(ip=self.pjlink.name)),
-                               call('({ip}) Command "CLSS" returned OK'.format(ip=self.pjlink.name))]
-
-            # WHEN: process_command is called with valid function and data
-            pjlink.process_command(cmd='CLSS', data='OK')
-
-            # THEN: Appropriate log entries should have been made and methods called
+            assert mock_disconnect.called is True, 'disconnect_from_host should have been called'
+            mock_log.error.assert_has_calls(log_error_calls)
             mock_log.debug.assert_has_calls(log_debug_calls)
-            mock_send_command.assert_called_once_with(cmd='CLSS')
-            mock_process_clss.assert_not_called()
+            mock_change_status.assert_called_once_with(status=E_AUTHENTICATION)
+            mock_authentication.emit.assert_called_once_with(pjlink.name)
+            mock_process_pjlink.assert_not_called()


Follow ups