Fix picnic sensor time unit (#62437)

This commit is contained in:
corneyl 2021-12-27 17:44:45 +01:00 committed by Franck Nijhof
parent d1a4e73ffd
commit 1f82fbe019
No known key found for this signature in database
GPG Key ID: D62583BA8AB11CA3
3 changed files with 53 additions and 20 deletions

View File

@ -3,11 +3,13 @@ from __future__ import annotations
from collections.abc import Callable from collections.abc import Callable
from dataclasses import dataclass from dataclasses import dataclass
from datetime import datetime
from typing import Any, Literal from typing import Any, Literal
from homeassistant.components.sensor import SensorEntityDescription from homeassistant.components.sensor import SensorEntityDescription
from homeassistant.const import CURRENCY_EURO, DEVICE_CLASS_TIMESTAMP from homeassistant.const import CURRENCY_EURO, DEVICE_CLASS_TIMESTAMP
from homeassistant.helpers.typing import StateType from homeassistant.helpers.typing import StateType
from homeassistant.util import dt as dt_util
DOMAIN = "picnic" DOMAIN = "picnic"
@ -42,7 +44,7 @@ class PicnicRequiredKeysMixin:
"""Mixin for required keys.""" """Mixin for required keys."""
data_type: Literal["cart_data", "slot_data", "last_order_data"] data_type: Literal["cart_data", "slot_data", "last_order_data"]
value_fn: Callable[[Any], StateType] value_fn: Callable[[Any], StateType | datetime]
@dataclass @dataclass
@ -73,7 +75,7 @@ SENSOR_TYPES: tuple[PicnicSensorEntityDescription, ...] = (
icon="mdi:calendar-start", icon="mdi:calendar-start",
entity_registry_enabled_default=True, entity_registry_enabled_default=True,
data_type="slot_data", 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( PicnicSensorEntityDescription(
key=SENSOR_SELECTED_SLOT_END, key=SENSOR_SELECTED_SLOT_END,
@ -81,7 +83,7 @@ SENSOR_TYPES: tuple[PicnicSensorEntityDescription, ...] = (
icon="mdi:calendar-end", icon="mdi:calendar-end",
entity_registry_enabled_default=True, entity_registry_enabled_default=True,
data_type="slot_data", 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( PicnicSensorEntityDescription(
key=SENSOR_SELECTED_SLOT_MAX_ORDER_TIME, key=SENSOR_SELECTED_SLOT_MAX_ORDER_TIME,
@ -89,7 +91,7 @@ SENSOR_TYPES: tuple[PicnicSensorEntityDescription, ...] = (
icon="mdi:clock-alert-outline", icon="mdi:clock-alert-outline",
entity_registry_enabled_default=True, entity_registry_enabled_default=True,
data_type="slot_data", 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( PicnicSensorEntityDescription(
key=SENSOR_SELECTED_SLOT_MIN_ORDER_VALUE, key=SENSOR_SELECTED_SLOT_MIN_ORDER_VALUE,
@ -108,14 +110,18 @@ SENSOR_TYPES: tuple[PicnicSensorEntityDescription, ...] = (
device_class=DEVICE_CLASS_TIMESTAMP, device_class=DEVICE_CLASS_TIMESTAMP,
icon="mdi:calendar-start", icon="mdi:calendar-start",
data_type="last_order_data", 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( PicnicSensorEntityDescription(
key=SENSOR_LAST_ORDER_SLOT_END, key=SENSOR_LAST_ORDER_SLOT_END,
device_class=DEVICE_CLASS_TIMESTAMP, device_class=DEVICE_CLASS_TIMESTAMP,
icon="mdi:calendar-end", icon="mdi:calendar-end",
data_type="last_order_data", 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( PicnicSensorEntityDescription(
key=SENSOR_LAST_ORDER_STATUS, key=SENSOR_LAST_ORDER_STATUS,
@ -129,7 +135,9 @@ SENSOR_TYPES: tuple[PicnicSensorEntityDescription, ...] = (
icon="mdi:clock-start", icon="mdi:clock-start",
entity_registry_enabled_default=True, entity_registry_enabled_default=True,
data_type="last_order_data", 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( PicnicSensorEntityDescription(
key=SENSOR_LAST_ORDER_ETA_END, key=SENSOR_LAST_ORDER_ETA_END,
@ -137,7 +145,9 @@ SENSOR_TYPES: tuple[PicnicSensorEntityDescription, ...] = (
icon="mdi:clock-end", icon="mdi:clock-end",
entity_registry_enabled_default=True, entity_registry_enabled_default=True,
data_type="last_order_data", 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( PicnicSensorEntityDescription(
key=SENSOR_LAST_ORDER_DELIVERY_TIME, key=SENSOR_LAST_ORDER_DELIVERY_TIME,
@ -145,7 +155,9 @@ SENSOR_TYPES: tuple[PicnicSensorEntityDescription, ...] = (
icon="mdi:timeline-clock", icon="mdi:timeline-clock",
entity_registry_enabled_default=True, entity_registry_enabled_default=True,
data_type="last_order_data", 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( PicnicSensorEntityDescription(
key=SENSOR_LAST_ORDER_TOTAL_PRICE, key=SENSOR_LAST_ORDER_TOTAL_PRICE,

View File

@ -1,6 +1,7 @@
"""Definition of Picnic sensors.""" """Definition of Picnic sensors."""
from __future__ import annotations from __future__ import annotations
from datetime import datetime
from typing import Any, cast from typing import Any, cast
from homeassistant.components.sensor import SensorEntity 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}" self._attr_unique_id = f"{config_entry.unique_id}.{description.key}"
@property @property
def native_value(self) -> StateType: def native_value(self) -> StateType | datetime:
"""Return the state of the entity.""" """Return the value reported by the sensor."""
data_set = ( data_set = (
self.coordinator.data.get(self.entity_description.data_type, {}) self.coordinator.data.get(self.entity_description.data_type, {})
if self.coordinator.data is not None if self.coordinator.data is not None
@ -73,8 +74,8 @@ class PicnicSensor(SensorEntity, CoordinatorEntity):
@property @property
def available(self) -> bool: def available(self) -> bool:
"""Return True if entity is available.""" """Return True if last update was successful."""
return self.coordinator.last_update_success and self.state is not None return self.coordinator.last_update_success
@property @property
def device_info(self) -> DeviceInfo: def device_info(self) -> DeviceInfo:

View File

@ -1,6 +1,7 @@
"""The tests for the Picnic sensor platform.""" """The tests for the Picnic sensor platform."""
import copy import copy
from datetime import timedelta from datetime import timedelta
from typing import Dict
import unittest import unittest
from unittest.mock import patch from unittest.mock import patch
@ -15,6 +16,7 @@ from homeassistant.const import (
CURRENCY_EURO, CURRENCY_EURO,
DEVICE_CLASS_TIMESTAMP, DEVICE_CLASS_TIMESTAMP,
STATE_UNAVAILABLE, STATE_UNAVAILABLE,
STATE_UNKNOWN,
) )
from homeassistant.helpers.device_registry import DeviceEntryType from homeassistant.helpers.device_registry import DeviceEntryType
from homeassistant.util import dt from homeassistant.util import dt
@ -103,6 +105,7 @@ class TestPicnicSensor(unittest.IsolatedAsyncioTestCase):
# Patch the api client # Patch the api client
self.picnic_patcher = patch("homeassistant.components.picnic.PicnicAPI") self.picnic_patcher = patch("homeassistant.components.picnic.PicnicAPI")
self.picnic_mock = self.picnic_patcher.start() self.picnic_mock = self.picnic_patcher.start()
self.picnic_mock().session.auth_token = "3q29fpwhulzes"
# Add a config entry and setup the integration # Add a config entry and setup the integration
config_data = { config_data = {
@ -281,13 +284,11 @@ class TestPicnicSensor(unittest.IsolatedAsyncioTestCase):
await self._setup_platform() await self._setup_platform()
# Assert sensors are unknown # Assert sensors are unknown
self._assert_sensor("sensor.picnic_selected_slot_start", STATE_UNAVAILABLE) self._assert_sensor("sensor.picnic_selected_slot_start", STATE_UNKNOWN)
self._assert_sensor("sensor.picnic_selected_slot_end", STATE_UNAVAILABLE) 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( self._assert_sensor(
"sensor.picnic_selected_slot_max_order_time", STATE_UNAVAILABLE "sensor.picnic_selected_slot_min_order_value", STATE_UNKNOWN
)
self._assert_sensor(
"sensor.picnic_selected_slot_min_order_value", STATE_UNAVAILABLE
) )
async def test_sensors_last_order_in_future(self): async def test_sensors_last_order_in_future(self):
@ -304,7 +305,7 @@ class TestPicnicSensor(unittest.IsolatedAsyncioTestCase):
await self._setup_platform() await self._setup_platform()
# Assert delivery time is not available, but eta is # 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( self._assert_sensor(
"sensor.picnic_last_order_eta_start", "2021-02-26T19:54:00+00:00" "sensor.picnic_last_order_eta_start", "2021-02-26T19:54:00+00:00"
) )
@ -312,6 +313,25 @@ class TestPicnicSensor(unittest.IsolatedAsyncioTestCase):
"sensor.picnic_last_order_eta_end", "2021-02-26T20:14:00+00:00" "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): async def test_sensors_use_detailed_eta_if_available(self):
"""Test sensor states when last order is not yet delivered.""" """Test sensor states when last order is not yet delivered."""
# Set-up platform with default mock responses # Set-up platform with default mock responses