Skip to content

Commit 82bce7a

Browse files
authored
Merge pull request #42 from zigpy/rc
0.2.0 Release
2 parents 7369d85 + e5683bc commit 82bce7a

File tree

10 files changed

+479
-119
lines changed

10 files changed

+479
-119
lines changed

setup.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22

33
from setuptools import find_packages, setup
44

5+
import zigpy_xbee.const as xbee_const
6+
57
setup(
68
name="zigpy-xbee-homeassistant",
7-
version="0.1.3",
9+
version=xbee_const.__version__,
810
description="A library which communicates with XBee radios for zigpy",
911
url="http://github.com/zigpy/zigpy-xbee",
1012
author="Russell Cloran",

tests/test_api.py

Lines changed: 151 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,9 @@ def test_close(api):
3333
def test_commands():
3434
import string
3535
anum = string.ascii_letters + string.digits + '_'
36+
commands = {**xbee_api.COMMAND_REQUESTS, **xbee_api.COMMAND_RESPONSES}
3637

37-
for cmd_name, cmd_opts in xbee_api.COMMANDS.items():
38+
for cmd_name, cmd_opts in commands.items():
3839
assert isinstance(cmd_name, str) is True
3940
assert all([c in anum for c in cmd_name]), cmd_name
4041
assert len(cmd_opts) == 3
@@ -47,12 +48,12 @@ def test_commands():
4748
@pytest.mark.asyncio
4849
async def test_command(api):
4950
def mock_api_frame(name, *args):
50-
c = xbee_api.COMMANDS[name]
51+
c = xbee_api.COMMAND_REQUESTS[name]
5152
return mock.sentinel.api_frame_data, c[2]
5253
api._api_frame = mock.MagicMock(side_effect=mock_api_frame)
5354
api._uart.send = mock.MagicMock()
5455

55-
for cmd_name, cmd_opts in xbee_api.COMMANDS.items():
56+
for cmd_name, cmd_opts in xbee_api.COMMAND_REQUESTS.items():
5657
cmd_id, schema, expect_reply = cmd_opts
5758
ret = api._command(cmd_name, mock.sentinel.cmd_data)
5859
if expect_reply:
@@ -62,28 +63,30 @@ def mock_api_frame(name, *args):
6263
assert ret is None
6364
assert api._api_frame.call_count == 1
6465
assert api._api_frame.call_args[0][0] == cmd_name
65-
assert api._api_frame.call_args[0][1] == mock.sentinel.cmd_data
66+
assert api._api_frame.call_args[0][1] == api._seq - 1
67+
assert api._api_frame.call_args[0][2] == mock.sentinel.cmd_data
6668
assert api._uart.send.call_count == 1
6769
assert api._uart.send.call_args[0][0] == mock.sentinel.api_frame_data
6870
api._api_frame.reset_mock()
6971
api._uart.send.reset_mock()
7072

71-
72-
def test_seq_command(api):
73-
api._command = mock.MagicMock()
74-
api._seq = mock.sentinel.seq
75-
api._seq_command(mock.sentinel.cmd_name, mock.sentinel.args)
76-
assert api._command.call_count == 1
77-
assert api._command.call_args[0][0] == mock.sentinel.cmd_name
78-
assert api._command.call_args[0][1] == mock.sentinel.seq
79-
assert api._command.call_args[0][2] == mock.sentinel.args
73+
ret = api._command(cmd_name, mock.sentinel.cmd_data, mask_frame_id=True)
74+
assert ret is None
75+
assert api._api_frame.call_count == 1
76+
assert api._api_frame.call_args[0][0] == cmd_name
77+
assert api._api_frame.call_args[0][1] == 0
78+
assert api._api_frame.call_args[0][2] == mock.sentinel.cmd_data
79+
assert api._uart.send.call_count == 1
80+
assert api._uart.send.call_args[0][0] == mock.sentinel.api_frame_data
81+
api._api_frame.reset_mock()
82+
api._uart.send.reset_mock()
8083

8184

8285
async def _test_at_or_queued_at_command(api, cmd, monkeypatch, do_reply=True):
8386
monkeypatch.setattr(t, 'serialize', mock.MagicMock(return_value=mock.sentinel.serialize))
8487

8588
def mock_command(name, *args):
86-
rsp = xbee_api.COMMANDS[name][2]
89+
rsp = xbee_api.COMMAND_REQUESTS[name][2]
8790
ret = None
8891
if rsp:
8992
ret = asyncio.Future()
@@ -99,9 +102,8 @@ def mock_command(name, *args):
99102
assert t.serialize.call_count == 1
100103
assert api._command.call_count == 1
101104
assert api._command.call_args[0][0] in ('at', 'queued_at')
102-
assert api._command.call_args[0][1] == mock.sentinel.seq
103-
assert api._command.call_args[0][2] == at_cmd.encode('ascii')
104-
assert api._command.call_args[0][3] == mock.sentinel.serialize
105+
assert api._command.call_args[0][1] == at_cmd.encode('ascii')
106+
assert api._command.call_args[0][2] == mock.sentinel.serialize
105107
assert res == mock.sentinel.at_result
106108
t.serialize.reset_mock()
107109
api._command.reset_mock()
@@ -125,23 +127,79 @@ async def test_queued_at_command(api, monkeypatch):
125127
await _test_at_or_queued_at_command(api, api._queued_at, monkeypatch)
126128

127129

130+
async def _test_remote_at_command(api, monkeypatch, do_reply=True):
131+
monkeypatch.setattr(t, 'serialize', mock.MagicMock(return_value=mock.sentinel.serialize))
132+
133+
def mock_command(name, *args):
134+
rsp = xbee_api.COMMAND_REQUESTS[name][2]
135+
ret = None
136+
if rsp:
137+
ret = asyncio.Future()
138+
if do_reply:
139+
ret.set_result(mock.sentinel.at_result)
140+
return ret
141+
142+
api._command = mock.MagicMock(side_effect=mock_command)
143+
api._seq = mock.sentinel.seq
144+
145+
for at_cmd in xbee_api.AT_COMMANDS:
146+
res = await api._remote_at_command(
147+
mock.sentinel.ieee, mock.sentinel.nwk, mock.sentinel.opts, at_cmd,
148+
mock.sentinel.args)
149+
assert t.serialize.call_count == 1
150+
assert api._command.call_count == 1
151+
assert api._command.call_args[0][0] == 'remote_at'
152+
assert api._command.call_args[0][1] == mock.sentinel.ieee
153+
assert api._command.call_args[0][2] == mock.sentinel.nwk
154+
assert api._command.call_args[0][3] == mock.sentinel.opts
155+
assert api._command.call_args[0][4] == at_cmd.encode('ascii')
156+
assert api._command.call_args[0][5] == mock.sentinel.serialize
157+
assert res == mock.sentinel.at_result
158+
t.serialize.reset_mock()
159+
api._command.reset_mock()
160+
161+
162+
@pytest.mark.asyncio
163+
async def test_remote_at_cmd(api, monkeypatch):
164+
await _test_remote_at_command(api, monkeypatch)
165+
166+
167+
@pytest.mark.asyncio
168+
async def test_remote_at_cmd_no_rsp(api, monkeypatch):
169+
monkeypatch.setattr(xbee_api, 'REMOTE_AT_COMMAND_TIMEOUT', 0.1)
170+
with pytest.raises(asyncio.TimeoutError):
171+
await _test_remote_at_command(api, monkeypatch, do_reply=False)
172+
173+
128174
def test_api_frame(api):
129175
ieee = t.EUI64([t.uint8_t(a) for a in range(0, 8)])
130-
for cmd_name, cmd_opts in xbee_api.COMMANDS.items():
176+
for cmd_name, cmd_opts in xbee_api.COMMAND_REQUESTS.items():
131177
cmd_id, schema, repl = cmd_opts
132178
if schema:
133-
args = [ieee if isinstance(a(), t.EUI64) else a() for a in schema]
179+
args = [ieee if issubclass(a, t.EUI64) else a() for a in schema]
134180
frame, repl = api._api_frame(cmd_name, *args)
135181
else:
136182
frame, repl = api._api_frame(cmd_name)
137183

138184

139185
def test_frame_received(api, monkeypatch):
140186
monkeypatch.setattr(t, 'deserialize', mock.MagicMock(
141-
return_value=(mock.sentinel.deserialize_data, b'')))
187+
return_value=(
188+
[
189+
mock.sentinel.arg_0,
190+
mock.sentinel.arg_1,
191+
mock.sentinel.arg_2,
192+
mock.sentinel.arg_3,
193+
mock.sentinel.arg_4,
194+
mock.sentinel.arg_5,
195+
mock.sentinel.arg_6,
196+
mock.sentinel.arg_7,
197+
mock.sentinel.arg_8,
198+
], b'')
199+
))
142200
my_handler = mock.MagicMock()
143201

144-
for cmd, cmd_opts in xbee_api.COMMANDS.items():
202+
for cmd, cmd_opts in xbee_api.COMMAND_RESPONSES.items():
145203
cmd_id = cmd_opts[0]
146204
payload = b'\x01\x02\x03\x04'
147205
data = cmd_id.to_bytes(1, 'big') + payload
@@ -150,7 +208,10 @@ def test_frame_received(api, monkeypatch):
150208
assert t.deserialize.call_count == 1
151209
assert t.deserialize.call_args[0][0] == payload
152210
assert my_handler.call_count == 1
153-
assert my_handler.call_args[0][0] == mock.sentinel.deserialize_data
211+
assert my_handler.call_args[0][0] == mock.sentinel.arg_0
212+
assert my_handler.call_args[0][1] == mock.sentinel.arg_1
213+
assert my_handler.call_args[0][2] == mock.sentinel.arg_2
214+
assert my_handler.call_args[0][3] == mock.sentinel.arg_3
154215
t.deserialize.reset_mock()
155216
my_handler.reset_mock()
156217

@@ -161,10 +222,10 @@ def test_frame_received_no_handler(api, monkeypatch):
161222
my_handler = mock.MagicMock()
162223
cmd = 'no_handler'
163224
cmd_id = 0x00
164-
xbee_api.COMMANDS[cmd] = (cmd_id, (), None)
225+
xbee_api.COMMAND_RESPONSES[cmd] = (cmd_id, (), None)
165226
api._commands_by_id[cmd_id] = cmd
166227

167-
cmd_opts = xbee_api.COMMANDS[cmd]
228+
cmd_opts = xbee_api.COMMAND_RESPONSES[cmd]
168229
cmd_id = cmd_opts[0]
169230
payload = b'\x01\x02\x03\x04'
170231
data = cmd_id.to_bytes(1, 'big') + payload
@@ -178,7 +239,7 @@ def _handle_at_response(api, tsn, status, at_response=b''):
178239
data = (tsn, 'AI'.encode('ascii'), status, at_response)
179240
response = asyncio.Future()
180241
api._awaiting[tsn] = (response, )
181-
api._handle_at_response(data)
242+
api._handle_at_response(*data)
182243
return response
183244

184245

@@ -215,9 +276,21 @@ def test_handle_at_response_undef_error(api):
215276
assert fut.exception() is not None
216277

217278

279+
def test_handle_remote_at_rsp(api):
280+
api._handle_at_response = mock.MagicMock()
281+
s = mock.sentinel
282+
api._handle_remote_at_response(s.frame_id, s.ieee, s.nwk, s.cmd,
283+
s.status, s.data)
284+
assert api._handle_at_response.call_count == 1
285+
assert api._handle_at_response.call_args[0][0] == s.frame_id
286+
assert api._handle_at_response.call_args[0][1] == s.cmd
287+
assert api._handle_at_response.call_args[0][2] == s.status
288+
assert api._handle_at_response.call_args[0][3] == s.data
289+
290+
218291
def _send_modem_event(api, event):
219292
api._app = mock.MagicMock(spec=ControllerApplication)
220-
api._handle_modem_status([event])
293+
api._handle_modem_status(event)
221294
assert api._app.handle_modem_status.call_count == 1
222295
assert api._app.handle_modem_status.call_args[0][0] == event
223296

@@ -243,15 +316,60 @@ def test_handle_modem_status(api):
243316

244317

245318
def test_handle_explicit_rx_indicator(api):
246-
data = b'\x00\x01\x02\x03\x04\x05\x06\x07'
319+
s = mock.sentinel
320+
data = [s.src_ieee, s.src_nwk, s.src_ep, s.dst_ep, s.cluster_id, s.profile,
321+
s.opts, b'abcdef']
247322
api._app = mock.MagicMock()
248323
api._app.handle_rx = mock.MagicMock()
249-
api._handle_explicit_rx_indicator(data)
324+
api._handle_explicit_rx_indicator(*data)
250325
assert api._app.handle_rx.call_count == 1
251326

252327

253-
def test_handle_tx_status(api):
254-
api._handle_tx_status(b'\x01\x02\x03\x04')
328+
def _handle_tx_status(api, status, wrong_frame_id=False):
329+
status = t.TXStatus(status)
330+
frame_id = 0x12
331+
send_fut = mock.MagicMock(spec=asyncio.Future)
332+
api._awaiting[frame_id] = (send_fut, )
333+
s = mock.sentinel
334+
if wrong_frame_id:
335+
frame_id += 1
336+
api._handle_tx_status(frame_id, s.dst_nwk, s.retries, status,
337+
t.DiscoveryStatus())
338+
return send_fut
339+
340+
341+
def test_handle_tx_status_success(api):
342+
fut = _handle_tx_status(api, t.TXStatus.SUCCESS)
343+
assert len(api._awaiting) == 0
344+
assert fut.set_result.call_count == 1
345+
assert fut.set_exception.call_count == 0
346+
347+
348+
def test_handle_tx_status_except(api):
349+
fut = _handle_tx_status(api, t.TXStatus.ADDRESS_NOT_FOUND)
350+
assert len(api._awaiting) == 0
351+
assert fut.set_result.call_count == 0
352+
assert fut.set_exception.call_count == 1
353+
354+
355+
def test_handle_tx_status_unexpected(api):
356+
fut = _handle_tx_status(api, 1, wrong_frame_id=True)
357+
assert len(api._awaiting) == 1
358+
assert fut.set_result.call_count == 0
359+
assert fut.set_exception.call_count == 0
360+
361+
362+
def test_handle_tx_status_duplicate(api):
363+
status = t.TXStatus.SUCCESS
364+
frame_id = 0x12
365+
send_fut = mock.MagicMock(spec=asyncio.Future)
366+
send_fut.set_result.side_effect = asyncio.InvalidStateError
367+
api._awaiting[frame_id] = (send_fut, )
368+
s = mock.sentinel
369+
api._handle_tx_status(frame_id, s.dst_nwk, s.retries, status, s.disc)
370+
assert len(api._awaiting) == 0
371+
assert send_fut.set_result.call_count == 1
372+
assert send_fut.set_exception.call_count == 0
255373

256374

257375
@pytest.mark.asyncio
@@ -364,10 +482,11 @@ def test_set_application(api):
364482

365483

366484
def test_handle_route_record_indicator(api):
367-
api._handle_route_record_indicator(mock.sentinel.ri)
485+
s = mock.sentinel
486+
api._handle_route_record_indicator(s.ieee, s.src, s.rx_opts, s.hops)
368487

369488

370489
def test_handle_many_to_one_rri(api):
371490
ieee = t.EUI64([t.uint8_t(a) for a in range(0, 8)])
372491
nwk = 0x1234
373-
api._handle_many_to_one_rri([ieee, nwk, 0])
492+
api._handle_many_to_one_rri(ieee, nwk, 0)

0 commit comments

Comments
 (0)