Implement bridge-backed Home Assistant integration
This commit is contained in:
@@ -0,0 +1,192 @@
|
||||
"""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
|
||||
Reference in New Issue
Block a user