test_relay_switch.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592
  1. from unittest.mock import AsyncMock, MagicMock
  2. import pytest
  3. from bleak.backends.device import BLEDevice
  4. from switchbot import SwitchBotAdvertisement, SwitchbotModel, SwitchbotOutdoorPlug
  5. from switchbot.devices import relay_switch
  6. from switchbot.devices.device import _merge_data as merge_data
  7. from .test_adv_parser import generate_ble_device
  8. common_params = [
  9. (b";\x00\x00\x00", SwitchbotModel.RELAY_SWITCH_1),
  10. (b"<\x00\x00\x00", SwitchbotModel.RELAY_SWITCH_1PM),
  11. (b">\x00\x00\x00", SwitchbotModel.GARAGE_DOOR_OPENER),
  12. (b"?\x00\x00\x00", SwitchbotModel.PLUG_MINI_EU),
  13. (b"\x00\x00\x00\x00\x12\x43\xc8\x15", SwitchbotModel.PLUG_MINI_IT),
  14. (b"\x00\x00\x00\x00\x12\x18\xd0\x15", SwitchbotModel.OUTDOOR_PLUG_EU),
  15. ]
  16. @pytest.fixture
  17. def common_parametrize_2pm():
  18. """Provide common test data."""
  19. return {
  20. "rawAdvData": b"\x00\x00\x00\x00\x00\x00",
  21. "model": SwitchbotModel.RELAY_SWITCH_2PM,
  22. }
  23. def create_device_for_command_testing(
  24. rawAdvData: bytes, model: str, init_data: dict | None = None
  25. ):
  26. """Create a device for command testing."""
  27. ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
  28. if model == SwitchbotModel.GARAGE_DOOR_OPENER:
  29. device_class = relay_switch.SwitchbotGarageDoorOpener
  30. elif model == SwitchbotModel.RELAY_SWITCH_2PM:
  31. device_class = relay_switch.SwitchbotRelaySwitch2PM
  32. elif model == SwitchbotModel.OUTDOOR_PLUG_EU:
  33. device_class = SwitchbotOutdoorPlug
  34. else:
  35. device_class = relay_switch.SwitchbotRelaySwitch
  36. device = device_class(
  37. ble_device, "ff", "ffffffffffffffffffffffffffffffff", model=model
  38. )
  39. device.update_from_advertisement(
  40. make_advertisement_data(ble_device, rawAdvData, model, init_data)
  41. )
  42. device._send_command = AsyncMock()
  43. device._check_command_result = MagicMock()
  44. device.update = AsyncMock()
  45. return device
  46. def make_advertisement_data(
  47. ble_device: BLEDevice, rawAdvData: bytes, model: str, init_data: dict | None = None
  48. ):
  49. """Set advertisement data with defaults."""
  50. if init_data is None:
  51. init_data = {}
  52. if model == SwitchbotModel.RELAY_SWITCH_2PM:
  53. return SwitchBotAdvertisement(
  54. address="aa:bb:cc:dd:ee:ff",
  55. data={
  56. "rawAdvData": rawAdvData,
  57. "data": {
  58. 1: {
  59. "switchMode": True,
  60. "sequence_number": 99,
  61. "isOn": True,
  62. },
  63. 2: {
  64. "switchMode": True,
  65. "sequence_number": 99,
  66. "isOn": False,
  67. },
  68. }
  69. | init_data,
  70. "isEncrypted": False,
  71. },
  72. device=ble_device,
  73. rssi=-80,
  74. active=True,
  75. )
  76. if model == SwitchbotModel.GARAGE_DOOR_OPENER:
  77. return SwitchBotAdvertisement(
  78. address="aa:bb:cc:dd:ee:ff",
  79. data={
  80. "rawAdvData": rawAdvData,
  81. "data": {
  82. "switchMode": True,
  83. "sequence_number": 96,
  84. "isOn": True,
  85. "door_open": False,
  86. }
  87. | init_data,
  88. "isEncrypted": False,
  89. },
  90. device=ble_device,
  91. rssi=-80,
  92. active=True,
  93. )
  94. return SwitchBotAdvertisement(
  95. address="aa:bb:cc:dd:ee:ff",
  96. data={
  97. "rawAdvData": rawAdvData,
  98. "data": {
  99. "switchMode": True,
  100. "sequence_number": 96,
  101. "isOn": True,
  102. }
  103. | init_data,
  104. "isEncrypted": False,
  105. },
  106. device=ble_device,
  107. rssi=-80,
  108. active=True,
  109. )
  110. @pytest.mark.asyncio
  111. @pytest.mark.parametrize(
  112. "init_data",
  113. [
  114. {1: {"isOn": True}, 2: {"isOn": True}},
  115. ],
  116. )
  117. async def test_turn_on_2PM(common_parametrize_2pm, init_data):
  118. """Test turn on command."""
  119. device = create_device_for_command_testing(
  120. common_parametrize_2pm["rawAdvData"], common_parametrize_2pm["model"], init_data
  121. )
  122. await device.turn_on(1)
  123. device._send_command.assert_called_with(
  124. relay_switch.MULTI_CHANNEL_COMMANDS_TURN_ON[common_parametrize_2pm["model"]][1]
  125. )
  126. assert device.is_on(1) is True
  127. await device.turn_on(2)
  128. device._send_command.assert_called_with(
  129. relay_switch.MULTI_CHANNEL_COMMANDS_TURN_ON[common_parametrize_2pm["model"]][2]
  130. )
  131. assert device.is_on(2) is True
  132. @pytest.mark.asyncio
  133. @pytest.mark.parametrize(
  134. "init_data",
  135. [
  136. {1: {"isOn": False}, 2: {"isOn": False}},
  137. ],
  138. )
  139. async def test_turn_off_2PM(common_parametrize_2pm, init_data):
  140. """Test turn off command."""
  141. device = create_device_for_command_testing(
  142. common_parametrize_2pm["rawAdvData"], common_parametrize_2pm["model"], init_data
  143. )
  144. await device.turn_off(1)
  145. device._send_command.assert_called_with(
  146. relay_switch.MULTI_CHANNEL_COMMANDS_TURN_OFF[common_parametrize_2pm["model"]][1]
  147. )
  148. assert device.is_on(1) is False
  149. await device.turn_off(2)
  150. device._send_command.assert_called_with(
  151. relay_switch.MULTI_CHANNEL_COMMANDS_TURN_OFF[common_parametrize_2pm["model"]][2]
  152. )
  153. assert device.is_on(2) is False
  154. @pytest.mark.asyncio
  155. async def test_turn_toggle_2PM(common_parametrize_2pm):
  156. """Test toggle command."""
  157. device = create_device_for_command_testing(
  158. common_parametrize_2pm["rawAdvData"], common_parametrize_2pm["model"]
  159. )
  160. await device.async_toggle(1)
  161. device._send_command.assert_called_with(
  162. relay_switch.MULTI_CHANNEL_COMMANDS_TOGGLE[common_parametrize_2pm["model"]][1]
  163. )
  164. assert device.is_on(1) is True
  165. await device.async_toggle(2)
  166. device._send_command.assert_called_with(
  167. relay_switch.MULTI_CHANNEL_COMMANDS_TOGGLE[common_parametrize_2pm["model"]][2]
  168. )
  169. assert device.is_on(2) is False
  170. @pytest.mark.asyncio
  171. async def test_get_switch_mode_2PM(common_parametrize_2pm):
  172. """Test get switch mode."""
  173. device = create_device_for_command_testing(
  174. common_parametrize_2pm["rawAdvData"], common_parametrize_2pm["model"]
  175. )
  176. assert device.switch_mode(1) is True
  177. assert device.switch_mode(2) is True
  178. @pytest.mark.asyncio
  179. @pytest.mark.parametrize(
  180. ("info_data", "result"),
  181. [
  182. (
  183. {
  184. "basic_info": b"\x01\x98A\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10",
  185. "channel1_info": b"\x01\x00\x00\x00\x00\x00\x00\x02\x99\x00\xe9\x00\x03\x00\x00",
  186. "channel2_info": b"\x01\x00\x055\x00'<\x02\x9f\x00\xe9\x01,\x00F",
  187. },
  188. [False, 0, 0, 0, 0, True, 0.02, 23, 0.3, 7.0],
  189. ),
  190. (
  191. {
  192. "basic_info": b"\x01\x9e\x81\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10",
  193. "channel1_info": b"\x01\x00\x00\x00\x00\x00\x00\x02\x99\x00\xe9\x00\x03\x00\x00",
  194. "channel2_info": b"\x01\x00\x05\xbc\x00'<\x02\xb1\x00\xea\x01-\x00F",
  195. },
  196. [True, 0, 23, 0.1, 0.0, False, 0.02, 0, 0, 0],
  197. ),
  198. ],
  199. )
  200. async def test_get_basic_info_2PM(common_parametrize_2pm, info_data, result):
  201. """Test get_basic_info for 2PM devices."""
  202. device = create_device_for_command_testing(
  203. common_parametrize_2pm["rawAdvData"], common_parametrize_2pm["model"]
  204. )
  205. assert device.channel == 2
  206. device.get_current_time_and_start_time = MagicMock(
  207. return_value=("683074d6", "682fba80")
  208. )
  209. async def mock_get_basic_info(arg):
  210. if arg == relay_switch.COMMAND_GET_BASIC_INFO:
  211. return info_data["basic_info"]
  212. if arg == relay_switch.COMMAND_GET_CHANNEL1_INFO.format("683074d6", "682fba80"):
  213. return info_data["channel1_info"]
  214. if arg == relay_switch.COMMAND_GET_CHANNEL2_INFO.format("683074d6", "682fba80"):
  215. return info_data["channel2_info"]
  216. return None
  217. device._get_basic_info = AsyncMock(side_effect=mock_get_basic_info)
  218. info = await device.get_basic_info()
  219. assert info is not None
  220. assert 1 in info
  221. assert 2 in info
  222. assert info[1]["isOn"] == result[0]
  223. assert info[1]["energy"] == result[1]
  224. assert info[1]["voltage"] == result[2]
  225. assert info[1]["current"] == result[3]
  226. assert info[1]["power"] == result[4]
  227. assert info[2]["isOn"] == result[5]
  228. assert info[2]["energy"] == result[6]
  229. assert info[2]["voltage"] == result[7]
  230. assert info[2]["current"] == result[8]
  231. assert info[2]["power"] == result[9]
  232. @pytest.mark.asyncio
  233. @pytest.mark.parametrize(
  234. "info_data",
  235. [
  236. {
  237. "basic_info": None,
  238. "channel1_info": b"\x01\x00\x00\x00\x00\x00\x00\x02\x99\x00\xe9\x00\x03\x00\x00",
  239. "channel2_info": b"\x01\x00\x055\x00'<\x02\x9f\x00\xe9\x01,\x00F",
  240. },
  241. {
  242. "basic_info": b"\x01\x98A\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10",
  243. "channel1_info": None,
  244. "channel2_info": b"\x01\x00\x055\x00'<\x02\x9f\x00\xe9\x01,\x00F",
  245. },
  246. {
  247. "basic_info": b"\x01\x98A\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10",
  248. "channel1_info": b"\x01\x00\x00\x00\x00\x00\x00\x02\x99\x00\xe9\x00\x03\x00\x00",
  249. "channel2_info": None,
  250. },
  251. ],
  252. )
  253. async def test_basic_info_exceptions_2PM(common_parametrize_2pm, info_data):
  254. """Test get_basic_info exceptions."""
  255. device = create_device_for_command_testing(
  256. common_parametrize_2pm["rawAdvData"], common_parametrize_2pm["model"]
  257. )
  258. device.get_current_time_and_start_time = MagicMock(
  259. return_value=("683074d6", "682fba80")
  260. )
  261. async def mock_get_basic_info(arg):
  262. if arg == relay_switch.COMMAND_GET_BASIC_INFO:
  263. return info_data["basic_info"]
  264. if arg == relay_switch.COMMAND_GET_CHANNEL1_INFO.format("683074d6", "682fba80"):
  265. return info_data["channel1_info"]
  266. if arg == relay_switch.COMMAND_GET_CHANNEL2_INFO.format("683074d6", "682fba80"):
  267. return info_data["channel2_info"]
  268. return None
  269. device._get_basic_info = AsyncMock(side_effect=mock_get_basic_info)
  270. info = await device.get_basic_info()
  271. assert info is None
  272. @pytest.mark.asyncio
  273. @pytest.mark.parametrize(
  274. "info_data",
  275. [
  276. # Truncated basic_info (single byte from the wire — repro of issue #369)
  277. {
  278. "basic_info": b"\x02",
  279. "channel1_info": b"\x01\x00\x00\x00\x00\x00\x00\x02\x99\x00\xe9\x00\x03\x00\x00",
  280. "channel2_info": b"\x01\x00\x055\x00'<\x02\x9f\x00\xe9\x01,\x00F",
  281. },
  282. # Basic_info just below the 17-byte minimum
  283. {
  284. "basic_info": b"\x01\x98A\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
  285. "channel1_info": b"\x01\x00\x00\x00\x00\x00\x00\x02\x99\x00\xe9\x00\x03\x00\x00",
  286. "channel2_info": b"\x01\x00\x055\x00'<\x02\x9f\x00\xe9\x01,\x00F",
  287. },
  288. # Truncated channel1_info (single byte — repro of issue #369 user_data crash)
  289. {
  290. "basic_info": b"\x01\x98A\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10",
  291. "channel1_info": b"\x01",
  292. "channel2_info": b"\x01\x00\x055\x00'<\x02\x9f\x00\xe9\x01,\x00F",
  293. },
  294. # Channel1_info just below the 15-byte minimum
  295. {
  296. "basic_info": b"\x01\x98A\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10",
  297. "channel1_info": b"\x01\x00\x00\x00\x00\x00\x00\x02\x99\x00\xe9\x00\x03\x00",
  298. "channel2_info": b"\x01\x00\x055\x00'<\x02\x9f\x00\xe9\x01,\x00F",
  299. },
  300. # Truncated channel2_info
  301. {
  302. "basic_info": b"\x01\x98A\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10",
  303. "channel1_info": b"\x01\x00\x00\x00\x00\x00\x00\x02\x99\x00\xe9\x00\x03\x00\x00",
  304. "channel2_info": b"\x01",
  305. },
  306. ],
  307. )
  308. async def test_get_basic_info_2PM_short_response(common_parametrize_2pm, info_data):
  309. """
  310. Truncated BLE responses must yield None instead of crashing.
  311. Regression coverage for issue #369: a single-byte payload reaches
  312. `_parse_common_data`/`_parse_user_data` and raises IndexError/ValueError.
  313. """
  314. device = create_device_for_command_testing(
  315. common_parametrize_2pm["rawAdvData"], common_parametrize_2pm["model"]
  316. )
  317. device.get_current_time_and_start_time = MagicMock(
  318. return_value=("683074d6", "682fba80")
  319. )
  320. async def mock_get_basic_info(arg):
  321. if arg == relay_switch.COMMAND_GET_BASIC_INFO:
  322. return info_data["basic_info"]
  323. if arg == relay_switch.COMMAND_GET_CHANNEL1_INFO.format("683074d6", "682fba80"):
  324. return info_data["channel1_info"]
  325. if arg == relay_switch.COMMAND_GET_CHANNEL2_INFO.format("683074d6", "682fba80"):
  326. return info_data["channel2_info"]
  327. return None
  328. device._get_basic_info = AsyncMock(side_effect=mock_get_basic_info)
  329. info = await device.get_basic_info()
  330. assert info is None
  331. @pytest.mark.asyncio
  332. @pytest.mark.parametrize(
  333. ("rawAdvData", "model"),
  334. common_params,
  335. )
  336. @pytest.mark.parametrize(
  337. "info_data",
  338. [
  339. {
  340. "basic_info": b"\x02",
  341. "channel1_info": b"\x01\x00\x00\x00\x00\x00\x00\x02\x99\x00\xe9\x00\x03\x00\x00",
  342. },
  343. {
  344. "basic_info": b"\x01\x98A\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10",
  345. "channel1_info": b"\x01",
  346. },
  347. ],
  348. )
  349. async def test_get_basic_info_short_response(rawAdvData, model, info_data):
  350. """Truncated BLE responses on single-channel relay/garage/plug must yield None."""
  351. device = create_device_for_command_testing(rawAdvData, model)
  352. device.get_current_time_and_start_time = MagicMock(
  353. return_value=("683074d6", "682fba80")
  354. )
  355. async def mock_get_basic_info(arg):
  356. if arg == relay_switch.COMMAND_GET_BASIC_INFO:
  357. return info_data["basic_info"]
  358. if arg == relay_switch.COMMAND_GET_CHANNEL1_INFO.format("683074d6", "682fba80"):
  359. return info_data["channel1_info"]
  360. return None
  361. device._get_basic_info = AsyncMock(side_effect=mock_get_basic_info)
  362. info = await device.get_basic_info()
  363. assert info is None
  364. @pytest.mark.asyncio
  365. async def test_get_parsed_data_2PM(common_parametrize_2pm):
  366. """Test get_parsed_data for 2PM devices."""
  367. device = create_device_for_command_testing(
  368. common_parametrize_2pm["rawAdvData"], common_parametrize_2pm["model"]
  369. )
  370. info = device.get_parsed_data(1)
  371. assert info["isOn"] is True
  372. info = device.get_parsed_data(2)
  373. assert info["isOn"] is False
  374. @pytest.mark.asyncio
  375. @pytest.mark.parametrize(
  376. ("rawAdvData", "model"),
  377. common_params,
  378. )
  379. async def test_turn_on(rawAdvData, model):
  380. """Test turn on command."""
  381. device = create_device_for_command_testing(rawAdvData, model)
  382. await device.turn_on()
  383. device._send_command.assert_awaited_once_with(device._turn_on_command)
  384. assert device.is_on() is True
  385. @pytest.mark.asyncio
  386. @pytest.mark.parametrize(
  387. ("rawAdvData", "model"),
  388. common_params,
  389. )
  390. async def test_turn_off(rawAdvData, model):
  391. """Test turn off command."""
  392. device = create_device_for_command_testing(rawAdvData, model, {"isOn": False})
  393. await device.turn_off()
  394. device._send_command.assert_awaited_once_with(device._turn_off_command)
  395. assert device.is_on() is False
  396. @pytest.mark.asyncio
  397. @pytest.mark.parametrize(
  398. ("rawAdvData", "model"),
  399. common_params,
  400. )
  401. async def test_toggle(rawAdvData, model):
  402. """Test toggle command."""
  403. device = create_device_for_command_testing(rawAdvData, model)
  404. await device.async_toggle()
  405. device._send_command.assert_awaited_once_with(relay_switch.COMMAND_TOGGLE)
  406. assert device.is_on() is True
  407. @pytest.mark.asyncio
  408. @pytest.mark.parametrize(
  409. ("rawAdvData", "model", "info_data"),
  410. [
  411. (
  412. b">\x00\x00\x00",
  413. SwitchbotModel.GARAGE_DOOR_OPENER,
  414. {
  415. "basic_info": b"\x01>\x80\x0c\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x10",
  416. "channel1_info": b"\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
  417. },
  418. )
  419. ],
  420. )
  421. async def test_get_basic_info_garage_door_opener(rawAdvData, model, info_data):
  422. """Test get_basic_info for garage door opener."""
  423. device = create_device_for_command_testing(rawAdvData, model)
  424. device.get_current_time_and_start_time = MagicMock(
  425. return_value=("683074d6", "682fba80")
  426. )
  427. async def mock_get_basic_info(arg):
  428. if arg == relay_switch.COMMAND_GET_BASIC_INFO:
  429. return info_data["basic_info"]
  430. if arg == relay_switch.COMMAND_GET_CHANNEL1_INFO.format("683074d6", "682fba80"):
  431. return info_data["channel1_info"]
  432. return None
  433. device._get_basic_info = AsyncMock(side_effect=mock_get_basic_info)
  434. info = await device.get_basic_info()
  435. assert info is not None
  436. assert info["isOn"] is True
  437. assert info["door_open"] is True
  438. @pytest.mark.parametrize(
  439. ("dev_cls", "expected_model"),
  440. [
  441. (relay_switch.SwitchbotRelaySwitch, SwitchbotModel.RELAY_SWITCH_1PM),
  442. (relay_switch.SwitchbotGarageDoorOpener, SwitchbotModel.GARAGE_DOOR_OPENER),
  443. (relay_switch.SwitchbotRelaySwitch2PM, SwitchbotModel.RELAY_SWITCH_2PM),
  444. ],
  445. )
  446. def test_default_model_classvar(dev_cls, expected_model):
  447. ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
  448. device = dev_cls(ble_device, "ff", "ffffffffffffffffffffffffffffffff")
  449. assert device._model == expected_model
  450. @pytest.mark.parametrize(
  451. ("old_data", "new_data", "expected_result"),
  452. [
  453. (
  454. {"isOn": True, "sequence_number": 1},
  455. {"isOn": False},
  456. {"isOn": False, "sequence_number": 1},
  457. ),
  458. (
  459. {
  460. 1: {"current": 0, "voltage": 220, "power": 0},
  461. 2: {"current": 1, "voltage": 0, "power": 10},
  462. },
  463. {1: {"current": 1, "power": 10}, 2: {"current": 0, "voltage": 220}},
  464. {
  465. 1: {"current": 1, "voltage": 220, "power": 10},
  466. 2: {"current": 0, "voltage": 220, "power": 10},
  467. },
  468. ),
  469. ],
  470. )
  471. def test_merge_data(old_data, new_data, expected_result):
  472. """Test merging of data dictionaries."""
  473. result = merge_data(old_data, new_data)
  474. assert result == expected_result
  475. @pytest.mark.asyncio
  476. async def test_garage_door_opener_open():
  477. """Test open the garage door."""
  478. device = create_device_for_command_testing(
  479. b">\x00\x00\x00", SwitchbotModel.GARAGE_DOOR_OPENER
  480. )
  481. await device.open()
  482. device._send_command.assert_awaited_once_with(device._open_command)
  483. @pytest.mark.asyncio
  484. async def test_garage_door_opener_close():
  485. """Test close the garage door."""
  486. device = create_device_for_command_testing(
  487. b">\x00\x00\x00", SwitchbotModel.GARAGE_DOOR_OPENER
  488. )
  489. await device.close()
  490. device._send_command.assert_awaited_once_with(device._close_command)
  491. @pytest.mark.parametrize(
  492. "door_open",
  493. [
  494. True,
  495. False,
  496. ],
  497. )
  498. @pytest.mark.asyncio
  499. async def test_garage_door_opener_door_open(door_open):
  500. """Test get garage door state."""
  501. device = create_device_for_command_testing(
  502. b">\x00\x00\x00", SwitchbotModel.GARAGE_DOOR_OPENER, {"door_open": door_open}
  503. )
  504. assert device.door_open() is door_open
  505. @pytest.mark.asyncio
  506. async def test_press():
  507. """Test the press command for garage door opener."""
  508. device = create_device_for_command_testing(
  509. b">\x00\x00\x00", SwitchbotModel.GARAGE_DOOR_OPENER
  510. )
  511. await device.press()
  512. device._send_command.assert_awaited_once_with(device._press_command)