Vizio: when checking new host against existing config entry hosts, make check hostname aware (#37397)

* make ip check hostname aware

* add executor job for sync function doing IO and remove errant comma

* revert change to update new_data explicitly for options keys since it is already done later

* empty commit to retrigger CI
This commit is contained in:
Raman Gupta 2020-07-08 15:31:41 -04:00 committed by GitHub
parent 368116d242
commit 572d5a09cd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 84 additions and 6 deletions

View File

@ -1,6 +1,7 @@
"""Config flow for Vizio.""" """Config flow for Vizio."""
import copy import copy
import logging import logging
import socket
from typing import Any, Dict, Optional from typing import Any, Dict, Optional
from pyvizio import VizioAsync, async_guess_device_type from pyvizio import VizioAsync, async_guess_device_type
@ -29,6 +30,7 @@ from homeassistant.core import callback
from homeassistant.helpers import config_validation as cv from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.typing import DiscoveryInfoType from homeassistant.helpers.typing import DiscoveryInfoType
from homeassistant.util.network import is_ip_address
from .const import ( from .const import (
CONF_APPS, CONF_APPS,
@ -90,7 +92,11 @@ def _get_pairing_schema(input_dict: Dict[str, Any] = None) -> vol.Schema:
def _host_is_same(host1: str, host2: str) -> bool: def _host_is_same(host1: str, host2: str) -> bool:
"""Check if host1 and host2 are the same.""" """Check if host1 and host2 are the same."""
return host1.split(":")[0] == host2.split(":")[0] host1 = host1.split(":")[0]
host1 = host1 if is_ip_address(host1) else socket.gethostbyname(host1)
host2 = host2.split(":")[0]
host2 = host2 if is_ip_address(host2) else socket.gethostbyname(host2)
return host1 == host2
class VizioOptionsConfigFlow(config_entries.OptionsFlow): class VizioOptionsConfigFlow(config_entries.OptionsFlow):
@ -206,8 +212,9 @@ class VizioConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
# If source is ignore bypass host and name check and continue through loop # If source is ignore bypass host and name check and continue through loop
if entry.source == SOURCE_IGNORE: if entry.source == SOURCE_IGNORE:
continue continue
if await self.hass.async_add_executor_job(
if _host_is_same(entry.data[CONF_HOST], user_input[CONF_HOST]): _host_is_same, entry.data[CONF_HOST], user_input[CONF_HOST]
):
errors[CONF_HOST] = "host_exists" errors[CONF_HOST] = "host_exists"
if entry.data[CONF_NAME] == user_input[CONF_NAME]: if entry.data[CONF_NAME] == user_input[CONF_NAME]:
@ -284,11 +291,16 @@ class VizioConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
if entry.source == SOURCE_IGNORE: if entry.source == SOURCE_IGNORE:
continue continue
if _host_is_same(entry.data[CONF_HOST], import_config[CONF_HOST]): if await self.hass.async_add_executor_job(
_host_is_same, entry.data[CONF_HOST], import_config[CONF_HOST]
):
updated_options = {} updated_options = {}
updated_data = {} updated_data = {}
remove_apps = False remove_apps = False
if entry.data[CONF_HOST] != import_config[CONF_HOST]:
updated_data[CONF_HOST] = import_config[CONF_HOST]
if entry.data[CONF_NAME] != import_config[CONF_NAME]: if entry.data[CONF_NAME] != import_config[CONF_NAME]:
updated_data[CONF_NAME] = import_config[CONF_NAME] updated_data[CONF_NAME] = import_config[CONF_NAME]
@ -314,6 +326,7 @@ class VizioConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
if updated_data: if updated_data:
new_data.update(updated_data) new_data.update(updated_data)
# options are stored in entry options and data so update both
if updated_options: if updated_options:
new_data.update(updated_options) new_data.update(updated_options)
new_options.update(updated_options) new_options.update(updated_options)
@ -353,7 +366,9 @@ class VizioConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
if entry.source == SOURCE_IGNORE: if entry.source == SOURCE_IGNORE:
continue continue
if _host_is_same(entry.data[CONF_HOST], discovery_info[CONF_HOST]): if await self.hass.async_add_executor_job(
_host_is_same, entry.data[CONF_HOST], discovery_info[CONF_HOST]
):
return self.async_abort(reason="already_configured_device") return self.async_abort(reason="already_configured_device")
# Set default name to discovered device name by stripping zeroconf service # Set default name to discovered device name by stripping zeroconf service

View File

@ -110,7 +110,7 @@ async def async_setup_entry(
_LOGGER.warning("Failed to connect to %s", host) _LOGGER.warning("Failed to connect to %s", host)
raise PlatformNotReady raise PlatformNotReady
entity = VizioDevice(config_entry, device, name, device_class,) entity = VizioDevice(config_entry, device, name, device_class)
async_add_entities([entity], update_before_add=True) async_add_entities([entity], update_before_add=True)

View File

@ -16,6 +16,7 @@ from .const import (
RESPONSE_TOKEN, RESPONSE_TOKEN,
UNIQUE_ID, UNIQUE_ID,
VERSION, VERSION,
ZEROCONF_HOST,
MockCompletePairingResponse, MockCompletePairingResponse,
MockStartPairingResponse, MockStartPairingResponse,
) )
@ -183,3 +184,13 @@ def vizio_update_with_apps_fixture(vizio_update: pytest.fixture):
return_value=CURRENT_APP_CONFIG, return_value=CURRENT_APP_CONFIG,
): ):
yield yield
@pytest.fixture(name="vizio_hostname_check")
def vizio_hostname_check():
"""Mock vizio hostname resolution."""
with patch(
"homeassistant.components.vizio.config_flow.socket.gethostbyname",
return_value=ZEROCONF_HOST,
):
yield

View File

@ -853,3 +853,55 @@ async def test_zeroconf_abort_when_ignored(
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
assert result["reason"] == "already_configured" assert result["reason"] == "already_configured"
async def test_zeroconf_flow_already_configured_hostname(
hass: HomeAssistantType,
vizio_connect: pytest.fixture,
vizio_bypass_setup: pytest.fixture,
vizio_hostname_check: pytest.fixture,
) -> None:
"""Test entity is already configured during zeroconf setup when existing entry uses hostname."""
config = MOCK_SPEAKER_CONFIG.copy()
config[CONF_HOST] = "hostname"
entry = MockConfigEntry(
domain=DOMAIN, data=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_configured_device"
async def test_import_flow_already_configured_hostname(
hass: HomeAssistantType,
vizio_connect: pytest.fixture,
vizio_bypass_setup: pytest.fixture,
vizio_hostname_check: pytest.fixture,
) -> None:
"""Test entity is already configured during import setup when existing entry uses hostname."""
config = MOCK_SPEAKER_CONFIG.copy()
config[CONF_HOST] = "hostname"
entry = MockConfigEntry(
domain=DOMAIN, data=config, options={CONF_VOLUME_STEP: VOLUME_STEP}
)
entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_IMPORT},
data=vol.Schema(VIZIO_SCHEMA)(MOCK_SPEAKER_CONFIG),
)
# Flow should abort because device was updated
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
assert result["reason"] == "updated_entry"
assert entry.data[CONF_HOST] == HOST