mirror of
https://github.com/home-assistant/core.git
synced 2026-08-24 10:13:52 -05:00
Deprecate Netatmo webhook services (#175372)
This commit is contained in:
@@ -1,19 +1,14 @@
|
||||
"""The Netatmo integration."""
|
||||
|
||||
import logging
|
||||
import secrets
|
||||
from typing import Any
|
||||
|
||||
from aiohttp import ClientError
|
||||
import pyatmo
|
||||
|
||||
from homeassistant.components import cloud
|
||||
from homeassistant.components.webhook import (
|
||||
async_generate_url as webhook_generate_url,
|
||||
async_register as webhook_register,
|
||||
async_unregister as webhook_unregister,
|
||||
)
|
||||
from homeassistant.const import CONF_WEBHOOK_ID, EVENT_HOMEASSISTANT_STOP
|
||||
from homeassistant.components.webhook import async_unregister as webhook_unregister
|
||||
from homeassistant.const import CONF_WEBHOOK_ID
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import (
|
||||
ConfigEntryAuthFailed,
|
||||
@@ -35,7 +30,6 @@ from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from . import api
|
||||
from .const import (
|
||||
CONF_CLOUDHOOK_URL,
|
||||
DATA_CAMERAS,
|
||||
DATA_DEVICE_IDS,
|
||||
DATA_EVENTS,
|
||||
@@ -44,11 +38,10 @@ from .const import (
|
||||
DATA_SCHEDULES,
|
||||
DOMAIN,
|
||||
PLATFORMS,
|
||||
WEBHOOK_DEACTIVATION,
|
||||
WEBHOOK_PUSH_TYPE,
|
||||
)
|
||||
from .data_handler import NetatmoConfigEntry, NetatmoDataHandler
|
||||
from .webhook import async_handle_webhook
|
||||
from .services import async_setup_services
|
||||
from .webhook import async_register_webhook, async_unregister_webhook
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -70,6 +63,8 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
DATA_CAMERAS: {},
|
||||
}
|
||||
|
||||
async_setup_services(hass)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@@ -111,106 +106,34 @@ async def async_setup_entry(hass: HomeAssistant, entry: NetatmoConfigEntry) -> b
|
||||
entry.runtime_data = data_handler
|
||||
await data_handler.async_setup()
|
||||
|
||||
async def unregister_webhook(
|
||||
_: Any,
|
||||
) -> None:
|
||||
if CONF_WEBHOOK_ID not in entry.data:
|
||||
return
|
||||
_LOGGER.debug("Unregister Netatmo webhook (%s)", entry.data[CONF_WEBHOOK_ID])
|
||||
async_dispatcher_send(
|
||||
hass,
|
||||
f"signal-{DOMAIN}-webhook-None",
|
||||
{"type": "None", "data": {WEBHOOK_PUSH_TYPE: WEBHOOK_DEACTIVATION}},
|
||||
)
|
||||
webhook_unregister(hass, entry.data[CONF_WEBHOOK_ID])
|
||||
try:
|
||||
await entry.runtime_data.auth.async_dropwebhook()
|
||||
except pyatmo.ApiError:
|
||||
_LOGGER.debug(
|
||||
"No webhook to be dropped for %s", entry.data[CONF_WEBHOOK_ID]
|
||||
)
|
||||
async def register_webhook(_: Any = None) -> None:
|
||||
await async_register_webhook(hass, entry)
|
||||
|
||||
async def register_webhook(
|
||||
_: Any,
|
||||
) -> None:
|
||||
if CONF_WEBHOOK_ID not in entry.data:
|
||||
data = {**entry.data, CONF_WEBHOOK_ID: secrets.token_hex()}
|
||||
hass.config_entries.async_update_entry(entry, data=data)
|
||||
|
||||
if cloud.async_active_subscription(hass):
|
||||
webhook_url = await async_cloudhook_generate_url(hass, entry)
|
||||
else:
|
||||
webhook_url = webhook_generate_url(hass, entry.data[CONF_WEBHOOK_ID])
|
||||
|
||||
if entry.data[
|
||||
"auth_implementation"
|
||||
] == cloud.DOMAIN and not webhook_url.startswith("https://"):
|
||||
_LOGGER.warning(
|
||||
"Webhook not registered - "
|
||||
"https and port 443 is required to register the webhook"
|
||||
)
|
||||
return
|
||||
|
||||
webhook_register(
|
||||
hass,
|
||||
DOMAIN,
|
||||
"Netatmo",
|
||||
entry.data[CONF_WEBHOOK_ID],
|
||||
async_handle_webhook,
|
||||
)
|
||||
|
||||
try:
|
||||
await entry.runtime_data.auth.async_addwebhook(webhook_url)
|
||||
_LOGGER.debug("Register Netatmo webhook: %s", webhook_url)
|
||||
# pylint: disable-next=home-assistant-action-swallowed-exception
|
||||
except pyatmo.ApiError as err:
|
||||
_LOGGER.error("Error during webhook registration - %s", err)
|
||||
else:
|
||||
entry.async_on_unload(
|
||||
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, unregister_webhook)
|
||||
)
|
||||
async def unregister_webhook(_: Any = None) -> None:
|
||||
await async_unregister_webhook(hass, entry)
|
||||
|
||||
async def manage_cloudhook(state: cloud.CloudConnectionState) -> None:
|
||||
if state is cloud.CloudConnectionState.CLOUD_CONNECTED:
|
||||
await register_webhook(None)
|
||||
await register_webhook()
|
||||
|
||||
if state is cloud.CloudConnectionState.CLOUD_DISCONNECTED:
|
||||
await unregister_webhook(None)
|
||||
await unregister_webhook()
|
||||
entry.async_on_unload(async_call_later(hass, 30, register_webhook))
|
||||
|
||||
if cloud.async_active_subscription(hass):
|
||||
if cloud.async_is_connected(hass):
|
||||
await register_webhook(None)
|
||||
await register_webhook()
|
||||
entry.async_on_unload(
|
||||
cloud.async_listen_connection_change(hass, manage_cloudhook)
|
||||
)
|
||||
else:
|
||||
entry.async_on_unload(async_at_started(hass, register_webhook))
|
||||
|
||||
# pylint: disable-next=home-assistant-service-registered-in-setup-entry
|
||||
hass.services.async_register(DOMAIN, "register_webhook", register_webhook)
|
||||
# pylint: disable-next=home-assistant-service-registered-in-setup-entry
|
||||
hass.services.async_register(DOMAIN, "unregister_webhook", unregister_webhook)
|
||||
|
||||
entry.async_on_unload(entry.add_update_listener(async_config_entry_updated))
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_cloudhook_generate_url(
|
||||
hass: HomeAssistant, entry: NetatmoConfigEntry
|
||||
) -> str:
|
||||
"""Generate the full URL for a webhook_id."""
|
||||
if CONF_CLOUDHOOK_URL not in entry.data:
|
||||
webhook_url = await cloud.async_create_cloudhook(
|
||||
hass, entry.data[CONF_WEBHOOK_ID]
|
||||
)
|
||||
data = {**entry.data, CONF_CLOUDHOOK_URL: webhook_url}
|
||||
hass.config_entries.async_update_entry(entry, data=data)
|
||||
return webhook_url
|
||||
return str(entry.data[CONF_CLOUDHOOK_URL])
|
||||
|
||||
|
||||
async def async_config_entry_updated(
|
||||
hass: HomeAssistant, entry: NetatmoConfigEntry
|
||||
) -> None:
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
"""Services for the Netatmo integration."""
|
||||
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.core import HomeAssistant, ServiceCall, callback
|
||||
from homeassistant.exceptions import ServiceValidationError
|
||||
from homeassistant.helpers import issue_registry as ir
|
||||
|
||||
from .const import DOMAIN
|
||||
from .data_handler import NetatmoConfigEntry
|
||||
from .webhook import async_register_webhook, async_unregister_webhook
|
||||
|
||||
SERVICE_REGISTER_WEBHOOK = "register_webhook"
|
||||
SERVICE_UNREGISTER_WEBHOOK = "unregister_webhook"
|
||||
|
||||
|
||||
def _get_loaded_entry(hass: HomeAssistant) -> NetatmoConfigEntry:
|
||||
"""Return the loaded config entry or raise if unavailable."""
|
||||
entry: NetatmoConfigEntry | None = (
|
||||
hass.config_entries.async_entry_for_domain_unique_id(DOMAIN, DOMAIN)
|
||||
)
|
||||
if entry is None or entry.state is not ConfigEntryState.LOADED:
|
||||
raise ServiceValidationError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="entry_not_loaded",
|
||||
)
|
||||
return entry
|
||||
|
||||
|
||||
def _deprecate(hass: HomeAssistant, service: str) -> None:
|
||||
"""Warn and raise a repair issue for the deprecated webhook actions."""
|
||||
ir.async_create_issue(
|
||||
hass,
|
||||
DOMAIN,
|
||||
f"deprecated_service_{service}",
|
||||
breaks_in_ha_version="2027.2.0",
|
||||
is_fixable=False,
|
||||
severity=ir.IssueSeverity.WARNING,
|
||||
translation_key="deprecated_webhook_service",
|
||||
)
|
||||
|
||||
|
||||
async def _register_webhook(call: ServiceCall) -> None:
|
||||
"""Handle the deprecated register_webhook action."""
|
||||
_deprecate(call.hass, SERVICE_REGISTER_WEBHOOK)
|
||||
entry = _get_loaded_entry(call.hass)
|
||||
# Drop any existing registration first so re-registering an already-active
|
||||
# webhook does not raise "Handler is already defined!".
|
||||
await async_unregister_webhook(call.hass, entry)
|
||||
await async_register_webhook(call.hass, entry)
|
||||
|
||||
|
||||
async def _unregister_webhook(call: ServiceCall) -> None:
|
||||
"""Handle the deprecated unregister_webhook action."""
|
||||
_deprecate(call.hass, SERVICE_UNREGISTER_WEBHOOK)
|
||||
await async_unregister_webhook(call.hass, _get_loaded_entry(call.hass))
|
||||
|
||||
|
||||
@callback
|
||||
def async_setup_services(hass: HomeAssistant) -> None:
|
||||
"""Register the Netatmo services."""
|
||||
hass.services.async_register(DOMAIN, SERVICE_REGISTER_WEBHOOK, _register_webhook)
|
||||
hass.services.async_register(
|
||||
DOMAIN, SERVICE_UNREGISTER_WEBHOOK, _unregister_webhook
|
||||
)
|
||||
@@ -119,5 +119,6 @@ set_person_away:
|
||||
selector:
|
||||
text:
|
||||
|
||||
# Deprecated: the webhook is managed automatically. Scheduled for removal in 2027.2.0.
|
||||
register_webhook:
|
||||
unregister_webhook:
|
||||
|
||||
@@ -156,10 +156,19 @@
|
||||
}
|
||||
},
|
||||
"exceptions": {
|
||||
"entry_not_loaded": {
|
||||
"message": "The Netatmo integration is not loaded. Please set it up before calling this action."
|
||||
},
|
||||
"oauth2_implementation_unavailable": {
|
||||
"message": "[%key:common::exceptions::oauth2_implementation_unavailable::message%]"
|
||||
}
|
||||
},
|
||||
"issues": {
|
||||
"deprecated_webhook_service": {
|
||||
"description": "The `register_webhook` and `unregister_webhook` actions are deprecated and will be removed in a future release. Netatmo manages the webhook automatically, so these actions are no longer needed. Please remove them from your automations and scripts.",
|
||||
"title": "Netatmo webhook actions are deprecated"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"step": {
|
||||
"public_weather": {
|
||||
@@ -191,7 +200,7 @@
|
||||
"name": "Clear temperature setting"
|
||||
},
|
||||
"register_webhook": {
|
||||
"description": "Registers the webhook to the Netatmo backend.",
|
||||
"description": "Registers the webhook to the Netatmo backend. Deprecated: the webhook is managed automatically and this action will be removed in 2027.2.0.",
|
||||
"name": "Register webhook"
|
||||
},
|
||||
"set_camera_light": {
|
||||
@@ -277,7 +286,7 @@
|
||||
"name": "Set temperature with time period"
|
||||
},
|
||||
"unregister_webhook": {
|
||||
"description": "Unregisters the webhook from the Netatmo backend.",
|
||||
"description": "Unregisters the webhook from the Netatmo backend. Deprecated: the webhook is managed automatically and this action will be removed in 2027.2.0.",
|
||||
"name": "Unregister webhook"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,28 @@
|
||||
"""The Netatmo integration."""
|
||||
|
||||
# pylint: disable=home-assistant-use-runtime-data # Uses legacy hass.data[DOMAIN] pattern
|
||||
|
||||
import logging
|
||||
import secrets
|
||||
|
||||
from aiohttp.web import Request
|
||||
import pyatmo
|
||||
|
||||
from homeassistant.const import ATTR_DEVICE_ID, ATTR_ID, ATTR_NAME, ATTR_PERSONS
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.components import cloud
|
||||
from homeassistant.components.webhook import (
|
||||
async_generate_url as webhook_generate_url,
|
||||
async_register as webhook_register,
|
||||
async_unregister as webhook_unregister,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
ATTR_DEVICE_ID,
|
||||
ATTR_ID,
|
||||
ATTR_NAME,
|
||||
ATTR_PERSONS,
|
||||
CONF_WEBHOOK_ID,
|
||||
EVENT_HOMEASSISTANT_STOP,
|
||||
)
|
||||
from homeassistant.core import Event, HomeAssistant
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||
|
||||
from .const import (
|
||||
@@ -14,13 +30,17 @@ from .const import (
|
||||
ATTR_FACE_URL,
|
||||
ATTR_HOME_ID,
|
||||
ATTR_IS_KNOWN,
|
||||
CONF_CLOUDHOOK_URL,
|
||||
DATA_DEVICE_IDS,
|
||||
DATA_PERSONS,
|
||||
DEFAULT_PERSON,
|
||||
DOMAIN,
|
||||
EVENT_ID_MAP,
|
||||
NETATMO_EVENT,
|
||||
WEBHOOK_DEACTIVATION,
|
||||
WEBHOOK_PUSH_TYPE,
|
||||
)
|
||||
from .data_handler import NetatmoConfigEntry
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -98,3 +118,82 @@ def async_send_event(hass: HomeAssistant, event_type: str, data: dict) -> None:
|
||||
event_type=NETATMO_EVENT,
|
||||
event_data=event_data,
|
||||
)
|
||||
|
||||
|
||||
async def async_cloudhook_generate_url(
|
||||
hass: HomeAssistant, entry: NetatmoConfigEntry
|
||||
) -> str:
|
||||
"""Generate the full URL for a webhook_id."""
|
||||
if CONF_CLOUDHOOK_URL not in entry.data:
|
||||
webhook_url = await cloud.async_create_cloudhook(
|
||||
hass, entry.data[CONF_WEBHOOK_ID]
|
||||
)
|
||||
data = {**entry.data, CONF_CLOUDHOOK_URL: webhook_url}
|
||||
hass.config_entries.async_update_entry(entry, data=data)
|
||||
return webhook_url
|
||||
return str(entry.data[CONF_CLOUDHOOK_URL])
|
||||
|
||||
|
||||
async def async_unregister_webhook(
|
||||
hass: HomeAssistant, entry: NetatmoConfigEntry
|
||||
) -> None:
|
||||
"""Unregister the webhook from the Netatmo backend."""
|
||||
if CONF_WEBHOOK_ID not in entry.data:
|
||||
return
|
||||
_LOGGER.debug("Unregister Netatmo webhook (%s)", entry.data[CONF_WEBHOOK_ID])
|
||||
async_dispatcher_send(
|
||||
hass,
|
||||
f"signal-{DOMAIN}-webhook-None",
|
||||
{"type": "None", "data": {WEBHOOK_PUSH_TYPE: WEBHOOK_DEACTIVATION}},
|
||||
)
|
||||
webhook_unregister(hass, entry.data[CONF_WEBHOOK_ID])
|
||||
try:
|
||||
await entry.runtime_data.auth.async_dropwebhook()
|
||||
except pyatmo.ApiError:
|
||||
_LOGGER.debug("No webhook to be dropped for %s", entry.data[CONF_WEBHOOK_ID])
|
||||
|
||||
|
||||
async def async_register_webhook(
|
||||
hass: HomeAssistant, entry: NetatmoConfigEntry
|
||||
) -> None:
|
||||
"""Register the webhook with the Netatmo backend."""
|
||||
if CONF_WEBHOOK_ID not in entry.data:
|
||||
data = {**entry.data, CONF_WEBHOOK_ID: secrets.token_hex()}
|
||||
hass.config_entries.async_update_entry(entry, data=data)
|
||||
|
||||
if cloud.async_active_subscription(hass):
|
||||
webhook_url = await async_cloudhook_generate_url(hass, entry)
|
||||
else:
|
||||
webhook_url = webhook_generate_url(hass, entry.data[CONF_WEBHOOK_ID])
|
||||
|
||||
if entry.data["auth_implementation"] == cloud.DOMAIN and not webhook_url.startswith(
|
||||
"https://"
|
||||
):
|
||||
_LOGGER.warning(
|
||||
"Webhook not registered - "
|
||||
"https and port 443 is required to register the webhook"
|
||||
)
|
||||
return
|
||||
|
||||
webhook_register(
|
||||
hass,
|
||||
DOMAIN,
|
||||
"Netatmo",
|
||||
entry.data[CONF_WEBHOOK_ID],
|
||||
async_handle_webhook,
|
||||
)
|
||||
|
||||
async def _handle_stop(_: Event) -> None:
|
||||
await async_unregister_webhook(hass, entry)
|
||||
|
||||
try:
|
||||
await entry.runtime_data.auth.async_addwebhook(webhook_url)
|
||||
_LOGGER.debug("Register Netatmo webhook: %s", webhook_url)
|
||||
# pylint: disable-next=home-assistant-action-swallowed-exception
|
||||
except pyatmo.ApiError as err:
|
||||
webhook_unregister(hass, entry.data[CONF_WEBHOOK_ID])
|
||||
_LOGGER.error("Error during webhook registration - %s", err)
|
||||
else:
|
||||
entry.async_on_unload(
|
||||
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _handle_stop)
|
||||
)
|
||||
|
||||
@@ -126,7 +126,7 @@ def selected_platforms(platforms: list[Platform]) -> Iterator[None]:
|
||||
"homeassistant.components.netatmo.async_get_config_entry_implementation",
|
||||
),
|
||||
patch(
|
||||
"homeassistant.components.netatmo.webhook_generate_url",
|
||||
"homeassistant.components.netatmo.webhook.webhook_generate_url",
|
||||
),
|
||||
):
|
||||
yield
|
||||
|
||||
@@ -67,7 +67,7 @@ async def test_doortag_setup(
|
||||
return_value=AsyncMock(),
|
||||
),
|
||||
patch(
|
||||
"homeassistant.components.netatmo.webhook_generate_url",
|
||||
"homeassistant.components.netatmo.webhook.webhook_generate_url",
|
||||
) as mock_webhook,
|
||||
):
|
||||
mock_auth.return_value.async_post_api_request.side_effect = fake_post
|
||||
@@ -179,7 +179,7 @@ async def test_doortag_opening_status_change(
|
||||
return_value=AsyncMock(),
|
||||
),
|
||||
patch(
|
||||
"homeassistant.components.netatmo.webhook_generate_url",
|
||||
"homeassistant.components.netatmo.webhook.webhook_generate_url",
|
||||
) as mock_webhook,
|
||||
):
|
||||
mock_auth.return_value.async_post_api_request.side_effect = fake_tag_post
|
||||
@@ -306,7 +306,7 @@ async def test_doortag_opening_category(
|
||||
return_value=AsyncMock(),
|
||||
),
|
||||
patch(
|
||||
"homeassistant.components.netatmo.webhook_generate_url",
|
||||
"homeassistant.components.netatmo.webhook.webhook_generate_url",
|
||||
) as mock_webhook,
|
||||
):
|
||||
mock_auth.return_value.async_post_api_request.side_effect = fake_tag_post
|
||||
|
||||
@@ -479,7 +479,7 @@ async def test_camera_reconnect_webhook(
|
||||
"homeassistant.components.netatmo.async_get_config_entry_implementation",
|
||||
),
|
||||
patch(
|
||||
"homeassistant.components.netatmo.webhook_generate_url",
|
||||
"homeassistant.components.netatmo.webhook.webhook_generate_url",
|
||||
) as mock_webhook,
|
||||
):
|
||||
mock_auth.return_value.async_post_api_request.side_effect = fake_post
|
||||
@@ -592,7 +592,7 @@ async def test_camera_webhook_consistency(
|
||||
"homeassistant.components.netatmo.async_get_config_entry_implementation",
|
||||
),
|
||||
patch(
|
||||
"homeassistant.components.netatmo.webhook_generate_url",
|
||||
"homeassistant.components.netatmo.webhook.webhook_generate_url",
|
||||
) as mock_webhook,
|
||||
):
|
||||
mock_auth.return_value.async_post_api_request.side_effect = fake_post
|
||||
@@ -712,7 +712,7 @@ async def test_setup_component_no_devices(
|
||||
"homeassistant.components.netatmo.async_get_config_entry_implementation",
|
||||
),
|
||||
patch(
|
||||
"homeassistant.components.netatmo.webhook_generate_url",
|
||||
"homeassistant.components.netatmo.webhook.webhook_generate_url",
|
||||
),
|
||||
):
|
||||
mock_auth.return_value.async_post_api_request.side_effect = fake_post_no_data
|
||||
@@ -755,7 +755,7 @@ async def test_camera_image_raises_exception(
|
||||
"homeassistant.components.netatmo.async_get_config_entry_implementation",
|
||||
),
|
||||
patch(
|
||||
"homeassistant.components.netatmo.webhook_generate_url",
|
||||
"homeassistant.components.netatmo.webhook.webhook_generate_url",
|
||||
),
|
||||
):
|
||||
mock_auth.return_value.async_post_api_request.side_effect = fake_post
|
||||
|
||||
@@ -32,7 +32,7 @@ async def test_entry_diagnostics(
|
||||
"homeassistant.components.netatmo.async_get_config_entry_implementation",
|
||||
),
|
||||
patch(
|
||||
"homeassistant.components.netatmo.webhook_generate_url",
|
||||
"homeassistant.components.netatmo.webhook.webhook_generate_url",
|
||||
),
|
||||
):
|
||||
mock_auth.return_value.async_post_api_request.side_effect = partial(
|
||||
|
||||
@@ -10,13 +10,20 @@ from pyatmo.const import ALL_SCOPES
|
||||
import pytest
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
|
||||
from homeassistant.components import cloud
|
||||
from homeassistant.components import cloud, webhook
|
||||
from homeassistant.components.netatmo import DOMAIN
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.const import CONF_WEBHOOK_ID, Platform
|
||||
from homeassistant.core import CoreState, HomeAssistant
|
||||
from homeassistant.exceptions import OAuth2TokenRequestReauthError
|
||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||
from homeassistant.exceptions import (
|
||||
OAuth2TokenRequestReauthError,
|
||||
ServiceValidationError,
|
||||
)
|
||||
from homeassistant.helpers import (
|
||||
device_registry as dr,
|
||||
entity_registry as er,
|
||||
issue_registry as ir,
|
||||
)
|
||||
from homeassistant.helpers.config_entry_oauth2_flow import (
|
||||
ImplementationUnavailableError,
|
||||
)
|
||||
@@ -71,7 +78,9 @@ async def test_setup_component(
|
||||
patch(
|
||||
"homeassistant.components.netatmo.async_get_config_entry_implementation",
|
||||
) as mock_impl,
|
||||
patch("homeassistant.components.netatmo.webhook_generate_url") as mock_webhook,
|
||||
patch(
|
||||
"homeassistant.components.netatmo.webhook.webhook_generate_url"
|
||||
) as mock_webhook,
|
||||
):
|
||||
mock_auth.return_value.async_post_api_request.side_effect = partial(
|
||||
fake_post_request, hass
|
||||
@@ -114,7 +123,9 @@ async def test_setup_component_with_config(
|
||||
patch(
|
||||
"homeassistant.components.netatmo.async_get_config_entry_implementation",
|
||||
) as mock_impl,
|
||||
patch("homeassistant.components.netatmo.webhook_generate_url") as mock_webhook,
|
||||
patch(
|
||||
"homeassistant.components.netatmo.webhook.webhook_generate_url"
|
||||
) as mock_webhook,
|
||||
patch(
|
||||
"homeassistant.components.netatmo.api.AsyncConfigEntryNetatmoAuth",
|
||||
) as mock_auth,
|
||||
@@ -171,6 +182,69 @@ async def test_setup_component_with_webhook(
|
||||
assert len(hass.config_entries.async_entries(DOMAIN)) == 0
|
||||
|
||||
|
||||
async def test_no_deprecation_issue_on_setup(
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
netatmo_auth: AsyncMock,
|
||||
issue_registry: ir.IssueRegistry,
|
||||
) -> None:
|
||||
"""Test the automatic webhook lifecycle does not raise the deprecation issue."""
|
||||
with selected_platforms([Platform.CLIMATE]):
|
||||
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert not issue_registry.async_get_issue(
|
||||
DOMAIN, "deprecated_service_register_webhook"
|
||||
)
|
||||
assert not issue_registry.async_get_issue(
|
||||
DOMAIN, "deprecated_service_unregister_webhook"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("service", "expected_registered"),
|
||||
[
|
||||
pytest.param("register_webhook", True, id="register"),
|
||||
pytest.param("unregister_webhook", False, id="unregister"),
|
||||
],
|
||||
)
|
||||
async def test_deprecated_webhook_service(
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
netatmo_auth: AsyncMock,
|
||||
issue_registry: ir.IssueRegistry,
|
||||
service: str,
|
||||
expected_registered: bool,
|
||||
) -> None:
|
||||
"""Test the deprecated webhook actions still work and raise a repair issue."""
|
||||
with selected_platforms([Platform.CLIMATE]):
|
||||
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
webhook_id = config_entry.data[CONF_WEBHOOK_ID]
|
||||
assert webhook_id in hass.data[webhook.DOMAIN]
|
||||
|
||||
# register_webhook re-registers the already-active webhook without
|
||||
# raising; unregister_webhook tears it down.
|
||||
await hass.services.async_call(DOMAIN, service, blocking=True)
|
||||
|
||||
assert (webhook_id in hass.data[webhook.DOMAIN]) is expected_registered
|
||||
|
||||
assert issue_registry.async_get_issue(DOMAIN, f"deprecated_service_{service}")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("service", ["register_webhook", "unregister_webhook"])
|
||||
async def test_deprecated_webhook_service_not_loaded(
|
||||
hass: HomeAssistant,
|
||||
service: str,
|
||||
) -> None:
|
||||
"""Test calling a webhook action without a loaded entry raises."""
|
||||
await async_setup_component(hass, DOMAIN, {})
|
||||
|
||||
with pytest.raises(ServiceValidationError):
|
||||
await hass.services.async_call(DOMAIN, service, blocking=True)
|
||||
|
||||
|
||||
async def test_setup_without_https(
|
||||
hass: HomeAssistant, config_entry: MockConfigEntry, caplog: pytest.LogCaptureFixture
|
||||
) -> None:
|
||||
@@ -188,7 +262,7 @@ async def test_setup_without_https(
|
||||
"homeassistant.components.netatmo.async_get_config_entry_implementation",
|
||||
),
|
||||
patch(
|
||||
"homeassistant.components.netatmo.webhook_generate_url"
|
||||
"homeassistant.components.netatmo.webhook.webhook_generate_url"
|
||||
) as mock_async_generate_url,
|
||||
):
|
||||
mock_auth.return_value.async_post_api_request.side_effect = partial(
|
||||
@@ -232,7 +306,7 @@ async def test_setup_with_cloud(
|
||||
"homeassistant.components.netatmo.async_get_config_entry_implementation",
|
||||
),
|
||||
patch(
|
||||
"homeassistant.components.netatmo.webhook_generate_url",
|
||||
"homeassistant.components.netatmo.webhook.webhook_generate_url",
|
||||
),
|
||||
):
|
||||
mock_auth.return_value.async_post_api_request.side_effect = partial(
|
||||
@@ -302,7 +376,7 @@ async def test_setup_with_cloudhook(hass: HomeAssistant) -> None:
|
||||
"homeassistant.components.netatmo.async_get_config_entry_implementation",
|
||||
),
|
||||
patch(
|
||||
"homeassistant.components.netatmo.webhook_generate_url",
|
||||
"homeassistant.components.netatmo.webhook.webhook_generate_url",
|
||||
),
|
||||
):
|
||||
mock_auth.return_value.async_post_api_request.side_effect = partial(
|
||||
@@ -346,7 +420,9 @@ async def test_setup_component_with_delay(
|
||||
patch(
|
||||
"homeassistant.components.netatmo.async_get_config_entry_implementation",
|
||||
) as mock_impl,
|
||||
patch("homeassistant.components.netatmo.webhook_generate_url") as mock_webhook,
|
||||
patch(
|
||||
"homeassistant.components.netatmo.webhook.webhook_generate_url"
|
||||
) as mock_webhook,
|
||||
patch(
|
||||
"pyatmo.AbstractAsyncAuth.async_post_api_request",
|
||||
side_effect=partial(fake_post_request, hass),
|
||||
@@ -416,7 +492,9 @@ async def test_setup_component_invalid_token_scope(hass: HomeAssistant) -> None:
|
||||
patch(
|
||||
"homeassistant.components.netatmo.async_get_config_entry_implementation",
|
||||
) as mock_impl,
|
||||
patch("homeassistant.components.netatmo.webhook_generate_url") as mock_webhook,
|
||||
patch(
|
||||
"homeassistant.components.netatmo.webhook.webhook_generate_url"
|
||||
) as mock_webhook,
|
||||
):
|
||||
mock_auth.return_value.async_post_api_request.side_effect = partial(
|
||||
fake_post_request, hass
|
||||
@@ -466,7 +544,9 @@ async def test_setup_component_invalid_token(
|
||||
patch(
|
||||
"homeassistant.components.netatmo.async_get_config_entry_implementation",
|
||||
) as mock_impl,
|
||||
patch("homeassistant.components.netatmo.webhook_generate_url") as mock_webhook,
|
||||
patch(
|
||||
"homeassistant.components.netatmo.webhook.webhook_generate_url"
|
||||
) as mock_webhook,
|
||||
patch("homeassistant.components.netatmo.OAuth2Session") as mock_session,
|
||||
):
|
||||
mock_auth.return_value.async_post_api_request.side_effect = partial(
|
||||
|
||||
@@ -132,7 +132,7 @@ async def test_setup_component_no_devices(hass: HomeAssistant, config_entry) ->
|
||||
"homeassistant.components.netatmo.async_get_config_entry_implementation",
|
||||
),
|
||||
patch(
|
||||
"homeassistant.components.netatmo.webhook_generate_url",
|
||||
"homeassistant.components.netatmo.webhook.webhook_generate_url",
|
||||
),
|
||||
):
|
||||
mock_auth.return_value.async_post_api_request.side_effect = (
|
||||
|
||||
Reference in New Issue
Block a user