1
0
mirror of https://github.com/home-assistant/core.git synced 2026-05-20 07:20:14 +01:00
Files
core/tests/components/switchbot/test_light.py
T
Retha Runolfsson b5580db910 Add LED control settings for switchbot air purifier (#167144)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2026-05-14 17:07:09 +02:00

549 lines
16 KiB
Python

"""Test the switchbot lights."""
from collections.abc import Callable
from typing import Any
from unittest.mock import AsyncMock, patch
import pytest
from switchbot import SwitchbotOperationError
from homeassistant.components.bluetooth import BluetoothServiceInfoBleak
from homeassistant.components.light import (
ATTR_BRIGHTNESS,
ATTR_COLOR_TEMP_KELVIN,
ATTR_EFFECT,
ATTR_RGB_COLOR,
DOMAIN as LIGHT_DOMAIN,
SERVICE_TURN_OFF,
SERVICE_TURN_ON,
)
from homeassistant.const import ATTR_ENTITY_ID, STATE_ON
from homeassistant.core import HomeAssistant, State
from homeassistant.exceptions import HomeAssistantError
from . import (
AIR_PURIFIER_JP_SERVICE_INFO,
AIR_PURIFIER_TABLE_JP_SERVICE_INFO,
AIR_PURIFIER_TABLE_US_SERVICE_INFO,
AIR_PURIFIER_US_SERVICE_INFO,
BULB_SERVICE_INFO,
CEILING_LIGHT_SERVICE_INFO,
FLOOR_LAMP_SERVICE_INFO,
RGBICWW_FLOOR_LAMP_SERVICE_INFO,
RGBICWW_STRIP_LIGHT_SERVICE_INFO,
STRIP_LIGHT_3_SERVICE_INFO,
WOSTRIP_SERVICE_INFO,
)
from tests.common import MockConfigEntry, mock_restore_cache
from tests.components.bluetooth import inject_bluetooth_service_info
COMMON_PARAMETERS = (
"service",
"service_data",
"mock_method",
"expected_args",
)
TURN_ON_PARAMETERS = (
SERVICE_TURN_ON,
{},
"turn_on",
{},
)
TURN_OFF_PARAMETERS = (
SERVICE_TURN_OFF,
{},
"turn_off",
{},
)
SET_BRIGHTNESS_PARAMETERS = (
SERVICE_TURN_ON,
{ATTR_BRIGHTNESS: 128},
"set_brightness",
(round(128 / 255 * 100),),
)
SET_RGB_PARAMETERS = (
SERVICE_TURN_ON,
{ATTR_BRIGHTNESS: 128, ATTR_RGB_COLOR: (255, 0, 0)},
"set_rgb",
(round(128 / 255 * 100), 255, 0, 0),
)
SET_COLOR_TEMP_PARAMETERS = (
SERVICE_TURN_ON,
{ATTR_BRIGHTNESS: 128, ATTR_COLOR_TEMP_KELVIN: 4000},
"set_color_temp",
(round(128 / 255 * 100), 4000),
)
BULB_PARAMETERS = (
COMMON_PARAMETERS,
[
TURN_ON_PARAMETERS,
TURN_OFF_PARAMETERS,
SET_BRIGHTNESS_PARAMETERS,
SET_RGB_PARAMETERS,
SET_COLOR_TEMP_PARAMETERS,
(
SERVICE_TURN_ON,
{ATTR_EFFECT: "breathing"},
"set_effect",
("breathing",),
),
],
)
CEILING_LIGHT_PARAMETERS = (
COMMON_PARAMETERS,
[
TURN_ON_PARAMETERS,
TURN_OFF_PARAMETERS,
SET_BRIGHTNESS_PARAMETERS,
SET_COLOR_TEMP_PARAMETERS,
],
)
STRIP_LIGHT_PARAMETERS = (
COMMON_PARAMETERS,
[
TURN_ON_PARAMETERS,
TURN_OFF_PARAMETERS,
SET_BRIGHTNESS_PARAMETERS,
SET_RGB_PARAMETERS,
(
SERVICE_TURN_ON,
{ATTR_EFFECT: "halloween"},
"set_effect",
("halloween",),
),
],
)
FLOOR_LAMP_PARAMETERS = (
COMMON_PARAMETERS,
[
TURN_ON_PARAMETERS,
TURN_OFF_PARAMETERS,
SET_BRIGHTNESS_PARAMETERS,
SET_RGB_PARAMETERS,
SET_COLOR_TEMP_PARAMETERS,
(
SERVICE_TURN_ON,
{ATTR_EFFECT: "halloween"},
"set_effect",
("halloween",),
),
],
)
AIR_PURIFIER_LIGHT_PARAMETERS = (
COMMON_PARAMETERS,
[
(
SERVICE_TURN_ON,
{},
"turn_led_on",
(),
),
(
SERVICE_TURN_OFF,
{},
"turn_led_off",
(),
),
SET_BRIGHTNESS_PARAMETERS,
SET_RGB_PARAMETERS,
],
)
@pytest.mark.parametrize(*BULB_PARAMETERS)
async def test_bulb_services(
hass: HomeAssistant,
mock_entry_factory: Callable[[str], MockConfigEntry],
service: str,
service_data: dict,
mock_method: str,
expected_args: Any,
) -> None:
"""Test all SwitchBot bulb services."""
inject_bluetooth_service_info(hass, BULB_SERVICE_INFO)
entry = mock_entry_factory(sensor_type="bulb")
entry.add_to_hass(hass)
entity_id = "light.test_name"
mocked_instance = AsyncMock(return_value=True)
with patch.multiple(
"homeassistant.components.switchbot.light.switchbot.SwitchbotBulb",
**{mock_method: mocked_instance},
update=AsyncMock(return_value=None),
):
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
await hass.services.async_call(
LIGHT_DOMAIN,
service,
{**service_data, ATTR_ENTITY_ID: entity_id},
blocking=True,
)
mocked_instance.assert_awaited_once_with(*expected_args)
@pytest.mark.parametrize(*BULB_PARAMETERS)
async def test_bulb_services_exception(
hass: HomeAssistant,
mock_entry_factory: Callable[[str], MockConfigEntry],
service: str,
service_data: dict,
mock_method: str,
expected_args: Any,
) -> None:
"""Test all SwitchBot bulb services with exception."""
inject_bluetooth_service_info(hass, BULB_SERVICE_INFO)
entry = mock_entry_factory(sensor_type="bulb")
entry.add_to_hass(hass)
entity_id = "light.test_name"
exception = SwitchbotOperationError("Operation failed")
error_message = "An error occurred while performing the action: Operation failed"
with patch.multiple(
"homeassistant.components.switchbot.light.switchbot.SwitchbotBulb",
**{mock_method: AsyncMock(side_effect=exception)},
update=AsyncMock(return_value=None),
):
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
with pytest.raises(HomeAssistantError, match=error_message):
await hass.services.async_call(
LIGHT_DOMAIN,
service,
{**service_data, ATTR_ENTITY_ID: entity_id},
blocking=True,
)
@pytest.mark.parametrize(*CEILING_LIGHT_PARAMETERS)
async def test_ceiling_light_services(
hass: HomeAssistant,
mock_entry_factory: Callable[[str], MockConfigEntry],
service: str,
service_data: dict,
mock_method: str,
expected_args: Any,
) -> None:
"""Test all SwitchBot ceiling light services."""
inject_bluetooth_service_info(hass, CEILING_LIGHT_SERVICE_INFO)
entry = mock_entry_factory(sensor_type="ceiling_light")
entry.add_to_hass(hass)
entity_id = "light.test_name"
mocked_instance = AsyncMock(return_value=True)
with patch.multiple(
"homeassistant.components.switchbot.light.switchbot.SwitchbotCeilingLight",
**{mock_method: mocked_instance},
update=AsyncMock(return_value=None),
):
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
await hass.services.async_call(
LIGHT_DOMAIN,
service,
{**service_data, ATTR_ENTITY_ID: entity_id},
blocking=True,
)
mocked_instance.assert_awaited_once_with(*expected_args)
@pytest.mark.parametrize(*CEILING_LIGHT_PARAMETERS)
async def test_ceiling_light_services_exception(
hass: HomeAssistant,
mock_entry_factory: Callable[[str], MockConfigEntry],
service: str,
service_data: dict,
mock_method: str,
expected_args: Any,
) -> None:
"""Test all SwitchBot ceiling light services with exception."""
inject_bluetooth_service_info(hass, CEILING_LIGHT_SERVICE_INFO)
entry = mock_entry_factory(sensor_type="ceiling_light")
entry.add_to_hass(hass)
entity_id = "light.test_name"
exception = SwitchbotOperationError("Operation failed")
error_message = "An error occurred while performing the action: Operation failed"
with patch.multiple(
"homeassistant.components.switchbot.light.switchbot.SwitchbotCeilingLight",
**{mock_method: AsyncMock(side_effect=exception)},
update=AsyncMock(return_value=None),
):
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
with pytest.raises(HomeAssistantError, match=error_message):
await hass.services.async_call(
LIGHT_DOMAIN,
service,
{**service_data, ATTR_ENTITY_ID: entity_id},
blocking=True,
)
@pytest.mark.parametrize(*STRIP_LIGHT_PARAMETERS)
async def test_strip_light_services(
hass: HomeAssistant,
mock_entry_factory: Callable[[str], MockConfigEntry],
service: str,
service_data: dict,
mock_method: str,
expected_args: Any,
) -> None:
"""Test all SwitchBot strip light services."""
inject_bluetooth_service_info(hass, WOSTRIP_SERVICE_INFO)
entry = mock_entry_factory(sensor_type="light_strip")
entry.add_to_hass(hass)
entity_id = "light.test_name"
mocked_instance = AsyncMock(return_value=True)
with patch.multiple(
"homeassistant.components.switchbot.light.switchbot.SwitchbotLightStrip",
**{mock_method: mocked_instance},
update=AsyncMock(return_value=None),
):
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
await hass.services.async_call(
LIGHT_DOMAIN,
service,
{**service_data, ATTR_ENTITY_ID: entity_id},
blocking=True,
)
mocked_instance.assert_awaited_once_with(*expected_args)
@pytest.mark.parametrize(*STRIP_LIGHT_PARAMETERS)
async def test_strip_light_services_exception(
hass: HomeAssistant,
mock_entry_factory: Callable[[str], MockConfigEntry],
service: str,
service_data: dict,
mock_method: str,
expected_args: Any,
) -> None:
"""Test all SwitchBot strip light services with exception."""
inject_bluetooth_service_info(hass, WOSTRIP_SERVICE_INFO)
entry = mock_entry_factory(sensor_type="light_strip")
entry.add_to_hass(hass)
entity_id = "light.test_name"
exception = SwitchbotOperationError("Operation failed")
error_message = "An error occurred while performing the action: Operation failed"
with patch.multiple(
"homeassistant.components.switchbot.light.switchbot.SwitchbotLightStrip",
**{mock_method: AsyncMock(side_effect=exception)},
update=AsyncMock(return_value=None),
):
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
with pytest.raises(HomeAssistantError, match=error_message):
await hass.services.async_call(
LIGHT_DOMAIN,
service,
{**service_data, ATTR_ENTITY_ID: entity_id},
blocking=True,
)
@pytest.mark.parametrize(
("sensor_type", "service_info", "dev_cls"),
[
("strip_light_3", STRIP_LIGHT_3_SERVICE_INFO, "SwitchbotStripLight3"),
("floor_lamp", FLOOR_LAMP_SERVICE_INFO, "SwitchbotStripLight3"),
(
"rgbicww_strip_light",
RGBICWW_STRIP_LIGHT_SERVICE_INFO,
"SwitchbotRgbicLight",
),
("rgbicww_floor_lamp", RGBICWW_FLOOR_LAMP_SERVICE_INFO, "SwitchbotRgbicLight"),
],
)
@pytest.mark.parametrize(*FLOOR_LAMP_PARAMETERS)
async def test_floor_lamp_services(
hass: HomeAssistant,
mock_entry_encrypted_factory: Callable[[str], MockConfigEntry],
sensor_type: str,
service_info: BluetoothServiceInfoBleak,
dev_cls: str,
service: str,
service_data: dict,
mock_method: str,
expected_args: Any,
) -> None:
"""Test all SwitchBot floor lamp services."""
inject_bluetooth_service_info(hass, service_info)
entry = mock_entry_encrypted_factory(sensor_type=sensor_type)
entry.add_to_hass(hass)
entity_id = "light.test_name"
mocked_instance = AsyncMock(return_value=True)
with patch.multiple(
f"homeassistant.components.switchbot.light.switchbot.{dev_cls}",
**{mock_method: mocked_instance},
update=AsyncMock(return_value=None),
):
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
await hass.services.async_call(
LIGHT_DOMAIN,
service,
{**service_data, ATTR_ENTITY_ID: entity_id},
blocking=True,
)
mocked_instance.assert_awaited_once_with(*expected_args)
@pytest.mark.parametrize(
("sensor_type", "service_info"),
[
("strip_light_3", STRIP_LIGHT_3_SERVICE_INFO),
("floor_lamp", FLOOR_LAMP_SERVICE_INFO),
],
)
@pytest.mark.parametrize(*FLOOR_LAMP_PARAMETERS)
async def test_floor_lamp_services_exception(
hass: HomeAssistant,
mock_entry_encrypted_factory: Callable[[str], MockConfigEntry],
sensor_type: str,
service_info: BluetoothServiceInfoBleak,
service: str,
service_data: dict,
mock_method: str,
expected_args: Any,
) -> None:
"""Test all SwitchBot floor lamp services with exception."""
inject_bluetooth_service_info(hass, service_info)
entry = mock_entry_encrypted_factory(sensor_type=sensor_type)
entry.add_to_hass(hass)
entity_id = "light.test_name"
exception = SwitchbotOperationError("Operation failed")
error_message = "An error occurred while performing the action: Operation failed"
with patch.multiple(
"homeassistant.components.switchbot.light.switchbot.SwitchbotStripLight3",
**{mock_method: AsyncMock(side_effect=exception)},
update=AsyncMock(return_value=None),
):
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
with pytest.raises(HomeAssistantError, match=error_message):
await hass.services.async_call(
LIGHT_DOMAIN,
service,
{**service_data, ATTR_ENTITY_ID: entity_id},
blocking=True,
)
@pytest.mark.parametrize(
("service_info", "sensor_type"),
[
(AIR_PURIFIER_JP_SERVICE_INFO, "air_purifier_jp"),
(AIR_PURIFIER_TABLE_JP_SERVICE_INFO, "air_purifier_table_jp"),
(AIR_PURIFIER_US_SERVICE_INFO, "air_purifier_us"),
(AIR_PURIFIER_TABLE_US_SERVICE_INFO, "air_purifier_table_us"),
],
)
@pytest.mark.parametrize(*AIR_PURIFIER_LIGHT_PARAMETERS)
async def test_air_purifier_light_services(
hass: HomeAssistant,
mock_entry_encrypted_factory: Callable[[str], MockConfigEntry],
service_info: BluetoothServiceInfoBleak,
sensor_type: str,
service: str,
service_data: dict,
mock_method: str,
expected_args: Any,
) -> None:
"""Test all SwitchBot air purifier light services."""
inject_bluetooth_service_info(hass, service_info)
entry = mock_entry_encrypted_factory(sensor_type=sensor_type)
entry.add_to_hass(hass)
entity_id = "light.test_name_light"
mocked_instance = AsyncMock(return_value=True)
mocked_none_instance = AsyncMock(return_value=None)
with patch.multiple(
"homeassistant.components.switchbot.light.switchbot.SwitchbotAirPurifier",
**{mock_method: mocked_instance},
get_basic_info=mocked_none_instance,
update=mocked_none_instance,
):
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
await hass.services.async_call(
LIGHT_DOMAIN,
service,
{**service_data, ATTR_ENTITY_ID: entity_id},
blocking=True,
)
mocked_instance.assert_awaited_once_with(*expected_args)
async def test_air_purifier_light_restore_state(
hass: HomeAssistant,
mock_entry_encrypted_factory: Callable[[str], MockConfigEntry],
) -> None:
"""Test restoring state of SwitchBot air purifier light."""
inject_bluetooth_service_info(hass, AIR_PURIFIER_JP_SERVICE_INFO)
entry = mock_entry_encrypted_factory(sensor_type="air_purifier_jp")
entry.add_to_hass(hass)
entity_id = "light.test_name_light"
mock_restore_cache(hass, [State(entity_id, "on")])
mocked_info = AsyncMock(
return_value=[
bytearray(
b"\x01\xa7\xe9\x8c\x08\x00\xb2\x01\x96\x00\x00\x00\x00\x01\x00\x17"
),
bytearray(b"\x01\x01\x02\x03\x04\x05"),
bytearray(b"\x01\x01"),
]
)
with patch.multiple(
"homeassistant.components.switchbot.light.switchbot.SwitchbotAirPurifier",
_get_basic_info_by_multi_commands=mocked_info,
):
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
state = hass.states.get(entity_id)
assert state.state == STATE_ON
assert state.attributes.get(ATTR_BRIGHTNESS) == 13
assert state.attributes.get(ATTR_RGB_COLOR) == (2, 3, 4)