test_strip_light.py 8.9 KB

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