"""ADR-117 P4 — Tests for HA-DISCO payload parsing. Pure parsing tests — no MQTT broker needed. """ from __future__ import annotations import json import pytest from wifi_densepose.client import ( HABlueprintHelper, HaDiscoveryPayload, HaEntity, ) from wifi_densepose.client.ha import ( parse_discovery_payload, parse_discovery_topic, ) # Real discovery payloads pulled from ADR-115 §3 (formatted for test # readability; payloads are otherwise verbatim). _PRESENCE_TOPIC = "homeassistant/binary_sensor/wifi_densepose_aabbccddeeff/presence/config" _PRESENCE_BODY = { "name": "Presence", "unique_id": "wifi_densepose_aabbccddeeff_presence", "object_id": "wifi_densepose_aabbccddeeff_presence", "state_topic": "homeassistant/binary_sensor/wifi_densepose_aabbccddeeff/presence/state", "availability_topic": "homeassistant/binary_sensor/wifi_densepose_aabbccddeeff/presence/availability", "device_class": "occupancy", "icon": "mdi:motion-sensor", } _HEART_RATE_TOPIC = "homeassistant/sensor/wifi_densepose_aabbccddeeff/heart_rate/config" _HEART_RATE_BODY = { "name": "Heart rate", "unique_id": "wifi_densepose_aabbccddeeff_heart_rate", "state_topic": "homeassistant/sensor/wifi_densepose_aabbccddeeff/heart_rate/state", "state_class": "measurement", "unit_of_measurement": "bpm", "icon": "mdi:heart-pulse", "json_attributes_topic": "homeassistant/sensor/wifi_densepose_aabbccddeeff/heart_rate/state", } # ─── Topic parsing ─────────────────────────────────────────────────── def test_parse_discovery_topic_binary_sensor() -> None: out = parse_discovery_topic(_PRESENCE_TOPIC) assert out == ("binary_sensor", "aabbccddeeff", "presence") def test_parse_discovery_topic_sensor() -> None: out = parse_discovery_topic(_HEART_RATE_TOPIC) assert out == ("sensor", "aabbccddeeff", "heart_rate") def test_parse_discovery_topic_event() -> None: out = parse_discovery_topic( "homeassistant/event/wifi_densepose_aabbccddeeff/fall/config" ) assert out == ("event", "aabbccddeeff", "fall") def test_parse_discovery_topic_returns_none_for_non_discovery() -> None: assert parse_discovery_topic("homeassistant/binary_sensor/foo/state") is None assert parse_discovery_topic("ruview/aabbccddeeff/raw/edge_vitals") is None assert parse_discovery_topic("") is None # ─── Payload parsing ───────────────────────────────────────────────── def test_parse_discovery_payload_from_dict() -> None: out = parse_discovery_payload(_PRESENCE_TOPIC, _PRESENCE_BODY) assert out is not None assert out.entity_kind == "binary_sensor" assert out.node_id == "aabbccddeeff" assert out.object_id == "presence" assert out.payload["device_class"] == "occupancy" def test_parse_discovery_payload_from_bytes() -> None: raw = json.dumps(_PRESENCE_BODY).encode("utf-8") out = parse_discovery_payload(_PRESENCE_TOPIC, raw) assert out is not None assert out.payload["unique_id"] == "wifi_densepose_aabbccddeeff_presence" def test_parse_discovery_payload_from_string() -> None: raw = json.dumps(_PRESENCE_BODY) out = parse_discovery_payload(_PRESENCE_TOPIC, raw) assert out is not None assert out.entity_kind == "binary_sensor" def test_parse_discovery_payload_rejects_malformed_json() -> None: assert parse_discovery_payload(_PRESENCE_TOPIC, "{ broken: json") is None def test_parse_discovery_payload_rejects_non_object_root() -> None: assert parse_discovery_payload(_PRESENCE_TOPIC, "[1, 2, 3]") is None def test_parse_discovery_payload_returns_none_for_non_discovery_topic() -> None: assert parse_discovery_payload( "ruview/aabbccddeeff/raw/edge_vitals", _PRESENCE_BODY, ) is None # ─── HaEntity projection ───────────────────────────────────────────── def test_ha_entity_from_payload_extracts_fields() -> None: p = HaDiscoveryPayload( entity_kind="sensor", node_id="aabbccddeeff", object_id="heart_rate", payload=_HEART_RATE_BODY, ) e = HaEntity.from_payload(p) assert e.entity_kind == "sensor" assert e.unique_id == "wifi_densepose_aabbccddeeff_heart_rate" assert e.unit_of_measurement == "bpm" assert e.icon == "mdi:heart-pulse" assert e.json_attributes_topic == _HEART_RATE_BODY["json_attributes_topic"] def test_ha_entity_handles_missing_optional_fields() -> None: p = HaDiscoveryPayload( entity_kind="event", node_id="aabbccddeeff", object_id="bed_exit", payload={"unique_id": "wifi_densepose_aabbccddeeff_bed_exit"}, ) e = HaEntity.from_payload(p) assert e.unique_id == "wifi_densepose_aabbccddeeff_bed_exit" assert e.device_class == "" assert e.unit_of_measurement == "" # ─── HABlueprintHelper aggregation ─────────────────────────────────── def _populated_helper() -> HABlueprintHelper: h = HABlueprintHelper() h.add_payload(_PRESENCE_TOPIC, _PRESENCE_BODY) h.add_payload(_HEART_RATE_TOPIC, _HEART_RATE_BODY) # Same fields but a different node h.add_payload( "homeassistant/binary_sensor/wifi_densepose_ff00ff00ff00/presence/config", {**_PRESENCE_BODY, "unique_id": "wifi_densepose_ff00ff00ff00_presence"}, ) return h def test_helper_starts_empty() -> None: h = HABlueprintHelper() assert len(h) == 0 assert h.nodes() == [] assert h.all_payloads() == [] def test_helper_aggregates_multiple_payloads() -> None: h = _populated_helper() assert len(h) == 3 assert h.nodes() == ["aabbccddeeff", "ff00ff00ff00"] def test_helper_entities_for_node() -> None: h = _populated_helper() entities = h.entities_for_node("aabbccddeeff") object_ids = sorted(e.object_id for e in entities) assert object_ids == ["heart_rate", "presence"] def test_helper_by_device_class() -> None: h = _populated_helper() occupancy_entities = h.by_device_class("occupancy") assert len(occupancy_entities) == 2 # presence on both nodes assert {e.node_id for e in occupancy_entities} == {"aabbccddeeff", "ff00ff00ff00"} def test_helper_remove() -> None: h = _populated_helper() assert h.remove("aabbccddeeff", "binary_sensor", "presence") is True assert h.remove("aabbccddeeff", "binary_sensor", "presence") is False # no-op assert len(h) == 2 def test_helper_rejects_non_discovery_topics() -> None: h = HABlueprintHelper() ok = h.add_payload("ruview/aabbccddeeff/raw/edge_vitals", _PRESENCE_BODY) assert ok is False assert len(h) == 0 def test_helper_in_operator() -> None: h = _populated_helper() assert ("aabbccddeeff", "binary_sensor", "presence") in h assert ("nonexistent", "binary_sensor", "presence") not in h