440 lines
14 KiB
Python
440 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(
|
|
hass,
|
|
mock_calendars_yaml,
|
|
test_api_calendar,
|
|
mock_calendars_list,
|
|
config_entry,
|
|
):
|
|
"""Fixture that pulls in the default fixtures for tests in this file."""
|
|
mock_calendars_list({"items": [test_api_calendar]})
|
|
config_entry.add_to_hass(hass)
|
|
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 beyond update/throttle point
|
|
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
|
|
|
|
|
|
async def test_scan_calendar_error(
|
|
hass,
|
|
calendar_resource,
|
|
component_setup,
|
|
test_api_calendar,
|
|
):
|
|
"""Test that the calendar update handles a server error."""
|
|
with patch(
|
|
"homeassistant.components.google.api.google_discovery.build",
|
|
side_effect=httplib2.ServerNotFoundError("unit test"),
|
|
):
|
|
assert await component_setup()
|
|
|
|
assert not hass.states.get(TEST_ENTITY)
|