Files
flucto-heisskleber/tests/test_mqtt.py
Felix Weiler 78ba98a587 Feature/async sources (#46)
* WIP: Added async file reader.

* Async resampling and synchronization refactored.

* Add async mqtt publisher. Remove queue from joint.

* Add async zmq publisher and subscriber.

* Modify integration tests for streaming.

* Name refactoring resampler.

* Added async source/sink to factory.

* Refactor joint and add integration tests.

* Add termcolor dev dependency

* Add conosole source and sink

* Add cli interface for different protocols

* Removed files unfit for merge.

* Fix review requests.

* Restore use of $MSB_CONFIG_DIR for now.

It seems that the default behaviour is not looking for
.config/heisskleber

* Remove version test, causing unnecessary failures.
2024-01-22 11:23:00 +01:00

154 lines
5.6 KiB
Python

import json
from queue import SimpleQueue
from unittest.mock import call, patch
import pytest
from paho.mqtt.client import MQTTMessage
from heisskleber.mqtt.config import MqttConf
from heisskleber.mqtt.mqtt_base import MqttBase
from heisskleber.mqtt.subscriber import MqttSubscriber
# Mock configuration for MQTT_Base
@pytest.fixture
def mock_mqtt_conf() -> MqttConf:
return MqttConf(
broker="localhost",
port=1883,
user="user",
password="passwd", # noqa: S106, this is a test password
ssl=False,
verbose=False,
qos=1,
)
# Mock the paho mqtt client
@pytest.fixture
def mock_mqtt_client():
with patch("heisskleber.mqtt.mqtt_base.mqtt_client", autospec=True) as mock:
yield mock
@pytest.fixture
def mock_queue():
with patch("heisskleber.mqtt.subscriber.SimpleQueue", spec=SimpleQueue) as mock:
yield mock
def test_mqtt_base_intialization(mock_mqtt_client, mock_mqtt_conf):
"""Test that the intialization of the mqtt client is as expected."""
base = MqttBase(config=mock_mqtt_conf)
mock_mqtt_client.assert_called_once()
mock_mqtt_client.return_value.loop_start.assert_called_once()
mock_client_instance = mock_mqtt_client.return_value
mock_client_instance.username_pw_set.assert_called_with(mock_mqtt_conf.user, mock_mqtt_conf.password)
mock_client_instance.connect.assert_called_with(mock_mqtt_conf.broker, mock_mqtt_conf.port)
assert base.client.on_connect == base._on_connect
assert base.client.on_disconnect == base._on_disconnect
assert base.client.on_publish == base._on_publish
assert base.client.on_message == base._on_message
def test_mqtt_base_on_connect(mock_mqtt_client, mock_mqtt_conf, capsys):
base = MqttBase(config=mock_mqtt_conf)
base._on_connect(None, None, {}, 0)
captured = capsys.readouterr()
assert f"MQTT node connected to {mock_mqtt_conf.broker}:{mock_mqtt_conf.port}" in captured.out
def test_mqtt_base_on_disconnect_with_error(mock_mqtt_client, mock_mqtt_conf, capsys):
"""Assert that the mqtt client shuts down when disconnect callback is received."""
base = MqttBase(config=mock_mqtt_conf)
with pytest.raises(SystemExit):
base._on_disconnect(None, None, 1)
captured = capsys.readouterr()
assert "Killing this service" in captured.out
print(captured.out)
def test_mqtt_subscribes_single_topic(mock_mqtt_client, mock_mqtt_conf):
"""Test that the mqtt client subscribes to a single topic."""
_ = MqttSubscriber(topics="singleTopic", config=mock_mqtt_conf)
actual_calls = mock_mqtt_client.return_value.subscribe.call_args_list
assert actual_calls == [call("singleTopic", mock_mqtt_conf.qos)]
def test_mqtt_subscribes_multiple_topics(mock_mqtt_client, mock_mqtt_conf):
"""Test that the mqtt client subscribes to multiple topics passed as list.
I would love to do this via parametrization, but the call argument is built differently for single size lists and longer lists.
"""
_ = MqttSubscriber(topics=["multiple1", "multiple2"], config=mock_mqtt_conf)
actual_calls = mock_mqtt_client.return_value.subscribe.call_args_list
assert actual_calls == [
call([("multiple1", mock_mqtt_conf.qos), ("multiple2", mock_mqtt_conf.qos)]),
]
def test_mqtt_subscribes_multiple_topics_tuple(mock_mqtt_client, mock_mqtt_conf):
"""Test that the mqtt client subscribes to multiple topics passed as tuple."""
_ = MqttSubscriber(topics=("multiple1", "multiple2"), config=mock_mqtt_conf)
actual_calls = mock_mqtt_client.return_value.subscribe.call_args_list
assert actual_calls == [
call([("multiple1", mock_mqtt_conf.qos), ("multiple2", mock_mqtt_conf.qos)]),
]
def create_fake_mqtt_message(topic: bytes, payload: bytes) -> MQTTMessage:
msg = MQTTMessage()
msg.topic = topic
msg.payload = payload
return msg
def test_receive_with_message(mock_mqtt_conf: MqttConf, mock_mqtt_client, mock_queue):
"""Test the mqtt receive function with fake MQTT messages."""
topic = b"test/topic"
payload = json.dumps({"key": "value"}).encode()
fake_message = create_fake_mqtt_message(topic, payload)
mock_queue.return_value.get.side_effect = [fake_message]
subscriber = MqttSubscriber(topics=[topic.decode()], config=mock_mqtt_conf)
received_topic, received_payload = subscriber.receive()
assert received_topic == "test/topic"
assert received_payload == {"key": "value"}
def test_message_is_put_into_queue(mock_mqtt_conf: MqttConf, mock_mqtt_client, mock_queue):
"""Test that values a put into a queue when on_message callback is called."""
topic = b"test/topic"
payload = json.dumps({"key": "value"}).encode()
fake_message = create_fake_mqtt_message(topic, payload)
mock_queue.return_value.get.side_effect = [fake_message]
subscriber = MqttSubscriber(topics=[topic.decode()], config=mock_mqtt_conf)
subscriber._on_message(None, None, fake_message)
mock_queue.return_value.put.assert_called_once_with(fake_message)
def test_message_is_put_into_queue_with_actual_queue(mock_mqtt_conf, mock_mqtt_client):
"""Test that the buffering via queue works as expected."""
topic = b"test/topic"
payload = json.dumps({"key": "value"}).encode()
fake_message = create_fake_mqtt_message(topic, payload)
# mock_queue.return_value.get.side_effect = [fake_message]
subscriber = MqttSubscriber(topics=[topic.decode()], config=mock_mqtt_conf)
subscriber._on_message(None, None, fake_message)
topic, return_dict = subscriber.receive()
assert topic == "test/topic"
assert return_dict == {"key": "value"}