import logging import unittest.mock import pytest from paho.mqtt.client import MQTT_ERR_QUEUE_SIZE, MQTT_ERR_SUCCESS, MQTTMessage import switchbot_mqtt # pylint: disable=protected-access @pytest.mark.parametrize("mqtt_host", ["mqtt-broker.local"]) @pytest.mark.parametrize("mqtt_port", [1833]) def test__run(mqtt_host, mqtt_port): with unittest.mock.patch( "paho.mqtt.client.Client" ) as mqtt_client_mock, unittest.mock.patch( "switchbot_mqtt._mqtt_on_message" ) as message_handler_mock: switchbot_mqtt._run( mqtt_host=mqtt_host, mqtt_port=mqtt_port, mqtt_username=None, mqtt_password=None, ) mqtt_client_mock.assert_called_once_with() assert not mqtt_client_mock().username_pw_set.called mqtt_client_mock().connect.assert_called_once_with(host=mqtt_host, port=mqtt_port) mqtt_client_mock().socket().getpeername.return_value = (mqtt_host, mqtt_port) mqtt_client_mock().on_connect(mqtt_client_mock(), None, {}, 0) mqtt_client_mock().subscribe.assert_called_once_with( "homeassistant/switch/switchbot/+/set" ) mqtt_client_mock().on_message(mqtt_client_mock(), None, "message") message_handler_mock.assert_called_once() mqtt_client_mock().loop_forever.assert_called_once_with() @pytest.mark.parametrize("mqtt_host", ["mqtt-broker.local"]) @pytest.mark.parametrize("mqtt_port", [1833]) @pytest.mark.parametrize("mqtt_username", ["me"]) @pytest.mark.parametrize("mqtt_password", [None, "secret"]) def test__run_authentication(mqtt_host, mqtt_port, mqtt_username, mqtt_password): with unittest.mock.patch("paho.mqtt.client.Client") as mqtt_client_mock: switchbot_mqtt._run( mqtt_host=mqtt_host, mqtt_port=mqtt_port, mqtt_username=mqtt_username, mqtt_password=mqtt_password, ) mqtt_client_mock.assert_called_once_with() mqtt_client_mock().username_pw_set.assert_called_once_with( username=mqtt_username, password=mqtt_password, ) @pytest.mark.parametrize("mqtt_host", ["mqtt-broker.local"]) @pytest.mark.parametrize("mqtt_port", [1833]) @pytest.mark.parametrize("mqtt_password", ["secret"]) def test__run_authentication_missing_username(mqtt_host, mqtt_port, mqtt_password): with unittest.mock.patch("paho.mqtt.client.Client"): with pytest.raises(ValueError): switchbot_mqtt._run( mqtt_host=mqtt_host, mqtt_port=mqtt_port, mqtt_username=None, mqtt_password=mqtt_password, ) @pytest.mark.parametrize( ("topic", "payload", "expected_mac_address", "expected_action"), [ ( b"homeassistant/switch/switchbot/aa:bb:cc:dd:ee:ff/set", b"ON", "aa:bb:cc:dd:ee:ff", switchbot_mqtt._SwitchbotAction.ON, ), ( b"homeassistant/switch/switchbot/aa:bb:cc:dd:ee:ff/set", b"OFF", "aa:bb:cc:dd:ee:ff", switchbot_mqtt._SwitchbotAction.OFF, ), ( b"homeassistant/switch/switchbot/aa:bb:cc:dd:ee:ff/set", b"on", "aa:bb:cc:dd:ee:ff", switchbot_mqtt._SwitchbotAction.ON, ), ( b"homeassistant/switch/switchbot/aa:bb:cc:dd:ee:ff/set", b"off", "aa:bb:cc:dd:ee:ff", switchbot_mqtt._SwitchbotAction.OFF, ), ( b"homeassistant/switch/switchbot/aa:01:23:45:67:89/set", b"ON", "aa:01:23:45:67:89", switchbot_mqtt._SwitchbotAction.ON, ), ], ) def test__mqtt_on_message( topic: bytes, payload: bytes, expected_mac_address: str, expected_action: switchbot_mqtt._SwitchbotAction, ): message = MQTTMessage(topic=topic) message.payload = payload with unittest.mock.patch("switchbot_mqtt._send_command") as send_command_mock: switchbot_mqtt._mqtt_on_message("client_dummy", None, message) send_command_mock.assert_called_once_with( mqtt_client="client_dummy", switchbot_mac_address=expected_mac_address, action=expected_action, ) @pytest.mark.parametrize( ("topic", "payload"), [ (b"homeassistant/switch/switchbot/aa:01:23:4E:RR:OR/set", b"ON"), (b"homeassistant/switch/switchbot/aa:bb:cc:dd:ee:ff", b"on"), (b"homeassistant/switch/switchbot/aa:bb:cc:dd:ee:ff/change", b"ON"), (b"homeassistant/switch/switchbot/aa:bb:cc:dd:ee:ff/set", b""), (b"homeassistant/switch/switchbot/aa:bb:cc:dd:ee:ff/set", b"EIN"), ], ) def test__mqtt_on_message_ignored( topic: bytes, payload: bytes, ): message = MQTTMessage(topic=topic) message.payload = payload with unittest.mock.patch("switchbot_mqtt._send_command") as send_command_mock: switchbot_mqtt._mqtt_on_message(None, None, message) assert not send_command_mock.called @pytest.mark.parametrize( ("topic", "payload"), [(b"homeassistant/switch/switchbot/aa:bb:cc:dd:ee:ff/set", b"ON")], ) def test__mqtt_on_message_ignored_retained( topic: bytes, payload: bytes, ): message = MQTTMessage(topic=topic) message.payload = payload message.retain = True with unittest.mock.patch("switchbot_mqtt._send_command") as send_command_mock: switchbot_mqtt._mqtt_on_message(None, None, message) assert not send_command_mock.called @pytest.mark.parametrize( ("switchbot_mac_address", "expected_topic"), # https://www.home-assistant.io/docs/mqtt/discovery/#switches [("aa:bb:cc:dd:ee:ff", "homeassistant/switch/switchbot/aa:bb:cc:dd:ee:ff/state")], ) @pytest.mark.parametrize( ("state", "expected_payload"), [ (switchbot_mqtt._SwitchbotState.ON, b"ON"), (switchbot_mqtt._SwitchbotState.OFF, b"OFF"), ], ) @pytest.mark.parametrize( "return_code", [MQTT_ERR_SUCCESS, MQTT_ERR_QUEUE_SIZE], ) def test__report_state( caplog, state: switchbot_mqtt._SwitchbotState, switchbot_mac_address: str, expected_topic: str, expected_payload: bytes, return_code: int, ): # pylint: disable=too-many-arguments mqtt_client_mock = unittest.mock.MagicMock() mqtt_client_mock.publish.return_value.rc = return_code with caplog.at_level(logging.WARNING): switchbot_mqtt._report_state( mqtt_client=mqtt_client_mock, switchbot_mac_address=switchbot_mac_address, switchbot_state=state, ) mqtt_client_mock.publish.assert_called_once_with( topic=expected_topic, payload=expected_payload, retain=True, ) if return_code == MQTT_ERR_SUCCESS: assert len(caplog.records) == 0 else: assert len(caplog.records) == 1 assert caplog.record_tuples[0] == ( "switchbot_mqtt", logging.ERROR, "failed to publish state (rc={})".format(return_code), )