"""Tests for Vizio config flow.""" import pytest import voluptuous as vol from homeassistant import data_entry_flow from homeassistant.components.media_player import DEVICE_CLASS_SPEAKER, DEVICE_CLASS_TV from homeassistant.components.vizio.const import ( CONF_VOLUME_STEP, DEFAULT_NAME, DEFAULT_VOLUME_STEP, DOMAIN, VIZIO_SCHEMA, ) from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER, SOURCE_ZEROCONF from homeassistant.const import ( CONF_ACCESS_TOKEN, CONF_DEVICE_CLASS, CONF_HOST, CONF_NAME, ) from homeassistant.helpers.typing import HomeAssistantType from .const import ( ACCESS_TOKEN, HOST, HOST2, MOCK_IMPORT_VALID_TV_CONFIG, MOCK_INVALID_TV_CONFIG, MOCK_SPEAKER_CONFIG, MOCK_USER_VALID_TV_CONFIG, MOCK_ZEROCONF_SERVICE_INFO, NAME, NAME2, UNIQUE_ID, VOLUME_STEP, ) from tests.common import MockConfigEntry async def test_user_flow_minimum_fields( hass: HomeAssistantType, vizio_connect: pytest.fixture, vizio_bypass_setup: pytest.fixture, ) -> None: """Test user config flow with minimum fields.""" # test form shows result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER} ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["step_id"] == "user" result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input=MOCK_SPEAKER_CONFIG ) assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY assert result["title"] == NAME assert result["data"][CONF_NAME] == NAME assert result["data"][CONF_HOST] == HOST assert result["data"][CONF_DEVICE_CLASS] == DEVICE_CLASS_SPEAKER async def test_user_flow_all_fields( hass: HomeAssistantType, vizio_connect: pytest.fixture, vizio_bypass_setup: pytest.fixture, ) -> None: """Test user config flow with all fields.""" # test form shows result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER} ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["step_id"] == "user" result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input=MOCK_USER_VALID_TV_CONFIG ) assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY assert result["title"] == NAME assert result["data"][CONF_NAME] == NAME assert result["data"][CONF_HOST] == HOST assert result["data"][CONF_DEVICE_CLASS] == DEVICE_CLASS_TV assert result["data"][CONF_ACCESS_TOKEN] == ACCESS_TOKEN async def test_options_flow(hass: HomeAssistantType) -> None: """Test options config flow.""" entry = MockConfigEntry(domain=DOMAIN, data=MOCK_SPEAKER_CONFIG) entry.add_to_hass(hass) assert not entry.options result = await hass.config_entries.options.async_init(entry.entry_id, data=None) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["step_id"] == "init" result = await hass.config_entries.options.async_configure( result["flow_id"], user_input={CONF_VOLUME_STEP: VOLUME_STEP} ) assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY assert result["title"] == "" assert result["data"][CONF_VOLUME_STEP] == VOLUME_STEP async def test_user_host_already_configured( hass: HomeAssistantType, vizio_connect: pytest.fixture, vizio_bypass_setup: pytest.fixture, ) -> None: """Test host is already configured during user setup.""" entry = MockConfigEntry( domain=DOMAIN, data=MOCK_SPEAKER_CONFIG, options={CONF_VOLUME_STEP: VOLUME_STEP} ) entry.add_to_hass(hass) fail_entry = MOCK_SPEAKER_CONFIG.copy() fail_entry[CONF_NAME] = "newtestname" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER}, data=fail_entry ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["errors"] == {CONF_HOST: "host_exists"} async def test_user_host_already_configured_no_port( hass: HomeAssistantType, vizio_connect: pytest.fixture, vizio_bypass_setup: pytest.fixture, ) -> None: """Test host is already configured during user setup when existing entry has no port.""" # Mock entry without port so we can test that the same entry WITH a port will fail no_port_entry = MOCK_SPEAKER_CONFIG.copy() no_port_entry[CONF_HOST] = no_port_entry[CONF_HOST].split(":")[0] entry = MockConfigEntry( domain=DOMAIN, data=no_port_entry, options={CONF_VOLUME_STEP: VOLUME_STEP} ) entry.add_to_hass(hass) fail_entry = MOCK_SPEAKER_CONFIG.copy() fail_entry[CONF_NAME] = "newtestname" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER}, data=fail_entry ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["errors"] == {CONF_HOST: "host_exists"} async def test_user_name_already_configured( hass: HomeAssistantType, vizio_connect: pytest.fixture, vizio_bypass_setup: pytest.fixture, ) -> None: """Test name is already configured during user setup.""" entry = MockConfigEntry( domain=DOMAIN, data=MOCK_SPEAKER_CONFIG, options={CONF_VOLUME_STEP: VOLUME_STEP} ) entry.add_to_hass(hass) fail_entry = MOCK_SPEAKER_CONFIG.copy() fail_entry[CONF_HOST] = "0.0.0.0" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER}, data=fail_entry ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["errors"] == {CONF_NAME: "name_exists"} async def test_user_esn_already_exists( hass: HomeAssistantType, vizio_connect: pytest.fixture, vizio_bypass_setup: pytest.fixture, ) -> None: """Test ESN is already configured with different host and name during user setup.""" # Set up new entry MockConfigEntry( domain=DOMAIN, data=MOCK_SPEAKER_CONFIG, unique_id=UNIQUE_ID ).add_to_hass(hass) # Set up new entry with same unique_id but different host and name fail_entry = MOCK_SPEAKER_CONFIG.copy() fail_entry[CONF_HOST] = HOST2 fail_entry[CONF_NAME] = NAME2 result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER}, data=fail_entry ) assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT assert result["reason"] == "already_setup_with_diff_host_and_name" async def test_user_error_on_could_not_connect( hass: HomeAssistantType, vizio_cant_connect: pytest.fixture ) -> None: """Test with could_not_connect during user_setup.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER}, data=MOCK_USER_VALID_TV_CONFIG ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["errors"] == {"base": "cant_connect"} async def test_user_error_on_tv_needs_token( hass: HomeAssistantType, vizio_connect: pytest.fixture, vizio_bypass_setup: pytest.fixture, ) -> None: """Test when config fails custom validation for non null access token when device_class = tv during user setup.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER}, data=MOCK_INVALID_TV_CONFIG ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["errors"] == {"base": "tv_needs_token"} async def test_import_flow_minimum_fields( hass: HomeAssistantType, vizio_connect: pytest.fixture, vizio_bypass_setup: pytest.fixture, ) -> None: """Test import config flow with minimum fields.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_IMPORT}, data=vol.Schema(VIZIO_SCHEMA)( {CONF_HOST: HOST, CONF_DEVICE_CLASS: DEVICE_CLASS_SPEAKER} ), ) assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY assert result["title"] == DEFAULT_NAME assert result["data"][CONF_NAME] == DEFAULT_NAME assert result["data"][CONF_HOST] == HOST assert result["data"][CONF_DEVICE_CLASS] == DEVICE_CLASS_SPEAKER assert result["data"][CONF_VOLUME_STEP] == DEFAULT_VOLUME_STEP async def test_import_flow_all_fields( hass: HomeAssistantType, vizio_connect: pytest.fixture, vizio_bypass_setup: pytest.fixture, ) -> None: """Test import config flow with all fields.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_IMPORT}, data=vol.Schema(VIZIO_SCHEMA)(MOCK_IMPORT_VALID_TV_CONFIG), ) assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY assert result["title"] == NAME assert result["data"][CONF_NAME] == NAME assert result["data"][CONF_HOST] == HOST assert result["data"][CONF_DEVICE_CLASS] == DEVICE_CLASS_TV assert result["data"][CONF_ACCESS_TOKEN] == ACCESS_TOKEN assert result["data"][CONF_VOLUME_STEP] == VOLUME_STEP async def test_import_entity_already_configured( hass: HomeAssistantType, vizio_connect: pytest.fixture, vizio_bypass_setup: pytest.fixture, ) -> None: """Test entity is already configured during import setup.""" entry = MockConfigEntry( domain=DOMAIN, data=vol.Schema(VIZIO_SCHEMA)(MOCK_SPEAKER_CONFIG), options={CONF_VOLUME_STEP: VOLUME_STEP}, ) entry.add_to_hass(hass) fail_entry = vol.Schema(VIZIO_SCHEMA)(MOCK_SPEAKER_CONFIG.copy()) result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_IMPORT}, data=fail_entry ) assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT assert result["reason"] == "already_setup" async def test_import_flow_update_options( hass: HomeAssistantType, vizio_connect: pytest.fixture, vizio_bypass_update: pytest.fixture, ) -> None: """Test import config flow with updated options.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_IMPORT}, data=vol.Schema(VIZIO_SCHEMA)(MOCK_IMPORT_VALID_TV_CONFIG), ) await hass.async_block_till_done() assert result["result"].options == {CONF_VOLUME_STEP: VOLUME_STEP} assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY entry_id = result["result"].entry_id updated_config = MOCK_IMPORT_VALID_TV_CONFIG.copy() updated_config[CONF_VOLUME_STEP] = VOLUME_STEP + 1 result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_IMPORT}, data=vol.Schema(VIZIO_SCHEMA)(updated_config), ) assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT assert result["reason"] == "updated_entry" assert ( hass.config_entries.async_get_entry(entry_id).options[CONF_VOLUME_STEP] == VOLUME_STEP + 1 ) async def test_import_flow_update_name( hass: HomeAssistantType, vizio_connect: pytest.fixture, vizio_bypass_update: pytest.fixture, ) -> None: """Test import config flow with updated name.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_IMPORT}, data=vol.Schema(VIZIO_SCHEMA)(MOCK_IMPORT_VALID_TV_CONFIG), ) await hass.async_block_till_done() assert result["result"].data[CONF_NAME] == NAME assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY entry_id = result["result"].entry_id updated_config = MOCK_IMPORT_VALID_TV_CONFIG.copy() updated_config[CONF_NAME] = NAME2 result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_IMPORT}, data=vol.Schema(VIZIO_SCHEMA)(updated_config), ) assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT assert result["reason"] == "updated_entry" assert hass.config_entries.async_get_entry(entry_id).data[CONF_NAME] == NAME2 async def test_zeroconf_flow( hass: HomeAssistantType, vizio_connect: pytest.fixture, vizio_bypass_setup: pytest.fixture, vizio_guess_device_type: pytest.fixture, ) -> None: """Test zeroconf config flow.""" discovery_info = MOCK_ZEROCONF_SERVICE_INFO.copy() result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_ZEROCONF}, data=discovery_info ) # Form should always show even if all required properties are discovered assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["step_id"] == "user" # Apply discovery updates to entry to mimic when user hits submit without changing # defaults which were set from discovery parameters user_input = result["data_schema"](discovery_info) result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input=user_input ) assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY assert result["title"] == NAME assert result["data"][CONF_HOST] == HOST assert result["data"][CONF_NAME] == NAME assert result["data"][CONF_DEVICE_CLASS] == DEVICE_CLASS_SPEAKER async def test_zeroconf_flow_already_configured( hass: HomeAssistantType, vizio_connect: pytest.fixture, vizio_bypass_setup: pytest.fixture, ) -> None: """Test entity is already configured during zeroconf setup.""" entry = MockConfigEntry( domain=DOMAIN, data=MOCK_SPEAKER_CONFIG, options={CONF_VOLUME_STEP: VOLUME_STEP} ) entry.add_to_hass(hass) # Try rediscovering same device discovery_info = MOCK_ZEROCONF_SERVICE_INFO.copy() result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_ZEROCONF}, data=discovery_info ) # Flow should abort because device is already setup assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT assert result["reason"] == "already_setup" async def test_zeroconf_dupe_fail( hass: HomeAssistantType, vizio_connect: pytest.fixture, vizio_bypass_setup: pytest.fixture, vizio_guess_device_type: pytest.fixture, ) -> None: """Test zeroconf config flow when device gets discovered multiple times.""" discovery_info = MOCK_ZEROCONF_SERVICE_INFO.copy() result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_ZEROCONF}, data=discovery_info ) # Form should always show even if all required properties are discovered assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["step_id"] == "user" discovery_info = MOCK_ZEROCONF_SERVICE_INFO.copy() result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_ZEROCONF}, data=discovery_info ) # Flow should abort because device is already setup assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT assert result["reason"] == "already_in_progress"