From 42e3e878dfb43142c89046dc51ee939e32571621 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 1 Apr 2019 05:07:12 -0700 Subject: [PATCH] Cloudhooks for webhook config flows (#22611) --- .../components/dialogflow/__init__.py | 5 ++ homeassistant/components/geofency/__init__.py | 5 ++ .../components/gpslogger/__init__.py | 5 ++ homeassistant/components/ifttt/__init__.py | 5 ++ homeassistant/components/locative/__init__.py | 6 ++- homeassistant/components/mailgun/__init__.py | 5 ++ homeassistant/components/twilio/__init__.py | 5 ++ homeassistant/helpers/config_entry_flow.py | 26 ++++++++-- tests/helpers/test_config_entry_flow.py | 52 ++++++++++++++++++- 9 files changed, 108 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/dialogflow/__init__.py b/homeassistant/components/dialogflow/__init__.py index 210aebe80d5..1536fe3d236 100644 --- a/homeassistant/components/dialogflow/__init__.py +++ b/homeassistant/components/dialogflow/__init__.py @@ -79,6 +79,11 @@ async def async_unload_entry(hass, entry): hass.components.webhook.async_unregister(entry.data[CONF_WEBHOOK_ID]) return True + +# pylint: disable=invalid-name +async_remove_entry = config_entry_flow.webhook_async_remove_entry + + config_entry_flow.register_webhook_flow( DOMAIN, 'Dialogflow Webhook', diff --git a/homeassistant/components/geofency/__init__.py b/homeassistant/components/geofency/__init__.py index f27798e9e0d..88b72f02cc2 100644 --- a/homeassistant/components/geofency/__init__.py +++ b/homeassistant/components/geofency/__init__.py @@ -133,6 +133,11 @@ async def async_unload_entry(hass, entry): await hass.config_entries.async_forward_entry_unload(entry, DEVICE_TRACKER) return True + +# pylint: disable=invalid-name +async_remove_entry = config_entry_flow.webhook_async_remove_entry + + config_entry_flow.register_webhook_flow( DOMAIN, 'Geofency Webhook', diff --git a/homeassistant/components/gpslogger/__init__.py b/homeassistant/components/gpslogger/__init__.py index 12da63d8ebb..6bc9d11a68e 100644 --- a/homeassistant/components/gpslogger/__init__.py +++ b/homeassistant/components/gpslogger/__init__.py @@ -104,6 +104,11 @@ async def async_unload_entry(hass, entry): await hass.config_entries.async_forward_entry_unload(entry, DEVICE_TRACKER) return True + +# pylint: disable=invalid-name +async_remove_entry = config_entry_flow.webhook_async_remove_entry + + config_entry_flow.register_webhook_flow( DOMAIN, 'GPSLogger Webhook', diff --git a/homeassistant/components/ifttt/__init__.py b/homeassistant/components/ifttt/__init__.py index 4ab361d41eb..bad3984ea5b 100644 --- a/homeassistant/components/ifttt/__init__.py +++ b/homeassistant/components/ifttt/__init__.py @@ -108,6 +108,11 @@ async def async_unload_entry(hass, entry): hass.components.webhook.async_unregister(entry.data[CONF_WEBHOOK_ID]) return True + +# pylint: disable=invalid-name +async_remove_entry = config_entry_flow.webhook_async_remove_entry + + config_entry_flow.register_webhook_flow( DOMAIN, 'IFTTT Webhook', diff --git a/homeassistant/components/locative/__init__.py b/homeassistant/components/locative/__init__.py index e6a5b56ecda..335ae4cfe1e 100644 --- a/homeassistant/components/locative/__init__.py +++ b/homeassistant/components/locative/__init__.py @@ -141,10 +141,14 @@ async def async_setup_entry(hass, entry): async def async_unload_entry(hass, entry): """Unload a config entry.""" hass.components.webhook.async_unregister(entry.data[CONF_WEBHOOK_ID]) - await hass.config_entries.async_forward_entry_unload(entry, DEVICE_TRACKER) return True + +# pylint: disable=invalid-name +async_remove_entry = config_entry_flow.webhook_async_remove_entry + + config_entry_flow.register_webhook_flow( DOMAIN, 'Locative Webhook', diff --git a/homeassistant/components/mailgun/__init__.py b/homeassistant/components/mailgun/__init__.py index 3903bd14e25..2a941d8bf50 100644 --- a/homeassistant/components/mailgun/__init__.py +++ b/homeassistant/components/mailgun/__init__.py @@ -88,6 +88,11 @@ async def async_unload_entry(hass, entry): hass.components.webhook.async_unregister(entry.data[CONF_WEBHOOK_ID]) return True + +# pylint: disable=invalid-name +async_remove_entry = config_entry_flow.webhook_async_remove_entry + + config_entry_flow.register_webhook_flow( DOMAIN, 'Mailgun Webhook', diff --git a/homeassistant/components/twilio/__init__.py b/homeassistant/components/twilio/__init__.py index ce8c272165f..e7ba06a05f7 100644 --- a/homeassistant/components/twilio/__init__.py +++ b/homeassistant/components/twilio/__init__.py @@ -60,6 +60,11 @@ async def async_unload_entry(hass, entry): hass.components.webhook.async_unregister(entry.data[CONF_WEBHOOK_ID]) return True + +# pylint: disable=invalid-name +async_remove_entry = config_entry_flow.webhook_async_remove_entry + + config_entry_flow.register_webhook_flow( DOMAIN, 'Twilio Webhook', diff --git a/homeassistant/helpers/config_entry_flow.py b/homeassistant/helpers/config_entry_flow.py index 8f5705bc67a..6d200a39c85 100644 --- a/homeassistant/helpers/config_entry_flow.py +++ b/homeassistant/helpers/config_entry_flow.py @@ -118,15 +118,35 @@ class WebhookFlowHandler(config_entries.ConfigFlow): ) webhook_id = self.hass.components.webhook.async_generate_id() - webhook_url = \ - self.hass.components.webhook.async_generate_url(webhook_id) + + if self.hass.components.cloud.async_active_subscription(): + webhook_url = \ + await self.hass.components.cloud.async_create_cloudhook( + webhook_id + ) + cloudhook = True + else: + webhook_url = \ + self.hass.components.webhook.async_generate_url(webhook_id) + cloudhook = False self._description_placeholder['webhook_url'] = webhook_url return self.async_create_entry( title=self._title, data={ - 'webhook_id': webhook_id + 'webhook_id': webhook_id, + 'cloudhook': cloudhook, }, description_placeholders=self._description_placeholder ) + + +async def webhook_async_remove_entry(hass, entry) -> None: + """Remove a webhook config entry.""" + if (not entry.data.get('cloudhook') or + 'cloud' not in hass.config.components): + return + + await hass.components.cloud.async_delete_cloudhook( + entry.data['webhook_id']) diff --git a/tests/helpers/test_config_entry_flow.py b/tests/helpers/test_config_entry_flow.py index 846c2cd1560..c198325b350 100644 --- a/tests/helpers/test_config_entry_flow.py +++ b/tests/helpers/test_config_entry_flow.py @@ -3,9 +3,9 @@ from unittest.mock import patch, Mock import pytest -from homeassistant import config_entries, data_entry_flow, loader +from homeassistant import config_entries, data_entry_flow, loader, setup from homeassistant.helpers import config_entry_flow -from tests.common import MockConfigEntry, MockModule +from tests.common import MockConfigEntry, MockModule, mock_coro @pytest.fixture @@ -193,3 +193,51 @@ async def test_webhook_config_flow_registers_webhook(hass, webhook_flow_conf): assert result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY assert result['data']['webhook_id'] is not None + + +async def test_webhook_create_cloudhook(hass, webhook_flow_conf): + """Test only a single entry is allowed.""" + assert await setup.async_setup_component(hass, 'cloud', {}) + + async_setup_entry = Mock(return_value=mock_coro(True)) + async_unload_entry = Mock(return_value=mock_coro(True)) + + loader.set_component(hass, 'test_single', MockModule( + 'test_single', + async_setup_entry=async_setup_entry, + async_unload_entry=async_unload_entry, + async_remove_entry=config_entry_flow.webhook_async_remove_entry, + )) + + result = await hass.config_entries.flow.async_init( + 'test_single', context={'source': config_entries.SOURCE_USER}) + assert result['type'] == data_entry_flow.RESULT_TYPE_FORM + + coro = mock_coro({ + 'cloudhook_url': 'https://example.com' + }) + + with patch('hass_nabucasa.cloudhooks.Cloudhooks.async_create', + return_value=coro) as mock_create, \ + patch('homeassistant.components.cloud.async_active_subscription', + return_value=True), \ + patch('homeassistant.components.cloud.async_is_logged_in', + return_value=True): + + result = await hass.config_entries.flow.async_configure( + result['flow_id'], {}) + + assert result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result['description_placeholders']['webhook_url'] == \ + 'https://example.com' + assert len(mock_create.mock_calls) == 1 + assert len(async_setup_entry.mock_calls) == 1 + + with patch('hass_nabucasa.cloudhooks.Cloudhooks.async_delete', + return_value=coro) as mock_delete: + + result = \ + await hass.config_entries.async_remove(result['result'].entry_id) + + assert len(mock_delete.mock_calls) == 1 + assert result['require_restart'] is False