diff --git a/homeassistant/components/google/config_flow.py b/homeassistant/components/google/config_flow.py index e320212ca1b..1945afe15e9 100644 --- a/homeassistant/components/google/config_flow.py +++ b/homeassistant/components/google/config_flow.py @@ -6,7 +6,7 @@ import logging from typing import Any from gcal_sync.api import GoogleCalendarService -from gcal_sync.exceptions import ApiException +from gcal_sync.exceptions import ApiException, ApiForbiddenException import voluptuous as vol from homeassistant import config_entries @@ -146,6 +146,12 @@ class OAuth2FlowHandler( ) try: primary_calendar = await calendar_service.async_get_calendar("primary") + except ApiForbiddenException as err: + _LOGGER.error( + "Error reading primary calendar, make sure Google Calendar API is enabled: %s", + err, + ) + return self.async_abort(reason="api_disabled") except ApiException as err: _LOGGER.error("Error reading primary calendar: %s", err) return self.async_abort(reason="cannot_connect") diff --git a/homeassistant/components/google/strings.json b/homeassistant/components/google/strings.json index b4c5270e003..5c9b6424473 100644 --- a/homeassistant/components/google/strings.json +++ b/homeassistant/components/google/strings.json @@ -21,7 +21,8 @@ "missing_configuration": "[%key:common::config_flow::abort::oauth2_missing_configuration%]", "code_expired": "Authentication code expired or credential setup is invalid, please try again.", "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]", - "invalid_access_token": "[%key:common::config_flow::error::invalid_access_token%]" + "invalid_access_token": "[%key:common::config_flow::error::invalid_access_token%]", + "api_disabled": "You must enable the Google Calendar API in the Google Cloud Console" }, "create_entry": { "default": "[%key:common::config_flow::create_entry::authenticated%]" diff --git a/tests/components/google/test_config_flow.py b/tests/components/google/test_config_flow.py index bce3f4855c7..78ce6710c18 100644 --- a/tests/components/google/test_config_flow.py +++ b/tests/components/google/test_config_flow.py @@ -4,6 +4,7 @@ from __future__ import annotations from collections.abc import Callable import datetime +from http import HTTPStatus from unittest.mock import Mock, patch from aiohttp.client_exceptions import ClientError @@ -95,10 +96,17 @@ async def primary_calendar_error() -> ClientError | None: return None +@pytest.fixture +async def primary_calendar_status() -> HTTPStatus | None: + """Fixture for tests to inject an error during calendar lookup.""" + return HTTPStatus.OK + + @pytest.fixture(autouse=True) async def primary_calendar( mock_calendar_get: Callable[[...], None], primary_calendar_error: ClientError | None, + primary_calendar_status: HTTPStatus | None, primary_calendar_email: str, ) -> None: """Fixture to return the primary calendar.""" @@ -106,6 +114,7 @@ async def primary_calendar( "primary", {"id": primary_calendar_email, "summary": "Personal", "accessRole": "owner"}, exc=primary_calendar_error, + status=primary_calendar_status, ) @@ -515,11 +524,19 @@ async def test_reauth_flow( assert len(mock_setup.mock_calls) == 1 -@pytest.mark.parametrize("primary_calendar_error", [ClientError()]) +@pytest.mark.parametrize( + "primary_calendar_error,primary_calendar_status,reason", + [ + (ClientError(), None, "cannot_connect"), + (None, HTTPStatus.FORBIDDEN, "api_disabled"), + (None, HTTPStatus.SERVICE_UNAVAILABLE, "cannot_connect"), + ], +) async def test_calendar_lookup_failure( hass: HomeAssistant, mock_code_flow: Mock, mock_exchange: Mock, + reason: str, ) -> None: """Test successful config flow and title fetch fails gracefully.""" await async_import_client_credential( @@ -544,7 +561,7 @@ async def test_calendar_lookup_failure( ) assert result.get("type") == "abort" - assert result.get("reason") == "cannot_connect" + assert result.get("reason") == reason async def test_options_flow_triggers_reauth(