Store tplink credentials_hash outside of device_config (#120597)
This commit is contained in:
parent
0d53ce4fb8
commit
970dd99226
6 changed files with 373 additions and 31 deletions
|
@ -43,6 +43,7 @@ from homeassistant.helpers.event import async_track_time_interval
|
|||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from .const import (
|
||||
CONF_CREDENTIALS_HASH,
|
||||
CONF_DEVICE_CONFIG,
|
||||
CONNECT_TIMEOUT,
|
||||
DISCOVERY_TIMEOUT,
|
||||
|
@ -73,6 +74,7 @@ def async_trigger_discovery(
|
|||
discovered_devices: dict[str, Device],
|
||||
) -> None:
|
||||
"""Trigger config flows for discovered devices."""
|
||||
|
||||
for formatted_mac, device in discovered_devices.items():
|
||||
discovery_flow.async_create_flow(
|
||||
hass,
|
||||
|
@ -83,7 +85,6 @@ def async_trigger_discovery(
|
|||
CONF_HOST: device.host,
|
||||
CONF_MAC: formatted_mac,
|
||||
CONF_DEVICE_CONFIG: device.config.to_dict(
|
||||
credentials_hash=device.credentials_hash,
|
||||
exclude_credentials=True,
|
||||
),
|
||||
},
|
||||
|
@ -133,6 +134,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: TPLinkConfigEntry) -> bo
|
|||
"""Set up TPLink from a config entry."""
|
||||
host: str = entry.data[CONF_HOST]
|
||||
credentials = await get_credentials(hass)
|
||||
entry_credentials_hash = entry.data.get(CONF_CREDENTIALS_HASH)
|
||||
|
||||
config: DeviceConfig | None = None
|
||||
if config_dict := entry.data.get(CONF_DEVICE_CONFIG):
|
||||
|
@ -151,19 +153,31 @@ async def async_setup_entry(hass: HomeAssistant, entry: TPLinkConfigEntry) -> bo
|
|||
config.timeout = CONNECT_TIMEOUT
|
||||
if config.uses_http is True:
|
||||
config.http_client = create_async_tplink_clientsession(hass)
|
||||
|
||||
# If we have in memory credentials use them otherwise check for credentials_hash
|
||||
if credentials:
|
||||
config.credentials = credentials
|
||||
elif entry_credentials_hash:
|
||||
config.credentials_hash = entry_credentials_hash
|
||||
|
||||
try:
|
||||
device: Device = await Device.connect(config=config)
|
||||
except AuthenticationError as ex:
|
||||
# If the stored credentials_hash was used but doesn't work remove it
|
||||
if not credentials and entry_credentials_hash:
|
||||
data = {k: v for k, v in entry.data.items() if k != CONF_CREDENTIALS_HASH}
|
||||
hass.config_entries.async_update_entry(entry, data=data)
|
||||
raise ConfigEntryAuthFailed from ex
|
||||
except KasaException as ex:
|
||||
raise ConfigEntryNotReady from ex
|
||||
|
||||
device_config_dict = device.config.to_dict(
|
||||
credentials_hash=device.credentials_hash, exclude_credentials=True
|
||||
)
|
||||
device_credentials_hash = device.credentials_hash
|
||||
device_config_dict = device.config.to_dict(exclude_credentials=True)
|
||||
# Do not store the credentials hash inside the device_config
|
||||
device_config_dict.pop(CONF_CREDENTIALS_HASH, None)
|
||||
updates: dict[str, Any] = {}
|
||||
if device_credentials_hash and device_credentials_hash != entry_credentials_hash:
|
||||
updates[CONF_CREDENTIALS_HASH] = device_credentials_hash
|
||||
if device_config_dict != config_dict:
|
||||
updates[CONF_DEVICE_CONFIG] = device_config_dict
|
||||
if entry.data.get(CONF_ALIAS) != device.alias:
|
||||
|
@ -326,7 +340,25 @@ async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) ->
|
|||
|
||||
minor_version = 3
|
||||
hass.config_entries.async_update_entry(config_entry, minor_version=3)
|
||||
|
||||
_LOGGER.debug("Migration to version %s.%s successful", version, minor_version)
|
||||
|
||||
if version == 1 and minor_version == 3:
|
||||
# credentials_hash stored in the device_config should be moved to data.
|
||||
updates: dict[str, Any] = {}
|
||||
if config_dict := config_entry.data.get(CONF_DEVICE_CONFIG):
|
||||
assert isinstance(config_dict, dict)
|
||||
if credentials_hash := config_dict.pop(CONF_CREDENTIALS_HASH, None):
|
||||
updates[CONF_CREDENTIALS_HASH] = credentials_hash
|
||||
updates[CONF_DEVICE_CONFIG] = config_dict
|
||||
minor_version = 4
|
||||
hass.config_entries.async_update_entry(
|
||||
config_entry,
|
||||
data={
|
||||
**config_entry.data,
|
||||
**updates,
|
||||
},
|
||||
minor_version=minor_version,
|
||||
)
|
||||
_LOGGER.debug("Migration to version %s.%s complete", version, minor_version)
|
||||
|
||||
return True
|
||||
|
|
|
@ -44,7 +44,13 @@ from . import (
|
|||
mac_alias,
|
||||
set_credentials,
|
||||
)
|
||||
from .const import CONF_DEVICE_CONFIG, CONNECT_TIMEOUT, DOMAIN
|
||||
from .const import (
|
||||
CONF_CONNECTION_TYPE,
|
||||
CONF_CREDENTIALS_HASH,
|
||||
CONF_DEVICE_CONFIG,
|
||||
CONNECT_TIMEOUT,
|
||||
DOMAIN,
|
||||
)
|
||||
|
||||
STEP_AUTH_DATA_SCHEMA = vol.Schema(
|
||||
{vol.Required(CONF_USERNAME): str, vol.Required(CONF_PASSWORD): str}
|
||||
|
@ -55,7 +61,7 @@ class TPLinkConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||
"""Handle a config flow for tplink."""
|
||||
|
||||
VERSION = 1
|
||||
MINOR_VERSION = 3
|
||||
MINOR_VERSION = 4
|
||||
reauth_entry: ConfigEntry | None = None
|
||||
|
||||
def __init__(self) -> None:
|
||||
|
@ -95,9 +101,18 @@ class TPLinkConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||
entry_config_dict = entry_data.get(CONF_DEVICE_CONFIG)
|
||||
if entry_config_dict == config and entry_data[CONF_HOST] == host:
|
||||
return None
|
||||
updates = {**entry.data, CONF_DEVICE_CONFIG: config, CONF_HOST: host}
|
||||
# If the connection parameters have changed the credentials_hash will be invalid.
|
||||
if (
|
||||
entry_config_dict
|
||||
and isinstance(entry_config_dict, dict)
|
||||
and entry_config_dict.get(CONF_CONNECTION_TYPE)
|
||||
!= config.get(CONF_CONNECTION_TYPE)
|
||||
):
|
||||
updates.pop(CONF_CREDENTIALS_HASH, None)
|
||||
return self.async_update_reload_and_abort(
|
||||
entry,
|
||||
data={**entry.data, CONF_DEVICE_CONFIG: config, CONF_HOST: host},
|
||||
data=updates,
|
||||
reason="already_configured",
|
||||
)
|
||||
|
||||
|
@ -345,18 +360,22 @@ class TPLinkConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||
@callback
|
||||
def _async_create_entry_from_device(self, device: Device) -> ConfigFlowResult:
|
||||
"""Create a config entry from a smart device."""
|
||||
# This is only ever called after a successful device update so we know that
|
||||
# the credential_hash is correct and should be saved.
|
||||
self._abort_if_unique_id_configured(updates={CONF_HOST: device.host})
|
||||
data = {
|
||||
CONF_HOST: device.host,
|
||||
CONF_ALIAS: device.alias,
|
||||
CONF_MODEL: device.model,
|
||||
CONF_DEVICE_CONFIG: device.config.to_dict(
|
||||
exclude_credentials=True,
|
||||
),
|
||||
}
|
||||
if device.credentials_hash:
|
||||
data[CONF_CREDENTIALS_HASH] = device.credentials_hash
|
||||
return self.async_create_entry(
|
||||
title=f"{device.alias} {device.model}",
|
||||
data={
|
||||
CONF_HOST: device.host,
|
||||
CONF_ALIAS: device.alias,
|
||||
CONF_MODEL: device.model,
|
||||
CONF_DEVICE_CONFIG: device.config.to_dict(
|
||||
credentials_hash=device.credentials_hash,
|
||||
exclude_credentials=True,
|
||||
),
|
||||
},
|
||||
data=data,
|
||||
)
|
||||
|
||||
async def _async_try_discover_and_update(
|
||||
|
|
|
@ -20,6 +20,8 @@ ATTR_TODAY_ENERGY_KWH: Final = "today_energy_kwh"
|
|||
ATTR_TOTAL_ENERGY_KWH: Final = "total_energy_kwh"
|
||||
|
||||
CONF_DEVICE_CONFIG: Final = "device_config"
|
||||
CONF_CREDENTIALS_HASH: Final = "credentials_hash"
|
||||
CONF_CONNECTION_TYPE: Final = "connection_type"
|
||||
|
||||
PLATFORMS: Final = [
|
||||
Platform.BINARY_SENSOR,
|
||||
|
|
|
@ -22,6 +22,7 @@ from syrupy import SnapshotAssertion
|
|||
|
||||
from homeassistant.components.tplink import (
|
||||
CONF_ALIAS,
|
||||
CONF_CREDENTIALS_HASH,
|
||||
CONF_DEVICE_CONFIG,
|
||||
CONF_HOST,
|
||||
CONF_MODEL,
|
||||
|
@ -53,9 +54,7 @@ MAC_ADDRESS2 = "11:22:33:44:55:66"
|
|||
DEFAULT_ENTRY_TITLE = f"{ALIAS} {MODEL}"
|
||||
CREDENTIALS_HASH_LEGACY = ""
|
||||
DEVICE_CONFIG_LEGACY = DeviceConfig(IP_ADDRESS)
|
||||
DEVICE_CONFIG_DICT_LEGACY = DEVICE_CONFIG_LEGACY.to_dict(
|
||||
credentials_hash=CREDENTIALS_HASH_LEGACY, exclude_credentials=True
|
||||
)
|
||||
DEVICE_CONFIG_DICT_LEGACY = DEVICE_CONFIG_LEGACY.to_dict(exclude_credentials=True)
|
||||
CREDENTIALS = Credentials("foo", "bar")
|
||||
CREDENTIALS_HASH_AUTH = "abcdefghijklmnopqrstuv=="
|
||||
DEVICE_CONFIG_AUTH = DeviceConfig(
|
||||
|
@ -74,12 +73,8 @@ DEVICE_CONFIG_AUTH2 = DeviceConfig(
|
|||
),
|
||||
uses_http=True,
|
||||
)
|
||||
DEVICE_CONFIG_DICT_AUTH = DEVICE_CONFIG_AUTH.to_dict(
|
||||
credentials_hash=CREDENTIALS_HASH_AUTH, exclude_credentials=True
|
||||
)
|
||||
DEVICE_CONFIG_DICT_AUTH2 = DEVICE_CONFIG_AUTH2.to_dict(
|
||||
credentials_hash=CREDENTIALS_HASH_AUTH, exclude_credentials=True
|
||||
)
|
||||
DEVICE_CONFIG_DICT_AUTH = DEVICE_CONFIG_AUTH.to_dict(exclude_credentials=True)
|
||||
DEVICE_CONFIG_DICT_AUTH2 = DEVICE_CONFIG_AUTH2.to_dict(exclude_credentials=True)
|
||||
|
||||
CREATE_ENTRY_DATA_LEGACY = {
|
||||
CONF_HOST: IP_ADDRESS,
|
||||
|
@ -92,14 +87,20 @@ CREATE_ENTRY_DATA_AUTH = {
|
|||
CONF_HOST: IP_ADDRESS,
|
||||
CONF_ALIAS: ALIAS,
|
||||
CONF_MODEL: MODEL,
|
||||
CONF_CREDENTIALS_HASH: CREDENTIALS_HASH_AUTH,
|
||||
CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_AUTH,
|
||||
}
|
||||
CREATE_ENTRY_DATA_AUTH2 = {
|
||||
CONF_HOST: IP_ADDRESS2,
|
||||
CONF_ALIAS: ALIAS,
|
||||
CONF_MODEL: MODEL,
|
||||
CONF_CREDENTIALS_HASH: CREDENTIALS_HASH_AUTH,
|
||||
CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_AUTH2,
|
||||
}
|
||||
NEW_CONNECTION_TYPE = DeviceConnectionParameters(
|
||||
DeviceFamily.IotSmartPlugSwitch, DeviceEncryptionType.Aes
|
||||
)
|
||||
NEW_CONNECTION_TYPE_DICT = NEW_CONNECTION_TYPE.to_dict()
|
||||
|
||||
|
||||
def _load_feature_fixtures():
|
||||
|
|
|
@ -14,8 +14,12 @@ from homeassistant.components.tplink import (
|
|||
DeviceConfig,
|
||||
KasaException,
|
||||
)
|
||||
from homeassistant.components.tplink.const import CONF_DEVICE_CONFIG
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.components.tplink.const import (
|
||||
CONF_CONNECTION_TYPE,
|
||||
CONF_CREDENTIALS_HASH,
|
||||
CONF_DEVICE_CONFIG,
|
||||
)
|
||||
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntryState
|
||||
from homeassistant.const import (
|
||||
CONF_ALIAS,
|
||||
CONF_DEVICE,
|
||||
|
@ -32,6 +36,7 @@ from . import (
|
|||
CREATE_ENTRY_DATA_AUTH,
|
||||
CREATE_ENTRY_DATA_AUTH2,
|
||||
CREATE_ENTRY_DATA_LEGACY,
|
||||
CREDENTIALS_HASH_AUTH,
|
||||
DEFAULT_ENTRY_TITLE,
|
||||
DEVICE_CONFIG_DICT_AUTH,
|
||||
DEVICE_CONFIG_DICT_LEGACY,
|
||||
|
@ -40,6 +45,7 @@ from . import (
|
|||
MAC_ADDRESS,
|
||||
MAC_ADDRESS2,
|
||||
MODULE,
|
||||
NEW_CONNECTION_TYPE_DICT,
|
||||
_mocked_device,
|
||||
_patch_connect,
|
||||
_patch_discovery,
|
||||
|
@ -811,6 +817,77 @@ async def test_integration_discovery_with_ip_change(
|
|||
mock_connect["connect"].assert_awaited_once_with(config=config)
|
||||
|
||||
|
||||
async def test_integration_discovery_with_connection_change(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_discovery: AsyncMock,
|
||||
mock_connect: AsyncMock,
|
||||
) -> None:
|
||||
"""Test that config entry is updated with new device config.
|
||||
|
||||
And that connection_hash is removed as it will be invalid.
|
||||
"""
|
||||
mock_connect["connect"].side_effect = KasaException()
|
||||
|
||||
mock_config_entry = MockConfigEntry(
|
||||
title="TPLink",
|
||||
domain=DOMAIN,
|
||||
data=CREATE_ENTRY_DATA_AUTH,
|
||||
unique_id=MAC_ADDRESS,
|
||||
)
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
with patch("homeassistant.components.tplink.Discover.discover", return_value={}):
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done(wait_background_tasks=True)
|
||||
|
||||
assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY
|
||||
assert (
|
||||
len(
|
||||
hass.config_entries.flow.async_progress_by_handler(
|
||||
DOMAIN, match_context={"source": SOURCE_REAUTH}
|
||||
)
|
||||
)
|
||||
== 0
|
||||
)
|
||||
assert mock_config_entry.data[CONF_DEVICE_CONFIG] == DEVICE_CONFIG_DICT_AUTH
|
||||
assert mock_config_entry.data[CONF_DEVICE_CONFIG].get(CONF_HOST) == "127.0.0.1"
|
||||
assert mock_config_entry.data[CONF_CREDENTIALS_HASH] == CREDENTIALS_HASH_AUTH
|
||||
|
||||
NEW_DEVICE_CONFIG = {
|
||||
**DEVICE_CONFIG_DICT_AUTH,
|
||||
CONF_CONNECTION_TYPE: NEW_CONNECTION_TYPE_DICT,
|
||||
}
|
||||
config = DeviceConfig.from_dict(NEW_DEVICE_CONFIG)
|
||||
# Reset the connect mock so when the config flow reloads the entry it succeeds
|
||||
mock_connect["connect"].reset_mock(side_effect=True)
|
||||
bulb = _mocked_device(
|
||||
device_config=config,
|
||||
mac=mock_config_entry.unique_id,
|
||||
)
|
||||
mock_connect["connect"].return_value = bulb
|
||||
|
||||
discovery_result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY},
|
||||
data={
|
||||
CONF_HOST: "127.0.0.1",
|
||||
CONF_MAC: MAC_ADDRESS,
|
||||
CONF_ALIAS: ALIAS,
|
||||
CONF_DEVICE_CONFIG: NEW_DEVICE_CONFIG,
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done(wait_background_tasks=True)
|
||||
assert discovery_result["type"] is FlowResultType.ABORT
|
||||
assert discovery_result["reason"] == "already_configured"
|
||||
assert mock_config_entry.data[CONF_DEVICE_CONFIG] == NEW_DEVICE_CONFIG
|
||||
assert mock_config_entry.data[CONF_HOST] == "127.0.0.1"
|
||||
assert CREDENTIALS_HASH_AUTH not in mock_config_entry.data
|
||||
|
||||
assert mock_config_entry.state is ConfigEntryState.LOADED
|
||||
|
||||
mock_connect["connect"].assert_awaited_once_with(config=config)
|
||||
|
||||
|
||||
async def test_dhcp_discovery_with_ip_change(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
|
|
|
@ -7,12 +7,16 @@ from datetime import timedelta
|
|||
from unittest.mock import AsyncMock, MagicMock, PropertyMock, patch
|
||||
|
||||
from freezegun.api import FrozenDateTimeFactory
|
||||
from kasa import AuthenticationError, Feature, KasaException, Module
|
||||
from kasa import AuthenticationError, DeviceConfig, Feature, KasaException, Module
|
||||
import pytest
|
||||
|
||||
from homeassistant import setup
|
||||
from homeassistant.components import tplink
|
||||
from homeassistant.components.tplink.const import CONF_DEVICE_CONFIG, DOMAIN
|
||||
from homeassistant.components.tplink.const import (
|
||||
CONF_CREDENTIALS_HASH,
|
||||
CONF_DEVICE_CONFIG,
|
||||
DOMAIN,
|
||||
)
|
||||
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntryState
|
||||
from homeassistant.const import (
|
||||
CONF_AUTHENTICATION,
|
||||
|
@ -458,7 +462,214 @@ async def test_unlink_devices(
|
|||
expected_identifiers = identifiers[:expected_count]
|
||||
assert device_entries[0].identifiers == set(expected_identifiers)
|
||||
assert entry.version == 1
|
||||
assert entry.minor_version == 3
|
||||
assert entry.minor_version == 4
|
||||
|
||||
msg = f"{expected_message} identifiers for device dummy (hs300): {set(identifiers)}"
|
||||
assert msg in caplog.text
|
||||
|
||||
|
||||
async def test_move_credentials_hash(
|
||||
hass: HomeAssistant,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""Test credentials hash moved to parent.
|
||||
|
||||
As async_setup_entry will succeed the hash on the parent is updated
|
||||
from the device.
|
||||
"""
|
||||
device_config = {
|
||||
**DEVICE_CONFIG_AUTH.to_dict(
|
||||
exclude_credentials=True, credentials_hash="theHash"
|
||||
)
|
||||
}
|
||||
entry_data = {**CREATE_ENTRY_DATA_AUTH, CONF_DEVICE_CONFIG: device_config}
|
||||
|
||||
entry = MockConfigEntry(
|
||||
title="TPLink",
|
||||
domain=DOMAIN,
|
||||
data=entry_data,
|
||||
entry_id="123456",
|
||||
unique_id=MAC_ADDRESS,
|
||||
version=1,
|
||||
minor_version=3,
|
||||
)
|
||||
assert entry.data[CONF_DEVICE_CONFIG][CONF_CREDENTIALS_HASH] == "theHash"
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
async def _connect(config):
|
||||
config.credentials_hash = "theNewHash"
|
||||
return _mocked_device(device_config=config, credentials_hash="theNewHash")
|
||||
|
||||
with (
|
||||
patch("homeassistant.components.tplink.Device.connect", new=_connect),
|
||||
patch("homeassistant.components.tplink.PLATFORMS", []),
|
||||
):
|
||||
await hass.config_entries.async_setup(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert entry.minor_version == 4
|
||||
assert entry.state is ConfigEntryState.LOADED
|
||||
assert CONF_CREDENTIALS_HASH not in entry.data[CONF_DEVICE_CONFIG]
|
||||
assert CONF_CREDENTIALS_HASH in entry.data
|
||||
# Gets the new hash from the successful connection.
|
||||
assert entry.data[CONF_CREDENTIALS_HASH] == "theNewHash"
|
||||
assert "Migration to version 1.4 complete" in caplog.text
|
||||
|
||||
|
||||
async def test_move_credentials_hash_auth_error(
|
||||
hass: HomeAssistant,
|
||||
) -> None:
|
||||
"""Test credentials hash moved to parent.
|
||||
|
||||
If there is an auth error it should be deleted after migration
|
||||
in async_setup_entry.
|
||||
"""
|
||||
device_config = {
|
||||
**DEVICE_CONFIG_AUTH.to_dict(
|
||||
exclude_credentials=True, credentials_hash="theHash"
|
||||
)
|
||||
}
|
||||
entry_data = {**CREATE_ENTRY_DATA_AUTH, CONF_DEVICE_CONFIG: device_config}
|
||||
|
||||
entry = MockConfigEntry(
|
||||
title="TPLink",
|
||||
domain=DOMAIN,
|
||||
data=entry_data,
|
||||
unique_id=MAC_ADDRESS,
|
||||
version=1,
|
||||
minor_version=3,
|
||||
)
|
||||
assert entry.data[CONF_DEVICE_CONFIG][CONF_CREDENTIALS_HASH] == "theHash"
|
||||
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.tplink.Device.connect",
|
||||
side_effect=AuthenticationError,
|
||||
),
|
||||
patch("homeassistant.components.tplink.PLATFORMS", []),
|
||||
):
|
||||
entry.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert entry.minor_version == 4
|
||||
assert entry.state is ConfigEntryState.SETUP_ERROR
|
||||
assert CONF_CREDENTIALS_HASH not in entry.data[CONF_DEVICE_CONFIG]
|
||||
# Auth failure deletes the hash
|
||||
assert CONF_CREDENTIALS_HASH not in entry.data
|
||||
|
||||
|
||||
async def test_move_credentials_hash_other_error(
|
||||
hass: HomeAssistant,
|
||||
) -> None:
|
||||
"""Test credentials hash moved to parent.
|
||||
|
||||
When there is a KasaException the same hash should still be on the parent
|
||||
at the end of the test.
|
||||
"""
|
||||
device_config = {
|
||||
**DEVICE_CONFIG_AUTH.to_dict(
|
||||
exclude_credentials=True, credentials_hash="theHash"
|
||||
)
|
||||
}
|
||||
entry_data = {**CREATE_ENTRY_DATA_AUTH, CONF_DEVICE_CONFIG: device_config}
|
||||
|
||||
entry = MockConfigEntry(
|
||||
title="TPLink",
|
||||
domain=DOMAIN,
|
||||
data=entry_data,
|
||||
unique_id=MAC_ADDRESS,
|
||||
version=1,
|
||||
minor_version=3,
|
||||
)
|
||||
assert entry.data[CONF_DEVICE_CONFIG][CONF_CREDENTIALS_HASH] == "theHash"
|
||||
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.tplink.Device.connect", side_effect=KasaException
|
||||
),
|
||||
patch("homeassistant.components.tplink.PLATFORMS", []),
|
||||
):
|
||||
entry.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert entry.minor_version == 4
|
||||
assert entry.state is ConfigEntryState.SETUP_RETRY
|
||||
assert CONF_CREDENTIALS_HASH not in entry.data[CONF_DEVICE_CONFIG]
|
||||
assert CONF_CREDENTIALS_HASH in entry.data
|
||||
assert entry.data[CONF_CREDENTIALS_HASH] == "theHash"
|
||||
|
||||
|
||||
async def test_credentials_hash(
|
||||
hass: HomeAssistant,
|
||||
) -> None:
|
||||
"""Test credentials_hash used to call connect."""
|
||||
device_config = {**DEVICE_CONFIG_AUTH.to_dict(exclude_credentials=True)}
|
||||
entry_data = {
|
||||
**CREATE_ENTRY_DATA_AUTH,
|
||||
CONF_DEVICE_CONFIG: device_config,
|
||||
CONF_CREDENTIALS_HASH: "theHash",
|
||||
}
|
||||
|
||||
entry = MockConfigEntry(
|
||||
title="TPLink",
|
||||
domain=DOMAIN,
|
||||
data=entry_data,
|
||||
unique_id=MAC_ADDRESS,
|
||||
)
|
||||
|
||||
async def _connect(config):
|
||||
config.credentials_hash = "theHash"
|
||||
return _mocked_device(device_config=config, credentials_hash="theHash")
|
||||
|
||||
with (
|
||||
patch("homeassistant.components.tplink.PLATFORMS", []),
|
||||
patch("homeassistant.components.tplink.Device.connect", new=_connect),
|
||||
):
|
||||
entry.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert entry.state is ConfigEntryState.LOADED
|
||||
assert CONF_CREDENTIALS_HASH not in entry.data[CONF_DEVICE_CONFIG]
|
||||
assert CONF_CREDENTIALS_HASH in entry.data
|
||||
assert entry.data[CONF_DEVICE_CONFIG] == device_config
|
||||
assert entry.data[CONF_CREDENTIALS_HASH] == "theHash"
|
||||
|
||||
|
||||
async def test_credentials_hash_auth_error(
|
||||
hass: HomeAssistant,
|
||||
) -> None:
|
||||
"""Test credentials_hash is deleted after an auth failure."""
|
||||
device_config = {**DEVICE_CONFIG_AUTH.to_dict(exclude_credentials=True)}
|
||||
entry_data = {
|
||||
**CREATE_ENTRY_DATA_AUTH,
|
||||
CONF_DEVICE_CONFIG: device_config,
|
||||
CONF_CREDENTIALS_HASH: "theHash",
|
||||
}
|
||||
|
||||
entry = MockConfigEntry(
|
||||
title="TPLink",
|
||||
domain=DOMAIN,
|
||||
data=entry_data,
|
||||
unique_id=MAC_ADDRESS,
|
||||
)
|
||||
|
||||
with (
|
||||
patch("homeassistant.components.tplink.PLATFORMS", []),
|
||||
patch(
|
||||
"homeassistant.components.tplink.Device.connect",
|
||||
side_effect=AuthenticationError,
|
||||
) as connect_mock,
|
||||
):
|
||||
entry.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
expected_config = DeviceConfig.from_dict(
|
||||
DEVICE_CONFIG_AUTH.to_dict(exclude_credentials=True, credentials_hash="theHash")
|
||||
)
|
||||
connect_mock.assert_called_with(config=expected_config)
|
||||
assert entry.state is ConfigEntryState.SETUP_ERROR
|
||||
assert CONF_CREDENTIALS_HASH not in entry.data
|
||||
|
|
Loading…
Add table
Reference in a new issue