hass-core/tests/components/google/test_calendar.py
Allen Porter 8adcd10f55
Make google calendar loading API centric, not loading from yaml (#67722)
* Make google calendar loading API centric, not loading from yaml

Update the behavior for google calendar to focus on loading calendars based on the
API and using the yaml configuration to override behavior. The old behavior was
to first load from yaml, then also load from the API, which is atypical.

This is pulled out from a larger change to rewrite calendar using async and config
flows.

Tests needed to be updated to reflect the new API centric behavior, and changing
the API call ordering required changing tests that exercise failures.

* Update to use async_fire_time_changed to invoke updates
2022-03-08 21:56:49 +01:00

419 lines
14 KiB
Python

"""The tests for the google calendar platform."""
from __future__ import annotations
import datetime
from http import HTTPStatus
from typing import Any
from unittest.mock import patch
import httplib2
import pytest
from homeassistant.const import STATE_OFF, STATE_ON
from homeassistant.helpers.template import DATE_STR_FORMAT
import homeassistant.util.dt as dt_util
from .conftest import TEST_YAML_ENTITY, TEST_YAML_ENTITY_NAME
from tests.common import async_fire_time_changed
TEST_ENTITY = TEST_YAML_ENTITY
TEST_ENTITY_NAME = TEST_YAML_ENTITY_NAME
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",
}
@pytest.fixture(autouse=True)
def mock_test_setup(
mock_calendars_yaml, test_api_calendar, mock_calendars_list, mock_token_read
):
"""Fixture that pulls in the default fixtures for tests in this file."""
mock_calendars_list({"items": [test_api_calendar]})
return
def upcoming() -> dict[str, Any]:
"""Create a test event with an arbitrary start/end time fetched from the api url."""
now = dt_util.now()
return {
"start": {"dateTime": now.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 - 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, component_setup
):
"""Test that we can create an event trigger on device."""
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()},
"end": {"date": end_event.isoformat()},
}
mock_events_list_items([event])
assert await component_setup()
state = hass.states.get(TEST_ENTITY)
assert state.name == TEST_ENTITY_NAME
assert state.state == STATE_OFF
assert dict(state.attributes) == {
"friendly_name": TEST_ENTITY_NAME,
"message": event["summary"],
"all_day": True,
"offset_reached": False,
"start_time": week_from_today.strftime(DATE_STR_FORMAT),
"end_time": end_event.strftime(DATE_STR_FORMAT),
"location": event["location"],
"description": event["description"],
}
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() + datetime.timedelta(minutes=30)
end_event = one_hour_from_now + datetime.timedelta(minutes=60)
event = {
**TEST_EVENT,
"start": {"dateTime": one_hour_from_now.isoformat()},
"end": {"dateTime": end_event.isoformat()},
}
mock_events_list_items([event])
assert await component_setup()
state = hass.states.get(TEST_ENTITY)
assert state.name == TEST_ENTITY_NAME
assert state.state == STATE_OFF
assert dict(state.attributes) == {
"friendly_name": TEST_ENTITY_NAME,
"message": event["summary"],
"all_day": False,
"offset_reached": False,
"start_time": one_hour_from_now.strftime(DATE_STR_FORMAT),
"end_time": end_event.strftime(DATE_STR_FORMAT),
"location": event["location"],
"description": event["description"],
}
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() - datetime.timedelta(minutes=30)
end_event = middle_of_event + datetime.timedelta(minutes=60)
event = {
**TEST_EVENT,
"start": {"dateTime": middle_of_event.isoformat()},
"end": {"dateTime": end_event.isoformat()},
}
mock_events_list_items([event])
assert await component_setup()
state = hass.states.get(TEST_ENTITY)
assert state.name == TEST_ENTITY_NAME
assert state.state == STATE_ON
assert dict(state.attributes) == {
"friendly_name": TEST_ENTITY_NAME,
"message": event["summary"],
"all_day": False,
"offset_reached": False,
"start_time": middle_of_event.strftime(DATE_STR_FORMAT),
"end_time": end_event.strftime(DATE_STR_FORMAT),
"location": event["location"],
"description": event["description"],
}
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() + datetime.timedelta(minutes=14)
end_event = middle_of_event + datetime.timedelta(minutes=60)
event_summary = "Test Event in Progress"
event = {
**TEST_EVENT,
"start": {"dateTime": middle_of_event.isoformat()},
"end": {"dateTime": end_event.isoformat()},
"summary": f"{event_summary} !!-15",
}
mock_events_list_items([event])
assert await component_setup()
state = hass.states.get(TEST_ENTITY)
assert state.name == TEST_ENTITY_NAME
assert state.state == STATE_OFF
assert dict(state.attributes) == {
"friendly_name": TEST_ENTITY_NAME,
"message": event_summary,
"all_day": False,
"offset_reached": True,
"start_time": middle_of_event.strftime(DATE_STR_FORMAT),
"end_time": end_event.strftime(DATE_STR_FORMAT),
"location": event["location"],
"description": event["description"],
}
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.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,
"start": {"date": tomorrow.isoformat()},
"end": {"date": end_event.isoformat()},
"summary": f"{event_summary} !!-25:0",
}
mock_events_list_items([event])
assert await component_setup()
state = hass.states.get(TEST_ENTITY)
assert state.name == TEST_ENTITY_NAME
assert state.state == STATE_OFF
assert dict(state.attributes) == {
"friendly_name": TEST_ENTITY_NAME,
"message": event_summary,
"all_day": True,
"offset_reached": True,
"start_time": tomorrow.strftime(DATE_STR_FORMAT),
"end_time": end_event.strftime(DATE_STR_FORMAT),
"location": event["location"],
"description": event["description"],
}
async def test_all_day_offset_event(hass, mock_events_list_items, component_setup):
"""Test that we can create an event trigger on device."""
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": day_after_tomorrow.isoformat()},
"end": {"date": end_event.isoformat()},
"summary": f"{event_summary} !!-{offset_hours}:0",
}
mock_events_list_items([event])
assert await component_setup()
state = hass.states.get(TEST_ENTITY)
assert state.name == TEST_ENTITY_NAME
assert state.state == STATE_OFF
assert dict(state.attributes) == {
"friendly_name": TEST_ENTITY_NAME,
"message": event_summary,
"all_day": True,
"offset_reached": False,
"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, component_setup, test_api_calendar
):
"""Test that the calendar update handles a server error."""
now = dt_util.now()
with patch("homeassistant.components.google.api.google_discovery.build") as mock:
mock.return_value.calendarList.return_value.list.return_value.execute.return_value = {
"items": [test_api_calendar]
}
mock.return_value.events.return_value.list.return_value.execute.return_value = {
"items": [
{
**TEST_EVENT,
"start": {
"dateTime": (now + datetime.timedelta(minutes=-30)).isoformat()
},
"end": {
"dateTime": (now + datetime.timedelta(minutes=30)).isoformat()
},
}
]
}
assert await component_setup()
state = hass.states.get(TEST_ENTITY)
assert state.name == TEST_ENTITY_NAME
assert state.state == "on"
# Advance time to avoid throttling
now += datetime.timedelta(minutes=30)
with patch(
"homeassistant.components.google.api.google_discovery.build",
side_effect=httplib2.ServerNotFoundError("unit test"),
), patch("homeassistant.util.utcnow", return_value=now):
async_fire_time_changed(hass, now)
await hass.async_block_till_done()
# No change
state = hass.states.get(TEST_ENTITY)
assert state.name == TEST_ENTITY_NAME
assert state.state == "on"
# Advance time to avoid throttling
now += datetime.timedelta(minutes=30)
with patch(
"homeassistant.components.google.api.google_discovery.build"
) as mock, patch("homeassistant.util.utcnow", return_value=now):
mock.return_value.events.return_value.list.return_value.execute.return_value = {
"items": [
{
**TEST_EVENT,
"start": {
"dateTime": (now + datetime.timedelta(minutes=30)).isoformat()
},
"end": {
"dateTime": (now + datetime.timedelta(minutes=60)).isoformat()
},
}
]
}
async_fire_time_changed(hass, now)
await hass.async_block_till_done()
# State updated
state = hass.states.get(TEST_ENTITY)
assert state.name == TEST_ENTITY_NAME
assert state.state == "off"
async def test_calendars_api(hass, hass_client, component_setup):
"""Test the Rest API returns the calendar."""
assert await component_setup()
client = await hass_client()
response = await client.get("/api/calendars")
assert response.status == HTTPStatus.OK
data = await response.json()
assert data == [
{
"entity_id": TEST_ENTITY,
"name": TEST_ENTITY_NAME,
}
]
async def test_http_event_api_failure(
hass, hass_client, calendar_resource, component_setup
):
"""Test the Rest API response during a calendar failure."""
assert await component_setup()
client = await hass_client()
calendar_resource.side_effect = httplib2.ServerNotFoundError("unit test")
response = await client.get(upcoming_event_url())
assert response.status == HTTPStatus.OK
# A failure to talk to the server results in an empty list of events
events = await response.json()
assert events == []
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 component_setup()
client = await hass_client()
response = await client.get(upcoming_event_url())
assert response.status == HTTPStatus.OK
events = await response.json()
assert len(events) == 1
assert "summary" in events[0]
assert events[0]["summary"] == event["summary"]
@pytest.mark.parametrize(
"calendars_config_ignore_availability,transparency,expect_visible_event",
[
# 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,
component_setup,
transparency,
expect_visible_event,
):
"""Test querying the API and fetching events from the server."""
event = {
**TEST_EVENT,
**upcoming(),
"transparency": transparency,
}
mock_events_list_items([event])
assert await component_setup()
client = await hass_client()
response = await client.get(upcoming_event_url())
assert response.status == HTTPStatus.OK
events = await response.json()
assert (len(events) > 0) == expect_visible_event