Skip to content

Commit 2cf2f28

Browse files
committed
add protocol level test for SUBSCRIBE
1 parent d65b797 commit 2cf2f28

File tree

1 file changed

+148
-0
lines changed

1 file changed

+148
-0
lines changed

tests/test_subscribe.py

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
# SPDX-FileCopyrightText: 2023 Vladimír Kotal
2+
#
3+
# SPDX-License-Identifier: Unlicense
4+
5+
"""subscribe tests"""
6+
7+
import logging
8+
import ssl
9+
from unittest import mock
10+
11+
import pytest
12+
13+
import adafruit_minimqtt.adafruit_minimqtt as MQTT
14+
15+
16+
class Mocket:
17+
"""
18+
Mock Socket tailored for MiniMQTT testing. Records sent data,
19+
hands out pre-recorded reply.
20+
21+
Inspired by the Mocket class from Adafruit_CircuitPython_Requests
22+
"""
23+
24+
def __init__(self, to_send):
25+
self._to_send = to_send
26+
27+
self.sent = bytearray()
28+
29+
self.timeout = mock.Mock()
30+
self.connect = mock.Mock()
31+
self.close = mock.Mock()
32+
33+
def send(self, bytes_to_send):
34+
"""merely record the bytes. return the length of this bytearray."""
35+
self.sent.extend(bytes_to_send)
36+
return len(bytes_to_send)
37+
38+
# MiniMQTT checks for the presence of "recv_into" and switches behavior based on that.
39+
def recv_into(self, retbuf, bufsize):
40+
"""return data from internal buffer"""
41+
size = min(bufsize, len(self._to_send))
42+
if size == 0:
43+
return size
44+
chop = self._to_send[0:size]
45+
retbuf[0:] = chop
46+
self._to_send = self._to_send[size:]
47+
return size
48+
49+
50+
# pylint: disable=unused-argument
51+
def handle_subscribe(client, user_data, topic, qos):
52+
"""
53+
Record topics into user data.
54+
"""
55+
assert topic
56+
assert qos == 0
57+
58+
user_data.append(topic)
59+
60+
61+
# The MQTT packet contents below were captured using Mosquitto client+server.
62+
testdata = [
63+
# short topic with remaining length encoded as single byte
64+
(
65+
"foo/bar",
66+
bytearray([0x90, 0x03, 0x00, 0x01, 0x00]),
67+
bytearray(
68+
[
69+
0x82, # fixed header
70+
0x0C, # remaining length
71+
0x00,
72+
0x01, # message ID
73+
0x00,
74+
0x07, # topic length
75+
0x66, # topic
76+
0x6F,
77+
0x6F,
78+
0x2F,
79+
0x62,
80+
0x61,
81+
0x72,
82+
0x00, # QoS
83+
]
84+
),
85+
),
86+
# remaining length is encoded as 2 bytes due to long topic name.
87+
(
88+
"f" + "o" * 257,
89+
bytearray([0x90, 0x03, 0x00, 0x01, 0x00]),
90+
bytearray(
91+
[
92+
0x82, # fixed header
93+
0x87, # remaining length
94+
0x02,
95+
0x00, # message ID
96+
0x01,
97+
0x01, # topic length
98+
0x02,
99+
0x66, # topic
100+
]
101+
+ [0x6F] * 257
102+
+ [0x00] # QoS
103+
),
104+
),
105+
]
106+
107+
108+
@pytest.mark.parametrize(
109+
"topic,to_send,exp_recv", testdata, ids=["short_topic", "long_topic"]
110+
)
111+
def test_subscribe(topic, to_send, exp_recv) -> None:
112+
"""
113+
Protocol level testing of SUBSCRIBE and SUBACK packet handling.
114+
115+
Nothing will travel over the wire, it is all fake.
116+
"""
117+
logging.basicConfig()
118+
logger = logging.getLogger(__name__)
119+
logger.setLevel(logging.DEBUG)
120+
121+
host = "localhost"
122+
port = 1883
123+
124+
subscribed_topics = []
125+
mqtt_client = MQTT.MQTT(
126+
broker=host,
127+
port=port,
128+
ssl_context=ssl.create_default_context(),
129+
connect_retries=1,
130+
user_data=subscribed_topics,
131+
)
132+
133+
mqtt_client.on_subscribe = handle_subscribe
134+
135+
# patch is_connected() to avoid CONNECT/CONNACK handling.
136+
mqtt_client.is_connected = lambda: True
137+
mocket = Mocket(to_send)
138+
# pylint: disable=protected-access
139+
mqtt_client._sock = mocket
140+
141+
mqtt_client.logger = logger
142+
143+
# pylint: disable=logging-fstring-interpolation
144+
logger.info(f"subscribing to {topic}")
145+
mqtt_client.subscribe(topic)
146+
147+
assert topic in subscribed_topics
148+
assert mocket.sent == exp_recv

0 commit comments

Comments
 (0)