1017 lines
29 KiB
Python
1017 lines
29 KiB
Python
"""The tests for the Template lock platform."""
|
|
|
|
from typing import Any
|
|
|
|
import pytest
|
|
|
|
from homeassistant import setup
|
|
from homeassistant.components import lock, template
|
|
from homeassistant.components.lock import LockEntityFeature, LockState
|
|
from homeassistant.const import (
|
|
ATTR_CODE,
|
|
ATTR_ENTITY_ID,
|
|
STATE_OFF,
|
|
STATE_ON,
|
|
STATE_OPEN,
|
|
STATE_UNAVAILABLE,
|
|
)
|
|
from homeassistant.core import HomeAssistant, ServiceCall
|
|
from homeassistant.helpers import entity_registry as er
|
|
from homeassistant.setup import async_setup_component
|
|
|
|
from .conftest import ConfigurationStyle
|
|
|
|
from tests.common import assert_setup_component
|
|
|
|
TEST_OBJECT_ID = "test_template_lock"
|
|
TEST_ENTITY_ID = f"lock.{TEST_OBJECT_ID}"
|
|
TEST_STATE_ENTITY_ID = "switch.test_state"
|
|
|
|
LOCK_ACTION = {
|
|
"lock": {
|
|
"service": "test.automation",
|
|
"data_template": {
|
|
"action": "lock",
|
|
"caller": "{{ this.entity_id }}",
|
|
"code": "{{ code if code is defined else None }}",
|
|
},
|
|
},
|
|
}
|
|
UNLOCK_ACTION = {
|
|
"unlock": {
|
|
"service": "test.automation",
|
|
"data_template": {
|
|
"action": "unlock",
|
|
"caller": "{{ this.entity_id }}",
|
|
"code": "{{ code if code is defined else None }}",
|
|
},
|
|
},
|
|
}
|
|
OPEN_ACTION = {
|
|
"open": {
|
|
"service": "test.automation",
|
|
"data_template": {
|
|
"action": "open",
|
|
"caller": "{{ this.entity_id }}",
|
|
},
|
|
},
|
|
}
|
|
|
|
|
|
OPTIMISTIC_LOCK = {
|
|
**LOCK_ACTION,
|
|
**UNLOCK_ACTION,
|
|
}
|
|
|
|
|
|
OPTIMISTIC_LOCK_CONFIG = {
|
|
"platform": "template",
|
|
**LOCK_ACTION,
|
|
**UNLOCK_ACTION,
|
|
**OPEN_ACTION,
|
|
}
|
|
|
|
OPTIMISTIC_CODED_LOCK_CONFIG = {
|
|
"platform": "template",
|
|
**LOCK_ACTION,
|
|
**UNLOCK_ACTION,
|
|
}
|
|
|
|
|
|
async def async_setup_legacy_format(
|
|
hass: HomeAssistant, count: int, lock_config: dict[str, Any]
|
|
) -> None:
|
|
"""Do setup of lock integration via legacy format."""
|
|
config = {"lock": {"platform": "template", "name": TEST_OBJECT_ID, **lock_config}}
|
|
|
|
with assert_setup_component(count, lock.DOMAIN):
|
|
assert await async_setup_component(
|
|
hass,
|
|
lock.DOMAIN,
|
|
config,
|
|
)
|
|
await hass.async_block_till_done()
|
|
await hass.async_start()
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
async def async_setup_modern_format(
|
|
hass: HomeAssistant, count: int, lock_config: dict[str, Any]
|
|
) -> None:
|
|
"""Do setup of lock integration via modern format."""
|
|
config = {"template": {"lock": {"name": TEST_OBJECT_ID, **lock_config}}}
|
|
|
|
with assert_setup_component(count, template.DOMAIN):
|
|
assert await async_setup_component(
|
|
hass,
|
|
template.DOMAIN,
|
|
config,
|
|
)
|
|
|
|
await hass.async_block_till_done()
|
|
await hass.async_start()
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
@pytest.fixture
|
|
async def setup_lock(
|
|
hass: HomeAssistant,
|
|
count: int,
|
|
style: ConfigurationStyle,
|
|
lock_config: dict[str, Any],
|
|
) -> None:
|
|
"""Do setup of lock integration."""
|
|
if style == ConfigurationStyle.LEGACY:
|
|
await async_setup_legacy_format(hass, count, lock_config)
|
|
elif style == ConfigurationStyle.MODERN:
|
|
await async_setup_modern_format(hass, count, lock_config)
|
|
|
|
|
|
@pytest.fixture
|
|
async def setup_base_lock(
|
|
hass: HomeAssistant,
|
|
count: int,
|
|
style: ConfigurationStyle,
|
|
state_template: str,
|
|
extra_config: dict,
|
|
):
|
|
"""Do setup of cover integration using a state template."""
|
|
if style == ConfigurationStyle.LEGACY:
|
|
await async_setup_legacy_format(
|
|
hass,
|
|
count,
|
|
{"value_template": state_template, **extra_config},
|
|
)
|
|
elif style == ConfigurationStyle.MODERN:
|
|
await async_setup_modern_format(
|
|
hass,
|
|
count,
|
|
{"state": state_template, **extra_config},
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
async def setup_state_lock(
|
|
hass: HomeAssistant,
|
|
count: int,
|
|
style: ConfigurationStyle,
|
|
state_template: str,
|
|
):
|
|
"""Do setup of cover integration using a state template."""
|
|
if style == ConfigurationStyle.LEGACY:
|
|
await async_setup_legacy_format(
|
|
hass,
|
|
count,
|
|
{
|
|
**OPTIMISTIC_LOCK,
|
|
"value_template": state_template,
|
|
},
|
|
)
|
|
elif style == ConfigurationStyle.MODERN:
|
|
await async_setup_modern_format(
|
|
hass,
|
|
count,
|
|
{
|
|
**OPTIMISTIC_LOCK,
|
|
"state": state_template,
|
|
},
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
async def setup_state_lock_with_extra_config(
|
|
hass: HomeAssistant,
|
|
count: int,
|
|
style: ConfigurationStyle,
|
|
state_template: str,
|
|
extra_config: dict,
|
|
):
|
|
"""Do setup of cover integration using a state template."""
|
|
if style == ConfigurationStyle.LEGACY:
|
|
await async_setup_legacy_format(
|
|
hass,
|
|
count,
|
|
{**OPTIMISTIC_LOCK, "value_template": state_template, **extra_config},
|
|
)
|
|
elif style == ConfigurationStyle.MODERN:
|
|
await async_setup_modern_format(
|
|
hass,
|
|
count,
|
|
{**OPTIMISTIC_LOCK, "state": state_template, **extra_config},
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
async def setup_state_lock_with_attribute(
|
|
hass: HomeAssistant,
|
|
count: int,
|
|
style: ConfigurationStyle,
|
|
state_template: str,
|
|
attribute: str,
|
|
attribute_template: str,
|
|
):
|
|
"""Do setup of cover integration using a state template."""
|
|
extra = {attribute: attribute_template} if attribute else {}
|
|
if style == ConfigurationStyle.LEGACY:
|
|
await async_setup_legacy_format(
|
|
hass,
|
|
count,
|
|
{
|
|
**OPTIMISTIC_LOCK,
|
|
"value_template": state_template,
|
|
**extra,
|
|
},
|
|
)
|
|
elif style == ConfigurationStyle.MODERN:
|
|
await async_setup_modern_format(
|
|
hass,
|
|
count,
|
|
{**OPTIMISTIC_LOCK, "state": state_template, **extra},
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("count", "state_template"), [(1, "{{ states.switch.test_state.state }}")]
|
|
)
|
|
@pytest.mark.parametrize(
|
|
"style", [ConfigurationStyle.LEGACY, ConfigurationStyle.MODERN]
|
|
)
|
|
@pytest.mark.usefixtures("setup_state_lock")
|
|
async def test_template_state(hass: HomeAssistant) -> None:
|
|
"""Test template."""
|
|
hass.states.async_set(TEST_STATE_ENTITY_ID, STATE_ON)
|
|
await hass.async_block_till_done()
|
|
|
|
state = hass.states.get("lock.test_template_lock")
|
|
assert state.state == LockState.LOCKED
|
|
|
|
hass.states.async_set(TEST_STATE_ENTITY_ID, STATE_OFF)
|
|
await hass.async_block_till_done()
|
|
|
|
state = hass.states.get("lock.test_template_lock")
|
|
assert state.state == LockState.UNLOCKED
|
|
|
|
hass.states.async_set(TEST_STATE_ENTITY_ID, STATE_OPEN)
|
|
await hass.async_block_till_done()
|
|
|
|
state = hass.states.get("lock.test_template_lock")
|
|
assert state.state == LockState.OPEN
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("count", "state_template", "extra_config"),
|
|
[(1, "{{ states.switch.test_state.state }}", {"optimistic": True, **OPEN_ACTION})],
|
|
)
|
|
@pytest.mark.parametrize(
|
|
"style", [ConfigurationStyle.LEGACY, ConfigurationStyle.MODERN]
|
|
)
|
|
@pytest.mark.usefixtures("setup_state_lock_with_extra_config")
|
|
async def test_open_lock_optimistic(
|
|
hass: HomeAssistant, calls: list[ServiceCall]
|
|
) -> None:
|
|
"""Test optimistic open."""
|
|
hass.states.async_set(TEST_STATE_ENTITY_ID, STATE_ON)
|
|
await hass.async_block_till_done()
|
|
|
|
state = hass.states.get(TEST_ENTITY_ID)
|
|
assert state.state == LockState.LOCKED
|
|
|
|
await hass.services.async_call(
|
|
lock.DOMAIN,
|
|
lock.SERVICE_OPEN,
|
|
{ATTR_ENTITY_ID: TEST_ENTITY_ID},
|
|
)
|
|
await hass.async_block_till_done()
|
|
|
|
assert len(calls) == 1
|
|
assert calls[0].data["action"] == "open"
|
|
assert calls[0].data["caller"] == TEST_ENTITY_ID
|
|
|
|
state = hass.states.get(TEST_ENTITY_ID)
|
|
assert state.state == LockState.OPEN
|
|
|
|
|
|
@pytest.mark.parametrize(("count", "state_template"), [(1, "{{ 1 == 1 }}")])
|
|
@pytest.mark.parametrize(
|
|
"style", [ConfigurationStyle.LEGACY, ConfigurationStyle.MODERN]
|
|
)
|
|
@pytest.mark.usefixtures("setup_state_lock")
|
|
async def test_template_state_boolean_on(hass: HomeAssistant) -> None:
|
|
"""Test the setting of the state with boolean on."""
|
|
state = hass.states.get(TEST_ENTITY_ID)
|
|
assert state.state == LockState.LOCKED
|
|
|
|
|
|
@pytest.mark.parametrize(("count", "state_template"), [(1, "{{ 1 == 2 }}")])
|
|
@pytest.mark.parametrize(
|
|
"style", [ConfigurationStyle.LEGACY, ConfigurationStyle.MODERN]
|
|
)
|
|
@pytest.mark.usefixtures("setup_state_lock")
|
|
async def test_template_state_boolean_off(hass: HomeAssistant) -> None:
|
|
"""Test the setting of the state with off."""
|
|
state = hass.states.get(TEST_ENTITY_ID)
|
|
assert state.state == LockState.UNLOCKED
|
|
|
|
|
|
@pytest.mark.parametrize("count", [0])
|
|
@pytest.mark.parametrize(
|
|
"style", [ConfigurationStyle.LEGACY, ConfigurationStyle.MODERN]
|
|
)
|
|
@pytest.mark.parametrize(
|
|
("state_template", "extra_config"),
|
|
[
|
|
("{% if rubbish %}", OPTIMISTIC_LOCK),
|
|
("{{ rubbish }", OPTIMISTIC_LOCK),
|
|
("Invalid", {}),
|
|
(
|
|
"{{ 1==1 }}",
|
|
{
|
|
"not_value_template": "{{ states.switch.test_state.state }}",
|
|
**OPTIMISTIC_LOCK,
|
|
},
|
|
),
|
|
],
|
|
)
|
|
@pytest.mark.usefixtures("setup_base_lock")
|
|
async def test_template_syntax_error(hass: HomeAssistant) -> None:
|
|
"""Test templating syntax errors don't create entities."""
|
|
assert hass.states.async_all("lock") == []
|
|
|
|
|
|
@pytest.mark.parametrize(("count", "state_template"), [(0, "{{ 1==1 }}")])
|
|
@pytest.mark.parametrize("attribute_template", ["{{ rubbish }", "{% if rubbish %}"])
|
|
@pytest.mark.parametrize(
|
|
("style", "attribute"),
|
|
[
|
|
(ConfigurationStyle.LEGACY, "code_format_template"),
|
|
(ConfigurationStyle.MODERN, "code_format"),
|
|
],
|
|
)
|
|
@pytest.mark.usefixtures("setup_state_lock_with_attribute")
|
|
async def test_template_code_template_syntax_error(hass: HomeAssistant) -> None:
|
|
"""Test templating code_format syntax errors don't create entities."""
|
|
assert hass.states.async_all("lock") == []
|
|
|
|
|
|
@pytest.mark.parametrize(("count", "state_template"), [(1, "{{ 1 + 1 }}")])
|
|
@pytest.mark.parametrize(
|
|
"style", [ConfigurationStyle.LEGACY, ConfigurationStyle.MODERN]
|
|
)
|
|
@pytest.mark.usefixtures("setup_state_lock")
|
|
async def test_template_static(hass: HomeAssistant) -> None:
|
|
"""Test that we allow static templates."""
|
|
state = hass.states.get(TEST_ENTITY_ID)
|
|
assert state.state == LockState.UNLOCKED
|
|
|
|
hass.states.async_set(TEST_ENTITY_ID, LockState.LOCKED)
|
|
await hass.async_block_till_done()
|
|
state = hass.states.get(TEST_ENTITY_ID)
|
|
assert state.state == LockState.LOCKED
|
|
|
|
|
|
@pytest.mark.parametrize("count", [1])
|
|
@pytest.mark.parametrize(
|
|
"style", [ConfigurationStyle.LEGACY, ConfigurationStyle.MODERN]
|
|
)
|
|
@pytest.mark.parametrize(
|
|
("state_template", "expected"),
|
|
[
|
|
("{{ True }}", LockState.LOCKED),
|
|
("{{ False }}", LockState.UNLOCKED),
|
|
("{{ x - 12 }}", STATE_UNAVAILABLE),
|
|
],
|
|
)
|
|
@pytest.mark.usefixtures("setup_state_lock")
|
|
async def test_state_template(hass: HomeAssistant, expected: str) -> None:
|
|
"""Test state and value_template template."""
|
|
state = hass.states.get(TEST_ENTITY_ID)
|
|
assert state.state == expected
|
|
|
|
|
|
@pytest.mark.parametrize(("count", "state_template"), [(1, "{{ 1==1 }}")])
|
|
@pytest.mark.parametrize(
|
|
"attribute_template",
|
|
["{% if states.switch.test_state.state %}/local/switch.png{% endif %}"],
|
|
)
|
|
@pytest.mark.parametrize(
|
|
("style", "attribute"),
|
|
[
|
|
(ConfigurationStyle.MODERN, "picture"),
|
|
],
|
|
)
|
|
@pytest.mark.usefixtures("setup_state_lock_with_attribute")
|
|
async def test_picture_template(hass: HomeAssistant) -> None:
|
|
"""Test entity_picture template."""
|
|
state = hass.states.get(TEST_ENTITY_ID)
|
|
assert state.attributes.get("entity_picture") in ("", None)
|
|
|
|
hass.states.async_set(TEST_STATE_ENTITY_ID, STATE_ON)
|
|
await hass.async_block_till_done()
|
|
|
|
state = hass.states.get(TEST_ENTITY_ID)
|
|
assert state.attributes["entity_picture"] == "/local/switch.png"
|
|
|
|
|
|
@pytest.mark.parametrize(("count", "state_template"), [(1, "{{ 1==1 }}")])
|
|
@pytest.mark.parametrize(
|
|
"attribute_template",
|
|
["{% if states.switch.test_state.state %}mdi:eye{% endif %}"],
|
|
)
|
|
@pytest.mark.parametrize(
|
|
("style", "attribute"),
|
|
[
|
|
(ConfigurationStyle.MODERN, "icon"),
|
|
],
|
|
)
|
|
@pytest.mark.usefixtures("setup_state_lock_with_attribute")
|
|
async def test_icon_template(hass: HomeAssistant) -> None:
|
|
"""Test entity_picture template."""
|
|
state = hass.states.get(TEST_ENTITY_ID)
|
|
assert state.attributes.get("icon") in ("", None)
|
|
|
|
hass.states.async_set(TEST_STATE_ENTITY_ID, STATE_ON)
|
|
await hass.async_block_till_done()
|
|
|
|
state = hass.states.get(TEST_ENTITY_ID)
|
|
assert state.attributes["icon"] == "mdi:eye"
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("count", "state_template"), [(1, "{{ states.switch.test_state.state }}")]
|
|
)
|
|
@pytest.mark.parametrize(
|
|
"style", [ConfigurationStyle.LEGACY, ConfigurationStyle.MODERN]
|
|
)
|
|
@pytest.mark.usefixtures("setup_state_lock")
|
|
async def test_lock_action(hass: HomeAssistant, calls: list[ServiceCall]) -> None:
|
|
"""Test lock action."""
|
|
hass.states.async_set(TEST_STATE_ENTITY_ID, STATE_OFF)
|
|
await hass.async_block_till_done()
|
|
|
|
state = hass.states.get(TEST_ENTITY_ID)
|
|
assert state.state == LockState.UNLOCKED
|
|
|
|
await hass.services.async_call(
|
|
lock.DOMAIN,
|
|
lock.SERVICE_LOCK,
|
|
{ATTR_ENTITY_ID: TEST_ENTITY_ID},
|
|
)
|
|
await hass.async_block_till_done()
|
|
|
|
assert len(calls) == 1
|
|
assert calls[0].data["action"] == "lock"
|
|
assert calls[0].data["caller"] == TEST_ENTITY_ID
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("count", "state_template"), [(1, "{{ states.switch.test_state.state }}")]
|
|
)
|
|
@pytest.mark.parametrize(
|
|
"style", [ConfigurationStyle.LEGACY, ConfigurationStyle.MODERN]
|
|
)
|
|
@pytest.mark.usefixtures("setup_state_lock")
|
|
async def test_unlock_action(hass: HomeAssistant, calls: list[ServiceCall]) -> None:
|
|
"""Test unlock action."""
|
|
hass.states.async_set(TEST_STATE_ENTITY_ID, STATE_ON)
|
|
await hass.async_block_till_done()
|
|
|
|
state = hass.states.get(TEST_ENTITY_ID)
|
|
assert state.state == LockState.LOCKED
|
|
|
|
await hass.services.async_call(
|
|
lock.DOMAIN,
|
|
lock.SERVICE_UNLOCK,
|
|
{ATTR_ENTITY_ID: TEST_ENTITY_ID},
|
|
)
|
|
await hass.async_block_till_done()
|
|
|
|
assert len(calls) == 1
|
|
assert calls[0].data["action"] == "unlock"
|
|
assert calls[0].data["caller"] == TEST_ENTITY_ID
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("count", "state_template", "extra_config"),
|
|
[(1, "{{ states.switch.test_state.state }}", OPEN_ACTION)],
|
|
)
|
|
@pytest.mark.parametrize(
|
|
"style", [ConfigurationStyle.LEGACY, ConfigurationStyle.MODERN]
|
|
)
|
|
@pytest.mark.usefixtures("setup_state_lock_with_extra_config")
|
|
async def test_open_action(hass: HomeAssistant, calls: list[ServiceCall]) -> None:
|
|
"""Test open action."""
|
|
hass.states.async_set(TEST_STATE_ENTITY_ID, STATE_ON)
|
|
await hass.async_block_till_done()
|
|
|
|
state = hass.states.get(TEST_ENTITY_ID)
|
|
assert state.state == LockState.LOCKED
|
|
|
|
await hass.services.async_call(
|
|
lock.DOMAIN,
|
|
lock.SERVICE_OPEN,
|
|
{ATTR_ENTITY_ID: TEST_ENTITY_ID},
|
|
)
|
|
await hass.async_block_till_done()
|
|
|
|
assert len(calls) == 1
|
|
assert calls[0].data["action"] == "open"
|
|
assert calls[0].data["caller"] == TEST_ENTITY_ID
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("count", "state_template", "attribute_template"),
|
|
[
|
|
(
|
|
1,
|
|
"{{ states.switch.test_state.state }}",
|
|
"{{ '.+' }}",
|
|
)
|
|
],
|
|
)
|
|
@pytest.mark.parametrize(
|
|
("style", "attribute"),
|
|
[
|
|
(ConfigurationStyle.LEGACY, "code_format_template"),
|
|
(ConfigurationStyle.MODERN, "code_format"),
|
|
],
|
|
)
|
|
@pytest.mark.usefixtures("setup_state_lock_with_attribute")
|
|
async def test_lock_action_with_code(
|
|
hass: HomeAssistant, calls: list[ServiceCall]
|
|
) -> None:
|
|
"""Test lock action with defined code format and supplied lock code."""
|
|
hass.states.async_set(TEST_STATE_ENTITY_ID, STATE_OFF)
|
|
await hass.async_block_till_done()
|
|
|
|
state = hass.states.get(TEST_ENTITY_ID)
|
|
assert state.state == LockState.UNLOCKED
|
|
|
|
await hass.services.async_call(
|
|
lock.DOMAIN,
|
|
lock.SERVICE_LOCK,
|
|
{ATTR_ENTITY_ID: TEST_ENTITY_ID, ATTR_CODE: "LOCK_CODE"},
|
|
)
|
|
await hass.async_block_till_done()
|
|
|
|
assert len(calls) == 1
|
|
assert calls[0].data["action"] == "lock"
|
|
assert calls[0].data["caller"] == TEST_ENTITY_ID
|
|
assert calls[0].data["code"] == "LOCK_CODE"
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("count", "state_template", "attribute_template"),
|
|
[
|
|
(
|
|
1,
|
|
"{{ states.switch.test_state.state }}",
|
|
"{{ '.+' }}",
|
|
)
|
|
],
|
|
)
|
|
@pytest.mark.parametrize(
|
|
("style", "attribute"),
|
|
[
|
|
(ConfigurationStyle.LEGACY, "code_format_template"),
|
|
(ConfigurationStyle.MODERN, "code_format"),
|
|
],
|
|
)
|
|
@pytest.mark.usefixtures("setup_state_lock_with_attribute")
|
|
async def test_unlock_action_with_code(
|
|
hass: HomeAssistant, calls: list[ServiceCall]
|
|
) -> None:
|
|
"""Test unlock action with code format and supplied unlock code."""
|
|
await setup.async_setup_component(hass, "switch", {})
|
|
hass.states.async_set(TEST_STATE_ENTITY_ID, STATE_ON)
|
|
await hass.async_block_till_done()
|
|
|
|
state = hass.states.get(TEST_ENTITY_ID)
|
|
assert state.state == LockState.LOCKED
|
|
|
|
await hass.services.async_call(
|
|
lock.DOMAIN,
|
|
lock.SERVICE_UNLOCK,
|
|
{ATTR_ENTITY_ID: TEST_ENTITY_ID, ATTR_CODE: "UNLOCK_CODE"},
|
|
)
|
|
await hass.async_block_till_done()
|
|
|
|
assert len(calls) == 1
|
|
assert calls[0].data["action"] == "unlock"
|
|
assert calls[0].data["caller"] == TEST_ENTITY_ID
|
|
assert calls[0].data["code"] == "UNLOCK_CODE"
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("count", "state_template", "attribute_template"),
|
|
[
|
|
(
|
|
1,
|
|
"{{ 1 == 1 }}",
|
|
"{{ '\\\\d+' }}",
|
|
)
|
|
],
|
|
)
|
|
@pytest.mark.parametrize(
|
|
("style", "attribute"),
|
|
[
|
|
(ConfigurationStyle.LEGACY, "code_format_template"),
|
|
(ConfigurationStyle.MODERN, "code_format"),
|
|
],
|
|
)
|
|
@pytest.mark.parametrize(
|
|
"test_action",
|
|
[
|
|
lock.SERVICE_LOCK,
|
|
lock.SERVICE_UNLOCK,
|
|
],
|
|
)
|
|
@pytest.mark.usefixtures("setup_state_lock_with_attribute")
|
|
async def test_lock_actions_fail_with_invalid_code(
|
|
hass: HomeAssistant, calls: list[ServiceCall], test_action
|
|
) -> None:
|
|
"""Test invalid lock codes."""
|
|
await hass.services.async_call(
|
|
lock.DOMAIN,
|
|
test_action,
|
|
{ATTR_ENTITY_ID: TEST_ENTITY_ID, ATTR_CODE: "non-number-value"},
|
|
)
|
|
await hass.services.async_call(
|
|
lock.DOMAIN,
|
|
test_action,
|
|
{ATTR_ENTITY_ID: TEST_ENTITY_ID},
|
|
)
|
|
await hass.async_block_till_done()
|
|
|
|
assert len(calls) == 0
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("count", "state_template", "attribute_template"),
|
|
[
|
|
(
|
|
1,
|
|
"{{ 1 == 1 }}",
|
|
"{{ 1/0 }}",
|
|
)
|
|
],
|
|
)
|
|
@pytest.mark.parametrize(
|
|
("style", "attribute"),
|
|
[
|
|
(ConfigurationStyle.LEGACY, "code_format_template"),
|
|
(ConfigurationStyle.MODERN, "code_format"),
|
|
],
|
|
)
|
|
@pytest.mark.usefixtures("setup_state_lock_with_attribute")
|
|
async def test_lock_actions_dont_execute_with_code_template_rendering_error(
|
|
hass: HomeAssistant, calls: list[ServiceCall]
|
|
) -> None:
|
|
"""Test lock code format rendering fails block lock/unlock actions."""
|
|
await hass.services.async_call(
|
|
lock.DOMAIN,
|
|
lock.SERVICE_LOCK,
|
|
{ATTR_ENTITY_ID: TEST_ENTITY_ID},
|
|
)
|
|
await hass.services.async_call(
|
|
lock.DOMAIN,
|
|
lock.SERVICE_UNLOCK,
|
|
{ATTR_ENTITY_ID: TEST_ENTITY_ID, ATTR_CODE: "any-value"},
|
|
)
|
|
await hass.async_block_till_done()
|
|
|
|
assert len(calls) == 0
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("count", "state_template", "attribute_template"),
|
|
[
|
|
(
|
|
1,
|
|
"{{ states.switch.test_state.state }}",
|
|
"{{ None }}",
|
|
)
|
|
],
|
|
)
|
|
@pytest.mark.parametrize(
|
|
("style", "attribute"),
|
|
[
|
|
(ConfigurationStyle.LEGACY, "code_format_template"),
|
|
(ConfigurationStyle.MODERN, "code_format"),
|
|
],
|
|
)
|
|
@pytest.mark.parametrize("action", [lock.SERVICE_LOCK, lock.SERVICE_UNLOCK])
|
|
@pytest.mark.usefixtures("setup_state_lock_with_attribute")
|
|
async def test_actions_with_none_as_codeformat_ignores_code(
|
|
hass: HomeAssistant, action, calls: list[ServiceCall]
|
|
) -> None:
|
|
"""Test lock actions with supplied lock code."""
|
|
hass.states.async_set(TEST_STATE_ENTITY_ID, STATE_OFF)
|
|
await hass.async_block_till_done()
|
|
|
|
state = hass.states.get(TEST_ENTITY_ID)
|
|
assert state.state == LockState.UNLOCKED
|
|
|
|
await hass.services.async_call(
|
|
lock.DOMAIN,
|
|
action,
|
|
{ATTR_ENTITY_ID: TEST_ENTITY_ID, ATTR_CODE: "any code"},
|
|
)
|
|
await hass.async_block_till_done()
|
|
|
|
assert len(calls) == 1
|
|
assert calls[0].data["action"] == action
|
|
assert calls[0].data["caller"] == TEST_ENTITY_ID
|
|
assert calls[0].data["code"] == "any code"
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("count", "state_template", "attribute_template"),
|
|
[
|
|
(
|
|
1,
|
|
"{{ states.switch.test_state.state }}",
|
|
"[12]{1",
|
|
)
|
|
],
|
|
)
|
|
@pytest.mark.parametrize(
|
|
("style", "attribute"),
|
|
[
|
|
(ConfigurationStyle.LEGACY, "code_format_template"),
|
|
(ConfigurationStyle.MODERN, "code_format"),
|
|
],
|
|
)
|
|
@pytest.mark.parametrize("action", [lock.SERVICE_LOCK, lock.SERVICE_UNLOCK])
|
|
@pytest.mark.usefixtures("setup_state_lock_with_attribute")
|
|
async def test_actions_with_invalid_regexp_as_codeformat_never_execute(
|
|
hass: HomeAssistant, action, calls: list[ServiceCall]
|
|
) -> None:
|
|
"""Test lock actions don't execute with invalid regexp."""
|
|
hass.states.async_set(TEST_STATE_ENTITY_ID, STATE_OFF)
|
|
await hass.async_block_till_done()
|
|
|
|
state = hass.states.get(TEST_ENTITY_ID)
|
|
assert state.state == LockState.UNLOCKED
|
|
|
|
await hass.services.async_call(
|
|
lock.DOMAIN,
|
|
action,
|
|
{ATTR_ENTITY_ID: TEST_ENTITY_ID, ATTR_CODE: "1"},
|
|
)
|
|
await hass.services.async_call(
|
|
lock.DOMAIN,
|
|
action,
|
|
{ATTR_ENTITY_ID: TEST_ENTITY_ID, ATTR_CODE: "x"},
|
|
)
|
|
await hass.services.async_call(
|
|
lock.DOMAIN,
|
|
action,
|
|
{ATTR_ENTITY_ID: TEST_ENTITY_ID},
|
|
)
|
|
await hass.async_block_till_done()
|
|
|
|
assert len(calls) == 0
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("count", "state_template"), [(1, "{{ states.input_select.test_state.state }}")]
|
|
)
|
|
@pytest.mark.parametrize(
|
|
"style", [ConfigurationStyle.LEGACY, ConfigurationStyle.MODERN]
|
|
)
|
|
@pytest.mark.parametrize(
|
|
"test_state", [LockState.UNLOCKING, LockState.LOCKING, LockState.JAMMED]
|
|
)
|
|
@pytest.mark.usefixtures("setup_state_lock")
|
|
async def test_lock_state(hass: HomeAssistant, test_state) -> None:
|
|
"""Test value template."""
|
|
hass.states.async_set("input_select.test_state", test_state)
|
|
await hass.async_block_till_done()
|
|
|
|
state = hass.states.get(TEST_ENTITY_ID)
|
|
assert state.state == test_state
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("count", "state_template", "attribute_template"),
|
|
[
|
|
(
|
|
1,
|
|
"{{ states('switch.test_state') }}",
|
|
"{{ is_state('availability_state.state', 'on') }}",
|
|
)
|
|
],
|
|
)
|
|
@pytest.mark.parametrize(
|
|
("style", "attribute"),
|
|
[
|
|
(ConfigurationStyle.LEGACY, "availability_template"),
|
|
(ConfigurationStyle.MODERN, "availability"),
|
|
],
|
|
)
|
|
@pytest.mark.usefixtures("setup_state_lock_with_attribute")
|
|
async def test_available_template_with_entities(hass: HomeAssistant) -> None:
|
|
"""Test availability templates with values from other entities."""
|
|
# When template returns true..
|
|
hass.states.async_set("availability_state.state", STATE_ON)
|
|
await hass.async_block_till_done()
|
|
|
|
# Device State should not be unavailable
|
|
assert hass.states.get(TEST_ENTITY_ID).state != STATE_UNAVAILABLE
|
|
|
|
# When Availability template returns false
|
|
hass.states.async_set("availability_state.state", STATE_OFF)
|
|
await hass.async_block_till_done()
|
|
|
|
# device state should be unavailable
|
|
assert hass.states.get(TEST_ENTITY_ID).state == STATE_UNAVAILABLE
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("count", "state_template", "attribute_template"),
|
|
[
|
|
(
|
|
1,
|
|
"{{ 1 + 1 }}",
|
|
"{{ x - 12 }}",
|
|
)
|
|
],
|
|
)
|
|
@pytest.mark.parametrize(
|
|
("style", "attribute"),
|
|
[
|
|
(ConfigurationStyle.LEGACY, "availability_template"),
|
|
(ConfigurationStyle.MODERN, "availability"),
|
|
],
|
|
)
|
|
@pytest.mark.usefixtures("setup_state_lock_with_attribute")
|
|
async def test_invalid_availability_template_keeps_component_available(
|
|
hass: HomeAssistant, caplog_setup_text
|
|
) -> None:
|
|
"""Test that an invalid availability keeps the device available."""
|
|
assert hass.states.get(TEST_ENTITY_ID).state != STATE_UNAVAILABLE
|
|
assert ("UndefinedError: 'x' is undefined") in caplog_setup_text
|
|
|
|
|
|
@pytest.mark.parametrize(("count", "domain"), [(1, lock.DOMAIN)])
|
|
@pytest.mark.parametrize(
|
|
"config",
|
|
[
|
|
{
|
|
lock.DOMAIN: {
|
|
**OPTIMISTIC_LOCK_CONFIG,
|
|
"name": "test_template_lock_01",
|
|
"unique_id": "not-so-unique-anymore",
|
|
"value_template": "{{ true }}",
|
|
}
|
|
},
|
|
],
|
|
)
|
|
@pytest.mark.usefixtures("start_ha")
|
|
async def test_legacy_unique_id(hass: HomeAssistant) -> None:
|
|
"""Test unique_id option only creates one lock per id."""
|
|
await setup.async_setup_component(
|
|
hass,
|
|
lock.DOMAIN,
|
|
{
|
|
"lock": {
|
|
**OPTIMISTIC_LOCK_CONFIG,
|
|
"name": "test_template_lock_02",
|
|
"unique_id": "not-so-unique-anymore",
|
|
"value_template": "{{ false }}",
|
|
},
|
|
},
|
|
)
|
|
|
|
await hass.async_block_till_done()
|
|
await hass.async_start()
|
|
await hass.async_block_till_done()
|
|
|
|
assert len(hass.states.async_all("lock")) == 1
|
|
|
|
|
|
async def test_modern_unique_id(hass: HomeAssistant) -> None:
|
|
"""Test unique_id option only creates one cover per id."""
|
|
config = {
|
|
"template": {
|
|
"lock": [
|
|
{
|
|
"name": "test_template_lock_01",
|
|
"unique_id": "not-so-unique-anymore",
|
|
"state": "{{ false }}",
|
|
**OPTIMISTIC_LOCK,
|
|
},
|
|
{
|
|
"name": "test_template_lock_02",
|
|
"unique_id": "not-so-unique-anymore",
|
|
"state": "{{ false }}",
|
|
**OPTIMISTIC_LOCK,
|
|
},
|
|
]
|
|
}
|
|
}
|
|
|
|
with assert_setup_component(1, template.DOMAIN):
|
|
assert await async_setup_component(
|
|
hass,
|
|
template.DOMAIN,
|
|
config,
|
|
)
|
|
|
|
await hass.async_block_till_done()
|
|
await hass.async_start()
|
|
await hass.async_block_till_done()
|
|
|
|
assert len(hass.states.async_all()) == 1
|
|
|
|
|
|
async def test_nested_unique_id(
|
|
hass: HomeAssistant, entity_registry: er.EntityRegistry
|
|
) -> None:
|
|
"""Test a template unique_id propagates to lock unique_ids."""
|
|
with assert_setup_component(1, template.DOMAIN):
|
|
assert await async_setup_component(
|
|
hass,
|
|
template.DOMAIN,
|
|
{
|
|
"template": {
|
|
"unique_id": "x",
|
|
"lock": [
|
|
{
|
|
**OPTIMISTIC_LOCK,
|
|
"name": "test_a",
|
|
"unique_id": "a",
|
|
"state": "{{ true }}",
|
|
},
|
|
{
|
|
**OPTIMISTIC_LOCK,
|
|
"name": "test_b",
|
|
"unique_id": "b",
|
|
"state": "{{ true }}",
|
|
},
|
|
],
|
|
},
|
|
},
|
|
)
|
|
|
|
await hass.async_block_till_done()
|
|
await hass.async_start()
|
|
await hass.async_block_till_done()
|
|
|
|
assert len(hass.states.async_all("lock")) == 2
|
|
|
|
entry = entity_registry.async_get("lock.test_a")
|
|
assert entry
|
|
assert entry.unique_id == "x-a"
|
|
|
|
entry = entity_registry.async_get("lock.test_b")
|
|
assert entry
|
|
assert entry.unique_id == "x-b"
|
|
|
|
|
|
async def test_emtpy_action_config(hass: HomeAssistant) -> None:
|
|
"""Test configuration with empty script."""
|
|
with assert_setup_component(1, lock.DOMAIN):
|
|
assert await setup.async_setup_component(
|
|
hass,
|
|
lock.DOMAIN,
|
|
{
|
|
lock.DOMAIN: {
|
|
"platform": "template",
|
|
"value_template": "{{ 0 == 1 }}",
|
|
"lock": [],
|
|
"unlock": [],
|
|
"open": [],
|
|
"name": "test_template_lock",
|
|
"optimistic": True,
|
|
},
|
|
},
|
|
)
|
|
|
|
await hass.async_block_till_done()
|
|
await hass.async_start()
|
|
await hass.async_block_till_done()
|
|
|
|
state = hass.states.get("lock.test_template_lock")
|
|
assert state.attributes["supported_features"] == LockEntityFeature.OPEN
|
|
|
|
await hass.services.async_call(
|
|
lock.DOMAIN,
|
|
lock.SERVICE_UNLOCK,
|
|
{ATTR_ENTITY_ID: "lock.test_template_lock"},
|
|
)
|
|
await hass.async_block_till_done()
|
|
|
|
state = hass.states.get("lock.test_template_lock")
|
|
assert state.state == LockState.UNLOCKED
|
|
|
|
await hass.services.async_call(
|
|
lock.DOMAIN,
|
|
lock.SERVICE_LOCK,
|
|
{ATTR_ENTITY_ID: "lock.test_template_lock"},
|
|
)
|
|
await hass.async_block_till_done()
|
|
|
|
state = hass.states.get("lock.test_template_lock")
|
|
assert state.state == LockState.LOCKED
|