Files
home-assistant-mb-secure/tests/test_entities.py
T

193 lines
6.1 KiB
Python

"""Tests for MB-Secure devices and entities."""
from copy import deepcopy
from unittest.mock import patch
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers import entity_registry as er
from pytest_homeassistant_custom_component.common import MockConfigEntry
from custom_components.mb_secure.const import CONF_TOKEN, DOMAIN
from custom_components.mb_secure.coordinator import MBSecureCoordinator
from custom_components.mb_secure.models import BridgeInfo, BridgeSnapshot
ENTRY_DATA = {
"host": "bridge.local",
"port": 8099,
CONF_TOKEN: "x" * 32,
}
def snapshot_payload() -> dict[str, object]:
"""Return a capability-complete normalized test snapshot."""
return {
"revision": 1,
"controllers": [
{
"id": "controller-1",
"name": "Main Controller",
"state": "online",
"capabilities": ["snapshot"],
}
],
"areas": [
{
"id": "area-1",
"controller_id": "controller-1",
"name": "Ground Floor",
"state": "disarmed",
"capabilities": ["arm_away", "disarm"],
}
],
"points": [
{
"id": "point-1",
"controller_id": "controller-1",
"area_id": "area-1",
"module_id": None,
"name": "Front Door",
"state": "closed",
"capabilities": ["binary_state"],
},
{
"id": "point-without-capability",
"controller_id": "controller-1",
"area_id": "area-1",
"module_id": None,
"name": "Unsupported Point",
"state": "unknown",
"capabilities": [],
},
],
"modules": [
{
"id": "module-1",
"controller_id": "controller-1",
"name": "Expansion Module",
"state": "online",
"capabilities": ["status"],
}
],
"outputs": [
{
"id": "output-1",
"controller_id": "controller-1",
"module_id": "module-1",
"name": "Indicator",
"state": "off",
"capabilities": ["set"],
}
],
}
async def setup_entry(hass: HomeAssistant) -> MockConfigEntry:
"""Set up an entry against mocked neutral Bridge API responses."""
entry = MockConfigEntry(
domain=DOMAIN,
data=ENTRY_DATA,
unique_id="bridge-installation-1",
)
entry.add_to_hass(hass)
info = BridgeInfo(
bridge_id="bridge-installation-1",
bridge_version="0.1.0",
api_version=1,
connected=True,
capabilities=frozenset({"snapshot", "events", "commands"}),
)
snapshot = BridgeSnapshot.from_dict(snapshot_payload())
with (
patch(
"custom_components.mb_secure.api.BridgeClient.async_get_info",
return_value=info,
),
patch(
"custom_components.mb_secure.api.BridgeClient.async_get_snapshot",
return_value=snapshot,
),
patch.object(MBSecureCoordinator, "async_start_event_listener"),
):
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
return entry
async def test_setup_creates_capability_gated_entities_and_hierarchy(
hass: HomeAssistant,
) -> None:
"""Only reported capabilities create stable entities and linked devices."""
entry = await setup_entry(hass)
registry = er.async_get(hass)
unique_ids = {
entity.unique_id
for entity in er.async_entries_for_config_entry(registry, entry.entry_id)
}
assert unique_ids == {
"bridge-installation-1:areas:area-1",
"bridge-installation-1:controllers:controller-1",
"bridge-installation-1:modules:module-1",
"bridge-installation-1:outputs:output-1",
"bridge-installation-1:points:point-1",
}
device_registry = dr.async_get(hass)
controller = device_registry.async_get_device_by_identifier(
(DOMAIN, "bridge-installation-1:controller:controller-1"),
entry.entry_id,
)
area = device_registry.async_get_device_by_identifier(
(DOMAIN, "bridge-installation-1:area:area-1"),
entry.entry_id,
)
module = device_registry.async_get_device_by_identifier(
(DOMAIN, "bridge-installation-1:module:module-1"),
entry.entry_id,
)
assert controller is not None
assert area is not None and area.via_device_id == controller.id
assert module is not None and module.via_device_id == controller.id
async def test_snapshot_topology_changes_add_and_remove_entities(
hass: HomeAssistant,
) -> None:
"""Snapshot reconciliation updates the runtime entity topology."""
entry = await setup_entry(hass)
coordinator = entry.runtime_data
registry = er.async_get(hass)
old_entry = registry.async_get_entity_id(
"binary_sensor",
DOMAIN,
"bridge-installation-1:points:point-1",
)
assert old_entry is not None
payload = deepcopy(snapshot_payload())
payload["revision"] = 2
points = payload["points"]
assert isinstance(points, list)
points.pop(0)
points.append(
{
"id": "point-2",
"controller_id": "controller-1",
"area_id": "area-1",
"module_id": None,
"name": "Back Door",
"state": "open",
"capabilities": ["binary_state"],
}
)
coordinator.async_set_updated_data(BridgeSnapshot.from_dict(payload))
await hass.async_block_till_done()
unique_ids = {
entity.unique_id
for entity in er.async_entries_for_config_entry(registry, entry.entry_id)
}
assert "bridge-installation-1:points:point-1" not in unique_ids
assert "bridge-installation-1:points:point-2" in unique_ids
assert hass.states.get(old_entry) is None