472 lines
13 KiB
Python
472 lines
13 KiB
Python
"""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, patch
|
|
|
|
import pytest
|
|
|
|
from homeassistant.components.google import (
|
|
DOMAIN,
|
|
SERVICE_ADD_EVENT,
|
|
SERVICE_SCAN_CALENDARS,
|
|
)
|
|
from homeassistant.config_entries import ConfigEntryState
|
|
from homeassistant.const import STATE_OFF
|
|
from homeassistant.core import HomeAssistant, State
|
|
from homeassistant.util.dt import utcnow
|
|
|
|
from .conftest import (
|
|
CALENDAR_ID,
|
|
TEST_API_ENTITY,
|
|
TEST_API_ENTITY_NAME,
|
|
TEST_YAML_ENTITY,
|
|
TEST_YAML_ENTITY_NAME,
|
|
ApiResult,
|
|
ComponentSetup,
|
|
)
|
|
|
|
from tests.common import MockConfigEntry
|
|
|
|
# Typing helpers
|
|
HassApi = Callable[[], Awaitable[dict[str, Any]]]
|
|
|
|
|
|
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
|
|
|
|
|
|
@pytest.fixture
|
|
def setup_config_entry(
|
|
hass: HomeAssistant, config_entry: MockConfigEntry
|
|
) -> MockConfigEntry:
|
|
"""Fixture to initialize the config entry."""
|
|
config_entry.add_to_hass(hass)
|
|
|
|
|
|
async def test_unload_entry(
|
|
hass: HomeAssistant,
|
|
component_setup: ComponentSetup,
|
|
setup_config_entry: MockConfigEntry,
|
|
) -> None:
|
|
"""Test load and unload of a ConfigEntry."""
|
|
await component_setup()
|
|
|
|
entries = hass.config_entries.async_entries(DOMAIN)
|
|
assert len(entries) == 1
|
|
entry = entries[0]
|
|
assert entry.state is ConfigEntryState.LOADED
|
|
|
|
assert await hass.config_entries.async_unload(entry.entry_id)
|
|
assert entry.state == ConfigEntryState.NOT_LOADED
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"token_scopes", ["https://www.googleapis.com/auth/calendar.readonly"]
|
|
)
|
|
async def test_existing_token_missing_scope(
|
|
hass: HomeAssistant,
|
|
token_scopes: list[str],
|
|
component_setup: ComponentSetup,
|
|
config_entry: MockConfigEntry,
|
|
) -> None:
|
|
"""Test setup where existing token does not have sufficient scopes."""
|
|
config_entry.add_to_hass(hass)
|
|
assert await component_setup()
|
|
|
|
entries = hass.config_entries.async_entries(DOMAIN)
|
|
assert len(entries) == 1
|
|
assert entries[0].state is ConfigEntryState.SETUP_ERROR
|
|
|
|
flows = hass.config_entries.flow.async_progress()
|
|
assert len(flows) == 1
|
|
assert flows[0]["step_id"] == "reauth_confirm"
|
|
|
|
|
|
@pytest.mark.parametrize("calendars_config", [[{"cal_id": "invalid-schema"}]])
|
|
async def test_calendar_yaml_missing_required_fields(
|
|
hass: HomeAssistant,
|
|
component_setup: ComponentSetup,
|
|
calendars_config: list[dict[str, Any]],
|
|
mock_calendars_yaml: None,
|
|
setup_config_entry: MockConfigEntry,
|
|
) -> None:
|
|
"""Test setup with a missing schema fields, ignores the error and continues."""
|
|
assert await component_setup()
|
|
|
|
assert not hass.states.get(TEST_YAML_ENTITY)
|
|
|
|
|
|
@pytest.mark.parametrize("calendars_config", [[{"missing-cal_id": "invalid-schema"}]])
|
|
async def test_invalid_calendar_yaml(
|
|
hass: HomeAssistant,
|
|
component_setup: ComponentSetup,
|
|
calendars_config: list[dict[str, Any]],
|
|
mock_calendars_yaml: None,
|
|
setup_config_entry: MockConfigEntry,
|
|
) -> None:
|
|
"""Test setup with missing entity id fields fails to setup the config entry."""
|
|
# Integration fails to setup
|
|
assert await component_setup()
|
|
|
|
# XXX No config entries
|
|
|
|
assert not hass.states.get(TEST_YAML_ENTITY)
|
|
|
|
|
|
async def test_calendar_yaml_error(
|
|
hass: HomeAssistant,
|
|
component_setup: ComponentSetup,
|
|
mock_calendars_list: ApiResult,
|
|
test_api_calendar: dict[str, Any],
|
|
setup_config_entry: MockConfigEntry,
|
|
) -> None:
|
|
"""Test setup with yaml file not found."""
|
|
mock_calendars_list({"items": [test_api_calendar]})
|
|
|
|
with patch("homeassistant.components.google.open", side_effect=FileNotFoundError()):
|
|
assert await component_setup()
|
|
|
|
assert not hass.states.get(TEST_YAML_ENTITY)
|
|
assert hass.states.get(TEST_API_ENTITY)
|
|
|
|
|
|
@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,
|
|
component_setup: ComponentSetup,
|
|
mock_calendars_list: ApiResult,
|
|
test_api_calendar: dict[str, Any],
|
|
mock_calendars_yaml: None,
|
|
expected_state: State,
|
|
setup_config_entry: MockConfigEntry,
|
|
) -> 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()
|
|
|
|
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,
|
|
component_setup: ComponentSetup,
|
|
mock_calendars_yaml: None,
|
|
mock_calendars_list: ApiResult,
|
|
test_api_calendar: dict[str, Any],
|
|
setup_config_entry: MockConfigEntry,
|
|
) -> None:
|
|
"""Test finding a calendar from the API."""
|
|
|
|
mock_calendars_list({"items": [test_api_calendar]})
|
|
assert await component_setup()
|
|
|
|
state = hass.states.get(TEST_API_ENTITY)
|
|
assert state
|
|
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,
|
|
component_setup: ComponentSetup,
|
|
mock_calendars_yaml: None,
|
|
mock_calendars_list: ApiResult,
|
|
test_api_calendar: dict[str, Any],
|
|
calendars_config_track: bool,
|
|
expected_state: State,
|
|
setup_config_entry: MockConfigEntry,
|
|
) -> 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)
|
|
|
|
|
|
async def test_add_event(
|
|
hass: HomeAssistant,
|
|
component_setup: ComponentSetup,
|
|
mock_calendars_list: ApiResult,
|
|
test_api_calendar: dict[str, Any],
|
|
mock_insert_event: Mock,
|
|
setup_config_entry: MockConfigEntry,
|
|
) -> None:
|
|
"""Test service call that adds an event."""
|
|
|
|
assert await component_setup()
|
|
|
|
await hass.services.async_call(
|
|
DOMAIN,
|
|
SERVICE_ADD_EVENT,
|
|
{
|
|
"calendar_id": CALENDAR_ID,
|
|
"summary": "Summary",
|
|
"description": "Description",
|
|
},
|
|
blocking=True,
|
|
)
|
|
mock_insert_event.assert_called()
|
|
assert mock_insert_event.mock_calls[0] == call(
|
|
calendarId=CALENDAR_ID,
|
|
body={
|
|
"summary": "Summary",
|
|
"description": "Description",
|
|
"start": {},
|
|
"end": {},
|
|
},
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"date_fields,start_timedelta,end_timedelta",
|
|
[
|
|
(
|
|
{"in": {"days": 3}},
|
|
datetime.timedelta(days=3),
|
|
datetime.timedelta(days=4),
|
|
),
|
|
(
|
|
{"in": {"weeks": 1}},
|
|
datetime.timedelta(days=7),
|
|
datetime.timedelta(days=8),
|
|
),
|
|
],
|
|
ids=["in_days", "in_weeks"],
|
|
)
|
|
async def test_add_event_date_in_x(
|
|
hass: HomeAssistant,
|
|
component_setup: ComponentSetup,
|
|
mock_calendars_list: ApiResult,
|
|
test_api_calendar: dict[str, Any],
|
|
mock_insert_event: Mock,
|
|
date_fields: dict[str, Any],
|
|
start_timedelta: datetime.timedelta,
|
|
end_timedelta: datetime.timedelta,
|
|
setup_config_entry: MockConfigEntry,
|
|
) -> None:
|
|
"""Test service call that adds an event with various time ranges."""
|
|
|
|
assert await component_setup()
|
|
|
|
await hass.services.async_call(
|
|
DOMAIN,
|
|
SERVICE_ADD_EVENT,
|
|
{
|
|
"calendar_id": CALENDAR_ID,
|
|
"summary": "Summary",
|
|
"description": "Description",
|
|
**date_fields,
|
|
},
|
|
blocking=True,
|
|
)
|
|
mock_insert_event.assert_called()
|
|
|
|
now = datetime.datetime.now()
|
|
start_date = now + start_timedelta
|
|
end_date = now + end_timedelta
|
|
|
|
assert mock_insert_event.mock_calls[0] == call(
|
|
calendarId=CALENDAR_ID,
|
|
body={
|
|
"summary": "Summary",
|
|
"description": "Description",
|
|
"start": {"date": start_date.date().isoformat()},
|
|
"end": {"date": end_date.date().isoformat()},
|
|
},
|
|
)
|
|
|
|
|
|
async def test_add_event_date(
|
|
hass: HomeAssistant,
|
|
component_setup: ComponentSetup,
|
|
mock_calendars_list: ApiResult,
|
|
mock_insert_event: Mock,
|
|
setup_config_entry: MockConfigEntry,
|
|
) -> None:
|
|
"""Test service call that sets a date range."""
|
|
|
|
assert await component_setup()
|
|
|
|
now = utcnow()
|
|
today = now.date()
|
|
end_date = today + datetime.timedelta(days=2)
|
|
|
|
await hass.services.async_call(
|
|
DOMAIN,
|
|
SERVICE_ADD_EVENT,
|
|
{
|
|
"calendar_id": CALENDAR_ID,
|
|
"summary": "Summary",
|
|
"description": "Description",
|
|
"start_date": today.isoformat(),
|
|
"end_date": end_date.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": {"date": today.isoformat()},
|
|
"end": {"date": end_date.isoformat()},
|
|
},
|
|
)
|
|
|
|
|
|
async def test_add_event_date_time(
|
|
hass: HomeAssistant,
|
|
component_setup: ComponentSetup,
|
|
mock_calendars_list: ApiResult,
|
|
test_api_calendar: dict[str, Any],
|
|
mock_insert_event: Mock,
|
|
setup_config_entry: MockConfigEntry,
|
|
) -> 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": "America/Regina",
|
|
},
|
|
"end": {
|
|
"dateTime": end_datetime.isoformat(timespec="seconds"),
|
|
"timeZone": "America/Regina",
|
|
},
|
|
},
|
|
)
|
|
|
|
|
|
async def test_scan_calendars(
|
|
hass: HomeAssistant,
|
|
component_setup: ComponentSetup,
|
|
mock_calendars_list: ApiResult,
|
|
test_api_calendar: dict[str, Any],
|
|
setup_config_entry: MockConfigEntry,
|
|
) -> None:
|
|
"""Test finding a calendar from the API."""
|
|
|
|
assert await component_setup()
|
|
|
|
calendar_1 = {
|
|
"id": "calendar-id-1",
|
|
"summary": "Calendar 1",
|
|
}
|
|
calendar_2 = {
|
|
"id": "calendar-id-2",
|
|
"summary": "Calendar 2",
|
|
}
|
|
|
|
mock_calendars_list({"items": [calendar_1]})
|
|
await hass.services.async_call(DOMAIN, SERVICE_SCAN_CALENDARS, {}, blocking=True)
|
|
await hass.async_block_till_done()
|
|
|
|
state = hass.states.get("calendar.calendar_1")
|
|
assert state
|
|
assert state.name == "Calendar 1"
|
|
assert state.state == STATE_OFF
|
|
assert not hass.states.get("calendar.calendar_2")
|
|
|
|
mock_calendars_list({"items": [calendar_1, calendar_2]})
|
|
await hass.services.async_call(DOMAIN, SERVICE_SCAN_CALENDARS, {}, blocking=True)
|
|
await hass.async_block_till_done()
|
|
|
|
state = hass.states.get("calendar.calendar_1")
|
|
assert state
|
|
assert state.name == "Calendar 1"
|
|
assert state.state == STATE_OFF
|
|
state = hass.states.get("calendar.calendar_2")
|
|
assert state
|
|
assert state.name == "Calendar 2"
|
|
assert state.state == STATE_OFF
|