core/tests/components/template/test_fan.py

692 lines
22 KiB
Python
Raw Normal View History

"""The tests for the Template fan platform."""
import logging
import pytest
import voluptuous as vol
from homeassistant import setup
from homeassistant.const import STATE_ON, STATE_OFF, STATE_UNAVAILABLE
from homeassistant.components.fan import (
2019-07-31 19:25:30 +00:00
ATTR_SPEED,
ATTR_OSCILLATING,
SPEED_LOW,
SPEED_MEDIUM,
SPEED_HIGH,
ATTR_DIRECTION,
DIRECTION_FORWARD,
DIRECTION_REVERSE,
)
from tests.common import async_mock_service, assert_setup_component
from tests.components.fan import common
_LOGGER = logging.getLogger(__name__)
2019-07-31 19:25:30 +00:00
_TEST_FAN = "fan.test_fan"
# Represent for fan's state
2019-07-31 19:25:30 +00:00
_STATE_INPUT_BOOLEAN = "input_boolean.state"
# Represent for fan's state
_STATE_AVAILABILITY_BOOLEAN = "availability_boolean.state"
# Represent for fan's speed
2019-07-31 19:25:30 +00:00
_SPEED_INPUT_SELECT = "input_select.speed"
# Represent for fan's oscillating
2019-07-31 19:25:30 +00:00
_OSC_INPUT = "input_select.osc"
# Represent for fan's direction
2019-07-31 19:25:30 +00:00
_DIRECTION_INPUT_SELECT = "input_select.direction"
@pytest.fixture
def calls(hass):
"""Track calls to a mock serivce."""
2019-07-31 19:25:30 +00:00
return async_mock_service(hass, "test", "automation")
# Configuration tests #
async def test_missing_optional_config(hass, calls):
"""Test: missing optional template is ok."""
2019-07-31 19:25:30 +00:00
with assert_setup_component(1, "fan"):
assert await setup.async_setup_component(
hass,
"fan",
{
"fan": {
"platform": "template",
"fans": {
"test_fan": {
"value_template": "{{ 'on' }}",
"turn_on": {"service": "script.fan_on"},
"turn_off": {"service": "script.fan_off"},
}
2019-07-31 19:25:30 +00:00
},
}
2019-07-31 19:25:30 +00:00
},
)
await hass.async_start()
await hass.async_block_till_done()
_verify(hass, STATE_ON, None, None, None)
async def test_missing_value_template_config(hass, calls):
"""Test: missing 'value_template' will fail."""
2019-07-31 19:25:30 +00:00
with assert_setup_component(0, "fan"):
assert await setup.async_setup_component(
hass,
"fan",
{
"fan": {
"platform": "template",
"fans": {
"test_fan": {
"turn_on": {"service": "script.fan_on"},
"turn_off": {"service": "script.fan_off"},
}
2019-07-31 19:25:30 +00:00
},
}
2019-07-31 19:25:30 +00:00
},
)
await hass.async_start()
await hass.async_block_till_done()
assert hass.states.async_all() == []
async def test_missing_turn_on_config(hass, calls):
"""Test: missing 'turn_on' will fail."""
2019-07-31 19:25:30 +00:00
with assert_setup_component(0, "fan"):
assert await setup.async_setup_component(
hass,
"fan",
{
"fan": {
"platform": "template",
"fans": {
"test_fan": {
"value_template": "{{ 'on' }}",
"turn_off": {"service": "script.fan_off"},
}
2019-07-31 19:25:30 +00:00
},
}
2019-07-31 19:25:30 +00:00
},
)
await hass.async_start()
await hass.async_block_till_done()
assert hass.states.async_all() == []
async def test_missing_turn_off_config(hass, calls):
"""Test: missing 'turn_off' will fail."""
2019-07-31 19:25:30 +00:00
with assert_setup_component(0, "fan"):
assert await setup.async_setup_component(
hass,
"fan",
{
"fan": {
"platform": "template",
"fans": {
"test_fan": {
"value_template": "{{ 'on' }}",
"turn_on": {"service": "script.fan_on"},
}
2019-07-31 19:25:30 +00:00
},
}
2019-07-31 19:25:30 +00:00
},
)
await hass.async_start()
await hass.async_block_till_done()
assert hass.states.async_all() == []
async def test_invalid_config(hass, calls):
"""Test: missing 'turn_off' will fail."""
2019-07-31 19:25:30 +00:00
with assert_setup_component(0, "fan"):
assert await setup.async_setup_component(
hass,
"fan",
{
"platform": "template",
"fans": {
"test_fan": {
"value_template": "{{ 'on' }}",
"turn_on": {"service": "script.fan_on"},
}
2019-07-31 19:25:30 +00:00
},
},
)
await hass.async_start()
await hass.async_block_till_done()
assert hass.states.async_all() == []
2019-07-31 19:25:30 +00:00
# End of configuration tests #
# Template tests #
async def test_templates_with_entities(hass, calls):
"""Test tempalates with values from other entities."""
value_template = """
{% if is_state('input_boolean.state', 'True') %}
{{ 'on' }}
{% else %}
{{ 'off' }}
{% endif %}
"""
2019-07-31 19:25:30 +00:00
with assert_setup_component(1, "fan"):
assert await setup.async_setup_component(
hass,
"fan",
{
"fan": {
"platform": "template",
"fans": {
"test_fan": {
"value_template": value_template,
"speed_template": "{{ states('input_select.speed') }}",
"oscillating_template": "{{ states('input_select.osc') }}",
"direction_template": "{{ states('input_select.direction') }}",
"turn_on": {"service": "script.fan_on"},
"turn_off": {"service": "script.fan_off"},
}
2019-07-31 19:25:30 +00:00
},
}
2019-07-31 19:25:30 +00:00
},
)
await hass.async_start()
await hass.async_block_till_done()
_verify(hass, STATE_OFF, None, None, None)
hass.states.async_set(_STATE_INPUT_BOOLEAN, True)
hass.states.async_set(_SPEED_INPUT_SELECT, SPEED_MEDIUM)
2019-07-31 19:25:30 +00:00
hass.states.async_set(_OSC_INPUT, "True")
hass.states.async_set(_DIRECTION_INPUT_SELECT, DIRECTION_FORWARD)
await hass.async_block_till_done()
_verify(hass, STATE_ON, SPEED_MEDIUM, True, DIRECTION_FORWARD)
async def test_availability_template_with_entities(hass, calls):
"""Test availability tempalates with values from other entities."""
with assert_setup_component(1, "fan"):
assert await setup.async_setup_component(
hass,
"fan",
{
"fan": {
"platform": "template",
"fans": {
"test_fan": {
"availability_template": "{{ is_state('availability_boolean.state', 'on') }}",
"value_template": "{{ 'on' }}",
"speed_template": "{{ 'medium' }}",
"oscillating_template": "{{ 1 == 1 }}",
"direction_template": "{{ 'forward' }}",
"turn_on": {"service": "script.fan_on"},
"turn_off": {"service": "script.fan_off"},
}
},
}
},
)
await hass.async_start()
await hass.async_block_till_done()
# When template returns true..
hass.states.async_set(_STATE_AVAILABILITY_BOOLEAN, STATE_ON)
await hass.async_block_till_done()
# Device State should not be unavailable
assert hass.states.get(_TEST_FAN).state != STATE_UNAVAILABLE
# When Availability template returns false
hass.states.async_set(_STATE_AVAILABILITY_BOOLEAN, STATE_OFF)
await hass.async_block_till_done()
# device state should be unavailable
assert hass.states.get(_TEST_FAN).state == STATE_UNAVAILABLE
async def test_templates_with_valid_values(hass, calls):
"""Test templates with valid values."""
2019-07-31 19:25:30 +00:00
with assert_setup_component(1, "fan"):
assert await setup.async_setup_component(
hass,
"fan",
{
"fan": {
"platform": "template",
"fans": {
"test_fan": {
"value_template": "{{ 'on' }}",
"speed_template": "{{ 'medium' }}",
"oscillating_template": "{{ 1 == 1 }}",
"direction_template": "{{ 'forward' }}",
"turn_on": {"service": "script.fan_on"},
"turn_off": {"service": "script.fan_off"},
}
2019-07-31 19:25:30 +00:00
},
}
2019-07-31 19:25:30 +00:00
},
)
await hass.async_start()
await hass.async_block_till_done()
_verify(hass, STATE_ON, SPEED_MEDIUM, True, DIRECTION_FORWARD)
async def test_templates_invalid_values(hass, calls):
"""Test templates with invalid values."""
2019-07-31 19:25:30 +00:00
with assert_setup_component(1, "fan"):
assert await setup.async_setup_component(
hass,
"fan",
{
"fan": {
"platform": "template",
"fans": {
"test_fan": {
"value_template": "{{ 'abc' }}",
"speed_template": "{{ '0' }}",
"oscillating_template": "{{ 'xyz' }}",
"direction_template": "{{ 'right' }}",
"turn_on": {"service": "script.fan_on"},
"turn_off": {"service": "script.fan_off"},
}
2019-07-31 19:25:30 +00:00
},
}
2019-07-31 19:25:30 +00:00
},
)
await hass.async_start()
await hass.async_block_till_done()
_verify(hass, STATE_OFF, None, None, None)
2019-07-31 19:25:30 +00:00
async def test_invalid_availability_template_keeps_component_available(hass, caplog):
"""Test that an invalid availability keeps the device available."""
with assert_setup_component(1, "fan"):
assert await setup.async_setup_component(
hass,
"fan",
{
"fan": {
"platform": "template",
"fans": {
"test_fan": {
"value_template": "{{ 'on' }}",
"availability_template": "{{ x - 12 }}",
"speed_template": "{{ states('input_select.speed') }}",
"oscillating_template": "{{ states('input_select.osc') }}",
"direction_template": "{{ states('input_select.direction') }}",
"turn_on": {"service": "script.fan_on"},
"turn_off": {"service": "script.fan_off"},
}
},
}
},
)
await hass.async_start()
await hass.async_block_till_done()
assert hass.states.get("fan.test_fan").state != STATE_UNAVAILABLE
assert ("Could not render availability_template template") in caplog.text
assert ("UndefinedError: 'x' is undefined") in caplog.text
# End of template tests #
# Function tests #
async def test_on_off(hass, calls):
"""Test turn on and turn off."""
await _register_components(hass)
# Turn on fan
await common.async_turn_on(hass, _TEST_FAN)
# verify
assert hass.states.get(_STATE_INPUT_BOOLEAN).state == STATE_ON
_verify(hass, STATE_ON, None, None, None)
# Turn off fan
await common.async_turn_off(hass, _TEST_FAN)
# verify
assert hass.states.get(_STATE_INPUT_BOOLEAN).state == STATE_OFF
_verify(hass, STATE_OFF, None, None, None)
async def test_on_with_speed(hass, calls):
"""Test turn on with speed."""
await _register_components(hass)
# Turn on fan with high speed
await common.async_turn_on(hass, _TEST_FAN, SPEED_HIGH)
# verify
assert hass.states.get(_STATE_INPUT_BOOLEAN).state == STATE_ON
assert hass.states.get(_SPEED_INPUT_SELECT).state == SPEED_HIGH
_verify(hass, STATE_ON, SPEED_HIGH, None, None)
async def test_set_speed(hass, calls):
"""Test set valid speed."""
await _register_components(hass)
# Turn on fan
await common.async_turn_on(hass, _TEST_FAN)
# Set fan's speed to high
await common.async_set_speed(hass, _TEST_FAN, SPEED_HIGH)
# verify
assert hass.states.get(_SPEED_INPUT_SELECT).state == SPEED_HIGH
_verify(hass, STATE_ON, SPEED_HIGH, None, None)
# Set fan's speed to medium
await common.async_set_speed(hass, _TEST_FAN, SPEED_MEDIUM)
# verify
assert hass.states.get(_SPEED_INPUT_SELECT).state == SPEED_MEDIUM
_verify(hass, STATE_ON, SPEED_MEDIUM, None, None)
async def test_set_invalid_speed_from_initial_stage(hass, calls):
"""Test set invalid speed when fan is in initial state."""
await _register_components(hass)
# Turn on fan
await common.async_turn_on(hass, _TEST_FAN)
# Set fan's speed to 'invalid'
2019-07-31 19:25:30 +00:00
await common.async_set_speed(hass, _TEST_FAN, "invalid")
# verify speed is unchanged
2019-07-31 19:25:30 +00:00
assert hass.states.get(_SPEED_INPUT_SELECT).state == ""
_verify(hass, STATE_ON, None, None, None)
async def test_set_invalid_speed(hass, calls):
"""Test set invalid speed when fan has valid speed."""
await _register_components(hass)
# Turn on fan
await common.async_turn_on(hass, _TEST_FAN)
# Set fan's speed to high
await common.async_set_speed(hass, _TEST_FAN, SPEED_HIGH)
# verify
assert hass.states.get(_SPEED_INPUT_SELECT).state == SPEED_HIGH
_verify(hass, STATE_ON, SPEED_HIGH, None, None)
# Set fan's speed to 'invalid'
2019-07-31 19:25:30 +00:00
await common.async_set_speed(hass, _TEST_FAN, "invalid")
# verify speed is unchanged
assert hass.states.get(_SPEED_INPUT_SELECT).state == SPEED_HIGH
_verify(hass, STATE_ON, SPEED_HIGH, None, None)
async def test_custom_speed_list(hass, calls):
"""Test set custom speed list."""
2019-07-31 19:25:30 +00:00
await _register_components(hass, ["1", "2", "3"])
# Turn on fan
await common.async_turn_on(hass, _TEST_FAN)
# Set fan's speed to '1'
2019-07-31 19:25:30 +00:00
await common.async_set_speed(hass, _TEST_FAN, "1")
# verify
2019-07-31 19:25:30 +00:00
assert hass.states.get(_SPEED_INPUT_SELECT).state == "1"
_verify(hass, STATE_ON, "1", None, None)
# Set fan's speed to 'medium' which is invalid
await common.async_set_speed(hass, _TEST_FAN, SPEED_MEDIUM)
# verify that speed is unchanged
2019-07-31 19:25:30 +00:00
assert hass.states.get(_SPEED_INPUT_SELECT).state == "1"
_verify(hass, STATE_ON, "1", None, None)
async def test_set_osc(hass, calls):
"""Test set oscillating."""
await _register_components(hass)
# Turn on fan
await common.async_turn_on(hass, _TEST_FAN)
# Set fan's osc to True
await common.async_oscillate(hass, _TEST_FAN, True)
# verify
2019-07-31 19:25:30 +00:00
assert hass.states.get(_OSC_INPUT).state == "True"
_verify(hass, STATE_ON, None, True, None)
# Set fan's osc to False
await common.async_oscillate(hass, _TEST_FAN, False)
# verify
2019-07-31 19:25:30 +00:00
assert hass.states.get(_OSC_INPUT).state == "False"
_verify(hass, STATE_ON, None, False, None)
async def test_set_invalid_osc_from_initial_state(hass, calls):
"""Test set invalid oscillating when fan is in initial state."""
await _register_components(hass)
# Turn on fan
await common.async_turn_on(hass, _TEST_FAN)
# Set fan's osc to 'invalid'
with pytest.raises(vol.Invalid):
2019-07-31 19:25:30 +00:00
await common.async_oscillate(hass, _TEST_FAN, "invalid")
# verify
2019-07-31 19:25:30 +00:00
assert hass.states.get(_OSC_INPUT).state == ""
_verify(hass, STATE_ON, None, None, None)
async def test_set_invalid_osc(hass, calls):
"""Test set invalid oscillating when fan has valid osc."""
await _register_components(hass)
# Turn on fan
await common.async_turn_on(hass, _TEST_FAN)
# Set fan's osc to True
await common.async_oscillate(hass, _TEST_FAN, True)
# verify
2019-07-31 19:25:30 +00:00
assert hass.states.get(_OSC_INPUT).state == "True"
_verify(hass, STATE_ON, None, True, None)
# Set fan's osc to None
with pytest.raises(vol.Invalid):
await common.async_oscillate(hass, _TEST_FAN, None)
# verify osc is unchanged
2019-07-31 19:25:30 +00:00
assert hass.states.get(_OSC_INPUT).state == "True"
_verify(hass, STATE_ON, None, True, None)
async def test_set_direction(hass, calls):
"""Test set valid direction."""
await _register_components(hass)
# Turn on fan
await common.async_turn_on(hass, _TEST_FAN)
# Set fan's direction to forward
await common.async_set_direction(hass, _TEST_FAN, DIRECTION_FORWARD)
# verify
2019-07-31 19:25:30 +00:00
assert hass.states.get(_DIRECTION_INPUT_SELECT).state == DIRECTION_FORWARD
_verify(hass, STATE_ON, None, None, DIRECTION_FORWARD)
# Set fan's direction to reverse
await common.async_set_direction(hass, _TEST_FAN, DIRECTION_REVERSE)
# verify
2019-07-31 19:25:30 +00:00
assert hass.states.get(_DIRECTION_INPUT_SELECT).state == DIRECTION_REVERSE
_verify(hass, STATE_ON, None, None, DIRECTION_REVERSE)
async def test_set_invalid_direction_from_initial_stage(hass, calls):
"""Test set invalid direction when fan is in initial state."""
await _register_components(hass)
# Turn on fan
await common.async_turn_on(hass, _TEST_FAN)
# Set fan's direction to 'invalid'
2019-07-31 19:25:30 +00:00
await common.async_set_direction(hass, _TEST_FAN, "invalid")
# verify direction is unchanged
2019-07-31 19:25:30 +00:00
assert hass.states.get(_DIRECTION_INPUT_SELECT).state == ""
_verify(hass, STATE_ON, None, None, None)
async def test_set_invalid_direction(hass, calls):
"""Test set invalid direction when fan has valid direction."""
await _register_components(hass)
# Turn on fan
await common.async_turn_on(hass, _TEST_FAN)
# Set fan's direction to forward
await common.async_set_direction(hass, _TEST_FAN, DIRECTION_FORWARD)
# verify
2019-07-31 19:25:30 +00:00
assert hass.states.get(_DIRECTION_INPUT_SELECT).state == DIRECTION_FORWARD
_verify(hass, STATE_ON, None, None, DIRECTION_FORWARD)
# Set fan's direction to 'invalid'
2019-07-31 19:25:30 +00:00
await common.async_set_direction(hass, _TEST_FAN, "invalid")
# verify direction is unchanged
2019-07-31 19:25:30 +00:00
assert hass.states.get(_DIRECTION_INPUT_SELECT).state == DIRECTION_FORWARD
_verify(hass, STATE_ON, None, None, DIRECTION_FORWARD)
2019-07-31 19:25:30 +00:00
def _verify(
hass, expected_state, expected_speed, expected_oscillating, expected_direction
):
"""Verify fan's state, speed and osc."""
state = hass.states.get(_TEST_FAN)
attributes = state.attributes
assert state.state == expected_state
assert attributes.get(ATTR_SPEED, None) == expected_speed
assert attributes.get(ATTR_OSCILLATING, None) == expected_oscillating
assert attributes.get(ATTR_DIRECTION, None) == expected_direction
async def _register_components(hass, speed_list=None):
"""Register basic components for testing."""
2019-07-31 19:25:30 +00:00
with assert_setup_component(1, "input_boolean"):
assert await setup.async_setup_component(
2019-07-31 19:25:30 +00:00
hass, "input_boolean", {"input_boolean": {"state": None}}
)
2019-07-31 19:25:30 +00:00
with assert_setup_component(3, "input_select"):
assert await setup.async_setup_component(
hass,
"input_select",
{
"input_select": {
"speed": {
"name": "Speed",
"options": [
"",
SPEED_LOW,
SPEED_MEDIUM,
SPEED_HIGH,
"1",
"2",
"3",
],
},
"osc": {"name": "oscillating", "options": ["", "True", "False"]},
"direction": {
"name": "Direction",
"options": ["", DIRECTION_FORWARD, DIRECTION_REVERSE],
},
}
},
)
2019-07-31 19:25:30 +00:00
with assert_setup_component(1, "fan"):
value_template = """
{% if is_state('input_boolean.state', 'on') %}
{{ 'on' }}
{% else %}
{{ 'off' }}
{% endif %}
"""
test_fan_config = {
2019-07-31 19:25:30 +00:00
"value_template": value_template,
"speed_template": "{{ states('input_select.speed') }}",
"oscillating_template": "{{ states('input_select.osc') }}",
"direction_template": "{{ states('input_select.direction') }}",
"turn_on": {
"service": "input_boolean.turn_on",
"entity_id": _STATE_INPUT_BOOLEAN,
},
2019-07-31 19:25:30 +00:00
"turn_off": {
"service": "input_boolean.turn_off",
"entity_id": _STATE_INPUT_BOOLEAN,
},
2019-07-31 19:25:30 +00:00
"set_speed": {
"service": "input_select.select_option",
"data_template": {
"entity_id": _SPEED_INPUT_SELECT,
"option": "{{ speed }}",
},
},
2019-07-31 19:25:30 +00:00
"set_oscillating": {
"service": "input_select.select_option",
"data_template": {
"entity_id": _OSC_INPUT,
"option": "{{ oscillating }}",
},
},
"set_direction": {
"service": "input_select.select_option",
"data_template": {
"entity_id": _DIRECTION_INPUT_SELECT,
"option": "{{ direction }}",
},
},
}
if speed_list:
2019-07-31 19:25:30 +00:00
test_fan_config["speeds"] = speed_list
2019-07-31 19:25:30 +00:00
assert await setup.async_setup_component(
hass,
"fan",
{"fan": {"platform": "template", "fans": {"test_fan": test_fan_config}}},
)
await hass.async_start()
await hass.async_block_till_done()