From b98b133d79bc95be9265a1b6fff7b49754bd93aa Mon Sep 17 00:00:00 2001 From: Stefan Allius Date: Sun, 18 May 2025 23:03:09 +0200 Subject: [PATCH] test MQTT error and exception handling --- app/src/mqtt.py | 21 +++++----- app/tests/test_mqtt.py | 87 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 98 insertions(+), 10 deletions(-) diff --git a/app/src/mqtt.py b/app/src/mqtt.py index 6b72939..bc0d3a3 100755 --- a/app/src/mqtt.py +++ b/app/src/mqtt.py @@ -203,12 +203,15 @@ class Mqtt(metaclass=Singleton): def _dcu_cmd(self, message): payload = message.payload.decode("UTF-8") - val = round(float(payload) * 10) - if val < 1000 or val > 8000: - logger_mqtt.error('dcu_power: value must be in' - 'the range 100..800,' - f' got: {payload}') - else: - pdu = struct.pack('>BBBBBBH', 1, 1, 6, 1, 0, 1, val) - for fnc in self.each_inverter(message, "send_dcu_cmd"): - fnc(pdu) + try: + val = round(float(payload) * 10) + if val < 1000 or val > 8000: + logger_mqtt.error('dcu_power: value must be in' + 'the range 100..800,' + f' got: {payload}') + else: + pdu = struct.pack('>BBBBBBH', 1, 1, 6, 1, 0, 1, val) + for fnc in self.each_inverter(message, "send_dcu_cmd"): + fnc(pdu) + except Exception: + pass diff --git a/app/tests/test_mqtt.py b/app/tests/test_mqtt.py index e80d07c..c9abc72 100755 --- a/app/tests/test_mqtt.py +++ b/app/tests/test_mqtt.py @@ -3,8 +3,9 @@ import pytest import asyncio import aiomqtt import logging - +from aiomqtt import MqttError from mock import patch, Mock + from async_stream import AsyncIfcImpl from singleton import Singleton from mqtt import Mqtt @@ -44,6 +45,14 @@ def config_no_conn(test_port): Config.act_config = {'mqtt':{'host': "", 'port': test_port, 'user': '', 'passwd': ''}, 'ha':{'auto_conf_prefix': 'homeassistant','discovery_prefix': 'homeassistant', 'entity_prefix': 'tsun'} } + Config.def_config = {} + +@pytest.fixture +def config_def_conn(test_port): + Config.act_config = {'mqtt':{'host': "unknown_url", 'port': test_port, 'user': '', 'passwd': ''}, + 'ha':{'auto_conf_prefix': 'homeassistant','discovery_prefix': 'homeassistant', 'entity_prefix': 'tsun'} + } + Config.def_config = Config.act_config @pytest.fixture def spy_at_cmd(): @@ -175,6 +184,76 @@ async def test_mqtt_no_config(config_no_conn): finally: await m.close() +@pytest.mark.asyncio +async def test_mqtt_except_no_config(config_no_conn, monkeypatch, caplog): + _ = config_no_conn + + assert asyncio.get_running_loop() + + on_connect = asyncio.Event() + async def cb(): + on_connect.set() + + async def my_aenter(self): + raise MqttError('TestException') from None + + monkeypatch.setattr(aiomqtt.Client, "__aenter__", my_aenter) + + LOGGER = logging.getLogger("mqtt") + LOGGER.propagate = True + LOGGER.setLevel(logging.INFO) + + with caplog.at_level(logging.INFO): + m = Mqtt(cb) + assert m.task + await asyncio.sleep(0) + assert not on_connect.is_set() + try: + await m.publish('homeassistant/status', 'online') + assert False + except MqttError: + pass + except Exception: + assert False + finally: + await m.close() + assert 'Connection lost; Reconnecting in 5 seconds' in caplog.text + +@pytest.mark.asyncio +async def test_mqtt_except_def_config(config_def_conn, monkeypatch, caplog): + _ = config_def_conn + + assert asyncio.get_running_loop() + + on_connect = asyncio.Event() + async def cb(): + on_connect.set() + + async def my_aenter(self): + raise MqttError('TestException') from None + + monkeypatch.setattr(aiomqtt.Client, "__aenter__", my_aenter) + + LOGGER = logging.getLogger("mqtt") + LOGGER.propagate = True + LOGGER.setLevel(logging.INFO) + + with caplog.at_level(logging.INFO): + m = Mqtt(cb) + assert m.task + await asyncio.sleep(0) + assert not on_connect.is_set() + try: + await m.publish('homeassistant/status', 'online') + assert False + except MqttError: + pass + except Exception: + assert False + finally: + await m.close() + assert 'MQTT is unconfigured; Check your config.toml!' in caplog.text + @pytest.mark.asyncio async def test_msg_dispatch(config_mqtt_conn, spy_modbus_cmd): _ = config_mqtt_conn @@ -235,6 +314,12 @@ async def test_msg_dispatch_err(config_mqtt_conn, spy_modbus_cmd): msg = aiomqtt.Message(topic= 'tsun/inv_1/modbus_read_regs', payload= b'0x3000, 10, 7', qos= 0, retain = False, mid= 0, properties= None) await m.dispatch_msg(msg) spy.assert_not_called() + + spy.reset_mock() + msg = aiomqtt.Message(topic= 'tsun/inv_1/dcu_power', payload= b'100W', qos= 0, retain = False, mid= 0, properties= None) + await m.dispatch_msg(msg) + spy.assert_not_called() + finally: await m.close()