diff --git a/homeassistant/components/lcn/services.py b/homeassistant/components/lcn/services.py index 49b54fc0c8d..611a7353bcd 100644 --- a/homeassistant/components/lcn/services.py +++ b/homeassistant/components/lcn/services.py @@ -1,5 +1,7 @@ """Service calls related dependencies for LCN component.""" +from enum import StrEnum, auto + import pypck import voluptuous as vol @@ -394,18 +396,36 @@ class Pck(LcnServiceCall): await device_connection.pck(pck) +class LcnService(StrEnum): + """LCN service names.""" + + OUTPUT_ABS = auto() + OUTPUT_REL = auto() + OUTPUT_TOGGLE = auto() + RELAYS = auto() + VAR_ABS = auto() + VAR_RESET = auto() + VAR_REL = auto() + LOCK_REGULATOR = auto() + LED = auto() + SEND_KEYS = auto() + LOCK_KEYS = auto() + DYN_TEXT = auto() + PCK = auto() + + SERVICES = ( - ("output_abs", OutputAbs), - ("output_rel", OutputRel), - ("output_toggle", OutputToggle), - ("relays", Relays), - ("var_abs", VarAbs), - ("var_reset", VarReset), - ("var_rel", VarRel), - ("lock_regulator", LockRegulator), - ("led", Led), - ("send_keys", SendKeys), - ("lock_keys", LockKeys), - ("dyn_text", DynText), - ("pck", Pck), + (LcnService.OUTPUT_ABS, OutputAbs), + (LcnService.OUTPUT_REL, OutputRel), + (LcnService.OUTPUT_TOGGLE, OutputToggle), + (LcnService.RELAYS, Relays), + (LcnService.VAR_ABS, VarAbs), + (LcnService.VAR_RESET, VarReset), + (LcnService.VAR_REL, VarRel), + (LcnService.LOCK_REGULATOR, LockRegulator), + (LcnService.LED, Led), + (LcnService.SEND_KEYS, SendKeys), + (LcnService.LOCK_KEYS, LockKeys), + (LcnService.DYN_TEXT, DynText), + (LcnService.PCK, Pck), ) diff --git a/tests/components/lcn/snapshots/test_services.ambr b/tests/components/lcn/snapshots/test_services.ambr new file mode 100644 index 00000000000..29e8da72fd7 --- /dev/null +++ b/tests/components/lcn/snapshots/test_services.ambr @@ -0,0 +1,203 @@ +# serializer version: 1 +# name: test_service_dyn_text + tuple( + 0, + 'text in row 1', + ) +# --- +# name: test_service_led + tuple( + , + , + ) +# --- +# name: test_service_lock_keys + tuple( + 0, + list([ + , + , + , + , + , + , + , + , + ]), + ) +# --- +# name: test_service_lock_keys_tab_a_temporary + tuple( + 10, + , + list([ + , + , + , + , + , + , + , + , + ]), + ) +# --- +# name: test_service_lock_regulator + tuple( + 0, + True, + ) +# --- +# name: test_service_output_abs + tuple( + 0, + 100, + 9, + ) +# --- +# name: test_service_output_rel + tuple( + 0, + 25, + ) +# --- +# name: test_service_output_toggle + tuple( + 0, + 9, + ) +# --- +# name: test_service_pck + tuple( + 'PIN4', + ) +# --- +# name: test_service_relays + tuple( + list([ + , + , + , + , + , + , + , + , + ]), + ) +# --- +# name: test_service_send_keys + tuple( + list([ + list([ + True, + False, + False, + False, + True, + False, + False, + False, + ]), + list([ + False, + False, + False, + False, + False, + False, + False, + False, + ]), + list([ + False, + False, + False, + False, + False, + False, + False, + False, + ]), + list([ + False, + False, + False, + False, + False, + False, + False, + True, + ]), + ]), + , + ) +# --- +# name: test_service_send_keys_hit_deferred + tuple( + list([ + list([ + True, + False, + False, + False, + True, + False, + False, + False, + ]), + list([ + False, + False, + False, + False, + False, + False, + False, + False, + ]), + list([ + False, + False, + False, + False, + False, + False, + False, + False, + ]), + list([ + False, + False, + False, + False, + False, + False, + False, + True, + ]), + ]), + 5, + , + ) +# --- +# name: test_service_var_abs + tuple( + , + 75.0, + , + ) +# --- +# name: test_service_var_rel + tuple( + , + 10.0, + , + , + ) +# --- +# name: test_service_var_reset + tuple( + , + ) +# --- diff --git a/tests/components/lcn/test_services.py b/tests/components/lcn/test_services.py new file mode 100644 index 00000000000..9cb53289065 --- /dev/null +++ b/tests/components/lcn/test_services.py @@ -0,0 +1,425 @@ +"""Test for the LCN services.""" + +from unittest.mock import patch + +import pytest +from syrupy import SnapshotAssertion + +from homeassistant.components.lcn import DOMAIN +from homeassistant.components.lcn.const import ( + CONF_KEYS, + CONF_LED, + CONF_OUTPUT, + CONF_PCK, + CONF_RELVARREF, + CONF_ROW, + CONF_SETPOINT, + CONF_TABLE, + CONF_TEXT, + CONF_TIME, + CONF_TIME_UNIT, + CONF_TRANSITION, + CONF_VALUE, + CONF_VARIABLE, +) +from homeassistant.components.lcn.services import LcnService +from homeassistant.const import ( + CONF_ADDRESS, + CONF_BRIGHTNESS, + CONF_STATE, + CONF_UNIT_OF_MEASUREMENT, +) +from homeassistant.core import HomeAssistant +from homeassistant.setup import async_setup_component + +from .conftest import MockModuleConnection, MockPchkConnectionManager, setup_component + + +@patch("pypck.connection.PchkConnectionManager", MockPchkConnectionManager) +async def test_service_output_abs( + hass: HomeAssistant, snapshot: SnapshotAssertion +) -> None: + """Test output_abs service.""" + await async_setup_component(hass, "persistent_notification", {}) + await setup_component(hass) + + with patch.object(MockModuleConnection, "dim_output") as dim_output: + await hass.services.async_call( + DOMAIN, + LcnService.OUTPUT_ABS, + { + CONF_ADDRESS: "pchk.s0.m7", + CONF_OUTPUT: "output1", + CONF_BRIGHTNESS: 100, + CONF_TRANSITION: 5, + }, + blocking=True, + ) + + assert dim_output.await_args.args == snapshot() + + +@patch("pypck.connection.PchkConnectionManager", MockPchkConnectionManager) +async def test_service_output_rel( + hass: HomeAssistant, snapshot: SnapshotAssertion +) -> None: + """Test output_rel service.""" + await async_setup_component(hass, "persistent_notification", {}) + await setup_component(hass) + + with patch.object(MockModuleConnection, "rel_output") as rel_output: + await hass.services.async_call( + DOMAIN, + LcnService.OUTPUT_REL, + { + CONF_ADDRESS: "pchk.s0.m7", + CONF_OUTPUT: "output1", + CONF_BRIGHTNESS: 25, + }, + blocking=True, + ) + + assert rel_output.await_args.args == snapshot() + + +@patch("pypck.connection.PchkConnectionManager", MockPchkConnectionManager) +async def test_service_output_toggle( + hass: HomeAssistant, snapshot: SnapshotAssertion +) -> None: + """Test output_toggle service.""" + await async_setup_component(hass, "persistent_notification", {}) + await setup_component(hass) + + with patch.object(MockModuleConnection, "toggle_output") as toggle_output: + await hass.services.async_call( + DOMAIN, + LcnService.OUTPUT_TOGGLE, + { + CONF_ADDRESS: "pchk.s0.m7", + CONF_OUTPUT: "output1", + CONF_TRANSITION: 5, + }, + blocking=True, + ) + + assert toggle_output.await_args.args == snapshot() + + +@patch("pypck.connection.PchkConnectionManager", MockPchkConnectionManager) +async def test_service_relays(hass: HomeAssistant, snapshot: SnapshotAssertion) -> None: + """Test relays service.""" + await async_setup_component(hass, "persistent_notification", {}) + await setup_component(hass) + + with patch.object(MockModuleConnection, "control_relays") as control_relays: + await hass.services.async_call( + DOMAIN, + LcnService.RELAYS, + {CONF_ADDRESS: "pchk.s0.m7", CONF_STATE: "0011TT--"}, + blocking=True, + ) + + assert control_relays.await_args.args == snapshot() + + +@patch("pypck.connection.PchkConnectionManager", MockPchkConnectionManager) +async def test_service_led(hass: HomeAssistant, snapshot: SnapshotAssertion) -> None: + """Test led service.""" + await async_setup_component(hass, "persistent_notification", {}) + await setup_component(hass) + + with patch.object(MockModuleConnection, "control_led") as control_led: + await hass.services.async_call( + DOMAIN, + LcnService.LED, + {CONF_ADDRESS: "pchk.s0.m7", CONF_LED: "led6", CONF_STATE: "blink"}, + blocking=True, + ) + + assert control_led.await_args.args == snapshot() + + +@patch("pypck.connection.PchkConnectionManager", MockPchkConnectionManager) +async def test_service_var_abs( + hass: HomeAssistant, snapshot: SnapshotAssertion +) -> None: + """Test var_abs service.""" + await async_setup_component(hass, "persistent_notification", {}) + await setup_component(hass) + + with patch.object(MockModuleConnection, "var_abs") as var_abs: + await hass.services.async_call( + DOMAIN, + LcnService.VAR_ABS, + { + CONF_ADDRESS: "pchk.s0.m7", + CONF_VARIABLE: "var1", + CONF_VALUE: 75, + CONF_UNIT_OF_MEASUREMENT: "%", + }, + blocking=True, + ) + + assert var_abs.await_args.args == snapshot() + + +@patch("pypck.connection.PchkConnectionManager", MockPchkConnectionManager) +async def test_service_var_rel( + hass: HomeAssistant, snapshot: SnapshotAssertion +) -> None: + """Test var_rel service.""" + await async_setup_component(hass, "persistent_notification", {}) + await setup_component(hass) + + with patch.object(MockModuleConnection, "var_rel") as var_rel: + await hass.services.async_call( + DOMAIN, + LcnService.VAR_REL, + { + CONF_ADDRESS: "pchk.s0.m7", + CONF_VARIABLE: "var1", + CONF_VALUE: 10, + CONF_UNIT_OF_MEASUREMENT: "%", + CONF_RELVARREF: "current", + }, + blocking=True, + ) + + assert var_rel.await_args.args == snapshot() + + +@patch("pypck.connection.PchkConnectionManager", MockPchkConnectionManager) +async def test_service_var_reset( + hass: HomeAssistant, snapshot: SnapshotAssertion +) -> None: + """Test var_reset service.""" + await async_setup_component(hass, "persistent_notification", {}) + await setup_component(hass) + + with patch.object(MockModuleConnection, "var_reset") as var_reset: + await hass.services.async_call( + DOMAIN, + LcnService.VAR_RESET, + {CONF_ADDRESS: "pchk.s0.m7", CONF_VARIABLE: "var1"}, + blocking=True, + ) + + assert var_reset.await_args.args == snapshot() + + +@patch("pypck.connection.PchkConnectionManager", MockPchkConnectionManager) +async def test_service_lock_regulator( + hass: HomeAssistant, snapshot: SnapshotAssertion +) -> None: + """Test lock_regulator service.""" + await async_setup_component(hass, "persistent_notification", {}) + await setup_component(hass) + + with patch.object(MockModuleConnection, "lock_regulator") as lock_regulator: + await hass.services.async_call( + DOMAIN, + LcnService.LOCK_REGULATOR, + { + CONF_ADDRESS: "pchk.s0.m7", + CONF_SETPOINT: "r1varsetpoint", + CONF_STATE: True, + }, + blocking=True, + ) + + assert lock_regulator.await_args.args == snapshot() + + +@patch("pypck.connection.PchkConnectionManager", MockPchkConnectionManager) +async def test_service_send_keys( + hass: HomeAssistant, snapshot: SnapshotAssertion +) -> None: + """Test send_keys service.""" + await async_setup_component(hass, "persistent_notification", {}) + await setup_component(hass) + + with patch.object(MockModuleConnection, "send_keys") as send_keys: + await hass.services.async_call( + DOMAIN, + LcnService.SEND_KEYS, + {CONF_ADDRESS: "pchk.s0.m7", CONF_KEYS: "a1a5d8", CONF_STATE: "hit"}, + blocking=True, + ) + + keys = [[False] * 8 for i in range(4)] + keys[0][0] = True + keys[0][4] = True + keys[3][7] = True + + assert send_keys.await_args.args == snapshot() + + +@patch("pypck.connection.PchkConnectionManager", MockPchkConnectionManager) +async def test_service_send_keys_hit_deferred( + hass: HomeAssistant, snapshot: SnapshotAssertion +) -> None: + """Test send_keys (hit_deferred) service.""" + await async_setup_component(hass, "persistent_notification", {}) + await setup_component(hass) + + keys = [[False] * 8 for i in range(4)] + keys[0][0] = True + keys[0][4] = True + keys[3][7] = True + + # success + with patch.object( + MockModuleConnection, "send_keys_hit_deferred" + ) as send_keys_hit_deferred: + await hass.services.async_call( + DOMAIN, + LcnService.SEND_KEYS, + { + CONF_ADDRESS: "pchk.s0.m7", + CONF_KEYS: "a1a5d8", + CONF_TIME: 5, + CONF_TIME_UNIT: "s", + }, + blocking=True, + ) + + assert send_keys_hit_deferred.await_args.args == snapshot() + + # wrong key action + with ( + patch.object( + MockModuleConnection, "send_keys_hit_deferred" + ) as send_keys_hit_deferred, + pytest.raises(ValueError), + ): + await hass.services.async_call( + DOMAIN, + LcnService.SEND_KEYS, + { + CONF_ADDRESS: "pchk.s0.m7", + CONF_KEYS: "a1a5d8", + CONF_STATE: "make", + CONF_TIME: 5, + CONF_TIME_UNIT: "s", + }, + blocking=True, + ) + + +@patch("pypck.connection.PchkConnectionManager", MockPchkConnectionManager) +async def test_service_lock_keys( + hass: HomeAssistant, snapshot: SnapshotAssertion +) -> None: + """Test lock_keys service.""" + await async_setup_component(hass, "persistent_notification", {}) + await setup_component(hass) + + with patch.object(MockModuleConnection, "lock_keys") as lock_keys: + await hass.services.async_call( + DOMAIN, + LcnService.LOCK_KEYS, + {CONF_ADDRESS: "pchk.s0.m7", CONF_TABLE: "a", CONF_STATE: "0011TT--"}, + blocking=True, + ) + + assert lock_keys.await_args.args == snapshot() + + +@patch("pypck.connection.PchkConnectionManager", MockPchkConnectionManager) +async def test_service_lock_keys_tab_a_temporary( + hass: HomeAssistant, snapshot: SnapshotAssertion +) -> None: + """Test lock_keys (tab_a_temporary) service.""" + await async_setup_component(hass, "persistent_notification", {}) + await setup_component(hass) + + # success + with patch.object( + MockModuleConnection, "lock_keys_tab_a_temporary" + ) as lock_keys_tab_a_temporary: + await hass.services.async_call( + DOMAIN, + LcnService.LOCK_KEYS, + { + CONF_ADDRESS: "pchk.s0.m7", + CONF_STATE: "0011TT--", + CONF_TIME: 10, + CONF_TIME_UNIT: "s", + }, + blocking=True, + ) + + assert lock_keys_tab_a_temporary.await_args.args == snapshot() + + # wrong table + with ( + patch.object( + MockModuleConnection, "lock_keys_tab_a_temporary" + ) as lock_keys_tab_a_temporary, + pytest.raises(ValueError), + ): + await hass.services.async_call( + DOMAIN, + LcnService.LOCK_KEYS, + { + CONF_ADDRESS: "pchk.s0.m7", + CONF_TABLE: "b", + CONF_STATE: "0011TT--", + CONF_TIME: 10, + CONF_TIME_UNIT: "s", + }, + blocking=True, + ) + + +@patch("pypck.connection.PchkConnectionManager", MockPchkConnectionManager) +async def test_service_dyn_text( + hass: HomeAssistant, snapshot: SnapshotAssertion +) -> None: + """Test dyn_text service.""" + await async_setup_component(hass, "persistent_notification", {}) + await setup_component(hass) + + with patch.object(MockModuleConnection, "dyn_text") as dyn_text: + await hass.services.async_call( + DOMAIN, + LcnService.DYN_TEXT, + {CONF_ADDRESS: "pchk.s0.m7", CONF_ROW: 1, CONF_TEXT: "text in row 1"}, + blocking=True, + ) + + assert dyn_text.await_args.args == snapshot() + + +@patch("pypck.connection.PchkConnectionManager", MockPchkConnectionManager) +async def test_service_pck(hass: HomeAssistant, snapshot: SnapshotAssertion) -> None: + """Test pck service.""" + await async_setup_component(hass, "persistent_notification", {}) + await setup_component(hass) + + with patch.object(MockModuleConnection, "pck") as pck: + await hass.services.async_call( + DOMAIN, + LcnService.PCK, + {CONF_ADDRESS: "pchk.s0.m7", CONF_PCK: "PIN4"}, + blocking=True, + ) + + assert pck.await_args.args == snapshot() + + +@patch("pypck.connection.PchkConnectionManager", MockPchkConnectionManager) +async def test_service_called_with_invalid_host_id(hass: HomeAssistant) -> None: + """Test service was called with non existing host id.""" + await async_setup_component(hass, "persistent_notification", {}) + await setup_component(hass) + + with patch.object(MockModuleConnection, "pck") as pck, pytest.raises(ValueError): + await hass.services.async_call( + DOMAIN, + LcnService.PCK, + {CONF_ADDRESS: "foobar.s0.m7", CONF_PCK: "PIN4"}, + blocking=True, + ) + + pck.assert_not_awaited()