mirror of
https://github.com/home-assistant/core.git
synced 2026-05-15 13:01:22 +01:00
1064 lines
30 KiB
Python
1064 lines
30 KiB
Python
"""The tests for the Template lock platform."""
|
|
|
|
from typing import Any
|
|
|
|
import pytest
|
|
from syrupy.assertion import SnapshotAssertion
|
|
|
|
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,
|
|
STATE_UNKNOWN,
|
|
)
|
|
from homeassistant.core import HomeAssistant, ServiceCall
|
|
from homeassistant.helpers import entity_registry as er
|
|
from homeassistant.helpers.typing import ConfigType
|
|
|
|
from .conftest import (
|
|
ConfigurationStyle,
|
|
TemplatePlatformSetup,
|
|
async_get_flow_preview_state,
|
|
async_trigger,
|
|
make_test_action,
|
|
make_test_trigger,
|
|
setup_and_test_nested_unique_id,
|
|
setup_and_test_unique_id,
|
|
setup_entity,
|
|
)
|
|
|
|
from tests.common import MockConfigEntry, assert_setup_component
|
|
from tests.typing import WebSocketGenerator
|
|
|
|
TEST_STATE_ENTITY_ID = "sensor.test_state"
|
|
TEST_AVAILABILITY_ENTITY_ID = "availability_state.state"
|
|
TEST_LOCK = TemplatePlatformSetup(
|
|
lock.DOMAIN,
|
|
None,
|
|
"test_template_lock",
|
|
make_test_trigger(
|
|
TEST_AVAILABILITY_ENTITY_ID,
|
|
TEST_STATE_ENTITY_ID,
|
|
),
|
|
)
|
|
|
|
|
|
CODE_DATA = {
|
|
"code": "{{ code if code is defined else None }}",
|
|
}
|
|
LOCK_ACTION = make_test_action("lock", CODE_DATA)
|
|
UNLOCK_ACTION = make_test_action("unlock", CODE_DATA)
|
|
OPEN_ACTION = make_test_action("open")
|
|
|
|
|
|
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,
|
|
}
|
|
|
|
|
|
@pytest.fixture
|
|
async def setup_lock(
|
|
hass: HomeAssistant,
|
|
count: int,
|
|
style: ConfigurationStyle,
|
|
config: dict[str, Any],
|
|
) -> None:
|
|
"""Do setup of lock integration."""
|
|
await setup_entity(hass, TEST_LOCK, style, count, config)
|
|
|
|
|
|
@pytest.fixture
|
|
async def setup_base_lock(
|
|
hass: HomeAssistant,
|
|
count: int,
|
|
style: ConfigurationStyle,
|
|
state_template: str,
|
|
extra_config: dict,
|
|
):
|
|
"""Do setup of lock integration using a state template."""
|
|
await setup_entity(hass, TEST_LOCK, style, count, extra_config, state_template)
|
|
|
|
|
|
@pytest.fixture
|
|
async def setup_state_lock(
|
|
hass: HomeAssistant,
|
|
count: int,
|
|
style: ConfigurationStyle,
|
|
state_template: str,
|
|
):
|
|
"""Do setup of lock integration using a state template."""
|
|
await setup_entity(hass, TEST_LOCK, style, count, OPTIMISTIC_LOCK, 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 lock integration using a state template."""
|
|
await setup_entity(
|
|
hass, TEST_LOCK, style, count, OPTIMISTIC_LOCK, 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 lock integration using a state template."""
|
|
await setup_entity(
|
|
hass,
|
|
TEST_LOCK,
|
|
style,
|
|
count,
|
|
OPTIMISTIC_LOCK,
|
|
state_template,
|
|
{attribute: attribute_template} if attribute else {},
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("count", "state_template"), [(1, "{{ states.sensor.test_state.state }}")]
|
|
)
|
|
@pytest.mark.parametrize(
|
|
"style",
|
|
[ConfigurationStyle.LEGACY, ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER],
|
|
)
|
|
@pytest.mark.usefixtures("setup_state_lock")
|
|
async def test_template_state(hass: HomeAssistant) -> None:
|
|
"""Test template."""
|
|
await async_trigger(hass, TEST_STATE_ENTITY_ID, STATE_ON)
|
|
|
|
state = hass.states.get(TEST_LOCK.entity_id)
|
|
assert state.state == LockState.LOCKED
|
|
|
|
await async_trigger(hass, TEST_STATE_ENTITY_ID, STATE_OFF)
|
|
|
|
state = hass.states.get(TEST_LOCK.entity_id)
|
|
assert state.state == LockState.UNLOCKED
|
|
|
|
await async_trigger(hass, TEST_STATE_ENTITY_ID, STATE_OPEN)
|
|
|
|
state = hass.states.get(TEST_LOCK.entity_id)
|
|
assert state.state == LockState.OPEN
|
|
|
|
hass.states.async_set(TEST_STATE_ENTITY_ID, "None")
|
|
await hass.async_block_till_done()
|
|
|
|
state = hass.states.get(TEST_LOCK.entity_id)
|
|
assert state.state == STATE_UNKNOWN
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("count", "state_template", "extra_config"),
|
|
[(1, "{{ states.sensor.test_state.state }}", {"optimistic": True, **OPEN_ACTION})],
|
|
)
|
|
@pytest.mark.parametrize(
|
|
"style",
|
|
[ConfigurationStyle.LEGACY, ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER],
|
|
)
|
|
@pytest.mark.usefixtures("setup_state_lock_with_extra_config")
|
|
async def test_open_lock_optimistic(
|
|
hass: HomeAssistant, calls: list[ServiceCall]
|
|
) -> None:
|
|
"""Test optimistic open."""
|
|
await async_trigger(hass, TEST_STATE_ENTITY_ID, STATE_ON)
|
|
|
|
state = hass.states.get(TEST_LOCK.entity_id)
|
|
assert state.state == LockState.LOCKED
|
|
|
|
await hass.services.async_call(
|
|
lock.DOMAIN,
|
|
lock.SERVICE_OPEN,
|
|
{ATTR_ENTITY_ID: TEST_LOCK.entity_id},
|
|
)
|
|
await hass.async_block_till_done()
|
|
|
|
assert len(calls) == 1
|
|
assert calls[0].data["action"] == "open"
|
|
assert calls[0].data["caller"] == TEST_LOCK.entity_id
|
|
|
|
state = hass.states.get(TEST_LOCK.entity_id)
|
|
assert state.state == LockState.OPEN
|
|
|
|
|
|
@pytest.mark.parametrize(("count", "state_template"), [(1, "{{ 1 == 1 }}")])
|
|
@pytest.mark.parametrize(
|
|
"style",
|
|
[ConfigurationStyle.LEGACY, ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER],
|
|
)
|
|
@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."""
|
|
# Ensure the trigger executes for trigger configurations
|
|
await async_trigger(hass, TEST_STATE_ENTITY_ID, STATE_ON)
|
|
|
|
state = hass.states.get(TEST_LOCK.entity_id)
|
|
assert state.state == LockState.LOCKED
|
|
|
|
|
|
@pytest.mark.parametrize(("count", "state_template"), [(1, "{{ 1 == 2 }}")])
|
|
@pytest.mark.parametrize(
|
|
"style",
|
|
[ConfigurationStyle.LEGACY, ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER],
|
|
)
|
|
@pytest.mark.usefixtures("setup_state_lock")
|
|
async def test_template_state_boolean_off(hass: HomeAssistant) -> None:
|
|
"""Test the setting of the state with off."""
|
|
# Ensure the trigger executes for trigger configurations
|
|
await async_trigger(hass, TEST_STATE_ENTITY_ID, STATE_ON)
|
|
|
|
state = hass.states.get(TEST_LOCK.entity_id)
|
|
assert state.state == LockState.UNLOCKED
|
|
|
|
|
|
@pytest.mark.parametrize("count", [0])
|
|
@pytest.mark.parametrize(
|
|
"style",
|
|
[ConfigurationStyle.LEGACY, ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER],
|
|
)
|
|
@pytest.mark.parametrize(
|
|
("state_template", "extra_config"),
|
|
[
|
|
("{% if rubbish %}", OPTIMISTIC_LOCK),
|
|
("{{ rubbish }", OPTIMISTIC_LOCK),
|
|
("Invalid", {}),
|
|
(
|
|
"{{ 1==1 }}",
|
|
{
|
|
"not_value_template": "{{ states.sensor.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"),
|
|
(ConfigurationStyle.TRIGGER, "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, ConfigurationStyle.TRIGGER],
|
|
)
|
|
@pytest.mark.usefixtures("setup_state_lock")
|
|
async def test_template_static(hass: HomeAssistant) -> None:
|
|
"""Test that we allow static templates."""
|
|
hass.states.async_set(TEST_LOCK.entity_id, LockState.LOCKED)
|
|
await hass.async_block_till_done()
|
|
state = hass.states.get(TEST_LOCK.entity_id)
|
|
assert state.state == LockState.LOCKED
|
|
|
|
|
|
@pytest.mark.parametrize("count", [1])
|
|
@pytest.mark.parametrize(
|
|
"style",
|
|
[ConfigurationStyle.LEGACY, ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER],
|
|
)
|
|
@pytest.mark.parametrize(
|
|
("state_template", "expected"),
|
|
[
|
|
("{{ True }}", LockState.LOCKED),
|
|
("{{ False }}", LockState.UNLOCKED),
|
|
("{{ x - 12 }}", STATE_UNAVAILABLE),
|
|
("{{ None }}", STATE_UNKNOWN),
|
|
],
|
|
)
|
|
@pytest.mark.usefixtures("setup_state_lock")
|
|
async def test_state_template(hass: HomeAssistant, expected: str) -> None:
|
|
"""Test state and value_template template."""
|
|
# Ensure the trigger executes for trigger configurations
|
|
await async_trigger(hass, TEST_STATE_ENTITY_ID, STATE_ON)
|
|
|
|
state = hass.states.get(TEST_LOCK.entity_id)
|
|
assert state.state == expected
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("count", "state_template", "attribute"), [(1, "{{ 1==1 }}", "picture")]
|
|
)
|
|
@pytest.mark.parametrize(
|
|
"attribute_template",
|
|
["{% if states.sensor.test_state.state %}/local/switch.png{% endif %}"],
|
|
)
|
|
@pytest.mark.parametrize(
|
|
("style", "initial_state"),
|
|
[
|
|
(ConfigurationStyle.MODERN, ""),
|
|
(ConfigurationStyle.TRIGGER, None),
|
|
],
|
|
)
|
|
@pytest.mark.usefixtures("setup_state_lock_with_attribute")
|
|
async def test_picture_template(hass: HomeAssistant, initial_state: str) -> None:
|
|
"""Test entity_picture template."""
|
|
state = hass.states.get(TEST_LOCK.entity_id)
|
|
assert state.attributes.get("entity_picture") == initial_state
|
|
|
|
await async_trigger(hass, TEST_STATE_ENTITY_ID, STATE_ON)
|
|
|
|
state = hass.states.get(TEST_LOCK.entity_id)
|
|
assert state.attributes["entity_picture"] == "/local/switch.png"
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("count", "state_template", "attribute"), [(1, "{{ 1==1 }}", "icon")]
|
|
)
|
|
@pytest.mark.parametrize(
|
|
"attribute_template",
|
|
["{% if states.sensor.test_state.state %}mdi:eye{% endif %}"],
|
|
)
|
|
@pytest.mark.parametrize(
|
|
("style", "initial_state"),
|
|
[
|
|
(ConfigurationStyle.MODERN, ""),
|
|
(ConfigurationStyle.TRIGGER, None),
|
|
],
|
|
)
|
|
@pytest.mark.usefixtures("setup_state_lock_with_attribute")
|
|
async def test_icon_template(hass: HomeAssistant, initial_state: str) -> None:
|
|
"""Test icon template."""
|
|
state = hass.states.get(TEST_LOCK.entity_id)
|
|
assert state.attributes.get("icon") == initial_state
|
|
|
|
await async_trigger(hass, TEST_STATE_ENTITY_ID, STATE_ON)
|
|
|
|
state = hass.states.get(TEST_LOCK.entity_id)
|
|
assert state.attributes["icon"] == "mdi:eye"
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("count", "state_template"), [(1, "{{ states.sensor.test_state.state }}")]
|
|
)
|
|
@pytest.mark.parametrize(
|
|
"style",
|
|
[ConfigurationStyle.LEGACY, ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER],
|
|
)
|
|
@pytest.mark.usefixtures("setup_state_lock")
|
|
async def test_lock_action(hass: HomeAssistant, calls: list[ServiceCall]) -> None:
|
|
"""Test lock action."""
|
|
await async_trigger(hass, TEST_STATE_ENTITY_ID, STATE_OFF)
|
|
|
|
state = hass.states.get(TEST_LOCK.entity_id)
|
|
assert state.state == LockState.UNLOCKED
|
|
|
|
await hass.services.async_call(
|
|
lock.DOMAIN,
|
|
lock.SERVICE_LOCK,
|
|
{ATTR_ENTITY_ID: TEST_LOCK.entity_id},
|
|
)
|
|
await hass.async_block_till_done()
|
|
|
|
assert len(calls) == 1
|
|
assert calls[0].data["action"] == "lock"
|
|
assert calls[0].data["caller"] == TEST_LOCK.entity_id
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("count", "state_template"), [(1, "{{ states.sensor.test_state.state }}")]
|
|
)
|
|
@pytest.mark.parametrize(
|
|
"style",
|
|
[ConfigurationStyle.LEGACY, ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER],
|
|
)
|
|
@pytest.mark.usefixtures("setup_state_lock")
|
|
async def test_unlock_action(hass: HomeAssistant, calls: list[ServiceCall]) -> None:
|
|
"""Test unlock action."""
|
|
await async_trigger(hass, TEST_STATE_ENTITY_ID, STATE_ON)
|
|
|
|
state = hass.states.get(TEST_LOCK.entity_id)
|
|
assert state.state == LockState.LOCKED
|
|
|
|
await hass.services.async_call(
|
|
lock.DOMAIN,
|
|
lock.SERVICE_UNLOCK,
|
|
{ATTR_ENTITY_ID: TEST_LOCK.entity_id},
|
|
)
|
|
await hass.async_block_till_done()
|
|
|
|
assert len(calls) == 1
|
|
assert calls[0].data["action"] == "unlock"
|
|
assert calls[0].data["caller"] == TEST_LOCK.entity_id
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("count", "state_template", "extra_config"),
|
|
[(1, "{{ states.sensor.test_state.state }}", OPEN_ACTION)],
|
|
)
|
|
@pytest.mark.parametrize(
|
|
"style",
|
|
[ConfigurationStyle.LEGACY, ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER],
|
|
)
|
|
@pytest.mark.usefixtures("setup_state_lock_with_extra_config")
|
|
async def test_open_action(hass: HomeAssistant, calls: list[ServiceCall]) -> None:
|
|
"""Test open action."""
|
|
await async_trigger(hass, TEST_STATE_ENTITY_ID, STATE_ON)
|
|
|
|
state = hass.states.get(TEST_LOCK.entity_id)
|
|
assert state.state == LockState.LOCKED
|
|
|
|
await hass.services.async_call(
|
|
lock.DOMAIN,
|
|
lock.SERVICE_OPEN,
|
|
{ATTR_ENTITY_ID: TEST_LOCK.entity_id},
|
|
)
|
|
await hass.async_block_till_done()
|
|
|
|
assert len(calls) == 1
|
|
assert calls[0].data["action"] == "open"
|
|
assert calls[0].data["caller"] == TEST_LOCK.entity_id
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("count", "state_template", "attribute_template"),
|
|
[
|
|
(
|
|
1,
|
|
"{{ states.sensor.test_state.state }}",
|
|
"{{ '.+' }}",
|
|
)
|
|
],
|
|
)
|
|
@pytest.mark.parametrize(
|
|
("style", "attribute"),
|
|
[
|
|
(ConfigurationStyle.LEGACY, "code_format_template"),
|
|
(ConfigurationStyle.MODERN, "code_format"),
|
|
(ConfigurationStyle.TRIGGER, "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."""
|
|
await async_trigger(hass, TEST_STATE_ENTITY_ID, STATE_OFF)
|
|
|
|
state = hass.states.get(TEST_LOCK.entity_id)
|
|
assert state.state == LockState.UNLOCKED
|
|
|
|
await hass.services.async_call(
|
|
lock.DOMAIN,
|
|
lock.SERVICE_LOCK,
|
|
{ATTR_ENTITY_ID: TEST_LOCK.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_LOCK.entity_id
|
|
assert calls[0].data["code"] == "LOCK_CODE"
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("count", "state_template", "attribute_template"),
|
|
[
|
|
(
|
|
1,
|
|
"{{ states.sensor.test_state.state }}",
|
|
"{{ '.+' }}",
|
|
)
|
|
],
|
|
)
|
|
@pytest.mark.parametrize(
|
|
("style", "attribute"),
|
|
[
|
|
(ConfigurationStyle.LEGACY, "code_format_template"),
|
|
(ConfigurationStyle.MODERN, "code_format"),
|
|
(ConfigurationStyle.TRIGGER, "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", {})
|
|
await async_trigger(hass, TEST_STATE_ENTITY_ID, STATE_ON)
|
|
|
|
state = hass.states.get(TEST_LOCK.entity_id)
|
|
assert state.state == LockState.LOCKED
|
|
|
|
await hass.services.async_call(
|
|
lock.DOMAIN,
|
|
lock.SERVICE_UNLOCK,
|
|
{ATTR_ENTITY_ID: TEST_LOCK.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_LOCK.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"),
|
|
(ConfigurationStyle.TRIGGER, "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."""
|
|
# Ensure trigger entities updated
|
|
await async_trigger(hass, TEST_STATE_ENTITY_ID, STATE_ON)
|
|
|
|
await hass.services.async_call(
|
|
lock.DOMAIN,
|
|
test_action,
|
|
{ATTR_ENTITY_ID: TEST_LOCK.entity_id, ATTR_CODE: "non-number-value"},
|
|
)
|
|
await hass.services.async_call(
|
|
lock.DOMAIN,
|
|
test_action,
|
|
{ATTR_ENTITY_ID: TEST_LOCK.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"),
|
|
(ConfigurationStyle.TRIGGER, "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."""
|
|
|
|
# Ensure trigger entities updated
|
|
await async_trigger(hass, TEST_STATE_ENTITY_ID, STATE_ON)
|
|
|
|
await hass.services.async_call(
|
|
lock.DOMAIN,
|
|
lock.SERVICE_LOCK,
|
|
{ATTR_ENTITY_ID: TEST_LOCK.entity_id},
|
|
)
|
|
await hass.services.async_call(
|
|
lock.DOMAIN,
|
|
lock.SERVICE_UNLOCK,
|
|
{ATTR_ENTITY_ID: TEST_LOCK.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.sensor.test_state.state }}",
|
|
"{{ None }}",
|
|
)
|
|
],
|
|
)
|
|
@pytest.mark.parametrize(
|
|
("style", "attribute"),
|
|
[
|
|
(ConfigurationStyle.LEGACY, "code_format_template"),
|
|
(ConfigurationStyle.MODERN, "code_format"),
|
|
(ConfigurationStyle.TRIGGER, "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."""
|
|
await async_trigger(hass, TEST_STATE_ENTITY_ID, STATE_OFF)
|
|
|
|
state = hass.states.get(TEST_LOCK.entity_id)
|
|
assert state.state == LockState.UNLOCKED
|
|
|
|
await hass.services.async_call(
|
|
lock.DOMAIN,
|
|
action,
|
|
{ATTR_ENTITY_ID: TEST_LOCK.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_LOCK.entity_id
|
|
assert calls[0].data["code"] == "any code"
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("count", "state_template", "attribute_template"),
|
|
[
|
|
(
|
|
1,
|
|
"{{ states.sensor.test_state.state }}",
|
|
"[12]{1",
|
|
)
|
|
],
|
|
)
|
|
@pytest.mark.parametrize(
|
|
("style", "attribute"),
|
|
[
|
|
(ConfigurationStyle.LEGACY, "code_format_template"),
|
|
(ConfigurationStyle.MODERN, "code_format"),
|
|
(ConfigurationStyle.TRIGGER, "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."""
|
|
await async_trigger(hass, TEST_STATE_ENTITY_ID, STATE_OFF)
|
|
|
|
state = hass.states.get(TEST_LOCK.entity_id)
|
|
assert state.state == LockState.UNLOCKED
|
|
|
|
await hass.services.async_call(
|
|
lock.DOMAIN,
|
|
action,
|
|
{ATTR_ENTITY_ID: TEST_LOCK.entity_id, ATTR_CODE: "1"},
|
|
)
|
|
await hass.services.async_call(
|
|
lock.DOMAIN,
|
|
action,
|
|
{ATTR_ENTITY_ID: TEST_LOCK.entity_id, ATTR_CODE: "x"},
|
|
)
|
|
await hass.services.async_call(
|
|
lock.DOMAIN,
|
|
action,
|
|
{ATTR_ENTITY_ID: TEST_LOCK.entity_id},
|
|
)
|
|
await hass.async_block_till_done()
|
|
|
|
assert len(calls) == 0
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("count", "state_template"), [(1, "{{ states.sensor.test_state.state }}")]
|
|
)
|
|
@pytest.mark.parametrize(
|
|
"style",
|
|
[ConfigurationStyle.LEGACY, ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER],
|
|
)
|
|
@pytest.mark.parametrize(
|
|
"test_state",
|
|
[
|
|
LockState.LOCKED,
|
|
LockState.UNLOCKED,
|
|
LockState.OPEN,
|
|
LockState.UNLOCKING,
|
|
LockState.LOCKING,
|
|
LockState.JAMMED,
|
|
LockState.OPENING,
|
|
],
|
|
)
|
|
@pytest.mark.usefixtures("setup_state_lock")
|
|
async def test_lock_state(hass: HomeAssistant, test_state) -> None:
|
|
"""Test value template."""
|
|
await async_trigger(hass, TEST_STATE_ENTITY_ID, test_state)
|
|
|
|
state = hass.states.get(TEST_LOCK.entity_id)
|
|
assert state.state == test_state
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("count", "state_template", "attribute_template"),
|
|
[
|
|
(
|
|
1,
|
|
"{{ states('sensor.test_state') }}",
|
|
"{{ is_state('availability_state.state', 'on') }}",
|
|
)
|
|
],
|
|
)
|
|
@pytest.mark.parametrize(
|
|
("style", "attribute"),
|
|
[
|
|
(ConfigurationStyle.LEGACY, "availability_template"),
|
|
(ConfigurationStyle.MODERN, "availability"),
|
|
(ConfigurationStyle.TRIGGER, "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(TEST_AVAILABILITY_ENTITY_ID, STATE_ON)
|
|
await hass.async_block_till_done()
|
|
|
|
# Device State should not be unavailable
|
|
assert hass.states.get(TEST_LOCK.entity_id).state != STATE_UNAVAILABLE
|
|
|
|
# When Availability template returns false
|
|
hass.states.async_set(TEST_AVAILABILITY_ENTITY_ID, STATE_OFF)
|
|
await hass.async_block_till_done()
|
|
|
|
# device state should be unavailable
|
|
assert hass.states.get(TEST_LOCK.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"),
|
|
(ConfigurationStyle.TRIGGER, "availability"),
|
|
],
|
|
)
|
|
@pytest.mark.usefixtures("setup_state_lock_with_attribute")
|
|
async def test_invalid_availability_template_keeps_component_available(
|
|
hass: HomeAssistant, caplog_setup_text, caplog: pytest.LogCaptureFixture
|
|
) -> None:
|
|
"""Test that an invalid availability keeps the device available."""
|
|
await async_trigger(hass, TEST_STATE_ENTITY_ID, STATE_ON)
|
|
|
|
assert hass.states.get(TEST_LOCK.entity_id).state != STATE_UNAVAILABLE
|
|
err = "'x' is undefined"
|
|
assert err in caplog_setup_text or err in caplog.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
|
|
|
|
|
|
@pytest.mark.parametrize("config", [OPTIMISTIC_LOCK])
|
|
@pytest.mark.parametrize(
|
|
"style",
|
|
[ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER],
|
|
)
|
|
async def test_unique_id(
|
|
hass: HomeAssistant, style: ConfigurationStyle, config: ConfigType
|
|
) -> None:
|
|
"""Test unique_id option only creates one entity per id."""
|
|
await setup_and_test_unique_id(hass, TEST_LOCK, style, config, "{{ 'on' }}")
|
|
|
|
|
|
@pytest.mark.parametrize("config", [OPTIMISTIC_LOCK])
|
|
@pytest.mark.parametrize(
|
|
"style", [ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER]
|
|
)
|
|
async def test_nested_unique_id(
|
|
hass: HomeAssistant,
|
|
style: ConfigurationStyle,
|
|
config: ConfigType,
|
|
entity_registry: er.EntityRegistry,
|
|
) -> None:
|
|
"""Test a template unique_id propagates unique_ids."""
|
|
await setup_and_test_nested_unique_id(
|
|
hass, TEST_LOCK, style, entity_registry, config, "{{ 'on' }}"
|
|
)
|
|
|
|
|
|
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
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("count", "config"),
|
|
[
|
|
(
|
|
1,
|
|
{
|
|
"lock": [],
|
|
"unlock": [],
|
|
},
|
|
)
|
|
],
|
|
)
|
|
@pytest.mark.parametrize(
|
|
"style",
|
|
[ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER],
|
|
)
|
|
@pytest.mark.usefixtures("setup_lock")
|
|
async def test_optimistic(hass: HomeAssistant) -> None:
|
|
"""Test configuration with optimistic state."""
|
|
|
|
state = hass.states.get(TEST_LOCK.entity_id)
|
|
assert state.state == STATE_UNKNOWN
|
|
|
|
# Ensure Trigger template entities update.
|
|
await async_trigger(hass, TEST_STATE_ENTITY_ID, "anything")
|
|
|
|
await hass.services.async_call(
|
|
lock.DOMAIN,
|
|
lock.SERVICE_LOCK,
|
|
{ATTR_ENTITY_ID: TEST_LOCK.entity_id},
|
|
blocking=True,
|
|
)
|
|
|
|
state = hass.states.get(TEST_LOCK.entity_id)
|
|
assert state.state == LockState.LOCKED
|
|
|
|
await hass.services.async_call(
|
|
lock.DOMAIN,
|
|
lock.SERVICE_UNLOCK,
|
|
{ATTR_ENTITY_ID: TEST_LOCK.entity_id},
|
|
blocking=True,
|
|
)
|
|
|
|
state = hass.states.get(TEST_LOCK.entity_id)
|
|
assert state.state == LockState.UNLOCKED
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("count", "config"),
|
|
[
|
|
(
|
|
1,
|
|
{
|
|
"state": "{{ is_state('sensor.test_state', 'on') }}",
|
|
"lock": [],
|
|
"unlock": [],
|
|
"optimistic": False,
|
|
},
|
|
)
|
|
],
|
|
)
|
|
@pytest.mark.parametrize(
|
|
"style",
|
|
[ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER],
|
|
)
|
|
@pytest.mark.usefixtures("setup_lock")
|
|
async def test_not_optimistic(hass: HomeAssistant) -> None:
|
|
"""Test optimistic yaml option set to false."""
|
|
await hass.services.async_call(
|
|
lock.DOMAIN,
|
|
lock.SERVICE_LOCK,
|
|
{ATTR_ENTITY_ID: TEST_LOCK.entity_id},
|
|
blocking=True,
|
|
)
|
|
|
|
# Ensure Trigger template entities update.
|
|
hass.states.async_set(TEST_AVAILABILITY_ENTITY_ID, "anything")
|
|
await hass.async_block_till_done()
|
|
|
|
state = hass.states.get(TEST_LOCK.entity_id)
|
|
assert state.state == LockState.UNLOCKED
|
|
|
|
|
|
async def test_setup_config_entry(
|
|
hass: HomeAssistant,
|
|
snapshot: SnapshotAssertion,
|
|
) -> None:
|
|
"""Tests creating a lock from a config entry."""
|
|
|
|
hass.states.async_set(
|
|
"sensor.test_state",
|
|
LockState.LOCKED,
|
|
{},
|
|
)
|
|
|
|
template_config_entry = MockConfigEntry(
|
|
data={},
|
|
domain=template.DOMAIN,
|
|
options={
|
|
"name": "My template",
|
|
"state": "{{ states('sensor.test_state') }}",
|
|
"lock": [],
|
|
"unlock": [],
|
|
"template_type": lock.DOMAIN,
|
|
},
|
|
title="My template",
|
|
)
|
|
template_config_entry.add_to_hass(hass)
|
|
|
|
assert await hass.config_entries.async_setup(template_config_entry.entry_id)
|
|
await hass.async_block_till_done()
|
|
|
|
state = hass.states.get("lock.my_template")
|
|
assert state is not None
|
|
assert state == snapshot
|
|
|
|
|
|
async def test_flow_preview(
|
|
hass: HomeAssistant,
|
|
hass_ws_client: WebSocketGenerator,
|
|
) -> None:
|
|
"""Test the config flow preview."""
|
|
|
|
state = await async_get_flow_preview_state(
|
|
hass,
|
|
hass_ws_client,
|
|
lock.DOMAIN,
|
|
{
|
|
"name": "My template",
|
|
"state": "{{ 'locked' }}",
|
|
"lock": [],
|
|
"unlock": [],
|
|
},
|
|
)
|
|
|
|
assert state["state"] == LockState.LOCKED
|