mirror of
https://github.com/home-assistant/core.git
synced 2025-04-23 08:47:57 +00:00
Simplify vizio unique ID check since only IP and device class are needed (#37692)
This commit is contained in:
parent
444df4a7d2
commit
fbf44b37a9
@ -180,14 +180,8 @@ class VizioConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
self._data = None
|
||||
self._apps = {}
|
||||
|
||||
async def _create_entry_if_unique(
|
||||
self, input_dict: Dict[str, Any]
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Create entry if ID is unique.
|
||||
|
||||
If it is, create entry. If it isn't, abort config flow.
|
||||
"""
|
||||
async def _create_entry(self, input_dict: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Create vizio config entry."""
|
||||
# Remove extra keys that will not be used by entry setup
|
||||
input_dict.pop(CONF_APPS_TO_INCLUDE_OR_EXCLUDE, None)
|
||||
input_dict.pop(CONF_INCLUDE_OR_EXCLUDE, None)
|
||||
@ -206,19 +200,25 @@ class VizioConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
if user_input is not None:
|
||||
# Store current values in case setup fails and user needs to edit
|
||||
self._user_schema = _get_config_schema(user_input)
|
||||
unique_id = await VizioAsync.get_unique_id(
|
||||
user_input[CONF_HOST],
|
||||
user_input[CONF_DEVICE_CLASS],
|
||||
session=async_get_clientsession(self.hass, False),
|
||||
)
|
||||
|
||||
# Check if new config entry matches any existing config entries
|
||||
for entry in self.hass.config_entries.async_entries(DOMAIN):
|
||||
# If source is ignore bypass host and name check and continue through loop
|
||||
if entry.source == SOURCE_IGNORE:
|
||||
continue
|
||||
if await self.hass.async_add_executor_job(
|
||||
_host_is_same, entry.data[CONF_HOST], user_input[CONF_HOST]
|
||||
):
|
||||
errors[CONF_HOST] = "host_exists"
|
||||
|
||||
if entry.data[CONF_NAME] == user_input[CONF_NAME]:
|
||||
errors[CONF_NAME] = "name_exists"
|
||||
if not unique_id:
|
||||
errors[CONF_HOST] = "cannot_connect"
|
||||
else:
|
||||
# Set unique ID and abort if a flow with the same unique ID is already in progress
|
||||
existing_entry = await self.async_set_unique_id(
|
||||
unique_id=unique_id, raise_on_progress=True
|
||||
)
|
||||
# If device was discovered, abort if existing entry found, otherwise display an error
|
||||
# pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167
|
||||
if self.context["source"] == SOURCE_ZEROCONF:
|
||||
self._abort_if_unique_id_configured()
|
||||
elif existing_entry:
|
||||
errors[CONF_HOST] = "existing_config_entry_found"
|
||||
|
||||
if not errors:
|
||||
# pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167
|
||||
@ -239,21 +239,7 @@ class VizioConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
errors["base"] = "cannot_connect"
|
||||
|
||||
if not errors:
|
||||
unique_id = await VizioAsync.get_unique_id(
|
||||
user_input[CONF_HOST],
|
||||
user_input.get(CONF_ACCESS_TOKEN),
|
||||
user_input[CONF_DEVICE_CLASS],
|
||||
session=async_get_clientsession(self.hass, False),
|
||||
)
|
||||
|
||||
# Set unique ID and abort if unique ID is already configured on an entry or a flow
|
||||
# with the unique ID is already in progress
|
||||
await self.async_set_unique_id(
|
||||
unique_id=unique_id, raise_on_progress=True
|
||||
)
|
||||
self._abort_if_unique_id_configured()
|
||||
|
||||
return await self._create_entry_if_unique(user_input)
|
||||
return await self._create_entry(user_input)
|
||||
# pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167
|
||||
elif self._must_show_form and self.context["source"] == SOURCE_IMPORT:
|
||||
# Import should always display the config form if CONF_ACCESS_TOKEN
|
||||
@ -350,27 +336,10 @@ class VizioConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
) -> Dict[str, Any]:
|
||||
"""Handle zeroconf discovery."""
|
||||
|
||||
# Set unique ID early to prevent device from getting rediscovered multiple times
|
||||
await self.async_set_unique_id(
|
||||
unique_id=discovery_info[CONF_HOST].split(":")[0], raise_on_progress=True
|
||||
)
|
||||
self._abort_if_unique_id_configured()
|
||||
|
||||
discovery_info[
|
||||
CONF_HOST
|
||||
] = f"{discovery_info[CONF_HOST]}:{discovery_info[CONF_PORT]}"
|
||||
|
||||
# Check if new config entry matches any existing config entries and abort if so
|
||||
for entry in self.hass.config_entries.async_entries(DOMAIN):
|
||||
# If source is ignore bypass host check and continue through loop
|
||||
if entry.source == SOURCE_IGNORE:
|
||||
continue
|
||||
|
||||
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")
|
||||
|
||||
# Set default name to discovered device name by stripping zeroconf service
|
||||
# (`type`) from `name`
|
||||
num_chars_to_strip = len(discovery_info[CONF_TYPE]) + 1
|
||||
@ -436,20 +405,6 @@ class VizioConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
self._data[CONF_ACCESS_TOKEN] = pair_data.auth_token
|
||||
self._must_show_form = True
|
||||
|
||||
unique_id = await VizioAsync.get_unique_id(
|
||||
self._data[CONF_HOST],
|
||||
self._data[CONF_ACCESS_TOKEN],
|
||||
self._data[CONF_DEVICE_CLASS],
|
||||
session=async_get_clientsession(self.hass, False),
|
||||
)
|
||||
|
||||
# Set unique ID and abort if unique ID is already configured on an entry or a flow
|
||||
# with the unique ID is already in progress
|
||||
await self.async_set_unique_id(
|
||||
unique_id=unique_id, raise_on_progress=True
|
||||
)
|
||||
self._abort_if_unique_id_configured()
|
||||
|
||||
# pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167
|
||||
if self.context["source"] == SOURCE_IMPORT:
|
||||
# If user is pairing via config import, show different message
|
||||
@ -470,7 +425,7 @@ class VizioConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
async def _pairing_complete(self, step_id: str) -> Dict[str, Any]:
|
||||
"""Handle config flow completion."""
|
||||
if not self._must_show_form:
|
||||
return await self._create_entry_if_unique(self._data)
|
||||
return await self._create_entry(self._data)
|
||||
|
||||
self._must_show_form = False
|
||||
return self.async_show_form(
|
||||
|
@ -2,7 +2,7 @@
|
||||
"domain": "vizio",
|
||||
"name": "VIZIO SmartCast",
|
||||
"documentation": "https://www.home-assistant.io/integrations/vizio",
|
||||
"requirements": ["pyvizio==0.1.49"],
|
||||
"requirements": ["pyvizio==0.1.51"],
|
||||
"codeowners": ["@raman325"],
|
||||
"config_flow": true,
|
||||
"zeroconf": ["_viziocast._tcp.local."],
|
||||
|
@ -294,7 +294,7 @@ class VizioDevice(MediaPlayerEntity):
|
||||
setting_type, setting_name, new_value,
|
||||
)
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Register callbacks when entity is added."""
|
||||
# Register callback for when config entry is updated.
|
||||
self._async_unsub_listeners.append(
|
||||
@ -310,7 +310,7 @@ class VizioDevice(MediaPlayerEntity):
|
||||
)
|
||||
)
|
||||
|
||||
async def async_will_remove_from_hass(self):
|
||||
async def async_will_remove_from_hass(self) -> None:
|
||||
"""Disconnect callbacks when entity is removed."""
|
||||
for listener in self._async_unsub_listeners:
|
||||
listener()
|
||||
@ -323,7 +323,7 @@ class VizioDevice(MediaPlayerEntity):
|
||||
return self._available
|
||||
|
||||
@property
|
||||
def state(self) -> str:
|
||||
def state(self) -> Optional[str]:
|
||||
"""Return the state of the device."""
|
||||
return self._state
|
||||
|
||||
@ -338,7 +338,7 @@ class VizioDevice(MediaPlayerEntity):
|
||||
return self._icon
|
||||
|
||||
@property
|
||||
def volume_level(self) -> float:
|
||||
def volume_level(self) -> Optional[float]:
|
||||
"""Return the volume level of the device."""
|
||||
return self._volume_level
|
||||
|
||||
@ -348,7 +348,7 @@ class VizioDevice(MediaPlayerEntity):
|
||||
return self._is_volume_muted
|
||||
|
||||
@property
|
||||
def source(self) -> str:
|
||||
def source(self) -> Optional[str]:
|
||||
"""Return current input of the device."""
|
||||
if self._current_app is not None and self._current_input in INPUT_APPS:
|
||||
return self._current_app
|
||||
|
@ -28,10 +28,9 @@
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"host_exists": "[%key:component::vizio::config::step::user::title%] with specified host already configured.",
|
||||
"name_exists": "[%key:component::vizio::config::step::user::title%] with specified name already configured.",
|
||||
"complete_pairing_failed": "Unable to complete pairing. Ensure the PIN you provided is correct and the TV is still powered and connected to the network before resubmitting.",
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||
"existing_config_entry_found": "An existing [%key:component::vizio::config::step::user::title%] config entry with the same serial number has already been configured. You must delete the existing entry in order to configure this one."
|
||||
},
|
||||
"abort": {
|
||||
"already_configured_device": "[%key:common::config_flow::abort::already_configured_device%]",
|
||||
|
@ -1834,7 +1834,7 @@ pyversasense==0.0.6
|
||||
pyvesync==1.1.0
|
||||
|
||||
# homeassistant.components.vizio
|
||||
pyvizio==0.1.49
|
||||
pyvizio==0.1.51
|
||||
|
||||
# homeassistant.components.velux
|
||||
pyvlx==0.2.16
|
||||
|
@ -839,7 +839,7 @@ pyvera==0.3.9
|
||||
pyvesync==1.1.0
|
||||
|
||||
# homeassistant.components.vizio
|
||||
pyvizio==0.1.49
|
||||
pyvizio==0.1.51
|
||||
|
||||
# homeassistant.components.volumio
|
||||
pyvolumio==0.1.1
|
||||
|
@ -47,15 +47,32 @@ def skip_notifications_fixture():
|
||||
yield
|
||||
|
||||
|
||||
@pytest.fixture(name="vizio_get_unique_id", autouse=True)
|
||||
def vizio_get_unique_id_fixture():
|
||||
"""Mock get vizio unique ID."""
|
||||
with patch(
|
||||
"homeassistant.components.vizio.config_flow.VizioAsync.get_unique_id",
|
||||
return_value=UNIQUE_ID,
|
||||
):
|
||||
yield
|
||||
|
||||
|
||||
@pytest.fixture(name="vizio_no_unique_id")
|
||||
def vizio_no_unique_id_fixture():
|
||||
"""Mock no vizio unique ID returrned."""
|
||||
with patch(
|
||||
"homeassistant.components.vizio.config_flow.VizioAsync.get_unique_id",
|
||||
return_value=None,
|
||||
):
|
||||
yield
|
||||
|
||||
|
||||
@pytest.fixture(name="vizio_connect")
|
||||
def vizio_connect_fixture():
|
||||
"""Mock valid vizio device and entry setup."""
|
||||
with patch(
|
||||
"homeassistant.components.vizio.config_flow.VizioAsync.validate_ha_config",
|
||||
return_value=True,
|
||||
), patch(
|
||||
"homeassistant.components.vizio.config_flow.VizioAsync.get_unique_id",
|
||||
return_value=UNIQUE_ID,
|
||||
):
|
||||
yield
|
||||
|
||||
|
@ -51,7 +51,6 @@ from .const import (
|
||||
NAME2,
|
||||
UNIQUE_ID,
|
||||
VOLUME_STEP,
|
||||
ZEROCONF_HOST,
|
||||
)
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
@ -223,7 +222,10 @@ async def test_user_host_already_configured(
|
||||
) -> None:
|
||||
"""Test host is already configured during user setup."""
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN, data=MOCK_SPEAKER_CONFIG, options={CONF_VOLUME_STEP: VOLUME_STEP}
|
||||
domain=DOMAIN,
|
||||
data=MOCK_SPEAKER_CONFIG,
|
||||
options={CONF_VOLUME_STEP: VOLUME_STEP},
|
||||
unique_id=UNIQUE_ID,
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
fail_entry = MOCK_SPEAKER_CONFIG.copy()
|
||||
@ -234,61 +236,15 @@ async def test_user_host_already_configured(
|
||||
)
|
||||
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
assert result["errors"] == {CONF_HOST: "host_exists"}
|
||||
assert result["errors"] == {CONF_HOST: "existing_config_entry_found"}
|
||||
|
||||
|
||||
async def test_user_host_already_configured_no_port(
|
||||
async def test_user_serial_number_already_exists(
|
||||
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."""
|
||||
"""Test serial_number 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
|
||||
@ -303,14 +259,26 @@ async def test_user_esn_already_exists(
|
||||
DOMAIN, context={"source": SOURCE_USER}, data=fail_entry
|
||||
)
|
||||
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
|
||||
assert result["reason"] == "already_configured"
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
assert result["errors"] == {CONF_HOST: "existing_config_entry_found"}
|
||||
|
||||
|
||||
async def test_user_error_on_could_not_connect(
|
||||
hass: HomeAssistantType, vizio_no_unique_id: pytest.fixture
|
||||
) -> None:
|
||||
"""Test with could_not_connect during user setup due to no connectivity."""
|
||||
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"] == {CONF_HOST: "cannot_connect"}
|
||||
|
||||
|
||||
async def test_user_error_on_could_not_connect_invalid_token(
|
||||
hass: HomeAssistantType, vizio_cant_connect: pytest.fixture
|
||||
) -> None:
|
||||
"""Test with could_not_connect during user_setup."""
|
||||
"""Test with could_not_connect during user setup due to invalid token."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": SOURCE_USER}, data=MOCK_USER_VALID_TV_CONFIG
|
||||
)
|
||||
@ -683,6 +651,7 @@ async def test_import_error(
|
||||
domain=DOMAIN,
|
||||
data=vol.Schema(VIZIO_SCHEMA)(MOCK_SPEAKER_CONFIG),
|
||||
options={CONF_VOLUME_STEP: VOLUME_STEP},
|
||||
unique_id=UNIQUE_ID,
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
fail_entry = MOCK_SPEAKER_CONFIG.copy()
|
||||
@ -763,10 +732,14 @@ async def test_zeroconf_flow_already_configured(
|
||||
hass: HomeAssistantType,
|
||||
vizio_connect: pytest.fixture,
|
||||
vizio_bypass_setup: pytest.fixture,
|
||||
vizio_guess_device_type: 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}
|
||||
domain=DOMAIN,
|
||||
data=MOCK_SPEAKER_CONFIG,
|
||||
options={CONF_VOLUME_STEP: VOLUME_STEP},
|
||||
unique_id=UNIQUE_ID,
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
@ -778,7 +751,7 @@ async def test_zeroconf_flow_already_configured(
|
||||
|
||||
# Flow should abort because device is already setup
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
|
||||
assert result["reason"] == "already_configured_device"
|
||||
assert result["reason"] == "already_configured"
|
||||
|
||||
|
||||
async def test_zeroconf_dupe_fail(
|
||||
@ -842,7 +815,7 @@ async def test_zeroconf_abort_when_ignored(
|
||||
data=MOCK_SPEAKER_CONFIG,
|
||||
options={CONF_VOLUME_STEP: VOLUME_STEP},
|
||||
source=SOURCE_IGNORE,
|
||||
unique_id=ZEROCONF_HOST,
|
||||
unique_id=UNIQUE_ID,
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
@ -860,12 +833,16 @@ async def test_zeroconf_flow_already_configured_hostname(
|
||||
vizio_connect: pytest.fixture,
|
||||
vizio_bypass_setup: pytest.fixture,
|
||||
vizio_hostname_check: pytest.fixture,
|
||||
vizio_guess_device_type: 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}
|
||||
domain=DOMAIN,
|
||||
data=config,
|
||||
options={CONF_VOLUME_STEP: VOLUME_STEP},
|
||||
unique_id=UNIQUE_ID,
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
@ -877,7 +854,7 @@ async def test_zeroconf_flow_already_configured_hostname(
|
||||
|
||||
# Flow should abort because device is already setup
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
|
||||
assert result["reason"] == "already_configured_device"
|
||||
assert result["reason"] == "already_configured"
|
||||
|
||||
|
||||
async def test_import_flow_already_configured_hostname(
|
||||
|
Loading…
x
Reference in New Issue
Block a user