diff --git a/pyproject.toml b/pyproject.toml index 1fdb3c2..672aa27 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,6 +36,7 @@ dev = [ "hatch>=1.14.1", "hypothesis>=6.130.8", "mypy>=1.15.0", + "paho-mqtt>=2.1.0", "poethepoet>=0.34.0", "pre-commit>=4.2.0", # https://pypi.org/project/pre-commit "psutil>=7.0.0", # https://pypi.org/project/psutil diff --git a/tests/test_paho.py b/tests/test_paho.py new file mode 100644 index 0000000..2ffca53 --- /dev/null +++ b/tests/test_paho.py @@ -0,0 +1,130 @@ +import asyncio +import logging +import random +from unittest.mock import MagicMock, call, patch + +import pytest +from paho.mqtt import client as mqtt_client + +from amqtt.broker import EVENT_BROKER_CLIENT_CONNECTED, EVENT_BROKER_CLIENT_DISCONNECTED, EVENT_BROKER_PRE_START, \ + EVENT_BROKER_POST_START +from amqtt.client import MQTTClient +from amqtt.mqtt.constants import QOS_1, QOS_2 + +logger = logging.getLogger(__name__) +paho_logger = logging.getLogger("paho_client") + + +# monkey patch MagicMock +# taken from https://stackoverflow.com/questions/51394411/python-object-magicmock-cant-be-used-in-await-expression +async def async_magic(): + pass + + +MagicMock.__await__ = lambda _: async_magic().__await__() + +@pytest.mark.asyncio +async def test_paho_connect(broker, mock_plugin_manager): + + test_complete = asyncio.Event() + + host = "localhost" + port = 1883 + client_id = f'python-mqtt-{random.randint(0, 1000)}' + + def on_connect(client, userdata, flags, rc, properties=None): + assert rc == 0, f"Connection failed with result code {rc}" + client.disconnect() + + def on_disconnect(client, userdata, flags, rc, properties=None): + assert rc == 0, f"Disconnect failed with result code {rc}" + test_complete.set() + + test_client = mqtt_client.Client(mqtt_client.CallbackAPIVersion.VERSION2, client_id=client_id) + test_client.enable_logger(paho_logger) + + test_client.on_connect = on_connect + test_client.on_disconnect = on_disconnect + + test_client.connect(host, port) + test_client.loop_start() + + await asyncio.wait_for(test_complete.wait(), timeout=5) + await asyncio.sleep(0.1) + broker.plugins_manager.assert_has_calls( + [ + call.fire_event( + EVENT_BROKER_CLIENT_CONNECTED, + client_id=client_id, + ), + call.fire_event( + EVENT_BROKER_CLIENT_DISCONNECTED, + client_id=client_id, + ), + ], + any_order=True, + ) + test_client.loop_stop() + + +@pytest.mark.asyncio +async def test_paho_qos1(broker, mock_plugin_manager): + + sub_client = MQTTClient() + await sub_client.connect("mqtt://127.0.0.1") + ret = await sub_client.subscribe( + [("/qos1", QOS_1),], + ) + + host = "localhost" + port = 1883 + client_id = f'python-mqtt-{random.randint(0, 1000)}' + + test_client = mqtt_client.Client(mqtt_client.CallbackAPIVersion.VERSION2, client_id=client_id) + test_client.enable_logger(paho_logger) + + test_client.connect(host, port) + test_client.loop_start() + await asyncio.sleep(0.1) + test_client.publish("/qos1", "test message", qos=1) + await asyncio.sleep(0.1) + test_client.loop_stop() + + message = await sub_client.deliver_message() + assert message is not None + assert message.qos == 1 + assert message.topic == "/qos1" + assert message.data == b"test message" + await sub_client.disconnect() + await asyncio.sleep(0.1) + + +@pytest.mark.asyncio +async def test_paho_qos2(broker, mock_plugin_manager): + sub_client = MQTTClient() + await sub_client.connect("mqtt://127.0.0.1") + ret = await sub_client.subscribe( + [("/qos2", QOS_2), ], + ) + + host = "localhost" + port = 1883 + client_id = f'python-mqtt-{random.randint(0, 1000)}' + + test_client = mqtt_client.Client(mqtt_client.CallbackAPIVersion.VERSION2, client_id=client_id) + test_client.enable_logger(paho_logger) + + test_client.connect(host, port) + test_client.loop_start() + await asyncio.sleep(0.1) + test_client.publish("/qos2", "test message", qos=2) + await asyncio.sleep(0.1) + test_client.loop_stop() + + message = await sub_client.deliver_message() + assert message is not None + assert message.qos == 2 + assert message.topic == "/qos2" + assert message.data == b"test message" + await sub_client.disconnect() + await asyncio.sleep(0.1) diff --git a/uv.lock b/uv.lock index d5c6ba2..27c7655 100644 --- a/uv.lock +++ b/uv.lock @@ -29,6 +29,7 @@ dev = [ { name = "hatch" }, { name = "hypothesis" }, { name = "mypy" }, + { name = "paho-mqtt" }, { name = "poethepoet" }, { name = "pre-commit" }, { name = "psutil" }, @@ -76,6 +77,7 @@ dev = [ { name = "hatch", specifier = ">=1.14.1" }, { name = "hypothesis", specifier = ">=6.130.8" }, { name = "mypy", specifier = ">=1.15.0" }, + { name = "paho-mqtt", specifier = ">=2.1.0" }, { name = "poethepoet", specifier = ">=0.34.0" }, { name = "pre-commit", specifier = ">=4.2.0" }, { name = "psutil", specifier = ">=7.0.0" }, @@ -1264,6 +1266,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl", hash = "sha256:b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591", size = 13746, upload-time = "2024-08-25T14:17:22.55Z" }, ] +[[package]] +name = "paho-mqtt" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/39/15/0a6214e76d4d32e7f663b109cf71fb22561c2be0f701d67f93950cd40542/paho_mqtt-2.1.0.tar.gz", hash = "sha256:12d6e7511d4137555a3f6ea167ae846af2c7357b10bc6fa4f7c3968fc1723834", size = 148848, upload-time = "2024-04-29T19:52:55.591Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c4/cb/00451c3cf31790287768bb12c6bec834f5d292eaf3022afc88e14b8afc94/paho_mqtt-2.1.0-py3-none-any.whl", hash = "sha256:6db9ba9b34ed5bc6b6e3812718c7e06e2fd7444540df2455d2c51bd58808feee", size = 67219, upload-time = "2024-04-29T19:52:48.345Z" }, +] + [[package]] name = "passlib" version = "1.7.4"