diff --git a/homeassistant/components/immich/config_flow.py b/homeassistant/components/immich/config_flow.py index 69fae3ff1eb..98709f25de7 100644 --- a/homeassistant/components/immich/config_flow.py +++ b/homeassistant/components/immich/config_flow.py @@ -172,3 +172,64 @@ class ImmichConfigFlow(ConfigFlow, domain=DOMAIN): description_placeholders={"name": self._name}, errors=errors, ) + + async def async_step_reconfigure( + self, + user_input: Mapping[str, Any] | None = None, + ) -> ConfigFlowResult: + """Handle reconfiguration of immich.""" + entry = self._get_reconfigure_entry() + current_data = entry.data + + url = f"{'https' if current_data[CONF_SSL] else 'http'}://{current_data[CONF_HOST]}:{current_data[CONF_PORT]}" + verify_ssl = current_data[CONF_VERIFY_SSL] + + errors: dict[str, str] = {} + if user_input is not None: + url = user_input[CONF_URL] + verify_ssl = user_input[CONF_VERIFY_SSL] + try: + (host, port, ssl) = _parse_url(user_input[CONF_URL]) + except InvalidUrl: + errors[CONF_URL] = "invalid_url" + else: + try: + await check_user_info( + self.hass, + host, + port, + ssl, + user_input[CONF_VERIFY_SSL], + current_data[CONF_API_KEY], + ) + except ImmichUnauthorizedError: + errors["base"] = "invalid_auth" + except CONNECT_ERRORS: + errors["base"] = "cannot_connect" + except Exception: + _LOGGER.exception("Unexpected exception") + errors["base"] = "unknown" + else: + return self.async_update_reload_and_abort( + entry, + data_updates={ + **current_data, + CONF_HOST: host, + CONF_PORT: port, + CONF_SSL: ssl, + CONF_VERIFY_SSL: user_input[CONF_VERIFY_SSL], + }, + ) + + return self.async_show_form( + step_id="reconfigure", + data_schema=vol.Schema( + { + vol.Required(CONF_URL, default=url): TextSelector( + config=TextSelectorConfig(type=TextSelectorType.URL) + ), + vol.Required(CONF_VERIFY_SSL, default=verify_ssl): bool, + } + ), + errors=errors, + ) diff --git a/homeassistant/components/immich/quality_scale.yaml b/homeassistant/components/immich/quality_scale.yaml index 059327834a4..041db391f6b 100644 --- a/homeassistant/components/immich/quality_scale.yaml +++ b/homeassistant/components/immich/quality_scale.yaml @@ -62,7 +62,7 @@ rules: entity-translations: done exception-translations: done icon-translations: done - reconfiguration-flow: todo + reconfiguration-flow: done repair-issues: status: exempt comment: No repair issues needed diff --git a/homeassistant/components/immich/strings.json b/homeassistant/components/immich/strings.json index e9cd24256b2..322a0a6c8e8 100644 --- a/homeassistant/components/immich/strings.json +++ b/homeassistant/components/immich/strings.json @@ -8,6 +8,7 @@ "abort": { "already_configured": "This user is already configured for this immich instance.", "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]", + "reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]", "unique_id_mismatch": "The provided API key does not match the configured user." }, "error": { @@ -26,6 +27,16 @@ }, "description": "Update the API key for {name}." }, + "reconfigure": { + "data": { + "url": "[%key:common::config_flow::data::url%]", + "verify_ssl": "[%key:common::config_flow::data::verify_ssl%]" + }, + "data_description": { + "url": "[%key:component::immich::common::data_desc_url%]", + "verify_ssl": "[%key:component::immich::common::data_desc_ssl_verify%]" + } + }, "user": { "data": { "api_key": "[%key:common::config_flow::data::api_key%]", diff --git a/tests/components/immich/test_config_flow.py b/tests/components/immich/test_config_flow.py index e26cb4df5a1..cc0f7d40ae5 100644 --- a/tests/components/immich/test_config_flow.py +++ b/tests/components/immich/test_config_flow.py @@ -8,7 +8,14 @@ import pytest from homeassistant.components.immich.const import DOMAIN from homeassistant.config_entries import SOURCE_USER -from homeassistant.const import CONF_API_KEY, CONF_URL +from homeassistant.const import ( + CONF_API_KEY, + CONF_HOST, + CONF_PORT, + CONF_SSL, + CONF_URL, + CONF_VERIFY_SSL, +) from homeassistant.core import HomeAssistant from homeassistant.data_entry_flow import FlowResultType @@ -242,3 +249,108 @@ async def test_reauth_flow_mismatch( assert result["type"] is FlowResultType.ABORT assert result["reason"] == "unique_id_mismatch" + + +async def test_reconfigure_flow( + hass: HomeAssistant, + mock_setup_entry: AsyncMock, + mock_immich: Mock, + mock_config_entry: MockConfigEntry, +) -> None: + """Test reconfigure flow.""" + 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_URL: "https://localhost:8443", CONF_VERIFY_SSL: True}, + ) + + assert result["type"] is FlowResultType.ABORT + assert result["reason"] == "reconfigure_successful" + assert mock_config_entry.data[CONF_HOST] == "localhost" + assert mock_config_entry.data[CONF_PORT] == 8443 + assert mock_config_entry.data[CONF_SSL] is True + assert mock_config_entry.data[CONF_VERIFY_SSL] is True + + +@pytest.mark.parametrize( + ("exception", "error"), + [ + ( + ImmichUnauthorizedError( + { + "message": "Invalid API key", + "error": "Unauthenticated", + "statusCode": 401, + "correlationId": "abcdefg", + } + ), + "invalid_auth", + ), + (ClientError, "cannot_connect"), + (Exception, "unknown"), + ], +) +async def test_step_reconfigure_error_handling( + hass: HomeAssistant, + mock_setup_entry: AsyncMock, + mock_immich: Mock, + mock_config_entry: MockConfigEntry, + exception: Exception, + error: str, +) -> None: + """Test a user initiated config flow with errors.""" + 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" + + mock_immich.users.async_get_my_user.side_effect = exception + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={CONF_URL: "https://localhost:8443", CONF_VERIFY_SSL: True}, + ) + assert result["type"] is FlowResultType.FORM + assert result["step_id"] == "reconfigure" + assert result["errors"] == {"base": error} + + mock_immich.users.async_get_my_user.side_effect = None + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={CONF_URL: "https://localhost:8443", CONF_VERIFY_SSL: True}, + ) + assert result["type"] is FlowResultType.ABORT + assert result["reason"] == "reconfigure_successful" + + +async def test_step_reconfigure_invalid_url( + hass: HomeAssistant, + mock_setup_entry: AsyncMock, + mock_immich: Mock, + mock_config_entry: MockConfigEntry, +) -> None: + """Test a user initiated config flow with errors.""" + 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_URL: "hts://invalid"}, + ) + assert result["type"] is FlowResultType.FORM + assert result["step_id"] == "reconfigure" + assert result["errors"] == {CONF_URL: "invalid_url"} + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={CONF_URL: "https://localhost:8443", CONF_VERIFY_SSL: True}, + ) + assert result["type"] is FlowResultType.ABORT + assert result["reason"] == "reconfigure_successful"