Improve Google Health config flow error messages (#179940)

Co-authored-by: Home Assistant Developer <hello@home-assistant.io>
This commit is contained in:
Allen Porter
2026-08-24 02:49:13 -04:00
committed by GitHub
co-authored by Home Assistant Developer
parent 52e0c3c6a7
commit ef8e89180b
3 changed files with 58 additions and 9 deletions
@@ -8,7 +8,8 @@ from google_health_api import GoogleHealthApi
from google_health_api.const import HealthApiScope
from google_health_api.exceptions import (
GoogleHealthApiError,
HealthApiForbiddenException,
HealthApiScopeInsufficientException,
HealthApiServiceDisabledException,
)
from homeassistant.config_entries import (
@@ -81,19 +82,22 @@ class OAuth2FlowHandler(
try:
identity = await api.get_identity()
except HealthApiForbiddenException as err:
except HealthApiServiceDisabledException as err:
_LOGGER.error("Error getting Google Health identity: %s", err)
return self.async_abort(
reason="api_not_enabled",
description_placeholders={"url": API_CONSOLE_URL},
)
except HealthApiScopeInsufficientException as err:
_LOGGER.error("Error getting Google Health identity: %s", err)
return self.async_abort(reason="missing_profile_scope")
except GoogleHealthApiError as err:
_LOGGER.error("Error getting Google Health identity: %s", err)
return self.async_abort(reason="cannot_connect")
if not identity.health_user_id:
_LOGGER.error("Google Health identity has no health_user_id")
return self.async_abort(reason="cannot_connect")
return self.async_abort(reason="missing_profile_scope")
await self.async_set_unique_id(identity.health_user_id)
if self.source in (SOURCE_REAUTH, SOURCE_RECONFIGURE):
@@ -110,7 +114,7 @@ class OAuth2FlowHandler(
try:
userinfo = await api.get_user_info()
display_name = userinfo.given_name or userinfo.name
except Exception as err: # pylint: disable=broad-except # noqa: BLE001
except GoogleHealthApiError as err:
_LOGGER.warning("Error fetching user profile name: %s", err)
return self.async_create_entry(
@@ -10,7 +10,7 @@
"authorize_url_timeout": "[%key:common::config_flow::abort::oauth2_authorize_url_timeout%]",
"cannot_connect": "Failed to connect.",
"missing_configuration": "[%key:common::config_flow::abort::oauth2_missing_configuration%]",
"missing_profile_scope": "Missing required Google Health profile read permission.",
"missing_profile_scope": "Missing required Google Health profile read permission. Please try again and select the right permission.",
"no_url_available": "[%key:common::config_flow::abort::oauth2_no_url_available%]",
"oauth_error": "[%key:common::config_flow::abort::oauth2_error%]",
"oauth_failed": "[%key:common::config_flow::abort::oauth2_failed%]",
@@ -5,7 +5,8 @@ from unittest.mock import AsyncMock, patch
from google_health_api.const import HealthApiScope
from google_health_api.exceptions import (
GoogleHealthApiError,
HealthApiForbiddenException,
HealthApiScopeInsufficientException,
HealthApiServiceDisabledException,
)
from google_health_api.model import Identity
import pytest
@@ -243,8 +244,8 @@ async def test_config_flow_api_not_enabled(
mock_google_health_client: AsyncMock,
) -> None:
"""Test config flow aborts if the Google Health API is not enabled."""
mock_google_health_client.get_identity.side_effect = HealthApiForbiddenException(
"Forbidden"
mock_google_health_client.get_identity.side_effect = (
HealthApiServiceDisabledException
)
result = await hass.config_entries.flow.async_init(
@@ -280,6 +281,50 @@ async def test_config_flow_api_not_enabled(
}
@pytest.mark.usefixtures(
"current_request_with_host", "mock_setup_entry", "setup_credentials"
)
async def test_config_flow_scope_insufficient(
hass: HomeAssistant,
hass_client_no_auth: ClientSessionGenerator,
aioclient_mock: AiohttpClientMocker,
mock_google_health_client: AsyncMock,
) -> None:
"""Test config flow aborts if the OAuth token has insufficient scope."""
mock_google_health_client.get_identity.side_effect = (
HealthApiScopeInsufficientException
)
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}
)
state = config_entry_oauth2_flow._encode_jwt(
hass,
{
"flow_id": result["flow_id"],
"redirect_uri": "https://example.com/auth/external/callback",
},
)
client = await hass_client_no_auth()
await client.get(f"/auth/external/callback?code=abcd&state={state}")
aioclient_mock.post(
OAUTH2_TOKEN,
json={
"refresh_token": "mock-refresh-token",
"access_token": "mock-access-token",
"type": "Bearer",
"expires_in": 60,
"scope": " ".join(OAUTH_SCOPES),
},
)
result = await hass.config_entries.flow.async_configure(result["flow_id"])
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "missing_profile_scope"
@pytest.mark.usefixtures(
"current_request_with_host", "mock_setup_entry", "setup_credentials"
)
@@ -321,7 +366,7 @@ async def test_config_flow_missing_health_user_id(
result = await hass.config_entries.flow.async_configure(result["flow_id"])
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "cannot_connect"
assert result["reason"] == "missing_profile_scope"
@pytest.mark.usefixtures(