test_evaporative_humidifier.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  1. import datetime
  2. from unittest.mock import AsyncMock, MagicMock, patch
  3. import pytest
  4. from bleak.backends.device import BLEDevice
  5. from switchbot import (
  6. HumidifierAction,
  7. HumidifierMode,
  8. HumidifierWaterLevel,
  9. SwitchBotAdvertisement,
  10. SwitchbotModel,
  11. )
  12. from switchbot.devices import evaporative_humidifier
  13. from switchbot.devices.device import SwitchbotEncryptedDevice, SwitchbotOperationError
  14. from .test_adv_parser import generate_ble_device
  15. def create_device_for_command_testing(init_data: dict | None = None):
  16. ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
  17. evaporative_humidifier_device = (
  18. evaporative_humidifier.SwitchbotEvaporativeHumidifier(
  19. ble_device, "ff", "ffffffffffffffffffffffffffffffff"
  20. )
  21. )
  22. evaporative_humidifier_device.update_from_advertisement(
  23. make_advertisement_data(ble_device, init_data)
  24. )
  25. evaporative_humidifier_device._send_command = AsyncMock()
  26. evaporative_humidifier_device._check_command_result = MagicMock()
  27. evaporative_humidifier_device.update = AsyncMock()
  28. return evaporative_humidifier_device
  29. def make_advertisement_data(ble_device: BLEDevice, init_data: dict | None = None):
  30. if init_data is None:
  31. init_data = {}
  32. """Set advertisement data with defaults."""
  33. return SwitchBotAdvertisement(
  34. address="aa:bb:cc:dd:ee:ff",
  35. data={
  36. "rawAdvData": b"#\x00\x00\x15\x1c\x00",
  37. "data": {
  38. "isOn": False,
  39. "mode": None,
  40. "target_humidity": 52,
  41. "child_lock": False,
  42. "over_humidify_protection": True,
  43. "tank_removed": False,
  44. "tilted_alert": False,
  45. "filter_missing": False,
  46. "is_meter_binded": True,
  47. "humidity": 51,
  48. "temperature": 16.8,
  49. "temp": {"c": 16.8, "f": 62.24},
  50. "filter_run_time": datetime.timedelta(days=3, seconds=57600),
  51. "filter_alert": False,
  52. "water_level": "medium",
  53. }
  54. | init_data,
  55. "isEncrypted": False,
  56. "model": "#",
  57. "modelFriendlyName": "Evaporative Humidifier",
  58. "modelName": SwitchbotModel.EVAPORATIVE_HUMIDIFIER,
  59. },
  60. device=ble_device,
  61. rssi=-80,
  62. active=True,
  63. )
  64. @pytest.mark.asyncio
  65. async def test_turn_on():
  66. """Test the turn_on method."""
  67. device = create_device_for_command_testing({"isOn": True})
  68. await device.turn_on()
  69. assert device.is_on() is True
  70. @pytest.mark.asyncio
  71. async def test_turn_off():
  72. """Test the turn_off method."""
  73. device = create_device_for_command_testing({"isOn": False})
  74. await device.turn_off()
  75. assert device.is_on() is False
  76. @pytest.mark.asyncio
  77. async def test_get_basic_is_none():
  78. """Test the get_basic_info when it returns None."""
  79. device = create_device_for_command_testing()
  80. device._get_basic_info = AsyncMock(return_value=None)
  81. assert await device.get_basic_info() is None
  82. @pytest.mark.asyncio
  83. @pytest.mark.parametrize(
  84. ("basic_info", "result"),
  85. [
  86. (
  87. bytearray(b"\x01\x86\x88\xb1\x98\x82\x00\x1e\x00\x88-\xc4\xff\xff \n\x07"),
  88. [
  89. True,
  90. HumidifierMode(6),
  91. True,
  92. False,
  93. False,
  94. False,
  95. False,
  96. True,
  97. 49,
  98. 24.8,
  99. 24.8,
  100. 76.64,
  101. "medium",
  102. 30,
  103. 45,
  104. ],
  105. ),
  106. (
  107. bytearray(b"\x01\x08 \xb1\x98r\x00\x1e\x00\x89-\xc4\xff\xff\x00\x00\x00"),
  108. [
  109. False,
  110. HumidifierMode(8),
  111. False,
  112. True,
  113. False,
  114. False,
  115. False,
  116. True,
  117. 49,
  118. 24.7,
  119. 24.7,
  120. 76.46,
  121. "medium",
  122. 30,
  123. 45,
  124. ],
  125. ),
  126. ],
  127. )
  128. async def test_get_basic_info(basic_info, result):
  129. """Test the get_basic_info method."""
  130. device = create_device_for_command_testing()
  131. device._get_basic_info = AsyncMock(return_value=basic_info)
  132. info = await device.get_basic_info()
  133. assert info["isOn"] is result[0]
  134. assert info["mode"] == result[1]
  135. assert info["over_humidify_protection"] is result[2]
  136. assert info["child_lock"] is result[3]
  137. assert info["tank_removed"] is result[4]
  138. assert info["tilted_alert"] is result[5]
  139. assert info["filter_missing"] is result[6]
  140. assert info["is_meter_binded"] is result[7]
  141. assert info["humidity"] == result[8]
  142. assert info["temperature"] == result[9]
  143. assert info["temp"]["c"] == result[10]
  144. assert info["temp"]["f"] == result[11]
  145. assert info["water_level"] == result[12]
  146. assert info["filter_run_time"] == result[13]
  147. assert info["target_humidity"] == result[14]
  148. @pytest.mark.asyncio
  149. @pytest.mark.parametrize(
  150. ("err_msg", "mode", "water_level"),
  151. [
  152. (
  153. "Target humidity can only be set in target humidity mode or sleep mode",
  154. HumidifierMode.AUTO,
  155. "low",
  156. ),
  157. (
  158. "Cannot perform operation when water tank is empty",
  159. HumidifierMode.TARGET_HUMIDITY,
  160. "empty",
  161. ),
  162. ],
  163. )
  164. async def test_set_target_humidity_with_invalid_conditions(err_msg, mode, water_level):
  165. """Test setting target humidity with invalid mode."""
  166. device = create_device_for_command_testing()
  167. device.get_mode = MagicMock(return_value=mode)
  168. device.get_water_level = MagicMock(return_value=water_level)
  169. with pytest.raises(SwitchbotOperationError, match=err_msg):
  170. await device.set_target_humidity(45)
  171. @pytest.mark.asyncio
  172. @pytest.mark.parametrize(
  173. ("err_msg", "mode", "water_level", "is_meter_binded", "target_humidity"),
  174. [
  175. (
  176. "Cannot perform operation when water tank is empty",
  177. HumidifierMode.TARGET_HUMIDITY,
  178. "empty",
  179. True,
  180. 45,
  181. ),
  182. (
  183. "Cannot set target humidity or auto mode when meter is not binded",
  184. HumidifierMode.TARGET_HUMIDITY,
  185. "medium",
  186. False,
  187. 45,
  188. ),
  189. (
  190. "Target humidity must be set before switching to target humidity mode or sleep mode",
  191. HumidifierMode.TARGET_HUMIDITY,
  192. "medium",
  193. True,
  194. None,
  195. ),
  196. ],
  197. )
  198. async def test_set_mode_with_invalid_conditions(
  199. err_msg, mode, water_level, is_meter_binded, target_humidity
  200. ):
  201. """Test setting target humidity with invalid mode."""
  202. device = create_device_for_command_testing()
  203. device.get_water_level = MagicMock(return_value=water_level)
  204. device.is_meter_binded = MagicMock(return_value=is_meter_binded)
  205. device.get_target_humidity = MagicMock(return_value=target_humidity)
  206. with pytest.raises(SwitchbotOperationError, match=err_msg):
  207. await device.set_mode(mode)
  208. @pytest.mark.asyncio
  209. async def test_set_target_humidity():
  210. """Test setting target humidity."""
  211. device = create_device_for_command_testing()
  212. device.get_mode = MagicMock(return_value=HumidifierMode.TARGET_HUMIDITY)
  213. await device.set_target_humidity(45)
  214. device._send_command.assert_awaited_once_with("570f430202002d")
  215. @pytest.mark.asyncio
  216. @pytest.mark.parametrize(
  217. ("mode", "command"),
  218. [
  219. (HumidifierMode.TARGET_HUMIDITY, "570f430202002d"),
  220. (HumidifierMode.AUTO, "570f4302040000"),
  221. (HumidifierMode.SLEEP, "570f430203002d"),
  222. (HumidifierMode.DRYING_FILTER, "570f43010108"),
  223. ],
  224. )
  225. async def test_set_mode(mode, command):
  226. """Test setting mode."""
  227. device = create_device_for_command_testing()
  228. device.get_target_humidity = MagicMock(return_value=45)
  229. await device.set_mode(mode)
  230. device._send_command.assert_awaited_once_with(command)
  231. @pytest.mark.asyncio
  232. @pytest.mark.parametrize(
  233. ("init_data", "result"),
  234. [
  235. (
  236. {"isOn": False, "mode": HumidifierMode.AUTO},
  237. [False, HumidifierMode.AUTO, HumidifierAction.OFF],
  238. ),
  239. (
  240. {"isOn": True, "mode": HumidifierMode.TARGET_HUMIDITY},
  241. [True, HumidifierMode.TARGET_HUMIDITY, HumidifierAction.HUMIDIFYING],
  242. ),
  243. (
  244. {"isOn": True, "mode": HumidifierMode.DRYING_FILTER},
  245. [True, HumidifierMode.DRYING_FILTER, HumidifierAction.DRYING],
  246. ),
  247. ],
  248. )
  249. async def test_status_from_process_adv(init_data, result):
  250. """Test status from process advertisement."""
  251. device = create_device_for_command_testing(init_data)
  252. assert device.is_on() is result[0]
  253. assert device.get_mode() is result[1]
  254. assert device.is_child_lock_enabled() is False
  255. assert device.is_over_humidify_protection_enabled() is True
  256. assert device.is_tank_removed() is False
  257. assert device.is_filter_missing() is False
  258. assert device.is_filter_alert_on() is False
  259. assert device.is_tilted_alert_on() is False
  260. assert device.get_water_level() == "medium"
  261. assert device.get_filter_run_time() == datetime.timedelta(days=3, seconds=57600)
  262. assert device.get_target_humidity() == 52
  263. assert device.get_humidity() == 51
  264. assert device.get_temperature() == 16.8
  265. assert device.get_action() == result[2]
  266. assert device.is_meter_binded() is True
  267. @pytest.mark.asyncio
  268. @pytest.mark.parametrize(
  269. ("enabled", "command"),
  270. [
  271. (True, "570f430501"),
  272. (False, "570f430500"),
  273. ],
  274. )
  275. async def test_set_child_lock(enabled, command):
  276. """Test setting child lock."""
  277. device = create_device_for_command_testing()
  278. await device.set_child_lock(enabled)
  279. device._send_command.assert_awaited_once_with(command)
  280. @pytest.mark.asyncio
  281. @patch.object(SwitchbotEncryptedDevice, "verify_encryption_key", new_callable=AsyncMock)
  282. async def test_verify_encryption_key(mock_parent_verify):
  283. ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
  284. key_id = "ff"
  285. encryption_key = "ffffffffffffffffffffffffffffffff"
  286. mock_parent_verify.return_value = True
  287. result = await evaporative_humidifier.SwitchbotEvaporativeHumidifier.verify_encryption_key(
  288. device=ble_device,
  289. key_id=key_id,
  290. encryption_key=encryption_key,
  291. )
  292. mock_parent_verify.assert_awaited_once_with(
  293. ble_device,
  294. key_id,
  295. encryption_key,
  296. SwitchbotModel.EVAPORATIVE_HUMIDIFIER,
  297. )
  298. assert result is True
  299. def test_evaporative_humidifier_modes():
  300. assert HumidifierMode.get_modes() == [
  301. "high",
  302. "medium",
  303. "low",
  304. "quiet",
  305. "target_humidity",
  306. "sleep",
  307. "auto",
  308. "drying_filter",
  309. ]
  310. def test_evaporative_humidifier_water_levels():
  311. assert HumidifierWaterLevel.get_levels() == [
  312. "empty",
  313. "low",
  314. "medium",
  315. "high",
  316. ]