mirror of
https://github.com/home-assistant/core.git
synced 2026-08-28 02:24:46 -05:00
Fix VeSync crash on fan levels outside of defined list (#176234)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
co-authored by
Copilot Autofix powered by AI
parent
4224d8ff27
commit
75adfde436
@@ -163,15 +163,15 @@ class VeSyncFanHA(VeSyncBaseEntity[VeSyncFanBase | VeSyncPurifier], FanEntity):
|
||||
"""Return the currently set speed."""
|
||||
|
||||
current_level = self.device.state.fan_level
|
||||
if (
|
||||
self.device.state.mode in (VS_FAN_MODE_MANUAL, VS_FAN_MODE_NORMAL)
|
||||
and current_level is not None
|
||||
):
|
||||
if self.device.state.mode in (VS_FAN_MODE_MANUAL, VS_FAN_MODE_NORMAL):
|
||||
if current_level == 0:
|
||||
return 0
|
||||
return ordered_list_item_to_percentage(
|
||||
self.device.fan_levels, current_level
|
||||
)
|
||||
# The device can report an out-of-range level (e.g. -1) when the
|
||||
# speed is not applicable; treat it as unknown instead of crashing.
|
||||
if current_level in self.device.fan_levels:
|
||||
return ordered_list_item_to_percentage(
|
||||
self.device.fan_levels, current_level
|
||||
)
|
||||
return None
|
||||
|
||||
@property
|
||||
|
||||
@@ -99,9 +99,15 @@ DEVICE_FIXTURES: dict[str, list[tuple[str, str, str]]] = {
|
||||
|
||||
|
||||
def mock_devices_response(
|
||||
aioclient_mock: AiohttpClientMocker, device_name: str
|
||||
aioclient_mock: AiohttpClientMocker,
|
||||
device_name: str,
|
||||
details_override: dict[str, Any] | None = None,
|
||||
) -> None:
|
||||
"""Build a response for the Helpers.call_api method."""
|
||||
"""Build a response for the Helpers.call_api method.
|
||||
|
||||
``details_override`` is merged into the nested ``result`` payload of the
|
||||
device detail response, allowing tests to simulate specific device states.
|
||||
"""
|
||||
device_list = [
|
||||
device
|
||||
for device in ALL_DEVICES["result"]["list"]
|
||||
@@ -126,9 +132,16 @@ def mock_devices_response(
|
||||
)
|
||||
|
||||
for fixture in DEVICE_FIXTURES[device_name]:
|
||||
detail = load_json_object_fixture(fixture[2], DOMAIN)
|
||||
if details_override:
|
||||
assert "result" in detail.get("result", {}), (
|
||||
f"Fixture {fixture[2]} does not have the expected "
|
||||
"result.result payload to apply details_override"
|
||||
)
|
||||
detail["result"]["result"].update(details_override)
|
||||
getattr(aioclient_mock, fixture[0])(
|
||||
f"https://smartapi.vesync.com{fixture[1]}",
|
||||
json=load_json_object_fixture(fixture[2], DOMAIN),
|
||||
json=detail,
|
||||
)
|
||||
mock_firmware(aioclient_mock)
|
||||
|
||||
|
||||
@@ -6,8 +6,17 @@ from unittest.mock import AsyncMock, patch
|
||||
import pytest
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
|
||||
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.components.fan import (
|
||||
ATTR_PERCENTAGE,
|
||||
ATTR_PRESET_MODE,
|
||||
DOMAIN as FAN_DOMAIN,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID,
|
||||
SERVICE_TURN_OFF,
|
||||
SERVICE_TURN_ON,
|
||||
STATE_UNAVAILABLE,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||
@@ -188,6 +197,26 @@ async def test_set_preset_mode(
|
||||
update_mock.assert_called_once()
|
||||
|
||||
|
||||
async def test_out_of_range_fan_level(
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
aioclient_mock: AiohttpClientMocker,
|
||||
) -> None:
|
||||
"""Test that an out-of-range fan level produces an unknown percentage."""
|
||||
|
||||
mock_devices_response(
|
||||
aioclient_mock, "CoreBreeze 432S", details_override={"fanSpeedLevel": -1}
|
||||
)
|
||||
|
||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(ENTITY_PEDESTAL_FAN)
|
||||
assert state is not None
|
||||
assert state.state != STATE_UNAVAILABLE
|
||||
assert state.attributes[ATTR_PERCENTAGE] is None
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("action", "api_response", "expectation"),
|
||||
[
|
||||
|
||||
Reference in New Issue
Block a user