core/tests/components/cover/test_reproduce_state.py
J. Nick Koston 5dc1a321dd
Rework cover reproduce_state to consider supported features (#140558)
* Handle open/closed state in reproduce_state for tilt only covers

fixes #137144

* cleanups

* cleanups

* cleanups

* cleanups

* cleanups

* cleanups

* cleanups

* cleanups

* cleanups

* cleanups

* cleanups

* cleanups

* rework

* rework

* rework

* rework

* more coverage

* more coverage

* more coverage

* more coverage

* more coverage

* more coverage

* more coverage

* more coverage

* more coverage

* more coverage

* more coverage

* back compat

* back compat

* back compat

* cleanups

* cleanups

* cleanups

* cleanups

* comments

* comments
2025-03-14 22:14:09 -04:00

560 lines
18 KiB
Python

"""Test reproduce state for Cover."""
import pytest
from homeassistant.components.cover import (
ATTR_CURRENT_POSITION,
ATTR_CURRENT_TILT_POSITION,
ATTR_POSITION,
ATTR_TILT_POSITION,
CoverEntityFeature,
CoverState,
)
from homeassistant.const import (
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,
)
from homeassistant.core import HomeAssistant, State
from homeassistant.helpers.state import async_reproduce_state
from tests.common import async_mock_service
async def test_reproducing_states(
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
) -> None:
"""Test reproducing Cover states."""
hass.states.async_set(
"cover.entity_close",
CoverState.CLOSED,
{
ATTR_SUPPORTED_FEATURES: CoverEntityFeature.CLOSE | CoverEntityFeature.OPEN,
},
)
hass.states.async_set(
"cover.closed_only_supports_close_open",
CoverState.CLOSED,
{
ATTR_SUPPORTED_FEATURES: CoverEntityFeature.CLOSE | CoverEntityFeature.OPEN,
},
)
hass.states.async_set(
"cover.open_only_supports_close_open",
CoverState.OPEN,
{
ATTR_SUPPORTED_FEATURES: CoverEntityFeature.CLOSE | CoverEntityFeature.OPEN,
},
)
hass.states.async_set(
"cover.open_missing_all_features",
CoverState.OPEN,
)
hass.states.async_set(
"cover.closed_missing_all_features_has_position",
CoverState.CLOSED,
{
ATTR_CURRENT_POSITION: 0,
},
)
hass.states.async_set(
"cover.open_missing_all_features_has_tilt_position",
CoverState.OPEN,
{
ATTR_CURRENT_TILT_POSITION: 50,
},
)
hass.states.async_set(
"cover.closed_only_supports_tilt_close_open",
CoverState.CLOSED,
{
ATTR_SUPPORTED_FEATURES: CoverEntityFeature.CLOSE_TILT
| CoverEntityFeature.OPEN_TILT,
},
)
hass.states.async_set(
"cover.open_only_supports_tilt_close_open",
CoverState.OPEN,
{
ATTR_SUPPORTED_FEATURES: CoverEntityFeature.CLOSE_TILT
| CoverEntityFeature.OPEN_TILT,
},
)
hass.states.async_set(
"cover.closed_only_supports_position",
CoverState.CLOSED,
{
ATTR_CURRENT_POSITION: 0,
ATTR_SUPPORTED_FEATURES: CoverEntityFeature.SET_POSITION,
},
)
hass.states.async_set(
"cover.open_only_supports_position",
CoverState.OPEN,
{ATTR_SUPPORTED_FEATURES: CoverEntityFeature.SET_POSITION},
)
hass.states.async_set(
"cover.entity_close_attr",
CoverState.CLOSED,
{
ATTR_CURRENT_POSITION: 0,
ATTR_CURRENT_TILT_POSITION: 0,
ATTR_SUPPORTED_FEATURES: CoverEntityFeature.CLOSE_TILT
| CoverEntityFeature.OPEN_TILT
| CoverEntityFeature.SET_POSITION
| CoverEntityFeature.SET_TILT_POSITION
| CoverEntityFeature.CLOSE
| CoverEntityFeature.OPEN,
},
)
hass.states.async_set(
"cover.entity_close_tilt",
CoverState.CLOSED,
{
ATTR_CURRENT_TILT_POSITION: 50,
ATTR_SUPPORTED_FEATURES: CoverEntityFeature.CLOSE_TILT
| CoverEntityFeature.OPEN_TILT
| CoverEntityFeature.SET_TILT_POSITION,
},
)
hass.states.async_set(
"cover.entity_open",
CoverState.OPEN,
{ATTR_SUPPORTED_FEATURES: CoverEntityFeature.CLOSE | CoverEntityFeature.OPEN},
)
hass.states.async_set(
"cover.entity_slightly_open",
CoverState.OPEN,
{
ATTR_CURRENT_POSITION: 50,
ATTR_SUPPORTED_FEATURES: CoverEntityFeature.SET_POSITION
| CoverEntityFeature.CLOSE
| CoverEntityFeature.OPEN,
},
)
hass.states.async_set(
"cover.entity_open_attr",
CoverState.OPEN,
{
ATTR_CURRENT_POSITION: 100,
ATTR_CURRENT_TILT_POSITION: 0,
ATTR_SUPPORTED_FEATURES: CoverEntityFeature.CLOSE_TILT
| CoverEntityFeature.OPEN_TILT
| CoverEntityFeature.SET_POSITION
| CoverEntityFeature.SET_TILT_POSITION
| CoverEntityFeature.CLOSE
| CoverEntityFeature.OPEN,
},
)
hass.states.async_set(
"cover.entity_open_tilt",
CoverState.OPEN,
{
ATTR_CURRENT_POSITION: 50,
ATTR_CURRENT_TILT_POSITION: 50,
ATTR_SUPPORTED_FEATURES: CoverEntityFeature.CLOSE_TILT
| CoverEntityFeature.OPEN_TILT
| CoverEntityFeature.SET_POSITION
| CoverEntityFeature.SET_TILT_POSITION
| CoverEntityFeature.CLOSE
| CoverEntityFeature.OPEN,
},
)
hass.states.async_set(
"cover.entity_entirely_open",
CoverState.OPEN,
{
ATTR_CURRENT_POSITION: 100,
ATTR_CURRENT_TILT_POSITION: 100,
ATTR_SUPPORTED_FEATURES: CoverEntityFeature.CLOSE_TILT
| CoverEntityFeature.OPEN_TILT
| CoverEntityFeature.SET_POSITION
| CoverEntityFeature.SET_TILT_POSITION
| CoverEntityFeature.CLOSE
| CoverEntityFeature.OPEN,
},
)
hass.states.async_set(
"cover.tilt_only_open",
CoverState.OPEN,
{
ATTR_SUPPORTED_FEATURES: CoverEntityFeature.CLOSE_TILT
| CoverEntityFeature.OPEN_TILT,
},
)
hass.states.async_set(
"cover.tilt_only_closed",
CoverState.CLOSED,
{
ATTR_SUPPORTED_FEATURES: CoverEntityFeature.CLOSE_TILT
| CoverEntityFeature.OPEN_TILT,
},
)
hass.states.async_set(
"cover.tilt_only_tilt_position_100",
CoverState.OPEN,
{
ATTR_CURRENT_TILT_POSITION: 100,
ATTR_SUPPORTED_FEATURES: CoverEntityFeature.CLOSE_TILT
| CoverEntityFeature.OPEN_TILT
| CoverEntityFeature.SET_TILT_POSITION,
},
)
hass.states.async_set(
"cover.tilt_only_tilt_position_0",
CoverState.CLOSED,
{
ATTR_CURRENT_TILT_POSITION: 0,
ATTR_SUPPORTED_FEATURES: CoverEntityFeature.CLOSE_TILT
| CoverEntityFeature.OPEN_TILT
| CoverEntityFeature.SET_TILT_POSITION,
},
)
hass.states.async_set(
"cover.tilt_open_only_supports_tilt_position",
CoverState.OPEN,
{
ATTR_SUPPORTED_FEATURES: CoverEntityFeature.SET_TILT_POSITION,
},
)
hass.states.async_set(
"cover.tilt_partial_open_only_supports_tilt_position",
CoverState.OPEN,
{
ATTR_SUPPORTED_FEATURES: CoverEntityFeature.SET_TILT_POSITION,
ATTR_CURRENT_TILT_POSITION: 50,
},
)
hass.states.async_set(
"cover.tilt_closed_only_supports_tilt_position",
CoverState.CLOSED,
{
ATTR_SUPPORTED_FEATURES: CoverEntityFeature.SET_TILT_POSITION,
},
)
close_calls = async_mock_service(hass, "cover", SERVICE_CLOSE_COVER)
open_calls = async_mock_service(hass, "cover", SERVICE_OPEN_COVER)
close_tilt_calls = async_mock_service(hass, "cover", SERVICE_CLOSE_COVER_TILT)
open_tilt_calls = async_mock_service(hass, "cover", SERVICE_OPEN_COVER_TILT)
position_calls = async_mock_service(hass, "cover", SERVICE_SET_COVER_POSITION)
position_tilt_calls = async_mock_service(
hass, "cover", SERVICE_SET_COVER_TILT_POSITION
)
# These calls should do nothing as entities already in desired state
await async_reproduce_state(
hass,
[
State("cover.entity_close", CoverState.CLOSED),
State("cover.closed_only_supports_close_open", CoverState.CLOSED),
State("cover.closed_only_supports_tilt_close_open", CoverState.CLOSED),
State("cover.open_only_supports_close_open", CoverState.OPEN),
State("cover.open_only_supports_tilt_close_open", CoverState.OPEN),
State("cover.open_missing_all_features", CoverState.OPEN),
State(
"cover.closed_missing_all_features_has_position",
CoverState.CLOSED,
{
ATTR_CURRENT_POSITION: 0,
},
),
State(
"cover.open_missing_all_features_has_tilt_position",
CoverState.OPEN,
{
ATTR_CURRENT_TILT_POSITION: 50,
},
),
State(
"cover.closed_only_supports_position",
CoverState.CLOSED,
{ATTR_CURRENT_POSITION: 0},
),
State("cover.open_only_supports_position", CoverState.OPEN),
State(
"cover.entity_close_attr",
CoverState.CLOSED,
{ATTR_CURRENT_POSITION: 0, ATTR_CURRENT_TILT_POSITION: 0},
),
State(
"cover.entity_close_tilt",
CoverState.CLOSED,
{ATTR_CURRENT_TILT_POSITION: 50},
),
State("cover.entity_open", CoverState.OPEN),
State(
"cover.entity_slightly_open",
CoverState.OPEN,
{ATTR_CURRENT_POSITION: 50},
),
State(
"cover.entity_open_attr",
CoverState.OPEN,
{ATTR_CURRENT_POSITION: 100, ATTR_CURRENT_TILT_POSITION: 0},
),
State(
"cover.entity_open_tilt",
CoverState.OPEN,
{ATTR_CURRENT_POSITION: 50, ATTR_CURRENT_TILT_POSITION: 50},
),
State(
"cover.entity_entirely_open",
CoverState.OPEN,
{ATTR_CURRENT_POSITION: 100, ATTR_CURRENT_TILT_POSITION: 100},
),
State(
"cover.tilt_only_open",
CoverState.OPEN,
{},
),
State(
"cover.tilt_only_tilt_position_100",
CoverState.OPEN,
{ATTR_CURRENT_TILT_POSITION: 100},
),
State(
"cover.tilt_only_closed",
CoverState.CLOSED,
{},
),
State(
"cover.tilt_only_tilt_position_0",
CoverState.CLOSED,
{ATTR_CURRENT_TILT_POSITION: 0},
),
State(
"cover.tilt_partial_open_only_supports_tilt_position",
CoverState.OPEN,
{ATTR_CURRENT_TILT_POSITION: 50},
),
State(
"cover.tilt_open_only_supports_tilt_position",
CoverState.OPEN,
),
State(
"cover.tilt_closed_only_supports_tilt_position",
CoverState.CLOSED,
),
],
)
assert len(close_calls) == 0
assert len(open_calls) == 0
assert len(close_tilt_calls) == 0
assert len(open_tilt_calls) == 0
assert len(position_calls) == 0
assert len(position_tilt_calls) == 0
# Test invalid state is handled
await async_reproduce_state(hass, [State("cover.entity_close", "not_supported")])
assert "not_supported" in caplog.text
assert len(close_calls) == 0
assert len(open_calls) == 0
assert len(close_tilt_calls) == 0
assert len(open_tilt_calls) == 0
assert len(position_calls) == 0
assert len(position_tilt_calls) == 0
# Make sure correct services are called
await async_reproduce_state(
hass,
[
State("cover.entity_close", CoverState.OPEN),
State(
"cover.closed_only_supports_close_open",
CoverState.OPEN,
{ATTR_CURRENT_POSITION: 100},
),
State(
"cover.open_only_supports_close_open",
CoverState.CLOSED,
{ATTR_CURRENT_POSITION: 50},
),
State(
"cover.open_only_supports_tilt_close_open",
CoverState.CLOSED,
{ATTR_CURRENT_TILT_POSITION: 50},
),
State("cover.closed_only_supports_tilt_close_open", CoverState.OPEN),
State("cover.open_missing_all_features", CoverState.CLOSED),
State(
"cover.closed_missing_all_features_has_position",
CoverState.OPEN,
{ATTR_CURRENT_POSITION: 70},
),
State(
"cover.open_missing_all_features_has_tilt_position",
CoverState.OPEN,
{ATTR_CURRENT_TILT_POSITION: 20},
),
State("cover.closed_only_supports_position", CoverState.OPEN),
State("cover.open_only_supports_position", CoverState.CLOSED),
State(
"cover.entity_close_attr",
CoverState.OPEN,
{ATTR_CURRENT_POSITION: 50, ATTR_CURRENT_TILT_POSITION: 50},
),
State(
"cover.entity_close_tilt",
CoverState.CLOSED,
{ATTR_CURRENT_TILT_POSITION: 100},
),
State("cover.entity_open", CoverState.CLOSED),
State("cover.entity_slightly_open", CoverState.OPEN, {}),
State("cover.entity_open_attr", CoverState.CLOSED, {}),
State(
"cover.entity_open_tilt",
CoverState.OPEN,
{ATTR_CURRENT_TILT_POSITION: 0},
),
State(
"cover.entity_entirely_open",
CoverState.CLOSED,
{ATTR_CURRENT_POSITION: 0, ATTR_CURRENT_TILT_POSITION: 0},
),
# Should not raise
State("cover.non_existing", "on"),
State(
"cover.tilt_only_open",
CoverState.CLOSED,
{},
),
State(
"cover.tilt_only_tilt_position_100",
CoverState.CLOSED,
{ATTR_CURRENT_TILT_POSITION: 0},
),
State(
"cover.tilt_only_closed",
CoverState.OPEN,
{},
),
State(
"cover.tilt_only_tilt_position_0",
CoverState.OPEN,
{ATTR_CURRENT_TILT_POSITION: 100},
),
State(
"cover.tilt_partial_open_only_supports_tilt_position",
CoverState.OPEN,
{ATTR_CURRENT_TILT_POSITION: 70},
),
State(
"cover.tilt_open_only_supports_tilt_position",
CoverState.CLOSED,
),
State(
"cover.tilt_closed_only_supports_tilt_position",
CoverState.OPEN,
),
],
)
valid_close_calls = [
{"entity_id": "cover.entity_open"},
{"entity_id": "cover.entity_open_attr"},
{"entity_id": "cover.entity_entirely_open"},
{"entity_id": "cover.open_only_supports_close_open"},
{"entity_id": "cover.open_missing_all_features"},
]
assert len(close_calls) == len(valid_close_calls)
for call in close_calls:
assert call.domain == "cover"
assert call.data in valid_close_calls
valid_close_calls.remove(call.data)
valid_open_calls = [
{"entity_id": "cover.entity_close"},
{"entity_id": "cover.entity_slightly_open"},
{"entity_id": "cover.entity_open_tilt"},
{"entity_id": "cover.closed_only_supports_close_open"},
]
assert len(open_calls) == len(valid_open_calls)
for call in open_calls:
assert call.domain == "cover"
assert call.data in valid_open_calls
valid_open_calls.remove(call.data)
valid_close_tilt_calls = [
{"entity_id": "cover.entity_open_tilt"},
{"entity_id": "cover.entity_entirely_open"},
{"entity_id": "cover.tilt_only_open"},
{"entity_id": "cover.entity_open_attr"},
{"entity_id": "cover.tilt_only_tilt_position_100"},
{"entity_id": "cover.open_only_supports_tilt_close_open"},
]
assert len(close_tilt_calls) == len(valid_close_tilt_calls)
for call in close_tilt_calls:
assert call.domain == "cover"
assert call.data in valid_close_tilt_calls
valid_close_tilt_calls.remove(call.data)
valid_open_tilt_calls = [
{"entity_id": "cover.entity_close_tilt"},
{"entity_id": "cover.tilt_only_closed"},
{"entity_id": "cover.tilt_only_tilt_position_0"},
{"entity_id": "cover.closed_only_supports_tilt_close_open"},
]
assert len(open_tilt_calls) == len(valid_open_tilt_calls)
for call in open_tilt_calls:
assert call.domain == "cover"
assert call.data in valid_open_tilt_calls
valid_open_tilt_calls.remove(call.data)
valid_position_calls = [
{
"entity_id": "cover.entity_close_attr",
ATTR_POSITION: 50,
},
{
"entity_id": "cover.closed_missing_all_features_has_position",
ATTR_POSITION: 70,
},
{
"entity_id": "cover.closed_only_supports_position",
ATTR_POSITION: 100,
},
{
"entity_id": "cover.open_only_supports_position",
ATTR_POSITION: 0,
},
]
assert len(position_calls) == len(valid_position_calls)
for call in position_calls:
assert call.domain == "cover"
assert call.data in valid_position_calls
valid_position_calls.remove(call.data)
valid_position_tilt_calls = [
{
"entity_id": "cover.entity_close_attr",
ATTR_TILT_POSITION: 50,
},
{
"entity_id": "cover.open_missing_all_features_has_tilt_position",
ATTR_TILT_POSITION: 20,
},
{
"entity_id": "cover.tilt_open_only_supports_tilt_position",
ATTR_TILT_POSITION: 0,
},
{
"entity_id": "cover.tilt_closed_only_supports_tilt_position",
ATTR_TILT_POSITION: 100,
},
{
"entity_id": "cover.tilt_partial_open_only_supports_tilt_position",
ATTR_TILT_POSITION: 70,
},
]
assert len(position_tilt_calls) == len(valid_position_tilt_calls)
for call in position_tilt_calls:
assert call.domain == "cover"
assert call.data in valid_position_tilt_calls
valid_position_tilt_calls.remove(call.data)