From dbaca51bb3b7b0cea2acd5d3cc6fd1b7a396daf9 Mon Sep 17 00:00:00 2001 From: Xiaonan Shen Date: Mon, 25 Jan 2021 00:37:48 +0800 Subject: [PATCH] Rewrite dyson climate tests (#45493) * Rewrite dyson climate tests * Cleanup --- tests/components/dyson/common.py | 32 +- tests/components/dyson/test_climate.py | 904 ++++++++----------------- tests/components/dyson/test_sensor.py | 4 +- 3 files changed, 273 insertions(+), 667 deletions(-) diff --git a/tests/components/dyson/common.py b/tests/components/dyson/common.py index 279cfe83b58..b26c48d55f8 100644 --- a/tests/components/dyson/common.py +++ b/tests/components/dyson/common.py @@ -36,39 +36,21 @@ CONFIG = { } -def load_mock_device(device: DysonDevice) -> None: - """Load the mock with default values so it doesn't throw errors.""" +@callback +def async_get_basic_device(spec: Type[DysonDevice]) -> DysonDevice: + """Return a basic device with common fields filled out.""" + device = MagicMock(spec=spec) device.serial = SERIAL device.name = NAME device.connect = mock.Mock(return_value=True) device.auto_connect = mock.Mock(return_value=True) - device.state.hepa_filter_state = 0 - device.state.carbon_filter_state = 0 - device.state.speed = FanSpeed.FAN_SPEED_1.value - device.state.oscillation_angle_low = "000" - device.state.oscillation_angle_high = "000" - device.state.filter_life = "000" - device.state.heat_target = 200 - if hasattr(device, "environmental_state"): - device.environmental_state.particulate_matter_25 = "0000" - device.environmental_state.particulate_matter_10 = "0000" - device.environmental_state.nitrogen_dioxide = "0000" - device.environmental_state.volatil_organic_compounds = "0000" - device.environmental_state.volatile_organic_compounds = "0000" - device.environmental_state.temperature = 250 - - -def get_basic_device(spec: Type[DysonDevice]) -> DysonDevice: - """Return a basic device with common fields filled out.""" - device = MagicMock(spec=spec) - load_mock_device(device) return device @callback def async_get_360eye_device(state=Dyson360EyeMode.FULL_CLEAN_RUNNING) -> Dyson360Eye: """Return a Dyson 360 Eye device.""" - device = get_basic_device(Dyson360Eye) + device = async_get_basic_device(Dyson360Eye) device.state.state = state device.state.battery_level = 85 device.state.power_mode = PowerMode.QUIET @@ -79,7 +61,7 @@ def async_get_360eye_device(state=Dyson360EyeMode.FULL_CLEAN_RUNNING) -> Dyson36 @callback def async_get_purecoollink_device() -> DysonPureCoolLink: """Return a Dyson Pure Cool Link device.""" - device = get_basic_device(DysonPureCoolLink) + device = async_get_basic_device(DysonPureCoolLink) device.state.fan_mode = FanMode.FAN.value device.state.speed = FanSpeed.FAN_SPEED_1.value device.state.night_mode = "ON" @@ -90,7 +72,7 @@ def async_get_purecoollink_device() -> DysonPureCoolLink: @callback def async_get_purecool_device() -> DysonPureCool: """Return a Dyson Pure Cool device.""" - device = get_basic_device(DysonPureCool) + device = async_get_basic_device(DysonPureCool) device.state.fan_power = "ON" device.state.speed = FanSpeed.FAN_SPEED_1.value device.state.night_mode = "ON" diff --git a/tests/components/dyson/test_climate.py b/tests/components/dyson/test_climate.py index 484af9d48e8..0e389000c29 100644 --- a/tests/components/dyson/test_climate.py +++ b/tests/components/dyson/test_climate.py @@ -1,28 +1,24 @@ """Test the Dyson fan component.""" -import json -from unittest.mock import Mock, call, patch + +from typing import Type from libpurecool.const import ( + AutoMode, FanPower, FanSpeed, FanState, FocusMode, HeatMode, HeatState, - HeatTarget, ) +from libpurecool.dyson_device import DysonDevice from libpurecool.dyson_pure_hotcool import DysonPureHotCool from libpurecool.dyson_pure_hotcool_link import DysonPureHotCoolLink from libpurecool.dyson_pure_state import DysonPureHotCoolState from libpurecool.dyson_pure_state_v2 import DysonPureHotCoolV2State import pytest -from homeassistant.components.climate import ( - DOMAIN, - SERVICE_SET_FAN_MODE, - SERVICE_SET_HVAC_MODE, - SERVICE_SET_TEMPERATURE, -) +from homeassistant.components.climate import DOMAIN as PLATFORM_DOMAIN from homeassistant.components.climate.const import ( ATTR_CURRENT_HUMIDITY, ATTR_CURRENT_TEMPERATURE, @@ -33,12 +29,13 @@ from homeassistant.components.climate.const import ( ATTR_HVAC_MODES, ATTR_MAX_TEMP, ATTR_MIN_TEMP, - ATTR_TARGET_TEMP_HIGH, - ATTR_TARGET_TEMP_LOW, CURRENT_HVAC_COOL, CURRENT_HVAC_HEAT, CURRENT_HVAC_IDLE, + CURRENT_HVAC_OFF, FAN_AUTO, + FAN_DIFFUSE, + FAN_FOCUS, FAN_HIGH, FAN_LOW, FAN_MEDIUM, @@ -46,680 +43,307 @@ from homeassistant.components.climate.const import ( HVAC_MODE_COOL, HVAC_MODE_HEAT, HVAC_MODE_OFF, + SERVICE_SET_FAN_MODE, + SERVICE_SET_HVAC_MODE, + SERVICE_SET_TEMPERATURE, +) +from homeassistant.components.dyson.climate import ( + SUPPORT_FAN, + SUPPORT_FAN_PCOOL, + SUPPORT_FLAGS, + SUPPORT_HVAC, + SUPPORT_HVAC_PCOOL, ) -from homeassistant.components.dyson import CONF_LANGUAGE, DOMAIN as DYSON_DOMAIN -from homeassistant.components.dyson.climate import FAN_DIFFUSE, FAN_FOCUS, SUPPORT_FLAGS from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, ATTR_TEMPERATURE, - CONF_DEVICES, - CONF_PASSWORD, - CONF_USERNAME, - TEMP_CELSIUS, ) -from homeassistant.setup import async_setup_component +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers import entity_registry -from .common import load_mock_device +from .common import ( + ENTITY_NAME, + NAME, + SERIAL, + async_get_basic_device, + async_update_device, +) + +ENTITY_ID = f"{PLATFORM_DOMAIN}.{ENTITY_NAME}" -class MockDysonState(DysonPureHotCoolState): - """Mock Dyson state.""" - - # pylint: disable=super-init-not-called - - def __init__(self): - """Create new Mock Dyson State.""" - - def __repr__(self): - """Mock repr because original one fails since constructor not called.""" - return "" - - -def _get_config(): - """Return a config dictionary.""" - return { - DYSON_DOMAIN: { - CONF_USERNAME: "email", - CONF_PASSWORD: "password", - CONF_LANGUAGE: "GB", - CONF_DEVICES: [ - {"device_id": "XX-XXXXX-XX", "device_ip": "192.168.0.1"}, - {"device_id": "YY-YYYYY-YY", "device_ip": "192.168.0.2"}, - ], - } - } - - -def _get_dyson_purehotcool_device(): - """Return a valid device as provided by the Dyson web services.""" - device = Mock(spec=DysonPureHotCool) - load_mock_device(device) - device.name = "Living room" - device.state.heat_target = "0000" - device.state.heat_mode = HeatMode.HEAT_OFF.value - device.state.fan_power = FanPower.POWER_OFF.value - device.environmental_state.humidity = 42 - device.environmental_state.temperature = 298 +@callback +def async_get_device(spec: Type[DysonDevice]) -> DysonDevice: + """Return a Dyson climate device.""" + device = async_get_basic_device(spec) + device.state.heat_target = 2900 + device.environmental_state.temperature = 275 + device.environmental_state.humidity = 50 + if spec == DysonPureHotCoolLink: + device.state.heat_mode = HeatMode.HEAT_ON.value + device.state.heat_state = HeatState.HEAT_STATE_ON.value + device.state.focus_mode = FocusMode.FOCUS_ON.value + else: + device.state.fan_power = FanPower.POWER_ON.value + device.state.heat_mode = HeatMode.HEAT_ON.value + device.state.heat_state = HeatState.HEAT_STATE_ON.value + device.state.auto_mode = AutoMode.AUTO_ON.value + device.state.fan_state = FanState.FAN_OFF.value + device.state.speed = FanSpeed.FAN_SPEED_AUTO.value return device -def _get_device_off(): - """Return a device with state off.""" - device = Mock(spec=DysonPureHotCoolLink) - load_mock_device(device) - return device - - -def _get_device_cool(): - """Return a device with state of cooling.""" - device = Mock(spec=DysonPureHotCoolLink) - load_mock_device(device) - device.state.focus_mode = FocusMode.FOCUS_OFF.value - device.state.heat_target = HeatTarget.celsius(12) - device.state.heat_mode = HeatMode.HEAT_OFF.value - device.state.heat_state = HeatState.HEAT_STATE_OFF.value - return device - - -def _get_device_heat_on(): - """Return a device with state of heating.""" - device = Mock(spec=DysonPureHotCoolLink) - load_mock_device(device) - device.serial = "YY-YYYYY-YY" - device.state.heat_target = HeatTarget.celsius(23) - device.state.heat_mode = HeatMode.HEAT_ON.value - device.state.heat_state = HeatState.HEAT_STATE_ON.value - device.environmental_state.temperature = 289 - device.environmental_state.humidity = 53 - return device - - -@pytest.fixture(autouse=True) -def patch_platforms_fixture(): - """Only set up the climate platform for the climate tests.""" - with patch("homeassistant.components.dyson.DYSON_PLATFORMS", new=[DOMAIN]): - yield - - -@patch( - "homeassistant.components.dyson.DysonAccount.devices", - return_value=[_get_device_heat_on()], +@pytest.mark.parametrize( + "device", [DysonPureHotCoolLink, DysonPureHotCool], indirect=True ) -@patch("homeassistant.components.dyson.DysonAccount.login", return_value=True) -async def test_pure_hot_cool_link_set_mode(mocked_login, mocked_devices, hass): - """Test set climate mode.""" - await async_setup_component(hass, DYSON_DOMAIN, _get_config()) - await hass.async_block_till_done() +async def test_state_common(hass: HomeAssistant, device: DysonDevice) -> None: + """Test common state and attributes of two types of climate entities.""" + er = await entity_registry.async_get_registry(hass) + assert er.async_get(ENTITY_ID).unique_id == SERIAL - device = mocked_devices.return_value[0] - - await hass.services.async_call( - DOMAIN, - SERVICE_SET_HVAC_MODE, - {ATTR_ENTITY_ID: "climate.temp_name", ATTR_HVAC_MODE: HVAC_MODE_HEAT}, - True, - ) - - set_config = device.set_configuration - assert set_config.call_args == call(heat_mode=HeatMode.HEAT_ON) - - await hass.services.async_call( - DOMAIN, - SERVICE_SET_HVAC_MODE, - {ATTR_ENTITY_ID: "climate.temp_name", ATTR_HVAC_MODE: HVAC_MODE_COOL}, - True, - ) - - set_config = device.set_configuration - assert set_config.call_args == call(heat_mode=HeatMode.HEAT_OFF) - - -@patch( - "homeassistant.components.dyson.DysonAccount.devices", - return_value=[_get_device_heat_on()], -) -@patch("homeassistant.components.dyson.DysonAccount.login", return_value=True) -async def test_pure_hot_cool_link_set_fan(mocked_login, mocked_devices, hass): - """Test set climate fan.""" - await async_setup_component(hass, DYSON_DOMAIN, _get_config()) - await hass.async_block_till_done() - - device = mocked_devices.return_value[0] - device.temp_unit = TEMP_CELSIUS - - await hass.services.async_call( - DOMAIN, - SERVICE_SET_FAN_MODE, - {ATTR_ENTITY_ID: "climate.temp_name", ATTR_FAN_MODE: FAN_FOCUS}, - True, - ) - - set_config = device.set_configuration - assert set_config.call_args == call(focus_mode=FocusMode.FOCUS_ON) - - await hass.services.async_call( - DOMAIN, - SERVICE_SET_FAN_MODE, - {ATTR_ENTITY_ID: "climate.temp_name", ATTR_FAN_MODE: FAN_DIFFUSE}, - True, - ) - - set_config = device.set_configuration - assert set_config.call_args == call(focus_mode=FocusMode.FOCUS_OFF) - - -@patch( - "homeassistant.components.dyson.DysonAccount.devices", - return_value=[_get_device_heat_on()], -) -@patch("homeassistant.components.dyson.DysonAccount.login", return_value=True) -async def test_pure_hot_cool_link_state(mocked_login, mocked_devices, hass): - """Test set climate temperature.""" - await async_setup_component(hass, DYSON_DOMAIN, _get_config()) - await hass.async_block_till_done() - - state = hass.states.get("climate.temp_name") - assert state.attributes[ATTR_SUPPORTED_FEATURES] == SUPPORT_FLAGS - assert state.attributes[ATTR_TEMPERATURE] == 23 - assert state.attributes[ATTR_CURRENT_TEMPERATURE] == 289 - 273 - assert state.attributes[ATTR_CURRENT_HUMIDITY] == 53 - assert state.state == HVAC_MODE_HEAT - assert len(state.attributes[ATTR_HVAC_MODES]) == 2 - assert HVAC_MODE_HEAT in state.attributes[ATTR_HVAC_MODES] - assert HVAC_MODE_COOL in state.attributes[ATTR_HVAC_MODES] - assert len(state.attributes[ATTR_FAN_MODES]) == 2 - assert FAN_FOCUS in state.attributes[ATTR_FAN_MODES] - assert FAN_DIFFUSE in state.attributes[ATTR_FAN_MODES] - - device = mocked_devices.return_value[0] - update_callback = device.add_message_listener.call_args[0][0] - - device.state.focus_mode = FocusMode.FOCUS_ON.value - await hass.async_add_executor_job(update_callback, MockDysonState()) - await hass.async_block_till_done() - - state = hass.states.get("climate.temp_name") - assert state.attributes[ATTR_FAN_MODE] == FAN_FOCUS - - device.state.focus_mode = FocusMode.FOCUS_OFF.value - await hass.async_add_executor_job(update_callback, MockDysonState()) - await hass.async_block_till_done() - - state = hass.states.get("climate.temp_name") - assert state.attributes[ATTR_FAN_MODE] == FAN_DIFFUSE - - device.state.heat_mode = HeatMode.HEAT_ON.value - device.state.heat_state = HeatState.HEAT_STATE_OFF.value - await hass.async_add_executor_job(update_callback, MockDysonState()) - await hass.async_block_till_done() - - state = hass.states.get("climate.temp_name") - assert state.state == HVAC_MODE_HEAT - assert state.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_IDLE - - device.environmental_state.humidity = 0 - await hass.async_add_executor_job(update_callback, MockDysonState()) - await hass.async_block_till_done() - - state = hass.states.get("climate.temp_name") - assert state.attributes.get(ATTR_CURRENT_HUMIDITY) is None - - device.environmental_state = None - await hass.async_add_executor_job(update_callback, MockDysonState()) - await hass.async_block_till_done() - - state = hass.states.get("climate.temp_name") - assert state.attributes.get(ATTR_CURRENT_HUMIDITY) is None - - device.state.heat_mode = HeatMode.HEAT_OFF.value - device.state.heat_state = HeatState.HEAT_STATE_OFF.value - await hass.async_add_executor_job(update_callback, MockDysonState()) - await hass.async_block_till_done() - - state = hass.states.get("climate.temp_name") - assert state.state == HVAC_MODE_COOL - assert state.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_COOL - - -@patch( - "homeassistant.components.dyson.DysonAccount.devices", - return_value=[], -) -@patch("homeassistant.components.dyson.DysonAccount.login", return_value=True) -async def test_setup_component_without_devices(mocked_login, mocked_devices, hass): - """Test setup component with no devices.""" - await async_setup_component(hass, DYSON_DOMAIN, _get_config()) - await hass.async_block_till_done() - - entity_ids = hass.states.async_entity_ids(DOMAIN) - assert not entity_ids - - -@patch( - "homeassistant.components.dyson.DysonAccount.devices", - return_value=[_get_device_heat_on()], -) -@patch("homeassistant.components.dyson.DysonAccount.login", return_value=True) -async def test_dyson_set_temperature(mocked_login, mocked_devices, hass): - """Test set climate temperature.""" - await async_setup_component(hass, DYSON_DOMAIN, _get_config()) - await hass.async_block_till_done() - - device = mocked_devices.return_value[0] - device.temp_unit = TEMP_CELSIUS - - # Without correct target temp. - await hass.services.async_call( - DOMAIN, - SERVICE_SET_TEMPERATURE, - { - ATTR_ENTITY_ID: "climate.temp_name", - ATTR_TARGET_TEMP_HIGH: 25.0, - ATTR_TARGET_TEMP_LOW: 15.0, - }, - True, - ) - - set_config = device.set_configuration - assert set_config.call_count == 0 - - await hass.services.async_call( - DOMAIN, - SERVICE_SET_TEMPERATURE, - {ATTR_ENTITY_ID: "climate.temp_name", ATTR_TEMPERATURE: 23}, - True, - ) - - set_config = device.set_configuration - assert set_config.call_args == call( - heat_mode=HeatMode.HEAT_ON, heat_target=HeatTarget.celsius(23) - ) - - # Should clip the target temperature between 1 and 37 inclusive. - await hass.services.async_call( - DOMAIN, - SERVICE_SET_TEMPERATURE, - {ATTR_ENTITY_ID: "climate.temp_name", ATTR_TEMPERATURE: 50}, - True, - ) - - set_config = device.set_configuration - assert set_config.call_args == call( - heat_mode=HeatMode.HEAT_ON, heat_target=HeatTarget.celsius(37) - ) - - await hass.services.async_call( - DOMAIN, - SERVICE_SET_TEMPERATURE, - {ATTR_ENTITY_ID: "climate.temp_name", ATTR_TEMPERATURE: -5}, - True, - ) - - set_config = device.set_configuration - assert set_config.call_args == call( - heat_mode=HeatMode.HEAT_ON, heat_target=HeatTarget.celsius(1) - ) - - -@patch( - "homeassistant.components.dyson.DysonAccount.devices", - return_value=[_get_device_cool()], -) -@patch("homeassistant.components.dyson.DysonAccount.login", return_value=True) -async def test_dyson_set_temperature_when_cooling_mode( - mocked_login, mocked_devices, hass -): - """Test set climate temperature when heating is off.""" - await async_setup_component(hass, DYSON_DOMAIN, _get_config()) - await hass.async_block_till_done() - - device = mocked_devices.return_value[0] - device.temp_unit = TEMP_CELSIUS - - await hass.services.async_call( - DOMAIN, - SERVICE_SET_TEMPERATURE, - {ATTR_ENTITY_ID: "climate.temp_name", ATTR_TEMPERATURE: 23}, - True, - ) - - set_config = device.set_configuration - assert set_config.call_args == call( - heat_mode=HeatMode.HEAT_ON, heat_target=HeatTarget.celsius(23) - ) - - -@patch( - "homeassistant.components.dyson.DysonAccount.devices", - return_value=[_get_device_heat_on(), _get_device_cool()], -) -@patch("homeassistant.components.dyson.DysonAccount.login", return_value=True) -async def test_setup_component_with_parent_discovery( - mocked_login, mocked_devices, hass -): - """Test setup_component using discovery.""" - await async_setup_component(hass, DYSON_DOMAIN, _get_config()) - await hass.async_block_till_done() - - entity_ids = hass.states.async_entity_ids(DOMAIN) - assert len(entity_ids) == 2 - - -@patch("homeassistant.components.dyson.DysonAccount.login", return_value=True) -@patch( - "homeassistant.components.dyson.DysonAccount.devices", - return_value=[_get_dyson_purehotcool_device()], -) -async def test_purehotcool_component_setup_only_once(devices, login, hass): - """Test if entities are created only once.""" - config = _get_config() - await async_setup_component(hass, DYSON_DOMAIN, config) - await hass.async_block_till_done() - - entity_ids = hass.states.async_entity_ids(DOMAIN) - assert len(entity_ids) == 1 - state = hass.states.get(entity_ids[0]) - assert state.name == "Living room" - - -@patch("homeassistant.components.dyson.DysonAccount.login", return_value=True) -@patch( - "homeassistant.components.dyson.DysonAccount.devices", - return_value=[_get_device_off()], -) -async def test_purehotcoollink_component_setup_only_once(devices, login, hass): - """Test if entities are created only once.""" - config = _get_config() - await async_setup_component(hass, DYSON_DOMAIN, config) - await hass.async_block_till_done() - - entity_ids = hass.states.async_entity_ids(DOMAIN) - assert len(entity_ids) == 1 - state = hass.states.get(entity_ids[0]) - assert state.name == "Temp Name" - - -@patch("homeassistant.components.dyson.DysonAccount.login", return_value=True) -@patch( - "homeassistant.components.dyson.DysonAccount.devices", - return_value=[_get_dyson_purehotcool_device()], -) -async def test_purehotcool_update_state(devices, login, hass): - """Test state update.""" - device = devices.return_value[0] - await async_setup_component(hass, DYSON_DOMAIN, _get_config()) - await hass.async_block_till_done() - event = { - "msg": "CURRENT-STATE", - "product-state": { - "fpwr": "ON", - "fdir": "OFF", - "auto": "OFF", - "oscs": "ON", - "oson": "ON", - "nmod": "OFF", - "rhtm": "ON", - "fnst": "FAN", - "ercd": "11E1", - "wacd": "NONE", - "nmdv": "0004", - "fnsp": "0002", - "bril": "0002", - "corf": "ON", - "cflr": "0085", - "hflr": "0095", - "sltm": "OFF", - "osal": "0045", - "osau": "0095", - "ancp": "CUST", - "tilt": "OK", - "hsta": "HEAT", - "hmax": "2986", - "hmod": "HEAT", - }, - } - device.state = DysonPureHotCoolV2State(json.dumps(event)) - update_callback = device.add_message_listener.call_args[0][0] - - await hass.async_add_executor_job(update_callback, device.state) - await hass.async_block_till_done() - state = hass.states.get("climate.living_room") + state = hass.states.get(ENTITY_ID) + assert state.name == NAME attributes = state.attributes + assert attributes[ATTR_SUPPORTED_FEATURES] == SUPPORT_FLAGS + assert attributes[ATTR_CURRENT_TEMPERATURE] == 2 + assert attributes[ATTR_CURRENT_HUMIDITY] == 50 + assert attributes[ATTR_TEMPERATURE] == 17 + assert attributes[ATTR_MIN_TEMP] == 1 + assert attributes[ATTR_MAX_TEMP] == 37 - assert attributes[ATTR_TEMPERATURE] == 25 - assert attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_HEAT - - -@patch("homeassistant.components.dyson.DysonAccount.login", return_value=True) -@patch( - "homeassistant.components.dyson.DysonAccount.devices", - return_value=[_get_dyson_purehotcool_device()], -) -async def test_purehotcool_empty_env_attributes(devices, login, hass): - """Test empty environmental state update.""" - device = devices.return_value[0] + device.state.heat_target = 2800 device.environmental_state.temperature = 0 - device.environmental_state.humidity = None - await async_setup_component(hass, DYSON_DOMAIN, _get_config()) - await hass.async_block_till_done() - - state = hass.states.get("climate.living_room") - attributes = state.attributes - + device.environmental_state.humidity = 0 + await async_update_device( + hass, + device, + DysonPureHotCoolState + if isinstance(device, DysonPureHotCoolLink) + else DysonPureHotCoolV2State, + ) + attributes = hass.states.get(ENTITY_ID).attributes + assert attributes[ATTR_CURRENT_TEMPERATURE] is None assert ATTR_CURRENT_HUMIDITY not in attributes + assert attributes[ATTR_TEMPERATURE] == 7 -@patch("homeassistant.components.dyson.DysonAccount.login", return_value=True) -@patch( - "homeassistant.components.dyson.DysonAccount.devices", - return_value=[_get_dyson_purehotcool_device()], -) -async def test_purehotcool_fan_state_off(devices, login, hass): - """Test device fan state off.""" - device = devices.return_value[0] - device.state.fan_state = FanState.FAN_OFF.value - await async_setup_component(hass, DYSON_DOMAIN, _get_config()) - await hass.async_block_till_done() - - state = hass.states.get("climate.living_room") +@pytest.mark.parametrize("device", [DysonPureHotCoolLink], indirect=True) +async def test_state_purehotcoollink( + hass: HomeAssistant, device: DysonPureHotCoolLink +) -> None: + """Test common state and attributes of a PureHotCoolLink entity.""" + state = hass.states.get(ENTITY_ID) + assert state.state == HVAC_MODE_HEAT attributes = state.attributes + assert attributes[ATTR_HVAC_MODES] == SUPPORT_HVAC + assert attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_HEAT + assert attributes[ATTR_FAN_MODE] == FAN_FOCUS + assert attributes[ATTR_FAN_MODES] == SUPPORT_FAN - assert attributes[ATTR_FAN_MODE] == FAN_OFF - - -@patch("homeassistant.components.dyson.DysonAccount.login", return_value=True) -@patch( - "homeassistant.components.dyson.DysonAccount.devices", - return_value=[_get_dyson_purehotcool_device()], -) -async def test_purehotcool_hvac_action_cool(devices, login, hass): - """Test device HVAC action cool.""" - device = devices.return_value[0] - device.state.fan_power = FanPower.POWER_ON.value - await async_setup_component(hass, DYSON_DOMAIN, _get_config()) - await hass.async_block_till_done() - - state = hass.states.get("climate.living_room") + device.state.heat_state = HeatState.HEAT_STATE_OFF.value + device.state.focus_mode = FocusMode.FOCUS_OFF + await async_update_device(hass, device, DysonPureHotCoolState) + state = hass.states.get(ENTITY_ID) + assert state.state == HVAC_MODE_HEAT attributes = state.attributes + assert attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_IDLE + assert attributes[ATTR_FAN_MODE] == FAN_DIFFUSE + device.state.heat_mode = HeatMode.HEAT_OFF.value + await async_update_device(hass, device, DysonPureHotCoolState) + state = hass.states.get(ENTITY_ID) + assert state.state == HVAC_MODE_COOL + attributes = state.attributes assert attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_COOL -@patch("homeassistant.components.dyson.DysonAccount.login", return_value=True) -@patch( - "homeassistant.components.dyson.DysonAccount.devices", - return_value=[_get_dyson_purehotcool_device()], -) -async def test_purehotcool_hvac_action_idle(devices, login, hass): - """Test device HVAC action idle.""" - device = devices.return_value[0] - device.state.fan_power = FanPower.POWER_ON.value - device.state.heat_mode = HeatMode.HEAT_ON.value - await async_setup_component(hass, DYSON_DOMAIN, _get_config()) - await hass.async_block_till_done() - - state = hass.states.get("climate.living_room") +@pytest.mark.parametrize("device", [DysonPureHotCool], indirect=True) +async def test_state_purehotcool(hass: HomeAssistant, device: DysonPureHotCool) -> None: + """Test common state and attributes of a PureHotCool entity.""" + state = hass.states.get(ENTITY_ID) + assert state.state == HVAC_MODE_HEAT attributes = state.attributes + assert attributes[ATTR_HVAC_MODES] == SUPPORT_HVAC_PCOOL + assert attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_HEAT + assert attributes[ATTR_FAN_MODE] == FAN_AUTO + assert attributes[ATTR_FAN_MODES] == SUPPORT_FAN_PCOOL + device.state.heat_state = HeatState.HEAT_STATE_OFF.value + device.state.auto_mode = AutoMode.AUTO_OFF.value + await async_update_device(hass, device, DysonPureHotCoolV2State) + state = hass.states.get(ENTITY_ID) + assert state.state == HVAC_MODE_HEAT + attributes = state.attributes assert attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_IDLE + assert attributes[ATTR_FAN_MODE] == FAN_OFF - -@patch("homeassistant.components.dyson.DysonAccount.login", return_value=True) -@patch( - "homeassistant.components.dyson.DysonAccount.devices", - return_value=[_get_dyson_purehotcool_device()], -) -async def test_purehotcool_set_temperature(devices, login, hass): - """Test set temperature.""" - device = devices.return_value[0] - await async_setup_component(hass, DYSON_DOMAIN, _get_config()) - await hass.async_block_till_done() - state = hass.states.get("climate.living_room") + device.state.heat_mode = HeatMode.HEAT_OFF.value + device.state.fan_state = FanState.FAN_ON.value + device.state.speed = FanSpeed.FAN_SPEED_1.value + await async_update_device(hass, device, DysonPureHotCoolV2State) + state = hass.states.get(ENTITY_ID) + assert state.state == HVAC_MODE_COOL attributes = state.attributes - min_temp = attributes[ATTR_MIN_TEMP] - max_temp = attributes[ATTR_MAX_TEMP] + assert attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_COOL + assert attributes[ATTR_FAN_MODE] == FAN_LOW - await hass.services.async_call( - DOMAIN, - SERVICE_SET_TEMPERATURE, - {ATTR_ENTITY_ID: "climate.bed_room", ATTR_TEMPERATURE: 23}, - True, - ) - device.set_heat_target.assert_not_called() - - await hass.services.async_call( - DOMAIN, - SERVICE_SET_TEMPERATURE, - {ATTR_ENTITY_ID: "climate.living_room", ATTR_TEMPERATURE: 23}, - True, - ) - assert device.set_heat_target.call_count == 1 - device.set_heat_target.assert_called_with("2960") - - await hass.services.async_call( - DOMAIN, - SERVICE_SET_TEMPERATURE, - {ATTR_ENTITY_ID: "climate.living_room", ATTR_TEMPERATURE: min_temp - 1}, - True, - ) - assert device.set_heat_target.call_count == 2 - device.set_heat_target.assert_called_with(HeatTarget.celsius(min_temp)) - - await hass.services.async_call( - DOMAIN, - SERVICE_SET_TEMPERATURE, - {ATTR_ENTITY_ID: "climate.living_room", ATTR_TEMPERATURE: max_temp + 1}, - True, - ) - assert device.set_heat_target.call_count == 3 - device.set_heat_target.assert_called_with(HeatTarget.celsius(max_temp)) + device.state.fan_power = FanPower.POWER_OFF.value + await async_update_device(hass, device, DysonPureHotCoolV2State) + state = hass.states.get(ENTITY_ID) + assert state.state == HVAC_MODE_OFF + attributes = state.attributes + assert attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_OFF -@patch("homeassistant.components.dyson.DysonAccount.login", return_value=True) -@patch( - "homeassistant.components.dyson.DysonAccount.devices", - return_value=[_get_dyson_purehotcool_device()], +@pytest.mark.parametrize( + "service,service_data,configuration_data", + [ + ( + SERVICE_SET_TEMPERATURE, + {ATTR_TEMPERATURE: -5}, + {"heat_target": "2740", "heat_mode": HeatMode.HEAT_ON}, + ), + ( + SERVICE_SET_TEMPERATURE, + {ATTR_TEMPERATURE: 40}, + {"heat_target": "3100", "heat_mode": HeatMode.HEAT_ON}, + ), + ( + SERVICE_SET_TEMPERATURE, + {ATTR_TEMPERATURE: 20}, + {"heat_target": "2930", "heat_mode": HeatMode.HEAT_ON}, + ), + ( + SERVICE_SET_FAN_MODE, + {ATTR_FAN_MODE: FAN_FOCUS}, + {"focus_mode": FocusMode.FOCUS_ON}, + ), + ( + SERVICE_SET_FAN_MODE, + {ATTR_FAN_MODE: FAN_DIFFUSE}, + {"focus_mode": FocusMode.FOCUS_OFF}, + ), + ( + SERVICE_SET_HVAC_MODE, + {ATTR_HVAC_MODE: HVAC_MODE_HEAT}, + {"heat_mode": HeatMode.HEAT_ON}, + ), + ( + SERVICE_SET_HVAC_MODE, + {ATTR_HVAC_MODE: HVAC_MODE_COOL}, + {"heat_mode": HeatMode.HEAT_OFF}, + ), + ], ) -async def test_purehotcool_set_fan_mode(devices, login, hass): - """Test set fan mode.""" - device = devices.return_value[0] - await async_setup_component(hass, DYSON_DOMAIN, _get_config()) - await hass.async_block_till_done() - +@pytest.mark.parametrize("device", [DysonPureHotCoolLink], indirect=True) +async def test_commands_purehotcoollink( + hass: HomeAssistant, + device: DysonPureHotCoolLink, + service: str, + service_data: dict, + configuration_data: dict, +) -> None: + """Test sending commands to a PureHotCoolLink entity.""" await hass.services.async_call( - DOMAIN, - SERVICE_SET_FAN_MODE, - {ATTR_ENTITY_ID: "climate.bed_room", ATTR_FAN_MODE: FAN_OFF}, - True, + PLATFORM_DOMAIN, + service, + { + ATTR_ENTITY_ID: ENTITY_ID, + **service_data, + }, + blocking=True, ) - device.turn_off.assert_not_called() - - await hass.services.async_call( - DOMAIN, - SERVICE_SET_FAN_MODE, - {ATTR_ENTITY_ID: "climate.living_room", ATTR_FAN_MODE: FAN_OFF}, - True, - ) - assert device.turn_off.call_count == 1 - - await hass.services.async_call( - DOMAIN, - SERVICE_SET_FAN_MODE, - {ATTR_ENTITY_ID: "climate.living_room", ATTR_FAN_MODE: FAN_LOW}, - True, - ) - assert device.set_fan_speed.call_count == 1 - device.set_fan_speed.assert_called_with(FanSpeed.FAN_SPEED_4) - - await hass.services.async_call( - DOMAIN, - SERVICE_SET_FAN_MODE, - {ATTR_ENTITY_ID: "climate.living_room", ATTR_FAN_MODE: FAN_MEDIUM}, - True, - ) - assert device.set_fan_speed.call_count == 2 - device.set_fan_speed.assert_called_with(FanSpeed.FAN_SPEED_7) - - await hass.services.async_call( - DOMAIN, - SERVICE_SET_FAN_MODE, - {ATTR_ENTITY_ID: "climate.living_room", ATTR_FAN_MODE: FAN_HIGH}, - True, - ) - assert device.set_fan_speed.call_count == 3 - device.set_fan_speed.assert_called_with(FanSpeed.FAN_SPEED_10) - - await hass.services.async_call( - DOMAIN, - SERVICE_SET_FAN_MODE, - {ATTR_ENTITY_ID: "climate.living_room", ATTR_FAN_MODE: FAN_AUTO}, - True, - ) - assert device.enable_auto_mode.call_count == 1 + device.set_configuration.assert_called_once_with(**configuration_data) -@patch("homeassistant.components.dyson.DysonAccount.login", return_value=True) -@patch( - "homeassistant.components.dyson.DysonAccount.devices", - return_value=[_get_dyson_purehotcool_device()], +@pytest.mark.parametrize( + "service,service_data,command,command_args", + [ + (SERVICE_SET_TEMPERATURE, {ATTR_TEMPERATURE: 20}, "set_heat_target", ["2930"]), + (SERVICE_SET_FAN_MODE, {ATTR_FAN_MODE: FAN_OFF}, "turn_off", []), + ( + SERVICE_SET_FAN_MODE, + {ATTR_FAN_MODE: FAN_LOW}, + "set_fan_speed", + [FanSpeed.FAN_SPEED_4], + ), + ( + SERVICE_SET_FAN_MODE, + {ATTR_FAN_MODE: FAN_MEDIUM}, + "set_fan_speed", + [FanSpeed.FAN_SPEED_7], + ), + ( + SERVICE_SET_FAN_MODE, + {ATTR_FAN_MODE: FAN_HIGH}, + "set_fan_speed", + [FanSpeed.FAN_SPEED_10], + ), + (SERVICE_SET_FAN_MODE, {ATTR_FAN_MODE: FAN_AUTO}, "enable_auto_mode", []), + (SERVICE_SET_HVAC_MODE, {ATTR_HVAC_MODE: HVAC_MODE_OFF}, "turn_off", []), + ( + SERVICE_SET_HVAC_MODE, + {ATTR_HVAC_MODE: HVAC_MODE_HEAT}, + "enable_heat_mode", + [], + ), + ( + SERVICE_SET_HVAC_MODE, + {ATTR_HVAC_MODE: HVAC_MODE_COOL}, + "disable_heat_mode", + [], + ), + ], ) -async def test_purehotcool_set_hvac_mode(devices, login, hass): - """Test set HVAC mode.""" - device = devices.return_value[0] - await async_setup_component(hass, DYSON_DOMAIN, _get_config()) - await hass.async_block_till_done() - +@pytest.mark.parametrize("device", [DysonPureHotCool], indirect=True) +async def test_commands_purehotcool( + hass: HomeAssistant, + device: DysonPureHotCoolLink, + service: str, + service_data: dict, + command: str, + command_args: list, +) -> None: + """Test sending commands to a PureHotCool entity.""" await hass.services.async_call( - DOMAIN, - SERVICE_SET_HVAC_MODE, - {ATTR_ENTITY_ID: "climate.bed_room", ATTR_HVAC_MODE: HVAC_MODE_OFF}, - True, + PLATFORM_DOMAIN, + service, + { + ATTR_ENTITY_ID: ENTITY_ID, + **service_data, + }, + blocking=True, ) - device.turn_off.assert_not_called() + getattr(device, command).assert_called_once_with(*command_args) - await hass.services.async_call( - DOMAIN, - SERVICE_SET_HVAC_MODE, - {ATTR_ENTITY_ID: "climate.living_room", ATTR_HVAC_MODE: HVAC_MODE_OFF}, - True, - ) - assert device.turn_off.call_count == 1 +@pytest.mark.parametrize("hvac_mode", [HVAC_MODE_HEAT, HVAC_MODE_COOL]) +@pytest.mark.parametrize( + "fan_power,turn_on_call_count", + [ + (FanPower.POWER_ON.value, 0), + (FanPower.POWER_OFF.value, 1), + ], +) +@pytest.mark.parametrize("device", [DysonPureHotCool], indirect=True) +async def test_set_hvac_mode_purehotcool( + hass: HomeAssistant, + device: DysonPureHotCoolLink, + hvac_mode: str, + fan_power: str, + turn_on_call_count: int, +) -> None: + """Test setting HVAC mode of a PureHotCool entity turns on the device when it's off.""" + device.state.fan_power = fan_power + await async_update_device(hass, device) await hass.services.async_call( - DOMAIN, + PLATFORM_DOMAIN, SERVICE_SET_HVAC_MODE, - {ATTR_ENTITY_ID: "climate.living_room", ATTR_HVAC_MODE: HVAC_MODE_HEAT}, - True, + { + ATTR_ENTITY_ID: ENTITY_ID, + ATTR_HVAC_MODE: hvac_mode, + }, + blocking=True, ) - assert device.turn_on.call_count == 1 - assert device.enable_heat_mode.call_count == 1 - - await hass.services.async_call( - DOMAIN, - SERVICE_SET_HVAC_MODE, - {ATTR_ENTITY_ID: "climate.living_room", ATTR_HVAC_MODE: HVAC_MODE_COOL}, - True, - ) - assert device.turn_on.call_count == 2 - assert device.disable_heat_mode.call_count == 1 + assert device.turn_on.call_count == turn_on_call_count diff --git a/tests/components/dyson/test_sensor.py b/tests/components/dyson/test_sensor.py index 8342705a268..a1f8e4bb37c 100644 --- a/tests/components/dyson/test_sensor.py +++ b/tests/components/dyson/test_sensor.py @@ -25,8 +25,8 @@ from .common import ( ENTITY_NAME, NAME, SERIAL, + async_get_basic_device, async_update_device, - get_basic_device, ) from tests.common import async_setup_component @@ -81,7 +81,7 @@ def _async_assign_values( @callback def async_get_device(spec: Type[DysonPureCoolLink], combi=False) -> DysonPureCoolLink: """Return a device of the given type.""" - device = get_basic_device(spec) + device = async_get_basic_device(spec) _async_assign_values(device, combi=combi) return device