test_bulb.py 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  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 bulb
  6. from switchbot.devices.device import SwitchbotOperationError
  7. from .test_adv_parser import generate_ble_device
  8. def create_device_for_command_testing(init_data: dict | None = None):
  9. ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
  10. device = bulb.SwitchbotBulb(ble_device)
  11. device.update_from_advertisement(make_advertisement_data(ble_device, init_data))
  12. device._send_command = AsyncMock()
  13. device._check_command_result = MagicMock()
  14. device.update = AsyncMock()
  15. return device
  16. def make_advertisement_data(ble_device: BLEDevice, init_data: dict | None = None):
  17. """Set advertisement data with defaults."""
  18. if init_data is None:
  19. init_data = {}
  20. return SwitchBotAdvertisement(
  21. address="aa:bb:cc:dd:ee:ff",
  22. data={
  23. "rawAdvData": b"u\x00d",
  24. "data": {
  25. "brightness": 1,
  26. "color_mode": 2,
  27. "delay": False,
  28. "isOn": True,
  29. "loop_index": 0,
  30. "preset": False,
  31. "sequence_number": 2,
  32. "speed": 0,
  33. }
  34. | init_data,
  35. "isEncrypted": False,
  36. "model": "u",
  37. "modelFriendlyName": "Color Bulb",
  38. "modelName": SwitchbotModel.COLOR_BULB,
  39. },
  40. device=ble_device,
  41. rssi=-80,
  42. active=True,
  43. )
  44. @pytest.mark.asyncio
  45. async def test_default_info():
  46. """Test default initialization of the color bulb."""
  47. device = create_device_for_command_testing()
  48. assert device.rgb is None
  49. device._state = {"r": 30, "g": 0, "b": 0, "cw": 3200}
  50. assert device.is_on() is True
  51. assert device.on is True
  52. assert device.color_mode == bulb.BulbColorMode.RGB
  53. assert device.color_modes == {bulb.BulbColorMode.RGB, bulb.BulbColorMode.COLOR_TEMP}
  54. assert device.rgb == (30, 0, 0)
  55. assert device.color_temp == 3200
  56. assert device.brightness == 1
  57. assert device.min_temp == 2700
  58. assert device.max_temp == 6500
  59. assert device.get_effect_list == list(bulb.EFFECT_DICT.keys())
  60. @pytest.mark.asyncio
  61. @pytest.mark.parametrize(
  62. ("basic_info", "version_info"), [(True, False), (False, True), (False, False)]
  63. )
  64. async def test_get_basic_info_returns_none(basic_info, version_info):
  65. device = create_device_for_command_testing()
  66. async def mock_get_basic_info(arg):
  67. if arg == bulb.DEVICE_GET_BASIC_SETTINGS_KEY:
  68. return basic_info
  69. if arg == bulb.DEVICE_GET_VERSION_KEY:
  70. return version_info
  71. return None
  72. device._get_basic_info = AsyncMock(side_effect=mock_get_basic_info)
  73. assert await device.get_basic_info() is None
  74. @pytest.mark.asyncio
  75. @pytest.mark.parametrize(
  76. ("info_data", "result"),
  77. [
  78. (
  79. {
  80. "basic_info": b"\x01\x80\x01\xff\x91\x96\x00\x00\xff\xff\x02",
  81. "version_info": b"\x01\x01\x11",
  82. },
  83. [True, 1, 255, 145, 150, 0, 2, 1.7],
  84. ),
  85. (
  86. {
  87. "basic_info": b"\x01\x80;\x00\x00\x00\x0c\x99\xff\xff\x01",
  88. "version_info": b"\x01\x01\x11",
  89. },
  90. [True, 59, 0, 0, 0, 3225, 1, 1.7],
  91. ),
  92. (
  93. {
  94. "basic_info": b"\x01\x80\t!7\xff\x00\x00\xff\xff\x02",
  95. "version_info": b"\x01\x01\x11",
  96. },
  97. [True, 9, 33, 55, 255, 0, 2, 1.7],
  98. ),
  99. ],
  100. )
  101. async def test_get_basic_info(info_data, result):
  102. """Test getting basic info from the color bulb."""
  103. device = create_device_for_command_testing()
  104. async def mock_get_basic_info(args: str) -> list[int] | None:
  105. if args == bulb.DEVICE_GET_BASIC_SETTINGS_KEY:
  106. return info_data["basic_info"]
  107. if args == bulb.DEVICE_GET_VERSION_KEY:
  108. return info_data["version_info"]
  109. return None
  110. device._get_basic_info = AsyncMock(side_effect=mock_get_basic_info)
  111. info = await device.get_basic_info()
  112. assert info["isOn"] is result[0]
  113. assert info["brightness"] == result[1]
  114. assert info["r"] == result[2]
  115. assert info["g"] == result[3]
  116. assert info["b"] == result[4]
  117. assert info["cw"] == result[5]
  118. assert info["color_mode"] == result[6]
  119. assert info["firmware"] == result[7]
  120. @pytest.mark.asyncio
  121. async def test_set_color_temp():
  122. """Test setting color temperature."""
  123. device = create_device_for_command_testing()
  124. await device.set_color_temp(50, 3000)
  125. device._send_command.assert_called_with(f"{bulb.CW_BRIGHTNESS_KEY}320BB8")
  126. @pytest.mark.asyncio
  127. async def test_turn_on():
  128. """Test turning on the color bulb."""
  129. device = create_device_for_command_testing({"isOn": True})
  130. await device.turn_on()
  131. device._send_command.assert_called_with(bulb.BULB_ON_KEY)
  132. assert device.is_on() is True
  133. @pytest.mark.asyncio
  134. async def test_turn_off():
  135. """Test turning off the color bulb."""
  136. device = create_device_for_command_testing({"isOn": False})
  137. await device.turn_off()
  138. device._send_command.assert_called_with(bulb.BULB_OFF_KEY)
  139. assert device.is_on() is False
  140. @pytest.mark.asyncio
  141. async def test_set_brightness():
  142. """Test setting brightness."""
  143. device = create_device_for_command_testing()
  144. await device.set_brightness(75)
  145. device._send_command.assert_called_with(f"{bulb.BRIGHTNESS_KEY}4B")
  146. @pytest.mark.asyncio
  147. async def test_set_rgb():
  148. """Test setting RGB values."""
  149. device = create_device_for_command_testing()
  150. await device.set_rgb(100, 255, 128, 64)
  151. device._send_command.assert_called_with(f"{bulb.RGB_BRIGHTNESS_KEY}64FF8040")
  152. @pytest.mark.asyncio
  153. async def test_set_effect_with_invalid_effect():
  154. """Test setting an invalid effect."""
  155. device = create_device_for_command_testing()
  156. with pytest.raises(
  157. SwitchbotOperationError, match="Effect invalid_effect not supported"
  158. ):
  159. await device.set_effect("invalid_effect")
  160. @pytest.mark.asyncio
  161. async def test_set_effect_with_valid_effect():
  162. """Test setting a valid effect."""
  163. device = create_device_for_command_testing()
  164. await device.set_effect("Colorful")
  165. device._send_command.assert_called_with(bulb.EFFECT_DICT["Colorful"])
  166. assert device.get_effect() == "Colorful"