Don't crash entire Matter integration setup when one node is failing (#126491)
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>pull/126743/head
parent
a5b556b21b
commit
18766905f4
|
@ -56,10 +56,6 @@ class MatterAdapter:
|
|||
"""Set up all existing nodes and subscribe to new nodes."""
|
||||
initialized_nodes: set[int] = set()
|
||||
for node in self.matter_client.get_nodes():
|
||||
if not node.available:
|
||||
# ignore un-initialized nodes at startup
|
||||
# catch them later when they become available.
|
||||
continue
|
||||
initialized_nodes.add(node.node_id)
|
||||
self._setup_node(node)
|
||||
|
||||
|
@ -143,10 +139,18 @@ class MatterAdapter:
|
|||
def _setup_node(self, node: MatterNode) -> None:
|
||||
"""Set up an node."""
|
||||
LOGGER.debug("Setting up entities for node %s", node.node_id)
|
||||
|
||||
for endpoint in node.endpoints.values():
|
||||
# Node endpoints are translated into HA devices
|
||||
self._setup_endpoint(endpoint)
|
||||
try:
|
||||
for endpoint in node.endpoints.values():
|
||||
# Node endpoints are translated into HA devices
|
||||
self._setup_endpoint(endpoint)
|
||||
except Exception as err: # noqa: BLE001
|
||||
# We don't want to crash the whole setup when a single node fails to setup
|
||||
# for whatever reason, so we catch all exceptions here.
|
||||
LOGGER.exception(
|
||||
"Error setting up node %s: %s",
|
||||
node.node_id,
|
||||
err,
|
||||
)
|
||||
|
||||
def _create_device_registry(
|
||||
self,
|
||||
|
|
|
@ -34,15 +34,7 @@ async def setup_integration_with_node_fixture(
|
|||
override_attributes: dict[str, Any] | None = None,
|
||||
) -> MatterNode:
|
||||
"""Set up Matter integration with fixture as node."""
|
||||
node_data = load_and_parse_node_fixture(node_fixture)
|
||||
if override_attributes:
|
||||
node_data["attributes"].update(override_attributes)
|
||||
node = MatterNode(
|
||||
dataclass_from_dict(
|
||||
MatterNodeData,
|
||||
node_data,
|
||||
)
|
||||
)
|
||||
node = create_node_from_fixture(node_fixture, override_attributes)
|
||||
client.get_nodes.return_value = [node]
|
||||
client.get_node.return_value = node
|
||||
config_entry = MockConfigEntry(
|
||||
|
@ -56,6 +48,21 @@ async def setup_integration_with_node_fixture(
|
|||
return node
|
||||
|
||||
|
||||
def create_node_from_fixture(
|
||||
node_fixture: str, override_attributes: dict[str, Any] | None = None
|
||||
) -> MatterNode:
|
||||
"""Create a node from a fixture."""
|
||||
node_data = load_and_parse_node_fixture(node_fixture)
|
||||
if override_attributes:
|
||||
node_data["attributes"].update(override_attributes)
|
||||
return MatterNode(
|
||||
dataclass_from_dict(
|
||||
MatterNodeData,
|
||||
node_data,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def set_node_attribute(
|
||||
node: MatterNode,
|
||||
endpoint: int,
|
||||
|
|
|
@ -4,9 +4,7 @@ from __future__ import annotations
|
|||
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from matter_server.client.models.node import MatterNode
|
||||
from matter_server.common.helpers.util import dataclass_from_dict
|
||||
from matter_server.common.models import EventType, MatterNodeData
|
||||
from matter_server.common.models import EventType
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.matter.adapter import get_clean_name
|
||||
|
@ -14,7 +12,9 @@ from homeassistant.components.matter.const import DOMAIN
|
|||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
|
||||
from .common import load_and_parse_node_fixture, setup_integration_with_node_fixture
|
||||
from .common import create_node_from_fixture, setup_integration_with_node_fixture
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
# This tests needs to be adjusted to remove lingering tasks
|
||||
|
@ -156,13 +156,7 @@ async def test_node_added_subscription(
|
|||
)
|
||||
|
||||
node_added_callback = matter_client.subscribe_events.call_args.kwargs["callback"]
|
||||
node_data = load_and_parse_node_fixture("onoff-light")
|
||||
node = MatterNode(
|
||||
dataclass_from_dict(
|
||||
MatterNodeData,
|
||||
node_data,
|
||||
)
|
||||
)
|
||||
node = create_node_from_fixture("onoff-light")
|
||||
|
||||
entity_state = hass.states.get("light.mock_onoff_light_light")
|
||||
assert not entity_state
|
||||
|
@ -218,3 +212,27 @@ async def test_get_clean_name_() -> None:
|
|||
assert get_clean_name("") is None
|
||||
assert get_clean_name("Mock device") == "Mock device"
|
||||
assert get_clean_name("Mock device \x00") == "Mock device"
|
||||
|
||||
|
||||
async def test_bad_node_not_crash_integration(
|
||||
hass: HomeAssistant,
|
||||
matter_client: MagicMock,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""Test that a bad node does not crash the integration."""
|
||||
good_node = create_node_from_fixture("onoff-light")
|
||||
bad_node = create_node_from_fixture("onoff-light")
|
||||
del bad_node.endpoints[0].node
|
||||
matter_client.get_nodes.return_value = [good_node, bad_node]
|
||||
config_entry = MockConfigEntry(
|
||||
domain="matter", data={"url": "http://mock-matter-server-url"}
|
||||
)
|
||||
config_entry.add_to_hass(hass)
|
||||
|
||||
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert matter_client.get_nodes.call_count == 1
|
||||
assert hass.states.get("light.mock_onoff_light_light") is not None
|
||||
assert len(hass.states.async_all("light")) == 1
|
||||
assert "Error setting up node" in caplog.text
|
||||
|
|
|
@ -12,10 +12,7 @@ from matter_server.client.exceptions import (
|
|||
ServerVersionTooNew,
|
||||
ServerVersionTooOld,
|
||||
)
|
||||
from matter_server.client.models.node import MatterNode
|
||||
from matter_server.common.errors import MatterError
|
||||
from matter_server.common.helpers.util import dataclass_from_dict
|
||||
from matter_server.common.models import MatterNodeData
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.hassio import HassioAPIError
|
||||
|
@ -30,7 +27,7 @@ from homeassistant.helpers import (
|
|||
)
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from .common import load_and_parse_node_fixture, setup_integration_with_node_fixture
|
||||
from .common import create_node_from_fixture, setup_integration_with_node_fixture
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
from tests.typing import WebSocketGenerator
|
||||
|
@ -57,13 +54,7 @@ async def test_entry_setup_unload(
|
|||
matter_client: MagicMock,
|
||||
) -> None:
|
||||
"""Test the integration set up and unload."""
|
||||
node_data = load_and_parse_node_fixture("onoff-light")
|
||||
node = MatterNode(
|
||||
dataclass_from_dict(
|
||||
MatterNodeData,
|
||||
node_data,
|
||||
)
|
||||
)
|
||||
node = create_node_from_fixture("onoff-light")
|
||||
matter_client.get_nodes.return_value = [node]
|
||||
matter_client.get_node.return_value = node
|
||||
entry = MockConfigEntry(domain="matter", data={"url": "ws://localhost:5580/ws"})
|
||||
|
|
Loading…
Reference in New Issue