Files
core/tests/components/switchbot/test_cover.py
T

1031 lines
34 KiB
Python

"""Test the switchbot covers."""
from collections.abc import Callable
from unittest.mock import AsyncMock, patch
import pytest
from switchbot import SwitchbotOperationError
from homeassistant.components.bluetooth import BluetoothServiceInfoBleak
from homeassistant.components.cover import (
ATTR_CURRENT_POSITION,
ATTR_CURRENT_TILT_POSITION,
ATTR_POSITION,
ATTR_SPEED,
ATTR_TILT_POSITION,
DOMAIN as COVER_DOMAIN,
CoverEntityCapabilityAttribute,
CoverEntityFeature,
CoverState,
)
from homeassistant.components.switchbot.const import (
CONF_CURTAIN_SPEED,
CONF_RETRY_COUNT,
CURTAIN_SPEED_NORMAL,
CURTAIN_SPEED_SILENT,
DEFAULT_RETRY_COUNT,
ROLLER_SHADE_SPEED_PERFORMANCE,
ROLLER_SHADE_SPEED_QUIET,
)
from homeassistant.const import (
ATTR_ENTITY_ID,
ATTR_SUPPORTED_FEATURES,
SERVICE_CLOSE_COVER,
SERVICE_CLOSE_COVER_TILT,
SERVICE_OPEN_COVER,
SERVICE_OPEN_COVER_TILT,
SERVICE_SET_COVER_POSITION,
SERVICE_SET_COVER_TILT_POSITION,
SERVICE_STOP_COVER,
SERVICE_STOP_COVER_TILT,
)
from homeassistant.core import HomeAssistant, State
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
from . import (
GARAGE_DOOR_OPENER_SERVICE_INFO,
ROLLER_SHADE_SERVICE_INFO,
WOBLINDTILT_SERVICE_INFO,
WOCURTAIN3_SERVICE_INFO,
make_advertisement,
)
from tests.common import MockConfigEntry, mock_restore_cache
from tests.components.bluetooth import inject_bluetooth_service_info
ROLLER_SHADE_ENTITY_ID = "cover.test_name"
async def test_curtain3_setup(
hass: HomeAssistant, mock_entry_factory: Callable[[str], MockConfigEntry]
) -> None:
"""Test setting up the Curtain3."""
inject_bluetooth_service_info(hass, WOCURTAIN3_SERVICE_INFO)
entry = mock_entry_factory(sensor_type="curtain")
entity_id = "cover.test_name"
mock_restore_cache(
hass,
[
State(
entity_id,
CoverState.OPEN,
{ATTR_CURRENT_POSITION: 50},
)
],
)
entry.add_to_hass(hass)
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 == CoverState.OPEN
assert state.attributes[ATTR_CURRENT_POSITION] == 50
async def test_curtain3_controlling(
hass: HomeAssistant, mock_entry_factory: Callable[[str], MockConfigEntry]
) -> None:
"""Test Curtain3 controlling."""
inject_bluetooth_service_info(hass, WOCURTAIN3_SERVICE_INFO)
entry = mock_entry_factory(sensor_type="curtain")
entry.add_to_hass(hass)
with (
patch(
"homeassistant.components.switchbot.cover.switchbot.SwitchbotCurtain.open",
new=AsyncMock(return_value=True),
) as mock_open,
patch(
"homeassistant.components.switchbot.cover.switchbot.SwitchbotCurtain.close",
new=AsyncMock(return_value=True),
) as mock_close,
patch(
"homeassistant.components.switchbot.cover.switchbot.SwitchbotCurtain.stop",
new=AsyncMock(return_value=True),
) as mock_stop,
patch(
"homeassistant.components.switchbot.cover.switchbot.SwitchbotCurtain.set_position",
new=AsyncMock(return_value=True),
) as mock_set_position,
):
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
entity_id = "cover.test_name"
address = "AA:BB:CC:DD:EE:FF"
service_data = b"{\xc06\x00\x11D"
# Test open
manufacturer_data = b"\xcf;Zwu\x0c\x19\x0b\x05\x11D\x006"
await hass.services.async_call(
COVER_DOMAIN, SERVICE_OPEN_COVER, {ATTR_ENTITY_ID: entity_id}, blocking=True
)
inject_bluetooth_service_info(
hass, make_advertisement(address, manufacturer_data, service_data)
)
await hass.async_block_till_done()
mock_open.assert_awaited_once_with(255) # Default speed
state = hass.states.get(entity_id)
assert state.state == CoverState.OPEN
assert state.attributes[ATTR_CURRENT_POSITION] == 95
# Test close
manufacturer_data = b"\xcf;Zwu\x0c\x19\x0b\x58\x11D\x006"
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_CLOSE_COVER,
{ATTR_ENTITY_ID: entity_id},
blocking=True,
)
inject_bluetooth_service_info(
hass, make_advertisement(address, manufacturer_data, service_data)
)
await hass.async_block_till_done()
mock_close.assert_awaited_once_with(255) # Default speed
state = hass.states.get(entity_id)
assert state.state == CoverState.CLOSED
assert state.attributes[ATTR_CURRENT_POSITION] == 12
# Test stop
manufacturer_data = b"\xcf;Zwu\x0c\x19\x0b\x3c\x11D\x006"
await hass.services.async_call(
COVER_DOMAIN, SERVICE_STOP_COVER, {ATTR_ENTITY_ID: entity_id}, blocking=True
)
inject_bluetooth_service_info(
hass, make_advertisement(address, manufacturer_data, service_data)
)
await hass.async_block_till_done()
mock_stop.assert_awaited_once()
state = hass.states.get(entity_id)
assert state.state == CoverState.OPEN
assert state.attributes[ATTR_CURRENT_POSITION] == 40
# Test set position
manufacturer_data = b"\xcf;Zwu\x0c\x19\x0b(\x11D\x006"
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_SET_COVER_POSITION,
{ATTR_ENTITY_ID: entity_id, ATTR_POSITION: 50},
blocking=True,
)
inject_bluetooth_service_info(
hass, make_advertisement(address, manufacturer_data, service_data)
)
await hass.async_block_till_done()
mock_set_position.assert_awaited_once_with(50, 255) # Default speed
state = hass.states.get(entity_id)
assert state.state == CoverState.OPEN
assert state.attributes[ATTR_CURRENT_POSITION] == 60
async def test_curtain3_custom_speed_controlling(
hass: HomeAssistant, mock_entry_factory: Callable[[str], MockConfigEntry]
) -> None:
"""Test Curtain3 controlling with custom speed."""
inject_bluetooth_service_info(hass, WOCURTAIN3_SERVICE_INFO)
entry = mock_entry_factory(sensor_type="curtain")
entry.add_to_hass(hass)
# Update entry options using async_update_entry
hass.config_entries.async_update_entry(
entry,
options={
CONF_RETRY_COUNT: DEFAULT_RETRY_COUNT,
CONF_CURTAIN_SPEED: 50,
},
)
with (
patch(
"homeassistant.components.switchbot.cover.switchbot.SwitchbotCurtain.open",
new=AsyncMock(return_value=True),
) as mock_open,
patch(
"homeassistant.components.switchbot.cover.switchbot.SwitchbotCurtain.close",
new=AsyncMock(return_value=True),
) as mock_close,
):
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
entity_id = "cover.test_name"
await hass.services.async_call(
COVER_DOMAIN, SERVICE_OPEN_COVER, {ATTR_ENTITY_ID: entity_id}, blocking=True
)
await hass.async_block_till_done()
mock_open.assert_awaited_once_with(50)
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_CLOSE_COVER,
{ATTR_ENTITY_ID: entity_id},
blocking=True,
)
await hass.async_block_till_done()
mock_close.assert_awaited_once_with(50)
@pytest.mark.parametrize(
("speed", "expected_value"),
[
pytest.param(CURTAIN_SPEED_NORMAL, 255, id="normal"),
pytest.param(CURTAIN_SPEED_SILENT, 1, id="silent"),
],
)
async def test_curtain3_speed(
hass: HomeAssistant,
mock_entry_factory: Callable[[str], MockConfigEntry],
speed: str,
expected_value: int,
) -> None:
"""Test the curtain forwards the requested cover speed to the device."""
inject_bluetooth_service_info(hass, WOCURTAIN3_SERVICE_INFO)
entry = mock_entry_factory(sensor_type="curtain")
entry.add_to_hass(hass)
with (
patch(
"homeassistant.components.switchbot.cover.switchbot.SwitchbotCurtain.open",
new=AsyncMock(return_value=True),
) as mock_open,
patch(
"homeassistant.components.switchbot.cover.switchbot.SwitchbotCurtain.close",
new=AsyncMock(return_value=True),
) as mock_close,
patch(
"homeassistant.components.switchbot.cover.switchbot.SwitchbotCurtain.set_position",
new=AsyncMock(return_value=True),
) as mock_set_position,
):
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
entity_id = "cover.test_name"
state = hass.states.get(entity_id)
assert state.attributes[CoverEntityCapabilityAttribute.SUPPORTED_SPEEDS] == [
CURTAIN_SPEED_NORMAL,
CURTAIN_SPEED_SILENT,
]
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_OPEN_COVER,
{ATTR_ENTITY_ID: entity_id, ATTR_SPEED: speed},
blocking=True,
)
mock_open.assert_awaited_once_with(expected_value)
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_CLOSE_COVER,
{ATTR_ENTITY_ID: entity_id, ATTR_SPEED: speed},
blocking=True,
)
mock_close.assert_awaited_once_with(expected_value)
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_SET_COVER_POSITION,
{ATTR_ENTITY_ID: entity_id, ATTR_POSITION: 50, ATTR_SPEED: speed},
blocking=True,
)
mock_set_position.assert_awaited_once_with(50, expected_value)
async def test_curtain_speed_not_supported(
hass: HomeAssistant, mock_entry_factory: Callable[[str], MockConfigEntry]
) -> None:
"""Test a non-Curtain-3 curtain does not expose the speed feature."""
# A Curtain (not Curtain 3) advertises the "c" device type in its service data.
inject_bluetooth_service_info(
hass,
make_advertisement(
"AA:BB:CC:DD:EE:FF",
b"\xcf;Zwu\x0c\x19\x0b\x00\x11D\x006",
b"c\xc06\x00\x11D",
),
)
entry = mock_entry_factory(sensor_type="curtain")
entry.add_to_hass(hass)
with patch(
"homeassistant.components.switchbot.cover.switchbot.SwitchbotCurtain.open",
new=AsyncMock(return_value=True),
) as mock_open:
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
entity_id = "cover.test_name"
state = hass.states.get(entity_id)
assert not state.attributes[ATTR_SUPPORTED_FEATURES] & CoverEntityFeature.SPEED
assert CoverEntityCapabilityAttribute.SUPPORTED_SPEEDS not in state.attributes
# A speed passed to a model that does not support it is ignored and the
# curtain moves at the default speed.
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_OPEN_COVER,
{ATTR_ENTITY_ID: entity_id, ATTR_SPEED: CURTAIN_SPEED_SILENT},
blocking=True,
)
mock_open.assert_awaited_once_with(255)
async def test_blindtilt_setup(
hass: HomeAssistant, mock_entry_factory: Callable[[str], MockConfigEntry]
) -> None:
"""Test setting up the blindtilt."""
inject_bluetooth_service_info(hass, WOBLINDTILT_SERVICE_INFO)
entry = mock_entry_factory(sensor_type="blind_tilt")
entity_id = "cover.test_name"
mock_restore_cache(
hass,
[
State(
entity_id,
CoverState.OPEN,
{ATTR_CURRENT_TILT_POSITION: 40},
)
],
)
entry.add_to_hass(hass)
with patch(
"homeassistant.components.switchbot.cover.switchbot.SwitchbotBlindTilt.update",
new=AsyncMock(return_value=True),
):
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 == CoverState.OPEN
assert state.attributes[ATTR_CURRENT_TILT_POSITION] == 40
async def test_blindtilt_controlling(
hass: HomeAssistant, mock_entry_factory: Callable[[str], MockConfigEntry]
) -> None:
"""Test blindtilt controlling."""
inject_bluetooth_service_info(hass, WOBLINDTILT_SERVICE_INFO)
entry = mock_entry_factory(sensor_type="blind_tilt")
entry.add_to_hass(hass)
info = {
"motionDirection": {
"opening": False,
"closing": False,
"up": False,
"down": False,
},
}
with (
patch(
"homeassistant.components.switchbot.cover.switchbot.SwitchbotBlindTilt.get_basic_info",
new=AsyncMock(return_value=info),
),
patch(
"homeassistant.components.switchbot.cover.switchbot.SwitchbotBlindTilt.open",
new=AsyncMock(return_value=True),
) as mock_open,
patch(
"homeassistant.components.switchbot.cover.switchbot.SwitchbotBlindTilt.close",
new=AsyncMock(return_value=True),
) as mock_close,
patch(
"homeassistant.components.switchbot.cover.switchbot.SwitchbotBlindTilt.stop",
new=AsyncMock(return_value=True),
) as mock_stop,
patch(
"homeassistant.components.switchbot.cover.switchbot.SwitchbotBlindTilt.set_position",
new=AsyncMock(return_value=True),
) as mock_set_position,
):
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
entity_id = "cover.test_name"
address = "AA:BB:CC:DD:EE:FF"
service_data = b"x\x00*"
# Test open
manufacturer_data = b"\xfbgA`\x98\xe8\x1d%F\x12\x85"
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_OPEN_COVER_TILT,
{ATTR_ENTITY_ID: entity_id},
blocking=True,
)
with patch(
"homeassistant.components.switchbot.cover.switchbot.SwitchbotBlindTilt.get_basic_info",
return_value=info,
):
inject_bluetooth_service_info(
hass, make_advertisement(address, manufacturer_data, service_data)
)
await hass.async_block_till_done()
mock_open.assert_awaited_once()
state = hass.states.get(entity_id)
assert state.state == CoverState.OPEN
assert state.attributes[ATTR_CURRENT_TILT_POSITION] == 70
# Test close
manufacturer_data = b"\xfbgA`\x98\xe8\x1d%\x0f\x12\x85"
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_CLOSE_COVER_TILT,
{ATTR_ENTITY_ID: entity_id},
blocking=True,
)
with patch(
"homeassistant.components.switchbot.cover.switchbot.SwitchbotBlindTilt.get_basic_info",
return_value=info,
):
inject_bluetooth_service_info(
hass, make_advertisement(address, manufacturer_data, service_data)
)
await hass.async_block_till_done()
mock_close.assert_awaited_once()
state = hass.states.get(entity_id)
assert state.state == CoverState.CLOSED
assert state.attributes[ATTR_CURRENT_TILT_POSITION] == 15
# Test stop
manufacturer_data = b"\xfbgA`\x98\xe8\x1d%\n\x12\x85"
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_STOP_COVER_TILT,
{ATTR_ENTITY_ID: entity_id},
blocking=True,
)
with patch(
"homeassistant.components.switchbot.cover.switchbot.SwitchbotBlindTilt.get_basic_info",
return_value=info,
):
inject_bluetooth_service_info(
hass, make_advertisement(address, manufacturer_data, service_data)
)
await hass.async_block_till_done()
mock_stop.assert_awaited_once()
state = hass.states.get(entity_id)
assert state.state == CoverState.CLOSED
assert state.attributes[ATTR_CURRENT_TILT_POSITION] == 10
# Test set position
manufacturer_data = b"\xfbgA`\x98\xe8\x1d%2\x12\x85"
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_SET_COVER_TILT_POSITION,
{ATTR_ENTITY_ID: entity_id, ATTR_TILT_POSITION: 50},
blocking=True,
)
with patch(
"homeassistant.components.switchbot.cover.switchbot.SwitchbotBlindTilt.get_basic_info",
return_value=info,
):
inject_bluetooth_service_info(
hass, make_advertisement(address, manufacturer_data, service_data)
)
await hass.async_block_till_done()
mock_set_position.assert_awaited_once()
state = hass.states.get(entity_id)
assert state.state == CoverState.OPEN
assert state.attributes[ATTR_CURRENT_TILT_POSITION] == 50
async def test_blindtilt_idle_advertisement(
hass: HomeAssistant, mock_entry_factory: Callable[[str], MockConfigEntry]
) -> None:
"""Test blindtilt handles BLE advertisement without motionDirection."""
inject_bluetooth_service_info(hass, WOBLINDTILT_SERVICE_INFO)
entry = mock_entry_factory(sensor_type="blind_tilt")
entry.add_to_hass(hass)
with patch(
"homeassistant.components.switchbot.cover.switchbot.SwitchbotBlindTilt.get_basic_info",
new=AsyncMock(return_value={}),
):
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
entity_id = "cover.test_name"
address = "AA:BB:CC:DD:EE:FF"
service_data = b"x\x00*"
manufacturer_data = b"\xfbgA`\x98\xe8\x1d%F\x12\x85"
inject_bluetooth_service_info(
hass, make_advertisement(address, manufacturer_data, service_data)
)
await hass.async_block_till_done()
# Should not crash; entity should still exist
state = hass.states.get(entity_id)
assert state is not None
async def test_roller_shade_setup(
hass: HomeAssistant, mock_entry_factory: Callable[[str], MockConfigEntry]
) -> None:
"""Test setting up the RollerShade."""
inject_bluetooth_service_info(hass, WOCURTAIN3_SERVICE_INFO)
entry = mock_entry_factory(sensor_type="roller_shade")
entity_id = "cover.test_name"
mock_restore_cache(
hass,
[
State(
entity_id,
CoverState.OPEN,
{ATTR_CURRENT_POSITION: 60},
)
],
)
entry.add_to_hass(hass)
with patch(
"homeassistant.components.switchbot.cover.switchbot.SwitchbotRollerShade.update",
new=AsyncMock(return_value=True),
):
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 == CoverState.OPEN
assert state.attributes[ATTR_CURRENT_POSITION] == 60
async def test_roller_shade_controlling(
hass: HomeAssistant, mock_entry_factory: Callable[[str], MockConfigEntry]
) -> None:
"""Test Roller Shade controlling."""
inject_bluetooth_service_info(hass, ROLLER_SHADE_SERVICE_INFO)
entry = mock_entry_factory(sensor_type="roller_shade")
entry.add_to_hass(hass)
info = {"battery": 39}
with (
patch(
"homeassistant.components.switchbot.cover.switchbot.SwitchbotRollerShade.get_basic_info",
new=AsyncMock(return_value=info),
),
patch(
"homeassistant.components.switchbot.cover.switchbot.SwitchbotRollerShade.open",
new=AsyncMock(return_value=True),
) as mock_open,
patch(
"homeassistant.components.switchbot.cover.switchbot.SwitchbotRollerShade.close",
new=AsyncMock(return_value=True),
) as mock_close,
patch(
"homeassistant.components.switchbot.cover.switchbot.SwitchbotRollerShade.stop",
new=AsyncMock(return_value=True),
) as mock_stop,
patch(
"homeassistant.components.switchbot.cover.switchbot.SwitchbotRollerShade.set_position",
new=AsyncMock(return_value=True),
) as mock_set_position,
):
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
entity_id = "cover.test_name"
address = "AA:BB:CC:DD:EE:FF"
service_data = b",\x00'\x9f\x11\x04"
# Test open
manufacturer_data = b"\xb0\xe9\xfeT\x90\x1b,\x08\xa0\x11\x04'\x00"
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_OPEN_COVER,
{ATTR_ENTITY_ID: entity_id},
blocking=True,
)
with patch(
"homeassistant.components.switchbot.cover.switchbot.SwitchbotRollerShade.get_basic_info",
new=AsyncMock(return_value=info),
):
inject_bluetooth_service_info(
hass, make_advertisement(address, manufacturer_data, service_data)
)
await hass.async_block_till_done()
mock_open.assert_awaited_once_with(0)
state = hass.states.get(entity_id)
assert state.state == CoverState.OPEN
assert state.attributes[ATTR_CURRENT_POSITION] == 68
# Test close
manufacturer_data = b"\xb0\xe9\xfeT\x90\x1b,\x08\x5a\x11\x04'\x00"
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_CLOSE_COVER,
{ATTR_ENTITY_ID: entity_id},
blocking=True,
)
with patch(
"homeassistant.components.switchbot.cover.switchbot.SwitchbotRollerShade.get_basic_info",
return_value=info,
):
inject_bluetooth_service_info(
hass, make_advertisement(address, manufacturer_data, service_data)
)
await hass.async_block_till_done()
mock_close.assert_awaited_once_with(0)
state = hass.states.get(entity_id)
assert state.state == CoverState.CLOSED
assert state.attributes[ATTR_CURRENT_POSITION] == 10
# Test stop
manufacturer_data = b"\xb0\xe9\xfeT\x90\x1b,\x08\x5f\x11\x04'\x00"
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_STOP_COVER,
{ATTR_ENTITY_ID: entity_id},
blocking=True,
)
with patch(
"homeassistant.components.switchbot.cover.switchbot.SwitchbotRollerShade.get_basic_info",
return_value=info,
):
inject_bluetooth_service_info(
hass, make_advertisement(address, manufacturer_data, service_data)
)
await hass.async_block_till_done()
mock_stop.assert_awaited_once()
state = hass.states.get(entity_id)
assert state.state == CoverState.CLOSED
assert state.attributes[ATTR_CURRENT_POSITION] == 5
# Test set position
manufacturer_data = b"\xb0\xe9\xfeT\x90\x1b,\x08\x32\x11\x04'\x00"
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_SET_COVER_POSITION,
{ATTR_ENTITY_ID: entity_id, ATTR_POSITION: 50},
blocking=True,
)
with patch(
"homeassistant.components.switchbot.cover.switchbot.SwitchbotRollerShade.get_basic_info",
return_value=info,
):
inject_bluetooth_service_info(
hass, make_advertisement(address, manufacturer_data, service_data)
)
await hass.async_block_till_done()
mock_set_position.assert_awaited_once_with(50, 0)
state = hass.states.get(entity_id)
assert state.state == CoverState.OPEN
assert state.attributes[ATTR_CURRENT_POSITION] == 50
@pytest.mark.parametrize(
("speed", "expected_mode"),
[
pytest.param(ROLLER_SHADE_SPEED_PERFORMANCE, 0, id="performance"),
pytest.param(ROLLER_SHADE_SPEED_QUIET, 1, id="quiet"),
],
)
async def test_roller_shade_speed(
hass: HomeAssistant,
mock_entry_factory: Callable[[str], MockConfigEntry],
speed: str,
expected_mode: int,
) -> None:
"""Test the roller shade forwards the requested speed as a motor mode."""
inject_bluetooth_service_info(hass, ROLLER_SHADE_SERVICE_INFO)
entry = mock_entry_factory(sensor_type="roller_shade")
entry.add_to_hass(hass)
with (
patch(
"homeassistant.components.switchbot.cover.switchbot.SwitchbotRollerShade.update",
new=AsyncMock(return_value=True),
),
patch(
"homeassistant.components.switchbot.cover.switchbot.SwitchbotRollerShade.open",
new=AsyncMock(return_value=True),
) as mock_open,
patch(
"homeassistant.components.switchbot.cover.switchbot.SwitchbotRollerShade.close",
new=AsyncMock(return_value=True),
) as mock_close,
patch(
"homeassistant.components.switchbot.cover.switchbot.SwitchbotRollerShade.set_position",
new=AsyncMock(return_value=True),
) as mock_set_position,
):
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
state = hass.states.get(ROLLER_SHADE_ENTITY_ID)
assert state.attributes[CoverEntityCapabilityAttribute.SUPPORTED_SPEEDS] == [
ROLLER_SHADE_SPEED_PERFORMANCE,
ROLLER_SHADE_SPEED_QUIET,
]
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_OPEN_COVER,
{ATTR_ENTITY_ID: ROLLER_SHADE_ENTITY_ID, ATTR_SPEED: speed},
blocking=True,
)
mock_open.assert_awaited_once_with(expected_mode)
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_CLOSE_COVER,
{ATTR_ENTITY_ID: ROLLER_SHADE_ENTITY_ID, ATTR_SPEED: speed},
blocking=True,
)
mock_close.assert_awaited_once_with(expected_mode)
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_SET_COVER_POSITION,
{
ATTR_ENTITY_ID: ROLLER_SHADE_ENTITY_ID,
ATTR_POSITION: 50,
ATTR_SPEED: speed,
},
blocking=True,
)
mock_set_position.assert_awaited_once_with(50, expected_mode)
@pytest.mark.parametrize(
("service", "service_data"),
[
pytest.param(SERVICE_OPEN_COVER, {}, id="open"),
pytest.param(SERVICE_CLOSE_COVER, {}, id="close"),
pytest.param(
SERVICE_SET_COVER_POSITION, {ATTR_POSITION: 50}, id="set_position"
),
],
)
async def test_roller_shade_invalid_speed(
hass: HomeAssistant,
mock_entry_factory: Callable[[str], MockConfigEntry],
service: str,
service_data: dict[str, int],
) -> None:
"""Test an unsupported speed is rejected before reaching the device."""
inject_bluetooth_service_info(hass, ROLLER_SHADE_SERVICE_INFO)
entry = mock_entry_factory(sensor_type="roller_shade")
entry.add_to_hass(hass)
with (
patch(
"homeassistant.components.switchbot.cover.switchbot.SwitchbotRollerShade.update",
new=AsyncMock(return_value=True),
),
patch(
"homeassistant.components.switchbot.cover.switchbot.SwitchbotRollerShade.open",
new=AsyncMock(return_value=True),
) as mock_open,
patch(
"homeassistant.components.switchbot.cover.switchbot.SwitchbotRollerShade.close",
new=AsyncMock(return_value=True),
) as mock_close,
patch(
"homeassistant.components.switchbot.cover.switchbot.SwitchbotRollerShade.set_position",
new=AsyncMock(return_value=True),
) as mock_set_position,
):
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
with pytest.raises(ServiceValidationError) as exc_info:
await hass.services.async_call(
COVER_DOMAIN,
service,
{
ATTR_ENTITY_ID: ROLLER_SHADE_ENTITY_ID,
ATTR_SPEED: "turbo",
**service_data,
},
blocking=True,
)
assert exc_info.value.translation_key == "not_valid_speed"
mock_open.assert_not_awaited()
mock_close.assert_not_awaited()
mock_set_position.assert_not_awaited()
@pytest.mark.parametrize(
("exception", "error_message"),
[
(
SwitchbotOperationError("Operation failed"),
"An error occurred while performing the action: Operation failed",
),
],
)
@pytest.mark.parametrize(
(
"sensor_type",
"service_info",
"class_name",
"service",
"service_data",
"mock_method",
),
[
(
"curtain",
WOCURTAIN3_SERVICE_INFO,
"SwitchbotCurtain",
SERVICE_CLOSE_COVER,
{},
"close",
),
(
"curtain",
WOCURTAIN3_SERVICE_INFO,
"SwitchbotCurtain",
SERVICE_OPEN_COVER,
{},
"open",
),
(
"curtain",
WOCURTAIN3_SERVICE_INFO,
"SwitchbotCurtain",
SERVICE_STOP_COVER,
{},
"stop",
),
(
"curtain",
WOCURTAIN3_SERVICE_INFO,
"SwitchbotCurtain",
SERVICE_SET_COVER_POSITION,
{ATTR_POSITION: 50},
"set_position",
),
(
"roller_shade",
ROLLER_SHADE_SERVICE_INFO,
"SwitchbotRollerShade",
SERVICE_SET_COVER_POSITION,
{ATTR_POSITION: 50},
"set_position",
),
(
"roller_shade",
ROLLER_SHADE_SERVICE_INFO,
"SwitchbotRollerShade",
SERVICE_OPEN_COVER,
{},
"open",
),
(
"roller_shade",
ROLLER_SHADE_SERVICE_INFO,
"SwitchbotRollerShade",
SERVICE_CLOSE_COVER,
{},
"close",
),
(
"roller_shade",
ROLLER_SHADE_SERVICE_INFO,
"SwitchbotRollerShade",
SERVICE_STOP_COVER,
{},
"stop",
),
(
"blind_tilt",
WOBLINDTILT_SERVICE_INFO,
"SwitchbotBlindTilt",
SERVICE_SET_COVER_TILT_POSITION,
{ATTR_TILT_POSITION: 50},
"set_position",
),
(
"blind_tilt",
WOBLINDTILT_SERVICE_INFO,
"SwitchbotBlindTilt",
SERVICE_OPEN_COVER_TILT,
{},
"open",
),
(
"blind_tilt",
WOBLINDTILT_SERVICE_INFO,
"SwitchbotBlindTilt",
SERVICE_CLOSE_COVER_TILT,
{},
"close",
),
(
"blind_tilt",
WOBLINDTILT_SERVICE_INFO,
"SwitchbotBlindTilt",
SERVICE_STOP_COVER_TILT,
{},
"stop",
),
],
)
async def test_exception_handling_cover_service(
hass: HomeAssistant,
mock_entry_factory: Callable[[str], MockConfigEntry],
sensor_type: str,
service_info: BluetoothServiceInfoBleak,
class_name: str,
service: str,
service_data: dict,
mock_method: str,
exception: Exception,
error_message: str,
) -> None:
"""Test exception handling for cover service with exception."""
inject_bluetooth_service_info(hass, service_info)
entry = mock_entry_factory(sensor_type=sensor_type)
entry.add_to_hass(hass)
entity_id = "cover.test_name"
with patch.multiple(
f"homeassistant.components.switchbot.cover.switchbot.{class_name}",
update=AsyncMock(return_value=None),
**{mock_method: AsyncMock(side_effect=exception)},
):
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(
COVER_DOMAIN,
service,
{**service_data, ATTR_ENTITY_ID: entity_id},
blocking=True,
)
@pytest.mark.parametrize(
("service", "mock_method"),
[
(SERVICE_OPEN_COVER, "open"),
(SERVICE_CLOSE_COVER, "close"),
],
)
async def test_garage_door_opener_controlling(
hass: HomeAssistant,
mock_entry_encrypted_factory: Callable[[str], MockConfigEntry],
service: str,
mock_method: str,
) -> None:
"""Test Garage Door Opener controlling."""
inject_bluetooth_service_info(hass, GARAGE_DOOR_OPENER_SERVICE_INFO)
entry = mock_entry_encrypted_factory(sensor_type="garage_door_opener")
entry.add_to_hass(hass)
entity_id = "cover.test_name"
mocked_instance = AsyncMock(return_value=True)
with patch.multiple(
"homeassistant.components.switchbot.cover.switchbot.SwitchbotGarageDoorOpener",
update=AsyncMock(),
**{mock_method: mocked_instance},
):
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
await hass.services.async_call(
COVER_DOMAIN,
service,
{ATTR_ENTITY_ID: entity_id},
blocking=True,
)
mocked_instance.assert_awaited_once()