Add miele devices dynamically (#144216)

* Use device class transation

* WIP Cleanup tests

* First 3

* First 3

* Button

* Climate

* Light

* Switch

* New and cleaner variant

* Update homeassistant/components/miele/entity.py

---------

Co-authored-by: Josef Zweck <josef@zweck.dev>
This commit is contained in:
Åke Strandberg 2025-05-08 21:20:02 +02:00 committed by GitHub
parent 34dbd1fb10
commit 337c64d69d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 389 additions and 109 deletions

View File

@ -263,14 +263,24 @@ async def async_setup_entry(
) -> None: ) -> None:
"""Set up the binary sensor platform.""" """Set up the binary sensor platform."""
coordinator = config_entry.runtime_data coordinator = config_entry.runtime_data
added_devices: set[str] = set()
def _async_add_new_devices() -> None:
nonlocal added_devices
new_devices_set, current_devices = coordinator.async_add_devices(added_devices)
added_devices = current_devices
async_add_entities( async_add_entities(
MieleBinarySensor(coordinator, device_id, definition.description) MieleBinarySensor(coordinator, device_id, definition.description)
for device_id, device in coordinator.data.devices.items() for device_id, device in coordinator.data.devices.items()
for definition in BINARY_SENSOR_TYPES for definition in BINARY_SENSOR_TYPES
if device.device_type in definition.types if device_id in new_devices_set and device.device_type in definition.types
) )
config_entry.async_on_unload(coordinator.async_add_listener(_async_add_new_devices))
_async_add_new_devices()
class MieleBinarySensor(MieleEntity, BinarySensorEntity): class MieleBinarySensor(MieleEntity, BinarySensorEntity):
"""Representation of a Binary Sensor.""" """Representation of a Binary Sensor."""

View File

@ -111,14 +111,23 @@ async def async_setup_entry(
) -> None: ) -> None:
"""Set up the button platform.""" """Set up the button platform."""
coordinator = config_entry.runtime_data coordinator = config_entry.runtime_data
added_devices: set[str] = set()
def _async_add_new_devices() -> None:
nonlocal added_devices
new_devices_set, current_devices = coordinator.async_add_devices(added_devices)
added_devices = current_devices
async_add_entities( async_add_entities(
MieleButton(coordinator, device_id, definition.description) MieleButton(coordinator, device_id, definition.description)
for device_id, device in coordinator.data.devices.items() for device_id, device in coordinator.data.devices.items()
for definition in BUTTON_TYPES for definition in BUTTON_TYPES
if device.device_type in definition.types if device_id in new_devices_set and device.device_type in definition.types
) )
config_entry.async_on_unload(coordinator.async_add_listener(_async_add_new_devices))
_async_add_new_devices()
class MieleButton(MieleEntity, ButtonEntity): class MieleButton(MieleEntity, ButtonEntity):
"""Representation of a Button.""" """Representation of a Button."""

View File

@ -131,16 +131,30 @@ async def async_setup_entry(
) -> None: ) -> None:
"""Set up the climate platform.""" """Set up the climate platform."""
coordinator = config_entry.runtime_data coordinator = config_entry.runtime_data
added_devices: set[str] = set()
def _async_add_new_devices() -> None:
nonlocal added_devices
new_devices_set, current_devices = coordinator.async_add_devices(added_devices)
added_devices = current_devices
async_add_entities( async_add_entities(
MieleClimate(coordinator, device_id, definition.description) MieleClimate(coordinator, device_id, definition.description)
for device_id, device in coordinator.data.devices.items() for device_id, device in coordinator.data.devices.items()
for definition in CLIMATE_TYPES for definition in CLIMATE_TYPES
if ( if (
device.device_type in definition.types device_id in new_devices_set
and (definition.description.value_fn(device) not in DISABLED_TEMP_ENTITIES) and device.device_type in definition.types
and (
definition.description.value_fn(device)
not in DISABLED_TEMP_ENTITIES
) )
) )
)
config_entry.async_on_unload(coordinator.async_add_listener(_async_add_new_devices))
_async_add_new_devices()
class MieleClimate(MieleEntity, ClimateEntity): class MieleClimate(MieleEntity, ClimateEntity):

View File

@ -3,6 +3,7 @@
from __future__ import annotations from __future__ import annotations
import asyncio.timeouts import asyncio.timeouts
from collections.abc import Callable
from dataclasses import dataclass from dataclasses import dataclass
from datetime import timedelta from datetime import timedelta
import logging import logging
@ -33,6 +34,11 @@ class MieleCoordinatorData:
class MieleDataUpdateCoordinator(DataUpdateCoordinator[MieleCoordinatorData]): class MieleDataUpdateCoordinator(DataUpdateCoordinator[MieleCoordinatorData]):
"""Coordinator for Miele data.""" """Coordinator for Miele data."""
config_entry: MieleConfigEntry
new_device_callbacks: list[Callable[[dict[str, MieleDevice]], None]] = []
known_devices: set[str] = set()
devices: dict[str, MieleDevice] = {}
def __init__( def __init__(
self, self,
hass: HomeAssistant, hass: HomeAssistant,
@ -56,12 +62,20 @@ class MieleDataUpdateCoordinator(DataUpdateCoordinator[MieleCoordinatorData]):
device_id: MieleDevice(device) device_id: MieleDevice(device)
for device_id, device in devices_json.items() for device_id, device in devices_json.items()
} }
self.devices = devices
actions = {} actions = {}
for device_id in devices: for device_id in devices:
actions_json = await self.api.get_actions(device_id) actions_json = await self.api.get_actions(device_id)
actions[device_id] = MieleAction(actions_json) actions[device_id] = MieleAction(actions_json)
return MieleCoordinatorData(devices=devices, actions=actions) return MieleCoordinatorData(devices=devices, actions=actions)
def async_add_devices(self, added_devices: set[str]) -> tuple[set[str], set[str]]:
"""Add devices."""
current_devices = set(self.devices)
new_devices: set[str] = current_devices - added_devices
return (new_devices, current_devices)
async def callback_update_data(self, devices_json: dict[str, dict]) -> None: async def callback_update_data(self, devices_json: dict[str, dict]) -> None:
"""Handle data update from the API.""" """Handle data update from the API."""
devices = { devices = {

View File

@ -65,14 +65,23 @@ async def async_setup_entry(
) -> None: ) -> None:
"""Set up the fan platform.""" """Set up the fan platform."""
coordinator = config_entry.runtime_data coordinator = config_entry.runtime_data
added_devices: set[str] = set()
def _async_add_new_devices() -> None:
nonlocal added_devices
new_devices_set, current_devices = coordinator.async_add_devices(added_devices)
added_devices = current_devices
async_add_entities( async_add_entities(
MieleFan(coordinator, device_id, definition.description) MieleFan(coordinator, device_id, definition.description)
for device_id, device in coordinator.data.devices.items() for device_id, device in coordinator.data.devices.items()
for definition in FAN_TYPES for definition in FAN_TYPES
if device.device_type in definition.types if device_id in new_devices_set and device.device_type in definition.types
) )
config_entry.async_on_unload(coordinator.async_add_listener(_async_add_new_devices))
_async_add_new_devices()
class MieleFan(MieleEntity, FanEntity): class MieleFan(MieleEntity, FanEntity):
"""Representation of a Fan.""" """Representation of a Fan."""

View File

@ -85,14 +85,23 @@ async def async_setup_entry(
) -> None: ) -> None:
"""Set up the light platform.""" """Set up the light platform."""
coordinator = config_entry.runtime_data coordinator = config_entry.runtime_data
added_devices: set[str] = set()
def _async_add_new_devices() -> None:
nonlocal added_devices
new_devices_set, current_devices = coordinator.async_add_devices(added_devices)
added_devices = current_devices
async_add_entities( async_add_entities(
MieleLight(coordinator, device_id, definition.description) MieleLight(coordinator, device_id, definition.description)
for device_id, device in coordinator.data.devices.items() for device_id, device in coordinator.data.devices.items()
for definition in LIGHT_TYPES for definition in LIGHT_TYPES
if device.device_type in definition.types if device_id in new_devices_set and device.device_type in definition.types
) )
config_entry.async_on_unload(coordinator.async_add_listener(_async_add_new_devices))
_async_add_new_devices()
class MieleLight(MieleEntity, LightEntity): class MieleLight(MieleEntity, LightEntity):
"""Representation of a Light.""" """Representation of a Light."""

View File

@ -426,9 +426,15 @@ async def async_setup_entry(
) -> None: ) -> None:
"""Set up the sensor platform.""" """Set up the sensor platform."""
coordinator = config_entry.runtime_data coordinator = config_entry.runtime_data
added_devices: set[str] = set()
def _async_add_new_devices() -> None:
nonlocal added_devices
entities: list = [] entities: list = []
entity_class: type[MieleSensor] entity_class: type[MieleSensor]
new_devices_set, current_devices = coordinator.async_add_devices(added_devices)
added_devices = current_devices
for device_id, device in coordinator.data.devices.items(): for device_id, device in coordinator.data.devices.items():
for definition in SENSOR_TYPES: for definition in SENSOR_TYPES:
if device.device_type in definition.types: if device.device_type in definition.types:
@ -442,7 +448,9 @@ async def async_setup_entry(
case _: case _:
entity_class = MieleSensor entity_class = MieleSensor
if ( if (
definition.description.device_class == SensorDeviceClass.TEMPERATURE device_id in new_devices_set
and definition.description.device_class
== SensorDeviceClass.TEMPERATURE
and definition.description.value_fn(device) and definition.description.value_fn(device)
== DISABLED_TEMPERATURE / 100 == DISABLED_TEMPERATURE / 100
): ):
@ -451,9 +459,11 @@ async def async_setup_entry(
entities.append( entities.append(
entity_class(coordinator, device_id, definition.description) entity_class(coordinator, device_id, definition.description)
) )
async_add_entities(entities) async_add_entities(entities)
config_entry.async_on_unload(coordinator.async_add_listener(_async_add_new_devices))
_async_add_new_devices()
APPLIANCE_ICONS = { APPLIANCE_ICONS = {
MieleAppliance.WASHING_MACHINE: "mdi:washing-machine", MieleAppliance.WASHING_MACHINE: "mdi:washing-machine",

View File

@ -116,12 +116,21 @@ async def async_setup_entry(
) -> None: ) -> None:
"""Set up the switch platform.""" """Set up the switch platform."""
coordinator = config_entry.runtime_data coordinator = config_entry.runtime_data
added_devices: set[str] = set()
entities: list = [] def _async_add_new_devices() -> None:
entity_class: type[MieleSwitch] nonlocal added_devices
new_devices_set, current_devices = coordinator.async_add_devices(added_devices)
added_devices = current_devices
entities = []
for device_id, device in coordinator.data.devices.items(): for device_id, device in coordinator.data.devices.items():
for definition in SWITCH_TYPES: for definition in SWITCH_TYPES:
if device.device_type in definition.types: if (
device_id in new_devices_set
and device.device_type in definition.types
):
entity_class: type[MieleSwitch] = MieleSwitch
match definition.description.key: match definition.description.key:
case "poweronoff": case "poweronoff":
entity_class = MielePowerSwitch entity_class = MielePowerSwitch
@ -133,6 +142,9 @@ async def async_setup_entry(
) )
async_add_entities(entities) async_add_entities(entities)
config_entry.async_on_unload(coordinator.async_add_listener(_async_add_new_devices))
_async_add_new_devices()
class MieleSwitch(MieleEntity, SwitchEntity): class MieleSwitch(MieleEntity, SwitchEntity):
"""Representation of a Switch.""" """Representation of a Switch."""

View File

@ -141,7 +141,7 @@ async def setup_platform(
with patch(f"homeassistant.components.{DOMAIN}.PLATFORMS", platforms): with patch(f"homeassistant.components.{DOMAIN}.PLATFORMS", platforms):
assert await hass.config_entries.async_setup(mock_config_entry.entry_id) assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()
yield yield mock_config_entry
@pytest.fixture @pytest.fixture

View File

@ -356,6 +356,106 @@
"batteryLevel": null "batteryLevel": null
} }
}, },
"DummyAppliance_18": {
"ident": {
"type": {
"key_localized": "Device type",
"value_raw": 18,
"value_localized": "Cooker Hood"
},
"deviceName": "",
"protocolVersion": 2,
"deviceIdentLabel": {
"fabNumber": "<fabNumber3>",
"fabIndex": "64",
"techType": "Fläkt",
"matNumber": "<matNumber3>",
"swids": ["<swid1>", "<swid2>", "<swid3>", "<...>"]
},
"xkmIdentLabel": {
"techType": "EK039W",
"releaseVersion": "02.72"
}
},
"state": {
"ProgramID": {
"value_raw": 1,
"value_localized": "Off",
"key_localized": "Program name"
},
"status": {
"value_raw": 1,
"value_localized": "Off",
"key_localized": "status"
},
"programType": {
"value_raw": 0,
"value_localized": "Program",
"key_localized": "Program type"
},
"programPhase": {
"value_raw": 4608,
"value_localized": "",
"key_localized": "Program phase"
},
"remainingTime": [0, 0],
"startTime": [0, 0],
"targetTemperature": [
{
"value_raw": -32768,
"value_localized": null,
"unit": "Celsius"
}
],
"temperature": [
{
"value_raw": -32768,
"value_localized": null,
"unit": "Celsius"
},
{
"value_raw": -32768,
"value_localized": null,
"unit": "Celsius"
},
{
"value_raw": -32768,
"value_localized": null,
"unit": "Celsius"
}
],
"signalInfo": false,
"signalFailure": false,
"signalDoor": false,
"remoteEnable": {
"fullRemoteControl": true,
"smartGrid": false,
"mobileStart": false
},
"ambientLight": 2,
"light": 1,
"elapsedTime": {},
"spinningSpeed": {
"unit": "rpm",
"value_raw": null,
"value_localized": null,
"key_localized": "Spin speed"
},
"dryingStep": {
"value_raw": null,
"value_localized": "",
"key_localized": "Drying level"
},
"ventilationStep": {
"value_raw": 0,
"value_localized": "0",
"key_localized": "Fan level"
},
"plateStep": [],
"ecoFeedback": null,
"batteryLevel": null
}
},
"Dummy_Appliance_4": { "Dummy_Appliance_4": {
"ident": { "ident": {
"type": { "type": {
@ -402,10 +502,28 @@
{ "value_raw": -32768, "value_localized": null, "unit": "Celsius" }, { "value_raw": -32768, "value_localized": null, "unit": "Celsius" },
{ "value_raw": -32768, "value_localized": null, "unit": "Celsius" } { "value_raw": -32768, "value_localized": null, "unit": "Celsius" }
], ],
"coreTargetTemperature": [
{ "value_raw": 7500, "value_localized": "75.0", "unit": "Celsius" }
],
"coreTemperature": [
{ "value_raw": 5200, "value_localized": "52.0", "unit": "Celsius" }
],
"temperature": [ "temperature": [
{ "value_raw": -32768, "value_localized": null, "unit": "Celsius" }, {
{ "value_raw": -32768, "value_localized": null, "unit": "Celsius" }, "value_raw": 17500,
{ "value_raw": -32768, "value_localized": null, "unit": "Celsius" } "value_localized": "175.0",
"unit": "Celsius"
},
{
"value_raw": -32768,
"value_localized": null,
"unit": "Celsius"
},
{
"value_raw": -32768,
"value_localized": null,
"unit": "Celsius"
}
], ],
"signalInfo": false, "signalInfo": false,
"signalFailure": false, "signalFailure": false,

View File

@ -5,7 +5,13 @@
"startTime": [], "startTime": [],
"ventilationStep": [], "ventilationStep": [],
"programId": [], "programId": [],
"targetTemperature": [], "targetTemperature": [
{
"zone": 1,
"min": -28,
"max": -14
}
],
"deviceName": true, "deviceName": true,
"powerOn": true, "powerOn": true,
"powerOff": false, "powerOff": false,

View File

@ -36,6 +36,11 @@
'startTime': list([ 'startTime': list([
]), ]),
'targetTemperature': list([ 'targetTemperature': list([
dict({
'max': -14,
'min': -28,
'zone': 1,
}),
]), ]),
'ventilationStep': list([ 'ventilationStep': list([
]), ]),
@ -64,6 +69,11 @@
'startTime': list([ 'startTime': list([
]), ]),
'targetTemperature': list([ 'targetTemperature': list([
dict({
'max': -14,
'min': -28,
'zone': 1,
}),
]), ]),
'ventilationStep': list([ 'ventilationStep': list([
]), ]),
@ -92,6 +102,11 @@
'startTime': list([ 'startTime': list([
]), ]),
'targetTemperature': list([ 'targetTemperature': list([
dict({
'max': -14,
'min': -28,
'zone': 1,
}),
]), ]),
'ventilationStep': list([ 'ventilationStep': list([
]), ]),
@ -120,6 +135,11 @@
'startTime': list([ 'startTime': list([
]), ]),
'targetTemperature': list([ 'targetTemperature': list([
dict({
'max': -14,
'min': -28,
'zone': 1,
}),
]), ]),
'ventilationStep': list([ 'ventilationStep': list([
]), ]),
@ -689,6 +709,11 @@
'startTime': list([ 'startTime': list([
]), ]),
'targetTemperature': list([ 'targetTemperature': list([
dict({
'max': -14,
'min': -28,
'zone': 1,
}),
]), ]),
'ventilationStep': list([ 'ventilationStep': list([
]), ]),

View File

@ -17,11 +17,10 @@ from tests.common import MockConfigEntry, snapshot_platform
async def test_binary_sensor_states( async def test_binary_sensor_states(
hass: HomeAssistant, hass: HomeAssistant,
mock_miele_client: MagicMock, mock_miele_client: MagicMock,
mock_config_entry: MockConfigEntry,
snapshot: SnapshotAssertion, snapshot: SnapshotAssertion,
entity_registry: er.EntityRegistry, entity_registry: er.EntityRegistry,
setup_platform: None, setup_platform: MockConfigEntry,
) -> None: ) -> None:
"""Test binary sensor state.""" """Test binary sensor state."""
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id) await snapshot_platform(hass, entity_registry, snapshot, setup_platform.entry_id)

View File

@ -24,21 +24,20 @@ ENTITY_ID = "button.washing_machine_start"
async def test_button_states( async def test_button_states(
hass: HomeAssistant, hass: HomeAssistant,
mock_miele_client: MagicMock, mock_miele_client: MagicMock,
mock_config_entry: MockConfigEntry,
snapshot: SnapshotAssertion, snapshot: SnapshotAssertion,
entity_registry: er.EntityRegistry, entity_registry: er.EntityRegistry,
setup_platform: None, setup_platform: MockConfigEntry,
) -> None: ) -> None:
"""Test button entity state.""" """Test button entity state."""
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id) await snapshot_platform(hass, entity_registry, snapshot, setup_platform.entry_id)
@pytest.mark.usefixtures("entity_registry_enabled_by_default") @pytest.mark.usefixtures("entity_registry_enabled_by_default")
async def test_button_press( async def test_button_press(
hass: HomeAssistant, hass: HomeAssistant,
mock_miele_client: MagicMock, mock_miele_client: MagicMock,
setup_platform: None, setup_platform: MockConfigEntry,
) -> None: ) -> None:
"""Test button press.""" """Test button press."""
@ -54,7 +53,7 @@ async def test_button_press(
async def test_api_failure( async def test_api_failure(
hass: HomeAssistant, hass: HomeAssistant,
mock_miele_client: MagicMock, mock_miele_client: MagicMock,
setup_platform: None, setup_platform: MockConfigEntry,
) -> None: ) -> None:
"""Test handling of exception from API.""" """Test handling of exception from API."""
mock_miele_client.send_action.side_effect = ClientResponseError("test", "Test") mock_miele_client.send_action.side_effect = ClientResponseError("test", "Test")

View File

@ -33,20 +33,19 @@ SERVICE_SET_TEMPERATURE = "set_temperature"
async def test_climate_states( async def test_climate_states(
hass: HomeAssistant, hass: HomeAssistant,
mock_miele_client: MagicMock, mock_miele_client: MagicMock,
mock_config_entry: MockConfigEntry,
snapshot: SnapshotAssertion, snapshot: SnapshotAssertion,
entity_registry: er.EntityRegistry, entity_registry: er.EntityRegistry,
setup_platform: None, setup_platform: MockConfigEntry,
) -> None: ) -> None:
"""Test climate entity state.""" """Test climate entity state."""
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id) await snapshot_platform(hass, entity_registry, snapshot, setup_platform.entry_id)
async def test_set_target( async def test_set_target(
hass: HomeAssistant, hass: HomeAssistant,
mock_miele_client: MagicMock, mock_miele_client: MagicMock,
setup_platform: None, setup_platform: MockConfigEntry,
) -> None: ) -> None:
"""Test the climate can be turned on/off.""" """Test the climate can be turned on/off."""
@ -64,7 +63,7 @@ async def test_set_target(
async def test_api_failure( async def test_api_failure(
hass: HomeAssistant, hass: HomeAssistant,
mock_miele_client: MagicMock, mock_miele_client: MagicMock,
setup_platform: None, setup_platform: MockConfigEntry,
) -> None: ) -> None:
"""Test handling of exception from API.""" """Test handling of exception from API."""
mock_miele_client.set_target_temperature.side_effect = ClientError mock_miele_client.set_target_temperature.side_effect = ClientError

View File

@ -25,14 +25,13 @@ ENTITY_ID = "fan.hood_fan"
async def test_fan_states( async def test_fan_states(
hass: HomeAssistant, hass: HomeAssistant,
mock_miele_client: MagicMock, mock_miele_client: MagicMock,
mock_config_entry: MockConfigEntry,
snapshot: SnapshotAssertion, snapshot: SnapshotAssertion,
entity_registry: er.EntityRegistry, entity_registry: er.EntityRegistry,
setup_platform: None, setup_platform: MockConfigEntry,
) -> None: ) -> None:
"""Test fan entity state.""" """Test fan entity state."""
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id) await snapshot_platform(hass, entity_registry, snapshot, setup_platform.entry_id)
@pytest.mark.parametrize("load_device_file", ["fan_devices.json"]) @pytest.mark.parametrize("load_device_file", ["fan_devices.json"])
@ -46,7 +45,7 @@ async def test_fan_states(
async def test_fan_control( async def test_fan_control(
hass: HomeAssistant, hass: HomeAssistant,
mock_miele_client: MagicMock, mock_miele_client: MagicMock,
setup_platform: None, setup_platform: MockConfigEntry,
service: str, service: str,
expected_argument: dict[str, Any], expected_argument: dict[str, Any],
) -> None: ) -> None:
@ -74,7 +73,7 @@ async def test_fan_control(
async def test_fan_set_speed( async def test_fan_set_speed(
hass: HomeAssistant, hass: HomeAssistant,
mock_miele_client: MagicMock, mock_miele_client: MagicMock,
setup_platform: None, setup_platform: MockConfigEntry,
service: str, service: str,
percentage: int, percentage: int,
expected_argument: dict[str, Any], expected_argument: dict[str, Any],
@ -102,7 +101,7 @@ async def test_fan_set_speed(
async def test_api_failure( async def test_api_failure(
hass: HomeAssistant, hass: HomeAssistant,
mock_miele_client: MagicMock, mock_miele_client: MagicMock,
setup_platform: None, setup_platform: MockConfigEntry,
service: str, service: str,
) -> None: ) -> None:
"""Test handling of exception from API.""" """Test handling of exception from API."""

View File

@ -1,10 +1,12 @@
"""Tests for init module.""" """Tests for init module."""
from datetime import timedelta
import http import http
import time import time
from unittest.mock import MagicMock from unittest.mock import MagicMock
from aiohttp import ClientConnectionError from aiohttp import ClientConnectionError
from freezegun.api import FrozenDateTimeFactory
from pymiele import OAUTH2_TOKEN from pymiele import OAUTH2_TOKEN
import pytest import pytest
from syrupy import SnapshotAssertion from syrupy import SnapshotAssertion
@ -17,7 +19,11 @@ from homeassistant.setup import async_setup_component
from . import setup_integration from . import setup_integration
from tests.common import MockConfigEntry from tests.common import (
MockConfigEntry,
async_fire_time_changed,
load_json_object_fixture,
)
from tests.test_util.aiohttp import AiohttpClientMocker from tests.test_util.aiohttp import AiohttpClientMocker
from tests.typing import WebSocketGenerator from tests.typing import WebSocketGenerator
@ -157,3 +163,48 @@ async def test_device_remove_devices(
old_device_entry.id, mock_config_entry.entry_id old_device_entry.id, mock_config_entry.entry_id
) )
assert response["success"] assert response["success"]
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
async def test_setup_all_platforms(
hass: HomeAssistant,
mock_miele_client: MagicMock,
mock_config_entry: MockConfigEntry,
device_registry: dr.DeviceRegistry,
load_device_file: str,
freezer: FrozenDateTimeFactory,
) -> None:
"""Test that all platforms can be set up."""
await setup_integration(hass, mock_config_entry)
assert hass.states.get("binary_sensor.freezer_door").state == "off"
assert hass.states.get("binary_sensor.hood_problem").state == "off"
assert (
hass.states.get("button.washing_machine_start").object_id
== "washing_machine_start"
)
assert hass.states.get("climate.freezer").state == "cool"
assert hass.states.get("light.hood_light").state == "on"
assert hass.states.get("sensor.freezer_temperature").state == "-18.0"
assert hass.states.get("sensor.washing_machine").state == "off"
assert hass.states.get("switch.washing_machine_power").state == "off"
# Add two devices and let the clock tick for 130 seconds
freezer.tick(timedelta(seconds=130))
mock_miele_client.get_devices.return_value = load_json_object_fixture(
"5_devices.json", DOMAIN
)
async_fire_time_changed(hass)
await hass.async_block_till_done()
assert len(device_registry.devices) == 6
# Check a sample sensor for each new device
assert hass.states.get("sensor.dishwasher").state == "in_use"
assert hass.states.get("sensor.oven_temperature").state == "175.0"

View File

@ -23,14 +23,13 @@ ENTITY_ID = "light.hood_light"
async def test_light_states( async def test_light_states(
hass: HomeAssistant, hass: HomeAssistant,
mock_miele_client: MagicMock, mock_miele_client: MagicMock,
mock_config_entry: MockConfigEntry,
snapshot: SnapshotAssertion, snapshot: SnapshotAssertion,
entity_registry: er.EntityRegistry, entity_registry: er.EntityRegistry,
setup_platform: None, setup_platform: MockConfigEntry,
) -> None: ) -> None:
"""Test light entity state.""" """Test light entity state."""
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id) await snapshot_platform(hass, entity_registry, snapshot, setup_platform.entry_id)
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -43,7 +42,7 @@ async def test_light_states(
async def test_light_toggle( async def test_light_toggle(
hass: HomeAssistant, hass: HomeAssistant,
mock_miele_client: MagicMock, mock_miele_client: MagicMock,
setup_platform: None, setup_platform: MockConfigEntry,
service: str, service: str,
light_state: int, light_state: int,
) -> None: ) -> None:
@ -67,7 +66,7 @@ async def test_light_toggle(
async def test_api_failure( async def test_api_failure(
hass: HomeAssistant, hass: HomeAssistant,
mock_miele_client: MagicMock, mock_miele_client: MagicMock,
setup_platform: None, setup_platform: MockConfigEntry,
service: str, service: str,
) -> None: ) -> None:
"""Test handling of exception from API.""" """Test handling of exception from API."""

View File

@ -17,11 +17,10 @@ from tests.common import MockConfigEntry, snapshot_platform
async def test_sensor_states( async def test_sensor_states(
hass: HomeAssistant, hass: HomeAssistant,
mock_miele_client: MagicMock, mock_miele_client: MagicMock,
mock_config_entry: MockConfigEntry,
snapshot: SnapshotAssertion, snapshot: SnapshotAssertion,
entity_registry: er.EntityRegistry, entity_registry: er.EntityRegistry,
setup_platform: None, setup_platform: MockConfigEntry,
) -> None: ) -> None:
"""Test sensor state.""" """Test sensor state."""
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id) await snapshot_platform(hass, entity_registry, snapshot, setup_platform.entry_id)

View File

@ -23,14 +23,13 @@ ENTITY_ID = "switch.freezer_superfreezing"
async def test_switch_states( async def test_switch_states(
hass: HomeAssistant, hass: HomeAssistant,
mock_miele_client: MagicMock, mock_miele_client: MagicMock,
mock_config_entry: MockConfigEntry,
snapshot: SnapshotAssertion, snapshot: SnapshotAssertion,
entity_registry: er.EntityRegistry, entity_registry: er.EntityRegistry,
setup_platform: None, setup_platform: MockConfigEntry,
) -> None: ) -> None:
"""Test switch entity state.""" """Test switch entity state."""
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id) await snapshot_platform(hass, entity_registry, snapshot, setup_platform.entry_id)
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -51,7 +50,7 @@ async def test_switch_states(
async def test_switching( async def test_switching(
hass: HomeAssistant, hass: HomeAssistant,
mock_miele_client: MagicMock, mock_miele_client: MagicMock,
setup_platform: None, setup_platform: MockConfigEntry,
service: str, service: str,
entity: str, entity: str,
) -> None: ) -> None:
@ -81,7 +80,7 @@ async def test_switching(
async def test_api_failure( async def test_api_failure(
hass: HomeAssistant, hass: HomeAssistant,
mock_miele_client: MagicMock, mock_miele_client: MagicMock,
setup_platform: None, setup_platform: MockConfigEntry,
service: str, service: str,
entity: str, entity: str,
) -> None: ) -> None: