"""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