"""Test the WiZ Platform config flow.""" from contextlib import contextmanager from copy import deepcopy from unittest.mock import patch import pytest from pywizlight.discovery import DiscoveredBulb from pywizlight.exceptions import WizLightConnectionError, WizLightTimeOutError from homeassistant import config_entries from homeassistant.components import dhcp from homeassistant.components.wiz.config_flow import CONF_DEVICE from homeassistant.components.wiz.const import DOMAIN from homeassistant.const import CONF_HOST from homeassistant.data_entry_flow import RESULT_TYPE_ABORT, RESULT_TYPE_FORM from tests.common import MockConfigEntry FAKE_IP = "1.1.1.1" FAKE_MAC = "ABCABCABCABC" FAKE_BULB_CONFIG = { "method": "getSystemConfig", "env": "pro", "result": { "mac": FAKE_MAC, "homeId": 653906, "roomId": 989983, "moduleName": "ESP_0711_STR", "fwVersion": "1.21.0", "groupId": 0, "drvConf": [20, 2], "ewf": [255, 0, 255, 255, 0, 0, 0], "ewfHex": "ff00ffff000000", "ping": 0, }, } FAKE_SOCKET_CONFIG = deepcopy(FAKE_BULB_CONFIG) FAKE_SOCKET_CONFIG["result"]["moduleName"] = "ESP10_SOCKET_06" FAKE_EXTENDED_WHITE_RANGE = [2200, 2700, 6500, 6500] TEST_SYSTEM_INFO = {"id": FAKE_MAC, "name": "Test Bulb"} TEST_CONNECTION = {CONF_HOST: "1.1.1.1"} TEST_NO_IP = {CONF_HOST: "this is no IP input"} DHCP_DISCOVERY = dhcp.DhcpServiceInfo( hostname="wiz_abcabc", ip=FAKE_IP, macaddress=FAKE_MAC, ) INTEGRATION_DISCOVERY = { "ip_address": FAKE_IP, "mac_address": FAKE_MAC, } def _patch_wizlight(device=None, extended_white_range=None): @contextmanager def _patcher(): with patch( "homeassistant.components.wiz.wizlight.getBulbConfig", return_value=device or FAKE_BULB_CONFIG, ), patch( "homeassistant.components.wiz.wizlight.getExtendedWhiteRange", return_value=extended_white_range or FAKE_EXTENDED_WHITE_RANGE, ), patch( "homeassistant.components.wiz.wizlight.getMac", return_value=FAKE_MAC, ): yield return _patcher() def _patch_discovery(): @contextmanager def _patcher(): with patch( "homeassistant.components.wiz.discovery.find_wizlights", return_value=[DiscoveredBulb(FAKE_IP, FAKE_MAC)], ): yield return _patcher() async def test_form(hass): """Test we get the form.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} ) assert result["type"] == "form" assert result["errors"] == {} # Patch functions with _patch_wizlight(), patch( "homeassistant.components.wiz.async_setup_entry", return_value=True, ) as mock_setup_entry, patch( "homeassistant.components.wiz.async_setup", return_value=True ) as mock_setup: result2 = await hass.config_entries.flow.async_configure( result["flow_id"], TEST_CONNECTION, ) await hass.async_block_till_done() assert result2["type"] == "create_entry" assert result2["title"] == "WiZ Dimmable White ABCABC" assert result2["data"] == { CONF_HOST: "1.1.1.1", } assert len(mock_setup.mock_calls) == 1 assert len(mock_setup_entry.mock_calls) == 1 @pytest.mark.parametrize( "side_effect, error_base", [ (WizLightTimeOutError, "bulb_time_out"), (WizLightConnectionError, "no_wiz_light"), (Exception, "unknown"), (ConnectionRefusedError, "cannot_connect"), ], ) async def test_user_form_exceptions(hass, side_effect, error_base): """Test all user exceptions in the flow.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} ) with patch( "homeassistant.components.wiz.wizlight.getBulbConfig", side_effect=side_effect, ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], TEST_CONNECTION, ) assert result2["type"] == "form" assert result2["errors"] == {"base": error_base} async def test_form_updates_unique_id(hass): """Test a duplicate id aborts and updates existing entry.""" entry = MockConfigEntry( domain=DOMAIN, unique_id=TEST_SYSTEM_INFO["id"], data={CONF_HOST: "dummy"}, ) entry.add_to_hass(hass) result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} ) with _patch_wizlight(): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], TEST_CONNECTION, ) await hass.async_block_till_done() assert result2["type"] == "abort" assert result2["reason"] == "already_configured" assert entry.data[CONF_HOST] == FAKE_IP @pytest.mark.parametrize( "source, data", [ (config_entries.SOURCE_DHCP, DHCP_DISCOVERY), (config_entries.SOURCE_INTEGRATION_DISCOVERY, INTEGRATION_DISCOVERY), ], ) async def test_discovered_by_dhcp_connection_fails(hass, source, data): """Test we abort on connection failure.""" with patch( "homeassistant.components.wiz.wizlight.getBulbConfig", side_effect=WizLightTimeOutError, ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": source}, data=data ) await hass.async_block_till_done() assert result["type"] == RESULT_TYPE_ABORT assert result["reason"] == "cannot_connect" @pytest.mark.parametrize( "source, data, device, extended_white_range, name", [ ( config_entries.SOURCE_DHCP, DHCP_DISCOVERY, FAKE_BULB_CONFIG, FAKE_EXTENDED_WHITE_RANGE, "WiZ Dimmable White ABCABC", ), ( config_entries.SOURCE_INTEGRATION_DISCOVERY, INTEGRATION_DISCOVERY, FAKE_BULB_CONFIG, FAKE_EXTENDED_WHITE_RANGE, "WiZ Dimmable White ABCABC", ), ( config_entries.SOURCE_DHCP, DHCP_DISCOVERY, FAKE_SOCKET_CONFIG, None, "WiZ Socket ABCABC", ), ( config_entries.SOURCE_INTEGRATION_DISCOVERY, INTEGRATION_DISCOVERY, FAKE_SOCKET_CONFIG, None, "WiZ Socket ABCABC", ), ], ) async def test_discovered_by_dhcp_or_integration_discovery( hass, source, data, device, extended_white_range, name ): """Test we can configure when discovered from dhcp or discovery.""" with _patch_wizlight(device=device, extended_white_range=extended_white_range): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": source}, data=data ) await hass.async_block_till_done() assert result["type"] == RESULT_TYPE_FORM assert result["step_id"] == "discovery_confirm" with patch( "homeassistant.components.wiz.async_setup_entry", return_value=True, ) as mock_setup_entry, patch( "homeassistant.components.wiz.async_setup", return_value=True ) as mock_setup: result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {}, ) await hass.async_block_till_done() assert result2["type"] == "create_entry" assert result2["title"] == name assert result2["data"] == { CONF_HOST: "1.1.1.1", } assert len(mock_setup.mock_calls) == 1 assert len(mock_setup_entry.mock_calls) == 1 @pytest.mark.parametrize( "source, data", [ (config_entries.SOURCE_DHCP, DHCP_DISCOVERY), (config_entries.SOURCE_INTEGRATION_DISCOVERY, INTEGRATION_DISCOVERY), ], ) async def test_discovered_by_dhcp_or_integration_discovery_updates_host( hass, source, data ): """Test dhcp or discovery updates existing host.""" entry = MockConfigEntry( domain=DOMAIN, unique_id=TEST_SYSTEM_INFO["id"], data={CONF_HOST: "dummy"}, ) entry.add_to_hass(hass) with _patch_wizlight(): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": source}, data=data ) await hass.async_block_till_done() assert result["type"] == RESULT_TYPE_ABORT assert result["reason"] == "already_configured" assert entry.data[CONF_HOST] == FAKE_IP async def test_setup_via_discovery(hass): """Test setting up via discovery.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} ) await hass.async_block_till_done() assert result["type"] == "form" assert result["step_id"] == "user" assert not result["errors"] with _patch_discovery(): result2 = await hass.config_entries.flow.async_configure(result["flow_id"], {}) await hass.async_block_till_done() assert result2["type"] == "form" assert result2["step_id"] == "pick_device" assert not result2["errors"] # test we can try again result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} ) assert result["type"] == "form" assert result["step_id"] == "user" assert not result["errors"] with _patch_discovery(): result2 = await hass.config_entries.flow.async_configure(result["flow_id"], {}) await hass.async_block_till_done() assert result2["type"] == "form" assert result2["step_id"] == "pick_device" assert not result2["errors"] with _patch_wizlight(), patch( "homeassistant.components.wiz.async_setup", return_value=True ) as mock_setup, patch( "homeassistant.components.wiz.async_setup_entry", return_value=True ) as mock_setup_entry: result3 = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_DEVICE: FAKE_MAC}, ) await hass.async_block_till_done() assert result3["type"] == "create_entry" assert result3["title"] == "WiZ Dimmable White ABCABC" assert result3["data"] == { CONF_HOST: "1.1.1.1", } assert len(mock_setup.mock_calls) == 1 assert len(mock_setup_entry.mock_calls) == 1 # ignore configured devices result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} ) assert result["type"] == "form" assert result["step_id"] == "user" assert not result["errors"] with _patch_discovery(): result2 = await hass.config_entries.flow.async_configure(result["flow_id"], {}) await hass.async_block_till_done() assert result2["type"] == "abort" assert result2["reason"] == "no_devices_found" async def test_setup_via_discovery_cannot_connect(hass): """Test setting up via discovery and we fail to connect to the discovered device.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} ) await hass.async_block_till_done() assert result["type"] == "form" assert result["step_id"] == "user" assert not result["errors"] with _patch_discovery(): result2 = await hass.config_entries.flow.async_configure(result["flow_id"], {}) await hass.async_block_till_done() assert result2["type"] == "form" assert result2["step_id"] == "pick_device" assert not result2["errors"] with patch( "homeassistant.components.wiz.wizlight.getBulbConfig", side_effect=WizLightTimeOutError, ), _patch_discovery(): result3 = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_DEVICE: FAKE_MAC}, ) await hass.async_block_till_done() assert result3["type"] == "abort" assert result3["reason"] == "cannot_connect"