Add switchbot cover unit tests (#140265)

* add cover unit tests

* Add unit test for SwitchBot cover

* fix: use mock_restore_cache to mock the last state

* modify unit tests

* modify scripts as suggest

* improve readability

* adjust patch target per review comments

* adjust patch target per review comments

---------

Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
Retha Runolfsson 2025-03-31 17:42:31 +08:00 committed by Franck Nijhof
parent 04f5315ab2
commit d0b61af7ec
No known key found for this signature in database
GPG Key ID: D62583BA8AB11CA3
4 changed files with 414 additions and 1 deletions

View File

@ -154,7 +154,7 @@ class SwitchBotBlindTiltEntity(SwitchbotEntity, CoverEntity, RestoreEntity):
ATTR_CURRENT_TILT_POSITION
)
self._last_run_success = last_state.attributes.get("last_run_success")
if (_tilt := self._attr_current_cover_position) is not None:
if (_tilt := self._attr_current_cover_tilt_position) is not None:
self._attr_is_closed = (_tilt < self.CLOSED_DOWN_THRESHOLD) or (
_tilt > self.CLOSED_UP_THRESHOLD
)

View File

@ -319,3 +319,70 @@ WOHUB2_SERVICE_INFO = BluetoothServiceInfoBleak(
connectable=True,
tx_power=-127,
)
WOCURTAIN3_SERVICE_INFO = BluetoothServiceInfoBleak(
name="WoCurtain3",
address="AA:BB:CC:DD:EE:FF",
manufacturer_data={2409: b"\xcf;Zwu\x0c\x19\x0b\x00\x11D\x006"},
service_data={"00000d00-0000-1000-8000-00805f9b34fb": b"{\xc06\x00\x11D"},
service_uuids=["cba20d00-224d-11e6-9fb8-0002a5d5c51b"],
rssi=-60,
source="local",
advertisement=generate_advertisement_data(
local_name="WoCurtain3",
manufacturer_data={2409: b"\xcf;Zwu\x0c\x19\x0b\x00\x11D\x006"},
service_data={"00000d00-0000-1000-8000-00805f9b34fb": b"{\xc06\x00\x11D"},
service_uuids=["cba20d00-224d-11e6-9fb8-0002a5d5c51b"],
),
device=generate_ble_device("AA:BB:CC:DD:EE:FF", "WoCurtain3"),
time=0,
connectable=True,
tx_power=-127,
)
WOBLINDTILT_SERVICE_INFO = BluetoothServiceInfoBleak(
name="WoBlindTilt",
address="AA:BB:CC:DD:EE:FF",
manufacturer_data={2409: b"\xfbgA`\x98\xe8\x1d%2\x11\x84"},
service_data={"00000d00-0000-1000-8000-00805f9b34fb": b"x\x00*"},
service_uuids=["cba20d00-224d-11e6-9fb8-0002a5d5c51b"],
rssi=-60,
source="local",
advertisement=generate_advertisement_data(
local_name="WoBlindTilt",
manufacturer_data={2409: b"\xfbgA`\x98\xe8\x1d%2\x11\x84"},
service_data={"00000d00-0000-1000-8000-00805f9b34fb": b"x\x00*"},
service_uuids=["cba20d00-224d-11e6-9fb8-0002a5d5c51b"],
),
device=generate_ble_device("AA:BB:CC:DD:EE:FF", "WoBlindTilt"),
time=0,
connectable=True,
tx_power=-127,
)
def make_advertisement(
address: str, manufacturer_data: bytes, service_data: bytes
) -> BluetoothServiceInfoBleak:
"""Make a dummy advertisement."""
return BluetoothServiceInfoBleak(
name="Test Device",
address=address,
manufacturer_data={2409: manufacturer_data},
service_data={"00000d00-0000-1000-8000-00805f9b34fb": service_data},
service_uuids=["cba20d00-224d-11e6-9fb8-0002a5d5c51b"],
rssi=-60,
source="local",
advertisement=generate_advertisement_data(
local_name="Test Device",
manufacturer_data={2409: manufacturer_data},
service_data={"00000d00-0000-1000-8000-00805f9b34fb": service_data},
service_uuids=["cba20d00-224d-11e6-9fb8-0002a5d5c51b"],
),
device=generate_ble_device(address, "Test Device"),
time=0,
connectable=True,
tx_power=-127,
)

View File

@ -2,7 +2,26 @@
import pytest
from homeassistant.components.switchbot.const import DOMAIN
from homeassistant.const import CONF_ADDRESS, CONF_NAME, CONF_SENSOR_TYPE
from tests.common import MockConfigEntry
@pytest.fixture(autouse=True)
def mock_bluetooth(enable_bluetooth: None) -> None:
"""Auto mock bluetooth."""
@pytest.fixture
def mock_entry_factory():
"""Fixture to create a MockConfigEntry with a customizable sensor type."""
return lambda sensor_type="curtain": MockConfigEntry(
domain=DOMAIN,
data={
CONF_ADDRESS: "aa:bb:cc:dd:ee:ff",
CONF_NAME: "test-name",
CONF_SENSOR_TYPE: sensor_type,
},
unique_id="aabbccddeeff",
)

View File

@ -0,0 +1,327 @@
"""Test the switchbot covers."""
from collections.abc import Callable
from unittest.mock import AsyncMock, patch
from homeassistant.components.cover import (
ATTR_CURRENT_POSITION,
ATTR_CURRENT_TILT_POSITION,
ATTR_POSITION,
ATTR_TILT_POSITION,
DOMAIN as COVER_DOMAIN,
CoverState,
)
from homeassistant.const import (
ATTR_ENTITY_ID,
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 . import 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
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()
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()
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()
state = hass.states.get(entity_id)
assert state.state == CoverState.OPEN
assert state.attributes[ATTR_CURRENT_POSITION] == 60
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