core/tests/components/google/test_init.py

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