Fix display issues with ESPHome encryption key steps (#143483)

This commit is contained in:
J. Nick Koston 2025-04-23 06:39:32 -10:00 committed by GitHub
parent 11f02e48d7
commit 65db3c1164
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 239 additions and 152 deletions

View File

@ -662,10 +662,12 @@ class EsphomeFlowHandler(ConfigFlow, domain=DOMAIN):
return ERROR_REQUIRES_ENCRYPTION_KEY
except InvalidEncryptionKeyAPIError as ex:
if ex.received_name:
device_name_changed = self._device_name != ex.received_name
self._device_name = ex.received_name
if ex.received_mac:
self._device_mac = format_mac(ex.received_mac)
self._name = ex.received_name
if not self._name or device_name_changed:
self._name = ex.received_name
return ERROR_INVALID_ENCRYPTION_KEY
except ResolveAPIError:
return "resolve_error"

View File

@ -19,10 +19,11 @@
"reconfigure_unique_id_changed": "**Reconfiguration of `{name}` was aborted** because the address `{host}` points to a different device: `{unexpected_device_name}` (MAC: `{unexpected_mac}`) instead of the expected one (MAC: `{expected_mac}`)."
},
"error": {
"resolve_error": "Can't resolve address of the ESP. If this error persists, please set a static IP address",
"connection_error": "Can't connect to ESP. Please make sure your YAML file contains an 'api:' line.",
"resolve_error": "Unable to resolve the address of the ESPHome device. If this issue continues, consider setting a static IP address.",
"connection_error": "Unable to connect to the ESPHome device. Make sure the devices YAML configuration includes an `api` section.",
"requires_encryption_key": "The ESPHome device requires an encryption key. Enter the key defined in the devices YAML configuration under `api -> encryption -> key`.",
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
"invalid_psk": "The transport encryption key is invalid. Please ensure it matches what you have in your configuration"
"invalid_psk": "The encryption key is invalid. Make sure it matches the value in the devices YAML configuration under `api -> encryption -> key`."
},
"step": {
"user": {

View File

@ -1,5 +1,6 @@
"""Test config flow."""
from collections.abc import Awaitable, Callable
from ipaddress import ip_address
import json
from typing import Any
@ -9,10 +10,13 @@ from aioesphomeapi import (
APIClient,
APIConnectionError,
DeviceInfo,
EntityInfo,
EntityState,
InvalidAuthAPIError,
InvalidEncryptionKeyAPIError,
RequiresEncryptionAPIError,
ResolveAPIError,
UserService,
)
import aiohttp
import pytest
@ -62,9 +66,9 @@ def get_flow_context(hass: HomeAssistant, result: ConfigFlowResult) -> dict[str,
return flow["context"]
@pytest.mark.usefixtures("mock_zeroconf")
@pytest.mark.usefixtures("mock_setup_entry", "mock_zeroconf")
async def test_user_connection_works(
hass: HomeAssistant, mock_client, mock_setup_entry: None
hass: HomeAssistant, mock_client: APIClient
) -> None:
"""Test we can finish a config flow."""
result = await hass.config_entries.flow.async_init(
@ -105,10 +109,8 @@ async def test_user_connection_works(
assert mock_client.noise_psk is None
@pytest.mark.usefixtures("mock_zeroconf")
async def test_user_connection_updates_host(
hass: HomeAssistant, mock_client, mock_setup_entry: None
) -> None:
@pytest.mark.usefixtures("mock_client", "mock_setup_entry", "mock_zeroconf")
async def test_user_connection_updates_host(hass: HomeAssistant) -> None:
"""Test setup up the same name updates the host."""
entry = MockConfigEntry(
domain=DOMAIN,
@ -140,10 +142,8 @@ async def test_user_connection_updates_host(
assert entry.data[CONF_HOST] == "127.0.0.1"
@pytest.mark.usefixtures("mock_zeroconf")
async def test_user_sets_unique_id(
hass: HomeAssistant, mock_client, mock_setup_entry: None
) -> None:
@pytest.mark.usefixtures("mock_client", "mock_setup_entry", "mock_zeroconf")
async def test_user_sets_unique_id(hass: HomeAssistant) -> None:
"""Test that the user flow sets the unique id."""
service_info = ZeroconfServiceInfo(
ip_address=ip_address("192.168.43.183"),
@ -201,10 +201,8 @@ async def test_user_sets_unique_id(
}
@pytest.mark.usefixtures("mock_zeroconf")
async def test_user_resolve_error(
hass: HomeAssistant, mock_client, mock_setup_entry: None
) -> None:
@pytest.mark.usefixtures("mock_setup_entry", "mock_zeroconf")
async def test_user_resolve_error(hass: HomeAssistant, mock_client: APIClient) -> None:
"""Test user step with IP resolve error."""
with patch(
@ -226,11 +224,27 @@ async def test_user_resolve_error(
assert len(mock_client.device_info.mock_calls) == 1
assert len(mock_client.disconnect.mock_calls) == 1
# Now simulate the user retrying with the same host and a successful connection
mock_client.device_info.side_effect = None
@pytest.mark.usefixtures("mock_zeroconf")
async def test_user_causes_zeroconf_to_abort(
hass: HomeAssistant, mock_client, mock_setup_entry: None
) -> None:
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={CONF_HOST: "127.0.0.1", CONF_PORT: 6053},
)
assert result2["type"] == FlowResultType.CREATE_ENTRY
assert result2["title"] == "test"
assert result2["data"] == {
CONF_HOST: "127.0.0.1",
CONF_PORT: 6053,
CONF_DEVICE_NAME: "test",
CONF_PASSWORD: "",
CONF_NOISE_PSK: "",
}
@pytest.mark.usefixtures("mock_client", "mock_setup_entry", "mock_zeroconf")
async def test_user_causes_zeroconf_to_abort(hass: HomeAssistant) -> None:
"""Test that the user flow sets the unique id and aborts the zeroconf flow."""
service_info = ZeroconfServiceInfo(
ip_address=ip_address("192.168.43.183"),
@ -278,9 +292,10 @@ async def test_user_causes_zeroconf_to_abort(
assert not hass.config_entries.flow.async_progress_by_handler(DOMAIN)
@pytest.mark.usefixtures("mock_zeroconf")
@pytest.mark.usefixtures("mock_setup_entry", "mock_zeroconf")
async def test_user_connection_error(
hass: HomeAssistant, mock_client, mock_setup_entry: None
hass: HomeAssistant,
mock_client: APIClient,
) -> None:
"""Test user step with connection error."""
mock_client.device_info.side_effect = APIConnectionError
@ -299,10 +314,29 @@ async def test_user_connection_error(
assert len(mock_client.device_info.mock_calls) == 1
assert len(mock_client.disconnect.mock_calls) == 1
# Now simulate the user retrying with the same host and a successful connection
mock_client.device_info.side_effect = None
@pytest.mark.usefixtures("mock_zeroconf")
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={CONF_HOST: "127.0.0.1", CONF_PORT: 6053},
)
assert result2["type"] == FlowResultType.CREATE_ENTRY
assert result2["title"] == "test"
assert result2["data"] == {
CONF_HOST: "127.0.0.1",
CONF_PORT: 6053,
CONF_DEVICE_NAME: "test",
CONF_PASSWORD: "",
CONF_NOISE_PSK: "",
}
@pytest.mark.usefixtures("mock_setup_entry", "mock_zeroconf")
async def test_user_with_password(
hass: HomeAssistant, mock_client, mock_setup_entry: None
hass: HomeAssistant,
mock_client: APIClient,
) -> None:
"""Test user step with password."""
mock_client.device_info.return_value = DeviceInfo(uses_password=True, name="test")
@ -333,7 +367,9 @@ async def test_user_with_password(
@pytest.mark.usefixtures("mock_zeroconf")
async def test_user_invalid_password(hass: HomeAssistant, mock_client) -> None:
async def test_user_invalid_password(
hass: HomeAssistant, mock_client: APIClient
) -> None:
"""Test user step with invalid password."""
mock_client.device_info.return_value = DeviceInfo(uses_password=True, name="test")
@ -358,13 +394,27 @@ async def test_user_invalid_password(hass: HomeAssistant, mock_client) -> None:
assert result["description_placeholders"] == {"name": "test"}
assert result["errors"] == {"base": "invalid_auth"}
mock_client.connect.side_effect = None
@pytest.mark.usefixtures("mock_zeroconf")
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input={CONF_PASSWORD: "good"}
)
assert result2["type"] == FlowResultType.CREATE_ENTRY
assert result2["title"] == "test"
assert result2["data"] == {
CONF_HOST: "127.0.0.1",
CONF_PORT: 6053,
CONF_DEVICE_NAME: "test",
CONF_PASSWORD: "good",
CONF_NOISE_PSK: "",
}
@pytest.mark.usefixtures("mock_dashboard", "mock_setup_entry", "mock_zeroconf")
async def test_user_dashboard_has_wrong_key(
hass: HomeAssistant,
mock_client,
mock_dashboard: dict[str, Any],
mock_setup_entry: None,
mock_client: APIClient,
) -> None:
"""Test user step with key from dashboard that is incorrect."""
mock_client.device_info.side_effect = [
@ -407,12 +457,11 @@ async def test_user_dashboard_has_wrong_key(
assert mock_client.noise_psk == VALID_NOISE_PSK
@pytest.mark.usefixtures("mock_zeroconf")
@pytest.mark.usefixtures("mock_setup_entry", "mock_zeroconf")
async def test_user_discovers_name_and_gets_key_from_dashboard(
hass: HomeAssistant,
mock_client,
mock_client: APIClient,
mock_dashboard: dict[str, Any],
mock_setup_entry: None,
) -> None:
"""Test user step can discover the name and get the key from the dashboard."""
mock_client.device_info.side_effect = [
@ -459,13 +508,12 @@ async def test_user_discovers_name_and_gets_key_from_dashboard(
"dashboard_exception",
[aiohttp.ClientError(), json.JSONDecodeError("test", "test", 0)],
)
@pytest.mark.usefixtures("mock_zeroconf")
@pytest.mark.usefixtures("mock_setup_entry", "mock_zeroconf")
async def test_user_discovers_name_and_gets_key_from_dashboard_fails(
hass: HomeAssistant,
dashboard_exception: Exception,
mock_client,
mock_client: APIClient,
mock_dashboard: dict[str, Any],
mock_setup_entry: None,
) -> None:
"""Test user step can discover the name and get the key from the dashboard."""
mock_client.device_info.side_effect = [
@ -516,12 +564,11 @@ async def test_user_discovers_name_and_gets_key_from_dashboard_fails(
assert mock_client.noise_psk == VALID_NOISE_PSK
@pytest.mark.usefixtures("mock_zeroconf")
@pytest.mark.usefixtures("mock_setup_entry", "mock_zeroconf")
async def test_user_discovers_name_and_dashboard_is_unavailable(
hass: HomeAssistant,
mock_client,
mock_client: APIClient,
mock_dashboard: dict[str, Any],
mock_setup_entry: None,
) -> None:
"""Test user step can discover the name but the dashboard is unavailable."""
mock_client.device_info.side_effect = [
@ -572,9 +619,9 @@ async def test_user_discovers_name_and_dashboard_is_unavailable(
assert mock_client.noise_psk == VALID_NOISE_PSK
@pytest.mark.usefixtures("mock_zeroconf")
@pytest.mark.usefixtures("mock_setup_entry", "mock_zeroconf")
async def test_login_connection_error(
hass: HomeAssistant, mock_client, mock_setup_entry: None
hass: HomeAssistant, mock_client: APIClient
) -> None:
"""Test user step with connection error on login attempt."""
mock_client.device_info.return_value = DeviceInfo(uses_password=True, name="test")
@ -600,11 +647,25 @@ async def test_login_connection_error(
assert result["description_placeholders"] == {"name": "test"}
assert result["errors"] == {"base": "connection_error"}
mock_client.connect.side_effect = None
@pytest.mark.usefixtures("mock_zeroconf")
async def test_discovery_initiation(
hass: HomeAssistant, mock_client, mock_setup_entry: None
) -> None:
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input={CONF_PASSWORD: "good"}
)
assert result2["type"] == FlowResultType.CREATE_ENTRY
assert result2["title"] == "test"
assert result2["data"] == {
CONF_HOST: "127.0.0.1",
CONF_PORT: 6053,
CONF_DEVICE_NAME: "test",
CONF_PASSWORD: "good",
CONF_NOISE_PSK: "",
}
@pytest.mark.usefixtures("mock_client", "mock_setup_entry", "mock_zeroconf")
async def test_discovery_initiation(hass: HomeAssistant) -> None:
"""Test discovery importing works."""
service_info = ZeroconfServiceInfo(
ip_address=ip_address("192.168.43.183"),
@ -640,10 +701,8 @@ async def test_discovery_initiation(
assert result["result"].unique_id == "11:22:33:44:55:aa"
@pytest.mark.usefixtures("mock_zeroconf")
async def test_discovery_no_mac(
hass: HomeAssistant, mock_client, mock_setup_entry: None
) -> None:
@pytest.mark.usefixtures("mock_client", "mock_setup_entry", "mock_zeroconf")
async def test_discovery_no_mac(hass: HomeAssistant) -> None:
"""Test discovery aborted if old ESPHome without mac in zeroconf."""
service_info = ZeroconfServiceInfo(
ip_address=ip_address("192.168.43.183"),
@ -661,9 +720,8 @@ async def test_discovery_no_mac(
assert flow["reason"] == "mdns_missing_mac"
async def test_discovery_already_configured(
hass: HomeAssistant, mock_client: APIClient, mock_setup_entry: None
) -> None:
@pytest.mark.usefixtures("mock_client", "mock_setup_entry", "mock_zeroconf")
async def test_discovery_already_configured(hass: HomeAssistant) -> None:
"""Test discovery aborts if already configured via hostname."""
entry = MockConfigEntry(
domain=DOMAIN,
@ -695,9 +753,8 @@ async def test_discovery_already_configured(
}
async def test_discovery_duplicate_data(
hass: HomeAssistant, mock_client: APIClient, mock_setup_entry: None
) -> None:
@pytest.mark.usefixtures("mock_client", "mock_setup_entry", "mock_zeroconf")
async def test_discovery_duplicate_data(hass: HomeAssistant) -> None:
"""Test discovery aborts if same mDNS packet arrives."""
service_info = ZeroconfServiceInfo(
ip_address=ip_address("192.168.43.183"),
@ -723,9 +780,8 @@ async def test_discovery_duplicate_data(
assert result["reason"] == "already_in_progress"
async def test_discovery_updates_unique_id(
hass: HomeAssistant, mock_client: APIClient, mock_setup_entry: None
) -> None:
@pytest.mark.usefixtures("mock_client", "mock_setup_entry", "mock_zeroconf")
async def test_discovery_updates_unique_id(hass: HomeAssistant) -> None:
"""Test a duplicate discovery host aborts and updates existing entry."""
entry = MockConfigEntry(
domain=DOMAIN,
@ -759,10 +815,8 @@ async def test_discovery_updates_unique_id(
assert entry.unique_id == "11:22:33:44:55:aa"
@pytest.mark.usefixtures("mock_zeroconf")
async def test_user_requires_psk(
hass: HomeAssistant, mock_client, mock_setup_entry: None
) -> None:
@pytest.mark.usefixtures("mock_setup_entry", "mock_zeroconf")
async def test_user_requires_psk(hass: HomeAssistant, mock_client: APIClient) -> None:
"""Test user step with requiring encryption key."""
mock_client.device_info.side_effect = RequiresEncryptionAPIError
@ -781,10 +835,32 @@ async def test_user_requires_psk(
assert len(mock_client.device_info.mock_calls) == 2
assert len(mock_client.disconnect.mock_calls) == 2
result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input={CONF_NOISE_PSK: INVALID_NOISE_PSK}
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "encryption_key"
assert result["errors"] == {"base": "requires_encryption_key"}
assert result["description_placeholders"] == {"name": "ESPHome"}
@pytest.mark.usefixtures("mock_zeroconf")
mock_client.device_info.side_effect = None
result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input={CONF_NOISE_PSK: VALID_NOISE_PSK}
)
assert result["type"] is FlowResultType.CREATE_ENTRY
assert result["data"] == {
CONF_HOST: "127.0.0.1",
CONF_PORT: 6053,
CONF_PASSWORD: "",
CONF_NOISE_PSK: VALID_NOISE_PSK,
CONF_DEVICE_NAME: "test",
}
assert mock_client.noise_psk == VALID_NOISE_PSK
@pytest.mark.usefixtures("mock_setup_entry", "mock_zeroconf")
async def test_encryption_key_valid_psk(
hass: HomeAssistant, mock_client, mock_setup_entry: None
hass: HomeAssistant, mock_client: APIClient
) -> None:
"""Test encryption key step with valid key."""
@ -818,9 +894,9 @@ async def test_encryption_key_valid_psk(
assert mock_client.noise_psk == VALID_NOISE_PSK
@pytest.mark.usefixtures("mock_zeroconf")
@pytest.mark.usefixtures("mock_setup_entry", "mock_zeroconf")
async def test_encryption_key_invalid_psk(
hass: HomeAssistant, mock_client, mock_setup_entry: None
hass: HomeAssistant, mock_client: APIClient
) -> None:
"""Test encryption key step with invalid key."""
@ -847,27 +923,25 @@ async def test_encryption_key_invalid_psk(
assert result["description_placeholders"] == {"name": "ESPHome"}
assert mock_client.noise_psk == INVALID_NOISE_PSK
@pytest.mark.usefixtures("mock_zeroconf")
async def test_reauth_initiation(hass: HomeAssistant, mock_client) -> None:
"""Test reauth initiation shows form."""
entry = MockConfigEntry(
domain=DOMAIN,
data={CONF_HOST: "127.0.0.1", CONF_PORT: 6053, CONF_PASSWORD: ""},
mock_client.device_info.side_effect = None
result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input={CONF_NOISE_PSK: VALID_NOISE_PSK}
)
entry.add_to_hass(hass)
result = await entry.start_reauth_flow(hass)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "reauth_confirm"
assert result["description_placeholders"] == {
"name": "Mock Title (test)",
assert result["type"] is FlowResultType.CREATE_ENTRY
assert result["data"] == {
CONF_HOST: "127.0.0.1",
CONF_PORT: 6053,
CONF_PASSWORD: "",
CONF_NOISE_PSK: VALID_NOISE_PSK,
CONF_DEVICE_NAME: "test",
}
assert mock_client.noise_psk == VALID_NOISE_PSK
@pytest.mark.usefixtures("mock_zeroconf")
@pytest.mark.usefixtures("mock_setup_entry", "mock_zeroconf")
async def test_reauth_confirm_valid(
hass: HomeAssistant, mock_client, mock_setup_entry: None
hass: HomeAssistant, mock_client: APIClient
) -> None:
"""Test reauth initiation with valid PSK."""
entry = MockConfigEntry(
@ -878,6 +952,11 @@ async def test_reauth_confirm_valid(
entry.add_to_hass(hass)
result = await entry.start_reauth_flow(hass)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "reauth_confirm"
assert result["description_placeholders"] == {
"name": "Mock Title (test)",
}
mock_client.device_info.return_value = DeviceInfo(
uses_password=False, name="test", mac_address="11:22:33:44:55:aa"
@ -933,12 +1012,11 @@ async def test_reauth_attempt_to_change_mac_aborts(
}
@pytest.mark.usefixtures("mock_zeroconf")
@pytest.mark.usefixtures("mock_setup_entry", "mock_zeroconf")
async def test_reauth_fixed_via_dashboard(
hass: HomeAssistant,
mock_client,
mock_client: APIClient,
mock_dashboard: dict[str, Any],
mock_setup_entry: None,
) -> None:
"""Test reauth fixed automatically via dashboard."""
@ -980,13 +1058,12 @@ async def test_reauth_fixed_via_dashboard(
assert len(mock_get_encryption_key.mock_calls) == 1
@pytest.mark.usefixtures("mock_zeroconf")
@pytest.mark.usefixtures("mock_setup_entry", "mock_zeroconf")
async def test_reauth_fixed_via_dashboard_add_encryption_remove_password(
hass: HomeAssistant,
mock_client,
mock_client: APIClient,
mock_dashboard: dict[str, Any],
mock_config_entry: MockConfigEntry,
mock_setup_entry: None,
) -> None:
"""Test reauth fixed automatically via dashboard with password removed."""
mock_client.device_info.side_effect = (
@ -1017,12 +1094,11 @@ async def test_reauth_fixed_via_dashboard_add_encryption_remove_password(
assert len(mock_get_encryption_key.mock_calls) == 1
@pytest.mark.usefixtures("mock_dashboard", "mock_setup_entry", "mock_zeroconf")
async def test_reauth_fixed_via_remove_password(
hass: HomeAssistant,
mock_client,
mock_client: APIClient,
mock_config_entry: MockConfigEntry,
mock_dashboard: dict[str, Any],
mock_setup_entry: None,
) -> None:
"""Test reauth fixed automatically by seeing password removed."""
mock_client.device_info.return_value = DeviceInfo(
@ -1036,12 +1112,11 @@ async def test_reauth_fixed_via_remove_password(
assert mock_config_entry.data[CONF_PASSWORD] == ""
@pytest.mark.usefixtures("mock_zeroconf")
@pytest.mark.usefixtures("mock_setup_entry", "mock_zeroconf")
async def test_reauth_fixed_via_dashboard_at_confirm(
hass: HomeAssistant,
mock_client,
mock_client: APIClient,
mock_dashboard: dict[str, Any],
mock_setup_entry: None,
) -> None:
"""Test reauth fixed automatically via dashboard at confirm step."""
@ -1092,9 +1167,9 @@ async def test_reauth_fixed_via_dashboard_at_confirm(
assert len(mock_get_encryption_key.mock_calls) == 1
@pytest.mark.usefixtures("mock_zeroconf")
@pytest.mark.usefixtures("mock_setup_entry", "mock_zeroconf")
async def test_reauth_confirm_invalid(
hass: HomeAssistant, mock_client, mock_setup_entry: None
hass: HomeAssistant, mock_client: APIClient
) -> None:
"""Test reauth initiation with invalid PSK."""
entry = MockConfigEntry(
@ -1133,9 +1208,9 @@ async def test_reauth_confirm_invalid(
assert entry.data[CONF_NOISE_PSK] == VALID_NOISE_PSK
@pytest.mark.usefixtures("mock_zeroconf")
@pytest.mark.usefixtures("mock_setup_entry", "mock_zeroconf")
async def test_reauth_confirm_invalid_with_unique_id(
hass: HomeAssistant, mock_client, mock_setup_entry: None
hass: HomeAssistant, mock_client: APIClient
) -> None:
"""Test reauth initiation with invalid PSK."""
entry = MockConfigEntry(
@ -1174,10 +1249,8 @@ async def test_reauth_confirm_invalid_with_unique_id(
assert entry.data[CONF_NOISE_PSK] == VALID_NOISE_PSK
@pytest.mark.usefixtures("mock_zeroconf")
async def test_reauth_encryption_key_removed(
hass: HomeAssistant, mock_client: APIClient, mock_setup_entry: None
) -> None:
@pytest.mark.usefixtures("mock_client", "mock_setup_entry", "mock_zeroconf")
async def test_reauth_encryption_key_removed(hass: HomeAssistant) -> None:
"""Test reauth when the encryption key was removed."""
entry = MockConfigEntry(
domain=DOMAIN,
@ -1207,8 +1280,9 @@ async def test_reauth_encryption_key_removed(
assert entry.data[CONF_NOISE_PSK] == ""
@pytest.mark.usefixtures("mock_setup_entry", "mock_zeroconf")
async def test_discovery_dhcp_updates_host(
hass: HomeAssistant, mock_client: APIClient, mock_setup_entry: None
hass: HomeAssistant, mock_client: APIClient
) -> None:
"""Test dhcp discovery updates host and aborts."""
entry = MockConfigEntry(
@ -1241,8 +1315,10 @@ async def test_discovery_dhcp_updates_host(
assert entry.data[CONF_HOST] == "192.168.43.184"
@pytest.mark.usefixtures("mock_setup_entry")
async def test_discovery_dhcp_does_not_update_host_wrong_mac(
hass: HomeAssistant, mock_client: APIClient, mock_setup_entry: None
hass: HomeAssistant,
mock_client: APIClient,
) -> None:
"""Test dhcp discovery does not update the host if the mac is wrong."""
entry = MockConfigEntry(
@ -1276,8 +1352,9 @@ async def test_discovery_dhcp_does_not_update_host_wrong_mac(
assert entry.data[CONF_HOST] == "192.168.43.183"
@pytest.mark.usefixtures("mock_setup_entry")
async def test_discovery_dhcp_does_not_update_host_wrong_mac_bad_key(
hass: HomeAssistant, mock_client: APIClient, mock_setup_entry: None
hass: HomeAssistant, mock_client: APIClient
) -> None:
"""Test dhcp discovery does not update the host if the mac is wrong."""
entry = MockConfigEntry(
@ -1310,8 +1387,9 @@ async def test_discovery_dhcp_does_not_update_host_wrong_mac_bad_key(
assert entry.data[CONF_HOST] == "192.168.43.183"
@pytest.mark.usefixtures("mock_setup_entry")
async def test_discovery_dhcp_does_not_update_host_missing_mac_bad_key(
hass: HomeAssistant, mock_client: APIClient, mock_setup_entry: None
hass: HomeAssistant, mock_client: APIClient
) -> None:
"""Test dhcp discovery does not update the host if the mac is missing."""
entry = MockConfigEntry(
@ -1344,8 +1422,9 @@ async def test_discovery_dhcp_does_not_update_host_missing_mac_bad_key(
assert entry.data[CONF_HOST] == "192.168.43.183"
@pytest.mark.usefixtures("mock_setup_entry")
async def test_discovery_dhcp_no_changes(
hass: HomeAssistant, mock_client: APIClient, mock_setup_entry: None
hass: HomeAssistant, mock_client: APIClient
) -> None:
"""Test dhcp discovery updates host and aborts."""
entry = MockConfigEntry(
@ -1371,9 +1450,8 @@ async def test_discovery_dhcp_no_changes(
assert entry.data[CONF_HOST] == "192.168.43.183"
async def test_discovery_hassio(
hass: HomeAssistant, mock_dashboard: dict[str, Any]
) -> None:
@pytest.mark.usefixtures("mock_dashboard")
async def test_discovery_hassio(hass: HomeAssistant) -> None:
"""Test dashboard discovery."""
result = await hass.config_entries.flow.async_init(
"esphome",
@ -1397,12 +1475,11 @@ async def test_discovery_hassio(
assert dash.addon_slug == "mock-slug"
@pytest.mark.usefixtures("mock_zeroconf")
@pytest.mark.usefixtures("mock_setup_entry", "mock_zeroconf")
async def test_zeroconf_encryption_key_via_dashboard(
hass: HomeAssistant,
mock_client,
mock_client: APIClient,
mock_dashboard: dict[str, Any],
mock_setup_entry: None,
) -> None:
"""Test encryption key retrieved from dashboard."""
service_info = ZeroconfServiceInfo(
@ -1464,12 +1541,11 @@ async def test_zeroconf_encryption_key_via_dashboard(
assert mock_client.noise_psk == VALID_NOISE_PSK
@pytest.mark.usefixtures("mock_zeroconf")
@pytest.mark.usefixtures("mock_setup_entry", "mock_zeroconf")
async def test_zeroconf_encryption_key_via_dashboard_with_api_encryption_prop(
hass: HomeAssistant,
mock_client,
mock_client: APIClient,
mock_dashboard: dict[str, Any],
mock_setup_entry: None,
) -> None:
"""Test encryption key retrieved from dashboard with api_encryption property set."""
service_info = ZeroconfServiceInfo(
@ -1531,12 +1607,10 @@ async def test_zeroconf_encryption_key_via_dashboard_with_api_encryption_prop(
assert mock_client.noise_psk == VALID_NOISE_PSK
@pytest.mark.usefixtures("mock_zeroconf")
@pytest.mark.usefixtures("mock_dashboard", "mock_setup_entry", "mock_zeroconf")
async def test_zeroconf_no_encryption_key_via_dashboard(
hass: HomeAssistant,
mock_client,
mock_dashboard: dict[str, Any],
mock_setup_entry: None,
mock_client: APIClient,
) -> None:
"""Test encryption key not retrieved from dashboard."""
service_info = ZeroconfServiceInfo(
@ -1570,11 +1644,29 @@ async def test_zeroconf_no_encryption_key_via_dashboard(
assert result["step_id"] == "encryption_key"
assert result["description_placeholders"] == {"name": "test8266"}
mock_client.device_info.side_effect = None
result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input={CONF_NOISE_PSK: VALID_NOISE_PSK}
)
assert result["type"] is FlowResultType.CREATE_ENTRY
assert result["data"] == {
CONF_HOST: "192.168.43.183",
CONF_PORT: 6053,
CONF_PASSWORD: "",
CONF_NOISE_PSK: VALID_NOISE_PSK,
CONF_DEVICE_NAME: "test",
}
assert mock_client.noise_psk == VALID_NOISE_PSK
async def test_option_flow_allow_service_calls(
hass: HomeAssistant,
mock_client: APIClient,
mock_generic_device_entry,
mock_generic_device_entry: Callable[
[APIClient, list[EntityInfo], list[UserService], list[EntityState]],
Awaitable[MockConfigEntry],
],
) -> None:
"""Test config flow options for allow service calls."""
entry = await mock_generic_device_entry(
@ -1619,7 +1711,10 @@ async def test_option_flow_allow_service_calls(
async def test_option_flow_subscribe_logs(
hass: HomeAssistant,
mock_client: APIClient,
mock_generic_device_entry,
mock_generic_device_entry: Callable[
[APIClient, list[EntityInfo], list[UserService], list[EntityState]],
Awaitable[MockConfigEntry],
],
) -> None:
"""Test config flow options with subscribe logs."""
entry = await mock_generic_device_entry(
@ -1655,11 +1750,10 @@ async def test_option_flow_subscribe_logs(
assert len(mock_reload.mock_calls) == 1
@pytest.mark.usefixtures("mock_zeroconf")
@pytest.mark.usefixtures("mock_setup_entry", "mock_zeroconf")
async def test_user_discovers_name_no_dashboard(
hass: HomeAssistant,
mock_client,
mock_setup_entry: None,
mock_client: APIClient,
) -> None:
"""Test user step can discover the name and the there is not dashboard."""
mock_client.device_info.side_effect = [
@ -1698,7 +1792,9 @@ async def test_user_discovers_name_no_dashboard(
assert mock_client.noise_psk == VALID_NOISE_PSK
async def mqtt_discovery_test_abort(hass: HomeAssistant, payload: str, reason: str):
async def mqtt_discovery_test_abort(
hass: HomeAssistant, payload: str, reason: str
) -> None:
"""Test discovery aborted."""
service_info = MqttServiceInfo(
topic="esphome/discover/test",
@ -1715,44 +1811,34 @@ async def mqtt_discovery_test_abort(hass: HomeAssistant, payload: str, reason: s
assert flow["reason"] == reason
@pytest.mark.usefixtures("mock_zeroconf")
async def test_discovery_mqtt_no_mac(
hass: HomeAssistant, mock_client, mock_setup_entry: None
) -> None:
@pytest.mark.usefixtures("mock_client", "mock_setup_entry", "mock_zeroconf")
async def test_discovery_mqtt_no_mac(hass: HomeAssistant) -> None:
"""Test discovery aborted if mac is missing in MQTT payload."""
await mqtt_discovery_test_abort(hass, "{}", "mqtt_missing_mac")
@pytest.mark.usefixtures("mock_zeroconf")
async def test_discovery_mqtt_empty_payload(
hass: HomeAssistant, mock_client, mock_setup_entry: None
) -> None:
@pytest.mark.usefixtures("mock_client", "mock_setup_entry", "mock_zeroconf")
async def test_discovery_mqtt_empty_payload(hass: HomeAssistant) -> None:
"""Test discovery aborted if MQTT payload is empty."""
await mqtt_discovery_test_abort(hass, "", "mqtt_missing_payload")
@pytest.mark.usefixtures("mock_zeroconf")
async def test_discovery_mqtt_no_api(
hass: HomeAssistant, mock_client, mock_setup_entry: None
) -> None:
@pytest.mark.usefixtures("mock_client", "mock_setup_entry", "mock_zeroconf")
async def test_discovery_mqtt_no_api(hass: HomeAssistant) -> None:
"""Test discovery aborted if api/port is missing in MQTT payload."""
await mqtt_discovery_test_abort(hass, '{"mac":"abcdef123456"}', "mqtt_missing_api")
@pytest.mark.usefixtures("mock_zeroconf")
async def test_discovery_mqtt_no_ip(
hass: HomeAssistant, mock_client, mock_setup_entry: None
) -> None:
@pytest.mark.usefixtures("mock_client", "mock_setup_entry", "mock_zeroconf")
async def test_discovery_mqtt_no_ip(hass: HomeAssistant) -> None:
"""Test discovery aborted if ip is missing in MQTT payload."""
await mqtt_discovery_test_abort(
hass, '{"mac":"abcdef123456","port":6053}', "mqtt_missing_ip"
)
@pytest.mark.usefixtures("mock_zeroconf")
async def test_discovery_mqtt_initiation(
hass: HomeAssistant, mock_client, mock_setup_entry: None
) -> None:
@pytest.mark.usefixtures("mock_client", "mock_setup_entry", "mock_zeroconf")
async def test_discovery_mqtt_initiation(hass: HomeAssistant) -> None:
"""Test discovery importing works."""
service_info = MqttServiceInfo(
topic="esphome/discover/test",
@ -1779,11 +1865,10 @@ async def test_discovery_mqtt_initiation(
assert result["result"].unique_id == "11:22:33:44:55:aa"
@pytest.mark.usefixtures("mock_zeroconf")
@pytest.mark.usefixtures("mock_setup_entry", "mock_zeroconf")
async def test_user_flow_name_conflict_migrate(
hass: HomeAssistant,
mock_client,
mock_setup_entry: None,
mock_client: APIClient,
) -> None:
"""Test handle migration on name conflict."""
existing_entry = MockConfigEntry(
@ -1830,11 +1915,10 @@ async def test_user_flow_name_conflict_migrate(
assert existing_entry.unique_id == "11:22:33:44:55:aa"
@pytest.mark.usefixtures("mock_zeroconf")
@pytest.mark.usefixtures("mock_setup_entry", "mock_zeroconf")
async def test_user_flow_name_conflict_overwrite(
hass: HomeAssistant,
mock_client,
mock_setup_entry: None,
mock_client: APIClient,
) -> None:
"""Test handle overwrite on name conflict."""
existing_entry = MockConfigEntry(