1
0
mirror of https://github.com/home-assistant/core.git synced 2026-05-16 05:21:35 +01:00
Files
core/tests/components/google/conftest.py
T

367 lines
10 KiB
Python

"""Test configuration and mocks for the google integration."""
from collections.abc import AsyncGenerator, Awaitable, Callable, Generator
import datetime
import http
import time
from typing import Any
from unittest.mock import Mock, mock_open, patch
from aiohttp.client_exceptions import ClientError
from gcal_sync.auth import API_BASE_URL
from oauth2client.client import OAuth2Credentials
import pytest
import yaml
from homeassistant.components.application_credentials import (
DOMAIN as APPLICATION_CREDENTIALS_DOMAIN,
ClientCredential,
async_import_client_credential,
)
from homeassistant.components.google import DOMAIN
from homeassistant.core import HomeAssistant
from homeassistant.setup import async_setup_component
from homeassistant.util import dt as dt_util
from tests.common import MockConfigEntry
from tests.test_util.aiohttp import AiohttpClientMocker
type ApiResult = Callable[[dict[str, Any]], None]
type ComponentSetup = Callable[[], Awaitable[bool]]
type AsyncYieldFixture[_T] = AsyncGenerator[_T]
CALENDAR_ID = "qwertyuiopasdfghjklzxcvbnm@import.calendar.google.com"
EMAIL_ADDRESS = "user@gmail.com"
# Entities can either be created based on data directly from the API, or from
# the yaml config that overrides the entity name and other settings. A test
# can use a fixture to exercise either case.
TEST_API_ENTITY = "calendar.we_are_we_are_a_test_calendar"
TEST_API_ENTITY_NAME = "We are, we are, a... test calendar"
# Name of the entity when using yaml configuration overrides
TEST_YAML_ENTITY = "calendar.backyard_light"
TEST_YAML_ENTITY_NAME = "Backyard light"
# A calendar object returned from the API
TEST_API_CALENDAR = {
"id": CALENDAR_ID,
"etag": '"3584134138943410"',
"timeZone": "UTC",
"foregroundColor": "#000000",
"selected": True,
"kind": "calendar#calendarListEntry",
"backgroundColor": "#16a765",
"description": "Test Calendar",
"summary": "We are, we are, a... Test Calendar",
"colorId": "8",
"defaultReminders": [],
}
TEST_EVENT = {
"summary": "Test All Day Event",
"start": {},
"end": {},
"location": "Test Cases",
"description": "test event",
"kind": "calendar#event",
"created": "2016-06-23T16:37:57.000Z",
"transparency": "transparent",
"updated": "2016-06-24T01:57:21.045Z",
"reminders": {"useDefault": True},
"organizer": {
"email": "uvrttabwegnui4gtia3vyqb@import.calendar.google.com",
"displayName": "Organizer Name",
"self": True,
},
"sequence": 0,
"creator": {
"email": "uvrttabwegnui4gtia3vyqb@import.calendar.google.com",
"displayName": "Organizer Name",
"self": True,
},
"id": "_c8rinwq863h45qnucyoi43ny8",
"etag": '"2933466882090000"',
"htmlLink": "https://www.google.com/calendar/event?eid=*******",
"iCalUID": "cydrevtfuybguinhomj@google.com",
"status": "confirmed",
}
CLIENT_ID = "client-id"
CLIENT_SECRET = "client-secret"
@pytest.fixture
def calendar_access_role() -> str:
"""Set default access role to use for test_api_calendar in tests."""
return "owner"
@pytest.fixture
def calendar_is_primary() -> bool:
"""Set if the calendar is the primary or not."""
return False
@pytest.fixture(name="test_api_calendar")
def api_calendar(
calendar_access_role: str, calendar_is_primary: bool
) -> dict[str, Any]:
"""Return a test calendar object used in API responses."""
return {
**TEST_API_CALENDAR,
"accessRole": calendar_access_role,
"primary": calendar_is_primary,
}
@pytest.fixture
def calendars_config_track() -> bool:
"""Fixture that determines the 'track' setting in yaml config."""
return True
@pytest.fixture
def calendars_config_ignore_availability() -> bool:
"""Fixture that determines the 'ignore_availability' setting in yaml config."""
return None
@pytest.fixture
def calendars_config_entity(
calendars_config_track: bool, calendars_config_ignore_availability: bool | None
) -> dict[str, Any]:
"""Fixture that creates an entity within the yaml configuration."""
entity = {
"device_id": "backyard_light",
"name": "Backyard Light",
"search": "#Backyard",
"track": calendars_config_track,
}
if calendars_config_ignore_availability is not None:
entity["ignore_availability"] = calendars_config_ignore_availability
return entity
@pytest.fixture
def calendars_config(calendars_config_entity: dict[str, Any]) -> list[dict[str, Any]]:
"""Fixture that specifies the calendar yaml configuration."""
return [
{
"cal_id": CALENDAR_ID,
"entities": [calendars_config_entity],
}
]
@pytest.fixture
def mock_calendars_yaml(
hass: HomeAssistant,
calendars_config: list[dict[str, Any]],
) -> Generator[Mock]:
"""Fixture that prepares the google_calendars.yaml mocks."""
mocked_open_function = mock_open(
read_data=yaml.dump(calendars_config) if calendars_config else None
)
with patch("homeassistant.components.google.open", mocked_open_function):
yield mocked_open_function
@pytest.fixture
def token_scopes() -> list[str]:
"""Fixture for scopes used during test."""
return ["https://www.googleapis.com/auth/calendar"]
@pytest.fixture
def token_expiry() -> datetime.datetime:
"""Expiration time for credentials used in the test."""
# OAuth library returns an offset-naive timestamp
return dt_util.utcnow().replace(tzinfo=None) + datetime.timedelta(hours=1)
@pytest.fixture
def creds(
token_scopes: list[str], token_expiry: datetime.datetime
) -> OAuth2Credentials:
"""Fixture that defines creds used in the test."""
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
def config_entry_token_expiry() -> float:
"""Fixture for token expiration value stored in the config entry."""
return time.time() + 86400
@pytest.fixture
def config_entry_options() -> dict[str, Any] | None:
"""Fixture to set initial config entry options."""
return None
@pytest.fixture
def config_entry_unique_id() -> str:
"""Fixture that returns the default config entry unique id."""
return EMAIL_ADDRESS
@pytest.fixture
def config_entry(
config_entry_unique_id: str,
token_scopes: list[str],
config_entry_token_expiry: float,
config_entry_options: dict[str, Any] | None,
) -> MockConfigEntry:
"""Fixture to create a config entry for the integration."""
return MockConfigEntry(
domain=DOMAIN,
unique_id=config_entry_unique_id,
data={
"auth_implementation": DOMAIN,
"token": {
"access_token": "ACCESS_TOKEN",
"refresh_token": "REFRESH_TOKEN",
"scope": " ".join(token_scopes),
"token_type": "Bearer",
"expires_at": config_entry_token_expiry,
},
},
options=config_entry_options,
)
@pytest.fixture
def mock_events_list(
aioclient_mock: AiohttpClientMocker,
) -> ApiResult:
"""Fixture to construct a fake event list API response."""
def _put_result(
response: dict[str, Any],
calendar_id: str | None = None,
exc: ClientError | None = None,
) -> None:
if calendar_id is None:
calendar_id = CALENDAR_ID
resp = {
**response,
"nextSyncToken": "sync-token",
}
aioclient_mock.get(
f"{API_BASE_URL}/calendars/{calendar_id}/events",
json=resp,
exc=exc,
)
return _put_result
@pytest.fixture
def mock_events_list_items(
mock_events_list: Callable[[dict[str, Any]], None],
) -> Callable[[list[dict[str, Any]]], None]:
"""Fixture to construct an API response containing event items."""
def _put_items(items: list[dict[str, Any]]) -> None:
mock_events_list({"items": items})
return _put_items
@pytest.fixture
def mock_calendars_list(
aioclient_mock: AiohttpClientMocker,
) -> ApiResult:
"""Fixture to construct a fake calendar list API response."""
def _result(response: dict[str, Any], exc: ClientError | None = None) -> None:
resp = {
**response,
"nextSyncToken": "sync-token",
}
aioclient_mock.get(
f"{API_BASE_URL}/users/me/calendarList",
json=resp,
exc=exc,
)
return _result
@pytest.fixture
def mock_calendar_get(
aioclient_mock: AiohttpClientMocker,
) -> Callable[..., None]:
"""Fixture for returning a calendar get response."""
def _result(
calendar_id: str,
response: dict[str, Any],
exc: ClientError | None = None,
status: http.HTTPStatus = http.HTTPStatus.OK,
) -> None:
aioclient_mock.get(
f"{API_BASE_URL}/calendars/{calendar_id}",
json=response,
exc=exc,
status=status,
)
return _result
@pytest.fixture
def mock_insert_event(
aioclient_mock: AiohttpClientMocker,
) -> Callable[..., None]:
"""Fixture for capturing event creation."""
def _expect_result(
calendar_id: str = CALENDAR_ID, exc: ClientError | None = None
) -> None:
aioclient_mock.post(
f"{API_BASE_URL}/calendars/{calendar_id}/events",
exc=exc,
)
return _expect_result
@pytest.fixture(autouse=True)
async def set_time_zone(hass: HomeAssistant) -> None:
"""Set the time zone for the tests."""
# Set our timezone to CST/Regina so we can check calculations
# This keeps UTC-6 all year round
await hass.config.async_set_time_zone("America/Regina")
@pytest.fixture
def component_setup(
hass: HomeAssistant, config_entry: MockConfigEntry
) -> ComponentSetup:
"""Fixture for setting up the integration."""
async def _setup_func() -> bool:
assert await async_setup_component(hass, APPLICATION_CREDENTIALS_DOMAIN, {})
await async_import_client_credential(
hass,
DOMAIN,
ClientCredential("client-id", "client-secret"),
)
config_entry.add_to_hass(hass)
result = await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done(wait_background_tasks=True)
return result
return _setup_func