mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 11:17:21 +00:00
Don't crash entire Matter integration setup when one node is failing (#126491)
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
This commit is contained in:
parent
a5b556b21b
commit
18766905f4
@ -56,10 +56,6 @@ class MatterAdapter:
|
|||||||
"""Set up all existing nodes and subscribe to new nodes."""
|
"""Set up all existing nodes and subscribe to new nodes."""
|
||||||
initialized_nodes: set[int] = set()
|
initialized_nodes: set[int] = set()
|
||||||
for node in self.matter_client.get_nodes():
|
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)
|
initialized_nodes.add(node.node_id)
|
||||||
self._setup_node(node)
|
self._setup_node(node)
|
||||||
|
|
||||||
@ -143,10 +139,18 @@ class MatterAdapter:
|
|||||||
def _setup_node(self, node: MatterNode) -> None:
|
def _setup_node(self, node: MatterNode) -> None:
|
||||||
"""Set up an node."""
|
"""Set up an node."""
|
||||||
LOGGER.debug("Setting up entities for node %s", node.node_id)
|
LOGGER.debug("Setting up entities for node %s", node.node_id)
|
||||||
|
try:
|
||||||
for endpoint in node.endpoints.values():
|
for endpoint in node.endpoints.values():
|
||||||
# Node endpoints are translated into HA devices
|
# Node endpoints are translated into HA devices
|
||||||
self._setup_endpoint(endpoint)
|
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(
|
def _create_device_registry(
|
||||||
self,
|
self,
|
||||||
|
@ -34,15 +34,7 @@ async def setup_integration_with_node_fixture(
|
|||||||
override_attributes: dict[str, Any] | None = None,
|
override_attributes: dict[str, Any] | None = None,
|
||||||
) -> MatterNode:
|
) -> MatterNode:
|
||||||
"""Set up Matter integration with fixture as node."""
|
"""Set up Matter integration with fixture as node."""
|
||||||
node_data = load_and_parse_node_fixture(node_fixture)
|
node = create_node_from_fixture(node_fixture, override_attributes)
|
||||||
if override_attributes:
|
|
||||||
node_data["attributes"].update(override_attributes)
|
|
||||||
node = MatterNode(
|
|
||||||
dataclass_from_dict(
|
|
||||||
MatterNodeData,
|
|
||||||
node_data,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
client.get_nodes.return_value = [node]
|
client.get_nodes.return_value = [node]
|
||||||
client.get_node.return_value = node
|
client.get_node.return_value = node
|
||||||
config_entry = MockConfigEntry(
|
config_entry = MockConfigEntry(
|
||||||
@ -56,6 +48,21 @@ async def setup_integration_with_node_fixture(
|
|||||||
return node
|
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(
|
def set_node_attribute(
|
||||||
node: MatterNode,
|
node: MatterNode,
|
||||||
endpoint: int,
|
endpoint: int,
|
||||||
|
@ -4,9 +4,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
from matter_server.client.models.node import MatterNode
|
from matter_server.common.models import EventType
|
||||||
from matter_server.common.helpers.util import dataclass_from_dict
|
|
||||||
from matter_server.common.models import EventType, MatterNodeData
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from homeassistant.components.matter.adapter import get_clean_name
|
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.core import HomeAssistant
|
||||||
from homeassistant.helpers import device_registry as dr
|
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
|
# 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_added_callback = matter_client.subscribe_events.call_args.kwargs["callback"]
|
||||||
node_data = load_and_parse_node_fixture("onoff-light")
|
node = create_node_from_fixture("onoff-light")
|
||||||
node = MatterNode(
|
|
||||||
dataclass_from_dict(
|
|
||||||
MatterNodeData,
|
|
||||||
node_data,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
entity_state = hass.states.get("light.mock_onoff_light_light")
|
entity_state = hass.states.get("light.mock_onoff_light_light")
|
||||||
assert not entity_state
|
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("") is None
|
||||||
assert get_clean_name("Mock device") == "Mock device"
|
assert get_clean_name("Mock device") == "Mock device"
|
||||||
assert get_clean_name("Mock device \x00") == "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,
|
ServerVersionTooNew,
|
||||||
ServerVersionTooOld,
|
ServerVersionTooOld,
|
||||||
)
|
)
|
||||||
from matter_server.client.models.node import MatterNode
|
|
||||||
from matter_server.common.errors import MatterError
|
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
|
import pytest
|
||||||
|
|
||||||
from homeassistant.components.hassio import HassioAPIError
|
from homeassistant.components.hassio import HassioAPIError
|
||||||
@ -30,7 +27,7 @@ from homeassistant.helpers import (
|
|||||||
)
|
)
|
||||||
from homeassistant.setup import async_setup_component
|
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.common import MockConfigEntry
|
||||||
from tests.typing import WebSocketGenerator
|
from tests.typing import WebSocketGenerator
|
||||||
@ -57,13 +54,7 @@ async def test_entry_setup_unload(
|
|||||||
matter_client: MagicMock,
|
matter_client: MagicMock,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test the integration set up and unload."""
|
"""Test the integration set up and unload."""
|
||||||
node_data = load_and_parse_node_fixture("onoff-light")
|
node = create_node_from_fixture("onoff-light")
|
||||||
node = MatterNode(
|
|
||||||
dataclass_from_dict(
|
|
||||||
MatterNodeData,
|
|
||||||
node_data,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
matter_client.get_nodes.return_value = [node]
|
matter_client.get_nodes.return_value = [node]
|
||||||
matter_client.get_node.return_value = node
|
matter_client.get_node.return_value = node
|
||||||
entry = MockConfigEntry(domain="matter", data={"url": "ws://localhost:5580/ws"})
|
entry = MockConfigEntry(domain="matter", data={"url": "ws://localhost:5580/ws"})
|
||||||
|
Loading…
x
Reference in New Issue
Block a user