mirror of https://github.com/home-assistant/core
481 lines
15 KiB
Python
481 lines
15 KiB
Python
"""Tests for fan platforms."""
|
|
|
|
from unittest.mock import patch
|
|
|
|
import pytest
|
|
|
|
from homeassistant.components import fan
|
|
from homeassistant.components.fan import (
|
|
ATTR_PRESET_MODE,
|
|
ATTR_PRESET_MODES,
|
|
DOMAIN,
|
|
SERVICE_SET_PRESET_MODE,
|
|
FanEntity,
|
|
FanEntityFeature,
|
|
NotValidPresetModeError,
|
|
)
|
|
from homeassistant.config_entries import ConfigEntry
|
|
from homeassistant.const import SERVICE_TURN_OFF, SERVICE_TURN_ON
|
|
from homeassistant.core import HomeAssistant
|
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
|
import homeassistant.helpers.entity_registry as er
|
|
from homeassistant.setup import async_setup_component
|
|
|
|
from .common import MockFan
|
|
|
|
from tests.common import (
|
|
MockConfigEntry,
|
|
MockModule,
|
|
MockPlatform,
|
|
help_test_all,
|
|
import_and_test_deprecated_constant_enum,
|
|
mock_integration,
|
|
mock_platform,
|
|
setup_test_component_platform,
|
|
)
|
|
|
|
|
|
class BaseFan(FanEntity):
|
|
"""Implementation of the abstract FanEntity."""
|
|
|
|
def __init__(self) -> None:
|
|
"""Initialize the fan."""
|
|
|
|
|
|
def test_fanentity() -> None:
|
|
"""Test fan entity methods."""
|
|
fan = BaseFan()
|
|
assert fan.state == "off"
|
|
assert fan.preset_modes is None
|
|
assert fan.supported_features == 0
|
|
assert fan.percentage_step == 1
|
|
assert fan.speed_count == 100
|
|
assert fan.capability_attributes == {}
|
|
# Test set_speed not required
|
|
with pytest.raises(NotImplementedError):
|
|
fan.oscillate(True)
|
|
with pytest.raises(AttributeError):
|
|
fan.set_speed("low")
|
|
with pytest.raises(NotImplementedError):
|
|
fan.set_percentage(0)
|
|
with pytest.raises(NotImplementedError):
|
|
fan.set_preset_mode("auto")
|
|
with pytest.raises(NotImplementedError):
|
|
fan.turn_on()
|
|
with pytest.raises(NotImplementedError):
|
|
fan.turn_off()
|
|
|
|
|
|
async def test_async_fanentity(hass: HomeAssistant) -> None:
|
|
"""Test async fan entity methods."""
|
|
fan = BaseFan()
|
|
fan.hass = hass
|
|
assert fan.state == "off"
|
|
assert fan.preset_modes is None
|
|
assert fan.supported_features == 0
|
|
assert fan.percentage_step == 1
|
|
assert fan.speed_count == 100
|
|
assert fan.capability_attributes == {}
|
|
# Test set_speed not required
|
|
with pytest.raises(NotImplementedError):
|
|
await fan.async_oscillate(True)
|
|
with pytest.raises(AttributeError):
|
|
await fan.async_set_speed("low")
|
|
with pytest.raises(NotImplementedError):
|
|
await fan.async_set_percentage(0)
|
|
with pytest.raises(NotImplementedError):
|
|
await fan.async_set_preset_mode("auto")
|
|
with pytest.raises(NotImplementedError):
|
|
await fan.async_turn_on()
|
|
with pytest.raises(NotImplementedError):
|
|
await fan.async_turn_off()
|
|
with pytest.raises(NotImplementedError):
|
|
await fan.async_increase_speed()
|
|
with pytest.raises(NotImplementedError):
|
|
await fan.async_decrease_speed()
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("attribute_name", "attribute_value"),
|
|
[
|
|
("current_direction", "forward"),
|
|
("oscillating", True),
|
|
("percentage", 50),
|
|
("preset_mode", "medium"),
|
|
("preset_modes", ["low", "medium", "high"]),
|
|
("speed_count", 50),
|
|
("supported_features", 1),
|
|
],
|
|
)
|
|
def test_fanentity_attributes(attribute_name, attribute_value) -> None:
|
|
"""Test fan entity attribute shorthand."""
|
|
fan = BaseFan()
|
|
setattr(fan, f"_attr_{attribute_name}", attribute_value)
|
|
assert getattr(fan, attribute_name) == attribute_value
|
|
|
|
|
|
async def test_preset_mode_validation(
|
|
hass: HomeAssistant,
|
|
caplog: pytest.LogCaptureFixture,
|
|
entity_registry: er.EntityRegistry,
|
|
) -> None:
|
|
"""Test preset mode validation."""
|
|
await hass.async_block_till_done()
|
|
|
|
test_fan = MockFan(
|
|
name="Support fan with preset_mode support",
|
|
supported_features=FanEntityFeature.PRESET_MODE,
|
|
unique_id="unique_support_preset_mode",
|
|
preset_modes=["auto", "eco"],
|
|
)
|
|
setup_test_component_platform(hass, "fan", [test_fan])
|
|
|
|
assert await async_setup_component(hass, "fan", {"fan": {"platform": "test"}})
|
|
await hass.async_block_till_done()
|
|
|
|
state = hass.states.get("fan.support_fan_with_preset_mode_support")
|
|
assert state.attributes.get(ATTR_PRESET_MODES) == ["auto", "eco"]
|
|
|
|
await hass.services.async_call(
|
|
DOMAIN,
|
|
SERVICE_SET_PRESET_MODE,
|
|
{
|
|
"entity_id": "fan.support_fan_with_preset_mode_support",
|
|
"preset_mode": "eco",
|
|
},
|
|
blocking=True,
|
|
)
|
|
|
|
state = hass.states.get("fan.support_fan_with_preset_mode_support")
|
|
assert state.attributes.get(ATTR_PRESET_MODE) == "eco"
|
|
|
|
with pytest.raises(NotValidPresetModeError) as exc:
|
|
await hass.services.async_call(
|
|
DOMAIN,
|
|
SERVICE_SET_PRESET_MODE,
|
|
{
|
|
"entity_id": "fan.support_fan_with_preset_mode_support",
|
|
"preset_mode": "invalid",
|
|
},
|
|
blocking=True,
|
|
)
|
|
assert exc.value.translation_key == "not_valid_preset_mode"
|
|
|
|
with pytest.raises(NotValidPresetModeError) as exc:
|
|
await test_fan._valid_preset_mode_or_raise("invalid")
|
|
assert exc.value.translation_key == "not_valid_preset_mode"
|
|
|
|
|
|
def test_all() -> None:
|
|
"""Test module.__all__ is correctly set."""
|
|
help_test_all(fan)
|
|
|
|
|
|
@pytest.mark.parametrize(("enum"), list(fan.FanEntityFeature))
|
|
def test_deprecated_constants(
|
|
caplog: pytest.LogCaptureFixture,
|
|
enum: fan.FanEntityFeature,
|
|
) -> None:
|
|
"""Test deprecated constants."""
|
|
if not FanEntityFeature.TURN_OFF and not FanEntityFeature.TURN_ON:
|
|
import_and_test_deprecated_constant_enum(
|
|
caplog, fan, enum, "SUPPORT_", "2025.1"
|
|
)
|
|
|
|
|
|
def test_deprecated_supported_features_ints(caplog: pytest.LogCaptureFixture) -> None:
|
|
"""Test deprecated supported features ints."""
|
|
|
|
class MockFan(FanEntity):
|
|
@property
|
|
def supported_features(self) -> int:
|
|
"""Return supported features."""
|
|
return 1
|
|
|
|
entity = MockFan()
|
|
assert entity.supported_features is FanEntityFeature(1)
|
|
assert "MockFan" in caplog.text
|
|
assert "is using deprecated supported features values" in caplog.text
|
|
assert "Instead it should use" in caplog.text
|
|
assert "FanEntityFeature.SET_SPEED" in caplog.text
|
|
caplog.clear()
|
|
assert entity.supported_features is FanEntityFeature(1)
|
|
assert "is using deprecated supported features values" not in caplog.text
|
|
|
|
|
|
async def test_warning_not_implemented_turn_on_off_feature(
|
|
hass: HomeAssistant, caplog: pytest.LogCaptureFixture, config_flow_fixture: None
|
|
) -> None:
|
|
"""Test adding feature flag and warn if missing when methods are set."""
|
|
|
|
called = []
|
|
|
|
class MockFanEntityTest(MockFan):
|
|
"""Mock Fan device."""
|
|
|
|
def turn_on(
|
|
self,
|
|
percentage: int | None = None,
|
|
preset_mode: str | None = None,
|
|
) -> None:
|
|
"""Turn on."""
|
|
called.append("turn_on")
|
|
|
|
def turn_off(self) -> None:
|
|
"""Turn off."""
|
|
called.append("turn_off")
|
|
|
|
async def async_setup_entry_init(
|
|
hass: HomeAssistant, config_entry: ConfigEntry
|
|
) -> bool:
|
|
"""Set up test config entry."""
|
|
await hass.config_entries.async_forward_entry_setups(config_entry, [DOMAIN])
|
|
return True
|
|
|
|
async def async_setup_entry_fan_platform(
|
|
hass: HomeAssistant,
|
|
config_entry: ConfigEntry,
|
|
async_add_entities: AddEntitiesCallback,
|
|
) -> None:
|
|
"""Set up test fan platform via config entry."""
|
|
async_add_entities([MockFanEntityTest(name="test", entity_id="fan.test")])
|
|
|
|
mock_integration(
|
|
hass,
|
|
MockModule(
|
|
"test",
|
|
async_setup_entry=async_setup_entry_init,
|
|
),
|
|
built_in=False,
|
|
)
|
|
mock_platform(
|
|
hass,
|
|
"test.fan",
|
|
MockPlatform(async_setup_entry=async_setup_entry_fan_platform),
|
|
)
|
|
|
|
with patch.object(
|
|
MockFanEntityTest, "__module__", "tests.custom_components.fan.test_init"
|
|
):
|
|
config_entry = MockConfigEntry(domain="test")
|
|
config_entry.add_to_hass(hass)
|
|
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
|
await hass.async_block_till_done()
|
|
|
|
state = hass.states.get("fan.test")
|
|
assert state is not None
|
|
|
|
assert (
|
|
"Entity fan.test (<class 'tests.custom_components.fan.test_init.test_warning_not_implemented_turn_on_off_feature.<locals>.MockFanEntityTest'>) "
|
|
"does not set FanEntityFeature.TURN_OFF but implements the turn_off method. Please report it to the author of the 'test' custom integration"
|
|
in caplog.text
|
|
)
|
|
assert (
|
|
"Entity fan.test (<class 'tests.custom_components.fan.test_init.test_warning_not_implemented_turn_on_off_feature.<locals>.MockFanEntityTest'>) "
|
|
"does not set FanEntityFeature.TURN_ON but implements the turn_on method. Please report it to the author of the 'test' custom integration"
|
|
in caplog.text
|
|
)
|
|
|
|
await hass.services.async_call(
|
|
DOMAIN,
|
|
SERVICE_TURN_ON,
|
|
{
|
|
"entity_id": "fan.test",
|
|
},
|
|
blocking=True,
|
|
)
|
|
await hass.services.async_call(
|
|
DOMAIN,
|
|
SERVICE_TURN_OFF,
|
|
{
|
|
"entity_id": "fan.test",
|
|
},
|
|
blocking=True,
|
|
)
|
|
|
|
assert len(called) == 2
|
|
assert "turn_on" in called
|
|
assert "turn_off" in called
|
|
|
|
|
|
async def test_no_warning_implemented_turn_on_off_feature(
|
|
hass: HomeAssistant, caplog: pytest.LogCaptureFixture, config_flow_fixture: None
|
|
) -> None:
|
|
"""Test no warning when feature flags are set."""
|
|
|
|
class MockFanEntityTest(MockFan):
|
|
"""Mock Fan device."""
|
|
|
|
_attr_supported_features = (
|
|
FanEntityFeature.DIRECTION
|
|
| FanEntityFeature.OSCILLATE
|
|
| FanEntityFeature.SET_SPEED
|
|
| FanEntityFeature.PRESET_MODE
|
|
| FanEntityFeature.TURN_OFF
|
|
| FanEntityFeature.TURN_ON
|
|
)
|
|
|
|
async def async_setup_entry_init(
|
|
hass: HomeAssistant, config_entry: ConfigEntry
|
|
) -> bool:
|
|
"""Set up test config entry."""
|
|
await hass.config_entries.async_forward_entry_setups(config_entry, [DOMAIN])
|
|
return True
|
|
|
|
async def async_setup_entry_fan_platform(
|
|
hass: HomeAssistant,
|
|
config_entry: ConfigEntry,
|
|
async_add_entities: AddEntitiesCallback,
|
|
) -> None:
|
|
"""Set up test fan platform via config entry."""
|
|
async_add_entities([MockFanEntityTest(name="test", entity_id="fan.test")])
|
|
|
|
mock_integration(
|
|
hass,
|
|
MockModule(
|
|
"test",
|
|
async_setup_entry=async_setup_entry_init,
|
|
),
|
|
built_in=False,
|
|
)
|
|
mock_platform(
|
|
hass,
|
|
"test.fan",
|
|
MockPlatform(async_setup_entry=async_setup_entry_fan_platform),
|
|
)
|
|
|
|
with patch.object(
|
|
MockFanEntityTest, "__module__", "tests.custom_components.fan.test_init"
|
|
):
|
|
config_entry = MockConfigEntry(domain="test")
|
|
config_entry.add_to_hass(hass)
|
|
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
|
await hass.async_block_till_done()
|
|
|
|
state = hass.states.get("fan.test")
|
|
assert state is not None
|
|
|
|
assert "does not set FanEntityFeature.TURN_OFF" not in caplog.text
|
|
assert "does not set FanEntityFeature.TURN_ON" not in caplog.text
|
|
|
|
|
|
async def test_no_warning_integration_has_migrated(
|
|
hass: HomeAssistant, caplog: pytest.LogCaptureFixture, config_flow_fixture: None
|
|
) -> None:
|
|
"""Test no warning when integration migrated using `_enable_turn_on_off_backwards_compatibility`."""
|
|
|
|
class MockFanEntityTest(MockFan):
|
|
"""Mock Fan device."""
|
|
|
|
_enable_turn_on_off_backwards_compatibility = False
|
|
_attr_supported_features = (
|
|
FanEntityFeature.DIRECTION
|
|
| FanEntityFeature.OSCILLATE
|
|
| FanEntityFeature.SET_SPEED
|
|
| FanEntityFeature.PRESET_MODE
|
|
)
|
|
|
|
async def async_setup_entry_init(
|
|
hass: HomeAssistant, config_entry: ConfigEntry
|
|
) -> bool:
|
|
"""Set up test config entry."""
|
|
await hass.config_entries.async_forward_entry_setups(config_entry, [DOMAIN])
|
|
return True
|
|
|
|
async def async_setup_entry_fan_platform(
|
|
hass: HomeAssistant,
|
|
config_entry: ConfigEntry,
|
|
async_add_entities: AddEntitiesCallback,
|
|
) -> None:
|
|
"""Set up test fan platform via config entry."""
|
|
async_add_entities([MockFanEntityTest(name="test", entity_id="fan.test")])
|
|
|
|
mock_integration(
|
|
hass,
|
|
MockModule(
|
|
"test",
|
|
async_setup_entry=async_setup_entry_init,
|
|
),
|
|
built_in=False,
|
|
)
|
|
mock_platform(
|
|
hass,
|
|
"test.fan",
|
|
MockPlatform(async_setup_entry=async_setup_entry_fan_platform),
|
|
)
|
|
|
|
with patch.object(
|
|
MockFanEntityTest, "__module__", "tests.custom_components.fan.test_init"
|
|
):
|
|
config_entry = MockConfigEntry(domain="test")
|
|
config_entry.add_to_hass(hass)
|
|
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
|
await hass.async_block_till_done()
|
|
|
|
state = hass.states.get("fan.test")
|
|
assert state is not None
|
|
|
|
assert "does not set FanEntityFeature.TURN_OFF" not in caplog.text
|
|
assert "does not set FanEntityFeature.TURN_ON" not in caplog.text
|
|
|
|
|
|
async def test_no_warning_integration_implement_feature_flags(
|
|
hass: HomeAssistant, caplog: pytest.LogCaptureFixture, config_flow_fixture: None
|
|
) -> None:
|
|
"""Test no warning when integration uses the correct feature flags."""
|
|
|
|
class MockFanEntityTest(MockFan):
|
|
"""Mock Fan device."""
|
|
|
|
_attr_supported_features = (
|
|
FanEntityFeature.DIRECTION
|
|
| FanEntityFeature.OSCILLATE
|
|
| FanEntityFeature.SET_SPEED
|
|
| FanEntityFeature.PRESET_MODE
|
|
| FanEntityFeature.TURN_OFF
|
|
| FanEntityFeature.TURN_ON
|
|
)
|
|
|
|
async def async_setup_entry_init(
|
|
hass: HomeAssistant, config_entry: ConfigEntry
|
|
) -> bool:
|
|
"""Set up test config entry."""
|
|
await hass.config_entries.async_forward_entry_setups(config_entry, [DOMAIN])
|
|
return True
|
|
|
|
async def async_setup_entry_fan_platform(
|
|
hass: HomeAssistant,
|
|
config_entry: ConfigEntry,
|
|
async_add_entities: AddEntitiesCallback,
|
|
) -> None:
|
|
"""Set up test fan platform via config entry."""
|
|
async_add_entities([MockFanEntityTest(name="test", entity_id="fan.test")])
|
|
|
|
mock_integration(
|
|
hass,
|
|
MockModule(
|
|
"test",
|
|
async_setup_entry=async_setup_entry_init,
|
|
),
|
|
built_in=False,
|
|
)
|
|
mock_platform(
|
|
hass,
|
|
"test.fan",
|
|
MockPlatform(async_setup_entry=async_setup_entry_fan_platform),
|
|
)
|
|
|
|
with patch.object(
|
|
MockFanEntityTest, "__module__", "tests.custom_components.fan.test_init"
|
|
):
|
|
config_entry = MockConfigEntry(domain="test")
|
|
config_entry.add_to_hass(hass)
|
|
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
|
await hass.async_block_till_done()
|
|
|
|
state = hass.states.get("fan.test")
|
|
assert state is not None
|
|
|
|
assert "does not set FanEntityFeature.TURN_OFF" not in caplog.text
|
|
assert "does not set FanEntityFeature.TURN_ON" not in caplog.text
|