Files
2026-08-24 17:05:23 +02:00

347 lines
12 KiB
Python

"""Tests for midea switch.py."""
from collections.abc import Callable
from unittest.mock import patch
from midealocal.const import DeviceType
from midealocal.devices.ac import DeviceAttributes as ACAttributes
from midealocal.devices.c3 import DeviceAttributes as C3Attributes
from midealocal.devices.cc import DeviceAttributes as CCAttributes
from midealocal.devices.cf import DeviceAttributes as CFAttributes
from midealocal.exceptions import SocketException
import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.components.switch import (
DOMAIN as SWITCH_DOMAIN,
SERVICE_TURN_OFF,
SERVICE_TURN_ON,
)
from homeassistant.const import ATTR_ENTITY_ID, Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import entity_registry as er
from . import setup_integration
from .conftest import DummyDevice, entity_entries
from .const import TEST_DEVICE_ID
from tests.common import MockConfigEntry, snapshot_platform
async def _assert_service_call(
hass: HomeAssistant,
entity_id: str,
service: str,
expected_calls: list[tuple],
device: DummyDevice,
) -> None:
"""Call a switch service and assert the fake device recorded the right call."""
device.calls.clear()
await hass.services.async_call(
SWITCH_DOMAIN,
service,
{ATTR_ENTITY_ID: entity_id},
blocking=True,
)
assert device.calls == expected_calls
@pytest.mark.parametrize(
"device",
[
pytest.param(
DummyDevice(
DeviceType.AC,
attributes={
ACAttributes.power: True,
ACAttributes.mode: 1,
ACAttributes.target_temperature: 22.0,
ACAttributes.indoor_temperature: 21.0,
ACAttributes.aux_heating: True,
ACAttributes.breezeless: False,
ACAttributes.dry: False,
ACAttributes.indirect_wind: False,
ACAttributes.natural_wind: False,
ACAttributes.prompt_tone: True,
ACAttributes.screen_display: True,
ACAttributes.screen_display_alternate: False,
ACAttributes.out_silent: False,
ACAttributes.smart_eye: False,
ACAttributes.anion: False,
ACAttributes.sound: True,
ACAttributes.self_clean: False,
ACAttributes.comfort_mode: False,
ACAttributes.eco_mode: False,
ACAttributes.boost_mode: False,
ACAttributes.sleep_mode: False,
ACAttributes.frost_protect: False,
ACAttributes.swing_vertical: True,
ACAttributes.swing_horizontal: True,
},
),
id="ac",
),
pytest.param(
DummyDevice(
DeviceType.C3,
attributes={
C3Attributes.zone1_power: True,
C3Attributes.zone2_power: False,
C3Attributes.mode: 1,
C3Attributes.target_temperature: [22, 23],
C3Attributes.disinfect: False,
C3Attributes.dhw_power: True,
C3Attributes.eco_mode: False,
C3Attributes.fast_dhw: False,
C3Attributes.silent_mode: True,
C3Attributes.tbh: False,
C3Attributes.zone1_curve: False,
C3Attributes.zone2_curve: True,
},
),
id="c3",
),
pytest.param(
DummyDevice(
DeviceType.CC,
attributes={
CCAttributes.power: True,
CCAttributes.mode: 1,
CCAttributes.eco_mode: False,
CCAttributes.sleep_mode: False,
CCAttributes.swing: False,
CCAttributes.aux_heating: True,
CCAttributes.night_light: False,
},
),
id="cc",
),
pytest.param(
DummyDevice(DeviceType.CF, attributes={CFAttributes.aux_heating: True}),
id="cf",
),
],
)
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
async def test_switch_state_snapshot(
hass: HomeAssistant,
mock_config_entry: Callable[[DummyDevice], MockConfigEntry],
snapshot: SnapshotAssertion,
entity_registry: er.EntityRegistry,
device: DummyDevice,
) -> None:
"""Test async_setup_entry creates the right switch entities per device type."""
config_entry = mock_config_entry(device)
with patch("homeassistant.components.midea._PLATFORMS", [Platform.SWITCH]):
await setup_integration(hass, config_entry, device)
await snapshot_platform(hass, entity_registry, snapshot, config_entry.entry_id)
async def test_ac_switch_services(
hass: HomeAssistant,
mock_config_entry: Callable[[DummyDevice], MockConfigEntry],
) -> None:
"""Test AC switch service calls reach the device."""
device = DummyDevice(
DeviceType.AC,
attributes={
ACAttributes.power: True,
ACAttributes.mode: 1,
ACAttributes.target_temperature: 22.0,
ACAttributes.indoor_temperature: 21.0,
ACAttributes.aux_heating: False,
},
)
config_entry = mock_config_entry(device)
with patch("homeassistant.components.midea._PLATFORMS", [Platform.SWITCH]):
await setup_integration(hass, config_entry, device)
entity_entry = entity_entries(hass, config_entry)[f"{TEST_DEVICE_ID}_aux_heating"]
assert (state := hass.states.get(entity_entry.entity_id)) is not None
assert state.state == "off"
await _assert_service_call(
hass,
entity_entry.entity_id,
SERVICE_TURN_ON,
[("set_attribute", ACAttributes.aux_heating, True)],
device,
)
await _assert_service_call(
hass,
entity_entry.entity_id,
SERVICE_TURN_OFF,
[("set_attribute", ACAttributes.aux_heating, False)],
device,
)
async def test_switch_unknown_when_attribute_becomes_non_bool(
hass: HomeAssistant,
mock_config_entry: Callable[[DummyDevice], MockConfigEntry],
) -> None:
"""Test is_on gracefully reports unknown if a later update clears it."""
device = DummyDevice(
DeviceType.AC,
attributes={
ACAttributes.power: True,
ACAttributes.mode: 1,
ACAttributes.target_temperature: 22.0,
ACAttributes.indoor_temperature: 21.0,
ACAttributes.aux_heating: False,
},
)
config_entry = mock_config_entry(device)
with patch("homeassistant.components.midea._PLATFORMS", [Platform.SWITCH]):
await setup_integration(hass, config_entry, device)
entity_entry = entity_entries(hass, config_entry)[f"{TEST_DEVICE_ID}_aux_heating"]
assert (state := hass.states.get(entity_entry.entity_id))
assert state.state == "off"
device.attributes[ACAttributes.aux_heating] = True
device.notify_update({ACAttributes.aux_heating: True})
await hass.async_block_till_done()
assert (state := hass.states.get(entity_entry.entity_id))
assert state.state == "on"
device.attributes[ACAttributes.aux_heating] = None
device.notify_update({ACAttributes.aux_heating: None})
await hass.async_block_till_done()
assert (state := hass.states.get(entity_entry.entity_id))
assert state.state == "unknown"
async def test_switch_turn_on_raises_on_device_communication_error(
hass: HomeAssistant,
mock_config_entry: Callable[[DummyDevice], MockConfigEntry],
) -> None:
"""Test a device communication failure surfaces as a HomeAssistantError."""
device = DummyDevice(
DeviceType.AC,
attributes={
ACAttributes.power: True,
ACAttributes.mode: 1,
ACAttributes.target_temperature: 22.0,
ACAttributes.indoor_temperature: 21.0,
ACAttributes.aux_heating: False,
},
)
config_entry = mock_config_entry(device)
with patch("homeassistant.components.midea._PLATFORMS", [Platform.SWITCH]):
await setup_integration(hass, config_entry, device)
entity_entry = entity_entries(hass, config_entry)[f"{TEST_DEVICE_ID}_aux_heating"]
with (
patch.object(device, "set_attribute", side_effect=SocketException("offline")),
pytest.raises(HomeAssistantError),
):
await hass.services.async_call(
SWITCH_DOMAIN,
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: entity_entry.entity_id},
blocking=True,
)
async def test_fb_has_no_switch_entities(
hass: HomeAssistant,
mock_config_entry: Callable[[DummyDevice], MockConfigEntry],
) -> None:
"""Test FB devices, which only expose the climate-owned power attribute, get no switches."""
device = DummyDevice(DeviceType.FB, attributes={"power": True})
config_entry = mock_config_entry(device)
with patch("homeassistant.components.midea._PLATFORMS", [Platform.SWITCH]):
await setup_integration(hass, config_entry, device)
assert entity_entries(hass, config_entry) == {}
async def test_switch_not_created_when_attribute_missing(
hass: HomeAssistant,
mock_config_entry: Callable[[DummyDevice], MockConfigEntry],
) -> None:
"""Test a switch is not created when the device does not report the attribute."""
device = DummyDevice(
DeviceType.AC,
attributes={
ACAttributes.power: True,
ACAttributes.mode: 1,
ACAttributes.target_temperature: 22.0,
ACAttributes.indoor_temperature: 21.0,
},
)
config_entry = mock_config_entry(device)
with patch("homeassistant.components.midea._PLATFORMS", [Platform.SWITCH]):
await setup_integration(hass, config_entry, device)
assert f"{TEST_DEVICE_ID}_aux_heating" not in entity_entries(hass, config_entry)
async def test_switch_not_created_for_climate_owned_attribute(
hass: HomeAssistant,
mock_config_entry: Callable[[DummyDevice], MockConfigEntry],
) -> None:
"""Test no switch is created for attributes already controlled by climate."""
device = DummyDevice(
DeviceType.AC,
attributes={
ACAttributes.power: True,
ACAttributes.mode: 1,
ACAttributes.target_temperature: 22.0,
ACAttributes.indoor_temperature: 21.0,
ACAttributes.comfort_mode: False,
ACAttributes.eco_mode: False,
ACAttributes.boost_mode: False,
ACAttributes.sleep_mode: False,
ACAttributes.frost_protect: False,
ACAttributes.swing_vertical: True,
ACAttributes.swing_horizontal: True,
},
)
config_entry = mock_config_entry(device)
with patch("homeassistant.components.midea._PLATFORMS", [Platform.SWITCH]):
await setup_integration(hass, config_entry, device)
created = entity_entries(hass, config_entry)
for key in (
"power",
"comfort_mode",
"eco_mode",
"boost_mode",
"sleep_mode",
"frost_protect",
"swing_vertical",
"swing_horizontal",
):
assert f"{TEST_DEVICE_ID}_{key}" not in created
async def test_switch_not_created_for_other_device_type(
hass: HomeAssistant,
mock_config_entry: Callable[[DummyDevice], MockConfigEntry],
) -> None:
"""Test a switch scoped to one device type is not created for another."""
device = DummyDevice(
DeviceType.CC,
attributes={
CCAttributes.power: True,
CCAttributes.mode: 1,
# C3-only attribute name reused here to prove the CC device type
# is excluded from the description's `models` even when the
# attribute happens to be present.
"disinfect": True,
},
)
config_entry = mock_config_entry(device)
with patch("homeassistant.components.midea._PLATFORMS", [Platform.SWITCH]):
await setup_integration(hass, config_entry, device)
assert f"{TEST_DEVICE_ID}_disinfect" not in entity_entries(hass, config_entry)