hass-core/tests/components/nest/test_init_sdm.py
Allen Porter cc543b200d
Update nest config flow to dramatically simplify end user setup with automated pub/sub subscription creation (#59260)
* Configure nest pubsub subscriber automatically

Update the config flow to configure the nest pubsub subscriber automatically.
After completing the authentication step, the user is now asked for the google
cloud console ID, which is needed to create a subscription.

Home Assistant manages the lifecycle of a subscription only when it is created
by the ConfigFlow. Otherwise (if specified in configuration.yaml) it treats
it similarly as before.

These are the considerations or failure modes taken into account:
- Subscription is created with reasonable default values as previously recommended (e.g. retion only keeps 5-15 minutes of backlog messages)
- Subscriptions are created with a naming scheme that makes it clear they came from home assistant, and with a random
  string
- Subscriptions are cleaned up when the ConfigEntry is removed. If removal fails, a subscription that is orphaned will
  be deleted after 30 days
- If the subscription gets into a bad state or deleted, the user can go through the re-auth flow to re-create it.
- Users can still specifcy a CONF_SUBSCRIBER_ID in the configuration.yaml, and
skip automatic subscriber creation

* Remove unnecessary nest config flow diffs and merge in upstream changes

* Incorporate review feedback into nest subscription config flow

* Update text wording in nest config flow
2021-11-29 22:41:29 -08:00

260 lines
8.8 KiB
Python

"""
Test for setup methods for the SDM API.
The tests fake out the subscriber/devicemanager and simulate setup behavior
and failure modes.
"""
import copy
import logging
from unittest.mock import patch
from google_nest_sdm.exceptions import (
AuthException,
ConfigurationException,
GoogleNestException,
)
from homeassistant.components.nest import DOMAIN
from homeassistant.config_entries import ConfigEntryState
from homeassistant.setup import async_setup_component
from .common import CONFIG, async_setup_sdm_platform, create_config_entry
PLATFORM = "sensor"
async def test_setup_success(hass, caplog):
"""Test successful setup."""
with caplog.at_level(logging.ERROR, logger="homeassistant.components.nest"):
await async_setup_sdm_platform(hass, PLATFORM)
assert not caplog.records
entries = hass.config_entries.async_entries(DOMAIN)
assert len(entries) == 1
assert entries[0].state is ConfigEntryState.LOADED
async def async_setup_sdm(hass, config=CONFIG, with_config=True):
"""Prepare test setup."""
if with_config:
create_config_entry(hass)
with patch(
"homeassistant.helpers.config_entry_oauth2_flow.async_get_config_entry_implementation"
):
return await async_setup_component(hass, DOMAIN, config)
async def test_setup_configuration_failure(hass, caplog):
"""Test configuration error."""
config = copy.deepcopy(CONFIG)
config[DOMAIN]["subscriber_id"] = "invalid-subscriber-format"
result = await async_setup_sdm(hass, config)
assert result
entries = hass.config_entries.async_entries(DOMAIN)
assert len(entries) == 1
assert entries[0].state is ConfigEntryState.SETUP_ERROR
# This error comes from the python google-nest-sdm library, as a check added
# to prevent common misconfigurations (e.g. confusing topic and subscriber)
assert "Subscription misconfigured. Expected subscriber_id" in caplog.text
async def test_setup_susbcriber_failure(hass, caplog):
"""Test configuration error."""
with patch(
"homeassistant.components.nest.api.GoogleNestSubscriber.start_async",
side_effect=GoogleNestException(),
), caplog.at_level(logging.ERROR, logger="homeassistant.components.nest"):
result = await async_setup_sdm(hass)
assert result
assert "Subscriber error:" in caplog.text
entries = hass.config_entries.async_entries(DOMAIN)
assert len(entries) == 1
assert entries[0].state is ConfigEntryState.SETUP_RETRY
async def test_setup_device_manager_failure(hass, caplog):
"""Test configuration error."""
with patch(
"homeassistant.components.nest.api.GoogleNestSubscriber.start_async"
), patch(
"homeassistant.components.nest.api.GoogleNestSubscriber.async_get_device_manager",
side_effect=GoogleNestException(),
), caplog.at_level(
logging.ERROR, logger="homeassistant.components.nest"
):
result = await async_setup_sdm(hass)
assert result
assert len(caplog.messages) == 1
assert "Device manager error:" in caplog.text
entries = hass.config_entries.async_entries(DOMAIN)
assert len(entries) == 1
assert entries[0].state is ConfigEntryState.SETUP_RETRY
async def test_subscriber_auth_failure(hass, caplog):
"""Test configuration error."""
with patch(
"homeassistant.components.nest.api.GoogleNestSubscriber.start_async",
side_effect=AuthException(),
):
result = await async_setup_sdm(hass, CONFIG)
assert result
entries = hass.config_entries.async_entries(DOMAIN)
assert len(entries) == 1
assert entries[0].state is ConfigEntryState.SETUP_ERROR
flows = hass.config_entries.flow.async_progress()
assert len(flows) == 1
assert flows[0]["step_id"] == "reauth_confirm"
async def test_setup_missing_subscriber_id(hass, caplog):
"""Test missing susbcriber id from config and config entry."""
config = copy.deepcopy(CONFIG)
del config[DOMAIN]["subscriber_id"]
with caplog.at_level(logging.WARNING, logger="homeassistant.components.nest"):
result = await async_setup_sdm(hass, config)
assert result
assert "Configuration option" in caplog.text
entries = hass.config_entries.async_entries(DOMAIN)
assert len(entries) == 1
assert entries[0].state is ConfigEntryState.SETUP_ERROR
async def test_setup_subscriber_id_config_entry(hass, caplog):
"""Test successful setup with subscriber id in ConfigEntry."""
config = copy.deepcopy(CONFIG)
subscriber_id = config[DOMAIN]["subscriber_id"]
del config[DOMAIN]["subscriber_id"]
config_entry = create_config_entry(hass)
data = {**config_entry.data}
data["subscriber_id"] = subscriber_id
hass.config_entries.async_update_entry(config_entry, data=data)
with caplog.at_level(logging.ERROR, logger="homeassistant.components.nest"):
await async_setup_sdm_platform(hass, PLATFORM, with_config=False)
assert not caplog.records
entries = hass.config_entries.async_entries(DOMAIN)
assert len(entries) == 1
assert entries[0].state is ConfigEntryState.LOADED
async def test_subscriber_configuration_failure(hass, caplog):
"""Test configuration error."""
with patch(
"homeassistant.components.nest.api.GoogleNestSubscriber.start_async",
side_effect=ConfigurationException(),
), caplog.at_level(logging.ERROR, logger="homeassistant.components.nest"):
result = await async_setup_sdm(hass, CONFIG)
assert result
assert "Configuration error: " in caplog.text
entries = hass.config_entries.async_entries(DOMAIN)
assert len(entries) == 1
assert entries[0].state is ConfigEntryState.SETUP_ERROR
async def test_empty_config(hass, caplog):
"""Test successful setup."""
with caplog.at_level(logging.ERROR, logger="homeassistant.components.nest"):
result = await async_setup_component(hass, DOMAIN, {})
assert result
assert not caplog.records
entries = hass.config_entries.async_entries(DOMAIN)
assert len(entries) == 0
async def test_unload_entry(hass, caplog):
"""Test successful unload of a ConfigEntry."""
await async_setup_sdm_platform(hass, PLATFORM)
entries = hass.config_entries.async_entries(DOMAIN)
assert len(entries) == 1
entry = entries[0]
assert entry.state is ConfigEntryState.LOADED
assert await hass.config_entries.async_unload(entry.entry_id)
assert entry.state == ConfigEntryState.NOT_LOADED
async def test_remove_entry(hass, caplog):
"""Test successful unload of a ConfigEntry."""
await async_setup_sdm_platform(hass, PLATFORM)
entries = hass.config_entries.async_entries(DOMAIN)
assert len(entries) == 1
entry = entries[0]
assert entry.state is ConfigEntryState.LOADED
assert await hass.config_entries.async_remove(entry.entry_id)
entries = hass.config_entries.async_entries(DOMAIN)
assert not entries
async def test_remove_entry_deletes_subscriber(hass, caplog):
"""Test ConfigEntry unload deletes a subscription."""
config = copy.deepcopy(CONFIG)
subscriber_id = config[DOMAIN]["subscriber_id"]
del config[DOMAIN]["subscriber_id"]
config_entry = create_config_entry(hass)
data = {**config_entry.data}
data["subscriber_id"] = subscriber_id
hass.config_entries.async_update_entry(config_entry, data=data)
await async_setup_sdm_platform(hass, PLATFORM, with_config=False)
entries = hass.config_entries.async_entries(DOMAIN)
assert len(entries) == 1
entry = entries[0]
assert entry.state is ConfigEntryState.LOADED
with patch(
"homeassistant.components.nest.api.GoogleNestSubscriber.delete_subscription",
) as delete:
assert await hass.config_entries.async_remove(entry.entry_id)
assert delete.called
entries = hass.config_entries.async_entries(DOMAIN)
assert not entries
async def test_remove_entry_delete_subscriber_failure(hass, caplog):
"""Test a failure when deleting the subscription."""
config = copy.deepcopy(CONFIG)
subscriber_id = config[DOMAIN]["subscriber_id"]
del config[DOMAIN]["subscriber_id"]
config_entry = create_config_entry(hass)
data = {**config_entry.data}
data["subscriber_id"] = subscriber_id
hass.config_entries.async_update_entry(config_entry, data=data)
await async_setup_sdm_platform(hass, PLATFORM, with_config=False)
entries = hass.config_entries.async_entries(DOMAIN)
assert len(entries) == 1
entry = entries[0]
assert entry.state is ConfigEntryState.LOADED
with patch(
"homeassistant.components.nest.api.GoogleNestSubscriber.delete_subscription",
side_effect=GoogleNestException(),
):
assert await hass.config_entries.async_remove(entry.entry_id)
entries = hass.config_entries.async_entries(DOMAIN)
assert not entries