test_mqtt_message.py 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  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(
  8. ("topic", "address"),
  9. (
  10. (b"intertechno-cc1101/12345678/0/set", 12345678),
  11. (b"intertechno-cc1101/1234/0/set", 1234),
  12. ),
  13. )
  14. def test__mqtt_on_message_address(topic, address):
  15. message = MQTTMessage(topic=topic)
  16. message.payload = b"ON"
  17. with unittest.mock.patch("intertechno_cc1101.RemoteControl") as remote_control_mock:
  18. intertechno_cc1101_mqtt._mqtt_on_message(
  19. "dummy",
  20. intertechno_cc1101_mqtt._MQTTEventUserData(aliases={}, power_setting=0xC6),
  21. message,
  22. )
  23. remote_control_mock.assert_called_once_with(address=address)
  24. @pytest.mark.parametrize(
  25. ("topic", "address_str"),
  26. (
  27. (b"intertechno-cc1101/abcdef/0/set", "abcdef"),
  28. (b"intertechno-cc1101//0/set", ""),
  29. ),
  30. )
  31. def test__mqtt_on_message_invalid_address(caplog, topic, address_str):
  32. message = MQTTMessage(topic=topic)
  33. message.payload = b"ON"
  34. with unittest.mock.patch("intertechno_cc1101.RemoteControl") as remote_control_mock:
  35. with caplog.at_level(logging.WARNING):
  36. intertechno_cc1101_mqtt._mqtt_on_message(
  37. "dummy",
  38. intertechno_cc1101_mqtt._MQTTEventUserData(
  39. aliases={}, power_setting=0xC6
  40. ),
  41. message,
  42. )
  43. remote_control_mock.assert_not_called()
  44. assert caplog.record_tuples == [
  45. (
  46. "intertechno_cc1101_mqtt",
  47. logging.WARNING,
  48. "failed to parse address {!r}, expected integer; ignoring message".format(
  49. address_str
  50. ),
  51. )
  52. ]
  53. @pytest.mark.parametrize(
  54. ("topic", "button_index"),
  55. ((b"intertechno-cc1101/12345678/0/set", 0), (b"intertechno-cc1101/1234/7/set", 7)),
  56. )
  57. @pytest.mark.parametrize(
  58. ("payload", "turn_on"),
  59. ((b"ON", True), (b"On", True), (b"on", True), (b"OFF", False), (b"off", False)),
  60. )
  61. @pytest.mark.parametrize("retain", [True, False])
  62. @pytest.mark.parametrize("power_setting", [0xC6, 0xC0])
  63. def test__mqtt_on_message_button_index_action(
  64. topic, button_index, payload, turn_on, retain, power_setting
  65. ):
  66. # pylint: disable=too-many-arguments
  67. message = MQTTMessage(topic=topic)
  68. message.payload = payload
  69. message.retain = retain
  70. with unittest.mock.patch("intertechno_cc1101.RemoteControl") as remote_control_mock:
  71. intertechno_cc1101_mqtt._mqtt_on_message(
  72. "dummy",
  73. intertechno_cc1101_mqtt._MQTTEventUserData(
  74. aliases={}, power_setting=power_setting
  75. ),
  76. message,
  77. )
  78. if turn_on:
  79. remote_control_mock().turn_on.assert_called_once_with(
  80. button_index=button_index, power_setting=power_setting
  81. )
  82. remote_control_mock().turn_off.assert_not_called()
  83. else:
  84. remote_control_mock().turn_off.assert_called_once_with(
  85. button_index=button_index, power_setting=power_setting
  86. )
  87. remote_control_mock().turn_on.assert_not_called()
  88. @pytest.mark.parametrize(
  89. ("topic", "button_index_str"),
  90. (
  91. (b"intertechno-cc1101/12345678/abc/set", "abc"),
  92. (b"intertechno-cc1101/12345678//set", ""),
  93. ),
  94. )
  95. def test__mqtt_on_message_invalid_button_index(caplog, topic, button_index_str):
  96. message = MQTTMessage(topic=topic)
  97. message.payload = b"ON"
  98. with unittest.mock.patch("intertechno_cc1101.RemoteControl") as remote_control_mock:
  99. with caplog.at_level(logging.WARNING):
  100. intertechno_cc1101_mqtt._mqtt_on_message(
  101. "dummy",
  102. intertechno_cc1101_mqtt._MQTTEventUserData(
  103. aliases={}, power_setting=0xC6
  104. ),
  105. message,
  106. )
  107. remote_control_mock().turn_on.assert_not_called()
  108. remote_control_mock().turn_off.assert_not_called()
  109. assert caplog.record_tuples == [
  110. (
  111. "intertechno_cc1101_mqtt",
  112. logging.WARNING,
  113. "failed to parse button index {!r}, expected integer; ignoring message".format(
  114. button_index_str
  115. ),
  116. )
  117. ]
  118. @pytest.mark.parametrize(
  119. ("topic", "address", "button_index"),
  120. (
  121. (b"intertechno-cc1101/some-name/set", 21, 0),
  122. (b"intertechno-cc1101/another-name/set", 42, 7),
  123. ),
  124. )
  125. def test__mqtt_on_message_alias(topic, address, button_index):
  126. message = MQTTMessage(topic=topic)
  127. message.payload = b"ON"
  128. with unittest.mock.patch("intertechno_cc1101.RemoteControl") as remote_control_mock:
  129. intertechno_cc1101_mqtt._mqtt_on_message(
  130. "dummy",
  131. intertechno_cc1101_mqtt._MQTTEventUserData(
  132. aliases={
  133. "some-name": {"address": 21, "button-index": 0},
  134. "another-name": {"address": "42", "button-index": "7"}, # :O
  135. },
  136. power_setting=0xC6,
  137. ),
  138. message,
  139. )
  140. remote_control_mock.assert_called_once_with(address=address)
  141. remote_control_mock().turn_on.assert_called_once_with(
  142. button_index=button_index, power_setting=0xC6
  143. )
  144. @pytest.mark.parametrize(
  145. ("topic", "alias"),
  146. (
  147. (b"intertechno-cc1101//set", ""),
  148. (b"intertechno-cc1101/unknown-name/set", "unknown-name"),
  149. ),
  150. )
  151. def test__mqtt_on_message_undefined_alias(caplog, topic, alias):
  152. message = MQTTMessage(topic=topic)
  153. message.payload = b"ON"
  154. with unittest.mock.patch("intertechno_cc1101.RemoteControl") as remote_control_mock:
  155. with caplog.at_level(logging.WARNING):
  156. intertechno_cc1101_mqtt._mqtt_on_message(
  157. "dummy",
  158. intertechno_cc1101_mqtt._MQTTEventUserData(
  159. aliases={"some-name": {"address": 21, "button-index": 0}},
  160. power_setting=0xC6,
  161. ),
  162. message,
  163. )
  164. remote_control_mock.assert_not_called()
  165. assert caplog.record_tuples == [
  166. (
  167. "intertechno_cc1101_mqtt",
  168. logging.WARNING,
  169. "unknown alias {!r}; ignoring message".format(alias),
  170. )
  171. ]
  172. @pytest.mark.parametrize(
  173. "aliases",
  174. (
  175. {"some-name": {"address": 21}},
  176. {"some-name": {"button-index": 0}},
  177. {"some-name": {"adresse": 21, "button-index": 0}},
  178. ),
  179. )
  180. def test__mqtt_on_message_alias_missing_attrs(caplog, aliases):
  181. message = MQTTMessage(topic=b"intertechno-cc1101/some-name/set")
  182. message.payload = b"ON"
  183. with unittest.mock.patch("intertechno_cc1101.RemoteControl") as remote_control_mock:
  184. with caplog.at_level(logging.WARNING):
  185. intertechno_cc1101_mqtt._mqtt_on_message(
  186. "dummy",
  187. intertechno_cc1101_mqtt._MQTTEventUserData(
  188. aliases=aliases, power_setting=0xC6
  189. ),
  190. message,
  191. )
  192. remote_control_mock.assert_not_called()
  193. assert caplog.record_tuples == [
  194. (
  195. "intertechno_cc1101_mqtt",
  196. logging.ERROR,
  197. "alias file must provide fields 'address' and 'button-index' for each alias",
  198. )
  199. ]
  200. @pytest.mark.parametrize(
  201. "topic", (b"intertechno-cc1101/123456789/0/set", b"intertechno-cc1101/-21/0/set")
  202. )
  203. def test__mqtt_on_message_remote_init_failed(caplog, topic):
  204. message = MQTTMessage(topic=topic)
  205. message.payload = b"ON"
  206. with caplog.at_level(logging.WARNING):
  207. intertechno_cc1101_mqtt._mqtt_on_message(
  208. "dummy",
  209. intertechno_cc1101_mqtt._MQTTEventUserData(aliases={}, power_setting=0xC6),
  210. message,
  211. )
  212. assert len(caplog.records) == 1
  213. assert caplog.records[0].levelno == logging.WARNING
  214. assert (
  215. caplog.records[0].message
  216. == "failed to initialize remote control, invalid address? ignoring message"
  217. )
  218. assert isinstance(caplog.records[0].exc_info[1], ValueError)
  219. def test__mqtt_on_message_transmission_failed(caplog):
  220. message = MQTTMessage(topic=b"intertechno-cc1101/12345678/3/set")
  221. message.payload = b"ON"
  222. with unittest.mock.patch(
  223. "cc1101.CC1101.__enter__",
  224. side_effect=FileNotFoundError("[Errno 2] No such file or directory"),
  225. ), caplog.at_level(logging.ERROR):
  226. intertechno_cc1101_mqtt._mqtt_on_message(
  227. "dummy",
  228. intertechno_cc1101_mqtt._MQTTEventUserData(aliases={}, power_setting=0xC6),
  229. message,
  230. )
  231. assert len(caplog.records) == 1
  232. assert caplog.records[0].levelno == logging.ERROR
  233. assert caplog.records[0].message == "failed to send signal"
  234. assert isinstance(caplog.records[0].exc_info[1], FileNotFoundError)
  235. @pytest.mark.parametrize("payload", (b"EIN", b"aus", b""))
  236. def test__mqtt_on_message_invalid_payload(caplog, payload):
  237. message = MQTTMessage(topic=b"intertechno-cc1101/1234/7/set")
  238. message.payload = payload
  239. with unittest.mock.patch("intertechno_cc1101.RemoteControl") as remote_control_mock:
  240. intertechno_cc1101_mqtt._mqtt_on_message(
  241. "dummy",
  242. intertechno_cc1101_mqtt._MQTTEventUserData(aliases={}, power_setting=0xC6),
  243. message,
  244. )
  245. remote_control_mock().turn_off.assert_not_called()
  246. remote_control_mock().turn_on.assert_not_called()
  247. assert caplog.record_tuples == [
  248. (
  249. "intertechno_cc1101_mqtt",
  250. 30,
  251. "unexpected payload {!r}; expected 'ON' or 'OFF'".format(payload),
  252. )
  253. ]