diff --git a/homeassistant/components/mysensors/__init__.py b/homeassistant/components/mysensors/__init__.py index 0bf4bb80a18..4d3c3046a89 100644 --- a/homeassistant/components/mysensors/__init__.py +++ b/homeassistant/components/mysensors/__init__.py @@ -15,6 +15,7 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_OPTIMISTIC, Platform from homeassistant.core import HomeAssistant, callback import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.device_registry import DeviceEntry from homeassistant.helpers.discovery import async_load_platform from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.typing import ConfigType @@ -264,6 +265,23 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: return True +async def async_remove_config_entry_device( + hass: HomeAssistant, config_entry: ConfigEntry, device_entry: DeviceEntry +) -> bool: + """Remove a MySensors config entry from a device.""" + gateway: BaseAsyncGateway = hass.data[DOMAIN][MYSENSORS_GATEWAYS][ + config_entry.entry_id + ] + device_id = next( + device_id for domain, device_id in device_entry.identifiers if domain == DOMAIN + ) + node_id = int(device_id.partition("-")[2]) + gateway.sensors.pop(node_id, None) + gateway.tasks.persistence.need_save = True + + return True + + @callback def setup_mysensors_platform( hass: HomeAssistant, diff --git a/homeassistant/components/mysensors/device.py b/homeassistant/components/mysensors/device.py index 0ced1520758..b1e562e878c 100644 --- a/homeassistant/components/mysensors/device.py +++ b/homeassistant/components/mysensors/device.py @@ -65,10 +65,6 @@ class MySensorsDevice: """ return self.gateway_id, self.node_id, self.child_id, self.value_type - @property - def _logger(self) -> logging.Logger: - return logging.getLogger(f"{__name__}.{self.name}") - async def async_will_remove_from_hass(self) -> None: """Remove this entity from home assistant.""" for platform in PLATFORM_TYPES: @@ -77,9 +73,7 @@ class MySensorsDevice: platform_dict = self.hass.data[DOMAIN][platform_str] if self.dev_id in platform_dict: del platform_dict[self.dev_id] - self._logger.debug( - "deleted %s from platform %s", self.dev_id, platform - ) + _LOGGER.debug("Deleted %s from platform %s", self.dev_id, platform) @property def _node(self) -> Sensor: diff --git a/tests/components/mysensors/conftest.py b/tests/components/mysensors/conftest.py index 6dd7add37e6..fe98b3f7e0a 100644 --- a/tests/components/mysensors/conftest.py +++ b/tests/components/mysensors/conftest.py @@ -6,6 +6,7 @@ import json from typing import Any from unittest.mock import MagicMock, patch +from mysensors import BaseSyncGateway from mysensors.persistence import MySensorsJSONDecoder from mysensors.sensor import Sensor import pytest @@ -142,6 +143,12 @@ async def integration( yield config_entry, receive_message +@pytest.fixture(name="gateway") +def gateway_fixture(transport, integration) -> BaseSyncGateway: + """Return a setup gateway.""" + return transport.call_args[0][0] + + def load_nodes_state(fixture_path: str) -> dict: """Load mysensors nodes fixture.""" return json.loads(load_fixture(fixture_path), cls=MySensorsJSONDecoder) diff --git a/tests/components/mysensors/test_init.py b/tests/components/mysensors/test_init.py index 7c83334d8f3..6f97c312ec0 100644 --- a/tests/components/mysensors/test_init.py +++ b/tests/components/mysensors/test_init.py @@ -1,9 +1,13 @@ """Test function in __init__.py.""" from __future__ import annotations -from typing import Any +from collections.abc import Callable +from typing import Any, Awaitable from unittest.mock import patch +from aiohttp import ClientWebSocketResponse +from mysensors import BaseSyncGateway +from mysensors.sensor import Sensor import pytest from homeassistant.components.mysensors import ( @@ -27,9 +31,12 @@ from homeassistant.components.mysensors.const import ( CONF_TOPIC_OUT_PREFIX, ) from homeassistant.core import HomeAssistant +from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers.typing import ConfigType from homeassistant.setup import async_setup_component +from tests.common import MockConfigEntry + @pytest.mark.parametrize( "config, expected_calls, expected_to_succeed, expected_config_entry_data", @@ -347,3 +354,51 @@ async def test_import( persistence_path = config_entry_data.pop(CONF_PERSISTENCE_FILE) assert persistence_path == expected_persistence_path assert config_entry_data == expected_config_entry_data[idx] + + +async def test_remove_config_entry_device( + hass: HomeAssistant, + gps_sensor: Sensor, + integration: tuple[MockConfigEntry, Callable[[str], None]], + gateway: BaseSyncGateway, + hass_ws_client: Callable[[HomeAssistant], Awaitable[ClientWebSocketResponse]], +) -> None: + """Test that a device can be removed ok.""" + entity_id = "sensor.gps_sensor_1_1" + node_id = 1 + config_entry, _ = integration + assert await async_setup_component(hass, "config", {}) + await hass.async_block_till_done() + + device_registry = dr.async_get(hass) + device_entry = device_registry.async_get_device( + identifiers={(DOMAIN, f"{config_entry.entry_id}-{node_id}")} + ) + entity_registry = er.async_get(hass) + state = hass.states.get(entity_id) + + assert gateway.sensors + assert gateway.sensors[node_id] + assert device_entry + assert state + + client = await hass_ws_client(hass) + await client.send_json( + { + "id": 5, + "type": "config/device_registry/remove_config_entry", + "config_entry_id": config_entry.entry_id, + "device_id": device_entry.id, + } + ) + response = await client.receive_json() + assert response["success"] + await hass.async_block_till_done() + + assert node_id not in gateway.sensors + assert gateway.tasks.persistence.need_save is True + assert not device_registry.async_get_device( + identifiers={(DOMAIN, f"{config_entry.entry_id}-1")} + ) + assert not entity_registry.async_get(entity_id) + assert not hass.states.get(entity_id)