From 93fa9e778b6a584f17529402eb39167f2ee51cdb Mon Sep 17 00:00:00 2001 From: Kevin Stillhammer Date: Sun, 9 Jun 2024 18:13:32 +0200 Subject: [PATCH] Add reconfigure step for google_travel_time (#115178) * Add reconfigure step for google_travel_time * Do not allow to change name * Duplicate tests for reconfigure * Use link for description in strings.json * Try except else * Extend existing config flow tests --- .../google_travel_time/config_flow.py | 68 ++++- .../google_travel_time/strings.json | 11 +- tests/components/google_travel_time/const.py | 6 + .../google_travel_time/test_config_flow.py | 240 +++++++++++++++++- 4 files changed, 300 insertions(+), 25 deletions(-) diff --git a/homeassistant/components/google_travel_time/config_flow.py b/homeassistant/components/google_travel_time/config_flow.py index 424ad56b9d4..d8ba7643bc9 100644 --- a/homeassistant/components/google_travel_time/config_flow.py +++ b/homeassistant/components/google_travel_time/config_flow.py @@ -2,6 +2,8 @@ from __future__ import annotations +from typing import TYPE_CHECKING, Any + import voluptuous as vol from homeassistant.config_entries import ( @@ -49,6 +51,20 @@ from .const import ( ) from .helpers import InvalidApiKeyException, UnknownException, validate_config_entry +RECONFIGURE_SCHEMA = vol.Schema( + { + vol.Required(CONF_API_KEY): cv.string, + vol.Required(CONF_DESTINATION): cv.string, + vol.Required(CONF_ORIGIN): cv.string, + } +) + +CONFIG_SCHEMA = RECONFIGURE_SCHEMA.extend( + { + vol.Required(CONF_NAME, default=DEFAULT_NAME): cv.string, + } +) + OPTIONS_SCHEMA = vol.Schema( { vol.Required(CONF_MODE): SelectSelector( @@ -190,29 +206,61 @@ class GoogleTravelTimeConfigFlow(ConfigFlow, domain=DOMAIN): user_input[CONF_ORIGIN], user_input[CONF_DESTINATION], ) + except InvalidApiKeyException: + errors["base"] = "invalid_auth" + except TimeoutError: + errors["base"] = "timeout_connect" + except UnknownException: + errors["base"] = "cannot_connect" + else: return self.async_create_entry( title=user_input.get(CONF_NAME, DEFAULT_NAME), data=user_input, options=default_options(self.hass), ) + + return self.async_show_form( + step_id="user", + data_schema=self.add_suggested_values_to_schema(CONFIG_SCHEMA, user_input), + errors=errors, + ) + + async def async_step_reconfigure( + self, user_input: dict[str, Any] | None = None + ) -> ConfigFlowResult: + """Handle reconfiguration.""" + entry = self.hass.config_entries.async_get_entry(self.context["entry_id"]) + if TYPE_CHECKING: + assert entry + + errors = {} + user_input = user_input or {} + if user_input: + try: + await self.hass.async_add_executor_job( + validate_config_entry, + self.hass, + user_input[CONF_API_KEY], + user_input[CONF_ORIGIN], + user_input[CONF_DESTINATION], + ) except InvalidApiKeyException: errors["base"] = "invalid_auth" except TimeoutError: errors["base"] = "timeout_connect" except UnknownException: errors["base"] = "cannot_connect" + else: + return self.async_update_reload_and_abort( + entry, + data=user_input, + reason="reconfigure_successful", + ) return self.async_show_form( - step_id="user", - data_schema=vol.Schema( - { - vol.Required( - CONF_NAME, default=user_input.get(CONF_NAME, DEFAULT_NAME) - ): cv.string, - vol.Required(CONF_API_KEY): cv.string, - vol.Required(CONF_DESTINATION): cv.string, - vol.Required(CONF_ORIGIN): cv.string, - } + step_id="reconfigure", + data_schema=self.add_suggested_values_to_schema( + RECONFIGURE_SCHEMA, entry.data.copy() ), errors=errors, ) diff --git a/homeassistant/components/google_travel_time/strings.json b/homeassistant/components/google_travel_time/strings.json index 2c7840b23d8..765cfc9c4b6 100644 --- a/homeassistant/components/google_travel_time/strings.json +++ b/homeassistant/components/google_travel_time/strings.json @@ -10,6 +10,14 @@ "origin": "Origin", "destination": "Destination" } + }, + "reconfigure": { + "description": "[%key:component::google_travel_time::config::step::user::description%]", + "data": { + "api_key": "[%key:common::config_flow::data::api_key%]", + "origin": "[%key:component::google_travel_time::config::step::user::data::origin%]", + "destination": "[%key:component::google_travel_time::config::step::user::data::destination%]" + } } }, "error": { @@ -18,7 +26,8 @@ "timeout_connect": "[%key:common::config_flow::error::timeout_connect%]" }, "abort": { - "already_configured": "[%key:common::config_flow::abort::already_configured_location%]" + "already_configured": "[%key:common::config_flow::abort::already_configured_location%]", + "reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]" } }, "options": { diff --git a/tests/components/google_travel_time/const.py b/tests/components/google_travel_time/const.py index 77e99ffbf68..29cf32b8e29 100644 --- a/tests/components/google_travel_time/const.py +++ b/tests/components/google_travel_time/const.py @@ -11,3 +11,9 @@ MOCK_CONFIG = { CONF_ORIGIN: "location1", CONF_DESTINATION: "location2", } + +RECONFIGURE_CONFIG = { + CONF_API_KEY: "api_key2", + CONF_ORIGIN: "location3", + CONF_DESTINATION: "location4", +} diff --git a/tests/components/google_travel_time/test_config_flow.py b/tests/components/google_travel_time/test_config_flow.py index 6e73bfd8d23..e9b383a0120 100644 --- a/tests/components/google_travel_time/test_config_flow.py +++ b/tests/components/google_travel_time/test_config_flow.py @@ -1,5 +1,7 @@ """Test the Google Maps Travel Time config flow.""" +from unittest.mock import patch + import pytest from homeassistant import config_entries @@ -25,7 +27,58 @@ from homeassistant.const import CONF_API_KEY, CONF_LANGUAGE, CONF_MODE, CONF_NAM from homeassistant.core import HomeAssistant from homeassistant.data_entry_flow import FlowResultType -from .const import MOCK_CONFIG +from .const import MOCK_CONFIG, RECONFIGURE_CONFIG + + +async def assert_common_reconfigure_steps( + hass: HomeAssistant, reconfigure_result: config_entries.ConfigFlowResult +) -> None: + """Step through and assert the happy case reconfigure flow.""" + with ( + patch("homeassistant.components.google_travel_time.helpers.Client"), + patch( + "homeassistant.components.google_travel_time.helpers.distance_matrix", + return_value=None, + ), + ): + reconfigure_successful_result = await hass.config_entries.flow.async_configure( + reconfigure_result["flow_id"], + RECONFIGURE_CONFIG, + ) + assert reconfigure_successful_result["type"] is FlowResultType.ABORT + assert reconfigure_successful_result["reason"] == "reconfigure_successful" + await hass.async_block_till_done() + + entry = hass.config_entries.async_entries(DOMAIN)[0] + assert entry.data == RECONFIGURE_CONFIG + + +async def assert_common_create_steps( + hass: HomeAssistant, user_step_result: config_entries.ConfigFlowResult +) -> None: + """Step through and assert the happy case create flow.""" + with ( + patch("homeassistant.components.google_travel_time.helpers.Client"), + patch( + "homeassistant.components.google_travel_time.helpers.distance_matrix", + return_value=None, + ), + ): + create_result = await hass.config_entries.flow.async_configure( + user_step_result["flow_id"], + MOCK_CONFIG, + ) + assert create_result["type"] is FlowResultType.CREATE_ENTRY + await hass.async_block_till_done() + + entry = hass.config_entries.async_entries(DOMAIN)[0] + assert entry.title == DEFAULT_NAME + assert entry.data == { + CONF_NAME: DEFAULT_NAME, + CONF_API_KEY: "api_key", + CONF_ORIGIN: "location1", + CONF_DESTINATION: "location2", + } @pytest.mark.usefixtures("validate_config_entry", "bypass_setup") @@ -37,19 +90,7 @@ async def test_minimum_fields(hass: HomeAssistant) -> None: assert result["type"] is FlowResultType.FORM assert result["errors"] == {} - result2 = await hass.config_entries.flow.async_configure( - result["flow_id"], - MOCK_CONFIG, - ) - - assert result2["type"] is FlowResultType.CREATE_ENTRY - assert result2["title"] == DEFAULT_NAME - assert result2["data"] == { - CONF_NAME: DEFAULT_NAME, - CONF_API_KEY: "api_key", - CONF_ORIGIN: "location1", - CONF_DESTINATION: "location2", - } + await assert_common_create_steps(hass, result) @pytest.mark.usefixtures("invalidate_config_entry") @@ -67,6 +108,7 @@ async def test_invalid_config_entry(hass: HomeAssistant) -> None: assert result2["type"] is FlowResultType.FORM assert result2["errors"] == {"base": "cannot_connect"} + await assert_common_create_steps(hass, result2) @pytest.mark.usefixtures("invalid_api_key") @@ -84,6 +126,7 @@ async def test_invalid_api_key(hass: HomeAssistant) -> None: assert result2["type"] is FlowResultType.FORM assert result2["errors"] == {"base": "invalid_auth"} + await assert_common_create_steps(hass, result2) @pytest.mark.usefixtures("transport_error") @@ -101,6 +144,7 @@ async def test_transport_error(hass: HomeAssistant) -> None: assert result2["type"] is FlowResultType.FORM assert result2["errors"] == {"base": "cannot_connect"} + await assert_common_create_steps(hass, result2) @pytest.mark.usefixtures("timeout") @@ -118,6 +162,7 @@ async def test_timeout(hass: HomeAssistant) -> None: assert result2["type"] is FlowResultType.FORM assert result2["errors"] == {"base": "timeout_connect"} + await assert_common_create_steps(hass, result2) async def test_malformed_api_key(hass: HomeAssistant) -> None: @@ -136,6 +181,173 @@ async def test_malformed_api_key(hass: HomeAssistant) -> None: assert result2["errors"] == {"base": "invalid_auth"} +@pytest.mark.parametrize( + ("data", "options"), + [ + ( + MOCK_CONFIG, + { + CONF_MODE: "driving", + CONF_UNITS: UNITS_IMPERIAL, + }, + ) + ], +) +@pytest.mark.usefixtures("validate_config_entry", "bypass_setup") +async def test_reconfigure(hass: HomeAssistant, mock_config) -> None: + """Test reconfigure flow.""" + reconfigure_result = await hass.config_entries.flow.async_init( + DOMAIN, + context={ + "source": config_entries.SOURCE_RECONFIGURE, + "entry_id": mock_config.entry_id, + }, + ) + assert reconfigure_result["type"] is FlowResultType.FORM + assert reconfigure_result["step_id"] == "reconfigure" + + await assert_common_reconfigure_steps(hass, reconfigure_result) + + +@pytest.mark.parametrize( + ("data", "options"), + [ + ( + MOCK_CONFIG, + { + CONF_MODE: "driving", + CONF_UNITS: UNITS_IMPERIAL, + }, + ) + ], +) +@pytest.mark.usefixtures("invalidate_config_entry") +async def test_reconfigure_invalid_config_entry( + hass: HomeAssistant, mock_config +) -> None: + """Test we get the form.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={ + "source": config_entries.SOURCE_RECONFIGURE, + "entry_id": mock_config.entry_id, + }, + ) + assert result["type"] is FlowResultType.FORM + assert result["errors"] == {} + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + RECONFIGURE_CONFIG, + ) + + assert result2["type"] is FlowResultType.FORM + assert result2["errors"] == {"base": "cannot_connect"} + + await assert_common_reconfigure_steps(hass, result2) + + +@pytest.mark.parametrize( + ("data", "options"), + [ + ( + MOCK_CONFIG, + { + CONF_MODE: "driving", + CONF_UNITS: UNITS_IMPERIAL, + }, + ) + ], +) +@pytest.mark.usefixtures("invalid_api_key") +async def test_reconfigure_invalid_api_key(hass: HomeAssistant, mock_config) -> None: + """Test we get the form.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={ + "source": config_entries.SOURCE_RECONFIGURE, + "entry_id": mock_config.entry_id, + }, + ) + assert result["type"] is FlowResultType.FORM + assert result["errors"] == {} + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + RECONFIGURE_CONFIG, + ) + + assert result2["type"] is FlowResultType.FORM + assert result2["errors"] == {"base": "invalid_auth"} + await assert_common_reconfigure_steps(hass, result2) + + +@pytest.mark.parametrize( + ("data", "options"), + [ + ( + MOCK_CONFIG, + { + CONF_MODE: "driving", + CONF_UNITS: UNITS_IMPERIAL, + }, + ) + ], +) +@pytest.mark.usefixtures("transport_error") +async def test_reconfigure_transport_error(hass: HomeAssistant, mock_config) -> None: + """Test we get the form.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={ + "source": config_entries.SOURCE_RECONFIGURE, + "entry_id": mock_config.entry_id, + }, + ) + assert result["type"] is FlowResultType.FORM + assert result["errors"] == {} + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + RECONFIGURE_CONFIG, + ) + + assert result2["type"] is FlowResultType.FORM + assert result2["errors"] == {"base": "cannot_connect"} + await assert_common_reconfigure_steps(hass, result2) + + +@pytest.mark.parametrize( + ("data", "options"), + [ + ( + MOCK_CONFIG, + { + CONF_MODE: "driving", + CONF_UNITS: UNITS_IMPERIAL, + }, + ) + ], +) +@pytest.mark.usefixtures("timeout") +async def test_reconfigure_timeout(hass: HomeAssistant, mock_config) -> None: + """Test we get the form.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={ + "source": config_entries.SOURCE_RECONFIGURE, + "entry_id": mock_config.entry_id, + }, + ) + assert result["type"] is FlowResultType.FORM + assert result["errors"] == {} + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + RECONFIGURE_CONFIG, + ) + + assert result2["type"] is FlowResultType.FORM + assert result2["errors"] == {"base": "timeout_connect"} + await assert_common_reconfigure_steps(hass, result2) + + @pytest.mark.parametrize( ("data", "options"), [