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:
Allen Porter 2022-03-03 23:12:24 -08:00 committed by GitHub
parent 4e52f26ed1
commit 57ffc65af2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 385 additions and 214 deletions

View File

@ -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

View File

@ -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())

View File

@ -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]})
mocked_open_function = mock_open(read_data=yaml.dump([]))
with patch("homeassistant.components.google.open", mocked_open_function):
mock_calendars_list({"items": [test_api_calendar]})
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",
},
},
)