core/tests/components/lifx/test_migration.py

286 lines
10 KiB
Python

"""Tests the lifx migration."""
from __future__ import annotations
from datetime import timedelta
from unittest.mock import patch
from homeassistant import setup
from homeassistant.components import lifx
from homeassistant.components.lifx import DOMAIN, discovery
from homeassistant.const import CONF_HOST, EVENT_HOMEASSISTANT_STARTED
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.helpers.device_registry import DeviceRegistry
from homeassistant.helpers.entity_registry import EntityRegistry
from homeassistant.setup import async_setup_component
from homeassistant.util import dt as dt_util
from . import (
IP_ADDRESS,
LABEL,
MAC_ADDRESS,
SERIAL,
_mocked_bulb,
_patch_config_flow_try_connect,
_patch_device,
_patch_discovery,
)
from tests.common import MockConfigEntry, async_fire_time_changed
async def test_migration_device_online_end_to_end(
hass: HomeAssistant, device_reg: DeviceRegistry, entity_reg: EntityRegistry
) -> None:
"""Test migration from single config entry."""
config_entry = MockConfigEntry(
domain=DOMAIN, title="LEGACY", data={}, unique_id=DOMAIN
)
config_entry.add_to_hass(hass)
device = device_reg.async_get_or_create(
config_entry_id=config_entry.entry_id,
identifiers={(DOMAIN, SERIAL)},
connections={(dr.CONNECTION_NETWORK_MAC, MAC_ADDRESS)},
name=LABEL,
)
light_entity_reg = entity_reg.async_get_or_create(
config_entry=config_entry,
platform=DOMAIN,
domain="light",
unique_id=dr.format_mac(SERIAL),
original_name=LABEL,
device_id=device.id,
)
with _patch_discovery(), _patch_config_flow_try_connect(), _patch_device():
await setup.async_setup_component(hass, DOMAIN, {})
await hass.async_block_till_done()
migrated_entry = None
for entry in hass.config_entries.async_entries(DOMAIN):
if entry.unique_id == DOMAIN:
migrated_entry = entry
break
assert migrated_entry is not None
assert device.config_entries == {migrated_entry.entry_id}
assert light_entity_reg.config_entry_id == migrated_entry.entry_id
assert er.async_entries_for_config_entry(entity_reg, config_entry) == []
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
await hass.async_block_till_done()
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=20))
await hass.async_block_till_done()
legacy_entry = None
for entry in hass.config_entries.async_entries(DOMAIN):
if entry.unique_id == DOMAIN:
legacy_entry = entry
break
assert legacy_entry is None
async def test_discovery_is_more_frequent_during_migration(
hass: HomeAssistant, device_reg: DeviceRegistry, entity_reg: EntityRegistry
) -> None:
"""Test that discovery is more frequent during migration."""
config_entry = MockConfigEntry(
domain=DOMAIN, title="LEGACY", data={}, unique_id=DOMAIN
)
config_entry.add_to_hass(hass)
device = device_reg.async_get_or_create(
config_entry_id=config_entry.entry_id,
identifiers={(DOMAIN, SERIAL)},
connections={(dr.CONNECTION_NETWORK_MAC, MAC_ADDRESS)},
name=LABEL,
)
entity_reg.async_get_or_create(
config_entry=config_entry,
platform=DOMAIN,
domain="light",
unique_id=dr.format_mac(SERIAL),
original_name=LABEL,
device_id=device.id,
)
bulb = _mocked_bulb()
start_calls = 0
class MockLifxDiscovery:
"""Mock lifx discovery."""
def __init__(self, *args, **kwargs):
"""Init discovery."""
self.bulb = bulb
self.lights = {}
def start(self):
"""Mock start."""
nonlocal start_calls
start_calls += 1
# Discover the bulb so we can complete migration
# and verify we switch back to normal discovery
# interval
if start_calls == 4:
self.lights = {self.bulb.mac_addr: self.bulb}
def cleanup(self):
"""Mock cleanup."""
with (
_patch_device(device=bulb),
_patch_config_flow_try_connect(device=bulb),
patch.object(discovery, "DEFAULT_TIMEOUT", 0),
patch(
"homeassistant.components.lifx.discovery.LifxDiscovery", MockLifxDiscovery
),
):
await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}})
await hass.async_block_till_done()
assert start_calls == 0
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
await hass.async_block_till_done()
assert start_calls == 1
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=5))
await hass.async_block_till_done(wait_background_tasks=True)
assert start_calls == 3
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=10))
await hass.async_block_till_done(wait_background_tasks=True)
assert start_calls == 4
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=15))
await hass.async_block_till_done(wait_background_tasks=True)
assert start_calls == 5
async def test_migration_device_online_end_to_end_after_downgrade(
hass: HomeAssistant, device_reg: DeviceRegistry, entity_reg: EntityRegistry
) -> None:
"""Test migration from single config entry can happen again after a downgrade."""
config_entry = MockConfigEntry(
domain=DOMAIN, title="LEGACY", data={}, unique_id=DOMAIN
)
config_entry.add_to_hass(hass)
already_migrated_config_entry = MockConfigEntry(
domain=DOMAIN, data={CONF_HOST: IP_ADDRESS}, unique_id=SERIAL
)
already_migrated_config_entry.add_to_hass(hass)
device = device_reg.async_get_or_create(
config_entry_id=config_entry.entry_id,
identifiers={(DOMAIN, SERIAL)},
connections={(dr.CONNECTION_NETWORK_MAC, MAC_ADDRESS)},
name=LABEL,
)
light_entity_reg = entity_reg.async_get_or_create(
config_entry=config_entry,
platform=DOMAIN,
domain="light",
unique_id=SERIAL,
original_name=LABEL,
device_id=device.id,
)
with _patch_discovery(), _patch_config_flow_try_connect(), _patch_device():
await setup.async_setup_component(hass, DOMAIN, {})
await hass.async_block_till_done()
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
await hass.async_block_till_done()
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=20))
await hass.async_block_till_done()
assert device.config_entries == {config_entry.entry_id}
assert light_entity_reg.config_entry_id == config_entry.entry_id
assert er.async_entries_for_config_entry(entity_reg, config_entry) == []
legacy_entry = None
for entry in hass.config_entries.async_entries(DOMAIN):
if entry.unique_id == DOMAIN:
legacy_entry = entry
break
assert legacy_entry is None
async def test_migration_device_online_end_to_end_ignores_other_devices(
hass: HomeAssistant, device_reg: DeviceRegistry, entity_reg: EntityRegistry
) -> None:
"""Test migration from single config entry."""
legacy_config_entry = MockConfigEntry(
domain=DOMAIN, title="LEGACY", data={}, unique_id=DOMAIN
)
legacy_config_entry.add_to_hass(hass)
other_domain_config_entry = MockConfigEntry(
domain="other_domain", data={}, unique_id="other_domain"
)
other_domain_config_entry.add_to_hass(hass)
device = device_reg.async_get_or_create(
config_entry_id=legacy_config_entry.entry_id,
identifiers={(DOMAIN, SERIAL)},
connections={(dr.CONNECTION_NETWORK_MAC, MAC_ADDRESS)},
name=LABEL,
)
other_device = device_reg.async_get_or_create(
config_entry_id=other_domain_config_entry.entry_id,
connections={(dr.CONNECTION_NETWORK_MAC, "556655665566")},
name=LABEL,
)
light_entity_reg = entity_reg.async_get_or_create(
config_entry=legacy_config_entry,
platform=DOMAIN,
domain="light",
unique_id=SERIAL,
original_name=LABEL,
device_id=device.id,
)
ignored_entity_reg = entity_reg.async_get_or_create(
config_entry=other_domain_config_entry,
platform=DOMAIN,
domain="sensor",
unique_id="00:00:00:00:00:00_sensor",
original_name=LABEL,
device_id=device.id,
)
garbage_entity_reg = entity_reg.async_get_or_create(
config_entry=legacy_config_entry,
platform=DOMAIN,
domain="sensor",
unique_id="garbage",
original_name=LABEL,
device_id=other_device.id,
)
with _patch_discovery(), _patch_config_flow_try_connect(), _patch_device():
await setup.async_setup_component(hass, DOMAIN, {})
await hass.async_block_till_done()
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
await hass.async_block_till_done()
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=20))
await hass.async_block_till_done()
new_entry = None
legacy_entry = None
for entry in hass.config_entries.async_entries(DOMAIN):
if entry.unique_id == DOMAIN:
legacy_entry = entry
else:
new_entry = entry
assert new_entry is not None
assert legacy_entry is None
assert device.config_entries == {legacy_config_entry.entry_id}
assert light_entity_reg.config_entry_id == legacy_config_entry.entry_id
assert ignored_entity_reg.config_entry_id == other_domain_config_entry.entry_id
assert garbage_entity_reg.config_entry_id == legacy_config_entry.entry_id
assert er.async_entries_for_config_entry(entity_reg, legacy_config_entry) == []
assert dr.async_entries_for_config_entry(device_reg, legacy_config_entry) == []