1
0
mirror of https://github.com/home-assistant/core.git synced 2026-05-15 13:01:22 +01:00
Files
core/tests/components/google/test_init.py
T

923 lines
28 KiB
Python

"""The tests for the Google Calendar component."""
from collections.abc import Awaitable, Callable
import datetime
import http
import time
from typing import Any
from unittest.mock import Mock, patch
import zoneinfo
from aiohttp.client_exceptions import ClientError
import pytest
import voluptuous as vol
from homeassistant.components.google import DOMAIN
from homeassistant.components.google.calendar import SERVICE_CREATE_EVENT
from homeassistant.components.google.const import CONF_CALENDAR_ACCESS
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import ATTR_FRIENDLY_NAME, STATE_OFF
from homeassistant.core import HomeAssistant, State
from homeassistant.exceptions import HomeAssistantError, ServiceNotSupported
from homeassistant.helpers.config_entry_oauth2_flow import (
ImplementationUnavailableError,
)
from homeassistant.setup import async_setup_component
from homeassistant.util.dt import UTC, utcnow
from .conftest import (
CALENDAR_ID,
EMAIL_ADDRESS,
TEST_API_ENTITY,
TEST_API_ENTITY_NAME,
TEST_YAML_ENTITY,
ApiResult,
ComponentSetup,
)
from tests.common import MockConfigEntry
from tests.test_util.aiohttp import AiohttpClientMocker
EXPIRED_TOKEN_TIMESTAMP = datetime.datetime(2022, 4, 8).timestamp()
# Typing helpers
type HassApi = Callable[[], Awaitable[dict[str, Any]]]
TEST_EVENT_SUMMARY = "Test Summary"
TEST_EVENT_DESCRIPTION = "Test Description"
TEST_EVENT_LOCATION = "Test Location"
def assert_state(actual: State | None, expected: State | None) -> None:
"""Assert that the two states are equal."""
if actual is None or expected 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(
params=[
(
DOMAIN,
SERVICE_CREATE_EVENT,
{},
{"entity_id": TEST_API_ENTITY},
),
(
"calendar",
SERVICE_CREATE_EVENT,
{},
{"entity_id": TEST_API_ENTITY},
),
],
ids=("google.create_event", "calendar.create_event"),
)
def add_event_call_service(
hass: HomeAssistant,
request: pytest.FixtureRequest,
) -> Callable[[dict[str, Any]], Awaitable[None]]:
"""Fixture for calling the add or create event service."""
(domain, service_call, data, target) = request.param
async def call_service(params: dict[str, Any]) -> None:
await hass.services.async_call(
domain,
service_call,
{
**data,
**params,
"summary": TEST_EVENT_SUMMARY,
"description": TEST_EVENT_DESCRIPTION,
},
target=target,
blocking=True,
)
return call_service
async def test_unload_entry(
hass: HomeAssistant,
component_setup: ComponentSetup,
) -> 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 is 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."""
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("config_entry_options", [{CONF_CALENDAR_ACCESS: "read_only"}])
async def test_config_entry_scope_reauth(
hass: HomeAssistant,
token_scopes: list[str],
component_setup: ComponentSetup,
config_entry: MockConfigEntry,
) -> None:
"""Test setup where the config entry options requires reauth to match the scope."""
await component_setup()
assert config_entry.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,
config_entry: MockConfigEntry,
) -> None:
"""Test setup with a missing schema fields, ignores the error and continues."""
assert not await component_setup()
assert config_entry.state is ConfigEntryState.SETUP_ERROR
@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,
config_entry: MockConfigEntry,
) -> None:
"""Test setup with missing entity id fields fails to load the platform."""
assert not await component_setup()
assert config_entry.state is ConfigEntryState.SETUP_ERROR
async def test_calendar_yaml_error(
hass: HomeAssistant,
component_setup: ComponentSetup,
mock_calendars_list: ApiResult,
test_api_calendar: dict[str, Any],
mock_events_list: ApiResult,
) -> None:
"""Test setup with yaml file not found."""
mock_calendars_list({"items": [test_api_calendar]})
mock_events_list({})
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("calendars_config", [None])
async def test_empty_calendar_yaml(
hass: HomeAssistant,
component_setup: ComponentSetup,
calendars_config: list[dict[str, Any]],
mock_calendars_yaml: None,
mock_calendars_list: ApiResult,
test_api_calendar: dict[str, Any],
mock_events_list: ApiResult,
) -> None:
"""Test an empty yaml file is equivalent to a missing yaml file."""
mock_calendars_list({"items": [test_api_calendar]})
mock_events_list({})
assert await component_setup()
assert not hass.states.get(TEST_YAML_ENTITY)
assert hass.states.get(TEST_API_ENTITY)
async def test_init_calendar(
hass: HomeAssistant,
component_setup: ComponentSetup,
mock_calendars_list: ApiResult,
test_api_calendar: dict[str, Any],
mock_events_list: ApiResult,
) -> None:
"""Test finding a calendar from the API."""
mock_calendars_list({"items": [test_api_calendar]})
mock_events_list({})
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)
async def test_multiple_config_entries(
hass: HomeAssistant,
component_setup: ComponentSetup,
config_entry: MockConfigEntry,
mock_calendars_list: ApiResult,
test_api_calendar: dict[str, Any],
mock_events_list: ApiResult,
aioclient_mock: AiohttpClientMocker,
) -> None:
"""Test finding a calendar from the API."""
mock_calendars_list({"items": [test_api_calendar]})
mock_events_list({})
assert await component_setup()
state = hass.states.get(TEST_API_ENTITY)
assert state
assert state.state == STATE_OFF
assert state.attributes.get(ATTR_FRIENDLY_NAME) == TEST_API_ENTITY_NAME
config_entry2 = MockConfigEntry(
domain=DOMAIN, data=config_entry.data, unique_id="other-address@example.com"
)
calendar2 = {
**test_api_calendar,
"id": "calendar-id2",
"summary": "Example Calendar 2",
}
aioclient_mock.clear_requests()
mock_calendars_list({"items": [calendar2]})
mock_events_list({}, calendar_id="calendar-id2")
config_entry2.add_to_hass(hass)
await hass.config_entries.async_setup(config_entry2.entry_id)
await hass.async_block_till_done()
state = hass.states.get("calendar.example_calendar_2")
assert state
assert state.attributes.get(ATTR_FRIENDLY_NAME) == "Example calendar 2"
@pytest.mark.parametrize(
("date_fields", "expected_error", "error_match"),
[
(
{},
vol.error.MultipleInvalid,
"must contain at least one of start_date, start_date_time, in",
),
(
{
"start_date": "2022-04-01",
},
vol.error.MultipleInvalid,
"Start and end dates must both be specified",
),
(
{
"end_date": "2022-04-02",
},
vol.error.MultipleInvalid,
"must contain at least one of start_date, start_date_time, in.",
),
(
{
"start_date_time": "2022-04-01T06:00:00",
},
vol.error.MultipleInvalid,
"Start and end datetimes must both be specified",
),
(
{
"end_date_time": "2022-04-02T07:00:00",
},
vol.error.MultipleInvalid,
"must contain at least one of start_date, start_date_time, in.",
),
(
{
"start_date": "2022-04-01",
"start_date_time": "2022-04-01T06:00:00",
"end_date_time": "2022-04-02T07:00:00",
},
vol.error.MultipleInvalid,
"must contain at most one of start_date, start_date_time, in.",
),
(
{
"start_date_time": "2022-04-01T06:00:00",
"end_date_time": "2022-04-01T07:00:00",
"end_date": "2022-04-02",
},
vol.error.MultipleInvalid,
"Start and end dates must both be specified",
),
(
{
"start_date": "2022-04-01",
"end_date_time": "2022-04-02T07:00:00",
},
vol.error.MultipleInvalid,
"Start and end dates must both be specified",
),
(
{
"start_date_time": "2022-04-01T07:00:00",
"end_date": "2022-04-02",
},
vol.error.MultipleInvalid,
"Start and end dates must both be specified",
),
(
{
"in": {
"days": 2,
"weeks": 2,
}
},
vol.error.MultipleInvalid,
"two or more values in the same group of exclusion 'event_types'",
),
(
{
"start_date": "2022-04-01",
"end_date": "2022-04-02",
"in": {
"days": 2,
},
},
vol.error.MultipleInvalid,
"must contain at most one of start_date, start_date_time, in.",
),
(
{
"start_date_time": "2022-04-01T07:00:00",
"end_date_time": "2022-04-01T07:00:00",
"in": {
"days": 2,
},
},
vol.error.MultipleInvalid,
"must contain at most one of start_date, start_date_time, in.",
),
],
ids=[
"missing_all",
"missing_end_date",
"missing_start_date",
"missing_end_datetime",
"missing_start_datetime",
"multiple_start",
"multiple_end",
"missing_end_date",
"missing_end_date_time",
"multiple_in",
"unexpected_in_with_date",
"unexpected_in_with_datetime",
],
)
async def test_add_event_invalid_params(
hass: HomeAssistant,
component_setup: ComponentSetup,
mock_calendars_list: ApiResult,
test_api_calendar: dict[str, Any],
mock_events_list: ApiResult,
add_event_call_service: Callable[[dict[str, Any]], Awaitable[None]],
date_fields: dict[str, Any],
expected_error: type[Exception],
error_match: str | None,
) -> None:
"""Test service calls with incorrect fields."""
mock_calendars_list({"items": [test_api_calendar]})
mock_events_list({})
assert await component_setup()
with pytest.raises(expected_error, match=error_match):
await add_event_call_service(date_fields)
@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,
mock_insert_event: Callable[..., None],
test_api_calendar: dict[str, Any],
mock_events_list: ApiResult,
date_fields: dict[str, Any],
start_timedelta: datetime.timedelta,
end_timedelta: datetime.timedelta,
aioclient_mock: AiohttpClientMocker,
add_event_call_service: Callable[[dict[str, Any]], Awaitable[None]],
) -> None:
"""Test service call that adds an event with various time ranges."""
mock_calendars_list({"items": [test_api_calendar]})
mock_events_list({})
assert await component_setup()
now = datetime.datetime.now()
start_date = now + start_timedelta
end_date = now + end_timedelta
aioclient_mock.clear_requests()
mock_insert_event(
calendar_id=CALENDAR_ID,
)
await add_event_call_service(date_fields)
assert len(aioclient_mock.mock_calls) == 1
assert aioclient_mock.mock_calls[0][2] == {
"summary": TEST_EVENT_SUMMARY,
"description": TEST_EVENT_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,
test_api_calendar: dict[str, Any],
mock_insert_event: Callable[..., None],
mock_events_list: ApiResult,
aioclient_mock: AiohttpClientMocker,
add_event_call_service: Callable[[dict[str, Any]], Awaitable[None]],
) -> None:
"""Test service call that sets a date range."""
mock_calendars_list({"items": [test_api_calendar]})
mock_events_list({})
assert await component_setup()
now = utcnow()
today = now.date()
end_date = today + datetime.timedelta(days=2)
aioclient_mock.clear_requests()
mock_insert_event(
calendar_id=CALENDAR_ID,
)
await add_event_call_service(
{
"start_date": today.isoformat(),
"end_date": end_date.isoformat(),
},
)
assert len(aioclient_mock.mock_calls) == 1
assert aioclient_mock.mock_calls[0][2] == {
"summary": TEST_EVENT_SUMMARY,
"description": TEST_EVENT_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,
mock_insert_event: Callable[..., None],
test_api_calendar: dict[str, Any],
mock_events_list: ApiResult,
aioclient_mock: AiohttpClientMocker,
add_event_call_service: Callable[[dict[str, Any]], Awaitable[None]],
) -> None:
"""Test service call that adds an event with a date time range."""
mock_calendars_list({"items": [test_api_calendar]})
mock_events_list({})
assert await component_setup()
start_datetime = datetime.datetime.now(tz=zoneinfo.ZoneInfo("America/Regina"))
delta = datetime.timedelta(days=3, hours=3)
end_datetime = start_datetime + delta
aioclient_mock.clear_requests()
mock_insert_event(
calendar_id=CALENDAR_ID,
)
await add_event_call_service(
{
"start_date_time": start_datetime.isoformat(),
"end_date_time": end_datetime.isoformat(),
},
)
assert len(aioclient_mock.mock_calls) == 1
assert aioclient_mock.mock_calls[0][2] == {
"summary": TEST_EVENT_SUMMARY,
"description": TEST_EVENT_DESCRIPTION,
"start": {
"dateTime": start_datetime.isoformat(timespec="seconds"),
"timeZone": "America/Regina",
},
"end": {
"dateTime": end_datetime.isoformat(timespec="seconds"),
"timeZone": "America/Regina",
},
}
@pytest.mark.parametrize(
"calendars_config",
[
[
{
"cal_id": CALENDAR_ID,
"entities": [
{
"device_id": "backyard_light",
"name": "Backyard Light",
"search": "#Backyard",
},
],
}
],
],
)
async def test_unsupported_create_event(
hass: HomeAssistant,
mock_calendars_yaml: Mock,
component_setup: ComponentSetup,
mock_calendars_list: ApiResult,
mock_insert_event: Callable[..., None],
test_api_calendar: dict[str, Any],
mock_events_list: ApiResult,
aioclient_mock: AiohttpClientMocker,
) -> None:
"""Test create event service call is unsupported for virtual calendars."""
await async_setup_component(hass, "homeassistant", {})
mock_calendars_list({"items": [test_api_calendar]})
mock_events_list({})
assert await component_setup()
start_datetime = datetime.datetime.now(tz=zoneinfo.ZoneInfo("America/Regina"))
delta = datetime.timedelta(days=3, hours=3)
end_datetime = start_datetime + delta
entity_id = "calendar.backyard_light"
with pytest.raises(
ServiceNotSupported,
match=f"Entity {entity_id} does not support action google.create_event",
):
await hass.services.async_call(
DOMAIN,
"create_event",
{
# **data,
"start_date_time": start_datetime.isoformat(),
"end_date_time": end_datetime.isoformat(),
"summary": TEST_EVENT_SUMMARY,
"description": TEST_EVENT_DESCRIPTION,
},
target={"entity_id": entity_id},
blocking=True,
)
async def test_add_event_failure(
hass: HomeAssistant,
component_setup: ComponentSetup,
mock_calendars_list: ApiResult,
test_api_calendar: dict[str, Any],
mock_events_list: ApiResult,
mock_insert_event: Callable[..., None],
add_event_call_service: Callable[[dict[str, Any]], Awaitable[None]],
) -> None:
"""Test service calls with incorrect fields."""
mock_calendars_list({"items": [test_api_calendar]})
mock_events_list({})
assert await component_setup()
mock_insert_event(
calendar_id=CALENDAR_ID,
exc=ClientError(),
)
with pytest.raises(HomeAssistantError):
await add_event_call_service(
{"start_date": "2022-05-01", "end_date": "2022-05-02"}
)
async def test_add_event_location(
hass: HomeAssistant,
component_setup: ComponentSetup,
mock_calendars_list: ApiResult,
test_api_calendar: dict[str, Any],
mock_insert_event: Callable[..., None],
mock_events_list: ApiResult,
aioclient_mock: AiohttpClientMocker,
add_event_call_service: Callable[[dict[str, Any]], Awaitable[None]],
) -> None:
"""Test service call that sets a location field."""
mock_calendars_list({"items": [test_api_calendar]})
mock_events_list({})
assert await component_setup()
now = utcnow()
today = now.date()
end_date = today + datetime.timedelta(days=2)
aioclient_mock.clear_requests()
mock_insert_event(
calendar_id=CALENDAR_ID,
)
await add_event_call_service(
{
"start_date": today.isoformat(),
"end_date": end_date.isoformat(),
"location": TEST_EVENT_LOCATION,
},
)
assert len(aioclient_mock.mock_calls) == 1
assert aioclient_mock.mock_calls[0][2] == {
"summary": TEST_EVENT_SUMMARY,
"description": TEST_EVENT_DESCRIPTION,
"location": TEST_EVENT_LOCATION,
"start": {"date": today.isoformat()},
"end": {"date": end_date.isoformat()},
}
@pytest.mark.parametrize(
"config_entry_token_expiry",
[
(datetime.datetime.max.replace(tzinfo=UTC).timestamp() + 1),
(utcnow().replace(tzinfo=None).timestamp()),
],
ids=["max_timestamp", "timestamp_naive"],
)
async def test_invalid_token_expiry_in_config_entry(
hass: HomeAssistant,
component_setup: ComponentSetup,
aioclient_mock: AiohttpClientMocker,
) -> None:
"""Exercise case in issue #69623 with invalid token expiration persisted."""
# The token is refreshed and new expiration values are returned
expires_in = 86400
expires_at = time.time() + expires_in
aioclient_mock.post(
"https://oauth2.googleapis.com/token",
json={
"refresh_token": "some-refresh-token",
"access_token": "some-updated-token",
"expires_at": expires_at,
"expires_in": expires_in,
},
)
assert await component_setup()
# Verify token expiration values are updated
entries = hass.config_entries.async_entries(DOMAIN)
assert len(entries) == 1
assert entries[0].state is ConfigEntryState.LOADED
assert entries[0].data["token"]["access_token"] == "some-updated-token"
assert entries[0].data["token"]["expires_in"] == expires_in
@pytest.mark.parametrize("config_entry_token_expiry", [EXPIRED_TOKEN_TIMESTAMP])
async def test_expired_token_refresh_internal_error(
hass: HomeAssistant,
component_setup: ComponentSetup,
aioclient_mock: AiohttpClientMocker,
) -> None:
"""Generic errors on reauth are treated as a retryable setup error."""
aioclient_mock.post(
"https://oauth2.googleapis.com/token",
status=http.HTTPStatus.INTERNAL_SERVER_ERROR,
)
await component_setup()
entries = hass.config_entries.async_entries(DOMAIN)
assert len(entries) == 1
assert entries[0].state is ConfigEntryState.SETUP_RETRY
@pytest.mark.parametrize(
"config_entry_token_expiry",
[EXPIRED_TOKEN_TIMESTAMP],
)
async def test_expired_token_requires_reauth(
hass: HomeAssistant,
component_setup: ComponentSetup,
aioclient_mock: AiohttpClientMocker,
) -> None:
"""Test case where reauth is required for token that cannot be refreshed."""
aioclient_mock.post(
"https://oauth2.googleapis.com/token",
status=http.HTTPStatus.BAD_REQUEST,
)
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", "expect_write_calls"),
[
(
[
{
"cal_id": "ignored",
"entities": {"device_id": "existing", "name": "existing"},
}
],
True,
),
([], False),
],
ids=["has_yaml", "no_yaml"],
)
async def test_calendar_yaml_update(
hass: HomeAssistant,
component_setup: ComponentSetup,
mock_calendars_yaml: Mock,
mock_calendars_list: ApiResult,
test_api_calendar: dict[str, Any],
mock_events_list: ApiResult,
calendars_config: dict[str, Any],
expect_write_calls: bool,
) -> None:
"""Test updating the yaml file with a new calendar."""
mock_calendars_list({"items": [test_api_calendar]})
mock_events_list({})
assert await component_setup()
mock_calendars_yaml().read.assert_called()
assert mock_calendars_yaml().write.called is expect_write_calls
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("config_entry_unique_id", [None])
async def test_assign_unique_id(
hass: HomeAssistant,
component_setup: ComponentSetup,
mock_calendars_list: ApiResult,
test_api_calendar: dict[str, Any],
mock_events_list: ApiResult,
mock_calendar_get: Callable[..., None],
config_entry: MockConfigEntry,
) -> None:
"""Test an existing config is updated to have unique id if it does not exist."""
assert config_entry.state is ConfigEntryState.NOT_LOADED
assert config_entry.unique_id is None
mock_calendar_get(
"primary",
{"id": EMAIL_ADDRESS, "summary": "Personal", "accessRole": "owner"},
)
mock_calendars_list({"items": [test_api_calendar]})
mock_events_list({})
assert await component_setup()
assert config_entry.state is ConfigEntryState.LOADED
assert config_entry.unique_id == EMAIL_ADDRESS
@pytest.mark.parametrize(
("config_entry_unique_id", "request_status", "config_entry_status"),
[
(None, http.HTTPStatus.BAD_REQUEST, ConfigEntryState.SETUP_RETRY),
(
None,
http.HTTPStatus.UNAUTHORIZED,
ConfigEntryState.SETUP_ERROR,
),
],
)
async def test_assign_unique_id_failure(
hass: HomeAssistant,
component_setup: ComponentSetup,
mock_calendars_list: ApiResult,
test_api_calendar: dict[str, Any],
config_entry: MockConfigEntry,
mock_events_list: ApiResult,
mock_calendar_get: Callable[..., None],
request_status: http.HTTPStatus,
config_entry_status: ConfigEntryState,
) -> None:
"""Test lookup failures during unique id assignment are handled gracefully."""
assert config_entry.state is ConfigEntryState.NOT_LOADED
assert config_entry.unique_id is None
mock_calendar_get(
"primary",
{},
status=request_status,
)
mock_calendars_list({"items": [test_api_calendar]})
mock_events_list({})
await component_setup()
assert config_entry.state is config_entry_status
assert config_entry.unique_id is None
async def test_remove_entry(
hass: HomeAssistant,
mock_calendars_list: ApiResult,
component_setup: ComponentSetup,
test_api_calendar: dict[str, Any],
mock_events_list: ApiResult,
) -> None:
"""Test load and remove of a ConfigEntry."""
mock_calendars_list({"items": [test_api_calendar]})
mock_events_list({})
assert 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_remove(entry.entry_id)
assert entry.state is ConfigEntryState.NOT_LOADED
async def test_oauth_implementation_not_available(
hass: HomeAssistant,
config_entry: MockConfigEntry,
) -> None:
"""Test that unavailable OAuth implementation raises ConfigEntryNotReady."""
config_entry.add_to_hass(hass)
with patch(
"homeassistant.helpers.config_entry_oauth2_flow.async_get_config_entry_implementation",
side_effect=ImplementationUnavailableError,
):
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
assert config_entry.state is ConfigEntryState.SETUP_RETRY