HA-008 HA-009: add discovery and history pipeline
Closes #17 Closes #18
This commit is contained in:
@@ -1,8 +1,11 @@
|
||||
from collections.abc import Sequence
|
||||
from datetime import datetime
|
||||
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from app.ha.exceptions import HaTimeoutError
|
||||
from app.ha.discovery import DiscoveredEntity, EntityRole
|
||||
from app.ha.history import EntityHistorySeries, NumericHistoryPoint
|
||||
from app.ha.models import HaEntitySummary
|
||||
from app.ha.reader import HaReader
|
||||
from app.main import app
|
||||
@@ -15,6 +18,38 @@ class FakeHaReader(HaReader):
|
||||
def read_entities(self) -> Sequence[HaEntitySummary]:
|
||||
return [HaEntitySummary(entity_id="sensor.temperature", domain="sensor")]
|
||||
|
||||
def discover(
|
||||
self,
|
||||
domains: set[str] | None = None,
|
||||
learnable: bool | None = None,
|
||||
) -> Sequence[DiscoveredEntity]:
|
||||
result = DiscoveredEntity(
|
||||
entity_id="sensor.temperature",
|
||||
domain="sensor",
|
||||
device_class="temperature",
|
||||
role=EntityRole.MEASUREMENT,
|
||||
learnable=True,
|
||||
reason="Numerischer Messsensor für Zeitreihen und Training.",
|
||||
)
|
||||
if domains and result.domain not in domains:
|
||||
return []
|
||||
if learnable is not None and result.learnable is not learnable:
|
||||
return []
|
||||
return [result]
|
||||
|
||||
def read_history(
|
||||
self,
|
||||
entity_ids: list[str],
|
||||
start_time: datetime,
|
||||
end_time: datetime,
|
||||
) -> Sequence[EntityHistorySeries]:
|
||||
return [
|
||||
EntityHistorySeries(
|
||||
entity_id=entity_ids[0],
|
||||
points=[NumericHistoryPoint(timestamp=start_time, value=21.5)],
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
class TimeoutHaReader(HaReader):
|
||||
def __init__(self) -> None:
|
||||
@@ -59,3 +94,44 @@ def test_entities_maps_ha_errors_without_leaking_details() -> None:
|
||||
response = client.get("/v1/entities")
|
||||
assert response.status_code == 504
|
||||
assert response.json() == {"detail": "Home Assistant request timed out."}
|
||||
|
||||
|
||||
def test_discovery_filters_entities() -> None:
|
||||
with TestClient(app) as client:
|
||||
app.state.ha_reader = FakeHaReader()
|
||||
response = client.get("/v1/discovery?domain=sensor&learnable=true")
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json() == [
|
||||
{
|
||||
"entity_id": "sensor.temperature",
|
||||
"domain": "sensor",
|
||||
"device_class": "temperature",
|
||||
"state_class": None,
|
||||
"unit_of_measurement": None,
|
||||
"role": "measurement",
|
||||
"learnable": True,
|
||||
"reason": "Numerischer Messsensor für Zeitreihen und Training.",
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
def test_history_returns_normalized_series() -> None:
|
||||
with TestClient(app) as client:
|
||||
app.state.ha_reader = FakeHaReader()
|
||||
response = client.get(
|
||||
"/v1/history",
|
||||
params=[
|
||||
("entity_id", "sensor.temperature"),
|
||||
("start_time", "2026-06-01T00:00:00Z"),
|
||||
("end_time", "2026-06-02T00:00:00Z"),
|
||||
],
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json() == [
|
||||
{
|
||||
"entity_id": "sensor.temperature",
|
||||
"points": [{"timestamp": "2026-06-01T00:00:00Z", "value": 21.5}],
|
||||
}
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user