Cleanup code from nest yaml migration and OOB auth deprecation (#92311)
parent
c0d0c89293
commit
e7433c42b9
|
@ -22,10 +22,6 @@ from google_nest_sdm.exceptions import (
|
|||
import voluptuous as vol
|
||||
|
||||
from homeassistant.auth.permissions.const import POLICY_READ
|
||||
from homeassistant.components.application_credentials import (
|
||||
ClientCredential,
|
||||
async_import_client_credential,
|
||||
)
|
||||
from homeassistant.components.camera import Image, img_util
|
||||
from homeassistant.components.http import KEY_HASS_USER
|
||||
from homeassistant.components.http.view import HomeAssistantView
|
||||
|
@ -52,11 +48,6 @@ from homeassistant.helpers import (
|
|||
entity_registry as er,
|
||||
)
|
||||
from homeassistant.helpers.entity_registry import async_entries_for_device
|
||||
from homeassistant.helpers.issue_registry import (
|
||||
IssueSeverity,
|
||||
async_create_issue,
|
||||
async_delete_issue,
|
||||
)
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from . import api, config_flow
|
||||
|
@ -69,8 +60,6 @@ from .const import (
|
|||
DATA_SDM,
|
||||
DATA_SUBSCRIBER,
|
||||
DOMAIN,
|
||||
INSTALLED_AUTH_DOMAIN,
|
||||
WEB_AUTH_DOMAIN,
|
||||
)
|
||||
from .events import EVENT_NAME_MAP, NEST_EVENT
|
||||
from .legacy import async_setup_legacy, async_setup_legacy_entry
|
||||
|
@ -128,9 +117,6 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
|||
if DOMAIN not in config:
|
||||
return True # ConfigMode.SDM_APPLICATION_CREDENTIALS
|
||||
|
||||
# Note that configuration.yaml deprecation warnings are handled in the
|
||||
# config entry since we don't know what type of credentials we have and
|
||||
# whether or not they can be imported.
|
||||
hass.data[DOMAIN][DATA_NEST_CONFIG] = config[DOMAIN]
|
||||
|
||||
config_mode = config_flow.get_config_mode(hass)
|
||||
|
@ -185,15 +171,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||
if DATA_SDM not in entry.data or config_mode == config_flow.ConfigMode.LEGACY:
|
||||
return await async_setup_legacy_entry(hass, entry)
|
||||
|
||||
if config_mode == config_flow.ConfigMode.SDM:
|
||||
await async_import_config(hass, entry)
|
||||
elif entry.unique_id != entry.data[CONF_PROJECT_ID]:
|
||||
if entry.unique_id != entry.data[CONF_PROJECT_ID]:
|
||||
hass.config_entries.async_update_entry(
|
||||
entry, unique_id=entry.data[CONF_PROJECT_ID]
|
||||
)
|
||||
|
||||
async_delete_issue(hass, DOMAIN, "removed_app_auth")
|
||||
|
||||
subscriber = await api.new_subscriber(hass, entry)
|
||||
if not subscriber:
|
||||
return False
|
||||
|
@ -239,71 +221,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||
return True
|
||||
|
||||
|
||||
async def async_import_config(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
||||
"""Attempt to import configuration.yaml settings."""
|
||||
config = hass.data[DOMAIN][DATA_NEST_CONFIG]
|
||||
new_data = {
|
||||
CONF_PROJECT_ID: config[CONF_PROJECT_ID],
|
||||
**entry.data,
|
||||
}
|
||||
if CONF_SUBSCRIBER_ID not in entry.data:
|
||||
if CONF_SUBSCRIBER_ID not in config:
|
||||
raise ValueError("Configuration option 'subscriber_id' missing")
|
||||
new_data.update(
|
||||
{
|
||||
CONF_SUBSCRIBER_ID: config[CONF_SUBSCRIBER_ID],
|
||||
# Don't delete user managed subscriber
|
||||
CONF_SUBSCRIBER_ID_IMPORTED: True,
|
||||
}
|
||||
)
|
||||
hass.config_entries.async_update_entry(
|
||||
entry, data=new_data, unique_id=new_data[CONF_PROJECT_ID]
|
||||
)
|
||||
|
||||
if entry.data["auth_implementation"] == INSTALLED_AUTH_DOMAIN:
|
||||
# App Auth credentials have been deprecated and must be re-created
|
||||
# by the user in the config flow
|
||||
async_create_issue(
|
||||
hass,
|
||||
DOMAIN,
|
||||
"removed_app_auth",
|
||||
is_fixable=False,
|
||||
severity=IssueSeverity.ERROR,
|
||||
translation_key="removed_app_auth",
|
||||
translation_placeholders={
|
||||
"more_info_url": (
|
||||
"https://www.home-assistant.io/more-info/nest-auth-deprecation"
|
||||
),
|
||||
"documentation_url": "https://www.home-assistant.io/integrations/nest/",
|
||||
},
|
||||
)
|
||||
raise ConfigEntryAuthFailed(
|
||||
"Google has deprecated App Auth credentials, and the integration "
|
||||
"must be reconfigured in the UI to restore access to Nest Devices."
|
||||
)
|
||||
|
||||
if entry.data["auth_implementation"] == WEB_AUTH_DOMAIN:
|
||||
await async_import_client_credential(
|
||||
hass,
|
||||
DOMAIN,
|
||||
ClientCredential(
|
||||
config[CONF_CLIENT_ID],
|
||||
config[CONF_CLIENT_SECRET],
|
||||
),
|
||||
WEB_AUTH_DOMAIN,
|
||||
)
|
||||
|
||||
async_create_issue(
|
||||
hass,
|
||||
DOMAIN,
|
||||
"deprecated_yaml",
|
||||
breaks_in_ha_version="2022.10.0",
|
||||
is_fixable=False,
|
||||
severity=IssueSeverity.WARNING,
|
||||
translation_key="deprecated_yaml",
|
||||
)
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Unload a config entry."""
|
||||
if DATA_SDM not in entry.data:
|
||||
|
|
|
@ -43,7 +43,6 @@ from .const import (
|
|||
DATA_NEST_CONFIG,
|
||||
DATA_SDM,
|
||||
DOMAIN,
|
||||
INSTALLED_AUTH_DOMAIN,
|
||||
OAUTH2_AUTHORIZE,
|
||||
SDM_SCOPES,
|
||||
)
|
||||
|
@ -64,10 +63,6 @@ PUBSUB_API_URL = "https://console.cloud.google.com/apis/library/pubsub.googleapi
|
|||
# URLs for Configure Device Access Project step
|
||||
DEVICE_ACCESS_CONSOLE_URL = "https://console.nest.google.com/device-access/"
|
||||
|
||||
# URLs for App Auth deprecation and upgrade
|
||||
UPGRADE_MORE_INFO_URL = (
|
||||
"https://www.home-assistant.io/integrations/nest/#deprecated-app-auth-credentials"
|
||||
)
|
||||
DEVICE_ACCESS_CONSOLE_EDIT_URL = (
|
||||
"https://console.nest.google.com/device-access/project/{project_id}/information"
|
||||
)
|
||||
|
@ -161,7 +156,6 @@ class NestFlowHandler(
|
|||
def __init__(self) -> None:
|
||||
"""Initialize NestFlowHandler."""
|
||||
super().__init__()
|
||||
self._upgrade = False
|
||||
self._data: dict[str, Any] = {DATA_SDM: {}}
|
||||
# Possible name to use for config entry based on the Google Home name
|
||||
self._structure_config_title: str | None = None
|
||||
|
@ -233,38 +227,8 @@ class NestFlowHandler(
|
|||
assert self.config_mode != ConfigMode.LEGACY, "Step only supported for SDM API"
|
||||
if user_input is None:
|
||||
return self.async_show_form(step_id="reauth_confirm")
|
||||
if self._data["auth_implementation"] == INSTALLED_AUTH_DOMAIN:
|
||||
# The config entry points to an auth mechanism that no longer works and the
|
||||
# user needs to take action in the google cloud console to resolve. First
|
||||
# prompt to create app creds, then later ensure they've updated the device
|
||||
# access console.
|
||||
self._upgrade = True
|
||||
implementations = await config_entry_oauth2_flow.async_get_implementations(
|
||||
self.hass, self.DOMAIN
|
||||
)
|
||||
if not implementations:
|
||||
return await self.async_step_auth_upgrade()
|
||||
return await self.async_step_user()
|
||||
|
||||
async def async_step_auth_upgrade(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> FlowResult:
|
||||
"""Give instructions for upgrade of deprecated app auth."""
|
||||
assert self.config_mode != ConfigMode.LEGACY, "Step only supported for SDM API"
|
||||
if user_input is None:
|
||||
return self.async_show_form(
|
||||
step_id="auth_upgrade",
|
||||
description_placeholders={
|
||||
"more_info_url": UPGRADE_MORE_INFO_URL,
|
||||
},
|
||||
)
|
||||
# Abort this flow and ask the user for application credentials. The frontend
|
||||
# will restart a new config flow after the user finishes so schedule a new
|
||||
# re-auth config flow for the same entry so the user may resume.
|
||||
if reauth_entry := self._async_reauth_entry():
|
||||
self.hass.async_add_job(reauth_entry.async_start_reauth, self.hass)
|
||||
return self.async_abort(reason="missing_credentials")
|
||||
|
||||
async def async_step_user(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> FlowResult:
|
||||
|
@ -358,39 +322,6 @@ class NestFlowHandler(
|
|||
errors=errors,
|
||||
)
|
||||
|
||||
async def async_step_auth(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> FlowResult:
|
||||
"""Verify any last pre-requisites before sending user through OAuth flow."""
|
||||
if user_input is None and self._upgrade:
|
||||
# During app auth upgrade we need the user to update their device
|
||||
# access project before we redirect to the authentication flow.
|
||||
return await self.async_step_device_project_upgrade()
|
||||
return await super().async_step_auth(user_input)
|
||||
|
||||
async def async_step_device_project_upgrade(
|
||||
self, user_input: dict | None = None
|
||||
) -> FlowResult:
|
||||
"""Update the device access project."""
|
||||
if user_input is not None:
|
||||
# Resume OAuth2 redirects
|
||||
return await super().async_step_auth()
|
||||
if not isinstance(
|
||||
self.flow_impl, config_entry_oauth2_flow.LocalOAuth2Implementation
|
||||
):
|
||||
raise TypeError(f"Unexpected OAuth implementation: {self.flow_impl}")
|
||||
client_id = self.flow_impl.client_id
|
||||
return self.async_show_form(
|
||||
step_id="device_project_upgrade",
|
||||
description_placeholders={
|
||||
"device_access_console_url": DEVICE_ACCESS_CONSOLE_EDIT_URL.format(
|
||||
project_id=self._data[CONF_PROJECT_ID]
|
||||
),
|
||||
"more_info_url": UPGRADE_MORE_INFO_URL,
|
||||
"client_id": client_id,
|
||||
},
|
||||
)
|
||||
|
||||
async def async_step_pubsub(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> FlowResult:
|
||||
|
|
|
@ -4,14 +4,6 @@
|
|||
},
|
||||
"config": {
|
||||
"step": {
|
||||
"auth_upgrade": {
|
||||
"title": "Nest: App Auth Deprecation",
|
||||
"description": "App Auth has been deprecated by Google to improve security, and you need to take action by creating new application credentials.\n\nOpen the [documentation]({more_info_url}) to follow along as the next steps will guide you through the steps you need to take to restore access to your Nest devices."
|
||||
},
|
||||
"device_project_upgrade": {
|
||||
"title": "Nest: Update Device Access Project",
|
||||
"description": "Update the Nest Device Access Project with your new OAuth Client ID ([more info]({more_info_url}))\n1. Go to the [Device Access Console]({device_access_console_url}).\n1. Click the trash icon next to *OAuth Client ID*.\n1. Click the `...` overflow menu and *Add Client ID*.\n1. Enter your new OAuth Client ID and click **Add**.\n\nYour OAuth Client ID is: `{client_id}`"
|
||||
},
|
||||
"create_cloud_project": {
|
||||
"title": "Nest: Create and configure Cloud Project",
|
||||
"description": "The Nest integration allows you to integrate your Nest Thermostats, Cameras, and Doorbells using the Smart Device Management API. The SDM API **requires a US $5** one time setup fee. See documentation for [more info]({more_info_url}).\n\n1. Go to the [Google Cloud Console]({cloud_console_url}).\n1. If this is your first project, click **Create Project** then **New Project**.\n1. Give your Cloud Project a Name and then click **Create**.\n1. Save the Cloud Project ID e.g. *example-project-12345* as you will need it later\n1. Go to API Library for [Smart Device Management API]({sdm_api_url}) and click **Enable**.\n1. Go to API Library for [Cloud Pub/Sub API]({pubsub_api_url}) and click **Enable**.\n\nProceed when your cloud project is set up."
|
||||
|
@ -90,14 +82,6 @@
|
|||
}
|
||||
},
|
||||
"issues": {
|
||||
"deprecated_yaml": {
|
||||
"title": "The Nest YAML configuration is being removed",
|
||||
"description": "Configuring Nest in configuration.yaml is being removed in Home Assistant 2022.10.\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_app_auth": {
|
||||
"title": "Nest Authentication Credentials must be updated",
|
||||
"description": "To improve security and reduce phishing risk Google has deprecated the authentication method used by Home Assistant.\n\n**This requires action by you to resolve** ([more info]({more_info_url}))\n\n1. Visit the integrations page\n1. Click Reconfigure on the Nest integration.\n1. Home Assistant will walk you through the steps to upgrade to Web Authentication.\n\nSee the Nest [integration instructions]({documentation_url}) for troubleshooting information."
|
||||
},
|
||||
"legacy_nest_deprecated": {
|
||||
"title": "Legacy Works With Nest is being removed",
|
||||
"description": "Legacy Works With Nest is being removed from Home Assistant.\n\nYou must take action to use the SDM API. Remove all `nest` configuration from `configuration.yaml` and restart Home Assistant, then see the Nest [integration instructions]({documentation_url}) for set up instructions and supported devices."
|
||||
|
|
|
@ -17,9 +17,6 @@ from google_nest_sdm.google_nest_subscriber import GoogleNestSubscriber
|
|||
|
||||
from homeassistant.components.application_credentials import ClientCredential
|
||||
from homeassistant.components.nest import DOMAIN
|
||||
from homeassistant.components.nest.const import SDM_SCOPES
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
# Typing helpers
|
||||
PlatformSetup = Callable[[], Awaitable[None]]
|
||||
|
@ -36,98 +33,28 @@ CLOUD_PROJECT_ID = "cloud-id-9876"
|
|||
SUBSCRIBER_ID = "projects/cloud-id-9876/subscriptions/subscriber-id-9876"
|
||||
|
||||
|
||||
CONFIG = {
|
||||
"nest": {
|
||||
"client_id": CLIENT_ID,
|
||||
"client_secret": CLIENT_SECRET,
|
||||
"project_id": PROJECT_ID,
|
||||
"subscriber_id": SUBSCRIBER_ID,
|
||||
},
|
||||
}
|
||||
|
||||
FAKE_TOKEN = "some-token"
|
||||
FAKE_REFRESH_TOKEN = "some-refresh-token"
|
||||
|
||||
|
||||
def create_token_entry(token_expiration_time=None):
|
||||
"""Create OAuth 'token' data for a ConfigEntry."""
|
||||
if token_expiration_time is None:
|
||||
token_expiration_time = time.time() + 86400
|
||||
return {
|
||||
"access_token": FAKE_TOKEN,
|
||||
"refresh_token": FAKE_REFRESH_TOKEN,
|
||||
"scope": " ".join(SDM_SCOPES),
|
||||
"token_type": "Bearer",
|
||||
"expires_at": token_expiration_time,
|
||||
}
|
||||
|
||||
|
||||
def create_config_entry(token_expiration_time=None) -> MockConfigEntry:
|
||||
"""Create a ConfigEntry and add it to Home Assistant."""
|
||||
config_entry_data = {
|
||||
"sdm": {}, # Indicates new SDM API, not legacy API
|
||||
"auth_implementation": "nest",
|
||||
"token": create_token_entry(token_expiration_time),
|
||||
}
|
||||
return MockConfigEntry(domain=DOMAIN, data=config_entry_data)
|
||||
|
||||
|
||||
@dataclass
|
||||
class NestTestConfig:
|
||||
"""Holder for integration configuration."""
|
||||
|
||||
config: dict[str, Any] = field(default_factory=dict)
|
||||
config_entry_data: dict[str, Any] | None = None
|
||||
auth_implementation: str = WEB_AUTH_DOMAIN
|
||||
credential: ClientCredential | None = None
|
||||
|
||||
|
||||
# Exercises mode where all configuration is in configuration.yaml
|
||||
TEST_CONFIG_YAML_ONLY = NestTestConfig(
|
||||
config=CONFIG,
|
||||
config_entry_data={
|
||||
"sdm": {},
|
||||
"token": create_token_entry(),
|
||||
},
|
||||
)
|
||||
TEST_CONFIGFLOW_YAML_ONLY = NestTestConfig(
|
||||
config=TEST_CONFIG_YAML_ONLY.config,
|
||||
)
|
||||
|
||||
# Exercises mode where subscriber id is created in the config flow, but
|
||||
# all authentication is defined in configuration.yaml
|
||||
TEST_CONFIG_HYBRID = NestTestConfig(
|
||||
config={
|
||||
"nest": {
|
||||
"client_id": CLIENT_ID,
|
||||
"client_secret": CLIENT_SECRET,
|
||||
"project_id": PROJECT_ID,
|
||||
},
|
||||
},
|
||||
config_entry_data={
|
||||
"sdm": {},
|
||||
"token": create_token_entry(),
|
||||
"cloud_project_id": CLOUD_PROJECT_ID,
|
||||
"subscriber_id": SUBSCRIBER_ID,
|
||||
},
|
||||
)
|
||||
TEST_CONFIGFLOW_HYBRID = NestTestConfig(TEST_CONFIG_HYBRID.config)
|
||||
|
||||
# Exercises mode where all configuration is from the config flow
|
||||
TEST_CONFIG_APP_CREDS = NestTestConfig(
|
||||
config_entry_data={
|
||||
"sdm": {},
|
||||
"token": create_token_entry(),
|
||||
"project_id": PROJECT_ID,
|
||||
"cloud_project_id": CLOUD_PROJECT_ID,
|
||||
"subscriber_id": SUBSCRIBER_ID,
|
||||
"auth_implementation": "imported-cred",
|
||||
},
|
||||
auth_implementation="imported-cred",
|
||||
credential=ClientCredential(CLIENT_ID, CLIENT_SECRET),
|
||||
)
|
||||
TEST_CONFIGFLOW_APP_CREDS = NestTestConfig(
|
||||
config=TEST_CONFIG_APP_CREDS.config,
|
||||
auth_implementation="imported-cred",
|
||||
credential=ClientCredential(CLIENT_ID, CLIENT_SECRET),
|
||||
)
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ from __future__ import annotations
|
|||
from collections.abc import Generator
|
||||
import copy
|
||||
import shutil
|
||||
import time
|
||||
from typing import Any
|
||||
from unittest.mock import AsyncMock, patch
|
||||
import uuid
|
||||
|
@ -18,7 +19,7 @@ from homeassistant.components.application_credentials import (
|
|||
async_import_client_credential,
|
||||
)
|
||||
from homeassistant.components.nest import DOMAIN
|
||||
from homeassistant.components.nest.const import CONF_SUBSCRIBER_ID
|
||||
from homeassistant.components.nest.const import CONF_SUBSCRIBER_ID, SDM_SCOPES
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
|
@ -27,7 +28,6 @@ from .common import (
|
|||
PROJECT_ID,
|
||||
SUBSCRIBER_ID,
|
||||
TEST_CONFIG_APP_CREDS,
|
||||
TEST_CONFIG_YAML_ONLY,
|
||||
CreateDevice,
|
||||
FakeSubscriber,
|
||||
NestTestConfig,
|
||||
|
@ -37,6 +37,9 @@ from .common import (
|
|||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
FAKE_TOKEN = "some-token"
|
||||
FAKE_REFRESH_TOKEN = "some-refresh-token"
|
||||
|
||||
|
||||
class FakeAuth(AbstractAuth):
|
||||
"""A fake implementation of the auth class that records requests.
|
||||
|
@ -186,18 +189,9 @@ def subscriber_id() -> str:
|
|||
|
||||
|
||||
@pytest.fixture
|
||||
def auth_implementation(nest_test_config: NestTestConfig) -> str | None:
|
||||
"""Fixture to let tests override the auth implementation in the config entry."""
|
||||
return nest_test_config.auth_implementation
|
||||
|
||||
|
||||
@pytest.fixture(
|
||||
params=[TEST_CONFIG_YAML_ONLY, TEST_CONFIG_APP_CREDS],
|
||||
ids=["yaml-config-only", "app-creds"],
|
||||
)
|
||||
def nest_test_config(request) -> NestTestConfig:
|
||||
"""Fixture that sets up the configuration used for the test."""
|
||||
return request.param
|
||||
return TEST_CONFIG_APP_CREDS
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
@ -220,12 +214,30 @@ def config_entry_unique_id() -> str:
|
|||
return PROJECT_ID
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def token_expiration_time() -> float:
|
||||
"""Fixture for expiration time of the config entry auth token."""
|
||||
return time.time() + 86400
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def token_entry(token_expiration_time: float) -> dict[str, Any]:
|
||||
"""Fixture for OAuth 'token' data for a ConfigEntry."""
|
||||
return {
|
||||
"access_token": FAKE_TOKEN,
|
||||
"refresh_token": FAKE_REFRESH_TOKEN,
|
||||
"scope": " ".join(SDM_SCOPES),
|
||||
"token_type": "Bearer",
|
||||
"expires_at": token_expiration_time,
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def config_entry(
|
||||
subscriber_id: str | None,
|
||||
auth_implementation: str | None,
|
||||
nest_test_config: NestTestConfig,
|
||||
config_entry_unique_id: str,
|
||||
token_entry: dict[str, Any],
|
||||
) -> MockConfigEntry | None:
|
||||
"""Fixture that sets up the ConfigEntry for the test."""
|
||||
if nest_test_config.config_entry_data is None:
|
||||
|
@ -236,7 +248,7 @@ def config_entry(
|
|||
data[CONF_SUBSCRIBER_ID] = subscriber_id
|
||||
else:
|
||||
del data[CONF_SUBSCRIBER_ID]
|
||||
data["auth_implementation"] = auth_implementation
|
||||
data["token"] = token_entry
|
||||
return MockConfigEntry(domain=DOMAIN, data=data, unique_id=config_entry_unique_id)
|
||||
|
||||
|
||||
|
@ -247,10 +259,7 @@ async def credential(hass: HomeAssistant, nest_test_config: NestTestConfig) -> N
|
|||
return
|
||||
assert await async_setup_component(hass, "application_credentials", {})
|
||||
await async_import_client_credential(
|
||||
hass,
|
||||
DOMAIN,
|
||||
nest_test_config.credential,
|
||||
nest_test_config.auth_implementation,
|
||||
hass, DOMAIN, nest_test_config.credential, "imported-cred"
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -12,43 +12,38 @@ from unittest.mock import patch
|
|||
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.nest import DOMAIN
|
||||
from homeassistant.components.nest.const import API_URL, OAUTH2_TOKEN, SDM_SCOPES
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.setup import async_setup_component
|
||||
from homeassistant.util import dt
|
||||
|
||||
from .common import (
|
||||
CLIENT_ID,
|
||||
CLIENT_SECRET,
|
||||
CONFIG,
|
||||
FAKE_REFRESH_TOKEN,
|
||||
FAKE_TOKEN,
|
||||
PROJECT_ID,
|
||||
TEST_CONFIGFLOW_YAML_ONLY,
|
||||
create_config_entry,
|
||||
)
|
||||
from .common import CLIENT_ID, CLIENT_SECRET, PROJECT_ID, PlatformSetup
|
||||
from .conftest import FAKE_REFRESH_TOKEN, FAKE_TOKEN
|
||||
|
||||
from tests.test_util.aiohttp import AiohttpClientMocker
|
||||
|
||||
FAKE_UPDATED_TOKEN = "fake-updated-token"
|
||||
|
||||
|
||||
async def async_setup_sdm(hass):
|
||||
"""Set up the integration."""
|
||||
assert await async_setup_component(hass, DOMAIN, CONFIG)
|
||||
await hass.async_block_till_done()
|
||||
@pytest.fixture
|
||||
def subscriber() -> None:
|
||||
"""Disable default subscriber since tests use their own patch."""
|
||||
return None
|
||||
|
||||
|
||||
# This tests needs to be adjusted to remove lingering tasks
|
||||
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
||||
@pytest.mark.parametrize("nest_test_config", [TEST_CONFIGFLOW_YAML_ONLY])
|
||||
async def test_auth(hass: HomeAssistant, aioclient_mock: AiohttpClientMocker) -> None:
|
||||
@pytest.mark.parametrize(
|
||||
"token_expiration_time",
|
||||
[time.time() + 7 * 86400],
|
||||
ids=["expires-in-future"],
|
||||
)
|
||||
async def test_auth(
|
||||
hass: HomeAssistant,
|
||||
aioclient_mock: AiohttpClientMocker,
|
||||
setup_platform: PlatformSetup,
|
||||
token_expiration_time: float,
|
||||
) -> None:
|
||||
"""Exercise authentication library creates valid credentials."""
|
||||
|
||||
expiration_time = time.time() + 86400
|
||||
create_config_entry(expiration_time).add_to_hass(hass)
|
||||
|
||||
# Prepare to capture credentials in API request. Empty payloads just mean
|
||||
# no devices or structures are loaded.
|
||||
aioclient_mock.get(f"{API_URL}/enterprises/{PROJECT_ID}/structures", json={})
|
||||
|
@ -69,7 +64,7 @@ async def test_auth(hass: HomeAssistant, aioclient_mock: AiohttpClientMocker) ->
|
|||
"google_nest_sdm.google_nest_subscriber.DefaultSubscriberFactory.async_new_subscriber",
|
||||
side_effect=async_new_subscriber,
|
||||
) as new_subscriber_mock:
|
||||
await async_setup_sdm(hass)
|
||||
await setup_platform()
|
||||
|
||||
# Verify API requests are made with the correct credentials
|
||||
calls = aioclient_mock.mock_calls
|
||||
|
@ -85,7 +80,7 @@ async def test_auth(hass: HomeAssistant, aioclient_mock: AiohttpClientMocker) ->
|
|||
creds = captured_creds
|
||||
assert creds.token == FAKE_TOKEN
|
||||
assert creds.refresh_token == FAKE_REFRESH_TOKEN
|
||||
assert int(dt.as_timestamp(creds.expiry)) == int(expiration_time)
|
||||
assert int(dt.as_timestamp(creds.expiry)) == int(token_expiration_time)
|
||||
assert creds.valid
|
||||
assert not creds.expired
|
||||
assert creds.token_uri == OAUTH2_TOKEN
|
||||
|
@ -96,15 +91,18 @@ async def test_auth(hass: HomeAssistant, aioclient_mock: AiohttpClientMocker) ->
|
|||
|
||||
# This tests needs to be adjusted to remove lingering tasks
|
||||
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
||||
@pytest.mark.parametrize("nest_test_config", [TEST_CONFIGFLOW_YAML_ONLY])
|
||||
@pytest.mark.parametrize(
|
||||
"token_expiration_time",
|
||||
[time.time() - 7 * 86400],
|
||||
ids=["expires-in-past"],
|
||||
)
|
||||
async def test_auth_expired_token(
|
||||
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker
|
||||
hass: HomeAssistant,
|
||||
aioclient_mock: AiohttpClientMocker,
|
||||
setup_platform: PlatformSetup,
|
||||
token_expiration_time: float,
|
||||
) -> None:
|
||||
"""Verify behavior of an expired token."""
|
||||
|
||||
expiration_time = time.time() - 86400
|
||||
create_config_entry(expiration_time).add_to_hass(hass)
|
||||
|
||||
# Prepare a token refresh response
|
||||
aioclient_mock.post(
|
||||
OAUTH2_TOKEN,
|
||||
|
@ -134,7 +132,7 @@ async def test_auth_expired_token(
|
|||
"google_nest_sdm.google_nest_subscriber.DefaultSubscriberFactory.async_new_subscriber",
|
||||
side_effect=async_new_subscriber,
|
||||
) as new_subscriber_mock:
|
||||
await async_setup_sdm(hass)
|
||||
await setup_platform()
|
||||
|
||||
calls = aioclient_mock.mock_calls
|
||||
assert len(calls) == 3
|
||||
|
@ -159,7 +157,7 @@ async def test_auth_expired_token(
|
|||
creds = captured_creds
|
||||
assert creds.token == FAKE_TOKEN
|
||||
assert creds.refresh_token == FAKE_REFRESH_TOKEN
|
||||
assert int(dt.as_timestamp(creds.expiry)) == int(expiration_time)
|
||||
assert int(dt.as_timestamp(creds.expiry)) == int(token_expiration_time)
|
||||
assert not creds.valid
|
||||
assert creds.expired
|
||||
assert creds.token_uri == OAUTH2_TOKEN
|
||||
|
|
|
@ -14,10 +14,6 @@ import pytest
|
|||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components import dhcp
|
||||
from homeassistant.components.application_credentials import (
|
||||
ClientCredential,
|
||||
async_import_client_credential,
|
||||
)
|
||||
from homeassistant.components.nest.const import DOMAIN, OAUTH2_AUTHORIZE, OAUTH2_TOKEN
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
@ -25,23 +21,17 @@ from homeassistant.data_entry_flow import FlowResult
|
|||
from homeassistant.helpers import config_entry_oauth2_flow
|
||||
|
||||
from .common import (
|
||||
APP_AUTH_DOMAIN,
|
||||
CLIENT_ID,
|
||||
CLIENT_SECRET,
|
||||
CLOUD_PROJECT_ID,
|
||||
FAKE_TOKEN,
|
||||
PROJECT_ID,
|
||||
SUBSCRIBER_ID,
|
||||
TEST_CONFIG_APP_CREDS,
|
||||
TEST_CONFIG_HYBRID,
|
||||
TEST_CONFIG_YAML_ONLY,
|
||||
TEST_CONFIGFLOW_APP_CREDS,
|
||||
TEST_CONFIGFLOW_YAML_ONLY,
|
||||
WEB_AUTH_DOMAIN,
|
||||
MockConfigEntry,
|
||||
NestTestConfig,
|
||||
)
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
WEB_REDIRECT_URL = "https://example.com/auth/external/callback"
|
||||
APP_REDIRECT_URL = "urn:ietf:wg:oauth:2.0:oob"
|
||||
|
||||
|
@ -51,6 +41,12 @@ FAKE_DHCP_DATA = dhcp.DhcpServiceInfo(
|
|||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def nest_test_config(request) -> NestTestConfig:
|
||||
"""Fixture with empty configuration and no existing config entry."""
|
||||
return TEST_CONFIGFLOW_APP_CREDS
|
||||
|
||||
|
||||
class OAuthFixture:
|
||||
"""Simulate the oauth flow used by the config flow."""
|
||||
|
||||
|
@ -196,7 +192,6 @@ async def oauth(hass, hass_client_no_auth, aioclient_mock, current_request_with_
|
|||
return OAuthFixture(hass, hass_client_no_auth, aioclient_mock)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("nest_test_config", [TEST_CONFIGFLOW_APP_CREDS])
|
||||
async def test_app_credentials(
|
||||
hass: HomeAssistant, oauth, subscriber, setup_platform
|
||||
) -> None:
|
||||
|
@ -230,7 +225,6 @@ async def test_app_credentials(
|
|||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("nest_test_config", [TEST_CONFIGFLOW_APP_CREDS])
|
||||
async def test_config_flow_restart(
|
||||
hass: HomeAssistant, oauth, subscriber, setup_platform
|
||||
) -> None:
|
||||
|
@ -283,7 +277,6 @@ async def test_config_flow_restart(
|
|||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("nest_test_config", [TEST_CONFIGFLOW_APP_CREDS])
|
||||
async def test_config_flow_wrong_project_id(
|
||||
hass: HomeAssistant, oauth, subscriber, setup_platform
|
||||
) -> None:
|
||||
|
@ -335,7 +328,6 @@ async def test_config_flow_wrong_project_id(
|
|||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("nest_test_config", [TEST_CONFIGFLOW_APP_CREDS])
|
||||
async def test_config_flow_pubsub_configuration_error(
|
||||
hass: HomeAssistant,
|
||||
oauth,
|
||||
|
@ -358,7 +350,6 @@ async def test_config_flow_pubsub_configuration_error(
|
|||
assert result["errors"]["cloud_project_id"] == "bad_project_id"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("nest_test_config", [TEST_CONFIGFLOW_APP_CREDS])
|
||||
async def test_config_flow_pubsub_subscriber_error(
|
||||
hass: HomeAssistant, oauth, setup_platform, mock_subscriber
|
||||
) -> None:
|
||||
|
@ -379,50 +370,7 @@ async def test_config_flow_pubsub_subscriber_error(
|
|||
assert result["errors"]["cloud_project_id"] == "subscriber_error"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("nest_test_config", [TEST_CONFIGFLOW_YAML_ONLY])
|
||||
async def test_config_yaml_ignored(hass: HomeAssistant, oauth, setup_platform) -> None:
|
||||
"""Check full flow."""
|
||||
await setup_platform()
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert result["type"] == "form"
|
||||
assert result["step_id"] == "create_cloud_project"
|
||||
|
||||
result = await oauth.async_configure(result, {})
|
||||
assert result.get("type") == "abort"
|
||||
assert result.get("reason") == "missing_credentials"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("nest_test_config", [TEST_CONFIG_YAML_ONLY])
|
||||
async def test_web_reauth(
|
||||
hass: HomeAssistant, oauth, setup_platform, config_entry
|
||||
) -> None:
|
||||
"""Test Nest reauthentication."""
|
||||
await setup_platform()
|
||||
|
||||
assert config_entry.data["token"].get("access_token") == FAKE_TOKEN
|
||||
|
||||
orig_subscriber_id = config_entry.data.get("subscriber_id")
|
||||
result = await oauth.async_reauth(config_entry)
|
||||
|
||||
await oauth.async_oauth_web_flow(result)
|
||||
entry = await oauth.async_finish_setup(result)
|
||||
# Verify existing tokens are replaced
|
||||
entry.data["token"].pop("expires_at")
|
||||
assert entry.unique_id == PROJECT_ID
|
||||
assert entry.data["token"] == {
|
||||
"refresh_token": "mock-refresh-token",
|
||||
"access_token": "mock-access-token",
|
||||
"type": "Bearer",
|
||||
"expires_in": 60,
|
||||
}
|
||||
assert entry.data["auth_implementation"] == WEB_AUTH_DOMAIN
|
||||
assert entry.data.get("subscriber_id") == orig_subscriber_id # Not updated
|
||||
|
||||
|
||||
@pytest.mark.parametrize("nest_test_config", [TEST_CONFIG_APP_CREDS])
|
||||
async def test_multiple_config_entries(
|
||||
hass: HomeAssistant, oauth, setup_platform
|
||||
) -> None:
|
||||
|
@ -444,6 +392,7 @@ async def test_multiple_config_entries(
|
|||
assert len(entries) == 2
|
||||
|
||||
|
||||
@pytest.mark.parametrize("nest_test_config", [TEST_CONFIG_APP_CREDS])
|
||||
async def test_duplicate_config_entries(
|
||||
hass: HomeAssistant, oauth, setup_platform
|
||||
) -> None:
|
||||
|
@ -468,6 +417,7 @@ async def test_duplicate_config_entries(
|
|||
assert result.get("reason") == "already_configured"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("nest_test_config", [TEST_CONFIG_APP_CREDS])
|
||||
async def test_reauth_multiple_config_entries(
|
||||
hass: HomeAssistant, oauth, setup_platform, config_entry
|
||||
) -> None:
|
||||
|
@ -517,102 +467,6 @@ async def test_reauth_multiple_config_entries(
|
|||
assert entry.data.get("extra_data")
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("nest_test_config", "auth_implementation"), [(TEST_CONFIG_HYBRID, APP_AUTH_DOMAIN)]
|
||||
)
|
||||
async def test_app_auth_yaml_reauth(
|
||||
hass: HomeAssistant, oauth, setup_platform, config_entry
|
||||
) -> None:
|
||||
"""Test reauth for deprecated app auth credentails upgrade instructions."""
|
||||
|
||||
await setup_platform()
|
||||
|
||||
orig_subscriber_id = config_entry.data.get("subscriber_id")
|
||||
assert config_entry.data["auth_implementation"] == APP_AUTH_DOMAIN
|
||||
|
||||
result = oauth.async_progress()
|
||||
assert result.get("step_id") == "reauth_confirm"
|
||||
|
||||
result = await oauth.async_configure(result, {})
|
||||
assert result.get("type") == "form"
|
||||
assert result.get("step_id") == "auth_upgrade"
|
||||
|
||||
result = await oauth.async_configure(result, {})
|
||||
assert result.get("type") == "abort"
|
||||
assert result.get("reason") == "missing_credentials"
|
||||
await hass.async_block_till_done()
|
||||
# Config flow is aborted, but new one created back in re-auth state waiting for user
|
||||
# to create application credentials
|
||||
flows = hass.config_entries.flow.async_progress()
|
||||
assert len(flows) == 1
|
||||
|
||||
# Emulate user entering credentials (different from configuration.yaml creds)
|
||||
await async_import_client_credential(
|
||||
hass,
|
||||
DOMAIN,
|
||||
ClientCredential(CLIENT_ID, CLIENT_SECRET),
|
||||
)
|
||||
|
||||
# Config flow is placed back into a reuath state
|
||||
result = oauth.async_progress()
|
||||
assert result.get("step_id") == "reauth_confirm"
|
||||
|
||||
result = await oauth.async_configure(result, {})
|
||||
assert result.get("type") == "form"
|
||||
assert result.get("step_id") == "device_project_upgrade"
|
||||
|
||||
# Frontend sends user back through the config flow again
|
||||
result = await oauth.async_configure(result, {})
|
||||
await oauth.async_oauth_web_flow(result)
|
||||
|
||||
# Verify existing tokens are replaced
|
||||
entry = await oauth.async_finish_setup(result, {"code": "1234"})
|
||||
entry.data["token"].pop("expires_at")
|
||||
assert entry.unique_id == PROJECT_ID
|
||||
assert entry.data["token"] == {
|
||||
"refresh_token": "mock-refresh-token",
|
||||
"access_token": "mock-access-token",
|
||||
"type": "Bearer",
|
||||
"expires_in": 60,
|
||||
}
|
||||
assert entry.data["auth_implementation"] == DOMAIN
|
||||
assert entry.data.get("subscriber_id") == orig_subscriber_id # Not updated
|
||||
|
||||
# Existing entry is updated
|
||||
assert config_entry.data["auth_implementation"] == DOMAIN
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("nest_test_config", "auth_implementation"),
|
||||
[(TEST_CONFIG_YAML_ONLY, WEB_AUTH_DOMAIN)],
|
||||
)
|
||||
async def test_web_auth_yaml_reauth(
|
||||
hass: HomeAssistant, oauth, setup_platform, config_entry
|
||||
) -> None:
|
||||
"""Test Nest reauthentication for Installed App Auth."""
|
||||
|
||||
await setup_platform()
|
||||
|
||||
orig_subscriber_id = config_entry.data.get("subscriber_id")
|
||||
|
||||
result = await oauth.async_reauth(config_entry)
|
||||
await oauth.async_oauth_web_flow(result)
|
||||
|
||||
# Verify existing tokens are replaced
|
||||
entry = await oauth.async_finish_setup(result, {"code": "1234"})
|
||||
entry.data["token"].pop("expires_at")
|
||||
assert entry.unique_id == PROJECT_ID
|
||||
assert entry.data["token"] == {
|
||||
"refresh_token": "mock-refresh-token",
|
||||
"access_token": "mock-access-token",
|
||||
"type": "Bearer",
|
||||
"expires_in": 60,
|
||||
}
|
||||
assert entry.data["auth_implementation"] == WEB_AUTH_DOMAIN
|
||||
assert entry.data.get("subscriber_id") == orig_subscriber_id # Not updated
|
||||
|
||||
|
||||
@pytest.mark.parametrize("nest_test_config", [TEST_CONFIGFLOW_APP_CREDS])
|
||||
async def test_pubsub_subscription_strip_whitespace(
|
||||
hass: HomeAssistant, oauth, subscriber, setup_platform
|
||||
) -> None:
|
||||
|
@ -641,7 +495,6 @@ async def test_pubsub_subscription_strip_whitespace(
|
|||
assert entry.data["cloud_project_id"] == CLOUD_PROJECT_ID
|
||||
|
||||
|
||||
@pytest.mark.parametrize("nest_test_config", [TEST_CONFIGFLOW_APP_CREDS])
|
||||
async def test_pubsub_subscription_auth_failure(
|
||||
hass: HomeAssistant, oauth, setup_platform, mock_subscriber
|
||||
) -> None:
|
||||
|
@ -668,7 +521,6 @@ async def test_pubsub_subscriber_config_entry_reauth(
|
|||
setup_platform,
|
||||
subscriber,
|
||||
config_entry,
|
||||
auth_implementation,
|
||||
) -> None:
|
||||
"""Test the pubsub subscriber id is preserved during reauth."""
|
||||
await setup_platform()
|
||||
|
@ -686,12 +538,11 @@ async def test_pubsub_subscriber_config_entry_reauth(
|
|||
"type": "Bearer",
|
||||
"expires_in": 60,
|
||||
}
|
||||
assert entry.data["auth_implementation"] == auth_implementation
|
||||
assert entry.data["auth_implementation"] == "imported-cred"
|
||||
assert entry.data["subscriber_id"] == SUBSCRIBER_ID
|
||||
assert entry.data["cloud_project_id"] == CLOUD_PROJECT_ID
|
||||
|
||||
|
||||
@pytest.mark.parametrize("nest_test_config", [TEST_CONFIGFLOW_APP_CREDS])
|
||||
async def test_config_entry_title_from_home(
|
||||
hass: HomeAssistant, oauth, setup_platform, subscriber
|
||||
) -> None:
|
||||
|
@ -725,7 +576,6 @@ async def test_config_entry_title_from_home(
|
|||
assert entry.data["cloud_project_id"] == CLOUD_PROJECT_ID
|
||||
|
||||
|
||||
@pytest.mark.parametrize("nest_test_config", [TEST_CONFIGFLOW_APP_CREDS])
|
||||
async def test_config_entry_title_multiple_homes(
|
||||
hass: HomeAssistant, oauth, setup_platform, subscriber
|
||||
) -> None:
|
||||
|
@ -768,7 +618,6 @@ async def test_config_entry_title_multiple_homes(
|
|||
assert entry.title == "Example Home #1, Example Home #2"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("nest_test_config", [TEST_CONFIGFLOW_APP_CREDS])
|
||||
async def test_title_failure_fallback(
|
||||
hass: HomeAssistant, oauth, setup_platform, mock_subscriber
|
||||
) -> None:
|
||||
|
@ -788,7 +637,6 @@ async def test_title_failure_fallback(
|
|||
assert entry.data["cloud_project_id"] == CLOUD_PROJECT_ID
|
||||
|
||||
|
||||
@pytest.mark.parametrize("nest_test_config", [TEST_CONFIGFLOW_APP_CREDS])
|
||||
async def test_structure_missing_trait(
|
||||
hass: HomeAssistant, oauth, setup_platform, subscriber
|
||||
) -> None:
|
||||
|
@ -818,7 +666,9 @@ async def test_structure_missing_trait(
|
|||
|
||||
|
||||
@pytest.mark.parametrize("nest_test_config", [NestTestConfig()])
|
||||
async def test_dhcp_discovery(hass: HomeAssistant, oauth, subscriber) -> None:
|
||||
async def test_dhcp_discovery(
|
||||
hass: HomeAssistant, oauth: OAuthFixture, nest_test_config: NestTestConfig
|
||||
) -> None:
|
||||
"""Exercise discovery dhcp starts the config flow and kicks user to frontend creds flow."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
|
@ -834,7 +684,6 @@ async def test_dhcp_discovery(hass: HomeAssistant, oauth, subscriber) -> None:
|
|||
assert result.get("reason") == "missing_credentials"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("nest_test_config", [TEST_CONFIGFLOW_APP_CREDS])
|
||||
async def test_dhcp_discovery_with_creds(
|
||||
hass: HomeAssistant, oauth, subscriber, setup_platform
|
||||
) -> None:
|
||||
|
|
|
@ -26,9 +26,6 @@ from homeassistant.core import HomeAssistant
|
|||
from .common import (
|
||||
PROJECT_ID,
|
||||
SUBSCRIBER_ID,
|
||||
TEST_CONFIG_APP_CREDS,
|
||||
TEST_CONFIG_HYBRID,
|
||||
TEST_CONFIG_YAML_ONLY,
|
||||
TEST_CONFIGFLOW_APP_CREDS,
|
||||
FakeSubscriber,
|
||||
YieldFixture,
|
||||
|
@ -180,10 +177,7 @@ async def test_subscriber_configuration_failure(
|
|||
assert entries[0].state is ConfigEntryState.SETUP_ERROR
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"nest_test_config",
|
||||
[TEST_CONFIGFLOW_APP_CREDS],
|
||||
)
|
||||
@pytest.mark.parametrize("nest_test_config", [TEST_CONFIGFLOW_APP_CREDS])
|
||||
async def test_empty_config(
|
||||
hass: HomeAssistant, error_caplog, config, setup_platform
|
||||
) -> None:
|
||||
|
@ -208,26 +202,9 @@ async def test_unload_entry(hass: HomeAssistant, setup_platform) -> None:
|
|||
assert entry.state == ConfigEntryState.NOT_LOADED
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("nest_test_config", "delete_called"),
|
||||
[
|
||||
(
|
||||
TEST_CONFIG_YAML_ONLY,
|
||||
False,
|
||||
), # User manually created subscriber, preserve on remove
|
||||
(
|
||||
TEST_CONFIG_HYBRID,
|
||||
True,
|
||||
), # Integration created subscriber, garbage collect on remove
|
||||
(
|
||||
TEST_CONFIG_APP_CREDS,
|
||||
True,
|
||||
), # Integration created subscriber, garbage collect on remove
|
||||
],
|
||||
ids=["yaml-config-only", "hybrid-config", "config-entry"],
|
||||
)
|
||||
async def test_remove_entry(
|
||||
hass: HomeAssistant, nest_test_config, setup_base_platform, delete_called
|
||||
hass: HomeAssistant,
|
||||
setup_base_platform,
|
||||
) -> None:
|
||||
"""Test successful unload of a ConfigEntry."""
|
||||
with patch(
|
||||
|
@ -250,19 +227,14 @@ async def test_remove_entry(
|
|||
"homeassistant.components.nest.api.GoogleNestSubscriber.delete_subscription",
|
||||
) as delete:
|
||||
assert await hass.config_entries.async_remove(entry.entry_id)
|
||||
assert delete.called == delete_called
|
||||
assert delete.called
|
||||
|
||||
entries = hass.config_entries.async_entries(DOMAIN)
|
||||
assert not entries
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"nest_test_config",
|
||||
[TEST_CONFIG_HYBRID, TEST_CONFIG_APP_CREDS],
|
||||
ids=["hyrbid-config", "app-creds"],
|
||||
)
|
||||
async def test_remove_entry_delete_subscriber_failure(
|
||||
hass: HomeAssistant, nest_test_config, setup_base_platform
|
||||
hass: HomeAssistant, setup_base_platform
|
||||
) -> None:
|
||||
"""Test a failure when deleting the subscription."""
|
||||
with patch(
|
||||
|
|
Loading…
Reference in New Issue