# -*- coding: utf-8 -*- ########################################################################## # OpenLP - Open Source Lyrics Projection # # ---------------------------------------------------------------------- # # Copyright (c) 2008-2022 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 . # ########################################################################## """ Test PJLink.get_data 01 """ import logging import openlp.core.common import openlp.core.projectors.pjlink from unittest.mock import DEFAULT, patch from openlp.core.projectors.constants import PJLINK_MAX_PACKET, PJLINK_PREFIX, \ E_AUTHENTICATION, S_AUTHENTICATE, S_CONNECT, S_CONNECTED, S_DATA_OK from tests.resources.projector.data import TEST_HASH, TEST_PIN, TEST_SALT test_module = openlp.core.projectors.pjlink.__name__ test_qmd5 = openlp.core.common.__name__ def test_buff_short(pjlink, caplog): """ Test method with invalid short buffer """ # GIVEN: Initial setup t_buff = "short" t_err = 'get_data(): Invalid packet - length' logs = [(f"{test_module}", logging.DEBUG, f'({pjlink.entry.name}) get_data(buffer="{t_buff}"'), (f'{test_module}', logging.DEBUG, f'({pjlink.entry.name}) Setting ignore_class to "False"')] with patch.object(openlp.core.projectors.pjlink, "process_command") as mock_command, \ patch.multiple(pjlink, _trash_buffer=DEFAULT, receive_data_signal=DEFAULT) as mock_pjlink: # WHEN: get_data called caplog.set_level(logging.DEBUG) t_check = pjlink.get_data(buff=t_buff) # THEN: t_check should be None and log entry made assert t_check is None, "Invalid return code" assert caplog.record_tuples == logs, "Invalid log entries" mock_pjlink['_trash_buffer'].assert_called_once_with(msg=t_err) mock_pjlink['receive_data_signal'].assert_called_once() mock_command.assert_not_called() def test_buff_long(pjlink, caplog): """ Test method with invalid long buffer """ # GIVEN: Initial setup t_buff = "X" * (PJLINK_MAX_PACKET + 10) t_err = f'get_data(): Invalid packet - too long ({len(t_buff)} bytes)' logs = [(f"{test_module}", logging.DEBUG, f'({pjlink.entry.name}) get_data(buffer="{t_buff}"'), (f'{test_module}', logging.DEBUG, f'({pjlink.entry.name}) Setting ignore_class to "False"')] with patch.object(openlp.core.projectors.pjlink, "process_command") as mock_command, \ patch.multiple(pjlink, _trash_buffer=DEFAULT, receive_data_signal=DEFAULT) as mock_pjlink: # WHEN: get_data called caplog.set_level(logging.DEBUG) t_check = pjlink.get_data(buff=t_buff) # THEN: t_check should be None and log entry made assert t_check is None, "Invalid return code" assert caplog.record_tuples == logs, "Invalid log entries" mock_pjlink['_trash_buffer'].assert_called_once_with(msg=t_err) mock_pjlink['receive_data_signal'].assert_called_once() mock_command.assert_not_called() def test_invalid_prefix(pjlink, caplog): """ Test method with invalid PJLink prefix character """ # GIVEN: Initial setup t_buff = "#1CLSS=OK" t_err = 'get_data(): Invalid packet - PJLink prefix missing' logs = [(f"{test_module}", logging.DEBUG, f'({pjlink.entry.name}) get_data(buffer="{t_buff}"'), (f'{test_module}', logging.DEBUG, f'({pjlink.entry.name}) Setting ignore_class to "False"')] with patch.object(openlp.core.projectors.pjlink, "process_command") as mock_command, \ patch.multiple(pjlink, _trash_buffer=DEFAULT, receive_data_signal=DEFAULT) as mock_pjlink: # WHEN: get_data called caplog.set_level(logging.DEBUG) t_check = pjlink.get_data(buff=t_buff) # THEN: t_check should be None and log entry made assert t_check is None, "Invalid return code" assert caplog.record_tuples == logs, "Invalid log entries" mock_pjlink['_trash_buffer'].assert_called_once_with(msg=t_err) mock_pjlink['receive_data_signal'].assert_called_once() mock_command.assert_not_called() def test_missing_equal(pjlink, caplog): """ Test method with missing command/data separator """ # GIVEN: Initial setup t_buff = f"{PJLINK_PREFIX}1CLSS OK" t_err = 'get_data(): Invalid reply - Does not have "="' logs = [(f"{test_module}", logging.DEBUG, f'({pjlink.entry.name}) get_data(buffer="{t_buff}"'), (f'{test_module}', logging.DEBUG, f'({pjlink.entry.name}) Setting ignore_class to "False"')] with patch.object(openlp.core.projectors.pjlink, "process_command") as mock_command, \ patch.multiple(pjlink, _trash_buffer=DEFAULT, receive_data_signal=DEFAULT) as mock_pjlink: # WHEN: get_data called caplog.set_level(logging.DEBUG) t_check = pjlink.get_data(buff=t_buff) # THEN: t_check should be None and log entry made assert t_check is None, "Invalid return code" assert caplog.record_tuples == logs, "Invalid log entries" mock_pjlink['_trash_buffer'].assert_called_once_with(msg=t_err) mock_pjlink['receive_data_signal'].assert_called_once() mock_command.assert_not_called() def test_invalid_command(pjlink, caplog): """ Test method with invalid command """ # GIVEN: Initial setup t_cmd = "CLASSS" t_ver = "1" t_data = "1" t_buff = f"{PJLINK_PREFIX}{t_ver}{t_cmd}={t_data}" t_err = f'get_data(): Invalid packet - unknown command "{t_cmd}"' logs = [(f"{test_module}", logging.DEBUG, f'({pjlink.entry.name}) get_data(buffer="{t_buff}"'), (f'{test_module}', logging.DEBUG, f'({pjlink.entry.name}) Setting ignore_class to "False"'), (f'{test_module}', logging.DEBUG, f'({pjlink.entry.name}) get_data(): Checking new data "{t_buff}"'), (f'{test_module}', logging.DEBUG, f'({pjlink.entry.name}) get_data() header="{PJLINK_PREFIX}{t_ver}{t_cmd}" data="{t_data}"'), (f'{test_module}', logging.DEBUG, f'({pjlink.entry.name}) get_data() version="{t_ver}" cmd="{t_cmd}" data="{t_data}"') ] with patch.object(openlp.core.projectors.pjlink, "process_command") as mock_command, \ patch.multiple(pjlink, _trash_buffer=DEFAULT, receive_data_signal=DEFAULT) as mock_pjlink: # WHEN: get_data called caplog.set_level(logging.DEBUG) t_check = pjlink.get_data(buff=t_buff) # THEN: t_check should be None and log entry made assert t_check is None, "Invalid return code" assert caplog.record_tuples == logs, "Invalid log entries" mock_pjlink['_trash_buffer'].assert_called_once_with(msg=t_err) mock_pjlink['receive_data_signal'].assert_called_once() mock_command.assert_not_called() def test_mismatch_command_versions(pjlink, caplog): """ Test method with pjlink.pjlink_class < cmd.class """ # GIVEN: Initial setup t_cmd = "CLSS" t_ver = "2" t_data = "1" t_buff = f"{PJLINK_PREFIX}{t_ver}{t_cmd}={t_data}" t_err = 'get_data() Command reply version does not match a valid command version' logs = [(f"{test_module}", logging.DEBUG, f'({pjlink.entry.name}) get_data(buffer="{t_buff}"'), (f'{test_module}', logging.DEBUG, f'({pjlink.entry.name}) Setting ignore_class to "False"'), (f'{test_module}', logging.DEBUG, f'({pjlink.entry.name}) get_data(): Checking new data "{t_buff}"'), (f'{test_module}', logging.DEBUG, f'({pjlink.entry.name}) get_data() header="{PJLINK_PREFIX}{t_ver}{t_cmd}" data="{t_data}"'), (f'{test_module}', logging.DEBUG, f'({pjlink.entry.name}) get_data() version="{t_ver}" cmd="{t_cmd}" data="{t_data}"') ] with patch.object(openlp.core.projectors.pjlink, "process_command") as mock_command, \ patch.multiple(pjlink, _trash_buffer=DEFAULT, receive_data_signal=DEFAULT) as mock_pjlink: # WHEN: get_data called caplog.set_level(logging.DEBUG) t_check = pjlink.get_data(buff=t_buff) # THEN: t_check should be None and log entry made assert t_check is None, "Invalid return code" assert caplog.record_tuples == logs, "Invalid log entries" mock_pjlink['receive_data_signal'].assert_called_once() mock_pjlink['_trash_buffer'].assert_called_once_with(msg=t_err) mock_command.assert_not_called() def test_mismatch_class_versions(pjlink, caplog): """ Test method with pjlink.pjlink_class < cmd.class """ # GIVEN: Initial setup t_cmd = "FILT" t_ver = "2" t_data = "2" t_buff = f"{PJLINK_PREFIX}{t_ver}{t_cmd}={t_data}" logs = [(f"{test_module}", logging.DEBUG, f'({pjlink.entry.name}) get_data(buffer="{t_buff}"'), (f'{test_module}', logging.DEBUG, f'({pjlink.entry.name}) Setting ignore_class to "False"'), (f'{test_module}', logging.DEBUG, f'({pjlink.entry.name}) get_data(): Checking new data "{t_buff}"'), (f'{test_module}', logging.DEBUG, f'({pjlink.entry.name}) get_data() header="{PJLINK_PREFIX}{t_ver}{t_cmd}" data="{t_data}"'), (f'{test_module}', logging.DEBUG, f'({pjlink.entry.name}) get_data() version="{t_ver}" cmd="{t_cmd}" data="{t_data}"'), (f'{test_module}', logging.WARNING, f'({pjlink.entry.name}) get_data(): pjlink_class={pjlink.pjlink_class} packet={t_ver}'), (f'{test_module}', logging.WARNING, f'({pjlink.entry.name}) get_data(): Projector returned class reply higher than projector stated class') ] with patch.object(openlp.core.projectors.pjlink, "process_command") as mock_command, \ patch.multiple(pjlink, _trash_buffer=DEFAULT, receive_data_signal=DEFAULT) as mock_pjlink: pjlink.pjlink_class = 1 # Set class to 1 and call with cmd class = 2 # WHEN: get_data called caplog.set_level(logging.DEBUG) t_check = pjlink.get_data(buff=t_buff) # THEN: t_check should be None and log entry made assert t_check is None, "Invalid return code" assert caplog.record_tuples == logs, "Invalid log entries" mock_pjlink['receive_data_signal'].assert_called_once() mock_command.assert_not_called() def test_ignore_class_versions(pjlink, caplog): """ Test method with pjlink.pjlink_class < cmd.class """ # GIVEN: Initial setup t_cmd = "FILT" t_ver = "2" t_data = "2" t_buff = f"{PJLINK_PREFIX}{t_ver}{t_cmd}={t_data}" logs = [(f"{test_module}", logging.DEBUG, f'({pjlink.entry.name}) get_data(buffer="{t_buff}"'), (f'{test_module}', logging.DEBUG, f'({pjlink.entry.name}) Setting ignore_class to "True"'), (f'{test_module}', logging.DEBUG, f'({pjlink.entry.name}) get_data(): Checking new data "{t_buff}"'), (f'{test_module}', logging.DEBUG, f'({pjlink.entry.name}) get_data() header="{PJLINK_PREFIX}{t_ver}{t_cmd}" data="{t_data}"'), (f'{test_module}', logging.DEBUG, f'({pjlink.entry.name}) get_data() version="{t_ver}" cmd="{t_cmd}" data="{t_data}"'), (f'{test_module}', logging.WARNING, f'({pjlink.entry.name}) get_data(): pjlink_class={pjlink.pjlink_class} packet={t_ver}') ] with patch.object(openlp.core.projectors.pjlink, "process_command") as mock_command, \ patch.multiple(pjlink, _trash_buffer=DEFAULT, receive_data_signal=DEFAULT) as mock_pjlink: pjlink.pjlink_class = 1 # Set class to 1 and call with cmd class = 2 mock_command.return_value = None # WHEN: get_data called with ignore_class=True caplog.set_level(logging.DEBUG) t_check = pjlink.get_data(buff=t_buff, ignore_class=True) # THEN: t_check should be None and log entry made assert t_check is None, "Invalid return code" assert caplog.record_tuples == logs, "Invalid log entries" mock_pjlink['receive_data_signal'].assert_called_once() mock_command.assert_called_with(pjlink, t_cmd, t_data) def test_s_data_ok(pjlink, caplog): """ Test projector returns "OK" """ t_cmd = "INPT" t_ver = "1" t_data = "OK" t_buff = f"{PJLINK_PREFIX}{t_ver}{t_cmd}={t_data}" logs = [(f"{test_module}", logging.DEBUG, f'({pjlink.entry.name}) get_data(buffer="{t_buff}"'), (f'{test_module}', logging.DEBUG, f'({pjlink.entry.name}) Setting ignore_class to "False"'), (f'{test_module}', logging.DEBUG, f'({pjlink.entry.name}) get_data(): Checking new data "{t_buff}"'), (f'{test_module}', logging.DEBUG, f'({pjlink.entry.name}) get_data() header="{PJLINK_PREFIX}{t_ver}{t_cmd}" data="{t_data}"'), (f'{test_module}', logging.DEBUG, f'({pjlink.entry.name}) get_data() version="{t_ver}" cmd="{t_cmd}" data="{t_data}"'), (f'{test_module}', logging.DEBUG, f'({pjlink.entry.name}) OK returned - resending command') ] with patch.object(openlp.core.projectors.pjlink, "process_command") as mock_command, \ patch.multiple(pjlink, _trash_buffer=DEFAULT, receive_data_signal=DEFAULT, send_command=DEFAULT) as mock_pjlink: mock_command.return_value = S_DATA_OK # WHEN: get_data called with OK caplog.set_level(logging.DEBUG) t_check = pjlink.get_data(buff=t_buff) # THEN: t_check should be None and log entry made assert t_check is None, "Invalid return code" assert caplog.record_tuples == logs, "Invalid log entries" mock_pjlink['receive_data_signal'].assert_called_once() mock_pjlink['_trash_buffer'].assert_not_called() mock_pjlink['send_command'].assert_called_with(cmd=t_cmd, priority=True) mock_command.assert_called_with(pjlink, t_cmd, t_data) def test_s_connect(pjlink, caplog): """ Test projector connect with no authenticate """ t_cmd = "PJLINK" t_ver = "1" t_data = "0" t_buff = f"{PJLINK_PREFIX}{t_ver}{t_cmd}={t_data}" logs = [(f"{test_module}", logging.DEBUG, f'({pjlink.entry.name}) get_data(buffer="{t_buff}"'), (f'{test_module}', logging.DEBUG, f'({pjlink.entry.name}) Setting ignore_class to "False"'), (f'{test_module}', logging.DEBUG, f'({pjlink.entry.name}) get_data(): Checking new data "{t_buff}"'), (f'{test_module}', logging.DEBUG, f'({pjlink.entry.name}) get_data() header="{PJLINK_PREFIX}{t_ver}{t_cmd}" data="{t_data}"'), (f'{test_module}', logging.DEBUG, f'({pjlink.entry.name}) get_data() version="{t_ver}" cmd="{t_cmd}" data="{t_data}"'), (f'{test_module}', logging.DEBUG, f'({pjlink.entry.name}) Connecting normal') ] with patch.object(openlp.core.projectors.pjlink, "process_command") as mock_command, \ patch.multiple(pjlink, _trash_buffer=DEFAULT, receive_data_signal=DEFAULT, send_command=DEFAULT, change_status=DEFAULT, readyRead=DEFAULT, get_socket=DEFAULT) as mock_pjlink: mock_command.return_value = S_CONNECT # WHEN: get_data called with OK caplog.set_level(logging.DEBUG) t_check = pjlink.get_data(buff=t_buff) # THEN: t_check should be None and log entry made assert t_check is None, "Invalid return code" assert caplog.record_tuples == logs, "Invalid log entries" mock_pjlink['receive_data_signal'].assert_called_once() mock_pjlink['_trash_buffer'].assert_not_called() mock_pjlink['send_command'].assert_called_with(cmd='CLSS', priority=True) mock_pjlink['change_status'].assert_called_with(S_CONNECTED) mock_pjlink['readyRead'].connect.assert_called_once_with(mock_pjlink['get_socket']) mock_command.assert_called_with(pjlink, t_cmd, t_data) def test_s_authenticate(pjlink, caplog): """ Test projector connect with no authenticate """ ''' Projector sends "PJLINK 1 " Combine salt+pin Send first command "" Projector will either: - Not reply (socket timeout) indicating invalid hash - Reply with command reply Example in documentation: - Send "" - Received "PJLINK 1 498e4a67" - Prefix = "%" - Salt = "498e4a67" - Pin = "JBMIAProjectorLink" - Hash = "5d8409bc1c3fa39749434aa3a5c38682" - Send "5d8409bc1c3fa39749434aa3a5c38682%1CLSS?" - Reply "%1CLSS=1" ''' # GIVEN: Initial setup t_cmd = "PJLINK" t_ver = "1" t_data = f"1 {TEST_SALT}" t_buff = f"{PJLINK_PREFIX}{t_ver}{t_cmd}={t_data}" logs = [(f"{test_module}", logging.DEBUG, f'({pjlink.entry.name}) get_data(buffer="{t_buff}"'), (f'{test_module}', logging.DEBUG, f'({pjlink.entry.name}) Setting ignore_class to "False"'), (f'{test_module}', logging.DEBUG, f'({pjlink.entry.name}) get_data(): Checking new data "{t_buff}"'), (f'{test_module}', logging.DEBUG, f'({pjlink.entry.name}) get_data() header="{PJLINK_PREFIX}{t_ver}{t_cmd}" data="{t_data}"'), (f'{test_module}', logging.DEBUG, f'({pjlink.entry.name}) get_data() version="{t_ver}" cmd="{t_cmd}" data="{t_data}"'), (f'{test_module}', logging.DEBUG, f'({pjlink.entry.name}) Connecting with pin'), (f'{test_qmd5}.__init__', logging.DEBUG, f'qmd5_hash(salt="b\'{TEST_SALT}\'"'), (f'{test_qmd5}.__init__', logging.DEBUG, f'qmd5_hash() returning "b\'{TEST_HASH}\'"') ] with patch.object(openlp.core.projectors.pjlink, "process_command") as mock_command, \ patch.multiple(pjlink, _trash_buffer=DEFAULT, receive_data_signal=DEFAULT, send_command=DEFAULT, change_status=DEFAULT, readyRead=DEFAULT, get_socket=DEFAULT) as mock_pjlink: mock_command.return_value = S_AUTHENTICATE pjlink.pin = TEST_PIN # WHEN: get_data called with OK caplog.set_level(logging.DEBUG) t_check = pjlink.get_data(buff=t_buff) # THEN: t_check should be None and log entry made assert t_check is None, "Invalid return code" assert caplog.record_tuples == logs, "Invalid log entries" mock_pjlink['receive_data_signal'].assert_called_once() mock_pjlink['_trash_buffer'].assert_not_called() mock_pjlink['send_command'].assert_called_with(cmd='CLSS', salt=TEST_HASH, priority=True) mock_pjlink['change_status'].assert_called_with(S_CONNECTED) mock_pjlink['readyRead'].connect.assert_called_once_with(mock_pjlink['get_socket']) mock_command.assert_called_with(pjlink, t_cmd, t_data) def test_e_authenticate(pjlink, caplog): """ Test projector connect with invalid pin """ t_salt = '498e4a67' t_cmd = "PJLINK" t_ver = "1" t_data = f"1 {t_salt}" t_buff = f"{PJLINK_PREFIX}{t_ver}{t_cmd}={t_data}" logs = [(f"{test_module}", logging.DEBUG, f'({pjlink.entry.name}) get_data(buffer="{t_buff}"'), (f'{test_module}', logging.DEBUG, f'({pjlink.entry.name}) Setting ignore_class to "False"'), (f'{test_module}', logging.DEBUG, f'({pjlink.entry.name}) get_data(): Checking new data "{t_buff}"'), (f'{test_module}', logging.DEBUG, f'({pjlink.entry.name}) get_data() header="{PJLINK_PREFIX}{t_ver}{t_cmd}" data="{t_data}"'), (f'{test_module}', logging.DEBUG, f'({pjlink.entry.name}) get_data() version="{t_ver}" cmd="{t_cmd}" data="{t_data}"'), (f'{test_module}', logging.WARNING, f'({pjlink.entry.name}) Failed authentication - disconnecting') ] with patch.object(openlp.core.projectors.pjlink, "process_command") as mock_command, \ patch.multiple(pjlink, _trash_buffer=DEFAULT, receive_data_signal=DEFAULT, disconnect_from_host=DEFAULT, change_status=DEFAULT, projectorAuthentication=DEFAULT) as mock_pjlink: mock_command.return_value = E_AUTHENTICATION # WHEN: get_data called with OK caplog.set_level(logging.DEBUG) t_check = pjlink.get_data(buff=t_buff) # THEN: t_check should be None and log entry made assert t_check is None, "Invalid return code" assert caplog.record_tuples == logs, "Invalid log entries" mock_pjlink['receive_data_signal'].assert_called_once() mock_pjlink['_trash_buffer'].assert_not_called() mock_pjlink['change_status'].assert_called_with(status=E_AUTHENTICATION) mock_pjlink['projectorAuthentication'].emit.assert_called_with(pjlink.entry.name) mock_command.assert_called_with(pjlink, t_cmd, t_data)