"""Tests for the lifx component.""" from __future__ import annotations from datetime import timedelta import socket from unittest.mock import patch import pytest from homeassistant.components import lifx from homeassistant.components.lifx import DOMAIN, discovery from homeassistant.config_entries import ConfigEntryState from homeassistant.const import CONF_HOST, EVENT_HOMEASSISTANT_STARTED from homeassistant.core import HomeAssistant from homeassistant.setup import async_setup_component from homeassistant.util import dt as dt_util from . import ( IP_ADDRESS, SERIAL, MockFailingLifxCommand, _mocked_bulb, _mocked_failing_bulb, _patch_config_flow_try_connect, _patch_device, _patch_discovery, ) from tests.common import MockConfigEntry, async_fire_time_changed async def test_configuring_lifx_causes_discovery(hass: HomeAssistant) -> None: """Test that specifying empty config does discovery.""" start_calls = 0 class MockLifxDiscovery: """Mock lifx discovery.""" def __init__(self, *args, **kwargs): """Init discovery.""" discovered = _mocked_bulb() self.lights = {discovered.mac_addr: discovered} def start(self): """Mock start.""" nonlocal start_calls start_calls += 1 def cleanup(self): """Mock cleanup.""" with ( _patch_config_flow_try_connect(), 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 == 2 async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=15)) await hass.async_block_till_done(wait_background_tasks=True) assert start_calls == 3 async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=30)) await hass.async_block_till_done(wait_background_tasks=True) assert start_calls == 4 async def test_config_entry_reload(hass: HomeAssistant) -> None: """Test that a config entry can be reloaded.""" already_migrated_config_entry = MockConfigEntry( domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=SERIAL ) already_migrated_config_entry.add_to_hass(hass) with _patch_discovery(), _patch_config_flow_try_connect(), _patch_device(): await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}}) await hass.async_block_till_done() assert already_migrated_config_entry.state is ConfigEntryState.LOADED await hass.config_entries.async_unload(already_migrated_config_entry.entry_id) await hass.async_block_till_done() assert already_migrated_config_entry.state is ConfigEntryState.NOT_LOADED async def test_config_entry_retry(hass: HomeAssistant) -> None: """Test that a config entry can be retried.""" already_migrated_config_entry = MockConfigEntry( domain=DOMAIN, data={CONF_HOST: IP_ADDRESS}, unique_id=SERIAL ) already_migrated_config_entry.add_to_hass(hass) with ( _patch_discovery(no_device=True), _patch_config_flow_try_connect(no_device=True), _patch_device(no_device=True), ): await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}}) await hass.async_block_till_done() assert already_migrated_config_entry.state is ConfigEntryState.SETUP_RETRY async def test_get_version_fails(hass: HomeAssistant) -> None: """Test we handle get version failing.""" already_migrated_config_entry = MockConfigEntry( domain=DOMAIN, data={CONF_HOST: IP_ADDRESS}, unique_id=SERIAL ) already_migrated_config_entry.add_to_hass(hass) bulb = _mocked_bulb() bulb.product = None bulb.host_firmware_version = None bulb.get_version = MockFailingLifxCommand(bulb) with _patch_discovery(device=bulb), _patch_device(device=bulb): await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}}) await hass.async_block_till_done() assert already_migrated_config_entry.state is ConfigEntryState.SETUP_RETRY async def test_dns_error_at_startup(hass: HomeAssistant) -> None: """Test we handle get version failing.""" already_migrated_config_entry = MockConfigEntry( domain=DOMAIN, data={CONF_HOST: IP_ADDRESS}, unique_id=SERIAL ) already_migrated_config_entry.add_to_hass(hass) bulb = _mocked_failing_bulb() class MockLifxConnectonDnsError: """Mock lifx connection with a dns error.""" def __init__(self, *args, **kwargs): """Init connection.""" self.device = bulb async def async_setup(self): """Mock setup.""" raise socket.gaierror def async_stop(self): """Mock teardown.""" # Cannot connect due to dns error with ( _patch_discovery(device=bulb), patch( "homeassistant.components.lifx.LIFXConnection", MockLifxConnectonDnsError, ), ): await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}}) await hass.async_block_till_done() assert already_migrated_config_entry.state is ConfigEntryState.SETUP_RETRY async def test_config_entry_wrong_serial( hass: HomeAssistant, caplog: pytest.LogCaptureFixture ) -> None: """Test config entry enters setup retry when serial mismatches.""" mismatched_serial = f"{SERIAL[:-1]}0" already_migrated_config_entry = MockConfigEntry( domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=mismatched_serial ) already_migrated_config_entry.add_to_hass(hass) with _patch_discovery(), _patch_config_flow_try_connect(), _patch_device(): await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}}) await hass.async_block_till_done() assert already_migrated_config_entry.state is ConfigEntryState.SETUP_RETRY assert ( "Unexpected device found at 127.0.0.1; expected aa:bb:cc:dd:ee:c0, found aa:bb:cc:dd:ee:cc" in caplog.text )