mirror of
https://github.com/home-assistant/core.git
synced 2025-07-16 01:37:08 +00:00
Add created_at/modified_at to category registry (#122454)
This commit is contained in:
parent
92acfc1464
commit
545514c5cd
@ -130,6 +130,8 @@ def _entry_dict(entry: cr.CategoryEntry) -> dict[str, Any]:
|
|||||||
"""Convert entry to API format."""
|
"""Convert entry to API format."""
|
||||||
return {
|
return {
|
||||||
"category_id": entry.category_id,
|
"category_id": entry.category_id,
|
||||||
|
"created_at": entry.created_at.timestamp(),
|
||||||
"icon": entry.icon,
|
"icon": entry.icon,
|
||||||
|
"modified_at": entry.modified_at.timestamp(),
|
||||||
"name": entry.name,
|
"name": entry.name,
|
||||||
}
|
}
|
||||||
|
@ -5,9 +5,11 @@ from __future__ import annotations
|
|||||||
from collections.abc import Iterable
|
from collections.abc import Iterable
|
||||||
import dataclasses
|
import dataclasses
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from typing import Literal, TypedDict
|
from datetime import datetime
|
||||||
|
from typing import Any, Literal, TypedDict
|
||||||
|
|
||||||
from homeassistant.core import Event, HomeAssistant, callback
|
from homeassistant.core import Event, HomeAssistant, callback
|
||||||
|
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.ulid import ulid_now
|
from homeassistant.util.ulid import ulid_now
|
||||||
@ -23,13 +25,16 @@ EVENT_CATEGORY_REGISTRY_UPDATED: EventType[EventCategoryRegistryUpdatedData] = (
|
|||||||
)
|
)
|
||||||
STORAGE_KEY = "core.category_registry"
|
STORAGE_KEY = "core.category_registry"
|
||||||
STORAGE_VERSION_MAJOR = 1
|
STORAGE_VERSION_MAJOR = 1
|
||||||
|
STORAGE_VERSION_MINOR = 2
|
||||||
|
|
||||||
|
|
||||||
class _CategoryStoreData(TypedDict):
|
class _CategoryStoreData(TypedDict):
|
||||||
"""Data type for individual category. Used in CategoryRegistryStoreData."""
|
"""Data type for individual category. Used in CategoryRegistryStoreData."""
|
||||||
|
|
||||||
category_id: str
|
category_id: str
|
||||||
|
created_at: str
|
||||||
icon: str | None
|
icon: str | None
|
||||||
|
modified_at: str
|
||||||
name: str
|
name: str
|
||||||
|
|
||||||
|
|
||||||
@ -55,10 +60,36 @@ class CategoryEntry:
|
|||||||
"""Category registry entry."""
|
"""Category registry entry."""
|
||||||
|
|
||||||
category_id: str = field(default_factory=ulid_now)
|
category_id: str = field(default_factory=ulid_now)
|
||||||
|
created_at: datetime = field(default_factory=utcnow)
|
||||||
icon: str | None = None
|
icon: str | None = None
|
||||||
|
modified_at: datetime = field(default_factory=utcnow)
|
||||||
name: str
|
name: str
|
||||||
|
|
||||||
|
|
||||||
|
class CategoryRegistryStore(Store[CategoryRegistryStoreData]):
|
||||||
|
"""Store category registry data."""
|
||||||
|
|
||||||
|
async def _async_migrate_func(
|
||||||
|
self,
|
||||||
|
old_major_version: int,
|
||||||
|
old_minor_version: int,
|
||||||
|
old_data: dict[str, dict[str, list[dict[str, Any]]]],
|
||||||
|
) -> CategoryRegistryStoreData:
|
||||||
|
"""Migrate to the new version."""
|
||||||
|
if old_major_version > STORAGE_VERSION_MAJOR:
|
||||||
|
raise ValueError("Can't migrate to future version")
|
||||||
|
|
||||||
|
if old_major_version == 1:
|
||||||
|
if old_minor_version < 2:
|
||||||
|
# Version 1.2 implements migration and adds created_at and modified_at
|
||||||
|
created_at = utc_from_timestamp(0).isoformat()
|
||||||
|
for categories in old_data["categories"].values():
|
||||||
|
for category in categories:
|
||||||
|
category["created_at"] = category["modified_at"] = created_at
|
||||||
|
|
||||||
|
return old_data # type: ignore[return-value]
|
||||||
|
|
||||||
|
|
||||||
class CategoryRegistry(BaseRegistry[CategoryRegistryStoreData]):
|
class CategoryRegistry(BaseRegistry[CategoryRegistryStoreData]):
|
||||||
"""Class to hold a registry of categories by scope."""
|
"""Class to hold a registry of categories by scope."""
|
||||||
|
|
||||||
@ -66,11 +97,12 @@ class CategoryRegistry(BaseRegistry[CategoryRegistryStoreData]):
|
|||||||
"""Initialize the category registry."""
|
"""Initialize the category registry."""
|
||||||
self.hass = hass
|
self.hass = hass
|
||||||
self.categories: dict[str, dict[str, CategoryEntry]] = {}
|
self.categories: dict[str, dict[str, CategoryEntry]] = {}
|
||||||
self._store = Store(
|
self._store = CategoryRegistryStore(
|
||||||
hass,
|
hass,
|
||||||
STORAGE_VERSION_MAJOR,
|
STORAGE_VERSION_MAJOR,
|
||||||
STORAGE_KEY,
|
STORAGE_KEY,
|
||||||
atomic_writes=True,
|
atomic_writes=True,
|
||||||
|
minor_version=STORAGE_VERSION_MINOR,
|
||||||
)
|
)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
@ -145,7 +177,7 @@ class CategoryRegistry(BaseRegistry[CategoryRegistryStoreData]):
|
|||||||
) -> CategoryEntry:
|
) -> CategoryEntry:
|
||||||
"""Update name or icon of the category."""
|
"""Update name or icon of the category."""
|
||||||
old = self.categories[scope][category_id]
|
old = self.categories[scope][category_id]
|
||||||
changes = {}
|
changes: dict[str, Any] = {}
|
||||||
|
|
||||||
if icon is not UNDEFINED and icon != old.icon:
|
if icon is not UNDEFINED and icon != old.icon:
|
||||||
changes["icon"] = icon
|
changes["icon"] = icon
|
||||||
@ -157,8 +189,10 @@ class CategoryRegistry(BaseRegistry[CategoryRegistryStoreData]):
|
|||||||
if not changes:
|
if not changes:
|
||||||
return old
|
return old
|
||||||
|
|
||||||
|
changes["modified_at"] = utcnow()
|
||||||
|
|
||||||
self.hass.verify_event_loop_thread("category_registry.async_update")
|
self.hass.verify_event_loop_thread("category_registry.async_update")
|
||||||
new = self.categories[scope][category_id] = dataclasses.replace(old, **changes) # type: ignore[arg-type]
|
new = self.categories[scope][category_id] = dataclasses.replace(old, **changes)
|
||||||
|
|
||||||
self.async_schedule_save()
|
self.async_schedule_save()
|
||||||
self.hass.bus.async_fire_internal(
|
self.hass.bus.async_fire_internal(
|
||||||
@ -180,7 +214,9 @@ class CategoryRegistry(BaseRegistry[CategoryRegistryStoreData]):
|
|||||||
category_entries[scope] = {
|
category_entries[scope] = {
|
||||||
category["category_id"]: CategoryEntry(
|
category["category_id"]: CategoryEntry(
|
||||||
category_id=category["category_id"],
|
category_id=category["category_id"],
|
||||||
|
created_at=datetime.fromisoformat(category["created_at"]),
|
||||||
icon=category["icon"],
|
icon=category["icon"],
|
||||||
|
modified_at=datetime.fromisoformat(category["modified_at"]),
|
||||||
name=category["name"],
|
name=category["name"],
|
||||||
)
|
)
|
||||||
for category in categories
|
for category in categories
|
||||||
@ -196,7 +232,9 @@ class CategoryRegistry(BaseRegistry[CategoryRegistryStoreData]):
|
|||||||
scope: [
|
scope: [
|
||||||
{
|
{
|
||||||
"category_id": entry.category_id,
|
"category_id": entry.category_id,
|
||||||
|
"created_at": entry.created_at.isoformat(),
|
||||||
"icon": entry.icon,
|
"icon": entry.icon,
|
||||||
|
"modified_at": entry.modified_at.isoformat(),
|
||||||
"name": entry.name,
|
"name": entry.name,
|
||||||
}
|
}
|
||||||
for entry in entries.values()
|
for entry in entries.values()
|
||||||
|
@ -1,10 +1,14 @@
|
|||||||
"""Test category registry API."""
|
"""Test category registry API."""
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from freezegun.api import FrozenDateTimeFactory
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from homeassistant.components.config import category_registry
|
from homeassistant.components.config import category_registry
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers import category_registry as cr
|
from homeassistant.helpers import category_registry as cr
|
||||||
|
from homeassistant.util.dt import utcnow
|
||||||
|
|
||||||
from tests.common import ANY
|
from tests.common import ANY
|
||||||
from tests.typing import MockHAClientWebSocket, WebSocketGenerator
|
from tests.typing import MockHAClientWebSocket, WebSocketGenerator
|
||||||
@ -19,6 +23,7 @@ async def client_fixture(
|
|||||||
return await hass_ws_client(hass)
|
return await hass_ws_client(hass)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures("freezer")
|
||||||
async def test_list_categories(
|
async def test_list_categories(
|
||||||
client: MockHAClientWebSocket,
|
client: MockHAClientWebSocket,
|
||||||
category_registry: cr.CategoryRegistry,
|
category_registry: cr.CategoryRegistry,
|
||||||
@ -53,11 +58,15 @@ async def test_list_categories(
|
|||||||
assert len(msg["result"]) == 2
|
assert len(msg["result"]) == 2
|
||||||
assert msg["result"][0] == {
|
assert msg["result"][0] == {
|
||||||
"category_id": category1.category_id,
|
"category_id": category1.category_id,
|
||||||
|
"created_at": utcnow().timestamp(),
|
||||||
|
"modified_at": utcnow().timestamp(),
|
||||||
"name": "Energy saving",
|
"name": "Energy saving",
|
||||||
"icon": "mdi:leaf",
|
"icon": "mdi:leaf",
|
||||||
}
|
}
|
||||||
assert msg["result"][1] == {
|
assert msg["result"][1] == {
|
||||||
"category_id": category2.category_id,
|
"category_id": category2.category_id,
|
||||||
|
"created_at": utcnow().timestamp(),
|
||||||
|
"modified_at": utcnow().timestamp(),
|
||||||
"name": "Something else",
|
"name": "Something else",
|
||||||
"icon": "mdi:home",
|
"icon": "mdi:home",
|
||||||
}
|
}
|
||||||
@ -71,6 +80,8 @@ async def test_list_categories(
|
|||||||
assert len(msg["result"]) == 1
|
assert len(msg["result"]) == 1
|
||||||
assert msg["result"][0] == {
|
assert msg["result"][0] == {
|
||||||
"category_id": category3.category_id,
|
"category_id": category3.category_id,
|
||||||
|
"created_at": utcnow().timestamp(),
|
||||||
|
"modified_at": utcnow().timestamp(),
|
||||||
"name": "Grocery stores",
|
"name": "Grocery stores",
|
||||||
"icon": "mdi:store",
|
"icon": "mdi:store",
|
||||||
}
|
}
|
||||||
@ -79,8 +90,11 @@ async def test_list_categories(
|
|||||||
async def test_create_category(
|
async def test_create_category(
|
||||||
client: MockHAClientWebSocket,
|
client: MockHAClientWebSocket,
|
||||||
category_registry: cr.CategoryRegistry,
|
category_registry: cr.CategoryRegistry,
|
||||||
|
freezer: FrozenDateTimeFactory,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test create entry."""
|
"""Test create entry."""
|
||||||
|
created1 = datetime(2024, 2, 14, 12, 0, 0)
|
||||||
|
freezer.move_to(created1)
|
||||||
await client.send_json_auto_id(
|
await client.send_json_auto_id(
|
||||||
{
|
{
|
||||||
"type": "config/category_registry/create",
|
"type": "config/category_registry/create",
|
||||||
@ -98,9 +112,14 @@ async def test_create_category(
|
|||||||
assert msg["result"] == {
|
assert msg["result"] == {
|
||||||
"icon": "mdi:leaf",
|
"icon": "mdi:leaf",
|
||||||
"category_id": ANY,
|
"category_id": ANY,
|
||||||
|
"created_at": created1.timestamp(),
|
||||||
|
"modified_at": created1.timestamp(),
|
||||||
"name": "Energy saving",
|
"name": "Energy saving",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
created2 = datetime(2024, 3, 14, 12, 0, 0)
|
||||||
|
freezer.move_to(created2)
|
||||||
|
|
||||||
await client.send_json_auto_id(
|
await client.send_json_auto_id(
|
||||||
{
|
{
|
||||||
"scope": "automation",
|
"scope": "automation",
|
||||||
@ -117,9 +136,14 @@ async def test_create_category(
|
|||||||
assert msg["result"] == {
|
assert msg["result"] == {
|
||||||
"icon": None,
|
"icon": None,
|
||||||
"category_id": ANY,
|
"category_id": ANY,
|
||||||
|
"created_at": created2.timestamp(),
|
||||||
|
"modified_at": created2.timestamp(),
|
||||||
"name": "Something else",
|
"name": "Something else",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
created3 = datetime(2024, 4, 14, 12, 0, 0)
|
||||||
|
freezer.move_to(created3)
|
||||||
|
|
||||||
# Test adding the same one again in a different scope
|
# Test adding the same one again in a different scope
|
||||||
await client.send_json_auto_id(
|
await client.send_json_auto_id(
|
||||||
{
|
{
|
||||||
@ -139,6 +163,8 @@ async def test_create_category(
|
|||||||
assert msg["result"] == {
|
assert msg["result"] == {
|
||||||
"icon": "mdi:leaf",
|
"icon": "mdi:leaf",
|
||||||
"category_id": ANY,
|
"category_id": ANY,
|
||||||
|
"created_at": created3.timestamp(),
|
||||||
|
"modified_at": created3.timestamp(),
|
||||||
"name": "Energy saving",
|
"name": "Energy saving",
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -249,8 +275,11 @@ async def test_delete_non_existing_category(
|
|||||||
async def test_update_category(
|
async def test_update_category(
|
||||||
client: MockHAClientWebSocket,
|
client: MockHAClientWebSocket,
|
||||||
category_registry: cr.CategoryRegistry,
|
category_registry: cr.CategoryRegistry,
|
||||||
|
freezer: FrozenDateTimeFactory,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test update entry."""
|
"""Test update entry."""
|
||||||
|
created = datetime(2024, 2, 14, 12, 0, 0)
|
||||||
|
freezer.move_to(created)
|
||||||
category = category_registry.async_create(
|
category = category_registry.async_create(
|
||||||
scope="automation",
|
scope="automation",
|
||||||
name="Energy saving",
|
name="Energy saving",
|
||||||
@ -258,6 +287,9 @@ async def test_update_category(
|
|||||||
assert len(category_registry.categories) == 1
|
assert len(category_registry.categories) == 1
|
||||||
assert len(category_registry.categories["automation"]) == 1
|
assert len(category_registry.categories["automation"]) == 1
|
||||||
|
|
||||||
|
modified = datetime(2024, 3, 14, 12, 0, 0)
|
||||||
|
freezer.move_to(modified)
|
||||||
|
|
||||||
await client.send_json_auto_id(
|
await client.send_json_auto_id(
|
||||||
{
|
{
|
||||||
"scope": "automation",
|
"scope": "automation",
|
||||||
@ -275,9 +307,14 @@ async def test_update_category(
|
|||||||
assert msg["result"] == {
|
assert msg["result"] == {
|
||||||
"icon": "mdi:left",
|
"icon": "mdi:left",
|
||||||
"category_id": category.category_id,
|
"category_id": category.category_id,
|
||||||
|
"created_at": created.timestamp(),
|
||||||
|
"modified_at": modified.timestamp(),
|
||||||
"name": "ENERGY SAVING",
|
"name": "ENERGY SAVING",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
modified = datetime(2024, 4, 14, 12, 0, 0)
|
||||||
|
freezer.move_to(modified)
|
||||||
|
|
||||||
await client.send_json_auto_id(
|
await client.send_json_auto_id(
|
||||||
{
|
{
|
||||||
"scope": "automation",
|
"scope": "automation",
|
||||||
@ -295,6 +332,8 @@ async def test_update_category(
|
|||||||
assert msg["result"] == {
|
assert msg["result"] == {
|
||||||
"icon": None,
|
"icon": None,
|
||||||
"category_id": category.category_id,
|
"category_id": category.category_id,
|
||||||
|
"created_at": created.timestamp(),
|
||||||
|
"modified_at": modified.timestamp(),
|
||||||
"name": "Energy saving",
|
"name": "Energy saving",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,13 +1,16 @@
|
|||||||
"""Tests for the category registry."""
|
"""Tests for the category registry."""
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
from functools import partial
|
from functools import partial
|
||||||
import re
|
import re
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
from freezegun.api import FrozenDateTimeFactory
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers import category_registry as cr
|
from homeassistant.helpers import category_registry as cr
|
||||||
|
from homeassistant.util.dt import UTC
|
||||||
|
|
||||||
from tests.common import async_capture_events, flush_store
|
from tests.common import async_capture_events, flush_store
|
||||||
|
|
||||||
@ -152,9 +155,13 @@ async def test_delete_non_existing_category(
|
|||||||
|
|
||||||
|
|
||||||
async def test_update_category(
|
async def test_update_category(
|
||||||
hass: HomeAssistant, category_registry: cr.CategoryRegistry
|
hass: HomeAssistant,
|
||||||
|
category_registry: cr.CategoryRegistry,
|
||||||
|
freezer: FrozenDateTimeFactory,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Make sure that we can update categories."""
|
"""Make sure that we can update categories."""
|
||||||
|
created = datetime(2024, 2, 14, 12, 0, 0, tzinfo=UTC)
|
||||||
|
freezer.move_to(created)
|
||||||
update_events = async_capture_events(hass, cr.EVENT_CATEGORY_REGISTRY_UPDATED)
|
update_events = async_capture_events(hass, cr.EVENT_CATEGORY_REGISTRY_UPDATED)
|
||||||
category = category_registry.async_create(
|
category = category_registry.async_create(
|
||||||
scope="automation",
|
scope="automation",
|
||||||
@ -162,9 +169,16 @@ async def test_update_category(
|
|||||||
)
|
)
|
||||||
|
|
||||||
assert len(category_registry.categories["automation"]) == 1
|
assert len(category_registry.categories["automation"]) == 1
|
||||||
assert category.category_id
|
assert category == cr.CategoryEntry(
|
||||||
assert category.name == "Energy saving"
|
category_id=category.category_id,
|
||||||
assert category.icon is None
|
created_at=created,
|
||||||
|
modified_at=created,
|
||||||
|
name="Energy saving",
|
||||||
|
icon=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
modified = datetime(2024, 3, 14, 12, 0, 0, tzinfo=UTC)
|
||||||
|
freezer.move_to(modified)
|
||||||
|
|
||||||
updated_category = category_registry.async_update(
|
updated_category = category_registry.async_update(
|
||||||
scope="automation",
|
scope="automation",
|
||||||
@ -174,9 +188,13 @@ async def test_update_category(
|
|||||||
)
|
)
|
||||||
|
|
||||||
assert updated_category != category
|
assert updated_category != category
|
||||||
assert updated_category.category_id == category.category_id
|
assert updated_category == cr.CategoryEntry(
|
||||||
assert updated_category.name == "ENERGY SAVING"
|
category_id=category.category_id,
|
||||||
assert updated_category.icon == "mdi:leaf"
|
created_at=created,
|
||||||
|
modified_at=modified,
|
||||||
|
name="ENERGY SAVING",
|
||||||
|
icon="mdi:leaf",
|
||||||
|
)
|
||||||
|
|
||||||
assert len(category_registry.categories["automation"]) == 1
|
assert len(category_registry.categories["automation"]) == 1
|
||||||
|
|
||||||
@ -343,18 +361,25 @@ async def test_loading_categories_from_storage(
|
|||||||
hass: HomeAssistant, hass_storage: dict[str, Any]
|
hass: HomeAssistant, hass_storage: dict[str, Any]
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test loading stored categories on start."""
|
"""Test loading stored categories on start."""
|
||||||
|
date_1 = datetime(2024, 2, 14, 12, 0, 0)
|
||||||
|
date_2 = datetime(2024, 2, 14, 12, 0, 0)
|
||||||
hass_storage[cr.STORAGE_KEY] = {
|
hass_storage[cr.STORAGE_KEY] = {
|
||||||
"version": cr.STORAGE_VERSION_MAJOR,
|
"version": cr.STORAGE_VERSION_MAJOR,
|
||||||
|
"minor_version": cr.STORAGE_VERSION_MINOR,
|
||||||
"data": {
|
"data": {
|
||||||
"categories": {
|
"categories": {
|
||||||
"automation": [
|
"automation": [
|
||||||
{
|
{
|
||||||
"category_id": "uuid1",
|
"category_id": "uuid1",
|
||||||
|
"created_at": date_1.isoformat(),
|
||||||
|
"modified_at": date_1.isoformat(),
|
||||||
"name": "Energy saving",
|
"name": "Energy saving",
|
||||||
"icon": "mdi:leaf",
|
"icon": "mdi:leaf",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"category_id": "uuid2",
|
"category_id": "uuid2",
|
||||||
|
"created_at": date_1.isoformat(),
|
||||||
|
"modified_at": date_2.isoformat(),
|
||||||
"name": "Something else",
|
"name": "Something else",
|
||||||
"icon": None,
|
"icon": None,
|
||||||
},
|
},
|
||||||
@ -362,6 +387,8 @@ async def test_loading_categories_from_storage(
|
|||||||
"zone": [
|
"zone": [
|
||||||
{
|
{
|
||||||
"category_id": "uuid3",
|
"category_id": "uuid3",
|
||||||
|
"created_at": date_2.isoformat(),
|
||||||
|
"modified_at": date_2.isoformat(),
|
||||||
"name": "Grocery stores",
|
"name": "Grocery stores",
|
||||||
"icon": "mdi:store",
|
"icon": "mdi:store",
|
||||||
},
|
},
|
||||||
@ -380,21 +407,33 @@ async def test_loading_categories_from_storage(
|
|||||||
category1 = category_registry.async_get_category(
|
category1 = category_registry.async_get_category(
|
||||||
scope="automation", category_id="uuid1"
|
scope="automation", category_id="uuid1"
|
||||||
)
|
)
|
||||||
assert category1.category_id == "uuid1"
|
assert category1 == cr.CategoryEntry(
|
||||||
assert category1.name == "Energy saving"
|
category_id="uuid1",
|
||||||
assert category1.icon == "mdi:leaf"
|
created_at=date_1,
|
||||||
|
modified_at=date_1,
|
||||||
|
name="Energy saving",
|
||||||
|
icon="mdi:leaf",
|
||||||
|
)
|
||||||
|
|
||||||
category2 = category_registry.async_get_category(
|
category2 = category_registry.async_get_category(
|
||||||
scope="automation", category_id="uuid2"
|
scope="automation", category_id="uuid2"
|
||||||
)
|
)
|
||||||
assert category2.category_id == "uuid2"
|
assert category2 == cr.CategoryEntry(
|
||||||
assert category2.name == "Something else"
|
category_id="uuid2",
|
||||||
assert category2.icon is None
|
created_at=date_1,
|
||||||
|
modified_at=date_2,
|
||||||
|
name="Something else",
|
||||||
|
icon=None,
|
||||||
|
)
|
||||||
|
|
||||||
category3 = category_registry.async_get_category(scope="zone", category_id="uuid3")
|
category3 = category_registry.async_get_category(scope="zone", category_id="uuid3")
|
||||||
assert category3.category_id == "uuid3"
|
assert category3 == cr.CategoryEntry(
|
||||||
assert category3.name == "Grocery stores"
|
category_id="uuid3",
|
||||||
assert category3.icon == "mdi:store"
|
created_at=date_2,
|
||||||
|
modified_at=date_2,
|
||||||
|
name="Grocery stores",
|
||||||
|
icon="mdi:store",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def test_async_create_thread_safety(
|
async def test_async_create_thread_safety(
|
||||||
@ -447,3 +486,83 @@ async def test_async_update_thread_safety(
|
|||||||
name="new name",
|
name="new name",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("load_registries", [False])
|
||||||
|
async def test_migration_from_1_1(
|
||||||
|
hass: HomeAssistant, hass_storage: dict[str, Any]
|
||||||
|
) -> None:
|
||||||
|
"""Test migration from version 1.1."""
|
||||||
|
hass_storage[cr.STORAGE_KEY] = {
|
||||||
|
"version": 1,
|
||||||
|
"data": {
|
||||||
|
"categories": {
|
||||||
|
"automation": [
|
||||||
|
{
|
||||||
|
"category_id": "uuid1",
|
||||||
|
"name": "Energy saving",
|
||||||
|
"icon": "mdi:leaf",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"category_id": "uuid2",
|
||||||
|
"name": "Something else",
|
||||||
|
"icon": None,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"zone": [
|
||||||
|
{
|
||||||
|
"category_id": "uuid3",
|
||||||
|
"name": "Grocery stores",
|
||||||
|
"icon": "mdi:store",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
await cr.async_load(hass)
|
||||||
|
registry = cr.async_get(hass)
|
||||||
|
|
||||||
|
# Test data was loaded
|
||||||
|
assert len(registry.categories) == 2
|
||||||
|
assert len(registry.categories["automation"]) == 2
|
||||||
|
assert len(registry.categories["zone"]) == 1
|
||||||
|
|
||||||
|
assert registry.async_get_category(scope="automation", category_id="uuid1")
|
||||||
|
|
||||||
|
# Check we store migrated data
|
||||||
|
await flush_store(registry._store)
|
||||||
|
assert hass_storage[cr.STORAGE_KEY] == {
|
||||||
|
"version": cr.STORAGE_VERSION_MAJOR,
|
||||||
|
"minor_version": cr.STORAGE_VERSION_MINOR,
|
||||||
|
"key": cr.STORAGE_KEY,
|
||||||
|
"data": {
|
||||||
|
"categories": {
|
||||||
|
"automation": [
|
||||||
|
{
|
||||||
|
"category_id": "uuid1",
|
||||||
|
"created_at": "1970-01-01T00:00:00+00:00",
|
||||||
|
"modified_at": "1970-01-01T00:00:00+00:00",
|
||||||
|
"name": "Energy saving",
|
||||||
|
"icon": "mdi:leaf",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"category_id": "uuid2",
|
||||||
|
"created_at": "1970-01-01T00:00:00+00:00",
|
||||||
|
"modified_at": "1970-01-01T00:00:00+00:00",
|
||||||
|
"name": "Something else",
|
||||||
|
"icon": None,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"zone": [
|
||||||
|
{
|
||||||
|
"category_id": "uuid3",
|
||||||
|
"created_at": "1970-01-01T00:00:00+00:00",
|
||||||
|
"modified_at": "1970-01-01T00:00:00+00:00",
|
||||||
|
"name": "Grocery stores",
|
||||||
|
"icon": "mdi:store",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user