diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 7bce2ab5c01890..360f98032570a9 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -24,11 +24,11 @@ jobs: uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - name: Initialize CodeQL - uses: github/codeql-action/init@014f16e7ab1402f30e7c3329d33797e7948572db # v4.31.3 + uses: github/codeql-action/init@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v4.31.4 with: languages: python - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@014f16e7ab1402f30e7c3329d33797e7948572db # v4.31.3 + uses: github/codeql-action/analyze@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v4.31.4 with: category: "/language:python" diff --git a/homeassistant/components/brother/entity.py b/homeassistant/components/brother/entity.py index 827dac4957581e..0b8ddc988a1d6d 100644 --- a/homeassistant/components/brother/entity.py +++ b/homeassistant/components/brother/entity.py @@ -24,7 +24,7 @@ def __init__( connections={(CONNECTION_NETWORK_MAC, coordinator.brother.mac)}, serial_number=coordinator.brother.serial, manufacturer="Brother", - model=coordinator.brother.model, + model_id=coordinator.brother.model, name=coordinator.brother.model, sw_version=coordinator.brother.firmware, ) diff --git a/homeassistant/components/brother/sensor.py b/homeassistant/components/brother/sensor.py index 873e734b1d6cbd..dda4231dd30960 100644 --- a/homeassistant/components/brother/sensor.py +++ b/homeassistant/components/brother/sensor.py @@ -17,7 +17,7 @@ SensorStateClass, ) from homeassistant.const import PERCENTAGE, EntityCategory -from homeassistant.core import HomeAssistant, callback +from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from homeassistant.helpers.typing import StateType @@ -345,12 +345,10 @@ def __init__( """Initialize.""" super().__init__(coordinator) - self._attr_native_value = description.value(coordinator.data) self._attr_unique_id = f"{coordinator.brother.serial.lower()}_{description.key}" self.entity_description = description - @callback - def _handle_coordinator_update(self) -> None: - """Handle updated data from the coordinator.""" - self._attr_native_value = self.entity_description.value(self.coordinator.data) - self.async_write_ha_state() + @property + def native_value(self) -> StateType | datetime: + """Return the native value of the sensor.""" + return self.entity_description.value(self.coordinator.data) diff --git a/homeassistant/components/config/area_registry.py b/homeassistant/components/config/area_registry.py index b2a590928c1733..f97bb7ddd8a4e3 100644 --- a/homeassistant/components/config/area_registry.py +++ b/homeassistant/components/config/area_registry.py @@ -18,6 +18,7 @@ def async_setup(hass: HomeAssistant) -> bool: websocket_api.async_register_command(hass, websocket_create_area) websocket_api.async_register_command(hass, websocket_delete_area) websocket_api.async_register_command(hass, websocket_update_area) + websocket_api.async_register_command(hass, websocket_reorder_areas) return True @@ -145,3 +146,27 @@ def websocket_update_area( connection.send_error(msg["id"], "invalid_info", str(err)) else: connection.send_result(msg["id"], entry.json_fragment) + + +@websocket_api.websocket_command( + { + vol.Required("type"): "config/area_registry/reorder", + vol.Required("area_ids"): [str], + } +) +@websocket_api.require_admin +@callback +def websocket_reorder_areas( + hass: HomeAssistant, + connection: websocket_api.ActiveConnection, + msg: dict[str, Any], +) -> None: + """Handle reorder areas websocket command.""" + registry = ar.async_get(hass) + + try: + registry.async_reorder(msg["area_ids"]) + except ValueError as err: + connection.send_error(msg["id"], websocket_api.ERR_INVALID_FORMAT, str(err)) + else: + connection.send_result(msg["id"]) diff --git a/homeassistant/components/config/floor_registry.py b/homeassistant/components/config/floor_registry.py index afa74e7f9b8478..f33051dfc7fcac 100644 --- a/homeassistant/components/config/floor_registry.py +++ b/homeassistant/components/config/floor_registry.py @@ -18,6 +18,7 @@ def async_setup(hass: HomeAssistant) -> bool: websocket_api.async_register_command(hass, websocket_create_floor) websocket_api.async_register_command(hass, websocket_delete_floor) websocket_api.async_register_command(hass, websocket_update_floor) + websocket_api.async_register_command(hass, websocket_reorder_floors) return True @@ -127,6 +128,28 @@ def websocket_update_floor( connection.send_result(msg["id"], _entry_dict(entry)) +@websocket_api.websocket_command( + { + vol.Required("type"): "config/floor_registry/reorder", + vol.Required("floor_ids"): [str], + } +) +@websocket_api.require_admin +@callback +def websocket_reorder_floors( + hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any] +) -> None: + """Handle reorder floors websocket command.""" + registry = fr.async_get(hass) + + try: + registry.async_reorder(msg["floor_ids"]) + except ValueError as err: + connection.send_error(msg["id"], websocket_api.ERR_INVALID_FORMAT, str(err)) + else: + connection.send_result(msg["id"]) + + @callback def _entry_dict(entry: FloorEntry) -> dict[str, Any]: """Convert entry to API format.""" diff --git a/homeassistant/components/frontend/storage.py b/homeassistant/components/frontend/storage.py index 11d155dbcb4a99..aa1ce27e3cd020 100644 --- a/homeassistant/components/frontend/storage.py +++ b/homeassistant/components/frontend/storage.py @@ -11,11 +11,14 @@ from homeassistant.components import websocket_api from homeassistant.components.websocket_api import ActiveConnection from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers import singleton from homeassistant.helpers.storage import Store from homeassistant.util.hass_dict import HassKey DATA_STORAGE: HassKey[dict[str, UserStore]] = HassKey("frontend_storage") +DATA_SYSTEM_STORAGE: HassKey[SystemStore] = HassKey("frontend_system_storage") STORAGE_VERSION_USER_DATA = 1 +STORAGE_VERSION_SYSTEM_DATA = 1 async def async_setup_frontend_storage(hass: HomeAssistant) -> None: @@ -23,6 +26,9 @@ async def async_setup_frontend_storage(hass: HomeAssistant) -> None: websocket_api.async_register_command(hass, websocket_set_user_data) websocket_api.async_register_command(hass, websocket_get_user_data) websocket_api.async_register_command(hass, websocket_subscribe_user_data) + websocket_api.async_register_command(hass, websocket_set_system_data) + websocket_api.async_register_command(hass, websocket_get_system_data) + websocket_api.async_register_command(hass, websocket_subscribe_system_data) async def async_user_store(hass: HomeAssistant, user_id: str) -> UserStore: @@ -83,6 +89,52 @@ def __init__(self, hass: HomeAssistant, user_id: str) -> None: ) +@singleton.singleton(DATA_SYSTEM_STORAGE, async_=True) +async def async_system_store(hass: HomeAssistant) -> SystemStore: + """Access the system store.""" + store = SystemStore(hass) + await store.async_load() + return store + + +class SystemStore: + """System store for frontend data.""" + + def __init__(self, hass: HomeAssistant) -> None: + """Initialize the system store.""" + self._store: Store[dict[str, Any]] = Store( + hass, + STORAGE_VERSION_SYSTEM_DATA, + "frontend.system_data", + ) + self.data: dict[str, Any] = {} + self.subscriptions: dict[str, list[Callable[[], None]]] = {} + + async def async_load(self) -> None: + """Load the data from the store.""" + self.data = await self._store.async_load() or {} + + async def async_set_item(self, key: str, value: Any) -> None: + """Set an item and save the store.""" + self.data[key] = value + self._store.async_delay_save(lambda: self.data, 1.0) + for cb in self.subscriptions.get(key, []): + cb() + + @callback + def async_subscribe( + self, key: str, on_update_callback: Callable[[], None] + ) -> Callable[[], None]: + """Subscribe to store updates.""" + self.subscriptions.setdefault(key, []).append(on_update_callback) + + def unsubscribe() -> None: + """Unsubscribe from the store.""" + self.subscriptions[key].remove(on_update_callback) + + return unsubscribe + + def with_user_store( orig_func: Callable[ [HomeAssistant, ActiveConnection, dict[str, Any], UserStore], @@ -107,6 +159,28 @@ async def with_user_store_func( return with_user_store_func +def with_system_store( + orig_func: Callable[ + [HomeAssistant, ActiveConnection, dict[str, Any], SystemStore], + Coroutine[Any, Any, None], + ], +) -> Callable[ + [HomeAssistant, ActiveConnection, dict[str, Any]], Coroutine[Any, Any, None] +]: + """Decorate function to provide system store.""" + + @wraps(orig_func) + async def with_system_store_func( + hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any] + ) -> None: + """Provide system store to function.""" + store = await async_system_store(hass) + + await orig_func(hass, connection, msg, store) + + return with_system_store_func + + @websocket_api.websocket_command( { vol.Required("type"): "frontend/set_user_data", @@ -169,3 +243,65 @@ def on_data_update() -> None: connection.subscriptions[msg["id"]] = store.async_subscribe(key, on_data_update) on_data_update() connection.send_result(msg["id"]) + + +@websocket_api.websocket_command( + { + vol.Required("type"): "frontend/set_system_data", + vol.Required("key"): str, + vol.Required("value"): vol.Any(bool, str, int, float, dict, list, None), + } +) +@websocket_api.require_admin +@websocket_api.async_response +@with_system_store +async def websocket_set_system_data( + hass: HomeAssistant, + connection: ActiveConnection, + msg: dict[str, Any], + store: SystemStore, +) -> None: + """Handle set system data command.""" + await store.async_set_item(msg["key"], msg["value"]) + connection.send_result(msg["id"]) + + +@websocket_api.websocket_command( + {vol.Required("type"): "frontend/get_system_data", vol.Required("key"): str} +) +@websocket_api.async_response +@with_system_store +async def websocket_get_system_data( + hass: HomeAssistant, + connection: ActiveConnection, + msg: dict[str, Any], + store: SystemStore, +) -> None: + """Handle get system data command.""" + connection.send_result(msg["id"], {"value": store.data.get(msg["key"])}) + + +@websocket_api.websocket_command( + { + vol.Required("type"): "frontend/subscribe_system_data", + vol.Required("key"): str, + } +) +@websocket_api.async_response +@with_system_store +async def websocket_subscribe_system_data( + hass: HomeAssistant, + connection: ActiveConnection, + msg: dict[str, Any], + store: SystemStore, +) -> None: + """Handle subscribe to system data command.""" + key: str = msg["key"] + + def on_data_update() -> None: + """Handle system data update.""" + connection.send_event(msg["id"], {"value": store.data.get(key)}) + + connection.subscriptions[msg["id"]] = store.async_subscribe(key, on_data_update) + on_data_update() + connection.send_result(msg["id"]) diff --git a/homeassistant/components/homeassistant_hardware/manifest.json b/homeassistant/components/homeassistant_hardware/manifest.json index 6911dbd5eefa94..552302a45ec7d8 100644 --- a/homeassistant/components/homeassistant_hardware/manifest.json +++ b/homeassistant/components/homeassistant_hardware/manifest.json @@ -7,7 +7,7 @@ "documentation": "https://www.home-assistant.io/integrations/homeassistant_hardware", "integration_type": "system", "requirements": [ - "universal-silabs-flasher==0.1.0", + "universal-silabs-flasher==0.1.2", "ha-silabs-firmware-client==0.3.0" ] } diff --git a/homeassistant/components/lamarzocco/coordinator.py b/homeassistant/components/lamarzocco/coordinator.py index b5fa0ed9028f54..31ce0d275b09c6 100644 --- a/homeassistant/components/lamarzocco/coordinator.py +++ b/homeassistant/components/lamarzocco/coordinator.py @@ -3,6 +3,7 @@ from __future__ import annotations from abc import abstractmethod +from asyncio import Task from dataclasses import dataclass from datetime import timedelta import logging @@ -44,7 +45,7 @@ class LaMarzoccoUpdateCoordinator(DataUpdateCoordinator[None]): _default_update_interval = SCAN_INTERVAL config_entry: LaMarzoccoConfigEntry - websocket_terminated = True + _websocket_task: Task | None = None def __init__( self, @@ -64,6 +65,13 @@ def __init__( self.device = device self.cloud_client = cloud_client + @property + def websocket_terminated(self) -> bool: + """Return True if the websocket task is terminated or not running.""" + if self._websocket_task is None: + return True + return self._websocket_task.done() + async def _async_update_data(self) -> None: """Do the data update.""" try: @@ -95,13 +103,14 @@ async def _internal_async_update_data(self) -> None: # ensure token stays valid; does nothing if token is still valid await self.cloud_client.async_get_access_token() - if self.device.websocket.connected: + # Only skip websocket reconnection if it's currently connected and the task is still running + if self.device.websocket.connected and not self.websocket_terminated: return await self.device.get_dashboard() _LOGGER.debug("Current status: %s", self.device.dashboard.to_dict()) - self.config_entry.async_create_background_task( + self._websocket_task = self.config_entry.async_create_background_task( hass=self.hass, target=self.connect_websocket(), name="lm_websocket_task", @@ -120,7 +129,6 @@ async def connect_websocket(self) -> None: _LOGGER.debug("Init WebSocket in background task") - self.websocket_terminated = False self.async_update_listeners() await self.device.connect_dashboard_websocket( @@ -129,7 +137,6 @@ async def connect_websocket(self) -> None: disconnect_callback=self.async_update_listeners, ) - self.websocket_terminated = True self.async_update_listeners() diff --git a/homeassistant/components/onedrive/manifest.json b/homeassistant/components/onedrive/manifest.json index d40fe4b5924115..df861f99751bca 100644 --- a/homeassistant/components/onedrive/manifest.json +++ b/homeassistant/components/onedrive/manifest.json @@ -10,5 +10,5 @@ "iot_class": "cloud_polling", "loggers": ["onedrive_personal_sdk"], "quality_scale": "platinum", - "requirements": ["onedrive-personal-sdk==0.0.16"] + "requirements": ["onedrive-personal-sdk==0.0.17"] } diff --git a/homeassistant/components/tuya/sensor.py b/homeassistant/components/tuya/sensor.py index 507db996df7c01..95557de6bf25f5 100644 --- a/homeassistant/components/tuya/sensor.py +++ b/homeassistant/components/tuya/sensor.py @@ -851,11 +851,16 @@ class TuyaSensorEntityDescription(SensorEntityDescription): key=DPCode.EXCRETION_TIME_DAY, translation_key="excretion_time_day", device_class=SensorDeviceClass.DURATION, + state_class=SensorStateClass.MEASUREMENT, ), TuyaSensorEntityDescription( key=DPCode.EXCRETION_TIMES_DAY, translation_key="excretion_times_day", ), + TuyaSensorEntityDescription( + key=DPCode.STATUS, + translation_key="cat_litter_box_status", + ), ), DeviceCategory.MZJ: ( TuyaSensorEntityDescription( diff --git a/homeassistant/components/tuya/strings.json b/homeassistant/components/tuya/strings.json index 7fa7d6e136f14e..08c0c2c6f83267 100644 --- a/homeassistant/components/tuya/strings.json +++ b/homeassistant/components/tuya/strings.json @@ -617,6 +617,17 @@ "carbon_monoxide": { "name": "[%key:component::sensor::entity_component::carbon_monoxide::name%]" }, + "cat_litter_box_status": { + "name": "[%key:component::tuya::entity::sensor::status::name%]", + "state": { + "clean": "Cleaning", + "empty": "Emptying", + "level": "Leveling", + "sleep": "Sleeping", + "standby": "[%key:common::state::standby%]", + "uv": "Sanitizing" + } + }, "cat_weight": { "name": "Cat weight" }, @@ -645,7 +656,7 @@ "name": "Duster cloth lifetime" }, "excretion_time_day": { - "name": "Excretion time (day)" + "name": "Excretion duration" }, "excretion_times_day": { "name": "Excretion times (day)" diff --git a/homeassistant/components/unifi/manifest.json b/homeassistant/components/unifi/manifest.json index 0f2750ca5dbe72..f9954e9743efb1 100644 --- a/homeassistant/components/unifi/manifest.json +++ b/homeassistant/components/unifi/manifest.json @@ -7,7 +7,7 @@ "integration_type": "hub", "iot_class": "local_push", "loggers": ["aiounifi"], - "requirements": ["aiounifi==87"], + "requirements": ["aiounifi==88"], "ssdp": [ { "manufacturer": "Ubiquiti Networks", diff --git a/homeassistant/helpers/area_registry.py b/homeassistant/helpers/area_registry.py index 75fabc81696a9a..8fb3d59acd5725 100644 --- a/homeassistant/helpers/area_registry.py +++ b/homeassistant/helpers/area_registry.py @@ -68,8 +68,8 @@ class AreasRegistryStoreData(TypedDict): class EventAreaRegistryUpdatedData(TypedDict): """EventAreaRegistryUpdated data.""" - action: Literal["create", "remove", "update"] - area_id: str + action: Literal["create", "remove", "update", "reorder"] + area_id: str | None @dataclass(frozen=True, kw_only=True, slots=True) @@ -420,6 +420,26 @@ def _async_update( self.async_schedule_save() return new + @callback + def async_reorder(self, area_ids: list[str]) -> None: + """Reorder areas.""" + self.hass.verify_event_loop_thread("area_registry.async_reorder") + + if set(area_ids) != set(self.areas.data.keys()): + raise ValueError( + "The area_ids list must contain all existing area IDs exactly once" + ) + + reordered_data = {area_id: self.areas.data[area_id] for area_id in area_ids} + self.areas.data.clear() + self.areas.data.update(reordered_data) + + self.async_schedule_save() + self.hass.bus.async_fire_internal( + EVENT_AREA_REGISTRY_UPDATED, + EventAreaRegistryUpdatedData(action="reorder", area_id=None), + ) + async def async_load(self) -> None: """Load the area registry.""" self._async_setup_cleanup() @@ -489,7 +509,9 @@ def _removed_from_registry_filter( @callback def _handle_floor_registry_update(event: fr.EventFloorRegistryUpdated) -> None: """Update areas that are associated with a floor that has been removed.""" - floor_id = event.data["floor_id"] + floor_id = event.data.get("floor_id") + if floor_id is None: + return for area in self.areas.get_areas_for_floor(floor_id): self.async_update(area.id, floor_id=None) diff --git a/homeassistant/helpers/floor_registry.py b/homeassistant/helpers/floor_registry.py index 8578d85a3d32a0..56f4df2e581756 100644 --- a/homeassistant/helpers/floor_registry.py +++ b/homeassistant/helpers/floor_registry.py @@ -54,8 +54,8 @@ class FloorRegistryStoreData(TypedDict): class EventFloorRegistryUpdatedData(TypedDict): """Event data for when the floor registry is updated.""" - action: Literal["create", "remove", "update"] - floor_id: str + action: Literal["create", "remove", "update", "reorder"] + floor_id: str | None type EventFloorRegistryUpdated = Event[EventFloorRegistryUpdatedData] @@ -261,6 +261,28 @@ def async_update( return new + @callback + def async_reorder(self, floor_ids: list[str]) -> None: + """Reorder floors.""" + self.hass.verify_event_loop_thread("floor_registry.async_reorder") + + if set(floor_ids) != set(self.floors.data.keys()): + raise ValueError( + "The floor_ids list must contain all existing floor IDs exactly once" + ) + + reordered_data = { + floor_id: self.floors.data[floor_id] for floor_id in floor_ids + } + self.floors.data.clear() + self.floors.data.update(reordered_data) + + self.async_schedule_save() + self.hass.bus.async_fire_internal( + EVENT_FLOOR_REGISTRY_UPDATED, + EventFloorRegistryUpdatedData(action="reorder", floor_id=None), + ) + async def async_load(self) -> None: """Load the floor registry.""" data = await self._store.async_load() diff --git a/requirements_all.txt b/requirements_all.txt index 2547e066313754..55b9adc94ee91f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -422,7 +422,7 @@ aiotedee==0.2.25 aiotractive==0.6.0 # homeassistant.components.unifi -aiounifi==87 +aiounifi==88 # homeassistant.components.usb aiousbwatcher==1.1.1 @@ -1613,7 +1613,7 @@ omnilogic==0.4.5 ondilo==0.5.0 # homeassistant.components.onedrive -onedrive-personal-sdk==0.0.16 +onedrive-personal-sdk==0.0.17 # homeassistant.components.onvif onvif-zeep-async==4.0.4 @@ -3054,7 +3054,7 @@ unifi_ap==0.0.2 unifiled==0.11 # homeassistant.components.homeassistant_hardware -universal-silabs-flasher==0.1.0 +universal-silabs-flasher==0.1.2 # homeassistant.components.upb upb-lib==0.6.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 4d07c43d80a667..eb6a59374aa268 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -404,7 +404,7 @@ aiotedee==0.2.25 aiotractive==0.6.0 # homeassistant.components.unifi -aiounifi==87 +aiounifi==88 # homeassistant.components.usb aiousbwatcher==1.1.1 @@ -1384,7 +1384,7 @@ omnilogic==0.4.5 ondilo==0.5.0 # homeassistant.components.onedrive -onedrive-personal-sdk==0.0.16 +onedrive-personal-sdk==0.0.17 # homeassistant.components.onvif onvif-zeep-async==4.0.4 @@ -2521,7 +2521,7 @@ ultraheat-api==0.5.7 unifi-discovery==1.2.0 # homeassistant.components.homeassistant_hardware -universal-silabs-flasher==0.1.0 +universal-silabs-flasher==0.1.2 # homeassistant.components.upb upb-lib==0.6.1 diff --git a/tests/components/brother/__init__.py b/tests/components/brother/__init__.py index 7b4e937a9f8743..ba68fa829735ee 100644 --- a/tests/components/brother/__init__.py +++ b/tests/components/brother/__init__.py @@ -5,9 +5,7 @@ from tests.common import MockConfigEntry -async def init_integration( - hass: HomeAssistant, entry: MockConfigEntry -) -> MockConfigEntry: +async def init_integration(hass: HomeAssistant, entry: MockConfigEntry) -> None: """Set up the Brother integration in Home Assistant.""" entry.add_to_hass(hass) diff --git a/tests/components/brother/conftest.py b/tests/components/brother/conftest.py index 09860f5a0db12f..9fd226df8a7bc2 100644 --- a/tests/components/brother/conftest.py +++ b/tests/components/brother/conftest.py @@ -90,15 +90,6 @@ def mock_setup_entry() -> Generator[AsyncMock]: yield mock_setup_entry -@pytest.fixture -def mock_unload_entry() -> Generator[AsyncMock]: - """Override async_unload_entry.""" - with patch( - "homeassistant.components.brother.async_unload_entry", return_value=True - ) as mock_unload_entry: - yield mock_unload_entry - - @pytest.fixture def mock_brother() -> Generator[AsyncMock]: """Mock the Brother class.""" diff --git a/tests/components/brother/test_config_flow.py b/tests/components/brother/test_config_flow.py index 68ea3b57af19bb..41d2743263a4b5 100644 --- a/tests/components/brother/test_config_flow.py +++ b/tests/components/brother/test_config_flow.py @@ -1,7 +1,7 @@ """Define tests for the Brother Printer config flow.""" from ipaddress import ip_address -from unittest.mock import AsyncMock, patch +from unittest.mock import AsyncMock from brother import SnmpError, UnsupportedModelError import pytest @@ -27,17 +27,7 @@ SECTION_ADVANCED_SETTINGS: {CONF_PORT: 161, CONF_COMMUNITY: "public"}, } -pytestmark = pytest.mark.usefixtures("mock_setup_entry", "mock_unload_entry") - - -async def test_show_form(hass: HomeAssistant) -> None: - """Test that the form is served with no input.""" - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_USER} - ) - - assert result["type"] is FlowResultType.FORM - assert result["step_id"] == "user" +pytestmark = pytest.mark.usefixtures("mock_setup_entry") @pytest.mark.parametrize("host", ["example.local", "127.0.0.1", "2001:db8::1428:57ab"]) @@ -49,9 +39,15 @@ async def test_create_entry( config[CONF_HOST] = host result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": SOURCE_USER}, - data=config, + DOMAIN, context={"source": SOURCE_USER} + ) + + assert result["type"] is FlowResultType.FORM + assert result["step_id"] == "user" + + result = await hass.config_entries.flow.async_configure( + flow_id=result["flow_id"], + user_input=config, ) assert result["type"] is FlowResultType.CREATE_ENTRY @@ -60,6 +56,7 @@ async def test_create_entry( assert result["data"][CONF_TYPE] == "laser" assert result["data"][SECTION_ADVANCED_SETTINGS][CONF_PORT] == 161 assert result["data"][SECTION_ADVANCED_SETTINGS][CONF_COMMUNITY] == "public" + assert result["result"].unique_id == "0123456789" async def test_invalid_hostname( @@ -97,6 +94,7 @@ async def test_invalid_hostname( assert result["data"][CONF_TYPE] == "laser" assert result["data"][SECTION_ADVANCED_SETTINGS][CONF_PORT] == 161 assert result["data"][SECTION_ADVANCED_SETTINGS][CONF_COMMUNITY] == "public" + assert result["result"].unique_id == "0123456789" @pytest.mark.parametrize( @@ -142,6 +140,7 @@ async def test_errors( assert result["data"][CONF_TYPE] == "laser" assert result["data"][SECTION_ADVANCED_SETTINGS][CONF_PORT] == 161 assert result["data"][SECTION_ADVANCED_SETTINGS][CONF_COMMUNITY] == "public" + assert result["result"].unique_id == "0123456789" async def test_unsupported_model_error( @@ -250,32 +249,31 @@ async def test_zeroconf_device_exists_abort( assert mock_config_entry.data[CONF_HOST] == "127.0.0.1" -async def test_zeroconf_no_probe_existing_device(hass: HomeAssistant) -> None: +async def test_zeroconf_no_probe_existing_device( + hass: HomeAssistant, mock_brother_client: AsyncMock +) -> None: """Test we do not probe the device is the host is already configured.""" entry = MockConfigEntry(domain=DOMAIN, unique_id="0123456789", data=CONFIG) entry.add_to_hass(hass) - with ( - patch("homeassistant.components.brother.Brother.initialize"), - patch("homeassistant.components.brother.Brother._get_data") as mock_get_data, - ): - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": SOURCE_ZEROCONF}, - data=ZeroconfServiceInfo( - ip_address=ip_address("127.0.0.1"), - ip_addresses=[ip_address("127.0.0.1")], - hostname="example.local.", - name="Brother Printer", - port=None, - properties={}, - type="mock_type", - ), - ) - await hass.async_block_till_done() + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_ZEROCONF}, + data=ZeroconfServiceInfo( + ip_address=ip_address("127.0.0.1"), + ip_addresses=[ip_address("127.0.0.1")], + hostname="example.local.", + name="Brother Printer", + port=None, + properties={}, + type="mock_type", + ), + ) + await hass.async_block_till_done() assert result["type"] is FlowResultType.ABORT assert result["reason"] == "already_configured" - assert len(mock_get_data.mock_calls) == 0 + mock_brother_client.async_update.assert_not_called() async def test_zeroconf_confirm_create_entry( @@ -315,6 +313,7 @@ async def test_zeroconf_confirm_create_entry( assert result["data"][CONF_TYPE] == "laser" assert result["data"][SECTION_ADVANCED_SETTINGS][CONF_PORT] == 161 assert result["data"][SECTION_ADVANCED_SETTINGS][CONF_COMMUNITY] == "public" + assert result["result"].unique_id == "0123456789" async def test_reconfigure_successful( diff --git a/tests/components/config/test_area_registry.py b/tests/components/config/test_area_registry.py index 81c696bc6a7c79..2421a7d10c5c49 100644 --- a/tests/components/config/test_area_registry.py +++ b/tests/components/config/test_area_registry.py @@ -1,6 +1,7 @@ """Test area_registry API.""" from datetime import datetime +from typing import Any from freezegun.api import FrozenDateTimeFactory import pytest @@ -346,3 +347,92 @@ async def test_update_area_with_name_already_in_use( assert msg["error"]["code"] == "invalid_info" assert msg["error"]["message"] == "The name mock 2 (mock2) is already in use" assert len(area_registry.areas) == 2 + + +async def test_reorder_areas( + client: MockHAClientWebSocket, area_registry: ar.AreaRegistry +) -> None: + """Test reorder areas.""" + area1 = area_registry.async_create("mock 1") + area2 = area_registry.async_create("mock 2") + area3 = area_registry.async_create("mock 3") + + await client.send_json_auto_id({"type": "config/area_registry/list"}) + msg = await client.receive_json() + assert [area["area_id"] for area in msg["result"]] == [area1.id, area2.id, area3.id] + + await client.send_json_auto_id( + { + "type": "config/area_registry/reorder", + "area_ids": [area3.id, area1.id, area2.id], + } + ) + msg = await client.receive_json() + assert msg["success"] + + await client.send_json_auto_id({"type": "config/area_registry/list"}) + msg = await client.receive_json() + assert [area["area_id"] for area in msg["result"]] == [area3.id, area1.id, area2.id] + + +async def test_reorder_areas_invalid_area_ids( + client: MockHAClientWebSocket, area_registry: ar.AreaRegistry +) -> None: + """Test reorder with invalid area IDs.""" + area1 = area_registry.async_create("mock 1") + area_registry.async_create("mock 2") + + await client.send_json_auto_id( + { + "type": "config/area_registry/reorder", + "area_ids": [area1.id], + } + ) + msg = await client.receive_json() + assert not msg["success"] + assert msg["error"]["code"] == "invalid_format" + assert "must contain all existing area IDs" in msg["error"]["message"] + + +async def test_reorder_areas_with_nonexistent_id( + client: MockHAClientWebSocket, area_registry: ar.AreaRegistry +) -> None: + """Test reorder with nonexistent area ID.""" + area1 = area_registry.async_create("mock 1") + area2 = area_registry.async_create("mock 2") + + await client.send_json_auto_id( + { + "type": "config/area_registry/reorder", + "area_ids": [area1.id, area2.id, "nonexistent"], + } + ) + msg = await client.receive_json() + assert not msg["success"] + assert msg["error"]["code"] == "invalid_format" + + +async def test_reorder_areas_persistence( + hass: HomeAssistant, + client: MockHAClientWebSocket, + area_registry: ar.AreaRegistry, + hass_storage: dict[str, Any], +) -> None: + """Test that area reordering is persisted.""" + area1 = area_registry.async_create("mock 1") + area2 = area_registry.async_create("mock 2") + area3 = area_registry.async_create("mock 3") + + await client.send_json_auto_id( + { + "type": "config/area_registry/reorder", + "area_ids": [area2.id, area3.id, area1.id], + } + ) + msg = await client.receive_json() + assert msg["success"] + + await hass.async_block_till_done() + + area_ids = [area.id for area in area_registry.async_list_areas()] + assert area_ids == [area2.id, area3.id, area1.id] diff --git a/tests/components/config/test_floor_registry.py b/tests/components/config/test_floor_registry.py index da6e550b1f6f74..3b0770aa976fe4 100644 --- a/tests/components/config/test_floor_registry.py +++ b/tests/components/config/test_floor_registry.py @@ -1,6 +1,7 @@ """Test floor registry API.""" from datetime import datetime +from typing import Any from freezegun.api import FrozenDateTimeFactory import pytest @@ -275,3 +276,100 @@ async def test_update_with_name_already_in_use( == "The name Second floor (secondfloor) is already in use" ) assert len(floor_registry.floors) == 2 + + +async def test_reorder_floors( + client: MockHAClientWebSocket, floor_registry: fr.FloorRegistry +) -> None: + """Test reorder floors.""" + floor1 = floor_registry.async_create("First floor") + floor2 = floor_registry.async_create("Second floor") + floor3 = floor_registry.async_create("Third floor") + + await client.send_json_auto_id({"type": "config/floor_registry/list"}) + msg = await client.receive_json() + assert [floor["floor_id"] for floor in msg["result"]] == [ + floor1.floor_id, + floor2.floor_id, + floor3.floor_id, + ] + + await client.send_json_auto_id( + { + "type": "config/floor_registry/reorder", + "floor_ids": [floor3.floor_id, floor1.floor_id, floor2.floor_id], + } + ) + msg = await client.receive_json() + assert msg["success"] + + await client.send_json_auto_id({"type": "config/floor_registry/list"}) + msg = await client.receive_json() + assert [floor["floor_id"] for floor in msg["result"]] == [ + floor3.floor_id, + floor1.floor_id, + floor2.floor_id, + ] + + +async def test_reorder_floors_invalid_floor_ids( + client: MockHAClientWebSocket, floor_registry: fr.FloorRegistry +) -> None: + """Test reorder with invalid floor IDs.""" + floor1 = floor_registry.async_create("First floor") + floor_registry.async_create("Second floor") + + await client.send_json_auto_id( + { + "type": "config/floor_registry/reorder", + "floor_ids": [floor1.floor_id], + } + ) + msg = await client.receive_json() + assert not msg["success"] + assert msg["error"]["code"] == "invalid_format" + assert "must contain all existing floor IDs" in msg["error"]["message"] + + +async def test_reorder_floors_with_nonexistent_id( + client: MockHAClientWebSocket, floor_registry: fr.FloorRegistry +) -> None: + """Test reorder with nonexistent floor ID.""" + floor1 = floor_registry.async_create("First floor") + floor2 = floor_registry.async_create("Second floor") + + await client.send_json_auto_id( + { + "type": "config/floor_registry/reorder", + "floor_ids": [floor1.floor_id, floor2.floor_id, "nonexistent"], + } + ) + msg = await client.receive_json() + assert not msg["success"] + assert msg["error"]["code"] == "invalid_format" + + +async def test_reorder_floors_persistence( + hass: HomeAssistant, + client: MockHAClientWebSocket, + floor_registry: fr.FloorRegistry, + hass_storage: dict[str, Any], +) -> None: + """Test that floor reordering is persisted.""" + floor1 = floor_registry.async_create("First floor") + floor2 = floor_registry.async_create("Second floor") + floor3 = floor_registry.async_create("Third floor") + + await client.send_json_auto_id( + { + "type": "config/floor_registry/reorder", + "floor_ids": [floor2.floor_id, floor3.floor_id, floor1.floor_id], + } + ) + msg = await client.receive_json() + assert msg["success"] + + await hass.async_block_till_done() + + floor_ids = [floor.floor_id for floor in floor_registry.async_list_floors()] + assert floor_ids == [floor2.floor_id, floor3.floor_id, floor1.floor_id] diff --git a/tests/components/frontend/test_storage.py b/tests/components/frontend/test_storage.py index f4a61b743c530b..097b00e2e9c119 100644 --- a/tests/components/frontend/test_storage.py +++ b/tests/components/frontend/test_storage.py @@ -301,3 +301,274 @@ async def test_set_user_data( res = await client.receive_json() assert res["success"], res assert res["result"]["value"] == "test-value" + + +async def test_get_system_data_empty( + hass: HomeAssistant, hass_ws_client: WebSocketGenerator +) -> None: + """Test get_system_data command.""" + client = await hass_ws_client(hass) + + await client.send_json( + {"id": 5, "type": "frontend/get_system_data", "key": "non-existing-key"} + ) + + res = await client.receive_json() + assert res["success"], res + assert res["result"]["value"] is None + + +async def test_get_system_data( + hass: HomeAssistant, + hass_ws_client: WebSocketGenerator, + hass_storage: dict[str, Any], +) -> None: + """Test get_system_data command.""" + storage_key = f"{DOMAIN}.system_data" + hass_storage[storage_key] = { + "key": storage_key, + "version": 1, + "data": {"test-key": "test-value", "test-complex": [{"foo": "bar"}]}, + } + + client = await hass_ws_client(hass) + + # Get a simple string key + + await client.send_json( + {"id": 6, "type": "frontend/get_system_data", "key": "test-key"} + ) + + res = await client.receive_json() + assert res["success"], res + assert res["result"]["value"] == "test-value" + + # Get a more complex key + + await client.send_json( + {"id": 7, "type": "frontend/get_system_data", "key": "test-complex"} + ) + + res = await client.receive_json() + assert res["success"], res + assert res["result"]["value"][0]["foo"] == "bar" + + +@pytest.mark.parametrize( + ("subscriptions", "events"), + [ + ([], []), + ([(1, {"key": "test-key"}, None)], [(1, "test-value")]), + ([(1, {"key": "other-key"}, None)], []), + ], +) +async def test_set_system_data_empty( + hass: HomeAssistant, + hass_ws_client: WebSocketGenerator, + subscriptions: list[tuple[int, dict[str, str], Any]], + events: list[tuple[int, Any]], +) -> None: + """Test set_system_data command. + + Also test subscribing. + """ + client = await hass_ws_client(hass) + + for msg_id, key, event_data in subscriptions: + await client.send_json( + { + "id": msg_id, + "type": "frontend/subscribe_system_data", + } + | key + ) + + event = await client.receive_json() + assert event == { + "id": msg_id, + "type": "event", + "event": {"value": event_data}, + } + + res = await client.receive_json() + assert res["success"], res + + # test creating + + await client.send_json( + {"id": 6, "type": "frontend/get_system_data", "key": "test-key"} + ) + + res = await client.receive_json() + assert res["success"], res + assert res["result"]["value"] is None + + await client.send_json( + { + "id": 7, + "type": "frontend/set_system_data", + "key": "test-key", + "value": "test-value", + } + ) + + for msg_id, event_data in events: + event = await client.receive_json() + assert event == {"id": msg_id, "type": "event", "event": {"value": event_data}} + + res = await client.receive_json() + assert res["success"], res + + await client.send_json( + {"id": 8, "type": "frontend/get_system_data", "key": "test-key"} + ) + + res = await client.receive_json() + assert res["success"], res + assert res["result"]["value"] == "test-value" + + +@pytest.mark.parametrize( + ("subscriptions", "events"), + [ + ( + [], + [[], []], + ), + ( + [(1, {"key": "test-key"}, "test-value")], + [[], []], + ), + ( + [(1, {"key": "test-non-existent-key"}, None)], + [[(1, "test-value-new")], []], + ), + ( + [(1, {"key": "test-complex"}, "string")], + [[], [(1, [{"foo": "bar"}])]], + ), + ( + [(1, {"key": "other-key"}, None)], + [[], []], + ), + ], +) +async def test_set_system_data( + hass: HomeAssistant, + hass_ws_client: WebSocketGenerator, + hass_storage: dict[str, Any], + subscriptions: list[tuple[int, dict[str, str], Any]], + events: list[list[tuple[int, Any]]], +) -> None: + """Test set_system_data command with initial data.""" + storage_key = f"{DOMAIN}.system_data" + hass_storage[storage_key] = { + "version": 1, + "data": {"test-key": "test-value", "test-complex": "string"}, + } + + client = await hass_ws_client(hass) + + for msg_id, key, event_data in subscriptions: + await client.send_json( + { + "id": msg_id, + "type": "frontend/subscribe_system_data", + } + | key + ) + + event = await client.receive_json() + assert event == { + "id": msg_id, + "type": "event", + "event": {"value": event_data}, + } + + res = await client.receive_json() + assert res["success"], res + + # test creating + + await client.send_json( + { + "id": 5, + "type": "frontend/set_system_data", + "key": "test-non-existent-key", + "value": "test-value-new", + } + ) + + for msg_id, event_data in events[0]: + event = await client.receive_json() + assert event == {"id": msg_id, "type": "event", "event": {"value": event_data}} + + res = await client.receive_json() + assert res["success"], res + + await client.send_json( + {"id": 6, "type": "frontend/get_system_data", "key": "test-non-existent-key"} + ) + + res = await client.receive_json() + assert res["success"], res + assert res["result"]["value"] == "test-value-new" + + # test updating with complex data + + await client.send_json( + { + "id": 7, + "type": "frontend/set_system_data", + "key": "test-complex", + "value": [{"foo": "bar"}], + } + ) + + for msg_id, event_data in events[1]: + event = await client.receive_json() + assert event == {"id": msg_id, "type": "event", "event": {"value": event_data}} + + res = await client.receive_json() + assert res["success"], res + + await client.send_json( + {"id": 8, "type": "frontend/get_system_data", "key": "test-complex"} + ) + + res = await client.receive_json() + assert res["success"], res + assert res["result"]["value"][0]["foo"] == "bar" + + # ensure other existing key was not modified + + await client.send_json( + {"id": 9, "type": "frontend/get_system_data", "key": "test-key"} + ) + + res = await client.receive_json() + assert res["success"], res + assert res["result"]["value"] == "test-value" + + +async def test_set_system_data_requires_admin( + hass: HomeAssistant, + hass_ws_client: WebSocketGenerator, + hass_read_only_access_token: str, +) -> None: + """Test set_system_data requires admin permissions.""" + client = await hass_ws_client(hass, hass_read_only_access_token) + + await client.send_json( + { + "id": 5, + "type": "frontend/set_system_data", + "key": "test-key", + "value": "test-value", + } + ) + + res = await client.receive_json() + assert not res["success"], res + assert res["error"]["code"] == "unauthorized" + assert res["error"]["message"] == "Unauthorized" diff --git a/tests/components/lamarzocco/conftest.py b/tests/components/lamarzocco/conftest.py index 7907a1d6a7ed6d..e35dd459701716 100644 --- a/tests/components/lamarzocco/conftest.py +++ b/tests/components/lamarzocco/conftest.py @@ -1,7 +1,7 @@ """Lamarzocco session fixtures.""" from collections.abc import Generator -from unittest.mock import MagicMock, patch +from unittest.mock import AsyncMock, MagicMock, PropertyMock, patch from bleak.backends.device import BLEDevice from pylamarzocco.const import ModelName @@ -132,6 +132,10 @@ def mock_lamarzocco(device_fixture: ModelName) -> Generator[MagicMock]: "schedule": machine_mock.schedule.to_dict(), "settings": machine_mock.settings.to_dict(), } + machine_mock.connect_dashboard_websocket = AsyncMock() + machine_mock.websocket = MagicMock() + machine_mock.websocket.connected = True + machine_mock.websocket.disconnect = AsyncMock() yield machine_mock @@ -149,10 +153,11 @@ def mock_ble_device() -> BLEDevice: @pytest.fixture -def mock_websocket_terminated() -> Generator[bool]: +def mock_websocket_terminated() -> Generator[PropertyMock]: """Mock websocket terminated.""" with patch( "homeassistant.components.lamarzocco.coordinator.LaMarzoccoUpdateCoordinator.websocket_terminated", - new=False, + new_callable=PropertyMock, ) as mock_websocket_terminated: + mock_websocket_terminated.return_value = False yield mock_websocket_terminated diff --git a/tests/components/lamarzocco/test_binary_sensor.py b/tests/components/lamarzocco/test_binary_sensor.py index ef8c7e17d9797f..8012cb3baf03b2 100644 --- a/tests/components/lamarzocco/test_binary_sensor.py +++ b/tests/components/lamarzocco/test_binary_sensor.py @@ -1,8 +1,7 @@ """Tests for La Marzocco binary sensors.""" -from collections.abc import Generator from datetime import timedelta -from unittest.mock import MagicMock, patch +from unittest.mock import MagicMock, PropertyMock, patch from freezegun.api import FrozenDateTimeFactory from pylamarzocco.exceptions import RequestNotSuccessful @@ -36,24 +35,15 @@ async def test_binary_sensors( await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id) -@pytest.fixture(autouse=True) -def mock_websocket_terminated() -> Generator[bool]: - """Mock websocket terminated.""" - with patch( - "homeassistant.components.lamarzocco.coordinator.LaMarzoccoUpdateCoordinator.websocket_terminated", - new=False, - ) as mock_websocket_terminated: - yield mock_websocket_terminated - - async def test_brew_active_unavailable( hass: HomeAssistant, mock_lamarzocco: MagicMock, mock_config_entry: MockConfigEntry, + mock_websocket_terminated: PropertyMock, ) -> None: """Test the La Marzocco brew active becomes unavailable.""" - mock_lamarzocco.websocket.connected = False + mock_websocket_terminated.return_value = True await async_init_integration(hass, mock_config_entry) state = hass.states.get( f"binary_sensor.{mock_lamarzocco.serial_number}_brewing_active" diff --git a/tests/components/lamarzocco/test_init.py b/tests/components/lamarzocco/test_init.py index e6bf4a0af62e5a..62c4f6ed37b8b2 100644 --- a/tests/components/lamarzocco/test_init.py +++ b/tests/components/lamarzocco/test_init.py @@ -1,7 +1,9 @@ """Test initialization of lamarzocco.""" +from datetime import timedelta from unittest.mock import AsyncMock, MagicMock, patch +from freezegun.api import FrozenDateTimeFactory from pylamarzocco.const import FirmwareType, ModelName from pylamarzocco.exceptions import AuthFail, RequestNotSuccessful from pylamarzocco.models import WebSocketDetails @@ -30,7 +32,7 @@ get_bluetooth_service_info, ) -from tests.common import MockConfigEntry +from tests.common import MockConfigEntry, async_fire_time_changed async def test_load_unload_config_entry( @@ -310,3 +312,37 @@ async def test_device( device = device_registry.async_get(entry.device_id) assert device assert device == snapshot + + +async def test_websocket_reconnects_after_termination( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_lamarzocco: MagicMock, + freezer: FrozenDateTimeFactory, +) -> None: + """Test the websocket reconnects after background task terminates.""" + # Setup: websocket connected initially + mock_websocket = MagicMock() + mock_websocket.closed = False + mock_lamarzocco.websocket = WebSocketDetails(mock_websocket, None) + + await async_init_integration(hass, mock_config_entry) + + # Verify initial websocket connection was attempted + assert mock_lamarzocco.connect_dashboard_websocket.call_count == 1 + + # Simulate websocket disconnection (e.g., after internet outage) + mock_websocket.closed = True + + # Simulate the background task terminating by patching websocket_terminated + with patch( + "homeassistant.components.lamarzocco.coordinator.LaMarzoccoConfigUpdateCoordinator.websocket_terminated", + new=True, + ): + # Trigger the coordinator's update (which runs every 60 seconds) + freezer.tick(timedelta(seconds=61)) + async_fire_time_changed(hass) + await hass.async_block_till_done() + + # Verify websocket reconnection was attempted + assert mock_lamarzocco.connect_dashboard_websocket.call_count == 2 diff --git a/tests/components/matter/conftest.py b/tests/components/matter/conftest.py index e61f2ec88f6c1b..3aa5fdd2a5238e 100644 --- a/tests/components/matter/conftest.py +++ b/tests/components/matter/conftest.py @@ -112,6 +112,7 @@ async def integration_fixture( "leak_sensor", "light_sensor", "microwave_oven", + "mock_lock", "mounted_dimmable_load_control_fixture", "multi_endpoint_light", "occupancy_sensor", diff --git a/tests/components/matter/fixtures/nodes/mock_lock.json b/tests/components/matter/fixtures/nodes/mock_lock.json new file mode 100644 index 00000000000000..658d0a6c79564e --- /dev/null +++ b/tests/components/matter/fixtures/nodes/mock_lock.json @@ -0,0 +1,471 @@ +{ + "node_id": 151, + "date_commissioned": "2025-11-18T20:53:49.656346", + "last_interview": "2025-11-18T20:53:49.656374", + "interview_version": 6, + "available": true, + "is_bridge": false, + "attributes": { + "0/49/0": 1, + "0/49/1": [ + { + "0": "ZW5zMzM=", + "1": true + } + ], + "0/49/4": true, + "0/49/5": 0, + "0/49/6": "ZW5zMzM=", + "0/49/7": null, + "0/49/65532": 4, + "0/49/65533": 2, + "0/49/65528": [], + "0/49/65529": [], + "0/49/65531": [0, 1, 4, 5, 6, 7, 65532, 65533, 65528, 65529, 65531], + "0/70/0": 300, + "0/70/1": 300, + "0/70/2": 5000, + "0/70/65532": 0, + "0/70/65533": 3, + "0/70/65528": [], + "0/70/65529": [], + "0/70/65531": [0, 1, 2, 65532, 65533, 65528, 65529, 65531], + "0/65/0": [], + "0/65/65532": 0, + "0/65/65533": 1, + "0/65/65528": [], + "0/65/65529": [], + "0/65/65531": [0, 65532, 65533, 65528, 65529, 65531], + "0/63/0": [], + "0/63/1": [], + "0/63/2": 4, + "0/63/3": 3, + "0/63/65532": 0, + "0/63/65533": 2, + "0/63/65528": [5, 2], + "0/63/65529": [0, 1, 3, 4], + "0/63/65531": [0, 1, 2, 3, 65532, 65533, 65528, 65529, 65531], + "0/62/0": [ + { + "1": "FTABAQEkAgE3AyQTAhgmBIAigScmBYAlTTo3BiQVAiQRlxgkBwEkCAEwCUEEXezptKw8pXZnNDtNP/6ytcTdilcvU5tGmhRizeaObdQklNW6TLSBvoX5icYne5L5ESsO6yuHCKieC120pOsVVjcKNQEoARgkAgE2AwQCBAEYMAQUtiskyCanVk/hj/Gf0pOAxSBXGLAwBRS5+zzv8ZPGnI9mC3wH9vq10JnwlhgwC0DrKZjxUW15mceAFXSKE0BCJf3c4iBeFXaK9nrYnHOfiOH9QYqZ71iUyvOrlahFR9xELuLKaj5j/NlVR3CVGU/KGA==", + "2": "FTABAQEkAgE3AyQUARgmBIAigScmBYAlTTo3BiQTAhgkBwEkCAEwCUEE/DujEcdTsX19xbxX+KuKKWiMaA5D9u99P/pVxIOmscd2BA2PadEMNnjvtPOpf+WE2Zxar4rby1IfAClGUUuQrTcKNQEpARgkAmAwBBS5+zzv8ZPGnI9mC3wH9vq10JnwljAFFPT6p93JKGcb7g+rTWnA6evF2EdGGDALQGkPpvsbkAFEbfPN6H3Kf23R0zzmW/gpAA3kgaL6wKB2Ofm+Tmylw22qM536Kj8mOMwaV0EL1dCCGcuxF98aL6gY", + "254": 1 + } + ], + "0/62/1": [ + { + "1": "BBmX+KwLR5HGlVNbvlC+dO8Jv9fPthHiTfGpUzi2JJADX5az6GxBAFn02QKHwLcZHyh+lh9faf6rf38/nPYF7/M=", + "2": 4939, + "3": 2, + "4": 151, + "5": "ha", + "254": 1 + } + ], + "0/62/2": 16, + "0/62/3": 1, + "0/62/4": [ + "FTABAQEkAgE3AyQUARgmBIAigScmBYAlTTo3BiQUARgkBwEkCAEwCUEEGZf4rAtHkcaVU1u+UL507wm/18+2EeJN8alTOLYkkANflrPobEEAWfTZAofAtxkfKH6WH19p/qt/fz+c9gXv8zcKNQEpARgkAmAwBBT0+qfdyShnG+4Pq01pwOnrxdhHRjAFFPT6p93JKGcb7g+rTWnA6evF2EdGGDALQPVrsFnfFplsQGV5m5EUua+rmo9hAr+OP1bvaifdLqiEIn3uXLTLoKmVUkPImRL2Fb+xcMEAqR2p7RM6ZlFCR20Y" + ], + "0/62/5": 1, + "0/62/65532": 0, + "0/62/65533": 2, + "0/62/65528": [1, 3, 5, 8, 14], + "0/62/65529": [0, 2, 4, 6, 7, 9, 10, 11, 12, 13], + "0/62/65531": [0, 1, 2, 3, 4, 5, 65532, 65533, 65528, 65529, 65531], + "0/60/0": 0, + "0/60/1": null, + "0/60/2": null, + "0/60/65532": 0, + "0/60/65533": 1, + "0/60/65528": [], + "0/60/65529": [0, 2], + "0/60/65531": [0, 1, 2, 65532, 65533, 65528, 65529, 65531], + "0/55/2": 894, + "0/55/3": 64, + "0/55/4": 0, + "0/55/5": 0, + "0/55/6": 0, + "0/55/7": null, + "0/55/1": true, + "0/55/0": 2, + "0/55/8": 19, + "0/55/65532": 3, + "0/55/65533": 1, + "0/55/65528": [], + "0/55/65529": [0], + "0/55/65531": [ + 2, 3, 4, 5, 6, 7, 1, 0, 8, 65532, 65533, 65528, 65529, 65531 + ], + "0/54/0": null, + "0/54/1": null, + "0/54/2": 3, + "0/54/3": null, + "0/54/4": null, + "0/54/5": null, + "0/54/12": null, + "0/54/6": null, + "0/54/7": null, + "0/54/8": null, + "0/54/9": null, + "0/54/10": null, + "0/54/11": null, + "0/54/65532": 3, + "0/54/65533": 1, + "0/54/65528": [], + "0/54/65529": [0], + "0/54/65531": [ + 0, 1, 2, 3, 4, 5, 12, 6, 7, 8, 9, 10, 11, 65532, 65533, 65528, 65529, + 65531 + ], + "0/52/0": [ + { + "0": 12306, + "1": "12306" + }, + { + "0": 12305, + "1": "12305" + }, + { + "0": 12304, + "1": "12304" + }, + { + "0": 12303, + "1": "12303" + }, + { + "0": 12302, + "1": "12302" + } + ], + "0/52/1": 537280, + "0/52/2": 662848, + "0/52/3": 662848, + "0/52/65532": 1, + "0/52/65533": 1, + "0/52/65528": [], + "0/52/65529": [0], + "0/52/65531": [0, 1, 2, 3, 65532, 65533, 65528, 65529, 65531], + "0/51/0": [ + { + "0": "docker0", + "1": false, + "2": null, + "3": null, + "4": "8mJ0KirG", + "5": ["rBEAAQ=="], + "6": [], + "7": 0 + }, + { + "0": "ens33", + "1": true, + "2": null, + "3": null, + "4": "AAwpaqXN", + "5": ["wKgBxA=="], + "6": [ + "KgEOCgKzOZBI/+PPpfBv9Q==", + "KgEOCgKzOZA2wMm9YG06Ag==", + "/oAAAAAAAACluAo+qvkuxw==" + ], + "7": 2 + }, + { + "0": "lo", + "1": true, + "2": null, + "3": null, + "4": "AAAAAAAA", + "5": ["fwAAAQ=="], + "6": ["AAAAAAAAAAAAAAAAAAAAAQ=="], + "7": 0 + } + ], + "0/51/1": 1, + "0/51/8": false, + "0/51/3": 0, + "0/51/4": 0, + "0/51/2": 19, + "0/51/65532": 0, + "0/51/65533": 2, + "0/51/65528": [2], + "0/51/65529": [0, 1], + "0/51/65531": [0, 1, 8, 3, 4, 2, 65532, 65533, 65528, 65529, 65531], + "0/50/65532": 0, + "0/50/65533": 1, + "0/50/65528": [1], + "0/50/65529": [0], + "0/50/65531": [65532, 65533, 65528, 65529, 65531], + "0/48/0": 0, + "0/48/1": { + "0": 60, + "1": 900 + }, + "0/48/2": 0, + "0/48/3": 2, + "0/48/4": true, + "0/48/65532": 0, + "0/48/65533": 2, + "0/48/65528": [1, 3, 5], + "0/48/65529": [0, 2, 4], + "0/48/65531": [0, 1, 2, 3, 4, 65532, 65533, 65528, 65529, 65531], + "0/40/0": 19, + "0/40/1": "TEST_VENDOR", + "0/40/2": 65521, + "0/40/3": "Mock Lock", + "0/40/4": 32769, + "0/40/5": "", + "0/40/6": "**REDACTED**", + "0/40/7": 0, + "0/40/8": "TEST_VERSION", + "0/40/9": 1, + "0/40/10": "1.0", + "0/40/19": { + "0": 3, + "1": 65535 + }, + "0/40/21": 17104896, + "0/40/22": 1, + "0/40/24": 1, + "0/40/11": "20200101", + "0/40/12": "", + "0/40/13": "", + "0/40/14": "", + "0/40/15": "TEST_SN", + "0/40/16": false, + "0/40/18": "714373B6E9864224", + "0/40/65532": 0, + "0/40/65533": 5, + "0/40/65528": [], + "0/40/65529": [], + "0/40/65531": [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 19, 21, 22, 24, 11, 12, 13, 14, 15, 16, + 18, 65532, 65533, 65528, 65529, 65531 + ], + "0/31/0": [ + { + "1": 5, + "2": 2, + "3": [112233], + "4": null, + "254": 1 + } + ], + "0/31/2": 4, + "0/31/3": 3, + "0/31/4": 4, + "0/31/1": [], + "0/31/65532": 1, + "0/31/65533": 3, + "0/31/65528": [], + "0/31/65529": [], + "0/31/65531": [0, 2, 3, 4, 1, 65532, 65533, 65528, 65529, 65531], + "0/29/0": [ + { + "0": 17, + "1": 1 + }, + { + "0": 22, + "1": 4 + } + ], + "0/29/1": [ + 49, 70, 65, 63, 62, 60, 55, 54, 52, 51, 50, 48, 40, 31, 29, 42, 47, 53 + ], + "0/29/2": [41], + "0/29/3": [1], + "0/29/65532": 0, + "0/29/65533": 3, + "0/29/65528": [], + "0/29/65529": [], + "0/29/65531": [0, 1, 2, 3, 65532, 65533, 65528, 65529, 65531], + "0/42/0": [], + "0/42/1": true, + "0/42/2": 0, + "0/42/3": 0, + "0/42/65532": 0, + "0/42/65533": 1, + "0/42/65528": [], + "0/42/65529": [0], + "0/42/65531": [0, 1, 2, 3, 65532, 65533, 65528, 65529, 65531], + "0/47/0": 1, + "0/47/1": 0, + "0/47/2": "USB", + "0/47/5": 0, + "0/47/6": 0, + "0/47/31": [], + "0/47/65532": 1, + "0/47/65533": 3, + "0/47/65528": [], + "0/47/65529": [], + "0/47/65531": [0, 1, 2, 5, 6, 31, 65532, 65533, 65528, 65529, 65531], + "0/53/0": null, + "0/53/1": null, + "0/53/2": null, + "0/53/3": null, + "0/53/4": null, + "0/53/5": null, + "0/53/6": 0, + "0/53/7": [], + "0/53/8": [], + "0/53/9": null, + "0/53/10": null, + "0/53/11": null, + "0/53/12": null, + "0/53/13": null, + "0/53/14": 0, + "0/53/15": 0, + "0/53/16": 0, + "0/53/17": 0, + "0/53/18": 0, + "0/53/19": 0, + "0/53/20": 0, + "0/53/21": 0, + "0/53/22": 0, + "0/53/23": 0, + "0/53/24": 0, + "0/53/25": 0, + "0/53/26": 0, + "0/53/27": 0, + "0/53/28": 0, + "0/53/29": 0, + "0/53/30": 0, + "0/53/31": 0, + "0/53/32": 0, + "0/53/33": 0, + "0/53/34": 0, + "0/53/35": 0, + "0/53/36": 0, + "0/53/37": 0, + "0/53/38": 0, + "0/53/39": 0, + "0/53/40": 0, + "0/53/41": 0, + "0/53/42": 0, + "0/53/43": 0, + "0/53/44": 0, + "0/53/45": 0, + "0/53/46": 0, + "0/53/47": 0, + "0/53/48": 0, + "0/53/49": 0, + "0/53/50": 0, + "0/53/51": 0, + "0/53/52": 0, + "0/53/53": 0, + "0/53/54": 0, + "0/53/55": 0, + "0/53/56": null, + "0/53/57": null, + "0/53/58": null, + "0/53/59": null, + "0/53/60": null, + "0/53/61": null, + "0/53/62": [], + "0/53/65532": 15, + "0/53/65533": 3, + "0/53/65528": [], + "0/53/65529": [0], + "0/53/65531": [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, + 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, + 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, + 57, 58, 59, 60, 61, 62, 65532, 65533, 65528, 65529, 65531 + ], + "1/3/0": 0, + "1/3/1": 2, + "1/3/65532": 0, + "1/3/65533": 6, + "1/3/65528": [], + "1/3/65529": [0], + "1/3/65531": [0, 1, 65532, 65533, 65528, 65529, 65531], + "1/29/0": [ + { + "0": 10, + "1": 3 + }, + { + "0": 17, + "1": 1 + } + ], + "1/29/1": [3, 29, 47, 257], + "1/29/2": [], + "1/29/3": [], + "1/29/65532": 0, + "1/29/65533": 3, + "1/29/65528": [], + "1/29/65529": [], + "1/29/65531": [0, 1, 2, 3, 65532, 65533, 65528, 65529, 65531], + "1/47/0": 1, + "1/47/1": 1, + "1/47/2": "Battery", + "1/47/14": 0, + "1/47/15": false, + "1/47/16": 0, + "1/47/19": "", + "1/47/25": 1, + "1/47/31": [], + "1/47/65532": 10, + "1/47/65533": 3, + "1/47/65528": [], + "1/47/65529": [], + "1/47/65531": [ + 0, 1, 2, 14, 15, 16, 19, 25, 31, 65532, 65533, 65528, 65529, 65531 + ], + "1/257/0": 1, + "1/257/1": 0, + "1/257/2": true, + "1/257/3": 1, + "1/257/4": 10, + "1/257/5": 10, + "1/257/17": 10, + "1/257/18": 10, + "1/257/19": 10, + "1/257/20": 10, + "1/257/21": 10, + "1/257/22": 10, + "1/257/23": 8, + "1/257/24": 6, + "1/257/25": 20, + "1/257/26": 10, + "1/257/27": 1, + "1/257/28": 5, + "1/257/33": "en", + "1/257/35": 60, + "1/257/36": 0, + "1/257/37": 0, + "1/257/38": 65526, + "1/257/41": false, + "1/257/43": false, + "1/257/48": 3, + "1/257/49": 10, + "1/257/51": false, + "1/257/128": null, + "1/257/129": null, + "1/257/130": "JE4bj1uR9Kt/VH7lYJwxbw==", + "1/257/131": ["AQA="], + "1/257/132": null, + "1/257/133": ["AQA="], + "1/257/134": 0, + "1/257/135": 10, + "1/257/136": 10, + "1/257/65532": 32179, + "1/257/65533": 8, + "1/257/65528": [12, 15, 18, 28, 35, 37], + "1/257/65529": [ + 0, 1, 3, 11, 12, 13, 14, 15, 16, 17, 18, 19, 26, 27, 29, 34, 36, 38, 39, + 40, 41 + ], + "1/257/65531": [ + 0, 1, 2, 3, 4, 5, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 33, 35, + 36, 37, 38, 41, 43, 48, 49, 51, 128, 129, 130, 131, 132, 133, 134, 135, + 136, 65532, 65533, 65528, 65529, 65531 + ] + }, + "attribute_subscriptions": [] +} diff --git a/tests/components/matter/snapshots/test_binary_sensor.ambr b/tests/components/matter/snapshots/test_binary_sensor.ambr index e7c77835313fec..da6a59659f96d7 100644 --- a/tests/components/matter/snapshots/test_binary_sensor.ambr +++ b/tests/components/matter/snapshots/test_binary_sensor.ambr @@ -489,6 +489,104 @@ 'state': 'on', }) # --- +# name: test_binary_sensors[mock_lock][binary_sensor.mock_lock_battery-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': , + 'entity_id': 'binary_sensor.mock_lock_battery', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Battery', + 'platform': 'matter', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': '00000000000004D2-0000000000000097-MatterNodeDevice-1-BatteryChargeLevel-47-14', + 'unit_of_measurement': None, + }) +# --- +# name: test_binary_sensors[mock_lock][binary_sensor.mock_lock_battery-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'battery', + 'friendly_name': 'Mock Lock Battery', + }), + 'context': , + 'entity_id': 'binary_sensor.mock_lock_battery', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- +# name: test_binary_sensors[mock_lock][binary_sensor.mock_lock_door-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': None, + 'entity_id': 'binary_sensor.mock_lock_door', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Door', + 'platform': 'matter', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': '00000000000004D2-0000000000000097-MatterNodeDevice-1-LockDoorStateSensor-257-3', + 'unit_of_measurement': None, + }) +# --- +# name: test_binary_sensors[mock_lock][binary_sensor.mock_lock_door-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'door', + 'friendly_name': 'Mock Lock Door', + }), + 'context': , + 'entity_id': 'binary_sensor.mock_lock_door', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- # name: test_binary_sensors[occupancy_sensor][binary_sensor.mock_occupancy_sensor_occupancy-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ diff --git a/tests/components/matter/snapshots/test_button.ambr b/tests/components/matter/snapshots/test_button.ambr index a9ba27ac0b71d9..52232069d21d1a 100644 --- a/tests/components/matter/snapshots/test_button.ambr +++ b/tests/components/matter/snapshots/test_button.ambr @@ -2241,6 +2241,55 @@ 'state': 'unknown', }) # --- +# name: test_buttons[mock_lock][button.mock_lock_identify-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'button', + 'entity_category': , + 'entity_id': 'button.mock_lock_identify', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Identify', + 'platform': 'matter', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': '00000000000004D2-0000000000000097-MatterNodeDevice-1-IdentifyButton-3-1', + 'unit_of_measurement': None, + }) +# --- +# name: test_buttons[mock_lock][button.mock_lock_identify-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'identify', + 'friendly_name': 'Mock Lock Identify', + }), + 'context': , + 'entity_id': 'button.mock_lock_identify', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- # name: test_buttons[multi_endpoint_light][button.inovelli_identify_1-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ diff --git a/tests/components/matter/snapshots/test_lock.ambr b/tests/components/matter/snapshots/test_lock.ambr index 240bf3e7e3b92f..0afd565e80d04b 100644 --- a/tests/components/matter/snapshots/test_lock.ambr +++ b/tests/components/matter/snapshots/test_lock.ambr @@ -149,3 +149,53 @@ 'state': 'locked', }) # --- +# name: test_locks[mock_lock][lock.mock_lock-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'lock', + 'entity_category': None, + 'entity_id': 'lock.mock_lock', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': None, + 'platform': 'matter', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': , + 'translation_key': None, + 'unique_id': '00000000000004D2-0000000000000097-MatterNodeDevice-1-MatterLock-257-0', + 'unit_of_measurement': None, + }) +# --- +# name: test_locks[mock_lock][lock.mock_lock-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'changed_by': 'Unknown', + 'friendly_name': 'Mock Lock', + 'supported_features': , + }), + 'context': , + 'entity_id': 'lock.mock_lock', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'locked', + }) +# --- diff --git a/tests/components/matter/snapshots/test_number.ambr b/tests/components/matter/snapshots/test_number.ambr index 0bf38ec717597e..309c301bd03528 100644 --- a/tests/components/matter/snapshots/test_number.ambr +++ b/tests/components/matter/snapshots/test_number.ambr @@ -1731,6 +1731,179 @@ 'state': '30', }) # --- +# name: test_numbers[mock_lock][number.mock_lock_auto_relock_time-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'max': 65534, + 'min': 0, + 'mode': , + 'step': 1.0, + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'number', + 'entity_category': , + 'entity_id': 'number.mock_lock_auto_relock_time', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Auto-relock time', + 'platform': 'matter', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'auto_relock_timer', + 'unique_id': '00000000000004D2-0000000000000097-MatterNodeDevice-1-AutoRelockTimer-257-35', + 'unit_of_measurement': , + }) +# --- +# name: test_numbers[mock_lock][number.mock_lock_auto_relock_time-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Mock Lock Auto-relock time', + 'max': 65534, + 'min': 0, + 'mode': , + 'step': 1.0, + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'number.mock_lock_auto_relock_time', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '60', + }) +# --- +# name: test_numbers[mock_lock][number.mock_lock_user_code_temporary_disable_time-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'max': 255, + 'min': 1, + 'mode': , + 'step': 1, + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'number', + 'entity_category': , + 'entity_id': 'number.mock_lock_user_code_temporary_disable_time', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'User code temporary disable time', + 'platform': 'matter', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'user_code_temporary_disable_time', + 'unique_id': '00000000000004D2-0000000000000097-MatterNodeDevice-1-DoorLockUserCodeTemporaryDisableTime-257-49', + 'unit_of_measurement': , + }) +# --- +# name: test_numbers[mock_lock][number.mock_lock_user_code_temporary_disable_time-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Mock Lock User code temporary disable time', + 'max': 255, + 'min': 1, + 'mode': , + 'step': 1, + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'number.mock_lock_user_code_temporary_disable_time', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '10', + }) +# --- +# name: test_numbers[mock_lock][number.mock_lock_wrong_code_limit-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'max': 255, + 'min': 1, + 'mode': , + 'step': 1, + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'number', + 'entity_category': , + 'entity_id': 'number.mock_lock_wrong_code_limit', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Wrong code limit', + 'platform': 'matter', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'wrong_code_entry_limit', + 'unique_id': '00000000000004D2-0000000000000097-MatterNodeDevice-1-DoorLockWrongCodeEntryLimit-257-48', + 'unit_of_measurement': None, + }) +# --- +# name: test_numbers[mock_lock][number.mock_lock_wrong_code_limit-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Mock Lock Wrong code limit', + 'max': 255, + 'min': 1, + 'mode': , + 'step': 1, + }), + 'context': , + 'entity_id': 'number.mock_lock_wrong_code_limit', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '3', + }) +# --- # name: test_numbers[mounted_dimmable_load_control_fixture][number.mock_mounted_dimmable_load_control_on_level-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ diff --git a/tests/components/matter/snapshots/test_select.ambr b/tests/components/matter/snapshots/test_select.ambr index d8a5449ac7d4dd..2f391f10ec6955 100644 --- a/tests/components/matter/snapshots/test_select.ambr +++ b/tests/components/matter/snapshots/test_select.ambr @@ -2257,6 +2257,67 @@ 'state': '1000', }) # --- +# name: test_selects[mock_lock][select.mock_lock_sound_volume-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'options': list([ + 'silent', + 'low', + 'medium', + 'high', + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'select', + 'entity_category': , + 'entity_id': 'select.mock_lock_sound_volume', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Sound volume', + 'platform': 'matter', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'door_lock_sound_volume', + 'unique_id': '00000000000004D2-0000000000000097-MatterNodeDevice-1-DoorLockSoundVolume-257-36', + 'unit_of_measurement': None, + }) +# --- +# name: test_selects[mock_lock][select.mock_lock_sound_volume-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Mock Lock Sound volume', + 'options': list([ + 'silent', + 'low', + 'medium', + 'high', + ]), + }), + 'context': , + 'entity_id': 'select.mock_lock_sound_volume', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'silent', + }) +# --- # name: test_selects[mounted_dimmable_load_control_fixture][select.mock_mounted_dimmable_load_control_power_on_behavior_on_startup-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ diff --git a/tests/components/matter/snapshots/test_switch.ambr b/tests/components/matter/snapshots/test_switch.ambr index 57633384190942..54556906b0dddf 100644 --- a/tests/components/matter/snapshots/test_switch.ambr +++ b/tests/components/matter/snapshots/test_switch.ambr @@ -682,6 +682,54 @@ 'state': 'on', }) # --- +# name: test_switches[mock_lock][switch.mock_lock_privacy_mode_button-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'switch', + 'entity_category': , + 'entity_id': 'switch.mock_lock_privacy_mode_button', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Privacy mode button', + 'platform': 'matter', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'privacy_mode_button', + 'unique_id': '00000000000004D2-0000000000000097-MatterNodeDevice-1-DoorLockEnablePrivacyModeButton-257-43', + 'unit_of_measurement': None, + }) +# --- +# name: test_switches[mock_lock][switch.mock_lock_privacy_mode_button-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Mock Lock Privacy mode button', + }), + 'context': , + 'entity_id': 'switch.mock_lock_privacy_mode_button', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- # name: test_switches[on_off_plugin_unit][switch.mock_onoffpluginunit-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ diff --git a/tests/components/tuya/__init__.py b/tests/components/tuya/__init__.py index cf9d56557dd773..c38460a004e129 100644 --- a/tests/components/tuya/__init__.py +++ b/tests/components/tuya/__init__.py @@ -2,6 +2,7 @@ from __future__ import annotations +import pathlib from typing import Any from unittest.mock import patch @@ -12,281 +13,11 @@ from tests.common import MockConfigEntry -DEVICE_MOCKS = [ - "bzyd_45idzfufidgee7ir", # https://github.com/orgs/home-assistant/discussions/717 - "bzyd_ssimhf6r8kgwepfb", # https://github.com/orgs/home-assistant/discussions/718 - "cjkg_hmabvy81", # https://github.com/orgs/home-assistant/discussions/1297 - "cjkg_uenof8jd", # https://github.com/home-assistant/core/issues/151825 - "ckmkzq_1yyqfw4djv9eii3q", # https://github.com/home-assistant/core/issues/150856 - "cl_3r8gc33pnqsxfe1g", # https://github.com/tuya/tuya-home-assistant/issues/754 - "cl_669wsr2w4cvinbh4", # https://github.com/home-assistant/core/issues/150856 - "cl_cpbo62rn", # https://github.com/orgs/home-assistant/discussions/539 - "cl_ebt12ypvexnixvtf", # https://github.com/tuya/tuya-home-assistant/issues/754 - "cl_g1cp07dsqnbdbbki", # https://github.com/home-assistant/core/issues/139966 - "cl_lfkr93x0ukp5gaia", # https://github.com/home-assistant/core/issues/152826 - "cl_n3xgr5pdmpinictg", # https://github.com/home-assistant/core/issues/153537 - "cl_qqdxfdht", # https://github.com/orgs/home-assistant/discussions/539 - "cl_rD7uqAAgQOpSA2Rx", # https://github.com/home-assistant/core/issues/139966 - "cl_zah67ekd", # https://github.com/home-assistant/core/issues/71242 - "clkg_nhyj64w2", # https://github.com/home-assistant/core/issues/136055 - "clkg_wltqkykhni0papzj", # https://github.com/home-assistant/core/issues/151635 - "clkg_xqvhthwkbmp3aghs", # https://github.com/home-assistant/core/issues/139966 - "co2bj_yakol79dibtswovc", # https://github.com/home-assistant/core/issues/151784 - "co2bj_yrr3eiyiacm31ski", # https://github.com/orgs/home-assistant/discussions/842 - "cobj_hcdy5zrq3ikzthws", # https://github.com/orgs/home-assistant/discussions/482 - "cs_b9oyi2yofflroq1g", # https://github.com/home-assistant/core/issues/139966 - "cs_eguoms25tkxtf5u8", # https://github.com/home-assistant/core/issues/152361 - "cs_ipmyy4nigpqcnd8q", # https://github.com/home-assistant/core/pull/148726 - "cs_ka2wfrdoogpvgzfi", # https://github.com/home-assistant/core/issues/119865 - "cs_qhxmvae667uap4zh", # https://github.com/home-assistant/core/issues/141278 - "cs_vmxuxszzjwp5smli", # https://github.com/home-assistant/core/issues/119865 - "cs_u0wirz487erb0eka", # https://github.com/home-assistant/core/issues/155364 - "cs_zibqa9dutqyaxym2", # https://github.com/home-assistant/core/pull/125098 - "cwjwq_agwu93lr", # https://github.com/orgs/home-assistant/discussions/79 - "cwwsq_lxfvx41gqdotrkgi", # https://github.com/orgs/home-assistant/discussions/730 - "cwwsq_wfkzyy0evslzsmoi", # https://github.com/home-assistant/core/issues/144745 - "cwysj_akln8rb04cav403q", # https://github.com/home-assistant/core/pull/146599 - "cwysj_z3rpyvznfcch99aa", # https://github.com/home-assistant/core/pull/146599 - "cz_0fHWRe8ULjtmnBNd", # https://github.com/home-assistant/core/issues/139966 - "cz_0g1fmqh6d5io7lcn", # https://github.com/home-assistant/core/issues/149704 - "cz_2iepauebcvo74ujc", # https://github.com/home-assistant/core/issues/141278 - "cz_2jxesipczks0kdct", # https://github.com/home-assistant/core/issues/147149 - "cz_37mnhia3pojleqfh", # https://github.com/home-assistant/core/issues/146164 - "cz_39sy2g68gsjwo2xv", # https://github.com/home-assistant/core/issues/141278 - "cz_4mbgrfotyNzMxDAv", # https://github.com/home-assistant/core/issues/152295 - "cz_6fa7odsufen374x2", # https://github.com/home-assistant/core/issues/150029 - "cz_79a7z01v3n35kytb", # https://github.com/orgs/home-assistant/discussions/221 - "cz_9ivirni8wemum6cw", # https://github.com/home-assistant/core/issues/139735 - "cz_AiHXxAyyn7eAkLQY", # https://github.com/home-assistant/core/issues/150662 - "cz_CHLZe9HQ6QIXujVN", # https://github.com/home-assistant/core/issues/149233 - "cz_HBRBzv1UVBVfF6SL", # https://github.com/tuya/tuya-home-assistant/issues/754 - "cz_IGzCi97RpN2Lf9cu", # https://github.com/home-assistant/core/issues/139966 - "cz_PGEkBctAbtzKOZng", # https://github.com/home-assistant/core/issues/139966 - "cz_anwgf2xugjxpkfxb", # https://github.com/orgs/home-assistant/discussions/539 - "cz_cuhokdii7ojyw8k2", # https://github.com/home-assistant/core/issues/149704 - "cz_dhto3y4uachr1wll", # https://github.com/orgs/home-assistant/discussions/169 - "cz_dntgh2ngvshfxpsz", # https://github.com/home-assistant/core/issues/149704 - "cz_fencxse0bnut96ig", # https://github.com/home-assistant/core/issues/63978 - "cz_gbtxrqfy9xcsakyp", # https://github.com/home-assistant/core/issues/141278 - "cz_gjnozsaz", # https://github.com/orgs/home-assistant/discussions/482 - "cz_hA2GsgMfTQFTz9JL", # https://github.com/home-assistant/core/issues/148347 - "cz_hj0a5c7ckzzexu8l", # https://github.com/home-assistant/core/issues/149704 - "cz_ik9sbig3mthx9hjz", # https://github.com/home-assistant/core/issues/141278 - "cz_ipabufmlmodje1ws", # https://github.com/home-assistant/core/issues/63978 - "cz_iqhidxhhmgxk5eja", # https://github.com/home-assistant/core/issues/149233 - "cz_jnbbxsb84gvvyfg5", # https://github.com/tuya/tuya-home-assistant/issues/754 - "cz_jti3ce2hzvsposgj", # https://github.com/home-assistant/core/issues/152295 - "cz_mQUhiTg9kwydBFBd", # https://github.com/home-assistant/core/issues/139966 - "cz_n8iVBAPLFKAAAszH", # https://github.com/home-assistant/core/issues/146164 - "cz_nkb0fmtlfyqosnvk", # https://github.com/orgs/home-assistant/discussions/482 - "cz_nx8rv6jpe1tsnffk", # https://github.com/home-assistant/core/issues/148347 - "cz_piuensvr", # https://github.com/home-assistant/core/issues/139966 - "cz_qm0iq4nqnrlzh4qc", # https://github.com/home-assistant/core/issues/141278 - "cz_qxJSyTLEtX5WrzA9", # https://github.com/home-assistant/core/issues/139966 - "cz_raceucn29wk2yawe", # https://github.com/tuya/tuya-home-assistant/issues/754 - "cz_sb6bwb1n8ma2c5q4", # https://github.com/home-assistant/core/issues/141278 - "cz_t0a4hwsf8anfsadp", # https://github.com/home-assistant/core/issues/149704 - "cz_tf6qp8t3hl9h7m94", # https://github.com/home-assistant/core/issues/143209 - "cz_tkn2s79mzedk6pwr", # https://github.com/home-assistant/core/issues/146164 - "cz_vrbpx6h7fsi5mujb", # https://github.com/home-assistant/core/pull/149234 - "cz_vxqn72kwtosoy4d3", # https://github.com/home-assistant/core/issues/141278 - "cz_w0qqde0g", # https://github.com/orgs/home-assistant/discussions/482 - "cz_wifvoilfrqeo6hvu", # https://github.com/home-assistant/core/issues/146164 - "cz_wrz6vzch8htux2zp", # https://github.com/home-assistant/core/issues/141278 - "cz_y4jnobxh", # https://github.com/orgs/home-assistant/discussions/482 - "cz_yncyws7tu1q4cpsz", # https://github.com/home-assistant/core/issues/150662 - "cz_z6pht25s3p0gs26q", # https://github.com/home-assistant/core/issues/63978 - "dc_l3bpgg8ibsagon4x", # https://github.com/home-assistant/core/issues/149704 - "dd_gaobbrxqiblcng2p", # https://github.com/home-assistant/core/issues/149233 - "dj_0gyaslysqfp4gfis", # https://github.com/home-assistant/core/issues/149895 - "dj_8szt7whdvwpmxglk", # https://github.com/home-assistant/core/issues/149704 - "dj_8ugheslg", # https://github.com/home-assistant/core/issues/150856 - "dj_8y0aquaa8v6tho8w", # https://github.com/home-assistant/core/issues/149704 - "dj_AqHUMdcbYzIq1Of4", # https://github.com/orgs/home-assistant/discussions/539 - "dj_amx1bgdrfab6jngb", # https://github.com/orgs/home-assistant/discussions/482 - "dj_bSXSSFArVKtc4DyC", # https://github.com/orgs/home-assistant/discussions/539 - "dj_baf9tt9lb8t5uc7z", # https://github.com/home-assistant/core/issues/149704 - "dj_c3nsqogqovapdpfj", # https://github.com/home-assistant/core/issues/146164 - "dj_d4g0fbsoaal841o6", # https://github.com/home-assistant/core/issues/149704 - "dj_dbou1ap4", # https://github.com/orgs/home-assistant/discussions/482 - "dj_djnozmdyqyriow8z", # https://github.com/home-assistant/core/issues/149704 - "dj_ekwolitfjhxn55js", # https://github.com/home-assistant/core/issues/149704 - "dj_fuupmcr2mb1odkja", # https://github.com/home-assistant/core/issues/149704 - "dj_h4aX2JkHZNByQ4AV", # https://github.com/home-assistant/core/issues/150662 - "dj_hp6orhaqm6as3jnv", # https://github.com/home-assistant/core/issues/149704 - "dj_hpc8ddyfv85haxa7", # https://github.com/home-assistant/core/issues/149704 - "dj_iayz2jmtlipjnxj7", # https://github.com/home-assistant/core/issues/149704 - "dj_idnfq7xbx8qewyoa", # https://github.com/home-assistant/core/issues/149704 - "dj_ilddqqih3tucdk68", # https://github.com/home-assistant/core/issues/149704 - "dj_j1bgp31cffutizub", # https://github.com/home-assistant/core/issues/149704 - "dj_kgaob37tz2muf3mi", # https://github.com/home-assistant/core/issues/150856 - "dj_lmnt3uyltk1xffrt", # https://github.com/home-assistant/core/issues/149704 - "dj_mki13ie507rlry4r", # https://github.com/home-assistant/core/pull/126242 - "dj_nbumqpv8vz61enji", # https://github.com/home-assistant/core/issues/149704 - "dj_nlxvjzy1hoeiqsg6", # https://github.com/home-assistant/core/issues/149704 - "dj_oe0cpnjg", # https://github.com/home-assistant/core/issues/149704 - "dj_qoqolwtqzfuhgghq", # https://github.com/home-assistant/core/issues/149233 - "dj_riwp3k79", # https://github.com/home-assistant/core/issues/149704 - "dj_tgewj70aowigv8fz", # https://github.com/orgs/home-assistant/discussions/539 - "dj_tmsloaroqavbucgn", # https://github.com/home-assistant/core/issues/149704 - "dj_ufq2xwuzd4nb0qdr", # https://github.com/home-assistant/core/issues/149704 - "dj_vqwcnabamzrc2kab", # https://github.com/home-assistant/core/issues/149704 - "dj_xdvitmhhmgefaeuq", # https://github.com/home-assistant/core/issues/146164 - "dj_xokdfs6kh5ednakk", # https://github.com/home-assistant/core/issues/149704 - "dj_zakhnlpdiu0ycdxn", # https://github.com/home-assistant/core/issues/149704 - "dj_zav1pa32pyxray78", # https://github.com/home-assistant/core/issues/149704 - "dj_zputiamzanuk6yky", # https://github.com/home-assistant/core/issues/149704 - "dlq_0tnvg2xaisqdadcf", # https://github.com/home-assistant/core/issues/102769 - "dlq_cnpkf4xdmd9v49iq", # https://github.com/home-assistant/core/pull/149320 - "dlq_dikb3dp6", # https://github.com/home-assistant/core/pull/151601 - "dlq_jdj6ccklup7btq3a", # https://github.com/home-assistant/core/issues/143209 - "dlq_kxdr6su0c55p7bbo", # https://github.com/home-assistant/core/issues/143499 - "dlq_r9kg2g1uhhyicycb", # https://github.com/home-assistant/core/issues/149650 - "dlq_z3jngbyubvwgfrcv", # https://github.com/home-assistant/core/issues/150293 - "dr_pjvxl1wsyqxivsaf", # https://github.com/home-assistant/core/issues/84869 - "fs_g0ewlb1vmwqljzji", # https://github.com/home-assistant/core/issues/141231 - "fs_ibytpo6fpnugft1c", # https://github.com/home-assistant/core/issues/135541 - "fsd_9ecs16c53uqskxw6", # https://github.com/home-assistant/core/issues/149233 - "gyd_lgekqfxdabipm3tn", # https://github.com/home-assistant/core/issues/133173 - "hps_2aaelwxk", # https://github.com/home-assistant/core/issues/149704 - "hps_wqashyqo", # https://github.com/home-assistant/core/issues/146180 - "hwsb_ircs2n82vgrozoew", # https://github.com/home-assistant/core/issues/149233 - "jsq_r492ifwk6f2ssptb", # https://github.com/home-assistant/core/issues/151488 - "jtmspro_xqeob8h6", # https://github.com/orgs/home-assistant/discussions/517 - "kg_4nqs33emdwJxpQ8O", # https://github.com/orgs/home-assistant/discussions/539 - "kg_5ftkaulg", # https://github.com/orgs/home-assistant/discussions/539 - "kg_gbm9ata1zrzaez4a", # https://github.com/home-assistant/core/issues/148347 - "kj_CAjWAxBUZt7QZHfz", # https://github.com/home-assistant/core/issues/146023 - "kj_fsxtzzhujkrak2oy", # https://github.com/orgs/home-assistant/discussions/439 - "kj_s4uzibibgzdxzowo", # https://github.com/home-assistant/core/issues/150246 - "kj_yrzylxax1qspdgpp", # https://github.com/orgs/home-assistant/discussions/61 - "ks_j9fa8ahzac8uvlfl", # https://github.com/orgs/home-assistant/discussions/329 - "kt_5wnlzekkstwcdsvm", # https://github.com/home-assistant/core/pull/148646 - "kt_ibmmirhhq62mmf1g", # https://github.com/home-assistant/core/pull/150077 - "kt_vdadlnmsorlhw4td", # https://github.com/home-assistant/core/pull/149635 - "ldcg_9kbbfeho", # https://github.com/orgs/home-assistant/discussions/482 - "mal_gyitctrjj1kefxp2", # Alarm Host support - "mc_oSQljE9YDqwCwTUA", # https://github.com/home-assistant/core/issues/149233 - "mcs_6ywsnauy", # https://github.com/orgs/home-assistant/discussions/482 - "mcs_7jIGJAymiH8OsFFb", # https://github.com/home-assistant/core/issues/108301 - "mcs_8yhypbo7", # https://github.com/orgs/home-assistant/discussions/482 - "mcs_hx5ztlztij4yxxvg", # https://github.com/home-assistant/core/issues/148347 - "mcs_oxslv1c9", # https://github.com/home-assistant/core/issues/139966 - "mcs_qxu3flpqjsc1kqu3", # https://github.com/home-assistant/core/issues/141278 - "msp_3ddulzljdjjwkhoy", # https://github.com/orgs/home-assistant/discussions/262 - "msp_mzuro9jgjs7uiryn", # https://github.com/home-assistant/core/pull/156048 - "mzj_jlapoy5liocmtdvd", # https://github.com/home-assistant/core/issues/150662 - "mzj_qavcakohisj5adyh", # https://github.com/home-assistant/core/issues/141278 - "ntq_9mqdhwklpvnnvb7t", # https://github.com/orgs/home-assistant/discussions/517 - "pc_t2afic7i3v1bwhfp", # https://github.com/home-assistant/core/issues/149704 - "pc_trjopo1vdlt9q1tg", # https://github.com/home-assistant/core/issues/149704 - "pc_tsbguim4trl6fa7g", # https://github.com/home-assistant/core/issues/146164 - "pc_yku9wsimasckdt15", # https://github.com/orgs/home-assistant/discussions/482 - "pir_3amxzozho9xp4mkh", # https://github.com/home-assistant/core/issues/149704 - "pir_fcdjzz3s", # https://github.com/home-assistant/core/issues/149704 - "pir_j5jgnjvdaczeb6dc", # https://github.com/orgs/home-assistant/discussions/582 - "pir_wqz93nrdomectyoz", # https://github.com/home-assistant/core/issues/149704 - "qccdz_7bvgooyjhiua1yyq", # https://github.com/home-assistant/core/issues/136207 - "qn_5ls2jw49hpczwqng", # https://github.com/home-assistant/core/issues/149233 - "qt_TtXKwTMwiPpURWLJ", # https://github.com/home-assistant/core/issues/139966 - "qxj_fsea1lat3vuktbt6", # https://github.com/orgs/home-assistant/discussions/318 - "qxj_is2indt9nlth6esa", # https://github.com/home-assistant/core/issues/136472 - "qxj_xbwbniyt6bgws9ia", # https://github.com/orgs/home-assistant/discussions/823 - "rqbj_4iqe2hsfyd86kwwc", # https://github.com/orgs/home-assistant/discussions/100 - "rs_d7woucobqi8ncacf", # https://github.com/orgs/home-assistant/discussions/1021 - "sd_i6hyjg3af7doaswm", # https://github.com/orgs/home-assistant/discussions/539 - "sd_lr33znaodtyarrrz", # https://github.com/home-assistant/core/issues/141278 - "sfkzq_1fcnd8xk", # https://github.com/orgs/home-assistant/discussions/539 - "sfkzq_d4vpmigg", # https://github.com/home-assistant/core/issues/150662 - "sfkzq_ed7frwissyqrejic", # https://github.com/home-assistant/core/pull/149236 - "sfkzq_nxquc5lb", # https://github.com/home-assistant/core/issues/150662 - "sfkzq_o6dagifntoafakst", # https://github.com/home-assistant/core/issues/148116 - "sfkzq_rzklytdei8i8vo37", # https://github.com/home-assistant/core/issues/146164 - "sgbj_DYgId0sz6zWlmmYu", # https://github.com/orgs/home-assistant/discussions/583 - "sgbj_im2eqqhj72suwwko", # https://github.com/home-assistant/core/issues/151082 - "sgbj_ulv4nnue7gqp0rjk", # https://github.com/home-assistant/core/issues/149704 - "sj_rzeSU2h9uoklxEwq", # https://github.com/home-assistant/core/issues/150683 - "sj_tgvtvdoc", # https://github.com/orgs/home-assistant/discussions/482 - "sjz_ftbc8rp8ipksdfpv", # https://github.com/orgs/home-assistant/discussions/51 - "sp_6bmk1remyscwyx6i", # https://github.com/orgs/home-assistant/discussions/842 - "sp_csr2fqitalj5o0tq", # https://github.com/home-assistant/core/pull/156103 - "sp_drezasavompxpcgm", # https://github.com/home-assistant/core/issues/149704 - "sp_nzauwyj3mcnjnf35", # https://github.com/home-assistant/core/issues/141278 - "sp_rjKXWRohlvOTyLBu", # https://github.com/home-assistant/core/issues/149704 - "sp_rudejjigkywujjvs", # https://github.com/home-assistant/core/issues/146164 - "sp_sdd5f5f2dl5wydjf", # https://github.com/home-assistant/core/issues/144087 - "swtz_3rzngbyy", # https://github.com/orgs/home-assistant/discussions/688 - "szjcy_u5xgcpcngk3pfxb4", # https://github.com/orgs/home-assistant/discussions/934 - "tdq_1aegphq4yfd50e6b", # https://github.com/home-assistant/core/issues/143209 - "tdq_9htyiowaf5rtdhrv", # https://github.com/home-assistant/core/issues/143209 - "tdq_cq1p0nt0a4rixnex", # https://github.com/home-assistant/core/issues/146845 - "tdq_nockvv2k39vbrxxk", # https://github.com/home-assistant/core/issues/145849 - "tdq_p6sqiuesvhmhvv4f", # https://github.com/orgs/home-assistant/discussions/430 - "tdq_pu8uhxhwcp3tgoz7", # https://github.com/home-assistant/core/issues/141278 - "tdq_uoa3mayicscacseb", # https://github.com/home-assistant/core/issues/128911 - "tdq_x3o8epevyeo3z3oa", # https://github.com/orgs/home-assistant/discussions/430 - "tyndj_pyakuuoc", # https://github.com/home-assistant/core/issues/149704 - "wfcon_b25mh8sxawsgndck", # https://github.com/home-assistant/core/issues/149704 - "wfcon_lieerjyy6l4ykjor", # https://github.com/home-assistant/core/issues/136055 - "wfcon_plp0gnfcacdeqk5o", # https://github.com/home-assistant/core/issues/139966 - "wg2_2gowdgni", # https://github.com/home-assistant/core/issues/150856 - "wg2_haclbl0qkqlf2qds", # https://github.com/orgs/home-assistant/discussions/517 - "wg2_nwxr8qcu4seltoro", # https://github.com/orgs/home-assistant/discussions/430 - "wg2_setmxeqgs63xwopm", # https://github.com/orgs/home-assistant/discussions/539 - "wg2_tmwhss6ntjfc7prs", # https://github.com/home-assistant/core/issues/150662 - "wg2_v7owd9tzcaninc36", # https://github.com/orgs/home-assistant/discussions/539 - "wk_6kijc7nd", # https://github.com/home-assistant/core/issues/136513 - "wk_IAYz2WK1th0cMLmL", # https://github.com/home-assistant/core/issues/150077 - "wk_aqoouq7x", # https://github.com/home-assistant/core/issues/146263 - "wk_ccpwojhalfxryigz", # https://github.com/home-assistant/core/issues/145551 - "wk_cpmgn2cf", # https://github.com/orgs/home-assistant/discussions/684 - "wk_fi6dne5tu4t1nm6j", # https://github.com/orgs/home-assistant/discussions/243 - "wk_gc1bxoq2hafxpa35", # https://github.com/home-assistant/core/issues/145551 - "wk_gogb05wrtredz3bs", # https://github.com/home-assistant/core/issues/136337 - "wk_t94pit6zjbask9qo", # https://github.com/home-assistant/core/pull/156781 - "wk_tfbhw0mg", # https://github.com/home-assistant/core/issues/152282 - "wk_y5obtqhuztqsf2mj", # https://github.com/home-assistant/core/issues/139735 - "wkcz_gc4b1mdw7kebtuyz", # https://github.com/home-assistant/core/issues/135617 - "wkf_9xfjixap", # https://github.com/home-assistant/core/issues/139966 - "wkf_p3dbf6qs", # https://github.com/home-assistant/core/issues/139966 - "wnykq_kzwdw5bpxlbs9h9g", # https://github.com/orgs/home-assistant/discussions/842 - "wnykq_npbbca46yiug8ysk", # https://github.com/orgs/home-assistant/discussions/539 - "wnykq_om518smspsaltzdi", # https://github.com/home-assistant/core/issues/150662 - "wnykq_rqhxdyusjrwxyff6", # https://github.com/home-assistant/core/issues/133173 - "wsdcg_g2y6z3p3ja2qhyav", # https://github.com/home-assistant/core/issues/102769 - "wsdcg_iq4ygaai", # https://github.com/orgs/home-assistant/discussions/482 - "wsdcg_iv7hudlj", # https://github.com/home-assistant/core/issues/141278 - "wsdcg_krlcihrpzpc8olw9", # https://github.com/orgs/home-assistant/discussions/517 - "wsdcg_lf36y5nwb8jkxwgg", # https://github.com/orgs/home-assistant/discussions/539 - "wsdcg_qrztc3ev", # https://github.com/home-assistant/core/issues/139966 - "wsdcg_vtA4pDd6PLUZzXgZ", # https://github.com/orgs/home-assistant/discussions/482 - "wsdcg_xr3htd96", # https://github.com/orgs/home-assistant/discussions/482 - "wsdcg_yqiqbaldtr0i7mru", # https://github.com/home-assistant/core/issues/136223 - "wxkg_ja5osu5g", # https://github.com/orgs/home-assistant/discussions/482 - "wxkg_l8yaz4um5b3pwyvf", # https://github.com/home-assistant/core/issues/93975 - "wxnbq_5l1ht8jygsyr1wn1", # https://github.com/orgs/home-assistant/discussions/685 - "xdd_shx9mmadyyeaq88t", # https://github.com/home-assistant/core/issues/151141 - "xnyjcn_pb0tc75khaik8qbg", # https://github.com/home-assistant/core/pull/149237 - "ydkt_jevroj5aguwdbs2e", # https://github.com/orgs/home-assistant/discussions/288 - "ygsb_l6ax0u6jwbz82atk", # https://github.com/home-assistant/core/issues/146319 - "ykq_bngwdjsr", # https://github.com/orgs/home-assistant/discussions/482 - "ywbj_arywmw6h6vesoz5t", # https://github.com/home-assistant/core/issues/146164 - "ywbj_cjlutkuuvxnie17o", # https://github.com/home-assistant/core/issues/146164 - "ywbj_gf9dejhmzffgdyfj", # https://github.com/home-assistant/core/issues/149704 - "ywbj_kscbebaf3s1eogvt", # https://github.com/home-assistant/core/issues/141278 - "ywbj_rccxox8p", # https://github.com/orgs/home-assistant/discussions/625 - "ywcgq_h8lvyoahr6s6aybf", # https://github.com/home-assistant/core/issues/145932 - "ywcgq_wtzwyhkev3b4ubns", # https://github.com/home-assistant/core/issues/103818 - "zjq_nkkl7uzv", # https://github.com/orgs/home-assistant/discussions/482 - "zndb_4ggkyflayu1h1ho9", # https://github.com/home-assistant/core/pull/149317 - "zndb_v5jlnn5hwyffkhp3", # https://github.com/home-assistant/core/issues/143209 - "zndb_ze8faryrxr0glqnn", # https://github.com/home-assistant/core/issues/138372 - "znnbq_0kllybtbzftaee7y", # https://github.com/orgs/home-assistant/discussions/685 - "znnbq_6b3pbbuqbfabhfiq", # https://github.com/orgs/home-assistant/discussions/707 - "znrb_db81ge24jctwx8lo", # https://github.com/home-assistant/core/issues/136513 - "zwjcy_gvygg3m8", # https://github.com/orgs/home-assistant/discussions/949 - "zwjcy_myd45weu", # https://github.com/orgs/home-assistant/discussions/482 -] +FIXTURES_DIR = pathlib.Path(__file__).parent / "fixtures" +DEVICE_MOCKS = sorted( + str(path.relative_to(FIXTURES_DIR).with_suffix("")) + for path in FIXTURES_DIR.glob("*.json") +) class MockDeviceListener(DeviceListener): diff --git a/tests/components/tuya/snapshots/test_sensor.ambr b/tests/components/tuya/snapshots/test_sensor.ambr index 82f70f58208e65..7afe2ebc6a5692 100644 --- a/tests/components/tuya/snapshots/test_sensor.ambr +++ b/tests/components/tuya/snapshots/test_sensor.ambr @@ -10376,12 +10376,14 @@ 'state': '0.0', }) # --- -# name: test_platform_setup_and_discovery[sensor.kattenbak_excretion_time_day-entry] +# name: test_platform_setup_and_discovery[sensor.kattenbak_excretion_duration-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), 'area_id': None, - 'capabilities': None, + 'capabilities': dict({ + 'state_class': , + }), 'config_entry_id': , 'config_subentry_id': , 'device_class': None, @@ -10389,7 +10391,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.kattenbak_excretion_time_day', + 'entity_id': 'sensor.kattenbak_excretion_duration', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -10404,7 +10406,7 @@ }), 'original_device_class': , 'original_icon': None, - 'original_name': 'Excretion time (day)', + 'original_name': 'Excretion duration', 'platform': 'tuya', 'previous_unique_id': None, 'suggested_object_id': None, @@ -10414,15 +10416,16 @@ 'unit_of_measurement': 's', }) # --- -# name: test_platform_setup_and_discovery[sensor.kattenbak_excretion_time_day-state] +# name: test_platform_setup_and_discovery[sensor.kattenbak_excretion_duration-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'duration', - 'friendly_name': 'Kattenbak Excretion time (day)', + 'friendly_name': 'Kattenbak Excretion duration', + 'state_class': , 'unit_of_measurement': 's', }), 'context': , - 'entity_id': 'sensor.kattenbak_excretion_time_day', + 'entity_id': 'sensor.kattenbak_excretion_duration', 'last_changed': , 'last_reported': , 'last_updated': , @@ -10478,6 +10481,54 @@ 'state': '1.0', }) # --- +# name: test_platform_setup_and_discovery[sensor.kattenbak_status-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.kattenbak_status', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Status', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'cat_litter_box_status', + 'unique_id': 'tuya.yohkwjjdjlzludd3psmstatus', + 'unit_of_measurement': None, + }) +# --- +# name: test_platform_setup_and_discovery[sensor.kattenbak_status-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Kattenbak Status', + }), + 'context': , + 'entity_id': 'sensor.kattenbak_status', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'standby', + }) +# --- # name: test_platform_setup_and_discovery[sensor.keller_current-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ @@ -15243,12 +15294,14 @@ 'state': '3.6', }) # --- -# name: test_platform_setup_and_discovery[sensor.poopy_nano_2_excretion_time_day-entry] +# name: test_platform_setup_and_discovery[sensor.poopy_nano_2_excretion_duration-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), 'area_id': None, - 'capabilities': None, + 'capabilities': dict({ + 'state_class': , + }), 'config_entry_id': , 'config_subentry_id': , 'device_class': None, @@ -15256,7 +15309,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.poopy_nano_2_excretion_time_day', + 'entity_id': 'sensor.poopy_nano_2_excretion_duration', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -15271,7 +15324,7 @@ }), 'original_device_class': , 'original_icon': None, - 'original_name': 'Excretion time (day)', + 'original_name': 'Excretion duration', 'platform': 'tuya', 'previous_unique_id': None, 'suggested_object_id': None, @@ -15281,15 +15334,16 @@ 'unit_of_measurement': 's', }) # --- -# name: test_platform_setup_and_discovery[sensor.poopy_nano_2_excretion_time_day-state] +# name: test_platform_setup_and_discovery[sensor.poopy_nano_2_excretion_duration-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'duration', - 'friendly_name': 'Poopy Nano 2 Excretion time (day)', + 'friendly_name': 'Poopy Nano 2 Excretion duration', + 'state_class': , 'unit_of_measurement': 's', }), 'context': , - 'entity_id': 'sensor.poopy_nano_2_excretion_time_day', + 'entity_id': 'sensor.poopy_nano_2_excretion_duration', 'last_changed': , 'last_reported': , 'last_updated': , @@ -15345,6 +15399,54 @@ 'state': '4.0', }) # --- +# name: test_platform_setup_and_discovery[sensor.poopy_nano_2_status-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.poopy_nano_2_status', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Status', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'cat_litter_box_status', + 'unique_id': 'tuya.nyriu7sjgj9oruzmpsmstatus', + 'unit_of_measurement': None, + }) +# --- +# name: test_platform_setup_and_discovery[sensor.poopy_nano_2_status-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Poopy Nano 2 Status', + }), + 'context': , + 'entity_id': 'sensor.poopy_nano_2_status', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- # name: test_platform_setup_and_discovery[sensor.production_total_energy-entry] EntityRegistryEntrySnapshot({ 'aliases': set({