1
1
import asyncio
2
- from unittest import mock
2
+ import logging
3
3
4
+ from asynctest import CoroutineMock , mock
4
5
import pytest
6
+ import serial
7
+ import zigpy .exceptions
5
8
6
9
from zigpy_xbee import api as xbee_api , types as t , uart
7
10
from zigpy_xbee .zigbee .application import ControllerApplication
@@ -25,9 +28,10 @@ async def test_connect(monkeypatch):
25
28
26
29
27
30
def test_close (api ):
28
- api . _uart . close = mock . MagicMock ()
31
+ uart = api . _uart
29
32
api .close ()
30
- assert api ._uart .close .call_count == 1
33
+ assert api ._uart is None
34
+ assert uart .close .call_count == 1
31
35
32
36
33
37
def test_commands ():
@@ -84,6 +88,22 @@ def mock_api_frame(name, *args):
84
88
api ._uart .send .reset_mock ()
85
89
86
90
91
+ @pytest .mark .asyncio
92
+ async def test_command_not_connected (api ):
93
+ api ._uart = None
94
+
95
+ def mock_api_frame (name , * args ):
96
+ return mock .sentinel .api_frame_data , api ._seq
97
+
98
+ api ._api_frame = mock .MagicMock (side_effect = mock_api_frame )
99
+
100
+ for cmd , cmd_opts in xbee_api .COMMAND_REQUESTS .items ():
101
+ with pytest .raises (zigpy .exceptions .APIException ):
102
+ await api ._command (cmd , mock .sentinel .cmd_data )
103
+ assert api ._api_frame .call_count == 0
104
+ api ._api_frame .reset_mock ()
105
+
106
+
87
107
async def _test_at_or_queued_at_command (api , cmd , monkeypatch , do_reply = True ):
88
108
monkeypatch .setattr (
89
109
t , "serialize" , mock .MagicMock (return_value = mock .sentinel .serialize )
@@ -518,3 +538,131 @@ def test_handle_many_to_one_rri(api):
518
538
ieee = t .EUI64 ([t .uint8_t (a ) for a in range (0 , 8 )])
519
539
nwk = 0x1234
520
540
api ._handle_many_to_one_rri (ieee , nwk , 0 )
541
+
542
+
543
+ @pytest .mark .asyncio
544
+ async def test_reconnect_multiple_disconnects (monkeypatch , caplog ):
545
+ api = xbee_api .XBee ()
546
+ dev = mock .sentinel .uart
547
+ connect_mock = CoroutineMock ()
548
+ connect_mock .return_value = asyncio .Future ()
549
+ connect_mock .return_value .set_result (True )
550
+ monkeypatch .setattr (uart , "connect" , connect_mock )
551
+
552
+ await api .connect (dev , 115200 )
553
+
554
+ caplog .set_level (logging .DEBUG )
555
+ connected = asyncio .Future ()
556
+ connected .set_result (mock .sentinel .uart_reconnect )
557
+ connect_mock .reset_mock ()
558
+ connect_mock .side_effect = [asyncio .Future (), connected ]
559
+ api .connection_lost ("connection lost" )
560
+ await asyncio .sleep (0.3 )
561
+ api .connection_lost ("connection lost 2" )
562
+ await asyncio .sleep (0.3 )
563
+
564
+ assert "Cancelling reconnection attempt" in caplog .messages
565
+ assert api ._uart is mock .sentinel .uart_reconnect
566
+ assert connect_mock .call_count == 2
567
+
568
+
569
+ @pytest .mark .asyncio
570
+ async def test_reconnect_multiple_attempts (monkeypatch , caplog ):
571
+ api = xbee_api .XBee ()
572
+ dev = mock .sentinel .uart
573
+ connect_mock = CoroutineMock ()
574
+ connect_mock .return_value = asyncio .Future ()
575
+ connect_mock .return_value .set_result (True )
576
+ monkeypatch .setattr (uart , "connect" , connect_mock )
577
+
578
+ await api .connect (dev , 115200 )
579
+
580
+ caplog .set_level (logging .DEBUG )
581
+ connected = asyncio .Future ()
582
+ connected .set_result (mock .sentinel .uart_reconnect )
583
+ connect_mock .reset_mock ()
584
+ connect_mock .side_effect = [asyncio .TimeoutError , OSError , connected ]
585
+
586
+ with mock .patch ("asyncio.sleep" ):
587
+ api .connection_lost ("connection lost" )
588
+ await api ._conn_lost_task
589
+
590
+ assert api ._uart is mock .sentinel .uart_reconnect
591
+ assert connect_mock .call_count == 3
592
+
593
+
594
+ @pytest .mark .asyncio
595
+ @mock .patch .object (xbee_api .XBee , "_at_command" , new_callable = CoroutineMock )
596
+ @mock .patch .object (uart , "connect" )
597
+ async def test_probe_success (mock_connect , mock_at_cmd ):
598
+ """Test device probing."""
599
+
600
+ res = await xbee_api .XBee .probe (mock .sentinel .uart , mock .sentinel .baud )
601
+ assert res is True
602
+ assert mock_connect .call_count == 1
603
+ assert mock_connect .await_count == 1
604
+ assert mock_connect .call_args [0 ][0 ] is mock .sentinel .uart
605
+ assert mock_at_cmd .call_count == 1
606
+ assert mock_connect .return_value .close .call_count == 1
607
+
608
+
609
+ @pytest .mark .asyncio
610
+ @mock .patch .object (xbee_api .XBee , "init_api_mode" , return_value = True )
611
+ @mock .patch .object (xbee_api .XBee , "_at_command" , side_effect = asyncio .TimeoutError )
612
+ @mock .patch .object (uart , "connect" )
613
+ async def test_probe_success_api_mode (mock_connect , mock_at_cmd , mock_api_mode ):
614
+ """Test device probing."""
615
+
616
+ res = await xbee_api .XBee .probe (mock .sentinel .uart , mock .sentinel .baud )
617
+ assert res is True
618
+ assert mock_connect .call_count == 1
619
+ assert mock_connect .await_count == 1
620
+ assert mock_connect .call_args [0 ][0 ] is mock .sentinel .uart
621
+ assert mock_at_cmd .call_count == 1
622
+ assert mock_api_mode .call_count == 1
623
+ assert mock_connect .return_value .close .call_count == 1
624
+
625
+
626
+ @pytest .mark .asyncio
627
+ @mock .patch .object (xbee_api .XBee , "init_api_mode" )
628
+ @mock .patch .object (xbee_api .XBee , "_at_command" , side_effect = asyncio .TimeoutError )
629
+ @mock .patch .object (uart , "connect" )
630
+ @pytest .mark .parametrize (
631
+ "exception" ,
632
+ (asyncio .TimeoutError , serial .SerialException , zigpy .exceptions .APIException ),
633
+ )
634
+ async def test_probe_fail (mock_connect , mock_at_cmd , mock_api_mode , exception ):
635
+ """Test device probing fails."""
636
+
637
+ mock_api_mode .side_effect = exception
638
+ mock_api_mode .reset_mock ()
639
+ mock_at_cmd .reset_mock ()
640
+ mock_connect .reset_mock ()
641
+ res = await xbee_api .XBee .probe (mock .sentinel .uart , mock .sentinel .baud )
642
+ assert res is False
643
+ assert mock_connect .call_count == 1
644
+ assert mock_connect .await_count == 1
645
+ assert mock_connect .call_args [0 ][0 ] is mock .sentinel .uart
646
+ assert mock_at_cmd .call_count == 1
647
+ assert mock_api_mode .call_count == 1
648
+ assert mock_connect .return_value .close .call_count == 1
649
+
650
+
651
+ @pytest .mark .asyncio
652
+ @mock .patch .object (xbee_api .XBee , "init_api_mode" , return_value = False )
653
+ @mock .patch .object (xbee_api .XBee , "_at_command" , side_effect = asyncio .TimeoutError )
654
+ @mock .patch .object (uart , "connect" )
655
+ async def test_probe_fail_api_mode (mock_connect , mock_at_cmd , mock_api_mode ):
656
+ """Test device probing fails."""
657
+
658
+ mock_api_mode .reset_mock ()
659
+ mock_at_cmd .reset_mock ()
660
+ mock_connect .reset_mock ()
661
+ res = await xbee_api .XBee .probe (mock .sentinel .uart , mock .sentinel .baud )
662
+ assert res is False
663
+ assert mock_connect .call_count == 1
664
+ assert mock_connect .await_count == 1
665
+ assert mock_connect .call_args [0 ][0 ] is mock .sentinel .uart
666
+ assert mock_at_cmd .call_count == 1
667
+ assert mock_api_mode .call_count == 1
668
+ assert mock_connect .return_value .close .call_count == 1
0 commit comments