test_switchbot_curtain_motor_position.py 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. # switchbot-mqtt - MQTT client controlling SwitchBot button & curtain automators,
  2. # compatible with home-assistant.io's MQTT Switch & Cover platform
  3. #
  4. # Copyright (C) 2022 Fabian Peter Hammerle <fabian@hammerle.me>
  5. #
  6. # This program is free software: you can redistribute it and/or modify
  7. # it under the terms of the GNU General Public License as published by
  8. # the Free Software Foundation, either version 3 of the License, or
  9. # any later version.
  10. #
  11. # This program is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU General Public License
  17. # along with this program. If not, see <https://www.gnu.org/licenses/>.
  18. import logging
  19. import unittest.mock
  20. import _pytest.logging
  21. import pytest
  22. from paho.mqtt.client import MQTTMessage
  23. from switchbot_mqtt._actors import _CurtainMotor
  24. from switchbot_mqtt._actors.base import _MQTTCallbackUserdata
  25. # pylint: disable=protected-access
  26. @pytest.mark.parametrize(
  27. ("topic", "payload", "expected_mac_address", "expected_position_percent"),
  28. [
  29. (
  30. b"home/cover/switchbot-curtain/aa:bb:cc:dd:ee:ff/position/set-percent",
  31. b"42",
  32. "aa:bb:cc:dd:ee:ff",
  33. 42,
  34. ),
  35. (
  36. b"home/cover/switchbot-curtain/11:22:33:44:55:66/position/set-percent",
  37. b"0",
  38. "11:22:33:44:55:66",
  39. 0,
  40. ),
  41. (
  42. b"home/cover/switchbot-curtain/11:22:33:44:55:66/position/set-percent",
  43. b"100",
  44. "11:22:33:44:55:66",
  45. 100,
  46. ),
  47. ],
  48. )
  49. @pytest.mark.parametrize("retry_count", (3, 42))
  50. def test__mqtt_set_position_callback(
  51. caplog: _pytest.logging.LogCaptureFixture,
  52. topic: bytes,
  53. payload: bytes,
  54. expected_mac_address: str,
  55. retry_count: int,
  56. expected_position_percent: int,
  57. ) -> None:
  58. callback_userdata = _MQTTCallbackUserdata(
  59. retry_count=retry_count,
  60. device_passwords={},
  61. fetch_device_info=False,
  62. mqtt_topic_prefix="home/",
  63. )
  64. message = MQTTMessage(topic=topic)
  65. message.payload = payload
  66. with unittest.mock.patch(
  67. "switchbot.SwitchbotCurtain"
  68. ) as device_init_mock, caplog.at_level(logging.DEBUG):
  69. _CurtainMotor._mqtt_set_position_callback(
  70. mqtt_client="client dummy", userdata=callback_userdata, message=message
  71. )
  72. device_init_mock.assert_called_once_with(
  73. mac=expected_mac_address,
  74. password=None,
  75. retry_count=retry_count,
  76. reverse_mode=True,
  77. )
  78. device_init_mock().set_position.assert_called_once_with(expected_position_percent)
  79. assert caplog.record_tuples == [
  80. (
  81. "switchbot_mqtt._actors",
  82. logging.DEBUG,
  83. f"received topic=home/cover/switchbot-curtain/{expected_mac_address}"
  84. f"/position/set-percent payload=b'{expected_position_percent}'",
  85. ),
  86. (
  87. "switchbot_mqtt._actors",
  88. logging.INFO,
  89. f"set position of switchbot curtain {expected_mac_address}"
  90. f" to {expected_position_percent}%",
  91. ),
  92. ]
  93. def test__mqtt_set_position_callback_ignore_retained(
  94. caplog: _pytest.logging.LogCaptureFixture,
  95. ) -> None:
  96. callback_userdata = _MQTTCallbackUserdata(
  97. retry_count=3,
  98. device_passwords={},
  99. fetch_device_info=False,
  100. mqtt_topic_prefix="whatever",
  101. )
  102. message = MQTTMessage(
  103. topic=b"homeassistant/cover/switchbot-curtain/aa:bb:cc:dd:ee:ff/position/set-percent"
  104. )
  105. message.payload = b"42"
  106. message.retain = True
  107. with unittest.mock.patch(
  108. "switchbot.SwitchbotCurtain"
  109. ) as device_init_mock, caplog.at_level(logging.INFO):
  110. _CurtainMotor._mqtt_set_position_callback(
  111. mqtt_client="client dummy", userdata=callback_userdata, message=message
  112. )
  113. device_init_mock.assert_not_called()
  114. assert caplog.record_tuples == [
  115. (
  116. "switchbot_mqtt._actors",
  117. logging.INFO,
  118. "ignoring retained message on topic"
  119. " homeassistant/cover/switchbot-curtain/aa:bb:cc:dd:ee:ff/position/set-percent",
  120. ),
  121. ]
  122. def test__mqtt_set_position_callback_unexpected_topic(
  123. caplog: _pytest.logging.LogCaptureFixture,
  124. ) -> None:
  125. callback_userdata = _MQTTCallbackUserdata(
  126. retry_count=3,
  127. device_passwords={},
  128. fetch_device_info=False,
  129. mqtt_topic_prefix="",
  130. )
  131. message = MQTTMessage(topic=b"switchbot-curtain/aa:bb:cc:dd:ee:ff/position/set")
  132. message.payload = b"42"
  133. with unittest.mock.patch(
  134. "switchbot.SwitchbotCurtain"
  135. ) as device_init_mock, caplog.at_level(logging.INFO):
  136. _CurtainMotor._mqtt_set_position_callback(
  137. mqtt_client="client dummy", userdata=callback_userdata, message=message
  138. )
  139. device_init_mock.assert_not_called()
  140. assert caplog.record_tuples == [
  141. (
  142. "switchbot_mqtt._actors.base",
  143. logging.WARN,
  144. "unexpected topic switchbot-curtain/aa:bb:cc:dd:ee:ff/position/set",
  145. ),
  146. ]
  147. def test__mqtt_set_position_callback_invalid_mac_address(
  148. caplog: _pytest.logging.LogCaptureFixture,
  149. ) -> None:
  150. callback_userdata = _MQTTCallbackUserdata(
  151. retry_count=3,
  152. device_passwords={},
  153. fetch_device_info=False,
  154. mqtt_topic_prefix="tnatsissaemoh/",
  155. )
  156. message = MQTTMessage(
  157. topic=b"tnatsissaemoh/cover/switchbot-curtain/aa:bb:cc:dd:ee/position/set-percent"
  158. )
  159. message.payload = b"42"
  160. with unittest.mock.patch(
  161. "switchbot.SwitchbotCurtain"
  162. ) as device_init_mock, caplog.at_level(logging.INFO):
  163. _CurtainMotor._mqtt_set_position_callback(
  164. mqtt_client="client dummy", userdata=callback_userdata, message=message
  165. )
  166. device_init_mock.assert_not_called()
  167. assert caplog.record_tuples == [
  168. (
  169. "switchbot_mqtt._actors.base",
  170. logging.WARN,
  171. "invalid mac address aa:bb:cc:dd:ee",
  172. ),
  173. ]
  174. @pytest.mark.parametrize("payload", [b"-1", b"123"])
  175. def test__mqtt_set_position_callback_invalid_position(
  176. caplog: _pytest.logging.LogCaptureFixture,
  177. payload: bytes,
  178. ) -> None:
  179. callback_userdata = _MQTTCallbackUserdata(
  180. retry_count=3,
  181. device_passwords={},
  182. fetch_device_info=False,
  183. mqtt_topic_prefix="homeassistant/",
  184. )
  185. message = MQTTMessage(
  186. topic=b"homeassistant/cover/switchbot-curtain/aa:bb:cc:dd:ee:ff/position/set-percent"
  187. )
  188. message.payload = payload
  189. with unittest.mock.patch(
  190. "switchbot.SwitchbotCurtain"
  191. ) as device_init_mock, caplog.at_level(logging.INFO):
  192. _CurtainMotor._mqtt_set_position_callback(
  193. mqtt_client="client dummy", userdata=callback_userdata, message=message
  194. )
  195. device_init_mock.assert_called_once()
  196. device_init_mock().set_position.assert_not_called()
  197. assert caplog.record_tuples == [
  198. (
  199. "switchbot_mqtt._actors",
  200. logging.WARN,
  201. f"invalid position {payload.decode()}%, ignoring message",
  202. ),
  203. ]
  204. def test__mqtt_set_position_callback_command_failed(
  205. caplog: _pytest.logging.LogCaptureFixture,
  206. ) -> None:
  207. callback_userdata = _MQTTCallbackUserdata(
  208. retry_count=3,
  209. device_passwords={},
  210. fetch_device_info=False,
  211. mqtt_topic_prefix="",
  212. )
  213. message = MQTTMessage(
  214. topic=b"cover/switchbot-curtain/aa:bb:cc:dd:ee:ff/position/set-percent"
  215. )
  216. message.payload = b"21"
  217. with unittest.mock.patch(
  218. "switchbot.SwitchbotCurtain"
  219. ) as device_init_mock, caplog.at_level(logging.INFO):
  220. device_init_mock().set_position.return_value = False
  221. device_init_mock.reset_mock()
  222. _CurtainMotor._mqtt_set_position_callback(
  223. mqtt_client="client dummy", userdata=callback_userdata, message=message
  224. )
  225. device_init_mock.assert_called_once()
  226. device_init_mock().set_position.assert_called_with(21)
  227. assert caplog.record_tuples == [
  228. (
  229. "switchbot_mqtt._actors",
  230. logging.ERROR,
  231. "failed to set position of switchbot curtain aa:bb:cc:dd:ee:ff",
  232. ),
  233. ]