test_strip_light.py 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. from unittest.mock import AsyncMock, MagicMock, patch
  2. import pytest
  3. from bleak.backends.device import BLEDevice
  4. from switchbot import SwitchBotAdvertisement, SwitchbotModel
  5. from switchbot.const.light import ColorMode
  6. from switchbot.devices import light_strip
  7. from switchbot.devices.base_light import SwitchbotBaseLight
  8. from switchbot.devices.device import SwitchbotEncryptedDevice, SwitchbotOperationError
  9. from .test_adv_parser import generate_ble_device
  10. def create_device_for_command_testing(init_data: dict | None = None):
  11. ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
  12. device = light_strip.SwitchbotStripLight3(
  13. ble_device, "ff", "ffffffffffffffffffffffffffffffff"
  14. )
  15. device.update_from_advertisement(make_advertisement_data(ble_device, init_data))
  16. device._send_command = AsyncMock()
  17. device._check_command_result = MagicMock()
  18. device.update = AsyncMock()
  19. return device
  20. def make_advertisement_data(ble_device: BLEDevice, init_data: dict | None = None):
  21. """Set advertisement data with defaults."""
  22. if init_data is None:
  23. init_data = {}
  24. return SwitchBotAdvertisement(
  25. address="aa:bb:cc:dd:ee:ff",
  26. data={
  27. "rawAdvData": b"\x00\x00\x00\x00\x10\xd0\xb1",
  28. "data": {
  29. "sequence_number": 133,
  30. "isOn": True,
  31. "brightness": 30,
  32. "delay": False,
  33. "network_state": 2,
  34. "color_mode": 2,
  35. "cw": 4753,
  36. }
  37. | init_data,
  38. "isEncrypted": False,
  39. "model": b"\x00\x10\xd0\xb1",
  40. "modelFriendlyName": "Strip Light 3",
  41. "modelName": SwitchbotModel.STRIP_LIGHT_3,
  42. },
  43. device=ble_device,
  44. rssi=-80,
  45. active=True,
  46. )
  47. @pytest.mark.asyncio
  48. async def test_default_info():
  49. """Test default initialization of the strip light."""
  50. device = create_device_for_command_testing()
  51. assert device.rgb is None
  52. device._state = {"r": 30, "g": 0, "b": 0, "cw": 3200}
  53. assert device.is_on() is True
  54. assert device.on is True
  55. assert device.color_mode == ColorMode.RGB
  56. assert device.color_modes == {
  57. ColorMode.RGB,
  58. ColorMode.COLOR_TEMP,
  59. }
  60. assert device.rgb == (30, 0, 0)
  61. assert device.color_temp == 3200
  62. assert device.brightness == 30
  63. assert device.min_temp == 2700
  64. assert device.max_temp == 6500
  65. assert device.get_effect_list == list(light_strip.EFFECT_DICT.keys())
  66. @pytest.mark.asyncio
  67. @pytest.mark.parametrize(
  68. ("basic_info", "version_info"), [(True, False), (False, True), (False, False)]
  69. )
  70. async def test_get_basic_info_returns_none(basic_info, version_info):
  71. device = create_device_for_command_testing()
  72. async def mock_get_basic_info(arg):
  73. if arg == light_strip.STRIP_REQUEST:
  74. return basic_info
  75. if arg == light_strip.DEVICE_GET_VERSION_KEY:
  76. return version_info
  77. return None
  78. device._get_basic_info = AsyncMock(side_effect=mock_get_basic_info)
  79. assert await device.get_basic_info() is None
  80. @pytest.mark.asyncio
  81. @pytest.mark.parametrize(
  82. ("info_data", "result"),
  83. [
  84. (
  85. {
  86. "basic_info": b"\x01\x00<\xff\x00\xd8\x00\x19d\x00\x03",
  87. "version_info": b"\x01\x01\n",
  88. },
  89. [False, 60, 255, 0, 216, 6500, 3, 1.0],
  90. ),
  91. (
  92. {
  93. "basic_info": b"\x01\x80NK\xff:\x00\x19d\xff\x02",
  94. "version_info": b"\x01\x01\n",
  95. },
  96. [True, 78, 75, 255, 58, 6500, 2, 1.0],
  97. ),
  98. (
  99. {
  100. "basic_info": b"\x01\x80$K\xff:\x00\x13\xf9\xff\x06",
  101. "version_info": b"\x01\x01\n",
  102. },
  103. [True, 36, 75, 255, 58, 5113, 6, 1.0],
  104. ),
  105. ],
  106. )
  107. async def test_strip_light_get_basic_info(info_data, result):
  108. """Test getting basic info from the strip light."""
  109. device = create_device_for_command_testing()
  110. async def mock_get_basic_info(args: str) -> list[int] | None:
  111. if args == light_strip.STRIP_REQUEST:
  112. return info_data["basic_info"]
  113. if args == light_strip.DEVICE_GET_VERSION_KEY:
  114. return info_data["version_info"]
  115. return None
  116. device._get_basic_info = AsyncMock(side_effect=mock_get_basic_info)
  117. info = await device.get_basic_info()
  118. assert info["isOn"] is result[0]
  119. assert info["brightness"] == result[1]
  120. assert info["r"] == result[2]
  121. assert info["g"] == result[3]
  122. assert info["b"] == result[4]
  123. assert info["cw"] == result[5]
  124. assert info["color_mode"] == result[6]
  125. assert info["firmware"] == result[7]
  126. @pytest.mark.asyncio
  127. async def test_set_color_temp():
  128. """Test setting color temperature."""
  129. device = create_device_for_command_testing()
  130. await device.set_color_temp(50, 3000)
  131. device._send_command.assert_called_with(f"{light_strip.COLOR_TEMP_KEY}320BB8")
  132. @pytest.mark.asyncio
  133. async def test_turn_on():
  134. """Test turning on the strip light."""
  135. device = create_device_for_command_testing({"isOn": True})
  136. await device.turn_on()
  137. device._send_command.assert_called_with(light_strip.STRIP_ON_KEY)
  138. assert device.is_on() is True
  139. @pytest.mark.asyncio
  140. async def test_turn_off():
  141. """Test turning off the strip light."""
  142. device = create_device_for_command_testing({"isOn": False})
  143. await device.turn_off()
  144. device._send_command.assert_called_with(light_strip.STRIP_OFF_KEY)
  145. assert device.is_on() is False
  146. @pytest.mark.asyncio
  147. async def test_set_brightness():
  148. """Test setting brightness."""
  149. device = create_device_for_command_testing()
  150. await device.set_brightness(75)
  151. device._send_command.assert_called_with(f"{light_strip.BRIGHTNESS_KEY}4B")
  152. @pytest.mark.asyncio
  153. async def test_set_rgb():
  154. """Test setting RGB values."""
  155. device = create_device_for_command_testing()
  156. await device.set_rgb(100, 255, 128, 64)
  157. device._send_command.assert_called_with(f"{light_strip.RGB_BRIGHTNESS_KEY}64FF8040")
  158. @pytest.mark.asyncio
  159. async def test_set_effect_with_invalid_effect():
  160. """Test setting an invalid effect."""
  161. device = create_device_for_command_testing()
  162. with pytest.raises(
  163. SwitchbotOperationError, match="Effect invalid_effect not supported"
  164. ):
  165. await device.set_effect("invalid_effect")
  166. @pytest.mark.asyncio
  167. async def test_set_effect_with_valid_effect():
  168. """Test setting a valid effect."""
  169. device = create_device_for_command_testing()
  170. device._send_multiple_commands = AsyncMock()
  171. await device.set_effect("Christmas")
  172. device._send_multiple_commands.assert_called_with(
  173. light_strip.EFFECT_DICT["Christmas"]
  174. )
  175. assert device.get_effect() == "Christmas"
  176. @pytest.mark.asyncio
  177. @patch.object(SwitchbotEncryptedDevice, "verify_encryption_key", new_callable=AsyncMock)
  178. async def test_verify_encryption_key(mock_parent_verify):
  179. ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
  180. key_id = "ff"
  181. encryption_key = "ffffffffffffffffffffffffffffffff"
  182. mock_parent_verify.return_value = True
  183. result = await light_strip.SwitchbotStripLight3.verify_encryption_key(
  184. device=ble_device,
  185. key_id=key_id,
  186. encryption_key=encryption_key,
  187. )
  188. mock_parent_verify.assert_awaited_once_with(
  189. ble_device,
  190. key_id,
  191. encryption_key,
  192. SwitchbotModel.STRIP_LIGHT_3,
  193. )
  194. assert result is True
  195. def create_strip_light_device(init_data: dict | None = None):
  196. ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
  197. return light_strip.SwitchbotLightStrip(ble_device)
  198. @pytest.mark.asyncio
  199. async def test_strip_light_supported_color_modes():
  200. """Test that the strip light supports the expected color modes."""
  201. device = create_strip_light_device()
  202. assert device.color_modes == {
  203. ColorMode.RGB,
  204. }
  205. @pytest.mark.asyncio
  206. @pytest.mark.parametrize(
  207. ("commands", "results", "final_result"),
  208. [
  209. (("command1", "command2"), [(b"\x01", False), (None, False)], False),
  210. (("command1", "command2"), [(None, False), (b"\x01", True)], True),
  211. (("command1", "command2"), [(b"\x01", True), (b"\x01", False)], True),
  212. ],
  213. )
  214. async def test_send_multiple_commands(commands, results, final_result):
  215. """Test sending multiple commands."""
  216. device = create_device_for_command_testing()
  217. device._send_command = AsyncMock(side_effect=[r[0] for r in results])
  218. device._check_command_result = MagicMock(side_effect=[r[1] for r in results])
  219. result = await device._send_multiple_commands(list(commands))
  220. assert result is final_result
  221. @pytest.mark.asyncio
  222. async def test_unimplemented_color_mode():
  223. class TestDevice(SwitchbotBaseLight):
  224. pass
  225. ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
  226. device = TestDevice(ble_device)
  227. with pytest.raises(NotImplementedError):
  228. _ = device.color_mode