Add tests to miele fan entity and api push data pathway (#144481)

* Use device class transation

* WIP

* Test api push

* Use constants

* Use callbacks registered with mock

* Add comment

* Adress review comments

* Empty commit

* Fix tests

* Updates after review
This commit is contained in:
Åke Strandberg 2025-05-26 16:38:41 +02:00 committed by GitHub
parent 6f9a39ab89
commit 42cacd28e7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 3294 additions and 21 deletions

View File

@ -197,13 +197,13 @@ class MieleClimate(MieleEntity, ClimateEntity):
self._attr_name = None self._attr_name = None
if description.zone == 2: if description.zone == 2:
t_key = "zone_2"
if self.device.device_type in ( if self.device.device_type in (
MieleAppliance.FRIDGE_FREEZER, MieleAppliance.FRIDGE_FREEZER,
MieleAppliance.WINE_CABINET_FREEZER, MieleAppliance.WINE_CABINET_FREEZER,
): ):
t_key = DEVICE_TYPE_TAGS[MieleAppliance.FREEZER] t_key = DEVICE_TYPE_TAGS[MieleAppliance.FREEZER]
else:
t_key = "zone_2"
elif description.zone == 3: elif description.zone == 3:
t_key = "zone_3" t_key = "zone_3"
@ -234,11 +234,11 @@ class MieleClimate(MieleEntity, ClimateEntity):
async def async_set_temperature(self, **kwargs: Any) -> None: async def async_set_temperature(self, **kwargs: Any) -> None:
"""Set new target temperature.""" """Set new target temperature."""
if (temperature := kwargs.get(ATTR_TEMPERATURE)) is None:
return
try: try:
await self.api.set_target_temperature( await self.api.set_target_temperature(
self._device_id, temperature, self.entity_description.zone self._device_id,
cast(float, kwargs.get(ATTR_TEMPERATURE)),
self.entity_description.zone,
) )
except aiohttp.ClientError as err: except aiohttp.ClientError as err:
raise HomeAssistantError( raise HomeAssistantError(

View File

@ -1,5 +1,8 @@
"""Tests for the Miele integration.""" """Tests for the Miele integration."""
from collections.abc import Awaitable, Callable
from unittest.mock import AsyncMock
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from tests.common import MockConfigEntry from tests.common import MockConfigEntry
@ -11,3 +14,13 @@ async def setup_integration(hass: HomeAssistant, config_entry: MockConfigEntry)
await hass.config_entries.async_setup(config_entry.entry_id) await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()
def get_data_callback(mock: AsyncMock) -> Callable[[int], Awaitable[None]]:
"""Get registered callback for api data push."""
return mock.listen_events.call_args_list[0].kwargs.get("data_callback")
def get_actions_callback(mock: AsyncMock) -> Callable[[int], Awaitable[None]]:
"""Get registered callback for api data push."""
return mock.listen_events.call_args_list[0].kwargs.get("actions_callback")

View File

@ -15,6 +15,7 @@ from homeassistant.components.miele.const import DOMAIN
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.setup import async_setup_component from homeassistant.setup import async_setup_component
from . import get_actions_callback, get_data_callback
from .const import CLIENT_ID, CLIENT_SECRET from .const import CLIENT_ID, CLIENT_SECRET
from tests.common import MockConfigEntry, load_fixture, load_json_object_fixture from tests.common import MockConfigEntry, load_fixture, load_json_object_fixture
@ -157,3 +158,21 @@ def mock_setup_entry() -> Generator[AsyncMock]:
"homeassistant.components.miele.async_setup_entry", return_value=True "homeassistant.components.miele.async_setup_entry", return_value=True
) as mock_setup_entry: ) as mock_setup_entry:
yield mock_setup_entry yield mock_setup_entry
@pytest.fixture
async def push_data_and_actions(
hass: HomeAssistant,
mock_miele_client: MagicMock,
device_fixture: MieleDevices,
) -> None:
"""Fixture to push data and actions through mock."""
data_callback = get_data_callback(mock_miele_client)
await data_callback(device_fixture)
await hass.async_block_till_done()
act_file = load_json_object_fixture("4_actions.json", DOMAIN)
action_callback = get_actions_callback(mock_miele_client)
await action_callback(act_file)
await hass.async_block_till_done()

View File

@ -0,0 +1,86 @@
{
"Dummy_Appliance_1": {
"processAction": [4],
"light": [],
"ambientLight": [],
"startTime": [],
"ventilationStep": [],
"programId": [],
"targetTemperature": [
{
"zone": 1,
"min": -27,
"max": -13
}
],
"deviceName": true,
"powerOn": false,
"powerOff": false,
"colors": [],
"modes": [1],
"runOnTime": []
},
"Dummy_Appliance_2": {
"processAction": [6],
"light": [],
"ambientLight": [],
"startTime": [],
"ventilationStep": [],
"programId": [],
"targetTemperature": [
{
"zone": 1,
"min": 1,
"max": 9
}
],
"deviceName": true,
"powerOn": false,
"powerOff": false,
"colors": [],
"modes": [1],
"runOnTime": []
},
"Dummy_Appliance_3": {
"processAction": [1, 2, 3],
"light": [],
"ambientLight": [],
"startTime": [],
"ventilationStep": [],
"programId": [],
"targetTemperature": [
{
"zone": 1,
"min": 1,
"max": 9
}
],
"deviceName": true,
"powerOn": true,
"powerOff": false,
"colors": [],
"modes": [],
"runOnTime": []
},
"DummyAppliance_18": {
"processAction": [],
"light": [],
"ambientLight": [],
"startTime": [],
"ventilationStep": [],
"programId": [],
"targetTemperature": [
{
"zone": 1,
"min": 1,
"max": 9
}
],
"deviceName": true,
"powerOn": true,
"powerOff": false,
"colors": [],
"modes": [],
"runOnTime": []
}
}

View File

@ -0,0 +1,17 @@
{
"Dummy_Vacuum_1": {
"processAction": [],
"light": [],
"ambientLight": [],
"startTime": [],
"ventilationStep": [],
"programId": [3],
"targetTemperature": [],
"deviceName": true,
"powerOn": true,
"powerOff": false,
"colors": [],
"modes": [],
"runOnTime": []
}
}

View File

@ -9,7 +9,7 @@
{ {
"zone": 1, "zone": 1,
"min": -28, "min": -28,
"max": -14 "max": 28
} }
], ],
"deviceName": true, "deviceName": true,

File diff suppressed because it is too large Load Diff

View File

@ -187,3 +187,191 @@
'state': 'unknown', 'state': 'unknown',
}) })
# --- # ---
# name: test_button_states_api_push[platforms0][button.hood_stop-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'button',
'entity_category': None,
'entity_id': 'button.hood_stop',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Stop',
'platform': 'miele',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'stop',
'unique_id': 'DummyAppliance_18-stop',
'unit_of_measurement': None,
})
# ---
# name: test_button_states_api_push[platforms0][button.hood_stop-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Hood Stop',
}),
'context': <ANY>,
'entity_id': 'button.hood_stop',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unavailable',
})
# ---
# name: test_button_states_api_push[platforms0][button.washing_machine_pause-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'button',
'entity_category': None,
'entity_id': 'button.washing_machine_pause',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Pause',
'platform': 'miele',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'pause',
'unique_id': 'Dummy_Appliance_3-pause',
'unit_of_measurement': None,
})
# ---
# name: test_button_states_api_push[platforms0][button.washing_machine_pause-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Washing machine Pause',
}),
'context': <ANY>,
'entity_id': 'button.washing_machine_pause',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
# name: test_button_states_api_push[platforms0][button.washing_machine_start-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'button',
'entity_category': None,
'entity_id': 'button.washing_machine_start',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Start',
'platform': 'miele',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'start',
'unique_id': 'Dummy_Appliance_3-start',
'unit_of_measurement': None,
})
# ---
# name: test_button_states_api_push[platforms0][button.washing_machine_start-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Washing machine Start',
}),
'context': <ANY>,
'entity_id': 'button.washing_machine_start',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
# name: test_button_states_api_push[platforms0][button.washing_machine_stop-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'button',
'entity_category': None,
'entity_id': 'button.washing_machine_stop',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Stop',
'platform': 'miele',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'stop',
'unique_id': 'Dummy_Appliance_3-stop',
'unit_of_measurement': None,
})
# ---
# name: test_button_states_api_push[platforms0][button.washing_machine_stop-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Washing machine Stop',
}),
'context': <ANY>,
'entity_id': 'button.washing_machine_stop',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---

View File

@ -125,3 +125,129 @@
'state': 'cool', 'state': 'cool',
}) })
# --- # ---
# name: test_climate_states_api_push[platforms0-freezer][climate.freezer-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'hvac_modes': list([
<HVACMode.COOL: 'cool'>,
]),
'max_temp': -13,
'min_temp': -27,
'target_temp_step': 1.0,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'climate',
'entity_category': None,
'entity_id': 'climate.freezer',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': None,
'platform': 'miele',
'previous_unique_id': None,
'supported_features': <ClimateEntityFeature: 1>,
'translation_key': 'freezer',
'unique_id': 'Dummy_Appliance_1-thermostat-1',
'unit_of_measurement': None,
})
# ---
# name: test_climate_states_api_push[platforms0-freezer][climate.freezer-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'current_temperature': -18,
'friendly_name': 'Freezer',
'hvac_modes': list([
<HVACMode.COOL: 'cool'>,
]),
'max_temp': -13,
'min_temp': -27,
'supported_features': <ClimateEntityFeature: 1>,
'target_temp_step': 1.0,
'temperature': -18,
}),
'context': <ANY>,
'entity_id': 'climate.freezer',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'cool',
})
# ---
# name: test_climate_states_api_push[platforms0-freezer][climate.refrigerator-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'hvac_modes': list([
<HVACMode.COOL: 'cool'>,
]),
'max_temp': 9,
'min_temp': 1,
'target_temp_step': 1.0,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'climate',
'entity_category': None,
'entity_id': 'climate.refrigerator',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': None,
'platform': 'miele',
'previous_unique_id': None,
'supported_features': <ClimateEntityFeature: 1>,
'translation_key': 'refrigerator',
'unique_id': 'Dummy_Appliance_2-thermostat-1',
'unit_of_measurement': None,
})
# ---
# name: test_climate_states_api_push[platforms0-freezer][climate.refrigerator-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'current_temperature': 4,
'friendly_name': 'Refrigerator',
'hvac_modes': list([
<HVACMode.COOL: 'cool'>,
]),
'max_temp': 9,
'min_temp': 1,
'supported_features': <ClimateEntityFeature: 1>,
'target_temp_step': 1.0,
'temperature': 4,
}),
'context': <ANY>,
'entity_id': 'climate.refrigerator',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'cool',
})
# ---

View File

@ -37,7 +37,7 @@
]), ]),
'targetTemperature': list([ 'targetTemperature': list([
dict({ dict({
'max': -14, 'max': 28,
'min': -28, 'min': -28,
'zone': 1, 'zone': 1,
}), }),
@ -70,7 +70,7 @@
]), ]),
'targetTemperature': list([ 'targetTemperature': list([
dict({ dict({
'max': -14, 'max': 28,
'min': -28, 'min': -28,
'zone': 1, 'zone': 1,
}), }),
@ -103,7 +103,7 @@
]), ]),
'targetTemperature': list([ 'targetTemperature': list([
dict({ dict({
'max': -14, 'max': 28,
'min': -28, 'min': -28,
'zone': 1, 'zone': 1,
}), }),
@ -136,7 +136,7 @@
]), ]),
'targetTemperature': list([ 'targetTemperature': list([
dict({ dict({
'max': -14, 'max': 28,
'min': -28, 'min': -28,
'zone': 1, 'zone': 1,
}), }),
@ -710,7 +710,7 @@
]), ]),
'targetTemperature': list([ 'targetTemperature': list([
dict({ dict({
'max': -14, 'max': 28,
'min': -28, 'min': -28,
'zone': 1, 'zone': 1,
}), }),

View File

@ -151,3 +151,57 @@
'state': 'off', 'state': 'off',
}) })
# --- # ---
# name: test_fan_states_api_push[platforms0][fan.hood_fan-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'preset_modes': None,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'fan',
'entity_category': None,
'entity_id': 'fan.hood_fan',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Fan',
'platform': 'miele',
'previous_unique_id': None,
'supported_features': <FanEntityFeature: 49>,
'translation_key': 'fan',
'unique_id': 'DummyAppliance_18-fan',
'unit_of_measurement': None,
})
# ---
# name: test_fan_states_api_push[platforms0][fan.hood_fan-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Hood Fan',
'percentage': 0,
'percentage_step': 25.0,
'preset_mode': None,
'preset_modes': None,
'supported_features': <FanEntityFeature: 49>,
}),
'context': <ANY>,
'entity_id': 'fan.hood_fan',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---

View File

@ -111,3 +111,115 @@
'state': 'on', 'state': 'on',
}) })
# --- # ---
# name: test_light_states_api_push[platforms0][light.hood_ambient_light-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'supported_color_modes': list([
<ColorMode.ONOFF: 'onoff'>,
]),
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'light',
'entity_category': None,
'entity_id': 'light.hood_ambient_light',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Ambient light',
'platform': 'miele',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'ambient_light',
'unique_id': 'DummyAppliance_18-ambient_light',
'unit_of_measurement': None,
})
# ---
# name: test_light_states_api_push[platforms0][light.hood_ambient_light-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'color_mode': None,
'friendly_name': 'Hood Ambient light',
'supported_color_modes': list([
<ColorMode.ONOFF: 'onoff'>,
]),
'supported_features': <LightEntityFeature: 0>,
}),
'context': <ANY>,
'entity_id': 'light.hood_ambient_light',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_light_states_api_push[platforms0][light.hood_light-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'supported_color_modes': list([
<ColorMode.ONOFF: 'onoff'>,
]),
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'light',
'entity_category': None,
'entity_id': 'light.hood_light',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Light',
'platform': 'miele',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'light',
'unique_id': 'DummyAppliance_18-light',
'unit_of_measurement': None,
})
# ---
# name: test_light_states_api_push[platforms0][light.hood_light-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'color_mode': <ColorMode.ONOFF: 'onoff'>,
'friendly_name': 'Hood Light',
'supported_color_modes': list([
<ColorMode.ONOFF: 'onoff'>,
]),
'supported_features': <LightEntityFeature: 0>,
}),
'context': <ANY>,
'entity_id': 'light.hood_light',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'on',
})
# ---

File diff suppressed because it is too large Load Diff

View File

@ -187,3 +187,191 @@
'state': 'off', 'state': 'off',
}) })
# --- # ---
# name: test_switch_states_api_push[platforms0][switch.freezer_superfreezing-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'switch',
'entity_category': None,
'entity_id': 'switch.freezer_superfreezing',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Superfreezing',
'platform': 'miele',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'superfreezing',
'unique_id': 'Dummy_Appliance_1-superfreezing',
'unit_of_measurement': None,
})
# ---
# name: test_switch_states_api_push[platforms0][switch.freezer_superfreezing-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Freezer Superfreezing',
}),
'context': <ANY>,
'entity_id': 'switch.freezer_superfreezing',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_switch_states_api_push[platforms0][switch.hood_power-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'switch',
'entity_category': None,
'entity_id': 'switch.hood_power',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Power',
'platform': 'miele',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'power',
'unique_id': 'DummyAppliance_18-poweronoff',
'unit_of_measurement': None,
})
# ---
# name: test_switch_states_api_push[platforms0][switch.hood_power-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Hood Power',
}),
'context': <ANY>,
'entity_id': 'switch.hood_power',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_switch_states_api_push[platforms0][switch.refrigerator_supercooling-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'switch',
'entity_category': None,
'entity_id': 'switch.refrigerator_supercooling',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Supercooling',
'platform': 'miele',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'supercooling',
'unique_id': 'Dummy_Appliance_2-supercooling',
'unit_of_measurement': None,
})
# ---
# name: test_switch_states_api_push[platforms0][switch.refrigerator_supercooling-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Refrigerator Supercooling',
}),
'context': <ANY>,
'entity_id': 'switch.refrigerator_supercooling',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_switch_states_api_push[platforms0][switch.washing_machine_power-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'switch',
'entity_category': None,
'entity_id': 'switch.washing_machine_power',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Power',
'platform': 'miele',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'power',
'unique_id': 'Dummy_Appliance_3-poweronoff',
'unit_of_measurement': None,
})
# ---
# name: test_switch_states_api_push[platforms0][switch.washing_machine_power-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Washing machine Power',
}),
'context': <ANY>,
'entity_id': 'switch.washing_machine_power',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---

View File

@ -61,3 +61,65 @@
'state': 'cleaning', 'state': 'cleaning',
}) })
# --- # ---
# name: test_vacuum_states_api_push[platforms0-vacuum_device.json][vacuum.robot_vacuum_cleaner-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'fan_speed_list': list([
'normal',
'turbo',
'silent',
]),
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'vacuum',
'entity_category': None,
'entity_id': 'vacuum.robot_vacuum_cleaner',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': None,
'platform': 'miele',
'previous_unique_id': None,
'supported_features': <VacuumEntityFeature: 13420>,
'translation_key': 'vacuum',
'unique_id': 'Dummy_Vacuum_1-vacuum',
'unit_of_measurement': None,
})
# ---
# name: test_vacuum_states_api_push[platforms0-vacuum_device.json][vacuum.robot_vacuum_cleaner-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'battery_icon': 'mdi:battery-60',
'battery_level': 65,
'fan_speed': 'normal',
'fan_speed_list': list([
'normal',
'turbo',
'silent',
]),
'friendly_name': 'Robot vacuum cleaner',
'supported_features': <VacuumEntityFeature: 13420>,
}),
'context': <ANY>,
'entity_id': 'vacuum.robot_vacuum_cleaner',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'cleaning',
})
# ---

View File

@ -24,3 +24,18 @@ async def test_binary_sensor_states(
"""Test binary sensor state.""" """Test binary sensor state."""
await snapshot_platform(hass, entity_registry, snapshot, setup_platform.entry_id) await snapshot_platform(hass, entity_registry, snapshot, setup_platform.entry_id)
@pytest.mark.parametrize("platforms", [(BINARY_SENSOR_DOMAIN,)])
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
async def test_binary_sensor_states_api_push(
hass: HomeAssistant,
mock_miele_client: MagicMock,
snapshot: SnapshotAssertion,
entity_registry: er.EntityRegistry,
setup_platform: MockConfigEntry,
push_data_and_actions: None,
) -> None:
"""Test binary sensor state when the API pushes data via SSE."""
await snapshot_platform(hass, entity_registry, snapshot, setup_platform.entry_id)

View File

@ -33,6 +33,20 @@ async def test_button_states(
await snapshot_platform(hass, entity_registry, snapshot, setup_platform.entry_id) await snapshot_platform(hass, entity_registry, snapshot, setup_platform.entry_id)
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
async def test_button_states_api_push(
hass: HomeAssistant,
mock_miele_client: MagicMock,
snapshot: SnapshotAssertion,
entity_registry: er.EntityRegistry,
setup_platform: MockConfigEntry,
push_data_and_actions: None,
) -> None:
"""Test binary sensor state when the API pushes data via SSE."""
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,
@ -58,7 +72,9 @@ async def test_api_failure(
"""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")
with pytest.raises(HomeAssistantError): with pytest.raises(
HomeAssistantError, match=f"Failed to set state for {ENTITY_ID}"
):
await hass.services.async_call( await hass.services.async_call(
TEST_PLATFORM, SERVICE_PRESS, {ATTR_ENTITY_ID: ENTITY_ID}, blocking=True TEST_PLATFORM, SERVICE_PRESS, {ATTR_ENTITY_ID: ENTITY_ID}, blocking=True
) )

View File

@ -42,6 +42,20 @@ async def test_climate_states(
await snapshot_platform(hass, entity_registry, snapshot, setup_platform.entry_id) await snapshot_platform(hass, entity_registry, snapshot, setup_platform.entry_id)
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
async def test_climate_states_api_push(
hass: HomeAssistant,
mock_miele_client: MagicMock,
snapshot: SnapshotAssertion,
entity_registry: er.EntityRegistry,
setup_platform: MockConfigEntry,
push_data_and_actions: None,
) -> None:
"""Test climate state when the API pushes data via SSE."""
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,
@ -68,7 +82,9 @@ async def test_api_failure(
"""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
with pytest.raises(HomeAssistantError): with pytest.raises(
HomeAssistantError, match=f"Failed to set state for {ENTITY_ID}"
):
await hass.services.async_call( await hass.services.async_call(
TEST_PLATFORM, TEST_PLATFORM,
SERVICE_SET_TEMPERATURE, SERVICE_SET_TEMPERATURE,

View File

@ -7,7 +7,11 @@ from aiohttp import ClientResponseError
import pytest import pytest
from syrupy.assertion import SnapshotAssertion from syrupy.assertion import SnapshotAssertion
from homeassistant.components.fan import ATTR_PERCENTAGE, DOMAIN as FAN_DOMAIN from homeassistant.components.fan import (
ATTR_PERCENTAGE,
DOMAIN as FAN_DOMAIN,
SERVICE_SET_PERCENTAGE,
)
from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
@ -34,6 +38,20 @@ async def test_fan_states(
await snapshot_platform(hass, entity_registry, snapshot, setup_platform.entry_id) await snapshot_platform(hass, entity_registry, snapshot, setup_platform.entry_id)
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
async def test_fan_states_api_push(
hass: HomeAssistant,
mock_miele_client: MagicMock,
snapshot: SnapshotAssertion,
entity_registry: er.EntityRegistry,
setup_platform: MockConfigEntry,
push_data_and_actions: None,
) -> None:
"""Test fan state when the API pushes data via SSE."""
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"])
@pytest.mark.parametrize( @pytest.mark.parametrize(
("service", "expected_argument"), ("service", "expected_argument"),
@ -78,7 +96,7 @@ async def test_fan_set_speed(
percentage: int, percentage: int,
expected_argument: dict[str, Any], expected_argument: dict[str, Any],
) -> None: ) -> None:
"""Test the fan can be turned on/off.""" """Test the fan can set percentage."""
await hass.services.async_call( await hass.services.async_call(
TEST_PLATFORM, TEST_PLATFORM,
@ -91,6 +109,24 @@ async def test_fan_set_speed(
) )
async def test_fan_turn_on_w_percentage(
hass: HomeAssistant,
mock_miele_client: MagicMock,
setup_platform: None,
) -> None:
"""Test the fan can turn on with percentage."""
await hass.services.async_call(
TEST_PLATFORM,
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: ENTITY_ID, ATTR_PERCENTAGE: 50},
blocking=True,
)
mock_miele_client.send_action.assert_called_with(
"DummyAppliance_18", {"ventilationStep": 2}
)
@pytest.mark.parametrize( @pytest.mark.parametrize(
("service"), ("service"),
[ [
@ -112,3 +148,23 @@ async def test_api_failure(
TEST_PLATFORM, service, {ATTR_ENTITY_ID: ENTITY_ID}, blocking=True TEST_PLATFORM, service, {ATTR_ENTITY_ID: ENTITY_ID}, blocking=True
) )
mock_miele_client.send_action.assert_called_once() mock_miele_client.send_action.assert_called_once()
async def test_set_percentage(
hass: HomeAssistant,
mock_miele_client: MagicMock,
setup_platform: None,
) -> None:
"""Test handling of exception at set_percentage."""
mock_miele_client.send_action.side_effect = ClientResponseError("test", "Test")
with pytest.raises(
HomeAssistantError, match=f"Failed to set state for {ENTITY_ID}"
):
await hass.services.async_call(
TEST_PLATFORM,
SERVICE_SET_PERCENTAGE,
{ATTR_ENTITY_ID: ENTITY_ID, ATTR_PERCENTAGE: 50},
blocking=True,
)
mock_miele_client.send_action.assert_called_once()

View File

@ -32,6 +32,20 @@ async def test_light_states(
await snapshot_platform(hass, entity_registry, snapshot, setup_platform.entry_id) await snapshot_platform(hass, entity_registry, snapshot, setup_platform.entry_id)
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
async def test_light_states_api_push(
hass: HomeAssistant,
mock_miele_client: MagicMock,
snapshot: SnapshotAssertion,
entity_registry: er.EntityRegistry,
setup_platform: MockConfigEntry,
push_data_and_actions: None,
) -> None:
"""Test light state when the API pushes data via SSE."""
await snapshot_platform(hass, entity_registry, snapshot, setup_platform.entry_id)
@pytest.mark.parametrize( @pytest.mark.parametrize(
("service", "light_state"), ("service", "light_state"),
[ [
@ -72,7 +86,9 @@ async def test_api_failure(
"""Test handling of exception from API.""" """Test handling of exception from API."""
mock_miele_client.send_action.side_effect = ClientError mock_miele_client.send_action.side_effect = ClientError
with pytest.raises(HomeAssistantError): with pytest.raises(
HomeAssistantError, match=f"Failed to set state for {ENTITY_ID}"
):
await hass.services.async_call( await hass.services.async_call(
TEST_PLATFORM, service, {ATTR_ENTITY_ID: ENTITY_ID}, blocking=True TEST_PLATFORM, service, {ATTR_ENTITY_ID: ENTITY_ID}, blocking=True
) )

View File

@ -21,7 +21,22 @@ async def test_sensor_states(
entity_registry: er.EntityRegistry, entity_registry: er.EntityRegistry,
setup_platform: MockConfigEntry, setup_platform: MockConfigEntry,
) -> None: ) -> None:
"""Test sensor state.""" """Test sensor state after polling the API for data."""
await snapshot_platform(hass, entity_registry, snapshot, setup_platform.entry_id)
@pytest.mark.parametrize("platforms", [(SENSOR_DOMAIN,)])
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
async def test_sensor_states_api_push(
hass: HomeAssistant,
mock_miele_client: MagicMock,
snapshot: SnapshotAssertion,
entity_registry: er.EntityRegistry,
setup_platform: MockConfigEntry,
push_data_and_actions: None,
) -> None:
"""Test sensor state when the API pushes data via SSE."""
await snapshot_platform(hass, entity_registry, snapshot, setup_platform.entry_id) await snapshot_platform(hass, entity_registry, snapshot, setup_platform.entry_id)

View File

@ -32,6 +32,20 @@ async def test_switch_states(
await snapshot_platform(hass, entity_registry, snapshot, setup_platform.entry_id) await snapshot_platform(hass, entity_registry, snapshot, setup_platform.entry_id)
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
async def test_switch_states_api_push(
hass: HomeAssistant,
mock_miele_client: MagicMock,
snapshot: SnapshotAssertion,
entity_registry: er.EntityRegistry,
setup_platform: MockConfigEntry,
push_data_and_actions: None,
) -> None:
"""Test switch state when the API pushes data via SSE."""
await snapshot_platform(hass, entity_registry, snapshot, setup_platform.entry_id)
@pytest.mark.parametrize( @pytest.mark.parametrize(
("entity"), ("entity"),
[ [
@ -87,7 +101,7 @@ async def test_api_failure(
"""Test handling of exception from API.""" """Test handling of exception from API."""
mock_miele_client.send_action.side_effect = ClientError mock_miele_client.send_action.side_effect = ClientError
with pytest.raises(HomeAssistantError): with pytest.raises(HomeAssistantError, match=f"Failed to set state for {entity}"):
await hass.services.async_call( await hass.services.async_call(
TEST_PLATFORM, service, {ATTR_ENTITY_ID: entity}, blocking=True TEST_PLATFORM, service, {ATTR_ENTITY_ID: entity}, blocking=True
) )

View File

@ -3,10 +3,11 @@
from unittest.mock import MagicMock from unittest.mock import MagicMock
from aiohttp import ClientResponseError from aiohttp import ClientResponseError
from pymiele import MieleDevices
import pytest import pytest
from syrupy.assertion import SnapshotAssertion from syrupy.assertion import SnapshotAssertion
from homeassistant.components.miele.const import PROCESS_ACTION, PROGRAM_ID from homeassistant.components.miele.const import DOMAIN, PROCESS_ACTION, PROGRAM_ID
from homeassistant.components.vacuum import ( from homeassistant.components.vacuum import (
ATTR_FAN_SPEED, ATTR_FAN_SPEED,
DOMAIN as VACUUM_DOMAIN, DOMAIN as VACUUM_DOMAIN,
@ -21,7 +22,9 @@ from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import entity_registry as er from homeassistant.helpers import entity_registry as er
from tests.common import MockConfigEntry, snapshot_platform from . import get_actions_callback, get_data_callback
from tests.common import MockConfigEntry, load_json_object_fixture, snapshot_platform
TEST_PLATFORM = VACUUM_DOMAIN TEST_PLATFORM = VACUUM_DOMAIN
ENTITY_ID = "vacuum.robot_vacuum_cleaner" ENTITY_ID = "vacuum.robot_vacuum_cleaner"
@ -46,6 +49,29 @@ async def test_sensor_states(
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id) await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
async def test_vacuum_states_api_push(
hass: HomeAssistant,
mock_miele_client: MagicMock,
snapshot: SnapshotAssertion,
entity_registry: er.EntityRegistry,
setup_platform: MockConfigEntry,
device_fixture: MieleDevices,
) -> None:
"""Test vacuum state when the API pushes data via SSE."""
data_callback = get_data_callback(mock_miele_client)
await data_callback(device_fixture)
await hass.async_block_till_done()
act_file = load_json_object_fixture("action_push_vacuum.json", DOMAIN)
action_callback = get_actions_callback(mock_miele_client)
await action_callback(act_file)
await hass.async_block_till_done()
await snapshot_platform(hass, entity_registry, snapshot, setup_platform.entry_id)
@pytest.mark.parametrize( @pytest.mark.parametrize(
("service", "action_command", "vacuum_power"), ("service", "action_command", "vacuum_power"),
[ [
@ -112,7 +138,9 @@ async def test_api_failure(
"""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")
with pytest.raises(HomeAssistantError): with pytest.raises(
HomeAssistantError, match=f"Failed to set state for {ENTITY_ID}"
):
await hass.services.async_call( await hass.services.async_call(
TEST_PLATFORM, service, {ATTR_ENTITY_ID: ENTITY_ID}, blocking=True TEST_PLATFORM, service, {ATTR_ENTITY_ID: ENTITY_ID}, blocking=True
) )