Remove google calendar configuration.yaml deprecated in 2022.6 (#77814)
parent
6644f62ad2
commit
8280b8422c
|
@ -10,20 +10,12 @@ import aiohttp
|
|||
from gcal_sync.api import GoogleCalendarService
|
||||
from gcal_sync.exceptions import ApiException, AuthException
|
||||
from gcal_sync.model import DateOrDatetime, Event
|
||||
from oauth2client.file import Storage
|
||||
import voluptuous as vol
|
||||
from voluptuous.error import Error as VoluptuousError
|
||||
import yaml
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components.application_credentials import (
|
||||
ClientCredential,
|
||||
async_import_client_credential,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
CONF_CLIENT_ID,
|
||||
CONF_CLIENT_SECRET,
|
||||
CONF_DEVICE_ID,
|
||||
CONF_ENTITIES,
|
||||
CONF_NAME,
|
||||
|
@ -40,15 +32,10 @@ from homeassistant.helpers import config_entry_oauth2_flow
|
|||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.entity import generate_entity_id
|
||||
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from .api import ApiAuthImpl, get_feature_access
|
||||
from .const import (
|
||||
CONF_CALENDAR_ACCESS,
|
||||
DATA_CONFIG,
|
||||
DATA_SERVICE,
|
||||
DEVICE_AUTH_IMPL,
|
||||
DOMAIN,
|
||||
EVENT_DESCRIPTION,
|
||||
EVENT_END_DATE,
|
||||
|
@ -79,37 +66,15 @@ DEFAULT_CONF_OFFSET = "!!"
|
|||
|
||||
EVENT_CALENDAR_ID = "calendar_id"
|
||||
|
||||
NOTIFICATION_ID = "google_calendar_notification"
|
||||
NOTIFICATION_TITLE = "Google Calendar Setup"
|
||||
GROUP_NAME_ALL_CALENDARS = "Google Calendar Sensors"
|
||||
|
||||
SERVICE_ADD_EVENT = "add_event"
|
||||
|
||||
YAML_DEVICES = f"{DOMAIN}_calendars.yaml"
|
||||
|
||||
TOKEN_FILE = f".{DOMAIN}.token"
|
||||
|
||||
PLATFORMS = [Platform.CALENDAR]
|
||||
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema(
|
||||
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,
|
||||
)
|
||||
CONFIG_SCHEMA = vol.Schema(cv.removed(DOMAIN), extra=vol.ALLOW_EXTRA)
|
||||
|
||||
|
||||
_SINGLE_CALSEARCH_CONFIG = vol.All(
|
||||
cv.deprecated(CONF_MAX_RESULTS),
|
||||
|
@ -171,65 +136,6 @@ ADD_EVENT_SERVICE_SCHEMA = vol.All(
|
|||
)
|
||||
|
||||
|
||||
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}
|
||||
|
||||
if CONF_CLIENT_ID in conf and CONF_CLIENT_SECRET in conf:
|
||||
await async_import_client_credential(
|
||||
hass,
|
||||
DOMAIN,
|
||||
ClientCredential(
|
||||
conf[CONF_CLIENT_ID],
|
||||
conf[CONF_CLIENT_SECRET],
|
||||
),
|
||||
DEVICE_AUTH_IMPL,
|
||||
)
|
||||
|
||||
# Import credentials from the old token file into the new way as
|
||||
# a ConfigEntry managed by home assistant.
|
||||
storage = Storage(hass.config.path(TOKEN_FILE))
|
||||
creds = await hass.async_add_executor_job(storage.get)
|
||||
if creds and get_feature_access(hass).scope in creds.scopes:
|
||||
_LOGGER.debug("Importing configuration entry with credentials")
|
||||
hass.async_create_task(
|
||||
hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_IMPORT},
|
||||
data={
|
||||
"creds": creds,
|
||||
},
|
||||
)
|
||||
)
|
||||
async_create_issue(
|
||||
hass,
|
||||
DOMAIN,
|
||||
"deprecated_yaml",
|
||||
breaks_in_ha_version="2022.9.0", # Warning first added in 2022.6.0
|
||||
is_fixable=False,
|
||||
severity=IssueSeverity.WARNING,
|
||||
translation_key="deprecated_yaml",
|
||||
)
|
||||
if conf.get(CONF_TRACK_NEW) is False:
|
||||
# The track_new as False would previously result in new entries
|
||||
# in google_calendars.yaml with track set to False which is
|
||||
# handled at calendar entity creation time.
|
||||
async_create_issue(
|
||||
hass,
|
||||
DOMAIN,
|
||||
"removed_track_new_yaml",
|
||||
breaks_in_ha_version="2022.6.0",
|
||||
is_fixable=False,
|
||||
severity=IssueSeverity.WARNING,
|
||||
translation_key="removed_track_new_yaml",
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Set up Google from a config entry."""
|
||||
hass.data.setdefault(DOMAIN, {})
|
||||
|
|
|
@ -41,15 +41,5 @@
|
|||
},
|
||||
"application_credentials": {
|
||||
"description": "Follow the [instructions]({more_info_url}) for [OAuth consent screen]({oauth_consent_url}) to give Home Assistant access to your Google Calendar. You also need to create Application Credentials linked to your Calendar:\n1. Go to [Credentials]({oauth_creds_url}) and click **Create Credentials**.\n1. From the drop-down list select **OAuth client ID**.\n1. Select **TV and Limited Input devices** for the Application Type.\n\n"
|
||||
},
|
||||
"issues": {
|
||||
"deprecated_yaml": {
|
||||
"title": "The Google Calendar YAML configuration is being removed",
|
||||
"description": "Configuring the Google Calendar in configuration.yaml is being removed in Home Assistant 2022.9.\n\nYour existing OAuth Application Credentials and access settings have been imported into the UI automatically. Remove the YAML configuration from your configuration.yaml file and restart Home Assistant to fix this issue."
|
||||
},
|
||||
"removed_track_new_yaml": {
|
||||
"title": "Google Calendar entity tracking has changed",
|
||||
"description": "You have disabled entity tracking for Google Calendar in configuration.yaml, which is no longer supported. You must manually change the integration System Options in the UI to disable newly discovered entities going forward. Remove the track_new setting from configuration.yaml and restart Home Assistant to fix this issue."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,12 +9,15 @@ from unittest.mock import Mock, mock_open, patch
|
|||
|
||||
from aiohttp.client_exceptions import ClientError
|
||||
from gcal_sync.auth import API_BASE_URL
|
||||
from oauth2client.client import Credentials, OAuth2Credentials
|
||||
from oauth2client.client import OAuth2Credentials
|
||||
import pytest
|
||||
import yaml
|
||||
|
||||
from homeassistant.components.google import CONF_TRACK_NEW, DOMAIN
|
||||
from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET
|
||||
from homeassistant.components.application_credentials import (
|
||||
ClientCredential,
|
||||
async_import_client_credential,
|
||||
)
|
||||
from homeassistant.components.google import DOMAIN
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
|
@ -115,22 +118,6 @@ def mock_calendars_yaml(
|
|||
yield mocked_open_function
|
||||
|
||||
|
||||
class FakeStorage:
|
||||
"""A fake storage object for persiting creds."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Initialize FakeStorage."""
|
||||
self._creds: Credentials | None = None
|
||||
|
||||
def get(self) -> Credentials | None:
|
||||
"""Get credentials from storage."""
|
||||
return self._creds
|
||||
|
||||
def put(self, creds: Credentials) -> None:
|
||||
"""Put credentials in storage."""
|
||||
self._creds = creds
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def token_scopes() -> list[str]:
|
||||
"""Fixture for scopes used during test."""
|
||||
|
@ -163,14 +150,6 @@ def creds(
|
|||
)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def storage() -> YieldFixture[FakeStorage]:
|
||||
"""Fixture to populate an existing token file for read on startup."""
|
||||
storage = FakeStorage()
|
||||
with patch("homeassistant.components.google.Storage", return_value=storage):
|
||||
yield storage
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def config_entry_token_expiry(token_expiry: datetime.datetime) -> float:
|
||||
"""Fixture for token expiration value stored in the config entry."""
|
||||
|
@ -214,16 +193,6 @@ def config_entry(
|
|||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_token_read(
|
||||
hass: HomeAssistant,
|
||||
creds: OAuth2Credentials,
|
||||
storage: FakeStorage,
|
||||
) -> None:
|
||||
"""Fixture to populate an existing token file for read on startup."""
|
||||
storage.put(creds)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_events_list(
|
||||
aioclient_mock: AiohttpClientMocker,
|
||||
|
@ -327,33 +296,17 @@ def set_time_zone(hass):
|
|||
|
||||
|
||||
@pytest.fixture
|
||||
def google_config_track_new() -> None:
|
||||
"""Fixture for tests to set the 'track_new' configuration.yaml setting."""
|
||||
return None
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def google_config(google_config_track_new: bool | None) -> dict[str, Any]:
|
||||
"""Fixture for overriding component config."""
|
||||
google_config = {CONF_CLIENT_ID: CLIENT_ID, CONF_CLIENT_SECRET: CLIENT_SECRET}
|
||||
if google_config_track_new is not None:
|
||||
google_config[CONF_TRACK_NEW] = google_config_track_new
|
||||
return google_config
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def config(google_config: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Fixture for overriding component config."""
|
||||
return {DOMAIN: google_config} if google_config else {}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def component_setup(hass: HomeAssistant, config: dict[str, Any]) -> ComponentSetup:
|
||||
def component_setup(
|
||||
hass: HomeAssistant, config_entry: MockConfigEntry
|
||||
) -> 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
|
||||
assert await async_setup_component(hass, "application_credentials", {})
|
||||
await async_import_client_credential(
|
||||
hass, DOMAIN, ClientCredential("client-id", "client-secret"), "device_auth"
|
||||
)
|
||||
config_entry.add_to_hass(hass)
|
||||
return await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
|
||||
return _setup_func
|
||||
|
|
|
@ -4,11 +4,9 @@ from __future__ import annotations
|
|||
|
||||
from collections.abc import Callable
|
||||
import datetime
|
||||
from typing import Any
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
from aiohttp.client_exceptions import ClientError
|
||||
from freezegun.api import FrozenDateTimeFactory
|
||||
from oauth2client.client import (
|
||||
DeviceFlowInfo,
|
||||
FlowExchangeError,
|
||||
|
@ -26,15 +24,10 @@ 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.setup import async_setup_component
|
||||
from homeassistant.util.dt import utcnow
|
||||
|
||||
from .conftest import (
|
||||
CLIENT_ID,
|
||||
CLIENT_SECRET,
|
||||
EMAIL_ADDRESS,
|
||||
ComponentSetup,
|
||||
YieldFixture,
|
||||
)
|
||||
from .conftest import CLIENT_ID, CLIENT_SECRET, EMAIL_ADDRESS, YieldFixture
|
||||
|
||||
from tests.common import MockConfigEntry, async_fire_time_changed
|
||||
|
||||
|
@ -48,6 +41,12 @@ async def request_setup(current_request_with_host) -> None:
|
|||
return
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
async def setup_app_creds(hass: HomeAssistant) -> None:
|
||||
"""Fixture to setup application credentials component."""
|
||||
await async_setup_component(hass, "application_credentials", {})
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def code_expiration_delta() -> datetime.timedelta:
|
||||
"""Fixture for code expiration time, defaulting to the future."""
|
||||
|
@ -117,74 +116,12 @@ async def fire_alarm(hass, point_in_time):
|
|||
await hass.async_block_till_done()
|
||||
|
||||
|
||||
async def test_full_flow_yaml_creds(
|
||||
hass: HomeAssistant,
|
||||
mock_code_flow: Mock,
|
||||
mock_exchange: Mock,
|
||||
component_setup: ComponentSetup,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
) -> None:
|
||||
"""Test successful creds setup."""
|
||||
assert await component_setup()
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
assert result.get("type") == "progress"
|
||||
assert result.get("step_id") == "auth"
|
||||
assert "description_placeholders" in result
|
||||
assert "url" in result["description_placeholders"]
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.google.async_setup_entry", return_value=True
|
||||
) as mock_setup:
|
||||
# Run one tick to invoke the credential exchange check
|
||||
now = utcnow()
|
||||
await fire_alarm(hass, now + CODE_CHECK_ALARM_TIMEDELTA)
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
flow_id=result["flow_id"]
|
||||
)
|
||||
|
||||
assert result.get("type") == "create_entry"
|
||||
assert result.get("title") == EMAIL_ADDRESS
|
||||
assert "data" in result
|
||||
data = result["data"]
|
||||
assert "token" in data
|
||||
assert 0 < data["token"]["expires_in"] <= 60 * 60
|
||||
assert (
|
||||
datetime.datetime.now().timestamp()
|
||||
<= data["token"]["expires_at"]
|
||||
< (datetime.datetime.now() + datetime.timedelta(days=8)).timestamp()
|
||||
)
|
||||
data["token"].pop("expires_at")
|
||||
data["token"].pop("expires_in")
|
||||
assert data == {
|
||||
"auth_implementation": "device_auth",
|
||||
"token": {
|
||||
"access_token": "ACCESS_TOKEN",
|
||||
"refresh_token": "REFRESH_TOKEN",
|
||||
"scope": "https://www.googleapis.com/auth/calendar",
|
||||
"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)
|
||||
assert len(entries) == 1
|
||||
|
||||
|
||||
@pytest.mark.parametrize("google_config", [None])
|
||||
async def test_full_flow_application_creds(
|
||||
hass: HomeAssistant,
|
||||
mock_code_flow: Mock,
|
||||
mock_exchange: Mock,
|
||||
config: dict[str, Any],
|
||||
component_setup: ComponentSetup,
|
||||
) -> None:
|
||||
"""Test successful creds setup."""
|
||||
assert await component_setup()
|
||||
|
||||
await async_import_client_credential(
|
||||
hass, DOMAIN, ClientCredential(CLIENT_ID, CLIENT_SECRET), "imported-cred"
|
||||
)
|
||||
|
@ -240,10 +177,11 @@ async def test_full_flow_application_creds(
|
|||
async def test_code_error(
|
||||
hass: HomeAssistant,
|
||||
mock_code_flow: Mock,
|
||||
component_setup: ComponentSetup,
|
||||
) -> None:
|
||||
"""Test server error setting up the oauth flow."""
|
||||
assert await component_setup()
|
||||
await async_import_client_credential(
|
||||
hass, DOMAIN, ClientCredential(CLIENT_ID, CLIENT_SECRET), "imported-cred"
|
||||
)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.google.api.OAuth2WebServerFlow.step1_get_device_and_user_codes",
|
||||
|
@ -259,10 +197,11 @@ async def test_code_error(
|
|||
async def test_timeout_error(
|
||||
hass: HomeAssistant,
|
||||
mock_code_flow: Mock,
|
||||
component_setup: ComponentSetup,
|
||||
) -> None:
|
||||
"""Test timeout error setting up the oauth flow."""
|
||||
assert await component_setup()
|
||||
await async_import_client_credential(
|
||||
hass, DOMAIN, ClientCredential(CLIENT_ID, CLIENT_SECRET), "imported-cred"
|
||||
)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.google.api.OAuth2WebServerFlow.step1_get_device_and_user_codes",
|
||||
|
@ -279,10 +218,11 @@ async def test_timeout_error(
|
|||
async def test_expired_after_exchange(
|
||||
hass: HomeAssistant,
|
||||
mock_code_flow: Mock,
|
||||
component_setup: ComponentSetup,
|
||||
) -> None:
|
||||
"""Test credential exchange expires."""
|
||||
assert await component_setup()
|
||||
await async_import_client_credential(
|
||||
hass, DOMAIN, ClientCredential(CLIENT_ID, CLIENT_SECRET), "imported-cred"
|
||||
)
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
|
@ -310,10 +250,11 @@ async def test_exchange_error(
|
|||
hass: HomeAssistant,
|
||||
mock_code_flow: Mock,
|
||||
mock_exchange: Mock,
|
||||
component_setup: ComponentSetup,
|
||||
) -> None:
|
||||
"""Test an error while exchanging the code for credentials."""
|
||||
assert await component_setup()
|
||||
await async_import_client_credential(
|
||||
hass, DOMAIN, ClientCredential(CLIENT_ID, CLIENT_SECRET), "device_auth"
|
||||
)
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
|
@ -371,17 +312,13 @@ async def test_exchange_error(
|
|||
assert len(entries) == 1
|
||||
|
||||
|
||||
@pytest.mark.parametrize("google_config", [None])
|
||||
async def test_duplicate_config_entries(
|
||||
hass: HomeAssistant,
|
||||
mock_code_flow: Mock,
|
||||
mock_exchange: Mock,
|
||||
config: dict[str, Any],
|
||||
config_entry: MockConfigEntry,
|
||||
component_setup: ComponentSetup,
|
||||
) -> None:
|
||||
"""Test that the same account cannot be setup twice."""
|
||||
assert await component_setup()
|
||||
await async_import_client_credential(
|
||||
hass, DOMAIN, ClientCredential(CLIENT_ID, CLIENT_SECRET), "imported-cred"
|
||||
)
|
||||
|
@ -416,19 +353,14 @@ async def test_duplicate_config_entries(
|
|||
assert result.get("reason") == "already_configured"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"google_config,primary_calendar_email", [(None, "another-email@example.com")]
|
||||
)
|
||||
@pytest.mark.parametrize("primary_calendar_email", ["another-email@example.com"])
|
||||
async def test_multiple_config_entries(
|
||||
hass: HomeAssistant,
|
||||
mock_code_flow: Mock,
|
||||
mock_exchange: Mock,
|
||||
config: dict[str, Any],
|
||||
config_entry: MockConfigEntry,
|
||||
component_setup: ComponentSetup,
|
||||
) -> None:
|
||||
"""Test that multiple config entries can be set at once."""
|
||||
assert await component_setup()
|
||||
await async_import_client_credential(
|
||||
hass, DOMAIN, ClientCredential(CLIENT_ID, CLIENT_SECRET), "imported-cred"
|
||||
)
|
||||
|
@ -483,21 +415,6 @@ async def test_missing_configuration(
|
|||
assert result.get("reason") == "missing_credentials"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("google_config", [None])
|
||||
async def test_missing_configuration_yaml_empty(
|
||||
hass: HomeAssistant,
|
||||
component_setup: ComponentSetup,
|
||||
) -> None:
|
||||
"""Test setup with an empty yaml configuration and no credentials."""
|
||||
assert await component_setup()
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
assert result.get("type") == "abort"
|
||||
assert result.get("reason") == "missing_credentials"
|
||||
|
||||
|
||||
async def test_wrong_configuration(
|
||||
hass: HomeAssistant,
|
||||
) -> None:
|
||||
|
@ -524,36 +441,10 @@ async def test_wrong_configuration(
|
|||
assert result.get("reason") == "oauth_error"
|
||||
|
||||
|
||||
async def test_import_config_entry_from_existing_token(
|
||||
hass: HomeAssistant,
|
||||
mock_token_read: None,
|
||||
component_setup: ComponentSetup,
|
||||
) -> None:
|
||||
"""Test setup with an existing token file."""
|
||||
assert await component_setup()
|
||||
|
||||
entries = hass.config_entries.async_entries(DOMAIN)
|
||||
assert len(entries) == 1
|
||||
data = entries[0].data
|
||||
assert "token" in data
|
||||
data["token"].pop("expires_at")
|
||||
data["token"].pop("expires_in")
|
||||
assert data == {
|
||||
"auth_implementation": "device_auth",
|
||||
"token": {
|
||||
"access_token": "ACCESS_TOKEN",
|
||||
"refresh_token": "REFRESH_TOKEN",
|
||||
"scope": "https://www.googleapis.com/auth/calendar",
|
||||
"token_type": "Bearer",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
async def test_reauth_flow(
|
||||
hass: HomeAssistant,
|
||||
mock_code_flow: Mock,
|
||||
mock_exchange: Mock,
|
||||
component_setup: ComponentSetup,
|
||||
) -> None:
|
||||
"""Test can't configure when config entry already exists."""
|
||||
config_entry = MockConfigEntry(
|
||||
|
@ -564,12 +455,13 @@ async def test_reauth_flow(
|
|||
},
|
||||
)
|
||||
config_entry.add_to_hass(hass)
|
||||
await async_import_client_credential(
|
||||
hass, DOMAIN, ClientCredential(CLIENT_ID, CLIENT_SECRET), "device_auth"
|
||||
)
|
||||
|
||||
entries = hass.config_entries.async_entries(DOMAIN)
|
||||
assert len(entries) == 1
|
||||
|
||||
assert await component_setup()
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={
|
||||
|
@ -628,10 +520,11 @@ async def test_calendar_lookup_failure(
|
|||
hass: HomeAssistant,
|
||||
mock_code_flow: Mock,
|
||||
mock_exchange: Mock,
|
||||
component_setup: ComponentSetup,
|
||||
) -> None:
|
||||
"""Test successful config flow and title fetch fails gracefully."""
|
||||
assert await component_setup()
|
||||
await async_import_client_credential(
|
||||
hass, DOMAIN, ClientCredential(CLIENT_ID, CLIENT_SECRET), "device_auth"
|
||||
)
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
|
@ -656,7 +549,6 @@ async def test_calendar_lookup_failure(
|
|||
|
||||
async def test_options_flow_triggers_reauth(
|
||||
hass: HomeAssistant,
|
||||
component_setup: ComponentSetup,
|
||||
config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test load and unload of a ConfigEntry."""
|
||||
|
@ -665,7 +557,7 @@ async def test_options_flow_triggers_reauth(
|
|||
with patch(
|
||||
"homeassistant.components.google.async_setup_entry", return_value=True
|
||||
) as mock_setup:
|
||||
await component_setup()
|
||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
mock_setup.assert_called_once()
|
||||
|
||||
assert config_entry.state is ConfigEntryState.LOADED
|
||||
|
@ -689,7 +581,6 @@ async def test_options_flow_triggers_reauth(
|
|||
|
||||
async def test_options_flow_no_changes(
|
||||
hass: HomeAssistant,
|
||||
component_setup: ComponentSetup,
|
||||
config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test load and unload of a ConfigEntry."""
|
||||
|
@ -698,7 +589,7 @@ async def test_options_flow_no_changes(
|
|||
with patch(
|
||||
"homeassistant.components.google.async_setup_entry", return_value=True
|
||||
) as mock_setup:
|
||||
await component_setup()
|
||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
mock_setup.assert_called_once()
|
||||
|
||||
assert config_entry.state is ConfigEntryState.LOADED
|
||||
|
|
|
@ -12,10 +12,6 @@ from aiohttp.client_exceptions import ClientError
|
|||
import pytest
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.application_credentials import (
|
||||
ClientCredential,
|
||||
async_import_client_credential,
|
||||
)
|
||||
from homeassistant.components.google import DOMAIN, SERVICE_ADD_EVENT
|
||||
from homeassistant.components.google.calendar import SERVICE_CREATE_EVENT
|
||||
from homeassistant.components.google.const import CONF_CALENDAR_ACCESS
|
||||
|
@ -23,7 +19,6 @@ 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
|
||||
from homeassistant.setup import async_setup_component
|
||||
from homeassistant.util.dt import utcnow
|
||||
|
||||
from .conftest import (
|
||||
|
@ -32,7 +27,6 @@ from .conftest import (
|
|||
TEST_API_ENTITY,
|
||||
TEST_API_ENTITY_NAME,
|
||||
TEST_YAML_ENTITY,
|
||||
TEST_YAML_ENTITY_NAME,
|
||||
ApiResult,
|
||||
ComponentSetup,
|
||||
)
|
||||
|
@ -59,15 +53,6 @@ def assert_state(actual: State | None, expected: State | None) -> None:
|
|||
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)
|
||||
return config_entry
|
||||
|
||||
|
||||
@pytest.fixture(
|
||||
params=[
|
||||
(
|
||||
|
@ -110,7 +95,6 @@ def add_event_call_service(
|
|||
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()
|
||||
|
@ -134,8 +118,7 @@ async def test_existing_token_missing_scope(
|
|||
config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test setup where existing token does not have sufficient scopes."""
|
||||
config_entry.add_to_hass(hass)
|
||||
assert await component_setup()
|
||||
await component_setup()
|
||||
|
||||
entries = hass.config_entries.async_entries(DOMAIN)
|
||||
assert len(entries) == 1
|
||||
|
@ -154,8 +137,7 @@ async def test_config_entry_scope_reauth(
|
|||
config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test setup where the config entry options requires reauth to match the scope."""
|
||||
config_entry.add_to_hass(hass)
|
||||
assert await component_setup()
|
||||
await component_setup()
|
||||
|
||||
assert config_entry.state is ConfigEntryState.SETUP_ERROR
|
||||
|
||||
|
@ -170,7 +152,6 @@ async def test_calendar_yaml_missing_required_fields(
|
|||
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()
|
||||
|
@ -187,7 +168,6 @@ async def test_invalid_calendar_yaml(
|
|||
mock_calendars_list: ApiResult,
|
||||
test_api_calendar: dict[str, Any],
|
||||
mock_events_list: ApiResult,
|
||||
setup_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test setup with missing entity id fields fails to load the platform."""
|
||||
mock_calendars_list({"items": [test_api_calendar]})
|
||||
|
@ -210,7 +190,6 @@ async def test_calendar_yaml_error(
|
|||
mock_calendars_list: ApiResult,
|
||||
test_api_calendar: dict[str, Any],
|
||||
mock_events_list: ApiResult,
|
||||
setup_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test setup with yaml file not found."""
|
||||
mock_calendars_list({"items": [test_api_calendar]})
|
||||
|
@ -229,7 +208,6 @@ async def test_init_calendar(
|
|||
mock_calendars_list: ApiResult,
|
||||
test_api_calendar: dict[str, Any],
|
||||
mock_events_list: ApiResult,
|
||||
setup_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test finding a calendar from the API."""
|
||||
|
||||
|
@ -246,37 +224,6 @@ async def test_init_calendar(
|
|||
assert not hass.states.get(TEST_YAML_ENTITY)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"google_config,config_entry_options",
|
||||
[({}, {CONF_CALENDAR_ACCESS: "read_write"})],
|
||||
)
|
||||
async def test_load_application_credentials(
|
||||
hass: HomeAssistant,
|
||||
component_setup: ComponentSetup,
|
||||
mock_calendars_list: ApiResult,
|
||||
test_api_calendar: dict[str, Any],
|
||||
mock_events_list: ApiResult,
|
||||
setup_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test loading an application credentials and a config entry."""
|
||||
assert await async_setup_component(hass, "application_credentials", {})
|
||||
await async_import_client_credential(
|
||||
hass, DOMAIN, ClientCredential("client-id", "client-secret"), "device_auth"
|
||||
)
|
||||
|
||||
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,
|
||||
|
@ -330,71 +277,6 @@ async def test_multiple_config_entries(
|
|||
assert state.attributes.get(ATTR_FRIENDLY_NAME) == "Example calendar 2"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"calendars_config_track,expected_state,google_config_track_new",
|
||||
[
|
||||
(
|
||||
True,
|
||||
State(
|
||||
TEST_YAML_ENTITY,
|
||||
STATE_OFF,
|
||||
attributes={
|
||||
"offset_reached": False,
|
||||
"friendly_name": TEST_YAML_ENTITY_NAME,
|
||||
},
|
||||
),
|
||||
None,
|
||||
),
|
||||
(
|
||||
True,
|
||||
State(
|
||||
TEST_YAML_ENTITY,
|
||||
STATE_OFF,
|
||||
attributes={
|
||||
"offset_reached": False,
|
||||
"friendly_name": TEST_YAML_ENTITY_NAME,
|
||||
},
|
||||
),
|
||||
True,
|
||||
),
|
||||
(
|
||||
True,
|
||||
State(
|
||||
TEST_YAML_ENTITY,
|
||||
STATE_OFF,
|
||||
attributes={
|
||||
"offset_reached": False,
|
||||
"friendly_name": TEST_YAML_ENTITY_NAME,
|
||||
},
|
||||
),
|
||||
False, # Has no effect
|
||||
),
|
||||
(False, None, None),
|
||||
(False, None, True),
|
||||
(False, None, False),
|
||||
],
|
||||
)
|
||||
async def test_calendar_config_track_new(
|
||||
hass: HomeAssistant,
|
||||
component_setup: ComponentSetup,
|
||||
mock_calendars_yaml: None,
|
||||
mock_calendars_list: ApiResult,
|
||||
mock_events_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]})
|
||||
mock_events_list({})
|
||||
assert await component_setup()
|
||||
|
||||
state = hass.states.get(TEST_YAML_ENTITY)
|
||||
assert_state(state, expected_state)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"date_fields,expected_error,error_match",
|
||||
[
|
||||
|
@ -519,7 +401,6 @@ async def test_add_event_invalid_params(
|
|||
mock_calendars_list: ApiResult,
|
||||
test_api_calendar: dict[str, Any],
|
||||
mock_events_list: ApiResult,
|
||||
setup_config_entry: MockConfigEntry,
|
||||
add_event_call_service: Callable[dict[str, Any], Awaitable[None]],
|
||||
date_fields: dict[str, Any],
|
||||
expected_error: type[Exception],
|
||||
|
@ -561,7 +442,6 @@ async def test_add_event_date_in_x(
|
|||
date_fields: dict[str, Any],
|
||||
start_timedelta: datetime.timedelta,
|
||||
end_timedelta: datetime.timedelta,
|
||||
setup_config_entry: MockConfigEntry,
|
||||
aioclient_mock: AiohttpClientMocker,
|
||||
add_event_call_service: Callable[dict[str, Any], Awaitable[None]],
|
||||
) -> None:
|
||||
|
@ -597,7 +477,6 @@ async def test_add_event_date(
|
|||
test_api_calendar: dict[str, Any],
|
||||
mock_insert_event: Callable[[str, dict[str, Any]], None],
|
||||
mock_events_list: ApiResult,
|
||||
setup_config_entry: MockConfigEntry,
|
||||
aioclient_mock: AiohttpClientMocker,
|
||||
add_event_call_service: Callable[dict[str, Any], Awaitable[None]],
|
||||
) -> None:
|
||||
|
@ -638,7 +517,6 @@ async def test_add_event_date_time(
|
|||
mock_insert_event: Callable[[str, dict[str, Any]], None],
|
||||
test_api_calendar: dict[str, Any],
|
||||
mock_events_list: ApiResult,
|
||||
setup_config_entry: MockConfigEntry,
|
||||
aioclient_mock: AiohttpClientMocker,
|
||||
add_event_call_service: Callable[dict[str, Any], Awaitable[None]],
|
||||
) -> None:
|
||||
|
@ -685,7 +563,6 @@ async def test_add_event_failure(
|
|||
test_api_calendar: dict[str, Any],
|
||||
mock_events_list: ApiResult,
|
||||
mock_insert_event: Callable[[..., dict[str, Any]], None],
|
||||
setup_config_entry: MockConfigEntry,
|
||||
add_event_call_service: Callable[dict[str, Any], Awaitable[None]],
|
||||
) -> None:
|
||||
"""Test service calls with incorrect fields."""
|
||||
|
@ -711,7 +588,6 @@ async def test_add_event_failure(
|
|||
async def test_invalid_token_expiry_in_config_entry(
|
||||
hass: HomeAssistant,
|
||||
component_setup: ComponentSetup,
|
||||
setup_config_entry: MockConfigEntry,
|
||||
aioclient_mock: AiohttpClientMocker,
|
||||
) -> None:
|
||||
"""Exercise case in issue #69623 with invalid token expiration persisted."""
|
||||
|
@ -743,7 +619,6 @@ async def test_invalid_token_expiry_in_config_entry(
|
|||
async def test_expired_token_refresh_internal_error(
|
||||
hass: HomeAssistant,
|
||||
component_setup: ComponentSetup,
|
||||
setup_config_entry: MockConfigEntry,
|
||||
aioclient_mock: AiohttpClientMocker,
|
||||
) -> None:
|
||||
"""Generic errors on reauth are treated as a retryable setup error."""
|
||||
|
@ -753,7 +628,7 @@ async def test_expired_token_refresh_internal_error(
|
|||
status=http.HTTPStatus.INTERNAL_SERVER_ERROR,
|
||||
)
|
||||
|
||||
assert await component_setup()
|
||||
await component_setup()
|
||||
|
||||
entries = hass.config_entries.async_entries(DOMAIN)
|
||||
assert len(entries) == 1
|
||||
|
@ -767,7 +642,6 @@ async def test_expired_token_refresh_internal_error(
|
|||
async def test_expired_token_requires_reauth(
|
||||
hass: HomeAssistant,
|
||||
component_setup: ComponentSetup,
|
||||
setup_config_entry: MockConfigEntry,
|
||||
aioclient_mock: AiohttpClientMocker,
|
||||
) -> None:
|
||||
"""Test case where reauth is required for token that cannot be refreshed."""
|
||||
|
@ -777,7 +651,7 @@ async def test_expired_token_requires_reauth(
|
|||
status=http.HTTPStatus.BAD_REQUEST,
|
||||
)
|
||||
|
||||
assert await component_setup()
|
||||
await component_setup()
|
||||
|
||||
entries = hass.config_entries.async_entries(DOMAIN)
|
||||
assert len(entries) == 1
|
||||
|
@ -811,7 +685,6 @@ async def test_calendar_yaml_update(
|
|||
mock_calendars_list: ApiResult,
|
||||
test_api_calendar: dict[str, Any],
|
||||
mock_events_list: ApiResult,
|
||||
setup_config_entry: MockConfigEntry,
|
||||
calendars_config: dict[str, Any],
|
||||
expect_write_calls: bool,
|
||||
) -> None:
|
||||
|
@ -836,7 +709,6 @@ async def test_calendar_yaml_update(
|
|||
async def test_update_will_reload(
|
||||
hass: HomeAssistant,
|
||||
component_setup: ComponentSetup,
|
||||
setup_config_entry: Any,
|
||||
mock_calendars_list: ApiResult,
|
||||
test_api_calendar: dict[str, Any],
|
||||
mock_events_list: ApiResult,
|
||||
|
@ -887,12 +759,12 @@ async def test_assign_unique_id(
|
|||
test_api_calendar: dict[str, Any],
|
||||
mock_events_list: ApiResult,
|
||||
mock_calendar_get: Callable[[...], None],
|
||||
setup_config_entry: MockConfigEntry,
|
||||
config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test an existing config is updated to have unique id if it does not exist."""
|
||||
|
||||
assert setup_config_entry.state is ConfigEntryState.NOT_LOADED
|
||||
assert setup_config_entry.unique_id is None
|
||||
assert config_entry.state is ConfigEntryState.NOT_LOADED
|
||||
assert config_entry.unique_id is None
|
||||
|
||||
mock_calendar_get(
|
||||
"primary",
|
||||
|
@ -903,8 +775,8 @@ async def test_assign_unique_id(
|
|||
mock_events_list({})
|
||||
assert await component_setup()
|
||||
|
||||
assert setup_config_entry.state is ConfigEntryState.LOADED
|
||||
assert setup_config_entry.unique_id == EMAIL_ADDRESS
|
||||
assert config_entry.state is ConfigEntryState.LOADED
|
||||
assert config_entry.unique_id == EMAIL_ADDRESS
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
@ -923,16 +795,16 @@ async def test_assign_unique_id_failure(
|
|||
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],
|
||||
setup_config_entry: MockConfigEntry,
|
||||
request_status: http.HTTPStatus,
|
||||
config_entry_status: ConfigEntryState,
|
||||
) -> None:
|
||||
"""Test lookup failures during unique id assignment are handled gracefully."""
|
||||
|
||||
assert setup_config_entry.state is ConfigEntryState.NOT_LOADED
|
||||
assert setup_config_entry.unique_id is None
|
||||
assert config_entry.state is ConfigEntryState.NOT_LOADED
|
||||
assert config_entry.unique_id is None
|
||||
|
||||
mock_calendar_get(
|
||||
"primary",
|
||||
|
@ -942,7 +814,7 @@ async def test_assign_unique_id_failure(
|
|||
|
||||
mock_calendars_list({"items": [test_api_calendar]})
|
||||
mock_events_list({})
|
||||
assert await component_setup()
|
||||
await component_setup()
|
||||
|
||||
assert setup_config_entry.state is config_entry_status
|
||||
assert setup_config_entry.unique_id is None
|
||||
assert config_entry.state is config_entry_status
|
||||
assert config_entry.unique_id is None
|
||||
|
|
Loading…
Reference in New Issue