test_relay_switch.py 19 KB

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