Fix picnic sensor time unit (#62437)
This commit is contained in:
parent
dc3f21dd1e
commit
b0704c190f
3 changed files with 58 additions and 21 deletions
|
@ -3,11 +3,13 @@ from __future__ import annotations
|
|||
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from typing import Any, Literal
|
||||
|
||||
from homeassistant.components.sensor import SensorDeviceClass, SensorEntityDescription
|
||||
from homeassistant.const import CURRENCY_EURO
|
||||
from homeassistant.helpers.typing import StateType
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
DOMAIN = "picnic"
|
||||
|
||||
|
@ -42,7 +44,7 @@ class PicnicRequiredKeysMixin:
|
|||
"""Mixin for required keys."""
|
||||
|
||||
data_type: Literal["cart_data", "slot_data", "last_order_data"]
|
||||
value_fn: Callable[[Any], StateType]
|
||||
value_fn: Callable[[Any], StateType | datetime]
|
||||
|
||||
|
||||
@dataclass
|
||||
|
@ -73,7 +75,7 @@ SENSOR_TYPES: tuple[PicnicSensorEntityDescription, ...] = (
|
|||
icon="mdi:calendar-start",
|
||||
entity_registry_enabled_default=True,
|
||||
data_type="slot_data",
|
||||
value_fn=lambda slot: slot.get("window_start"),
|
||||
value_fn=lambda slot: dt_util.parse_datetime(str(slot.get("window_start"))),
|
||||
),
|
||||
PicnicSensorEntityDescription(
|
||||
key=SENSOR_SELECTED_SLOT_END,
|
||||
|
@ -81,7 +83,7 @@ SENSOR_TYPES: tuple[PicnicSensorEntityDescription, ...] = (
|
|||
icon="mdi:calendar-end",
|
||||
entity_registry_enabled_default=True,
|
||||
data_type="slot_data",
|
||||
value_fn=lambda slot: slot.get("window_end"),
|
||||
value_fn=lambda slot: dt_util.parse_datetime(str(slot.get("window_end"))),
|
||||
),
|
||||
PicnicSensorEntityDescription(
|
||||
key=SENSOR_SELECTED_SLOT_MAX_ORDER_TIME,
|
||||
|
@ -89,7 +91,7 @@ SENSOR_TYPES: tuple[PicnicSensorEntityDescription, ...] = (
|
|||
icon="mdi:clock-alert-outline",
|
||||
entity_registry_enabled_default=True,
|
||||
data_type="slot_data",
|
||||
value_fn=lambda slot: slot.get("cut_off_time"),
|
||||
value_fn=lambda slot: dt_util.parse_datetime(str(slot.get("cut_off_time"))),
|
||||
),
|
||||
PicnicSensorEntityDescription(
|
||||
key=SENSOR_SELECTED_SLOT_MIN_ORDER_VALUE,
|
||||
|
@ -108,14 +110,18 @@ SENSOR_TYPES: tuple[PicnicSensorEntityDescription, ...] = (
|
|||
device_class=SensorDeviceClass.TIMESTAMP,
|
||||
icon="mdi:calendar-start",
|
||||
data_type="last_order_data",
|
||||
value_fn=lambda last_order: last_order.get("slot", {}).get("window_start"),
|
||||
value_fn=lambda last_order: dt_util.parse_datetime(
|
||||
str(last_order.get("slot", {}).get("window_start"))
|
||||
),
|
||||
),
|
||||
PicnicSensorEntityDescription(
|
||||
key=SENSOR_LAST_ORDER_SLOT_END,
|
||||
device_class=SensorDeviceClass.TIMESTAMP,
|
||||
icon="mdi:calendar-end",
|
||||
data_type="last_order_data",
|
||||
value_fn=lambda last_order: last_order.get("slot", {}).get("window_end"),
|
||||
value_fn=lambda last_order: dt_util.parse_datetime(
|
||||
str(last_order.get("slot", {}).get("window_end"))
|
||||
),
|
||||
),
|
||||
PicnicSensorEntityDescription(
|
||||
key=SENSOR_LAST_ORDER_STATUS,
|
||||
|
@ -129,7 +135,9 @@ SENSOR_TYPES: tuple[PicnicSensorEntityDescription, ...] = (
|
|||
icon="mdi:clock-start",
|
||||
entity_registry_enabled_default=True,
|
||||
data_type="last_order_data",
|
||||
value_fn=lambda last_order: last_order.get("eta", {}).get("start"),
|
||||
value_fn=lambda last_order: dt_util.parse_datetime(
|
||||
str(last_order.get("eta", {}).get("start"))
|
||||
),
|
||||
),
|
||||
PicnicSensorEntityDescription(
|
||||
key=SENSOR_LAST_ORDER_ETA_END,
|
||||
|
@ -137,7 +145,9 @@ SENSOR_TYPES: tuple[PicnicSensorEntityDescription, ...] = (
|
|||
icon="mdi:clock-end",
|
||||
entity_registry_enabled_default=True,
|
||||
data_type="last_order_data",
|
||||
value_fn=lambda last_order: last_order.get("eta", {}).get("end"),
|
||||
value_fn=lambda last_order: dt_util.parse_datetime(
|
||||
str(last_order.get("eta", {}).get("end"))
|
||||
),
|
||||
),
|
||||
PicnicSensorEntityDescription(
|
||||
key=SENSOR_LAST_ORDER_DELIVERY_TIME,
|
||||
|
@ -145,7 +155,9 @@ SENSOR_TYPES: tuple[PicnicSensorEntityDescription, ...] = (
|
|||
icon="mdi:timeline-clock",
|
||||
entity_registry_enabled_default=True,
|
||||
data_type="last_order_data",
|
||||
value_fn=lambda last_order: last_order.get("delivery_time", {}).get("start"),
|
||||
value_fn=lambda last_order: dt_util.parse_datetime(
|
||||
str(last_order.get("delivery_time", {}).get("start"))
|
||||
),
|
||||
),
|
||||
PicnicSensorEntityDescription(
|
||||
key=SENSOR_LAST_ORDER_TOTAL_PRICE,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
"""Definition of Picnic sensors."""
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Any, cast
|
||||
|
||||
from homeassistant.components.sensor import SensorEntity
|
||||
|
@ -62,8 +63,8 @@ class PicnicSensor(SensorEntity, CoordinatorEntity):
|
|||
self._attr_unique_id = f"{config_entry.unique_id}.{description.key}"
|
||||
|
||||
@property
|
||||
def native_value(self) -> StateType:
|
||||
"""Return the state of the entity."""
|
||||
def native_value(self) -> StateType | datetime:
|
||||
"""Return the value reported by the sensor."""
|
||||
data_set = (
|
||||
self.coordinator.data.get(self.entity_description.data_type, {})
|
||||
if self.coordinator.data is not None
|
||||
|
@ -73,8 +74,8 @@ class PicnicSensor(SensorEntity, CoordinatorEntity):
|
|||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
"""Return True if entity is available."""
|
||||
return self.coordinator.last_update_success and self.state is not None
|
||||
"""Return True if last update was successful."""
|
||||
return self.coordinator.last_update_success
|
||||
|
||||
@property
|
||||
def device_info(self) -> DeviceInfo:
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
"""The tests for the Picnic sensor platform."""
|
||||
import copy
|
||||
from datetime import timedelta
|
||||
from typing import Dict
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
|
@ -11,7 +12,12 @@ from homeassistant import config_entries
|
|||
from homeassistant.components.picnic import const
|
||||
from homeassistant.components.picnic.const import CONF_COUNTRY_CODE, SENSOR_TYPES
|
||||
from homeassistant.components.sensor import SensorDeviceClass
|
||||
from homeassistant.const import CONF_ACCESS_TOKEN, CURRENCY_EURO, STATE_UNAVAILABLE
|
||||
from homeassistant.const import (
|
||||
CONF_ACCESS_TOKEN,
|
||||
CURRENCY_EURO,
|
||||
STATE_UNAVAILABLE,
|
||||
STATE_UNKNOWN,
|
||||
)
|
||||
from homeassistant.helpers.device_registry import DeviceEntryType
|
||||
from homeassistant.util import dt
|
||||
|
||||
|
@ -99,6 +105,7 @@ class TestPicnicSensor(unittest.IsolatedAsyncioTestCase):
|
|||
# Patch the api client
|
||||
self.picnic_patcher = patch("homeassistant.components.picnic.PicnicAPI")
|
||||
self.picnic_mock = self.picnic_patcher.start()
|
||||
self.picnic_mock().session.auth_token = "3q29fpwhulzes"
|
||||
|
||||
# Add a config entry and setup the integration
|
||||
config_data = {
|
||||
|
@ -277,13 +284,11 @@ class TestPicnicSensor(unittest.IsolatedAsyncioTestCase):
|
|||
await self._setup_platform()
|
||||
|
||||
# Assert sensors are unknown
|
||||
self._assert_sensor("sensor.picnic_selected_slot_start", STATE_UNAVAILABLE)
|
||||
self._assert_sensor("sensor.picnic_selected_slot_end", STATE_UNAVAILABLE)
|
||||
self._assert_sensor("sensor.picnic_selected_slot_start", STATE_UNKNOWN)
|
||||
self._assert_sensor("sensor.picnic_selected_slot_end", STATE_UNKNOWN)
|
||||
self._assert_sensor("sensor.picnic_selected_slot_max_order_time", STATE_UNKNOWN)
|
||||
self._assert_sensor(
|
||||
"sensor.picnic_selected_slot_max_order_time", STATE_UNAVAILABLE
|
||||
)
|
||||
self._assert_sensor(
|
||||
"sensor.picnic_selected_slot_min_order_value", STATE_UNAVAILABLE
|
||||
"sensor.picnic_selected_slot_min_order_value", STATE_UNKNOWN
|
||||
)
|
||||
|
||||
async def test_sensors_last_order_in_future(self):
|
||||
|
@ -300,7 +305,7 @@ class TestPicnicSensor(unittest.IsolatedAsyncioTestCase):
|
|||
await self._setup_platform()
|
||||
|
||||
# Assert delivery time is not available, but eta is
|
||||
self._assert_sensor("sensor.picnic_last_order_delivery_time", STATE_UNAVAILABLE)
|
||||
self._assert_sensor("sensor.picnic_last_order_delivery_time", STATE_UNKNOWN)
|
||||
self._assert_sensor(
|
||||
"sensor.picnic_last_order_eta_start", "2021-02-26T19:54:00+00:00"
|
||||
)
|
||||
|
@ -308,6 +313,25 @@ class TestPicnicSensor(unittest.IsolatedAsyncioTestCase):
|
|||
"sensor.picnic_last_order_eta_end", "2021-02-26T20:14:00+00:00"
|
||||
)
|
||||
|
||||
async def test_sensors_eta_date_malformed(self):
|
||||
"""Test sensor states when last order eta dates are malformed."""
|
||||
# Set-up platform with default mock responses
|
||||
await self._setup_platform(use_default_responses=True)
|
||||
|
||||
# Set non-datetime strings as eta
|
||||
eta_dates: Dict[str, str] = {
|
||||
"start": "wrong-time",
|
||||
"end": "other-malformed-datetime",
|
||||
}
|
||||
delivery_response = copy.deepcopy(DEFAULT_DELIVERY_RESPONSE)
|
||||
delivery_response["eta2"] = eta_dates
|
||||
self.picnic_mock().get_deliveries.return_value = [delivery_response]
|
||||
await self._coordinator.async_refresh()
|
||||
|
||||
# Assert eta times are not available due to malformed date strings
|
||||
self._assert_sensor("sensor.picnic_last_order_eta_start", STATE_UNKNOWN)
|
||||
self._assert_sensor("sensor.picnic_last_order_eta_end", STATE_UNKNOWN)
|
||||
|
||||
async def test_sensors_use_detailed_eta_if_available(self):
|
||||
"""Test sensor states when last order is not yet delivered."""
|
||||
# Set-up platform with default mock responses
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue