140 lines
3.9 KiB
Python
140 lines
3.9 KiB
Python
from __future__ import annotations
|
|
|
|
from datetime import datetime, timezone
|
|
|
|
import pytest
|
|
|
|
from app.ha.exceptions import HaUnexpectedPayloadError
|
|
from app.ha.history import (
|
|
normalize_history_payload,
|
|
normalize_logbook_payload,
|
|
normalize_state_history_payload,
|
|
)
|
|
|
|
|
|
def test_normalize_history_payload_groups_and_sorts_numeric_states() -> None:
|
|
payload = [
|
|
[
|
|
{
|
|
"entity_id": "sensor.temperature",
|
|
"state": "22.5",
|
|
"last_changed": "2026-06-01T12:15:00+00:00",
|
|
},
|
|
{
|
|
"state": "21.0",
|
|
"last_changed": "2026-06-01T12:00:00Z",
|
|
},
|
|
],
|
|
[
|
|
{
|
|
"entity_id": "sensor.humidity",
|
|
"state": 45,
|
|
"last_updated": "2026-06-01T12:00:00+00:00",
|
|
}
|
|
],
|
|
]
|
|
|
|
result = normalize_history_payload(payload)
|
|
|
|
assert [series.entity_id for series in result] == [
|
|
"sensor.humidity",
|
|
"sensor.temperature",
|
|
]
|
|
temperature = result[1]
|
|
assert [point.value for point in temperature.points] == [21.0, 22.5]
|
|
assert temperature.points[0].timestamp == datetime(
|
|
2026, 6, 1, 12, 0, tzinfo=timezone.utc
|
|
)
|
|
|
|
|
|
def test_normalize_history_payload_skips_non_numeric_and_non_finite_states() -> None:
|
|
payload = [
|
|
[
|
|
{
|
|
"entity_id": "sensor.temperature",
|
|
"state": state,
|
|
"last_changed": "2026-06-01T12:00:00+00:00",
|
|
}
|
|
for state in ("unknown", "unavailable", "nan", "inf", "-inf", True, None)
|
|
]
|
|
]
|
|
|
|
assert normalize_history_payload(payload) == []
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"payload",
|
|
[
|
|
{},
|
|
[{}],
|
|
[["invalid"]],
|
|
[[{"entity_id": "invalid", "state": "21", "last_changed": "2026-06-01"}]],
|
|
[[{"entity_id": "sensor.a", "state": "21", "last_changed": "invalid"}]],
|
|
[[{"state": "21", "last_changed": "2026-06-01T12:00:00+00:00"}]],
|
|
[
|
|
[
|
|
{
|
|
"entity_id": "sensor.a",
|
|
"state": "21",
|
|
"last_changed": "2026-06-01T12:00:00+00:00",
|
|
},
|
|
{
|
|
"entity_id": "sensor.b",
|
|
"state": "22",
|
|
"last_changed": "2026-06-01T12:01:00+00:00",
|
|
},
|
|
]
|
|
],
|
|
],
|
|
)
|
|
def test_normalize_history_payload_rejects_malformed_structure(payload: object) -> None:
|
|
with pytest.raises(HaUnexpectedPayloadError):
|
|
normalize_history_payload(payload)
|
|
|
|
|
|
def test_normalize_history_payload_accepts_empty_series() -> None:
|
|
assert normalize_history_payload([[]]) == []
|
|
|
|
|
|
def test_normalize_state_history_keeps_categorical_changes() -> None:
|
|
result = normalize_state_history_payload(
|
|
[
|
|
[
|
|
{
|
|
"entity_id": "light.office",
|
|
"state": "off",
|
|
"last_changed": "2026-06-01T08:00:00+00:00",
|
|
},
|
|
{
|
|
"state": "on",
|
|
"last_changed": "2026-06-01T08:05:00+00:00",
|
|
},
|
|
{
|
|
"state": "on",
|
|
"last_changed": "2026-06-01T08:06:00+00:00",
|
|
},
|
|
]
|
|
]
|
|
)
|
|
|
|
assert [point.state for point in result[0].points] == ["off", "on"]
|
|
|
|
|
|
def test_normalize_logbook_preserves_action_origin() -> None:
|
|
result = normalize_logbook_payload(
|
|
[
|
|
{
|
|
"entity_id": "light.office",
|
|
"when": "2026-06-01T08:05:00+00:00",
|
|
"message": "turned on",
|
|
"context_user_id": "user-1",
|
|
"context_domain": "light",
|
|
"context_service": "turn_on",
|
|
}
|
|
],
|
|
"light.office",
|
|
)
|
|
|
|
assert result[0].context_user_id == "user-1"
|
|
assert result[0].context_service == "turn_on"
|