mirror of
https://github.com/Electric-Special/ha-core.git
synced 2026-03-21 06:05:26 +01:00
Mark datetime sensors as unknown when parsing fails (#161952)
This commit is contained in:
@@ -540,7 +540,17 @@ class APCUPSdSensor(APCUPSdEntity, SensorEntity):
|
||||
data = self.coordinator.data[key]
|
||||
|
||||
if self.entity_description.device_class == SensorDeviceClass.TIMESTAMP:
|
||||
self._attr_native_value = dateutil.parser.parse(data)
|
||||
# The date could be "N/A" for certain fields (e.g., XOFFBATT), indicating there is no value yet.
|
||||
if data == "N/A":
|
||||
self._attr_native_value = None
|
||||
return
|
||||
|
||||
try:
|
||||
self._attr_native_value = dateutil.parser.parse(data)
|
||||
except (dateutil.parser.ParserError, OverflowError):
|
||||
# If parsing fails we should mark it as unknown, with a log for further debugging.
|
||||
_LOGGER.warning('Failed to parse date for %s: "%s"', key, data)
|
||||
self._attr_native_value = None
|
||||
return
|
||||
|
||||
self._attr_native_value, inferred_unit = infer_unit(data)
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
from datetime import timedelta
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
import dateutil.parser
|
||||
import pytest
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
|
||||
@@ -135,42 +134,64 @@ async def test_manual_update_entity(
|
||||
assert state.state == "15.0"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("mock_request_status", [MOCK_MINIMAL_STATUS], indirect=True)
|
||||
@pytest.mark.parametrize(
|
||||
("mock_request_status", "entity_id", "known_status"),
|
||||
[
|
||||
pytest.param(
|
||||
# Even though the "LASTSTEST" field is not available, we should still create the entity.
|
||||
MOCK_MINIMAL_STATUS,
|
||||
"sensor.apc_ups_last_self_test",
|
||||
MOCK_MINIMAL_STATUS | {"LASTSTEST": "1970-01-01 00:00:00 +0000"},
|
||||
id="last_self_test_missing",
|
||||
),
|
||||
pytest.param(
|
||||
MOCK_MINIMAL_STATUS | {"XOFFBATT": "N/A"},
|
||||
"sensor.apc_ups_transfer_from_battery",
|
||||
MOCK_MINIMAL_STATUS | {"XOFFBATT": "1970-01-01 00:00:00 +0000"},
|
||||
id="xoffbatt_na",
|
||||
),
|
||||
pytest.param(
|
||||
MOCK_MINIMAL_STATUS | {"XOFFBATT": "invalid-time-string"},
|
||||
"sensor.apc_ups_transfer_from_battery",
|
||||
MOCK_MINIMAL_STATUS | {"XOFFBATT": "1970-01-01 00:00:00 +0000"},
|
||||
id="xoffbatt_invalid_time_string",
|
||||
),
|
||||
],
|
||||
indirect=["mock_request_status"],
|
||||
)
|
||||
async def test_sensor_unknown(
|
||||
hass: HomeAssistant,
|
||||
mock_request_status: AsyncMock,
|
||||
entity_id: str,
|
||||
known_status: dict[str, str],
|
||||
) -> None:
|
||||
"""Test if our integration can properly mark certain sensors as unknown when it becomes so."""
|
||||
ups_mode_id = "sensor.apc_ups_mode"
|
||||
last_self_test_id = "sensor.apc_ups_last_self_test"
|
||||
"""Test if our integration can properly mark certain sensors as known/unknown when it becomes so."""
|
||||
base_status = mock_request_status.return_value
|
||||
|
||||
assert hass.states.get(ups_mode_id).state == MOCK_MINIMAL_STATUS["UPSMODE"]
|
||||
# Last self test sensor should be added even if our status does not report it initially (it is
|
||||
# a sensor that appears only after a periodical or manual self test is performed).
|
||||
assert hass.states.get(last_self_test_id) is not None
|
||||
assert hass.states.get(last_self_test_id).state == STATE_UNKNOWN
|
||||
# The state should be unknown initially.
|
||||
state = hass.states.get(entity_id)
|
||||
assert state
|
||||
assert state.state == STATE_UNKNOWN
|
||||
|
||||
# Simulate an event (a self test) such that "LASTSTEST" field is being reported, the state of
|
||||
# the sensor should be properly updated with the corresponding value.
|
||||
last_self_test_value = "1970-01-01 00:00:00 +0000"
|
||||
mock_request_status.return_value = MOCK_MINIMAL_STATUS | {
|
||||
"LASTSTEST": last_self_test_value
|
||||
}
|
||||
# Update to a payload that should make the entity known.
|
||||
mock_request_status.return_value = known_status
|
||||
future = utcnow() + timedelta(minutes=2)
|
||||
async_fire_time_changed(hass, future)
|
||||
await hass.async_block_till_done()
|
||||
assert (
|
||||
hass.states.get(last_self_test_id).state
|
||||
== dateutil.parser.parse(last_self_test_value).isoformat()
|
||||
)
|
||||
|
||||
# Simulate another event (e.g., daemon restart) such that "LASTSTEST" is no longer reported.
|
||||
mock_request_status.return_value = MOCK_MINIMAL_STATUS
|
||||
state = hass.states.get(entity_id)
|
||||
assert state
|
||||
assert state.state != STATE_UNKNOWN
|
||||
|
||||
# Revert back to the initial status, and the state should now be unknown again.
|
||||
mock_request_status.return_value = base_status
|
||||
future = utcnow() + timedelta(minutes=2)
|
||||
async_fire_time_changed(hass, future)
|
||||
await hass.async_block_till_done()
|
||||
# The state should become unknown again.
|
||||
assert hass.states.get(last_self_test_id).state == STATE_UNKNOWN
|
||||
|
||||
state = hass.states.get(entity_id)
|
||||
assert state
|
||||
assert state.state == STATE_UNKNOWN
|
||||
|
||||
|
||||
@pytest.mark.parametrize(("entity_key", "issue_key"), DEPRECATED_SENSORS.items())
|
||||
|
||||
Reference in New Issue
Block a user