test_mqtt.py 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. import logging
  2. import unittest.mock
  3. import pytest
  4. from paho.mqtt.client import MQTTMessage
  5. import intertechno_cc1101_mqtt
  6. # pylint: disable=protected-access
  7. @pytest.mark.parametrize("mqtt_host", ["mqtt-broker.local"])
  8. @pytest.mark.parametrize("mqtt_port", [1833])
  9. def test__run(caplog, mqtt_host, mqtt_port):
  10. with unittest.mock.patch(
  11. "paho.mqtt.client.Client"
  12. ) as mqtt_client_mock, caplog.at_level(logging.DEBUG):
  13. intertechno_cc1101_mqtt._run(
  14. mqtt_host=mqtt_host,
  15. mqtt_port=mqtt_port,
  16. mqtt_username=None,
  17. mqtt_password=None,
  18. )
  19. mqtt_client_mock.assert_called_once_with()
  20. assert not mqtt_client_mock().username_pw_set.called
  21. mqtt_client_mock().connect.assert_called_once_with(host=mqtt_host, port=mqtt_port)
  22. mqtt_client_mock().socket().getpeername.return_value = (mqtt_host, mqtt_port)
  23. with caplog.at_level(logging.DEBUG):
  24. mqtt_client_mock().on_connect(mqtt_client_mock(), None, {}, 0)
  25. # pylint: disable=comparison-with-callable
  26. assert mqtt_client_mock().on_message == intertechno_cc1101_mqtt._mqtt_on_message
  27. mqtt_client_mock().subscribe.assert_called_once_with("intertechno-cc1101/+/+/set")
  28. mqtt_client_mock().loop_forever.assert_called_once_with()
  29. assert caplog.record_tuples == [
  30. (
  31. "intertechno_cc1101_mqtt",
  32. logging.INFO,
  33. "connecting to MQTT broker {}:{}".format(mqtt_host, mqtt_port),
  34. ),
  35. (
  36. "intertechno_cc1101_mqtt",
  37. logging.DEBUG,
  38. "connected to MQTT broker {}:{}".format(mqtt_host, mqtt_port),
  39. ),
  40. (
  41. "intertechno_cc1101_mqtt",
  42. logging.INFO,
  43. "subscribing to MQTT topic 'intertechno-cc1101/+/+/set'",
  44. ),
  45. ]
  46. @pytest.mark.parametrize("mqtt_host", ["mqtt-broker.local"])
  47. @pytest.mark.parametrize("mqtt_port", [1833])
  48. @pytest.mark.parametrize("mqtt_username", ["me"])
  49. @pytest.mark.parametrize("mqtt_password", [None, "secret"])
  50. def test__run_authentication(mqtt_host, mqtt_port, mqtt_username, mqtt_password):
  51. with unittest.mock.patch("paho.mqtt.client.Client") as mqtt_client_mock:
  52. intertechno_cc1101_mqtt._run(
  53. mqtt_host=mqtt_host,
  54. mqtt_port=mqtt_port,
  55. mqtt_username=mqtt_username,
  56. mqtt_password=mqtt_password,
  57. )
  58. mqtt_client_mock.assert_called_once_with()
  59. mqtt_client_mock().username_pw_set.assert_called_once_with(
  60. username=mqtt_username, password=mqtt_password
  61. )
  62. @pytest.mark.parametrize("mqtt_host", ["mqtt-broker.local"])
  63. @pytest.mark.parametrize("mqtt_port", [1833])
  64. @pytest.mark.parametrize("mqtt_password", ["secret"])
  65. def test__run_authentication_missing_username(mqtt_host, mqtt_port, mqtt_password):
  66. with unittest.mock.patch("paho.mqtt.client.Client"):
  67. with pytest.raises(ValueError):
  68. intertechno_cc1101_mqtt._run(
  69. mqtt_host=mqtt_host,
  70. mqtt_port=mqtt_port,
  71. mqtt_username=None,
  72. mqtt_password=mqtt_password,
  73. )
  74. def test__mqtt_on_message_retained(caplog):
  75. message = MQTTMessage(topic=b"intertechno-cc1101/12345678/0/set")
  76. message.payload = b"ON"
  77. message.retain = True
  78. with unittest.mock.patch("intertechno_cc1101.RemoteControl") as remote_control_mock:
  79. with caplog.at_level(logging.DEBUG):
  80. intertechno_cc1101_mqtt._mqtt_on_message("dummy", None, message)
  81. remote_control_mock.assert_not_called()
  82. assert caplog.record_tuples == [
  83. (
  84. "intertechno_cc1101_mqtt",
  85. logging.DEBUG,
  86. "received topic=intertechno-cc1101/12345678/0/set payload=b'ON'",
  87. ),
  88. ("intertechno_cc1101_mqtt", logging.WARNING, "ignoring retained message"),
  89. ]
  90. @pytest.mark.parametrize(
  91. ("topic", "address"),
  92. (
  93. (b"intertechno-cc1101/12345678/0/set", 12345678),
  94. (b"intertechno-cc1101/1234/0/set", 1234),
  95. ),
  96. )
  97. def test__mqtt_on_message_address(topic, address):
  98. message = MQTTMessage(topic=topic)
  99. message.payload = b"ON"
  100. with unittest.mock.patch("intertechno_cc1101.RemoteControl") as remote_control_mock:
  101. intertechno_cc1101_mqtt._mqtt_on_message("dummy", None, message)
  102. remote_control_mock.assert_called_once_with(address=address)
  103. @pytest.mark.parametrize(
  104. ("topic", "address_str"),
  105. (
  106. (b"intertechno-cc1101/abcdef/0/set", "abcdef"),
  107. (b"intertechno-cc1101//0/set", ""),
  108. ),
  109. )
  110. def test__mqtt_on_message_invalid_address(caplog, topic, address_str):
  111. message = MQTTMessage(topic=topic)
  112. message.payload = b"ON"
  113. with unittest.mock.patch("intertechno_cc1101.RemoteControl") as remote_control_mock:
  114. with caplog.at_level(logging.WARNING):
  115. intertechno_cc1101_mqtt._mqtt_on_message("dummy", None, message)
  116. remote_control_mock.assert_not_called()
  117. assert caplog.record_tuples == [
  118. (
  119. "intertechno_cc1101_mqtt",
  120. logging.WARNING,
  121. "failed to parse address {!r}, expected integer; ignoring message".format(
  122. address_str
  123. ),
  124. )
  125. ]
  126. @pytest.mark.parametrize(
  127. ("topic", "button_index"),
  128. ((b"intertechno-cc1101/12345678/0/set", 0), (b"intertechno-cc1101/1234/7/set", 7)),
  129. )
  130. @pytest.mark.parametrize(
  131. ("payload", "turn_on"),
  132. ((b"ON", True), (b"On", True), (b"on", True), (b"OFF", False), (b"off", False)),
  133. )
  134. def test__mqtt_on_message_button_index_action(topic, button_index, payload, turn_on):
  135. message = MQTTMessage(topic=topic)
  136. message.payload = payload
  137. with unittest.mock.patch("intertechno_cc1101.RemoteControl") as remote_control_mock:
  138. intertechno_cc1101_mqtt._mqtt_on_message("dummy", None, message)
  139. if turn_on:
  140. remote_control_mock().turn_on.assert_called_once_with(button_index=button_index)
  141. remote_control_mock().turn_off.assert_not_called()
  142. else:
  143. remote_control_mock().turn_off.assert_called_once_with(
  144. button_index=button_index
  145. )
  146. remote_control_mock().turn_on.assert_not_called()
  147. @pytest.mark.parametrize(
  148. ("topic", "button_index_str"),
  149. (
  150. (b"intertechno-cc1101/12345678/abc/set", "abc"),
  151. (b"intertechno-cc1101/12345678//set", ""),
  152. ),
  153. )
  154. def test__mqtt_on_message_invalid_button_index(caplog, topic, button_index_str):
  155. message = MQTTMessage(topic=topic)
  156. message.payload = b"ON"
  157. with unittest.mock.patch("intertechno_cc1101.RemoteControl") as remote_control_mock:
  158. with caplog.at_level(logging.WARNING):
  159. intertechno_cc1101_mqtt._mqtt_on_message("dummy", None, message)
  160. remote_control_mock().turn_on.assert_not_called()
  161. remote_control_mock().turn_off.assert_not_called()
  162. assert caplog.record_tuples == [
  163. (
  164. "intertechno_cc1101_mqtt",
  165. logging.WARNING,
  166. "failed to parse button index {!r}, expected integer; ignoring message".format(
  167. button_index_str
  168. ),
  169. )
  170. ]
  171. @pytest.mark.parametrize(
  172. "topic", (b"intertechno-cc1101/123456789/0/set", b"intertechno-cc1101/-21/0/set")
  173. )
  174. def test__mqtt_on_message_remote_init_failed(caplog, topic):
  175. message = MQTTMessage(topic=topic)
  176. message.payload = b"ON"
  177. with caplog.at_level(logging.WARNING):
  178. intertechno_cc1101_mqtt._mqtt_on_message("dummy", None, message)
  179. assert len(caplog.records) == 1
  180. assert caplog.records[0].levelno == logging.WARNING
  181. assert (
  182. caplog.records[0].message
  183. == "failed to initialize remote control, invalid address? ignoring message"
  184. )
  185. assert isinstance(caplog.records[0].exc_info[1], AssertionError)
  186. def test__mqtt_on_message_transmission_failed(caplog):
  187. message = MQTTMessage(topic=b"intertechno-cc1101/12345678/3/set")
  188. message.payload = b"ON"
  189. with unittest.mock.patch(
  190. "cc1101.CC1101.__enter__",
  191. side_effect=FileNotFoundError("[Errno 2] No such file or directory"),
  192. ), caplog.at_level(logging.ERROR):
  193. intertechno_cc1101_mqtt._mqtt_on_message("dummy", None, message)
  194. assert len(caplog.records) == 1
  195. assert caplog.records[0].levelno == logging.ERROR
  196. assert caplog.records[0].message == "failed to send signal"
  197. assert isinstance(caplog.records[0].exc_info[1], FileNotFoundError)
  198. @pytest.mark.parametrize("payload", (b"EIN", b"aus", b""))
  199. def test__mqtt_on_message_invalid_payload(caplog, payload):
  200. message = MQTTMessage(topic=b"intertechno-cc1101/1234/7/set")
  201. message.payload = payload
  202. with unittest.mock.patch("intertechno_cc1101.RemoteControl") as remote_control_mock:
  203. intertechno_cc1101_mqtt._mqtt_on_message("dummy", None, message)
  204. remote_control_mock().turn_off.assert_not_called()
  205. remote_control_mock().turn_on.assert_not_called()
  206. assert caplog.record_tuples == [
  207. (
  208. "intertechno_cc1101_mqtt",
  209. 30,
  210. "unexpected payload {!r}; expected 'ON' or 'OFF'".format(payload),
  211. )
  212. ]