Add HDFury integration (#159996)

This commit is contained in:
Glenn de Haan
2026-01-08 12:21:04 +01:00
committed by GitHub
parent a1edf0a77c
commit 341c441e61
20 changed files with 903 additions and 0 deletions
+13
View File
@@ -0,0 +1,13 @@
"""Tests for the HDFury integration."""
from homeassistant.core import HomeAssistant
from tests.common import MockConfigEntry
async def setup_integration(hass: HomeAssistant, config_entry: MockConfigEntry) -> None:
"""Fixture for setting up the integration."""
config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
+79
View File
@@ -0,0 +1,79 @@
"""Common fixtures for the HDFury tests."""
from collections.abc import Generator
from unittest.mock import AsyncMock, patch
import pytest
from homeassistant.components.hdfury.const import DOMAIN
from homeassistant.const import CONF_HOST
from tests.common import MockConfigEntry
TEST_HOST = "192.168.1.123"
@pytest.fixture
def mock_setup_entry() -> Generator[AsyncMock]:
"""Override async_setup_entry."""
with patch(
"homeassistant.components.hdfury.async_setup_entry",
return_value=True,
) as mock_setup_entry:
yield mock_setup_entry
@pytest.fixture
def mock_config_entry() -> MockConfigEntry:
"""Return the default mocked config entry."""
return MockConfigEntry(
domain=DOMAIN,
unique_id="000123456789",
data={
CONF_HOST: TEST_HOST,
},
)
@pytest.fixture(autouse=True)
def mock_hdfury_client() -> Generator[AsyncMock]:
"""Mock a HDFury client."""
with (
patch(
"homeassistant.components.hdfury.config_flow.HDFuryAPI",
autospec=True,
) as mock_cf_client,
patch(
"homeassistant.components.hdfury.coordinator.HDFuryAPI",
autospec=True,
) as mock_coord_client,
):
# Config flow client
cf_client = mock_cf_client.return_value
cf_client.get_board = AsyncMock(
return_value={
"hostname": "VRROOM-02",
"ipaddress": "192.168.1.123",
"serial": "000123456789",
"pcbv": "3",
"version": "FW: 0.61",
}
)
# Coordinator client
coord_client = mock_coord_client.return_value
coord_client.get_board = cf_client.get_board
coord_client.get_info = AsyncMock(
return_value={
"portseltx0": "0",
"portseltx1": "4",
"opmode": "0",
}
)
coord_client.get_config = AsyncMock(
return_value={
"macaddr": "c7:1c:df:9d:f6:40",
}
)
yield coord_client
@@ -0,0 +1,192 @@
# serializer version: 1
# name: test_select_entities[select.hdfury_vrroom_02_operation_mode-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'options': list([
'0',
'1',
'2',
'3',
'4',
'5',
]),
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'select',
'entity_category': None,
'entity_id': 'select.hdfury_vrroom_02_operation_mode',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Operation mode',
'platform': 'hdfury',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'opmode',
'unique_id': '000123456789_opmode',
'unit_of_measurement': None,
})
# ---
# name: test_select_entities[select.hdfury_vrroom_02_operation_mode-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'HDFury VRROOM-02 Operation mode',
'options': list([
'0',
'1',
'2',
'3',
'4',
'5',
]),
}),
'context': <ANY>,
'entity_id': 'select.hdfury_vrroom_02_operation_mode',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '0',
})
# ---
# name: test_select_entities[select.hdfury_vrroom_02_port_select_tx0-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'options': list([
'0',
'1',
'2',
'3',
'4',
]),
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'select',
'entity_category': None,
'entity_id': 'select.hdfury_vrroom_02_port_select_tx0',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Port select TX0',
'platform': 'hdfury',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'portseltx0',
'unique_id': '000123456789_portseltx0',
'unit_of_measurement': None,
})
# ---
# name: test_select_entities[select.hdfury_vrroom_02_port_select_tx0-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'HDFury VRROOM-02 Port select TX0',
'options': list([
'0',
'1',
'2',
'3',
'4',
]),
}),
'context': <ANY>,
'entity_id': 'select.hdfury_vrroom_02_port_select_tx0',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '0',
})
# ---
# name: test_select_entities[select.hdfury_vrroom_02_port_select_tx1-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'options': list([
'0',
'1',
'2',
'3',
'4',
]),
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'select',
'entity_category': None,
'entity_id': 'select.hdfury_vrroom_02_port_select_tx1',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Port select TX1',
'platform': 'hdfury',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'portseltx1',
'unique_id': '000123456789_portseltx1',
'unit_of_measurement': None,
})
# ---
# name: test_select_entities[select.hdfury_vrroom_02_port_select_tx1-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'HDFury VRROOM-02 Port select TX1',
'options': list([
'0',
'1',
'2',
'3',
'4',
]),
}),
'context': <ANY>,
'entity_id': 'select.hdfury_vrroom_02_port_select_tx1',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '4',
})
# ---
@@ -0,0 +1,96 @@
"""Test the HDFury config flow."""
from unittest.mock import AsyncMock
from hdfury import HDFuryError
from homeassistant.components.hdfury.const import DOMAIN
from homeassistant.config_entries import SOURCE_USER
from homeassistant.const import CONF_HOST
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType
from tests.common import MockConfigEntry
async def test_async_step_user_gets_form_and_creates_entry(
hass: HomeAssistant,
mock_hdfury_client: AsyncMock,
mock_setup_entry: AsyncMock,
) -> None:
"""Test that the we can view the form and that the config flow creates an entry."""
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_USER},
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "user"
assert result["errors"] == {}
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={CONF_HOST: "192.168.1.123"},
)
assert result["type"] is FlowResultType.CREATE_ENTRY
assert result["data"] == {
CONF_HOST: "192.168.1.123",
}
assert result["result"].unique_id == "000123456789"
async def test_abort_if_already_configured(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_setup_entry: AsyncMock,
) -> None:
"""Test that we abort if we attempt to submit the same entry twice."""
mock_config_entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_USER},
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "user"
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={CONF_HOST: "192.168.1.123"},
)
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "already_configured"
async def test_successful_recovery_after_connection_error(
hass: HomeAssistant,
mock_hdfury_client: AsyncMock,
mock_setup_entry: AsyncMock,
) -> None:
"""Test error shown when connection fails."""
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_USER},
)
# Simulate a connection error by raising a HDFuryError
mock_hdfury_client.get_board.side_effect = HDFuryError()
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={CONF_HOST: "192.168.1.123"},
)
assert result["type"] is FlowResultType.FORM
assert result["errors"] == {"base": "cannot_connect"}
# Simulate successful connection on retry
mock_hdfury_client.get_board.side_effect = None
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={CONF_HOST: "192.168.1.123"},
)
assert result["type"] is FlowResultType.CREATE_ENTRY
assert result["data"] == {
CONF_HOST: "192.168.1.123",
}
assert result["result"].unique_id == "000123456789"
+30
View File
@@ -0,0 +1,30 @@
"""Tests for the HDFury select platform."""
import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
import homeassistant.helpers.entity_registry as er
from . import setup_integration
from tests.common import MockConfigEntry, snapshot_platform
@pytest.fixture
def platforms() -> list[Platform]:
"""Fixture to specify platforms to test."""
return [Platform.SELECT]
async def test_select_entities(
hass: HomeAssistant,
snapshot: SnapshotAssertion,
entity_registry: er.EntityRegistry,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test HDFury select entities."""
await setup_integration(hass, mock_config_entry)
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)