diff --git a/homeassistant/components/yale_smart_alarm/config_flow.py b/homeassistant/components/yale_smart_alarm/config_flow.py index b961d714213..c3eaebaa80c 100644 --- a/homeassistant/components/yale_smart_alarm/config_flow.py +++ b/homeassistant/components/yale_smart_alarm/config_flow.py @@ -1,15 +1,27 @@ """Adds config flow for Yale Smart Alarm integration.""" from __future__ import annotations +from typing import Any + import voluptuous as vol from yalesmartalarmclient.client import YaleSmartAlarmClient from yalesmartalarmclient.exceptions import AuthenticationError -from homeassistant import config_entries -from homeassistant.const import CONF_NAME, CONF_PASSWORD, CONF_USERNAME +from homeassistant.config_entries import ConfigEntry, ConfigFlow, OptionsFlow +from homeassistant.const import CONF_CODE, CONF_NAME, CONF_PASSWORD, CONF_USERNAME +from homeassistant.core import callback +from homeassistant.data_entry_flow import FlowResult import homeassistant.helpers.config_validation as cv -from .const import CONF_AREA_ID, DEFAULT_AREA_ID, DEFAULT_NAME, DOMAIN, LOGGER +from .const import ( + CONF_AREA_ID, + CONF_LOCK_CODE_DIGITS, + DEFAULT_AREA_ID, + DEFAULT_LOCK_CODE_DIGITS, + DEFAULT_NAME, + DOMAIN, + LOGGER, +) DATA_SCHEMA = vol.Schema( { @@ -28,12 +40,18 @@ DATA_SCHEMA_AUTH = vol.Schema( ) -class YaleConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class YaleConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Yale integration.""" VERSION = 1 - entry: config_entries.ConfigEntry + entry: ConfigEntry + + @staticmethod + @callback + def async_get_options_flow(config_entry: ConfigEntry) -> YaleOptionsFlowHandler: + """Get the options flow for this handler.""" + return YaleOptionsFlowHandler(config_entry) async def async_step_import(self, config: dict): """Import a configuration from config.yaml.""" @@ -127,3 +145,41 @@ class YaleConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): data_schema=DATA_SCHEMA, errors=errors, ) + + +class YaleOptionsFlowHandler(OptionsFlow): + """Handle Yale options.""" + + def __init__(self, entry: ConfigEntry) -> None: + """Initialize Yale options flow.""" + self.entry = entry + + async def async_step_init( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Manage Yale options.""" + errors = {} + + if user_input: + if len(user_input[CONF_CODE]) not in [0, user_input[CONF_LOCK_CODE_DIGITS]]: + errors["base"] = "code_format_mismatch" + else: + return self.async_create_entry(title="", data=user_input) + + return self.async_show_form( + step_id="init", + data_schema=vol.Schema( + { + vol.Optional( + CONF_CODE, default=self.entry.options.get(CONF_CODE) + ): str, + vol.Optional( + CONF_LOCK_CODE_DIGITS, + default=self.entry.options.get( + CONF_LOCK_CODE_DIGITS, DEFAULT_LOCK_CODE_DIGITS + ), + ): int, + } + ), + errors=errors, + ) diff --git a/homeassistant/components/yale_smart_alarm/const.py b/homeassistant/components/yale_smart_alarm/const.py index 6bf61cea610..8095e87df2e 100644 --- a/homeassistant/components/yale_smart_alarm/const.py +++ b/homeassistant/components/yale_smart_alarm/const.py @@ -15,8 +15,10 @@ from homeassistant.const import ( ) CONF_AREA_ID = "area_id" +CONF_LOCK_CODE_DIGITS = "lock_code_digits" DEFAULT_NAME = "Yale Smart Alarm" DEFAULT_AREA_ID = "1" +DEFAULT_LOCK_CODE_DIGITS = 4 MANUFACTURER = "Yale" MODEL = "main" diff --git a/homeassistant/components/yale_smart_alarm/strings.json b/homeassistant/components/yale_smart_alarm/strings.json index cec588a3cc8..0d08834d6d6 100644 --- a/homeassistant/components/yale_smart_alarm/strings.json +++ b/homeassistant/components/yale_smart_alarm/strings.json @@ -21,9 +21,22 @@ "username": "[%key:common::config_flow::data::username%]", "password": "[%key:common::config_flow::data::password%]", "name": "[%key:common::config_flow::data::name%]", - "area_id": "[%key:component::yale_smart_alarm::config::step::user::data::area_id%]" + "area_id": "Area ID" } } } + }, + "options": { + "step": { + "init": { + "data": { + "code": "Default code for locks, used if none is given", + "lock_code_digits": "Number of digits in PIN code for locks" + } + } + }, + "error": { + "code_format_mismatch": "The code does not match the required number of digits" + } } -} +} \ No newline at end of file diff --git a/homeassistant/components/yale_smart_alarm/translations/en.json b/homeassistant/components/yale_smart_alarm/translations/en.json index e198b0329b9..4405df2cf67 100644 --- a/homeassistant/components/yale_smart_alarm/translations/en.json +++ b/homeassistant/components/yale_smart_alarm/translations/en.json @@ -1,29 +1,41 @@ { "config": { - "abort": { - "already_configured": "Account is already configured", - "reauth_successful": "Re-authentication was successful" + "abort": { + "already_configured": "Account is already configured" + }, + "error": { + "invalid_auth": "Invalid authentication" + }, + "step": { + "reauth_confirm": { + "data": { + "area_id": "Area ID", + "name": "Name", + "password": "Password", + "username": "Username" + } }, - "error": { - "invalid_auth": "Invalid authentication" - }, - "step": { - "reauth_confirm": { - "data": { - "area_id": "Area ID", - "name": "Name", - "password": "Password", - "username": "Username" - } - }, - "user": { - "data": { - "area_id": "Area ID", - "name": "Name", - "password": "Password", - "username": "Username" - } - } + "user": { + "data": { + "area_id": "Area ID", + "name": "Name", + "password": "Password", + "username": "Username" + } } + } + }, + "options": { + "step": { + "init": { + "data": { + "code": "Default code for locks, used if none is given", + "lock_code_digits": "Number of digits in PIN code for locks" + } + } + }, + "error": { + "code_format_mismatch": "The code does not match the required number of digits" + } } -} \ No newline at end of file + } \ No newline at end of file diff --git a/tests/components/yale_smart_alarm/test_config_flow.py b/tests/components/yale_smart_alarm/test_config_flow.py index 0e1be53be6c..ca4e203a43b 100644 --- a/tests/components/yale_smart_alarm/test_config_flow.py +++ b/tests/components/yale_smart_alarm/test_config_flow.py @@ -9,7 +9,11 @@ from yalesmartalarmclient.exceptions import AuthenticationError from homeassistant import config_entries from homeassistant.components.yale_smart_alarm.const import DOMAIN from homeassistant.core import HomeAssistant -from homeassistant.data_entry_flow import RESULT_TYPE_ABORT, RESULT_TYPE_FORM +from homeassistant.data_entry_flow import ( + RESULT_TYPE_ABORT, + RESULT_TYPE_CREATE_ENTRY, + RESULT_TYPE_FORM, +) from tests.common import MockConfigEntry @@ -77,7 +81,7 @@ async def test_form_invalid_auth(hass: HomeAssistant) -> None: @pytest.mark.parametrize( - "input,output", + "p_input,p_output", [ ( { @@ -107,7 +111,9 @@ async def test_form_invalid_auth(hass: HomeAssistant) -> None: ), ], ) -async def test_import_flow_success(hass, input: dict[str, str], output: dict[str, str]): +async def test_import_flow_success( + hass, p_input: dict[str, str], p_output: dict[str, str] +): """Test a successful import of yaml.""" with patch( @@ -119,13 +125,13 @@ async def test_import_flow_success(hass, input: dict[str, str], output: dict[str result2 = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, - data=input, + data=p_input, ) await hass.async_block_till_done() assert result2["type"] == "create_entry" assert result2["title"] == "test-username" - assert result2["data"] == output + assert result2["data"] == p_output assert len(mock_setup_entry.mock_calls) == 1 @@ -222,3 +228,73 @@ async def test_reauth_flow_invalid_login(hass: HomeAssistant) -> None: assert result2["step_id"] == "reauth_confirm" assert result2["type"] == "form" assert result2["errors"] == {"base": "invalid_auth"} + + +async def test_options_flow(hass: HomeAssistant) -> None: + """Test options config flow.""" + entry = MockConfigEntry( + domain=DOMAIN, + unique_id="test-username", + data={}, + ) + entry.add_to_hass(hass) + + with patch( + "homeassistant.components.yale_smart_alarm.async_setup_entry", + return_value=True, + ): + assert await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + result = await hass.config_entries.options.async_init(entry.entry_id) + + assert result["type"] == RESULT_TYPE_FORM + assert result["step_id"] == "init" + + result = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={"code": "123456", "lock_code_digits": 6}, + ) + + assert result["type"] == RESULT_TYPE_CREATE_ENTRY + assert result["data"] == {"code": "123456", "lock_code_digits": 6} + + +async def test_options_flow_format_mismatch(hass: HomeAssistant) -> None: + """Test options config flow with a code format mismatch error.""" + entry = MockConfigEntry( + domain=DOMAIN, + unique_id="test-username", + data={}, + ) + entry.add_to_hass(hass) + + with patch( + "homeassistant.components.yale_smart_alarm.async_setup_entry", + return_value=True, + ): + assert await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + result = await hass.config_entries.options.async_init(entry.entry_id) + + assert result["type"] == RESULT_TYPE_FORM + assert result["step_id"] == "init" + assert result["errors"] == {} + + result = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={"code": "123", "lock_code_digits": 6}, + ) + + assert result["type"] == RESULT_TYPE_FORM + assert result["step_id"] == "init" + assert result["errors"] == {"base": "code_format_mismatch"} + + result = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={"code": "123456", "lock_code_digits": 6}, + ) + + assert result["type"] == RESULT_TYPE_CREATE_ENTRY + assert result["data"] == {"code": "123456", "lock_code_digits": 6}