Add created_at/modified_at to entity registry (#122444)

This commit is contained in:
Robert Resch 2024-07-23 13:12:29 +02:00 committed by GitHub
parent 8d14095cb9
commit 0d765a27c9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 277 additions and 83 deletions

View File

@ -48,6 +48,7 @@ from homeassistant.core import (
from homeassistant.exceptions import MaxLengthExceeded from homeassistant.exceptions import MaxLengthExceeded
from homeassistant.loader import async_suggest_report_issue from homeassistant.loader import async_suggest_report_issue
from homeassistant.util import slugify, uuid as uuid_util from homeassistant.util import slugify, uuid as uuid_util
from homeassistant.util.dt import utc_from_timestamp, utcnow
from homeassistant.util.event_type import EventType from homeassistant.util.event_type import EventType
from homeassistant.util.hass_dict import HassKey from homeassistant.util.hass_dict import HassKey
from homeassistant.util.json import format_unserializable_data from homeassistant.util.json import format_unserializable_data
@ -74,7 +75,7 @@ EVENT_ENTITY_REGISTRY_UPDATED: EventType[EventEntityRegistryUpdatedData] = Event
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
STORAGE_VERSION_MAJOR = 1 STORAGE_VERSION_MAJOR = 1
STORAGE_VERSION_MINOR = 14 STORAGE_VERSION_MINOR = 15
STORAGE_KEY = "core.entity_registry" STORAGE_KEY = "core.entity_registry"
CLEANUP_INTERVAL = 3600 * 24 CLEANUP_INTERVAL = 3600 * 24
@ -174,6 +175,7 @@ class RegistryEntry:
categories: dict[str, str] = attr.ib(factory=dict) categories: dict[str, str] = attr.ib(factory=dict)
capabilities: Mapping[str, Any] | None = attr.ib(default=None) capabilities: Mapping[str, Any] | None = attr.ib(default=None)
config_entry_id: str | None = attr.ib(default=None) config_entry_id: str | None = attr.ib(default=None)
created_at: datetime = attr.ib(factory=utcnow)
device_class: str | None = attr.ib(default=None) device_class: str | None = attr.ib(default=None)
device_id: str | None = attr.ib(default=None) device_id: str | None = attr.ib(default=None)
domain: str = attr.ib(init=False, repr=False) domain: str = attr.ib(init=False, repr=False)
@ -187,6 +189,7 @@ class RegistryEntry:
) )
has_entity_name: bool = attr.ib(default=False) has_entity_name: bool = attr.ib(default=False)
labels: set[str] = attr.ib(factory=set) labels: set[str] = attr.ib(factory=set)
modified_at: datetime = attr.ib(factory=utcnow)
name: str | None = attr.ib(default=None) name: str | None = attr.ib(default=None)
options: ReadOnlyEntityOptionsType = attr.ib( options: ReadOnlyEntityOptionsType = attr.ib(
default=None, converter=_protect_entity_options default=None, converter=_protect_entity_options
@ -271,6 +274,7 @@ class RegistryEntry:
"area_id": self.area_id, "area_id": self.area_id,
"categories": self.categories, "categories": self.categories,
"config_entry_id": self.config_entry_id, "config_entry_id": self.config_entry_id,
"created_at": self.created_at.timestamp(),
"device_id": self.device_id, "device_id": self.device_id,
"disabled_by": self.disabled_by, "disabled_by": self.disabled_by,
"entity_category": self.entity_category, "entity_category": self.entity_category,
@ -280,6 +284,7 @@ class RegistryEntry:
"icon": self.icon, "icon": self.icon,
"id": self.id, "id": self.id,
"labels": list(self.labels), "labels": list(self.labels),
"modified_at": self.modified_at.timestamp(),
"name": self.name, "name": self.name,
"options": self.options, "options": self.options,
"original_name": self.original_name, "original_name": self.original_name,
@ -330,6 +335,7 @@ class RegistryEntry:
"categories": self.categories, "categories": self.categories,
"capabilities": self.capabilities, "capabilities": self.capabilities,
"config_entry_id": self.config_entry_id, "config_entry_id": self.config_entry_id,
"created_at": self.created_at.isoformat(),
"device_class": self.device_class, "device_class": self.device_class,
"device_id": self.device_id, "device_id": self.device_id,
"disabled_by": self.disabled_by, "disabled_by": self.disabled_by,
@ -340,6 +346,7 @@ class RegistryEntry:
"id": self.id, "id": self.id,
"has_entity_name": self.has_entity_name, "has_entity_name": self.has_entity_name,
"labels": list(self.labels), "labels": list(self.labels),
"modified_at": self.modified_at.isoformat(),
"name": self.name, "name": self.name,
"options": self.options, "options": self.options,
"original_device_class": self.original_device_class, "original_device_class": self.original_device_class,
@ -395,6 +402,8 @@ class DeletedRegistryEntry:
domain: str = attr.ib(init=False, repr=False) domain: str = attr.ib(init=False, repr=False)
id: str = attr.ib() id: str = attr.ib()
orphaned_timestamp: float | None = attr.ib() orphaned_timestamp: float | None = attr.ib()
created_at: datetime = attr.ib(factory=utcnow)
modified_at: datetime = attr.ib(factory=utcnow)
@domain.default @domain.default
def _domain_default(self) -> str: def _domain_default(self) -> str:
@ -408,8 +417,10 @@ class DeletedRegistryEntry:
json_bytes( json_bytes(
{ {
"config_entry_id": self.config_entry_id, "config_entry_id": self.config_entry_id,
"created_at": self.created_at.isoformat(),
"entity_id": self.entity_id, "entity_id": self.entity_id,
"id": self.id, "id": self.id,
"modified_at": self.modified_at.isoformat(),
"orphaned_timestamp": self.orphaned_timestamp, "orphaned_timestamp": self.orphaned_timestamp,
"platform": self.platform, "platform": self.platform,
"unique_id": self.unique_id, "unique_id": self.unique_id,
@ -429,88 +440,97 @@ class EntityRegistryStore(storage.Store[dict[str, list[dict[str, Any]]]]):
) -> dict: ) -> dict:
"""Migrate to the new version.""" """Migrate to the new version."""
data = old_data data = old_data
if old_major_version == 1 and old_minor_version < 2: if old_major_version == 1:
# Version 1.2 implements migration and freezes the available keys if old_minor_version < 2:
for entity in data["entities"]: # Version 1.2 implements migration and freezes the available keys
# Populate keys which were introduced before version 1.2 for entity in data["entities"]:
entity.setdefault("area_id", None) # Populate keys which were introduced before version 1.2
entity.setdefault("capabilities", {}) entity.setdefault("area_id", None)
entity.setdefault("config_entry_id", None) entity.setdefault("capabilities", {})
entity.setdefault("device_class", None) entity.setdefault("config_entry_id", None)
entity.setdefault("device_id", None) entity.setdefault("device_class", None)
entity.setdefault("disabled_by", None) entity.setdefault("device_id", None)
entity.setdefault("entity_category", None) entity.setdefault("disabled_by", None)
entity.setdefault("icon", None) entity.setdefault("entity_category", None)
entity.setdefault("name", None) entity.setdefault("icon", None)
entity.setdefault("original_icon", None) entity.setdefault("name", None)
entity.setdefault("original_name", None) entity.setdefault("original_icon", None)
entity.setdefault("supported_features", 0) entity.setdefault("original_name", None)
entity.setdefault("unit_of_measurement", None) entity.setdefault("supported_features", 0)
entity.setdefault("unit_of_measurement", None)
if old_major_version == 1 and old_minor_version < 3: if old_minor_version < 3:
# Version 1.3 adds original_device_class # Version 1.3 adds original_device_class
for entity in data["entities"]: for entity in data["entities"]:
# Move device_class to original_device_class # Move device_class to original_device_class
entity["original_device_class"] = entity["device_class"] entity["original_device_class"] = entity["device_class"]
entity["device_class"] = None entity["device_class"] = None
if old_major_version == 1 and old_minor_version < 4: if old_minor_version < 4:
# Version 1.4 adds id # Version 1.4 adds id
for entity in data["entities"]: for entity in data["entities"]:
entity["id"] = uuid_util.random_uuid_hex() entity["id"] = uuid_util.random_uuid_hex()
if old_major_version == 1 and old_minor_version < 5: if old_minor_version < 5:
# Version 1.5 adds entity options # Version 1.5 adds entity options
for entity in data["entities"]: for entity in data["entities"]:
entity["options"] = {} entity["options"] = {}
if old_major_version == 1 and old_minor_version < 6: if old_minor_version < 6:
# Version 1.6 adds hidden_by # Version 1.6 adds hidden_by
for entity in data["entities"]: for entity in data["entities"]:
entity["hidden_by"] = None entity["hidden_by"] = None
if old_major_version == 1 and old_minor_version < 7: if old_minor_version < 7:
# Version 1.7 adds has_entity_name # Version 1.7 adds has_entity_name
for entity in data["entities"]: for entity in data["entities"]:
entity["has_entity_name"] = False entity["has_entity_name"] = False
if old_major_version == 1 and old_minor_version < 8: if old_minor_version < 8:
# Cleanup after frontend bug which incorrectly updated device_class # Cleanup after frontend bug which incorrectly updated device_class
# Fixed by frontend PR #13551 # Fixed by frontend PR #13551
for entity in data["entities"]: for entity in data["entities"]:
domain = split_entity_id(entity["entity_id"])[0] domain = split_entity_id(entity["entity_id"])[0]
if domain in [Platform.BINARY_SENSOR, Platform.COVER]: if domain in [Platform.BINARY_SENSOR, Platform.COVER]:
continue continue
entity["device_class"] = None entity["device_class"] = None
if old_major_version == 1 and old_minor_version < 9: if old_minor_version < 9:
# Version 1.9 adds translation_key # Version 1.9 adds translation_key
for entity in data["entities"]: for entity in data["entities"]:
entity["translation_key"] = None entity["translation_key"] = None
if old_major_version == 1 and old_minor_version < 10: if old_minor_version < 10:
# Version 1.10 adds aliases # Version 1.10 adds aliases
for entity in data["entities"]: for entity in data["entities"]:
entity["aliases"] = [] entity["aliases"] = []
if old_major_version == 1 and old_minor_version < 11: if old_minor_version < 11:
# Version 1.11 adds deleted_entities # Version 1.11 adds deleted_entities
data["deleted_entities"] = data.get("deleted_entities", []) data["deleted_entities"] = data.get("deleted_entities", [])
if old_major_version == 1 and old_minor_version < 12: if old_minor_version < 12:
# Version 1.12 adds previous_unique_id # Version 1.12 adds previous_unique_id
for entity in data["entities"]: for entity in data["entities"]:
entity["previous_unique_id"] = None entity["previous_unique_id"] = None
if old_major_version == 1 and old_minor_version < 13: if old_minor_version < 13:
# Version 1.13 adds labels # Version 1.13 adds labels
for entity in data["entities"]: for entity in data["entities"]:
entity["labels"] = [] entity["labels"] = []
if old_major_version == 1 and old_minor_version < 14: if old_minor_version < 14:
# Version 1.14 adds categories # Version 1.14 adds categories
for entity in data["entities"]: for entity in data["entities"]:
entity["categories"] = {} entity["categories"] = {}
if old_minor_version < 15:
# Version 1.15 adds created_at and modified_at
created_at = utc_from_timestamp(0).isoformat()
for entity in data["entities"]:
entity["created_at"] = entity["modified_at"] = created_at
for entity in data["deleted_entities"]:
entity["created_at"] = entity["modified_at"] = created_at
if old_major_version > 1: if old_major_version > 1:
raise NotImplementedError raise NotImplementedError
@ -837,10 +857,12 @@ class EntityRegistry(BaseRegistry):
) )
entity_registry_id: str | None = None entity_registry_id: str | None = None
created_at = utcnow()
deleted_entity = self.deleted_entities.pop((domain, platform, unique_id), None) deleted_entity = self.deleted_entities.pop((domain, platform, unique_id), None)
if deleted_entity is not None: if deleted_entity is not None:
# Restore id # Restore id
entity_registry_id = deleted_entity.id entity_registry_id = deleted_entity.id
created_at = deleted_entity.created_at
entity_id = self.async_generate_entity_id( entity_id = self.async_generate_entity_id(
domain, domain,
@ -865,6 +887,7 @@ class EntityRegistry(BaseRegistry):
entry = RegistryEntry( entry = RegistryEntry(
capabilities=none_if_undefined(capabilities), capabilities=none_if_undefined(capabilities),
config_entry_id=none_if_undefined(config_entry_id), config_entry_id=none_if_undefined(config_entry_id),
created_at=created_at,
device_id=none_if_undefined(device_id), device_id=none_if_undefined(device_id),
disabled_by=disabled_by, disabled_by=disabled_by,
entity_category=none_if_undefined(entity_category), entity_category=none_if_undefined(entity_category),
@ -906,6 +929,7 @@ class EntityRegistry(BaseRegistry):
orphaned_timestamp = None if config_entry_id else time.time() orphaned_timestamp = None if config_entry_id else time.time()
self.deleted_entities[key] = DeletedRegistryEntry( self.deleted_entities[key] = DeletedRegistryEntry(
config_entry_id=config_entry_id, config_entry_id=config_entry_id,
created_at=entity.created_at,
entity_id=entity_id, entity_id=entity_id,
id=entity.id, id=entity.id,
orphaned_timestamp=orphaned_timestamp, orphaned_timestamp=orphaned_timestamp,
@ -1093,6 +1117,8 @@ class EntityRegistry(BaseRegistry):
if not new_values: if not new_values:
return old return old
new_values["modified_at"] = utcnow()
self.hass.verify_event_loop_thread("entity_registry.async_update_entity") self.hass.verify_event_loop_thread("entity_registry.async_update_entity")
new = self.entities[entity_id] = attr.evolve(old, **new_values) new = self.entities[entity_id] = attr.evolve(old, **new_values)
@ -1260,6 +1286,7 @@ class EntityRegistry(BaseRegistry):
categories=entity["categories"], categories=entity["categories"],
capabilities=entity["capabilities"], capabilities=entity["capabilities"],
config_entry_id=entity["config_entry_id"], config_entry_id=entity["config_entry_id"],
created_at=entity["created_at"],
device_class=entity["device_class"], device_class=entity["device_class"],
device_id=entity["device_id"], device_id=entity["device_id"],
disabled_by=RegistryEntryDisabler(entity["disabled_by"]) disabled_by=RegistryEntryDisabler(entity["disabled_by"])
@ -1276,6 +1303,7 @@ class EntityRegistry(BaseRegistry):
id=entity["id"], id=entity["id"],
has_entity_name=entity["has_entity_name"], has_entity_name=entity["has_entity_name"],
labels=set(entity["labels"]), labels=set(entity["labels"]),
modified_at=entity["modified_at"],
name=entity["name"], name=entity["name"],
options=entity["options"], options=entity["options"],
original_device_class=entity["original_device_class"], original_device_class=entity["original_device_class"],
@ -1307,8 +1335,10 @@ class EntityRegistry(BaseRegistry):
) )
deleted_entities[key] = DeletedRegistryEntry( deleted_entities[key] = DeletedRegistryEntry(
config_entry_id=entity["config_entry_id"], config_entry_id=entity["config_entry_id"],
created_at=entity["created_at"],
entity_id=entity["entity_id"], entity_id=entity["entity_id"],
id=entity["id"], id=entity["id"],
modified_at=entity["modified_at"],
orphaned_timestamp=entity["orphaned_timestamp"], orphaned_timestamp=entity["orphaned_timestamp"],
platform=entity["platform"], platform=entity["platform"],
unique_id=entity["unique_id"], unique_id=entity["unique_id"],

View File

@ -1,5 +1,8 @@
"""Test entity_registry API.""" """Test entity_registry API."""
from datetime import datetime
from freezegun.api import FrozenDateTimeFactory
import pytest import pytest
from pytest_unordered import unordered from pytest_unordered import unordered
@ -13,6 +16,7 @@ from homeassistant.helpers.entity_registry import (
RegistryEntryDisabler, RegistryEntryDisabler,
RegistryEntryHider, RegistryEntryHider,
) )
from homeassistant.util.dt import utcnow
from tests.common import ( from tests.common import (
ANY, ANY,
@ -33,6 +37,7 @@ async def client(
return await hass_ws_client(hass) return await hass_ws_client(hass)
@pytest.mark.usefixtures("freezer")
async def test_list_entities( async def test_list_entities(
hass: HomeAssistant, client: MockHAClientWebSocket hass: HomeAssistant, client: MockHAClientWebSocket
) -> None: ) -> None:
@ -62,6 +67,7 @@ async def test_list_entities(
"area_id": None, "area_id": None,
"categories": {}, "categories": {},
"config_entry_id": None, "config_entry_id": None,
"created_at": utcnow().timestamp(),
"device_id": None, "device_id": None,
"disabled_by": None, "disabled_by": None,
"entity_category": None, "entity_category": None,
@ -71,6 +77,7 @@ async def test_list_entities(
"icon": None, "icon": None,
"id": ANY, "id": ANY,
"labels": [], "labels": [],
"modified_at": utcnow().timestamp(),
"name": "Hello World", "name": "Hello World",
"options": {}, "options": {},
"original_name": None, "original_name": None,
@ -82,6 +89,7 @@ async def test_list_entities(
"area_id": None, "area_id": None,
"categories": {}, "categories": {},
"config_entry_id": None, "config_entry_id": None,
"created_at": utcnow().timestamp(),
"device_id": None, "device_id": None,
"disabled_by": None, "disabled_by": None,
"entity_category": None, "entity_category": None,
@ -91,6 +99,7 @@ async def test_list_entities(
"icon": None, "icon": None,
"id": ANY, "id": ANY,
"labels": [], "labels": [],
"modified_at": utcnow().timestamp(),
"name": None, "name": None,
"options": {}, "options": {},
"original_name": None, "original_name": None,
@ -129,6 +138,7 @@ async def test_list_entities(
"area_id": None, "area_id": None,
"categories": {}, "categories": {},
"config_entry_id": None, "config_entry_id": None,
"created_at": utcnow().timestamp(),
"device_id": None, "device_id": None,
"disabled_by": None, "disabled_by": None,
"entity_category": None, "entity_category": None,
@ -138,6 +148,7 @@ async def test_list_entities(
"icon": None, "icon": None,
"id": ANY, "id": ANY,
"labels": [], "labels": [],
"modified_at": utcnow().timestamp(),
"name": "Hello World", "name": "Hello World",
"options": {}, "options": {},
"original_name": None, "original_name": None,
@ -325,6 +336,8 @@ async def test_list_entities_for_display(
async def test_get_entity(hass: HomeAssistant, client: MockHAClientWebSocket) -> None: async def test_get_entity(hass: HomeAssistant, client: MockHAClientWebSocket) -> None:
"""Test get entry.""" """Test get entry."""
name_created_at = datetime(1994, 2, 14, 12, 0, 0)
no_name_created_at = datetime(2024, 2, 14, 12, 0, 1)
mock_registry( mock_registry(
hass, hass,
{ {
@ -333,11 +346,15 @@ async def test_get_entity(hass: HomeAssistant, client: MockHAClientWebSocket) ->
unique_id="1234", unique_id="1234",
platform="test_platform", platform="test_platform",
name="Hello World", name="Hello World",
created_at=name_created_at,
modified_at=name_created_at,
), ),
"test_domain.no_name": RegistryEntry( "test_domain.no_name": RegistryEntry(
entity_id="test_domain.no_name", entity_id="test_domain.no_name",
unique_id="6789", unique_id="6789",
platform="test_platform", platform="test_platform",
created_at=no_name_created_at,
modified_at=no_name_created_at,
), ),
}, },
) )
@ -353,6 +370,7 @@ async def test_get_entity(hass: HomeAssistant, client: MockHAClientWebSocket) ->
"capabilities": None, "capabilities": None,
"categories": {}, "categories": {},
"config_entry_id": None, "config_entry_id": None,
"created_at": name_created_at.timestamp(),
"device_class": None, "device_class": None,
"device_id": None, "device_id": None,
"disabled_by": None, "disabled_by": None,
@ -363,6 +381,7 @@ async def test_get_entity(hass: HomeAssistant, client: MockHAClientWebSocket) ->
"icon": None, "icon": None,
"id": ANY, "id": ANY,
"labels": [], "labels": [],
"modified_at": name_created_at.timestamp(),
"name": "Hello World", "name": "Hello World",
"options": {}, "options": {},
"original_device_class": None, "original_device_class": None,
@ -387,6 +406,7 @@ async def test_get_entity(hass: HomeAssistant, client: MockHAClientWebSocket) ->
"capabilities": None, "capabilities": None,
"categories": {}, "categories": {},
"config_entry_id": None, "config_entry_id": None,
"created_at": no_name_created_at.timestamp(),
"device_class": None, "device_class": None,
"device_id": None, "device_id": None,
"disabled_by": None, "disabled_by": None,
@ -397,6 +417,7 @@ async def test_get_entity(hass: HomeAssistant, client: MockHAClientWebSocket) ->
"icon": None, "icon": None,
"id": ANY, "id": ANY,
"labels": [], "labels": [],
"modified_at": no_name_created_at.timestamp(),
"name": None, "name": None,
"options": {}, "options": {},
"original_device_class": None, "original_device_class": None,
@ -410,6 +431,8 @@ async def test_get_entity(hass: HomeAssistant, client: MockHAClientWebSocket) ->
async def test_get_entities(hass: HomeAssistant, client: MockHAClientWebSocket) -> None: async def test_get_entities(hass: HomeAssistant, client: MockHAClientWebSocket) -> None:
"""Test get entry.""" """Test get entry."""
name_created_at = datetime(1994, 2, 14, 12, 0, 0)
no_name_created_at = datetime(2024, 2, 14, 12, 0, 1)
mock_registry( mock_registry(
hass, hass,
{ {
@ -418,11 +441,15 @@ async def test_get_entities(hass: HomeAssistant, client: MockHAClientWebSocket)
unique_id="1234", unique_id="1234",
platform="test_platform", platform="test_platform",
name="Hello World", name="Hello World",
created_at=name_created_at,
modified_at=name_created_at,
), ),
"test_domain.no_name": RegistryEntry( "test_domain.no_name": RegistryEntry(
entity_id="test_domain.no_name", entity_id="test_domain.no_name",
unique_id="6789", unique_id="6789",
platform="test_platform", platform="test_platform",
created_at=no_name_created_at,
modified_at=no_name_created_at,
), ),
}, },
) )
@ -446,6 +473,7 @@ async def test_get_entities(hass: HomeAssistant, client: MockHAClientWebSocket)
"capabilities": None, "capabilities": None,
"categories": {}, "categories": {},
"config_entry_id": None, "config_entry_id": None,
"created_at": name_created_at.timestamp(),
"device_class": None, "device_class": None,
"device_id": None, "device_id": None,
"disabled_by": None, "disabled_by": None,
@ -456,6 +484,7 @@ async def test_get_entities(hass: HomeAssistant, client: MockHAClientWebSocket)
"icon": None, "icon": None,
"id": ANY, "id": ANY,
"labels": [], "labels": [],
"modified_at": name_created_at.timestamp(),
"name": "Hello World", "name": "Hello World",
"options": {}, "options": {},
"original_device_class": None, "original_device_class": None,
@ -471,6 +500,7 @@ async def test_get_entities(hass: HomeAssistant, client: MockHAClientWebSocket)
"capabilities": None, "capabilities": None,
"categories": {}, "categories": {},
"config_entry_id": None, "config_entry_id": None,
"created_at": no_name_created_at.timestamp(),
"device_class": None, "device_class": None,
"device_id": None, "device_id": None,
"disabled_by": None, "disabled_by": None,
@ -481,6 +511,7 @@ async def test_get_entities(hass: HomeAssistant, client: MockHAClientWebSocket)
"icon": None, "icon": None,
"id": ANY, "id": ANY,
"labels": [], "labels": [],
"modified_at": no_name_created_at.timestamp(),
"name": None, "name": None,
"options": {}, "options": {},
"original_device_class": None, "original_device_class": None,
@ -495,9 +526,11 @@ async def test_get_entities(hass: HomeAssistant, client: MockHAClientWebSocket)
async def test_update_entity( async def test_update_entity(
hass: HomeAssistant, client: MockHAClientWebSocket hass: HomeAssistant, client: MockHAClientWebSocket, freezer: FrozenDateTimeFactory
) -> None: ) -> None:
"""Test updating entity.""" """Test updating entity."""
created = datetime.fromisoformat("2024-02-14T12:00:00.900075+00:00")
freezer.move_to(created)
registry = mock_registry( registry = mock_registry(
hass, hass,
{ {
@ -520,6 +553,9 @@ async def test_update_entity(
assert state.name == "before update" assert state.name == "before update"
assert state.attributes[ATTR_ICON] == "icon:before update" assert state.attributes[ATTR_ICON] == "icon:before update"
modified = datetime.fromisoformat("2024-07-17T13:30:00.900075+00:00")
freezer.move_to(modified)
# Update area, categories, device_class, hidden_by, icon, labels & name # Update area, categories, device_class, hidden_by, icon, labels & name
await client.send_json_auto_id( await client.send_json_auto_id(
{ {
@ -544,6 +580,7 @@ async def test_update_entity(
"area_id": "mock-area-id", "area_id": "mock-area-id",
"capabilities": None, "capabilities": None,
"categories": {"scope1": "id", "scope2": "id"}, "categories": {"scope1": "id", "scope2": "id"},
"created_at": created.timestamp(),
"config_entry_id": None, "config_entry_id": None,
"device_class": "custom_device_class", "device_class": "custom_device_class",
"device_id": None, "device_id": None,
@ -555,6 +592,7 @@ async def test_update_entity(
"icon": "icon:after update", "icon": "icon:after update",
"id": ANY, "id": ANY,
"labels": unordered(["label1", "label2"]), "labels": unordered(["label1", "label2"]),
"modified_at": modified.timestamp(),
"name": "after update", "name": "after update",
"options": {}, "options": {},
"original_device_class": None, "original_device_class": None,
@ -570,6 +608,9 @@ async def test_update_entity(
assert state.name == "after update" assert state.name == "after update"
assert state.attributes[ATTR_ICON] == "icon:after update" assert state.attributes[ATTR_ICON] == "icon:after update"
modified = datetime.fromisoformat("2024-07-20T00:00:00.900075+00:00")
freezer.move_to(modified)
# Update hidden_by to illegal value # Update hidden_by to illegal value
await client.send_json_auto_id( await client.send_json_auto_id(
{ {
@ -597,9 +638,13 @@ async def test_update_entity(
assert msg["success"] assert msg["success"]
assert hass.states.get("test_domain.world") is None assert hass.states.get("test_domain.world") is None
assert ( entry = registry.entities["test_domain.world"]
registry.entities["test_domain.world"].disabled_by is RegistryEntryDisabler.USER assert entry.disabled_by is RegistryEntryDisabler.USER
) assert entry.created_at == created
assert entry.modified_at == modified
modified = datetime.fromisoformat("2024-07-21T00:00:00.900075+00:00")
freezer.move_to(modified)
# Update disabled_by to None # Update disabled_by to None
await client.send_json_auto_id( await client.send_json_auto_id(
@ -619,6 +664,7 @@ async def test_update_entity(
"capabilities": None, "capabilities": None,
"categories": {"scope1": "id", "scope2": "id"}, "categories": {"scope1": "id", "scope2": "id"},
"config_entry_id": None, "config_entry_id": None,
"created_at": created.timestamp(),
"device_class": "custom_device_class", "device_class": "custom_device_class",
"device_id": None, "device_id": None,
"disabled_by": None, "disabled_by": None,
@ -629,6 +675,7 @@ async def test_update_entity(
"icon": "icon:after update", "icon": "icon:after update",
"id": ANY, "id": ANY,
"labels": unordered(["label1", "label2"]), "labels": unordered(["label1", "label2"]),
"modified_at": modified.timestamp(),
"name": "after update", "name": "after update",
"options": {}, "options": {},
"original_device_class": None, "original_device_class": None,
@ -641,6 +688,9 @@ async def test_update_entity(
"require_restart": True, "require_restart": True,
} }
modified = datetime.fromisoformat("2024-07-22T00:00:00.900075+00:00")
freezer.move_to(modified)
# Update entity option # Update entity option
await client.send_json_auto_id( await client.send_json_auto_id(
{ {
@ -660,6 +710,7 @@ async def test_update_entity(
"capabilities": None, "capabilities": None,
"categories": {"scope1": "id", "scope2": "id"}, "categories": {"scope1": "id", "scope2": "id"},
"config_entry_id": None, "config_entry_id": None,
"created_at": created.timestamp(),
"device_class": "custom_device_class", "device_class": "custom_device_class",
"device_id": None, "device_id": None,
"disabled_by": None, "disabled_by": None,
@ -670,6 +721,7 @@ async def test_update_entity(
"icon": "icon:after update", "icon": "icon:after update",
"id": ANY, "id": ANY,
"labels": unordered(["label1", "label2"]), "labels": unordered(["label1", "label2"]),
"modified_at": modified.timestamp(),
"name": "after update", "name": "after update",
"options": {"sensor": {"unit_of_measurement": "beard_second"}}, "options": {"sensor": {"unit_of_measurement": "beard_second"}},
"original_device_class": None, "original_device_class": None,
@ -681,6 +733,9 @@ async def test_update_entity(
}, },
} }
modified = datetime.fromisoformat("2024-07-23T00:00:00.900075+00:00")
freezer.move_to(modified)
# Add a category to the entity # Add a category to the entity
await client.send_json_auto_id( await client.send_json_auto_id(
{ {
@ -700,6 +755,7 @@ async def test_update_entity(
"capabilities": None, "capabilities": None,
"categories": {"scope1": "id", "scope2": "id", "scope3": "id"}, "categories": {"scope1": "id", "scope2": "id", "scope3": "id"},
"config_entry_id": None, "config_entry_id": None,
"created_at": created.timestamp(),
"device_class": "custom_device_class", "device_class": "custom_device_class",
"device_id": None, "device_id": None,
"disabled_by": None, "disabled_by": None,
@ -710,6 +766,7 @@ async def test_update_entity(
"icon": "icon:after update", "icon": "icon:after update",
"id": ANY, "id": ANY,
"labels": unordered(["label1", "label2"]), "labels": unordered(["label1", "label2"]),
"modified_at": modified.timestamp(),
"name": "after update", "name": "after update",
"options": {"sensor": {"unit_of_measurement": "beard_second"}}, "options": {"sensor": {"unit_of_measurement": "beard_second"}},
"original_device_class": None, "original_device_class": None,
@ -721,6 +778,9 @@ async def test_update_entity(
}, },
} }
modified = datetime.fromisoformat("2024-07-24T00:00:00.900075+00:00")
freezer.move_to(modified)
# Move the entity to a different category # Move the entity to a different category
await client.send_json_auto_id( await client.send_json_auto_id(
{ {
@ -740,6 +800,7 @@ async def test_update_entity(
"capabilities": None, "capabilities": None,
"categories": {"scope1": "id", "scope2": "id", "scope3": "other_id"}, "categories": {"scope1": "id", "scope2": "id", "scope3": "other_id"},
"config_entry_id": None, "config_entry_id": None,
"created_at": created.timestamp(),
"device_class": "custom_device_class", "device_class": "custom_device_class",
"device_id": None, "device_id": None,
"disabled_by": None, "disabled_by": None,
@ -750,6 +811,7 @@ async def test_update_entity(
"icon": "icon:after update", "icon": "icon:after update",
"id": ANY, "id": ANY,
"labels": unordered(["label1", "label2"]), "labels": unordered(["label1", "label2"]),
"modified_at": modified.timestamp(),
"name": "after update", "name": "after update",
"options": {"sensor": {"unit_of_measurement": "beard_second"}}, "options": {"sensor": {"unit_of_measurement": "beard_second"}},
"original_device_class": None, "original_device_class": None,
@ -761,6 +823,9 @@ async def test_update_entity(
}, },
} }
modified = datetime.fromisoformat("2024-07-23T10:00:00.900075+00:00")
freezer.move_to(modified)
# Move the entity to a different category # Move the entity to a different category
await client.send_json_auto_id( await client.send_json_auto_id(
{ {
@ -780,6 +845,7 @@ async def test_update_entity(
"capabilities": None, "capabilities": None,
"categories": {"scope1": "id", "scope3": "other_id"}, "categories": {"scope1": "id", "scope3": "other_id"},
"config_entry_id": None, "config_entry_id": None,
"created_at": created.timestamp(),
"device_class": "custom_device_class", "device_class": "custom_device_class",
"device_id": None, "device_id": None,
"disabled_by": None, "disabled_by": None,
@ -790,6 +856,7 @@ async def test_update_entity(
"icon": "icon:after update", "icon": "icon:after update",
"id": ANY, "id": ANY,
"labels": unordered(["label1", "label2"]), "labels": unordered(["label1", "label2"]),
"modified_at": modified.timestamp(),
"name": "after update", "name": "after update",
"options": {"sensor": {"unit_of_measurement": "beard_second"}}, "options": {"sensor": {"unit_of_measurement": "beard_second"}},
"original_device_class": None, "original_device_class": None,
@ -803,9 +870,11 @@ async def test_update_entity(
async def test_update_entity_require_restart( async def test_update_entity_require_restart(
hass: HomeAssistant, client: MockHAClientWebSocket hass: HomeAssistant, client: MockHAClientWebSocket, freezer: FrozenDateTimeFactory
) -> None: ) -> None:
"""Test updating entity.""" """Test updating entity."""
created = datetime.fromisoformat("2024-02-14T12:00:00+00:00")
freezer.move_to(created)
entity_id = "test_domain.test_platform_1234" entity_id = "test_domain.test_platform_1234"
config_entry = MockConfigEntry(domain="test_platform") config_entry = MockConfigEntry(domain="test_platform")
config_entry.add_to_hass(hass) config_entry.add_to_hass(hass)
@ -817,6 +886,9 @@ async def test_update_entity_require_restart(
state = hass.states.get(entity_id) state = hass.states.get(entity_id)
assert state is not None assert state is not None
modified = datetime.fromisoformat("2024-07-20T13:30:00+00:00")
freezer.move_to(modified)
# UPDATE DISABLED_BY TO NONE # UPDATE DISABLED_BY TO NONE
await client.send_json_auto_id( await client.send_json_auto_id(
{ {
@ -835,6 +907,7 @@ async def test_update_entity_require_restart(
"capabilities": None, "capabilities": None,
"categories": {}, "categories": {},
"config_entry_id": config_entry.entry_id, "config_entry_id": config_entry.entry_id,
"created_at": created.timestamp(),
"device_class": None, "device_class": None,
"device_id": None, "device_id": None,
"disabled_by": None, "disabled_by": None,
@ -845,6 +918,7 @@ async def test_update_entity_require_restart(
"icon": None, "icon": None,
"id": ANY, "id": ANY,
"labels": [], "labels": [],
"modified_at": created.timestamp(),
"name": None, "name": None,
"options": {}, "options": {},
"original_device_class": None, "original_device_class": None,
@ -909,9 +983,11 @@ async def test_enable_entity_disabled_device(
async def test_update_entity_no_changes( async def test_update_entity_no_changes(
hass: HomeAssistant, client: MockHAClientWebSocket hass: HomeAssistant, client: MockHAClientWebSocket, freezer: FrozenDateTimeFactory
) -> None: ) -> None:
"""Test update entity with no changes.""" """Test update entity with no changes."""
created = datetime.fromisoformat("2024-02-14T12:00:00.900075+00:00")
freezer.move_to(created)
mock_registry( mock_registry(
hass, hass,
{ {
@ -932,6 +1008,9 @@ async def test_update_entity_no_changes(
assert state is not None assert state is not None
assert state.name == "name of entity" assert state.name == "name of entity"
modified = datetime.fromisoformat("2024-07-20T13:30:00.900075+00:00")
freezer.move_to(modified)
await client.send_json_auto_id( await client.send_json_auto_id(
{ {
"type": "config/entity_registry/update", "type": "config/entity_registry/update",
@ -949,6 +1028,7 @@ async def test_update_entity_no_changes(
"capabilities": None, "capabilities": None,
"categories": {}, "categories": {},
"config_entry_id": None, "config_entry_id": None,
"created_at": created.timestamp(),
"device_class": None, "device_class": None,
"device_id": None, "device_id": None,
"disabled_by": None, "disabled_by": None,
@ -959,6 +1039,7 @@ async def test_update_entity_no_changes(
"icon": None, "icon": None,
"id": ANY, "id": ANY,
"labels": [], "labels": [],
"modified_at": created.timestamp(),
"name": "name of entity", "name": "name of entity",
"options": {}, "options": {},
"original_device_class": None, "original_device_class": None,
@ -1002,9 +1083,11 @@ async def test_update_nonexisting_entity(client: MockHAClientWebSocket) -> None:
async def test_update_entity_id( async def test_update_entity_id(
hass: HomeAssistant, client: MockHAClientWebSocket hass: HomeAssistant, client: MockHAClientWebSocket, freezer: FrozenDateTimeFactory
) -> None: ) -> None:
"""Test update entity id.""" """Test update entity id."""
created = datetime.fromisoformat("2024-02-14T12:00:00.900075+00:00")
freezer.move_to(created)
mock_registry( mock_registry(
hass, hass,
{ {
@ -1022,6 +1105,9 @@ async def test_update_entity_id(
assert hass.states.get("test_domain.world") is not None assert hass.states.get("test_domain.world") is not None
modified = datetime.fromisoformat("2024-07-20T13:30:00.900075+00:00")
freezer.move_to(modified)
await client.send_json_auto_id( await client.send_json_auto_id(
{ {
"type": "config/entity_registry/update", "type": "config/entity_registry/update",
@ -1039,6 +1125,7 @@ async def test_update_entity_id(
"capabilities": None, "capabilities": None,
"categories": {}, "categories": {},
"config_entry_id": None, "config_entry_id": None,
"created_at": created.timestamp(),
"device_class": None, "device_class": None,
"device_id": None, "device_id": None,
"disabled_by": None, "disabled_by": None,
@ -1049,6 +1136,7 @@ async def test_update_entity_id(
"icon": None, "icon": None,
"id": ANY, "id": ANY,
"labels": [], "labels": [],
"modified_at": modified.timestamp(),
"name": None, "name": None,
"options": {}, "options": {},
"original_device_class": None, "original_device_class": None,

View File

@ -287,6 +287,8 @@ async def test_snapshots(
entry = asdict(entity_entry) entry = asdict(entity_entry)
entry.pop("id", None) entry.pop("id", None)
entry.pop("device_id", None) entry.pop("device_id", None)
entry.pop("created_at", None)
entry.pop("modified_at", None)
entities.append({"entry": entry, "state": state_dict}) entities.append({"entry": entry, "state": state_dict})

View File

@ -1422,6 +1422,7 @@ async def test_entity_hidden_by_integration(
assert entry_hidden.hidden_by is er.RegistryEntryHider.INTEGRATION assert entry_hidden.hidden_by is er.RegistryEntryHider.INTEGRATION
@pytest.mark.usefixtures("freezer")
async def test_entity_info_added_to_entity_registry( async def test_entity_info_added_to_entity_registry(
hass: HomeAssistant, entity_registry: er.EntityRegistry hass: HomeAssistant, entity_registry: er.EntityRegistry
) -> None: ) -> None:
@ -1450,11 +1451,13 @@ async def test_entity_info_added_to_entity_registry(
"default", "default",
"test_domain", "test_domain",
capabilities={"max": 100}, capabilities={"max": 100},
created_at=dt_util.utcnow(),
device_class=None, device_class=None,
entity_category=EntityCategory.CONFIG, entity_category=EntityCategory.CONFIG,
has_entity_name=True, has_entity_name=True,
icon=None, icon=None,
id=ANY, id=ANY,
modified_at=dt_util.utcnow(),
name=None, name=None,
original_device_class="mock-device-class", original_device_class="mock-device-class",
original_icon="nice:icon", original_icon="nice:icon",

View File

@ -1,6 +1,6 @@
"""Tests for the Entity Registry.""" """Tests for the Entity Registry."""
from datetime import timedelta from datetime import datetime, timedelta
from functools import partial from functools import partial
from typing import Any from typing import Any
from unittest.mock import patch from unittest.mock import patch
@ -21,6 +21,7 @@ from homeassistant.exceptions import MaxLengthExceeded
from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers import device_registry as dr, entity_registry as er
from tests.common import ( from tests.common import (
ANY,
MockConfigEntry, MockConfigEntry,
async_capture_events, async_capture_events,
async_fire_time_changed, async_fire_time_changed,
@ -69,9 +70,14 @@ def test_get_or_create_suggested_object_id(entity_registry: er.EntityRegistry) -
assert entry.entity_id == "light.beer" assert entry.entity_id == "light.beer"
def test_get_or_create_updates_data(entity_registry: er.EntityRegistry) -> None: def test_get_or_create_updates_data(
entity_registry: er.EntityRegistry,
freezer: FrozenDateTimeFactory,
) -> None:
"""Test that we update data in get_or_create.""" """Test that we update data in get_or_create."""
orig_config_entry = MockConfigEntry(domain="light") orig_config_entry = MockConfigEntry(domain="light")
created = datetime.fromisoformat("2024-02-14T12:00:00.0+00:00")
freezer.move_to(created)
orig_entry = entity_registry.async_get_or_create( orig_entry = entity_registry.async_get_or_create(
"light", "light",
@ -100,6 +106,7 @@ def test_get_or_create_updates_data(entity_registry: er.EntityRegistry) -> None:
"hue", "hue",
capabilities={"max": 100}, capabilities={"max": 100},
config_entry_id=orig_config_entry.entry_id, config_entry_id=orig_config_entry.entry_id,
created_at=created,
device_class=None, device_class=None,
device_id="mock-dev-id", device_id="mock-dev-id",
disabled_by=er.RegistryEntryDisabler.HASS, disabled_by=er.RegistryEntryDisabler.HASS,
@ -108,6 +115,7 @@ def test_get_or_create_updates_data(entity_registry: er.EntityRegistry) -> None:
hidden_by=er.RegistryEntryHider.INTEGRATION, hidden_by=er.RegistryEntryHider.INTEGRATION,
icon=None, icon=None,
id=orig_entry.id, id=orig_entry.id,
modified_at=created,
name=None, name=None,
original_device_class="mock-device-class", original_device_class="mock-device-class",
original_icon="initial-original_icon", original_icon="initial-original_icon",
@ -118,6 +126,8 @@ def test_get_or_create_updates_data(entity_registry: er.EntityRegistry) -> None:
) )
new_config_entry = MockConfigEntry(domain="light") new_config_entry = MockConfigEntry(domain="light")
modified = created + timedelta(minutes=5)
freezer.move_to(modified)
new_entry = entity_registry.async_get_or_create( new_entry = entity_registry.async_get_or_create(
"light", "light",
@ -146,6 +156,7 @@ def test_get_or_create_updates_data(entity_registry: er.EntityRegistry) -> None:
area_id=None, area_id=None,
capabilities={"new-max": 150}, capabilities={"new-max": 150},
config_entry_id=new_config_entry.entry_id, config_entry_id=new_config_entry.entry_id,
created_at=created,
device_class=None, device_class=None,
device_id="new-mock-dev-id", device_id="new-mock-dev-id",
disabled_by=er.RegistryEntryDisabler.HASS, # Should not be updated disabled_by=er.RegistryEntryDisabler.HASS, # Should not be updated
@ -154,6 +165,7 @@ def test_get_or_create_updates_data(entity_registry: er.EntityRegistry) -> None:
hidden_by=er.RegistryEntryHider.INTEGRATION, # Should not be updated hidden_by=er.RegistryEntryHider.INTEGRATION, # Should not be updated
icon=None, icon=None,
id=orig_entry.id, id=orig_entry.id,
modified_at=modified,
name=None, name=None,
original_device_class="new-mock-device-class", original_device_class="new-mock-device-class",
original_icon="updated-original_icon", original_icon="updated-original_icon",
@ -164,6 +176,8 @@ def test_get_or_create_updates_data(entity_registry: er.EntityRegistry) -> None:
) )
assert set(entity_registry.async_device_ids()) == {"new-mock-dev-id"} assert set(entity_registry.async_device_ids()) == {"new-mock-dev-id"}
modified = created + timedelta(minutes=5)
freezer.move_to(modified)
new_entry = entity_registry.async_get_or_create( new_entry = entity_registry.async_get_or_create(
"light", "light",
@ -192,6 +206,7 @@ def test_get_or_create_updates_data(entity_registry: er.EntityRegistry) -> None:
area_id=None, area_id=None,
capabilities=None, capabilities=None,
config_entry_id=None, config_entry_id=None,
created_at=created,
device_class=None, device_class=None,
device_id=None, device_id=None,
disabled_by=er.RegistryEntryDisabler.HASS, # Should not be updated disabled_by=er.RegistryEntryDisabler.HASS, # Should not be updated
@ -200,6 +215,7 @@ def test_get_or_create_updates_data(entity_registry: er.EntityRegistry) -> None:
hidden_by=er.RegistryEntryHider.INTEGRATION, # Should not be updated hidden_by=er.RegistryEntryHider.INTEGRATION, # Should not be updated
icon=None, icon=None,
id=orig_entry.id, id=orig_entry.id,
modified_at=modified,
name=None, name=None,
original_device_class=None, original_device_class=None,
original_icon=None, original_icon=None,
@ -309,8 +325,12 @@ async def test_loading_saving_data(
assert orig_entry1 == new_entry1 assert orig_entry1 == new_entry1
assert orig_entry2 == new_entry2 assert orig_entry2 == new_entry2
assert orig_entry3 == new_entry3
assert orig_entry4 == new_entry4 # By converting a deleted device to a active device, the modified_at will be updated
assert orig_entry3.modified_at < new_entry3.modified_at
assert attr.evolve(orig_entry3, modified_at=new_entry3.modified_at) == new_entry3
assert orig_entry4.modified_at < new_entry4.modified_at
assert attr.evolve(orig_entry4, modified_at=new_entry4.modified_at) == new_entry4
assert new_entry2.area_id == "mock-area-id" assert new_entry2.area_id == "mock-area-id"
assert new_entry2.categories == {"scope", "id"} assert new_entry2.categories == {"scope", "id"}
@ -453,6 +473,7 @@ async def test_load_bad_data(
"capabilities": None, "capabilities": None,
"categories": {}, "categories": {},
"config_entry_id": None, "config_entry_id": None,
"created_at": "2024-02-14T12:00:00.900075+00:00",
"device_class": None, "device_class": None,
"device_id": None, "device_id": None,
"disabled_by": None, "disabled_by": None,
@ -463,6 +484,7 @@ async def test_load_bad_data(
"icon": None, "icon": None,
"id": "00001", "id": "00001",
"labels": [], "labels": [],
"modified_at": "2024-02-14T12:00:00.900075+00:00",
"name": None, "name": None,
"options": None, "options": None,
"original_device_class": None, "original_device_class": None,
@ -481,6 +503,7 @@ async def test_load_bad_data(
"capabilities": None, "capabilities": None,
"categories": {}, "categories": {},
"config_entry_id": None, "config_entry_id": None,
"created_at": "2024-02-14T12:00:00.900075+00:00",
"device_class": None, "device_class": None,
"device_id": None, "device_id": None,
"disabled_by": None, "disabled_by": None,
@ -491,6 +514,7 @@ async def test_load_bad_data(
"icon": None, "icon": None,
"id": "00002", "id": "00002",
"labels": [], "labels": [],
"modified_at": "2024-02-14T12:00:00.900075+00:00",
"name": None, "name": None,
"options": None, "options": None,
"original_device_class": None, "original_device_class": None,
@ -507,16 +531,20 @@ async def test_load_bad_data(
"deleted_entities": [ "deleted_entities": [
{ {
"config_entry_id": None, "config_entry_id": None,
"created_at": "2024-02-14T12:00:00.900075+00:00",
"entity_id": "test.test3", "entity_id": "test.test3",
"id": "00003", "id": "00003",
"modified_at": "2024-02-14T12:00:00.900075+00:00",
"orphaned_timestamp": None, "orphaned_timestamp": None,
"platform": "super_platform", "platform": "super_platform",
"unique_id": 234, # Should not load "unique_id": 234, # Should not load
}, },
{ {
"config_entry_id": None, "config_entry_id": None,
"created_at": "2024-02-14T12:00:00.900075+00:00",
"entity_id": "test.test4", "entity_id": "test.test4",
"id": "00004", "id": "00004",
"modified_at": "2024-02-14T12:00:00.900075+00:00",
"orphaned_timestamp": None, "orphaned_timestamp": None,
"platform": "super_platform", "platform": "super_platform",
"unique_id": ["also", "not", "valid"], # Should not load "unique_id": ["also", "not", "valid"], # Should not load
@ -695,6 +723,49 @@ async def test_migration_1_1(hass: HomeAssistant, hass_storage: dict[str, Any])
assert entry.device_class is None assert entry.device_class is None
assert entry.original_device_class == "best_class" assert entry.original_device_class == "best_class"
# Check we store migrated data
await flush_store(registry._store)
assert hass_storage[er.STORAGE_KEY] == {
"version": er.STORAGE_VERSION_MAJOR,
"minor_version": er.STORAGE_VERSION_MINOR,
"key": er.STORAGE_KEY,
"data": {
"entities": [
{
"aliases": [],
"area_id": None,
"capabilities": {},
"categories": {},
"config_entry_id": None,
"created_at": "1970-01-01T00:00:00+00:00",
"device_id": None,
"disabled_by": None,
"entity_category": None,
"entity_id": "test.entity",
"has_entity_name": False,
"hidden_by": None,
"icon": None,
"id": ANY,
"labels": [],
"modified_at": "1970-01-01T00:00:00+00:00",
"name": None,
"options": {},
"original_device_class": "best_class",
"original_icon": None,
"original_name": None,
"platform": "super_platform",
"previous_unique_id": None,
"supported_features": 0,
"translation_key": None,
"unique_id": "very_unique",
"unit_of_measurement": None,
"device_class": None,
}
],
"deleted_entities": [],
},
}
@pytest.mark.parametrize("load_registries", [False]) @pytest.mark.parametrize("load_registries", [False])
async def test_migration_1_7(hass: HomeAssistant, hass_storage: dict[str, Any]) -> None: async def test_migration_1_7(hass: HomeAssistant, hass_storage: dict[str, Any]) -> None:

View File

@ -181,7 +181,7 @@ class HomeAssistantSnapshotSerializer(AmberDataSerializer):
} }
) )
serialized.pop("categories") serialized.pop("categories")
return serialized return cls._remove_created_and_modified_at(serialized)
@classmethod @classmethod
def _serializable_flow_result(cls, data: FlowResult) -> SerializableData: def _serializable_flow_result(cls, data: FlowResult) -> SerializableData: