diff --git a/bumper/__init__.py b/bumper/__init__.py index 162c333..88758fa 100644 --- a/bumper/__init__.py +++ b/bumper/__init__.py @@ -5,7 +5,8 @@ import socket import sys from bumper.db import revoke_expired_oauths, revoke_expired_tokens -from bumper.mqttserver import MQTTHelperBot, MQTTServer +from bumper.mqtt.helper_bot import HelperBot +from bumper.mqttserver import MQTTServer from bumper.util import get_logger, log_to_stdout from bumper.web.server import WebServer, WebserverBinding from bumper.xmppserver import XMPPServer @@ -50,7 +51,7 @@ token_validity_seconds = 3600 # 1 hour oauth_validity_days = 15 mqtt_server: MQTTServer -mqtt_helperbot: MQTTHelperBot +mqtt_helperbot: HelperBot web_server: WebServer xmpp_server: XMPPServer @@ -103,7 +104,7 @@ async def start(): global mqtt_server mqtt_server = MQTTServer(bumper_listen, mqtt_listen_port) global mqtt_helperbot - mqtt_helperbot = MQTTHelperBot(bumper_listen, mqtt_listen_port) + mqtt_helperbot = HelperBot(bumper_listen, mqtt_listen_port) global web_server web_server = WebServer(web_server_bindings) global xmpp_server diff --git a/bumper/mqtt/__init__.py b/bumper/mqtt/__init__.py new file mode 100644 index 0000000..13c2b41 --- /dev/null +++ b/bumper/mqtt/__init__.py @@ -0,0 +1 @@ +"""Mqtt module.""" diff --git a/bumper/mqtt/helper_bot.py b/bumper/mqtt/helper_bot.py new file mode 100644 index 0000000..7605c7d --- /dev/null +++ b/bumper/mqtt/helper_bot.py @@ -0,0 +1,149 @@ +"""Helper bot module.""" +import asyncio +import json +import ssl +from typing import Any, MutableMapping, Union + +from cachetools import TTLCache +from gmqtt import Client, Subscription +from gmqtt.mqtt.constants import MQTTv311 + +from bumper.util import get_logger + +_LOGGER = get_logger("helperbot") + + +class CommandDto: + """Command DTO.""" + + def __init__(self, payload_type: str) -> None: + self._payload_type = payload_type + self._event = asyncio.Event() + self._response: Union[str, bytes] + + async def wait_for_response(self) -> Union[str, dict[str, Any]]: + """Wait for the response to be received.""" + await self._event.wait() + if self._payload_type == "j": + return json.loads(self._response) # type:ignore[no-any-return] + + return str(self._response) + + def add_response(self, response: Union[str, bytes]) -> None: + """Add received response.""" + self._response = response + self._event.set() + + +class HelperBot: + """Helper bot, which converts commands from the rest api to mqtt ones.""" + + def __init__(self, host: str, port: int, timeout: float = 60): + self._commands: MutableMapping[str, CommandDto] = TTLCache( + maxsize=timeout * 60, ttl=timeout * 1.1 + ) + self._host = host + self._port = port + self._client_id = "helperbot@bumper/helperbot" + self._timeout = timeout + self._client = Client("helperbot@bumper/helperbot") + + # pylint: disable=unused-argument + async def _on_message( + client: Client, topic: str, payload: bytes, qos: int, properties: dict + ) -> None: + _LOGGER.debug("Got message: topic=%s; payload=%s;", topic, payload.decode()) + topic_split = topic.split("/") + data_decoded = str(payload.decode()) + if topic_split[10] in self._commands: + self._commands[topic_split[10]].add_response(data_decoded) + + self._client.on_message = _on_message + + @property + def is_connected(self) -> bool: + """Return True if client is connected successfully.""" + return self._client.is_connected # type: ignore[no-any-return] + + async def start(self) -> None: + """Connect and subscribe helper bot.""" + try: + if self.is_connected: + return + + ssl_ctx = ssl.create_default_context() + ssl_ctx.check_hostname = False + ssl_ctx.verify_mode = ssl.CERT_NONE + await self._client.connect( + self._host, self._port, ssl=ssl_ctx, version=MQTTv311 + ) + self._client.subscribe( + Subscription("iot/p2p/+/+/+/+/helperbot/bumper/helperbot/+/+/+") + ) + except Exception: + _LOGGER.exception("An exception occurred during startup", exc_info=True) + raise + + async def _wait_for_resp( + self, command_dto: CommandDto, request_id: str + ) -> dict[str, Any]: + try: + payload = await asyncio.wait_for( + command_dto.wait_for_response(), timeout=self._timeout + ) + return {"id": request_id, "ret": "ok", "resp": payload} + except asyncio.TimeoutError: + _LOGGER.debug("wait_for_resp timeout reached") + except asyncio.CancelledError: + _LOGGER.debug("wait_for_resp cancelled by asyncio", exc_info=True) + except Exception: # pylint: disable=broad-except + _LOGGER.exception("An unknown error occurred", exc_info=True) + + return { + "id": request_id, + "errno": 500, + "ret": "fail", + "debug": "wait for response timed out", + } + + async def send_command( + self, cmdjson: dict[str, Any], request_id: str + ) -> dict[str, Any]: + """Send command over MQTT.""" + if not self.is_connected: + await self.start() + + try: + topic = ( + f"iot/p2p/{cmdjson['cmdName']}/helperbot/bumper/helperbot/{cmdjson['toId']}/" + f"{cmdjson['toType']}/{cmdjson['toRes']}/q/{request_id}/{cmdjson['payloadType']}" + ) + + if cmdjson["payloadType"] == "j": + payload = json.dumps(cmdjson["payload"]) + else: + payload = str(cmdjson["payload"]) + + command_dto = CommandDto(cmdjson["payloadType"]) + self._commands[request_id] = command_dto + + _LOGGER.debug("Sending message %s", topic) + self._client.publish(topic, payload.encode()) + + resp = await self._wait_for_resp(command_dto, request_id) + return resp + except Exception: # pylint: disable=broad-except + _LOGGER.exception("Could not send command.", exc_info=True) + return { + "id": request_id, + "errno": 500, + "ret": "fail", + "debug": "exception occurred please check bumper logs", + } + finally: + self._commands.pop(request_id, None) + + async def disconnect(self) -> None: + """Disconnect client.""" + if self.is_connected: + await self._client.disconnect() diff --git a/bumper/mqttserver.py b/bumper/mqttserver.py index 0d21d75..9953327 100644 --- a/bumper/mqttserver.py +++ b/bumper/mqttserver.py @@ -1,18 +1,12 @@ """Mqtt module.""" -import asyncio -import json import os -from asyncio import Task -from typing import Any, MutableMapping, Optional, Union +from typing import Any import amqtt import pkg_resources from amqtt.broker import Broker, BrokerContext -from amqtt.client import MQTTClient -from amqtt.mqtt.constants import QOS_0 from amqtt.session import IncomingApplicationMessage, Session -from cachetools import TTLCache from passlib.apps import custom_app_context as pwd_context import bumper @@ -32,164 +26,6 @@ helperbotlog = get_logger("helperbot") boterrorlog = get_logger("boterror") -class CommandDto: - """Command DTO.""" - - def __init__(self, payload_type: str) -> None: - self._payload_type = payload_type - self._event = asyncio.Event() - self._response: Union[str, bytes] - - async def wait_for_response(self) -> Union[str, dict[str, Any]]: - """Wait for the response to be received.""" - await self._event.wait() - if self._payload_type == "j": - return json.loads(self._response) # type:ignore[no-any-return] - - return str(self._response) - - def add_response(self, response: Union[str, bytes]) -> None: - """Add received response.""" - self._response = response - self._event.set() - - -class MQTTHelperBot: - """Helper bot, which converts commands from the rest api to mqtt ones.""" - - def __init__(self, host: str, port: int, timeout: float = 60): - self._commands: MutableMapping[str, CommandDto] = TTLCache( - maxsize=timeout * 60, ttl=timeout * 1.1 - ) - self._host = host - self._port = port - self._client_id = "helperbot@bumper/helperbot" - self._timeout = timeout - self._client: Optional[MQTTClient] = None - self._new_messages_task: Optional[Task] = None - - @property - def is_connected(self) -> bool: - """Return True if client is connected successfully.""" - return ( - self._client is not None - and self._client.session.transitions.state == "connected" - ) - - async def start(self) -> None: - """Connect and subscribe helper bot.""" - try: - if self._client is None: - self._client = MQTTClient( - client_id=self._client_id, - config={"check_hostname": False, "reconnect_retries": 20}, - ) - - await self._client.connect( - f"mqtts://{self._host}:{self._port}/", cafile=bumper.ca_cert - ) - await self._client.subscribe( - [ - ("iot/p2p/+/+/+/+/helperbot/bumper/helperbot/+/+/+", QOS_0), - ] - ) - self._new_messages_task = asyncio.create_task( - self._check_for_new_messages() - ) - except Exception: - mqttserverlog.exception( - "An exception occurred during startup", exc_info=True - ) - raise - - async def _check_for_new_messages(self) -> None: - assert self._client is not None - while True: - try: - message: Optional[ - IncomingApplicationMessage - ] = await self._client.deliver_message() - if message is not None: - topic_split = str(message.topic).split("/") - data_decoded = str(message.data.decode("utf-8")) - if topic_split[10] in self._commands: - self._commands[topic_split[10]].add_response(data_decoded) - except asyncio.CancelledError: - pass - except Exception: # pylint: disable=broad-except - helperbotlog.error( - "An exception occurred during handling new messages", exc_info=True - ) - - async def _wait_for_resp( - self, command_dto: CommandDto, request_id: str - ) -> dict[str, Any]: - try: - payload = await asyncio.wait_for( - command_dto.wait_for_response(), timeout=self._timeout - ) - return {"id": request_id, "ret": "ok", "resp": payload} - except asyncio.TimeoutError: - helperbotlog.debug("wait_for_resp timeout reached") - except asyncio.CancelledError: - helperbotlog.debug("wait_for_resp cancelled by asyncio", exc_info=True) - except Exception: # pylint: disable=broad-except - helperbotlog.exception("An unknown error occurred", exc_info=True) - - return { - "id": request_id, - "errno": 500, - "ret": "fail", - "debug": "wait for response timed out", - } - - async def send_command( - self, cmdjson: dict[str, Any], request_id: str - ) -> dict[str, Any]: - """Send command over MQTT.""" - if self._client is None: - await self.start() - assert self._client is not None - - try: - topic = ( - f"iot/p2p/{cmdjson['cmdName']}/helperbot/bumper/helperbot/{cmdjson['toId']}/" - f"{cmdjson['toType']}/{cmdjson['toRes']}/q/{request_id}/{cmdjson['payloadType']}" - ) - command_dto = CommandDto(cmdjson["payloadType"]) - self._commands[request_id] = command_dto - - if cmdjson["payloadType"] == "j": - payload = json.dumps(cmdjson["payload"]) - else: - payload = str(cmdjson["payload"]) - - await self._client.publish(topic, payload.encode(), QOS_0) - - resp = await self._wait_for_resp(command_dto, request_id) - return resp - except Exception: # pylint: disable=broad-except - helperbotlog.exception("Could not send command.", exc_info=True) - return { - "id": request_id, - "errno": 500, - "ret": "fail", - "debug": "exception occurred please check bumper logs", - } - finally: - self._commands.pop(request_id, None) - - async def disconnect(self) -> None: - """Disconnect client.""" - if self._new_messages_task is not None: - self._new_messages_task.cancel() - self._new_messages_task = None - - if self._client is not None: - await self._client.disconnect() - self._client = None - - class MQTTServer: """Mqtt server.""" diff --git a/bumper/util.py b/bumper/util.py index 400a09d..a3c2674 100644 --- a/bumper/util.py +++ b/bumper/util.py @@ -41,7 +41,7 @@ def get_logger(name: str, rotate: RotatingFileHandler = None) -> logging.Logger: ) # Ignore this logger get_logger("amqtt.broker", rotate) get_logger("amqtt.mqtt.protocol", rotate) - get_logger("amqtt.client", rotate) + get_logger("gmqtt", rotate) return logger diff --git a/requirements.txt b/requirements.txt index 9b77355..6507fcf 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,5 +2,6 @@ aiohttp==3.8.1 aiohttp-jinja2==1.5 amqtt==0.10.0 cachetools==5.0.0 +gmqtt==0.6.11 Jinja2==3.0.3 tinydb==4.6.1 diff --git a/tests/conftest.py b/tests/conftest.py index c996c8f..30176be 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,10 +1,12 @@ import asyncio +import ssl import pytest -from amqtt.client import MQTTClient +from gmqtt import Client +from gmqtt.mqtt.constants import MQTTv311 import bumper -from bumper import MQTTServer, WebserverBinding +from bumper import HelperBot, MQTTServer, WebserverBinding from tests import HOST, MQTT_PORT, WEBSERVER_PORT @@ -16,26 +18,40 @@ async def mqtt_server(): while not mqtt_server.state == "started": await asyncio.sleep(0.1) - yield + yield mqtt_server await mqtt_server.shutdown() -@pytest.mark.usefixtures("mqtt_server") @pytest.fixture -async def mqtt_client(): - client = MQTTClient( - client_id="helperbot@bumper/test", - config={"check_hostname": False, "auto_reconnect": False}, - ) +async def mqtt_client(mqtt_server: MQTTServer): + assert mqtt_server.state == "started" - await client.connect(f"mqtts://{HOST}:{MQTT_PORT}/", cafile=bumper.ca_cert) + client = Client("helperbot@bumper/test") + ssl_ctx = ssl.create_default_context() + ssl_ctx.check_hostname = False + ssl_ctx.verify_mode = ssl.CERT_NONE + await client.connect(HOST, MQTT_PORT, ssl=ssl_ctx, version=MQTTv311) yield client await client.disconnect() +@pytest.fixture +async def helper_bot(mqtt_server: MQTTServer): + assert mqtt_server.state == "started" + + helper_bot = HelperBot(HOST, MQTT_PORT, 0.1) + bumper.mqtt_helperbot = helper_bot + await helper_bot.start() + assert helper_bot.is_connected + + yield helper_bot + + await helper_bot.disconnect() + + @pytest.fixture async def webserver_client(aiohttp_client): webserver = bumper.WebServer(WebserverBinding(HOST, WEBSERVER_PORT, False)) diff --git a/tests/test_mqttserver.py b/tests/test_mqttserver.py index 30d3f52..812f73b 100644 --- a/tests/test_mqttserver.py +++ b/tests/test_mqttserver.py @@ -1,28 +1,27 @@ import asyncio import os +import ssl import time -import pytest -from amqtt.client import MQTTClient -from amqtt.mqtt.constants import QOS_0 +from gmqtt import Client +from gmqtt.mqtt.constants import MQTTv311 from testfixtures import LogCapture -import bumper -from bumper import MQTTHelperBot, MQTTServer, ca_cert, db +from bumper import MQTTServer, db +from bumper.mqtt.helper_bot import HelperBot from tests import HOST, MQTT_PORT -@pytest.mark.usefixtures("mqtt_server") -async def test_helperbot_message(mqtt_client: MQTTClient): +async def test_helperbot_message(mqtt_client: Client): with LogCapture() as l: # Test broadcast message - mqtt_helperbot = MQTTHelperBot(HOST, MQTT_PORT) + mqtt_helperbot = HelperBot(HOST, MQTT_PORT) await mqtt_helperbot.start() assert mqtt_helperbot.is_connected msg_payload = "" msg_topic_name = "iot/atr/DustCaseST/bot_serial/ls1ok3/wC3g/x" - await mqtt_client.publish(msg_topic_name, msg_payload.encode(), QOS_0) + mqtt_client.publish(msg_topic_name, msg_payload.encode()) await asyncio.sleep(0.1) @@ -37,12 +36,12 @@ async def test_helperbot_message(mqtt_client: MQTTClient): await mqtt_helperbot.disconnect() # Send command to bot - mqtt_helperbot = MQTTHelperBot(HOST, MQTT_PORT) + mqtt_helperbot = HelperBot(HOST, MQTT_PORT) await mqtt_helperbot.start() assert mqtt_helperbot.is_connected msg_payload = "{}" msg_topic_name = "iot/p2p/GetWKVer/helperbot/bumper/helperbot/bot_serial/ls1ok3/wC3g/q/iCmuqp/j" - await mqtt_client.publish(msg_topic_name, msg_payload.encode(), QOS_0) + mqtt_client.publish(msg_topic_name, msg_payload.encode()) await asyncio.sleep(0.1) @@ -57,12 +56,12 @@ async def test_helperbot_message(mqtt_client: MQTTClient): await mqtt_helperbot.disconnect() # Received response to command - mqtt_helperbot = MQTTHelperBot(HOST, MQTT_PORT) + mqtt_helperbot = HelperBot(HOST, MQTT_PORT) await mqtt_helperbot.start() assert mqtt_helperbot.is_connected msg_payload = '{"ret":"ok","ver":"0.13.5"}' msg_topic_name = "iot/p2p/GetWKVer/bot_serial/ls1ok3/wC3g/helperbot/bumper/helperbot/p/iCmuqp/j" - await mqtt_client.publish(msg_topic_name, msg_payload.encode(), QOS_0) + mqtt_client.publish(msg_topic_name, msg_payload.encode()) await asyncio.sleep(0.1) @@ -77,12 +76,12 @@ async def test_helperbot_message(mqtt_client: MQTTClient): await mqtt_helperbot.disconnect() # Received unknown message - mqtt_helperbot = MQTTHelperBot(HOST, MQTT_PORT) + mqtt_helperbot = HelperBot(HOST, MQTT_PORT) await mqtt_helperbot.start() assert mqtt_helperbot.is_connected msg_payload = "test" msg_topic_name = "iot/p2p/GetWKVer/bot_serial/ls1ok3/wC3g/TESTBAD/bumper/helperbot/p/iCmuqp/j" - await mqtt_client.publish(msg_topic_name, msg_payload.encode(), QOS_0) + mqtt_client.publish(msg_topic_name, msg_payload.encode()) await asyncio.sleep(0.1) @@ -97,12 +96,12 @@ async def test_helperbot_message(mqtt_client: MQTTClient): await mqtt_helperbot.disconnect() # Received error message - mqtt_helperbot = MQTTHelperBot(HOST, MQTT_PORT) + mqtt_helperbot = HelperBot(HOST, MQTT_PORT) await mqtt_helperbot.start() assert mqtt_helperbot.is_connected msg_payload = "" msg_topic_name = "iot/atr/errors/bot_serial/ls1ok3/wC3g/x" - await mqtt_client.publish(msg_topic_name, msg_payload.encode(), QOS_0) + mqtt_client.publish(msg_topic_name, msg_payload.encode()) await asyncio.sleep(0.1) @@ -117,15 +116,7 @@ async def test_helperbot_message(mqtt_client: MQTTClient): await mqtt_helperbot.disconnect() -@pytest.mark.usefixtures("mqtt_server") -async def test_helperbot_expire_message(mqtt_client: MQTTClient): - timeout = 0.1 - # Test broadcast message - mqtt_helperbot = MQTTHelperBot(HOST, MQTT_PORT, timeout) - bumper.mqtt_helperbot = mqtt_helperbot - await mqtt_helperbot.start() - assert mqtt_helperbot.is_connected - +async def test_helperbot_expire_message(mqtt_client: Client, helper_bot: HelperBot): expire_msg_payload = '{"ret":"ok","ver":"0.13.5"}' expire_msg_topic_name = "iot/p2p/GetWKVer/bot_serial/ls1ok3/wC3g/helperbot/bumper/helperbot/p/testgood/j" currenttime = time.time() @@ -136,32 +127,23 @@ async def test_helperbot_expire_message(mqtt_client: MQTTClient): "payload": expire_msg_payload, } - mqtt_helperbot._commands[request_id] = data + helper_bot._commands[request_id] = data - assert mqtt_helperbot._commands[request_id] == data + assert helper_bot._commands[request_id] == data await asyncio.sleep(0.1) msg_payload = "" msg_topic_name = "iot/atr/DustCaseST/bot_serial/ls1ok3/wC3g/x" - await mqtt_client.publish( - msg_topic_name, msg_payload.encode(), QOS_0 + mqtt_client.publish( + msg_topic_name, msg_payload.encode() ) # Send another message to force get_msg - await asyncio.sleep(timeout * 2) + await asyncio.sleep(0.1 * 2) - assert mqtt_helperbot._commands.get(request_id, None) == None - - await mqtt_helperbot.disconnect() + assert helper_bot._commands.get(request_id, None) == None -@pytest.mark.usefixtures("mqtt_server") -async def test_helperbot_sendcommand(mqtt_client: MQTTClient): - timeout = 0.1 - mqtt_helperbot = MQTTHelperBot(HOST, MQTT_PORT, timeout) - bumper.mqtt_helperbot = mqtt_helperbot - await mqtt_helperbot.start() - assert mqtt_helperbot.is_connected - +async def test_helperbot_sendcommand(mqtt_client: Client, helper_bot: HelperBot): cmdjson = { "toType": "ls1ok3", "payloadType": "j", @@ -178,7 +160,7 @@ async def test_helperbot_sendcommand(mqtt_client: MQTTClient): "realm": "ecouser.net", }, } - commandresult = await mqtt_helperbot.send_command(cmdjson, "testfail") + commandresult = await helper_bot.send_command(cmdjson, "testfail") # Don't send a response, ensure timeout assert commandresult == { "debug": "wait for response timed out", @@ -190,9 +172,10 @@ async def test_helperbot_sendcommand(mqtt_client: MQTTClient): # Send response beforehand msg_payload = '{"ret":"ok","ver":"0.13.5"}' msg_topic_name = "iot/p2p/GetWKVer/bot_serial/ls1ok3/wC3g/helperbot/bumper/helperbot/p/testgood/j" - await mqtt_client.publish(msg_topic_name, msg_payload.encode(), QOS_0) + loop = asyncio.get_event_loop() + loop.call_soon(lambda: mqtt_client.publish(msg_topic_name, msg_payload.encode())) - commandresult = await mqtt_helperbot.send_command(cmdjson, "testgood") + commandresult = await helper_bot.send_command(cmdjson, "testgood") assert commandresult == { "id": "testgood", "resp": {"ret": "ok", "ver": "0.13.5"}, @@ -222,9 +205,9 @@ async def test_helperbot_sendcommand(mqtt_client: MQTTClient): # Send response beforehand msg_payload = "" msg_topic_name = "iot/p2p/GetLifeSpan/bot_serial/ls1ok3/wC3g/helperbot/bumper/helperbot/p/testx/q" - await mqtt_client.publish(msg_topic_name, msg_payload.encode(), QOS_0) + mqtt_client.publish(msg_topic_name, msg_payload.encode()) - commandresult = await mqtt_helperbot.send_command(cmdjson, "testx") + commandresult = await helper_bot.send_command(cmdjson, "testx") assert commandresult == { "id": "testx", "resp": "", @@ -257,9 +240,9 @@ async def test_helperbot_sendcommand(mqtt_client: MQTTClient): msg_topic_name = ( "iot/p2p/getStats/bot_serial/ls1ok3/wC3g/helperbot/bumper/helperbot/p/testj/j" ) - await mqtt_client.publish(msg_topic_name, msg_payload.encode(), QOS_0) + mqtt_client.publish(msg_topic_name, msg_payload.encode()) - commandresult = await mqtt_helperbot.send_command(cmdjson, "testj") + commandresult = await helper_bot.send_command(cmdjson, "testj") assert commandresult == { "id": "testj", @@ -287,8 +270,6 @@ async def test_helperbot_sendcommand(mqtt_client: MQTTClient): "ret": "ok", } - await mqtt_helperbot.disconnect() - async def test_mqttserver(): if os.path.exists("tests/tmp.db"): @@ -302,7 +283,7 @@ async def test_mqttserver(): try: # Test helperbot connect - mqtt_helperbot = MQTTHelperBot(HOST, MQTT_PORT) + mqtt_helperbot = HelperBot(HOST, MQTT_PORT) await mqtt_helperbot.start() assert mqtt_helperbot.is_connected await mqtt_helperbot.disconnect() @@ -311,60 +292,35 @@ async def test_mqttserver(): db.user_add("user_123") # Add user to db db.client_add("user_123", "ecouser.net", "resource_123") # Add client to db - client = MQTTClient( - client_id="user_123@ecouser.net/resource_123", - config={"check_hostname": False}, - ) - - await client.connect( - f"mqtts://{HOST}:{MQTT_PORT}/", - cafile=ca_cert, - ) - assert client._connected_state._value == True # Check client is connected + ssl_ctx = ssl.create_default_context() + ssl_ctx.check_hostname = False + ssl_ctx.verify_mode = ssl.CERT_NONE + client = Client("user_123@ecouser.net/resource_123") + await client.connect(HOST, MQTT_PORT, ssl=ssl_ctx, version=MQTTv311) + assert client.is_connected await client.disconnect() - assert client._connected_state._value == False # Check client is disconnected + assert not client.is_connected # Test fake_bot connect - client = MQTTClient( - client_id="bot_serial@ls1ok3/wC3g", config={"check_hostname": False} - ) - - await client.connect( - f"mqtts://{HOST}:{MQTT_PORT}/", - cafile=ca_cert, - ) - assert client._connected_state._value == True # Check fake_bot is connected + client = Client("bot_serial@ls1ok3/wC3g") + await client.connect(HOST, MQTT_PORT, ssl=ssl_ctx, version=MQTTv311) + assert client.is_connected await client.disconnect() # Test file auth client connect - client = MQTTClient( - client_id="test-file-auth", - config={ - "check_hostname": False, - "auto_reconnect": False, - "reconnect_retries": 1, - }, - ) - - # good user/pass - await client.connect( - f"mqtts://test-client:abc123!@{HOST}:{MQTT_PORT}/", - cafile=ca_cert, - cleansession=True, - ) - - assert client._connected_state._value == True # Check client is connected + client = Client("test-file-auth") + client.set_auth_credentials("test-client", "abc123!") + await client.connect(HOST, MQTT_PORT, ssl=ssl_ctx, version=MQTTv311) + assert client.is_connected await client.disconnect() - assert client._connected_state._value == False # Check client is disconnected + assert not client.is_connected # bad password with LogCapture() as l: - await client.connect( - f"mqtts://test-client:notvalid!@{HOST}:{MQTT_PORT}/", - cafile=ca_cert, - cleansession=True, - ) + client.set_auth_credentials("test-client", "notvalid!") + await client.connect(HOST, MQTT_PORT, ssl=ssl_ctx, version=MQTTv311) + await client.disconnect() l.check_present( ( @@ -377,11 +333,9 @@ async def test_mqttserver(): l.clear() # no username in file - await client.connect( - f"mqtts://test-client-noexist:notvalid!@{HOST}:{MQTT_PORT}/", - cafile=ca_cert, - cleansession=True, - ) + client.set_auth_credentials("test-client-noexist", "notvalid!") + await client.connect(HOST, MQTT_PORT, ssl=ssl_ctx, version=MQTTv311) + await client.disconnect() l.check_present( ( diff --git a/tests/web/test_server.py b/tests/web/test_server.py index cef97be..48ac055 100644 --- a/tests/web/test_server.py +++ b/tests/web/test_server.py @@ -6,7 +6,7 @@ from unittest import mock import pytest import bumper -from bumper import MQTTHelperBot, WebServer, WebserverBinding, XMPPServer, db +from bumper import HelperBot, WebServer, WebserverBinding, XMPPServer, db from bumper.models import ERR_TOKEN_INVALID, RETURN_API_SUCCESS from tests import HOST, MQTT_PORT, WEBSERVER_PORT @@ -36,7 +36,7 @@ async def test_webserver_no_ssl(): await webserver.start() -@pytest.mark.usefixtures("mqtt_server") +@pytest.mark.usefixtures("helper_bot") async def test_base(webserver_client): remove_existing_db() @@ -46,20 +46,13 @@ async def test_base(webserver_client): bumper.xmpp_server = xmpp_server await xmpp_server.start_async_server() - # Start Helperbot - mqtt_helperbot = MQTTHelperBot(HOST, MQTT_PORT) - bumper.mqtt_helperbot = mqtt_helperbot - await mqtt_helperbot.start() - resp = await webserver_client.get("/") assert resp.status == 200 - await mqtt_helperbot.disconnect() - bumper.xmpp_server.disconnect() -@pytest.mark.usefixtures("mqtt_server") +@pytest.mark.usefixtures("helper_bot") async def test_restartService(webserver_client): remove_existing_db() @@ -69,11 +62,6 @@ async def test_restartService(webserver_client): bumper.xmpp_server = xmpp_server await xmpp_server.start_async_server() - # Start Helperbot - mqtt_helperbot = MQTTHelperBot(HOST, MQTT_PORT) - bumper.mqtt_helperbot = mqtt_helperbot - await mqtt_helperbot.start() - resp = await webserver_client.get("/restart_Helperbot") assert resp.status == 200 @@ -83,8 +71,6 @@ async def test_restartService(webserver_client): resp = await webserver_client.get("/restart_XMPPServer") assert resp.status == 200 - await mqtt_helperbot.disconnect() - xmpp_server.disconnect() @@ -718,12 +704,11 @@ async def test_appsvr_api(webserver_client): assert jsonresp["ret"] == "ok" -async def test_lg_logs(webserver_client): +async def test_lg_logs(webserver_client, helper_bot: HelperBot): remove_existing_db() db.bot_add("sn_1234", "did_1234", "ls1ok3", "res_1234", "eco-ng") db.bot_set_mqtt("did_1234", True) confserver = create_webserver() - bumper.mqtt_helperbot = MQTTHelperBot(HOST, MQTT_PORT) # Test return get status command_getstatus_resp = { @@ -731,7 +716,7 @@ async def test_lg_logs(webserver_client): "resp": "", "ret": "ok", } - bumper.mqtt_helperbot.send_command = mock.MagicMock( + helper_bot.send_command = mock.MagicMock( return_value=async_return(command_getstatus_resp) ) @@ -775,10 +760,9 @@ async def test_postLookup(webserver_client): assert test_resp["result"] == "ok" -async def test_devmgr(webserver_client): +async def test_devmgr(webserver_client, helper_bot: HelperBot): remove_existing_db() confserver = create_webserver() - bumper.mqtt_helperbot = MQTTHelperBot(HOST, MQTT_PORT) # Test PollSCResult postbody = {"td": "PollSCResult"} @@ -808,7 +792,7 @@ async def test_devmgr(webserver_client): "resp": "", "ret": "ok", } - bumper.mqtt_helperbot.send_command = mock.MagicMock( + helper_bot.send_command = mock.MagicMock( return_value=async_return(command_getstatus_resp) ) resp = await webserver_client.post("/api/iot/devmanager.do", json=postbody) @@ -819,7 +803,7 @@ async def test_devmgr(webserver_client): # Test return fail timeout command_timeout_resp = {"id": "resp_1234", "errno": "timeout", "ret": "fail"} - bumper.mqtt_helperbot.send_command = mock.MagicMock( + helper_bot.send_command = mock.MagicMock( return_value=async_return(command_timeout_resp) ) resp = await webserver_client.post("/api/iot/devmanager.do", json=postbody) @@ -829,10 +813,9 @@ async def test_devmgr(webserver_client): assert test_resp["ret"] == "fail" -async def test_dim_devmanager(webserver_client): +async def test_dim_devmanager(webserver_client, helper_bot: HelperBot): remove_existing_db() confserver = create_webserver() - bumper.mqtt_helperbot = MQTTHelperBot(HOST, MQTT_PORT) # Test PollSCResult postbody = {"td": "PollSCResult"} @@ -862,7 +845,7 @@ async def test_dim_devmanager(webserver_client): "resp": "", "ret": "ok", } - bumper.mqtt_helperbot.send_command = mock.MagicMock( + helper_bot.send_command = mock.MagicMock( return_value=async_return(command_getstatus_resp) ) resp = await webserver_client.post("/api/dim/devmanager.do", json=postbody) @@ -873,7 +856,7 @@ async def test_dim_devmanager(webserver_client): # Test return fail timeout command_timeout_resp = {"id": "resp_1234", "errno": "timeout", "ret": "fail"} - bumper.mqtt_helperbot.send_command = mock.MagicMock( + helper_bot.send_command = mock.MagicMock( return_value=async_return(command_timeout_resp) ) resp = await webserver_client.post("/api/dim/devmanager.do", json=postbody) @@ -885,7 +868,7 @@ async def test_dim_devmanager(webserver_client): # Set bot not on mqtt db.bot_set_mqtt("did_1234", False) - bumper.mqtt_helperbot.send_command = mock.MagicMock( + helper_bot.send_command = mock.MagicMock( return_value=async_return(command_getstatus_resp) ) resp = await webserver_client.post("/api/dim/devmanager.do", json=postbody)