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.""" """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 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: 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.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done() 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 = MagicMock()
homee.settings.uid = HOMEE_ID homee.settings.uid = HOMEE_ID
homee.settings.homee_name = HOMEE_NAME 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.reconnect_interval = 10
homee.connected = True 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, "id": 3,
"name": "Test%20Cover", "name": "Test Cover",
"profile": 2002, "profile": 2002,
"image": "default", "image": "default",
"favorite": 0, "favorite": 0,
@ -27,7 +27,7 @@
"current_value": 1.0, "current_value": 1.0,
"target_value": 1.0, "target_value": 1.0,
"last_value": 4.0, "last_value": 4.0,
"unit": "n%2Fa", "unit": "n/a",
"step_value": 1.0, "step_value": 1.0,
"editable": 1, "editable": 1,
"type": 135, "type": 135,
@ -53,7 +53,7 @@
"current_value": 0.0, "current_value": 0.0,
"target_value": 0.0, "target_value": 0.0,
"last_value": 0.0, "last_value": 0.0,
"unit": "%25", "unit": "%",
"step_value": 0.5, "step_value": 0.5,
"editable": 1, "editable": 1,
"type": 15, "type": 15,
@ -82,7 +82,7 @@
"current_value": -45.0, "current_value": -45.0,
"target_value": 0.0, "target_value": 0.0,
"last_value": -45.0, "last_value": -45.0,
"unit": "%C2%B0", "unit": "°",
"step_value": 1.0, "step_value": 1.0,
"editable": 1, "editable": 1,
"type": 113, "type": 113,

View File

@ -1,19 +1,19 @@
{ {
"id": 1, "id": 1,
"name": "Test%20Cover", "name": "Test Slats",
"profile": 2002, "profile": 2002,
"image": "default", "image": "default",
"favorite": 0, "favorite": 0,
"order": 4, "order": 1,
"protocol": 23, "protocol": 23,
"routing": 0, "routing": 0,
"state": 1, "state": 1,
"state_changed": 1687175681, "state_changed": 1676901608,
"added": 1672086680, "added": 1672148537,
"history": 1, "history": 1,
"cube_type": 14, "cube_type": 14,
"note": "TestCoverDevice", "note": "",
"services": 7, "services": 70,
"phonetic_name": "", "phonetic_name": "",
"owner": 2, "owner": 2,
"security": 0, "security": 0,
@ -22,67 +22,12 @@
"id": 1, "id": 1,
"node_id": 1, "node_id": 1,
"instance": 0, "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, "minimum": -45,
"maximum": 90, "maximum": 90,
"current_value": 90.0, "current_value": 1.0,
"target_value": 0.0, "target_value": 1.0,
"last_value": -45.0, "last_value": -21.0,
"unit": "%C2%B0", "unit": "°",
"step_value": 1.0, "step_value": 1.0,
"editable": 1, "editable": 1,
"type": 113, "type": 113,
@ -96,6 +41,31 @@
"options": { "options": {
"automations": ["step"] "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.""" """Test homee covers."""
from unittest.mock import AsyncMock, MagicMock from unittest.mock import MagicMock
from pyHomee import HomeeNode from homeassistant.components.cover import (
ATTR_POSITION,
from homeassistant.components.cover import DOMAIN as COVER_DOMAIN, CoverState ATTR_TILT_POSITION,
from homeassistant.components.homee.const import DOMAIN DOMAIN as COVER_DOMAIN,
from homeassistant.const import ATTR_ENTITY_ID, SERVICE_CLOSE_COVER, SERVICE_OPEN_COVER 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 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( async def test_open_close_stop_cover(
hass: HomeAssistant, mock_homee: AsyncMock, mock_config_entry: MockConfigEntry hass: HomeAssistant,
) -> None: mock_homee: MagicMock,
"""Test an open cover.""" mock_config_entry: MockConfigEntry,
# 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
) -> None: ) -> None:
"""Test opening the cover.""" """Test opening the cover."""
# Cover closed, tilt closed. mock_homee.nodes = [build_mock_node("cover_with_position_slats.json")]
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) await setup_integration(hass, mock_config_entry)
@ -101,24 +42,214 @@ async def test_open_cover(
{ATTR_ENTITY_ID: "cover.test_cover"}, {ATTR_ENTITY_ID: "cover.test_cover"},
blocking=True, 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( await hass.services.async_call(
COVER_DOMAIN, COVER_DOMAIN,
SERVICE_CLOSE_COVER, SERVICE_CLOSE_COVER,
{ATTR_ENTITY_ID: "cover.test_cover"}, {ATTR_ENTITY_ID: "cover.test_cover"},
blocking=True, 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