Simplify google calendar authentication setup (#67314)

Simplify google calendar authentication to combine some of the cases together, and reduce unecessarily checks. Make the
tests share common authentication setup and reduce use of mocks by introducing a fake for holding on to credentials.
This makes future refactoring simpler, so we don't have to care as much about the interactions with the credentials
storage.
This commit is contained in:
Allen Porter 2022-02-26 15:17:02 -08:00 committed by GitHub
parent a151d3f9a0
commit 0e74184e4f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 77 additions and 59 deletions

View file

@ -5,7 +5,6 @@ from collections.abc import Mapping
from datetime import datetime, timedelta, timezone from datetime import datetime, timedelta, timezone
from enum import Enum from enum import Enum
import logging import logging
import os
from typing import Any from typing import Any
from googleapiclient import discovery as google_discovery from googleapiclient import discovery as google_discovery
@ -163,7 +162,10 @@ ADD_EVENT_SERVICE_SCHEMA = vol.Schema(
def do_authentication( def do_authentication(
hass: HomeAssistant, hass_config: ConfigType, config: ConfigType hass: HomeAssistant,
hass_config: ConfigType,
config: ConfigType,
storage: Storage,
) -> bool: ) -> bool:
"""Notify user of actions and authenticate. """Notify user of actions and authenticate.
@ -226,7 +228,6 @@ def do_authentication(
# not ready yet, call again # not ready yet, call again
return return
storage = Storage(hass.config.path(TOKEN_FILE))
storage.put(credentials) storage.put(credentials)
do_setup(hass, hass_config, config) do_setup(hass, hass_config, config)
assert listener assert listener
@ -256,14 +257,14 @@ def setup(hass: HomeAssistant, config: ConfigType) -> bool:
# component is set up by tts platform # component is set up by tts platform
return True return True
token_file = hass.config.path(TOKEN_FILE) storage = Storage(hass.config.path(TOKEN_FILE))
if not os.path.isfile(token_file): creds = storage.get()
_LOGGER.debug("Token file does not exist, authenticating for first time") if (
do_authentication(hass, config, conf) not creds
else: or not creds.scopes
if not check_correct_scopes(hass, token_file, conf): or conf[CONF_CALENDAR_ACCESS].scope not in creds.scopes
_LOGGER.debug("Existing scopes are not sufficient, re-authenticating") ):
do_authentication(hass, config, conf) do_authentication(hass, config, conf, storage)
else: else:
do_setup(hass, config, conf) do_setup(hass, config, conf)

View file

@ -1,11 +1,17 @@
"""Test configuration and mocks for the google integration.""" """Test configuration and mocks for the google integration."""
from __future__ import annotations
from collections.abc import Callable from collections.abc import Callable
import datetime
from typing import Any, Generator, TypeVar from typing import Any, Generator, TypeVar
from unittest.mock import Mock, patch from unittest.mock import Mock, patch
from oauth2client.client import Credentials, OAuth2Credentials
import pytest import pytest
from homeassistant.components.google import GoogleCalendarService from homeassistant.components.google import GoogleCalendarService
from homeassistant.core import HomeAssistant
from homeassistant.util.dt import utcnow
ApiResult = Callable[[dict[str, Any]], None] ApiResult = Callable[[dict[str, Any]], None]
T = TypeVar("T") T = TypeVar("T")
@ -36,6 +42,62 @@ def test_calendar():
return TEST_CALENDAR return TEST_CALENDAR
class FakeStorage:
"""A fake storage object for persiting creds."""
def __init__(self) -> None:
"""Initialize FakeStorage."""
self._creds: Credentials | None = None
def get(self) -> Credentials | None:
"""Get credentials from storage."""
return self._creds
def put(self, creds: Credentials) -> None:
"""Put credentials in storage."""
self._creds = creds
@pytest.fixture
async def token_scopes() -> list[str]:
"""Fixture for scopes used during test."""
return ["https://www.googleapis.com/auth/calendar"]
@pytest.fixture
async def creds(token_scopes: list[str]) -> OAuth2Credentials:
"""Fixture that defines creds used in the test."""
token_expiry = utcnow() + datetime.timedelta(days=7)
return OAuth2Credentials(
access_token="ACCESS_TOKEN",
client_id="client-id",
client_secret="client-secret",
refresh_token="REFRESH_TOKEN",
token_expiry=token_expiry,
token_uri="http://example.com",
user_agent="n/a",
scopes=token_scopes,
)
@pytest.fixture(autouse=True)
async def storage() -> YieldFixture[FakeStorage]:
"""Fixture to populate an existing token file for read on startup."""
storage = FakeStorage()
with patch("homeassistant.components.google.Storage", return_value=storage):
yield storage
@pytest.fixture
async def mock_token_read(
hass: HomeAssistant,
creds: OAuth2Credentials,
storage: FakeStorage,
) -> None:
"""Fixture to populate an existing token file for read on startup."""
storage.put(creds)
@pytest.fixture @pytest.fixture
def mock_next_event(): def mock_next_event():
"""Mock the google calendar data.""" """Mock the google calendar data."""

View file

@ -21,7 +21,6 @@ from homeassistant.components.google import (
CONF_TRACK, CONF_TRACK,
DEVICE_SCHEMA, DEVICE_SCHEMA,
SERVICE_SCAN_CALENDARS, SERVICE_SCAN_CALENDARS,
do_setup,
) )
from homeassistant.const import STATE_OFF, STATE_ON from homeassistant.const import STATE_OFF, STATE_ON
from homeassistant.helpers.template import DATE_STR_FORMAT from homeassistant.helpers.template import DATE_STR_FORMAT
@ -86,21 +85,18 @@ def get_calendar_info(calendar):
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def mock_google_setup(hass, test_calendar): def mock_google_setup(hass, test_calendar, mock_token_read):
"""Mock the google set up functions.""" """Mock the google set up functions."""
hass.loop.run_until_complete(async_setup_component(hass, "group", {"group": {}})) hass.loop.run_until_complete(async_setup_component(hass, "group", {"group": {}}))
calendar = get_calendar_info(test_calendar) calendar = get_calendar_info(test_calendar)
calendars = {calendar[CONF_CAL_ID]: calendar} calendars = {calendar[CONF_CAL_ID]: calendar}
patch_google_auth = patch(
"homeassistant.components.google.do_authentication", side_effect=do_setup
)
patch_google_load = patch( patch_google_load = patch(
"homeassistant.components.google.load_config", return_value=calendars "homeassistant.components.google.load_config", return_value=calendars
) )
patch_google_services = patch("homeassistant.components.google.setup_services") patch_google_services = patch("homeassistant.components.google.setup_services")
async_mock_service(hass, "google", SERVICE_SCAN_CALENDARS) async_mock_service(hass, "google", SERVICE_SCAN_CALENDARS)
with patch_google_auth, patch_google_load, patch_google_services: with patch_google_load, patch_google_services:
yield yield

View file

@ -53,28 +53,6 @@ async def mock_code_flow(
yield mock_flow yield mock_flow
@pytest.fixture
async def token_scopes() -> list[str]:
"""Fixture for scopes used during test."""
return ["https://www.googleapis.com/auth/calendar"]
@pytest.fixture
async def creds(token_scopes: list[str]) -> OAuth2Credentials:
"""Fixture that defines creds used in the test."""
token_expiry = utcnow() + datetime.timedelta(days=7)
return OAuth2Credentials(
access_token="ACCESS_TOKEN",
client_id="client-id",
client_secret="client-secret",
refresh_token="REFRESH_TOKEN",
token_expiry=token_expiry,
token_uri="http://example.com",
user_agent="n/a",
scopes=token_scopes,
)
@pytest.fixture @pytest.fixture
async def mock_exchange(creds: OAuth2Credentials) -> YieldFixture[Mock]: async def mock_exchange(creds: OAuth2Credentials) -> YieldFixture[Mock]:
"""Fixture for mocking out the exchange for credentials.""" """Fixture for mocking out the exchange for credentials."""
@ -84,25 +62,6 @@ async def mock_exchange(creds: OAuth2Credentials) -> YieldFixture[Mock]:
yield mock yield mock
@pytest.fixture(autouse=True)
async def mock_token_write(hass: HomeAssistant) -> None:
"""Fixture to avoid writing token files to disk."""
with patch(
"homeassistant.components.google.os.path.isfile", return_value=True
), patch("homeassistant.components.google.Storage.put"):
yield
@pytest.fixture
async def mock_token_read(
hass: HomeAssistant,
creds: OAuth2Credentials,
) -> None:
"""Fixture to populate an existing token file."""
with patch("homeassistant.components.google.Storage.get", return_value=creds):
yield
@pytest.fixture @pytest.fixture
async def calendars_config() -> list[dict[str, Any]]: async def calendars_config() -> list[dict[str, Any]]:
"""Fixture for tests to override default calendar configuration.""" """Fixture for tests to override default calendar configuration."""