From 066af3a5da596c44f020547fed9988a526325514 Mon Sep 17 00:00:00 2001 From: G Johansson Date: Tue, 26 Nov 2024 10:29:46 +0100 Subject: [PATCH] Add reconfigure flow to filesize (#131106) --- .../components/filesize/config_flow.py | 51 +++++--- .../components/filesize/strings.json | 8 +- tests/components/filesize/test_config_flow.py | 118 +++++++++++++++++- 3 files changed, 156 insertions(+), 21 deletions(-) diff --git a/homeassistant/components/filesize/config_flow.py b/homeassistant/components/filesize/config_flow.py index 51eff46bdb3ac7..8ffe3f94353caf 100644 --- a/homeassistant/components/filesize/config_flow.py +++ b/homeassistant/components/filesize/config_flow.py @@ -11,7 +11,6 @@ from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_FILE_PATH from homeassistant.core import HomeAssistant -from homeassistant.exceptions import HomeAssistantError from .const import DOMAIN @@ -20,20 +19,20 @@ _LOGGER = logging.getLogger(__name__) -def validate_path(hass: HomeAssistant, path: str) -> str: +def validate_path(hass: HomeAssistant, path: str) -> tuple[str | None, dict[str, str]]: """Validate path.""" get_path = pathlib.Path(path) if not get_path.exists() or not get_path.is_file(): _LOGGER.error("Can not access file %s", path) - raise NotValidError + return (None, {"base": "not_valid"}) if not hass.config.is_allowed_path(path): _LOGGER.error("Filepath %s is not allowed", path) - raise NotAllowedError + return (None, {"base": "not_allowed"}) full_path = get_path.absolute() - return str(full_path) + return (str(full_path), {}) class FilesizeConfigFlow(ConfigFlow, domain=DOMAIN): @@ -45,18 +44,13 @@ async def async_step_user( self, user_input: dict[str, Any] | None = None ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" - errors: dict[str, Any] = {} + errors: dict[str, str] = {} if user_input is not None: - try: - full_path = await self.hass.async_add_executor_job( - validate_path, self.hass, user_input[CONF_FILE_PATH] - ) - except NotValidError: - errors["base"] = "not_valid" - except NotAllowedError: - errors["base"] = "not_allowed" - else: + full_path, errors = await self.hass.async_add_executor_job( + validate_path, self.hass, user_input[CONF_FILE_PATH] + ) + if not errors: await self.async_set_unique_id(full_path) self._abort_if_unique_id_configured() @@ -70,10 +64,29 @@ async def async_step_user( step_id="user", data_schema=DATA_SCHEMA, errors=errors ) + async def async_step_reconfigure( + self, user_input: dict[str, Any] | None = None + ) -> ConfigFlowResult: + """Handle a reconfigure flow initialized by the user.""" + errors: dict[str, str] = {} -class NotValidError(HomeAssistantError): - """Path is not valid error.""" + if user_input is not None: + reconfigure_entry = self._get_reconfigure_entry() + full_path, errors = await self.hass.async_add_executor_job( + validate_path, self.hass, user_input[CONF_FILE_PATH] + ) + if not errors: + await self.async_set_unique_id(full_path) + self._abort_if_unique_id_configured() + name = str(user_input[CONF_FILE_PATH]).rsplit("/", maxsplit=1)[-1] + return self.async_update_reload_and_abort( + reconfigure_entry, + title=name, + unique_id=self.unique_id, + data_updates={CONF_FILE_PATH: user_input[CONF_FILE_PATH]}, + ) -class NotAllowedError(HomeAssistantError): - """Path is not allowed error.""" + return self.async_show_form( + step_id="reconfigure", data_schema=DATA_SCHEMA, errors=errors + ) diff --git a/homeassistant/components/filesize/strings.json b/homeassistant/components/filesize/strings.json index 27d83d9fb62622..6623cf9c375309 100644 --- a/homeassistant/components/filesize/strings.json +++ b/homeassistant/components/filesize/strings.json @@ -5,6 +5,11 @@ "data": { "file_path": "Path to file" } + }, + "reconfigure": { + "data": { + "file_path": "[%key:component::filesize::config::step::user::data::file_path%]" + } } }, "error": { @@ -12,7 +17,8 @@ "not_allowed": "Path is not allowed" }, "abort": { - "already_configured": "[%key:common::config_flow::abort::already_configured_service%]" + "already_configured": "[%key:common::config_flow::abort::already_configured_service%]", + "reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]" } }, "title": "Filesize", diff --git a/tests/components/filesize/test_config_flow.py b/tests/components/filesize/test_config_flow.py index 4b275e66d029a2..383b1f596f8b4a 100644 --- a/tests/components/filesize/test_config_flow.py +++ b/tests/components/filesize/test_config_flow.py @@ -11,7 +11,7 @@ from homeassistant.core import HomeAssistant from homeassistant.data_entry_flow import FlowResultType -from . import TEST_FILE_NAME, async_create_file +from . import TEST_FILE_NAME, TEST_FILE_NAME2, async_create_file from tests.common import MockConfigEntry @@ -108,3 +108,119 @@ async def test_flow_fails_on_validation(hass: HomeAssistant, tmp_path: Path) -> assert result2["data"] == { CONF_FILE_PATH: test_file, } + + +async def test_reconfigure_flow( + hass: HomeAssistant, mock_config_entry: MockConfigEntry, tmp_path: Path +) -> None: + """Test a reconfigure flow.""" + test_file = str(tmp_path.joinpath(TEST_FILE_NAME2)) + await async_create_file(hass, test_file) + hass.config.allowlist_external_dirs = {tmp_path} + mock_config_entry.add_to_hass(hass) + + result = await mock_config_entry.start_reconfigure_flow(hass) + assert result["step_id"] == "reconfigure" + assert result["type"] is FlowResultType.FORM + assert result["errors"] == {} + + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_FILE_PATH: test_file}, + ) + await hass.async_block_till_done() + + assert result2["type"] is FlowResultType.ABORT + assert result2["reason"] == "reconfigure_successful" + assert mock_config_entry.data == {CONF_FILE_PATH: str(test_file)} + + +async def test_unique_id_already_exist_in_reconfigure_flow( + hass: HomeAssistant, tmp_path: Path +) -> None: + """Test a reconfigure flow fails when unique id already exist.""" + test_file = str(tmp_path.joinpath(TEST_FILE_NAME)) + test_file2 = str(tmp_path.joinpath(TEST_FILE_NAME2)) + await async_create_file(hass, test_file) + await async_create_file(hass, test_file2) + hass.config.allowlist_external_dirs = {tmp_path} + test_file = str(tmp_path.joinpath(TEST_FILE_NAME)) + mock_config_entry = MockConfigEntry( + title=TEST_FILE_NAME, + domain=DOMAIN, + data={CONF_FILE_PATH: test_file}, + unique_id=test_file, + ) + mock_config_entry2 = MockConfigEntry( + title=TEST_FILE_NAME2, + domain=DOMAIN, + data={CONF_FILE_PATH: test_file2}, + unique_id=test_file2, + ) + mock_config_entry.add_to_hass(hass) + mock_config_entry2.add_to_hass(hass) + + result = await mock_config_entry.start_reconfigure_flow(hass) + assert result["step_id"] == "reconfigure" + assert result["type"] is FlowResultType.FORM + assert result["errors"] == {} + + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_FILE_PATH: test_file2}, + ) + await hass.async_block_till_done() + + assert result2["type"] is FlowResultType.ABORT + assert result2["reason"] == "already_configured" + + +async def test_reconfigure_flow_fails_on_validation( + hass: HomeAssistant, mock_config_entry: MockConfigEntry, tmp_path: Path +) -> None: + """Test config flow errors in reconfigure.""" + test_file2 = str(tmp_path.joinpath(TEST_FILE_NAME2)) + hass.config.allowlist_external_dirs = {} + + mock_config_entry.add_to_hass(hass) + result = await mock_config_entry.start_reconfigure_flow(hass) + + assert result["type"] is FlowResultType.FORM + assert result["step_id"] == "reconfigure" + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={ + CONF_FILE_PATH: test_file2, + }, + ) + + assert result["errors"] == {"base": "not_valid"} + + await async_create_file(hass, test_file2) + + with patch( + "homeassistant.components.filesize.config_flow.pathlib.Path", + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={ + CONF_FILE_PATH: test_file2, + }, + ) + + assert result2["errors"] == {"base": "not_allowed"} + + hass.config.allowlist_external_dirs = {tmp_path} + with patch( + "homeassistant.components.filesize.config_flow.pathlib.Path", + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={ + CONF_FILE_PATH: test_file2, + }, + ) + + assert result2["type"] is FlowResultType.ABORT + assert result2["reason"] == "reconfigure_successful"