1
0
mirror of https://github.com/home-assistant/core.git synced 2026-02-21 02:18:47 +00:00
Files
core/tests/components/lcn/test_cover.py
2025-11-22 15:36:47 +01:00

606 lines
18 KiB
Python

"""Test for the LCN cover platform."""
from unittest.mock import AsyncMock, patch
from freezegun.api import FrozenDateTimeFactory
from pypck.inputs import (
ModStatusMotorPositionBS4,
ModStatusMotorPositionModule,
ModStatusOutput,
ModStatusRelays,
)
from pypck.lcn_addr import LcnAddr
from pypck.lcn_defs import MotorPositioningMode, MotorReverseTime, MotorStateModifier
import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.components.cover import (
ATTR_CURRENT_POSITION,
ATTR_POSITION,
DOMAIN as DOMAIN_COVER,
CoverState,
)
from homeassistant.components.lcn.cover import SCAN_INTERVAL
from homeassistant.components.lcn.helpers import get_device_connection
from homeassistant.const import (
ATTR_ENTITY_ID,
SERVICE_CLOSE_COVER,
SERVICE_OPEN_COVER,
SERVICE_SET_COVER_POSITION,
SERVICE_STOP_COVER,
STATE_UNAVAILABLE,
Platform,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from .conftest import MockConfigEntry, MockDeviceConnection, init_integration
from tests.common import async_fire_time_changed, snapshot_platform
COVER_OUTPUTS = "cover.testmodule_cover_outputs"
COVER_RELAYS = "cover.testmodule_cover_relays"
COVER_RELAYS_BS4 = "cover.testmodule_cover_relays_bs4"
COVER_RELAYS_MODULE = "cover.testmodule_cover_relays_module"
async def test_setup_lcn_cover(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
entry: MockConfigEntry,
snapshot: SnapshotAssertion,
) -> None:
"""Test the setup of cover."""
with patch("homeassistant.components.lcn.PLATFORMS", [Platform.COVER]):
await init_integration(hass, entry)
await snapshot_platform(hass, entity_registry, snapshot, entry.entry_id)
async def test_outputs_open(hass: HomeAssistant, entry: MockConfigEntry) -> None:
"""Test the outputs cover opens."""
await init_integration(hass, entry)
with patch.object(
MockDeviceConnection, "control_motor_outputs"
) as control_motor_outputs:
state = hass.states.get(COVER_OUTPUTS)
assert state is not None
assert state.state == CoverState.CLOSED
# command failed
control_motor_outputs.return_value = False
await hass.services.async_call(
DOMAIN_COVER,
SERVICE_OPEN_COVER,
{ATTR_ENTITY_ID: COVER_OUTPUTS},
blocking=True,
)
control_motor_outputs.assert_awaited_with(
MotorStateModifier.UP, MotorReverseTime.RT1200
)
state = hass.states.get(COVER_OUTPUTS)
assert state is not None
assert state.state != CoverState.OPENING
# command success
control_motor_outputs.reset_mock(return_value=True)
control_motor_outputs.return_value = True
await hass.services.async_call(
DOMAIN_COVER,
SERVICE_OPEN_COVER,
{ATTR_ENTITY_ID: COVER_OUTPUTS},
blocking=True,
)
control_motor_outputs.assert_awaited_with(
MotorStateModifier.UP, MotorReverseTime.RT1200
)
state = hass.states.get(COVER_OUTPUTS)
assert state is not None
assert state.state == CoverState.OPENING
async def test_outputs_close(hass: HomeAssistant, entry: MockConfigEntry) -> None:
"""Test the outputs cover closes."""
await init_integration(hass, entry)
with patch.object(
MockDeviceConnection, "control_motor_outputs"
) as control_motor_outputs:
await hass.services.async_call(
DOMAIN_COVER,
SERVICE_OPEN_COVER,
{ATTR_ENTITY_ID: COVER_OUTPUTS},
blocking=True,
)
# command failed
control_motor_outputs.return_value = False
await hass.services.async_call(
DOMAIN_COVER,
SERVICE_CLOSE_COVER,
{ATTR_ENTITY_ID: COVER_OUTPUTS},
blocking=True,
)
control_motor_outputs.assert_awaited_with(
MotorStateModifier.DOWN, MotorReverseTime.RT1200
)
state = hass.states.get(COVER_OUTPUTS)
assert state is not None
assert state.state != CoverState.CLOSING
# command success
control_motor_outputs.reset_mock(return_value=True)
control_motor_outputs.return_value = True
await hass.services.async_call(
DOMAIN_COVER,
SERVICE_CLOSE_COVER,
{ATTR_ENTITY_ID: COVER_OUTPUTS},
blocking=True,
)
control_motor_outputs.assert_awaited_with(
MotorStateModifier.DOWN, MotorReverseTime.RT1200
)
state = hass.states.get(COVER_OUTPUTS)
assert state is not None
assert state.state == CoverState.CLOSING
async def test_outputs_stop(hass: HomeAssistant, entry: MockConfigEntry) -> None:
"""Test the outputs cover stops."""
await init_integration(hass, entry)
with patch.object(
MockDeviceConnection, "control_motor_outputs"
) as control_motor_outputs:
await hass.services.async_call(
DOMAIN_COVER,
SERVICE_CLOSE_COVER,
{ATTR_ENTITY_ID: COVER_OUTPUTS},
blocking=True,
)
# command failed
control_motor_outputs.return_value = False
await hass.services.async_call(
DOMAIN_COVER,
SERVICE_STOP_COVER,
{ATTR_ENTITY_ID: COVER_OUTPUTS},
blocking=True,
)
control_motor_outputs.assert_awaited_with(MotorStateModifier.STOP)
state = hass.states.get(COVER_OUTPUTS)
assert state is not None
assert state.state == CoverState.CLOSING
# command success
control_motor_outputs.reset_mock(return_value=True)
control_motor_outputs.return_value = True
await hass.services.async_call(
DOMAIN_COVER,
SERVICE_STOP_COVER,
{ATTR_ENTITY_ID: COVER_OUTPUTS},
blocking=True,
)
control_motor_outputs.assert_awaited_with(MotorStateModifier.STOP)
state = hass.states.get(COVER_OUTPUTS)
assert state is not None
assert state.state not in (CoverState.CLOSING, CoverState.OPENING)
async def test_relays_open(hass: HomeAssistant, entry: MockConfigEntry) -> None:
"""Test the relays cover opens."""
await init_integration(hass, entry)
with patch.object(
MockDeviceConnection, "control_motor_relays"
) as control_motor_relays:
state = hass.states.get(COVER_RELAYS)
assert state is not None
assert state.state == CoverState.CLOSED
# command failed
control_motor_relays.return_value = False
await hass.services.async_call(
DOMAIN_COVER,
SERVICE_OPEN_COVER,
{ATTR_ENTITY_ID: COVER_RELAYS},
blocking=True,
)
control_motor_relays.assert_awaited_with(
0, MotorStateModifier.UP, MotorPositioningMode.NONE
)
state = hass.states.get(COVER_RELAYS)
assert state is not None
assert state.state != CoverState.OPENING
# command success
control_motor_relays.reset_mock(return_value=True)
control_motor_relays.return_value = True
await hass.services.async_call(
DOMAIN_COVER,
SERVICE_OPEN_COVER,
{ATTR_ENTITY_ID: COVER_RELAYS},
blocking=True,
)
control_motor_relays.assert_awaited_with(
0, MotorStateModifier.UP, MotorPositioningMode.NONE
)
state = hass.states.get(COVER_RELAYS)
assert state is not None
assert state.state == CoverState.OPENING
async def test_relays_close(hass: HomeAssistant, entry: MockConfigEntry) -> None:
"""Test the relays cover closes."""
await init_integration(hass, entry)
with patch.object(
MockDeviceConnection, "control_motor_relays"
) as control_motor_relays:
await hass.services.async_call(
DOMAIN_COVER,
SERVICE_OPEN_COVER,
{ATTR_ENTITY_ID: COVER_RELAYS},
blocking=True,
)
# command failed
control_motor_relays.return_value = False
await hass.services.async_call(
DOMAIN_COVER,
SERVICE_CLOSE_COVER,
{ATTR_ENTITY_ID: COVER_RELAYS},
blocking=True,
)
control_motor_relays.assert_awaited_with(
0, MotorStateModifier.DOWN, MotorPositioningMode.NONE
)
state = hass.states.get(COVER_RELAYS)
assert state is not None
assert state.state != CoverState.CLOSING
# command success
control_motor_relays.reset_mock(return_value=True)
control_motor_relays.return_value = True
await hass.services.async_call(
DOMAIN_COVER,
SERVICE_CLOSE_COVER,
{ATTR_ENTITY_ID: COVER_RELAYS},
blocking=True,
)
control_motor_relays.assert_awaited_with(
0, MotorStateModifier.DOWN, MotorPositioningMode.NONE
)
state = hass.states.get(COVER_RELAYS)
assert state is not None
assert state.state == CoverState.CLOSING
async def test_relays_stop(hass: HomeAssistant, entry: MockConfigEntry) -> None:
"""Test the relays cover stops."""
await init_integration(hass, entry)
with patch.object(
MockDeviceConnection, "control_motor_relays"
) as control_motor_relays:
await hass.services.async_call(
DOMAIN_COVER,
SERVICE_CLOSE_COVER,
{ATTR_ENTITY_ID: COVER_RELAYS},
blocking=True,
)
# command failed
control_motor_relays.return_value = False
await hass.services.async_call(
DOMAIN_COVER,
SERVICE_STOP_COVER,
{ATTR_ENTITY_ID: COVER_RELAYS},
blocking=True,
)
control_motor_relays.assert_awaited_with(
0, MotorStateModifier.STOP, MotorPositioningMode.NONE
)
state = hass.states.get(COVER_RELAYS)
assert state is not None
assert state.state == CoverState.CLOSING
# command success
control_motor_relays.reset_mock(return_value=True)
control_motor_relays.return_value = True
await hass.services.async_call(
DOMAIN_COVER,
SERVICE_STOP_COVER,
{ATTR_ENTITY_ID: COVER_RELAYS},
blocking=True,
)
control_motor_relays.assert_awaited_with(
0, MotorStateModifier.STOP, MotorPositioningMode.NONE
)
state = hass.states.get(COVER_RELAYS)
assert state is not None
assert state.state not in (CoverState.CLOSING, CoverState.OPENING)
@pytest.mark.parametrize(
("entity_id", "motor", "positioning_mode"),
[
(COVER_RELAYS_BS4, 1, MotorPositioningMode.BS4),
(COVER_RELAYS_MODULE, 2, MotorPositioningMode.MODULE),
],
)
async def test_relays_set_position(
hass: HomeAssistant,
entry: MockConfigEntry,
entity_id: str,
motor: int,
positioning_mode: MotorPositioningMode,
) -> None:
"""Test the relays cover moves to position."""
await init_integration(hass, entry)
with patch.object(
MockDeviceConnection, "control_motor_relays_position"
) as control_motor_relays_position:
state = hass.states.get(entity_id)
assert state is not None
assert state.state == CoverState.CLOSED
# command failed
control_motor_relays_position.return_value = False
await hass.services.async_call(
DOMAIN_COVER,
SERVICE_SET_COVER_POSITION,
{ATTR_ENTITY_ID: entity_id, ATTR_POSITION: 50},
blocking=True,
)
control_motor_relays_position.assert_awaited_with(
motor, 50, mode=positioning_mode
)
state = hass.states.get(entity_id)
assert state.state == CoverState.CLOSED
# command success
control_motor_relays_position.reset_mock(return_value=True)
control_motor_relays_position.return_value = True
await hass.services.async_call(
DOMAIN_COVER,
SERVICE_SET_COVER_POSITION,
{ATTR_ENTITY_ID: entity_id, ATTR_POSITION: 50},
blocking=True,
)
control_motor_relays_position.assert_awaited_with(
motor, 50, mode=positioning_mode
)
state = hass.states.get(entity_id)
assert state.state == CoverState.OPEN
async def test_pushed_outputs_status_change(
hass: HomeAssistant, entry: MockConfigEntry
) -> None:
"""Test the outputs cover changes its state on status received."""
await init_integration(hass, entry)
device_connection = get_device_connection(hass, (0, 7, False), entry)
address = LcnAddr(0, 7, False)
state = hass.states.get(COVER_OUTPUTS)
state.state = CoverState.CLOSED
# push status "open"
inp = ModStatusOutput(address, 0, 100)
await device_connection.async_process_input(inp)
await hass.async_block_till_done()
state = hass.states.get(COVER_OUTPUTS)
assert state is not None
assert state.state == CoverState.OPENING
# push status "stop"
inp = ModStatusOutput(address, 0, 0)
await device_connection.async_process_input(inp)
await hass.async_block_till_done()
state = hass.states.get(COVER_OUTPUTS)
assert state is not None
assert state.state not in (CoverState.OPENING, CoverState.CLOSING)
# push status "close"
inp = ModStatusOutput(address, 1, 100)
await device_connection.async_process_input(inp)
await hass.async_block_till_done()
state = hass.states.get(COVER_OUTPUTS)
assert state is not None
assert state.state == CoverState.CLOSING
async def test_pushed_relays_status_change(
hass: HomeAssistant, entry: MockConfigEntry
) -> None:
"""Test the relays cover changes its state on status received."""
await init_integration(hass, entry)
device_connection = get_device_connection(hass, (0, 7, False), entry)
address = LcnAddr(0, 7, False)
states = [False] * 8
for entity_id in (COVER_RELAYS, COVER_RELAYS_BS4, COVER_RELAYS_MODULE):
state = hass.states.get(entity_id)
state.state = CoverState.CLOSED
# push status "open"
states[0:2] = [True, False]
inp = ModStatusRelays(address, states)
await device_connection.async_process_input(inp)
await hass.async_block_till_done()
state = hass.states.get(COVER_RELAYS)
assert state is not None
assert state.state == CoverState.OPENING
# push status "stop"
states[0] = False
inp = ModStatusRelays(address, states)
await device_connection.async_process_input(inp)
await hass.async_block_till_done()
state = hass.states.get(COVER_RELAYS)
assert state is not None
assert state.state not in (CoverState.OPENING, CoverState.CLOSING)
# push status "close"
states[0:2] = [True, True]
inp = ModStatusRelays(address, states)
await device_connection.async_process_input(inp)
await hass.async_block_till_done()
state = hass.states.get(COVER_RELAYS)
assert state is not None
assert state.state == CoverState.CLOSING
# push status "set position" via BS4
inp = ModStatusMotorPositionBS4(address, 1, 50)
await device_connection.async_process_input(inp)
await hass.async_block_till_done()
state = hass.states.get(COVER_RELAYS_BS4)
assert state is not None
assert state.state == CoverState.OPEN
assert state.attributes[ATTR_CURRENT_POSITION] == 50
# push status "set position" via MODULE
inp = ModStatusMotorPositionModule(address, 2, 75)
await device_connection.async_process_input(inp)
await hass.async_block_till_done()
state = hass.states.get(COVER_RELAYS_MODULE)
assert state is not None
assert state.state == CoverState.OPEN
assert state.attributes[ATTR_CURRENT_POSITION] == 75
@pytest.mark.parametrize(
("entity_id", "request_methods", "return_values"),
[
(
COVER_OUTPUTS,
("request_status_output",),
(ModStatusOutput(LcnAddr(0, 7, False), 0, 100),),
),
(
COVER_RELAYS,
("request_status_relays",),
(ModStatusRelays(LcnAddr(0, 7, False), [False] * 8),),
),
(
COVER_RELAYS_BS4,
("request_status_relays", "request_status_motor_position"),
(
ModStatusRelays(LcnAddr(0, 7, False), [False] * 8),
ModStatusMotorPositionBS4(LcnAddr(0, 7, False), 1, 50),
),
),
],
)
async def test_availability(
hass: HomeAssistant,
freezer: FrozenDateTimeFactory,
entry: MockConfigEntry,
entity_id: str,
request_methods: list[str],
return_values: list[ModStatusOutput | ModStatusRelays],
) -> None:
"""Test the availability of cover entity."""
await init_integration(hass, entry)
state = hass.states.get(entity_id)
assert state is not None
assert state.state != STATE_UNAVAILABLE
# no response from device -> unavailable
with patch.multiple(
MockDeviceConnection,
**{
request_method: AsyncMock(return_value=None)
for request_method in request_methods
},
):
freezer.tick(SCAN_INTERVAL)
async_fire_time_changed(hass)
await hass.async_block_till_done(wait_background_tasks=True)
state = hass.states.get(entity_id)
assert state is not None
assert state.state == STATE_UNAVAILABLE
with patch.multiple(
MockDeviceConnection,
**{
request_method: AsyncMock(return_value=return_value)
for request_method, return_value in zip(
request_methods, return_values, strict=True
)
},
):
freezer.tick(SCAN_INTERVAL)
async_fire_time_changed(hass)
await hass.async_block_till_done(wait_background_tasks=True)
state = hass.states.get(entity_id)
assert state is not None
assert state.state != STATE_UNAVAILABLE
async def test_unload_config_entry(hass: HomeAssistant, entry: MockConfigEntry) -> None:
"""Test the cover is removed when the config entry is unloaded."""
await init_integration(hass, entry)
await hass.config_entries.async_unload(entry.entry_id)
assert hass.states.get(COVER_OUTPUTS).state == STATE_UNAVAILABLE
assert hass.states.get(COVER_RELAYS).state == STATE_UNAVAILABLE