1
0
mirror of https://github.com/home-assistant/core.git synced 2026-02-15 07:36:16 +00:00

Improve quality scale to silver HDFury integration (#161077)

This commit is contained in:
Glenn de Haan
2026-01-17 16:57:25 +01:00
committed by GitHub
parent 5d43efb22d
commit fa29d8180f
9 changed files with 214 additions and 32 deletions

View File

@@ -19,6 +19,8 @@ from .const import DOMAIN
from .coordinator import HDFuryConfigEntry
from .entity import HDFuryEntity
PARALLEL_UPDATES = 1
@dataclass(kw_only=True, frozen=True)
class HDFuryButtonEntityDescription(ButtonEntityDescription):

View File

@@ -6,6 +6,6 @@
"documentation": "https://www.home-assistant.io/integrations/hdfury",
"integration_type": "device",
"iot_class": "local_polling",
"quality_scale": "bronze",
"quality_scale": "silver",
"requirements": ["hdfury==1.3.1"]
}

View File

@@ -35,11 +35,11 @@ rules:
entity-unavailable: done
integration-owner: done
log-when-unavailable: done
parallel-updates: todo
parallel-updates: done
reauthentication-flow:
status: exempt
comment: Integration has no authentication flow.
test-coverage: todo
test-coverage: done
# Gold
devices: done

View File

@@ -20,6 +20,8 @@ from .const import DOMAIN
from .coordinator import HDFuryConfigEntry, HDFuryCoordinator
from .entity import HDFuryEntity
PARALLEL_UPDATES = 1
@dataclass(kw_only=True, frozen=True)
class HDFurySelectEntityDescription(SelectEntityDescription):
@@ -77,13 +79,11 @@ async def async_setup_entry(
coordinator = entry.runtime_data
entities: list[HDFuryEntity] = []
for description in SELECT_PORTS:
if description.key not in coordinator.data.info:
continue
entities.append(HDFurySelect(coordinator, description))
entities: list[HDFuryEntity] = [
HDFurySelect(coordinator, description)
for description in SELECT_PORTS
if description.key in coordinator.data.info
]
# Add OPMODE select if present
if "opmode" in coordinator.data.info:

View File

@@ -8,6 +8,8 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .coordinator import HDFuryConfigEntry
from .entity import HDFuryEntity
PARALLEL_UPDATES = 0
SENSORS: tuple[SensorEntityDescription, ...] = (
SensorEntityDescription(
key="RX0",

View File

@@ -16,6 +16,8 @@ from .const import DOMAIN
from .coordinator import HDFuryConfigEntry
from .entity import HDFuryEntity
PARALLEL_UPDATES = 1
@dataclass(kw_only=True, frozen=True)
class HDFurySwitchEntityDescription(SwitchEntityDescription):

View File

@@ -6,7 +6,8 @@ from hdfury import HDFuryError
import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.const import Platform
from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN, SERVICE_PRESS
from homeassistant.const import ATTR_ENTITY_ID, Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
import homeassistant.helpers.entity_registry as er
@@ -47,9 +48,9 @@ async def test_button_presses(
await setup_integration(hass, mock_config_entry, [Platform.BUTTON])
await hass.services.async_call(
"button",
"press",
{"entity_id": entity_id},
BUTTON_DOMAIN,
SERVICE_PRESS,
{ATTR_ENTITY_ID: entity_id},
blocking=True,
)
@@ -67,10 +68,13 @@ async def test_button_press_error(
await setup_integration(hass, mock_config_entry, [Platform.BUTTON])
with pytest.raises(HomeAssistantError):
with pytest.raises(
HomeAssistantError,
match="An error occurred while communicating with HDFury device",
):
await hass.services.async_call(
"button",
"press",
{"entity_id": "button.hdfury_vrroom_02_restart"},
BUTTON_DOMAIN,
SERVICE_PRESS,
{ATTR_ENTITY_ID: "button.hdfury_vrroom_02_restart"},
blocking=True,
)

View File

@@ -1,14 +1,25 @@
"""Tests for the HDFury select platform."""
from datetime import timedelta
from unittest.mock import AsyncMock
from freezegun.api import FrozenDateTimeFactory
from hdfury import HDFuryError
import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.const import Platform
from homeassistant.components.select import (
DOMAIN as SELECT_DOMAIN,
SERVICE_SELECT_OPTION,
)
from homeassistant.const import ATTR_ENTITY_ID, ATTR_OPTION, STATE_UNAVAILABLE, Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
import homeassistant.helpers.entity_registry as er
from . import setup_integration
from tests.common import MockConfigEntry, snapshot_platform
from tests.common import MockConfigEntry, async_fire_time_changed, snapshot_platform
async def test_select_entities(
@@ -21,3 +32,133 @@ async def test_select_entities(
await setup_integration(hass, mock_config_entry, [Platform.SELECT])
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
async def test_select_operation_mode(
hass: HomeAssistant,
mock_hdfury_client: AsyncMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test selecting operation mode."""
await setup_integration(hass, mock_config_entry, [Platform.SELECT])
await hass.services.async_call(
SELECT_DOMAIN,
SERVICE_SELECT_OPTION,
{
ATTR_ENTITY_ID: "select.hdfury_vrroom_02_operation_mode",
ATTR_OPTION: "1",
},
blocking=True,
)
mock_hdfury_client.set_operation_mode.assert_awaited_once_with("1")
@pytest.mark.parametrize(
("entity_id"),
[
("select.hdfury_vrroom_02_port_select_tx0"),
("select.hdfury_vrroom_02_port_select_tx1"),
],
)
async def test_select_tx_ports(
hass: HomeAssistant,
mock_hdfury_client: AsyncMock,
mock_config_entry: MockConfigEntry,
entity_id: str,
) -> None:
"""Test selecting TX ports."""
await setup_integration(hass, mock_config_entry, [Platform.SELECT])
await hass.services.async_call(
SELECT_DOMAIN,
SERVICE_SELECT_OPTION,
{
ATTR_ENTITY_ID: entity_id,
ATTR_OPTION: "1",
},
blocking=True,
)
mock_hdfury_client.set_port_selection.assert_awaited()
async def test_select_operation_mode_error(
hass: HomeAssistant,
mock_hdfury_client: AsyncMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test operation mode select raises HomeAssistantError."""
mock_hdfury_client.set_operation_mode.side_effect = HDFuryError()
await setup_integration(hass, mock_config_entry, [Platform.SELECT])
with pytest.raises(
HomeAssistantError,
match="An error occurred while communicating with HDFury device",
):
await hass.services.async_call(
SELECT_DOMAIN,
SERVICE_SELECT_OPTION,
{
ATTR_ENTITY_ID: "select.hdfury_vrroom_02_operation_mode",
ATTR_OPTION: "1",
},
blocking=True,
)
async def test_select_ports_missing_state(
hass: HomeAssistant,
mock_hdfury_client: AsyncMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test TX port selection fails when TX state is incomplete."""
mock_hdfury_client.get_info.return_value = {
"portseltx0": "0",
"portseltx1": None,
"opmode": "0",
}
await setup_integration(hass, mock_config_entry, [Platform.SELECT])
with pytest.raises(
HomeAssistantError,
match="An error occurred while validating TX states",
):
await hass.services.async_call(
SELECT_DOMAIN,
SERVICE_SELECT_OPTION,
{
ATTR_ENTITY_ID: "select.hdfury_vrroom_02_port_select_tx0",
ATTR_OPTION: "0",
},
blocking=True,
)
async def test_select_entities_unavailable_on_error(
hass: HomeAssistant,
mock_hdfury_client: AsyncMock,
mock_config_entry: MockConfigEntry,
freezer: FrozenDateTimeFactory,
) -> None:
"""Test API error causes entities to become unavailable."""
await setup_integration(hass, mock_config_entry, [Platform.SELECT])
mock_hdfury_client.get_info.side_effect = HDFuryError()
freezer.tick(timedelta(seconds=61))
async_fire_time_changed(hass)
await hass.async_block_till_done()
assert (
hass.states.get("select.hdfury_vrroom_02_port_select_tx0").state
== STATE_UNAVAILABLE
)

View File

@@ -1,19 +1,28 @@
"""Tests for the HDFury switch platform."""
from datetime import timedelta
from unittest.mock import AsyncMock
from freezegun.api import FrozenDateTimeFactory
from hdfury import HDFuryError
import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.const import Platform
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
from homeassistant.const import (
ATTR_ENTITY_ID,
SERVICE_TURN_OFF,
SERVICE_TURN_ON,
STATE_UNAVAILABLE,
Platform,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
import homeassistant.helpers.entity_registry as er
from . import setup_integration
from tests.common import MockConfigEntry, snapshot_platform
from tests.common import MockConfigEntry, async_fire_time_changed, snapshot_platform
async def test_switch_entities(
@@ -34,15 +43,15 @@ async def test_switch_entities(
(
"switch.hdfury_vrroom_02_auto_switch_inputs",
"set_auto_switch_inputs",
"turn_on",
SERVICE_TURN_ON,
),
(
"switch.hdfury_vrroom_02_auto_switch_inputs",
"set_auto_switch_inputs",
"turn_off",
SERVICE_TURN_OFF,
),
("switch.hdfury_vrroom_02_oled_display", "set_oled", "turn_on"),
("switch.hdfury_vrroom_02_oled_display", "set_oled", "turn_off"),
("switch.hdfury_vrroom_02_oled_display", "set_oled", SERVICE_TURN_ON),
("switch.hdfury_vrroom_02_oled_display", "set_oled", SERVICE_TURN_OFF),
],
)
async def test_switch_turn_on_off(
@@ -58,9 +67,9 @@ async def test_switch_turn_on_off(
await setup_integration(hass, mock_config_entry, [Platform.SWITCH])
await hass.services.async_call(
"switch",
SWITCH_DOMAIN,
service,
{"entity_id": entity_id},
{ATTR_ENTITY_ID: entity_id},
blocking=True,
)
@@ -70,8 +79,8 @@ async def test_switch_turn_on_off(
@pytest.mark.parametrize(
("service", "method"),
[
("turn_on", "set_auto_switch_inputs"),
("turn_off", "set_auto_switch_inputs"),
(SERVICE_TURN_ON, "set_auto_switch_inputs"),
(SERVICE_TURN_OFF, "set_auto_switch_inputs"),
],
)
async def test_switch_turn_error(
@@ -92,8 +101,30 @@ async def test_switch_turn_error(
match="An error occurred while communicating with HDFury device",
):
await hass.services.async_call(
"switch",
SWITCH_DOMAIN,
service,
{"entity_id": "switch.hdfury_vrroom_02_auto_switch_inputs"},
{ATTR_ENTITY_ID: "switch.hdfury_vrroom_02_auto_switch_inputs"},
blocking=True,
)
async def test_switch_entities_unavailable_on_error(
hass: HomeAssistant,
mock_hdfury_client: AsyncMock,
mock_config_entry: MockConfigEntry,
freezer: FrozenDateTimeFactory,
) -> None:
"""Test API error causes entities to become unavailable."""
await setup_integration(hass, mock_config_entry, [Platform.SWITCH])
mock_hdfury_client.get_info.side_effect = HDFuryError()
freezer.tick(timedelta(seconds=61))
async_fire_time_changed(hass)
await hass.async_block_till_done()
assert (
hass.states.get("switch.hdfury_vrroom_02_auto_switch_inputs").state
== STATE_UNAVAILABLE
)