Update tplink device config during reauth flow (#122089)

This commit is contained in:
Steven B. 2024-07-17 20:07:53 +01:00 committed by GitHub
parent fa0a5451b9
commit 55cee89392
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 219 additions and 99 deletions

View File

@ -3,6 +3,7 @@
from __future__ import annotations from __future__ import annotations
from collections.abc import Mapping from collections.abc import Mapping
import logging
from typing import Any from typing import Any
from kasa import ( from kasa import (
@ -52,6 +53,8 @@ from .const import (
DOMAIN, DOMAIN,
) )
_LOGGER = logging.getLogger(__name__)
STEP_AUTH_DATA_SCHEMA = vol.Schema( STEP_AUTH_DATA_SCHEMA = vol.Schema(
{vol.Required(CONF_USERNAME): str, vol.Required(CONF_PASSWORD): str} {vol.Required(CONF_USERNAME): str, vol.Required(CONF_PASSWORD): str}
) )
@ -88,15 +91,10 @@ class TPLinkConfigFlow(ConfigFlow, domain=DOMAIN):
) )
@callback @callback
def _update_config_if_entry_in_setup_error( def _get_config_updates(
self, entry: ConfigEntry, host: str, config: dict self, entry: ConfigEntry, host: str, config: dict
) -> ConfigFlowResult | None: ) -> dict | None:
"""If discovery encounters a device that is in SETUP_ERROR or SETUP_RETRY update the device config.""" """Return updates if the host or device config has changed."""
if entry.state not in (
ConfigEntryState.SETUP_ERROR,
ConfigEntryState.SETUP_RETRY,
):
return None
entry_data = entry.data entry_data = entry.data
entry_config_dict = entry_data.get(CONF_DEVICE_CONFIG) entry_config_dict = entry_data.get(CONF_DEVICE_CONFIG)
if entry_config_dict == config and entry_data[CONF_HOST] == host: if entry_config_dict == config and entry_data[CONF_HOST] == host:
@ -110,11 +108,31 @@ class TPLinkConfigFlow(ConfigFlow, domain=DOMAIN):
!= config.get(CONF_CONNECTION_TYPE) != config.get(CONF_CONNECTION_TYPE)
): ):
updates.pop(CONF_CREDENTIALS_HASH, None) updates.pop(CONF_CREDENTIALS_HASH, None)
return self.async_update_reload_and_abort( _LOGGER.debug(
entry, "Connection type changed for %s from %s to: %s",
data=updates, host,
reason="already_configured", entry_config_dict.get(CONF_CONNECTION_TYPE),
) config.get(CONF_CONNECTION_TYPE),
)
return updates
@callback
def _update_config_if_entry_in_setup_error(
self, entry: ConfigEntry, host: str, config: dict
) -> ConfigFlowResult | None:
"""If discovery encounters a device that is in SETUP_ERROR or SETUP_RETRY update the device config."""
if entry.state not in (
ConfigEntryState.SETUP_ERROR,
ConfigEntryState.SETUP_RETRY,
):
return None
if updates := self._get_config_updates(entry, host, config):
return self.async_update_reload_and_abort(
entry,
data=updates,
reason="already_configured",
)
return None
async def _async_handle_discovery( async def _async_handle_discovery(
self, host: str, formatted_mac: str, config: dict | None = None self, host: str, formatted_mac: str, config: dict | None = None
@ -454,7 +472,7 @@ class TPLinkConfigFlow(ConfigFlow, domain=DOMAIN):
password = user_input[CONF_PASSWORD] password = user_input[CONF_PASSWORD]
credentials = Credentials(username, password) credentials = Credentials(username, password)
try: try:
await self._async_try_discover_and_update( device = await self._async_try_discover_and_update(
host, host,
credentials=credentials, credentials=credentials,
raise_on_progress=True, raise_on_progress=True,
@ -467,6 +485,11 @@ class TPLinkConfigFlow(ConfigFlow, domain=DOMAIN):
placeholders["error"] = str(ex) placeholders["error"] = str(ex)
else: else:
await set_credentials(self.hass, username, password) await set_credentials(self.hass, username, password)
config = device.config.to_dict(exclude_credentials=True)
if updates := self._get_config_updates(reauth_entry, host, config):
self.hass.config_entries.async_update_entry(
reauth_entry, data=updates
)
self.hass.async_create_task( self.hass.async_create_task(
self._async_reload_requires_auth_entries(), eager_start=False self._async_reload_requires_auth_entries(), eager_start=False
) )

View File

@ -57,25 +57,26 @@ CREDENTIALS_HASH_LEGACY = ""
DEVICE_CONFIG_LEGACY = DeviceConfig(IP_ADDRESS) DEVICE_CONFIG_LEGACY = DeviceConfig(IP_ADDRESS)
DEVICE_CONFIG_DICT_LEGACY = DEVICE_CONFIG_LEGACY.to_dict(exclude_credentials=True) DEVICE_CONFIG_DICT_LEGACY = DEVICE_CONFIG_LEGACY.to_dict(exclude_credentials=True)
CREDENTIALS = Credentials("foo", "bar") CREDENTIALS = Credentials("foo", "bar")
CREDENTIALS_HASH_AUTH = "abcdefghijklmnopqrstuv==" CREDENTIALS_HASH_AES = "AES/abcdefghijklmnopqrstuvabcdefghijklmnopqrstuv=="
DEVICE_CONFIG_AUTH = DeviceConfig( CREDENTIALS_HASH_KLAP = "KLAP/abcdefghijklmnopqrstuv=="
DEVICE_CONFIG_KLAP = DeviceConfig(
IP_ADDRESS, IP_ADDRESS,
credentials=CREDENTIALS, credentials=CREDENTIALS,
connection_type=DeviceConnectionParameters( connection_type=DeviceConnectionParameters(
DeviceFamily.IotSmartPlugSwitch, DeviceEncryptionType.Klap DeviceFamily.SmartTapoPlug, DeviceEncryptionType.Klap
), ),
uses_http=True, uses_http=True,
) )
DEVICE_CONFIG_AUTH2 = DeviceConfig( DEVICE_CONFIG_AES = DeviceConfig(
IP_ADDRESS2, IP_ADDRESS2,
credentials=CREDENTIALS, credentials=CREDENTIALS,
connection_type=DeviceConnectionParameters( connection_type=DeviceConnectionParameters(
DeviceFamily.IotSmartPlugSwitch, DeviceEncryptionType.Klap DeviceFamily.SmartTapoPlug, DeviceEncryptionType.Aes
), ),
uses_http=True, uses_http=True,
) )
DEVICE_CONFIG_DICT_AUTH = DEVICE_CONFIG_AUTH.to_dict(exclude_credentials=True) DEVICE_CONFIG_DICT_KLAP = DEVICE_CONFIG_KLAP.to_dict(exclude_credentials=True)
DEVICE_CONFIG_DICT_AUTH2 = DEVICE_CONFIG_AUTH2.to_dict(exclude_credentials=True) DEVICE_CONFIG_DICT_AES = DEVICE_CONFIG_AES.to_dict(exclude_credentials=True)
CREATE_ENTRY_DATA_LEGACY = { CREATE_ENTRY_DATA_LEGACY = {
CONF_HOST: IP_ADDRESS, CONF_HOST: IP_ADDRESS,
@ -84,24 +85,28 @@ CREATE_ENTRY_DATA_LEGACY = {
CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_LEGACY, CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_LEGACY,
} }
CREATE_ENTRY_DATA_AUTH = { CREATE_ENTRY_DATA_KLAP = {
CONF_HOST: IP_ADDRESS, CONF_HOST: IP_ADDRESS,
CONF_ALIAS: ALIAS, CONF_ALIAS: ALIAS,
CONF_MODEL: MODEL, CONF_MODEL: MODEL,
CONF_CREDENTIALS_HASH: CREDENTIALS_HASH_AUTH, CONF_CREDENTIALS_HASH: CREDENTIALS_HASH_KLAP,
CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_AUTH, CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_KLAP,
} }
CREATE_ENTRY_DATA_AUTH2 = { CREATE_ENTRY_DATA_AES = {
CONF_HOST: IP_ADDRESS2, CONF_HOST: IP_ADDRESS2,
CONF_ALIAS: ALIAS, CONF_ALIAS: ALIAS,
CONF_MODEL: MODEL, CONF_MODEL: MODEL,
CONF_CREDENTIALS_HASH: CREDENTIALS_HASH_AUTH, CONF_CREDENTIALS_HASH: CREDENTIALS_HASH_AES,
CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_AUTH2, CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_AES,
} }
NEW_CONNECTION_TYPE = DeviceConnectionParameters( CONNECTION_TYPE_KLAP = DeviceConnectionParameters(
DeviceFamily.IotSmartPlugSwitch, DeviceEncryptionType.Aes DeviceFamily.SmartTapoPlug, DeviceEncryptionType.Klap
) )
NEW_CONNECTION_TYPE_DICT = NEW_CONNECTION_TYPE.to_dict() CONNECTION_TYPE_KLAP_DICT = CONNECTION_TYPE_KLAP.to_dict()
CONNECTION_TYPE_AES = DeviceConnectionParameters(
DeviceFamily.SmartTapoPlug, DeviceEncryptionType.Aes
)
CONNECTION_TYPE_AES_DICT = CONNECTION_TYPE_AES.to_dict()
def _load_feature_fixtures(): def _load_feature_fixtures():
@ -187,7 +192,7 @@ def _mocked_device(
device_id=DEVICE_ID, device_id=DEVICE_ID,
alias=ALIAS, alias=ALIAS,
model=MODEL, model=MODEL,
ip_address=IP_ADDRESS, ip_address: str | None = None,
modules: list[str] | None = None, modules: list[str] | None = None,
children: list[Device] | None = None, children: list[Device] | None = None,
features: list[str | Feature] | None = None, features: list[str | Feature] | None = None,
@ -202,12 +207,17 @@ def _mocked_device(
device.mac = mac device.mac = mac
device.alias = alias device.alias = alias
device.model = model device.model = model
device.host = ip_address
device.device_id = device_id device.device_id = device_id
device.hw_info = {"sw_ver": "1.0.0", "hw_ver": "1.0.0"} device.hw_info = {"sw_ver": "1.0.0", "hw_ver": "1.0.0"}
device.modules = {} device.modules = {}
device.features = {} device.features = {}
if not ip_address:
ip_address = IP_ADDRESS
else:
device_config.host = ip_address
device.host = ip_address
if modules: if modules:
device.modules = { device.modules = {
module_name: MODULE_TO_MOCK_GEN[module_name](device) module_name: MODULE_TO_MOCK_GEN[module_name](device)

View File

@ -11,8 +11,10 @@ from homeassistant.core import HomeAssistant
from . import ( from . import (
CREATE_ENTRY_DATA_LEGACY, CREATE_ENTRY_DATA_LEGACY,
CREDENTIALS_HASH_AUTH, CREDENTIALS_HASH_AES,
DEVICE_CONFIG_AUTH, CREDENTIALS_HASH_KLAP,
DEVICE_CONFIG_AES,
DEVICE_CONFIG_KLAP,
IP_ADDRESS, IP_ADDRESS,
IP_ADDRESS2, IP_ADDRESS2,
MAC_ADDRESS, MAC_ADDRESS,
@ -32,14 +34,14 @@ def mock_discovery():
discover_single=DEFAULT, discover_single=DEFAULT,
) as mock_discovery: ) as mock_discovery:
device = _mocked_device( device = _mocked_device(
device_config=copy.deepcopy(DEVICE_CONFIG_AUTH), device_config=copy.deepcopy(DEVICE_CONFIG_KLAP),
credentials_hash=CREDENTIALS_HASH_AUTH, credentials_hash=CREDENTIALS_HASH_KLAP,
alias=None, alias=None,
) )
devices = { devices = {
"127.0.0.1": _mocked_device( "127.0.0.1": _mocked_device(
device_config=copy.deepcopy(DEVICE_CONFIG_AUTH), device_config=copy.deepcopy(DEVICE_CONFIG_KLAP),
credentials_hash=CREDENTIALS_HASH_AUTH, credentials_hash=CREDENTIALS_HASH_KLAP,
alias=None, alias=None,
) )
} }
@ -55,12 +57,15 @@ def mock_connect():
with patch("homeassistant.components.tplink.Device.connect") as mock_connect: with patch("homeassistant.components.tplink.Device.connect") as mock_connect:
devices = { devices = {
IP_ADDRESS: _mocked_device( IP_ADDRESS: _mocked_device(
device_config=DEVICE_CONFIG_AUTH, credentials_hash=CREDENTIALS_HASH_AUTH device_config=DEVICE_CONFIG_KLAP,
credentials_hash=CREDENTIALS_HASH_KLAP,
ip_address=IP_ADDRESS,
), ),
IP_ADDRESS2: _mocked_device( IP_ADDRESS2: _mocked_device(
device_config=DEVICE_CONFIG_AUTH, device_config=DEVICE_CONFIG_AES,
credentials_hash=CREDENTIALS_HASH_AUTH, credentials_hash=CREDENTIALS_HASH_AES,
mac=MAC_ADDRESS2, mac=MAC_ADDRESS2,
ip_address=IP_ADDRESS2,
), ),
} }

View File

@ -1,5 +1,6 @@
"""Test the tplink config flow.""" """Test the tplink config flow."""
import logging
from unittest.mock import AsyncMock, patch from unittest.mock import AsyncMock, patch
from kasa import TimeoutError from kasa import TimeoutError
@ -11,6 +12,7 @@ from homeassistant.components.tplink import (
DOMAIN, DOMAIN,
AuthenticationError, AuthenticationError,
Credentials, Credentials,
Device,
DeviceConfig, DeviceConfig,
KasaException, KasaException,
) )
@ -33,19 +35,21 @@ from homeassistant.data_entry_flow import FlowResultType
from . import ( from . import (
ALIAS, ALIAS,
CREATE_ENTRY_DATA_AUTH, CONNECTION_TYPE_KLAP_DICT,
CREATE_ENTRY_DATA_AUTH2, CREATE_ENTRY_DATA_AES,
CREATE_ENTRY_DATA_KLAP,
CREATE_ENTRY_DATA_LEGACY, CREATE_ENTRY_DATA_LEGACY,
CREDENTIALS_HASH_AUTH, CREDENTIALS_HASH_AES,
CREDENTIALS_HASH_KLAP,
DEFAULT_ENTRY_TITLE, DEFAULT_ENTRY_TITLE,
DEVICE_CONFIG_DICT_AUTH, DEVICE_CONFIG_DICT_AES,
DEVICE_CONFIG_DICT_KLAP,
DEVICE_CONFIG_DICT_LEGACY, DEVICE_CONFIG_DICT_LEGACY,
DHCP_FORMATTED_MAC_ADDRESS, DHCP_FORMATTED_MAC_ADDRESS,
IP_ADDRESS, IP_ADDRESS,
MAC_ADDRESS, MAC_ADDRESS,
MAC_ADDRESS2, MAC_ADDRESS2,
MODULE, MODULE,
NEW_CONNECTION_TYPE_DICT,
_mocked_device, _mocked_device,
_patch_connect, _patch_connect,
_patch_discovery, _patch_discovery,
@ -135,7 +139,7 @@ async def test_discovery_auth(
CONF_HOST: IP_ADDRESS, CONF_HOST: IP_ADDRESS,
CONF_MAC: MAC_ADDRESS, CONF_MAC: MAC_ADDRESS,
CONF_ALIAS: ALIAS, CONF_ALIAS: ALIAS,
CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_AUTH, CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_KLAP,
}, },
) )
await hass.async_block_till_done() await hass.async_block_till_done()
@ -154,7 +158,7 @@ async def test_discovery_auth(
assert result2["type"] is FlowResultType.CREATE_ENTRY assert result2["type"] is FlowResultType.CREATE_ENTRY
assert result2["title"] == DEFAULT_ENTRY_TITLE assert result2["title"] == DEFAULT_ENTRY_TITLE
assert result2["data"] == CREATE_ENTRY_DATA_AUTH assert result2["data"] == CREATE_ENTRY_DATA_KLAP
assert result2["context"]["unique_id"] == MAC_ADDRESS assert result2["context"]["unique_id"] == MAC_ADDRESS
@ -187,7 +191,7 @@ async def test_discovery_auth_errors(
CONF_HOST: IP_ADDRESS, CONF_HOST: IP_ADDRESS,
CONF_MAC: MAC_ADDRESS, CONF_MAC: MAC_ADDRESS,
CONF_ALIAS: ALIAS, CONF_ALIAS: ALIAS,
CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_AUTH, CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_KLAP,
}, },
) )
await hass.async_block_till_done() await hass.async_block_till_done()
@ -218,7 +222,7 @@ async def test_discovery_auth_errors(
}, },
) )
assert result3["type"] is FlowResultType.CREATE_ENTRY assert result3["type"] is FlowResultType.CREATE_ENTRY
assert result3["data"] == CREATE_ENTRY_DATA_AUTH assert result3["data"] == CREATE_ENTRY_DATA_KLAP
assert result3["context"]["unique_id"] == MAC_ADDRESS assert result3["context"]["unique_id"] == MAC_ADDRESS
@ -238,7 +242,7 @@ async def test_discovery_new_credentials(
CONF_HOST: IP_ADDRESS, CONF_HOST: IP_ADDRESS,
CONF_MAC: MAC_ADDRESS, CONF_MAC: MAC_ADDRESS,
CONF_ALIAS: ALIAS, CONF_ALIAS: ALIAS,
CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_AUTH, CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_KLAP,
}, },
) )
await hass.async_block_till_done() await hass.async_block_till_done()
@ -267,7 +271,7 @@ async def test_discovery_new_credentials(
{}, {},
) )
assert result3["type"] is FlowResultType.CREATE_ENTRY assert result3["type"] is FlowResultType.CREATE_ENTRY
assert result3["data"] == CREATE_ENTRY_DATA_AUTH assert result3["data"] == CREATE_ENTRY_DATA_KLAP
assert result3["context"]["unique_id"] == MAC_ADDRESS assert result3["context"]["unique_id"] == MAC_ADDRESS
@ -290,7 +294,7 @@ async def test_discovery_new_credentials_invalid(
CONF_HOST: IP_ADDRESS, CONF_HOST: IP_ADDRESS,
CONF_MAC: MAC_ADDRESS, CONF_MAC: MAC_ADDRESS,
CONF_ALIAS: ALIAS, CONF_ALIAS: ALIAS,
CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_AUTH, CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_KLAP,
}, },
) )
await hass.async_block_till_done() await hass.async_block_till_done()
@ -323,7 +327,7 @@ async def test_discovery_new_credentials_invalid(
}, },
) )
assert result3["type"] is FlowResultType.CREATE_ENTRY assert result3["type"] is FlowResultType.CREATE_ENTRY
assert result3["data"] == CREATE_ENTRY_DATA_AUTH assert result3["data"] == CREATE_ENTRY_DATA_KLAP
assert result3["context"]["unique_id"] == MAC_ADDRESS assert result3["context"]["unique_id"] == MAC_ADDRESS
@ -543,7 +547,7 @@ async def test_manual_auth(
await hass.async_block_till_done() await hass.async_block_till_done()
assert result3["type"] is FlowResultType.CREATE_ENTRY assert result3["type"] is FlowResultType.CREATE_ENTRY
assert result3["title"] == DEFAULT_ENTRY_TITLE assert result3["title"] == DEFAULT_ENTRY_TITLE
assert result3["data"] == CREATE_ENTRY_DATA_AUTH assert result3["data"] == CREATE_ENTRY_DATA_KLAP
assert result3["context"]["unique_id"] == MAC_ADDRESS assert result3["context"]["unique_id"] == MAC_ADDRESS
@ -607,7 +611,7 @@ async def test_manual_auth_errors(
}, },
) )
assert result4["type"] is FlowResultType.CREATE_ENTRY assert result4["type"] is FlowResultType.CREATE_ENTRY
assert result4["data"] == CREATE_ENTRY_DATA_AUTH assert result4["data"] == CREATE_ENTRY_DATA_KLAP
assert result4["context"]["unique_id"] == MAC_ADDRESS assert result4["context"]["unique_id"] == MAC_ADDRESS
await hass.async_block_till_done() await hass.async_block_till_done()
@ -791,16 +795,16 @@ async def test_integration_discovery_with_ip_change(
CONF_HOST: "127.0.0.2", CONF_HOST: "127.0.0.2",
CONF_MAC: MAC_ADDRESS, CONF_MAC: MAC_ADDRESS,
CONF_ALIAS: ALIAS, CONF_ALIAS: ALIAS,
CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_AUTH, CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_KLAP,
}, },
) )
await hass.async_block_till_done() await hass.async_block_till_done()
assert discovery_result["type"] is FlowResultType.ABORT assert discovery_result["type"] is FlowResultType.ABORT
assert discovery_result["reason"] == "already_configured" assert discovery_result["reason"] == "already_configured"
assert mock_config_entry.data[CONF_DEVICE_CONFIG] == DEVICE_CONFIG_DICT_AUTH assert mock_config_entry.data[CONF_DEVICE_CONFIG] == DEVICE_CONFIG_DICT_KLAP
assert mock_config_entry.data[CONF_HOST] == "127.0.0.2" assert mock_config_entry.data[CONF_HOST] == "127.0.0.2"
config = DeviceConfig.from_dict(DEVICE_CONFIG_DICT_AUTH) config = DeviceConfig.from_dict(DEVICE_CONFIG_DICT_KLAP)
mock_connect["connect"].reset_mock(side_effect=True) mock_connect["connect"].reset_mock(side_effect=True)
bulb = _mocked_device( bulb = _mocked_device(
@ -832,8 +836,8 @@ async def test_integration_discovery_with_connection_change(
mock_config_entry = MockConfigEntry( mock_config_entry = MockConfigEntry(
title="TPLink", title="TPLink",
domain=DOMAIN, domain=DOMAIN,
data=CREATE_ENTRY_DATA_AUTH, data=CREATE_ENTRY_DATA_AES,
unique_id=MAC_ADDRESS, unique_id=MAC_ADDRESS2,
) )
mock_config_entry.add_to_hass(hass) mock_config_entry.add_to_hass(hass)
with patch("homeassistant.components.tplink.Discover.discover", return_value={}): with patch("homeassistant.components.tplink.Discover.discover", return_value={}):
@ -849,13 +853,15 @@ async def test_integration_discovery_with_connection_change(
) )
== 0 == 0
) )
assert mock_config_entry.data[CONF_DEVICE_CONFIG] == DEVICE_CONFIG_DICT_AUTH assert mock_config_entry.data[CONF_HOST] == "127.0.0.2"
assert mock_config_entry.data[CONF_DEVICE_CONFIG].get(CONF_HOST) == "127.0.0.1" assert mock_config_entry.data[CONF_DEVICE_CONFIG] == DEVICE_CONFIG_DICT_AES
assert mock_config_entry.data[CONF_CREDENTIALS_HASH] == CREDENTIALS_HASH_AUTH assert mock_config_entry.data[CONF_DEVICE_CONFIG].get(CONF_HOST) == "127.0.0.2"
assert mock_config_entry.data[CONF_CREDENTIALS_HASH] == CREDENTIALS_HASH_AES
NEW_DEVICE_CONFIG = { NEW_DEVICE_CONFIG = {
**DEVICE_CONFIG_DICT_AUTH, **DEVICE_CONFIG_DICT_KLAP,
CONF_CONNECTION_TYPE: NEW_CONNECTION_TYPE_DICT, CONF_CONNECTION_TYPE: CONNECTION_TYPE_KLAP_DICT,
CONF_HOST: "127.0.0.2",
} }
config = DeviceConfig.from_dict(NEW_DEVICE_CONFIG) config = DeviceConfig.from_dict(NEW_DEVICE_CONFIG)
# Reset the connect mock so when the config flow reloads the entry it succeeds # Reset the connect mock so when the config flow reloads the entry it succeeds
@ -870,8 +876,8 @@ async def test_integration_discovery_with_connection_change(
DOMAIN, DOMAIN,
context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY}, context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY},
data={ data={
CONF_HOST: "127.0.0.1", CONF_HOST: "127.0.0.2",
CONF_MAC: MAC_ADDRESS, CONF_MAC: MAC_ADDRESS2,
CONF_ALIAS: ALIAS, CONF_ALIAS: ALIAS,
CONF_DEVICE_CONFIG: NEW_DEVICE_CONFIG, CONF_DEVICE_CONFIG: NEW_DEVICE_CONFIG,
}, },
@ -880,8 +886,8 @@ async def test_integration_discovery_with_connection_change(
assert discovery_result["type"] is FlowResultType.ABORT assert discovery_result["type"] is FlowResultType.ABORT
assert discovery_result["reason"] == "already_configured" assert discovery_result["reason"] == "already_configured"
assert mock_config_entry.data[CONF_DEVICE_CONFIG] == NEW_DEVICE_CONFIG assert mock_config_entry.data[CONF_DEVICE_CONFIG] == NEW_DEVICE_CONFIG
assert mock_config_entry.data[CONF_HOST] == "127.0.0.1" assert mock_config_entry.data[CONF_HOST] == "127.0.0.2"
assert CREDENTIALS_HASH_AUTH not in mock_config_entry.data assert CREDENTIALS_HASH_AES not in mock_config_entry.data
assert mock_config_entry.state is ConfigEntryState.LOADED assert mock_config_entry.state is ConfigEntryState.LOADED
@ -953,6 +959,77 @@ async def test_reauth(
await hass.async_block_till_done() await hass.async_block_till_done()
async def test_reauth_update_with_encryption_change(
hass: HomeAssistant,
mock_discovery: AsyncMock,
mock_connect: AsyncMock,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test reauth flow."""
orig_side_effect = mock_connect["connect"].side_effect
mock_connect["connect"].side_effect = AuthenticationError()
mock_config_entry = MockConfigEntry(
title="TPLink",
domain=DOMAIN,
data={**CREATE_ENTRY_DATA_AES},
unique_id=MAC_ADDRESS2,
)
mock_config_entry.add_to_hass(hass)
assert mock_config_entry.data[CONF_DEVICE_CONFIG] == DEVICE_CONFIG_DICT_AES
assert mock_config_entry.data[CONF_CREDENTIALS_HASH] == CREDENTIALS_HASH_AES
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()
assert mock_config_entry.state is ConfigEntryState.SETUP_ERROR
caplog.set_level(logging.DEBUG)
flows = hass.config_entries.flow.async_progress()
assert len(flows) == 1
[result] = flows
assert result["step_id"] == "reauth_confirm"
assert mock_config_entry.data[CONF_DEVICE_CONFIG] == DEVICE_CONFIG_DICT_AES
assert CONF_CREDENTIALS_HASH not in mock_config_entry.data
new_config = DeviceConfig(
"127.0.0.2",
credentials=None,
connection_type=Device.ConnectionParameters(
Device.Family.SmartTapoPlug, Device.EncryptionType.Klap
),
uses_http=True,
)
mock_discovery["mock_device"].host = "127.0.0.2"
mock_discovery["mock_device"].config = new_config
mock_discovery["mock_device"].credentials_hash = None
mock_connect["mock_devices"]["127.0.0.2"].config = new_config
mock_connect["mock_devices"]["127.0.0.2"].credentials_hash = CREDENTIALS_HASH_KLAP
mock_connect["connect"].side_effect = orig_side_effect
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={
CONF_USERNAME: "fake_username",
CONF_PASSWORD: "fake_password",
},
)
await hass.async_block_till_done(wait_background_tasks=True)
assert "Connection type changed for 127.0.0.2" in caplog.text
credentials = Credentials("fake_username", "fake_password")
mock_discovery["discover_single"].assert_called_once_with(
"127.0.0.2", credentials=credentials
)
mock_discovery["mock_device"].update.assert_called_once_with()
assert result2["type"] is FlowResultType.ABORT
assert result2["reason"] == "reauth_successful"
assert mock_config_entry.state is ConfigEntryState.LOADED
assert mock_config_entry.data[CONF_DEVICE_CONFIG] == {
**DEVICE_CONFIG_DICT_KLAP,
CONF_HOST: "127.0.0.2",
}
assert mock_config_entry.data[CONF_CREDENTIALS_HASH] == CREDENTIALS_HASH_KLAP
async def test_reauth_update_from_discovery( async def test_reauth_update_from_discovery(
hass: HomeAssistant, hass: HomeAssistant,
mock_config_entry: MockConfigEntry, mock_config_entry: MockConfigEntry,
@ -981,13 +1058,13 @@ async def test_reauth_update_from_discovery(
CONF_HOST: IP_ADDRESS, CONF_HOST: IP_ADDRESS,
CONF_MAC: MAC_ADDRESS, CONF_MAC: MAC_ADDRESS,
CONF_ALIAS: ALIAS, CONF_ALIAS: ALIAS,
CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_AUTH, CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_KLAP,
}, },
) )
await hass.async_block_till_done() await hass.async_block_till_done()
assert discovery_result["type"] is FlowResultType.ABORT assert discovery_result["type"] is FlowResultType.ABORT
assert discovery_result["reason"] == "already_configured" assert discovery_result["reason"] == "already_configured"
assert mock_config_entry.data[CONF_DEVICE_CONFIG] == DEVICE_CONFIG_DICT_AUTH assert mock_config_entry.data[CONF_DEVICE_CONFIG] == DEVICE_CONFIG_DICT_KLAP
async def test_reauth_update_from_discovery_with_ip_change( async def test_reauth_update_from_discovery_with_ip_change(
@ -1017,13 +1094,13 @@ async def test_reauth_update_from_discovery_with_ip_change(
CONF_HOST: "127.0.0.2", CONF_HOST: "127.0.0.2",
CONF_MAC: MAC_ADDRESS, CONF_MAC: MAC_ADDRESS,
CONF_ALIAS: ALIAS, CONF_ALIAS: ALIAS,
CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_AUTH, CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_KLAP,
}, },
) )
await hass.async_block_till_done() await hass.async_block_till_done()
assert discovery_result["type"] is FlowResultType.ABORT assert discovery_result["type"] is FlowResultType.ABORT
assert discovery_result["reason"] == "already_configured" assert discovery_result["reason"] == "already_configured"
assert mock_config_entry.data[CONF_DEVICE_CONFIG] == DEVICE_CONFIG_DICT_AUTH assert mock_config_entry.data[CONF_DEVICE_CONFIG] == DEVICE_CONFIG_DICT_KLAP
assert mock_config_entry.data[CONF_HOST] == "127.0.0.2" assert mock_config_entry.data[CONF_HOST] == "127.0.0.2"
@ -1040,7 +1117,7 @@ async def test_reauth_no_update_if_config_and_ip_the_same(
mock_config_entry, mock_config_entry,
data={ data={
**mock_config_entry.data, **mock_config_entry.data,
CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_AUTH, CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_KLAP,
}, },
) )
await hass.config_entries.async_setup(mock_config_entry.entry_id) await hass.config_entries.async_setup(mock_config_entry.entry_id)
@ -1051,7 +1128,7 @@ async def test_reauth_no_update_if_config_and_ip_the_same(
assert len(flows) == 1 assert len(flows) == 1
[result] = flows [result] = flows
assert result["step_id"] == "reauth_confirm" assert result["step_id"] == "reauth_confirm"
assert mock_config_entry.data[CONF_DEVICE_CONFIG] == DEVICE_CONFIG_DICT_AUTH assert mock_config_entry.data[CONF_DEVICE_CONFIG] == DEVICE_CONFIG_DICT_KLAP
discovery_result = await hass.config_entries.flow.async_init( discovery_result = await hass.config_entries.flow.async_init(
DOMAIN, DOMAIN,
@ -1060,13 +1137,13 @@ async def test_reauth_no_update_if_config_and_ip_the_same(
CONF_HOST: IP_ADDRESS, CONF_HOST: IP_ADDRESS,
CONF_MAC: MAC_ADDRESS, CONF_MAC: MAC_ADDRESS,
CONF_ALIAS: ALIAS, CONF_ALIAS: ALIAS,
CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_AUTH, CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_KLAP,
}, },
) )
await hass.async_block_till_done() await hass.async_block_till_done()
assert discovery_result["type"] is FlowResultType.ABORT assert discovery_result["type"] is FlowResultType.ABORT
assert discovery_result["reason"] == "already_configured" assert discovery_result["reason"] == "already_configured"
assert mock_config_entry.data[CONF_DEVICE_CONFIG] == DEVICE_CONFIG_DICT_AUTH assert mock_config_entry.data[CONF_DEVICE_CONFIG] == DEVICE_CONFIG_DICT_KLAP
assert mock_config_entry.data[CONF_HOST] == IP_ADDRESS assert mock_config_entry.data[CONF_HOST] == IP_ADDRESS
@ -1214,15 +1291,20 @@ async def test_discovery_timeout_connect(
async def test_reauth_update_other_flows( async def test_reauth_update_other_flows(
hass: HomeAssistant, hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_discovery: AsyncMock, mock_discovery: AsyncMock,
mock_connect: AsyncMock, mock_connect: AsyncMock,
) -> None: ) -> None:
"""Test reauth updates other reauth flows.""" """Test reauth updates other reauth flows."""
mock_config_entry = MockConfigEntry(
title="TPLink",
domain=DOMAIN,
data={**CREATE_ENTRY_DATA_KLAP},
unique_id=MAC_ADDRESS,
)
mock_config_entry2 = MockConfigEntry( mock_config_entry2 = MockConfigEntry(
title="TPLink", title="TPLink",
domain=DOMAIN, domain=DOMAIN,
data={**CREATE_ENTRY_DATA_AUTH2}, data={**CREATE_ENTRY_DATA_AES},
unique_id=MAC_ADDRESS2, unique_id=MAC_ADDRESS2,
) )
default_side_effect = mock_connect["connect"].side_effect default_side_effect = mock_connect["connect"].side_effect
@ -1244,7 +1326,7 @@ async def test_reauth_update_other_flows(
flows_by_entry_id = {flow["context"]["entry_id"]: flow for flow in flows} flows_by_entry_id = {flow["context"]["entry_id"]: flow for flow in flows}
result = flows_by_entry_id[mock_config_entry.entry_id] result = flows_by_entry_id[mock_config_entry.entry_id]
assert result["step_id"] == "reauth_confirm" assert result["step_id"] == "reauth_confirm"
assert mock_config_entry.data[CONF_DEVICE_CONFIG] == DEVICE_CONFIG_DICT_LEGACY assert mock_config_entry.data[CONF_DEVICE_CONFIG] == DEVICE_CONFIG_DICT_KLAP
result2 = await hass.config_entries.flow.async_configure( result2 = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
user_input={ user_input={

View File

@ -33,9 +33,9 @@ from homeassistant.setup import async_setup_component
from homeassistant.util import dt as dt_util from homeassistant.util import dt as dt_util
from . import ( from . import (
CREATE_ENTRY_DATA_AUTH, CREATE_ENTRY_DATA_KLAP,
CREATE_ENTRY_DATA_LEGACY, CREATE_ENTRY_DATA_LEGACY,
DEVICE_CONFIG_AUTH, DEVICE_CONFIG_KLAP,
DEVICE_ID, DEVICE_ID,
DEVICE_ID_MAC, DEVICE_ID_MAC,
IP_ADDRESS, IP_ADDRESS,
@ -178,7 +178,7 @@ async def test_config_entry_device_config(
mock_config_entry = MockConfigEntry( mock_config_entry = MockConfigEntry(
title="TPLink", title="TPLink",
domain=DOMAIN, domain=DOMAIN,
data={**CREATE_ENTRY_DATA_AUTH}, data={**CREATE_ENTRY_DATA_KLAP},
unique_id=MAC_ADDRESS, unique_id=MAC_ADDRESS,
) )
mock_config_entry.add_to_hass(hass) mock_config_entry.add_to_hass(hass)
@ -197,7 +197,7 @@ async def test_config_entry_with_stored_credentials(
mock_config_entry = MockConfigEntry( mock_config_entry = MockConfigEntry(
title="TPLink", title="TPLink",
domain=DOMAIN, domain=DOMAIN,
data={**CREATE_ENTRY_DATA_AUTH}, data={**CREATE_ENTRY_DATA_KLAP},
unique_id=MAC_ADDRESS, unique_id=MAC_ADDRESS,
) )
auth = { auth = {
@ -210,7 +210,7 @@ async def test_config_entry_with_stored_credentials(
await hass.config_entries.async_setup(mock_config_entry.entry_id) await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()
assert mock_config_entry.state is ConfigEntryState.LOADED assert mock_config_entry.state is ConfigEntryState.LOADED
config = DEVICE_CONFIG_AUTH config = DEVICE_CONFIG_KLAP
assert config.credentials != stored_credentials assert config.credentials != stored_credentials
config.credentials = stored_credentials config.credentials = stored_credentials
mock_connect["connect"].assert_called_once_with(config=config) mock_connect["connect"].assert_called_once_with(config=config)
@ -223,7 +223,7 @@ async def test_config_entry_device_config_invalid(
caplog: pytest.LogCaptureFixture, caplog: pytest.LogCaptureFixture,
) -> None: ) -> None:
"""Test that an invalid device config logs an error and loads the config entry.""" """Test that an invalid device config logs an error and loads the config entry."""
entry_data = copy.deepcopy(CREATE_ENTRY_DATA_AUTH) entry_data = copy.deepcopy(CREATE_ENTRY_DATA_KLAP)
entry_data[CONF_DEVICE_CONFIG] = {"foo": "bar"} entry_data[CONF_DEVICE_CONFIG] = {"foo": "bar"}
mock_config_entry = MockConfigEntry( mock_config_entry = MockConfigEntry(
title="TPLink", title="TPLink",
@ -263,7 +263,7 @@ async def test_config_entry_errors(
mock_config_entry = MockConfigEntry( mock_config_entry = MockConfigEntry(
title="TPLink", title="TPLink",
domain=DOMAIN, domain=DOMAIN,
data={**CREATE_ENTRY_DATA_AUTH}, data={**CREATE_ENTRY_DATA_KLAP},
unique_id=MAC_ADDRESS, unique_id=MAC_ADDRESS,
) )
mock_config_entry.add_to_hass(hass) mock_config_entry.add_to_hass(hass)
@ -520,11 +520,11 @@ async def test_move_credentials_hash(
from the device. from the device.
""" """
device_config = { device_config = {
**DEVICE_CONFIG_AUTH.to_dict( **DEVICE_CONFIG_KLAP.to_dict(
exclude_credentials=True, credentials_hash="theHash" exclude_credentials=True, credentials_hash="theHash"
) )
} }
entry_data = {**CREATE_ENTRY_DATA_AUTH, CONF_DEVICE_CONFIG: device_config} entry_data = {**CREATE_ENTRY_DATA_KLAP, CONF_DEVICE_CONFIG: device_config}
entry = MockConfigEntry( entry = MockConfigEntry(
title="TPLink", title="TPLink",
@ -567,11 +567,11 @@ async def test_move_credentials_hash_auth_error(
in async_setup_entry. in async_setup_entry.
""" """
device_config = { device_config = {
**DEVICE_CONFIG_AUTH.to_dict( **DEVICE_CONFIG_KLAP.to_dict(
exclude_credentials=True, credentials_hash="theHash" exclude_credentials=True, credentials_hash="theHash"
) )
} }
entry_data = {**CREATE_ENTRY_DATA_AUTH, CONF_DEVICE_CONFIG: device_config} entry_data = {**CREATE_ENTRY_DATA_KLAP, CONF_DEVICE_CONFIG: device_config}
entry = MockConfigEntry( entry = MockConfigEntry(
title="TPLink", title="TPLink",
@ -610,11 +610,11 @@ async def test_move_credentials_hash_other_error(
at the end of the test. at the end of the test.
""" """
device_config = { device_config = {
**DEVICE_CONFIG_AUTH.to_dict( **DEVICE_CONFIG_KLAP.to_dict(
exclude_credentials=True, credentials_hash="theHash" exclude_credentials=True, credentials_hash="theHash"
) )
} }
entry_data = {**CREATE_ENTRY_DATA_AUTH, CONF_DEVICE_CONFIG: device_config} entry_data = {**CREATE_ENTRY_DATA_KLAP, CONF_DEVICE_CONFIG: device_config}
entry = MockConfigEntry( entry = MockConfigEntry(
title="TPLink", title="TPLink",
@ -647,9 +647,9 @@ async def test_credentials_hash(
hass: HomeAssistant, hass: HomeAssistant,
) -> None: ) -> None:
"""Test credentials_hash used to call connect.""" """Test credentials_hash used to call connect."""
device_config = {**DEVICE_CONFIG_AUTH.to_dict(exclude_credentials=True)} device_config = {**DEVICE_CONFIG_KLAP.to_dict(exclude_credentials=True)}
entry_data = { entry_data = {
**CREATE_ENTRY_DATA_AUTH, **CREATE_ENTRY_DATA_KLAP,
CONF_DEVICE_CONFIG: device_config, CONF_DEVICE_CONFIG: device_config,
CONF_CREDENTIALS_HASH: "theHash", CONF_CREDENTIALS_HASH: "theHash",
} }
@ -684,9 +684,9 @@ async def test_credentials_hash_auth_error(
hass: HomeAssistant, hass: HomeAssistant,
) -> None: ) -> None:
"""Test credentials_hash is deleted after an auth failure.""" """Test credentials_hash is deleted after an auth failure."""
device_config = {**DEVICE_CONFIG_AUTH.to_dict(exclude_credentials=True)} device_config = {**DEVICE_CONFIG_KLAP.to_dict(exclude_credentials=True)}
entry_data = { entry_data = {
**CREATE_ENTRY_DATA_AUTH, **CREATE_ENTRY_DATA_KLAP,
CONF_DEVICE_CONFIG: device_config, CONF_DEVICE_CONFIG: device_config,
CONF_CREDENTIALS_HASH: "theHash", CONF_CREDENTIALS_HASH: "theHash",
} }
@ -710,7 +710,7 @@ async def test_credentials_hash_auth_error(
await hass.async_block_till_done() await hass.async_block_till_done()
expected_config = DeviceConfig.from_dict( expected_config = DeviceConfig.from_dict(
DEVICE_CONFIG_AUTH.to_dict(exclude_credentials=True, credentials_hash="theHash") DEVICE_CONFIG_KLAP.to_dict(exclude_credentials=True, credentials_hash="theHash")
) )
connect_mock.assert_called_with(config=expected_config) connect_mock.assert_called_with(config=expected_config)
assert entry.state is ConfigEntryState.SETUP_ERROR assert entry.state is ConfigEntryState.SETUP_ERROR