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

144 lines
4.0 KiB
Python

"""Tests for vendor-neutral Bridge API v1 models."""
from typing import Any
import pytest
from custom_components.mb_secure.models import (
BridgeDataError,
BridgeInfo,
BridgeSnapshot,
DomainEvent,
)
def test_bridge_info_accepts_stable_bridge_id() -> None:
"""Bridge metadata accepts the installation identifier used by config flow."""
info = BridgeInfo.from_dict(
{
"bridge_id": "bridge-1",
"bridge_version": "0.1.0",
"api_version": 1,
"connected": False,
"capabilities": ["snapshot", "events"],
"future_field": "ignored",
}
)
assert info.bridge_id == "bridge-1"
def test_bridge_info_tolerates_legacy_v1_without_bridge_id() -> None:
"""A newer client remains compatible with the original API v1 response."""
info = BridgeInfo.from_dict(
{
"bridge_version": "0.1.0",
"api_version": 1,
"connected": False,
"capabilities": [],
}
)
assert info.bridge_id is None
def snapshot_payload() -> dict[str, Any]:
"""Return a minimal neutral snapshot."""
return {
"revision": 4,
"controllers": [
{
"id": "controller-1",
"name": "Controller",
"state": "online",
"capabilities": [],
"future_field": True,
}
],
"areas": [
{
"id": "area-1",
"controller_id": "controller-1",
"name": "Area",
"state": "disarmed",
"capabilities": ["arm_away", "disarm"],
}
],
"points": [],
"modules": [],
"outputs": [],
"future_collection": [],
}
def test_snapshot_tolerates_additive_fields() -> None:
"""API v1 parsers ignore fields added in compatible updates."""
snapshot = BridgeSnapshot.from_dict(snapshot_payload())
assert snapshot.controllers["controller-1"].name == "Controller"
assert snapshot.revision == 4
def test_snapshot_accepts_empty_display_name_allowed_by_v1() -> None:
"""Display names are not identifiers and may be empty in API v1."""
payload = snapshot_payload()
payload["controllers"][0]["name"] = ""
snapshot = BridgeSnapshot.from_dict(payload)
assert snapshot.controllers["controller-1"].name == ""
def test_snapshot_rejects_duplicate_stable_ids() -> None:
"""Duplicate technical IDs cannot create ambiguous entities."""
payload = snapshot_payload()
payload["controllers"].append(dict(payload["controllers"][0]))
with pytest.raises(BridgeDataError):
BridgeSnapshot.from_dict(payload)
def test_snapshot_rejects_unknown_parent_reference() -> None:
"""Objects cannot reference a controller absent from the snapshot."""
payload = snapshot_payload()
payload["areas"][0]["controller_id"] = "missing-controller"
with pytest.raises(BridgeDataError):
BridgeSnapshot.from_dict(payload)
def test_contiguous_state_event_is_applied() -> None:
"""A contiguous state event updates one object and the revision."""
snapshot = BridgeSnapshot.from_dict(snapshot_payload())
event = DomainEvent.from_dict(
{
"revision": 5,
"event_type": "state_changed",
"object_type": "area",
"object_id": "area-1",
"state": "armed_away",
}
)
updated = snapshot.apply_event(event)
assert updated is not None
assert updated.revision == 5
assert updated.areas["area-1"].state == "armed_away"
def test_event_gap_requires_snapshot_reconciliation() -> None:
"""A missing event is not applied over an incomplete state."""
snapshot = BridgeSnapshot.from_dict(snapshot_payload())
event = DomainEvent.from_dict(
{
"revision": 6,
"event_type": "state_changed",
"object_type": "area",
"object_id": "area-1",
"state": "armed_away",
}
)
assert snapshot.apply_event(event) is None