Merge lp:~alisonken1/openlp/pjlink2-m into lp:openlp
- pjlink2-m
- Merge into trunk
Status: | Merged |
---|---|
Merged at revision: | 2795 |
Proposed branch: | lp:~alisonken1/openlp/pjlink2-m |
Merge into: | lp:openlp |
Diff against target: |
1323 lines (+502/-243) 9 files modified
openlp/core/projectors/__init__.py (+0/-2) openlp/core/projectors/constants.py (+18/-0) openlp/core/projectors/db.py (+1/-1) openlp/core/projectors/pjlink.py (+195/-117) tests/functional/openlp_core/projectors/test_projector_bugfixes_01.py (+8/-35) tests/functional/openlp_core/projectors/test_projector_pjlink_base.py (+34/-48) tests/functional/openlp_core/projectors/test_projector_pjlink_cmd_routing.py (+35/-27) tests/functional/openlp_core/projectors/test_projector_pjlink_commands_01.py (+13/-13) tests/functional/openlp_core/projectors/test_projector_pjlink_commands_02.py (+198/-0) |
To merge this branch: | bzr merge lp:~alisonken1/openlp/pjlink2-m |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Tim Bentley | Approve | ||
Phill | Approve | ||
Review via email: mp+335001@code.launchpad.net |
This proposal supersedes a proposal from 2017-12-05.
Commit message
PJLink2 update M
Description of the change
- Added pjlink.
- Split pjlink.
- Added QAbstractSocket connect enum to constants
- Minor code cleanups for connection and command processing
- Updated packet queueing
- Fix get_object_
- Fix tests in test_projector_
- Fix tests in test_projector_
- Added tests for process_pjlink method
- Updated test_projector_
- Some OLP style cleanups
-------
lp:~alisonken1/openlp/pjlink2-m (revision 2796)
https:/
https:/
https:/
https:/
https:/
https:/
https:/
Phill (phill-ridout) wrote : Posted in a previous version of this proposal | # |
Tim Bentley (trb143) wrote : Posted in a previous version of this proposal | # |
See inline
Phill (phill-ridout) wrote : | # |
Great. Thanks for those changes :-)
Tim Bentley (trb143) : | # |
Preview Diff
1 | === modified file 'openlp/core/projectors/__init__.py' |
2 | --- openlp/core/projectors/__init__.py 2017-11-16 23:53:53 +0000 |
3 | +++ openlp/core/projectors/__init__.py 2017-12-09 11:19:42 +0000 |
4 | @@ -25,8 +25,6 @@ |
5 | Initialization for the openlp.core.projectors modules. |
6 | """ |
7 | |
8 | -from openlp.core.projectors.constants import PJLINK_PORT, ERROR_MSG, ERROR_STRING |
9 | - |
10 | |
11 | class DialogSourceStyle(object): |
12 | """ |
13 | |
14 | === modified file 'openlp/core/projectors/constants.py' |
15 | --- openlp/core/projectors/constants.py 2017-11-10 11:59:38 +0000 |
16 | +++ openlp/core/projectors/constants.py 2017-12-09 11:19:42 +0000 |
17 | @@ -144,6 +144,24 @@ |
18 | } |
19 | } |
20 | |
21 | +# QAbstractSocketState enums converted to string |
22 | +S_QSOCKET_STATE = { |
23 | + 0: 'QSocketState - UnconnectedState', |
24 | + 1: 'QSocketState - HostLookupState', |
25 | + 2: 'QSocketState - ConnectingState', |
26 | + 3: 'QSocketState - ConnectedState', |
27 | + 4: 'QSocketState - BoundState', |
28 | + 5: 'QSocketState - ListeningState (internal use only)', |
29 | + 6: 'QSocketState - ClosingState', |
30 | + 'UnconnectedState': 0, |
31 | + 'HostLookupState': 1, |
32 | + 'ConnectingState': 2, |
33 | + 'ConnectedState': 3, |
34 | + 'BoundState': 4, |
35 | + 'ListeningState': 5, |
36 | + 'ClosingState': 6 |
37 | +} |
38 | + |
39 | # Error and status codes |
40 | S_OK = E_OK = 0 # E_OK included since I sometimes forget |
41 | # Error codes. Start at 200 so we don't duplicate system error codes. |
42 | |
43 | === modified file 'openlp/core/projectors/db.py' |
44 | --- openlp/core/projectors/db.py 2017-11-10 11:59:38 +0000 |
45 | +++ openlp/core/projectors/db.py 2017-12-09 11:19:42 +0000 |
46 | @@ -415,7 +415,7 @@ |
47 | for key in projector.source_available: |
48 | item = self.get_object_filtered(ProjectorSource, |
49 | and_(ProjectorSource.code == key, |
50 | - ProjectorSource.projector_id == projector.dbid)) |
51 | + ProjectorSource.projector_id == projector.id)) |
52 | if item is None: |
53 | source_dict[key] = PJLINK_DEFAULT_CODES[key] |
54 | else: |
55 | |
56 | === modified file 'openlp/core/projectors/pjlink.py' |
57 | --- openlp/core/projectors/pjlink.py 2017-11-24 19:08:23 +0000 |
58 | +++ openlp/core/projectors/pjlink.py 2017-12-09 11:19:42 +0000 |
59 | @@ -58,8 +58,7 @@ |
60 | E_AUTHENTICATION, E_CONNECTION_REFUSED, E_GENERAL, E_INVALID_DATA, E_NETWORK, E_NOT_CONNECTED, E_OK, \ |
61 | E_PARAMETER, E_PROJECTOR, E_SOCKET_TIMEOUT, E_UNAVAILABLE, E_UNDEFINED, PJLINK_ERRORS, PJLINK_ERST_DATA, \ |
62 | PJLINK_ERST_STATUS, PJLINK_MAX_PACKET, PJLINK_PORT, PJLINK_POWR_STATUS, PJLINK_VALID_CMD, \ |
63 | - STATUS_STRING, S_CONNECTED, S_CONNECTING, S_INFO, S_NETWORK_RECEIVED, S_NETWORK_SENDING, \ |
64 | - S_NOT_CONNECTED, S_OFF, S_OK, S_ON, S_STATUS |
65 | + STATUS_STRING, S_CONNECTED, S_CONNECTING, S_INFO, S_NOT_CONNECTED, S_OFF, S_OK, S_ON, S_QSOCKET_STATE, S_STATUS |
66 | |
67 | log = logging.getLogger(__name__) |
68 | log.debug('pjlink loaded') |
69 | @@ -111,7 +110,7 @@ |
70 | """ |
71 | log.debug('PJlinkCommands(args={args} kwargs={kwargs})'.format(args=args, kwargs=kwargs)) |
72 | super().__init__() |
73 | - # Map command to function |
74 | + # Map PJLink command to method |
75 | self.pjlink_functions = { |
76 | 'AVMT': self.process_avmt, |
77 | 'CLSS': self.process_clss, |
78 | @@ -123,7 +122,9 @@ |
79 | 'INST': self.process_inst, |
80 | 'LAMP': self.process_lamp, |
81 | 'NAME': self.process_name, |
82 | - 'PJLINK': self.check_login, |
83 | + 'PJLINK': self.process_pjlink, |
84 | + # TODO: Part of check_login refactor - remove when done |
85 | + # 'PJLINK': self.check_login, |
86 | 'POWR': self.process_powr, |
87 | 'SNUM': self.process_snum, |
88 | 'SVER': self.process_sver, |
89 | @@ -135,7 +136,8 @@ |
90 | """ |
91 | Initialize instance variables. Also used to reset projector-specific information to default. |
92 | """ |
93 | - log.debug('({ip}) reset_information() connect status is {state}'.format(ip=self.ip, state=self.state())) |
94 | + log.debug('({ip}) reset_information() connect status is {state}'.format(ip=self.ip, |
95 | + state=S_QSOCKET_STATE[self.state()])) |
96 | self.fan = None # ERST |
97 | self.filter_time = None # FILT |
98 | self.lamp = None # LAMP |
99 | @@ -165,6 +167,7 @@ |
100 | self.socket_timer.stop() |
101 | self.send_busy = False |
102 | self.send_queue = [] |
103 | + self.priority_queue = [] |
104 | |
105 | def process_command(self, cmd, data): |
106 | """ |
107 | @@ -176,18 +179,19 @@ |
108 | log.debug('({ip}) Processing command "{cmd}" with data "{data}"'.format(ip=self.ip, |
109 | cmd=cmd, |
110 | data=data)) |
111 | + # cmd should already be in uppercase, but data may be in mixed-case. |
112 | + # Due to some replies should stay as mixed-case, validate using separate uppercase check |
113 | + _data = data.upper() |
114 | # Check if we have a future command not available yet |
115 | - _cmd = cmd.upper() |
116 | - _data = data.upper() |
117 | - if _cmd not in PJLINK_VALID_CMD: |
118 | - log.error("({ip}) Ignoring command='{cmd}' (Invalid/Unknown)".format(ip=self.ip, cmd=cmd)) |
119 | + if cmd not in PJLINK_VALID_CMD: |
120 | + log.error('({ip}) Ignoring command="{cmd}" (Invalid/Unknown)'.format(ip=self.ip, cmd=cmd)) |
121 | return |
122 | elif _data == 'OK': |
123 | log.debug('({ip}) Command "{cmd}" returned OK'.format(ip=self.ip, cmd=cmd)) |
124 | - # A command returned successfully, no further processing needed |
125 | - return |
126 | - elif _cmd not in self.pjlink_functions: |
127 | - log.warning("({ip}) Unable to process command='{cmd}' (Future option)".format(ip=self.ip, cmd=cmd)) |
128 | + # A command returned successfully, so do a query on command to verify status |
129 | + return self.send_command(cmd=cmd) |
130 | + elif cmd not in self.pjlink_functions: |
131 | + log.warning('({ip}) Unable to process command="{cmd}" (Future option?)'.format(ip=self.ip, cmd=cmd)) |
132 | return |
133 | elif _data in PJLINK_ERRORS: |
134 | # Oops - projector error |
135 | @@ -211,12 +215,10 @@ |
136 | elif _data == PJLINK_ERRORS[E_PROJECTOR]: |
137 | # Projector/display error |
138 | self.change_status(E_PROJECTOR) |
139 | - self.receive_data_signal() |
140 | return |
141 | # Command checks already passed |
142 | log.debug('({ip}) Calling function for {cmd}'.format(ip=self.ip, cmd=cmd)) |
143 | - self.receive_data_signal() |
144 | - self.pjlink_functions[_cmd](data) |
145 | + self.pjlink_functions[cmd](data) |
146 | |
147 | def process_avmt(self, data): |
148 | """ |
149 | @@ -259,19 +261,19 @@ |
150 | # : Received: '%1CLSS=Class 1' (Optoma) |
151 | # : Received: '%1CLSS=Version1' (BenQ) |
152 | if len(data) > 1: |
153 | - log.warning("({ip}) Non-standard CLSS reply: '{data}'".format(ip=self.ip, data=data)) |
154 | + log.warning('({ip}) Non-standard CLSS reply: "{data}"'.format(ip=self.ip, data=data)) |
155 | # Due to stupid projectors not following standards (Optoma, BenQ comes to mind), |
156 | # AND the different responses that can be received, the semi-permanent way to |
157 | # fix the class reply is to just remove all non-digit characters. |
158 | try: |
159 | clss = re.findall('\d', data)[0] # Should only be the first match |
160 | except IndexError: |
161 | - log.error("({ip}) No numbers found in class version reply '{data}' - " |
162 | - "defaulting to class '1'".format(ip=self.ip, data=data)) |
163 | + log.error('({ip}) No numbers found in class version reply "{data}" - ' |
164 | + 'defaulting to class "1"'.format(ip=self.ip, data=data)) |
165 | clss = '1' |
166 | elif not data.isdigit(): |
167 | - log.error("({ip}) NAN clss version reply '{data}' - " |
168 | - "defaulting to class '1'".format(ip=self.ip, data=data)) |
169 | + log.error('({ip}) NAN CLSS version reply "{data}" - ' |
170 | + 'defaulting to class "1"'.format(ip=self.ip, data=data)) |
171 | clss = '1' |
172 | else: |
173 | clss = data |
174 | @@ -289,7 +291,7 @@ |
175 | """ |
176 | if len(data) != PJLINK_ERST_DATA['DATA_LENGTH']: |
177 | count = PJLINK_ERST_DATA['DATA_LENGTH'] |
178 | - log.warning("{ip}) Invalid error status response '{data}': length != {count}".format(ip=self.ip, |
179 | + log.warning('{ip}) Invalid error status response "{data}": length != {count}'.format(ip=self.ip, |
180 | data=data, |
181 | count=count)) |
182 | return |
183 | @@ -297,7 +299,7 @@ |
184 | datacheck = int(data) |
185 | except ValueError: |
186 | # Bad data - ignore |
187 | - log.warning("({ip}) Invalid error status response '{data}'".format(ip=self.ip, data=data)) |
188 | + log.warning('({ip}) Invalid error status response "{data}"'.format(ip=self.ip, data=data)) |
189 | return |
190 | if datacheck == 0: |
191 | self.projector_errors = None |
192 | @@ -430,6 +432,51 @@ |
193 | log.debug('({ip}) Setting projector PJLink name to "{data}"'.format(ip=self.ip, data=self.pjlink_name)) |
194 | return |
195 | |
196 | + def process_pjlink(self, data): |
197 | + """ |
198 | + Process initial socket connection to terminal. |
199 | + |
200 | + :param data: Initial packet with authentication scheme |
201 | + """ |
202 | + log.debug('({ip}) Processing PJLINK command'.format(ip=self.ip)) |
203 | + chk = data.split(' ') |
204 | + if len(chk[0]) != 1: |
205 | + # Invalid - after splitting, first field should be 1 character, either '0' or '1' only |
206 | + log.error('({ip}) Invalid initial authentication scheme - aborting'.format(ip=self.ip)) |
207 | + return self.disconnect_from_host() |
208 | + elif chk[0] == '0': |
209 | + # Normal connection no authentication |
210 | + if len(chk) > 1: |
211 | + # Invalid data - there should be nothing after a normal authentication scheme |
212 | + log.error('({ip}) Normal connection with extra information - aborting'.format(ip=self.ip)) |
213 | + return self.disconnect_from_host() |
214 | + elif self.pin: |
215 | + log.error('({ip}) Normal connection but PIN set - aborting'.format(ip=self.ip)) |
216 | + return self.disconnect_from_host() |
217 | + else: |
218 | + data_hash = None |
219 | + elif chk[0] == '1': |
220 | + if len(chk) < 2: |
221 | + # Not enough information for authenticated connection |
222 | + log.error('({ip}) Authenticated connection but not enough info - aborting'.format(ip=self.ip)) |
223 | + return self.disconnect_from_host() |
224 | + elif not self.pin: |
225 | + log.error('({ip}) Authenticate connection but no PIN - aborting'.format(ip=self.ip)) |
226 | + return self.disconnect_from_host() |
227 | + else: |
228 | + data_hash = str(qmd5_hash(salt=chk[1].encode('utf-8'), data=self.pin.encode('utf-8')), |
229 | + encoding='ascii') |
230 | + # Passed basic checks, so start connection |
231 | + self.readyRead.connect(self.get_socket) |
232 | + if not self.no_poll: |
233 | + log.debug('({ip}) process_pjlink(): Starting timer'.format(ip=self.ip)) |
234 | + self.timer.setInterval(2000) # Set 2 seconds for initial information |
235 | + self.timer.start() |
236 | + self.change_status(S_CONNECTED) |
237 | + log.debug('({ip}) process_pjlink(): Sending "CLSS" initial command'.format(ip=self.ip)) |
238 | + # Since this is an initial connection, make it a priority just in case |
239 | + return self.send_command(cmd="CLSS", salt=data_hash, priority=True) |
240 | + |
241 | def process_powr(self, data): |
242 | """ |
243 | Power status. See PJLink specification for format. |
244 | @@ -450,7 +497,7 @@ |
245 | self.send_command('INST') |
246 | else: |
247 | # Log unknown status response |
248 | - log.warning('({ip}) Unknown power response: {data}'.format(ip=self.ip, data=data)) |
249 | + log.warning('({ip}) Unknown power response: "{data}"'.format(ip=self.ip, data=data)) |
250 | return |
251 | |
252 | def process_rfil(self, data): |
253 | @@ -460,9 +507,9 @@ |
254 | if self.model_filter is None: |
255 | self.model_filter = data |
256 | else: |
257 | - log.warning("({ip}) Filter model already set".format(ip=self.ip)) |
258 | - log.warning("({ip}) Saved model: '{old}'".format(ip=self.ip, old=self.model_filter)) |
259 | - log.warning("({ip}) New model: '{new}'".format(ip=self.ip, new=data)) |
260 | + log.warning('({ip}) Filter model already set'.format(ip=self.ip)) |
261 | + log.warning('({ip}) Saved model: "{old}"'.format(ip=self.ip, old=self.model_filter)) |
262 | + log.warning('({ip}) New model: "{new}"'.format(ip=self.ip, new=data)) |
263 | |
264 | def process_rlmp(self, data): |
265 | """ |
266 | @@ -471,9 +518,9 @@ |
267 | if self.model_lamp is None: |
268 | self.model_lamp = data |
269 | else: |
270 | - log.warning("({ip}) Lamp model already set".format(ip=self.ip)) |
271 | - log.warning("({ip}) Saved lamp: '{old}'".format(ip=self.ip, old=self.model_lamp)) |
272 | - log.warning("({ip}) New lamp: '{new}'".format(ip=self.ip, new=data)) |
273 | + log.warning('({ip}) Lamp model already set'.format(ip=self.ip)) |
274 | + log.warning('({ip}) Saved lamp: "{old}"'.format(ip=self.ip, old=self.model_lamp)) |
275 | + log.warning('({ip}) New lamp: "{new}"'.format(ip=self.ip, new=data)) |
276 | |
277 | def process_snum(self, data): |
278 | """ |
279 | @@ -482,16 +529,16 @@ |
280 | :param data: Serial number from projector. |
281 | """ |
282 | if self.serial_no is None: |
283 | - log.debug("({ip}) Setting projector serial number to '{data}'".format(ip=self.ip, data=data)) |
284 | + log.debug('({ip}) Setting projector serial number to "{data}"'.format(ip=self.ip, data=data)) |
285 | self.serial_no = data |
286 | self.db_update = False |
287 | else: |
288 | # Compare serial numbers and see if we got the same projector |
289 | if self.serial_no != data: |
290 | - log.warning("({ip}) Projector serial number does not match saved serial number".format(ip=self.ip)) |
291 | - log.warning("({ip}) Saved: '{old}'".format(ip=self.ip, old=self.serial_no)) |
292 | - log.warning("({ip}) Received: '{new}'".format(ip=self.ip, new=data)) |
293 | - log.warning("({ip}) NOT saving serial number".format(ip=self.ip)) |
294 | + log.warning('({ip}) Projector serial number does not match saved serial number'.format(ip=self.ip)) |
295 | + log.warning('({ip}) Saved: "{old}"'.format(ip=self.ip, old=self.serial_no)) |
296 | + log.warning('({ip}) Received: "{new}"'.format(ip=self.ip, new=data)) |
297 | + log.warning('({ip}) NOT saving serial number'.format(ip=self.ip)) |
298 | self.serial_no_received = data |
299 | |
300 | def process_sver(self, data): |
301 | @@ -500,20 +547,20 @@ |
302 | """ |
303 | if len(data) > 32: |
304 | # Defined in specs max version is 32 characters |
305 | - log.warning("Invalid software version - too long") |
306 | + log.warning('Invalid software version - too long') |
307 | return |
308 | elif self.sw_version is None: |
309 | - log.debug("({ip}) Setting projector software version to '{data}'".format(ip=self.ip, data=data)) |
310 | + log.debug('({ip}) Setting projector software version to "{data}"'.format(ip=self.ip, data=data)) |
311 | self.sw_version = data |
312 | self.db_update = True |
313 | else: |
314 | # Compare software version and see if we got the same projector |
315 | if self.serial_no != data: |
316 | - log.warning("({ip}) Projector software version does not match saved " |
317 | - "software version".format(ip=self.ip)) |
318 | - log.warning("({ip}) Saved: '{old}'".format(ip=self.ip, old=self.sw_version)) |
319 | - log.warning("({ip}) Received: '{new}'".format(ip=self.ip, new=data)) |
320 | - log.warning("({ip}) Saving new serial number as sw_version_received".format(ip=self.ip)) |
321 | + log.warning('({ip}) Projector software version does not match saved ' |
322 | + 'software version'.format(ip=self.ip)) |
323 | + log.warning('({ip}) Saved: "{old}"'.format(ip=self.ip, old=self.sw_version)) |
324 | + log.warning('({ip}) Received: "{new}"'.format(ip=self.ip, new=data)) |
325 | + log.warning('({ip}) Saving new serial number as sw_version_received'.format(ip=self.ip)) |
326 | self.sw_version_received = data |
327 | |
328 | |
329 | @@ -540,9 +587,9 @@ |
330 | :param poll_time: Time (in seconds) to poll connected projector |
331 | :param socket_timeout: Time (in seconds) to abort the connection if no response |
332 | """ |
333 | - log.debug('PJlink(projector={projector}, args={args} kwargs={kwargs})'.format(projector=projector, |
334 | - args=args, |
335 | - kwargs=kwargs)) |
336 | + log.debug('PJlink(projector="{projector}", args="{args}" kwargs="{kwargs}")'.format(projector=projector, |
337 | + args=args, |
338 | + kwargs=kwargs)) |
339 | super().__init__() |
340 | self.entry = projector |
341 | self.ip = self.entry.ip |
342 | @@ -573,6 +620,7 @@ |
343 | self.widget = None # QListBox entry |
344 | self.timer = None # Timer that calls the poll_loop |
345 | self.send_queue = [] |
346 | + self.priority_queue = [] |
347 | self.send_busy = False |
348 | # Socket timer for some possible brain-dead projectors or network cable pulled |
349 | self.socket_timer = None |
350 | @@ -586,6 +634,7 @@ |
351 | self.connected.connect(self.check_login) |
352 | self.disconnected.connect(self.disconnect_from_host) |
353 | self.error.connect(self.get_error) |
354 | + self.projectorReceivedData.connect(self._send_command) |
355 | |
356 | def thread_stopped(self): |
357 | """ |
358 | @@ -608,6 +657,10 @@ |
359 | self.projectorReceivedData.disconnect(self._send_command) |
360 | except TypeError: |
361 | pass |
362 | + try: |
363 | + self.readyRead.disconnect(self.get_socket) # Set in process_pjlink |
364 | + except TypeError: |
365 | + pass |
366 | self.disconnect_from_host() |
367 | self.deleteLater() |
368 | self.i_am_running = False |
369 | @@ -625,10 +678,10 @@ |
370 | Retrieve information from projector that changes. |
371 | Normally called by timer(). |
372 | """ |
373 | - if self.state() != self.ConnectedState: |
374 | - log.warning("({ip}) poll_loop(): Not connected - returning".format(ip=self.ip)) |
375 | + if self.state() != S_QSOCKET_STATE['ConnectedState']: |
376 | + log.warning('({ip}) poll_loop(): Not connected - returning'.format(ip=self.ip)) |
377 | return |
378 | - log.debug('({ip}) Updating projector status'.format(ip=self.ip)) |
379 | + log.debug('({ip}) poll_loop(): Updating projector status'.format(ip=self.ip)) |
380 | # Reset timer in case we were called from a set command |
381 | if self.timer.interval() < self.poll_time: |
382 | # Reset timer to 5 seconds |
383 | @@ -640,28 +693,28 @@ |
384 | if self.pjlink_class == '2': |
385 | check_list.extend(['FILT', 'FREZ']) |
386 | for command in check_list: |
387 | - self.send_command(command, queue=True) |
388 | + self.send_command(command) |
389 | # The following commands do not change, so only check them once |
390 | if self.power == S_ON and self.source_available is None: |
391 | - self.send_command('INST', queue=True) |
392 | + self.send_command('INST') |
393 | if self.other_info is None: |
394 | - self.send_command('INFO', queue=True) |
395 | + self.send_command('INFO') |
396 | if self.manufacturer is None: |
397 | - self.send_command('INF1', queue=True) |
398 | + self.send_command('INF1') |
399 | if self.model is None: |
400 | - self.send_command('INF2', queue=True) |
401 | + self.send_command('INF2') |
402 | if self.pjlink_name is None: |
403 | - self.send_command('NAME', queue=True) |
404 | + self.send_command('NAME') |
405 | if self.pjlink_class == '2': |
406 | # Class 2 specific checks |
407 | if self.serial_no is None: |
408 | - self.send_command('SNUM', queue=True) |
409 | + self.send_command('SNUM') |
410 | if self.sw_version is None: |
411 | - self.send_command('SVER', queue=True) |
412 | + self.send_command('SVER') |
413 | if self.model_filter is None: |
414 | - self.send_command('RFIL', queue=True) |
415 | + self.send_command('RFIL') |
416 | if self.model_lamp is None: |
417 | - self.send_command('RLMP', queue=True) |
418 | + self.send_command('RLMP') |
419 | |
420 | def _get_status(self, status): |
421 | """ |
422 | @@ -713,14 +766,12 @@ |
423 | code=status_code, |
424 | message=status_message if msg is None else msg)) |
425 | self.changeStatus.emit(self.ip, status, message) |
426 | + self.projectorUpdateIcons.emit() |
427 | |
428 | @QtCore.pyqtSlot() |
429 | def check_login(self, data=None): |
430 | """ |
431 | - Processes the initial connection and authentication (if needed). |
432 | - Starts poll timer if connection is established. |
433 | - |
434 | - NOTE: Qt md5 hash function doesn't work with projector authentication. Use the python md5 hash function. |
435 | + Processes the initial connection and convert to a PJLink packet if valid initial connection |
436 | |
437 | :param data: Optional data if called from another routine |
438 | """ |
439 | @@ -733,12 +784,12 @@ |
440 | self.change_status(E_SOCKET_TIMEOUT) |
441 | return |
442 | read = self.readLine(self.max_size) |
443 | - self.readLine(self.max_size) # Clean out the trailing \r\n |
444 | + self.readLine(self.max_size) # Clean out any trailing whitespace |
445 | if read is None: |
446 | log.warning('({ip}) read is None - socket error?'.format(ip=self.ip)) |
447 | return |
448 | elif len(read) < 8: |
449 | - log.warning('({ip}) Not enough data read)'.format(ip=self.ip)) |
450 | + log.warning('({ip}) Not enough data read - skipping'.format(ip=self.ip)) |
451 | return |
452 | data = decode(read, 'utf-8') |
453 | # Possibility of extraneous data on input when reading. |
454 | @@ -750,9 +801,16 @@ |
455 | # PJLink initial login will be: |
456 | # 'PJLink 0' - Unauthenticated login - no extra steps required. |
457 | # 'PJLink 1 XXXXXX' Authenticated login - extra processing required. |
458 | - if not data.upper().startswith('PJLINK'): |
459 | - # Invalid response |
460 | + if not data.startswith('PJLINK'): |
461 | + # Invalid initial packet - close socket |
462 | + log.error('({ip}) Invalid initial packet received - closing socket'.format(ip=self.ip)) |
463 | return self.disconnect_from_host() |
464 | + log.debug('({ip}) check_login(): Formatting initial connection prompt to PJLink packet'.format(ip=self.ip)) |
465 | + return self.get_data('{start}{clss}{data}'.format(start=PJLINK_PREFIX, |
466 | + clss='1', |
467 | + data=data.replace(' ', '=', 1)).encode('utf-8')) |
468 | + # TODO: The below is replaced by process_pjlink() - remove when working properly |
469 | + """ |
470 | if '=' in data: |
471 | # Processing a login reply |
472 | data_check = data.strip().split('=') |
473 | @@ -801,18 +859,19 @@ |
474 | log.debug('({ip}) Starting timer'.format(ip=self.ip)) |
475 | self.timer.setInterval(2000) # Set 2 seconds for initial information |
476 | self.timer.start() |
477 | + """ |
478 | |
479 | def _trash_buffer(self, msg=None): |
480 | """ |
481 | Clean out extraneous stuff in the buffer. |
482 | """ |
483 | - log.warning("({ip}) {message}".format(ip=self.ip, message='Invalid packet' if msg is None else msg)) |
484 | + log.warning('({ip}) {message}'.format(ip=self.ip, message='Invalid packet' if msg is None else msg)) |
485 | self.send_busy = False |
486 | trash_count = 0 |
487 | while self.bytesAvailable() > 0: |
488 | trash = self.read(self.max_size) |
489 | trash_count += len(trash) |
490 | - log.debug("({ip}) Finished cleaning buffer - {count} bytes dropped".format(ip=self.ip, |
491 | + log.debug('({ip}) Finished cleaning buffer - {count} bytes dropped'.format(ip=self.ip, |
492 | count=trash_count)) |
493 | return |
494 | |
495 | @@ -824,7 +883,7 @@ |
496 | :param data: Data to process. buffer must be formatted as a proper PJLink packet. |
497 | :param ip: Destination IP for buffer. |
498 | """ |
499 | - log.debug("({ip}) get_buffer(data='{buff}' ip='{ip_in}'".format(ip=self.ip, buff=data, ip_in=ip)) |
500 | + log.debug('({ip}) get_buffer(data="{buff}" ip="{ip_in}"'.format(ip=self.ip, buff=data, ip_in=ip)) |
501 | if ip is None: |
502 | log.debug("({ip}) get_buffer() Don't know who data is for - exiting".format(ip=self.ip)) |
503 | return |
504 | @@ -842,38 +901,52 @@ |
505 | return |
506 | # Although we have a packet length limit, go ahead and use a larger buffer |
507 | read = self.readLine(1024) |
508 | - log.debug("({ip}) get_socket(): '{buff}'".format(ip=self.ip, buff=read)) |
509 | + log.debug('({ip}) get_socket(): "{buff}"'.format(ip=self.ip, buff=read)) |
510 | if read == -1: |
511 | # No data available |
512 | log.debug('({ip}) get_socket(): No data available (-1)'.format(ip=self.ip)) |
513 | return self.receive_data_signal() |
514 | self.socket_timer.stop() |
515 | - return self.get_data(buff=read, ip=self.ip) |
516 | + self.get_data(buff=read, ip=self.ip) |
517 | + return self.receive_data_signal() |
518 | |
519 | - def get_data(self, buff, ip): |
520 | + def get_data(self, buff, ip=None): |
521 | """ |
522 | Process received data |
523 | |
524 | :param buff: Data to process. |
525 | :param ip: (optional) Destination IP. |
526 | """ |
527 | - log.debug("({ip}) get_data(ip='{ip_in}' buffer='{buff}'".format(ip=self.ip, ip_in=ip, buff=buff)) |
528 | + # Since "self" is not available to options and the "ip" keyword is a "maybe I'll use in the future", |
529 | + # set to default here |
530 | + if ip is None: |
531 | + ip = self.ip |
532 | + log.debug('({ip}) get_data(ip="{ip_in}" buffer="{buff}"'.format(ip=self.ip, ip_in=ip, buff=buff)) |
533 | # NOTE: Class2 has changed to some values being UTF-8 |
534 | data_in = decode(buff, 'utf-8') |
535 | data = data_in.strip() |
536 | - if (len(data) < 7) or (not data.startswith(PJLINK_PREFIX)): |
537 | - return self._trash_buffer(msg='get_data(): Invalid packet - length or prefix') |
538 | + # Initial packet checks |
539 | + if (len(data) < 7): |
540 | + return self._trash_buffer(msg='get_data(): Invalid packet - length') |
541 | elif len(data) > self.max_size: |
542 | return self._trash_buffer(msg='get_data(): Invalid packet - too long') |
543 | + elif not data.startswith(PJLINK_PREFIX): |
544 | + return self._trash_buffer(msg='get_data(): Invalid packet - PJLink prefix missing') |
545 | elif '=' not in data: |
546 | - return self._trash_buffer(msg='get_data(): Invalid packet does not have equal') |
547 | + return self._trash_buffer(msg='get_data(): Invalid reply - Does not have "="') |
548 | log.debug('({ip}) get_data(): Checking new data "{data}"'.format(ip=self.ip, data=data)) |
549 | header, data = data.split('=') |
550 | + # At this point, the header should contain: |
551 | + # "PVCCCC" |
552 | + # Where: |
553 | + # P = PJLINK_PREFIX |
554 | + # V = PJLink class or version |
555 | + # C = PJLink command |
556 | try: |
557 | - version, cmd = header[1], header[2:] |
558 | + version, cmd = header[1], header[2:].upper() |
559 | except ValueError as e: |
560 | self.change_status(E_INVALID_DATA) |
561 | - log.warning('({ip}) get_data(): Received data: "{data}"'.format(ip=self.ip, data=data_in.strip())) |
562 | + log.warning('({ip}) get_data(): Received data: "{data}"'.format(ip=self.ip, data=data_in)) |
563 | return self._trash_buffer('get_data(): Expected header + command + data') |
564 | if cmd not in PJLINK_VALID_CMD: |
565 | log.warning('({ip}) get_data(): Invalid packet - unknown command "{data}"'.format(ip=self.ip, data=cmd)) |
566 | @@ -881,6 +954,7 @@ |
567 | if int(self.pjlink_class) < int(version): |
568 | log.warning('({ip}) get_data(): Projector returned class reply higher ' |
569 | 'than projector stated class'.format(ip=self.ip)) |
570 | + self.send_busy = False |
571 | return self.process_command(cmd, data) |
572 | |
573 | @QtCore.pyqtSlot(QtNetwork.QAbstractSocket.SocketError) |
574 | @@ -910,19 +984,18 @@ |
575 | self.reset_information() |
576 | return |
577 | |
578 | - def send_command(self, cmd, opts='?', salt=None, queue=False): |
579 | + def send_command(self, cmd, opts='?', salt=None, priority=False): |
580 | """ |
581 | Add command to output queue if not already in queue. |
582 | |
583 | :param cmd: Command to send |
584 | :param opts: Command option (if any) - defaults to '?' (get information) |
585 | :param salt: Optional salt for md5 hash initial authentication |
586 | - :param queue: Option to force add to queue rather than sending directly |
587 | + :param priority: Option to send packet now rather than queue it up |
588 | """ |
589 | if self.state() != self.ConnectedState: |
590 | log.warning('({ip}) send_command(): Not connected - returning'.format(ip=self.ip)) |
591 | - self.send_queue = [] |
592 | - return |
593 | + return self.reset_information() |
594 | if cmd not in PJLINK_VALID_CMD: |
595 | log.error('({ip}) send_command(): Invalid command requested - ignoring.'.format(ip=self.ip)) |
596 | return |
597 | @@ -939,28 +1012,26 @@ |
598 | header = PJLINK_HEADER.format(linkclass=cmd_ver[0]) |
599 | else: |
600 | # NOTE: Once we get to version 3 then think about looping |
601 | - log.error('({ip}): send_command(): PJLink class check issue? aborting'.format(ip=self.ip)) |
602 | + log.error('({ip}): send_command(): PJLink class check issue? Aborting'.format(ip=self.ip)) |
603 | return |
604 | out = '{salt}{header}{command} {options}{suffix}'.format(salt="" if salt is None else salt, |
605 | header=header, |
606 | command=cmd, |
607 | options=opts, |
608 | suffix=CR) |
609 | - if out in self.send_queue: |
610 | - # Already there, so don't add |
611 | - log.debug('({ip}) send_command(out="{data}") Already in queue - skipping'.format(ip=self.ip, |
612 | - data=out.strip())) |
613 | - elif not queue and len(self.send_queue) == 0: |
614 | - # Nothing waiting to send, so just send it |
615 | - log.debug('({ip}) send_command(out="{data}") Sending data'.format(ip=self.ip, data=out.strip())) |
616 | - return self._send_command(data=out) |
617 | + if out in self.priority_queue: |
618 | + log.debug('({ip}) send_command(): Already in priority queue - skipping'.format(ip=self.ip)) |
619 | + elif out in self.send_queue: |
620 | + log.debug('({ip}) send_command(): Already in normal queue - skipping'.format(ip=self.ip)) |
621 | else: |
622 | - log.debug('({ip}) send_command(out="{data}") adding to queue'.format(ip=self.ip, data=out.strip())) |
623 | - self.send_queue.append(out) |
624 | - self.projectorReceivedData.emit() |
625 | - log.debug('({ip}) send_command(): send_busy is {data}'.format(ip=self.ip, data=self.send_busy)) |
626 | - if not self.send_busy: |
627 | - log.debug('({ip}) send_command() calling _send_string()'.format(ip=self.ip)) |
628 | + if priority: |
629 | + log.debug('({ip}) send_command(): Adding to priority queue'.format(ip=self.ip)) |
630 | + self.priority_queue.append(out) |
631 | + else: |
632 | + log.debug('({ip}) send_command(): Adding to normal queue'.format(ip=self.ip)) |
633 | + self.send_queue.append(out) |
634 | + if self.priority_queue or self.send_queue: |
635 | + # May be some initial connection setup so make sure we send data |
636 | self._send_command() |
637 | |
638 | @QtCore.pyqtSlot() |
639 | @@ -971,43 +1042,53 @@ |
640 | :param data: Immediate data to send |
641 | :param utf8: Send as UTF-8 string otherwise send as ASCII string |
642 | """ |
643 | - log.debug('({ip}) _send_string()'.format(ip=self.ip)) |
644 | - log.debug('({ip}) _send_string(): Connection status: {data}'.format(ip=self.ip, data=self.state())) |
645 | + # Funny looking data check, but it's a quick check for data=None |
646 | + log.debug('({ip}) _send_command(data="{data}")'.format(ip=self.ip, data=data.strip() if data else data)) |
647 | + log.debug('({ip}) _send_command(): Connection status: {data}'.format(ip=self.ip, |
648 | + data=S_QSOCKET_STATE[self.state()])) |
649 | if self.state() != self.ConnectedState: |
650 | - log.debug('({ip}) _send_string() Not connected - abort'.format(ip=self.ip)) |
651 | - self.send_queue = [] |
652 | + log.debug('({ip}) _send_command() Not connected - abort'.format(ip=self.ip)) |
653 | self.send_busy = False |
654 | - return |
655 | + return self.disconnect_from_host() |
656 | + if data and data not in self.priority_queue: |
657 | + log.debug('({ip}) _send_command(): Priority packet - adding to priority queue'.format(ip=self.ip)) |
658 | + self.priority_queue.append(data) |
659 | + |
660 | if self.send_busy: |
661 | # Still waiting for response from last command sent |
662 | + log.debug('({ip}) _send_command(): Still busy, returning'.format(ip=self.ip)) |
663 | + log.debug('({ip}) _send_command(): Priority queue = {data}'.format(ip=self.ip, data=self.priority_queue)) |
664 | + log.debug('({ip}) _send_command(): Normal queue = {data}'.format(ip=self.ip, data=self.send_queue)) |
665 | return |
666 | - if data is not None: |
667 | - out = data |
668 | - log.debug('({ip}) _send_string(data="{data}")'.format(ip=self.ip, data=out.strip())) |
669 | + |
670 | + if len(self.priority_queue) != 0: |
671 | + out = self.priority_queue.pop(0) |
672 | + log.debug('({ip}) _send_command(): Getting priority queued packet'.format(ip=self.ip)) |
673 | elif len(self.send_queue) != 0: |
674 | out = self.send_queue.pop(0) |
675 | - log.debug('({ip}) _send_string(queued data="{data}"%s)'.format(ip=self.ip, data=out.strip())) |
676 | + log.debug('({ip}) _send_command(): Getting normal queued packet'.format(ip=self.ip)) |
677 | else: |
678 | # No data to send |
679 | - log.debug('({ip}) _send_string(): No data to send'.format(ip=self.ip)) |
680 | + log.debug('({ip}) _send_command(): No data to send'.format(ip=self.ip)) |
681 | self.send_busy = False |
682 | return |
683 | self.send_busy = True |
684 | - log.debug('({ip}) _send_string(): Sending "{data}"'.format(ip=self.ip, data=out.strip())) |
685 | - log.debug('({ip}) _send_string(): Queue = {data}'.format(ip=self.ip, data=self.send_queue)) |
686 | + log.debug('({ip}) _send_command(): Sending "{data}"'.format(ip=self.ip, data=out.strip())) |
687 | self.socket_timer.start() |
688 | sent = self.write(out.encode('{string_encoding}'.format(string_encoding='utf-8' if utf8 else 'ascii'))) |
689 | self.waitForBytesWritten(2000) # 2 seconds should be enough |
690 | if sent == -1: |
691 | # Network error? |
692 | - log.warning("({ip}) _send_command(): -1 received".format(ip=self.ip)) |
693 | + log.warning('({ip}) _send_command(): -1 received - disconnecting from host'.format(ip=self.ip)) |
694 | self.change_status(E_NETWORK, |
695 | translate('OpenLP.PJLink', 'Error while sending data to projector')) |
696 | + self.disconnect_from_host() |
697 | |
698 | def connect_to_host(self): |
699 | """ |
700 | Initiate connection to projector. |
701 | """ |
702 | + log.debug('{ip}) connect_to_host(): Starting connection'.format(ip=self.ip)) |
703 | if self.state() == self.ConnectedState: |
704 | log.warning('({ip}) connect_to_host(): Already connected - returning'.format(ip=self.ip)) |
705 | return |
706 | @@ -1023,22 +1104,19 @@ |
707 | if abort: |
708 | log.warning('({ip}) disconnect_from_host(): Aborting connection'.format(ip=self.ip)) |
709 | else: |
710 | - log.warning('({ip}) disconnect_from_host(): Not connected - returning'.format(ip=self.ip)) |
711 | - self.reset_information() |
712 | + log.warning('({ip}) disconnect_from_host(): Not connected'.format(ip=self.ip)) |
713 | self.disconnectFromHost() |
714 | try: |
715 | self.readyRead.disconnect(self.get_socket) |
716 | except TypeError: |
717 | pass |
718 | + log.debug('({ip}) disconnect_from_host() ' |
719 | + 'Current status {data}'.format(ip=self.ip, data=self._get_status(self.status_connect)[0])) |
720 | if abort: |
721 | self.change_status(E_NOT_CONNECTED) |
722 | else: |
723 | - log.debug('({ip}) disconnect_from_host() ' |
724 | - 'Current status {data}'.format(ip=self.ip, data=self._get_status(self.status_connect)[0])) |
725 | - if self.status_connect != E_NOT_CONNECTED: |
726 | - self.change_status(S_NOT_CONNECTED) |
727 | + self.change_status(S_NOT_CONNECTED) |
728 | self.reset_information() |
729 | - self.projectorUpdateIcons.emit() |
730 | |
731 | def get_av_mute_status(self): |
732 | """ |
733 | |
734 | === modified file 'tests/functional/openlp_core/projectors/test_projector_bugfixes_01.py' |
735 | --- tests/functional/openlp_core/projectors/test_projector_bugfixes_01.py 2017-11-24 19:08:23 +0000 |
736 | +++ tests/functional/openlp_core/projectors/test_projector_bugfixes_01.py 2017-12-09 11:19:42 +0000 |
737 | @@ -23,12 +23,11 @@ |
738 | Package to test the openlp.core.projectors.pjlink base package. |
739 | """ |
740 | from unittest import TestCase |
741 | -from unittest.mock import patch |
742 | |
743 | from openlp.core.projectors.db import Projector |
744 | from openlp.core.projectors.pjlink import PJLink |
745 | |
746 | -from tests.resources.projector.data import TEST_PIN, TEST_CONNECT_AUTHENTICATE, TEST_HASH, TEST1_DATA |
747 | +from tests.resources.projector.data import TEST1_DATA |
748 | |
749 | |
750 | class TestPJLinkBugs(TestCase): |
751 | @@ -80,43 +79,17 @@ |
752 | """ |
753 | Test bug 1593882 no pin and authenticated request exception |
754 | """ |
755 | - # GIVEN: Test object and mocks |
756 | - mock_socket_timer = patch.object(self.pjlink_test, 'socket_timer').start() |
757 | - mock_timer = patch.object(self.pjlink_test, 'timer').start() |
758 | - mock_authentication = patch.object(self.pjlink_test, 'projectorAuthentication').start() |
759 | - mock_ready_read = patch.object(self.pjlink_test, 'waitForReadyRead').start() |
760 | - mock_send_command = patch.object(self.pjlink_test, 'send_command').start() |
761 | - pjlink = self.pjlink_test |
762 | - pjlink.pin = None |
763 | - mock_ready_read.return_value = True |
764 | - |
765 | - # WHEN: call with authentication request and pin not set |
766 | - pjlink.check_login(data=TEST_CONNECT_AUTHENTICATE) |
767 | - |
768 | - # THEN: 'No Authentication' signal should have been sent |
769 | - mock_authentication.emit.assert_called_with(pjlink.ip) |
770 | + # Test now part of test_projector_pjlink_commands_02 |
771 | + # Keeping here for bug reference |
772 | + pass |
773 | |
774 | def test_bug_1593883_pjlink_authentication(self): |
775 | """ |
776 | - Test bugfix 1593883 pjlink authentication |
777 | + Test bugfix 1593883 pjlink authentication and ticket 92187 |
778 | """ |
779 | - # GIVEN: Test object and data |
780 | - mock_socket_timer = patch.object(self.pjlink_test, 'socket_timer').start() |
781 | - mock_timer = patch.object(self.pjlink_test, 'timer').start() |
782 | - mock_send_command = patch.object(self.pjlink_test, 'write').start() |
783 | - mock_state = patch.object(self.pjlink_test, 'state').start() |
784 | - mock_waitForReadyRead = patch.object(self.pjlink_test, 'waitForReadyRead').start() |
785 | - pjlink = self.pjlink_test |
786 | - pjlink.pin = TEST_PIN |
787 | - mock_state.return_value = pjlink.ConnectedState |
788 | - mock_waitForReadyRead.return_value = True |
789 | - |
790 | - # WHEN: Athenticated connection is called |
791 | - pjlink.check_login(data=TEST_CONNECT_AUTHENTICATE) |
792 | - |
793 | - # THEN: send_command should have the proper authentication |
794 | - self.assertEqual("{test}".format(test=mock_send_command.call_args), |
795 | - "call(b'{hash}%1CLSS ?\\r')".format(hash=TEST_HASH)) |
796 | + # Test now part of test_projector_pjlink_commands_02 |
797 | + # Keeping here for bug reference |
798 | + pass |
799 | |
800 | def test_bug_1734275_process_lamp_nonstandard_reply(self): |
801 | """ |
802 | |
803 | === modified file 'tests/functional/openlp_core/projectors/test_projector_pjlink_base.py' |
804 | --- tests/functional/openlp_core/projectors/test_projector_pjlink_base.py 2017-11-24 08:30:37 +0000 |
805 | +++ tests/functional/openlp_core/projectors/test_projector_pjlink_base.py 2017-12-09 11:19:42 +0000 |
806 | @@ -25,11 +25,11 @@ |
807 | from unittest import TestCase |
808 | from unittest.mock import call, patch, MagicMock |
809 | |
810 | -from openlp.core.projectors.constants import E_PARAMETER, ERROR_STRING, S_ON, S_CONNECTED |
811 | +from openlp.core.projectors.constants import E_PARAMETER, ERROR_STRING, S_ON, S_CONNECTED, S_QSOCKET_STATE |
812 | from openlp.core.projectors.db import Projector |
813 | from openlp.core.projectors.pjlink import PJLink |
814 | |
815 | -from tests.resources.projector.data import TEST_PIN, TEST_SALT, TEST_CONNECT_AUTHENTICATE, TEST1_DATA |
816 | +from tests.resources.projector.data import TEST1_DATA |
817 | |
818 | pjlink_test = PJLink(Projector(**TEST1_DATA), no_poll=True) |
819 | |
820 | @@ -38,29 +38,17 @@ |
821 | """ |
822 | Tests for the PJLink module |
823 | """ |
824 | - @patch.object(pjlink_test, 'readyRead') |
825 | - @patch.object(pjlink_test, 'send_command') |
826 | - @patch.object(pjlink_test, 'waitForReadyRead') |
827 | - @patch('openlp.core.common.qmd5_hash') |
828 | - def test_authenticated_connection_call(self, |
829 | - mock_qmd5_hash, |
830 | - mock_waitForReadyRead, |
831 | - mock_send_command, |
832 | - mock_readyRead): |
833 | - """ |
834 | - Ticket 92187: Fix for projector connect with PJLink authentication exception. |
835 | - """ |
836 | - # GIVEN: Test object |
837 | - pjlink = pjlink_test |
838 | - |
839 | - # WHEN: Calling check_login with authentication request: |
840 | - pjlink.check_login(data=TEST_CONNECT_AUTHENTICATE) |
841 | - |
842 | - # THEN: Should have called qmd5_hash |
843 | - self.assertTrue(mock_qmd5_hash.called_with(TEST_SALT, |
844 | - "Connection request should have been called with TEST_SALT")) |
845 | - self.assertTrue(mock_qmd5_hash.called_with(TEST_PIN, |
846 | - "Connection request should have been called with TEST_PIN")) |
847 | + def setUp(self): |
848 | + ''' |
849 | + TestPJLinkCommands part 2 initialization |
850 | + ''' |
851 | + self.pjlink_test = PJLink(Projector(**TEST1_DATA), no_poll=True) |
852 | + |
853 | + def tearDown(self): |
854 | + ''' |
855 | + TestPJLinkCommands part 2 cleanups |
856 | + ''' |
857 | + self.pjlink_test = None |
858 | |
859 | @patch.object(pjlink_test, 'change_status') |
860 | def test_status_change(self, mock_change_status): |
861 | @@ -110,18 +98,18 @@ |
862 | # THEN: poll_loop should exit without calling any other method |
863 | self.assertFalse(pjlink.timer.called, 'Should have returned without calling any other method') |
864 | |
865 | - @patch.object(pjlink_test, 'send_command') |
866 | - def test_poll_loop_start(self, mock_send_command): |
867 | + def test_poll_loop_start(self): |
868 | """ |
869 | Test PJLink.poll_loop makes correct calls |
870 | """ |
871 | - # GIVEN: test object and test data |
872 | - pjlink = pjlink_test |
873 | - pjlink.state = MagicMock() |
874 | - pjlink.timer = MagicMock() |
875 | - pjlink.timer.interval = MagicMock() |
876 | - pjlink.timer.setInterval = MagicMock() |
877 | - pjlink.timer.start = MagicMock() |
878 | + # GIVEN: Mocks and test data |
879 | + mock_state = patch.object(self.pjlink_test, 'state').start() |
880 | + mock_state.return_value = S_QSOCKET_STATE['ConnectedState'] |
881 | + mock_timer = patch.object(self.pjlink_test, 'timer').start() |
882 | + mock_timer.interval.return_value = 10 |
883 | + mock_send_command = patch.object(self.pjlink_test, 'send_command').start() |
884 | + |
885 | + pjlink = self.pjlink_test |
886 | pjlink.poll_time = 20 |
887 | pjlink.power = S_ON |
888 | pjlink.source_available = None |
889 | @@ -130,19 +118,17 @@ |
890 | pjlink.model = None |
891 | pjlink.pjlink_name = None |
892 | pjlink.ConnectedState = S_CONNECTED |
893 | - pjlink.timer.interval.return_value = 10 |
894 | - pjlink.state.return_value = S_CONNECTED |
895 | call_list = [ |
896 | - call('POWR', queue=True), |
897 | - call('ERST', queue=True), |
898 | - call('LAMP', queue=True), |
899 | - call('AVMT', queue=True), |
900 | - call('INPT', queue=True), |
901 | - call('INST', queue=True), |
902 | - call('INFO', queue=True), |
903 | - call('INF1', queue=True), |
904 | - call('INF2', queue=True), |
905 | - call('NAME', queue=True), |
906 | + call('POWR'), |
907 | + call('ERST'), |
908 | + call('LAMP'), |
909 | + call('AVMT'), |
910 | + call('INPT'), |
911 | + call('INST'), |
912 | + call('INFO'), |
913 | + call('INF1'), |
914 | + call('INF2'), |
915 | + call('NAME'), |
916 | ] |
917 | |
918 | # WHEN: PJLink.poll_loop is called |
919 | @@ -150,8 +136,8 @@ |
920 | |
921 | # THEN: proper calls were made to retrieve projector data |
922 | # First, call to update the timer with the next interval |
923 | - self.assertTrue(pjlink.timer.setInterval.called, 'Should have updated the timer') |
924 | + self.assertTrue(mock_timer.setInterval.called) |
925 | # Next, should have called the timer to start |
926 | - self.assertTrue(pjlink.timer.start.called, 'Should have started the timer') |
927 | + self.assertTrue(mock_timer.start.called, 'Should have started the timer') |
928 | # Finally, should have called send_command with a list of projetctor status checks |
929 | mock_send_command.assert_has_calls(call_list, 'Should have queued projector queries') |
930 | |
931 | === modified file 'tests/functional/openlp_core/projectors/test_projector_pjlink_cmd_routing.py' |
932 | --- tests/functional/openlp_core/projectors/test_projector_pjlink_cmd_routing.py 2017-11-16 23:53:53 +0000 |
933 | +++ tests/functional/openlp_core/projectors/test_projector_pjlink_cmd_routing.py 2017-12-09 11:19:42 +0000 |
934 | @@ -46,6 +46,18 @@ |
935 | """ |
936 | Tests for the PJLink module command routing |
937 | """ |
938 | + def setUp(self): |
939 | + ''' |
940 | + TestPJLinkCommands part 2 initialization |
941 | + ''' |
942 | + self.pjlink_test = PJLink(Projector(**TEST1_DATA), no_poll=True) |
943 | + |
944 | + def tearDown(self): |
945 | + ''' |
946 | + TestPJLinkCommands part 2 cleanups |
947 | + ''' |
948 | + self.pjlink_test = None |
949 | + |
950 | @patch.object(openlp.core.projectors.pjlink, 'log') |
951 | def test_process_command_call_clss(self, mock_log): |
952 | """ |
953 | @@ -163,21 +175,20 @@ |
954 | mock_change_status.assert_called_once_with(E_AUTHENTICATION) |
955 | mock_log.error.assert_called_with(log_text) |
956 | |
957 | - @patch.object(openlp.core.projectors.pjlink, 'log') |
958 | - def test_process_command_future(self, mock_log): |
959 | + def test_process_command_future(self): |
960 | """ |
961 | Test command valid but no method to process yet |
962 | """ |
963 | - # GIVEN: Test object |
964 | - pjlink = pjlink_test |
965 | - log_text = "(127.0.0.1) Unable to process command='CLSS' (Future option)" |
966 | - mock_log.reset_mock() |
967 | - # Remove a valid command so we can test valid command but not available yet |
968 | - pjlink.pjlink_functions.pop('CLSS') |
969 | + # GIVEN: Initial mocks and data |
970 | + mock_log = patch.object(openlp.core.projectors.pjlink, 'log').start() |
971 | + mock_functions = patch.object(self.pjlink_test, 'pjlink_functions').start() |
972 | + mock_functions.return_value = [] |
973 | + |
974 | + pjlink = self.pjlink_test |
975 | + log_text = '(111.111.111.111) Unable to process command="CLSS" (Future option?)' |
976 | |
977 | # WHEN: process_command called with an unknown command |
978 | - with patch.object(pjlink, 'pjlink_functions') as mock_functions: |
979 | - pjlink.process_command(cmd='CLSS', data='DONT CARE') |
980 | + pjlink.process_command(cmd='CLSS', data='DONT CARE') |
981 | |
982 | # THEN: Error should be logged and no command called |
983 | self.assertFalse(mock_functions.called, 'Should not have gotten to the end of the method') |
984 | @@ -196,29 +207,26 @@ |
985 | |
986 | # WHEN: process_command called with an unknown command |
987 | pjlink.process_command(cmd='Unknown', data='Dont Care') |
988 | - log_text = "(127.0.0.1) Ignoring command='Unknown' (Invalid/Unknown)" |
989 | + log_text = '(127.0.0.1) Ignoring command="Unknown" (Invalid/Unknown)' |
990 | |
991 | # THEN: Error should be logged and no command called |
992 | self.assertFalse(mock_functions.called, 'Should not have gotten to the end of the method') |
993 | mock_log.error.assert_called_once_with(log_text) |
994 | |
995 | - @patch.object(pjlink_test, 'pjlink_functions') |
996 | - @patch.object(openlp.core.projectors.pjlink, 'log') |
997 | - def test_process_command_ok(self, mock_log, mock_functions): |
998 | + def test_process_command_ok(self): |
999 | """ |
1000 | Test command returned success |
1001 | """ |
1002 | - # GIVEN: Test object |
1003 | - pjlink = pjlink_test |
1004 | - mock_functions.reset_mock() |
1005 | - mock_log.reset_mock() |
1006 | - |
1007 | - # WHEN: process_command called with an unknown command |
1008 | - pjlink.process_command(cmd='CLSS', data='OK') |
1009 | - log_text = '(127.0.0.1) Command "CLSS" returned OK' |
1010 | - |
1011 | - # THEN: Error should be logged and no command called |
1012 | - self.assertFalse(mock_functions.called, 'Should not have gotten to the end of the method') |
1013 | - self.assertEqual(mock_log.debug.call_count, 2, 'log.debug() should have been called twice') |
1014 | - # Although we called it twice, only the last log entry is saved |
1015 | + # GIVEN: Initial mocks and data |
1016 | + mock_log = patch.object(openlp.core.projectors.pjlink, 'log').start() |
1017 | + mock_send_command = patch.object(self.pjlink_test, 'send_command').start() |
1018 | + |
1019 | + pjlink = self.pjlink_test |
1020 | + log_text = '(111.111.111.111) Command "POWR" returned OK' |
1021 | + |
1022 | + # WHEN: process_command called with a command that returns OK |
1023 | + pjlink.process_command(cmd='POWR', data='OK') |
1024 | + |
1025 | + # THEN: Appropriate calls should have been made |
1026 | mock_log.debug.assert_called_with(log_text) |
1027 | + mock_send_command.assert_called_once_with(cmd='POWR') |
1028 | |
1029 | === renamed file 'tests/functional/openlp_core/projectors/test_projector_pjlink_commands.py' => 'tests/functional/openlp_core/projectors/test_projector_pjlink_commands_01.py' |
1030 | --- tests/functional/openlp_core/projectors/test_projector_pjlink_commands.py 2017-11-24 08:30:37 +0000 |
1031 | +++ tests/functional/openlp_core/projectors/test_projector_pjlink_commands_01.py 2017-12-09 11:19:42 +0000 |
1032 | @@ -47,7 +47,7 @@ |
1033 | |
1034 | class TestPJLinkCommands(TestCase): |
1035 | """ |
1036 | - Tests for the PJLink module |
1037 | + Tests for the PJLinkCommands class part 1 |
1038 | """ |
1039 | @patch.object(pjlink_test, 'changeStatus') |
1040 | @patch.object(openlp.core.projectors.pjlink, 'log') |
1041 | @@ -580,7 +580,7 @@ |
1042 | |
1043 | # WHEN: Process invalid reply |
1044 | pjlink.process_clss('Z') |
1045 | - log_text = "(127.0.0.1) NAN clss version reply 'Z' - defaulting to class '1'" |
1046 | + log_text = '(127.0.0.1) NAN CLSS version reply "Z" - defaulting to class "1"' |
1047 | |
1048 | # THEN: Projector class should be set with default value |
1049 | self.assertEqual(pjlink.pjlink_class, '1', |
1050 | @@ -597,7 +597,7 @@ |
1051 | |
1052 | # WHEN: Process invalid reply |
1053 | pjlink.process_clss('Invalid') |
1054 | - log_text = "(127.0.0.1) No numbers found in class version reply 'Invalid' - defaulting to class '1'" |
1055 | + log_text = '(127.0.0.1) No numbers found in class version reply "Invalid" - defaulting to class "1"' |
1056 | |
1057 | # THEN: Projector class should be set with default value |
1058 | self.assertEqual(pjlink.pjlink_class, '1', |
1059 | @@ -627,7 +627,7 @@ |
1060 | # GIVEN: Test object |
1061 | pjlink = pjlink_test |
1062 | pjlink.projector_errors = None |
1063 | - log_text = "127.0.0.1) Invalid error status response '11111111': length != 6" |
1064 | + log_text = '127.0.0.1) Invalid error status response "11111111": length != 6' |
1065 | |
1066 | # WHEN: process_erst called with invalid data (too many values |
1067 | pjlink.process_erst('11111111') |
1068 | @@ -645,7 +645,7 @@ |
1069 | # GIVEN: Test object |
1070 | pjlink = pjlink_test |
1071 | pjlink.projector_errors = None |
1072 | - log_text = "(127.0.0.1) Invalid error status response '1111Z1'" |
1073 | + log_text = '(127.0.0.1) Invalid error status response "1111Z1"' |
1074 | |
1075 | # WHEN: process_erst called with invalid data (too many values |
1076 | pjlink.process_erst('1111Z1') |
1077 | @@ -671,8 +671,8 @@ |
1078 | # THEN: PJLink instance errors should match chk_value |
1079 | for chk in pjlink.projector_errors: |
1080 | self.assertEqual(pjlink.projector_errors[chk], chk_string, |
1081 | - "projector_errors['{chk}'] should have been set to {err}".format(chk=chk, |
1082 | - err=chk_string)) |
1083 | + 'projector_errors["{chk}"] should have been set to "{err}"'.format(chk=chk, |
1084 | + err=chk_string)) |
1085 | |
1086 | def test_projector_process_erst_all_error(self): |
1087 | """ |
1088 | @@ -690,8 +690,8 @@ |
1089 | # THEN: PJLink instance errors should match chk_value |
1090 | for chk in pjlink.projector_errors: |
1091 | self.assertEqual(pjlink.projector_errors[chk], chk_string, |
1092 | - "projector_errors['{chk}'] should have been set to {err}".format(chk=chk, |
1093 | - err=chk_string)) |
1094 | + 'projector_errors["{chk}"] should have been set to "{err}"'.format(chk=chk, |
1095 | + err=chk_string)) |
1096 | |
1097 | def test_projector_process_erst_warn_cover_only(self): |
1098 | """ |
1099 | @@ -744,9 +744,9 @@ |
1100 | pjlink = pjlink_test |
1101 | pjlink.source_available = [] |
1102 | test_data = '21 10 30 31 11 20' |
1103 | - test_saved = ['10', '11', '20', '21', '30', '31'] |
1104 | - log_data = '(127.0.0.1) Setting projector sources_available to ' \ |
1105 | - '"[\'10\', \'11\', \'20\', \'21\', \'30\', \'31\']"' |
1106 | + test_saved = ["10", "11", "20", "21", "30", "31"] |
1107 | + log_data = "(127.0.0.1) Setting projector sources_available to " \ |
1108 | + "\"['10', '11', '20', '21', '30', '31']\"" |
1109 | mock_UpdateIcons.reset_mock() |
1110 | mock_log.reset_mock() |
1111 | |
1112 | @@ -1021,7 +1021,7 @@ |
1113 | pjlink.sw_version = None |
1114 | pjlink.sw_version_received = None |
1115 | test_data = 'Test 1 Subtest 1' |
1116 | - test_log = "(127.0.0.1) Setting projector software version to 'Test 1 Subtest 1'" |
1117 | + test_log = '(127.0.0.1) Setting projector software version to "Test 1 Subtest 1"' |
1118 | mock_log.reset_mock() |
1119 | |
1120 | # WHEN: process_sver called with invalid data |
1121 | |
1122 | === added file 'tests/functional/openlp_core/projectors/test_projector_pjlink_commands_02.py' |
1123 | --- tests/functional/openlp_core/projectors/test_projector_pjlink_commands_02.py 1970-01-01 00:00:00 +0000 |
1124 | +++ tests/functional/openlp_core/projectors/test_projector_pjlink_commands_02.py 2017-12-09 11:19:42 +0000 |
1125 | @@ -0,0 +1,198 @@ |
1126 | +# -*- coding: utf-8 -*- |
1127 | +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 |
1128 | + |
1129 | +############################################################################### |
1130 | +# OpenLP - Open Source Lyrics Projection # |
1131 | +# --------------------------------------------------------------------------- # |
1132 | +# Copyright (c) 2008-2015 OpenLP Developers # |
1133 | +# --------------------------------------------------------------------------- # |
1134 | +# This program is free software; you can redistribute it and/or modify it # |
1135 | +# under the terms of the GNU General Public License as published by the Free # |
1136 | +# Software Foundation; version 2 of the License. # |
1137 | +# # |
1138 | +# This program is distributed in the hope that it will be useful, but WITHOUT # |
1139 | +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # |
1140 | +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # |
1141 | +# more details. # |
1142 | +# # |
1143 | +# You should have received a copy of the GNU General Public License along # |
1144 | +# with this program; if not, write to the Free Software Foundation, Inc., 59 # |
1145 | +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # |
1146 | +############################################################################### |
1147 | +""" |
1148 | +Package to test the openlp.core.projectors.pjlink commands package. |
1149 | +""" |
1150 | +from unittest import TestCase |
1151 | +from unittest.mock import patch, call |
1152 | + |
1153 | +import openlp.core.projectors.pjlink |
1154 | +from openlp.core.projectors.constants import S_CONNECTED |
1155 | +from openlp.core.projectors.db import Projector |
1156 | +from openlp.core.projectors.pjlink import PJLink |
1157 | + |
1158 | +from tests.resources.projector.data import TEST_HASH, TEST_PIN, TEST_SALT, TEST1_DATA |
1159 | + |
1160 | + |
1161 | +class TestPJLinkCommands(TestCase): |
1162 | + """ |
1163 | + Tests for the PJLinkCommands class part 2 |
1164 | + """ |
1165 | + def setUp(self): |
1166 | + ''' |
1167 | + TestPJLinkCommands part 2 initialization |
1168 | + ''' |
1169 | + self.pjlink_test = PJLink(Projector(**TEST1_DATA), no_poll=True) |
1170 | + |
1171 | + def tearDown(self): |
1172 | + ''' |
1173 | + TestPJLinkCommands part 2 cleanups |
1174 | + ''' |
1175 | + self.pjlink_test = None |
1176 | + |
1177 | + def test_process_pjlink_normal(self): |
1178 | + """ |
1179 | + Test initial connection prompt with no authentication |
1180 | + """ |
1181 | + # GIVEN: Initial mocks and data |
1182 | + mock_log = patch.object(openlp.core.projectors.pjlink, "log").start() |
1183 | + mock_disconnect_from_host = patch.object(self.pjlink_test, 'disconnect_from_host').start() |
1184 | + mock_send_command = patch.object(self.pjlink_test, 'send_command').start() |
1185 | + mock_readyRead = patch.object(self.pjlink_test, 'readyRead').start() |
1186 | + mock_change_status = patch.object(self.pjlink_test, 'change_status').start() |
1187 | + pjlink = self.pjlink_test |
1188 | + pjlink.pin = None |
1189 | + log_check = [call("({111.111.111.111}) process_pjlink(): Sending 'CLSS' initial command'"), ] |
1190 | + |
1191 | + # WHEN: process_pjlink called with no authentication required |
1192 | + pjlink.process_pjlink(data="0") |
1193 | + |
1194 | + # THEN: proper processing should have occured |
1195 | + mock_log.debug.has_calls(log_check) |
1196 | + mock_disconnect_from_host.assert_not_called() |
1197 | + self.assertEqual(mock_readyRead.connect.call_count, 1, 'Should have only been called once') |
1198 | + mock_change_status.assert_called_once_with(S_CONNECTED) |
1199 | + mock_send_command.assert_called_with(cmd='CLSS', priority=True, salt=None) |
1200 | + |
1201 | + def test_process_pjlink_authenticate(self): |
1202 | + """ |
1203 | + Test initial connection prompt with authentication |
1204 | + """ |
1205 | + # GIVEN: Initial mocks and data |
1206 | + mock_log = patch.object(openlp.core.projectors.pjlink, "log").start() |
1207 | + mock_disconnect_from_host = patch.object(self.pjlink_test, 'disconnect_from_host').start() |
1208 | + mock_send_command = patch.object(self.pjlink_test, 'send_command').start() |
1209 | + mock_readyRead = patch.object(self.pjlink_test, 'readyRead').start() |
1210 | + mock_change_status = patch.object(self.pjlink_test, 'change_status').start() |
1211 | + pjlink = self.pjlink_test |
1212 | + pjlink.pin = TEST_PIN |
1213 | + log_check = [call("({111.111.111.111}) process_pjlink(): Sending 'CLSS' initial command'"), ] |
1214 | + |
1215 | + # WHEN: process_pjlink called with no authentication required |
1216 | + pjlink.process_pjlink(data='1 {salt}'.format(salt=TEST_SALT)) |
1217 | + |
1218 | + # THEN: proper processing should have occured |
1219 | + mock_log.debug.has_calls(log_check) |
1220 | + mock_disconnect_from_host.assert_not_called() |
1221 | + self.assertEqual(mock_readyRead.connect.call_count, 1, 'Should have only been called once') |
1222 | + mock_change_status.assert_called_once_with(S_CONNECTED) |
1223 | + mock_send_command.assert_called_with(cmd='CLSS', priority=True, salt=TEST_HASH) |
1224 | + |
1225 | + def test_process_pjlink_normal_pin_set_error(self): |
1226 | + """ |
1227 | + Test process_pjlinnk called with no authentication but pin is set |
1228 | + """ |
1229 | + # GIVEN: Initial mocks and data |
1230 | + # GIVEN: Initial mocks and data |
1231 | + mock_log = patch.object(openlp.core.projectors.pjlink, 'log').start() |
1232 | + mock_disconnect_from_host = patch.object(self.pjlink_test, 'disconnect_from_host').start() |
1233 | + mock_send_command = patch.object(self.pjlink_test, 'send_command').start() |
1234 | + pjlink = self.pjlink_test |
1235 | + pjlink.pin = TEST_PIN |
1236 | + log_check = [call('(111.111.111.111) Normal connection but PIN set - aborting'), ] |
1237 | + |
1238 | + # WHEN: process_pjlink called with invalid authentication scheme |
1239 | + pjlink.process_pjlink(data='0') |
1240 | + |
1241 | + # THEN: Proper calls should be made |
1242 | + mock_log.error.assert_has_calls(log_check) |
1243 | + self.assertEqual(mock_disconnect_from_host.call_count, 1, 'Should have only been called once') |
1244 | + mock_send_command.assert_not_called() |
1245 | + |
1246 | + def test_process_pjlink_normal_with_salt_error(self): |
1247 | + """ |
1248 | + Test process_pjlinnk called with no authentication but pin is set |
1249 | + """ |
1250 | + # GIVEN: Initial mocks and data |
1251 | + # GIVEN: Initial mocks and data |
1252 | + mock_log = patch.object(openlp.core.projectors.pjlink, 'log').start() |
1253 | + mock_disconnect_from_host = patch.object(self.pjlink_test, 'disconnect_from_host').start() |
1254 | + mock_send_command = patch.object(self.pjlink_test, 'send_command').start() |
1255 | + pjlink = self.pjlink_test |
1256 | + pjlink.pin = TEST_PIN |
1257 | + log_check = [call('(111.111.111.111) Normal connection with extra information - aborting'), ] |
1258 | + |
1259 | + # WHEN: process_pjlink called with invalid authentication scheme |
1260 | + pjlink.process_pjlink(data='0 {salt}'.format(salt=TEST_SALT)) |
1261 | + |
1262 | + # THEN: Proper calls should be made |
1263 | + mock_log.error.assert_has_calls(log_check) |
1264 | + self.assertEqual(mock_disconnect_from_host.call_count, 1, 'Should have only been called once') |
1265 | + mock_send_command.assert_not_called() |
1266 | + |
1267 | + def test_process_pjlink_invalid_authentication_scheme_length_error(self): |
1268 | + """ |
1269 | + Test initial connection prompt with authentication scheme longer than 1 character |
1270 | + """ |
1271 | + # GIVEN: Initial mocks and data |
1272 | + mock_log = patch.object(openlp.core.projectors.pjlink, 'log').start() |
1273 | + mock_disconnect_from_host = patch.object(self.pjlink_test, 'disconnect_from_host').start() |
1274 | + mock_send_command = patch.object(self.pjlink_test, 'send_command').start() |
1275 | + pjlink = self.pjlink_test |
1276 | + log_check = [call('(111.111.111.111) Invalid initial authentication scheme - aborting'), ] |
1277 | + |
1278 | + # WHEN: process_pjlink called with invalid authentication scheme |
1279 | + pjlink.process_pjlink(data='01') |
1280 | + |
1281 | + # THEN: socket should be closed and invalid data logged |
1282 | + mock_log.error.assert_has_calls(log_check) |
1283 | + self.assertEqual(mock_disconnect_from_host.call_count, 1, 'Should have only been called once') |
1284 | + mock_send_command.assert_not_called() |
1285 | + |
1286 | + def test_process_pjlink_invalid_authentication_data_length_error(self): |
1287 | + """ |
1288 | + Test initial connection prompt with authentication no salt |
1289 | + """ |
1290 | + # GIVEN: Initial mocks and data |
1291 | + mock_log = patch.object(openlp.core.projectors.pjlink, 'log').start() |
1292 | + mock_disconnect_from_host = patch.object(self.pjlink_test, 'disconnect_from_host').start() |
1293 | + mock_send_command = patch.object(self.pjlink_test, 'send_command').start() |
1294 | + log_check = [call('(111.111.111.111) Authenticated connection but not enough info - aborting'), ] |
1295 | + pjlink = self.pjlink_test |
1296 | + |
1297 | + # WHEN: process_pjlink called with no salt |
1298 | + pjlink.process_pjlink(data='1') |
1299 | + |
1300 | + # THEN: socket should be closed and invalid data logged |
1301 | + mock_log.error.assert_has_calls(log_check) |
1302 | + self.assertEqual(mock_disconnect_from_host.call_count, 1, 'Should have only been called once') |
1303 | + mock_send_command.assert_not_called() |
1304 | + |
1305 | + def test_process_pjlink_authenticate_pin_not_set_error(self): |
1306 | + """ |
1307 | + Test process_pjlink authentication but pin not set |
1308 | + """ |
1309 | + # GIVEN: Initial mocks and data |
1310 | + mock_log = patch.object(openlp.core.projectors.pjlink, 'log').start() |
1311 | + mock_disconnect_from_host = patch.object(self.pjlink_test, 'disconnect_from_host').start() |
1312 | + mock_send_command = patch.object(self.pjlink_test, 'send_command').start() |
1313 | + log_check = [call('(111.111.111.111) Authenticate connection but no PIN - aborting'), ] |
1314 | + pjlink = self.pjlink_test |
1315 | + pjlink.pin = None |
1316 | + |
1317 | + # WHEN: process_pjlink called with no salt |
1318 | + pjlink.process_pjlink(data='1 {salt}'.format(salt=TEST_SALT)) |
1319 | + |
1320 | + # THEN: socket should be closed and invalid data logged |
1321 | + mock_log.error.assert_has_calls(log_check) |
1322 | + self.assertEqual(mock_disconnect_from_host.call_count, 1, 'Should have only been called once') |
1323 | + mock_send_command.assert_not_called() |
Just some style fixes please.
There's a couple inline comments.
Also you've got a right mismatch between single and double quoted strings. Can you change these so that strings use single quotes. I'm not sure there is a standard for quotes within the string, but double quotes are tidier as they do not need escaping. I'm sure I've seen this somewhere in the wiki, but I cannot find it on the coding standards page.