amqtt/tests/test_broker.py

481 wiersze
14 KiB
Python
Czysty Zwykły widok Historia

2015-09-30 19:34:21 +00:00
# Copyright (c) 2015 Nicolas JOUANIN
#
# See the file license.txt for copying permission.
import sys
import asyncio
import logging
2015-09-30 19:34:21 +00:00
import unittest
2021-01-09 19:38:41 +00:00
from unittest.mock import patch, call, MagicMock
2021-03-06 15:20:48 +00:00
import pytest
from hbmqtt.adapters import StreamReaderAdapter, StreamWriterAdapter
from hbmqtt.broker import (
EVENT_BROKER_PRE_START,
EVENT_BROKER_POST_START,
EVENT_BROKER_PRE_SHUTDOWN,
EVENT_BROKER_POST_SHUTDOWN,
EVENT_BROKER_CLIENT_CONNECTED,
EVENT_BROKER_CLIENT_DISCONNECTED,
EVENT_BROKER_CLIENT_SUBSCRIBED,
EVENT_BROKER_CLIENT_UNSUBSCRIBED,
EVENT_BROKER_MESSAGE_RECEIVED,
Broker,
)
2016-04-10 21:02:10 +00:00
from hbmqtt.client import MQTTClient, ConnectException
from hbmqtt.mqtt import (
ConnectPacket,
ConnackPacket,
PublishPacket,
PubrecPacket,
PubrelPacket,
PubcompPacket,
DisconnectPacket,
)
2016-05-04 20:35:38 +00:00
from hbmqtt.mqtt.connect import ConnectVariableHeader, ConnectPayload
from hbmqtt.mqtt.constants import QOS_0, QOS_1, QOS_2
2015-09-30 19:34:21 +00:00
formatter = "[%(asctime)s] %(name)s {%(filename)s:%(lineno)d} %(levelname)s - %(message)s"
logging.basicConfig(level=logging.DEBUG, format=formatter)
log = logging.getLogger(__name__)
2021-03-06 17:58:34 +00:00
2021-01-09 19:38:41 +00:00
# monkey patch MagicMock
# taken from https://stackoverflow.com/questions/51394411/python-object-magicmock-cant-be-used-in-await-expression
async def async_magic():
pass
2015-09-30 19:34:21 +00:00
MagicMock.__await__ = lambda x: async_magic().__await__()
@pytest.mark.asyncio
2021-03-06 17:58:34 +00:00
async def test_start_stop(broker, mock_plugin_manager):
mock_plugin_manager.assert_has_calls(
[call().fire_event(EVENT_BROKER_PRE_START), call().fire_event(EVENT_BROKER_POST_START)],
any_order=True,
)
2021-03-06 17:58:34 +00:00
mock_plugin_manager.reset_mock()
await broker.shutdown()
2021-03-06 17:58:34 +00:00
mock_plugin_manager.assert_has_calls(
[
call().fire_event(EVENT_BROKER_PRE_SHUTDOWN),
call().fire_event(EVENT_BROKER_POST_SHUTDOWN),
],
any_order=True,
)
assert broker.transitions.is_stopped()
@pytest.mark.asyncio
2021-03-06 17:58:34 +00:00
async def test_client_connect(broker, mock_plugin_manager):
client = MQTTClient()
ret = await client.connect("mqtt://127.0.0.1/")
assert ret == 0
assert client.session.client_id in broker._sessions
await client.disconnect()
2021-03-06 17:58:34 +00:00
await asyncio.sleep(0.01)
mock_plugin_manager.assert_has_calls(
[
call().fire_event(
EVENT_BROKER_CLIENT_CONNECTED, client_id=client.session.client_id
),
call().fire_event(
EVENT_BROKER_CLIENT_DISCONNECTED, client_id=client.session.client_id
),
],
any_order=True,
)
@pytest.mark.asyncio
2021-03-06 17:58:34 +00:00
async def test_client_connect_will_flag(broker, event_loop):
conn_reader, conn_writer = await asyncio.open_connection("127.0.0.1", 1883, loop=event_loop)
reader = StreamReaderAdapter(conn_reader)
writer = StreamWriterAdapter(conn_writer)
vh = ConnectVariableHeader()
payload = ConnectPayload()
vh.keep_alive = 10
vh.clean_session_flag = False
vh.will_retain_flag = False
vh.will_flag = True
vh.will_qos = QOS_0
payload.client_id = "test_id"
payload.will_message = b"test"
payload.will_topic = "/topic"
connect = ConnectPacket(vh=vh, payload=payload)
await connect.to_stream(writer)
await ConnackPacket.from_stream(reader)
await asyncio.sleep(0.1)
disconnect = DisconnectPacket()
await disconnect.to_stream(writer)
await asyncio.sleep(0.1)
@pytest.mark.asyncio
2021-03-06 17:58:34 +00:00
async def test_client_connect_clean_session_false(broker):
client = MQTTClient(client_id="", config={"auto_reconnect": False})
return_code = None
try:
await client.connect("mqtt://127.0.0.1/", cleansession=False)
except ConnectException as ce:
return_code = ce.return_code
assert return_code == 0x02
assert client.session.client_id not in broker._sessions
await client.disconnect()
await asyncio.sleep(0.1)
2021-03-06 17:58:34 +00:00
@pytest.mark.asyncio
2021-03-06 17:58:34 +00:00
async def test_client_subscribe(broker, mock_plugin_manager):
client = MQTTClient()
ret = await client.connect("mqtt://127.0.0.1/")
assert ret == 0
await client.subscribe([("/topic", QOS_0)])
# Test if the client test client subscription is registered
assert "/topic" in broker._subscriptions
subs = broker._subscriptions["/topic"]
assert len(subs) == 1
(s, qos) = subs[0]
assert s == client.session
assert qos == QOS_0
await client.disconnect()
await asyncio.sleep(0.1)
2021-03-06 17:58:34 +00:00
mock_plugin_manager.assert_has_calls(
[
call().fire_event(
EVENT_BROKER_CLIENT_SUBSCRIBED,
client_id=client.session.client_id,
topic="/topic",
qos=QOS_0,
)
],
any_order=True,
)
@pytest.mark.asyncio
2021-03-06 17:58:34 +00:00
async def test_client_subscribe_twice(broker, mock_plugin_manager):
client = MQTTClient()
ret = await client.connect("mqtt://127.0.0.1/")
assert ret == 0
await client.subscribe([("/topic", QOS_0)])
# Test if the client test client subscription is registered
assert "/topic" in broker._subscriptions
subs = broker._subscriptions["/topic"]
assert len(subs) == 1
(s, qos) = subs[0]
assert s == client.session
assert qos == QOS_0
await client.subscribe([("/topic", QOS_0)])
assert len(subs) == 1
(s, qos) = subs[0]
assert s == client.session
assert qos == QOS_0
await client.disconnect()
await asyncio.sleep(0.1)
2021-03-06 17:58:34 +00:00
mock_plugin_manager.assert_has_calls(
[
call().fire_event(
EVENT_BROKER_CLIENT_SUBSCRIBED,
client_id=client.session.client_id,
topic="/topic",
qos=QOS_0,
)
],
any_order=True,
)
@pytest.mark.asyncio
2021-03-06 17:58:34 +00:00
async def test_client_unsubscribe(broker, mock_plugin_manager):
client = MQTTClient()
ret = await client.connect("mqtt://127.0.0.1/")
assert ret == 0
await client.subscribe([("/topic", QOS_0)])
# Test if the client test client subscription is registered
assert "/topic" in broker._subscriptions
subs = broker._subscriptions["/topic"]
assert len(subs) == 1
(s, qos) = subs[0]
assert s == client.session
assert qos == QOS_0
await client.unsubscribe(["/topic"])
await asyncio.sleep(0.1)
assert broker._subscriptions["/topic"] == []
await client.disconnect()
await asyncio.sleep(0.1)
2021-03-06 17:58:34 +00:00
mock_plugin_manager.assert_has_calls(
[
call().fire_event(
EVENT_BROKER_CLIENT_SUBSCRIBED,
client_id=client.session.client_id,
topic="/topic",
qos=QOS_0,
),
call().fire_event(
EVENT_BROKER_CLIENT_UNSUBSCRIBED,
client_id=client.session.client_id,
topic="/topic",
),
],
any_order=True,
)
@pytest.mark.asyncio
2021-03-06 17:58:34 +00:00
async def test_client_publish(broker, mock_plugin_manager):
pub_client = MQTTClient()
ret = await pub_client.connect("mqtt://127.0.0.1/")
assert ret == 0
ret_message = await pub_client.publish("/topic", b"data", QOS_0)
await pub_client.disconnect()
assert broker._retained_messages == {}
await asyncio.sleep(0.1)
2021-03-06 17:58:34 +00:00
mock_plugin_manager.assert_has_calls(
[
call().fire_event(
EVENT_BROKER_MESSAGE_RECEIVED,
client_id=pub_client.session.client_id,
message=ret_message,
),
],
any_order=True,
)
@pytest.mark.asyncio
2021-03-06 17:58:34 +00:00
async def test_client_publish_dup(broker, event_loop):
conn_reader, conn_writer = await asyncio.open_connection("127.0.0.1", 1883, loop=event_loop)
reader = StreamReaderAdapter(conn_reader)
writer = StreamWriterAdapter(conn_writer)
vh = ConnectVariableHeader()
payload = ConnectPayload()
vh.keep_alive = 10
vh.clean_session_flag = False
vh.will_retain_flag = False
payload.client_id = "test_id"
connect = ConnectPacket(vh=vh, payload=payload)
await connect.to_stream(writer)
await ConnackPacket.from_stream(reader)
publish_1 = PublishPacket.build("/test", b"data", 1, False, QOS_2, False)
await publish_1.to_stream(writer)
asyncio.ensure_future(PubrecPacket.from_stream(reader), loop=event_loop)
await asyncio.sleep(2)
publish_dup = PublishPacket.build("/test", b"data", 1, True, QOS_2, False)
await publish_dup.to_stream(writer)
await PubrecPacket.from_stream(reader)
pubrel = PubrelPacket.build(1)
await pubrel.to_stream(writer)
await PubcompPacket.from_stream(reader)
disconnect = DisconnectPacket()
await disconnect.to_stream(writer)
@pytest.mark.asyncio
2021-03-06 17:58:34 +00:00
async def test_client_publish_invalid_topic(broker):
assert broker.transitions.is_started()
pub_client = MQTTClient()
ret = await pub_client.connect("mqtt://127.0.0.1/")
assert ret == 0
await pub_client.publish("/+", b"data", QOS_0)
await asyncio.sleep(0.1)
await pub_client.disconnect()
@pytest.mark.asyncio
2021-03-06 17:58:34 +00:00
async def test_client_publish_big(broker, mock_plugin_manager):
pub_client = MQTTClient()
ret = await pub_client.connect("mqtt://127.0.0.1/")
assert ret == 0
ret_message = await pub_client.publish("/topic", bytearray(b"\x99" * 256 * 1024), QOS_2)
await pub_client.disconnect()
assert broker._retained_messages == {}
await asyncio.sleep(0.1)
2021-03-06 17:58:34 +00:00
mock_plugin_manager.assert_has_calls(
[
call().fire_event(
EVENT_BROKER_MESSAGE_RECEIVED,
client_id=pub_client.session.client_id,
message=ret_message,
),
],
any_order=True,
)
@pytest.mark.asyncio
2021-03-06 17:58:34 +00:00
async def test_client_publish_retain(broker):
pub_client = MQTTClient()
ret = await pub_client.connect("mqtt://127.0.0.1/")
assert ret == 0
await pub_client.publish("/topic", b"data", QOS_0, retain=True)
await pub_client.disconnect()
await asyncio.sleep(0.1)
assert "/topic" in broker._retained_messages
retained_message = broker._retained_messages["/topic"]
assert retained_message.source_session == pub_client.session
assert retained_message.topic == "/topic"
assert retained_message.data == b"data"
assert retained_message.qos == QOS_0
2021-03-06 17:58:34 +00:00
@pytest.mark.asyncio
async def test_client_publish_retain_delete(broker):
pub_client = MQTTClient()
ret = await pub_client.connect("mqtt://127.0.0.1/")
assert ret == 0
await pub_client.publish("/topic", b"", QOS_0, retain=True)
await pub_client.disconnect()
await asyncio.sleep(0.1)
assert "/topic" not in broker._retained_messages
2021-03-06 17:58:34 +00:00
@pytest.mark.asyncio
2021-03-06 17:58:34 +00:00
async def test_client_subscribe_publish(broker):
sub_client = MQTTClient()
await sub_client.connect("mqtt://127.0.0.1")
ret = await sub_client.subscribe([("/qos0", QOS_0), ("/qos1", QOS_1), ("/qos2", QOS_2)])
assert ret == [QOS_0, QOS_1, QOS_2]
await _client_publish("/qos0", b"data", QOS_0)
await _client_publish("/qos1", b"data", QOS_1)
await _client_publish("/qos2", b"data", QOS_2)
await asyncio.sleep(0.1)
for qos in [QOS_0, QOS_1, QOS_2]:
message = await sub_client.deliver_message()
assert message is not None
assert message.topic == "/qos%s" % qos
assert message.data == b"data"
assert message.qos == qos
await sub_client.disconnect()
await asyncio.sleep(0.1)
@pytest.mark.asyncio
2021-03-06 17:58:34 +00:00
async def test_client_subscribe_invalid(broker):
sub_client = MQTTClient()
await sub_client.connect("mqtt://127.0.0.1")
ret = await sub_client.subscribe(
[("+", QOS_0), ("+/tennis/#", QOS_0), ("sport+", QOS_0), ("sport/+/player1", QOS_0)]
)
assert ret == [QOS_0, QOS_0, 0x80, QOS_0]
await asyncio.sleep(0.1)
await sub_client.disconnect()
await asyncio.sleep(0.1)
2021-03-06 17:58:34 +00:00
@pytest.mark.asyncio
2021-03-06 17:58:34 +00:00
async def test_client_subscribe_publish_dollar_topic_1(broker):
assert broker.transitions.is_started()
sub_client = MQTTClient()
await sub_client.connect("mqtt://127.0.0.1")
ret = await sub_client.subscribe([("#", QOS_0)])
assert ret == [QOS_0]
await _client_publish("/topic", b"data", QOS_0)
message = await sub_client.deliver_message()
assert message is not None
await _client_publish("$topic", b"data", QOS_0)
await asyncio.sleep(0.1)
message = None
try:
message = await sub_client.deliver_message(timeout=2)
except Exception as e:
pass
assert message is None
await sub_client.disconnect()
await asyncio.sleep(0.1)
2021-03-06 17:58:34 +00:00
@pytest.mark.asyncio
2021-03-06 17:58:34 +00:00
async def test_client_subscribe_publish_dollar_topic_2(broker):
sub_client = MQTTClient()
await sub_client.connect("mqtt://127.0.0.1")
ret = await sub_client.subscribe([("+/monitor/Clients", QOS_0)])
assert ret == [QOS_0]
await _client_publish("/test/monitor/Clients", b"data", QOS_0)
message = await sub_client.deliver_message()
assert message is not None
await _client_publish("$SYS/monitor/Clients", b"data", QOS_0)
await asyncio.sleep(0.1)
message = None
try:
message = await sub_client.deliver_message(timeout=2)
except Exception as e:
pass
assert message is None
await sub_client.disconnect()
await asyncio.sleep(0.1)
@pytest.mark.asyncio
@pytest.mark.xfail(reason="see https://github.com/Yakifo/aio-hbmqtt/issues/16", strict=False)
2021-03-06 17:58:34 +00:00
async def test_client_publish_retain_subscribe(broker):
sub_client = MQTTClient()
await sub_client.connect("mqtt://127.0.0.1", cleansession=False)
ret = await sub_client.subscribe([("/qos0", QOS_0), ("/qos1", QOS_1), ("/qos2", QOS_2)])
assert ret == [QOS_0, QOS_1, QOS_2]
await sub_client.disconnect()
await asyncio.sleep(0.1)
await _client_publish("/qos0", b"data", QOS_0, retain=True)
await _client_publish("/qos1", b"data", QOS_1, retain=True)
await _client_publish("/qos2", b"data", QOS_2, retain=True)
await sub_client.reconnect()
for qos in [QOS_0, QOS_1, QOS_2]:
log.debug("TEST QOS: %d" % qos)
message = await sub_client.deliver_message()
log.debug("Message: " + repr(message.publish_packet))
assert message is not None
assert message.topic == "/qos%s" % qos
assert message.data == b"data"
assert message.qos == qos
await sub_client.disconnect()
await asyncio.sleep(0.1)
@pytest.mark.asyncio
async def _client_publish(topic, data, qos, retain=False):
pub_client = MQTTClient()
ret = await pub_client.connect("mqtt://127.0.0.1/")
assert ret == 0
ret = await pub_client.publish(topic, data, qos, retain)
await pub_client.disconnect()
return ret