Refactor fan in vesync (#135744)

* Refactor Fan

* Add tower fan tests and mode

* Schedule update after turn off

* Adjust updates to refresh library

* correct off command

* Revert changes

* Merge corrections

* Remove unused code to increase test coverage

* Ruff

* Tests

* Test for preset mode

* Adjust to increase coverage

* Test Corrections

* tests to match other PR

---------

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
This commit is contained in:
cdnninja
2025-05-17 15:57:55 +02:00
committed by GitHub
co-authored by Joost Lekkerkerker
parent 4c40ec4948
commit 2dc63eb8c5
7 changed files with 244 additions and 84 deletions
+2
View File
@@ -15,6 +15,8 @@ ENTITY_HUMIDIFIER_MIST_LEVEL = "number.humidifier_200s_mist_level"
ENTITY_HUMIDIFIER_HUMIDITY = "sensor.humidifier_200s_humidity"
ENTITY_HUMIDIFIER_300S_NIGHT_LIGHT_SELECT = "select.humidifier_300s_night_light_level"
ENTITY_FAN = "fan.SmartTowerFan"
ENTITY_SWITCH_DISPLAY = "switch.humidifier_200s_display"
ALL_DEVICES = load_json_object_fixture("vesync-devices.json", DOMAIN)
+20
View File
@@ -198,6 +198,26 @@ async def install_humidifier_device(
await hass.async_block_till_done()
@pytest.fixture(name="fan_config_entry")
async def fan_config_entry(
hass: HomeAssistant, requests_mock: requests_mock.Mocker, config
) -> MockConfigEntry:
"""Create a mock VeSync config entry for `SmartTowerFan`."""
entry = MockConfigEntry(
title="VeSync",
domain=DOMAIN,
data=config[DOMAIN],
)
entry.add_to_hass(hass)
device_name = "SmartTowerFan"
mock_multiple_device_responses(requests_mock, [device_name])
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
return entry
@pytest.fixture(name="switch_old_id_config_entry")
async def switch_old_id_config_entry(
hass: HomeAssistant, requests_mock: requests_mock.Mocker, config
@@ -640,8 +640,8 @@
'preset_modes': list([
'advancedSleep',
'auto',
'turbo',
'normal',
'turbo',
]),
}),
'config_entry_id': <ANY>,
@@ -682,12 +682,12 @@
'night_light': 'off',
'percentage': None,
'percentage_step': 7.6923076923076925,
'preset_mode': None,
'preset_mode': 'normal',
'preset_modes': list([
'advancedSleep',
'auto',
'turbo',
'normal',
'turbo',
]),
'screen_status': False,
'supported_features': <FanEntityFeature: 57>,
+111 -2
View File
@@ -1,17 +1,24 @@
"""Tests for the fan module."""
from contextlib import nullcontext
from unittest.mock import patch
import pytest
import requests_mock
from syrupy import SnapshotAssertion
from homeassistant.components.fan import DOMAIN as FAN_DOMAIN
from homeassistant.components.fan import ATTR_PRESET_MODE, DOMAIN as FAN_DOMAIN
from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import device_registry as dr, entity_registry as er
from .common import ALL_DEVICE_NAMES, mock_devices_response
from .common import ALL_DEVICE_NAMES, ENTITY_FAN, mock_devices_response
from tests.common import MockConfigEntry
NoException = nullcontext()
@pytest.mark.parametrize("device_name", ALL_DEVICE_NAMES)
async def test_fan_state(
@@ -49,3 +56,105 @@ async def test_fan_state(
# Check states
for entity in entities:
assert hass.states.get(entity.entity_id) == snapshot(name=entity.entity_id)
@pytest.mark.parametrize(
("action", "command"),
[
(SERVICE_TURN_ON, "pyvesync.vesyncfan.VeSyncTowerFan.turn_on"),
(SERVICE_TURN_OFF, "pyvesync.vesyncfan.VeSyncTowerFan.turn_off"),
],
)
async def test_turn_on_off_success(
hass: HomeAssistant,
fan_config_entry: MockConfigEntry,
action: str,
command: str,
) -> None:
"""Test turn_on and turn_off method."""
with (
patch(command, return_value=True) as method_mock,
):
with patch(
"homeassistant.components.vesync.fan.VeSyncFanHA.schedule_update_ha_state"
) as update_mock:
await hass.services.async_call(
FAN_DOMAIN,
action,
{ATTR_ENTITY_ID: ENTITY_FAN},
blocking=True,
)
await hass.async_block_till_done()
method_mock.assert_called_once()
update_mock.assert_called_once()
@pytest.mark.parametrize(
("action", "command"),
[
(SERVICE_TURN_ON, "pyvesync.vesyncfan.VeSyncTowerFan.turn_on"),
(SERVICE_TURN_OFF, "pyvesync.vesyncfan.VeSyncTowerFan.turn_off"),
],
)
async def test_turn_on_off_raises_error(
hass: HomeAssistant,
fan_config_entry: MockConfigEntry,
action: str,
command: str,
) -> None:
"""Test turn_on and turn_off raises errors when fails."""
# returns False indicating failure in which case raises HomeAssistantError.
with (
patch(
command,
return_value=False,
) as method_mock,
pytest.raises(HomeAssistantError),
):
await hass.services.async_call(
FAN_DOMAIN,
action,
{ATTR_ENTITY_ID: ENTITY_FAN},
blocking=True,
)
await hass.async_block_till_done()
method_mock.assert_called_once()
@pytest.mark.parametrize(
("api_response", "expectation"),
[(True, NoException), (False, pytest.raises(HomeAssistantError))],
)
async def test_set_preset_mode(
hass: HomeAssistant,
fan_config_entry: MockConfigEntry,
api_response: bool,
expectation,
) -> None:
"""Test handling of value in set_preset_mode method. Does this via turn on as it increases test coverage."""
# If VeSyncTowerFan.normal_mode fails (returns False), then HomeAssistantError is raised
with (
expectation,
patch(
"pyvesync.vesyncfan.VeSyncTowerFan.normal_mode",
return_value=api_response,
) as method_mock,
):
with patch(
"homeassistant.components.vesync.fan.VeSyncFanHA.schedule_update_ha_state"
) as update_mock:
await hass.services.async_call(
FAN_DOMAIN,
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: ENTITY_FAN, ATTR_PRESET_MODE: "normal"},
blocking=True,
)
await hass.async_block_till_done()
method_mock.assert_called_once()
update_mock.assert_called_once()