Files
core/tests/components/izone/conftest.py
T

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