mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 13:17:32 +00:00
Add created_at/modified_at to entity registry (#122444)
This commit is contained in:
parent
8d14095cb9
commit
0d765a27c9
@ -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"],
|
||||||
|
@ -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,
|
||||||
|
@ -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})
|
||||||
|
|
||||||
|
@ -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",
|
||||||
|
@ -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:
|
||||||
|
@ -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:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user