diff --git a/homeassistant/components/waze_travel_time/__init__.py b/homeassistant/components/waze_travel_time/__init__.py index 4dd901e8bdcc..fa36af1a13c2 100644 --- a/homeassistant/components/waze_travel_time/__init__.py +++ b/homeassistant/components/waze_travel_time/__init__.py @@ -21,6 +21,7 @@ from homeassistant.helpers.selector import ( BooleanSelector, DurationSelector, DurationSelectorConfig, + LocationSelector, SelectSelector, SelectSelectorConfig, SelectSelectorMode, @@ -33,6 +34,7 @@ from .const import ( CONF_AVOID_FERRIES, CONF_AVOID_SUBSCRIPTION_ROADS, CONF_AVOID_TOLL_ROADS, + CONF_BASE_COORDINATES, CONF_DESTINATION, CONF_EXCL_FILTER, CONF_INCL_FILTER, @@ -52,6 +54,7 @@ from .const import ( VEHICLE_TYPES, ) from .coordinator import WazeTravelTimeCoordinator, async_get_travel_times +from .helpers import base_coordinates_to_tuple, default_base_coordinates_for_region PLATFORMS = [Platform.SENSOR] @@ -103,6 +106,7 @@ SERVICE_GET_TRAVEL_TIMES_SCHEMA = vol.Schema( vol.Optional(CONF_TIME_DELTA): DurationSelector( DurationSelectorConfig(allow_negative=True, enable_second=False) ), + vol.Optional(CONF_BASE_COORDINATES): LocationSelector(), } ) @@ -137,6 +141,9 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b origin = origin_coordinates or service.data[CONF_ORIGIN] destination = destination_coordinates or service.data[CONF_DESTINATION] + base_coordinates = base_coordinates_to_tuple( + service.data.get(CONF_BASE_COORDINATES) + ) time_delta = int( timedelta( @@ -158,6 +165,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b incl_filters=service.data.get(CONF_INCL_FILTER, DEFAULT_FILTER), excl_filters=service.data.get(CONF_EXCL_FILTER, DEFAULT_FILTER), time_delta=time_delta, + base_coordinates=base_coordinates, ) return {"routes": [vars(route) for route in response]} @@ -218,4 +226,24 @@ async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> config_entry.minor_version, ) + if config_entry.version == 2 and config_entry.minor_version == 2: + _LOGGER.debug( + "Migrating from version %s.%s", + config_entry.version, + config_entry.minor_version, + ) + options = dict(config_entry.options) + options.setdefault( + CONF_BASE_COORDINATES, + default_base_coordinates_for_region(config_entry.data[CONF_REGION]), + ) + hass.config_entries.async_update_entry( + config_entry, options=options, minor_version=3 + ) + _LOGGER.debug( + "Migration to version %s.%s successful", + config_entry.version, + config_entry.minor_version, + ) + return True diff --git a/homeassistant/components/waze_travel_time/config_flow.py b/homeassistant/components/waze_travel_time/config_flow.py index 1b97bed0a884..e15f65393654 100644 --- a/homeassistant/components/waze_travel_time/config_flow.py +++ b/homeassistant/components/waze_travel_time/config_flow.py @@ -13,12 +13,14 @@ from homeassistant.config_entries import ( ConfigFlowResult, OptionsFlow, ) -from homeassistant.const import CONF_NAME, CONF_REGION +from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME, CONF_REGION from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.selector import ( BooleanSelector, DurationSelector, DurationSelectorConfig, + LocationSelector, + LocationSelectorConfig, SelectSelector, SelectSelectorConfig, SelectSelectorMode, @@ -32,6 +34,7 @@ from .const import ( CONF_AVOID_FERRIES, CONF_AVOID_SUBSCRIPTION_ROADS, CONF_AVOID_TOLL_ROADS, + CONF_BASE_COORDINATES, CONF_DESTINATION, CONF_EXCL_FILTER, CONF_INCL_FILTER, @@ -92,6 +95,9 @@ OPTIONS_SCHEMA = vol.Schema( enable_second=False, ) ), + vol.Optional(CONF_BASE_COORDINATES): LocationSelector( + LocationSelectorConfig(radius=False) + ), } ) @@ -114,18 +120,24 @@ CONFIG_SCHEMA = vol.Schema( def default_options( hass: HomeAssistant, -) -> dict[str, str | bool | list[str] | dict[str, int]]: +) -> dict[str, str | bool | list[str] | dict[str, int] | dict[str, float]]: """Get the default options.""" defaults = DEFAULT_OPTIONS.copy() if hass.config.units is US_CUSTOMARY_SYSTEM: defaults[CONF_UNITS] = IMPERIAL_UNITS + defaults[CONF_BASE_COORDINATES] = { + CONF_LATITUDE: hass.config.latitude, + CONF_LONGITUDE: hass.config.longitude, + } return defaults class WazeOptionsFlow(OptionsFlow): """Handle an options flow for Waze Travel Time.""" - async def async_step_init(self, user_input=None) -> ConfigFlowResult: + async def async_step_init( + self, user_input: dict[str, Any] | None = None + ) -> ConfigFlowResult: """Handle the initial step.""" if user_input is not None: if user_input.get(CONF_INCL_FILTER) is None: @@ -151,7 +163,7 @@ class WazeConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Waze Travel Time.""" VERSION = 2 - MINOR_VERSION = 2 + MINOR_VERSION = 3 @staticmethod @callback diff --git a/homeassistant/components/waze_travel_time/const.py b/homeassistant/components/waze_travel_time/const.py index 894c8a6c0a82..fca801d054d7 100644 --- a/homeassistant/components/waze_travel_time/const.py +++ b/homeassistant/components/waze_travel_time/const.py @@ -5,6 +5,7 @@ from __future__ import annotations DOMAIN = "waze_travel_time" SEMAPHORE = "semaphore" +CONF_BASE_COORDINATES = "base_coordinates" CONF_DESTINATION = "destination" CONF_ORIGIN = "origin" CONF_INCL_FILTER = "incl_filter" @@ -33,7 +34,9 @@ UNITS = [METRIC_UNITS, IMPERIAL_UNITS] REGIONS = ["us", "na", "eu", "il", "au"] VEHICLE_TYPES = ["car", "taxi", "motorcycle"] -DEFAULT_OPTIONS: dict[str, str | bool | list[str] | dict[str, int]] = { +DEFAULT_OPTIONS: dict[ + str, str | bool | list[str] | dict[str, int] | dict[str, float] +] = { CONF_REALTIME: DEFAULT_REALTIME, CONF_VEHICLE_TYPE: DEFAULT_VEHICLE_TYPE, CONF_UNITS: METRIC_UNITS, diff --git a/homeassistant/components/waze_travel_time/coordinator.py b/homeassistant/components/waze_travel_time/coordinator.py index 0cf4f4ef7835..f3bdc24a20bb 100644 --- a/homeassistant/components/waze_travel_time/coordinator.py +++ b/homeassistant/components/waze_travel_time/coordinator.py @@ -20,6 +20,7 @@ from .const import ( CONF_AVOID_FERRIES, CONF_AVOID_SUBSCRIPTION_ROADS, CONF_AVOID_TOLL_ROADS, + CONF_BASE_COORDINATES, CONF_DESTINATION, CONF_EXCL_FILTER, CONF_INCL_FILTER, @@ -32,6 +33,7 @@ from .const import ( IMPERIAL_UNITS, SEMAPHORE, ) +from .helpers import base_coordinates_to_tuple _LOGGER = logging.getLogger(__name__) @@ -53,6 +55,7 @@ async def async_get_travel_times( incl_filters: Collection[str] | None = None, excl_filters: Collection[str] | None = None, time_delta: int = 0, + base_coordinates: tuple[float, float] | None = None, ) -> list[CalcRoutesResponse]: """Get all available routes.""" @@ -77,6 +80,7 @@ async def async_get_travel_times( real_time=realtime, alternatives=3, time_delta=time_delta, + base_coords=base_coordinates, ) if len(routes) < 1: @@ -211,6 +215,9 @@ class WazeTravelTimeCoordinator(DataUpdateCoordinator[WazeTravelTimeData]): timedelta(**self.config_entry.options[CONF_TIME_DELTA]).total_seconds() / 60 ) + base_coordinates = base_coordinates_to_tuple( + self.config_entry.options.get(CONF_BASE_COORDINATES) + ) routes = await async_get_travel_times( self.client, @@ -225,6 +232,7 @@ class WazeTravelTimeCoordinator(DataUpdateCoordinator[WazeTravelTimeData]): incl_filter, excl_filter, time_delta, + base_coordinates, ) if len(routes) < 1: travel_data = WazeTravelTimeData( diff --git a/homeassistant/components/waze_travel_time/helpers.py b/homeassistant/components/waze_travel_time/helpers.py index c6fe4d0c9bdc..7bee77e8e4fd 100644 --- a/homeassistant/components/waze_travel_time/helpers.py +++ b/homeassistant/components/waze_travel_time/helpers.py @@ -4,6 +4,7 @@ import logging from pywaze.route_calculator import WazeRouteCalculator, WRCError +from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE from homeassistant.core import HomeAssistant from homeassistant.helpers.httpx_client import get_async_client from homeassistant.helpers.location import find_coordinates @@ -11,6 +12,25 @@ from homeassistant.helpers.location import find_coordinates _LOGGER = logging.getLogger(__name__) +def base_coordinates_to_tuple( + base_coordinates: dict[str, float] | None, +) -> tuple[float, float] | None: + """Convert Home Assistant location data to Waze base coordinates.""" + if base_coordinates is None: + return None + + return (base_coordinates[CONF_LATITUDE], base_coordinates[CONF_LONGITUDE]) + + +def default_base_coordinates_for_region(region: str) -> dict[str, float]: + """Return pywaze's default base coordinates for a region.""" + base_coordinates = WazeRouteCalculator.BASE_COORDS[region.upper()] + return { + CONF_LATITUDE: base_coordinates["lat"], + CONF_LONGITUDE: base_coordinates["lon"], + } + + async def is_valid_config_entry( hass: HomeAssistant, origin: str, destination: str, region: str ) -> bool: diff --git a/homeassistant/components/waze_travel_time/services.yaml b/homeassistant/components/waze_travel_time/services.yaml index 6d1faf290451..857728ac0a17 100644 --- a/homeassistant/components/waze_travel_time/services.yaml +++ b/homeassistant/components/waze_travel_time/services.yaml @@ -69,3 +69,9 @@ get_travel_times: required: false selector: duration: + base_coordinates: + required: false + example: '{"latitude": -27.9699373, "longitude": 153.4081865}' + selector: + location: + radius: false diff --git a/homeassistant/components/waze_travel_time/strings.json b/homeassistant/components/waze_travel_time/strings.json index 55bb7cf995b1..221b0af5ccfb 100644 --- a/homeassistant/components/waze_travel_time/strings.json +++ b/homeassistant/components/waze_travel_time/strings.json @@ -26,6 +26,7 @@ "avoid_ferries": "Avoid ferries?", "avoid_subscription_roads": "Avoid roads needing a vignette / subscription?", "avoid_toll_roads": "Avoid toll roads?", + "base_coordinates": "Base coordinates", "excl_filter": "Exact street name which must NOT be part of the selected route", "incl_filter": "Exact street name which must be part of the selected route", "realtime": "Realtime travel time?", @@ -33,6 +34,9 @@ "units": "Units", "vehicle_type": "Vehicle type" }, + "data_description": { + "base_coordinates": "When Waze finds multiple matching locations for an address, it selects the one closest to these coordinates." + }, "description": "Some options will allow you to force the integration to use a particular route or avoid a particular route in its time travel calculation." } } @@ -77,6 +81,10 @@ "description": "Whether to avoid toll roads.", "name": "[%key:component::waze_travel_time::options::step::init::data::avoid_toll_roads%]" }, + "base_coordinates": { + "description": "[%key:component::waze_travel_time::options::step::init::data_description::base_coordinates%]", + "name": "[%key:component::waze_travel_time::options::step::init::data::base_coordinates%]" + }, "destination": { "description": "The destination of the route.", "name": "[%key:component::waze_travel_time::config::step::user::data::destination%]" diff --git a/tests/components/waze_travel_time/__init__.py b/tests/components/waze_travel_time/__init__.py index 1df3d9314d07..f485ec719281 100644 --- a/tests/components/waze_travel_time/__init__.py +++ b/tests/components/waze_travel_time/__init__.py @@ -1 +1,21 @@ """Tests for the Waze Travel Time integration.""" + +from homeassistant.components.waze_travel_time.const import ( + CONF_BASE_COORDINATES, + DEFAULT_OPTIONS, +) +from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE +from homeassistant.core import HomeAssistant + + +def get_default_options( + hass: HomeAssistant, +) -> dict[str, str | bool | list[str] | dict[str, int] | dict[str, float]]: + """Return the default options for Waze Travel Time.""" + return { + **DEFAULT_OPTIONS, + CONF_BASE_COORDINATES: { + CONF_LATITUDE: hass.config.latitude, + CONF_LONGITUDE: hass.config.longitude, + }, + } diff --git a/tests/components/waze_travel_time/conftest.py b/tests/components/waze_travel_time/conftest.py index fbaa7519ea84..2fb35053ea27 100644 --- a/tests/components/waze_travel_time/conftest.py +++ b/tests/components/waze_travel_time/conftest.py @@ -21,6 +21,7 @@ async def mock_config_fixture(hass: HomeAssistant, data, options): options=options, entry_id="test", version=WazeConfigFlow.VERSION, + minor_version=WazeConfigFlow.MINOR_VERSION, ) config_entry.add_to_hass(hass) await hass.config_entries.async_setup(config_entry.entry_id) diff --git a/tests/components/waze_travel_time/test_config_flow.py b/tests/components/waze_travel_time/test_config_flow.py index 3e7702f11ed2..e2306c27bc3d 100644 --- a/tests/components/waze_travel_time/test_config_flow.py +++ b/tests/components/waze_travel_time/test_config_flow.py @@ -8,6 +8,7 @@ from homeassistant.components.waze_travel_time.const import ( CONF_AVOID_FERRIES, CONF_AVOID_SUBSCRIPTION_ROADS, CONF_AVOID_TOLL_ROADS, + CONF_BASE_COORDINATES, CONF_DESTINATION, CONF_EXCL_FILTER, CONF_INCL_FILTER, @@ -21,10 +22,11 @@ from homeassistant.components.waze_travel_time.const import ( DOMAIN, IMPERIAL_UNITS, ) -from homeassistant.const import CONF_NAME, CONF_REGION +from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME, CONF_REGION from homeassistant.core import HomeAssistant from homeassistant.data_entry_flow import FlowResultType +from . import get_default_options from .const import CONFIG_FLOW_USER_INPUT, MOCK_CONFIG from tests.common import MockConfigEntry @@ -63,6 +65,7 @@ async def test_reconfigure(hass: HomeAssistant) -> None: data=MOCK_CONFIG, options=DEFAULT_OPTIONS, version=WazeConfigFlow.VERSION, + minor_version=WazeConfigFlow.MINOR_VERSION, ) entry.add_to_hass(hass) await hass.config_entries.async_setup(entry.entry_id) @@ -100,8 +103,9 @@ async def test_options(hass: HomeAssistant) -> None: entry = MockConfigEntry( domain=DOMAIN, data=MOCK_CONFIG, - options=DEFAULT_OPTIONS, + options=get_default_options(hass), version=WazeConfigFlow.VERSION, + minor_version=WazeConfigFlow.MINOR_VERSION, ) entry.add_to_hass(hass) await hass.config_entries.async_setup(entry.entry_id) @@ -118,6 +122,10 @@ async def test_options(hass: HomeAssistant) -> None: CONF_AVOID_FERRIES: True, CONF_AVOID_SUBSCRIPTION_ROADS: True, CONF_AVOID_TOLL_ROADS: True, + CONF_BASE_COORDINATES: { + CONF_LATITUDE: 1.123, + CONF_LONGITUDE: -1.123, + }, CONF_EXCL_FILTER: ["ExcludeThis"], CONF_INCL_FILTER: ["IncludeThis"], CONF_REALTIME: False, @@ -132,6 +140,10 @@ async def test_options(hass: HomeAssistant) -> None: CONF_AVOID_FERRIES: True, CONF_AVOID_SUBSCRIPTION_ROADS: True, CONF_AVOID_TOLL_ROADS: True, + CONF_BASE_COORDINATES: { + CONF_LATITUDE: 1.123, + CONF_LONGITUDE: -1.123, + }, CONF_EXCL_FILTER: ["ExcludeThis"], CONF_INCL_FILTER: ["IncludeThis"], CONF_REALTIME: False, @@ -144,6 +156,10 @@ async def test_options(hass: HomeAssistant) -> None: CONF_AVOID_FERRIES: True, CONF_AVOID_SUBSCRIPTION_ROADS: True, CONF_AVOID_TOLL_ROADS: True, + CONF_BASE_COORDINATES: { + CONF_LATITUDE: 1.123, + CONF_LONGITUDE: -1.123, + }, CONF_EXCL_FILTER: ["ExcludeThis"], CONF_INCL_FILTER: ["IncludeThis"], CONF_REALTIME: False, @@ -219,6 +235,7 @@ async def test_reset_filters(hass: HomeAssistant) -> None: options=options, entry_id="test", version=WazeConfigFlow.VERSION, + minor_version=WazeConfigFlow.MINOR_VERSION, ) config_entry.add_to_hass(hass) await hass.config_entries.async_setup(config_entry.entry_id) @@ -251,3 +268,34 @@ async def test_reset_filters(hass: HomeAssistant) -> None: CONF_UNITS: IMPERIAL_UNITS, CONF_VEHICLE_TYPE: "taxi", } + + +@pytest.mark.usefixtures("mock_update") +async def test_reset_base_coordinates(hass: HomeAssistant) -> None: + """Test clearing base coordinates in the options flow.""" + entry = MockConfigEntry( + domain=DOMAIN, + data=MOCK_CONFIG, + options=get_default_options(hass), + version=WazeConfigFlow.VERSION, + minor_version=WazeConfigFlow.MINOR_VERSION, + ) + entry.add_to_hass(hass) + 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, data=None) + result = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={ + CONF_AVOID_FERRIES: False, + CONF_AVOID_SUBSCRIPTION_ROADS: False, + CONF_AVOID_TOLL_ROADS: False, + CONF_REALTIME: True, + CONF_UNITS: IMPERIAL_UNITS, + CONF_VEHICLE_TYPE: "taxi", + }, + ) + + assert result["type"] is FlowResultType.CREATE_ENTRY + assert CONF_BASE_COORDINATES not in entry.options diff --git a/tests/components/waze_travel_time/test_init.py b/tests/components/waze_travel_time/test_init.py index 2a20b46f476f..bd9d63e8c118 100644 --- a/tests/components/waze_travel_time/test_init.py +++ b/tests/components/waze_travel_time/test_init.py @@ -6,6 +6,7 @@ from homeassistant.components.waze_travel_time.const import ( CONF_AVOID_FERRIES, CONF_AVOID_SUBSCRIPTION_ROADS, CONF_AVOID_TOLL_ROADS, + CONF_BASE_COORDINATES, CONF_EXCL_FILTER, CONF_INCL_FILTER, CONF_REALTIME, @@ -24,6 +25,7 @@ from homeassistant.components.waze_travel_time.const import ( METRIC_UNITS, ) from homeassistant.config_entries import ConfigEntryState +from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_REGION from homeassistant.core import HomeAssistant from .const import MOCK_CONFIG @@ -31,15 +33,56 @@ from .const import MOCK_CONFIG from tests.common import MockConfigEntry +async def call_service_get_travel_times( + hass: HomeAssistant, + origin: str, + destination: str, + vehicle_type: str, + region: str, + units: str, + incl_filter: list[str] | None = None, + time_delta: dict[str, int] | None = None, + base_coordinates: dict[str, float] | None = None, +) -> dict: + """Call the get_travel_times service.""" + params = { + "origin": origin, + "destination": destination, + "vehicle_type": vehicle_type, + "region": region, + "units": units, + "incl_filter": incl_filter or [], + "time_delta": time_delta or {}, + } + if base_coordinates is not None: + params["base_coordinates"] = base_coordinates + return await hass.services.async_call( + "waze_travel_time", + "get_travel_times", + params, + blocking=True, + return_response=True, + ) + + @pytest.mark.parametrize( ("data", "options"), [(MOCK_CONFIG, DEFAULT_OPTIONS)], ) @pytest.mark.parametrize( - ("time_delta", "expected_time_delta"), + ("time_delta", "expected_time_delta", "base_coordinates", "expected_base_coords"), [ - pytest.param({"hours": 1, "minutes": 30}, 90, id="positive"), - pytest.param({"hours": -1, "minutes": -30}, -90, id="negative"), + pytest.param({"hours": 1, "minutes": 30}, 90, None, None, id="positive"), + pytest.param( + {"hours": -1, "minutes": -30}, + -90, + {CONF_LATITUDE: 40.7128, CONF_LONGITUDE: -74.0060}, + ( + 40.7128, + -74.0060, + ), + id="negative_with_base_coordinates", + ), ], ) @pytest.mark.usefixtures("mock_update", "mock_config") @@ -48,22 +91,20 @@ async def test_service_get_travel_times( mock_update, time_delta: dict[str, int], expected_time_delta: int, + base_coordinates: dict[str, float] | None, + expected_base_coords: tuple[float, float] | None, ) -> None: """Test service get_travel_times.""" - response_data = await hass.services.async_call( - "waze_travel_time", - "get_travel_times", - { - "origin": "location1", - "destination": "location2", - "vehicle_type": "car", - "region": "us", - "units": "imperial", - "incl_filter": ["IncludeThis"], - "time_delta": time_delta, - }, - blocking=True, - return_response=True, + response_data = await call_service_get_travel_times( + hass, + origin="location1", + destination="location2", + vehicle_type="car", + region="us", + units="imperial", + incl_filter=["IncludeThis"], + time_delta=time_delta, + base_coordinates=base_coordinates, ) assert response_data == { "routes": [ @@ -76,6 +117,7 @@ async def test_service_get_travel_times( ] } assert mock_update.call_args_list[-1].kwargs["time_delta"] == expected_time_delta + assert mock_update.call_args_list[-1].kwargs["base_coords"] == expected_base_coords @pytest.mark.parametrize( @@ -106,8 +148,8 @@ async def test_service_get_travel_times_empty_response( @pytest.mark.usefixtures("mock_update") -async def test_migrate_entry_v1_v2(hass: HomeAssistant) -> None: - """Test successful migration of entry data from v1 to v2.2.""" +async def test_migrate_entry_v1_to_v2_3(hass: HomeAssistant) -> None: + """Test successful migration of entry data from v1 to v2.3.""" mock_entry = MockConfigEntry( domain=DOMAIN, version=1, @@ -130,10 +172,14 @@ async def test_migrate_entry_v1_v2(hass: HomeAssistant) -> None: assert updated_entry.state is ConfigEntryState.LOADED assert updated_entry.version == 2 - assert updated_entry.minor_version == 2 + assert updated_entry.minor_version == 3 assert updated_entry.options[CONF_INCL_FILTER] == DEFAULT_FILTER assert updated_entry.options[CONF_EXCL_FILTER] == DEFAULT_FILTER assert updated_entry.options[CONF_TIME_DELTA] == DEFAULT_TIME_DELTA + assert updated_entry.options[CONF_BASE_COORDINATES] == { + CONF_LATITUDE: 40.713, + CONF_LONGITUDE: -74.006, + } mock_entry = MockConfigEntry( domain=DOMAIN, @@ -159,15 +205,19 @@ async def test_migrate_entry_v1_v2(hass: HomeAssistant) -> None: assert updated_entry.state is ConfigEntryState.LOADED assert updated_entry.version == 2 - assert updated_entry.minor_version == 2 + assert updated_entry.minor_version == 3 assert updated_entry.options[CONF_INCL_FILTER] == ["IncludeThis"] assert updated_entry.options[CONF_EXCL_FILTER] == ["ExcludeThis"] assert updated_entry.options[CONF_TIME_DELTA] == DEFAULT_TIME_DELTA + assert updated_entry.options[CONF_BASE_COORDINATES] == { + CONF_LATITUDE: 40.713, + CONF_LONGITUDE: -74.006, + } @pytest.mark.usefixtures("mock_update") -async def test_migrate_entry_v2_1_to_v2_2(hass: HomeAssistant) -> None: - """Test successful migration of entry from version 2.1 to 2.2.""" +async def test_migrate_entry_v2_1_to_v2_3(hass: HomeAssistant) -> None: + """Test successful migration of entry from version 2.1 to 2.3.""" mock_entry = MockConfigEntry( domain=DOMAIN, version=2, @@ -193,5 +243,118 @@ async def test_migrate_entry_v2_1_to_v2_2(hass: HomeAssistant) -> None: assert updated_entry.state is ConfigEntryState.LOADED assert updated_entry.version == 2 - assert updated_entry.minor_version == 2 + assert updated_entry.minor_version == 3 assert updated_entry.options[CONF_TIME_DELTA] == DEFAULT_TIME_DELTA + assert updated_entry.options[CONF_BASE_COORDINATES] == { + CONF_LATITUDE: 40.713, + CONF_LONGITUDE: -74.006, + } + + +@pytest.mark.parametrize( + ("region", "expected_base_coordinates"), + [ + pytest.param( + "US", + {CONF_LATITUDE: 40.713, CONF_LONGITUDE: -74.006}, + id="us", + ), + pytest.param( + "NA", + {CONF_LATITUDE: 40.713, CONF_LONGITUDE: -74.006}, + id="na", + ), + pytest.param( + "EU", + {CONF_LATITUDE: 47.498, CONF_LONGITUDE: 19.040}, + id="eu", + ), + pytest.param( + "IL", + {CONF_LATITUDE: 31.768, CONF_LONGITUDE: 35.214}, + id="il", + ), + pytest.param( + "AU", + {CONF_LATITUDE: -35.281, CONF_LONGITUDE: 149.128}, + id="au", + ), + ], +) +@pytest.mark.usefixtures("mock_update") +async def test_migrate_entry_v2_2_to_v2_3_adds_region_base_coordinates( + hass: HomeAssistant, + region: str, + expected_base_coordinates: dict[str, float], +) -> None: + """Test migration adds pywaze's default base coordinates for each region.""" + mock_entry = MockConfigEntry( + domain=DOMAIN, + version=2, + minor_version=2, + data={**MOCK_CONFIG, CONF_REGION: region}, + options={ + CONF_REALTIME: DEFAULT_REALTIME, + CONF_VEHICLE_TYPE: DEFAULT_VEHICLE_TYPE, + CONF_UNITS: METRIC_UNITS, + CONF_AVOID_FERRIES: DEFAULT_AVOID_FERRIES, + CONF_AVOID_SUBSCRIPTION_ROADS: DEFAULT_AVOID_SUBSCRIPTION_ROADS, + CONF_AVOID_TOLL_ROADS: DEFAULT_AVOID_TOLL_ROADS, + CONF_INCL_FILTER: DEFAULT_FILTER, + CONF_EXCL_FILTER: DEFAULT_FILTER, + CONF_TIME_DELTA: DEFAULT_TIME_DELTA, + }, + ) + + mock_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_entry.entry_id) + await hass.async_block_till_done() + + updated_entry = hass.config_entries.async_get_entry(mock_entry.entry_id) + + assert updated_entry.state is ConfigEntryState.LOADED + assert updated_entry.version == 2 + assert updated_entry.minor_version == 3 + assert updated_entry.options[CONF_BASE_COORDINATES] == expected_base_coordinates + + +@pytest.mark.usefixtures("mock_update") +async def test_migrate_entry_v2_2_to_v2_3_preserves_existing_base_coordinates( + hass: HomeAssistant, +) -> None: + """Test migration preserves configured base coordinates.""" + mock_entry = MockConfigEntry( + domain=DOMAIN, + version=2, + minor_version=2, + data=MOCK_CONFIG, + options={ + CONF_REALTIME: DEFAULT_REALTIME, + CONF_VEHICLE_TYPE: DEFAULT_VEHICLE_TYPE, + CONF_UNITS: METRIC_UNITS, + CONF_AVOID_FERRIES: DEFAULT_AVOID_FERRIES, + CONF_AVOID_SUBSCRIPTION_ROADS: DEFAULT_AVOID_SUBSCRIPTION_ROADS, + CONF_AVOID_TOLL_ROADS: DEFAULT_AVOID_TOLL_ROADS, + CONF_INCL_FILTER: DEFAULT_FILTER, + CONF_EXCL_FILTER: DEFAULT_FILTER, + CONF_TIME_DELTA: DEFAULT_TIME_DELTA, + CONF_BASE_COORDINATES: { + CONF_LATITUDE: 1.23, + CONF_LONGITUDE: 4.56, + }, + }, + ) + + mock_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_entry.entry_id) + await hass.async_block_till_done() + + updated_entry = hass.config_entries.async_get_entry(mock_entry.entry_id) + + assert updated_entry.state is ConfigEntryState.LOADED + assert updated_entry.version == 2 + assert updated_entry.minor_version == 3 + assert updated_entry.options[CONF_BASE_COORDINATES] == { + CONF_LATITUDE: 1.23, + CONF_LONGITUDE: 4.56, + }