mirror of
https://github.com/home-assistant/core.git
synced 2026-08-07 05:46:41 +01:00
346 lines
11 KiB
Python
346 lines
11 KiB
Python
"""Fixtures for iZone integration tests."""
|
|
|
|
from collections.abc import AsyncGenerator, Generator, Iterable
|
|
from contextlib import contextmanager
|
|
from unittest.mock import AsyncMock, Mock, patch
|
|
|
|
from pizone import Controller, ControllerEndpoint, DiscoveryService, Zone
|
|
import pytest
|
|
|
|
from homeassistant.components.izone.const import DOMAIN
|
|
from homeassistant.const import CONF_EXCLUDE, CONF_HOST, Platform
|
|
from homeassistant.core import HomeAssistant
|
|
from homeassistant.setup import async_setup_component
|
|
|
|
from tests.common import MockConfigEntry
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_config_entry() -> MockConfigEntry:
|
|
"""Create a mock config entry for iZone with a stored host."""
|
|
return MockConfigEntry(
|
|
domain=DOMAIN,
|
|
title="iZone 000000001",
|
|
data={CONF_HOST: "192.0.2.1"},
|
|
entry_id="test_entry_id",
|
|
unique_id="000000001",
|
|
version=2,
|
|
)
|
|
|
|
|
|
def create_mock_controller(
|
|
device_uid: str = "000000001",
|
|
device_ip: str = "192.0.2.1",
|
|
sys_type: str = "iZone310",
|
|
zones_total: int = 4,
|
|
zone_ctrl: int = 1,
|
|
ras_mode: str = "master",
|
|
free_air_enabled: bool = False,
|
|
free_air: bool = False,
|
|
is_on: bool = True,
|
|
) -> Mock:
|
|
"""Create a mock Controller with configurable parameters."""
|
|
controller = Mock(spec=Controller)
|
|
controller.device_uid = device_uid
|
|
controller.device_ip = device_ip
|
|
controller.sys_type = sys_type
|
|
controller.zones_total = zones_total
|
|
controller.zone_ctrl = zone_ctrl
|
|
controller.ras_mode = ras_mode
|
|
controller.free_air_enabled = free_air_enabled
|
|
controller.free_air = free_air
|
|
controller.is_on = is_on
|
|
controller.connected = True
|
|
controller.bridge_connected = True
|
|
controller.is_v2 = False
|
|
controller.is_ipower = False
|
|
controller.mode = Controller.Mode.COOL
|
|
controller.temp_setpoint = 24.0
|
|
controller.temp_return = 22.0
|
|
controller.temp_supply = 16.0
|
|
controller.temp_min = 15.0
|
|
controller.temp_max = 30.0
|
|
controller.fan = Controller.Fan.MED
|
|
controller.fan_modes = [
|
|
Controller.Fan.LOW,
|
|
Controller.Fan.MED,
|
|
Controller.Fan.HIGH,
|
|
Controller.Fan.AUTO,
|
|
]
|
|
controller.zones = []
|
|
controller._system_settings = {
|
|
"AirStreamDeviceUId": device_uid,
|
|
"DeviceType": "ASH",
|
|
"SysOn": "on" if is_on else "off",
|
|
"SysMode": "cool",
|
|
"SysFan": "med",
|
|
"SleepTimer": 0,
|
|
"Supply": "16.0",
|
|
"Setpoint": "24.0",
|
|
"Temp": "22.0",
|
|
"RAS": ras_mode,
|
|
"CtrlZone": zone_ctrl,
|
|
"EcoLock": "true",
|
|
"EcoMax": "30.0",
|
|
"EcoMin": "15.0",
|
|
"NoOfConst": 0,
|
|
"NoOfZones": zones_total,
|
|
"SysType": sys_type,
|
|
"FreeAir": "disabled"
|
|
if not free_air_enabled
|
|
else ("on" if free_air else "off"),
|
|
"FanAuto": "3-speed",
|
|
}
|
|
controller.refresh_all = AsyncMock()
|
|
controller.close = AsyncMock()
|
|
controller.set_temp_setpoint = AsyncMock()
|
|
controller.set_fan = AsyncMock()
|
|
controller.set_on = AsyncMock()
|
|
controller.set_mode = AsyncMock()
|
|
controller.set_free_air = AsyncMock()
|
|
|
|
def dump_state() -> dict:
|
|
return {
|
|
"device_uid": controller.device_uid,
|
|
"device_ip": controller.device_ip,
|
|
"connected": controller.connected,
|
|
"bridge_connected": controller.bridge_connected,
|
|
"is_v2": controller.is_v2,
|
|
"is_ipower": controller.is_ipower,
|
|
"fan_modes": [
|
|
fan.value if hasattr(fan, "value") else fan
|
|
for fan in controller.fan_modes
|
|
],
|
|
"system_settings": dict(controller._system_settings),
|
|
"zones": [zone.dump_state() for zone in controller.zones],
|
|
"power": None,
|
|
}
|
|
|
|
controller.dump_state = dump_state
|
|
return controller
|
|
|
|
|
|
def create_mock_endpoint(
|
|
uid: str = "000000001",
|
|
host: str = "192.0.2.1",
|
|
) -> ControllerEndpoint:
|
|
"""Create a ControllerEndpoint for config-flow discovery patches."""
|
|
return ControllerEndpoint(uid=uid, host=host)
|
|
|
|
|
|
def endpoint_from_controller(controller: Mock) -> ControllerEndpoint:
|
|
"""Map a mock controller's uid/ip into a ControllerEndpoint."""
|
|
return ControllerEndpoint(uid=controller.device_uid, host=controller.device_ip)
|
|
|
|
|
|
def create_mock_zone(
|
|
index: int = 0,
|
|
name: str = "Zone",
|
|
temp_current: float | None = 22.5,
|
|
temp_setpoint: float = 24.0,
|
|
) -> Mock:
|
|
"""Create a mock Zone with configurable parameters."""
|
|
zone = Mock(spec=Zone)
|
|
zone.index = index
|
|
zone.name = name
|
|
zone.type = Zone.Type.AUTO
|
|
zone.mode = Zone.Mode.AUTO
|
|
zone.temp_current = temp_current
|
|
zone.temp_setpoint = temp_setpoint
|
|
zone.airflow_min = 0
|
|
zone.airflow_max = 100
|
|
zone.is_on = True
|
|
zone._zone_data = {
|
|
"AirStreamDeviceUId": "000000001",
|
|
"Id": 0,
|
|
"Index": index,
|
|
"Name": name,
|
|
"Type": "auto",
|
|
"Mode": "auto",
|
|
"SetPoint": temp_setpoint,
|
|
"Temp": temp_current if temp_current is not None else 0,
|
|
"MaxAir": 100,
|
|
"MinAir": 0,
|
|
"Const": 255,
|
|
"ConstA": "false",
|
|
"DmpFlt": "false",
|
|
"Master": "false",
|
|
"iSense": "false",
|
|
}
|
|
zone.set_airflow_min = AsyncMock()
|
|
zone.set_airflow_max = AsyncMock()
|
|
zone.set_temp_setpoint = AsyncMock()
|
|
zone.set_mode = AsyncMock()
|
|
|
|
def dump_state() -> dict:
|
|
return dict(zone._zone_data)
|
|
|
|
zone.dump_state = dump_state
|
|
return zone
|
|
|
|
|
|
@contextmanager
|
|
def patch_discovered_controllers(
|
|
controllers: Mock | dict[str, Mock] | Iterable[Mock],
|
|
) -> Generator[tuple[AsyncMock, AsyncMock]]:
|
|
"""Patch discovery helpers using mock controllers' uid/ip."""
|
|
if isinstance(controllers, dict):
|
|
ctrl_list = list(controllers.values())
|
|
elif isinstance(controllers, Mock):
|
|
ctrl_list = [controllers]
|
|
else:
|
|
ctrl_list = list(controllers)
|
|
endpoints = {
|
|
controller.device_uid: endpoint_from_controller(controller)
|
|
for controller in ctrl_list
|
|
}
|
|
|
|
async def _discover_all(
|
|
hass: HomeAssistant,
|
|
) -> dict[str, ControllerEndpoint]:
|
|
return dict(endpoints)
|
|
|
|
async def _discover_one(hass: HomeAssistant, uid: str) -> ControllerEndpoint | None:
|
|
return endpoints.get(uid)
|
|
|
|
mock_discover_all = AsyncMock(side_effect=_discover_all)
|
|
mock_discover_one = AsyncMock(side_effect=_discover_one)
|
|
with (
|
|
patch(
|
|
"homeassistant.components.izone.discovery.async_discover_all_endpoints",
|
|
new=mock_discover_all,
|
|
),
|
|
patch(
|
|
"homeassistant.components.izone.discovery.async_discover_endpoint",
|
|
new=mock_discover_one,
|
|
),
|
|
):
|
|
yield mock_discover_all, mock_discover_one
|
|
|
|
|
|
async def async_load_yaml_exclude(hass: HomeAssistant, *uids: str) -> None:
|
|
"""Load deprecated YAML exclude config through the integration setup path."""
|
|
with (
|
|
patch(
|
|
"homeassistant.components.izone.async_setup_entry",
|
|
return_value=True,
|
|
),
|
|
patch(
|
|
"homeassistant.components.izone.discovery.async_ensure_discovery",
|
|
new=AsyncMock(return_value=Mock(spec=DiscoveryService)),
|
|
),
|
|
):
|
|
assert await async_setup_component(
|
|
hass, DOMAIN, {DOMAIN: {CONF_EXCLUDE: list(uids)}}
|
|
)
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_entry_setup() -> Generator[None]:
|
|
"""Skip full entry setup for config-flow create-entry tests."""
|
|
with patch(
|
|
"homeassistant.components.izone.async_setup_entry",
|
|
return_value=True,
|
|
):
|
|
yield
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_zones() -> list[Mock]:
|
|
"""Create a list of mock zones."""
|
|
return [create_mock_zone(index=0, name="Living Room")]
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_controller(mock_zones: list[Mock]) -> Mock:
|
|
"""Create a mock controller with zones attached."""
|
|
controller = create_mock_controller(
|
|
device_uid="000000001",
|
|
device_ip="192.0.2.1",
|
|
ras_mode="master",
|
|
zone_ctrl=1,
|
|
zones_total=1,
|
|
)
|
|
controller.zones = mock_zones
|
|
return controller
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_discovery_service(mock_controller: Mock) -> Mock:
|
|
"""Return a mock pizone DiscoveryService wired for entry setup."""
|
|
service = Mock(spec=DiscoveryService)
|
|
service.scan = AsyncMock()
|
|
service.close = AsyncMock()
|
|
service.discover_all = AsyncMock(
|
|
return_value=[endpoint_from_controller(mock_controller)]
|
|
)
|
|
service.discover_by_uid = AsyncMock(
|
|
return_value=endpoint_from_controller(mock_controller)
|
|
)
|
|
service.create_controller = AsyncMock(return_value=mock_controller)
|
|
|
|
def dump_state() -> dict:
|
|
return {
|
|
"closed": False,
|
|
"udp_bound": True,
|
|
"claimed": [
|
|
{
|
|
"uid": mock_controller.device_uid,
|
|
"host": mock_controller.device_ip,
|
|
}
|
|
],
|
|
"known": [],
|
|
"recent_udp": [
|
|
{
|
|
"source_ip": mock_controller.device_ip,
|
|
"source_port": 12107,
|
|
"received_at": "2026-07-25T14:37:00.257385+00:00",
|
|
"message": (
|
|
f"ASPort_12107,Mac_{mock_controller.device_uid},"
|
|
f"IP_{mock_controller.device_ip},iZoneV2"
|
|
),
|
|
"uid": mock_controller.device_uid,
|
|
"host": mock_controller.device_ip,
|
|
"tags": ["iZoneV2"],
|
|
}
|
|
],
|
|
}
|
|
|
|
service.dump_state = dump_state
|
|
return service
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_create_discovery(
|
|
mock_discovery_service: Mock,
|
|
) -> Generator[AsyncMock]:
|
|
"""Patch pizone.create_discovery to return the mock discovery service."""
|
|
with patch(
|
|
"homeassistant.components.izone.discovery.pizone.create_discovery",
|
|
new=AsyncMock(return_value=mock_discovery_service),
|
|
) as mock_create:
|
|
yield mock_create
|
|
|
|
|
|
@pytest.fixture
|
|
def platforms() -> list[Platform]:
|
|
"""Platforms to load in integration tests."""
|
|
return [Platform.CLIMATE]
|
|
|
|
|
|
@pytest.fixture
|
|
async def init_integration(
|
|
hass: HomeAssistant,
|
|
mock_config_entry: MockConfigEntry,
|
|
mock_create_discovery: AsyncMock,
|
|
platforms: list[Platform],
|
|
) -> AsyncGenerator[MockConfigEntry]:
|
|
"""Set up the iZone integration with a mocked discovery/controller stack."""
|
|
mock_config_entry.add_to_hass(hass)
|
|
|
|
with patch("homeassistant.components.izone.PLATFORMS", platforms):
|
|
assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
|
await hass.async_block_till_done()
|
|
yield mock_config_entry
|