"""Tests for the Config Entry Flow helper.""" from unittest.mock import patch, Mock import pytest from homeassistant import config_entries, data_entry_flow, setup from homeassistant.helpers import config_entry_flow from tests.common import ( MockConfigEntry, MockModule, mock_coro, mock_integration, mock_entity_platform, ) @pytest.fixture def discovery_flow_conf(hass): """Register a handler.""" handler_conf = {"discovered": False} async def has_discovered_devices(hass): """Mock if we have discovered devices.""" return handler_conf["discovered"] with patch.dict(config_entries.HANDLERS): config_entry_flow.register_discovery_flow( "test", "Test", has_discovered_devices, config_entries.CONN_CLASS_LOCAL_POLL ) yield handler_conf @pytest.fixture def webhook_flow_conf(hass): """Register a handler.""" with patch.dict(config_entries.HANDLERS): config_entry_flow.register_webhook_flow("test_single", "Test Single", {}, False) config_entry_flow.register_webhook_flow( "test_multiple", "Test Multiple", {}, True ) yield {} async def test_single_entry_allowed(hass, discovery_flow_conf): """Test only a single entry is allowed.""" flow = config_entries.HANDLERS["test"]() flow.hass = hass MockConfigEntry(domain="test").add_to_hass(hass) result = await flow.async_step_user() assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT assert result["reason"] == "single_instance_allowed" async def test_user_no_devices_found(hass, discovery_flow_conf): """Test if no devices found.""" flow = config_entries.HANDLERS["test"]() flow.hass = hass flow.context = {"source": config_entries.SOURCE_USER} result = await flow.async_step_confirm(user_input={}) assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT assert result["reason"] == "no_devices_found" async def test_user_has_confirmation(hass, discovery_flow_conf): """Test user requires no confirmation to setup.""" flow = config_entries.HANDLERS["test"]() flow.hass = hass discovery_flow_conf["discovered"] = True result = await flow.async_step_user() assert result["type"] == data_entry_flow.RESULT_TYPE_FORM @pytest.mark.parametrize("source", ["discovery", "ssdp", "zeroconf"]) async def test_discovery_single_instance(hass, discovery_flow_conf, source): """Test we not allow duplicates.""" flow = config_entries.HANDLERS["test"]() flow.hass = hass flow.context = {} MockConfigEntry(domain="test").add_to_hass(hass) result = await getattr(flow, "async_step_{}".format(source))({}) assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT assert result["reason"] == "single_instance_allowed" @pytest.mark.parametrize("source", ["discovery", "ssdp", "zeroconf"]) async def test_discovery_confirmation(hass, discovery_flow_conf, source): """Test we ask for confirmation via discovery.""" flow = config_entries.HANDLERS["test"]() flow.hass = hass flow.context = {} result = await getattr(flow, "async_step_{}".format(source))({}) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["step_id"] == "confirm" result = await flow.async_step_confirm({}) assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY async def test_multiple_discoveries(hass, discovery_flow_conf): """Test we only create one instance for multiple discoveries.""" mock_entity_platform(hass, "config_flow.test", None) result = await hass.config_entries.flow.async_init( "test", context={"source": config_entries.SOURCE_DISCOVERY}, data={} ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM # Second discovery result = await hass.config_entries.flow.async_init( "test", context={"source": config_entries.SOURCE_DISCOVERY}, data={} ) assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT async def test_only_one_in_progress(hass, discovery_flow_conf): """Test a user initialized one will finish and cancel discovered one.""" mock_entity_platform(hass, "config_flow.test", None) # Discovery starts flow result = await hass.config_entries.flow.async_init( "test", context={"source": config_entries.SOURCE_DISCOVERY}, data={} ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM # User starts flow result = await hass.config_entries.flow.async_init( "test", context={"source": config_entries.SOURCE_USER}, data={} ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM # Discovery flow has not been aborted assert len(hass.config_entries.flow.async_progress()) == 2 # Discovery should be aborted once user confirms result = await hass.config_entries.flow.async_configure(result["flow_id"], {}) assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY assert len(hass.config_entries.flow.async_progress()) == 0 async def test_import_no_confirmation(hass, discovery_flow_conf): """Test import requires no confirmation to set up.""" flow = config_entries.HANDLERS["test"]() flow.hass = hass discovery_flow_conf["discovered"] = True result = await flow.async_step_import(None) assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY async def test_import_single_instance(hass, discovery_flow_conf): """Test import doesn't create second instance.""" flow = config_entries.HANDLERS["test"]() flow.hass = hass discovery_flow_conf["discovered"] = True MockConfigEntry(domain="test").add_to_hass(hass) result = await flow.async_step_import(None) assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT async def test_webhook_single_entry_allowed(hass, webhook_flow_conf): """Test only a single entry is allowed.""" flow = config_entries.HANDLERS["test_single"]() flow.hass = hass MockConfigEntry(domain="test_single").add_to_hass(hass) result = await flow.async_step_user() assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT assert result["reason"] == "one_instance_allowed" async def test_webhook_multiple_entries_allowed(hass, webhook_flow_conf): """Test multiple entries are allowed when specified.""" flow = config_entries.HANDLERS["test_multiple"]() flow.hass = hass MockConfigEntry(domain="test_multiple").add_to_hass(hass) hass.config.api = Mock(base_url="http://example.com") result = await flow.async_step_user() assert result["type"] == data_entry_flow.RESULT_TYPE_FORM async def test_webhook_config_flow_registers_webhook(hass, webhook_flow_conf): """Test setting up an entry creates a webhook.""" flow = config_entries.HANDLERS["test_single"]() flow.hass = hass hass.config.api = Mock(base_url="http://example.com") result = await flow.async_step_user(user_input={}) assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY assert result["data"]["webhook_id"] is not None async def test_webhook_create_cloudhook(hass, webhook_flow_conf): """Test only a single entry is allowed.""" assert await setup.async_setup_component(hass, "cloud", {}) async_setup_entry = Mock(return_value=mock_coro(True)) async_unload_entry = Mock(return_value=mock_coro(True)) mock_integration( hass, MockModule( "test_single", async_setup_entry=async_setup_entry, async_unload_entry=async_unload_entry, async_remove_entry=config_entry_flow.webhook_async_remove_entry, ), ) mock_entity_platform(hass, "config_flow.test_single", None) result = await hass.config_entries.flow.async_init( "test_single", context={"source": config_entries.SOURCE_USER} ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM coro = mock_coro({"cloudhook_url": "https://example.com"}) with patch( "hass_nabucasa.cloudhooks.Cloudhooks.async_create", return_value=coro ) as mock_create, patch( "homeassistant.components.cloud.async_active_subscription", return_value=True ), patch( "homeassistant.components.cloud.async_is_logged_in", return_value=True ): result = await hass.config_entries.flow.async_configure(result["flow_id"], {}) assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY assert result["description_placeholders"]["webhook_url"] == "https://example.com" assert len(mock_create.mock_calls) == 1 assert len(async_setup_entry.mock_calls) == 1 with patch( "hass_nabucasa.cloudhooks.Cloudhooks.async_delete", return_value=coro ) as mock_delete: result = await hass.config_entries.async_remove(result["result"].entry_id) assert len(mock_delete.mock_calls) == 1 assert result["require_restart"] is False