Improve google calendar test coverage to 97% (#65223)
* Improve google calendar test coverage to 97% * Remove commented out code. * Remove unnecessary (flaky) checks for token file persistence * Remove mock code assertions * Add debug logging to google calendar integration * Increase alarm time to polling code to reduce flakes * Setup every test in their own configuration directory * Mock out filesystem calls to avoid disk dependencies Update scope checking code to use Storage object rather than text file matching * Update tests to check entity states when integration is loaded * Mock out google service in multiple locations * Update homeassistant/components/google/__init__.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/google/__init__.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> Co-authored-by: Martin Hjelmare <marhje52@gmail.com>pull/65352/head
parent
5289935ac1
commit
88ed2f3b3e
|
@ -407,7 +407,6 @@ omit =
|
|||
homeassistant/components/goodwe/number.py
|
||||
homeassistant/components/goodwe/select.py
|
||||
homeassistant/components/goodwe/sensor.py
|
||||
homeassistant/components/google/__init__.py
|
||||
homeassistant/components/google_cloud/tts.py
|
||||
homeassistant/components/google_maps/device_tracker.py
|
||||
homeassistant/components/google_pubsub/__init__.py
|
||||
|
|
|
@ -194,6 +194,7 @@ def do_authentication(hass, hass_config, config):
|
|||
|
||||
def step2_exchange(now):
|
||||
"""Keep trying to validate the user_code until it expires."""
|
||||
_LOGGER.debug("Attempting to validate user code")
|
||||
|
||||
# For some reason, oauth.step1_get_device_and_user_codes() returns a datetime
|
||||
# object without tzinfo. For the comparison below to work, it needs one.
|
||||
|
@ -208,6 +209,7 @@ def do_authentication(hass, hass_config, config):
|
|||
notification_id=NOTIFICATION_ID,
|
||||
)
|
||||
listener()
|
||||
return
|
||||
|
||||
try:
|
||||
credentials = oauth.step2_exchange(device_flow_info=dev_flow)
|
||||
|
@ -247,9 +249,11 @@ def setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
|||
|
||||
token_file = hass.config.path(TOKEN_FILE)
|
||||
if not os.path.isfile(token_file):
|
||||
_LOGGER.debug("Token file does not exist, authenticating for first time")
|
||||
do_authentication(hass, config, conf)
|
||||
else:
|
||||
if not check_correct_scopes(token_file, conf):
|
||||
if not check_correct_scopes(hass, token_file, conf):
|
||||
_LOGGER.debug("Existing scopes are not sufficient, re-authenticating")
|
||||
do_authentication(hass, config, conf)
|
||||
else:
|
||||
do_setup(hass, config, conf)
|
||||
|
@ -257,17 +261,13 @@ def setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
|||
return True
|
||||
|
||||
|
||||
def check_correct_scopes(token_file, config):
|
||||
def check_correct_scopes(hass, token_file, config):
|
||||
"""Check for the correct scopes in file."""
|
||||
with open(token_file, encoding="utf8") as tokenfile:
|
||||
contents = tokenfile.read()
|
||||
|
||||
# Check for quoted scope as our scopes can be subsets of other scopes
|
||||
target_scope = f'"{config.get(CONF_CALENDAR_ACCESS).scope}"'
|
||||
if target_scope not in contents:
|
||||
_LOGGER.warning("Please re-authenticate with Google")
|
||||
return False
|
||||
return True
|
||||
creds = Storage(token_file).get()
|
||||
if not creds or not creds.scopes:
|
||||
return False
|
||||
target_scope = config[CONF_CALENDAR_ACCESS].scope
|
||||
return target_scope in creds.scopes
|
||||
|
||||
|
||||
def setup_services(
|
||||
|
@ -364,6 +364,7 @@ def setup_services(
|
|||
|
||||
def do_setup(hass, hass_config, config):
|
||||
"""Run the setup after we have everything configured."""
|
||||
_LOGGER.debug("Setting up integration")
|
||||
# Load calendars the user has configured
|
||||
hass.data[DATA_INDEX] = load_config(hass.config.path(YAML_DEVICES))
|
||||
|
||||
|
|
|
@ -1,10 +1,20 @@
|
|||
"""Test configuration and mocks for the google integration."""
|
||||
from unittest.mock import patch
|
||||
from collections.abc import Callable
|
||||
from typing import Any, Generator, TypeVar
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.google import GoogleCalendarService
|
||||
|
||||
ApiResult = Callable[[dict[str, Any]], None]
|
||||
T = TypeVar("T")
|
||||
YieldFixture = Generator[T, None, None]
|
||||
|
||||
|
||||
CALENDAR_ID = "qwertyuiopasdfghjklzxcvbnm@import.calendar.google.com"
|
||||
TEST_CALENDAR = {
|
||||
"id": "qwertyuiopasdfghjklzxcvbnm@import.calendar.google.com",
|
||||
"id": CALENDAR_ID,
|
||||
"etag": '"3584134138943410"',
|
||||
"timeZone": "UTC",
|
||||
"accessRole": "reader",
|
||||
|
@ -34,3 +44,45 @@ def mock_next_event():
|
|||
)
|
||||
with patch_google_cal as google_cal_data:
|
||||
yield google_cal_data
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_events_list(
|
||||
google_service: GoogleCalendarService,
|
||||
) -> Callable[[dict[str, Any]], None]:
|
||||
"""Fixture to construct a fake event list API response."""
|
||||
|
||||
def _put_result(response: dict[str, Any]) -> None:
|
||||
google_service.return_value.get.return_value.events.return_value.list.return_value.execute.return_value = (
|
||||
response
|
||||
)
|
||||
return
|
||||
|
||||
return _put_result
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_calendars_list(
|
||||
google_service: GoogleCalendarService,
|
||||
) -> ApiResult:
|
||||
"""Fixture to construct a fake calendar list API response."""
|
||||
|
||||
def _put_result(response: dict[str, Any]) -> None:
|
||||
google_service.return_value.get.return_value.calendarList.return_value.list.return_value.execute.return_value = (
|
||||
response
|
||||
)
|
||||
return
|
||||
|
||||
return _put_result
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_insert_event(
|
||||
google_service: GoogleCalendarService,
|
||||
) -> Mock:
|
||||
"""Fixture to create a mock to capture new events added to the API."""
|
||||
insert_mock = Mock()
|
||||
google_service.return_value.get.return_value.events.return_value.insert = (
|
||||
insert_mock
|
||||
)
|
||||
return insert_mock
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
import copy
|
||||
from http import HTTPStatus
|
||||
from typing import Any
|
||||
|
@ -22,7 +21,6 @@ from homeassistant.components.google import (
|
|||
CONF_TRACK,
|
||||
DEVICE_SCHEMA,
|
||||
SERVICE_SCAN_CALENDARS,
|
||||
GoogleCalendarService,
|
||||
do_setup,
|
||||
)
|
||||
from homeassistant.const import STATE_OFF, STATE_ON
|
||||
|
@ -358,21 +356,6 @@ async def test_http_event_api_failure(hass, hass_client, google_service):
|
|||
assert events == []
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_events_list(
|
||||
google_service: GoogleCalendarService,
|
||||
) -> Callable[[dict[str, Any]], None]:
|
||||
"""Fixture to construct a fake event list API response."""
|
||||
|
||||
def _put_result(response: dict[str, Any]) -> None:
|
||||
google_service.return_value.get.return_value.events.return_value.list.return_value.execute.return_value = (
|
||||
response
|
||||
)
|
||||
return
|
||||
|
||||
return _put_result
|
||||
|
||||
|
||||
async def test_http_api_event(hass, hass_client, google_service, mock_events_list):
|
||||
"""Test querying the API and fetching events from the server."""
|
||||
now = dt_util.now()
|
||||
|
|
|
@ -1,67 +1,497 @@
|
|||
"""The tests for the Google Calendar component."""
|
||||
from unittest.mock import patch
|
||||
from collections.abc import Awaitable, Callable
|
||||
import datetime
|
||||
from typing import Any
|
||||
from unittest.mock import Mock, call, mock_open, patch
|
||||
|
||||
from oauth2client.client import (
|
||||
FlowExchangeError,
|
||||
OAuth2Credentials,
|
||||
OAuth2DeviceCodeError,
|
||||
)
|
||||
import pytest
|
||||
import yaml
|
||||
|
||||
import homeassistant.components.google as google
|
||||
from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET
|
||||
from homeassistant.components.google import (
|
||||
DOMAIN,
|
||||
SERVICE_ADD_EVENT,
|
||||
GoogleCalendarService,
|
||||
)
|
||||
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.dt import utcnow
|
||||
|
||||
from .conftest import CALENDAR_ID, ApiResult, 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
|
||||
CODE_CHECK_ALARM_TIMEDELTA = datetime.timedelta(seconds=CODE_CHECK_INTERVAL * 2)
|
||||
|
||||
|
||||
@pytest.fixture(name="google_setup")
|
||||
def mock_google_setup(hass):
|
||||
"""Mock the google set up functions."""
|
||||
p_auth = patch(
|
||||
"homeassistant.components.google.do_authentication", side_effect=google.do_setup
|
||||
@pytest.fixture
|
||||
async def code_expiration_delta() -> datetime.timedelta:
|
||||
"""Fixture for code expiration time, defaulting to the future."""
|
||||
return datetime.timedelta(minutes=3)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def mock_code_flow(
|
||||
code_expiration_delta: datetime.timedelta,
|
||||
) -> YieldFixture[Mock]:
|
||||
"""Fixture for initiating OAuth flow."""
|
||||
with patch(
|
||||
"oauth2client.client.OAuth2WebServerFlow.step1_get_device_and_user_codes",
|
||||
) as mock_flow:
|
||||
mock_flow.return_value.user_code_expiry = utcnow() + code_expiration_delta
|
||||
mock_flow.return_value.interval = CODE_CHECK_INTERVAL
|
||||
yield mock_flow
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def token_scopes() -> list[str]:
|
||||
"""Fixture for scopes used during test."""
|
||||
return ["https://www.googleapis.com/auth/calendar"]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def creds(token_scopes: list[str]) -> OAuth2Credentials:
|
||||
"""Fixture that defines creds used in the test."""
|
||||
token_expiry = utcnow() + datetime.timedelta(days=7)
|
||||
return OAuth2Credentials(
|
||||
access_token="ACCESS_TOKEN",
|
||||
client_id="client-id",
|
||||
client_secret="client-secret",
|
||||
refresh_token="REFRESH_TOKEN",
|
||||
token_expiry=token_expiry,
|
||||
token_uri="http://example.com",
|
||||
user_agent="n/a",
|
||||
scopes=token_scopes,
|
||||
)
|
||||
p_service = patch("homeassistant.components.google.GoogleCalendarService.get")
|
||||
p_discovery = patch("homeassistant.components.google.discovery.load_platform")
|
||||
p_load = patch("homeassistant.components.google.load_config", return_value={})
|
||||
p_save = patch("homeassistant.components.google.update_config")
|
||||
|
||||
with p_auth, p_load, p_service, p_discovery, p_save:
|
||||
|
||||
@pytest.fixture
|
||||
async def mock_exchange(creds: OAuth2Credentials) -> YieldFixture[Mock]:
|
||||
"""Fixture for mocking out the exchange for credentials."""
|
||||
with patch(
|
||||
"oauth2client.client.OAuth2WebServerFlow.step2_exchange", return_value=creds
|
||||
) as mock:
|
||||
yield mock
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
async def mock_token_write(hass: HomeAssistant) -> None:
|
||||
"""Fixture to avoid writing token files to disk."""
|
||||
with patch(
|
||||
"homeassistant.components.google.os.path.isfile", return_value=True
|
||||
), patch("homeassistant.components.google.Storage.put"):
|
||||
yield
|
||||
|
||||
|
||||
async def test_setup_component(hass, google_setup):
|
||||
"""Test setup component."""
|
||||
config = {"google": {CONF_CLIENT_ID: "id", CONF_CLIENT_SECRET: "secret"}}
|
||||
|
||||
assert await async_setup_component(hass, "google", config)
|
||||
@pytest.fixture
|
||||
async def mock_token_read(
|
||||
hass: HomeAssistant,
|
||||
creds: OAuth2Credentials,
|
||||
) -> None:
|
||||
"""Fixture to populate an existing token file."""
|
||||
with patch("homeassistant.components.google.Storage.get", return_value=creds):
|
||||
yield
|
||||
|
||||
|
||||
async def test_get_calendar_info(hass, test_calendar):
|
||||
"""Test getting the calendar info."""
|
||||
calendar_info = await hass.async_add_executor_job(
|
||||
google.get_calendar_info, hass, test_calendar
|
||||
)
|
||||
assert calendar_info == {
|
||||
"cal_id": "qwertyuiopasdfghjklzxcvbnm@import.calendar.google.com",
|
||||
"entities": [
|
||||
{
|
||||
"device_id": "we_are_we_are_a_test_calendar",
|
||||
"name": "We are, we are, a... Test Calendar",
|
||||
"track": True,
|
||||
"ignore_availability": True,
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
async def test_found_calendar(hass, google_setup, mock_next_event, test_calendar):
|
||||
"""Test when a calendar is found."""
|
||||
config = {
|
||||
"google": {
|
||||
CONF_CLIENT_ID: "id",
|
||||
CONF_CLIENT_SECRET: "secret",
|
||||
"track_new_calendar": True,
|
||||
@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,
|
||||
}
|
||||
],
|
||||
}
|
||||
}
|
||||
assert await async_setup_component(hass, "google", config)
|
||||
assert hass.data[google.DATA_INDEX] == {}
|
||||
]
|
||||
|
||||
|
||||
@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."""
|
||||
with patch("homeassistant.components.persistent_notification.create") as 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
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def google_service() -> YieldFixture[GoogleCalendarService]:
|
||||
"""Fixture to capture service calls."""
|
||||
with patch("homeassistant.components.google.GoogleCalendarService") as mock, patch(
|
||||
"homeassistant.components.google.calendar.GoogleCalendarService", mock
|
||||
):
|
||||
yield mock
|
||||
|
||||
|
||||
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):
|
||||
async_fire_time_changed(hass, point_in_time)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("config", [{}])
|
||||
async def test_setup_config_empty(
|
||||
hass: HomeAssistant,
|
||||
component_setup: ComponentSetup,
|
||||
mock_notification: Mock,
|
||||
):
|
||||
"""Test setup component with an empty configuruation."""
|
||||
assert await component_setup()
|
||||
|
||||
mock_notification.assert_not_called()
|
||||
|
||||
assert not hass.states.get("calendar.backyard_light")
|
||||
|
||||
|
||||
async def test_init_success(
|
||||
hass: HomeAssistant,
|
||||
google_service: GoogleCalendarService,
|
||||
mock_code_flow: Mock,
|
||||
mock_exchange: Mock,
|
||||
mock_notification: Mock,
|
||||
mock_calendars_yaml: None,
|
||||
component_setup: ComponentSetup,
|
||||
) -> None:
|
||||
"""Test successful creds setup."""
|
||||
assert await component_setup()
|
||||
|
||||
# Run one tick to invoke the credential exchange check
|
||||
now = utcnow()
|
||||
await fire_alarm(hass, now + CODE_CHECK_ALARM_TIMEDELTA)
|
||||
|
||||
state = hass.states.get("calendar.backyard_light")
|
||||
assert state
|
||||
assert state.name == "Backyard Light"
|
||||
assert state.state == STATE_OFF
|
||||
|
||||
mock_notification.assert_called()
|
||||
assert "We are all setup now" in mock_notification.call_args[0][1]
|
||||
|
||||
|
||||
async def test_code_error(
|
||||
hass: HomeAssistant,
|
||||
mock_code_flow: Mock,
|
||||
component_setup: ComponentSetup,
|
||||
mock_notification: Mock,
|
||||
) -> None:
|
||||
"""Test loading the integration with no existing credentials."""
|
||||
|
||||
with patch(
|
||||
"oauth2client.client.OAuth2WebServerFlow.step1_get_device_and_user_codes",
|
||||
side_effect=OAuth2DeviceCodeError("Test Failure"),
|
||||
):
|
||||
assert await component_setup()
|
||||
|
||||
assert not hass.states.get("calendar.backyard_light")
|
||||
|
||||
mock_notification.assert_called()
|
||||
assert "Error: Test Failure" in mock_notification.call_args[0][1]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("code_expiration_delta", [datetime.timedelta(minutes=-5)])
|
||||
async def test_expired_after_exchange(
|
||||
hass: HomeAssistant,
|
||||
mock_code_flow: Mock,
|
||||
component_setup: ComponentSetup,
|
||||
mock_notification: Mock,
|
||||
) -> None:
|
||||
"""Test loading the integration with no existing credentials."""
|
||||
|
||||
assert await component_setup()
|
||||
|
||||
now = utcnow()
|
||||
await fire_alarm(hass, now + CODE_CHECK_ALARM_TIMEDELTA)
|
||||
|
||||
assert not hass.states.get("calendar.backyard_light")
|
||||
|
||||
mock_notification.assert_called()
|
||||
assert (
|
||||
"Authentication code expired, please restart Home-Assistant and try again"
|
||||
in mock_notification.call_args[0][1]
|
||||
)
|
||||
|
||||
|
||||
async def test_exchange_error(
|
||||
hass: HomeAssistant,
|
||||
mock_code_flow: Mock,
|
||||
component_setup: ComponentSetup,
|
||||
mock_notification: Mock,
|
||||
) -> None:
|
||||
"""Test an error while exchanging the code for credentials."""
|
||||
|
||||
with patch(
|
||||
"oauth2client.client.OAuth2WebServerFlow.step2_exchange",
|
||||
side_effect=FlowExchangeError(),
|
||||
):
|
||||
assert await component_setup()
|
||||
|
||||
now = utcnow()
|
||||
await fire_alarm(hass, now + CODE_CHECK_ALARM_TIMEDELTA)
|
||||
|
||||
assert not hass.states.get("calendar.backyard_light")
|
||||
|
||||
mock_notification.assert_called()
|
||||
assert "In order to authorize Home-Assistant" in mock_notification.call_args[0][1]
|
||||
|
||||
|
||||
async def test_existing_token(
|
||||
hass: HomeAssistant,
|
||||
mock_token_read: None,
|
||||
component_setup: ComponentSetup,
|
||||
google_service: GoogleCalendarService,
|
||||
mock_calendars_yaml: None,
|
||||
mock_notification: Mock,
|
||||
) -> None:
|
||||
"""Test setup with an existing token file."""
|
||||
assert await component_setup()
|
||||
|
||||
state = hass.states.get("calendar.backyard_light")
|
||||
assert state
|
||||
assert state.name == "Backyard Light"
|
||||
assert state.state == STATE_OFF
|
||||
|
||||
mock_notification.assert_not_called()
|
||||
|
||||
|
||||
@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],
|
||||
mock_token_read: None,
|
||||
component_setup: ComponentSetup,
|
||||
google_service: GoogleCalendarService,
|
||||
mock_calendars_yaml: None,
|
||||
mock_notification: Mock,
|
||||
mock_code_flow: Mock,
|
||||
mock_exchange: Mock,
|
||||
) -> None:
|
||||
"""Test setup where existing token does not have sufficient scopes."""
|
||||
assert await component_setup()
|
||||
|
||||
# Run one tick to invoke the credential exchange check
|
||||
now = utcnow()
|
||||
await fire_alarm(hass, now + CODE_CHECK_ALARM_TIMEDELTA)
|
||||
assert len(mock_exchange.mock_calls) == 1
|
||||
|
||||
state = hass.states.get("calendar.backyard_light")
|
||||
assert state
|
||||
assert state.name == "Backyard Light"
|
||||
assert state.state == STATE_OFF
|
||||
|
||||
# No notifications on success
|
||||
mock_notification.assert_called()
|
||||
assert "We are all setup now" in mock_notification.call_args[0][1]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("calendars_config", [[{"cal_id": "invalid-schema"}]])
|
||||
async def test_calendar_yaml_missing_required_fields(
|
||||
hass: HomeAssistant,
|
||||
mock_token_read: None,
|
||||
component_setup: ComponentSetup,
|
||||
google_service: GoogleCalendarService,
|
||||
calendars_config: list[dict[str, Any]],
|
||||
mock_calendars_yaml: None,
|
||||
mock_notification: Mock,
|
||||
) -> None:
|
||||
"""Test setup with a missing schema fields, ignores the error and continues."""
|
||||
assert await component_setup()
|
||||
|
||||
assert not hass.states.get("calendar.backyard_light")
|
||||
|
||||
mock_notification.assert_not_called()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("calendars_config", [[{"missing-cal_id": "invalid-schema"}]])
|
||||
async def test_invalid_calendar_yaml(
|
||||
hass: HomeAssistant,
|
||||
mock_token_read: None,
|
||||
component_setup: ComponentSetup,
|
||||
google_service: GoogleCalendarService,
|
||||
calendars_config: list[dict[str, Any]],
|
||||
mock_calendars_yaml: None,
|
||||
mock_notification: Mock,
|
||||
) -> None:
|
||||
"""Test setup with missing entity id fields fails to setup the integration."""
|
||||
|
||||
# Integration fails to setup
|
||||
assert not await component_setup()
|
||||
|
||||
assert not hass.states.get("calendar.backyard_light")
|
||||
|
||||
mock_notification.assert_not_called()
|
||||
|
||||
|
||||
async def test_found_calendar_from_api(
|
||||
hass: HomeAssistant,
|
||||
mock_token_read: None,
|
||||
component_setup: ComponentSetup,
|
||||
google_service: GoogleCalendarService,
|
||||
mock_calendars_list: ApiResult,
|
||||
test_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):
|
||||
assert await component_setup()
|
||||
|
||||
state = hass.states.get("calendar.we_are_we_are_a_test_calendar")
|
||||
assert state
|
||||
assert state.name == "We are, we are, a... Test Calendar"
|
||||
assert state.state == STATE_OFF
|
||||
|
||||
|
||||
async def test_add_event(
|
||||
hass: HomeAssistant,
|
||||
mock_token_read: None,
|
||||
component_setup: ComponentSetup,
|
||||
google_service: GoogleCalendarService,
|
||||
mock_calendars_list: ApiResult,
|
||||
test_calendar: dict[str, Any],
|
||||
mock_insert_event: Mock,
|
||||
) -> None:
|
||||
"""Test service call that adds an event."""
|
||||
|
||||
assert await component_setup()
|
||||
|
||||
await hass.services.async_call(
|
||||
"google", google.SERVICE_FOUND_CALENDARS, test_calendar, blocking=True
|
||||
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": {},
|
||||
},
|
||||
)
|
||||
|
||||
assert hass.data[google.DATA_INDEX].get(test_calendar["id"]) is not None
|
||||
|
||||
@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),
|
||||
),
|
||||
(
|
||||
{
|
||||
"start_date": datetime.date.today().isoformat(),
|
||||
"end_date": (
|
||||
datetime.date.today() + datetime.timedelta(days=2)
|
||||
).isoformat(),
|
||||
},
|
||||
datetime.timedelta(days=0),
|
||||
datetime.timedelta(days=2),
|
||||
),
|
||||
],
|
||||
ids=["in_days", "in_weeks", "explit_date"],
|
||||
)
|
||||
async def test_add_event_date_ranges(
|
||||
hass: HomeAssistant,
|
||||
mock_token_read: None,
|
||||
calendars_config: list[dict[str, Any]],
|
||||
component_setup: ComponentSetup,
|
||||
google_service: GoogleCalendarService,
|
||||
mock_calendars_list: ApiResult,
|
||||
test_calendar: dict[str, Any],
|
||||
mock_insert_event: Mock,
|
||||
date_fields: dict[str, Any],
|
||||
start_timedelta: datetime.timedelta,
|
||||
end_timedelta: datetime.timedelta,
|
||||
) -> 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()},
|
||||
},
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue