Fix picnic sensor time unit (#62437)

This commit is contained in:
corneyl 2021-12-27 17:44:45 +01:00 committed by GitHub
parent dc3f21dd1e
commit b0704c190f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 58 additions and 21 deletions

View file

@ -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,

View file

@ -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:

View file

@ -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