Add more Homee cover tests (#136568)

This commit is contained in:
Markus Adrario 2025-01-31 11:46:12 +01:00 committed by GitHub
parent 230e101ee4
commit e578327054
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 358 additions and 371 deletions

View File

@ -1,8 +1,14 @@
"""Tests for the homee component."""
from typing import Any
from unittest.mock import AsyncMock
from pyHomee.model import HomeeAttribute, HomeeNode
from homeassistant.components.homee.const import DOMAIN
from homeassistant.core import HomeAssistant
from tests.common import MockConfigEntry
from tests.common import MockConfigEntry, load_json_object_fixture
async def setup_integration(hass: HomeAssistant, config_entry: MockConfigEntry) -> None:
@ -11,3 +17,35 @@ async def setup_integration(hass: HomeAssistant, config_entry: MockConfigEntry)
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
def build_mock_node(file: str) -> AsyncMock:
"""Build a mocked Homee node from a json representation."""
json_node = load_json_object_fixture(file, DOMAIN)
mock_node = AsyncMock(spec=HomeeNode)
def get_attributes(attributes: list[Any]) -> list[AsyncMock]:
mock_attributes: list[AsyncMock] = []
for attribute in attributes:
att = AsyncMock(spec=HomeeAttribute)
for key, value in attribute.items():
setattr(att, key, value)
att.is_reversed = False
att.get_value = (
lambda att=att: att.data if att.unit == "text" else att.current_value
)
mock_attributes.append(att)
return mock_attributes
for key, value in json_node.items():
if key != "attributes":
setattr(mock_node, key, value)
mock_node.attributes = get_attributes(json_node["attributes"])
def attribute_by_type(type, instance=0) -> HomeeAttribute | None:
return {attr.type: attr for attr in mock_node.attributes}.get(type)
mock_node.get_attribute_by_type = attribute_by_type
return mock_node

View File

@ -61,6 +61,8 @@ def mock_homee() -> Generator[AsyncMock]:
homee.settings = MagicMock()
homee.settings.uid = HOMEE_ID
homee.settings.homee_name = HOMEE_NAME
homee.settings.version = "1.2.3"
homee.settings.mac_address = "00:05:55:11:ee:cc"
homee.reconnect_interval = 10
homee.connected = True

View File

@ -1,101 +0,0 @@
{
"id": 3,
"name": "Test%20Cover",
"profile": 2002,
"image": "default",
"favorite": 0,
"order": 4,
"protocol": 23,
"routing": 0,
"state": 1,
"state_changed": 1687175681,
"added": 1672086680,
"history": 1,
"cube_type": 14,
"note": "TestCoverDevice",
"services": 7,
"phonetic_name": "",
"owner": 2,
"security": 0,
"attributes": [
{
"id": 1,
"node_id": 3,
"instance": 0,
"minimum": 0,
"maximum": 4,
"current_value": 3.0,
"target_value": 0.0,
"last_value": 1.0,
"unit": "n%2Fa",
"step_value": 1.0,
"editable": 1,
"type": 135,
"state": 1,
"last_changed": 1687175680,
"changed_by": 1,
"changed_by_id": 0,
"based_on": 1,
"data": "",
"name": "",
"options": {
"can_observe": [300],
"observes": [75],
"automations": ["toggle"]
}
},
{
"id": 2,
"node_id": 3,
"instance": 0,
"minimum": 0,
"maximum": 100,
"current_value": 75.0,
"target_value": 0.0,
"last_value": 100.0,
"unit": "%25",
"step_value": 0.5,
"editable": 1,
"type": 15,
"state": 1,
"last_changed": 1687175680,
"changed_by": 1,
"changed_by_id": 0,
"based_on": 1,
"data": "",
"name": "",
"options": {
"automations": ["step"],
"history": {
"day": 35,
"week": 5,
"month": 1
}
}
},
{
"id": 3,
"node_id": 3,
"instance": 0,
"minimum": -45,
"maximum": 90,
"current_value": 56.0,
"target_value": 56.0,
"last_value": 0.0,
"unit": "%C2%B0",
"step_value": 1.0,
"editable": 1,
"type": 113,
"state": 1,
"last_changed": 1678284920,
"changed_by": 1,
"changed_by_id": 0,
"based_on": 1,
"data": "",
"name": "",
"options": {
"automations": ["step"]
}
}
]
}

View File

@ -1,101 +0,0 @@
{
"id": 3,
"name": "Test%20Cover",
"profile": 2002,
"image": "default",
"favorite": 0,
"order": 4,
"protocol": 23,
"routing": 0,
"state": 1,
"state_changed": 1687175681,
"added": 1672086680,
"history": 1,
"cube_type": 14,
"note": "TestCoverDevice",
"services": 7,
"phonetic_name": "",
"owner": 2,
"security": 0,
"attributes": [
{
"id": 1,
"node_id": 3,
"instance": 0,
"minimum": 0,
"maximum": 4,
"current_value": 4.0,
"target_value": 1.0,
"last_value": 0.0,
"unit": "n%2Fa",
"step_value": 1.0,
"editable": 1,
"type": 135,
"state": 1,
"last_changed": 1687175680,
"changed_by": 1,
"changed_by_id": 0,
"based_on": 1,
"data": "",
"name": "",
"options": {
"can_observe": [300],
"observes": [75],
"automations": ["toggle"]
}
},
{
"id": 2,
"node_id": 3,
"instance": 0,
"minimum": 0,
"maximum": 100,
"current_value": 25.0,
"target_value": 100.0,
"last_value": 0.0,
"unit": "%25",
"step_value": 0.5,
"editable": 1,
"type": 15,
"state": 1,
"last_changed": 1687175680,
"changed_by": 1,
"changed_by_id": 0,
"based_on": 1,
"data": "",
"name": "",
"options": {
"automations": ["step"],
"history": {
"day": 35,
"week": 5,
"month": 1
}
}
},
{
"id": 3,
"node_id": 3,
"instance": 0,
"minimum": -45,
"maximum": 90,
"current_value": -11.0,
"target_value": 0.0,
"last_value": -45.0,
"unit": "%C2%B0",
"step_value": 1.0,
"editable": 1,
"type": 113,
"state": 1,
"last_changed": 1678284920,
"changed_by": 1,
"changed_by_id": 0,
"based_on": 1,
"data": "",
"name": "",
"options": {
"automations": ["step"]
}
}
]
}

View File

@ -1,6 +1,6 @@
{
"id": 3,
"name": "Test%20Cover",
"name": "Test Cover",
"profile": 2002,
"image": "default",
"favorite": 0,
@ -27,7 +27,7 @@
"current_value": 1.0,
"target_value": 1.0,
"last_value": 4.0,
"unit": "n%2Fa",
"unit": "n/a",
"step_value": 1.0,
"editable": 1,
"type": 135,
@ -53,7 +53,7 @@
"current_value": 0.0,
"target_value": 0.0,
"last_value": 0.0,
"unit": "%25",
"unit": "%",
"step_value": 0.5,
"editable": 1,
"type": 15,
@ -82,7 +82,7 @@
"current_value": -45.0,
"target_value": 0.0,
"last_value": -45.0,
"unit": "%C2%B0",
"unit": "°",
"step_value": 1.0,
"editable": 1,
"type": 113,

View File

@ -1,19 +1,19 @@
{
"id": 1,
"name": "Test%20Cover",
"name": "Test Slats",
"profile": 2002,
"image": "default",
"favorite": 0,
"order": 4,
"order": 1,
"protocol": 23,
"routing": 0,
"state": 1,
"state_changed": 1687175681,
"added": 1672086680,
"state_changed": 1676901608,
"added": 1672148537,
"history": 1,
"cube_type": 14,
"note": "TestCoverDevice",
"services": 7,
"note": "",
"services": 70,
"phonetic_name": "",
"owner": 2,
"security": 0,
@ -22,67 +22,12 @@
"id": 1,
"node_id": 1,
"instance": 0,
"minimum": 0,
"maximum": 4,
"current_value": 1.0,
"target_value": 1.0,
"last_value": 0.0,
"unit": "n%2Fa",
"step_value": 1.0,
"editable": 1,
"type": 135,
"state": 1,
"last_changed": 1687175680,
"changed_by": 1,
"changed_by_id": 0,
"based_on": 1,
"data": "",
"name": "",
"options": {
"can_observe": [300],
"observes": [75],
"automations": ["toggle"]
}
},
{
"id": 2,
"node_id": 1,
"instance": 0,
"minimum": 0,
"maximum": 100,
"current_value": 100.0,
"target_value": 0.0,
"last_value": 0.0,
"unit": "%25",
"step_value": 0.5,
"editable": 1,
"type": 15,
"state": 1,
"last_changed": 1687175680,
"changed_by": 1,
"changed_by_id": 0,
"based_on": 1,
"data": "",
"name": "",
"options": {
"automations": ["step"],
"history": {
"day": 35,
"week": 5,
"month": 1
}
}
},
{
"id": 3,
"node_id": 1,
"instance": 0,
"minimum": -45,
"maximum": 90,
"current_value": 90.0,
"target_value": 0.0,
"last_value": -45.0,
"unit": "%C2%B0",
"current_value": 1.0,
"target_value": 1.0,
"last_value": -21.0,
"unit": "°",
"step_value": 1.0,
"editable": 1,
"type": 113,
@ -96,6 +41,31 @@
"options": {
"automations": ["step"]
}
},
{
"id": 2,
"node_id": 1,
"instance": 0,
"minimum": 0,
"maximum": 2,
"current_value": 0.0,
"target_value": 0.0,
"last_value": 1.0,
"unit": "n/a",
"step_value": 1.0,
"editable": 1,
"type": 337,
"state": 1,
"last_changed": 1678284911,
"changed_by": 1,
"changed_by_id": 0,
"based_on": 1,
"data": "",
"name": "",
"options": {
"can_observe": [300],
"observes": [72]
}
}
]
}

View File

@ -0,0 +1,48 @@
{
"id": 3,
"name": "Test Cover",
"profile": 2002,
"image": "default",
"favorite": 0,
"order": 4,
"protocol": 23,
"routing": 0,
"state": 1,
"state_changed": 1687175681,
"added": 1672086680,
"history": 1,
"cube_type": 14,
"note": "TestCoverDevice",
"services": 7,
"phonetic_name": "",
"owner": 2,
"security": 0,
"attributes": [
{
"id": 1,
"node_id": 3,
"instance": 0,
"minimum": 0,
"maximum": 4,
"current_value": 1.0,
"target_value": 1.0,
"last_value": 4.0,
"unit": "n/a",
"step_value": 1.0,
"editable": 1,
"type": 135,
"state": 1,
"last_changed": 1687175680,
"changed_by": 1,
"changed_by_id": 0,
"based_on": 1,
"data": "",
"name": "",
"options": {
"can_observe": [300],
"observes": [75],
"automations": ["toggle"]
}
}
]
}

View File

@ -1,97 +1,38 @@
"""Test homee covers."""
from unittest.mock import AsyncMock, MagicMock
from unittest.mock import MagicMock
from pyHomee import HomeeNode
from homeassistant.components.cover import DOMAIN as COVER_DOMAIN, CoverState
from homeassistant.components.homee.const import DOMAIN
from homeassistant.const import ATTR_ENTITY_ID, SERVICE_CLOSE_COVER, SERVICE_OPEN_COVER
from homeassistant.components.cover import (
ATTR_POSITION,
ATTR_TILT_POSITION,
DOMAIN as COVER_DOMAIN,
CoverEntityFeature,
CoverState,
)
from homeassistant.const import (
ATTR_ENTITY_ID,
SERVICE_CLOSE_COVER,
SERVICE_CLOSE_COVER_TILT,
SERVICE_OPEN_COVER,
SERVICE_OPEN_COVER_TILT,
SERVICE_SET_COVER_POSITION,
SERVICE_SET_COVER_TILT_POSITION,
SERVICE_STOP_COVER,
)
from homeassistant.core import HomeAssistant
from . import setup_integration
from . import build_mock_node, setup_integration
from tests.common import MockConfigEntry, load_json_object_fixture
from tests.common import MockConfigEntry
async def test_cover_open(
hass: HomeAssistant, mock_homee: AsyncMock, mock_config_entry: MockConfigEntry
) -> None:
"""Test an open cover."""
# Cover open, tilt open.
cover_json = load_json_object_fixture("cover1.json", DOMAIN)
cover_node = HomeeNode(cover_json)
mock_homee.nodes = [cover_node]
await setup_integration(hass, mock_config_entry)
assert hass.states.get("cover.test_cover").state == CoverState.OPEN
attributes = hass.states.get("cover.test_cover").attributes
assert attributes.get("supported_features") == 143
assert attributes.get("current_position") == 100
assert attributes.get("current_tilt_position") == 100
async def test_cover_closed(
hass: HomeAssistant, mock_homee: MagicMock, mock_config_entry: MockConfigEntry
) -> None:
"""Test a closed cover."""
# Cover closed, tilt closed.
cover_json = load_json_object_fixture("cover2.json", DOMAIN)
cover_node = HomeeNode(cover_json)
mock_homee.nodes = [cover_node]
await setup_integration(hass, mock_config_entry)
assert hass.states.get("cover.test_cover").state == CoverState.CLOSED
attributes = hass.states.get("cover.test_cover").attributes
assert attributes.get("current_position") == 0
assert attributes.get("current_tilt_position") == 0
async def test_cover_opening(
hass: HomeAssistant, mock_homee: MagicMock, mock_config_entry: MockConfigEntry
) -> None:
"""Test an opening cover."""
# opening, 75% homee / 25% HA
cover_json = load_json_object_fixture("cover3.json", DOMAIN)
cover_node = HomeeNode(cover_json)
mock_homee.nodes = [cover_node]
await setup_integration(hass, mock_config_entry)
assert hass.states.get("cover.test_cover").state == CoverState.OPENING
attributes = hass.states.get("cover.test_cover").attributes
assert attributes.get("current_position") == 25
assert attributes.get("current_tilt_position") == 25
async def test_cover_closing(
hass: HomeAssistant, mock_homee: MagicMock, mock_config_entry: MockConfigEntry
) -> None:
"""Test a closing cover."""
# closing, 25% homee / 75% HA
cover_json = load_json_object_fixture("cover4.json", DOMAIN)
cover_node = HomeeNode(cover_json)
mock_homee.nodes = [cover_node]
await setup_integration(hass, mock_config_entry)
assert hass.states.get("cover.test_cover").state == CoverState.CLOSING
attributes = hass.states.get("cover.test_cover").attributes
assert attributes.get("current_position") == 75
assert attributes.get("current_tilt_position") == 74
async def test_open_cover(
hass: HomeAssistant, mock_homee: MagicMock, mock_config_entry: MockConfigEntry
async def test_open_close_stop_cover(
hass: HomeAssistant,
mock_homee: MagicMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test opening the cover."""
# Cover closed, tilt closed.
cover_json = load_json_object_fixture("cover2.json", DOMAIN)
cover_node = HomeeNode(cover_json)
mock_homee.nodes = [cover_node]
mock_homee.nodes = [build_mock_node("cover_with_position_slats.json")]
await setup_integration(hass, mock_config_entry)
@ -101,24 +42,214 @@ async def test_open_cover(
{ATTR_ENTITY_ID: "cover.test_cover"},
blocking=True,
)
mock_homee.set_value.assert_called_once_with(cover_node.id, 1, 0)
async def test_close_cover(
hass: HomeAssistant, mock_homee: MagicMock, mock_config_entry: MockConfigEntry
) -> None:
"""Test opening the cover."""
# Cover open, tilt open.
cover_json = load_json_object_fixture("cover1.json", DOMAIN)
cover_node = HomeeNode(cover_json)
mock_homee.nodes = [cover_node]
await setup_integration(hass, mock_config_entry)
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_CLOSE_COVER,
{ATTR_ENTITY_ID: "cover.test_cover"},
blocking=True,
)
mock_homee.set_value.assert_called_once_with(cover_node.id, 1, 1)
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_STOP_COVER,
{ATTR_ENTITY_ID: "cover.test_cover"},
blocking=True,
)
calls = mock_homee.set_value.call_args_list
for index, call in enumerate(calls):
assert call[0] == (mock_homee.nodes[0].id, 1, index)
async def test_set_cover_position(
hass: HomeAssistant,
mock_homee: MagicMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test setting the cover position."""
mock_homee.nodes = [build_mock_node("cover_with_position_slats.json")]
await setup_integration(hass, mock_config_entry)
# Slats have a range of -45 to 90.
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_SET_COVER_POSITION,
{ATTR_ENTITY_ID: "cover.test_slats", ATTR_POSITION: 100},
blocking=True,
)
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_SET_COVER_POSITION,
{ATTR_ENTITY_ID: "cover.test_slats", ATTR_POSITION: 0},
blocking=True,
)
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_SET_COVER_POSITION,
{ATTR_ENTITY_ID: "cover.test_slats", ATTR_POSITION: 50},
blocking=True,
)
calls = mock_homee.set_value.call_args_list
positions = [0, 100, 50]
for call in calls:
assert call[0] == (1, 2, positions.pop(0))
async def test_close_open_slats(
hass: HomeAssistant,
mock_homee: MagicMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test closing and opening slats."""
mock_homee.nodes = [build_mock_node("cover_with_slats_position.json")]
await setup_integration(hass, mock_config_entry)
attributes = hass.states.get("cover.test_slats").attributes
assert attributes.get("supported_features") == (
CoverEntityFeature.OPEN_TILT
| CoverEntityFeature.CLOSE_TILT
| CoverEntityFeature.SET_TILT_POSITION
)
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_CLOSE_COVER_TILT,
{ATTR_ENTITY_ID: "cover.test_slats"},
blocking=True,
)
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_OPEN_COVER_TILT,
{ATTR_ENTITY_ID: "cover.test_slats"},
blocking=True,
)
calls = mock_homee.set_value.call_args_list
for index, call in enumerate(calls, start=1):
assert call[0] == (mock_homee.nodes[0].id, 2, index)
async def test_set_slat_position(
hass: HomeAssistant,
mock_homee: MagicMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test setting slats position."""
mock_homee.nodes = [build_mock_node("cover_with_slats_position.json")]
await setup_integration(hass, mock_config_entry)
# Slats have a range of -45 to 90 on this device.
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_SET_COVER_TILT_POSITION,
{ATTR_ENTITY_ID: "cover.test_slats", ATTR_TILT_POSITION: 100},
blocking=True,
)
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_SET_COVER_TILT_POSITION,
{ATTR_ENTITY_ID: "cover.test_slats", ATTR_TILT_POSITION: 0},
blocking=True,
)
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_SET_COVER_TILT_POSITION,
{ATTR_ENTITY_ID: "cover.test_slats", ATTR_TILT_POSITION: 50},
blocking=True,
)
calls = mock_homee.set_value.call_args_list
positions = [-45, 90, 22.5]
for call in calls:
assert call[0] == (1, 1, positions.pop(0))
async def test_cover_positions(
hass: HomeAssistant,
mock_homee: MagicMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test an open cover."""
# Cover open, tilt open.
# mock_homee.nodes = [cover]
mock_homee.nodes = [build_mock_node("cover_with_position_slats.json")]
cover = mock_homee.nodes[0]
await setup_integration(hass, mock_config_entry)
assert hass.states.get("cover.test_cover").state == CoverState.OPEN
attributes = hass.states.get("cover.test_cover").attributes
assert attributes.get("supported_features") == (
CoverEntityFeature.OPEN
| CoverEntityFeature.CLOSE
| CoverEntityFeature.SET_POSITION
| CoverEntityFeature.STOP
| CoverEntityFeature.SET_TILT_POSITION
)
assert attributes.get("current_position") == 100
assert attributes.get("current_tilt_position") == 100
cover.attributes[0].current_value = 1
cover.attributes[1].current_value = 100
cover.attributes[2].current_value = 90
cover.add_on_changed_listener.call_args_list[0][0][0](cover)
await hass.async_block_till_done()
attributes = hass.states.get("cover.test_cover").attributes
assert attributes.get("current_position") == 0
assert attributes.get("current_tilt_position") == 0
assert hass.states.get("cover.test_cover").state == CoverState.CLOSED
cover.attributes[0].current_value = 3
cover.attributes[1].current_value = 75
cover.attributes[2].current_value = 56
cover.add_on_changed_listener.call_args_list[0][0][0](cover)
await hass.async_block_till_done()
assert hass.states.get("cover.test_cover").state == CoverState.OPENING
attributes = hass.states.get("cover.test_cover").attributes
assert attributes.get("current_position") == 25
assert attributes.get("current_tilt_position") == 25
cover.attributes[0].current_value = 4
cover.attributes[1].current_value = 25
cover.attributes[2].current_value = -11
cover.add_on_changed_listener.call_args_list[0][0][0](cover)
await hass.async_block_till_done()
assert hass.states.get("cover.test_cover").state == CoverState.CLOSING
attributes = hass.states.get("cover.test_cover").attributes
assert attributes.get("current_position") == 75
assert attributes.get("current_tilt_position") == 74
async def test_reversed_cover(
hass: HomeAssistant,
mock_homee: MagicMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test a cover with inverted UP_DOWN attribute without position."""
mock_homee.nodes = [build_mock_node("cover_without_position.json")]
cover = mock_homee.nodes[0]
await setup_integration(hass, mock_config_entry)
cover.attributes[0].is_reversed = True
cover.add_on_changed_listener.call_args_list[0][0][0](cover)
await hass.async_block_till_done()
attributes = hass.states.get("cover.test_cover").attributes
assert attributes.get("supported_features") == (
CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE | CoverEntityFeature.STOP
)
assert hass.states.get("cover.test_cover").state == CoverState.OPEN
cover.attributes[0].current_value = 0
cover.add_on_changed_listener.call_args_list[0][0][0](cover)
await hass.async_block_till_done()
assert hass.states.get("cover.test_cover").state == CoverState.CLOSED