Deprecate google calendar configuration.yaml (#72288)
* Deprecate google calendar configuration.yaml * Remove unused translations * Enable strict type checking and address pr feedback * Move default hass.data init to `async_setup`
This commit is contained in:
parent
9c3f949165
commit
e6ffae8bd3
|
@ -101,6 +101,7 @@ homeassistant.components.geo_location.*
|
|||
homeassistant.components.geocaching.*
|
||||
homeassistant.components.gios.*
|
||||
homeassistant.components.goalzero.*
|
||||
homeassistant.components.google.*
|
||||
homeassistant.components.greeneye_monitor.*
|
||||
homeassistant.components.group.*
|
||||
homeassistant.components.guardian.*
|
||||
|
|
|
@ -97,23 +97,27 @@ PLATFORMS = ["calendar"]
|
|||
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema(
|
||||
{
|
||||
DOMAIN: vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_CLIENT_ID): cv.string,
|
||||
vol.Required(CONF_CLIENT_SECRET): cv.string,
|
||||
vol.Optional(CONF_TRACK_NEW, default=True): cv.boolean,
|
||||
vol.Optional(CONF_CALENDAR_ACCESS, default="read_write"): cv.enum(
|
||||
FeatureAccess
|
||||
),
|
||||
}
|
||||
)
|
||||
},
|
||||
vol.All(
|
||||
cv.deprecated(DOMAIN),
|
||||
{
|
||||
DOMAIN: vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_CLIENT_ID): cv.string,
|
||||
vol.Required(CONF_CLIENT_SECRET): cv.string,
|
||||
vol.Optional(CONF_TRACK_NEW, default=True): cv.boolean,
|
||||
vol.Optional(CONF_CALENDAR_ACCESS, default="read_write"): cv.enum(
|
||||
FeatureAccess
|
||||
),
|
||||
}
|
||||
)
|
||||
},
|
||||
),
|
||||
extra=vol.ALLOW_EXTRA,
|
||||
)
|
||||
|
||||
_SINGLE_CALSEARCH_CONFIG = vol.All(
|
||||
cv.deprecated(CONF_MAX_RESULTS),
|
||||
cv.deprecated(CONF_TRACK),
|
||||
vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_NAME): cv.string,
|
||||
|
@ -160,6 +164,9 @@ ADD_EVENT_SERVICE_SCHEMA = vol.Schema(
|
|||
|
||||
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
"""Set up the Google component."""
|
||||
if DOMAIN not in config:
|
||||
return True
|
||||
|
||||
conf = config.get(DOMAIN, {})
|
||||
hass.data[DOMAIN] = {DATA_CONFIG: conf}
|
||||
|
||||
|
@ -189,11 +196,22 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
|||
},
|
||||
)
|
||||
)
|
||||
|
||||
_LOGGER.warning(
|
||||
"Configuration of Google Calendar in YAML in configuration.yaml is "
|
||||
"is deprecated and will be removed in a future release; Your existing "
|
||||
"OAuth Application Credentials and other settings have been imported "
|
||||
"into the UI automatically and can be safely removed from your "
|
||||
"configuration.yaml file"
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Set up Google from a config entry."""
|
||||
hass.data.setdefault(DOMAIN, {})
|
||||
async_upgrade_entry(hass, entry)
|
||||
implementation = (
|
||||
await config_entry_oauth2_flow.async_get_config_entry_implementation(
|
||||
hass, entry
|
||||
|
@ -216,8 +234,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||
except aiohttp.ClientError as err:
|
||||
raise ConfigEntryNotReady from err
|
||||
|
||||
access = get_feature_access(hass)
|
||||
if access.scope not in session.token.get("scope", []):
|
||||
access = FeatureAccess[entry.options[CONF_CALENDAR_ACCESS]]
|
||||
token_scopes = session.token.get("scope", [])
|
||||
if access.scope not in token_scopes:
|
||||
_LOGGER.debug("Scope '%s' not in scopes '%s'", access.scope, token_scopes)
|
||||
raise ConfigEntryAuthFailed(
|
||||
"Required scopes are not available, reauth required"
|
||||
)
|
||||
|
@ -226,25 +246,53 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||
)
|
||||
hass.data[DOMAIN][DATA_SERVICE] = calendar_service
|
||||
|
||||
track_new = hass.data[DOMAIN][DATA_CONFIG].get(CONF_TRACK_NEW, True)
|
||||
await async_setup_services(hass, track_new, calendar_service)
|
||||
await async_setup_services(hass, calendar_service)
|
||||
# Only expose the add event service if we have the correct permissions
|
||||
if access is FeatureAccess.read_write:
|
||||
await async_setup_add_event_service(hass, calendar_service)
|
||||
|
||||
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
|
||||
|
||||
# Reload entry when options are updated
|
||||
entry.async_on_unload(entry.add_update_listener(async_reload_entry))
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def async_upgrade_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
||||
"""Upgrade the config entry if needed."""
|
||||
if DATA_CONFIG not in hass.data[DOMAIN] and entry.options:
|
||||
return
|
||||
|
||||
options = (
|
||||
entry.options
|
||||
if entry.options
|
||||
else {
|
||||
CONF_CALENDAR_ACCESS: get_feature_access(hass).name,
|
||||
}
|
||||
)
|
||||
disable_new_entities = (
|
||||
not hass.data[DOMAIN].get(DATA_CONFIG, {}).get(CONF_TRACK_NEW, True)
|
||||
)
|
||||
hass.config_entries.async_update_entry(
|
||||
entry,
|
||||
options=options,
|
||||
pref_disable_new_entities=disable_new_entities,
|
||||
)
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Unload a config entry."""
|
||||
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
|
||||
|
||||
async def async_reload_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
||||
"""Reload the config entry when it changed."""
|
||||
await hass.config_entries.async_reload(entry.entry_id)
|
||||
|
||||
|
||||
async def async_setup_services(
|
||||
hass: HomeAssistant,
|
||||
track_new: bool,
|
||||
calendar_service: GoogleCalendarService,
|
||||
) -> None:
|
||||
"""Set up the service listeners."""
|
||||
|
@ -256,10 +304,7 @@ async def async_setup_services(
|
|||
async def _found_calendar(calendar_item: Calendar) -> None:
|
||||
calendar = get_calendar_info(
|
||||
hass,
|
||||
{
|
||||
**calendar_item.dict(exclude_unset=True),
|
||||
CONF_TRACK: track_new,
|
||||
},
|
||||
calendar_item.dict(exclude_unset=True),
|
||||
)
|
||||
calendar_id = calendar_item.id
|
||||
# Populate the yaml file with all discovered calendars
|
||||
|
@ -363,7 +408,6 @@ def get_calendar_info(
|
|||
CONF_CAL_ID: calendar["id"],
|
||||
CONF_ENTITIES: [
|
||||
{
|
||||
CONF_TRACK: calendar["track"],
|
||||
CONF_NAME: calendar["summary"],
|
||||
CONF_DEVICE_ID: generate_entity_id(
|
||||
"{}", calendar["summary"], hass=hass
|
||||
|
|
|
@ -6,7 +6,7 @@ from collections.abc import Awaitable, Callable
|
|||
import datetime
|
||||
import logging
|
||||
import time
|
||||
from typing import Any
|
||||
from typing import Any, cast
|
||||
|
||||
import aiohttp
|
||||
from gcal_sync.auth import AbstractAuth
|
||||
|
@ -76,12 +76,12 @@ class DeviceFlow:
|
|||
@property
|
||||
def verification_url(self) -> str:
|
||||
"""Return the verification url that the user should visit to enter the code."""
|
||||
return self._device_flow_info.verification_url
|
||||
return self._device_flow_info.verification_url # type: ignore[no-any-return]
|
||||
|
||||
@property
|
||||
def user_code(self) -> str:
|
||||
"""Return the code that the user should enter at the verification url."""
|
||||
return self._device_flow_info.user_code
|
||||
return self._device_flow_info.user_code # type: ignore[no-any-return]
|
||||
|
||||
async def start_exchange_task(
|
||||
self, finished_cb: Callable[[Credentials | None], Awaitable[None]]
|
||||
|
@ -131,10 +131,13 @@ def get_feature_access(hass: HomeAssistant) -> FeatureAccess:
|
|||
"""Return the desired calendar feature access."""
|
||||
# This may be called during config entry setup without integration setup running when there
|
||||
# is no google entry in configuration.yaml
|
||||
return (
|
||||
hass.data.get(DOMAIN, {})
|
||||
.get(DATA_CONFIG, {})
|
||||
.get(CONF_CALENDAR_ACCESS, DEFAULT_FEATURE_ACCESS)
|
||||
return cast(
|
||||
FeatureAccess,
|
||||
(
|
||||
hass.data.get(DOMAIN, {})
|
||||
.get(DATA_CONFIG, {})
|
||||
.get(CONF_CALENDAR_ACCESS, DEFAULT_FEATURE_ACCESS)
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
|
@ -157,7 +160,7 @@ async def async_create_device_flow(
|
|||
return DeviceFlow(hass, oauth_flow, device_flow_info)
|
||||
|
||||
|
||||
class ApiAuthImpl(AbstractAuth):
|
||||
class ApiAuthImpl(AbstractAuth): # type: ignore[misc]
|
||||
"""Authentication implementation for google calendar api library."""
|
||||
|
||||
def __init__(
|
||||
|
@ -172,10 +175,10 @@ class ApiAuthImpl(AbstractAuth):
|
|||
async def async_get_access_token(self) -> str:
|
||||
"""Return a valid access token."""
|
||||
await self._session.async_ensure_token_valid()
|
||||
return self._session.token["access_token"]
|
||||
return cast(str, self._session.token["access_token"])
|
||||
|
||||
|
||||
class AccessTokenAuthImpl(AbstractAuth):
|
||||
class AccessTokenAuthImpl(AbstractAuth): # type: ignore[misc]
|
||||
"""Authentication implementation used during config flow, without refresh.
|
||||
|
||||
This exists to allow the config flow to use the API before it has fully
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""Support for Google Calendar Search binary sensors."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
|
@ -89,14 +90,25 @@ def _async_setup_entities(
|
|||
) -> None:
|
||||
calendar_service = hass.data[DOMAIN][DATA_SERVICE]
|
||||
entities = []
|
||||
num_entities = len(disc_info[CONF_ENTITIES])
|
||||
for data in disc_info[CONF_ENTITIES]:
|
||||
if not data[CONF_TRACK]:
|
||||
continue
|
||||
entity_id = generate_entity_id(
|
||||
ENTITY_ID_FORMAT, data[CONF_DEVICE_ID], hass=hass
|
||||
)
|
||||
entity_enabled = data.get(CONF_TRACK, True)
|
||||
entity_name = data[CONF_DEVICE_ID]
|
||||
entity_id = generate_entity_id(ENTITY_ID_FORMAT, entity_name, hass=hass)
|
||||
calendar_id = disc_info[CONF_CAL_ID]
|
||||
if num_entities > 1:
|
||||
# The google_calendars.yaml file lets users add multiple entities for
|
||||
# the same calendar id and needs additional disambiguation
|
||||
unique_id = f"{calendar_id}-{entity_name}"
|
||||
else:
|
||||
unique_id = calendar_id
|
||||
entity = GoogleCalendarEntity(
|
||||
calendar_service, disc_info[CONF_CAL_ID], data, entity_id
|
||||
calendar_service,
|
||||
disc_info[CONF_CAL_ID],
|
||||
data,
|
||||
entity_id,
|
||||
unique_id,
|
||||
entity_enabled,
|
||||
)
|
||||
entities.append(entity)
|
||||
|
||||
|
@ -112,6 +124,8 @@ class GoogleCalendarEntity(CalendarEntity):
|
|||
calendar_id: str,
|
||||
data: dict[str, Any],
|
||||
entity_id: str,
|
||||
unique_id: str,
|
||||
entity_enabled: bool,
|
||||
) -> None:
|
||||
"""Create the Calendar event device."""
|
||||
self._calendar_service = calendar_service
|
||||
|
@ -123,6 +137,8 @@ class GoogleCalendarEntity(CalendarEntity):
|
|||
self._offset = data.get(CONF_OFFSET, DEFAULT_CONF_OFFSET)
|
||||
self._offset_value: timedelta | None = None
|
||||
self.entity_id = entity_id
|
||||
self._attr_unique_id = unique_id
|
||||
self._attr_entity_registry_enabled_default = entity_enabled
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self) -> dict[str, bool]:
|
||||
|
@ -152,7 +168,7 @@ class GoogleCalendarEntity(CalendarEntity):
|
|||
"""Return True if the event is visible."""
|
||||
if self._ignore_availability:
|
||||
return True
|
||||
return event.transparency == OPAQUE
|
||||
return event.transparency == OPAQUE # type: ignore[no-any-return]
|
||||
|
||||
async def async_get_events(
|
||||
self, hass: HomeAssistant, start_date: datetime, end_date: datetime
|
||||
|
|
|
@ -7,7 +7,10 @@ from typing import Any
|
|||
from gcal_sync.api import GoogleCalendarService
|
||||
from gcal_sync.exceptions import ApiException
|
||||
from oauth2client.client import Credentials
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.data_entry_flow import FlowResult
|
||||
from homeassistant.helpers import config_entry_oauth2_flow
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
|
@ -21,7 +24,7 @@ from .api import (
|
|||
async_create_device_flow,
|
||||
get_feature_access,
|
||||
)
|
||||
from .const import DOMAIN
|
||||
from .const import CONF_CALENDAR_ACCESS, DOMAIN, FeatureAccess
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
@ -36,7 +39,7 @@ class OAuth2FlowHandler(
|
|||
def __init__(self) -> None:
|
||||
"""Set up instance."""
|
||||
super().__init__()
|
||||
self._reauth = False
|
||||
self._reauth_config_entry: config_entries.ConfigEntry | None = None
|
||||
self._device_flow: DeviceFlow | None = None
|
||||
|
||||
@property
|
||||
|
@ -60,7 +63,7 @@ class OAuth2FlowHandler(
|
|||
self, user_input: dict[str, Any] | None = None
|
||||
) -> FlowResult:
|
||||
"""Handle external yaml configuration."""
|
||||
if not self._reauth and self._async_current_entries():
|
||||
if not self._reauth_config_entry and self._async_current_entries():
|
||||
return self.async_abort(reason="already_configured")
|
||||
return await super().async_step_user(user_input)
|
||||
|
||||
|
@ -84,12 +87,17 @@ class OAuth2FlowHandler(
|
|||
self.flow_impl,
|
||||
)
|
||||
return self.async_abort(reason="oauth_error")
|
||||
calendar_access = get_feature_access(self.hass)
|
||||
if self._reauth_config_entry and self._reauth_config_entry.options:
|
||||
calendar_access = FeatureAccess[
|
||||
self._reauth_config_entry.options[CONF_CALENDAR_ACCESS]
|
||||
]
|
||||
try:
|
||||
device_flow = await async_create_device_flow(
|
||||
self.hass,
|
||||
self.flow_impl.client_id,
|
||||
self.flow_impl.client_secret,
|
||||
get_feature_access(self.hass),
|
||||
calendar_access,
|
||||
)
|
||||
except OAuthError as err:
|
||||
_LOGGER.error("Error initializing device flow: %s", str(err))
|
||||
|
@ -146,13 +154,21 @@ class OAuth2FlowHandler(
|
|||
_LOGGER.debug("Error reading calendar primary calendar: %s", err)
|
||||
primary_calendar = None
|
||||
title = primary_calendar.id if primary_calendar else self.flow_impl.name
|
||||
return self.async_create_entry(title=title, data=data)
|
||||
return self.async_create_entry(
|
||||
title=title,
|
||||
data=data,
|
||||
options={
|
||||
CONF_CALENDAR_ACCESS: get_feature_access(self.hass).name,
|
||||
},
|
||||
)
|
||||
|
||||
async def async_step_reauth(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> FlowResult:
|
||||
"""Perform reauth upon an API authentication error."""
|
||||
self._reauth = True
|
||||
self._reauth_config_entry = self.hass.config_entries.async_get_entry(
|
||||
self.context["entry_id"]
|
||||
)
|
||||
return await self.async_step_reauth_confirm()
|
||||
|
||||
async def async_step_reauth_confirm(
|
||||
|
@ -162,3 +178,43 @@ class OAuth2FlowHandler(
|
|||
if user_input is None:
|
||||
return self.async_show_form(step_id="reauth_confirm")
|
||||
return await self.async_step_user()
|
||||
|
||||
@staticmethod
|
||||
@callback
|
||||
def async_get_options_flow(
|
||||
config_entry: config_entries.ConfigEntry,
|
||||
) -> config_entries.OptionsFlow:
|
||||
"""Create an options flow."""
|
||||
return OptionsFlowHandler(config_entry)
|
||||
|
||||
|
||||
class OptionsFlowHandler(config_entries.OptionsFlow):
|
||||
"""Google Calendar options flow."""
|
||||
|
||||
def __init__(self, config_entry: config_entries.ConfigEntry) -> None:
|
||||
"""Initialize options flow."""
|
||||
self.config_entry = config_entry
|
||||
|
||||
async def async_step_init(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> FlowResult:
|
||||
"""Manage the options."""
|
||||
if user_input is not None:
|
||||
return self.async_create_entry(title="", data=user_input)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="init",
|
||||
data_schema=vol.Schema(
|
||||
{
|
||||
vol.Required(
|
||||
CONF_CALENDAR_ACCESS,
|
||||
default=self.config_entry.options.get(CONF_CALENDAR_ACCESS),
|
||||
): vol.In(
|
||||
{
|
||||
"read_write": "Read/Write access (can create events)",
|
||||
"read_only": "Read-only access",
|
||||
}
|
||||
)
|
||||
}
|
||||
),
|
||||
)
|
||||
|
|
|
@ -27,5 +27,14 @@
|
|||
"progress": {
|
||||
"exchange": "To link your Google account, visit the [{url}]({url}) and enter code:\n\n{user_code}"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"step": {
|
||||
"init": {
|
||||
"data": {
|
||||
"calendar_access": "Home Assistant access to Google Calendar"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,5 +27,14 @@
|
|||
"title": "Reauthenticate Integration"
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"step": {
|
||||
"init": {
|
||||
"data": {
|
||||
"calendar_access": "Home Assistant access to Google Calendar"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
11
mypy.ini
11
mypy.ini
|
@ -874,6 +874,17 @@ no_implicit_optional = true
|
|||
warn_return_any = true
|
||||
warn_unreachable = true
|
||||
|
||||
[mypy-homeassistant.components.google.*]
|
||||
check_untyped_defs = true
|
||||
disallow_incomplete_defs = true
|
||||
disallow_subclassing_any = true
|
||||
disallow_untyped_calls = true
|
||||
disallow_untyped_decorators = true
|
||||
disallow_untyped_defs = true
|
||||
no_implicit_optional = true
|
||||
warn_return_any = true
|
||||
warn_unreachable = true
|
||||
|
||||
[mypy-homeassistant.components.greeneye_monitor.*]
|
||||
check_untyped_defs = true
|
||||
disallow_incomplete_defs = true
|
||||
|
|
|
@ -21,6 +21,7 @@ from homeassistant.components.application_credentials import (
|
|||
async_import_client_credential,
|
||||
)
|
||||
from homeassistant.components.google.const import DOMAIN
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import config_entry_oauth2_flow
|
||||
from homeassistant.util.dt import utcnow
|
||||
|
@ -143,6 +144,7 @@ async def test_full_flow_yaml_creds(
|
|||
"token_type": "Bearer",
|
||||
},
|
||||
}
|
||||
assert result.get("options") == {"calendar_access": "read_write"}
|
||||
|
||||
assert len(mock_setup.mock_calls) == 1
|
||||
entries = hass.config_entries.async_entries(DOMAIN)
|
||||
|
@ -205,6 +207,7 @@ async def test_full_flow_application_creds(
|
|||
"token_type": "Bearer",
|
||||
},
|
||||
}
|
||||
assert result.get("options") == {"calendar_access": "read_write"}
|
||||
|
||||
assert len(mock_setup.mock_calls) == 1
|
||||
entries = hass.config_entries.async_entries(DOMAIN)
|
||||
|
@ -441,7 +444,12 @@ async def test_reauth_flow(
|
|||
assert await component_setup()
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_REAUTH}, data=config_entry.data
|
||||
DOMAIN,
|
||||
context={
|
||||
"source": config_entries.SOURCE_REAUTH,
|
||||
"entry_id": config_entry.entry_id,
|
||||
},
|
||||
data=config_entry.data,
|
||||
)
|
||||
assert result["type"] == "form"
|
||||
assert result["step_id"] == "reauth_confirm"
|
||||
|
@ -523,3 +531,66 @@ async def test_title_lookup_failure(
|
|||
assert len(mock_setup.mock_calls) == 1
|
||||
entries = hass.config_entries.async_entries(DOMAIN)
|
||||
assert len(entries) == 1
|
||||
|
||||
|
||||
async def test_options_flow_triggers_reauth(
|
||||
hass: HomeAssistant,
|
||||
component_setup: ComponentSetup,
|
||||
config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test load and unload of a ConfigEntry."""
|
||||
config_entry.add_to_hass(hass)
|
||||
await component_setup()
|
||||
assert config_entry.state is ConfigEntryState.LOADED
|
||||
assert config_entry.options == {"calendar_access": "read_write"}
|
||||
|
||||
result = await hass.config_entries.options.async_init(config_entry.entry_id)
|
||||
assert result["type"] == "form"
|
||||
assert result["step_id"] == "init"
|
||||
data_schema = result["data_schema"].schema
|
||||
assert set(data_schema) == {"calendar_access"}
|
||||
|
||||
result = await hass.config_entries.options.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={
|
||||
"calendar_access": "read_only",
|
||||
},
|
||||
)
|
||||
assert result["type"] == "create_entry"
|
||||
|
||||
await hass.async_block_till_done()
|
||||
assert config_entry.options == {"calendar_access": "read_only"}
|
||||
# Re-auth flow was initiated because access level changed
|
||||
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"
|
||||
|
||||
|
||||
async def test_options_flow_no_changes(
|
||||
hass: HomeAssistant,
|
||||
component_setup: ComponentSetup,
|
||||
config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test load and unload of a ConfigEntry."""
|
||||
config_entry.add_to_hass(hass)
|
||||
await component_setup()
|
||||
assert config_entry.state is ConfigEntryState.LOADED
|
||||
assert config_entry.options == {"calendar_access": "read_write"}
|
||||
|
||||
result = await hass.config_entries.options.async_init(config_entry.entry_id)
|
||||
assert result["type"] == "form"
|
||||
assert result["step_id"] == "init"
|
||||
|
||||
result = await hass.config_entries.options.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={
|
||||
"calendar_access": "read_write",
|
||||
},
|
||||
)
|
||||
assert result["type"] == "create_entry"
|
||||
|
||||
await hass.async_block_till_done()
|
||||
assert config_entry.options == {"calendar_access": "read_write"}
|
||||
# Re-auth flow was initiated because access level changed
|
||||
assert config_entry.state is ConfigEntryState.LOADED
|
||||
|
|
|
@ -46,7 +46,7 @@ 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:
|
||||
if actual is None or expected is None:
|
||||
assert actual == expected
|
||||
return
|
||||
assert actual.entity_id == expected.entity_id
|
||||
|
|
Loading…
Reference in New Issue