Improve human-readable name for new/reauth/reconfig in ESPHome (#143302)

* Improve human-readable prompt when requesting ESPHome credentials

Users reported difficulty identifying which device needs reauthentication, especially when names are similar (e.g., `power-meter` vs `power-meter-EEFF`). Previously, only the hostname was shown, which led to confusion. This change includes the config entry title or friendly name—when available—in the prompt to make device identification easier.

* Update homeassistant/components/esphome/config_flow.py

* add missing cover

* tweaks

* one more

* one more

* cover

* some are ``, some are not, make them all ``

* Apply suggestions from code review

---------

Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
This commit is contained in:
J. Nick Koston 2025-04-21 04:25:14 -10:00 committed by GitHub
parent ba6ce28d3c
commit 849121a124
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 146 additions and 16 deletions

View File

@ -57,6 +57,7 @@ ERROR_INVALID_ENCRYPTION_KEY = "invalid_psk"
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
ZERO_NOISE_PSK = "MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA=" ZERO_NOISE_PSK = "MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA="
DEFAULT_NAME = "ESPHome"
class EsphomeFlowHandler(ConfigFlow, domain=DOMAIN): class EsphomeFlowHandler(ConfigFlow, domain=DOMAIN):
@ -117,8 +118,8 @@ class EsphomeFlowHandler(ConfigFlow, domain=DOMAIN):
self._host = entry_data[CONF_HOST] self._host = entry_data[CONF_HOST]
self._port = entry_data[CONF_PORT] self._port = entry_data[CONF_PORT]
self._password = entry_data[CONF_PASSWORD] self._password = entry_data[CONF_PASSWORD]
self._name = self._reauth_entry.title
self._device_name = entry_data.get(CONF_DEVICE_NAME) self._device_name = entry_data.get(CONF_DEVICE_NAME)
self._name = self._reauth_entry.title
# Device without encryption allows fetching device info. We can then check # Device without encryption allows fetching device info. We can then check
# if the device is no longer using a password. If we did try with a password, # if the device is no longer using a password. If we did try with a password,
@ -147,7 +148,7 @@ class EsphomeFlowHandler(ConfigFlow, domain=DOMAIN):
return self.async_show_form( return self.async_show_form(
step_id="reauth_encryption_removed_confirm", step_id="reauth_encryption_removed_confirm",
description_placeholders={"name": self._name}, description_placeholders={"name": self._async_get_human_readable_name()},
) )
async def async_step_reauth_confirm( async def async_step_reauth_confirm(
@ -172,7 +173,7 @@ class EsphomeFlowHandler(ConfigFlow, domain=DOMAIN):
step_id="reauth_confirm", step_id="reauth_confirm",
data_schema=vol.Schema({vol.Required(CONF_NOISE_PSK): str}), data_schema=vol.Schema({vol.Required(CONF_NOISE_PSK): str}),
errors=errors, errors=errors,
description_placeholders={"name": self._name}, description_placeholders={"name": self._async_get_human_readable_name()},
) )
async def async_step_reconfigure( async def async_step_reconfigure(
@ -189,12 +190,14 @@ class EsphomeFlowHandler(ConfigFlow, domain=DOMAIN):
@property @property
def _name(self) -> str: def _name(self) -> str:
return self.__name or "ESPHome" return self.__name or DEFAULT_NAME
@_name.setter @_name.setter
def _name(self, value: str) -> None: def _name(self, value: str) -> None:
self.__name = value self.__name = value
self.context["title_placeholders"] = {"name": self._name} self.context["title_placeholders"] = {
"name": self._async_get_human_readable_name()
}
async def _async_try_fetch_device_info(self) -> ConfigFlowResult: async def _async_try_fetch_device_info(self) -> ConfigFlowResult:
"""Try to fetch device info and return any errors.""" """Try to fetch device info and return any errors."""
@ -254,7 +257,8 @@ class EsphomeFlowHandler(ConfigFlow, domain=DOMAIN):
if user_input is not None: if user_input is not None:
return await self._async_try_fetch_device_info() return await self._async_try_fetch_device_info()
return self.async_show_form( return self.async_show_form(
step_id="discovery_confirm", description_placeholders={"name": self._name} step_id="discovery_confirm",
description_placeholders={"name": self._async_get_human_readable_name()},
) )
async def async_step_zeroconf( async def async_step_zeroconf(
@ -274,8 +278,8 @@ class EsphomeFlowHandler(ConfigFlow, domain=DOMAIN):
# Hostname is format: livingroom.local. # Hostname is format: livingroom.local.
device_name = discovery_info.hostname.removesuffix(".local.") device_name = discovery_info.hostname.removesuffix(".local.")
self._name = discovery_info.properties.get("friendly_name", device_name)
self._device_name = device_name self._device_name = device_name
self._name = discovery_info.properties.get("friendly_name", device_name)
self._host = discovery_info.host self._host = discovery_info.host
self._port = discovery_info.port self._port = discovery_info.port
self._noise_required = bool(discovery_info.properties.get("api_encryption")) self._noise_required = bool(discovery_info.properties.get("api_encryption"))
@ -593,9 +597,30 @@ class EsphomeFlowHandler(ConfigFlow, domain=DOMAIN):
step_id="encryption_key", step_id="encryption_key",
data_schema=vol.Schema({vol.Required(CONF_NOISE_PSK): str}), data_schema=vol.Schema({vol.Required(CONF_NOISE_PSK): str}),
errors=errors, errors=errors,
description_placeholders={"name": self._name}, description_placeholders={"name": self._async_get_human_readable_name()},
) )
@callback
def _async_get_human_readable_name(self) -> str:
"""Return a human readable name for the entry."""
entry: ConfigEntry | None = None
if self.source == SOURCE_REAUTH:
entry = self._reauth_entry
elif self.source == SOURCE_RECONFIGURE:
entry = self._reconfig_entry
friendly_name = self._name
device_name = self._device_name
if (
device_name
and friendly_name in (DEFAULT_NAME, device_name)
and entry
and entry.title != friendly_name
):
friendly_name = entry.title
if not device_name or friendly_name == device_name:
return friendly_name
return f"{friendly_name} ({device_name})"
async def async_step_authenticate( async def async_step_authenticate(
self, user_input: dict[str, Any] | None = None, error: str | None = None self, user_input: dict[str, Any] | None = None, error: str | None = None
) -> ConfigFlowResult: ) -> ConfigFlowResult:
@ -614,7 +639,7 @@ class EsphomeFlowHandler(ConfigFlow, domain=DOMAIN):
return self.async_show_form( return self.async_show_form(
step_id="authenticate", step_id="authenticate",
data_schema=vol.Schema({vol.Required("password"): str}), data_schema=vol.Schema({vol.Required("password"): str}),
description_placeholders={"name": self._name}, description_placeholders={"name": self._async_get_human_readable_name()},
errors=errors, errors=errors,
) )
@ -648,9 +673,9 @@ class EsphomeFlowHandler(ConfigFlow, domain=DOMAIN):
return "connection_error" return "connection_error"
finally: finally:
await cli.disconnect(force=True) await cli.disconnect(force=True)
self._name = self._device_info.friendly_name or self._device_info.name
self._device_name = self._device_info.name
self._device_mac = format_mac(self._device_info.mac_address) self._device_mac = format_mac(self._device_info.mac_address)
self._device_name = self._device_info.name
self._name = self._device_info.friendly_name or self._device_info.name
return None return None
async def fetch_device_info(self) -> str | None: async def fetch_device_info(self) -> str | None:

View File

@ -43,7 +43,7 @@
"data_description": { "data_description": {
"password": "Passwords are deprecated and will be removed in a future version. Please update your ESPHome device YAML configuration to use an encryption key instead." "password": "Passwords are deprecated and will be removed in a future version. Please update your ESPHome device YAML configuration to use an encryption key instead."
}, },
"description": "Please enter the password you set in your ESPHome device YAML configuration for {name}." "description": "Please enter the password you set in your ESPHome device YAML configuration for `{name}`."
}, },
"encryption_key": { "encryption_key": {
"data": { "data": {
@ -52,7 +52,7 @@
"data_description": { "data_description": {
"noise_psk": "The encryption key is used to encrypt the connection between Home Assistant and the ESPHome device. You can find this in the api: section of your ESPHome device YAML configuration." "noise_psk": "The encryption key is used to encrypt the connection between Home Assistant and the ESPHome device. You can find this in the api: section of your ESPHome device YAML configuration."
}, },
"description": "Please enter the encryption key for {name}. You can find it in the ESPHome Dashboard or in your ESPHome device YAML configuration." "description": "Please enter the encryption key for `{name}`. You can find it in the ESPHome Dashboard or in your ESPHome device YAML configuration."
}, },
"reauth_confirm": { "reauth_confirm": {
"data": { "data": {
@ -61,10 +61,10 @@
"data_description": { "data_description": {
"noise_psk": "[%key:component::esphome::config::step::encryption_key::data_description::noise_psk%]" "noise_psk": "[%key:component::esphome::config::step::encryption_key::data_description::noise_psk%]"
}, },
"description": "The ESPHome device {name} enabled transport encryption or changed the encryption key. Please enter the updated key. You can find it in the ESPHome Dashboard or in your ESPHome device YAML configuration." "description": "The ESPHome device `{name}` enabled transport encryption or changed the encryption key. Please enter the updated key. You can find it in the ESPHome Dashboard or in your ESPHome device YAML configuration."
}, },
"reauth_encryption_removed_confirm": { "reauth_encryption_removed_confirm": {
"description": "The ESPHome device {name} disabled transport encryption. Please confirm that you want to remove the encryption key and allow unencrypted connections." "description": "The ESPHome device `{name}` disabled transport encryption. Please confirm that you want to remove the encryption key and allow unencrypted connections."
}, },
"discovery_confirm": { "discovery_confirm": {
"description": "Do you want to add the device `{name}` to Home Assistant?", "description": "Do you want to add the device `{name}` to Home Assistant?",

View File

@ -27,6 +27,7 @@ from homeassistant.components.esphome.const import (
DEFAULT_NEW_CONFIG_ALLOW_ALLOW_SERVICE_CALLS, DEFAULT_NEW_CONFIG_ALLOW_ALLOW_SERVICE_CALLS,
DOMAIN, DOMAIN,
) )
from homeassistant.config_entries import ConfigFlowResult
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType from homeassistant.data_entry_flow import FlowResultType
@ -50,6 +51,17 @@ def mock_setup_entry():
yield yield
def get_flow_context(hass: HomeAssistant, result: ConfigFlowResult) -> dict[str, Any]:
"""Get the flow context from the result of async_init or async_configure."""
flow = next(
flow
for flow in hass.config_entries.flow.async_progress()
if flow["flow_id"] == result["flow_id"]
)
return flow["context"]
@pytest.mark.usefixtures("mock_zeroconf") @pytest.mark.usefixtures("mock_zeroconf")
async def test_user_connection_works( async def test_user_connection_works(
hass: HomeAssistant, mock_client, mock_setup_entry: None hass: HomeAssistant, mock_client, mock_setup_entry: None
@ -150,6 +162,9 @@ async def test_user_sets_unique_id(
assert discovery_result["type"] is FlowResultType.FORM assert discovery_result["type"] is FlowResultType.FORM
assert discovery_result["step_id"] == "discovery_confirm" assert discovery_result["step_id"] == "discovery_confirm"
assert discovery_result["description_placeholders"] == {
"name": "test8266",
}
discovery_result = await hass.config_entries.flow.async_configure( discovery_result = await hass.config_entries.flow.async_configure(
discovery_result["flow_id"], discovery_result["flow_id"],
@ -234,6 +249,9 @@ async def test_user_causes_zeroconf_to_abort(
assert discovery_result["type"] is FlowResultType.FORM assert discovery_result["type"] is FlowResultType.FORM
assert discovery_result["step_id"] == "discovery_confirm" assert discovery_result["step_id"] == "discovery_confirm"
assert discovery_result["description_placeholders"] == {
"name": "test8266",
}
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
"esphome", "esphome",
@ -297,6 +315,7 @@ async def test_user_with_password(
assert result["type"] is FlowResultType.FORM assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "authenticate" assert result["step_id"] == "authenticate"
assert result["description_placeholders"] == {"name": "test"}
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input={CONF_PASSWORD: "password1"} result["flow_id"], user_input={CONF_PASSWORD: "password1"}
@ -326,6 +345,7 @@ async def test_user_invalid_password(hass: HomeAssistant, mock_client) -> None:
assert result["type"] is FlowResultType.FORM assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "authenticate" assert result["step_id"] == "authenticate"
assert result["description_placeholders"] == {"name": "test"}
mock_client.connect.side_effect = InvalidAuthAPIError mock_client.connect.side_effect = InvalidAuthAPIError
@ -335,6 +355,7 @@ async def test_user_invalid_password(hass: HomeAssistant, mock_client) -> None:
assert result["type"] is FlowResultType.FORM assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "authenticate" assert result["step_id"] == "authenticate"
assert result["description_placeholders"] == {"name": "test"}
assert result["errors"] == {"base": "invalid_auth"} assert result["errors"] == {"base": "invalid_auth"}
@ -348,7 +369,7 @@ async def test_user_dashboard_has_wrong_key(
"""Test user step with key from dashboard that is incorrect.""" """Test user step with key from dashboard that is incorrect."""
mock_client.device_info.side_effect = [ mock_client.device_info.side_effect = [
RequiresEncryptionAPIError, RequiresEncryptionAPIError,
InvalidEncryptionKeyAPIError, InvalidEncryptionKeyAPIError("Wrong key", "test"),
DeviceInfo( DeviceInfo(
uses_password=False, uses_password=False,
name="test", name="test",
@ -369,6 +390,7 @@ async def test_user_dashboard_has_wrong_key(
assert result["type"] is FlowResultType.FORM assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "encryption_key" assert result["step_id"] == "encryption_key"
assert result["description_placeholders"] == {"name": "test"}
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input={CONF_NOISE_PSK: VALID_NOISE_PSK} result["flow_id"], user_input={CONF_NOISE_PSK: VALID_NOISE_PSK}
@ -477,6 +499,7 @@ async def test_user_discovers_name_and_gets_key_from_dashboard_fails(
assert result["type"] is FlowResultType.FORM assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "encryption_key" assert result["step_id"] == "encryption_key"
assert result["description_placeholders"] == {"name": "test"}
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input={CONF_NOISE_PSK: VALID_NOISE_PSK} result["flow_id"], user_input={CONF_NOISE_PSK: VALID_NOISE_PSK}
@ -532,6 +555,7 @@ async def test_user_discovers_name_and_dashboard_is_unavailable(
assert result["type"] is FlowResultType.FORM assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "encryption_key" assert result["step_id"] == "encryption_key"
assert result["description_placeholders"] == {"name": "test"}
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input={CONF_NOISE_PSK: VALID_NOISE_PSK} result["flow_id"], user_input={CONF_NOISE_PSK: VALID_NOISE_PSK}
@ -563,6 +587,7 @@ async def test_login_connection_error(
assert result["type"] is FlowResultType.FORM assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "authenticate" assert result["step_id"] == "authenticate"
assert result["description_placeholders"] == {"name": "test"}
mock_client.connect.side_effect = APIConnectionError mock_client.connect.side_effect = APIConnectionError
@ -572,6 +597,7 @@ async def test_login_connection_error(
assert result["type"] is FlowResultType.FORM assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "authenticate" assert result["step_id"] == "authenticate"
assert result["description_placeholders"] == {"name": "test"}
assert result["errors"] == {"base": "connection_error"} assert result["errors"] == {"base": "connection_error"}
@ -588,12 +614,18 @@ async def test_discovery_initiation(
port=6053, port=6053,
properties={ properties={
"mac": "1122334455aa", "mac": "1122334455aa",
"friendly_name": "The Test",
}, },
type="mock_type", type="mock_type",
) )
flow = await hass.config_entries.flow.async_init( flow = await hass.config_entries.flow.async_init(
"esphome", context={"source": config_entries.SOURCE_ZEROCONF}, data=service_info "esphome", context={"source": config_entries.SOURCE_ZEROCONF}, data=service_info
) )
assert get_flow_context(hass, flow) == {
"source": config_entries.SOURCE_ZEROCONF,
"title_placeholders": {"name": "The Test (test)"},
"unique_id": "11:22:33:44:55:aa",
}
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
flow["flow_id"], user_input={} flow["flow_id"], user_input={}
@ -682,6 +714,7 @@ async def test_discovery_duplicate_data(
) )
assert result["type"] is FlowResultType.FORM assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "discovery_confirm" assert result["step_id"] == "discovery_confirm"
assert result["description_placeholders"] == {"name": "test"}
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
"esphome", data=service_info, context={"source": config_entries.SOURCE_ZEROCONF} "esphome", data=service_info, context={"source": config_entries.SOURCE_ZEROCONF}
@ -742,6 +775,7 @@ async def test_user_requires_psk(
assert result["type"] is FlowResultType.FORM assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "encryption_key" assert result["step_id"] == "encryption_key"
assert result["errors"] == {} assert result["errors"] == {}
assert result["description_placeholders"] == {"name": "ESPHome"}
assert len(mock_client.connect.mock_calls) == 2 assert len(mock_client.connect.mock_calls) == 2
assert len(mock_client.device_info.mock_calls) == 2 assert len(mock_client.device_info.mock_calls) == 2
@ -764,6 +798,7 @@ async def test_encryption_key_valid_psk(
assert result["type"] is FlowResultType.FORM assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "encryption_key" assert result["step_id"] == "encryption_key"
assert result["description_placeholders"] == {"name": "ESPHome"}
mock_client.device_info = AsyncMock( mock_client.device_info = AsyncMock(
return_value=DeviceInfo(uses_password=False, name="test") return_value=DeviceInfo(uses_password=False, name="test")
@ -799,6 +834,7 @@ async def test_encryption_key_invalid_psk(
assert result["type"] is FlowResultType.FORM assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "encryption_key" assert result["step_id"] == "encryption_key"
assert result["description_placeholders"] == {"name": "ESPHome"}
mock_client.device_info.side_effect = InvalidEncryptionKeyAPIError mock_client.device_info.side_effect = InvalidEncryptionKeyAPIError
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
@ -808,6 +844,7 @@ async def test_encryption_key_invalid_psk(
assert result["type"] is FlowResultType.FORM assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "encryption_key" assert result["step_id"] == "encryption_key"
assert result["errors"] == {"base": "invalid_psk"} assert result["errors"] == {"base": "invalid_psk"}
assert result["description_placeholders"] == {"name": "ESPHome"}
assert mock_client.noise_psk == INVALID_NOISE_PSK assert mock_client.noise_psk == INVALID_NOISE_PSK
@ -823,6 +860,9 @@ async def test_reauth_initiation(hass: HomeAssistant, mock_client) -> None:
result = await entry.start_reauth_flow(hass) result = await entry.start_reauth_flow(hass)
assert result["type"] is FlowResultType.FORM assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "reauth_confirm" assert result["step_id"] == "reauth_confirm"
assert result["description_placeholders"] == {
"name": "Mock Title (test)",
}
@pytest.mark.usefixtures("mock_zeroconf") @pytest.mark.usefixtures("mock_zeroconf")
@ -1025,6 +1065,9 @@ async def test_reauth_fixed_via_dashboard_at_confirm(
assert result["type"] is FlowResultType.FORM, result assert result["type"] is FlowResultType.FORM, result
assert result["step_id"] == "reauth_confirm" assert result["step_id"] == "reauth_confirm"
assert result["description_placeholders"] == {
"name": "Mock Title (test)",
}
mock_dashboard["configured"].append( mock_dashboard["configured"].append(
{ {
@ -1070,6 +1113,9 @@ async def test_reauth_confirm_invalid(
assert result["type"] is FlowResultType.FORM assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "reauth_confirm" assert result["step_id"] == "reauth_confirm"
assert result["description_placeholders"] == {
"name": "Mock Title (test)",
}
assert result["errors"] assert result["errors"]
assert result["errors"]["base"] == "invalid_psk" assert result["errors"]["base"] == "invalid_psk"
@ -1108,6 +1154,9 @@ async def test_reauth_confirm_invalid_with_unique_id(
assert result["type"] is FlowResultType.FORM assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "reauth_confirm" assert result["step_id"] == "reauth_confirm"
assert result["description_placeholders"] == {
"name": "Mock Title (test)",
}
assert result["errors"] assert result["errors"]
assert result["errors"]["base"] == "invalid_psk" assert result["errors"]["base"] == "invalid_psk"
@ -1145,6 +1194,9 @@ async def test_reauth_encryption_key_removed(
result = await entry.start_reauth_flow(hass) result = await entry.start_reauth_flow(hass)
assert result["type"] is FlowResultType.FORM assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "reauth_encryption_removed_confirm" assert result["step_id"] == "reauth_encryption_removed_confirm"
assert result["description_placeholders"] == {
"name": "Mock Title (test)",
}
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input={} result["flow_id"], user_input={}
@ -1370,6 +1422,7 @@ async def test_zeroconf_encryption_key_via_dashboard(
assert flow["type"] is FlowResultType.FORM assert flow["type"] is FlowResultType.FORM
assert flow["step_id"] == "discovery_confirm" assert flow["step_id"] == "discovery_confirm"
assert flow["description_placeholders"] == {"name": "test8266"}
mock_dashboard["configured"].append( mock_dashboard["configured"].append(
{ {
@ -1437,6 +1490,7 @@ async def test_zeroconf_encryption_key_via_dashboard_with_api_encryption_prop(
assert flow["type"] is FlowResultType.FORM assert flow["type"] is FlowResultType.FORM
assert flow["step_id"] == "discovery_confirm" assert flow["step_id"] == "discovery_confirm"
assert flow["description_placeholders"] == {"name": "test8266"}
mock_dashboard["configured"].append( mock_dashboard["configured"].append(
{ {
@ -1502,6 +1556,7 @@ async def test_zeroconf_no_encryption_key_via_dashboard(
assert flow["type"] is FlowResultType.FORM assert flow["type"] is FlowResultType.FORM
assert flow["step_id"] == "discovery_confirm" assert flow["step_id"] == "discovery_confirm"
assert flow["description_placeholders"] == {"name": "test8266"}
await dashboard.async_get_dashboard(hass).async_refresh() await dashboard.async_get_dashboard(hass).async_refresh()
@ -1513,6 +1568,7 @@ async def test_zeroconf_no_encryption_key_via_dashboard(
assert result["type"] is FlowResultType.FORM assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "encryption_key" assert result["step_id"] == "encryption_key"
assert result["description_placeholders"] == {"name": "test8266"}
async def test_option_flow_allow_service_calls( async def test_option_flow_allow_service_calls(
@ -1625,6 +1681,7 @@ async def test_user_discovers_name_no_dashboard(
assert result["type"] is FlowResultType.FORM assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "encryption_key" assert result["step_id"] == "encryption_key"
assert result["description_placeholders"] == {"name": "test"}
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input={CONF_NOISE_PSK: VALID_NOISE_PSK} result["flow_id"], user_input={CONF_NOISE_PSK: VALID_NOISE_PSK}
@ -1917,6 +1974,54 @@ async def test_reconfig_success_with_new_ip_same_name(
assert entry.data[CONF_NOISE_PSK] == VALID_NOISE_PSK assert entry.data[CONF_NOISE_PSK] == VALID_NOISE_PSK
@pytest.mark.usefixtures("mock_zeroconf", "mock_setup_entry")
async def test_reconfig_success_noise_psk_changes(
hass: HomeAssistant, mock_client: APIClient
) -> None:
"""Test reconfig initiation with new ip and new noise psk."""
entry = MockConfigEntry(
domain=DOMAIN,
data={
CONF_HOST: "127.0.0.1",
CONF_PORT: 6053,
CONF_PASSWORD: "",
CONF_DEVICE_NAME: "test",
CONF_NOISE_PSK: VALID_NOISE_PSK,
},
unique_id="11:22:33:44:55:aa",
)
entry.add_to_hass(hass)
result = await entry.start_reconfigure_flow(hass)
mock_client.device_info.side_effect = [
RequiresEncryptionAPIError,
InvalidEncryptionKeyAPIError("Wrong key", "test"),
DeviceInfo(uses_password=False, name="test", mac_address="11:22:33:44:55:aa"),
]
result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input={CONF_HOST: "127.0.0.1", CONF_PORT: 6053}
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "encryption_key"
assert result["description_placeholders"] == {"name": "Mock Title (test)"}
result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input={CONF_NOISE_PSK: VALID_NOISE_PSK}
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "encryption_key"
assert result["description_placeholders"] == {"name": "Mock Title (test)"}
result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input={CONF_NOISE_PSK: VALID_NOISE_PSK}
)
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "reconfigure_successful"
assert entry.data[CONF_HOST] == "127.0.0.1"
assert entry.data[CONF_DEVICE_NAME] == "test"
assert entry.data[CONF_NOISE_PSK] == VALID_NOISE_PSK
@pytest.mark.usefixtures("mock_zeroconf", "mock_setup_entry") @pytest.mark.usefixtures("mock_zeroconf", "mock_setup_entry")
async def test_reconfig_name_conflict_with_existing_entry( async def test_reconfig_name_conflict_with_existing_entry(
hass: HomeAssistant, mock_client: APIClient hass: HomeAssistant, mock_client: APIClient