Add reconfigure step for here_travel_time (#114667)

* Add reconfigure step for here_travel_time

* Add comments, reuse step_user, TYPE_CHECKING, remove defaults
This commit is contained in:
Kevin Stillhammer 2024-07-31 15:08:25 +02:00 committed by GitHub
parent e64e3c2778
commit cddb3bb668
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 235 additions and 53 deletions

View file

@ -3,7 +3,7 @@
from __future__ import annotations
import logging
from typing import Any
from typing import TYPE_CHECKING, Any
from here_routing import (
HERERoutingApi,
@ -104,6 +104,8 @@ class HERETravelTimeConfigFlow(ConfigFlow, domain=DOMAIN):
def __init__(self) -> None:
"""Init Config Flow."""
self._config: dict[str, Any] = {}
self._entry: ConfigEntry | None = None
self._is_reconfigure_flow: bool = False
@staticmethod
@callback
@ -119,21 +121,36 @@ class HERETravelTimeConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle the initial step."""
errors = {}
user_input = user_input or {}
if user_input:
try:
await async_validate_api_key(user_input[CONF_API_KEY])
except HERERoutingUnauthorizedError:
errors["base"] = "invalid_auth"
except (HERERoutingError, HERETransitError):
_LOGGER.exception("Unexpected exception")
errors["base"] = "unknown"
if not errors:
self._config = user_input
return await self.async_step_origin_menu()
if not self._is_reconfigure_flow: # Always show form first for reconfiguration
if user_input:
try:
await async_validate_api_key(user_input[CONF_API_KEY])
except HERERoutingUnauthorizedError:
errors["base"] = "invalid_auth"
except (HERERoutingError, HERETransitError):
_LOGGER.exception("Unexpected exception")
errors["base"] = "unknown"
if not errors:
self._config[CONF_NAME] = user_input[CONF_NAME]
self._config[CONF_API_KEY] = user_input[CONF_API_KEY]
self._config[CONF_MODE] = user_input[CONF_MODE]
return await self.async_step_origin_menu()
self._is_reconfigure_flow = False
return self.async_show_form(
step_id="user", data_schema=get_user_step_schema(user_input), errors=errors
)
async def async_step_reconfigure(
self, _: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle reconfiguration."""
self._is_reconfigure_flow = True
self._entry = self.hass.config_entries.async_get_entry(self.context["entry_id"])
if TYPE_CHECKING:
assert self._entry
self._config = self._entry.data.copy()
return await self.async_step_user(self._config)
async def async_step_origin_menu(self, _: None = None) -> ConfigFlowResult:
"""Show the origin menu."""
return self.async_show_menu(
@ -150,37 +167,57 @@ class HERETravelTimeConfigFlow(ConfigFlow, domain=DOMAIN):
self._config[CONF_ORIGIN_LONGITUDE] = user_input[CONF_ORIGIN][
CONF_LONGITUDE
]
# Remove possible previous configuration using an entity_id
self._config.pop(CONF_ORIGIN_ENTITY_ID, None)
return await self.async_step_destination_menu()
schema = vol.Schema(
schema = self.add_suggested_values_to_schema(
vol.Schema(
{
vol.Required(
CONF_ORIGIN,
): LocationSelector()
}
),
{
vol.Required(
CONF_ORIGIN,
default={
CONF_LATITUDE: self.hass.config.latitude,
CONF_LONGITUDE: self.hass.config.longitude,
},
): LocationSelector()
}
CONF_ORIGIN: {
CONF_LATITUDE: self._config.get(CONF_ORIGIN_LATITUDE)
or self.hass.config.latitude,
CONF_LONGITUDE: self._config.get(CONF_ORIGIN_LONGITUDE)
or self.hass.config.longitude,
}
},
)
return self.async_show_form(step_id="origin_coordinates", data_schema=schema)
async def async_step_destination_menu(self, _: None = None) -> ConfigFlowResult:
"""Show the destination menu."""
return self.async_show_menu(
step_id="destination_menu",
menu_options=["destination_coordinates", "destination_entity"],
)
async def async_step_origin_entity(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Configure origin by using an entity."""
if user_input is not None:
self._config[CONF_ORIGIN_ENTITY_ID] = user_input[CONF_ORIGIN_ENTITY_ID]
# Remove possible previous configuration using coordinates
self._config.pop(CONF_ORIGIN_LATITUDE, None)
self._config.pop(CONF_ORIGIN_LONGITUDE, None)
return await self.async_step_destination_menu()
schema = vol.Schema({vol.Required(CONF_ORIGIN_ENTITY_ID): EntitySelector()})
schema = self.add_suggested_values_to_schema(
vol.Schema(
{
vol.Required(
CONF_ORIGIN_ENTITY_ID,
): EntitySelector()
}
),
{CONF_ORIGIN_ENTITY_ID: self._config.get(CONF_ORIGIN_ENTITY_ID)},
)
return self.async_show_form(step_id="origin_entity", data_schema=schema)
async def async_step_destination_menu(self, _: None = None) -> ConfigFlowResult:
"""Show the destination menu."""
return self.async_show_menu(
step_id="destination_menu",
menu_options=["destination_coordinates", "destination_entity"],
)
async def async_step_destination_coordinates(
self,
user_input: dict[str, Any] | None = None,
@ -193,21 +230,36 @@ class HERETravelTimeConfigFlow(ConfigFlow, domain=DOMAIN):
self._config[CONF_DESTINATION_LONGITUDE] = user_input[CONF_DESTINATION][
CONF_LONGITUDE
]
# Remove possible previous configuration using an entity_id
self._config.pop(CONF_DESTINATION_ENTITY_ID, None)
if self._entry:
return self.async_update_reload_and_abort(
self._entry,
title=self._config[CONF_NAME],
data=self._config,
reason="reconfigure_successful",
)
return self.async_create_entry(
title=self._config[CONF_NAME],
data=self._config,
options=DEFAULT_OPTIONS,
)
schema = vol.Schema(
schema = self.add_suggested_values_to_schema(
vol.Schema(
{
vol.Required(
CONF_DESTINATION,
): LocationSelector()
}
),
{
vol.Required(
CONF_DESTINATION,
default={
CONF_LATITUDE: self.hass.config.latitude,
CONF_LONGITUDE: self.hass.config.longitude,
},
): LocationSelector()
}
CONF_DESTINATION: {
CONF_LATITUDE: self._config.get(CONF_DESTINATION_LATITUDE)
or self.hass.config.latitude,
CONF_LONGITUDE: self._config.get(CONF_DESTINATION_LONGITUDE)
or self.hass.config.longitude,
},
},
)
return self.async_show_form(
step_id="destination_coordinates", data_schema=schema
@ -222,13 +274,27 @@ class HERETravelTimeConfigFlow(ConfigFlow, domain=DOMAIN):
self._config[CONF_DESTINATION_ENTITY_ID] = user_input[
CONF_DESTINATION_ENTITY_ID
]
# Remove possible previous configuration using coordinates
self._config.pop(CONF_DESTINATION_LATITUDE, None)
self._config.pop(CONF_DESTINATION_LONGITUDE, None)
if self._entry:
return self.async_update_reload_and_abort(
self._entry, data=self._config, reason="reconfigure_successful"
)
return self.async_create_entry(
title=self._config[CONF_NAME],
data=self._config,
options=DEFAULT_OPTIONS,
)
schema = vol.Schema(
{vol.Required(CONF_DESTINATION_ENTITY_ID): EntitySelector()}
schema = self.add_suggested_values_to_schema(
vol.Schema(
{
vol.Required(
CONF_DESTINATION_ENTITY_ID,
): EntitySelector()
}
),
{CONF_DESTINATION_ENTITY_ID: self._config.get(CONF_DESTINATION_ENTITY_ID)},
)
return self.async_show_form(step_id="destination_entity", data_schema=schema)
@ -249,15 +315,22 @@ class HERETravelTimeOptionsFlow(OptionsFlow):
self._config = user_input
return await self.async_step_time_menu()
schema = vol.Schema(
schema = self.add_suggested_values_to_schema(
vol.Schema(
{
vol.Optional(
CONF_ROUTE_MODE,
default=self.config_entry.options.get(
CONF_ROUTE_MODE, DEFAULT_OPTIONS[CONF_ROUTE_MODE]
),
): vol.In(ROUTE_MODES),
}
),
{
vol.Optional(
CONF_ROUTE_MODE,
default=self.config_entry.options.get(
CONF_ROUTE_MODE, DEFAULT_OPTIONS[CONF_ROUTE_MODE]
),
): vol.In(ROUTE_MODES),
}
CONF_ROUTE_MODE: self.config_entry.options.get(
CONF_ROUTE_MODE, DEFAULT_OPTIONS[CONF_ROUTE_MODE]
),
},
)
return self.async_show_form(step_id="init", data_schema=schema)
@ -283,8 +356,11 @@ class HERETravelTimeOptionsFlow(OptionsFlow):
self._config[CONF_ARRIVAL_TIME] = user_input[CONF_ARRIVAL_TIME]
return self.async_create_entry(title="", data=self._config)
schema = vol.Schema(
{vol.Required(CONF_ARRIVAL_TIME, default="00:00:00"): TimeSelector()}
schema = self.add_suggested_values_to_schema(
vol.Schema(
{vol.Required(CONF_ARRIVAL_TIME, default="00:00:00"): TimeSelector()}
),
{CONF_ARRIVAL_TIME: "00:00:00"},
)
return self.async_show_form(step_id="arrival_time", data_schema=schema)
@ -297,8 +373,11 @@ class HERETravelTimeOptionsFlow(OptionsFlow):
self._config[CONF_DEPARTURE_TIME] = user_input[CONF_DEPARTURE_TIME]
return self.async_create_entry(title="", data=self._config)
schema = vol.Schema(
{vol.Required(CONF_DEPARTURE_TIME, default="00:00:00"): TimeSelector()}
schema = self.add_suggested_values_to_schema(
vol.Schema(
{vol.Required(CONF_DEPARTURE_TIME, default="00:00:00"): TimeSelector()}
),
{CONF_DEPARTURE_TIME: "00:00:00"},
)
return self.async_show_form(step_id="departure_time", data_schema=schema)

View file

@ -52,7 +52,8 @@
"unknown": "[%key:common::config_flow::error::unknown%]"
},
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]"
}
},
"options": {

View file

@ -6,17 +6,20 @@ from here_routing import HERERoutingError, HERERoutingUnauthorizedError
import pytest
from homeassistant import config_entries
from homeassistant.components.here_travel_time.config_flow import DEFAULT_OPTIONS
from homeassistant.components.here_travel_time.const import (
CONF_ARRIVAL_TIME,
CONF_DEPARTURE_TIME,
CONF_DESTINATION_ENTITY_ID,
CONF_DESTINATION_LATITUDE,
CONF_DESTINATION_LONGITUDE,
CONF_ORIGIN_ENTITY_ID,
CONF_ORIGIN_LATITUDE,
CONF_ORIGIN_LONGITUDE,
CONF_ROUTE_MODE,
DOMAIN,
ROUTE_MODE_FASTEST,
TRAVEL_MODE_BICYCLE,
TRAVEL_MODE_CAR,
TRAVEL_MODE_PUBLIC,
)
@ -249,6 +252,105 @@ async def test_step_destination_entity(
}
@pytest.mark.usefixtures("valid_response")
async def test_reconfigure_destination_entity(hass: HomeAssistant) -> None:
"""Test reconfigure flow when choosing a destination entity."""
origin_entity_selector_result = await do_common_reconfiguration_steps(hass)
menu_result = await hass.config_entries.flow.async_configure(
origin_entity_selector_result["flow_id"], {"next_step_id": "destination_entity"}
)
assert menu_result["type"] is FlowResultType.FORM
destination_entity_selector_result = await hass.config_entries.flow.async_configure(
menu_result["flow_id"],
{"destination_entity_id": "zone.home"},
)
assert destination_entity_selector_result["type"] is FlowResultType.ABORT
assert destination_entity_selector_result["reason"] == "reconfigure_successful"
entry = hass.config_entries.async_entries(DOMAIN)[0]
assert entry.data == {
CONF_NAME: "test",
CONF_API_KEY: API_KEY,
CONF_ORIGIN_ENTITY_ID: "zone.home",
CONF_DESTINATION_ENTITY_ID: "zone.home",
CONF_MODE: TRAVEL_MODE_BICYCLE,
}
@pytest.mark.usefixtures("valid_response")
async def test_reconfigure_destination_coordinates(hass: HomeAssistant) -> None:
"""Test reconfigure flow when choosing destination coordinates."""
origin_entity_selector_result = await do_common_reconfiguration_steps(hass)
menu_result = await hass.config_entries.flow.async_configure(
origin_entity_selector_result["flow_id"],
{"next_step_id": "destination_coordinates"},
)
assert menu_result["type"] is FlowResultType.FORM
destination_entity_selector_result = await hass.config_entries.flow.async_configure(
menu_result["flow_id"],
{
"destination": {
"latitude": 43.0,
"longitude": -80.3,
"radius": 5.0,
}
},
)
assert destination_entity_selector_result["type"] is FlowResultType.ABORT
assert destination_entity_selector_result["reason"] == "reconfigure_successful"
entry = hass.config_entries.async_entries(DOMAIN)[0]
assert entry.data == {
CONF_NAME: "test",
CONF_API_KEY: API_KEY,
CONF_ORIGIN_ENTITY_ID: "zone.home",
CONF_DESTINATION_LATITUDE: 43.0,
CONF_DESTINATION_LONGITUDE: -80.3,
CONF_MODE: TRAVEL_MODE_BICYCLE,
}
async def do_common_reconfiguration_steps(hass: HomeAssistant) -> None:
"""Walk through common flow steps for reconfiguring."""
entry = MockConfigEntry(
domain=DOMAIN,
unique_id="0123456789",
data=DEFAULT_CONFIG,
options=DEFAULT_OPTIONS,
)
entry.add_to_hass(hass)
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
reconfigure_result = await hass.config_entries.flow.async_init(
DOMAIN,
context={
"source": config_entries.SOURCE_RECONFIGURE,
"entry_id": entry.entry_id,
},
)
assert reconfigure_result["type"] is FlowResultType.FORM
assert reconfigure_result["step_id"] == "user"
user_step_result = await hass.config_entries.flow.async_configure(
reconfigure_result["flow_id"],
{
CONF_API_KEY: API_KEY,
CONF_MODE: TRAVEL_MODE_BICYCLE,
CONF_NAME: "test",
},
)
await hass.async_block_till_done()
menu_result = await hass.config_entries.flow.async_configure(
user_step_result["flow_id"], {"next_step_id": "origin_entity"}
)
return await hass.config_entries.flow.async_configure(
menu_result["flow_id"],
{"origin_entity_id": "zone.home"},
)
async def test_form_invalid_auth(hass: HomeAssistant) -> None:
"""Test we handle invalid auth."""
result = await hass.config_entries.flow.async_init(