Add Homee general tests (#137128)

This commit is contained in:
Markus Adrario 2025-07-06 12:05:43 +02:00 committed by GitHub
parent 8d7e387b46
commit 1b11ac9123
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 427 additions and 1 deletions

View File

@ -96,6 +96,55 @@
"options": {
"automations": ["step"]
}
},
{
"id": 4,
"node_id": 3,
"instance": 0,
"minimum": -50,
"maximum": 125,
"current_value": 20.3,
"target_value": 20.3,
"last_value": 20.3,
"unit": "°C",
"step_value": 1.0,
"editable": 0,
"type": 5,
"state": 1,
"last_changed": 1709982925,
"changed_by": 1,
"changed_by_id": 0,
"based_on": 1,
"data": "",
"name": "",
"options": {
"history": {
"day": 1,
"week": 26,
"month": 6
}
}
},
{
"id": 5,
"node_id": 3,
"instance": 0,
"minimum": 0,
"maximum": 0,
"current_value": 0.0,
"target_value": 0.0,
"last_value": 0.0,
"unit": "text",
"step_value": 1.0,
"editable": 0,
"type": 44,
"state": 1,
"last_changed": 0,
"changed_by": 1,
"changed_by_id": 0,
"based_on": 1,
"data": "4.54",
"name": ""
}
]
}

View File

@ -43,6 +43,27 @@
"observes": [75],
"automations": ["toggle"]
}
},
{
"id": 2,
"node_id": 3,
"instance": 0,
"minimum": 0,
"maximum": 0,
"current_value": 0.0,
"target_value": 0.0,
"last_value": 0.0,
"unit": "text",
"step_value": 1.0,
"editable": 0,
"type": 45,
"state": 1,
"last_changed": 0,
"changed_by": 1,
"changed_by_id": 0,
"based_on": 1,
"data": "1.45",
"name": ""
}
]
}

View File

@ -689,6 +689,55 @@
'type': 113,
'unit': '°',
}),
dict({
'based_on': 1,
'changed_by': 1,
'changed_by_id': 0,
'current_value': 20.3,
'data': '',
'editable': 0,
'id': 4,
'instance': 0,
'last_changed': 1709982925,
'last_value': 20.3,
'maximum': 125,
'minimum': -50,
'name': '',
'node_id': 3,
'options': dict({
'history': dict({
'day': 1,
'month': 6,
'week': 26,
}),
}),
'state': 1,
'step_value': 1.0,
'target_value': 20.3,
'type': 5,
'unit': '°C',
}),
dict({
'based_on': 1,
'changed_by': 1,
'changed_by_id': 0,
'current_value': 0.0,
'data': '4.54',
'editable': 0,
'id': 5,
'instance': 0,
'last_changed': 0,
'last_value': 0.0,
'maximum': 0,
'minimum': 0,
'name': '',
'node_id': 3,
'state': 1,
'step_value': 1.0,
'target_value': 0.0,
'type': 44,
'unit': 'text',
}),
]),
'cube_type': 14,
'favorite': 0,

View File

@ -0,0 +1,71 @@
# serializer version: 1
# name: test_general_data
DeviceRegistryEntrySnapshot({
'area_id': None,
'config_entries': <ANY>,
'config_entries_subentries': <ANY>,
'configuration_url': None,
'connections': set({
tuple(
'mac',
'00:05:55:11:ee:cc',
),
}),
'disabled_by': None,
'entry_type': None,
'hw_version': None,
'id': <ANY>,
'identifiers': set({
tuple(
'homee',
'00055511EECC',
),
}),
'is_new': False,
'labels': set({
}),
'manufacturer': 'homee',
'model': 'homee',
'model_id': None,
'name': 'TestHomee',
'name_by_user': None,
'primary_config_entry': <ANY>,
'serial_number': None,
'suggested_area': None,
'sw_version': '1.2.3',
'via_device_id': None,
})
# ---
# name: test_general_data.1
DeviceRegistryEntrySnapshot({
'area_id': None,
'config_entries': <ANY>,
'config_entries_subentries': <ANY>,
'configuration_url': None,
'connections': set({
}),
'disabled_by': None,
'entry_type': None,
'hw_version': None,
'id': <ANY>,
'identifiers': set({
tuple(
'homee',
'00055511EECC-3',
),
}),
'is_new': False,
'labels': set({
}),
'manufacturer': None,
'model': 'shutter_position_switch',
'model_id': None,
'name': 'Test Cover',
'name_by_user': None,
'primary_config_entry': <ANY>,
'serial_number': None,
'suggested_area': None,
'sw_version': '4.54',
'via_device_id': <ANY>,
})
# ---

View File

@ -13,6 +13,10 @@ from homeassistant.components.cover import (
CoverEntityFeature,
CoverState,
)
from homeassistant.components.homeassistant import (
DOMAIN as HA_DOMAIN,
SERVICE_UPDATE_ENTITY,
)
from homeassistant.components.homee.const import DOMAIN
from homeassistant.const import (
ATTR_ENTITY_ID,
@ -23,9 +27,11 @@ from homeassistant.const import (
SERVICE_SET_COVER_POSITION,
SERVICE_SET_COVER_TILT_POSITION,
SERVICE_STOP_COVER,
STATE_UNAVAILABLE,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.setup import async_setup_component
from . import build_mock_node, setup_integration
@ -39,6 +45,7 @@ async def test_open_close_stop_cover(
) -> None:
"""Test opening the cover."""
mock_homee.nodes = [build_mock_node("cover_with_position_slats.json")]
mock_homee.get_node_by_id.return_value = mock_homee.nodes[0]
await setup_integration(hass, mock_config_entry)
@ -73,6 +80,7 @@ async def test_open_close_reverse_cover(
) -> None:
"""Test opening the cover."""
mock_homee.nodes = [build_mock_node("cover_with_position_slats.json")]
mock_homee.get_node_by_id.return_value = mock_homee.nodes[0]
mock_homee.nodes[0].attributes[0].is_reversed = True
await setup_integration(hass, mock_config_entry)
@ -102,6 +110,7 @@ async def test_set_cover_position(
) -> None:
"""Test setting the cover position."""
mock_homee.nodes = [build_mock_node("cover_with_position_slats.json")]
mock_homee.get_node_by_id.return_value = mock_homee.nodes[0]
await setup_integration(hass, mock_config_entry)
@ -246,6 +255,7 @@ async def test_cover_positions(
# Cover open, tilt open.
# mock_homee.nodes = [cover]
mock_homee.nodes = [build_mock_node("cover_with_position_slats.json")]
mock_homee.get_node_by_id.return_value = mock_homee.nodes[0]
cover = mock_homee.nodes[0]
await setup_integration(hass, mock_config_entry)
@ -348,3 +358,50 @@ async def test_send_error(
assert exc_info.value.translation_domain == DOMAIN
assert exc_info.value.translation_key == "connection_closed"
async def test_node_entity_connection_listener(
hass: HomeAssistant,
mock_homee: MagicMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test if loss of connection is sensed correctly."""
mock_homee.nodes = [build_mock_node("cover_with_position_slats.json")]
mock_homee.get_node_by_id.return_value = mock_homee.nodes[0]
await setup_integration(hass, mock_config_entry)
states = hass.states.get("cover.test_cover")
assert states.state != STATE_UNAVAILABLE
await mock_homee.add_connection_listener.call_args_list[1][0][0](False)
await hass.async_block_till_done()
states = hass.states.get("cover.test_cover")
assert states.state == STATE_UNAVAILABLE
await mock_homee.add_connection_listener.call_args_list[1][0][0](True)
await hass.async_block_till_done()
states = hass.states.get("cover.test_cover")
assert states.state != STATE_UNAVAILABLE
async def test_node_entity_update_action(
hass: HomeAssistant,
mock_homee: MagicMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test the update_entity action for a HomeeEntity."""
mock_homee.nodes = [build_mock_node("cover_with_position_slats.json")]
mock_homee.get_node_by_id.return_value = mock_homee.nodes[0]
await setup_integration(hass, mock_config_entry)
await async_setup_component(hass, HA_DOMAIN, {})
await hass.services.async_call(
HA_DOMAIN,
SERVICE_UPDATE_ENTITY,
{ATTR_ENTITY_ID: "cover.test_cover"},
blocking=True,
)
mock_homee.update_node.assert_called_once_with(3)

View File

@ -0,0 +1,131 @@
"""Test Homee initialization."""
from unittest.mock import MagicMock
from pyHomee import HomeeAuthFailedException, HomeeConnectionFailedException
import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.components.homee.const import DOMAIN
from homeassistant.config_entries import ConfigEntryState
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr
from . import build_mock_node, setup_integration
from .conftest import HOMEE_ID
from tests.common import MockConfigEntry
@pytest.mark.parametrize(
"side_eff",
[
HomeeConnectionFailedException("connection timed out"),
HomeeAuthFailedException("wrong username or password"),
],
)
async def test_connection_errors(
hass: HomeAssistant,
mock_homee: MagicMock,
mock_config_entry: MockConfigEntry,
side_eff: Exception,
) -> None:
"""Test if connection errors on startup are handled correctly."""
mock_homee.get_access_token.side_effect = side_eff
mock_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_config_entry.entry_id)
assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY
async def test_connection_listener(
hass: HomeAssistant,
mock_homee: MagicMock,
mock_config_entry: MockConfigEntry,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test if loss of connection is sensed correctly."""
mock_homee.nodes = [build_mock_node("homee.json")]
mock_homee.get_node_by_id.return_value = mock_homee.nodes[0]
await setup_integration(hass, mock_config_entry)
await mock_homee.add_connection_listener.call_args_list[0][0][0](False)
await hass.async_block_till_done()
assert "Disconnected from Homee" in caplog.text
await mock_homee.add_connection_listener.call_args_list[0][0][0](True)
await hass.async_block_till_done()
assert "Reconnected to Homee" in caplog.text
async def test_general_data(
hass: HomeAssistant,
mock_homee: MagicMock,
mock_config_entry: MockConfigEntry,
device_registry: dr.DeviceRegistry,
snapshot: SnapshotAssertion,
) -> None:
"""Test if data is set correctly."""
mock_homee.nodes = [
build_mock_node("cover_with_position_slats.json"),
build_mock_node("homee.json"),
]
mock_homee.get_node_by_id = (
lambda node_id: mock_homee.nodes[0] if node_id == 3 else mock_homee.nodes[1]
)
await setup_integration(hass, mock_config_entry)
# Verify hub and device created correctly using snapshots.
hub = device_registry.async_get_device(identifiers={(DOMAIN, f"{HOMEE_ID}")})
device = device_registry.async_get_device(identifiers={(DOMAIN, f"{HOMEE_ID}-3")})
assert hub == snapshot
assert device == snapshot
async def test_software_version(
hass: HomeAssistant,
mock_homee: MagicMock,
mock_config_entry: MockConfigEntry,
device_registry: dr.DeviceRegistry,
) -> None:
"""Test sw_version for device with only AttributeType.SOFTWARE_VERSION."""
mock_homee.nodes = [build_mock_node("cover_without_position.json")]
await setup_integration(hass, mock_config_entry)
device = device_registry.async_get_device(identifiers={(DOMAIN, f"{HOMEE_ID}-3")})
assert device.sw_version == "1.45"
async def test_invalid_profile(
hass: HomeAssistant,
mock_homee: MagicMock,
mock_config_entry: MockConfigEntry,
device_registry: dr.DeviceRegistry,
) -> None:
"""Test unknown value passed to get_name_for_enum."""
mock_homee.nodes = [build_mock_node("cover_without_position.json")]
# This is a profile, that does not exist in the enum.
mock_homee.nodes[0].profile = 77
await setup_integration(hass, mock_config_entry)
device = device_registry.async_get_device(identifiers={(DOMAIN, f"{HOMEE_ID}-3")})
assert device.model is None
async def test_unload_entry(
hass: HomeAssistant,
mock_homee: MagicMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test unloading of config entry."""
mock_homee.nodes = [build_mock_node("cover_with_position_slats.json")]
mock_homee.get_node_by_id.return_value = mock_homee.nodes[0]
await setup_integration(hass, mock_config_entry)
assert mock_config_entry.state is ConfigEntryState.LOADED
await hass.config_entries.async_unload(mock_config_entry.entry_id)
await hass.async_block_till_done()
assert mock_config_entry.state is ConfigEntryState.NOT_LOADED

View File

@ -5,6 +5,10 @@ from unittest.mock import MagicMock, patch
import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.components.homeassistant import (
DOMAIN as HA_DOMAIN,
SERVICE_UPDATE_ENTITY,
)
from homeassistant.components.homee.const import (
DOMAIN,
OPEN_CLOSE_MAP,
@ -13,9 +17,10 @@ from homeassistant.components.homee.const import (
WINDOW_MAP_REVERSED,
)
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
from homeassistant.const import Platform
from homeassistant.const import ATTR_ENTITY_ID, STATE_UNAVAILABLE, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er, issue_registry as ir
from homeassistant.setup import async_setup_component
from . import async_update_attribute_value, build_mock_node, setup_integration
from .conftest import HOMEE_ID
@ -168,6 +173,49 @@ async def test_sensor_deprecation_unused_entity(
)
async def test_entity_connection_listener(
hass: HomeAssistant,
mock_homee: MagicMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test if loss of connection is sensed correctly."""
await setup_sensor(hass, mock_homee, mock_config_entry)
states = hass.states.get("sensor.test_multisensor_energy_1")
assert states.state is not STATE_UNAVAILABLE
await mock_homee.add_connection_listener.call_args_list[2][0][0](False)
await hass.async_block_till_done()
states = hass.states.get("sensor.test_multisensor_energy_1")
assert states.state is STATE_UNAVAILABLE
await mock_homee.add_connection_listener.call_args_list[2][0][0](True)
await hass.async_block_till_done()
states = hass.states.get("sensor.test_multisensor_energy_1")
assert states.state is not STATE_UNAVAILABLE
async def test_entity_update_action(
hass: HomeAssistant,
mock_homee: MagicMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test the update_entity action for a HomeeEntity."""
await setup_sensor(hass, mock_homee, mock_config_entry)
await async_setup_component(hass, HA_DOMAIN, {})
await hass.services.async_call(
HA_DOMAIN,
SERVICE_UPDATE_ENTITY,
{ATTR_ENTITY_ID: "sensor.test_multisensor_temperature"},
blocking=True,
)
mock_homee.update_attribute.assert_called_once_with(1, 23)
async def test_sensor_snapshot(
hass: HomeAssistant,
mock_homee: MagicMock,