mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 03:07:37 +00:00
Improve google calendar test quality and share setup (#67441)
* Improve google calendar test quality and share setup Improve google calendar test quality by exercising different combinations of cases and new coverage. The existing test cases do achieve very high coverage, however some of the subtle interactions between the components are not as well exercised, which is needed when we start changing how the internal code is structured moving to async or to config entries. Ipmrovement include: - Exercising additional cases around different types of configuration parameters that control how calendars are tracked or not tracked. The tracking can be configured in google_calendars.yaml, or by defaults set in configuration.yaml for new calendars. - Share even more test setup, used when exercising the above different scenarios - Add new test cases for event creation. - Improve test readability by making more clear the differences between tests exercising yaml and API test calendars. The data types are now more clearly separated in the two cases, as well as the entity names created in the two cases. * Undo some diffs for readability * Improve pydoc readability * Improve pydoc readability * Incorporate improvements from Martin * Make test parameters a State instance * Update test naming to be more correct * Fix flake8 errors
This commit is contained in:
parent
4e52f26ed1
commit
57ffc65af2
@ -1,25 +1,44 @@
|
||||
"""Test configuration and mocks for the google integration."""
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from collections.abc import Awaitable, Callable
|
||||
import datetime
|
||||
from typing import Any, Generator, TypeVar
|
||||
from unittest.mock import Mock, patch
|
||||
from unittest.mock import Mock, mock_open, patch
|
||||
|
||||
from googleapiclient import discovery as google_discovery
|
||||
from oauth2client.client import Credentials, OAuth2Credentials
|
||||
import pytest
|
||||
import yaml
|
||||
|
||||
from homeassistant.components.google import CONF_TRACK_NEW, DOMAIN
|
||||
from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.setup import async_setup_component
|
||||
import homeassistant.util.dt as dt_util
|
||||
from homeassistant.util.dt import utcnow
|
||||
|
||||
ORIG_TIMEZONE = dt_util.DEFAULT_TIME_ZONE
|
||||
|
||||
ApiResult = Callable[[dict[str, Any]], None]
|
||||
ComponentSetup = Callable[[], Awaitable[bool]]
|
||||
T = TypeVar("T")
|
||||
YieldFixture = Generator[T, None, None]
|
||||
|
||||
|
||||
CALENDAR_ID = "qwertyuiopasdfghjklzxcvbnm@import.calendar.google.com"
|
||||
TEST_CALENDAR = {
|
||||
|
||||
# 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",
|
||||
@ -32,14 +51,63 @@ TEST_CALENDAR = {
|
||||
"summary": "We are, we are, a... Test Calendar",
|
||||
"colorId": "8",
|
||||
"defaultReminders": [],
|
||||
"track": True,
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def test_calendar():
|
||||
"""Return a test calendar."""
|
||||
return TEST_CALENDAR
|
||||
def test_api_calendar():
|
||||
"""Return a test calendar object used in API responses."""
|
||||
return TEST_API_CALENDAR
|
||||
|
||||
|
||||
@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
|
||||
async def mock_calendars_yaml(
|
||||
hass: HomeAssistant,
|
||||
calendars_config: list[dict[str, Any]],
|
||||
) -> None:
|
||||
"""Fixture that prepares the google_calendars.yaml mocks."""
|
||||
mocked_open_function = mock_open(read_data=yaml.dump(calendars_config))
|
||||
with patch("homeassistant.components.google.open", mocked_open_function):
|
||||
yield
|
||||
|
||||
|
||||
class FakeStorage:
|
||||
@ -156,3 +224,49 @@ def mock_insert_event(
|
||||
insert_mock = Mock()
|
||||
calendar_resource.return_value.events.return_value.insert = insert_mock
|
||||
return insert_mock
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def set_time_zone(hass):
|
||||
"""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
|
||||
hass.config.time_zone = "CST"
|
||||
dt_util.set_default_time_zone(dt_util.get_time_zone("America/Regina"))
|
||||
yield
|
||||
dt_util.set_default_time_zone(ORIG_TIMEZONE)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def google_config_track_new() -> None:
|
||||
"""Fixture for tests to set the 'track_new' configuration.yaml setting."""
|
||||
return None
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def google_config(google_config_track_new: bool | None) -> dict[str, Any]:
|
||||
"""Fixture for overriding component config."""
|
||||
google_config = {CONF_CLIENT_ID: "client-id", CONF_CLIENT_SECRET: "client-secret"}
|
||||
if google_config_track_new is not None:
|
||||
google_config[CONF_TRACK_NEW] = google_config_track_new
|
||||
return google_config
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def config(google_config: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Fixture for overriding component config."""
|
||||
return {DOMAIN: google_config}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def component_setup(
|
||||
hass: HomeAssistant, config: dict[str, Any]
|
||||
) -> ComponentSetup:
|
||||
"""Fixture for setting up the integration."""
|
||||
|
||||
async def _setup_func() -> bool:
|
||||
result = await async_setup_component(hass, DOMAIN, config)
|
||||
await hass.async_block_till_done()
|
||||
return result
|
||||
|
||||
return _setup_func
|
||||
|
@ -2,38 +2,22 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime
|
||||
from http import HTTPStatus
|
||||
from typing import Any
|
||||
from unittest.mock import Mock, patch
|
||||
from unittest.mock import Mock
|
||||
|
||||
import httplib2
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.google import (
|
||||
CONF_CAL_ID,
|
||||
CONF_CLIENT_ID,
|
||||
CONF_CLIENT_SECRET,
|
||||
CONF_DEVICE_ID,
|
||||
CONF_ENTITIES,
|
||||
CONF_IGNORE_AVAILABILITY,
|
||||
CONF_NAME,
|
||||
CONF_TRACK,
|
||||
DEVICE_SCHEMA,
|
||||
SERVICE_SCAN_CALENDARS,
|
||||
)
|
||||
from homeassistant.const import STATE_OFF, STATE_ON
|
||||
from homeassistant.helpers.template import DATE_STR_FORMAT
|
||||
from homeassistant.setup import async_setup_component
|
||||
from homeassistant.util import slugify
|
||||
import homeassistant.util.dt as dt_util
|
||||
|
||||
from .conftest import TEST_CALENDAR
|
||||
from .conftest import TEST_YAML_ENTITY, TEST_YAML_ENTITY_NAME
|
||||
|
||||
from tests.common import async_mock_service
|
||||
|
||||
GOOGLE_CONFIG = {CONF_CLIENT_ID: "client_id", CONF_CLIENT_SECRET: "client_secret"}
|
||||
TEST_ENTITY = "calendar.we_are_we_are_a_test_calendar"
|
||||
TEST_ENTITY_NAME = "We are, we are, a... Test Calendar"
|
||||
TEST_ENTITY = TEST_YAML_ENTITY
|
||||
TEST_ENTITY_NAME = TEST_YAML_ENTITY_NAME
|
||||
|
||||
TEST_EVENT = {
|
||||
"summary": "Test All Day Event",
|
||||
@ -65,48 +49,10 @@ TEST_EVENT = {
|
||||
}
|
||||
|
||||
|
||||
def get_calendar_info(calendar):
|
||||
"""Convert data from Google into DEVICE_SCHEMA."""
|
||||
calendar_info = DEVICE_SCHEMA(
|
||||
{
|
||||
CONF_CAL_ID: calendar["id"],
|
||||
CONF_ENTITIES: [
|
||||
{
|
||||
CONF_TRACK: calendar["track"],
|
||||
CONF_NAME: calendar["summary"],
|
||||
CONF_DEVICE_ID: slugify(calendar["summary"]),
|
||||
CONF_IGNORE_AVAILABILITY: calendar.get("ignore_availability", True),
|
||||
}
|
||||
],
|
||||
}
|
||||
)
|
||||
return calendar_info
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def mock_google_setup(hass, test_calendar, mock_token_read):
|
||||
"""Mock the google set up functions."""
|
||||
hass.loop.run_until_complete(async_setup_component(hass, "group", {"group": {}}))
|
||||
calendar = get_calendar_info(test_calendar)
|
||||
calendars = {calendar[CONF_CAL_ID]: calendar}
|
||||
patch_google_load = patch(
|
||||
"homeassistant.components.google.load_config", return_value=calendars
|
||||
)
|
||||
patch_google_services = patch("homeassistant.components.google.setup_services")
|
||||
async_mock_service(hass, "google", SERVICE_SCAN_CALENDARS)
|
||||
|
||||
with patch_google_load, patch_google_services:
|
||||
yield
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def set_time_zone():
|
||||
"""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
|
||||
dt_util.set_default_time_zone(dt_util.get_time_zone("America/Regina"))
|
||||
yield
|
||||
dt_util.set_default_time_zone(dt_util.get_time_zone("UTC"))
|
||||
def mock_test_setup(mock_calendars_yaml, mock_token_read):
|
||||
"""Fixture that pulls in the default fixtures for tests in this file."""
|
||||
return
|
||||
|
||||
|
||||
def upcoming() -> dict[str, Any]:
|
||||
@ -114,22 +60,24 @@ def upcoming() -> dict[str, Any]:
|
||||
now = dt_util.now()
|
||||
return {
|
||||
"start": {"dateTime": now.isoformat()},
|
||||
"end": {"dateTime": (now + dt_util.dt.timedelta(minutes=5)).isoformat()},
|
||||
"end": {"dateTime": (now + datetime.timedelta(minutes=5)).isoformat()},
|
||||
}
|
||||
|
||||
|
||||
def upcoming_event_url() -> str:
|
||||
"""Return a calendar API to return events created by upcoming()."""
|
||||
now = dt_util.now()
|
||||
start = (now - dt_util.dt.timedelta(minutes=60)).isoformat()
|
||||
end = (now + dt_util.dt.timedelta(minutes=60)).isoformat()
|
||||
start = (now - datetime.timedelta(minutes=60)).isoformat()
|
||||
end = (now + datetime.timedelta(minutes=60)).isoformat()
|
||||
return f"/api/calendars/{TEST_ENTITY}?start={start}&end={end}"
|
||||
|
||||
|
||||
async def test_all_day_event(hass, mock_events_list_items, mock_token_read):
|
||||
async def test_all_day_event(
|
||||
hass, mock_events_list_items, mock_token_read, component_setup
|
||||
):
|
||||
"""Test that we can create an event trigger on device."""
|
||||
week_from_today = dt_util.dt.date.today() + dt_util.dt.timedelta(days=7)
|
||||
end_event = week_from_today + dt_util.dt.timedelta(days=1)
|
||||
week_from_today = dt_util.now().date() + datetime.timedelta(days=7)
|
||||
end_event = week_from_today + datetime.timedelta(days=1)
|
||||
event = {
|
||||
**TEST_EVENT,
|
||||
"start": {"date": week_from_today.isoformat()},
|
||||
@ -137,8 +85,7 @@ async def test_all_day_event(hass, mock_events_list_items, mock_token_read):
|
||||
}
|
||||
mock_events_list_items([event])
|
||||
|
||||
assert await async_setup_component(hass, "google", {"google": GOOGLE_CONFIG})
|
||||
await hass.async_block_till_done()
|
||||
assert await component_setup()
|
||||
|
||||
state = hass.states.get(TEST_ENTITY)
|
||||
assert state.name == TEST_ENTITY_NAME
|
||||
@ -155,10 +102,10 @@ async def test_all_day_event(hass, mock_events_list_items, mock_token_read):
|
||||
}
|
||||
|
||||
|
||||
async def test_future_event(hass, mock_events_list_items):
|
||||
async def test_future_event(hass, mock_events_list_items, component_setup):
|
||||
"""Test that we can create an event trigger on device."""
|
||||
one_hour_from_now = dt_util.now() + dt_util.dt.timedelta(minutes=30)
|
||||
end_event = one_hour_from_now + dt_util.dt.timedelta(minutes=60)
|
||||
one_hour_from_now = dt_util.now() + datetime.timedelta(minutes=30)
|
||||
end_event = one_hour_from_now + datetime.timedelta(minutes=60)
|
||||
event = {
|
||||
**TEST_EVENT,
|
||||
"start": {"dateTime": one_hour_from_now.isoformat()},
|
||||
@ -166,8 +113,7 @@ async def test_future_event(hass, mock_events_list_items):
|
||||
}
|
||||
mock_events_list_items([event])
|
||||
|
||||
assert await async_setup_component(hass, "google", {"google": GOOGLE_CONFIG})
|
||||
await hass.async_block_till_done()
|
||||
assert await component_setup()
|
||||
|
||||
state = hass.states.get(TEST_ENTITY)
|
||||
assert state.name == TEST_ENTITY_NAME
|
||||
@ -184,10 +130,10 @@ async def test_future_event(hass, mock_events_list_items):
|
||||
}
|
||||
|
||||
|
||||
async def test_in_progress_event(hass, mock_events_list_items):
|
||||
async def test_in_progress_event(hass, mock_events_list_items, component_setup):
|
||||
"""Test that we can create an event trigger on device."""
|
||||
middle_of_event = dt_util.now() - dt_util.dt.timedelta(minutes=30)
|
||||
end_event = middle_of_event + dt_util.dt.timedelta(minutes=60)
|
||||
middle_of_event = dt_util.now() - datetime.timedelta(minutes=30)
|
||||
end_event = middle_of_event + datetime.timedelta(minutes=60)
|
||||
event = {
|
||||
**TEST_EVENT,
|
||||
"start": {"dateTime": middle_of_event.isoformat()},
|
||||
@ -195,8 +141,7 @@ async def test_in_progress_event(hass, mock_events_list_items):
|
||||
}
|
||||
mock_events_list_items([event])
|
||||
|
||||
assert await async_setup_component(hass, "google", {"google": GOOGLE_CONFIG})
|
||||
await hass.async_block_till_done()
|
||||
assert await component_setup()
|
||||
|
||||
state = hass.states.get(TEST_ENTITY)
|
||||
assert state.name == TEST_ENTITY_NAME
|
||||
@ -213,10 +158,10 @@ async def test_in_progress_event(hass, mock_events_list_items):
|
||||
}
|
||||
|
||||
|
||||
async def test_offset_in_progress_event(hass, mock_events_list_items):
|
||||
async def test_offset_in_progress_event(hass, mock_events_list_items, component_setup):
|
||||
"""Test that we can create an event trigger on device."""
|
||||
middle_of_event = dt_util.now() + dt_util.dt.timedelta(minutes=14)
|
||||
end_event = middle_of_event + dt_util.dt.timedelta(minutes=60)
|
||||
middle_of_event = dt_util.now() + datetime.timedelta(minutes=14)
|
||||
end_event = middle_of_event + datetime.timedelta(minutes=60)
|
||||
event_summary = "Test Event in Progress"
|
||||
event = {
|
||||
**TEST_EVENT,
|
||||
@ -226,8 +171,7 @@ async def test_offset_in_progress_event(hass, mock_events_list_items):
|
||||
}
|
||||
mock_events_list_items([event])
|
||||
|
||||
assert await async_setup_component(hass, "google", {"google": GOOGLE_CONFIG})
|
||||
await hass.async_block_till_done()
|
||||
assert await component_setup()
|
||||
|
||||
state = hass.states.get(TEST_ENTITY)
|
||||
assert state.name == TEST_ENTITY_NAME
|
||||
@ -244,11 +188,12 @@ async def test_offset_in_progress_event(hass, mock_events_list_items):
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.skip
|
||||
async def test_all_day_offset_in_progress_event(hass, mock_events_list_items):
|
||||
async def test_all_day_offset_in_progress_event(
|
||||
hass, mock_events_list_items, component_setup
|
||||
):
|
||||
"""Test that we can create an event trigger on device."""
|
||||
tomorrow = dt_util.dt.date.today() + dt_util.dt.timedelta(days=1)
|
||||
end_event = tomorrow + dt_util.dt.timedelta(days=1)
|
||||
tomorrow = dt_util.now().date() + datetime.timedelta(days=1)
|
||||
end_event = tomorrow + datetime.timedelta(days=1)
|
||||
event_summary = "Test All Day Event Offset In Progress"
|
||||
event = {
|
||||
**TEST_EVENT,
|
||||
@ -258,8 +203,7 @@ async def test_all_day_offset_in_progress_event(hass, mock_events_list_items):
|
||||
}
|
||||
mock_events_list_items([event])
|
||||
|
||||
assert await async_setup_component(hass, "google", {"google": GOOGLE_CONFIG})
|
||||
await hass.async_block_till_done()
|
||||
assert await component_setup()
|
||||
|
||||
state = hass.states.get(TEST_ENTITY)
|
||||
assert state.name == TEST_ENTITY_NAME
|
||||
@ -276,22 +220,22 @@ async def test_all_day_offset_in_progress_event(hass, mock_events_list_items):
|
||||
}
|
||||
|
||||
|
||||
async def test_all_day_offset_event(hass, mock_events_list_items):
|
||||
async def test_all_day_offset_event(hass, mock_events_list_items, component_setup):
|
||||
"""Test that we can create an event trigger on device."""
|
||||
tomorrow = dt_util.dt.date.today() + dt_util.dt.timedelta(days=2)
|
||||
end_event = tomorrow + dt_util.dt.timedelta(days=1)
|
||||
offset_hours = 1 + dt_util.now().hour
|
||||
now = dt_util.now()
|
||||
day_after_tomorrow = now.date() + datetime.timedelta(days=2)
|
||||
end_event = day_after_tomorrow + datetime.timedelta(days=1)
|
||||
offset_hours = 1 + now.hour
|
||||
event_summary = "Test All Day Event Offset"
|
||||
event = {
|
||||
**TEST_EVENT,
|
||||
"start": {"date": tomorrow.isoformat()},
|
||||
"start": {"date": day_after_tomorrow.isoformat()},
|
||||
"end": {"date": end_event.isoformat()},
|
||||
"summary": f"{event_summary} !!-{offset_hours}:0",
|
||||
}
|
||||
mock_events_list_items([event])
|
||||
|
||||
assert await async_setup_component(hass, "google", {"google": GOOGLE_CONFIG})
|
||||
await hass.async_block_till_done()
|
||||
assert await component_setup()
|
||||
|
||||
state = hass.states.get(TEST_ENTITY)
|
||||
assert state.name == TEST_ENTITY_NAME
|
||||
@ -301,30 +245,28 @@ async def test_all_day_offset_event(hass, mock_events_list_items):
|
||||
"message": event_summary,
|
||||
"all_day": True,
|
||||
"offset_reached": False,
|
||||
"start_time": tomorrow.strftime(DATE_STR_FORMAT),
|
||||
"start_time": day_after_tomorrow.strftime(DATE_STR_FORMAT),
|
||||
"end_time": end_event.strftime(DATE_STR_FORMAT),
|
||||
"location": event["location"],
|
||||
"description": event["description"],
|
||||
}
|
||||
|
||||
|
||||
async def test_update_error(hass, calendar_resource):
|
||||
async def test_update_error(hass, calendar_resource, component_setup):
|
||||
"""Test that the calendar handles a server error."""
|
||||
calendar_resource.return_value.get = Mock(
|
||||
side_effect=httplib2.ServerNotFoundError("unit test")
|
||||
)
|
||||
assert await async_setup_component(hass, "google", {"google": GOOGLE_CONFIG})
|
||||
await hass.async_block_till_done()
|
||||
assert await component_setup()
|
||||
|
||||
state = hass.states.get(TEST_ENTITY)
|
||||
assert state.name == TEST_ENTITY_NAME
|
||||
assert state.state == "off"
|
||||
|
||||
|
||||
async def test_calendars_api(hass, hass_client):
|
||||
async def test_calendars_api(hass, hass_client, component_setup):
|
||||
"""Test the Rest API returns the calendar."""
|
||||
assert await async_setup_component(hass, "google", {"google": GOOGLE_CONFIG})
|
||||
await hass.async_block_till_done()
|
||||
assert await component_setup()
|
||||
|
||||
client = await hass_client()
|
||||
response = await client.get("/api/calendars")
|
||||
@ -338,12 +280,13 @@ async def test_calendars_api(hass, hass_client):
|
||||
]
|
||||
|
||||
|
||||
async def test_http_event_api_failure(hass, hass_client, calendar_resource):
|
||||
async def test_http_event_api_failure(
|
||||
hass, hass_client, calendar_resource, component_setup
|
||||
):
|
||||
"""Test the Rest API response during a calendar failure."""
|
||||
calendar_resource.side_effect = httplib2.ServerNotFoundError("unit test")
|
||||
|
||||
assert await async_setup_component(hass, "google", {"google": GOOGLE_CONFIG})
|
||||
await hass.async_block_till_done()
|
||||
assert await component_setup()
|
||||
|
||||
client = await hass_client()
|
||||
response = await client.get(upcoming_event_url())
|
||||
@ -353,15 +296,16 @@ async def test_http_event_api_failure(hass, hass_client, calendar_resource):
|
||||
assert events == []
|
||||
|
||||
|
||||
async def test_http_api_event(hass, hass_client, mock_events_list_items):
|
||||
async def test_http_api_event(
|
||||
hass, hass_client, mock_events_list_items, component_setup
|
||||
):
|
||||
"""Test querying the API and fetching events from the server."""
|
||||
event = {
|
||||
**TEST_EVENT,
|
||||
**upcoming(),
|
||||
}
|
||||
mock_events_list_items([event])
|
||||
assert await async_setup_component(hass, "google", {"google": GOOGLE_CONFIG})
|
||||
await hass.async_block_till_done()
|
||||
assert await component_setup()
|
||||
|
||||
client = await hass_client()
|
||||
response = await client.get(upcoming_event_url())
|
||||
@ -372,22 +316,27 @@ async def test_http_api_event(hass, hass_client, mock_events_list_items):
|
||||
assert events[0]["summary"] == event["summary"]
|
||||
|
||||
|
||||
def create_ignore_avail_calendar() -> dict[str, Any]:
|
||||
"""Create a calendar with ignore_availability set."""
|
||||
calendar = TEST_CALENDAR.copy()
|
||||
calendar["ignore_availability"] = False
|
||||
return calendar
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"test_calendar,transparency,expect_visible_event",
|
||||
"calendars_config_ignore_availability,transparency,expect_visible_event",
|
||||
[
|
||||
(create_ignore_avail_calendar(), "opaque", True),
|
||||
(create_ignore_avail_calendar(), "transparent", False),
|
||||
# Look at visibility to determine if entity is created
|
||||
(False, "opaque", True),
|
||||
(False, "transparent", False),
|
||||
# Ignoring availability and always show the entity
|
||||
(True, "opaque", True),
|
||||
(True, "transparency", True),
|
||||
# Default to ignore availability
|
||||
(None, "opaque", True),
|
||||
(None, "transparency", True),
|
||||
],
|
||||
)
|
||||
async def test_opaque_event(
|
||||
hass, hass_client, mock_events_list_items, transparency, expect_visible_event
|
||||
hass,
|
||||
hass_client,
|
||||
mock_events_list_items,
|
||||
component_setup,
|
||||
transparency,
|
||||
expect_visible_event,
|
||||
):
|
||||
"""Test querying the API and fetching events from the server."""
|
||||
event = {
|
||||
@ -396,8 +345,7 @@ async def test_opaque_event(
|
||||
"transparency": transparency,
|
||||
}
|
||||
mock_events_list_items([event])
|
||||
assert await async_setup_component(hass, "google", {"google": GOOGLE_CONFIG})
|
||||
await hass.async_block_till_done()
|
||||
assert await component_setup()
|
||||
|
||||
client = await hass_client()
|
||||
response = await client.get(upcoming_event_url())
|
||||
|
@ -1,8 +1,10 @@
|
||||
"""The tests for the Google Calendar component."""
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Awaitable, Callable
|
||||
import datetime
|
||||
from typing import Any
|
||||
from unittest.mock import Mock, call, mock_open, patch
|
||||
from unittest.mock import Mock, call, patch
|
||||
|
||||
from oauth2client.client import (
|
||||
FlowExchangeError,
|
||||
@ -10,21 +12,26 @@ from oauth2client.client import (
|
||||
OAuth2DeviceCodeError,
|
||||
)
|
||||
import pytest
|
||||
import yaml
|
||||
|
||||
from homeassistant.components.google import DOMAIN, SERVICE_ADD_EVENT
|
||||
from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET, STATE_OFF
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.setup import async_setup_component
|
||||
from homeassistant.util import dt as dt_util
|
||||
from homeassistant.const import STATE_OFF
|
||||
from homeassistant.core import HomeAssistant, State
|
||||
from homeassistant.util.dt import utcnow
|
||||
|
||||
from .conftest import CALENDAR_ID, ApiResult, YieldFixture
|
||||
from .conftest import (
|
||||
CALENDAR_ID,
|
||||
TEST_API_ENTITY,
|
||||
TEST_API_ENTITY_NAME,
|
||||
TEST_YAML_ENTITY,
|
||||
TEST_YAML_ENTITY_NAME,
|
||||
ApiResult,
|
||||
ComponentSetup,
|
||||
YieldFixture,
|
||||
)
|
||||
|
||||
from tests.common import async_fire_time_changed
|
||||
|
||||
# Typing helpers
|
||||
ComponentSetup = Callable[[], Awaitable[bool]]
|
||||
HassApi = Callable[[], Awaitable[dict[str, Any]]]
|
||||
|
||||
CODE_CHECK_INTERVAL = 1
|
||||
@ -59,35 +66,6 @@ async def mock_exchange(creds: OAuth2Credentials) -> YieldFixture[Mock]:
|
||||
yield mock
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def calendars_config() -> list[dict[str, Any]]:
|
||||
"""Fixture for tests to override default calendar configuration."""
|
||||
return [
|
||||
{
|
||||
"cal_id": CALENDAR_ID,
|
||||
"entities": [
|
||||
{
|
||||
"device_id": "backyard_light",
|
||||
"name": "Backyard Light",
|
||||
"search": "#Backyard",
|
||||
"track": True,
|
||||
}
|
||||
],
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def mock_calendars_yaml(
|
||||
hass: HomeAssistant,
|
||||
calendars_config: list[dict[str, Any]],
|
||||
) -> None:
|
||||
"""Fixture that prepares the calendars.yaml file."""
|
||||
mocked_open_function = mock_open(read_data=yaml.dump(calendars_config))
|
||||
with patch("homeassistant.components.google.open", mocked_open_function):
|
||||
yield
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def mock_notification() -> YieldFixture[Mock]:
|
||||
"""Fixture for capturing persistent notifications."""
|
||||
@ -95,26 +73,6 @@ async def mock_notification() -> YieldFixture[Mock]:
|
||||
yield mock
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def config() -> dict[str, Any]:
|
||||
"""Fixture for overriding component config."""
|
||||
return {DOMAIN: {CONF_CLIENT_ID: "client-id", CONF_CLIENT_SECRET: "client-ecret"}}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def component_setup(
|
||||
hass: HomeAssistant, config: dict[str, Any]
|
||||
) -> ComponentSetup:
|
||||
"""Fixture for setting up the integration."""
|
||||
|
||||
async def _setup_func() -> bool:
|
||||
result = await async_setup_component(hass, DOMAIN, config)
|
||||
await hass.async_block_till_done()
|
||||
return result
|
||||
|
||||
return _setup_func
|
||||
|
||||
|
||||
async def fire_alarm(hass, point_in_time):
|
||||
"""Fire an alarm and wait for callbacks to run."""
|
||||
with patch("homeassistant.util.dt.utcnow", return_value=point_in_time):
|
||||
@ -133,7 +91,17 @@ async def test_setup_config_empty(
|
||||
|
||||
mock_notification.assert_not_called()
|
||||
|
||||
assert not hass.states.get("calendar.backyard_light")
|
||||
assert not hass.states.get(TEST_YAML_ENTITY)
|
||||
|
||||
|
||||
def assert_state(actual: State | None, expected: State | None) -> None:
|
||||
"""Assert that the two states are equal."""
|
||||
if actual is None:
|
||||
assert actual == expected
|
||||
return
|
||||
assert actual.entity_id == expected.entity_id
|
||||
assert actual.state == expected.state
|
||||
assert actual.attributes == expected.attributes
|
||||
|
||||
|
||||
async def test_init_success(
|
||||
@ -151,9 +119,9 @@ async def test_init_success(
|
||||
now = utcnow()
|
||||
await fire_alarm(hass, now + CODE_CHECK_ALARM_TIMEDELTA)
|
||||
|
||||
state = hass.states.get("calendar.backyard_light")
|
||||
state = hass.states.get(TEST_YAML_ENTITY)
|
||||
assert state
|
||||
assert state.name == "Backyard Light"
|
||||
assert state.name == TEST_YAML_ENTITY_NAME
|
||||
assert state.state == STATE_OFF
|
||||
|
||||
mock_notification.assert_called()
|
||||
@ -174,7 +142,7 @@ async def test_code_error(
|
||||
):
|
||||
assert await component_setup()
|
||||
|
||||
assert not hass.states.get("calendar.backyard_light")
|
||||
assert not hass.states.get(TEST_YAML_ENTITY)
|
||||
|
||||
mock_notification.assert_called()
|
||||
assert "Error: Test Failure" in mock_notification.call_args[0][1]
|
||||
@ -194,7 +162,7 @@ async def test_expired_after_exchange(
|
||||
now = utcnow()
|
||||
await fire_alarm(hass, now + CODE_CHECK_ALARM_TIMEDELTA)
|
||||
|
||||
assert not hass.states.get("calendar.backyard_light")
|
||||
assert not hass.states.get(TEST_YAML_ENTITY)
|
||||
|
||||
mock_notification.assert_called()
|
||||
assert (
|
||||
@ -220,7 +188,7 @@ async def test_exchange_error(
|
||||
now = utcnow()
|
||||
await fire_alarm(hass, now + CODE_CHECK_ALARM_TIMEDELTA)
|
||||
|
||||
assert not hass.states.get("calendar.backyard_light")
|
||||
assert not hass.states.get(TEST_YAML_ENTITY)
|
||||
|
||||
mock_notification.assert_called()
|
||||
assert "In order to authorize Home-Assistant" in mock_notification.call_args[0][1]
|
||||
@ -236,9 +204,9 @@ async def test_existing_token(
|
||||
"""Test setup with an existing token file."""
|
||||
assert await component_setup()
|
||||
|
||||
state = hass.states.get("calendar.backyard_light")
|
||||
state = hass.states.get(TEST_YAML_ENTITY)
|
||||
assert state
|
||||
assert state.name == "Backyard Light"
|
||||
assert state.name == TEST_YAML_ENTITY_NAME
|
||||
assert state.state == STATE_OFF
|
||||
|
||||
mock_notification.assert_not_called()
|
||||
@ -265,9 +233,9 @@ async def test_existing_token_missing_scope(
|
||||
await fire_alarm(hass, now + CODE_CHECK_ALARM_TIMEDELTA)
|
||||
assert len(mock_exchange.mock_calls) == 1
|
||||
|
||||
state = hass.states.get("calendar.backyard_light")
|
||||
state = hass.states.get(TEST_YAML_ENTITY)
|
||||
assert state
|
||||
assert state.name == "Backyard Light"
|
||||
assert state.name == TEST_YAML_ENTITY_NAME
|
||||
assert state.state == STATE_OFF
|
||||
|
||||
# No notifications on success
|
||||
@ -287,7 +255,7 @@ async def test_calendar_yaml_missing_required_fields(
|
||||
"""Test setup with a missing schema fields, ignores the error and continues."""
|
||||
assert await component_setup()
|
||||
|
||||
assert not hass.states.get("calendar.backyard_light")
|
||||
assert not hass.states.get(TEST_YAML_ENTITY)
|
||||
|
||||
mock_notification.assert_not_called()
|
||||
|
||||
@ -306,38 +274,133 @@ async def test_invalid_calendar_yaml(
|
||||
# Integration fails to setup
|
||||
assert not await component_setup()
|
||||
|
||||
assert not hass.states.get("calendar.backyard_light")
|
||||
assert not hass.states.get(TEST_YAML_ENTITY)
|
||||
|
||||
mock_notification.assert_not_called()
|
||||
|
||||
|
||||
async def test_found_calendar_from_api(
|
||||
@pytest.mark.parametrize(
|
||||
"google_config_track_new,calendars_config,expected_state",
|
||||
[
|
||||
(
|
||||
None,
|
||||
[],
|
||||
State(
|
||||
TEST_API_ENTITY,
|
||||
STATE_OFF,
|
||||
attributes={
|
||||
"offset_reached": False,
|
||||
"friendly_name": TEST_API_ENTITY_NAME,
|
||||
},
|
||||
),
|
||||
),
|
||||
(
|
||||
True,
|
||||
[],
|
||||
State(
|
||||
TEST_API_ENTITY,
|
||||
STATE_OFF,
|
||||
attributes={
|
||||
"offset_reached": False,
|
||||
"friendly_name": TEST_API_ENTITY_NAME,
|
||||
},
|
||||
),
|
||||
),
|
||||
(False, [], None),
|
||||
],
|
||||
ids=["default", "True", "False"],
|
||||
)
|
||||
async def test_track_new(
|
||||
hass: HomeAssistant,
|
||||
mock_token_read: None,
|
||||
component_setup: ComponentSetup,
|
||||
mock_calendars_list: ApiResult,
|
||||
test_calendar: dict[str, Any],
|
||||
test_api_calendar: dict[str, Any],
|
||||
mock_calendars_yaml: None,
|
||||
expected_state: State,
|
||||
) -> None:
|
||||
"""Test behavior of configuration.yaml settings for tracking new calendars not in the config."""
|
||||
|
||||
mock_calendars_list({"items": [test_api_calendar]})
|
||||
assert await component_setup()
|
||||
|
||||
# The calendar does not
|
||||
state = hass.states.get(TEST_API_ENTITY)
|
||||
assert_state(state, expected_state)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("calendars_config", [[]])
|
||||
async def test_found_calendar_from_api(
|
||||
hass: HomeAssistant,
|
||||
mock_token_read: None,
|
||||
component_setup: ComponentSetup,
|
||||
mock_calendars_yaml: None,
|
||||
mock_calendars_list: ApiResult,
|
||||
test_api_calendar: dict[str, Any],
|
||||
) -> None:
|
||||
"""Test finding a calendar from the API."""
|
||||
|
||||
mock_calendars_list({"items": [test_calendar]})
|
||||
mock_calendars_list({"items": [test_api_calendar]})
|
||||
assert await component_setup()
|
||||
|
||||
mocked_open_function = mock_open(read_data=yaml.dump([]))
|
||||
with patch("homeassistant.components.google.open", mocked_open_function):
|
||||
assert await component_setup()
|
||||
|
||||
state = hass.states.get("calendar.we_are_we_are_a_test_calendar")
|
||||
# The calendar does not
|
||||
state = hass.states.get(TEST_API_ENTITY)
|
||||
assert state
|
||||
assert state.name == "We are, we are, a... Test Calendar"
|
||||
assert state.name == TEST_API_ENTITY_NAME
|
||||
assert state.state == STATE_OFF
|
||||
|
||||
# No yaml config loaded that overwrites the entity name
|
||||
assert not hass.states.get(TEST_YAML_ENTITY)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"calendars_config_track,expected_state",
|
||||
[
|
||||
(
|
||||
True,
|
||||
State(
|
||||
TEST_YAML_ENTITY,
|
||||
STATE_OFF,
|
||||
attributes={
|
||||
"offset_reached": False,
|
||||
"friendly_name": TEST_YAML_ENTITY_NAME,
|
||||
},
|
||||
),
|
||||
),
|
||||
(False, None),
|
||||
],
|
||||
)
|
||||
async def test_calendar_config_track_new(
|
||||
hass: HomeAssistant,
|
||||
mock_token_read: None,
|
||||
component_setup: ComponentSetup,
|
||||
mock_calendars_yaml: None,
|
||||
mock_calendars_list: ApiResult,
|
||||
test_api_calendar: dict[str, Any],
|
||||
calendars_config_track: bool,
|
||||
expected_state: State,
|
||||
) -> None:
|
||||
"""Test calendar config that overrides whether or not a calendar is tracked."""
|
||||
|
||||
mock_calendars_list({"items": [test_api_calendar]})
|
||||
assert await component_setup()
|
||||
|
||||
state = hass.states.get(TEST_YAML_ENTITY)
|
||||
assert_state(state, expected_state)
|
||||
if calendars_config_track:
|
||||
assert state
|
||||
assert state.name == TEST_YAML_ENTITY_NAME
|
||||
assert state.state == STATE_OFF
|
||||
else:
|
||||
assert not state
|
||||
|
||||
|
||||
async def test_add_event(
|
||||
hass: HomeAssistant,
|
||||
mock_token_read: None,
|
||||
component_setup: ComponentSetup,
|
||||
mock_calendars_list: ApiResult,
|
||||
test_calendar: dict[str, Any],
|
||||
test_api_calendar: dict[str, Any],
|
||||
mock_insert_event: Mock,
|
||||
) -> None:
|
||||
"""Test service call that adds an event."""
|
||||
@ -387,7 +450,7 @@ async def test_add_event_date_in_x(
|
||||
mock_token_read: None,
|
||||
component_setup: ComponentSetup,
|
||||
mock_calendars_list: ApiResult,
|
||||
test_calendar: dict[str, Any],
|
||||
test_api_calendar: dict[str, Any],
|
||||
mock_insert_event: Mock,
|
||||
date_fields: dict[str, Any],
|
||||
start_timedelta: datetime.timedelta,
|
||||
@ -425,19 +488,18 @@ async def test_add_event_date_in_x(
|
||||
)
|
||||
|
||||
|
||||
async def test_add_event_date_range(
|
||||
async def test_add_event_date(
|
||||
hass: HomeAssistant,
|
||||
mock_token_read: None,
|
||||
component_setup: ComponentSetup,
|
||||
mock_calendars_list: ApiResult,
|
||||
test_calendar: dict[str, Any],
|
||||
mock_insert_event: Mock,
|
||||
) -> None:
|
||||
"""Test service call that sets a date range."""
|
||||
|
||||
assert await component_setup()
|
||||
|
||||
now = dt_util.utcnow()
|
||||
now = utcnow()
|
||||
today = now.date()
|
||||
end_date = today + datetime.timedelta(days=2)
|
||||
|
||||
@ -464,3 +526,50 @@ async def test_add_event_date_range(
|
||||
"end": {"date": end_date.isoformat()},
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
async def test_add_event_date_time(
|
||||
hass: HomeAssistant,
|
||||
mock_token_read: None,
|
||||
component_setup: ComponentSetup,
|
||||
mock_calendars_list: ApiResult,
|
||||
test_api_calendar: dict[str, Any],
|
||||
mock_insert_event: Mock,
|
||||
) -> None:
|
||||
"""Test service call that adds an event with a date time range."""
|
||||
|
||||
assert await component_setup()
|
||||
|
||||
start_datetime = datetime.datetime.now()
|
||||
delta = datetime.timedelta(days=3, hours=3)
|
||||
end_datetime = start_datetime + delta
|
||||
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_ADD_EVENT,
|
||||
{
|
||||
"calendar_id": CALENDAR_ID,
|
||||
"summary": "Summary",
|
||||
"description": "Description",
|
||||
"start_date_time": start_datetime.isoformat(),
|
||||
"end_date_time": end_datetime.isoformat(),
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
mock_insert_event.assert_called()
|
||||
|
||||
assert mock_insert_event.mock_calls[0] == call(
|
||||
calendarId=CALENDAR_ID,
|
||||
body={
|
||||
"summary": "Summary",
|
||||
"description": "Description",
|
||||
"start": {
|
||||
"dateTime": start_datetime.isoformat(timespec="seconds"),
|
||||
"timeZone": "CST",
|
||||
},
|
||||
"end": {
|
||||
"dateTime": end_datetime.isoformat(timespec="seconds"),
|
||||
"timeZone": "CST",
|
||||
},
|
||||
},
|
||||
)
|
||||
|
Loading…
x
Reference in New Issue
Block a user