Files

302 lines
9.8 KiB
Python

"""Tests for midea light.py."""
from collections.abc import Callable
from typing import Any
from unittest.mock import patch
from midealocal.const import DeviceType
from midealocal.devices.ac import DeviceAttributes as ACAttributes
from midealocal.devices.x13 import DeviceAttributes as X13Attributes, Midea13Device
from midealocal.exceptions import SocketException
import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.components.light import (
ATTR_BRIGHTNESS,
ATTR_COLOR_MODE,
ATTR_COLOR_TEMP_KELVIN,
ATTR_EFFECT,
ATTR_EFFECT_LIST,
ATTR_MAX_COLOR_TEMP_KELVIN,
ATTR_MIN_COLOR_TEMP_KELVIN,
ATTR_SUPPORTED_COLOR_MODES,
DOMAIN as LIGHT_DOMAIN,
EFFECT_OFF,
ColorMode,
)
from homeassistant.const import (
ATTR_ENTITY_ID,
SERVICE_TURN_OFF,
SERVICE_TURN_ON,
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
X13_EFFECTS = list(Midea13Device._effects)
def _x13_device() -> DummyDevice:
device = DummyDevice(
DeviceType.X13,
attributes={
X13Attributes.power: True,
X13Attributes.brightness: 128,
X13Attributes.color_temperature: 4000,
X13Attributes.effect: "none",
X13Attributes.rgb_color: None,
},
)
device.color_temp_range = [2700, 6500]
device.effects = X13_EFFECTS
return device
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
async def test_light_state_snapshot(
hass: HomeAssistant,
mock_config_entry: Callable[[DummyDevice], MockConfigEntry],
snapshot: SnapshotAssertion,
entity_registry: er.EntityRegistry,
) -> None:
"""Test async_setup_entry creates the light entity for an X13 device."""
device = _x13_device()
config_entry = mock_config_entry(device)
with patch("homeassistant.components.midea._PLATFORMS", [Platform.LIGHT]):
await setup_integration(hass, config_entry, device)
await snapshot_platform(hass, entity_registry, snapshot, config_entry.entry_id)
async def test_light_state_and_services(
hass: HomeAssistant,
mock_config_entry: Callable[[DummyDevice], MockConfigEntry],
) -> None:
"""Test light state attributes and service calls reach the device."""
device = _x13_device()
config_entry = mock_config_entry(device)
with patch("homeassistant.components.midea._PLATFORMS", [Platform.LIGHT]):
await setup_integration(hass, config_entry, device)
entity_entry = entity_entries(hass, config_entry)[f"{TEST_DEVICE_ID}_light"]
assert (state := hass.states.get(entity_entry.entity_id)) is not None
assert state.state == "on"
assert state.attributes[ATTR_BRIGHTNESS] == 128
assert state.attributes[ATTR_COLOR_TEMP_KELVIN] == 4000
assert state.attributes[ATTR_MIN_COLOR_TEMP_KELVIN] == 2700
assert state.attributes[ATTR_MAX_COLOR_TEMP_KELVIN] == 6500
assert state.attributes[ATTR_EFFECT] == EFFECT_OFF
assert state.attributes[ATTR_EFFECT_LIST] == [
EFFECT_OFF,
*(effect for effect in X13_EFFECTS if effect != "none"),
]
assert state.attributes[ATTR_COLOR_MODE] == ColorMode.COLOR_TEMP
assert state.attributes[ATTR_SUPPORTED_COLOR_MODES] == [ColorMode.COLOR_TEMP]
device.calls.clear()
await hass.services.async_call(
LIGHT_DOMAIN,
SERVICE_TURN_ON,
{
ATTR_ENTITY_ID: entity_entry.entity_id,
ATTR_BRIGHTNESS: 200,
ATTR_COLOR_TEMP_KELVIN: 5000,
ATTR_EFFECT: "cinema",
},
blocking=True,
)
assert device.calls == [
("set_attribute", "brightness", 200),
("set_attribute", "color_temperature", 5000),
("set_attribute", "effect", "cinema"),
]
device.calls.clear()
await hass.services.async_call(
LIGHT_DOMAIN,
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: entity_entry.entity_id, ATTR_EFFECT: EFFECT_OFF},
blocking=True,
)
assert device.calls == [("set_attribute", "effect", "none")]
device.calls.clear()
await hass.services.async_call(
LIGHT_DOMAIN,
SERVICE_TURN_OFF,
{ATTR_ENTITY_ID: entity_entry.entity_id},
blocking=True,
)
assert device.calls == [("set_attribute", "power", False)]
@pytest.mark.parametrize(
("initial_power", "service_data", "expected_calls"),
[
pytest.param(
True,
{ATTR_BRIGHTNESS: 50},
[("set_attribute", "brightness", 50)],
id="already_on_skips_repower",
),
pytest.param(
False,
{},
[("set_attribute", "power", True)],
id="off_powers_on",
),
],
)
async def test_light_turn_on_power_handling(
hass: HomeAssistant,
mock_config_entry: Callable[[DummyDevice], MockConfigEntry],
initial_power: bool,
service_data: dict[str, Any],
expected_calls: list[tuple],
) -> None:
"""Test turn_on only resends power when the device is currently off."""
device = _x13_device()
device.attributes[X13Attributes.power] = initial_power
config_entry = mock_config_entry(device)
with patch("homeassistant.components.midea._PLATFORMS", [Platform.LIGHT]):
await setup_integration(hass, config_entry, device)
entity_entry = entity_entries(hass, config_entry)[f"{TEST_DEVICE_ID}_light"]
device.calls.clear()
await hass.services.async_call(
LIGHT_DOMAIN,
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: entity_entry.entity_id, **service_data},
blocking=True,
)
assert device.calls == expected_calls
@pytest.mark.parametrize(
(
"attributes",
"expected_state",
"expected_color_mode",
"expected_supported_color_modes",
"expected_has_effect",
),
[
pytest.param(
{},
"unknown",
# HA only reports a color_mode while is_on is True.
None,
[ColorMode.ONOFF],
False,
id="before_first_status",
),
pytest.param(
{
X13Attributes.power: True,
X13Attributes.brightness: None,
X13Attributes.color_temperature: None,
X13Attributes.effect: None,
X13Attributes.rgb_color: None,
},
"on",
ColorMode.ONOFF,
[ColorMode.ONOFF],
False,
id="onoff_only",
),
pytest.param(
{
X13Attributes.power: True,
X13Attributes.brightness: 50,
X13Attributes.color_temperature: None,
X13Attributes.effect: None,
X13Attributes.rgb_color: None,
},
"on",
ColorMode.BRIGHTNESS,
[ColorMode.BRIGHTNESS],
False,
id="brightness_only",
),
],
)
async def test_light_color_mode_fallbacks(
hass: HomeAssistant,
mock_config_entry: Callable[[DummyDevice], MockConfigEntry],
attributes: dict[X13Attributes, Any],
expected_state: str,
expected_color_mode: ColorMode | None,
expected_supported_color_modes: list[ColorMode],
expected_has_effect: bool,
) -> None:
"""Test the light falls back to the correct color mode based on reported capability."""
device = DummyDevice(DeviceType.X13, attributes=attributes)
device.color_temp_range = [2700, 6500]
device.effects = X13_EFFECTS
config_entry = mock_config_entry(device)
with patch("homeassistant.components.midea._PLATFORMS", [Platform.LIGHT]):
await setup_integration(hass, config_entry, device)
entity_entry = entity_entries(hass, config_entry)[f"{TEST_DEVICE_ID}_light"]
assert (state := hass.states.get(entity_entry.entity_id)) is not None
assert state.state == expected_state
assert state.attributes[ATTR_COLOR_MODE] == expected_color_mode
assert (
state.attributes[ATTR_SUPPORTED_COLOR_MODES] == expected_supported_color_modes
)
assert (ATTR_EFFECT in state.attributes) == expected_has_effect
async def test_light_not_created_for_other_device_type(
hass: HomeAssistant,
mock_config_entry: Callable[[DummyDevice], MockConfigEntry],
) -> None:
"""Test no light entity is created for a device type without one."""
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.LIGHT]):
await setup_integration(hass, config_entry, device)
assert entity_entries(hass, config_entry) == {}
async def test_light_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 = _x13_device()
config_entry = mock_config_entry(device)
with patch("homeassistant.components.midea._PLATFORMS", [Platform.LIGHT]):
await setup_integration(hass, config_entry, device)
entity_entry = entity_entries(hass, config_entry)[f"{TEST_DEVICE_ID}_light"]
with (
patch.object(device, "set_attribute", side_effect=SocketException("offline")),
pytest.raises(HomeAssistantError),
):
await hass.services.async_call(
LIGHT_DOMAIN,
SERVICE_TURN_OFF,
{ATTR_ENTITY_ID: entity_entry.entity_id},
blocking=True,
)