diff --git a/CODEOWNERS b/CODEOWNERS
index ae072cd092c..7e05cdf0b39 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -115,6 +115,7 @@ homeassistant/components/gtfs/* @robbiet480
homeassistant/components/harmony/* @ehendrix23
homeassistant/components/hassio/* @home-assistant/hass-io
homeassistant/components/heos/* @andrewsayre
+homeassistant/components/here_travel_time/* @eifinger
homeassistant/components/hikvision/* @mezz64
homeassistant/components/hikvisioncam/* @fbradyirl
homeassistant/components/history/* @home-assistant/core
diff --git a/homeassistant/components/here_travel_time/__init__.py b/homeassistant/components/here_travel_time/__init__.py
new file mode 100755
index 00000000000..9a5c8ec32ac
--- /dev/null
+++ b/homeassistant/components/here_travel_time/__init__.py
@@ -0,0 +1 @@
+"""The here_travel_time component."""
diff --git a/homeassistant/components/here_travel_time/manifest.json b/homeassistant/components/here_travel_time/manifest.json
new file mode 100755
index 00000000000..e26e2e1d6ea
--- /dev/null
+++ b/homeassistant/components/here_travel_time/manifest.json
@@ -0,0 +1,12 @@
+{
+ "domain": "here_travel_time",
+ "name": "HERE travel time",
+ "documentation": "https://www.home-assistant.io/components/here_travel_time",
+ "requirements": [
+ "herepy==0.6.3.1"
+ ],
+ "dependencies": [],
+ "codeowners": [
+ "@eifinger"
+ ]
+ }
diff --git a/homeassistant/components/here_travel_time/sensor.py b/homeassistant/components/here_travel_time/sensor.py
new file mode 100755
index 00000000000..ba4908fe85c
--- /dev/null
+++ b/homeassistant/components/here_travel_time/sensor.py
@@ -0,0 +1,431 @@
+"""Support for HERE travel time sensors."""
+from datetime import timedelta
+import logging
+from typing import Callable, Dict, Optional, Union
+
+import herepy
+import voluptuous as vol
+
+from homeassistant.components.sensor import PLATFORM_SCHEMA
+from homeassistant.const import (
+ ATTR_ATTRIBUTION,
+ ATTR_LATITUDE,
+ ATTR_LONGITUDE,
+ CONF_MODE,
+ CONF_NAME,
+ CONF_UNIT_SYSTEM,
+ CONF_UNIT_SYSTEM_IMPERIAL,
+ CONF_UNIT_SYSTEM_METRIC,
+)
+from homeassistant.core import HomeAssistant, State
+from homeassistant.helpers import location
+import homeassistant.helpers.config_validation as cv
+from homeassistant.helpers.entity import Entity
+
+_LOGGER = logging.getLogger(__name__)
+
+CONF_DESTINATION_LATITUDE = "destination_latitude"
+CONF_DESTINATION_LONGITUDE = "destination_longitude"
+CONF_DESTINATION_ENTITY_ID = "destination_entity_id"
+CONF_ORIGIN_LATITUDE = "origin_latitude"
+CONF_ORIGIN_LONGITUDE = "origin_longitude"
+CONF_ORIGIN_ENTITY_ID = "origin_entity_id"
+CONF_APP_ID = "app_id"
+CONF_APP_CODE = "app_code"
+CONF_TRAFFIC_MODE = "traffic_mode"
+CONF_ROUTE_MODE = "route_mode"
+
+DEFAULT_NAME = "HERE Travel Time"
+
+TRAVEL_MODE_BICYCLE = "bicycle"
+TRAVEL_MODE_CAR = "car"
+TRAVEL_MODE_PEDESTRIAN = "pedestrian"
+TRAVEL_MODE_PUBLIC = "publicTransport"
+TRAVEL_MODE_PUBLIC_TIME_TABLE = "publicTransportTimeTable"
+TRAVEL_MODE_TRUCK = "truck"
+TRAVEL_MODE = [
+ TRAVEL_MODE_BICYCLE,
+ TRAVEL_MODE_CAR,
+ TRAVEL_MODE_PEDESTRIAN,
+ TRAVEL_MODE_PUBLIC,
+ TRAVEL_MODE_PUBLIC_TIME_TABLE,
+ TRAVEL_MODE_TRUCK,
+]
+
+TRAVEL_MODES_PUBLIC = [TRAVEL_MODE_PUBLIC, TRAVEL_MODE_PUBLIC_TIME_TABLE]
+TRAVEL_MODES_VEHICLE = [TRAVEL_MODE_CAR, TRAVEL_MODE_TRUCK]
+TRAVEL_MODES_NON_VEHICLE = [TRAVEL_MODE_BICYCLE, TRAVEL_MODE_PEDESTRIAN]
+
+TRAFFIC_MODE_ENABLED = "traffic_enabled"
+TRAFFIC_MODE_DISABLED = "traffic_disabled"
+
+ROUTE_MODE_FASTEST = "fastest"
+ROUTE_MODE_SHORTEST = "shortest"
+ROUTE_MODE = [ROUTE_MODE_FASTEST, ROUTE_MODE_SHORTEST]
+
+ICON_BICYCLE = "mdi:bike"
+ICON_CAR = "mdi:car"
+ICON_PEDESTRIAN = "mdi:walk"
+ICON_PUBLIC = "mdi:bus"
+ICON_TRUCK = "mdi:truck"
+
+UNITS = [CONF_UNIT_SYSTEM_METRIC, CONF_UNIT_SYSTEM_IMPERIAL]
+
+ATTR_DURATION = "duration"
+ATTR_DISTANCE = "distance"
+ATTR_ROUTE = "route"
+ATTR_ORIGIN = "origin"
+ATTR_DESTINATION = "destination"
+
+ATTR_MODE = "mode"
+ATTR_UNIT_SYSTEM = CONF_UNIT_SYSTEM
+ATTR_TRAFFIC_MODE = CONF_TRAFFIC_MODE
+
+ATTR_DURATION_IN_TRAFFIC = "duration_in_traffic"
+ATTR_ORIGIN_NAME = "origin_name"
+ATTR_DESTINATION_NAME = "destination_name"
+
+UNIT_OF_MEASUREMENT = "min"
+
+SCAN_INTERVAL = timedelta(minutes=5)
+
+TRACKABLE_DOMAINS = ["device_tracker", "sensor", "zone", "person"]
+
+NO_ROUTE_ERROR_MESSAGE = "HERE could not find a route based on the input"
+
+COORDINATE_SCHEMA = vol.Schema(
+ {
+ vol.Inclusive(CONF_DESTINATION_LATITUDE, "coordinates"): cv.latitude,
+ vol.Inclusive(CONF_DESTINATION_LONGITUDE, "coordinates"): cv.longitude,
+ }
+)
+
+PLATFORM_SCHEMA = vol.All(
+ cv.has_at_least_one_key(CONF_DESTINATION_LATITUDE, CONF_DESTINATION_ENTITY_ID),
+ cv.has_at_least_one_key(CONF_ORIGIN_LATITUDE, CONF_ORIGIN_ENTITY_ID),
+ PLATFORM_SCHEMA.extend(
+ {
+ vol.Required(CONF_APP_ID): cv.string,
+ vol.Required(CONF_APP_CODE): cv.string,
+ vol.Inclusive(
+ CONF_DESTINATION_LATITUDE, "destination_coordinates"
+ ): cv.latitude,
+ vol.Inclusive(
+ CONF_DESTINATION_LONGITUDE, "destination_coordinates"
+ ): cv.longitude,
+ vol.Exclusive(CONF_DESTINATION_LATITUDE, "destination"): cv.latitude,
+ vol.Exclusive(CONF_DESTINATION_ENTITY_ID, "destination"): cv.entity_id,
+ vol.Inclusive(CONF_ORIGIN_LATITUDE, "origin_coordinates"): cv.latitude,
+ vol.Inclusive(CONF_ORIGIN_LONGITUDE, "origin_coordinates"): cv.longitude,
+ vol.Exclusive(CONF_ORIGIN_LATITUDE, "origin"): cv.latitude,
+ vol.Exclusive(CONF_ORIGIN_ENTITY_ID, "origin"): cv.entity_id,
+ vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
+ vol.Optional(CONF_MODE, default=TRAVEL_MODE_CAR): vol.In(TRAVEL_MODE),
+ vol.Optional(CONF_ROUTE_MODE, default=ROUTE_MODE_FASTEST): vol.In(
+ ROUTE_MODE
+ ),
+ vol.Optional(CONF_TRAFFIC_MODE, default=False): cv.boolean,
+ vol.Optional(CONF_UNIT_SYSTEM): vol.In(UNITS),
+ }
+ ),
+)
+
+
+async def async_setup_platform(
+ hass: HomeAssistant,
+ config: Dict[str, Union[str, bool]],
+ async_add_entities: Callable,
+ discovery_info: None = None,
+) -> None:
+ """Set up the HERE travel time platform."""
+
+ app_id = config[CONF_APP_ID]
+ app_code = config[CONF_APP_CODE]
+ here_client = herepy.RoutingApi(app_id, app_code)
+
+ if not await hass.async_add_executor_job(
+ _are_valid_client_credentials, here_client
+ ):
+ _LOGGER.error(
+ "Invalid credentials. This error is returned if the specified token was invalid or no contract could be found for this token."
+ )
+ return
+
+ if config.get(CONF_ORIGIN_LATITUDE) is not None:
+ origin = f"{config[CONF_ORIGIN_LATITUDE]},{config[CONF_ORIGIN_LONGITUDE]}"
+ else:
+ origin = config[CONF_ORIGIN_ENTITY_ID]
+
+ if config.get(CONF_DESTINATION_LATITUDE) is not None:
+ destination = (
+ f"{config[CONF_DESTINATION_LATITUDE]},{config[CONF_DESTINATION_LONGITUDE]}"
+ )
+ else:
+ destination = config[CONF_DESTINATION_ENTITY_ID]
+
+ travel_mode = config[CONF_MODE]
+ traffic_mode = config[CONF_TRAFFIC_MODE]
+ route_mode = config[CONF_ROUTE_MODE]
+ name = config[CONF_NAME]
+ units = config.get(CONF_UNIT_SYSTEM, hass.config.units.name)
+
+ here_data = HERETravelTimeData(
+ here_client, travel_mode, traffic_mode, route_mode, units
+ )
+
+ sensor = HERETravelTimeSensor(name, origin, destination, here_data)
+
+ async_add_entities([sensor], True)
+
+
+def _are_valid_client_credentials(here_client: herepy.RoutingApi) -> bool:
+ """Check if the provided credentials are correct using defaults."""
+ known_working_origin = [38.9, -77.04833]
+ known_working_destination = [39.0, -77.1]
+ try:
+ here_client.car_route(
+ known_working_origin,
+ known_working_destination,
+ [
+ herepy.RouteMode[ROUTE_MODE_FASTEST],
+ herepy.RouteMode[TRAVEL_MODE_CAR],
+ herepy.RouteMode[TRAFFIC_MODE_DISABLED],
+ ],
+ )
+ except herepy.InvalidCredentialsError:
+ return False
+ return True
+
+
+class HERETravelTimeSensor(Entity):
+ """Representation of a HERE travel time sensor."""
+
+ def __init__(
+ self, name: str, origin: str, destination: str, here_data: "HERETravelTimeData"
+ ) -> None:
+ """Initialize the sensor."""
+ self._name = name
+ self._here_data = here_data
+ self._unit_of_measurement = UNIT_OF_MEASUREMENT
+ self._origin_entity_id = None
+ self._destination_entity_id = None
+ self._attrs = {
+ ATTR_UNIT_SYSTEM: self._here_data.units,
+ ATTR_MODE: self._here_data.travel_mode,
+ ATTR_TRAFFIC_MODE: self._here_data.traffic_mode,
+ }
+
+ # Check if location is a trackable entity
+ if origin.split(".", 1)[0] in TRACKABLE_DOMAINS:
+ self._origin_entity_id = origin
+ else:
+ self._here_data.origin = origin
+
+ if destination.split(".", 1)[0] in TRACKABLE_DOMAINS:
+ self._destination_entity_id = destination
+ else:
+ self._here_data.destination = destination
+
+ @property
+ def state(self) -> Optional[str]:
+ """Return the state of the sensor."""
+ if self._here_data.traffic_mode:
+ if self._here_data.traffic_time is not None:
+ return str(round(self._here_data.traffic_time / 60))
+ if self._here_data.base_time is not None:
+ return str(round(self._here_data.base_time / 60))
+
+ return None
+
+ @property
+ def name(self) -> str:
+ """Get the name of the sensor."""
+ return self._name
+
+ @property
+ def device_state_attributes(
+ self
+ ) -> Optional[Dict[str, Union[None, float, str, bool]]]:
+ """Return the state attributes."""
+ if self._here_data.base_time is None:
+ return None
+
+ res = self._attrs
+ if self._here_data.attribution is not None:
+ res[ATTR_ATTRIBUTION] = self._here_data.attribution
+ res[ATTR_DURATION] = self._here_data.base_time / 60
+ res[ATTR_DISTANCE] = self._here_data.distance
+ res[ATTR_ROUTE] = self._here_data.route
+ res[ATTR_DURATION_IN_TRAFFIC] = self._here_data.traffic_time / 60
+ res[ATTR_ORIGIN] = self._here_data.origin
+ res[ATTR_DESTINATION] = self._here_data.destination
+ res[ATTR_ORIGIN_NAME] = self._here_data.origin_name
+ res[ATTR_DESTINATION_NAME] = self._here_data.destination_name
+ return res
+
+ @property
+ def unit_of_measurement(self) -> str:
+ """Return the unit this state is expressed in."""
+ return self._unit_of_measurement
+
+ @property
+ def icon(self) -> str:
+ """Icon to use in the frontend depending on travel_mode."""
+ if self._here_data.travel_mode == TRAVEL_MODE_BICYCLE:
+ return ICON_BICYCLE
+ if self._here_data.travel_mode == TRAVEL_MODE_PEDESTRIAN:
+ return ICON_PEDESTRIAN
+ if self._here_data.travel_mode in TRAVEL_MODES_PUBLIC:
+ return ICON_PUBLIC
+ if self._here_data.travel_mode == TRAVEL_MODE_TRUCK:
+ return ICON_TRUCK
+ return ICON_CAR
+
+ async def async_update(self) -> None:
+ """Update Sensor Information."""
+ # Convert device_trackers to HERE friendly location
+ if self._origin_entity_id is not None:
+ self._here_data.origin = await self._get_location_from_entity(
+ self._origin_entity_id
+ )
+
+ if self._destination_entity_id is not None:
+ self._here_data.destination = await self._get_location_from_entity(
+ self._destination_entity_id
+ )
+
+ await self.hass.async_add_executor_job(self._here_data.update)
+
+ async def _get_location_from_entity(self, entity_id: str) -> Optional[str]:
+ """Get the location from the entity state or attributes."""
+ entity = self.hass.states.get(entity_id)
+
+ if entity is None:
+ _LOGGER.error("Unable to find entity %s", entity_id)
+ return None
+
+ # Check if the entity has location attributes
+ if location.has_location(entity):
+ return self._get_location_from_attributes(entity)
+
+ # Check if device is in a zone
+ zone_entity = self.hass.states.get("zone.{}".format(entity.state))
+ if location.has_location(zone_entity):
+ _LOGGER.debug(
+ "%s is in %s, getting zone location", entity_id, zone_entity.entity_id
+ )
+ return self._get_location_from_attributes(zone_entity)
+
+ # If zone was not found in state then use the state as the location
+ if entity_id.startswith("sensor."):
+ return entity.state
+
+ @staticmethod
+ def _get_location_from_attributes(entity: State) -> str:
+ """Get the lat/long string from an entities attributes."""
+ attr = entity.attributes
+ return "{},{}".format(attr.get(ATTR_LATITUDE), attr.get(ATTR_LONGITUDE))
+
+
+class HERETravelTimeData:
+ """HERETravelTime data object."""
+
+ def __init__(
+ self,
+ here_client: herepy.RoutingApi,
+ travel_mode: str,
+ traffic_mode: bool,
+ route_mode: str,
+ units: str,
+ ) -> None:
+ """Initialize herepy."""
+ self.origin = None
+ self.destination = None
+ self.travel_mode = travel_mode
+ self.traffic_mode = traffic_mode
+ self.route_mode = route_mode
+ self.attribution = None
+ self.traffic_time = None
+ self.distance = None
+ self.route = None
+ self.base_time = None
+ self.origin_name = None
+ self.destination_name = None
+ self.units = units
+ self._client = here_client
+
+ def update(self) -> None:
+ """Get the latest data from HERE."""
+ if self.traffic_mode:
+ traffic_mode = TRAFFIC_MODE_ENABLED
+ else:
+ traffic_mode = TRAFFIC_MODE_DISABLED
+
+ if self.destination is not None and self.origin is not None:
+ # Convert location to HERE friendly location
+ destination = self.destination.split(",")
+ origin = self.origin.split(",")
+
+ _LOGGER.debug(
+ "Requesting route for origin: %s, destination: %s, route_mode: %s, mode: %s, traffic_mode: %s",
+ origin,
+ destination,
+ herepy.RouteMode[self.route_mode],
+ herepy.RouteMode[self.travel_mode],
+ herepy.RouteMode[traffic_mode],
+ )
+ try:
+ response = self._client.car_route(
+ origin,
+ destination,
+ [
+ herepy.RouteMode[self.route_mode],
+ herepy.RouteMode[self.travel_mode],
+ herepy.RouteMode[traffic_mode],
+ ],
+ )
+ except herepy.NoRouteFoundError:
+ # Better error message for cryptic no route error codes
+ _LOGGER.error(NO_ROUTE_ERROR_MESSAGE)
+ return
+
+ _LOGGER.debug("Raw response is: %s", response.response)
+
+ # pylint: disable=no-member
+ source_attribution = response.response.get("sourceAttribution")
+ if source_attribution is not None:
+ self.attribution = self._build_hass_attribution(source_attribution)
+ # pylint: disable=no-member
+ route = response.response["route"]
+ summary = route[0]["summary"]
+ waypoint = route[0]["waypoint"]
+ self.base_time = summary["baseTime"]
+ if self.travel_mode in TRAVEL_MODES_VEHICLE:
+ self.traffic_time = summary["trafficTime"]
+ else:
+ self.traffic_time = self.base_time
+ distance = summary["distance"]
+ if self.units == CONF_UNIT_SYSTEM_IMPERIAL:
+ # Convert to miles.
+ self.distance = distance / 1609.344
+ else:
+ # Convert to kilometers
+ self.distance = distance / 1000
+ # pylint: disable=no-member
+ self.route = response.route_short
+ self.origin_name = waypoint[0]["mappedRoadName"]
+ self.destination_name = waypoint[1]["mappedRoadName"]
+
+ @staticmethod
+ def _build_hass_attribution(source_attribution: Dict) -> Optional[str]:
+ """Build a hass frontend ready string out of the sourceAttribution."""
+ suppliers = source_attribution.get("supplier")
+ if suppliers is not None:
+ supplier_titles = []
+ for supplier in suppliers:
+ title = supplier.get("title")
+ if title is not None:
+ supplier_titles.append(title)
+ joined_supplier_titles = ",".join(supplier_titles)
+ attribution = f"With the support of {joined_supplier_titles}. All information is provided without warranty of any kind."
+ return attribution
diff --git a/requirements_all.txt b/requirements_all.txt
index a3c1548bc1a..bf217688d26 100644
--- a/requirements_all.txt
+++ b/requirements_all.txt
@@ -619,6 +619,9 @@ hdate==0.9.0
# homeassistant.components.heatmiser
heatmiserV3==0.9.1
+# homeassistant.components.here_travel_time
+herepy==0.6.3.1
+
# homeassistant.components.hikvisioncam
hikvision==0.4
diff --git a/requirements_test_all.txt b/requirements_test_all.txt
index 6b4d7fbc089..9e846c9c416 100644
--- a/requirements_test_all.txt
+++ b/requirements_test_all.txt
@@ -171,6 +171,9 @@ hbmqtt==0.9.5
# homeassistant.components.jewish_calendar
hdate==0.9.0
+# homeassistant.components.here_travel_time
+herepy==0.6.3.1
+
# homeassistant.components.pi_hole
hole==0.5.0
diff --git a/script/gen_requirements_all.py b/script/gen_requirements_all.py
index 384d50bccef..d74a57d678d 100755
--- a/script/gen_requirements_all.py
+++ b/script/gen_requirements_all.py
@@ -87,6 +87,7 @@ TEST_REQUIREMENTS = (
"haversine",
"hbmqtt",
"hdate",
+ "herepy",
"hole",
"holidays",
"home-assistant-frontend",
diff --git a/tests/components/here_travel_time/__init__.py b/tests/components/here_travel_time/__init__.py
new file mode 100644
index 00000000000..ac0ec709654
--- /dev/null
+++ b/tests/components/here_travel_time/__init__.py
@@ -0,0 +1 @@
+"""Tests for here_travel_time component."""
diff --git a/tests/components/here_travel_time/test_sensor.py b/tests/components/here_travel_time/test_sensor.py
new file mode 100644
index 00000000000..783209690a3
--- /dev/null
+++ b/tests/components/here_travel_time/test_sensor.py
@@ -0,0 +1,947 @@
+"""The test for the here_travel_time sensor platform."""
+import logging
+from unittest.mock import patch
+import urllib
+
+import herepy
+import pytest
+
+from homeassistant.components.here_travel_time.sensor import (
+ ATTR_ATTRIBUTION,
+ ATTR_DESTINATION,
+ ATTR_DESTINATION_NAME,
+ ATTR_DISTANCE,
+ ATTR_DURATION,
+ ATTR_DURATION_IN_TRAFFIC,
+ ATTR_ORIGIN,
+ ATTR_ORIGIN_NAME,
+ ATTR_ROUTE,
+ CONF_MODE,
+ CONF_TRAFFIC_MODE,
+ CONF_UNIT_SYSTEM,
+ ICON_BICYCLE,
+ ICON_CAR,
+ ICON_PEDESTRIAN,
+ ICON_PUBLIC,
+ ICON_TRUCK,
+ NO_ROUTE_ERROR_MESSAGE,
+ ROUTE_MODE_FASTEST,
+ ROUTE_MODE_SHORTEST,
+ SCAN_INTERVAL,
+ TRAFFIC_MODE_DISABLED,
+ TRAFFIC_MODE_ENABLED,
+ TRAVEL_MODE_BICYCLE,
+ TRAVEL_MODE_CAR,
+ TRAVEL_MODE_PEDESTRIAN,
+ TRAVEL_MODE_PUBLIC,
+ TRAVEL_MODE_PUBLIC_TIME_TABLE,
+ TRAVEL_MODE_TRUCK,
+ UNIT_OF_MEASUREMENT,
+)
+from homeassistant.const import ATTR_ICON
+from homeassistant.setup import async_setup_component
+import homeassistant.util.dt as dt_util
+
+from tests.common import async_fire_time_changed, load_fixture
+
+DOMAIN = "sensor"
+
+PLATFORM = "here_travel_time"
+
+APP_ID = "test"
+APP_CODE = "test"
+
+TRUCK_ORIGIN_LATITUDE = "41.9798"
+TRUCK_ORIGIN_LONGITUDE = "-87.8801"
+TRUCK_DESTINATION_LATITUDE = "41.9043"
+TRUCK_DESTINATION_LONGITUDE = "-87.9216"
+
+BIKE_ORIGIN_LATITUDE = "41.9798"
+BIKE_ORIGIN_LONGITUDE = "-87.8801"
+BIKE_DESTINATION_LATITUDE = "41.9043"
+BIKE_DESTINATION_LONGITUDE = "-87.9216"
+
+CAR_ORIGIN_LATITUDE = "38.9"
+CAR_ORIGIN_LONGITUDE = "-77.04833"
+CAR_DESTINATION_LATITUDE = "39.0"
+CAR_DESTINATION_LONGITUDE = "-77.1"
+
+
+def _build_mock_url(origin, destination, modes, app_id, app_code, departure):
+ """Construct a url for HERE."""
+ base_url = "https://route.cit.api.here.com/routing/7.2/calculateroute.json?"
+ parameters = {
+ "waypoint0": origin,
+ "waypoint1": destination,
+ "mode": ";".join(str(herepy.RouteMode[mode]) for mode in modes),
+ "app_id": app_id,
+ "app_code": app_code,
+ "departure": departure,
+ }
+ url = base_url + urllib.parse.urlencode(parameters)
+ return url
+
+
+def _assert_truck_sensor(sensor):
+ """Assert that states and attributes are correct for truck_response."""
+ assert sensor.state == "14"
+ assert sensor.attributes.get("unit_of_measurement") == UNIT_OF_MEASUREMENT
+
+ assert sensor.attributes.get(ATTR_ATTRIBUTION) is None
+ assert sensor.attributes.get(ATTR_DURATION) == 13.533333333333333
+ assert sensor.attributes.get(ATTR_DISTANCE) == 13.049
+ assert sensor.attributes.get(ATTR_ROUTE) == (
+ "I-190; I-294 S - Tri-State Tollway; I-290 W - Eisenhower Expy W; "
+ "IL-64 W - E North Ave; I-290 E - Eisenhower Expy E; I-290"
+ )
+ assert sensor.attributes.get(CONF_UNIT_SYSTEM) == "metric"
+ assert sensor.attributes.get(ATTR_DURATION_IN_TRAFFIC) == 13.533333333333333
+ assert sensor.attributes.get(ATTR_ORIGIN) == ",".join(
+ [TRUCK_ORIGIN_LATITUDE, TRUCK_ORIGIN_LONGITUDE]
+ )
+ assert sensor.attributes.get(ATTR_DESTINATION) == ",".join(
+ [TRUCK_DESTINATION_LATITUDE, TRUCK_DESTINATION_LONGITUDE]
+ )
+ assert sensor.attributes.get(ATTR_ORIGIN_NAME) == ""
+ assert sensor.attributes.get(ATTR_DESTINATION_NAME) == "Eisenhower Expy E"
+ assert sensor.attributes.get(CONF_MODE) == TRAVEL_MODE_TRUCK
+ assert sensor.attributes.get(CONF_TRAFFIC_MODE) is False
+
+ assert sensor.attributes.get(ATTR_ICON) == ICON_TRUCK
+
+
+@pytest.fixture
+def requests_mock_credentials_check(requests_mock):
+ """Add the url used in the api validation to all requests mock."""
+ modes = [ROUTE_MODE_FASTEST, TRAVEL_MODE_CAR, TRAFFIC_MODE_DISABLED]
+ response_url = _build_mock_url(
+ ",".join([CAR_ORIGIN_LATITUDE, CAR_ORIGIN_LONGITUDE]),
+ ",".join([CAR_DESTINATION_LATITUDE, CAR_DESTINATION_LONGITUDE]),
+ modes,
+ APP_ID,
+ APP_CODE,
+ "now",
+ )
+ requests_mock.get(
+ response_url, text=load_fixture("here_travel_time/car_response.json")
+ )
+ return requests_mock
+
+
+@pytest.fixture
+def requests_mock_truck_response(requests_mock_credentials_check):
+ """Return a requests_mock for truck respones."""
+ modes = [ROUTE_MODE_FASTEST, TRAVEL_MODE_TRUCK, TRAFFIC_MODE_DISABLED]
+ response_url = _build_mock_url(
+ ",".join([TRUCK_ORIGIN_LATITUDE, TRUCK_ORIGIN_LONGITUDE]),
+ ",".join([TRUCK_DESTINATION_LATITUDE, TRUCK_DESTINATION_LONGITUDE]),
+ modes,
+ APP_ID,
+ APP_CODE,
+ "now",
+ )
+ requests_mock_credentials_check.get(
+ response_url, text=load_fixture("here_travel_time/truck_response.json")
+ )
+
+
+@pytest.fixture
+def requests_mock_car_disabled_response(requests_mock_credentials_check):
+ """Return a requests_mock for truck respones."""
+ modes = [ROUTE_MODE_FASTEST, TRAVEL_MODE_CAR, TRAFFIC_MODE_DISABLED]
+ response_url = _build_mock_url(
+ ",".join([CAR_ORIGIN_LATITUDE, CAR_ORIGIN_LONGITUDE]),
+ ",".join([CAR_DESTINATION_LATITUDE, CAR_DESTINATION_LONGITUDE]),
+ modes,
+ APP_ID,
+ APP_CODE,
+ "now",
+ )
+ requests_mock_credentials_check.get(
+ response_url, text=load_fixture("here_travel_time/car_response.json")
+ )
+
+
+async def test_car(hass, requests_mock_car_disabled_response):
+ """Test that car works."""
+ config = {
+ DOMAIN: {
+ "platform": PLATFORM,
+ "name": "test",
+ "origin_latitude": CAR_ORIGIN_LATITUDE,
+ "origin_longitude": CAR_ORIGIN_LONGITUDE,
+ "destination_latitude": CAR_DESTINATION_LATITUDE,
+ "destination_longitude": CAR_DESTINATION_LONGITUDE,
+ "app_id": APP_ID,
+ "app_code": APP_CODE,
+ }
+ }
+
+ assert await async_setup_component(hass, DOMAIN, config)
+
+ sensor = hass.states.get("sensor.test")
+ assert sensor.state == "30"
+ assert sensor.attributes.get("unit_of_measurement") == UNIT_OF_MEASUREMENT
+
+ assert sensor.attributes.get(ATTR_ATTRIBUTION) is None
+ assert sensor.attributes.get(ATTR_DURATION) == 30.05
+ assert sensor.attributes.get(ATTR_DISTANCE) == 23.903
+ assert sensor.attributes.get(ATTR_ROUTE) == (
+ "US-29 - K St NW; US-29 - Whitehurst Fwy; "
+ "I-495 N - Capital Beltway; MD-187 S - Old Georgetown Rd"
+ )
+ assert sensor.attributes.get(CONF_UNIT_SYSTEM) == "metric"
+ assert sensor.attributes.get(ATTR_DURATION_IN_TRAFFIC) == 31.016666666666666
+ assert sensor.attributes.get(ATTR_ORIGIN) == ",".join(
+ [CAR_ORIGIN_LATITUDE, CAR_ORIGIN_LONGITUDE]
+ )
+ assert sensor.attributes.get(ATTR_DESTINATION) == ",".join(
+ [CAR_DESTINATION_LATITUDE, CAR_DESTINATION_LONGITUDE]
+ )
+ assert sensor.attributes.get(ATTR_ORIGIN_NAME) == "22nd St NW"
+ assert sensor.attributes.get(ATTR_DESTINATION_NAME) == "Service Rd S"
+ assert sensor.attributes.get(CONF_MODE) == TRAVEL_MODE_CAR
+ assert sensor.attributes.get(CONF_TRAFFIC_MODE) is False
+
+ assert sensor.attributes.get(ATTR_ICON) == ICON_CAR
+
+ # Test traffic mode disabled
+ assert sensor.attributes.get(ATTR_DURATION) != sensor.attributes.get(
+ ATTR_DURATION_IN_TRAFFIC
+ )
+
+
+async def test_traffic_mode_enabled(hass, requests_mock_credentials_check):
+ """Test that traffic mode enabled works."""
+ modes = [ROUTE_MODE_FASTEST, TRAVEL_MODE_CAR, TRAFFIC_MODE_ENABLED]
+ response_url = _build_mock_url(
+ ",".join([CAR_ORIGIN_LATITUDE, CAR_ORIGIN_LONGITUDE]),
+ ",".join([CAR_DESTINATION_LATITUDE, CAR_DESTINATION_LONGITUDE]),
+ modes,
+ APP_ID,
+ APP_CODE,
+ "now",
+ )
+ requests_mock_credentials_check.get(
+ response_url, text=load_fixture("here_travel_time/car_enabled_response.json")
+ )
+
+ config = {
+ DOMAIN: {
+ "platform": PLATFORM,
+ "name": "test",
+ "origin_latitude": CAR_ORIGIN_LATITUDE,
+ "origin_longitude": CAR_ORIGIN_LONGITUDE,
+ "destination_latitude": CAR_DESTINATION_LATITUDE,
+ "destination_longitude": CAR_DESTINATION_LONGITUDE,
+ "app_id": APP_ID,
+ "app_code": APP_CODE,
+ "traffic_mode": True,
+ }
+ }
+ assert await async_setup_component(hass, DOMAIN, config)
+
+ sensor = hass.states.get("sensor.test")
+
+ # Test traffic mode enabled
+ assert sensor.attributes.get(ATTR_DURATION) != sensor.attributes.get(
+ ATTR_DURATION_IN_TRAFFIC
+ )
+
+
+async def test_imperial(hass, requests_mock_car_disabled_response):
+ """Test that imperial units work."""
+ config = {
+ DOMAIN: {
+ "platform": PLATFORM,
+ "name": "test",
+ "origin_latitude": CAR_ORIGIN_LATITUDE,
+ "origin_longitude": CAR_ORIGIN_LONGITUDE,
+ "destination_latitude": CAR_DESTINATION_LATITUDE,
+ "destination_longitude": CAR_DESTINATION_LONGITUDE,
+ "app_id": APP_ID,
+ "app_code": APP_CODE,
+ "unit_system": "imperial",
+ }
+ }
+ assert await async_setup_component(hass, DOMAIN, config)
+
+ sensor = hass.states.get("sensor.test")
+ assert sensor.attributes.get(ATTR_DISTANCE) == 14.852635608048994
+
+
+async def test_route_mode_shortest(hass, requests_mock_credentials_check):
+ """Test that route mode shortest works."""
+ origin = "38.902981,-77.048338"
+ destination = "39.042158,-77.119116"
+ modes = [ROUTE_MODE_SHORTEST, TRAVEL_MODE_CAR, TRAFFIC_MODE_DISABLED]
+ response_url = _build_mock_url(origin, destination, modes, APP_ID, APP_CODE, "now")
+ requests_mock_credentials_check.get(
+ response_url, text=load_fixture("here_travel_time/car_shortest_response.json")
+ )
+
+ config = {
+ DOMAIN: {
+ "platform": PLATFORM,
+ "name": "test",
+ "origin_latitude": origin.split(",")[0],
+ "origin_longitude": origin.split(",")[1],
+ "destination_latitude": destination.split(",")[0],
+ "destination_longitude": destination.split(",")[1],
+ "app_id": APP_ID,
+ "app_code": APP_CODE,
+ "route_mode": ROUTE_MODE_SHORTEST,
+ }
+ }
+ assert await async_setup_component(hass, DOMAIN, config)
+
+ sensor = hass.states.get("sensor.test")
+ assert sensor.attributes.get(ATTR_DISTANCE) == 18.388
+
+
+async def test_route_mode_fastest(hass, requests_mock_credentials_check):
+ """Test that route mode fastest works."""
+ origin = "38.902981,-77.048338"
+ destination = "39.042158,-77.119116"
+ modes = [ROUTE_MODE_FASTEST, TRAVEL_MODE_CAR, TRAFFIC_MODE_ENABLED]
+ response_url = _build_mock_url(origin, destination, modes, APP_ID, APP_CODE, "now")
+ requests_mock_credentials_check.get(
+ response_url, text=load_fixture("here_travel_time/car_enabled_response.json")
+ )
+
+ config = {
+ DOMAIN: {
+ "platform": PLATFORM,
+ "name": "test",
+ "origin_latitude": origin.split(",")[0],
+ "origin_longitude": origin.split(",")[1],
+ "destination_latitude": destination.split(",")[0],
+ "destination_longitude": destination.split(",")[1],
+ "app_id": APP_ID,
+ "app_code": APP_CODE,
+ "traffic_mode": True,
+ }
+ }
+ assert await async_setup_component(hass, DOMAIN, config)
+
+ sensor = hass.states.get("sensor.test")
+ assert sensor.attributes.get(ATTR_DISTANCE) == 23.381
+
+
+async def test_truck(hass, requests_mock_truck_response):
+ """Test that truck works."""
+ config = {
+ DOMAIN: {
+ "platform": PLATFORM,
+ "name": "test",
+ "origin_latitude": TRUCK_ORIGIN_LATITUDE,
+ "origin_longitude": TRUCK_ORIGIN_LONGITUDE,
+ "destination_latitude": TRUCK_DESTINATION_LATITUDE,
+ "destination_longitude": TRUCK_DESTINATION_LONGITUDE,
+ "app_id": APP_ID,
+ "app_code": APP_CODE,
+ "mode": TRAVEL_MODE_TRUCK,
+ }
+ }
+ assert await async_setup_component(hass, DOMAIN, config)
+ sensor = hass.states.get("sensor.test")
+ _assert_truck_sensor(sensor)
+
+
+async def test_public_transport(hass, requests_mock_credentials_check):
+ """Test that publicTransport works."""
+ origin = "41.9798,-87.8801"
+ destination = "41.9043,-87.9216"
+ modes = [ROUTE_MODE_FASTEST, TRAVEL_MODE_PUBLIC, TRAFFIC_MODE_DISABLED]
+ response_url = _build_mock_url(origin, destination, modes, APP_ID, APP_CODE, "now")
+ requests_mock_credentials_check.get(
+ response_url, text=load_fixture("here_travel_time/public_response.json")
+ )
+
+ config = {
+ DOMAIN: {
+ "platform": PLATFORM,
+ "name": "test",
+ "origin_latitude": origin.split(",")[0],
+ "origin_longitude": origin.split(",")[1],
+ "destination_latitude": destination.split(",")[0],
+ "destination_longitude": destination.split(",")[1],
+ "app_id": APP_ID,
+ "app_code": APP_CODE,
+ "mode": TRAVEL_MODE_PUBLIC,
+ }
+ }
+ assert await async_setup_component(hass, DOMAIN, config)
+
+ sensor = hass.states.get("sensor.test")
+ assert sensor.state == "89"
+ assert sensor.attributes.get("unit_of_measurement") == UNIT_OF_MEASUREMENT
+
+ assert sensor.attributes.get(ATTR_ATTRIBUTION) is None
+ assert sensor.attributes.get(ATTR_DURATION) == 89.16666666666667
+ assert sensor.attributes.get(ATTR_DISTANCE) == 22.325
+ assert sensor.attributes.get(ATTR_ROUTE) == (
+ "332 - Palmer/Schiller; 332 - Cargo Rd./Delta Cargo; " "332 - Palmer/Schiller"
+ )
+ assert sensor.attributes.get(CONF_UNIT_SYSTEM) == "metric"
+ assert sensor.attributes.get(ATTR_DURATION_IN_TRAFFIC) == 89.16666666666667
+ assert sensor.attributes.get(ATTR_ORIGIN) == origin
+ assert sensor.attributes.get(ATTR_DESTINATION) == destination
+ assert sensor.attributes.get(ATTR_ORIGIN_NAME) == "Mannheim Rd"
+ assert sensor.attributes.get(ATTR_DESTINATION_NAME) == ""
+ assert sensor.attributes.get(CONF_MODE) == TRAVEL_MODE_PUBLIC
+ assert sensor.attributes.get(CONF_TRAFFIC_MODE) is False
+
+ assert sensor.attributes.get(ATTR_ICON) == ICON_PUBLIC
+
+
+async def test_public_transport_time_table(hass, requests_mock_credentials_check):
+ """Test that publicTransportTimeTable works."""
+ origin = "41.9798,-87.8801"
+ destination = "41.9043,-87.9216"
+ modes = [ROUTE_MODE_FASTEST, TRAVEL_MODE_PUBLIC_TIME_TABLE, TRAFFIC_MODE_DISABLED]
+ response_url = _build_mock_url(origin, destination, modes, APP_ID, APP_CODE, "now")
+ requests_mock_credentials_check.get(
+ response_url,
+ text=load_fixture("here_travel_time/public_time_table_response.json"),
+ )
+
+ config = {
+ DOMAIN: {
+ "platform": PLATFORM,
+ "name": "test",
+ "origin_latitude": origin.split(",")[0],
+ "origin_longitude": origin.split(",")[1],
+ "destination_latitude": destination.split(",")[0],
+ "destination_longitude": destination.split(",")[1],
+ "app_id": APP_ID,
+ "app_code": APP_CODE,
+ "mode": TRAVEL_MODE_PUBLIC_TIME_TABLE,
+ }
+ }
+ assert await async_setup_component(hass, DOMAIN, config)
+
+ sensor = hass.states.get("sensor.test")
+ assert sensor.state == "80"
+ assert sensor.attributes.get("unit_of_measurement") == UNIT_OF_MEASUREMENT
+
+ assert sensor.attributes.get(ATTR_ATTRIBUTION) is None
+ assert sensor.attributes.get(ATTR_DURATION) == 79.73333333333333
+ assert sensor.attributes.get(ATTR_DISTANCE) == 14.775
+ assert sensor.attributes.get(ATTR_ROUTE) == (
+ "330 - Archer/Harlem (Terminal); 309 - Elmhurst Metra Station"
+ )
+ assert sensor.attributes.get(CONF_UNIT_SYSTEM) == "metric"
+ assert sensor.attributes.get(ATTR_DURATION_IN_TRAFFIC) == 79.73333333333333
+ assert sensor.attributes.get(ATTR_ORIGIN) == origin
+ assert sensor.attributes.get(ATTR_DESTINATION) == destination
+ assert sensor.attributes.get(ATTR_ORIGIN_NAME) == "Mannheim Rd"
+ assert sensor.attributes.get(ATTR_DESTINATION_NAME) == ""
+ assert sensor.attributes.get(CONF_MODE) == TRAVEL_MODE_PUBLIC_TIME_TABLE
+ assert sensor.attributes.get(CONF_TRAFFIC_MODE) is False
+
+ assert sensor.attributes.get(ATTR_ICON) == ICON_PUBLIC
+
+
+async def test_pedestrian(hass, requests_mock_credentials_check):
+ """Test that pedestrian works."""
+ origin = "41.9798,-87.8801"
+ destination = "41.9043,-87.9216"
+ modes = [ROUTE_MODE_FASTEST, TRAVEL_MODE_PEDESTRIAN, TRAFFIC_MODE_DISABLED]
+ response_url = _build_mock_url(origin, destination, modes, APP_ID, APP_CODE, "now")
+ requests_mock_credentials_check.get(
+ response_url, text=load_fixture("here_travel_time/pedestrian_response.json")
+ )
+
+ config = {
+ DOMAIN: {
+ "platform": PLATFORM,
+ "name": "test",
+ "origin_latitude": origin.split(",")[0],
+ "origin_longitude": origin.split(",")[1],
+ "destination_latitude": destination.split(",")[0],
+ "destination_longitude": destination.split(",")[1],
+ "app_id": APP_ID,
+ "app_code": APP_CODE,
+ "mode": TRAVEL_MODE_PEDESTRIAN,
+ }
+ }
+ assert await async_setup_component(hass, DOMAIN, config)
+
+ sensor = hass.states.get("sensor.test")
+ assert sensor.state == "211"
+ assert sensor.attributes.get("unit_of_measurement") == UNIT_OF_MEASUREMENT
+
+ assert sensor.attributes.get(ATTR_ATTRIBUTION) is None
+ assert sensor.attributes.get(ATTR_DURATION) == 210.51666666666668
+ assert sensor.attributes.get(ATTR_DISTANCE) == 12.533
+ assert sensor.attributes.get(ATTR_ROUTE) == (
+ "Mannheim Rd; W Belmont Ave; Cullerton St; E Fullerton Ave; "
+ "La Porte Ave; E Palmer Ave; N Railroad Ave; W North Ave; "
+ "E North Ave; E Third St"
+ )
+ assert sensor.attributes.get(CONF_UNIT_SYSTEM) == "metric"
+ assert sensor.attributes.get(ATTR_DURATION_IN_TRAFFIC) == 210.51666666666668
+ assert sensor.attributes.get(ATTR_ORIGIN) == origin
+ assert sensor.attributes.get(ATTR_DESTINATION) == destination
+ assert sensor.attributes.get(ATTR_ORIGIN_NAME) == "Mannheim Rd"
+ assert sensor.attributes.get(ATTR_DESTINATION_NAME) == ""
+ assert sensor.attributes.get(CONF_MODE) == TRAVEL_MODE_PEDESTRIAN
+ assert sensor.attributes.get(CONF_TRAFFIC_MODE) is False
+
+ assert sensor.attributes.get(ATTR_ICON) == ICON_PEDESTRIAN
+
+
+async def test_bicycle(hass, requests_mock_credentials_check):
+ """Test that bicycle works."""
+ origin = "41.9798,-87.8801"
+ destination = "41.9043,-87.9216"
+ modes = [ROUTE_MODE_FASTEST, TRAVEL_MODE_BICYCLE, TRAFFIC_MODE_DISABLED]
+ response_url = _build_mock_url(origin, destination, modes, APP_ID, APP_CODE, "now")
+ requests_mock_credentials_check.get(
+ response_url, text=load_fixture("here_travel_time/bike_response.json")
+ )
+
+ config = {
+ DOMAIN: {
+ "platform": PLATFORM,
+ "name": "test",
+ "origin_latitude": origin.split(",")[0],
+ "origin_longitude": origin.split(",")[1],
+ "destination_latitude": destination.split(",")[0],
+ "destination_longitude": destination.split(",")[1],
+ "app_id": APP_ID,
+ "app_code": APP_CODE,
+ "mode": TRAVEL_MODE_BICYCLE,
+ }
+ }
+ assert await async_setup_component(hass, DOMAIN, config)
+
+ sensor = hass.states.get("sensor.test")
+ assert sensor.state == "55"
+ assert sensor.attributes.get("unit_of_measurement") == UNIT_OF_MEASUREMENT
+
+ assert sensor.attributes.get(ATTR_ATTRIBUTION) is None
+ assert sensor.attributes.get(ATTR_DURATION) == 54.86666666666667
+ assert sensor.attributes.get(ATTR_DISTANCE) == 12.613
+ assert sensor.attributes.get(ATTR_ROUTE) == (
+ "Mannheim Rd; W Belmont Ave; Cullerton St; N Landen Dr; "
+ "E Fullerton Ave; N Wolf Rd; W North Ave; N Clinton Ave; "
+ "E Third St; N Caroline Ave"
+ )
+ assert sensor.attributes.get(CONF_UNIT_SYSTEM) == "metric"
+ assert sensor.attributes.get(ATTR_DURATION_IN_TRAFFIC) == 54.86666666666667
+ assert sensor.attributes.get(ATTR_ORIGIN) == origin
+ assert sensor.attributes.get(ATTR_DESTINATION) == destination
+ assert sensor.attributes.get(ATTR_ORIGIN_NAME) == "Mannheim Rd"
+ assert sensor.attributes.get(ATTR_DESTINATION_NAME) == ""
+ assert sensor.attributes.get(CONF_MODE) == TRAVEL_MODE_BICYCLE
+ assert sensor.attributes.get(CONF_TRAFFIC_MODE) is False
+
+ assert sensor.attributes.get(ATTR_ICON) == ICON_BICYCLE
+
+
+async def test_location_zone(hass, requests_mock_truck_response):
+ """Test that origin/destination supplied by a zone works."""
+ utcnow = dt_util.utcnow()
+ # Patching 'utcnow' to gain more control over the timed update.
+ with patch("homeassistant.util.dt.utcnow", return_value=utcnow):
+ zone_config = {
+ "zone": [
+ {
+ "name": "Destination",
+ "latitude": TRUCK_DESTINATION_LATITUDE,
+ "longitude": TRUCK_DESTINATION_LONGITUDE,
+ "radius": 250,
+ "passive": False,
+ },
+ {
+ "name": "Origin",
+ "latitude": TRUCK_ORIGIN_LATITUDE,
+ "longitude": TRUCK_ORIGIN_LONGITUDE,
+ "radius": 250,
+ "passive": False,
+ },
+ ]
+ }
+ assert await async_setup_component(hass, "zone", zone_config)
+
+ config = {
+ DOMAIN: {
+ "platform": PLATFORM,
+ "name": "test",
+ "origin_entity_id": "zone.origin",
+ "destination_entity_id": "zone.destination",
+ "app_id": APP_ID,
+ "app_code": APP_CODE,
+ "mode": TRAVEL_MODE_TRUCK,
+ }
+ }
+ assert await async_setup_component(hass, DOMAIN, config)
+
+ sensor = hass.states.get("sensor.test")
+ _assert_truck_sensor(sensor)
+
+ # Test that update works more than once
+ async_fire_time_changed(hass, utcnow + SCAN_INTERVAL)
+ await hass.async_block_till_done()
+
+ sensor = hass.states.get("sensor.test")
+ _assert_truck_sensor(sensor)
+
+
+async def test_location_sensor(hass, requests_mock_truck_response):
+ """Test that origin/destination supplied by a sensor works."""
+ utcnow = dt_util.utcnow()
+ # Patching 'utcnow' to gain more control over the timed update.
+ with patch("homeassistant.util.dt.utcnow", return_value=utcnow):
+ hass.states.async_set(
+ "sensor.origin", ",".join([TRUCK_ORIGIN_LATITUDE, TRUCK_ORIGIN_LONGITUDE])
+ )
+ hass.states.async_set(
+ "sensor.destination",
+ ",".join([TRUCK_DESTINATION_LATITUDE, TRUCK_DESTINATION_LONGITUDE]),
+ )
+
+ config = {
+ DOMAIN: {
+ "platform": PLATFORM,
+ "name": "test",
+ "origin_entity_id": "sensor.origin",
+ "destination_entity_id": "sensor.destination",
+ "app_id": APP_ID,
+ "app_code": APP_CODE,
+ "mode": TRAVEL_MODE_TRUCK,
+ }
+ }
+ assert await async_setup_component(hass, DOMAIN, config)
+
+ sensor = hass.states.get("sensor.test")
+ _assert_truck_sensor(sensor)
+
+ # Test that update works more than once
+ async_fire_time_changed(hass, utcnow + SCAN_INTERVAL)
+ await hass.async_block_till_done()
+
+ sensor = hass.states.get("sensor.test")
+ _assert_truck_sensor(sensor)
+
+
+async def test_location_person(hass, requests_mock_truck_response):
+ """Test that origin/destination supplied by a person works."""
+ utcnow = dt_util.utcnow()
+ # Patching 'utcnow' to gain more control over the timed update.
+ with patch("homeassistant.util.dt.utcnow", return_value=utcnow):
+ hass.states.async_set(
+ "person.origin",
+ "unknown",
+ {
+ "latitude": float(TRUCK_ORIGIN_LATITUDE),
+ "longitude": float(TRUCK_ORIGIN_LONGITUDE),
+ },
+ )
+ hass.states.async_set(
+ "person.destination",
+ "unknown",
+ {
+ "latitude": float(TRUCK_DESTINATION_LATITUDE),
+ "longitude": float(TRUCK_DESTINATION_LONGITUDE),
+ },
+ )
+
+ config = {
+ DOMAIN: {
+ "platform": PLATFORM,
+ "name": "test",
+ "origin_entity_id": "person.origin",
+ "destination_entity_id": "person.destination",
+ "app_id": APP_ID,
+ "app_code": APP_CODE,
+ "mode": TRAVEL_MODE_TRUCK,
+ }
+ }
+ assert await async_setup_component(hass, DOMAIN, config)
+
+ sensor = hass.states.get("sensor.test")
+ _assert_truck_sensor(sensor)
+
+ # Test that update works more than once
+ async_fire_time_changed(hass, utcnow + SCAN_INTERVAL)
+ await hass.async_block_till_done()
+
+ sensor = hass.states.get("sensor.test")
+ _assert_truck_sensor(sensor)
+
+
+async def test_location_device_tracker(hass, requests_mock_truck_response):
+ """Test that origin/destination supplied by a device_tracker works."""
+ utcnow = dt_util.utcnow()
+ # Patching 'utcnow' to gain more control over the timed update.
+ with patch("homeassistant.util.dt.utcnow", return_value=utcnow):
+ hass.states.async_set(
+ "device_tracker.origin",
+ "unknown",
+ {
+ "latitude": float(TRUCK_ORIGIN_LATITUDE),
+ "longitude": float(TRUCK_ORIGIN_LONGITUDE),
+ },
+ )
+ hass.states.async_set(
+ "device_tracker.destination",
+ "unknown",
+ {
+ "latitude": float(TRUCK_DESTINATION_LATITUDE),
+ "longitude": float(TRUCK_DESTINATION_LONGITUDE),
+ },
+ )
+
+ config = {
+ DOMAIN: {
+ "platform": PLATFORM,
+ "name": "test",
+ "origin_entity_id": "device_tracker.origin",
+ "destination_entity_id": "device_tracker.destination",
+ "app_id": APP_ID,
+ "app_code": APP_CODE,
+ "mode": TRAVEL_MODE_TRUCK,
+ }
+ }
+ assert await async_setup_component(hass, DOMAIN, config)
+
+ sensor = hass.states.get("sensor.test")
+ _assert_truck_sensor(sensor)
+
+ # Test that update works more than once
+ async_fire_time_changed(hass, utcnow + SCAN_INTERVAL)
+ await hass.async_block_till_done()
+
+ sensor = hass.states.get("sensor.test")
+ _assert_truck_sensor(sensor)
+
+
+async def test_location_device_tracker_added_after_update(
+ hass, requests_mock_truck_response, caplog
+):
+ """Test that device_tracker added after first update works."""
+ caplog.set_level(logging.ERROR)
+ utcnow = dt_util.utcnow()
+ # Patching 'utcnow' to gain more control over the timed update.
+ with patch("homeassistant.util.dt.utcnow", return_value=utcnow):
+ config = {
+ DOMAIN: {
+ "platform": PLATFORM,
+ "name": "test",
+ "origin_entity_id": "device_tracker.origin",
+ "destination_entity_id": "device_tracker.destination",
+ "app_id": APP_ID,
+ "app_code": APP_CODE,
+ "mode": TRAVEL_MODE_TRUCK,
+ }
+ }
+ assert await async_setup_component(hass, DOMAIN, config)
+
+ sensor = hass.states.get("sensor.test")
+ assert len(caplog.records) == 2
+ assert "Unable to find entity" in caplog.text
+ caplog.clear()
+
+ # Device tracker appear after first update
+ hass.states.async_set(
+ "device_tracker.origin",
+ "unknown",
+ {
+ "latitude": float(TRUCK_ORIGIN_LATITUDE),
+ "longitude": float(TRUCK_ORIGIN_LONGITUDE),
+ },
+ )
+ hass.states.async_set(
+ "device_tracker.destination",
+ "unknown",
+ {
+ "latitude": float(TRUCK_DESTINATION_LATITUDE),
+ "longitude": float(TRUCK_DESTINATION_LONGITUDE),
+ },
+ )
+
+ # Test that update works more than once
+ async_fire_time_changed(hass, utcnow + SCAN_INTERVAL)
+ await hass.async_block_till_done()
+
+ sensor = hass.states.get("sensor.test")
+ _assert_truck_sensor(sensor)
+ assert len(caplog.records) == 0
+
+
+async def test_location_device_tracker_in_zone(
+ hass, requests_mock_truck_response, caplog
+):
+ """Test that device_tracker in zone uses device_tracker state works."""
+ caplog.set_level(logging.DEBUG)
+ zone_config = {
+ "zone": [
+ {
+ "name": "Origin",
+ "latitude": TRUCK_ORIGIN_LATITUDE,
+ "longitude": TRUCK_ORIGIN_LONGITUDE,
+ "radius": 250,
+ "passive": False,
+ }
+ ]
+ }
+ assert await async_setup_component(hass, "zone", zone_config)
+ hass.states.async_set(
+ "device_tracker.origin", "origin", {"latitude": None, "longitude": None}
+ )
+ config = {
+ DOMAIN: {
+ "platform": PLATFORM,
+ "name": "test",
+ "origin_entity_id": "device_tracker.origin",
+ "destination_latitude": TRUCK_DESTINATION_LATITUDE,
+ "destination_longitude": TRUCK_DESTINATION_LONGITUDE,
+ "app_id": APP_ID,
+ "app_code": APP_CODE,
+ "mode": TRAVEL_MODE_TRUCK,
+ }
+ }
+ assert await async_setup_component(hass, DOMAIN, config)
+
+ sensor = hass.states.get("sensor.test")
+ _assert_truck_sensor(sensor)
+ assert ", getting zone location" in caplog.text
+
+
+async def test_route_not_found(hass, requests_mock_credentials_check, caplog):
+ """Test that route not found error is correctly handled."""
+ caplog.set_level(logging.ERROR)
+ origin = "52.516,13.3779"
+ destination = "47.013399,-10.171986"
+ modes = [ROUTE_MODE_FASTEST, TRAVEL_MODE_CAR, TRAFFIC_MODE_DISABLED]
+ response_url = _build_mock_url(origin, destination, modes, APP_ID, APP_CODE, "now")
+ requests_mock_credentials_check.get(
+ response_url,
+ text=load_fixture("here_travel_time/routing_error_no_route_found.json"),
+ )
+
+ config = {
+ DOMAIN: {
+ "platform": PLATFORM,
+ "name": "test",
+ "origin_latitude": origin.split(",")[0],
+ "origin_longitude": origin.split(",")[1],
+ "destination_latitude": destination.split(",")[0],
+ "destination_longitude": destination.split(",")[1],
+ "app_id": APP_ID,
+ "app_code": APP_CODE,
+ }
+ }
+ assert await async_setup_component(hass, DOMAIN, config)
+ assert len(caplog.records) == 1
+ assert NO_ROUTE_ERROR_MESSAGE in caplog.text
+
+
+async def test_pattern_origin(hass, caplog):
+ """Test that pattern matching the origin works."""
+ caplog.set_level(logging.ERROR)
+ config = {
+ DOMAIN: {
+ "platform": PLATFORM,
+ "name": "test",
+ "origin_latitude": "138.90",
+ "origin_longitude": "-77.04833",
+ "destination_latitude": CAR_DESTINATION_LATITUDE,
+ "destination_longitude": CAR_DESTINATION_LONGITUDE,
+ "app_id": APP_ID,
+ "app_code": APP_CODE,
+ }
+ }
+ assert await async_setup_component(hass, DOMAIN, config)
+ assert len(caplog.records) == 1
+ assert "invalid latitude" in caplog.text
+
+
+async def test_pattern_destination(hass, caplog):
+ """Test that pattern matching the destination works."""
+ caplog.set_level(logging.ERROR)
+ config = {
+ DOMAIN: {
+ "platform": PLATFORM,
+ "name": "test",
+ "origin_latitude": CAR_ORIGIN_LATITUDE,
+ "origin_longitude": CAR_ORIGIN_LONGITUDE,
+ "destination_latitude": "139.0",
+ "destination_longitude": "-77.1",
+ "app_id": APP_ID,
+ "app_code": APP_CODE,
+ }
+ }
+ assert await async_setup_component(hass, DOMAIN, config)
+ assert len(caplog.records) == 1
+ assert "invalid latitude" in caplog.text
+
+
+async def test_invalid_credentials(hass, requests_mock, caplog):
+ """Test that invalid credentials error is correctly handled."""
+ caplog.set_level(logging.ERROR)
+ modes = [ROUTE_MODE_FASTEST, TRAVEL_MODE_CAR, TRAFFIC_MODE_DISABLED]
+ response_url = _build_mock_url(
+ ",".join([CAR_ORIGIN_LATITUDE, CAR_ORIGIN_LONGITUDE]),
+ ",".join([CAR_DESTINATION_LATITUDE, CAR_DESTINATION_LONGITUDE]),
+ modes,
+ APP_ID,
+ APP_CODE,
+ "now",
+ )
+ requests_mock.get(
+ response_url,
+ text=load_fixture("here_travel_time/routing_error_invalid_credentials.json"),
+ )
+
+ config = {
+ DOMAIN: {
+ "platform": PLATFORM,
+ "name": "test",
+ "origin_latitude": CAR_ORIGIN_LATITUDE,
+ "origin_longitude": CAR_ORIGIN_LONGITUDE,
+ "destination_latitude": CAR_DESTINATION_LATITUDE,
+ "destination_longitude": CAR_DESTINATION_LONGITUDE,
+ "app_id": APP_ID,
+ "app_code": APP_CODE,
+ }
+ }
+ assert await async_setup_component(hass, DOMAIN, config)
+ assert len(caplog.records) == 1
+ assert "Invalid credentials" in caplog.text
+
+
+async def test_attribution(hass, requests_mock_credentials_check):
+ """Test that attributions are correctly displayed."""
+ origin = "50.037751372637686,14.39233448220898"
+ destination = "50.07993838201255,14.42582157361062"
+ modes = [ROUTE_MODE_SHORTEST, TRAVEL_MODE_PUBLIC_TIME_TABLE, TRAFFIC_MODE_ENABLED]
+ response_url = _build_mock_url(origin, destination, modes, APP_ID, APP_CODE, "now")
+ requests_mock_credentials_check.get(
+ response_url, text=load_fixture("here_travel_time/attribution_response.json")
+ )
+
+ config = {
+ DOMAIN: {
+ "platform": PLATFORM,
+ "name": "test",
+ "origin_latitude": origin.split(",")[0],
+ "origin_longitude": origin.split(",")[1],
+ "destination_latitude": destination.split(",")[0],
+ "destination_longitude": destination.split(",")[1],
+ "app_id": APP_ID,
+ "app_code": APP_CODE,
+ "traffic_mode": True,
+ "route_mode": ROUTE_MODE_SHORTEST,
+ "mode": TRAVEL_MODE_PUBLIC_TIME_TABLE,
+ }
+ }
+ assert await async_setup_component(hass, DOMAIN, config)
+ sensor = hass.states.get("sensor.test")
+ assert (
+ sensor.attributes.get(ATTR_ATTRIBUTION)
+ == "With the support of HERE Technologies. All information is provided without warranty of any kind."
+ )
diff --git a/tests/fixtures/here_travel_time/attribution_response.json b/tests/fixtures/here_travel_time/attribution_response.json
new file mode 100644
index 00000000000..9b682f6c51f
--- /dev/null
+++ b/tests/fixtures/here_travel_time/attribution_response.json
@@ -0,0 +1,276 @@
+{
+ "response": {
+ "metaInfo": {
+ "timestamp": "2019-09-21T15:17:31Z",
+ "mapVersion": "8.30.100.154",
+ "moduleVersion": "7.2.201937-5251",
+ "interfaceVersion": "2.6.70",
+ "availableMapVersion": [
+ "8.30.100.154"
+ ]
+ },
+ "route": [
+ {
+ "waypoint": [
+ {
+ "linkId": "+565790671",
+ "mappedPosition": {
+ "latitude": 50.0378591,
+ "longitude": 14.3924721
+ },
+ "originalPosition": {
+ "latitude": 50.0377513,
+ "longitude": 14.3923344
+ },
+ "type": "stopOver",
+ "spot": 0.3,
+ "sideOfStreet": "left",
+ "mappedRoadName": "V Bokách III",
+ "label": "V Bokách III",
+ "shapeIndex": 0,
+ "source": "user"
+ },
+ {
+ "linkId": "+748931502",
+ "mappedPosition": {
+ "latitude": 50.0798786,
+ "longitude": 14.4260037
+ },
+ "originalPosition": {
+ "latitude": 50.0799383,
+ "longitude": 14.4258216
+ },
+ "type": "stopOver",
+ "spot": 1.0,
+ "sideOfStreet": "left",
+ "mappedRoadName": "Štěpánská",
+ "label": "Štěpánská",
+ "shapeIndex": 116,
+ "source": "user"
+ }
+ ],
+ "mode": {
+ "type": "shortest",
+ "transportModes": [
+ "publicTransportTimeTable"
+ ],
+ "trafficMode": "enabled",
+ "feature": []
+ },
+ "leg": [
+ {
+ "start": {
+ "linkId": "+565790671",
+ "mappedPosition": {
+ "latitude": 50.0378591,
+ "longitude": 14.3924721
+ },
+ "originalPosition": {
+ "latitude": 50.0377513,
+ "longitude": 14.3923344
+ },
+ "type": "stopOver",
+ "spot": 0.3,
+ "sideOfStreet": "left",
+ "mappedRoadName": "V Bokách III",
+ "label": "V Bokách III",
+ "shapeIndex": 0,
+ "source": "user"
+ },
+ "end": {
+ "linkId": "+748931502",
+ "mappedPosition": {
+ "latitude": 50.0798786,
+ "longitude": 14.4260037
+ },
+ "originalPosition": {
+ "latitude": 50.0799383,
+ "longitude": 14.4258216
+ },
+ "type": "stopOver",
+ "spot": 1.0,
+ "sideOfStreet": "left",
+ "mappedRoadName": "Štěpánská",
+ "label": "Štěpánská",
+ "shapeIndex": 116,
+ "source": "user"
+ },
+ "length": 7835,
+ "travelTime": 2413,
+ "maneuver": [
+ {
+ "position": {
+ "latitude": 50.0378591,
+ "longitude": 14.3924721
+ },
+ "instruction": "Head northwest on Kosořská. Go for 28 m.",
+ "travelTime": 32,
+ "length": 28,
+ "id": "M1",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 50.0380039,
+ "longitude": 14.3921542
+ },
+ "instruction": "Turn left onto Kosořská. Go for 24 m.",
+ "travelTime": 24,
+ "length": 24,
+ "id": "M2",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 50.0380039,
+ "longitude": 14.3918109
+ },
+ "instruction": "Take the street on the left, Slivenecká. Go for 343 m.",
+ "travelTime": 354,
+ "length": 343,
+ "id": "M3",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 50.0376499,
+ "longitude": 14.3871975
+ },
+ "instruction": "Turn left onto Slivenecká. Go for 64 m.",
+ "travelTime": 72,
+ "length": 64,
+ "id": "M4",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 50.0373602,
+ "longitude": 14.3879807
+ },
+ "instruction": "Turn right onto Slivenecká. Go for 91 m.",
+ "travelTime": 95,
+ "length": 91,
+ "id": "M5",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 50.0365448,
+ "longitude": 14.3878305
+ },
+ "instruction": "Turn left onto K Barrandovu. Go for 124 m.",
+ "travelTime": 126,
+ "length": 124,
+ "id": "M6",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 50.0363168,
+ "longitude": 14.3894618
+ },
+ "instruction": "Go to the Tram station Geologicka and take the rail 5 toward Ústřední dílny DP. Follow for 13 stations.",
+ "travelTime": 1440,
+ "length": 6911,
+ "id": "M7",
+ "stopName": "Geologicka",
+ "_type": "PublicTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 50.0800508,
+ "longitude": 14.423403
+ },
+ "instruction": "Get off at Vodickova.",
+ "travelTime": 0,
+ "length": 0,
+ "id": "M8",
+ "stopName": "Vodickova",
+ "nextRoadName": "Vodičkova",
+ "_type": "PublicTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 50.0800508,
+ "longitude": 14.423403
+ },
+ "instruction": "Head northeast on Vodičkova. Go for 65 m.",
+ "travelTime": 74,
+ "length": 65,
+ "id": "M9",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 50.0804901,
+ "longitude": 14.4239759
+ },
+ "instruction": "Turn right onto V Jámě. Go for 163 m.",
+ "travelTime": 174,
+ "length": 163,
+ "id": "M10",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 50.0796962,
+ "longitude": 14.4258857
+ },
+ "instruction": "Turn left onto Štěpánská. Go for 22 m.",
+ "travelTime": 22,
+ "length": 22,
+ "id": "M11",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 50.0798786,
+ "longitude": 14.4260037
+ },
+ "instruction": "Arrive at Štěpánská. Your destination is on the left.",
+ "travelTime": 0,
+ "length": 0,
+ "id": "M12",
+ "_type": "PrivateTransportManeuverType"
+ }
+ ]
+ }
+ ],
+ "publicTransportLine": [
+ {
+ "lineName": "5",
+ "lineForeground": "#F5ADCE",
+ "lineBackground": "#F5ADCE",
+ "companyName": "HERE Technologies",
+ "destination": "Ústřední dílny DP",
+ "type": "railLight",
+ "id": "L1"
+ }
+ ],
+ "summary": {
+ "distance": 7835,
+ "baseTime": 2413,
+ "flags": [
+ "noThroughRoad",
+ "builtUpArea"
+ ],
+ "text": "The trip takes 7.8 km and 40 mins.",
+ "travelTime": 2413,
+ "departure": "2019-09-21T17:16:17+02:00",
+ "timetableExpiration": "2019-09-21T00:00:00Z",
+ "_type": "PublicTransportRouteSummaryType"
+ }
+ }
+ ],
+ "language": "en-us",
+ "sourceAttribution": {
+ "attribution": "With the support of HERE Technologies. All information is provided without warranty of any kind.",
+ "supplier": [
+ {
+ "title": "HERE Technologies",
+ "href": "https://transit.api.here.com/r?appId=Mt1bOYh3m9uxE7r3wuUx&u=https://wego.here.com"
+ }
+ ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/fixtures/here_travel_time/bike_response.json b/tests/fixtures/here_travel_time/bike_response.json
new file mode 100644
index 00000000000..a3af39129d0
--- /dev/null
+++ b/tests/fixtures/here_travel_time/bike_response.json
@@ -0,0 +1,274 @@
+{
+ "response": {
+ "metaInfo": {
+ "timestamp": "2019-07-24T10:17:40Z",
+ "mapVersion": "8.30.98.154",
+ "moduleVersion": "7.2.201929-4522",
+ "interfaceVersion": "2.6.64",
+ "availableMapVersion": [
+ "8.30.98.154"
+ ]
+ },
+ "route": [
+ {
+ "waypoint": [
+ {
+ "linkId": "-1230414527",
+ "mappedPosition": {
+ "latitude": 41.9797859,
+ "longitude": -87.8790879
+ },
+ "originalPosition": {
+ "latitude": 41.9798,
+ "longitude": -87.8801
+ },
+ "type": "stopOver",
+ "spot": 0.5079365,
+ "sideOfStreet": "right",
+ "mappedRoadName": "Mannheim Rd",
+ "label": "Mannheim Rd - US-12",
+ "shapeIndex": 0,
+ "source": "user"
+ },
+ {
+ "linkId": "+924115108",
+ "mappedPosition": {
+ "latitude": 41.90413,
+ "longitude": -87.9223502
+ },
+ "originalPosition": {
+ "latitude": 41.9043,
+ "longitude": -87.9216001
+ },
+ "type": "stopOver",
+ "spot": 0.1925926,
+ "sideOfStreet": "right",
+ "mappedRoadName": "",
+ "label": "",
+ "shapeIndex": 87,
+ "source": "user"
+ }
+ ],
+ "mode": {
+ "type": "fastest",
+ "transportModes": [
+ "bicycle"
+ ],
+ "trafficMode": "enabled",
+ "feature": []
+ },
+ "leg": [
+ {
+ "start": {
+ "linkId": "-1230414527",
+ "mappedPosition": {
+ "latitude": 41.9797859,
+ "longitude": -87.8790879
+ },
+ "originalPosition": {
+ "latitude": 41.9798,
+ "longitude": -87.8801
+ },
+ "type": "stopOver",
+ "spot": 0.5079365,
+ "sideOfStreet": "right",
+ "mappedRoadName": "Mannheim Rd",
+ "label": "Mannheim Rd - US-12",
+ "shapeIndex": 0,
+ "source": "user"
+ },
+ "end": {
+ "linkId": "+924115108",
+ "mappedPosition": {
+ "latitude": 41.90413,
+ "longitude": -87.9223502
+ },
+ "originalPosition": {
+ "latitude": 41.9043,
+ "longitude": -87.9216001
+ },
+ "type": "stopOver",
+ "spot": 0.1925926,
+ "sideOfStreet": "right",
+ "mappedRoadName": "",
+ "label": "",
+ "shapeIndex": 87,
+ "source": "user"
+ },
+ "length": 12613,
+ "travelTime": 3292,
+ "maneuver": [
+ {
+ "position": {
+ "latitude": 41.9797859,
+ "longitude": -87.8790879
+ },
+ "instruction": "Head south on Mannheim Rd (US-12/US-45). Go for 2.6 km.",
+ "travelTime": 646,
+ "length": 2648,
+ "id": "M1",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 41.9579244,
+ "longitude": -87.8838551
+ },
+ "instruction": "Keep left onto Mannheim Rd (US-12/US-45). Go for 2.4 km.",
+ "travelTime": 621,
+ "length": 2427,
+ "id": "M2",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 41.9364238,
+ "longitude": -87.8849387
+ },
+ "instruction": "Turn right onto W Belmont Ave. Go for 595 m.",
+ "travelTime": 158,
+ "length": 595,
+ "id": "M3",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 41.9362521,
+ "longitude": -87.8921163
+ },
+ "instruction": "Turn left onto Cullerton St. Go for 669 m.",
+ "travelTime": 180,
+ "length": 669,
+ "id": "M4",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 41.9305658,
+ "longitude": -87.8932428
+ },
+ "instruction": "Continue on N Landen Dr. Go for 976 m.",
+ "travelTime": 246,
+ "length": 976,
+ "id": "M5",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 41.9217896,
+ "longitude": -87.8928781
+ },
+ "instruction": "Turn right onto E Fullerton Ave. Go for 904 m.",
+ "travelTime": 238,
+ "length": 904,
+ "id": "M6",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 41.921618,
+ "longitude": -87.9038107
+ },
+ "instruction": "Turn left onto N Wolf Rd. Go for 1.6 km.",
+ "travelTime": 417,
+ "length": 1604,
+ "id": "M7",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 41.907177,
+ "longitude": -87.9032314
+ },
+ "instruction": "Turn right onto W North Ave (IL-64). Go for 2.0 km.",
+ "travelTime": 574,
+ "length": 2031,
+ "id": "M8",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 41.9065225,
+ "longitude": -87.9277039
+ },
+ "instruction": "Turn left onto N Clinton Ave. Go for 275 m.",
+ "travelTime": 78,
+ "length": 275,
+ "id": "M9",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 41.9040549,
+ "longitude": -87.9277253
+ },
+ "instruction": "Turn left onto E Third St. Go for 249 m.",
+ "travelTime": 63,
+ "length": 249,
+ "id": "M10",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 41.9040334,
+ "longitude": -87.9247105
+ },
+ "instruction": "Continue on N Caroline Ave. Go for 96 m.",
+ "travelTime": 37,
+ "length": 96,
+ "id": "M11",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 41.9038832,
+ "longitude": -87.9236054
+ },
+ "instruction": "Turn slightly left. Go for 113 m.",
+ "travelTime": 28,
+ "length": 113,
+ "id": "M12",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 41.9039047,
+ "longitude": -87.9222536
+ },
+ "instruction": "Turn left. Go for 26 m.",
+ "travelTime": 6,
+ "length": 26,
+ "id": "M13",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 41.90413,
+ "longitude": -87.9223502
+ },
+ "instruction": "Arrive at your destination on the right.",
+ "travelTime": 0,
+ "length": 0,
+ "id": "M14",
+ "_type": "PrivateTransportManeuverType"
+ }
+ ]
+ }
+ ],
+ "summary": {
+ "distance": 12613,
+ "baseTime": 3292,
+ "flags": [
+ "noThroughRoad",
+ "builtUpArea",
+ "park"
+ ],
+ "text": "The trip takes 12.6 km and 55 mins.",
+ "travelTime": 3292,
+ "_type": "RouteSummaryType"
+ }
+ }
+ ],
+ "language": "en-us"
+ }
+}
\ No newline at end of file
diff --git a/tests/fixtures/here_travel_time/car_enabled_response.json b/tests/fixtures/here_travel_time/car_enabled_response.json
new file mode 100644
index 00000000000..08da738f046
--- /dev/null
+++ b/tests/fixtures/here_travel_time/car_enabled_response.json
@@ -0,0 +1,298 @@
+{
+ "response": {
+ "metaInfo": {
+ "timestamp": "2019-07-21T21:21:31Z",
+ "mapVersion": "8.30.98.154",
+ "moduleVersion": "7.2.201928-4478",
+ "interfaceVersion": "2.6.64",
+ "availableMapVersion": [
+ "8.30.98.154"
+ ]
+ },
+ "route": [
+ {
+ "waypoint": [
+ {
+ "linkId": "-1128310200",
+ "mappedPosition": {
+ "latitude": 38.9026523,
+ "longitude": -77.048338
+ },
+ "originalPosition": {
+ "latitude": 38.9029809,
+ "longitude": -77.048338
+ },
+ "type": "stopOver",
+ "spot": 0.3538462,
+ "sideOfStreet": "right",
+ "mappedRoadName": "K St NW",
+ "label": "K St NW",
+ "shapeIndex": 0,
+ "source": "user"
+ },
+ {
+ "linkId": "-18459081",
+ "mappedPosition": {
+ "latitude": 39.0422511,
+ "longitude": -77.1193526
+ },
+ "originalPosition": {
+ "latitude": 39.042158,
+ "longitude": -77.119116
+ },
+ "type": "stopOver",
+ "spot": 0.7253521,
+ "sideOfStreet": "left",
+ "mappedRoadName": "Commonwealth Dr",
+ "label": "Commonwealth Dr",
+ "shapeIndex": 283,
+ "source": "user"
+ }
+ ],
+ "mode": {
+ "type": "fastest",
+ "transportModes": [
+ "car"
+ ],
+ "trafficMode": "enabled",
+ "feature": []
+ },
+ "leg": [
+ {
+ "start": {
+ "linkId": "-1128310200",
+ "mappedPosition": {
+ "latitude": 38.9026523,
+ "longitude": -77.048338
+ },
+ "originalPosition": {
+ "latitude": 38.9029809,
+ "longitude": -77.048338
+ },
+ "type": "stopOver",
+ "spot": 0.3538462,
+ "sideOfStreet": "right",
+ "mappedRoadName": "K St NW",
+ "label": "K St NW",
+ "shapeIndex": 0,
+ "source": "user"
+ },
+ "end": {
+ "linkId": "-18459081",
+ "mappedPosition": {
+ "latitude": 39.0422511,
+ "longitude": -77.1193526
+ },
+ "originalPosition": {
+ "latitude": 39.042158,
+ "longitude": -77.119116
+ },
+ "type": "stopOver",
+ "spot": 0.7253521,
+ "sideOfStreet": "left",
+ "mappedRoadName": "Commonwealth Dr",
+ "label": "Commonwealth Dr",
+ "shapeIndex": 283,
+ "source": "user"
+ },
+ "length": 23381,
+ "travelTime": 1817,
+ "maneuver": [
+ {
+ "position": {
+ "latitude": 38.9026523,
+ "longitude": -77.048338
+ },
+ "instruction": "Head toward 22nd St NW on K St NW. Go for 140 m.",
+ "travelTime": 36,
+ "length": 140,
+ "id": "M1",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 38.9027703,
+ "longitude": -77.0494902
+ },
+ "instruction": "Take the 3rd exit from Washington Cir NW roundabout onto K St NW. Go for 325 m.",
+ "travelTime": 81,
+ "length": 325,
+ "id": "M2",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 38.9026523,
+ "longitude": -77.0529449
+ },
+ "instruction": "Keep left onto K St NW (US-29). Go for 201 m.",
+ "travelTime": 29,
+ "length": 201,
+ "id": "M3",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 38.9025235,
+ "longitude": -77.0552516
+ },
+ "instruction": "Keep right onto Whitehurst Fwy (US-29). Go for 1.4 km.",
+ "travelTime": 143,
+ "length": 1381,
+ "id": "M4",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 38.9050448,
+ "longitude": -77.0701969
+ },
+ "instruction": "Turn left onto M St NW. Go for 784 m.",
+ "travelTime": 80,
+ "length": 784,
+ "id": "M5",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 38.9060318,
+ "longitude": -77.0790696
+ },
+ "instruction": "Turn slightly left onto Canal Rd NW. Go for 4.2 km.",
+ "travelTime": 287,
+ "length": 4230,
+ "id": "M6",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 38.9303219,
+ "longitude": -77.1117926
+ },
+ "instruction": "Continue on Clara Barton Pkwy. Go for 844 m.",
+ "travelTime": 55,
+ "length": 844,
+ "id": "M7",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 38.9368558,
+ "longitude": -77.1166742
+ },
+ "instruction": "Continue on Clara Barton Pkwy. Go for 4.7 km.",
+ "travelTime": 294,
+ "length": 4652,
+ "id": "M8",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 38.9706838,
+ "longitude": -77.1461463
+ },
+ "instruction": "Keep right onto Cabin John Pkwy N toward I-495 N. Go for 2.1 km.",
+ "travelTime": 90,
+ "length": 2069,
+ "id": "M9",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 38.9858222,
+ "longitude": -77.1571326
+ },
+ "instruction": "Take left ramp onto I-495 N (Capital Beltway). Go for 2.9 km.",
+ "travelTime": 129,
+ "length": 2890,
+ "id": "M10",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 39.0104449,
+ "longitude": -77.1508026
+ },
+ "instruction": "Keep left onto I-270-SPUR toward I-270/Rockville/Frederick. Go for 1.1 km.",
+ "travelTime": 48,
+ "length": 1136,
+ "id": "M11",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 39.0192747,
+ "longitude": -77.144773
+ },
+ "instruction": "Take exit 1 toward Democracy Blvd/Old Georgetown Rd/MD-187 onto Democracy Blvd. Go for 1.8 km.",
+ "travelTime": 205,
+ "length": 1818,
+ "id": "M12",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 39.0247464,
+ "longitude": -77.1253431
+ },
+ "instruction": "Turn left onto Old Georgetown Rd (MD-187). Go for 2.3 km.",
+ "travelTime": 230,
+ "length": 2340,
+ "id": "M13",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 39.0447772,
+ "longitude": -77.1203649
+ },
+ "instruction": "Turn right onto Nicholson Ln. Go for 208 m.",
+ "travelTime": 31,
+ "length": 208,
+ "id": "M14",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 39.0448952,
+ "longitude": -77.1179724
+ },
+ "instruction": "Turn right onto Commonwealth Dr. Go for 341 m.",
+ "travelTime": 75,
+ "length": 341,
+ "id": "M15",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 39.0422511,
+ "longitude": -77.1193526
+ },
+ "instruction": "Arrive at Commonwealth Dr. Your destination is on the left.",
+ "travelTime": 4,
+ "length": 22,
+ "id": "M16",
+ "_type": "PrivateTransportManeuverType"
+ }
+ ]
+ }
+ ],
+ "summary": {
+ "distance": 23381,
+ "trafficTime": 1782,
+ "baseTime": 1712,
+ "flags": [
+ "noThroughRoad",
+ "motorway",
+ "builtUpArea",
+ "park"
+ ],
+ "text": "The trip takes 23.4 km and 30 mins.",
+ "travelTime": 1782,
+ "_type": "RouteSummaryType"
+ }
+ }
+ ],
+ "language": "en-us"
+ }
+}
\ No newline at end of file
diff --git a/tests/fixtures/here_travel_time/car_response.json b/tests/fixtures/here_travel_time/car_response.json
new file mode 100644
index 00000000000..bda8454f3f3
--- /dev/null
+++ b/tests/fixtures/here_travel_time/car_response.json
@@ -0,0 +1,299 @@
+{
+ "response": {
+ "metaInfo": {
+ "timestamp": "2019-07-19T07:38:39Z",
+ "mapVersion": "8.30.98.154",
+ "moduleVersion": "7.2.201928-4446",
+ "interfaceVersion": "2.6.64",
+ "availableMapVersion": [
+ "8.30.98.154"
+ ]
+ },
+ "route": [
+ {
+ "waypoint": [
+ {
+ "linkId": "+732182239",
+ "mappedPosition": {
+ "latitude": 38.9,
+ "longitude": -77.0488358
+ },
+ "originalPosition": {
+ "latitude": 38.9,
+ "longitude": -77.0483301
+ },
+ "type": "stopOver",
+ "spot": 0.4946237,
+ "sideOfStreet": "right",
+ "mappedRoadName": "22nd St NW",
+ "label": "22nd St NW",
+ "shapeIndex": 0,
+ "source": "user"
+ },
+ {
+ "linkId": "+942865877",
+ "mappedPosition": {
+ "latitude": 38.9999735,
+ "longitude": -77.100141
+ },
+ "originalPosition": {
+ "latitude": 38.9999999,
+ "longitude": -77.1000001
+ },
+ "type": "stopOver",
+ "spot": 1,
+ "sideOfStreet": "left",
+ "mappedRoadName": "Service Rd S",
+ "label": "Service Rd S",
+ "shapeIndex": 279,
+ "source": "user"
+ }
+ ],
+ "mode": {
+ "type": "fastest",
+ "transportModes": [
+ "car"
+ ],
+ "trafficMode": "enabled",
+ "feature": []
+ },
+ "leg": [
+ {
+ "start": {
+ "linkId": "+732182239",
+ "mappedPosition": {
+ "latitude": 38.9,
+ "longitude": -77.0488358
+ },
+ "originalPosition": {
+ "latitude": 38.9,
+ "longitude": -77.0483301
+ },
+ "type": "stopOver",
+ "spot": 0.4946237,
+ "sideOfStreet": "right",
+ "mappedRoadName": "22nd St NW",
+ "label": "22nd St NW",
+ "shapeIndex": 0,
+ "source": "user"
+ },
+ "end": {
+ "linkId": "+942865877",
+ "mappedPosition": {
+ "latitude": 38.9999735,
+ "longitude": -77.100141
+ },
+ "originalPosition": {
+ "latitude": 38.9999999,
+ "longitude": -77.1000001
+ },
+ "type": "stopOver",
+ "spot": 1,
+ "sideOfStreet": "left",
+ "mappedRoadName": "Service Rd S",
+ "label": "Service Rd S",
+ "shapeIndex": 279,
+ "source": "user"
+ },
+ "length": 23903,
+ "travelTime": 1884,
+ "maneuver": [
+ {
+ "position": {
+ "latitude": 38.9,
+ "longitude": -77.0488358
+ },
+ "instruction": "Head toward I St NW on 22nd St NW. Go for 279 m.",
+ "travelTime": 95,
+ "length": 279,
+ "id": "M1",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 38.9021051,
+ "longitude": -77.048825
+ },
+ "instruction": "Turn left toward Pennsylvania Ave NW. Go for 71 m.",
+ "travelTime": 21,
+ "length": 71,
+ "id": "M2",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 38.902545,
+ "longitude": -77.0494151
+ },
+ "instruction": "Take the 3rd exit from Washington Cir NW roundabout onto K St NW. Go for 352 m.",
+ "travelTime": 90,
+ "length": 352,
+ "id": "M3",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 38.9026523,
+ "longitude": -77.0529449
+ },
+ "instruction": "Keep left onto K St NW (US-29). Go for 201 m.",
+ "travelTime": 30,
+ "length": 201,
+ "id": "M4",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 38.9025235,
+ "longitude": -77.0552516
+ },
+ "instruction": "Keep right onto Whitehurst Fwy (US-29). Go for 1.4 km.",
+ "travelTime": 131,
+ "length": 1381,
+ "id": "M5",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 38.9050448,
+ "longitude": -77.0701969
+ },
+ "instruction": "Turn left onto M St NW. Go for 784 m.",
+ "travelTime": 78,
+ "length": 784,
+ "id": "M6",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 38.9060318,
+ "longitude": -77.0790696
+ },
+ "instruction": "Turn slightly left onto Canal Rd NW. Go for 4.2 km.",
+ "travelTime": 277,
+ "length": 4230,
+ "id": "M7",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 38.9303219,
+ "longitude": -77.1117926
+ },
+ "instruction": "Continue on Clara Barton Pkwy. Go for 844 m.",
+ "travelTime": 55,
+ "length": 844,
+ "id": "M8",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 38.9368558,
+ "longitude": -77.1166742
+ },
+ "instruction": "Continue on Clara Barton Pkwy. Go for 4.7 km.",
+ "travelTime": 298,
+ "length": 4652,
+ "id": "M9",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 38.9706838,
+ "longitude": -77.1461463
+ },
+ "instruction": "Keep right onto Cabin John Pkwy N toward I-495 N. Go for 2.1 km.",
+ "travelTime": 91,
+ "length": 2069,
+ "id": "M10",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 38.9858222,
+ "longitude": -77.1571326
+ },
+ "instruction": "Take left ramp onto I-495 N (Capital Beltway). Go for 5.5 km.",
+ "travelTime": 238,
+ "length": 5538,
+ "id": "M11",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 39.0153587,
+ "longitude": -77.1221781
+ },
+ "instruction": "Take exit 36 toward Bethesda onto MD-187 S (Old Georgetown Rd). Go for 2.4 km.",
+ "travelTime": 211,
+ "length": 2365,
+ "id": "M12",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 38.9981818,
+ "longitude": -77.1093571
+ },
+ "instruction": "Turn left onto Lincoln Dr. Go for 506 m.",
+ "travelTime": 127,
+ "length": 506,
+ "id": "M13",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 38.9987397,
+ "longitude": -77.1037138
+ },
+ "instruction": "Turn right onto Service Rd W. Go for 121 m.",
+ "travelTime": 36,
+ "length": 121,
+ "id": "M14",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 38.9976454,
+ "longitude": -77.1036172
+ },
+ "instruction": "Turn left onto Service Rd S. Go for 510 m.",
+ "travelTime": 106,
+ "length": 510,
+ "id": "M15",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 38.9999735,
+ "longitude": -77.100141
+ },
+ "instruction": "Arrive at Service Rd S. Your destination is on the left.",
+ "travelTime": 0,
+ "length": 0,
+ "id": "M16",
+ "_type": "PrivateTransportManeuverType"
+ }
+ ]
+ }
+ ],
+ "summary": {
+ "distance": 23903,
+ "trafficTime": 1861,
+ "baseTime": 1803,
+ "flags": [
+ "noThroughRoad",
+ "motorway",
+ "builtUpArea",
+ "park",
+ "privateRoad"
+ ],
+ "text": "The trip takes 23.9 km and 31 mins.",
+ "travelTime": 1861,
+ "_type": "RouteSummaryType"
+ }
+ }
+ ],
+ "language": "en-us"
+ }
+}
\ No newline at end of file
diff --git a/tests/fixtures/here_travel_time/car_shortest_response.json b/tests/fixtures/here_travel_time/car_shortest_response.json
new file mode 100644
index 00000000000..765c438c1cd
--- /dev/null
+++ b/tests/fixtures/here_travel_time/car_shortest_response.json
@@ -0,0 +1,231 @@
+{
+ "response": {
+ "metaInfo": {
+ "timestamp": "2019-07-21T21:05:28Z",
+ "mapVersion": "8.30.98.154",
+ "moduleVersion": "7.2.201928-4478",
+ "interfaceVersion": "2.6.64",
+ "availableMapVersion": [
+ "8.30.98.154"
+ ]
+ },
+ "route": [
+ {
+ "waypoint": [
+ {
+ "linkId": "-1128310200",
+ "mappedPosition": {
+ "latitude": 38.9026523,
+ "longitude": -77.048338
+ },
+ "originalPosition": {
+ "latitude": 38.9029809,
+ "longitude": -77.048338
+ },
+ "type": "stopOver",
+ "spot": 0.3538462,
+ "sideOfStreet": "right",
+ "mappedRoadName": "K St NW",
+ "label": "K St NW",
+ "shapeIndex": 0,
+ "source": "user"
+ },
+ {
+ "linkId": "-18459081",
+ "mappedPosition": {
+ "latitude": 39.0422511,
+ "longitude": -77.1193526
+ },
+ "originalPosition": {
+ "latitude": 39.042158,
+ "longitude": -77.119116
+ },
+ "type": "stopOver",
+ "spot": 0.7253521,
+ "sideOfStreet": "left",
+ "mappedRoadName": "Commonwealth Dr",
+ "label": "Commonwealth Dr",
+ "shapeIndex": 162,
+ "source": "user"
+ }
+ ],
+ "mode": {
+ "type": "shortest",
+ "transportModes": [
+ "car"
+ ],
+ "trafficMode": "enabled",
+ "feature": []
+ },
+ "leg": [
+ {
+ "start": {
+ "linkId": "-1128310200",
+ "mappedPosition": {
+ "latitude": 38.9026523,
+ "longitude": -77.048338
+ },
+ "originalPosition": {
+ "latitude": 38.9029809,
+ "longitude": -77.048338
+ },
+ "type": "stopOver",
+ "spot": 0.3538462,
+ "sideOfStreet": "right",
+ "mappedRoadName": "K St NW",
+ "label": "K St NW",
+ "shapeIndex": 0,
+ "source": "user"
+ },
+ "end": {
+ "linkId": "-18459081",
+ "mappedPosition": {
+ "latitude": 39.0422511,
+ "longitude": -77.1193526
+ },
+ "originalPosition": {
+ "latitude": 39.042158,
+ "longitude": -77.119116
+ },
+ "type": "stopOver",
+ "spot": 0.7253521,
+ "sideOfStreet": "left",
+ "mappedRoadName": "Commonwealth Dr",
+ "label": "Commonwealth Dr",
+ "shapeIndex": 162,
+ "source": "user"
+ },
+ "length": 18388,
+ "travelTime": 2493,
+ "maneuver": [
+ {
+ "position": {
+ "latitude": 38.9026523,
+ "longitude": -77.048338
+ },
+ "instruction": "Head west on K St NW. Go for 79 m.",
+ "travelTime": 22,
+ "length": 79,
+ "id": "M1",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 38.9026523,
+ "longitude": -77.048825
+ },
+ "instruction": "Turn right onto 22nd St NW. Go for 141 m.",
+ "travelTime": 79,
+ "length": 141,
+ "id": "M2",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 38.9039075,
+ "longitude": -77.048825
+ },
+ "instruction": "Keep left onto 22nd St NW. Go for 841 m.",
+ "travelTime": 256,
+ "length": 841,
+ "id": "M3",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 38.9114928,
+ "longitude": -77.0487821
+ },
+ "instruction": "Turn left onto Massachusetts Ave NW. Go for 145 m.",
+ "travelTime": 22,
+ "length": 145,
+ "id": "M4",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 38.9120293,
+ "longitude": -77.0502949
+ },
+ "instruction": "Take the 1st exit from Massachusetts Ave NW roundabout onto Massachusetts Ave NW. Go for 2.8 km.",
+ "travelTime": 301,
+ "length": 2773,
+ "id": "M5",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 38.9286053,
+ "longitude": -77.073158
+ },
+ "instruction": "Turn right onto Wisconsin Ave NW. Go for 3.8 km.",
+ "travelTime": 610,
+ "length": 3801,
+ "id": "M6",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 38.9607918,
+ "longitude": -77.0857322
+ },
+ "instruction": "Continue on Wisconsin Ave (MD-355). Go for 9.7 km.",
+ "travelTime": 1013,
+ "length": 9686,
+ "id": "M7",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 39.0447664,
+ "longitude": -77.1116638
+ },
+ "instruction": "Turn left onto Nicholson Ln. Go for 559 m.",
+ "travelTime": 111,
+ "length": 559,
+ "id": "M8",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 39.0448952,
+ "longitude": -77.1179724
+ },
+ "instruction": "Turn left onto Commonwealth Dr. Go for 341 m.",
+ "travelTime": 75,
+ "length": 341,
+ "id": "M9",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 39.0422511,
+ "longitude": -77.1193526
+ },
+ "instruction": "Arrive at Commonwealth Dr. Your destination is on the left.",
+ "travelTime": 4,
+ "length": 22,
+ "id": "M10",
+ "_type": "PrivateTransportManeuverType"
+ }
+ ]
+ }
+ ],
+ "summary": {
+ "distance": 18388,
+ "trafficTime": 2427,
+ "baseTime": 2150,
+ "flags": [
+ "noThroughRoad",
+ "builtUpArea",
+ "park"
+ ],
+ "text": "The trip takes 18.4 km and 40 mins.",
+ "travelTime": 2427,
+ "_type": "RouteSummaryType"
+ }
+ }
+ ],
+ "language": "en-us"
+ }
+}
\ No newline at end of file
diff --git a/tests/fixtures/here_travel_time/pedestrian_response.json b/tests/fixtures/here_travel_time/pedestrian_response.json
new file mode 100644
index 00000000000..07881e8bd3d
--- /dev/null
+++ b/tests/fixtures/here_travel_time/pedestrian_response.json
@@ -0,0 +1,308 @@
+{
+ "response": {
+ "metaInfo": {
+ "timestamp": "2019-07-21T18:40:10Z",
+ "mapVersion": "8.30.98.154",
+ "moduleVersion": "7.2.201928-4478",
+ "interfaceVersion": "2.6.64",
+ "availableMapVersion": [
+ "8.30.98.154"
+ ]
+ },
+ "route": [
+ {
+ "waypoint": [
+ {
+ "linkId": "-1230414527",
+ "mappedPosition": {
+ "latitude": 41.9797859,
+ "longitude": -87.8790879
+ },
+ "originalPosition": {
+ "latitude": 41.9798,
+ "longitude": -87.8801
+ },
+ "type": "stopOver",
+ "spot": 0.5079365,
+ "sideOfStreet": "right",
+ "mappedRoadName": "Mannheim Rd",
+ "label": "Mannheim Rd - US-12",
+ "shapeIndex": 0,
+ "source": "user"
+ },
+ {
+ "linkId": "+924115108",
+ "mappedPosition": {
+ "latitude": 41.90413,
+ "longitude": -87.9223502
+ },
+ "originalPosition": {
+ "latitude": 41.9043,
+ "longitude": -87.9216001
+ },
+ "type": "stopOver",
+ "spot": 0.1925926,
+ "sideOfStreet": "right",
+ "mappedRoadName": "",
+ "label": "",
+ "shapeIndex": 122,
+ "source": "user"
+ }
+ ],
+ "mode": {
+ "type": "fastest",
+ "transportModes": [
+ "pedestrian"
+ ],
+ "trafficMode": "disabled",
+ "feature": []
+ },
+ "leg": [
+ {
+ "start": {
+ "linkId": "-1230414527",
+ "mappedPosition": {
+ "latitude": 41.9797859,
+ "longitude": -87.8790879
+ },
+ "originalPosition": {
+ "latitude": 41.9798,
+ "longitude": -87.8801
+ },
+ "type": "stopOver",
+ "spot": 0.5079365,
+ "sideOfStreet": "right",
+ "mappedRoadName": "Mannheim Rd",
+ "label": "Mannheim Rd - US-12",
+ "shapeIndex": 0,
+ "source": "user"
+ },
+ "end": {
+ "linkId": "+924115108",
+ "mappedPosition": {
+ "latitude": 41.90413,
+ "longitude": -87.9223502
+ },
+ "originalPosition": {
+ "latitude": 41.9043,
+ "longitude": -87.9216001
+ },
+ "type": "stopOver",
+ "spot": 0.1925926,
+ "sideOfStreet": "right",
+ "mappedRoadName": "",
+ "label": "",
+ "shapeIndex": 122,
+ "source": "user"
+ },
+ "length": 12533,
+ "travelTime": 12631,
+ "maneuver": [
+ {
+ "position": {
+ "latitude": 41.9797859,
+ "longitude": -87.8790879
+ },
+ "instruction": "Head south on Mannheim Rd. Go for 848 m.",
+ "travelTime": 848,
+ "length": 848,
+ "id": "M1",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 41.9722581,
+ "longitude": -87.8776109
+ },
+ "instruction": "Take the street on the left, Mannheim Rd. Go for 4.2 km.",
+ "travelTime": 4239,
+ "length": 4227,
+ "id": "M2",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 41.9364238,
+ "longitude": -87.8849387
+ },
+ "instruction": "Turn right onto W Belmont Ave. Go for 595 m.",
+ "travelTime": 605,
+ "length": 595,
+ "id": "M3",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 41.9362521,
+ "longitude": -87.8921163
+ },
+ "instruction": "Turn left onto Cullerton St. Go for 406 m.",
+ "travelTime": 411,
+ "length": 406,
+ "id": "M4",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 41.9326043,
+ "longitude": -87.8919983
+ },
+ "instruction": "Turn right onto Cullerton St. Go for 1.2 km.",
+ "travelTime": 1249,
+ "length": 1239,
+ "id": "M5",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 41.9217896,
+ "longitude": -87.8928781
+ },
+ "instruction": "Turn right onto E Fullerton Ave. Go for 786 m.",
+ "travelTime": 796,
+ "length": 786,
+ "id": "M6",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 41.9216394,
+ "longitude": -87.9023838
+ },
+ "instruction": "Turn left onto La Porte Ave. Go for 424 m.",
+ "travelTime": 430,
+ "length": 424,
+ "id": "M7",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 41.9180024,
+ "longitude": -87.9028559
+ },
+ "instruction": "Turn right onto E Palmer Ave. Go for 864 m.",
+ "travelTime": 875,
+ "length": 864,
+ "id": "M8",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 41.9175196,
+ "longitude": -87.9132199
+ },
+ "instruction": "Turn left onto N Railroad Ave. Go for 1.2 km.",
+ "travelTime": 1180,
+ "length": 1170,
+ "id": "M9",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 41.9070268,
+ "longitude": -87.9130161
+ },
+ "instruction": "Turn right onto W North Ave. Go for 638 m.",
+ "travelTime": 638,
+ "length": 638,
+ "id": "M10",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 41.9068551,
+ "longitude": -87.9207087
+ },
+ "instruction": "Take the street on the left, E North Ave. Go for 354 m.",
+ "travelTime": 354,
+ "length": 354,
+ "id": "M11",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 41.9065869,
+ "longitude": -87.9249573
+ },
+ "instruction": "Take the street on the left, E North Ave. Go for 228 m.",
+ "travelTime": 242,
+ "length": 228,
+ "id": "M12",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 41.9065225,
+ "longitude": -87.9277039
+ },
+ "instruction": "Turn left. Go for 409 m.",
+ "travelTime": 419,
+ "length": 409,
+ "id": "M13",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 41.9040334,
+ "longitude": -87.9260409
+ },
+ "instruction": "Turn left onto E Third St. Go for 206 m.",
+ "travelTime": 206,
+ "length": 206,
+ "id": "M14",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 41.9038832,
+ "longitude": -87.9236054
+ },
+ "instruction": "Turn left. Go for 113 m.",
+ "travelTime": 113,
+ "length": 113,
+ "id": "M15",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 41.9039047,
+ "longitude": -87.9222536
+ },
+ "instruction": "Turn left. Go for 26 m.",
+ "travelTime": 26,
+ "length": 26,
+ "id": "M16",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 41.90413,
+ "longitude": -87.9223502
+ },
+ "instruction": "Arrive at your destination on the right.",
+ "travelTime": 0,
+ "length": 0,
+ "id": "M17",
+ "_type": "PrivateTransportManeuverType"
+ }
+ ]
+ }
+ ],
+ "summary": {
+ "distance": 12533,
+ "baseTime": 12631,
+ "flags": [
+ "noThroughRoad",
+ "builtUpArea",
+ "park",
+ "privateRoad"
+ ],
+ "text": "The trip takes 12.5 km and 3:31 h.",
+ "travelTime": 12631,
+ "_type": "RouteSummaryType"
+ }
+ }
+ ],
+ "language": "en-us"
+ }
+}
\ No newline at end of file
diff --git a/tests/fixtures/here_travel_time/public_response.json b/tests/fixtures/here_travel_time/public_response.json
new file mode 100644
index 00000000000..149b4d06c39
--- /dev/null
+++ b/tests/fixtures/here_travel_time/public_response.json
@@ -0,0 +1,294 @@
+{
+ "response": {
+ "metaInfo": {
+ "timestamp": "2019-07-21T18:40:37Z",
+ "mapVersion": "8.30.98.154",
+ "moduleVersion": "7.2.201928-4478",
+ "interfaceVersion": "2.6.64",
+ "availableMapVersion": [
+ "8.30.98.154"
+ ]
+ },
+ "route": [
+ {
+ "waypoint": [
+ {
+ "linkId": "-1230414527",
+ "mappedPosition": {
+ "latitude": 41.9797859,
+ "longitude": -87.8790879
+ },
+ "originalPosition": {
+ "latitude": 41.9798,
+ "longitude": -87.8801
+ },
+ "type": "stopOver",
+ "spot": 0.5079365,
+ "sideOfStreet": "right",
+ "mappedRoadName": "Mannheim Rd",
+ "label": "Mannheim Rd - US-12",
+ "shapeIndex": 0,
+ "source": "user"
+ },
+ {
+ "linkId": "+924115108",
+ "mappedPosition": {
+ "latitude": 41.90413,
+ "longitude": -87.9223502
+ },
+ "originalPosition": {
+ "latitude": 41.9043,
+ "longitude": -87.9216001
+ },
+ "type": "stopOver",
+ "spot": 0.1925926,
+ "sideOfStreet": "right",
+ "mappedRoadName": "",
+ "label": "",
+ "shapeIndex": 191,
+ "source": "user"
+ }
+ ],
+ "mode": {
+ "type": "fastest",
+ "transportModes": [
+ "publicTransport"
+ ],
+ "trafficMode": "disabled",
+ "feature": []
+ },
+ "leg": [
+ {
+ "start": {
+ "linkId": "-1230414527",
+ "mappedPosition": {
+ "latitude": 41.9797859,
+ "longitude": -87.8790879
+ },
+ "originalPosition": {
+ "latitude": 41.9798,
+ "longitude": -87.8801
+ },
+ "type": "stopOver",
+ "spot": 0.5079365,
+ "sideOfStreet": "right",
+ "mappedRoadName": "Mannheim Rd",
+ "label": "Mannheim Rd - US-12",
+ "shapeIndex": 0,
+ "source": "user"
+ },
+ "end": {
+ "linkId": "+924115108",
+ "mappedPosition": {
+ "latitude": 41.90413,
+ "longitude": -87.9223502
+ },
+ "originalPosition": {
+ "latitude": 41.9043,
+ "longitude": -87.9216001
+ },
+ "type": "stopOver",
+ "spot": 0.1925926,
+ "sideOfStreet": "right",
+ "mappedRoadName": "",
+ "label": "",
+ "shapeIndex": 191,
+ "source": "user"
+ },
+ "length": 22325,
+ "travelTime": 5350,
+ "maneuver": [
+ {
+ "position": {
+ "latitude": 41.9797859,
+ "longitude": -87.8790879
+ },
+ "instruction": "Head south on Mannheim Rd. Go for 848 m.",
+ "travelTime": 848,
+ "length": 848,
+ "id": "M1",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 41.9722581,
+ "longitude": -87.8776109
+ },
+ "instruction": "Take the street on the left, Mannheim Rd. Go for 825 m.",
+ "travelTime": 825,
+ "length": 825,
+ "id": "M2",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 41.9650483,
+ "longitude": -87.8769565
+ },
+ "instruction": "Go to the stop Mannheim/Lawrence and take the bus 332 toward Palmer/Schiller. Follow for 7 stops.",
+ "travelTime": 475,
+ "length": 4360,
+ "id": "M3",
+ "stopName": "Mannheim/Lawrence",
+ "_type": "PublicTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 41.9541478,
+ "longitude": -87.9133594
+ },
+ "instruction": "Get off at Irving Park/Taft.",
+ "travelTime": 0,
+ "length": 0,
+ "id": "M4",
+ "stopName": "Irving Park/Taft",
+ "_type": "PublicTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 41.9541478,
+ "longitude": -87.9133594
+ },
+ "instruction": "Take the bus 332 toward Cargo Rd./Delta Cargo. Follow for 1 stop.",
+ "travelTime": 155,
+ "length": 3505,
+ "id": "M5",
+ "stopName": "Irving Park/Taft",
+ "_type": "PublicTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 41.9599199,
+ "longitude": -87.9162776
+ },
+ "instruction": "Get off at Cargo Rd./S. Access Rd./Lufthansa.",
+ "travelTime": 0,
+ "length": 0,
+ "id": "M6",
+ "stopName": "Cargo Rd./S. Access Rd./Lufthansa",
+ "_type": "PublicTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 41.9599199,
+ "longitude": -87.9162776
+ },
+ "instruction": "Take the bus 332 toward Palmer/Schiller. Follow for 41 stops.",
+ "travelTime": 1510,
+ "length": 11261,
+ "id": "M7",
+ "stopName": "Cargo Rd./S. Access Rd./Lufthansa",
+ "_type": "PublicTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 41.9041729,
+ "longitude": -87.9399669
+ },
+ "instruction": "Get off at York/Third.",
+ "travelTime": 0,
+ "length": 0,
+ "id": "M8",
+ "stopName": "York/Third",
+ "nextRoadName": "N York St",
+ "_type": "PublicTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 41.9041729,
+ "longitude": -87.9399669
+ },
+ "instruction": "Head east on N York St. Go for 33 m.",
+ "travelTime": 43,
+ "length": 33,
+ "id": "M9",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 41.9039476,
+ "longitude": -87.9398811
+ },
+ "instruction": "Turn left onto E Third St. Go for 1.4 km.",
+ "travelTime": 1355,
+ "length": 1354,
+ "id": "M10",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 41.9038832,
+ "longitude": -87.9236054
+ },
+ "instruction": "Turn left. Go for 113 m.",
+ "travelTime": 113,
+ "length": 113,
+ "id": "M11",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 41.9039047,
+ "longitude": -87.9222536
+ },
+ "instruction": "Turn left. Go for 26 m.",
+ "travelTime": 26,
+ "length": 26,
+ "id": "M12",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 41.90413,
+ "longitude": -87.9223502
+ },
+ "instruction": "Arrive at your destination on the right.",
+ "travelTime": 0,
+ "length": 0,
+ "id": "M13",
+ "_type": "PrivateTransportManeuverType"
+ }
+ ]
+ }
+ ],
+ "publicTransportLine": [
+ {
+ "lineName": "332",
+ "companyName": "",
+ "destination": "Palmer/Schiller",
+ "type": "busPublic",
+ "id": "L1"
+ },
+ {
+ "lineName": "332",
+ "companyName": "",
+ "destination": "Cargo Rd./Delta Cargo",
+ "type": "busPublic",
+ "id": "L2"
+ },
+ {
+ "lineName": "332",
+ "companyName": "",
+ "destination": "Palmer/Schiller",
+ "type": "busPublic",
+ "id": "L3"
+ }
+ ],
+ "summary": {
+ "distance": 22325,
+ "baseTime": 5350,
+ "flags": [
+ "noThroughRoad",
+ "builtUpArea",
+ "park"
+ ],
+ "text": "The trip takes 22.3 km and 1:29 h.",
+ "travelTime": 5350,
+ "departure": "1970-01-01T00:00:00Z",
+ "_type": "PublicTransportRouteSummaryType"
+ }
+ }
+ ],
+ "language": "en-us"
+ }
+}
\ No newline at end of file
diff --git a/tests/fixtures/here_travel_time/public_time_table_response.json b/tests/fixtures/here_travel_time/public_time_table_response.json
new file mode 100644
index 00000000000..52df0d4eb35
--- /dev/null
+++ b/tests/fixtures/here_travel_time/public_time_table_response.json
@@ -0,0 +1,308 @@
+{
+ "response": {
+ "metaInfo": {
+ "timestamp": "2019-08-06T06:43:24Z",
+ "mapVersion": "8.30.99.152",
+ "moduleVersion": "7.2.201931-4739",
+ "interfaceVersion": "2.6.66",
+ "availableMapVersion": [
+ "8.30.99.152"
+ ]
+ },
+ "route": [
+ {
+ "waypoint": [
+ {
+ "linkId": "-1230414527",
+ "mappedPosition": {
+ "latitude": 41.9797859,
+ "longitude": -87.8790879
+ },
+ "originalPosition": {
+ "latitude": 41.9798,
+ "longitude": -87.8801
+ },
+ "type": "stopOver",
+ "spot": 0.5079365,
+ "sideOfStreet": "right",
+ "mappedRoadName": "Mannheim Rd",
+ "label": "Mannheim Rd - US-12",
+ "shapeIndex": 0,
+ "source": "user"
+ },
+ {
+ "linkId": "+924115108",
+ "mappedPosition": {
+ "latitude": 41.90413,
+ "longitude": -87.9223502
+ },
+ "originalPosition": {
+ "latitude": 41.9043,
+ "longitude": -87.9216001
+ },
+ "type": "stopOver",
+ "spot": 0.1925926,
+ "sideOfStreet": "right",
+ "mappedRoadName": "",
+ "label": "",
+ "shapeIndex": 111,
+ "source": "user"
+ }
+ ],
+ "mode": {
+ "type": "fastest",
+ "transportModes": [
+ "publicTransportTimeTable"
+ ],
+ "trafficMode": "disabled",
+ "feature": []
+ },
+ "leg": [
+ {
+ "start": {
+ "linkId": "-1230414527",
+ "mappedPosition": {
+ "latitude": 41.9797859,
+ "longitude": -87.8790879
+ },
+ "originalPosition": {
+ "latitude": 41.9798,
+ "longitude": -87.8801
+ },
+ "type": "stopOver",
+ "spot": 0.5079365,
+ "sideOfStreet": "right",
+ "mappedRoadName": "Mannheim Rd",
+ "label": "Mannheim Rd - US-12",
+ "shapeIndex": 0,
+ "source": "user"
+ },
+ "end": {
+ "linkId": "+924115108",
+ "mappedPosition": {
+ "latitude": 41.90413,
+ "longitude": -87.9223502
+ },
+ "originalPosition": {
+ "latitude": 41.9043,
+ "longitude": -87.9216001
+ },
+ "type": "stopOver",
+ "spot": 0.1925926,
+ "sideOfStreet": "right",
+ "mappedRoadName": "",
+ "label": "",
+ "shapeIndex": 111,
+ "source": "user"
+ },
+ "length": 14775,
+ "travelTime": 4784,
+ "maneuver": [
+ {
+ "position": {
+ "latitude": 41.9797859,
+ "longitude": -87.8790879
+ },
+ "instruction": "Head south on Mannheim Rd. Go for 848 m.",
+ "travelTime": 848,
+ "length": 848,
+ "id": "M1",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 41.9722581,
+ "longitude": -87.8776109
+ },
+ "instruction": "Take the street on the left, Mannheim Rd. Go for 812 m.",
+ "travelTime": 812,
+ "length": 812,
+ "id": "M2",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 41.965051,
+ "longitude": -87.8769591
+ },
+ "instruction": "Go to the Bus stop Mannheim/Lawrence and take the bus 330 toward Archer/Harlem (Terminal). Follow for 33 stops.",
+ "travelTime": 900,
+ "length": 7815,
+ "id": "M3",
+ "stopName": "Mannheim/Lawrence",
+ "_type": "PublicTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 41.896836,
+ "longitude": -87.883771
+ },
+ "instruction": "Get off at Mannheim/Lake.",
+ "travelTime": 0,
+ "length": 0,
+ "id": "M4",
+ "stopName": "Mannheim/Lake",
+ "_type": "PublicTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 41.896836,
+ "longitude": -87.883771
+ },
+ "instruction": "Walk to Bus Lake/Mannheim.",
+ "travelTime": 300,
+ "length": 72,
+ "id": "M5",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 41.897263,
+ "longitude": -87.8842648
+ },
+ "instruction": "Take the bus 309 toward Elmhurst Metra Station. Follow for 18 stops.",
+ "travelTime": 1020,
+ "length": 4362,
+ "id": "M6",
+ "stopName": "Lake/Mannheim",
+ "_type": "PublicTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 41.9066347,
+ "longitude": -87.928671
+ },
+ "instruction": "Get off at North/Berteau.",
+ "travelTime": 0,
+ "length": 0,
+ "id": "M7",
+ "stopName": "North/Berteau",
+ "nextRoadName": "E Berteau Ave",
+ "_type": "PublicTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 41.9066347,
+ "longitude": -87.928671
+ },
+ "instruction": "Head north on E Berteau Ave. Go for 23 m.",
+ "travelTime": 40,
+ "length": 23,
+ "id": "M8",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 41.9067693,
+ "longitude": -87.9284549
+ },
+ "instruction": "Turn right onto E Berteau Ave. Go for 40 m.",
+ "travelTime": 44,
+ "length": 40,
+ "id": "M9",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 41.9065011,
+ "longitude": -87.9282939
+ },
+ "instruction": "Turn left onto E North Ave. Go for 49 m.",
+ "travelTime": 56,
+ "length": 49,
+ "id": "M10",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 41.9065225,
+ "longitude": -87.9277039
+ },
+ "instruction": "Turn slightly right. Go for 409 m.",
+ "travelTime": 419,
+ "length": 409,
+ "id": "M11",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 41.9040334,
+ "longitude": -87.9260409
+ },
+ "instruction": "Turn left onto E Third St. Go for 206 m.",
+ "travelTime": 206,
+ "length": 206,
+ "id": "M12",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 41.9038832,
+ "longitude": -87.9236054
+ },
+ "instruction": "Turn left. Go for 113 m.",
+ "travelTime": 113,
+ "length": 113,
+ "id": "M13",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 41.9039047,
+ "longitude": -87.9222536
+ },
+ "instruction": "Turn left. Go for 26 m.",
+ "travelTime": 26,
+ "length": 26,
+ "id": "M14",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 41.90413,
+ "longitude": -87.9223502
+ },
+ "instruction": "Arrive at your destination on the right.",
+ "travelTime": 0,
+ "length": 0,
+ "id": "M15",
+ "_type": "PrivateTransportManeuverType"
+ }
+ ]
+ }
+ ],
+ "publicTransportLine": [
+ {
+ "lineName": "330",
+ "companyName": "PACE",
+ "destination": "Archer/Harlem (Terminal)",
+ "type": "busPublic",
+ "id": "L1"
+ },
+ {
+ "lineName": "309",
+ "companyName": "PACE",
+ "destination": "Elmhurst Metra Station",
+ "type": "busPublic",
+ "id": "L2"
+ }
+ ],
+ "summary": {
+ "distance": 14775,
+ "baseTime": 4784,
+ "flags": [
+ "noThroughRoad",
+ "builtUpArea",
+ "park"
+ ],
+ "text": "The trip takes 14.8 km and 1:20 h.",
+ "travelTime": 4784,
+ "departure": "2019-08-06T05:09:20-05:00",
+ "timetableExpiration": "2019-08-04T00:00:00Z",
+ "_type": "PublicTransportRouteSummaryType"
+ }
+ }
+ ],
+ "language": "en-us"
+ }
+}
\ No newline at end of file
diff --git a/tests/fixtures/here_travel_time/routing_error_invalid_credentials.json b/tests/fixtures/here_travel_time/routing_error_invalid_credentials.json
new file mode 100644
index 00000000000..81fb246178c
--- /dev/null
+++ b/tests/fixtures/here_travel_time/routing_error_invalid_credentials.json
@@ -0,0 +1,15 @@
+{
+ "_type": "ns2:RoutingServiceErrorType",
+ "type": "PermissionError",
+ "subtype": "InvalidCredentials",
+ "details": "This is not a valid app_id and app_code pair. Please verify that the values are not swapped between the app_id and app_code and the values provisioned by HERE (either by your customer representative or via http://developer.here.com/myapps) were copied correctly into the request.",
+ "metaInfo": {
+ "timestamp": "2019-07-10T09:43:14Z",
+ "mapVersion": "8.30.98.152",
+ "moduleVersion": "7.2.201927-4307",
+ "interfaceVersion": "2.6.64",
+ "availableMapVersion": [
+ "8.30.98.152"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/tests/fixtures/here_travel_time/routing_error_no_route_found.json b/tests/fixtures/here_travel_time/routing_error_no_route_found.json
new file mode 100644
index 00000000000..a776fa91c43
--- /dev/null
+++ b/tests/fixtures/here_travel_time/routing_error_no_route_found.json
@@ -0,0 +1,21 @@
+{
+ "_type": "ns2:RoutingServiceErrorType",
+ "type": "ApplicationError",
+ "subtype": "NoRouteFound",
+ "details": "Error is NGEO_ERROR_ROUTE_NO_END_POINT",
+ "additionalData": [
+ {
+ "key": "error_code",
+ "value": "NGEO_ERROR_ROUTE_NO_END_POINT"
+ }
+ ],
+ "metaInfo": {
+ "timestamp": "2019-07-10T09:51:04Z",
+ "mapVersion": "8.30.98.152",
+ "moduleVersion": "7.2.201927-4307",
+ "interfaceVersion": "2.6.64",
+ "availableMapVersion": [
+ "8.30.98.152"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/tests/fixtures/here_travel_time/truck_response.json b/tests/fixtures/here_travel_time/truck_response.json
new file mode 100644
index 00000000000..a302d564902
--- /dev/null
+++ b/tests/fixtures/here_travel_time/truck_response.json
@@ -0,0 +1,187 @@
+{
+ "response": {
+ "metaInfo": {
+ "timestamp": "2019-07-21T14:25:00Z",
+ "mapVersion": "8.30.98.154",
+ "moduleVersion": "7.2.201928-4478",
+ "interfaceVersion": "2.6.64",
+ "availableMapVersion": [
+ "8.30.98.154"
+ ]
+ },
+ "route": [
+ {
+ "waypoint": [
+ {
+ "linkId": "+930461269",
+ "mappedPosition": {
+ "latitude": 41.9800687,
+ "longitude": -87.8805614
+ },
+ "originalPosition": {
+ "latitude": 41.9798,
+ "longitude": -87.8801
+ },
+ "type": "stopOver",
+ "spot": 0.5555556,
+ "sideOfStreet": "right",
+ "mappedRoadName": "",
+ "label": "",
+ "shapeIndex": 0,
+ "source": "user"
+ },
+ {
+ "linkId": "-1035319462",
+ "mappedPosition": {
+ "latitude": 41.9042909,
+ "longitude": -87.9216528
+ },
+ "originalPosition": {
+ "latitude": 41.9043,
+ "longitude": -87.9216001
+ },
+ "type": "stopOver",
+ "spot": 0,
+ "sideOfStreet": "left",
+ "mappedRoadName": "Eisenhower Expy E",
+ "label": "Eisenhower Expy E - I-290",
+ "shapeIndex": 135,
+ "source": "user"
+ }
+ ],
+ "mode": {
+ "type": "fastest",
+ "transportModes": [
+ "truck"
+ ],
+ "trafficMode": "disabled",
+ "feature": []
+ },
+ "leg": [
+ {
+ "start": {
+ "linkId": "+930461269",
+ "mappedPosition": {
+ "latitude": 41.9800687,
+ "longitude": -87.8805614
+ },
+ "originalPosition": {
+ "latitude": 41.9798,
+ "longitude": -87.8801
+ },
+ "type": "stopOver",
+ "spot": 0.5555556,
+ "sideOfStreet": "right",
+ "mappedRoadName": "",
+ "label": "",
+ "shapeIndex": 0,
+ "source": "user"
+ },
+ "end": {
+ "linkId": "-1035319462",
+ "mappedPosition": {
+ "latitude": 41.9042909,
+ "longitude": -87.9216528
+ },
+ "originalPosition": {
+ "latitude": 41.9043,
+ "longitude": -87.9216001
+ },
+ "type": "stopOver",
+ "spot": 0,
+ "sideOfStreet": "left",
+ "mappedRoadName": "Eisenhower Expy E",
+ "label": "Eisenhower Expy E - I-290",
+ "shapeIndex": 135,
+ "source": "user"
+ },
+ "length": 13049,
+ "travelTime": 812,
+ "maneuver": [
+ {
+ "position": {
+ "latitude": 41.9800687,
+ "longitude": -87.8805614
+ },
+ "instruction": "Take ramp onto I-190. Go for 631 m.",
+ "travelTime": 53,
+ "length": 631,
+ "id": "M1",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 41.98259,
+ "longitude": -87.8744352
+ },
+ "instruction": "Take exit 1D toward Indiana onto I-294 S (Tri-State Tollway). Go for 10.9 km.",
+ "travelTime": 573,
+ "length": 10872,
+ "id": "M2",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 41.9059324,
+ "longitude": -87.9199362
+ },
+ "instruction": "Take exit 33 toward Rockford/US-20/IL-64 onto I-290 W (Eisenhower Expy W). Go for 475 m.",
+ "travelTime": 54,
+ "length": 475,
+ "id": "M3",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 41.9067156,
+ "longitude": -87.9237771
+ },
+ "instruction": "Take exit 13B toward North Ave onto IL-64 W (E North Ave). Go for 435 m.",
+ "travelTime": 51,
+ "length": 435,
+ "id": "M4",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 41.9065869,
+ "longitude": -87.9249573
+ },
+ "instruction": "Take ramp onto I-290 E (Eisenhower Expy E) toward Chicago/I-294 S. Go for 636 m.",
+ "travelTime": 81,
+ "length": 636,
+ "id": "M5",
+ "_type": "PrivateTransportManeuverType"
+ },
+ {
+ "position": {
+ "latitude": 41.9042909,
+ "longitude": -87.9216528
+ },
+ "instruction": "Arrive at Eisenhower Expy E (I-290). Your destination is on the left.",
+ "travelTime": 0,
+ "length": 0,
+ "id": "M6",
+ "_type": "PrivateTransportManeuverType"
+ }
+ ]
+ }
+ ],
+ "summary": {
+ "distance": 13049,
+ "trafficTime": 812,
+ "baseTime": 812,
+ "flags": [
+ "tollroad",
+ "motorway",
+ "builtUpArea"
+ ],
+ "text": "The trip takes 13.0 km and 14 mins.",
+ "travelTime": 812,
+ "_type": "RouteSummaryType"
+ }
+ }
+ ],
+ "language": "en-us"
+ }
+}
\ No newline at end of file