"""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 from homeassistant.components.fan import ( 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__) _TEST_FAN = 'fan.test_fan' # Represent for fan's state _STATE_INPUT_BOOLEAN = 'input_boolean.state' # Represent for fan's speed _SPEED_INPUT_SELECT = 'input_select.speed' # Represent for fan's oscillating _OSC_INPUT = 'input_select.osc' # Represent for fan's direction _DIRECTION_INPUT_SELECT = 'input_select.direction' @pytest.fixture def calls(hass): """Track calls to a mock serivce.""" return async_mock_service(hass, 'test', 'automation') # Configuration tests # async def test_missing_optional_config(hass, calls): """Test: missing optional template is ok.""" 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' } } } } }) 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.""" 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' } } } } }) 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.""" 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' } } } } }) 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.""" 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' } } } } }) 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.""" 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' } } } }) await hass.async_start() await hass.async_block_till_done() assert hass.states.async_all() == [] # 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 %} """ 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' } } } } }) 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) 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_templates_with_valid_values(hass, calls): """Test templates with valid values.""" 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' } } } } }) 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.""" 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' } } } } }) await hass.async_start() await hass.async_block_till_done() _verify(hass, STATE_OFF, None, None, None) # 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' await common.async_set_speed(hass, _TEST_FAN, 'invalid') # verify speed is unchanged 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' 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.""" await _register_components(hass, ['1', '2', '3']) # Turn on fan await common.async_turn_on(hass, _TEST_FAN) # Set fan's speed to '1' await common.async_set_speed(hass, _TEST_FAN, '1') # verify 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 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 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 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): await common.async_oscillate(hass, _TEST_FAN, 'invalid') # verify 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 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 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 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 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' await common.async_set_direction(hass, _TEST_FAN, 'invalid') # verify direction is unchanged 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 assert hass.states.get(_DIRECTION_INPUT_SELECT).state == \ DIRECTION_FORWARD _verify(hass, STATE_ON, None, None, DIRECTION_FORWARD) # Set fan's direction to 'invalid' await common.async_set_direction(hass, _TEST_FAN, 'invalid') # verify direction is unchanged assert hass.states.get(_DIRECTION_INPUT_SELECT).state == \ DIRECTION_FORWARD _verify(hass, STATE_ON, None, None, DIRECTION_FORWARD) 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.""" with assert_setup_component(1, 'input_boolean'): assert await setup.async_setup_component( hass, 'input_boolean', {'input_boolean': {'state': None}} ) 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] }, } }) with assert_setup_component(1, 'fan'): value_template = """ {% if is_state('input_boolean.state', 'on') %} {{ 'on' }} {% else %} {{ 'off' }} {% endif %} """ test_fan_config = { '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 }, 'turn_off': { 'service': 'input_boolean.turn_off', 'entity_id': _STATE_INPUT_BOOLEAN }, 'set_speed': { 'service': 'input_select.select_option', 'data_template': { 'entity_id': _SPEED_INPUT_SELECT, 'option': '{{ speed }}' } }, '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: test_fan_config['speeds'] = speed_list 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()