Migrate twilio webhooks to the webhook component (#17715)

* Migrate twilio webhooks to the webhook component

* Fix typos in twilio

* Mock out twilio in the tests

* Lint

* Fix regression in twilio response
This commit is contained in:
Rohan Kapoor 2018-10-25 00:46:22 -07:00 committed by Paulus Schoutsen
parent 599390d985
commit 5024a80d61
8 changed files with 155 additions and 59 deletions

View file

@ -333,7 +333,6 @@ omit =
homeassistant/components/tradfri.py homeassistant/components/tradfri.py
homeassistant/components/*/tradfri.py homeassistant/components/*/tradfri.py
homeassistant/components/twilio.py
homeassistant/components/notify/twilio_sms.py homeassistant/components/notify/twilio_sms.py
homeassistant/components/notify/twilio_call.py homeassistant/components/notify/twilio_call.py

View file

@ -1,58 +0,0 @@
"""
Support for Twilio.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/twilio/
"""
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.core import callback
from homeassistant.components.http import HomeAssistantView
REQUIREMENTS = ['twilio==6.19.1']
DOMAIN = 'twilio'
API_PATH = '/api/{}'.format(DOMAIN)
CONF_ACCOUNT_SID = 'account_sid'
CONF_AUTH_TOKEN = 'auth_token'
DATA_TWILIO = DOMAIN
DEPENDENCIES = ['http']
RECEIVED_DATA = '{}_data_received'.format(DOMAIN)
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(CONF_ACCOUNT_SID): cv.string,
vol.Required(CONF_AUTH_TOKEN): cv.string
}),
}, extra=vol.ALLOW_EXTRA)
def setup(hass, config):
"""Set up the Twilio component."""
from twilio.rest import TwilioRestClient
conf = config[DOMAIN]
hass.data[DATA_TWILIO] = TwilioRestClient(
conf.get(CONF_ACCOUNT_SID), conf.get(CONF_AUTH_TOKEN))
hass.http.register_view(TwilioReceiveDataView())
return True
class TwilioReceiveDataView(HomeAssistantView):
"""Handle data from Twilio inbound messages and calls."""
url = API_PATH
name = 'api:{}'.format(DOMAIN)
@callback
def post(self, request): # pylint: disable=no-self-use
"""Handle Twilio data post."""
from twilio.twiml import TwiML
hass = request.app['hass']
data = yield from request.post()
hass.bus.async_fire(RECEIVED_DATA, dict(data))
return TwiML().to_xml()

View file

@ -0,0 +1,18 @@
{
"config": {
"title": "Twilio",
"step": {
"user": {
"title": "Set up the Twilio Webhook",
"description": "Are you sure you want to set up Twilio?"
}
},
"abort": {
"one_instance_allowed": "Only a single instance is necessary.",
"not_internet_accessible": "Your Home Assistant instance needs to be accessible from the internet to receive Twilio messages."
},
"create_entry": {
"default": "To send events to Home Assistant, you will need to setup [Webhooks with Twilio]({twilio_url}).\n\nFill in the following info:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/x-www-form-urlencoded\n\nSee [the documentation]({docs_url}) on how to configure automations to handle incoming data."
}
}
}

View file

@ -0,0 +1,76 @@
"""
Support for Twilio.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/twilio/
"""
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.const import CONF_WEBHOOK_ID
from homeassistant.helpers import config_entry_flow
REQUIREMENTS = ['twilio==6.19.1']
DEPENDENCIES = ['webhook']
DOMAIN = 'twilio'
CONF_ACCOUNT_SID = 'account_sid'
CONF_AUTH_TOKEN = 'auth_token'
DATA_TWILIO = DOMAIN
RECEIVED_DATA = '{}_data_received'.format(DOMAIN)
CONFIG_SCHEMA = vol.Schema({
vol.Optional(DOMAIN): vol.Schema({
vol.Required(CONF_ACCOUNT_SID): cv.string,
vol.Required(CONF_AUTH_TOKEN): cv.string,
}),
}, extra=vol.ALLOW_EXTRA)
async def async_setup(hass, config):
"""Set up the Twilio component."""
from twilio.rest import TwilioRestClient
if DOMAIN not in config:
return True
conf = config[DOMAIN]
hass.data[DATA_TWILIO] = TwilioRestClient(
conf.get(CONF_ACCOUNT_SID), conf.get(CONF_AUTH_TOKEN))
return True
async def handle_webhook(hass, webhook_id, request):
"""Handle incoming webhook from Twilio for inbound messages and calls."""
from twilio.twiml import TwiML
data = dict(await request.post())
data['webhook_id'] = webhook_id
hass.bus.async_fire(RECEIVED_DATA, dict(data))
return TwiML().to_xml()
async def async_setup_entry(hass, entry):
"""Configure based on config entry."""
hass.components.webhook.async_register(
entry.data[CONF_WEBHOOK_ID], handle_webhook)
return True
async def async_unload_entry(hass, entry):
"""Unload a config entry."""
hass.components.webhook.async_unregister(entry.data[CONF_WEBHOOK_ID])
return True
config_entry_flow.register_webhook_flow(
DOMAIN,
'Twilio Webhook',
{
'twilio_url':
'https://www.twilio.com/docs/glossary/what-is-a-webhook',
'docs_url': 'https://www.home-assistant.io/components/twilio/'
}
)

View file

@ -0,0 +1,18 @@
{
"config": {
"title": "Twilio",
"step": {
"user": {
"title": "Set up the Twilio Webhook",
"description": "Are you sure you want to set up Twilio?"
}
},
"abort": {
"one_instance_allowed": "Only a single instance is necessary.",
"not_internet_accessible": "Your Home Assistant instance needs to be accessible from the internet to receive Twilio messages."
},
"create_entry": {
"default": "To send events to Home Assistant, you will need to setup [Webhooks with Twilio]({twilio_url}).\n\nFill in the following info:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/x-www-form-urlencoded\n\nSee [the documentation]({docs_url}) on how to configure automations to handle incoming data."
}
}
}

View file

@ -151,6 +151,7 @@ FLOWS = [
'smhi', 'smhi',
'sonos', 'sonos',
'tradfri', 'tradfri',
'twilio',
'unifi', 'unifi',
'upnp', 'upnp',
'zone', 'zone',

View file

@ -0,0 +1 @@
"""Tests for the Twilio component."""

View file

@ -0,0 +1,41 @@
"""Test the init file of Twilio."""
from unittest.mock import patch
from homeassistant import data_entry_flow
from homeassistant.components import twilio
from homeassistant.core import callback
from tests.common import MockDependency
@MockDependency('twilio', 'rest')
@MockDependency('twilio', 'twiml')
async def test_config_flow_registers_webhook(hass, aiohttp_client):
"""Test setting up Twilio and sending webhook."""
with patch('homeassistant.util.get_local_ip', return_value='example.com'):
result = await hass.config_entries.flow.async_init('twilio', context={
'source': 'user'
})
assert result['type'] == data_entry_flow.RESULT_TYPE_FORM, result
result = await hass.config_entries.flow.async_configure(
result['flow_id'], {})
assert result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
webhook_id = result['result'].data['webhook_id']
twilio_events = []
@callback
def handle_event(event):
"""Handle Twilio event."""
twilio_events.append(event)
hass.bus.async_listen(twilio.RECEIVED_DATA, handle_event)
client = await aiohttp_client(hass.http.app)
await client.post('/api/webhook/{}'.format(webhook_id), data={
'hello': 'twilio'
})
assert len(twilio_events) == 1
assert twilio_events[0].data['webhook_id'] == webhook_id
assert twilio_events[0].data['hello'] == 'twilio'