mirror of https://github.com/home-assistant/core
312 lines
10 KiB
Python
312 lines
10 KiB
Python
"""Tests for ZHA integration init."""
|
|
|
|
import asyncio
|
|
import typing
|
|
from unittest.mock import AsyncMock, Mock, patch
|
|
import zoneinfo
|
|
|
|
import pytest
|
|
from zigpy.application import ControllerApplication
|
|
from zigpy.config import CONF_DEVICE, CONF_DEVICE_PATH
|
|
from zigpy.exceptions import TransientConnectionError
|
|
|
|
from homeassistant.components.zha.const import (
|
|
CONF_BAUDRATE,
|
|
CONF_FLOW_CONTROL,
|
|
CONF_RADIO_TYPE,
|
|
CONF_USB_PATH,
|
|
DOMAIN,
|
|
)
|
|
from homeassistant.components.zha.helpers import get_zha_data, get_zha_gateway
|
|
from homeassistant.const import (
|
|
EVENT_HOMEASSISTANT_STOP,
|
|
MAJOR_VERSION,
|
|
MINOR_VERSION,
|
|
Platform,
|
|
)
|
|
from homeassistant.core import CoreState, HomeAssistant
|
|
from homeassistant.helpers.event import async_call_later
|
|
from homeassistant.setup import async_setup_component
|
|
|
|
from .test_light import LIGHT_ON_OFF
|
|
|
|
from tests.common import MockConfigEntry
|
|
|
|
DATA_RADIO_TYPE = "ezsp"
|
|
DATA_PORT_PATH = "/dev/serial/by-id/FTDI_USB__-__Serial_Cable_12345678-if00-port0"
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def disable_platform_only():
|
|
"""Disable platforms to speed up tests."""
|
|
with patch("homeassistant.components.zha.PLATFORMS", []):
|
|
yield
|
|
|
|
|
|
@pytest.fixture
|
|
def config_entry_v1(hass: HomeAssistant):
|
|
"""Config entry version 1 fixture."""
|
|
return MockConfigEntry(
|
|
domain=DOMAIN,
|
|
data={CONF_RADIO_TYPE: DATA_RADIO_TYPE, CONF_USB_PATH: DATA_PORT_PATH},
|
|
version=1,
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize("config", [{}, {DOMAIN: {}}])
|
|
@patch("homeassistant.components.zha.async_setup_entry", AsyncMock(return_value=True))
|
|
async def test_migration_from_v1_no_baudrate(
|
|
hass: HomeAssistant, config_entry_v1, config
|
|
) -> None:
|
|
"""Test migration of config entry from v1."""
|
|
config_entry_v1.add_to_hass(hass)
|
|
assert await async_setup_component(hass, DOMAIN, config)
|
|
|
|
assert config_entry_v1.data[CONF_RADIO_TYPE] == DATA_RADIO_TYPE
|
|
assert CONF_DEVICE in config_entry_v1.data
|
|
assert config_entry_v1.data[CONF_DEVICE][CONF_DEVICE_PATH] == DATA_PORT_PATH
|
|
assert CONF_USB_PATH not in config_entry_v1.data
|
|
assert config_entry_v1.version == 4
|
|
|
|
|
|
@patch("homeassistant.components.zha.async_setup_entry", AsyncMock(return_value=True))
|
|
async def test_migration_from_v1_with_baudrate(
|
|
hass: HomeAssistant, config_entry_v1
|
|
) -> None:
|
|
"""Test migration of config entry from v1 with baudrate in config."""
|
|
config_entry_v1.add_to_hass(hass)
|
|
assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_BAUDRATE: 115200}})
|
|
|
|
assert config_entry_v1.data[CONF_RADIO_TYPE] == DATA_RADIO_TYPE
|
|
assert CONF_DEVICE in config_entry_v1.data
|
|
assert config_entry_v1.data[CONF_DEVICE][CONF_DEVICE_PATH] == DATA_PORT_PATH
|
|
assert CONF_USB_PATH not in config_entry_v1.data
|
|
assert CONF_BAUDRATE in config_entry_v1.data[CONF_DEVICE]
|
|
assert config_entry_v1.data[CONF_DEVICE][CONF_BAUDRATE] == 115200
|
|
assert config_entry_v1.version == 4
|
|
|
|
|
|
@patch("homeassistant.components.zha.async_setup_entry", AsyncMock(return_value=True))
|
|
async def test_migration_from_v1_wrong_baudrate(
|
|
hass: HomeAssistant, config_entry_v1
|
|
) -> None:
|
|
"""Test migration of config entry from v1 with wrong baudrate."""
|
|
config_entry_v1.add_to_hass(hass)
|
|
assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_BAUDRATE: 115222}})
|
|
|
|
assert config_entry_v1.data[CONF_RADIO_TYPE] == DATA_RADIO_TYPE
|
|
assert CONF_DEVICE in config_entry_v1.data
|
|
assert config_entry_v1.data[CONF_DEVICE][CONF_DEVICE_PATH] == DATA_PORT_PATH
|
|
assert CONF_USB_PATH not in config_entry_v1.data
|
|
assert config_entry_v1.version == 4
|
|
|
|
|
|
@pytest.mark.skipif(
|
|
MAJOR_VERSION != 0 or (MAJOR_VERSION == 0 and MINOR_VERSION >= 112),
|
|
reason="Not applicaable for this version",
|
|
)
|
|
@pytest.mark.parametrize(
|
|
"zha_config",
|
|
[
|
|
{},
|
|
{CONF_USB_PATH: "str"},
|
|
{CONF_RADIO_TYPE: "ezsp"},
|
|
{CONF_RADIO_TYPE: "ezsp", CONF_USB_PATH: "str"},
|
|
],
|
|
)
|
|
async def test_config_depreciation(hass: HomeAssistant, zha_config) -> None:
|
|
"""Test config option depreciation."""
|
|
|
|
with patch(
|
|
"homeassistant.components.zha.async_setup", return_value=True
|
|
) as setup_mock:
|
|
assert await async_setup_component(hass, DOMAIN, {DOMAIN: zha_config})
|
|
assert setup_mock.call_count == 1
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("path", "cleaned_path"),
|
|
[
|
|
# No corrections
|
|
("/dev/path1", "/dev/path1"),
|
|
("/dev/path1[asd]", "/dev/path1[asd]"),
|
|
("/dev/path1 ", "/dev/path1 "),
|
|
("socket://1.2.3.4:5678", "socket://1.2.3.4:5678"),
|
|
# Brackets around URI
|
|
("socket://[1.2.3.4]:5678", "socket://1.2.3.4:5678"),
|
|
# Spaces
|
|
("socket://dev/path1 ", "socket://dev/path1"),
|
|
# Both
|
|
("socket://[1.2.3.4]:5678 ", "socket://1.2.3.4:5678"),
|
|
],
|
|
)
|
|
@patch(
|
|
"homeassistant.components.zha.websocket_api.async_load_api", Mock(return_value=True)
|
|
)
|
|
async def test_setup_with_v3_cleaning_uri(
|
|
hass: HomeAssistant,
|
|
path: str,
|
|
cleaned_path: str,
|
|
mock_zigpy_connect: ControllerApplication,
|
|
) -> None:
|
|
"""Test migration of config entry from v3, applying corrections to the port path."""
|
|
config_entry_v4 = MockConfigEntry(
|
|
domain=DOMAIN,
|
|
data={
|
|
CONF_RADIO_TYPE: DATA_RADIO_TYPE,
|
|
CONF_DEVICE: {
|
|
CONF_DEVICE_PATH: path,
|
|
CONF_BAUDRATE: 115200,
|
|
CONF_FLOW_CONTROL: None,
|
|
},
|
|
},
|
|
version=4,
|
|
)
|
|
config_entry_v4.add_to_hass(hass)
|
|
|
|
await hass.config_entries.async_setup(config_entry_v4.entry_id)
|
|
await hass.async_block_till_done()
|
|
await hass.config_entries.async_unload(config_entry_v4.entry_id)
|
|
|
|
assert config_entry_v4.data[CONF_RADIO_TYPE] == DATA_RADIO_TYPE
|
|
assert config_entry_v4.data[CONF_DEVICE][CONF_DEVICE_PATH] == cleaned_path
|
|
assert config_entry_v4.version == 4
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
(
|
|
"radio_type",
|
|
"old_baudrate",
|
|
"old_flow_control",
|
|
"new_baudrate",
|
|
"new_flow_control",
|
|
),
|
|
[
|
|
("znp", None, None, 115200, None),
|
|
("znp", None, "software", 115200, "software"),
|
|
("znp", 57600, "software", 57600, "software"),
|
|
("deconz", None, None, 38400, None),
|
|
("deconz", 115200, None, 115200, None),
|
|
],
|
|
)
|
|
@patch("homeassistant.components.zha.async_setup_entry", AsyncMock(return_value=True))
|
|
async def test_migration_baudrate_and_flow_control(
|
|
radio_type: str,
|
|
old_baudrate: int,
|
|
old_flow_control: typing.Literal["hardware", "software", None],
|
|
new_baudrate: int,
|
|
new_flow_control: typing.Literal["hardware", "software", None],
|
|
hass: HomeAssistant,
|
|
config_entry: MockConfigEntry,
|
|
) -> None:
|
|
"""Test baudrate and flow control migration."""
|
|
|
|
config_entry.add_to_hass(hass)
|
|
hass.config_entries.async_update_entry(
|
|
config_entry,
|
|
data={
|
|
**config_entry.data,
|
|
CONF_RADIO_TYPE: radio_type,
|
|
CONF_DEVICE: {
|
|
CONF_BAUDRATE: old_baudrate,
|
|
CONF_FLOW_CONTROL: old_flow_control,
|
|
CONF_DEVICE_PATH: "/dev/null",
|
|
},
|
|
},
|
|
version=3,
|
|
)
|
|
|
|
await hass.config_entries.async_setup(config_entry.entry_id)
|
|
await hass.async_block_till_done()
|
|
|
|
assert config_entry.version > 3
|
|
assert config_entry.data[CONF_DEVICE][CONF_BAUDRATE] == new_baudrate
|
|
assert config_entry.data[CONF_DEVICE][CONF_FLOW_CONTROL] == new_flow_control
|
|
|
|
|
|
@patch(
|
|
"homeassistant.components.zha.PLATFORMS",
|
|
[Platform.LIGHT, Platform.BUTTON, Platform.SENSOR, Platform.SELECT],
|
|
)
|
|
async def test_zha_retry_unique_ids(
|
|
hass: HomeAssistant,
|
|
config_entry: MockConfigEntry,
|
|
zigpy_device_mock,
|
|
mock_zigpy_connect: ControllerApplication,
|
|
caplog: pytest.LogCaptureFixture,
|
|
) -> None:
|
|
"""Test that ZHA retrying creates unique entity IDs."""
|
|
|
|
config_entry.add_to_hass(hass)
|
|
|
|
# Ensure we have some device to try to load
|
|
app = mock_zigpy_connect
|
|
light = zigpy_device_mock(LIGHT_ON_OFF)
|
|
app.devices[light.ieee] = light
|
|
|
|
# Re-try setup but have it fail once, so entities have two chances to be created
|
|
with patch.object(
|
|
app,
|
|
"startup",
|
|
side_effect=[TransientConnectionError(), None],
|
|
) as mock_connect:
|
|
with patch(
|
|
"homeassistant.config_entries.async_call_later",
|
|
lambda hass, delay, action: async_call_later(hass, 0.01, action),
|
|
):
|
|
await hass.config_entries.async_setup(config_entry.entry_id)
|
|
await hass.async_block_till_done(wait_background_tasks=True)
|
|
|
|
# Wait for the config entry setup to retry
|
|
await asyncio.sleep(0.1)
|
|
await hass.async_block_till_done(wait_background_tasks=True)
|
|
|
|
assert len(mock_connect.mock_calls) == 2
|
|
|
|
await hass.config_entries.async_unload(config_entry.entry_id)
|
|
|
|
assert "does not generate unique IDs" not in caplog.text
|
|
|
|
|
|
async def test_shutdown_on_ha_stop(
|
|
hass: HomeAssistant,
|
|
config_entry: MockConfigEntry,
|
|
mock_zigpy_connect: ControllerApplication,
|
|
) -> None:
|
|
"""Test that the ZHA gateway is stopped when HA is shut down."""
|
|
config_entry.add_to_hass(hass)
|
|
|
|
await hass.config_entries.async_setup(config_entry.entry_id)
|
|
await hass.async_block_till_done()
|
|
|
|
zha_data = get_zha_data(hass)
|
|
|
|
with patch.object(
|
|
zha_data.gateway_proxy, "shutdown", wraps=zha_data.gateway_proxy.shutdown
|
|
) as mock_shutdown:
|
|
hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP)
|
|
hass.set_state(CoreState.stopping)
|
|
await hass.async_block_till_done()
|
|
|
|
assert len(mock_shutdown.mock_calls) == 1
|
|
|
|
|
|
async def test_timezone_update(
|
|
hass: HomeAssistant,
|
|
config_entry: MockConfigEntry,
|
|
mock_zigpy_connect: ControllerApplication,
|
|
) -> None:
|
|
"""Test that the ZHA gateway timezone is updated when HA timezone changes."""
|
|
config_entry.add_to_hass(hass)
|
|
|
|
await hass.config_entries.async_setup(config_entry.entry_id)
|
|
gateway = get_zha_gateway(hass)
|
|
|
|
assert hass.config.time_zone == "US/Pacific"
|
|
assert gateway.config.local_timezone == zoneinfo.ZoneInfo("US/Pacific")
|
|
|
|
await hass.config.async_update(time_zone="America/New_York")
|
|
|
|
assert hass.config.time_zone == "America/New_York"
|
|
assert gateway.config.local_timezone == zoneinfo.ZoneInfo("America/New_York")
|