* 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
354 lines
11 KiB
Python
354 lines
11 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 Mock
|
|
|
|
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
|
|
|
|
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, mock_token_read):
|
|
"""Fixture that pulls in the default fixtures for tests in this file."""
|
|
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 that the calendar handles a server error."""
|
|
calendar_resource.return_value.get = Mock(
|
|
side_effect=httplib2.ServerNotFoundError("unit test")
|
|
)
|
|
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, 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."""
|
|
calendar_resource.side_effect = httplib2.ServerNotFoundError("unit test")
|
|
|
|
assert await component_setup()
|
|
|
|
client = await hass_client()
|
|
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
|