core/tests/components/valve/test_init.py

365 lines
11 KiB
Python

"""The tests for Valve."""
from collections.abc import Generator
import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.components.valve import (
DOMAIN,
ValveDeviceClass,
ValveEntity,
ValveEntityDescription,
ValveEntityFeature,
ValveState,
)
from homeassistant.config_entries import ConfigEntry, ConfigEntryState, ConfigFlow
from homeassistant.const import (
ATTR_ENTITY_ID,
SERVICE_SET_VALVE_POSITION,
SERVICE_TOGGLE,
STATE_UNAVAILABLE,
Platform,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from tests.common import (
MockConfigEntry,
MockModule,
MockPlatform,
mock_config_flow,
mock_integration,
mock_platform,
)
TEST_DOMAIN = "test"
class MockFlow(ConfigFlow):
"""Test flow."""
class MockValveEntity(ValveEntity):
"""Mock valve device to use in tests."""
_attr_should_poll = False
_target_valve_position: int
def __init__(
self,
unique_id: str = "mock_valve",
name: str = "Valve",
features: ValveEntityFeature = ValveEntityFeature(0),
current_position: int | None = None,
device_class: ValveDeviceClass = None,
reports_position: bool = True,
) -> None:
"""Initialize the valve."""
self._attr_name = name
self._attr_unique_id = unique_id
self._attr_supported_features = features
self._attr_current_valve_position = current_position
if reports_position is not None:
self._attr_reports_position = reports_position
if device_class is not None:
self._attr_device_class = device_class
def set_valve_position(self, position: int) -> None:
"""Set the valve to opening or closing towards a target percentage."""
if position > self._attr_current_valve_position:
self._attr_is_closing = False
self._attr_is_opening = True
else:
self._attr_is_closing = True
self._attr_is_opening = False
self._target_valve_position = position
self.schedule_update_ha_state()
def stop_valve(self) -> None:
"""Stop the valve."""
self._attr_is_closing = False
self._attr_is_opening = False
self._target_valve_position = None
self._attr_is_closed = self._attr_current_valve_position == 0
self.schedule_update_ha_state()
@callback
def finish_movement(self):
"""Set the value to the saved target and removes intermediate states."""
self._attr_current_valve_position = self._target_valve_position
self._attr_is_closing = False
self._attr_is_opening = False
self.async_write_ha_state()
class MockBinaryValveEntity(ValveEntity):
"""Mock valve device to use in tests."""
def __init__(
self,
unique_id: str = "mock_valve_2",
name: str = "Valve",
features: ValveEntityFeature = ValveEntityFeature(0),
is_closed: bool | None = None,
) -> None:
"""Initialize the valve."""
self._attr_name = name
self._attr_unique_id = unique_id
self._attr_supported_features = features
self._attr_is_closed = is_closed
self._attr_reports_position = False
def open_valve(self) -> None:
"""Open the valve."""
self._attr_is_closed = False
def close_valve(self) -> None:
"""Mock implementation for sync close function."""
self._attr_is_closed = True
@pytest.fixture(autouse=True)
def config_flow_fixture(hass: HomeAssistant) -> Generator[None]:
"""Mock config flow."""
mock_platform(hass, f"{TEST_DOMAIN}.config_flow")
with mock_config_flow(TEST_DOMAIN, MockFlow):
yield
@pytest.fixture
def mock_config_entry(hass: HomeAssistant) -> tuple[MockConfigEntry, list[ValveEntity]]:
"""Mock a config entry which sets up a couple of valve entities."""
entities = [
MockBinaryValveEntity(
is_closed=False,
features=ValveEntityFeature.OPEN | ValveEntityFeature.CLOSE,
),
MockValveEntity(
current_position=50,
features=ValveEntityFeature.OPEN
| ValveEntityFeature.CLOSE
| ValveEntityFeature.STOP
| ValveEntityFeature.SET_POSITION,
),
]
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, [Platform.VALVE]
)
return True
async def async_unload_entry_init(
hass: HomeAssistant, config_entry: ConfigEntry
) -> bool:
"""Unload up test config entry."""
await hass.config_entries.async_unload_platforms(config_entry, [Platform.VALVE])
return True
mock_platform(hass, f"{TEST_DOMAIN}.config_flow")
mock_integration(
hass,
MockModule(
TEST_DOMAIN,
async_setup_entry=async_setup_entry_init,
async_unload_entry=async_unload_entry_init,
),
)
async def async_setup_entry_platform(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up test platform via config entry."""
async_add_entities(entities)
mock_platform(
hass,
f"{TEST_DOMAIN}.{DOMAIN}",
MockPlatform(async_setup_entry=async_setup_entry_platform),
)
config_entry = MockConfigEntry(domain=TEST_DOMAIN)
config_entry.add_to_hass(hass)
return (config_entry, entities)
async def test_valve_setup(
hass: HomeAssistant,
mock_config_entry: tuple[MockConfigEntry, list[ValveEntity]],
snapshot: SnapshotAssertion,
) -> None:
"""Test setup and tear down of valve platform and entity."""
config_entry = mock_config_entry[0]
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
assert config_entry.state is ConfigEntryState.LOADED
for entity in mock_config_entry[1]:
entity_id = entity.entity_id
state = hass.states.get(entity_id)
assert state
assert state == snapshot
assert await hass.config_entries.async_unload(config_entry.entry_id)
await hass.async_block_till_done()
assert config_entry.state is ConfigEntryState.NOT_LOADED
for entity in mock_config_entry[1]:
entity_id = entity.entity_id
state = hass.states.get(entity_id)
assert state
assert state.state == STATE_UNAVAILABLE
assert state == snapshot
async def test_services(
hass: HomeAssistant, mock_config_entry: tuple[MockConfigEntry, list[ValveEntity]]
) -> None:
"""Test the provided services."""
config_entry = mock_config_entry[0]
ent1, ent2 = mock_config_entry[1]
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
# Test init all valves should be open
assert is_open(hass, ent1)
assert is_open(hass, ent2)
# call basic toggle services
await call_service(hass, SERVICE_TOGGLE, ent1)
await call_service(hass, SERVICE_TOGGLE, ent2)
# entities without stop should be closed and with stop should be closing
assert is_closed(hass, ent1)
assert is_closing(hass, ent2)
ent2.finish_movement()
assert is_closed(hass, ent2)
# call basic toggle services and set different valve position states
await call_service(hass, SERVICE_TOGGLE, ent1)
await call_service(hass, SERVICE_TOGGLE, ent2)
await hass.async_block_till_done()
# entities should be in correct state depending on the SUPPORT_STOP feature and valve position
assert is_open(hass, ent1)
assert is_opening(hass, ent2)
# call basic toggle services
await call_service(hass, SERVICE_TOGGLE, ent1)
await call_service(hass, SERVICE_TOGGLE, ent2)
# entities should be in correct state depending on the SUPPORT_STOP feature and valve position
assert is_closed(hass, ent1)
assert not is_opening(hass, ent2)
assert not is_closing(hass, ent2)
assert is_closed(hass, ent2)
await call_service(hass, SERVICE_SET_VALVE_POSITION, ent2, 50)
assert is_opening(hass, ent2)
async def test_valve_device_class(hass: HomeAssistant) -> None:
"""Test valve entity with defaults."""
default_valve = MockValveEntity()
default_valve.hass = hass
assert default_valve.device_class is None
entity_description = ValveEntityDescription(
key="test",
device_class=ValveDeviceClass.GAS,
)
default_valve.entity_description = entity_description
assert default_valve.device_class is ValveDeviceClass.GAS
water_valve = MockValveEntity(device_class=ValveDeviceClass.WATER)
water_valve.hass = hass
assert water_valve.device_class is ValveDeviceClass.WATER
async def test_valve_report_position(hass: HomeAssistant) -> None:
"""Test valve entity with defaults."""
default_valve = MockValveEntity(reports_position=None)
default_valve.hass = hass
with pytest.raises(ValueError):
_ = default_valve.reports_position
second_valve = MockValveEntity(reports_position=True)
second_valve.hass = hass
assert second_valve.reports_position is True
entity_description = ValveEntityDescription(key="test", reports_position=True)
third_valve = MockValveEntity(reports_position=None)
third_valve.entity_description = entity_description
assert third_valve.reports_position is True
async def test_none_state(hass: HomeAssistant) -> None:
"""Test different criteria for closeness."""
binary_valve_with_none_is_closed_attr = MockBinaryValveEntity(is_closed=None)
binary_valve_with_none_is_closed_attr.hass = hass
assert binary_valve_with_none_is_closed_attr.state is None
pos_valve_with_none_is_closed_attr = MockValveEntity()
pos_valve_with_none_is_closed_attr.hass = hass
assert pos_valve_with_none_is_closed_attr.state is None
async def test_supported_features(hass: HomeAssistant) -> None:
"""Test valve entity with defaults."""
valve = MockValveEntity(features=None)
valve.hass = hass
assert valve.supported_features is None
def call_service(
hass: HomeAssistant, service: str, ent: ValveEntity, position: int | None = None
):
"""Call any service on entity."""
params = {ATTR_ENTITY_ID: ent.entity_id}
if position is not None:
params["position"] = position
return hass.services.async_call(DOMAIN, service, params, blocking=True)
def set_valve_position(ent, position) -> None:
"""Set a position value to a valve."""
ent._values["current_valve_position"] = position
def is_open(hass: HomeAssistant, ent: ValveEntity) -> bool:
"""Return if the valve is closed based on the statemachine."""
return hass.states.is_state(ent.entity_id, ValveState.OPEN)
def is_opening(hass: HomeAssistant, ent: ValveEntity) -> bool:
"""Return if the valve is closed based on the statemachine."""
return hass.states.is_state(ent.entity_id, ValveState.OPENING)
def is_closed(hass: HomeAssistant, ent: ValveEntity) -> bool:
"""Return if the valve is closed based on the statemachine."""
return hass.states.is_state(ent.entity_id, ValveState.CLOSED)
def is_closing(hass: HomeAssistant, ent: ValveEntity) -> bool:
"""Return if the valve is closed based on the statemachine."""
return hass.states.is_state(ent.entity_id, ValveState.CLOSING)