Add Airtouch5 cover tests (#122769)

add airtouch5 cover tests
pull/122855/head
Luke Wale 2024-07-30 18:34:49 +08:00 committed by GitHub
parent 53a59412bb
commit 7c92287f97
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 372 additions and 0 deletions

View File

@ -1 +1,13 @@
"""Tests for the Airtouch 5 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 component."""
config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()

View File

@ -3,8 +3,22 @@
from collections.abc import Generator
from unittest.mock import AsyncMock, patch
from airtouch5py.data_packet_factory import DataPacketFactory
from airtouch5py.packets.ac_ability import AcAbility
from airtouch5py.packets.ac_status import AcFanSpeed, AcMode, AcPowerState, AcStatus
from airtouch5py.packets.zone_name import ZoneName
from airtouch5py.packets.zone_status import (
ControlMethod,
ZonePowerState,
ZoneStatusZone,
)
import pytest
from homeassistant.components.airtouch5.const import DOMAIN
from homeassistant.const import CONF_HOST
from tests.common import MockConfigEntry
@pytest.fixture
def mock_setup_entry() -> Generator[AsyncMock]:
@ -13,3 +27,107 @@ def mock_setup_entry() -> Generator[AsyncMock]:
"homeassistant.components.airtouch5.async_setup_entry", return_value=True
) as mock_setup_entry:
yield mock_setup_entry
@pytest.fixture
def mock_config_entry() -> MockConfigEntry:
"""Mock the config entry."""
return MockConfigEntry(
domain=DOMAIN,
unique_id="1.1.1.1",
data={
CONF_HOST: "1.1.1.1",
},
)
@pytest.fixture
def mock_airtouch5_client() -> Generator[AsyncMock]:
"""Mock an Airtouch5 client."""
with (
patch(
"homeassistant.components.airtouch5.Airtouch5SimpleClient",
autospec=True,
) as mock_client,
patch(
"homeassistant.components.airtouch5.config_flow.Airtouch5SimpleClient",
new=mock_client,
),
):
client = mock_client.return_value
# Default values for the tests using this mock :
client.data_packet_factory = DataPacketFactory()
client.ac = [
AcAbility(
ac_number=1,
ac_name="AC 1",
start_zone_number=1,
zone_count=2,
supports_mode_cool=True,
supports_mode_fan=True,
supports_mode_dry=True,
supports_mode_heat=True,
supports_mode_auto=True,
supports_fan_speed_intelligent_auto=True,
supports_fan_speed_turbo=True,
supports_fan_speed_powerful=True,
supports_fan_speed_high=True,
supports_fan_speed_medium=True,
supports_fan_speed_low=True,
supports_fan_speed_quiet=True,
supports_fan_speed_auto=True,
min_cool_set_point=15,
max_cool_set_point=25,
min_heat_set_point=20,
max_heat_set_point=30,
)
]
client.latest_ac_status = {
1: AcStatus(
ac_power_state=AcPowerState.ON,
ac_number=1,
ac_mode=AcMode.AUTO,
ac_fan_speed=AcFanSpeed.AUTO,
ac_setpoint=24,
turbo_active=False,
bypass_active=False,
spill_active=False,
timer_set=False,
temperature=24,
error_code=0,
)
}
client.zones = [ZoneName(1, "Zone 1"), ZoneName(2, "Zone 2")]
client.latest_zone_status = {
1: ZoneStatusZone(
zone_power_state=ZonePowerState.ON,
zone_number=1,
control_method=ControlMethod.PERCENTAGE_CONTROL,
open_percentage=0.9,
set_point=24,
has_sensor=False,
temperature=24,
spill_active=False,
is_low_battery=False,
),
2: ZoneStatusZone(
zone_power_state=ZonePowerState.ON,
zone_number=1,
control_method=ControlMethod.TEMPERATURE_CONTROL,
open_percentage=1,
set_point=24,
has_sensor=True,
temperature=24,
spill_active=False,
is_low_battery=False,
),
}
client.connection_state_callbacks = []
client.zone_status_callbacks = []
client.ac_status_callbacks = []
yield client

View File

@ -0,0 +1,99 @@
# serializer version: 1
# name: test_all_entities[cover.zone_1_damper-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'cover',
'entity_category': None,
'entity_id': 'cover.zone_1_damper',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <CoverDeviceClass.DAMPER: 'damper'>,
'original_icon': None,
'original_name': 'Damper',
'platform': 'airtouch5',
'previous_unique_id': None,
'supported_features': <CoverEntityFeature: 7>,
'translation_key': 'damper',
'unique_id': 'zone_1_open_percentage',
'unit_of_measurement': None,
})
# ---
# name: test_all_entities[cover.zone_1_damper-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'current_position': 90,
'device_class': 'damper',
'friendly_name': 'Zone 1 Damper',
'supported_features': <CoverEntityFeature: 7>,
}),
'context': <ANY>,
'entity_id': 'cover.zone_1_damper',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'open',
})
# ---
# name: test_all_entities[cover.zone_2_damper-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'cover',
'entity_category': None,
'entity_id': 'cover.zone_2_damper',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <CoverDeviceClass.DAMPER: 'damper'>,
'original_icon': None,
'original_name': 'Damper',
'platform': 'airtouch5',
'previous_unique_id': None,
'supported_features': <CoverEntityFeature: 7>,
'translation_key': 'damper',
'unique_id': 'zone_2_open_percentage',
'unit_of_measurement': None,
})
# ---
# name: test_all_entities[cover.zone_2_damper-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'current_position': 100,
'device_class': 'damper',
'friendly_name': 'Zone 2 Damper',
'supported_features': <CoverEntityFeature: 7>,
}),
'context': <ANY>,
'entity_id': 'cover.zone_2_damper',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'open',
})
# ---

View File

@ -0,0 +1,143 @@
"""Tests for the Airtouch5 cover platform."""
from collections.abc import Callable
from unittest.mock import AsyncMock, patch
from airtouch5py.packets.zone_status import (
ControlMethod,
ZonePowerState,
ZoneStatusZone,
)
from syrupy import SnapshotAssertion
from homeassistant.components.cover import (
ATTR_CURRENT_POSITION,
ATTR_POSITION,
DOMAIN as COVER_DOMAIN,
SERVICE_CLOSE_COVER,
SERVICE_OPEN_COVER,
SERVICE_SET_COVER_POSITION,
STATE_OPEN,
)
from homeassistant.const import ATTR_ENTITY_ID, STATE_CLOSED, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from . import setup_integration
from tests.common import MockConfigEntry, snapshot_platform
COVER_ENTITY_ID = "cover.zone_1_damper"
async def test_all_entities(
hass: HomeAssistant,
snapshot: SnapshotAssertion,
mock_airtouch5_client: AsyncMock,
mock_config_entry: MockConfigEntry,
entity_registry: er.EntityRegistry,
) -> None:
"""Test all entities."""
with patch("homeassistant.components.airtouch5.PLATFORMS", [Platform.COVER]):
await setup_integration(hass, mock_config_entry)
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
async def test_cover_actions(
hass: HomeAssistant,
mock_airtouch5_client: AsyncMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test the actions of the Airtouch5 covers."""
await setup_integration(hass, mock_config_entry)
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_CLOSE_COVER,
{ATTR_ENTITY_ID: COVER_ENTITY_ID},
blocking=True,
)
mock_airtouch5_client.send_packet.assert_called_once()
mock_airtouch5_client.reset_mock()
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_OPEN_COVER,
{ATTR_ENTITY_ID: COVER_ENTITY_ID},
blocking=True,
)
mock_airtouch5_client.send_packet.assert_called_once()
mock_airtouch5_client.reset_mock()
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_SET_COVER_POSITION,
{ATTR_ENTITY_ID: COVER_ENTITY_ID, ATTR_POSITION: 50},
blocking=True,
)
mock_airtouch5_client.send_packet.assert_called_once()
mock_airtouch5_client.reset_mock()
async def test_cover_callbacks(
hass: HomeAssistant,
mock_airtouch5_client: AsyncMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test the callbacks of the Airtouch5 covers."""
await setup_integration(hass, mock_config_entry)
# We find the callback method on the mock client
zone_status_callback: Callable[[dict[int, ZoneStatusZone]], None] = (
mock_airtouch5_client.zone_status_callbacks[2]
)
# Define a method to simply call it
async def _call_zone_status_callback(open_percentage: int) -> None:
zsz = ZoneStatusZone(
zone_power_state=ZonePowerState.ON,
zone_number=1,
control_method=ControlMethod.PERCENTAGE_CONTROL,
open_percentage=open_percentage,
set_point=None,
has_sensor=False,
temperature=None,
spill_active=False,
is_low_battery=False,
)
zone_status_callback({1: zsz})
await hass.async_block_till_done()
# And call it to effectively launch the callback as the server would do
# Partly open
await _call_zone_status_callback(0.7)
state = hass.states.get(COVER_ENTITY_ID)
assert state
assert state.state == STATE_OPEN
assert state.attributes.get(ATTR_CURRENT_POSITION) == 70
# Fully open
await _call_zone_status_callback(1)
state = hass.states.get(COVER_ENTITY_ID)
assert state
assert state.state == STATE_OPEN
assert state.attributes.get(ATTR_CURRENT_POSITION) == 100
# Fully closed
await _call_zone_status_callback(0.0)
state = hass.states.get(COVER_ENTITY_ID)
assert state
assert state.state == STATE_CLOSED
assert state.attributes.get(ATTR_CURRENT_POSITION) == 0
# Partly reopened
await _call_zone_status_callback(0.3)
state = hass.states.get(COVER_ENTITY_ID)
assert state
assert state.state == STATE_OPEN
assert state.attributes.get(ATTR_CURRENT_POSITION) == 30