test_air_purifier.py 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. from unittest.mock import AsyncMock, MagicMock, patch
  2. import pytest
  3. from bleak.backends.device import BLEDevice
  4. from switchbot import SwitchBotAdvertisement, SwitchbotEncryptedDevice, SwitchbotModel
  5. from switchbot.const.air_purifier import AirPurifierMode
  6. from switchbot.devices import air_purifier
  7. from .test_adv_parser import generate_ble_device
  8. common_params = [
  9. (b"7\x00\x00\x95-\x00", "7"),
  10. (b"*\x00\x00\x15\x04\x00", "*"),
  11. (b"+\x00\x00\x15\x04\x00", "+"),
  12. (b"8\x00\x00\x95-\x00", "8"),
  13. ]
  14. def create_device_for_command_testing(
  15. rawAdvData: bytes, model: str, init_data: dict | None = None
  16. ):
  17. ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
  18. device = air_purifier.SwitchbotAirPurifier(
  19. ble_device, "ff", "ffffffffffffffffffffffffffffffff"
  20. )
  21. device.update_from_advertisement(
  22. make_advertisement_data(ble_device, rawAdvData, model, init_data)
  23. )
  24. device._send_command = AsyncMock()
  25. device._check_command_result = MagicMock()
  26. device.update = AsyncMock()
  27. return device
  28. def make_advertisement_data(
  29. ble_device: BLEDevice, rawAdvData: bytes, model: str, init_data: dict | None = None
  30. ):
  31. """Set advertisement data with defaults."""
  32. if init_data is None:
  33. init_data = {}
  34. return SwitchBotAdvertisement(
  35. address="aa:bb:cc:dd:ee:ff",
  36. data={
  37. "rawAdvData": rawAdvData,
  38. "data": {
  39. "isOn": True,
  40. "mode": "level_3",
  41. "isAqiValid": False,
  42. "child_lock": False,
  43. "speed": 100,
  44. "aqi_level": "excellent",
  45. "filter element working time": 405,
  46. "err_code": 0,
  47. "sequence_number": 161,
  48. }
  49. | init_data,
  50. "isEncrypted": False,
  51. "model": model,
  52. "modelFriendlyName": "Air Purifier",
  53. "modelName": SwitchbotModel.AIR_PURIFIER,
  54. },
  55. device=ble_device,
  56. rssi=-80,
  57. active=True,
  58. )
  59. @pytest.mark.asyncio
  60. @pytest.mark.parametrize(
  61. ("rawAdvData", "model"),
  62. common_params,
  63. )
  64. @pytest.mark.parametrize(
  65. "pm25",
  66. [150],
  67. )
  68. async def test_status_from_proceess_adv(rawAdvData, model, pm25):
  69. device = create_device_for_command_testing(rawAdvData, model, {"pm25": pm25})
  70. assert device.get_current_percentage() == 100
  71. assert device.is_on() is True
  72. assert device.get_current_aqi_level() == "excellent"
  73. assert device.get_current_mode() == "level_3"
  74. assert device.get_current_pm25() == 150
  75. @pytest.mark.asyncio
  76. @pytest.mark.parametrize(
  77. ("rawAdvData", "model"),
  78. common_params,
  79. )
  80. async def test_get_basic_info_returns_none_when_no_data(rawAdvData, model):
  81. device = create_device_for_command_testing(rawAdvData, model)
  82. device._get_basic_info = AsyncMock(return_value=None)
  83. assert await device.get_basic_info() is None
  84. @pytest.mark.asyncio
  85. @pytest.mark.parametrize(
  86. ("rawAdvData", "model"),
  87. common_params,
  88. )
  89. @pytest.mark.parametrize(
  90. "mode", ["level_1", "level_2", "level_3", "auto", "pet", "sleep"]
  91. )
  92. async def test_set_preset_mode(rawAdvData, model, mode):
  93. device = create_device_for_command_testing(rawAdvData, model, {"mode": mode})
  94. await device.set_preset_mode(mode)
  95. assert device.get_current_mode() == mode
  96. @pytest.mark.asyncio
  97. @pytest.mark.parametrize(
  98. ("rawAdvData", "model"),
  99. common_params,
  100. )
  101. async def test_turn_on(rawAdvData, model):
  102. device = create_device_for_command_testing(rawAdvData, model, {"isOn": True})
  103. await device.turn_on()
  104. assert device.is_on() is True
  105. @pytest.mark.asyncio
  106. @pytest.mark.parametrize(
  107. ("rawAdvData", "model"),
  108. common_params,
  109. )
  110. async def test_turn_off(rawAdvData, model):
  111. device = create_device_for_command_testing(rawAdvData, model, {"isOn": False})
  112. await device.turn_off()
  113. assert device.is_on() is False
  114. @pytest.mark.asyncio
  115. @pytest.mark.parametrize(
  116. ("rawAdvData", "model"),
  117. common_params,
  118. )
  119. @pytest.mark.parametrize(
  120. ("response", "expected"),
  121. [
  122. (b"\x00", None),
  123. (b"\x07", None),
  124. (b"\x01\x02\x03", b"\x01\x02\x03"),
  125. ],
  126. )
  127. async def test__get_basic_info(rawAdvData, model, response, expected):
  128. device = create_device_for_command_testing(rawAdvData, model)
  129. device._send_command = AsyncMock(return_value=response)
  130. result = await device._get_basic_info()
  131. assert result == expected
  132. @pytest.mark.asyncio
  133. @pytest.mark.parametrize(
  134. ("rawAdvData", "model"),
  135. common_params,
  136. )
  137. @pytest.mark.parametrize(
  138. ("basic_info", "result"),
  139. [
  140. (
  141. bytearray(
  142. b"\x01\xa7\xe9\x8c\x08\x00\xb2\x01\x96\x00\x00\x00\xf0\x00\x00\x17"
  143. ),
  144. [True, 2, "level_2", True, False, "excellent", 50, 240, 2.3],
  145. ),
  146. (
  147. bytearray(
  148. b"\x01\xa8\xec\x8c\x08\x00\xb2\x01\x96\x00\x00\x00\xf0\x00\x00\x17"
  149. ),
  150. [True, 2, "sleep", True, False, "excellent", 50, 240, 2.3],
  151. ),
  152. ],
  153. )
  154. async def test_get_basic_info(rawAdvData, model, basic_info, result):
  155. device = create_device_for_command_testing(rawAdvData, model)
  156. async def mock_get_basic_info():
  157. return basic_info
  158. device._get_basic_info = AsyncMock(side_effect=mock_get_basic_info)
  159. info = await device.get_basic_info()
  160. assert info["isOn"] == result[0]
  161. assert info["version_info"] == result[1]
  162. assert info["mode"] == result[2]
  163. assert info["isAqiValid"] == result[3]
  164. assert info["child_lock"] == result[4]
  165. assert info["aqi_level"] == result[5]
  166. assert info["speed"] == result[6]
  167. assert info["pm25"] == result[7]
  168. assert info["firmware"] == result[8]
  169. @pytest.mark.asyncio
  170. @patch.object(SwitchbotEncryptedDevice, "verify_encryption_key", new_callable=AsyncMock)
  171. async def test_verify_encryption_key(mock_parent_verify):
  172. ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
  173. key_id = "ff"
  174. encryption_key = "ffffffffffffffffffffffffffffffff"
  175. mock_parent_verify.return_value = True
  176. result = await air_purifier.SwitchbotAirPurifier.verify_encryption_key(
  177. device=ble_device,
  178. key_id=key_id,
  179. encryption_key=encryption_key,
  180. )
  181. mock_parent_verify.assert_awaited_once_with(
  182. ble_device,
  183. key_id,
  184. encryption_key,
  185. SwitchbotModel.AIR_PURIFIER,
  186. )
  187. assert result is True
  188. def test_get_modes():
  189. assert AirPurifierMode.get_modes() == [
  190. "level_1",
  191. "level_2",
  192. "level_3",
  193. "auto",
  194. "pet",
  195. "sleep",
  196. ]