1
0
mirror of https://github.com/home-assistant/core.git synced 2026-04-02 08:26:41 +01:00

Fix invalid device registry identifiers in eafm (#164654)

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
epenet
2026-03-03 11:02:59 +01:00
committed by GitHub
parent a76b63912d
commit 0f9fdfe2de
5 changed files with 173 additions and 5 deletions

View File

@@ -2,14 +2,39 @@
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr
from .const import DOMAIN
from .coordinator import EafmConfigEntry, EafmCoordinator
PLATFORMS = [Platform.SENSOR]
def _fix_device_registry_identifiers(
hass: HomeAssistant, entry: EafmConfigEntry
) -> None:
"""Fix invalid identifiers in device registry.
Added in 2026.4, can be removed in 2026.10 or later.
"""
device_registry = dr.async_get(hass)
for device_entry in dr.async_entries_for_config_entry(
device_registry, entry.entry_id
):
old_identifier = (DOMAIN, "measure-id", entry.data["station"])
if old_identifier not in device_entry.identifiers: # type: ignore[comparison-overlap]
continue
new_identifiers = device_entry.identifiers.copy()
new_identifiers.discard(old_identifier) # type: ignore[arg-type]
new_identifiers.add((DOMAIN, entry.data["station"]))
device_registry.async_update_device(
device_entry.id, new_identifiers=new_identifiers
)
async def async_setup_entry(hass: HomeAssistant, entry: EafmConfigEntry) -> bool:
"""Set up flood monitoring sensors for this config entry."""
_fix_device_registry_identifiers(hass, entry)
coordinator = EafmCoordinator(hass, entry=entry)
await coordinator.async_config_entry_first_refresh()
entry.runtime_data = coordinator

View File

@@ -94,11 +94,11 @@ class Measurement(CoordinatorEntity, SensorEntity):
return self.coordinator.data["measures"][self.key]["parameterName"]
@property
def device_info(self):
def device_info(self) -> DeviceInfo:
"""Return the device info."""
return DeviceInfo(
entry_type=DeviceEntryType.SERVICE,
identifiers={(DOMAIN, "measure-id", self.station_id)},
identifiers={(DOMAIN, self.station_id)},
manufacturer="https://environment.data.gov.uk/",
model=self.parameter_name,
name=f"{self.station_name} {self.parameter_name} {self.qualifier}",

View File

@@ -1,19 +1,64 @@
"""eafm fixtures."""
from unittest.mock import patch
from collections.abc import Generator
from typing import Any
from unittest.mock import AsyncMock, patch
import pytest
from homeassistant.components.eafm.const import DOMAIN
from homeassistant.core import HomeAssistant
from tests.common import MockConfigEntry
@pytest.fixture
def mock_get_stations():
def mock_get_stations() -> Generator[AsyncMock]:
"""Mock aioeafm.get_stations."""
with patch("homeassistant.components.eafm.config_flow.get_stations") as patched:
patched.return_value = [
{"label": "My station", "stationReference": "L12345", "RLOIid": "R12345"}
]
yield patched
@pytest.fixture
def mock_get_station():
def mock_get_station(initial_value: dict[str, Any]) -> Generator[AsyncMock]:
"""Mock aioeafm.get_station."""
with patch("homeassistant.components.eafm.coordinator.get_station") as patched:
patched.return_value = initial_value
yield patched
@pytest.fixture
def initial_value() -> dict[str, Any]:
"""Mock aioeafm.get_station."""
return {
"label": "My station",
"measures": [
{
"@id": "really-long-unique-id",
"label": "York Viking Recorder - level-stage-i-15_min----",
"qualifier": "Stage",
"parameterName": "Water Level",
"latestReading": {"value": 5},
"stationReference": "L1234",
"unit": "http://qudt.org/1.1/vocab/unit#Meter",
"unitName": "m",
}
],
}
@pytest.fixture
def mock_config_entry(hass: HomeAssistant) -> MockConfigEntry:
"""Create a dummy config entry for testing."""
entry = MockConfigEntry(
version=1,
domain=DOMAIN,
entry_id="VikingRecorder1234",
data={"station": "L1234"},
title="Viking Recorder",
)
entry.add_to_hass(hass)
return entry

View File

@@ -0,0 +1,34 @@
# serializer version: 1
# name: test_load_unload_entry
list([
DeviceRegistryEntrySnapshot({
'area_id': None,
'config_entries': <ANY>,
'config_entries_subentries': <ANY>,
'configuration_url': None,
'connections': set({
}),
'disabled_by': None,
'entry_type': <DeviceEntryType.SERVICE: 'service'>,
'hw_version': None,
'id': <ANY>,
'identifiers': set({
tuple(
'eafm',
'L1234',
),
}),
'labels': set({
}),
'manufacturer': 'https://environment.data.gov.uk/',
'model': 'Water Level',
'model_id': None,
'name': 'My station Water Level Stage',
'name_by_user': None,
'primary_config_entry': <ANY>,
'serial_number': None,
'sw_version': None,
'via_device_id': None,
}),
])
# ---

View File

@@ -0,0 +1,64 @@
"""Tests for initialization."""
import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.components.eafm.const import DOMAIN
from homeassistant.config_entries import ConfigEntryState
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr
from tests.common import MockConfigEntry
@pytest.mark.usefixtures("mock_get_station")
async def test_load_unload_entry(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
device_registry: dr.DeviceRegistry,
snapshot: SnapshotAssertion,
) -> None:
"""Test being able to load and unload an entry."""
assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
assert mock_config_entry.state is ConfigEntryState.LOADED
await hass.async_block_till_done()
assert (
dr.async_entries_for_config_entry(device_registry, mock_config_entry.entry_id)
== snapshot
)
await hass.config_entries.async_unload(mock_config_entry.entry_id)
@pytest.mark.usefixtures("mock_get_station")
async def test_update_device_identifiers(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
device_registry: dr.DeviceRegistry,
) -> None:
"""Test being able to update device identifiers."""
device_entry = device_registry.async_get_or_create(
config_entry_id=mock_config_entry.entry_id,
identifiers={(DOMAIN, "measure-id", "L1234")},
)
entries = dr.async_entries_for_config_entry(
device_registry, mock_config_entry.entry_id
)
assert len(entries) == 1
device_entry = entries[0]
assert (DOMAIN, "measure-id", "L1234") in device_entry.identifiers
assert (DOMAIN, "L1234") not in device_entry.identifiers
assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
assert mock_config_entry.state is ConfigEntryState.LOADED
await hass.async_block_till_done()
entries = dr.async_entries_for_config_entry(
device_registry, mock_config_entry.entry_id
)
assert len(entries) == 1
device_entry = entries[0]
assert (DOMAIN, "measure-id", "L1234") not in device_entry.identifiers
assert (DOMAIN, "L1234") in device_entry.identifiers