Skip to content

Commit fc3f3ee

Browse files
committed
Bring test coverage to 100%
1 parent 30d0d8b commit fc3f3ee

File tree

5 files changed

+256
-18
lines changed

5 files changed

+256
-18
lines changed

tests/application/test_requests.py

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -917,3 +917,95 @@ async def test_route_discovery_concurrency(device, make_application):
917917
assert route_discovery2.call_count == 2
918918

919919
await app.shutdown()
920+
921+
922+
@pytest.mark.parametrize("device", FORMED_DEVICES)
923+
async def test_send_security_and_packet_source_route(device, make_application, mocker):
924+
app, znp_server = await make_application(server_cls=device)
925+
await app.startup(auto_form=False)
926+
927+
packet = zigpy_t.ZigbeePacket(
928+
src=zigpy_t.AddrModeAddress(
929+
addr_mode=zigpy_t.AddrMode.NWK, address=app.state.node_info.nwk
930+
),
931+
src_ep=0x9A,
932+
dst=zigpy_t.AddrModeAddress(addr_mode=zigpy_t.AddrMode.NWK, address=0xEEFF),
933+
dst_ep=0xBC,
934+
tsn=0xDE,
935+
profile_id=0x1234,
936+
cluster_id=0x0006,
937+
data=zigpy_t.SerializableBytes(b"test data"),
938+
extended_timeout=False,
939+
tx_options=(
940+
zigpy_t.TransmitOptions.ACK | zigpy_t.TransmitOptions.APS_Encryption
941+
),
942+
source_route=[0xAABB, 0xCCDD],
943+
)
944+
945+
data_req = znp_server.reply_once_to(
946+
request=c.AF.DataRequestSrcRtg.Req(
947+
DstAddr=packet.dst.address,
948+
DstEndpoint=packet.dst_ep,
949+
# SrcEndpoint=packet.src_ep,
950+
ClusterId=packet.cluster_id,
951+
TSN=packet.tsn,
952+
Data=packet.data.serialize(),
953+
SourceRoute=packet.source_route,
954+
partial=True,
955+
),
956+
responses=[
957+
c.AF.DataRequestSrcRtg.Rsp(Status=t.Status.SUCCESS),
958+
c.AF.DataConfirm.Callback(
959+
Status=t.Status.SUCCESS,
960+
Endpoint=packet.dst_ep,
961+
TSN=packet.tsn,
962+
),
963+
],
964+
)
965+
966+
await app.send_packet(packet)
967+
req = await data_req
968+
assert c.af.TransmitOptions.ENABLE_SECURITY in req.Options
969+
970+
await app.shutdown()
971+
972+
973+
@pytest.mark.parametrize("device", FORMED_DEVICES)
974+
async def test_send_packet_failure(device, make_application, mocker):
975+
app, znp_server = await make_application(server_cls=device)
976+
await app.startup(auto_form=False)
977+
978+
packet = zigpy_t.ZigbeePacket(
979+
src=zigpy_t.AddrModeAddress(addr_mode=zigpy_t.AddrMode.NWK, address=0x0000),
980+
src_ep=0x9A,
981+
dst=zigpy_t.AddrModeAddress(addr_mode=zigpy_t.AddrMode.NWK, address=0xEEFF),
982+
dst_ep=0xBC,
983+
tsn=0xDE,
984+
profile_id=0x1234,
985+
cluster_id=0x0006,
986+
data=zigpy_t.SerializableBytes(b"test data"),
987+
)
988+
989+
znp_server.reply_to(
990+
request=c.ZDO.ExtRouteDisc.Req(Dst=packet.dst.address, partial=True),
991+
responses=[c.ZDO.ExtRouteDisc.Rsp(Status=t.Status.SUCCESS)],
992+
)
993+
994+
znp_server.reply_to(
995+
request=c.AF.DataRequestExt.Req(partial=True),
996+
responses=[
997+
c.AF.DataRequestExt.Rsp(Status=t.Status.SUCCESS),
998+
c.AF.DataConfirm.Callback(
999+
Status=t.Status.MAC_NO_ACK,
1000+
Endpoint=packet.dst_ep,
1001+
TSN=packet.tsn,
1002+
),
1003+
],
1004+
)
1005+
1006+
with pytest.raises(zigpy.exceptions.DeliveryError) as excinfo:
1007+
await app.send_packet(packet)
1008+
1009+
assert excinfo.value.status == t.Status.MAC_NO_ACK
1010+
1011+
await app.shutdown()

tests/application/test_startup.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import pytest
2+
import voluptuous as vol
23
from zigpy.exceptions import NetworkNotFormed
34

45
import zigpy_znp.types as t
@@ -266,3 +267,18 @@ async def test_zstack_build_id_empty(device, make_application, mocker):
266267
assert app._zstack_build_id == 0x00000000
267268

268269
await app.shutdown()
270+
271+
272+
@pytest.mark.parametrize("device", [FormedLaunchpadCC26X2R1])
273+
async def test_deprecated_concurrency_config(device, make_application, mocker):
274+
with pytest.raises(vol.MultipleInvalid) as exc:
275+
app, znp_server = await make_application(
276+
server_cls=device,
277+
client_config={
278+
conf.CONF_ZNP_CONFIG: {
279+
conf.CONF_MAX_CONCURRENT_REQUESTS: 16,
280+
}
281+
},
282+
)
283+
284+
assert "max_concurrent_requests" in str(exc.value)

tests/application/test_zigpy_callbacks.py

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,3 +168,128 @@ async def test_on_af_message_callback(device, make_application, mocker):
168168
)
169169

170170
app.handle_message.reset_mock()
171+
172+
173+
@pytest.mark.parametrize("device", FORMED_DEVICES)
174+
async def test_receive_zdo_broadcast(device, make_application, mocker):
175+
app, znp_server = await make_application(server_cls=device)
176+
await app.startup(auto_form=False)
177+
178+
mocker.patch.object(app, "packet_received")
179+
180+
zdo_callback = c.ZDO.MsgCbIncoming.Callback(
181+
Src=0x35D9,
182+
IsBroadcast=t.Bool.true,
183+
ClusterId=19,
184+
SecurityUse=0,
185+
TSN=129,
186+
MacDst=0xFFFF,
187+
Data=b"bogus",
188+
)
189+
znp_server.send(zdo_callback)
190+
await asyncio.sleep(0.1)
191+
192+
assert app.packet_received.call_count == 1
193+
packet = app.packet_received.mock_calls[0].args[0]
194+
assert packet.src == zigpy_t.AddrModeAddress(
195+
addr_mode=zigpy_t.AddrMode.NWK, address=0x35D9
196+
)
197+
assert packet.src_ep == 0x00
198+
assert packet.dst == zigpy_t.AddrModeAddress(
199+
addr_mode=zigpy_t.AddrMode.Broadcast,
200+
address=zigpy_t.BroadcastAddress.ALL_ROUTERS_AND_COORDINATOR,
201+
)
202+
assert packet.dst_ep == 0x00
203+
assert packet.cluster_id == zdo_callback.ClusterId
204+
assert packet.tsn == zdo_callback.TSN
205+
assert packet.data.serialize() == bytes([zdo_callback.TSN]) + zdo_callback.Data
206+
207+
await app.shutdown()
208+
209+
210+
@pytest.mark.parametrize("device", FORMED_DEVICES)
211+
async def test_receive_af_broadcast(device, make_application, mocker):
212+
app, znp_server = await make_application(server_cls=device)
213+
await app.startup(auto_form=False)
214+
215+
mocker.patch.object(app, "packet_received")
216+
217+
af_callback = c.AF.IncomingMsg.Callback(
218+
GroupId=0x0000,
219+
ClusterId=4096,
220+
SrcAddr=0x1234,
221+
SrcEndpoint=254,
222+
DstEndpoint=2,
223+
WasBroadcast=t.Bool.true,
224+
LQI=90,
225+
SecurityUse=t.Bool.false,
226+
TimeStamp=4442962,
227+
TSN=0,
228+
Data=b"\x11\xA6\x00\x74\xB5\x7C\x00\x02\x5F",
229+
MacSrcAddr=0x0000,
230+
MsgResultRadius=0,
231+
)
232+
znp_server.send(af_callback)
233+
await asyncio.sleep(0.1)
234+
235+
assert app.packet_received.call_count == 1
236+
packet = app.packet_received.mock_calls[0].args[0]
237+
assert packet.src == zigpy_t.AddrModeAddress(
238+
addr_mode=zigpy_t.AddrMode.NWK,
239+
address=0x1234,
240+
)
241+
assert packet.src_ep == af_callback.SrcEndpoint
242+
assert packet.dst == zigpy_t.AddrModeAddress(
243+
addr_mode=zigpy_t.AddrMode.Broadcast,
244+
address=zigpy_t.BroadcastAddress.ALL_ROUTERS_AND_COORDINATOR,
245+
)
246+
assert packet.dst_ep == af_callback.DstEndpoint
247+
assert packet.cluster_id == af_callback.ClusterId
248+
assert packet.tsn == af_callback.TSN
249+
assert packet.lqi == af_callback.LQI
250+
assert packet.data.serialize() == af_callback.Data
251+
252+
await app.shutdown()
253+
254+
255+
@pytest.mark.parametrize("device", FORMED_DEVICES)
256+
async def test_receive_af_group(device, make_application, mocker):
257+
app, znp_server = await make_application(server_cls=device)
258+
await app.startup(auto_form=False)
259+
260+
mocker.patch.object(app, "packet_received")
261+
262+
af_callback = c.AF.IncomingMsg.Callback(
263+
GroupId=0x1234,
264+
ClusterId=4096,
265+
SrcAddr=0x1234,
266+
SrcEndpoint=254,
267+
DstEndpoint=0,
268+
WasBroadcast=t.Bool.false,
269+
LQI=90,
270+
SecurityUse=t.Bool.false,
271+
TimeStamp=4442962,
272+
TSN=0,
273+
Data=b"\x11\xA6\x00\x74\xB5\x7C\x00\x02\x5F",
274+
MacSrcAddr=0x0000,
275+
MsgResultRadius=0,
276+
)
277+
znp_server.send(af_callback)
278+
await asyncio.sleep(0.1)
279+
280+
assert app.packet_received.call_count == 1
281+
packet = app.packet_received.mock_calls[0].args[0]
282+
assert packet.src == zigpy_t.AddrModeAddress(
283+
addr_mode=zigpy_t.AddrMode.NWK,
284+
address=0x1234,
285+
)
286+
assert packet.src_ep == af_callback.SrcEndpoint
287+
assert packet.dst == zigpy_t.AddrModeAddress(
288+
addr_mode=zigpy_t.AddrMode.Group, address=0x1234
289+
)
290+
assert packet.cluster_id == af_callback.ClusterId
291+
assert packet.tsn == af_callback.TSN
292+
assert packet.lqi == af_callback.LQI
293+
assert packet.data.serialize() == af_callback.Data
294+
295+
await app.shutdown()

zigpy_znp/config.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,18 @@ def bool_to_upper_str(value: typing.Any) -> str:
7676
return str(value).upper()
7777

7878

79+
def cv_deprecated(message: str) -> typing.Callable[[typing.Any], None]:
80+
"""
81+
Raises a deprecation exception when a value is passed in.
82+
"""
83+
84+
def validator(value: typing.Any) -> None:
85+
if value is not None:
86+
raise vol.Invalid(message)
87+
88+
return validator
89+
90+
7991
CONF_ZNP_CONFIG = "znp_config"
8092
CONF_TX_POWER = "tx_power"
8193
CONF_LED_MODE = "led_mode"
@@ -104,8 +116,11 @@ def bool_to_upper_str(value: typing.Any) -> str:
104116
vol.Optional(CONF_LED_MODE, default=LEDMode.OFF): vol.Any(
105117
None, EnumValue(LEDMode, transformer=bool_to_upper_str)
106118
),
107-
vol.Optional(CONF_MAX_CONCURRENT_REQUESTS, default=None): vol.Any(
108-
None, "auto", VolPositiveNumber
119+
vol.Optional(CONF_MAX_CONCURRENT_REQUESTS, default=None): (
120+
cv_deprecated(
121+
"`zigpy_config: znp_config: max_concurrent_requests` has"
122+
" been renamed to `zigpy_config: max_concurrent_requests`."
123+
)
109124
),
110125
vol.Optional(
111126
CONF_CONNECT_RTS_STATES, default=[False, True, False]

zigpy_znp/zigbee/application.py

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -220,16 +220,13 @@ async def start_network(self, *, read_only=False):
220220

221221
# Deprecate ZNP-specific config
222222
if self.znp_config[conf.CONF_MAX_CONCURRENT_REQUESTS] is not None:
223-
LOGGER.warning(
223+
raise RuntimeError(
224224
"`zigpy_config:znp_config:max_concurrent_requests` is deprecated,"
225-
" move this key up to `zigpy_config:max_concurrent_requests` instead"
225+
" move this key up to `zigpy_config:max_concurrent_requests` instead."
226226
)
227-
concurrency = self.znp_config[conf.CONF_MAX_CONCURRENT_REQUESTS]
228-
else:
229-
concurrency = self._config[conf.CONF_MAX_CONCURRENT_REQUESTS]
230227

231228
# Now that we know what device we are, set the max concurrent requests
232-
if concurrency in (None, "auto"):
229+
if self._config[conf.CONF_MAX_CONCURRENT_REQUESTS] is None:
233230
max_concurrent_requests = 16 if self._znp.nvram.align_structs else 2
234231
else:
235232
max_concurrent_requests = self._config[conf.CONF_MAX_CONCURRENT_REQUESTS]
@@ -554,7 +551,7 @@ async def on_af_message(self, msg: c.AF.IncomingMsg.Callback) -> None:
554551
"""
555552

556553
# XXX: Is it possible to receive messages on non-assigned endpoints?
557-
if msg.DstEndpoint in self._device.endpoints:
554+
if msg.DstEndpoint != 0 and msg.DstEndpoint in self._device.endpoints:
558555
profile = self._device.endpoints[msg.DstEndpoint].profile_id
559556
else:
560557
LOGGER.warning("Received a message on an unregistered endpoint: %s", msg)
@@ -794,9 +791,6 @@ async def _send_request_raw(
794791
Picks the correct request sending mechanism and fixes endpoint information.
795792
"""
796793

797-
if radius is None:
798-
radius = 0
799-
800794
# Zigpy just sets src == dst, which doesn't work for devices with many endpoints
801795
# We pick ours based on the registered endpoints when using an older firmware
802796
src_ep = self._find_endpoint(dst_ep=dst_ep, profile=profile, cluster=cluster)
@@ -1022,11 +1016,12 @@ async def send_packet(self, packet: zigpy.types.ZigbeePacket) -> None:
10221016
cluster=packet.cluster_id,
10231017
sequence=packet.tsn,
10241018
options=options,
1025-
radius=packet.radius,
1019+
radius=packet.radius or 0,
10261020
data=packet.data.serialize(),
10271021
relays=force_relays,
10281022
extended_timeout=packet.extended_timeout,
10291023
)
1024+
status = response.Status
10301025
break
10311026
except InvalidCommandResponse as e:
10321027
status = e.response.Status
@@ -1159,8 +1154,3 @@ async def send_packet(self, packet: zigpy.types.ZigbeePacket) -> None:
11591154
NodeRelation=association.Device.nodeRelation,
11601155
)
11611156
)
1162-
1163-
if response.Status != t.Status.SUCCESS:
1164-
raise DeliveryError(
1165-
f"Failed to send request: {response.Status}", status=response.Status
1166-
)

0 commit comments

Comments
 (0)