Unifi access protect api key hint (#167404)

Co-authored-by: RaHehl <rahehl@users.noreply.github.com>
This commit is contained in:
Raphael Hehl
2026-04-08 13:21:54 +02:00
committed by GitHub
co-authored by RaHehl
parent b98aa0ad91
commit 726edf3a3b
4 changed files with 181 additions and 1 deletions
@@ -44,7 +44,11 @@ class UnifiAccessConfigFlow(ConfigFlow, domain=DOMAIN):
try:
await client.authenticate()
except ApiAuthError:
errors["base"] = "invalid_auth"
try:
is_protect = await client.is_protect_api_key()
except Exception: # noqa: BLE001
is_protect = False
errors["base"] = "protect_api_key" if is_protect else "invalid_auth"
except ApiConnectionError:
errors["base"] = "cannot_connect"
except Exception:
@@ -8,6 +8,7 @@
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
"protect_api_key": "This API key is associated with UniFi Protect, not UniFi Access. Please generate a new API key from the UniFi Access application settings.",
"unknown": "[%key:common::config_flow::error::unknown%]"
},
"step": {
@@ -105,6 +105,7 @@ def mock_client() -> Generator[MagicMock]:
):
client = client_mock.return_value
client.authenticate = AsyncMock()
client.is_protect_api_key = AsyncMock(return_value=False)
client.get_doors = AsyncMock(return_value=MOCK_DOORS)
client.get_emergency_status = AsyncMock(
return_value=EmergencyStatus(evacuation=False, lockdown=False)
@@ -398,3 +398,177 @@ async def test_user_flow_ssl_context(
_, call_kwargs = patched_client.call_args
assert isinstance(call_kwargs["ssl_context"], expected_ssl_context_type)
async def test_user_flow_protect_api_key(
hass: HomeAssistant,
mock_setup_entry: AsyncMock,
mock_client: MagicMock,
) -> None:
"""Test user config flow shows specific error when a Protect API key is used."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}
)
assert result["type"] is FlowResultType.FORM
assert result["errors"] == {}
mock_client.authenticate.side_effect = ApiAuthError()
mock_client.is_protect_api_key.return_value = True
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={
CONF_HOST: MOCK_HOST,
CONF_API_TOKEN: MOCK_API_TOKEN,
CONF_VERIFY_SSL: False,
},
)
assert result["type"] is FlowResultType.FORM
assert result["errors"] == {"base": "protect_api_key"}
# Test recovery
mock_client.authenticate.side_effect = None
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={
CONF_HOST: MOCK_HOST,
CONF_API_TOKEN: "correct-access-api-key",
CONF_VERIFY_SSL: False,
},
)
assert result["type"] is FlowResultType.CREATE_ENTRY
async def test_user_flow_protect_api_key_unreachable(
hass: HomeAssistant,
mock_setup_entry: AsyncMock,
mock_client: MagicMock,
) -> None:
"""Test user config flow falls back to invalid_auth when Protect is unreachable."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}
)
mock_client.authenticate.side_effect = ApiAuthError()
mock_client.is_protect_api_key.return_value = False
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={
CONF_HOST: MOCK_HOST,
CONF_API_TOKEN: MOCK_API_TOKEN,
CONF_VERIFY_SSL: False,
},
)
assert result["type"] is FlowResultType.FORM
assert result["errors"] == {"base": "invalid_auth"}
async def test_user_flow_protect_api_key_check_raises(
hass: HomeAssistant,
mock_setup_entry: AsyncMock,
mock_client: MagicMock,
) -> None:
"""Test user config flow falls back to invalid_auth when protect check raises."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}
)
mock_client.authenticate.side_effect = ApiAuthError()
mock_client.is_protect_api_key.side_effect = Exception("unexpected")
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={
CONF_HOST: MOCK_HOST,
CONF_API_TOKEN: MOCK_API_TOKEN,
CONF_VERIFY_SSL: False,
},
)
assert result["type"] is FlowResultType.FORM
assert result["errors"] == {"base": "invalid_auth"}
async def test_reauth_flow_protect_api_key(
hass: HomeAssistant,
mock_setup_entry: AsyncMock,
mock_client: MagicMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test reauth flow shows specific error when a Protect API key is used."""
mock_config_entry.add_to_hass(hass)
result = await mock_config_entry.start_reauth_flow(hass)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "reauth_confirm"
mock_client.authenticate.side_effect = ApiAuthError()
mock_client.is_protect_api_key.return_value = True
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={CONF_API_TOKEN: "protect-api-key"},
)
assert result["type"] is FlowResultType.FORM
assert result["errors"] == {"base": "protect_api_key"}
# Test recovery
mock_client.authenticate.side_effect = None
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={CONF_API_TOKEN: "correct-access-api-key"},
)
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "reauth_successful"
async def test_reconfigure_flow_protect_api_key(
hass: HomeAssistant,
mock_setup_entry: AsyncMock,
mock_client: MagicMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test reconfigure flow shows specific error when a Protect API key is used."""
mock_config_entry.add_to_hass(hass)
result = await mock_config_entry.start_reconfigure_flow(hass)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "reconfigure"
mock_client.authenticate.side_effect = ApiAuthError()
mock_client.is_protect_api_key.return_value = True
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={
CONF_HOST: "10.0.0.1",
CONF_API_TOKEN: "protect-api-key",
CONF_VERIFY_SSL: False,
},
)
assert result["type"] is FlowResultType.FORM
assert result["errors"] == {"base": "protect_api_key"}
# Test recovery
mock_client.authenticate.side_effect = None
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={
CONF_HOST: "10.0.0.1",
CONF_API_TOKEN: "correct-access-api-key",
CONF_VERIFY_SSL: False,
},
)
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "reconfigure_successful"