"""Test Snooz fan entity.""" from __future__ import annotations from datetime import timedelta from unittest.mock import Mock from pysnooz.api import SnoozDeviceState, UnknownSnoozState from pysnooz.commands import SnoozCommandResult, SnoozCommandResultStatus from pysnooz.testing import MockSnoozDevice import pytest from homeassistant.components import fan from homeassistant.components.snooz.const import ( ATTR_DURATION, DOMAIN, SERVICE_TRANSITION_OFF, SERVICE_TRANSITION_ON, ) from homeassistant.const import ( ATTR_ASSUMED_STATE, ATTR_ENTITY_ID, STATE_OFF, STATE_ON, STATE_UNAVAILABLE, STATE_UNKNOWN, Platform, ) from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import entity_registry from . import SnoozFixture, create_mock_snooz, create_mock_snooz_config_entry async def test_turn_on(hass: HomeAssistant, snooz_fan_entity_id: str): """Test turning on the device.""" await hass.services.async_call( fan.DOMAIN, fan.SERVICE_TURN_ON, {ATTR_ENTITY_ID: [snooz_fan_entity_id]}, blocking=True, ) state = hass.states.get(snooz_fan_entity_id) assert state.state == STATE_ON assert ATTR_ASSUMED_STATE not in state.attributes async def test_transition_on(hass: HomeAssistant, snooz_fan_entity_id: str): """Test transitioning on the device.""" await hass.services.async_call( DOMAIN, SERVICE_TRANSITION_ON, {ATTR_ENTITY_ID: [snooz_fan_entity_id], ATTR_DURATION: 1}, blocking=True, ) state = hass.states.get(snooz_fan_entity_id) assert state.state == STATE_ON assert ATTR_ASSUMED_STATE not in state.attributes @pytest.mark.parametrize("percentage", [1, 22, 50, 99, 100]) async def test_turn_on_with_percentage( hass: HomeAssistant, snooz_fan_entity_id: str, percentage: int ): """Test turning on the device with a percentage.""" await hass.services.async_call( fan.DOMAIN, fan.SERVICE_TURN_ON, {ATTR_ENTITY_ID: [snooz_fan_entity_id], fan.ATTR_PERCENTAGE: percentage}, blocking=True, ) state = hass.states.get(snooz_fan_entity_id) assert state.state == STATE_ON assert state.attributes[fan.ATTR_PERCENTAGE] == percentage assert ATTR_ASSUMED_STATE not in state.attributes @pytest.mark.parametrize("percentage", [1, 22, 50, 99, 100]) async def test_set_percentage( hass: HomeAssistant, snooz_fan_entity_id: str, percentage: int ): """Test setting the fan percentage.""" await hass.services.async_call( fan.DOMAIN, fan.SERVICE_SET_PERCENTAGE, {ATTR_ENTITY_ID: [snooz_fan_entity_id], fan.ATTR_PERCENTAGE: percentage}, blocking=True, ) state = hass.states.get(snooz_fan_entity_id) assert state.attributes[fan.ATTR_PERCENTAGE] == percentage assert ATTR_ASSUMED_STATE not in state.attributes async def test_set_0_percentage_turns_off( hass: HomeAssistant, snooz_fan_entity_id: str ): """Test turning off the device by setting the percentage/volume to 0.""" await hass.services.async_call( fan.DOMAIN, fan.SERVICE_TURN_ON, {ATTR_ENTITY_ID: [snooz_fan_entity_id], fan.ATTR_PERCENTAGE: 66}, blocking=True, ) await hass.services.async_call( fan.DOMAIN, fan.SERVICE_SET_PERCENTAGE, {ATTR_ENTITY_ID: [snooz_fan_entity_id], fan.ATTR_PERCENTAGE: 0}, blocking=True, ) state = hass.states.get(snooz_fan_entity_id) assert state.state == STATE_OFF # doesn't overwrite percentage when turning off assert state.attributes[fan.ATTR_PERCENTAGE] == 66 assert ATTR_ASSUMED_STATE not in state.attributes async def test_turn_off(hass: HomeAssistant, snooz_fan_entity_id: str): """Test turning off the device.""" await hass.services.async_call( fan.DOMAIN, fan.SERVICE_TURN_OFF, {ATTR_ENTITY_ID: [snooz_fan_entity_id]}, blocking=True, ) state = hass.states.get(snooz_fan_entity_id) assert state.state == STATE_OFF assert ATTR_ASSUMED_STATE not in state.attributes async def test_transition_off(hass: HomeAssistant, snooz_fan_entity_id: str): """Test transitioning off the device.""" await hass.services.async_call( DOMAIN, SERVICE_TRANSITION_OFF, {ATTR_ENTITY_ID: [snooz_fan_entity_id], ATTR_DURATION: 1}, blocking=True, ) state = hass.states.get(snooz_fan_entity_id) assert state.state == STATE_OFF assert ATTR_ASSUMED_STATE not in state.attributes async def test_push_events( hass: HomeAssistant, mock_connected_snooz: SnoozFixture, snooz_fan_entity_id: str ): """Test state update events from snooz device.""" mock_connected_snooz.device.trigger_state(SnoozDeviceState(False, 64)) state = hass.states.get(snooz_fan_entity_id) assert ATTR_ASSUMED_STATE not in state.attributes assert state.state == STATE_OFF assert state.attributes[fan.ATTR_PERCENTAGE] == 64 mock_connected_snooz.device.trigger_state(SnoozDeviceState(True, 12)) state = hass.states.get(snooz_fan_entity_id) assert ATTR_ASSUMED_STATE not in state.attributes assert state.state == STATE_ON assert state.attributes[fan.ATTR_PERCENTAGE] == 12 mock_connected_snooz.device.trigger_disconnect() state = hass.states.get(snooz_fan_entity_id) assert state.attributes[ATTR_ASSUMED_STATE] is True async def test_restore_state(hass: HomeAssistant): """Tests restoring entity state.""" device = await create_mock_snooz(connected=False, initial_state=UnknownSnoozState) entry = await create_mock_snooz_config_entry(hass, device) entity_id = get_fan_entity_id(hass, device) # call service to store state await hass.services.async_call( fan.DOMAIN, fan.SERVICE_TURN_ON, {ATTR_ENTITY_ID: [entity_id], fan.ATTR_PERCENTAGE: 33}, blocking=True, ) # unload entry await hass.config_entries.async_unload(entry.entry_id) state = hass.states.get(entity_id) assert state.state == STATE_UNAVAILABLE # reload entry await create_mock_snooz_config_entry(hass, device) # should match last known state state = hass.states.get(entity_id) assert state.state == STATE_ON assert state.attributes[fan.ATTR_PERCENTAGE] == 33 assert state.attributes[ATTR_ASSUMED_STATE] is True async def test_restore_unknown_state(hass: HomeAssistant): """Tests restoring entity state that was unknown.""" device = await create_mock_snooz(connected=False, initial_state=UnknownSnoozState) entry = await create_mock_snooz_config_entry(hass, device) entity_id = get_fan_entity_id(hass, device) # unload entry await hass.config_entries.async_unload(entry.entry_id) state = hass.states.get(entity_id) assert state.state == STATE_UNAVAILABLE # reload entry await create_mock_snooz_config_entry(hass, device) # should match last known state state = hass.states.get(entity_id) assert state.state == STATE_UNKNOWN async def test_command_results( hass: HomeAssistant, mock_connected_snooz: SnoozFixture, snooz_fan_entity_id: str ): """Test device command results.""" mock_execute = Mock(spec=mock_connected_snooz.device.async_execute_command) mock_connected_snooz.device.async_execute_command = mock_execute mock_execute.return_value = SnoozCommandResult( SnoozCommandResultStatus.SUCCESSFUL, timedelta() ) mock_connected_snooz.device.state = SnoozDeviceState(on=True, volume=56) await hass.services.async_call( fan.DOMAIN, fan.SERVICE_TURN_ON, {ATTR_ENTITY_ID: [snooz_fan_entity_id]}, blocking=True, ) state = hass.states.get(snooz_fan_entity_id) assert state.state == STATE_ON assert state.attributes[fan.ATTR_PERCENTAGE] == 56 mock_execute.return_value = SnoozCommandResult( SnoozCommandResultStatus.CANCELLED, timedelta() ) mock_connected_snooz.device.state = SnoozDeviceState(on=False, volume=15) await hass.services.async_call( fan.DOMAIN, fan.SERVICE_TURN_ON, {ATTR_ENTITY_ID: [snooz_fan_entity_id]}, blocking=True, ) # the device state shouldn't be written when cancelled state = hass.states.get(snooz_fan_entity_id) assert state.state == STATE_ON assert state.attributes[fan.ATTR_PERCENTAGE] == 56 mock_execute.return_value = SnoozCommandResult( SnoozCommandResultStatus.UNEXPECTED_ERROR, timedelta() ) with pytest.raises(HomeAssistantError) as failure: await hass.services.async_call( fan.DOMAIN, fan.SERVICE_TURN_ON, {ATTR_ENTITY_ID: [snooz_fan_entity_id]}, blocking=True, ) assert failure.match("failed with status") @pytest.fixture(name="snooz_fan_entity_id") async def fixture_snooz_fan_entity_id( hass: HomeAssistant, mock_connected_snooz: SnoozFixture ) -> str: """Mock a Snooz fan entity and config entry.""" return get_fan_entity_id(hass, mock_connected_snooz.device) def get_fan_entity_id(hass: HomeAssistant, device: MockSnoozDevice) -> str: """Get the entity ID for a mock device.""" return entity_registry.async_get(hass).async_get_entity_id( Platform.FAN, DOMAIN, device.address )