mirror of
https://github.com/home-assistant/core.git
synced 2025-07-18 10:47:10 +00:00
Switch OwnTracks HTTP to use webhook component (#17034)
* Update OwnTracks_HTTP to use the webhook component * Update owntracks_http.py * Update owntracks_http.py
This commit is contained in:
parent
589764900a
commit
eb385515c8
@ -4,52 +4,79 @@ Device tracker platform that adds support for OwnTracks over HTTP.
|
|||||||
For more details about this platform, please refer to the documentation at
|
For more details about this platform, please refer to the documentation at
|
||||||
https://home-assistant.io/components/device_tracker.owntracks_http/
|
https://home-assistant.io/components/device_tracker.owntracks_http/
|
||||||
"""
|
"""
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from aiohttp.web_exceptions import HTTPInternalServerError
|
from aiohttp.web import Response
|
||||||
|
import voluptuous as vol
|
||||||
from homeassistant.components.http import HomeAssistantView
|
|
||||||
|
|
||||||
# pylint: disable=unused-import
|
# pylint: disable=unused-import
|
||||||
from .owntracks import ( # NOQA
|
from homeassistant.components.device_tracker.owntracks import ( # NOQA
|
||||||
REQUIREMENTS, PLATFORM_SCHEMA, context_from_config, async_handle_message)
|
PLATFORM_SCHEMA, REQUIREMENTS, async_handle_message, context_from_config)
|
||||||
|
from homeassistant.const import CONF_WEBHOOK_ID
|
||||||
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
|
||||||
|
DEPENDENCIES = ['webhook']
|
||||||
|
|
||||||
DEPENDENCIES = ['http']
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
EVENT_RECEIVED = 'owntracks_http_webhook_received'
|
||||||
|
EVENT_RESPONSE = 'owntracks_http_webhook_response_'
|
||||||
|
|
||||||
|
DOMAIN = 'device_tracker.owntracks_http'
|
||||||
|
|
||||||
|
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||||
|
vol.Required(CONF_WEBHOOK_ID): cv.string
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_scanner(hass, config, async_see, discovery_info=None):
|
async def async_setup_scanner(hass, config, async_see, discovery_info=None):
|
||||||
"""Set up an OwnTracks tracker."""
|
"""Set up OwnTracks HTTP component."""
|
||||||
context = context_from_config(async_see, config)
|
context = context_from_config(async_see, config)
|
||||||
|
|
||||||
hass.http.register_view(OwnTracksView(context))
|
subscription = context.mqtt_topic
|
||||||
|
topic = re.sub('/#$', '', subscription)
|
||||||
|
|
||||||
return True
|
async def handle_webhook(hass, webhook_id, request):
|
||||||
|
"""Handle webhook callback."""
|
||||||
|
headers = request.headers
|
||||||
|
data = dict()
|
||||||
|
|
||||||
|
if 'X-Limit-U' in headers:
|
||||||
|
data['user'] = headers['X-Limit-U']
|
||||||
|
elif 'u' in request.query:
|
||||||
|
data['user'] = request.query['u']
|
||||||
|
else:
|
||||||
|
return Response(
|
||||||
|
body=json.dumps({'error': 'You need to supply username.'}),
|
||||||
|
content_type="application/json"
|
||||||
|
)
|
||||||
|
|
||||||
class OwnTracksView(HomeAssistantView):
|
if 'X-Limit-D' in headers:
|
||||||
"""View to handle OwnTracks HTTP requests."""
|
data['device'] = headers['X-Limit-D']
|
||||||
|
elif 'd' in request.query:
|
||||||
url = '/api/owntracks/{user}/{device}'
|
data['device'] = request.query['d']
|
||||||
name = 'api:owntracks'
|
else:
|
||||||
|
return Response(
|
||||||
def __init__(self, context):
|
body=json.dumps({'error': 'You need to supply device name.'}),
|
||||||
"""Initialize OwnTracks URL endpoints."""
|
content_type="application/json"
|
||||||
self.context = context
|
)
|
||||||
|
|
||||||
async def post(self, request, user, device):
|
|
||||||
"""Handle an OwnTracks message."""
|
|
||||||
hass = request.app['hass']
|
|
||||||
|
|
||||||
subscription = self.context.mqtt_topic
|
|
||||||
topic = re.sub('/#$', '', subscription)
|
|
||||||
|
|
||||||
message = await request.json()
|
message = await request.json()
|
||||||
message['topic'] = '{}/{}/{}'.format(topic, user, device)
|
|
||||||
|
message['topic'] = '{}/{}/{}'.format(topic, data['user'],
|
||||||
|
data['device'])
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await async_handle_message(hass, self.context, message)
|
await async_handle_message(hass, context, message)
|
||||||
return self.json([])
|
return Response(body=json.dumps([]), status=200,
|
||||||
|
content_type="application/json")
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise HTTPInternalServerError
|
_LOGGER.error("Received invalid JSON")
|
||||||
|
return None
|
||||||
|
|
||||||
|
hass.components.webhook.async_register(
|
||||||
|
'owntracks', 'OwnTracks', config['webhook_id'], handle_webhook)
|
||||||
|
|
||||||
|
return True
|
||||||
|
@ -2,11 +2,47 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
import os
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
from homeassistant.components import device_tracker
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
|
|
||||||
from tests.common import mock_coro, mock_component
|
from tests.common import mock_component, mock_coro
|
||||||
|
|
||||||
|
MINIMAL_LOCATION_MESSAGE = {
|
||||||
|
'_type': 'location',
|
||||||
|
'lon': 45,
|
||||||
|
'lat': 90,
|
||||||
|
'p': 101.3977584838867,
|
||||||
|
'tid': 'test',
|
||||||
|
'tst': 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
LOCATION_MESSAGE = {
|
||||||
|
'_type': 'location',
|
||||||
|
'acc': 60,
|
||||||
|
'alt': 27,
|
||||||
|
'batt': 92,
|
||||||
|
'cog': 248,
|
||||||
|
'lon': 45,
|
||||||
|
'lat': 90,
|
||||||
|
'p': 101.3977584838867,
|
||||||
|
'tid': 'test',
|
||||||
|
't': 'u',
|
||||||
|
'tst': 1,
|
||||||
|
'vac': 4,
|
||||||
|
'vel': 0
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def owntracks_http_cleanup(hass):
|
||||||
|
"""Remove known_devices.yaml."""
|
||||||
|
try:
|
||||||
|
os.remove(hass.config.path(device_tracker.YAML_DEVICES))
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
@ -19,42 +55,70 @@ def mock_client(hass, aiohttp_client):
|
|||||||
hass.loop.run_until_complete(
|
hass.loop.run_until_complete(
|
||||||
async_setup_component(hass, 'device_tracker', {
|
async_setup_component(hass, 'device_tracker', {
|
||||||
'device_tracker': {
|
'device_tracker': {
|
||||||
'platform': 'owntracks_http'
|
'platform': 'owntracks_http',
|
||||||
|
'webhook_id': 'owntracks_test'
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
return hass.loop.run_until_complete(aiohttp_client(hass.http.app))
|
return hass.loop.run_until_complete(aiohttp_client(hass.http.app))
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def mock_handle_message():
|
|
||||||
"""Mock async_handle_message."""
|
|
||||||
with patch('homeassistant.components.device_tracker.'
|
|
||||||
'owntracks_http.async_handle_message') as mock:
|
|
||||||
mock.return_value = mock_coro(None)
|
|
||||||
yield mock
|
|
||||||
|
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def test_forward_message_correctly(mock_client, mock_handle_message):
|
def test_handle_valid_message(mock_client):
|
||||||
"""Test that we forward messages correctly to OwnTracks handle message."""
|
"""Test that we forward messages correctly to OwnTracks."""
|
||||||
resp = yield from mock_client.post('/api/owntracks/user/device', json={
|
resp = yield from mock_client.post('/api/webhook/owntracks_test?'
|
||||||
'_type': 'test'
|
'u=test&d=test',
|
||||||
})
|
json=LOCATION_MESSAGE)
|
||||||
|
|
||||||
assert resp.status == 200
|
assert resp.status == 200
|
||||||
assert len(mock_handle_message.mock_calls) == 1
|
|
||||||
|
|
||||||
data = mock_handle_message.mock_calls[0][1][2]
|
json = yield from resp.json()
|
||||||
assert data == {
|
assert json == []
|
||||||
'_type': 'test',
|
|
||||||
'topic': 'owntracks/user/device'
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def test_handle_value_error(mock_client, mock_handle_message):
|
def test_handle_valid_minimal_message(mock_client):
|
||||||
"""Test that we handle errors from handle message correctly."""
|
"""Test that we forward messages correctly to OwnTracks."""
|
||||||
mock_handle_message.side_effect = ValueError
|
resp = yield from mock_client.post('/api/webhook/owntracks_test?'
|
||||||
resp = yield from mock_client.post('/api/owntracks/user/device', json={
|
'u=test&d=test',
|
||||||
'_type': 'test'
|
json=MINIMAL_LOCATION_MESSAGE)
|
||||||
})
|
|
||||||
assert resp.status == 500
|
assert resp.status == 200
|
||||||
|
|
||||||
|
json = yield from resp.json()
|
||||||
|
assert json == []
|
||||||
|
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def test_handle_value_error(mock_client):
|
||||||
|
"""Test we don't disclose that this is a valid webhook."""
|
||||||
|
resp = yield from mock_client.post('/api/webhook/owntracks_test'
|
||||||
|
'?u=test&d=test', json='')
|
||||||
|
|
||||||
|
assert resp.status == 200
|
||||||
|
|
||||||
|
json = yield from resp.text()
|
||||||
|
assert json == ""
|
||||||
|
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def test_returns_error_missing_username(mock_client):
|
||||||
|
"""Test that an error is returned when username is missing."""
|
||||||
|
resp = yield from mock_client.post('/api/webhook/owntracks_test?d=test',
|
||||||
|
json=LOCATION_MESSAGE)
|
||||||
|
|
||||||
|
assert resp.status == 200
|
||||||
|
|
||||||
|
json = yield from resp.json()
|
||||||
|
assert json == {'error': 'You need to supply username.'}
|
||||||
|
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def test_returns_error_missing_device(mock_client):
|
||||||
|
"""Test that an error is returned when device name is missing."""
|
||||||
|
resp = yield from mock_client.post('/api/webhook/owntracks_test?u=test',
|
||||||
|
json=LOCATION_MESSAGE)
|
||||||
|
|
||||||
|
assert resp.status == 200
|
||||||
|
|
||||||
|
json = yield from resp.json()
|
||||||
|
assert json == {'error': 'You need to supply device name.'}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user