mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 13:17:32 +00:00
Expose create/delete cloudhook (#21606)
* Expose create/delete cloudhook * Make sure we dont publish cloudhooks when not connected
This commit is contained in:
parent
c25cbccca9
commit
f5ed6432eb
@ -6,17 +6,21 @@ import os
|
|||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant.core import callback
|
||||||
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
EVENT_HOMEASSISTANT_START, CLOUD_NEVER_EXPOSED_ENTITIES, CONF_REGION,
|
EVENT_HOMEASSISTANT_START, CLOUD_NEVER_EXPOSED_ENTITIES, CONF_REGION,
|
||||||
CONF_MODE, CONF_NAME)
|
CONF_MODE, CONF_NAME)
|
||||||
from homeassistant.helpers import entityfilter, config_validation as cv
|
from homeassistant.helpers import entityfilter, config_validation as cv
|
||||||
|
from homeassistant.loader import bind_hass
|
||||||
from homeassistant.util import dt as dt_util
|
from homeassistant.util import dt as dt_util
|
||||||
|
from homeassistant.util.aiohttp import MockRequest
|
||||||
from homeassistant.components.alexa import smart_home as alexa_sh
|
from homeassistant.components.alexa import smart_home as alexa_sh
|
||||||
from homeassistant.components.google_assistant import helpers as ga_h
|
from homeassistant.components.google_assistant import helpers as ga_h
|
||||||
from homeassistant.components.google_assistant import const as ga_c
|
from homeassistant.components.google_assistant import const as ga_c
|
||||||
|
|
||||||
from . import http_api, iot, auth_api, prefs, cloudhooks
|
from . import http_api, iot, auth_api, prefs, cloudhooks
|
||||||
from .const import CONFIG_DIR, DOMAIN, SERVERS
|
from .const import CONFIG_DIR, DOMAIN, SERVERS, STATE_CONNECTED
|
||||||
|
|
||||||
REQUIREMENTS = ['warrant==0.6.1']
|
REQUIREMENTS = ['warrant==0.6.1']
|
||||||
|
|
||||||
@ -81,6 +85,43 @@ CONFIG_SCHEMA = vol.Schema({
|
|||||||
}, extra=vol.ALLOW_EXTRA)
|
}, extra=vol.ALLOW_EXTRA)
|
||||||
|
|
||||||
|
|
||||||
|
class CloudNotAvailable(HomeAssistantError):
|
||||||
|
"""Raised when an action requires the cloud but it's not available."""
|
||||||
|
|
||||||
|
|
||||||
|
@bind_hass
|
||||||
|
@callback
|
||||||
|
def async_is_logged_in(hass):
|
||||||
|
"""Test if user is logged in."""
|
||||||
|
return DOMAIN in hass.data and hass.data[DOMAIN].is_logged_in
|
||||||
|
|
||||||
|
|
||||||
|
@bind_hass
|
||||||
|
async def async_create_cloudhook(hass, webhook_id):
|
||||||
|
"""Create a cloudhook."""
|
||||||
|
if not async_is_logged_in(hass):
|
||||||
|
raise CloudNotAvailable
|
||||||
|
|
||||||
|
return await hass.data[DOMAIN].cloudhooks.async_create(webhook_id)
|
||||||
|
|
||||||
|
|
||||||
|
@bind_hass
|
||||||
|
async def async_delete_cloudhook(hass, webhook_id):
|
||||||
|
"""Delete a cloudhook."""
|
||||||
|
if not async_is_logged_in(hass):
|
||||||
|
raise CloudNotAvailable
|
||||||
|
|
||||||
|
return await hass.data[DOMAIN].cloudhooks.async_delete(webhook_id)
|
||||||
|
|
||||||
|
|
||||||
|
def is_cloudhook_request(request):
|
||||||
|
"""Test if a request came from a cloudhook.
|
||||||
|
|
||||||
|
Async friendly.
|
||||||
|
"""
|
||||||
|
return isinstance(request, MockRequest)
|
||||||
|
|
||||||
|
|
||||||
async def async_setup(hass, config):
|
async def async_setup(hass, config):
|
||||||
"""Initialize the Home Assistant cloud."""
|
"""Initialize the Home Assistant cloud."""
|
||||||
if DOMAIN in config:
|
if DOMAIN in config:
|
||||||
@ -152,6 +193,11 @@ class Cloud:
|
|||||||
"""Get if cloud is logged in."""
|
"""Get if cloud is logged in."""
|
||||||
return self.id_token is not None
|
return self.id_token is not None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_connected(self):
|
||||||
|
"""Get if cloud is connected."""
|
||||||
|
return self.iot.state == STATE_CONNECTED
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def subscription_expired(self):
|
def subscription_expired(self):
|
||||||
"""Return a boolean if the subscription has expired."""
|
"""Return a boolean if the subscription has expired."""
|
||||||
|
@ -14,6 +14,9 @@ class Cloudhooks:
|
|||||||
|
|
||||||
async def async_publish_cloudhooks(self):
|
async def async_publish_cloudhooks(self):
|
||||||
"""Inform the Relayer of the cloudhooks that we support."""
|
"""Inform the Relayer of the cloudhooks that we support."""
|
||||||
|
if not self.cloud.is_connected:
|
||||||
|
return
|
||||||
|
|
||||||
cloudhooks = self.cloud.prefs.cloudhooks
|
cloudhooks = self.cloud.prefs.cloudhooks
|
||||||
await self.cloud.iot.async_send_message('webhook-register', {
|
await self.cloud.iot.async_send_message('webhook-register', {
|
||||||
'cloudhook_ids': [info['cloudhook_id'] for info
|
'cloudhook_ids': [info['cloudhook_id'] for info
|
||||||
|
@ -32,3 +32,7 @@ You have been logged out of Home Assistant Cloud because we have been unable
|
|||||||
to verify your credentials. Please [log in](/config/cloud) again to continue
|
to verify your credentials. Please [log in](/config/cloud) again to continue
|
||||||
using the service.
|
using the service.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
STATE_CONNECTING = 'connecting'
|
||||||
|
STATE_CONNECTED = 'connected'
|
||||||
|
STATE_DISCONNECTED = 'disconnected'
|
||||||
|
@ -16,15 +16,14 @@ from homeassistant.util.aiohttp import MockRequest
|
|||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
from . import auth_api
|
from . import auth_api
|
||||||
from . import utils
|
from . import utils
|
||||||
from .const import MESSAGE_EXPIRATION, MESSAGE_AUTH_FAIL
|
from .const import (
|
||||||
|
MESSAGE_EXPIRATION, MESSAGE_AUTH_FAIL, STATE_CONNECTED, STATE_CONNECTING,
|
||||||
|
STATE_DISCONNECTED
|
||||||
|
)
|
||||||
|
|
||||||
HANDLERS = Registry()
|
HANDLERS = Registry()
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
STATE_CONNECTING = 'connecting'
|
|
||||||
STATE_CONNECTED = 'connected'
|
|
||||||
STATE_DISCONNECTED = 'disconnected'
|
|
||||||
|
|
||||||
|
|
||||||
class UnknownHandler(Exception):
|
class UnknownHandler(Exception):
|
||||||
"""Exception raised when trying to handle unknown handler."""
|
"""Exception raised when trying to handle unknown handler."""
|
||||||
|
@ -68,3 +68,29 @@ async def test_disable(mock_cloudhooks):
|
|||||||
assert publish_calls[0][1][1] == {
|
assert publish_calls[0][1][1] == {
|
||||||
'cloudhook_ids': []
|
'cloudhook_ids': []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_create_without_connected(mock_cloudhooks, aioclient_mock):
|
||||||
|
"""Test we don't publish a hook if not connected."""
|
||||||
|
mock_cloudhooks.cloud.is_connected = False
|
||||||
|
# Make sure we fail test when we send a message.
|
||||||
|
mock_cloudhooks.cloud.iot.async_send_message.side_effect = ValueError
|
||||||
|
|
||||||
|
aioclient_mock.post('https://webhook-create.url', json={
|
||||||
|
'cloudhook_id': 'mock-cloud-id',
|
||||||
|
'url': 'https://hooks.nabu.casa/ZXCZCXZ',
|
||||||
|
})
|
||||||
|
|
||||||
|
hook = {
|
||||||
|
'webhook_id': 'mock-webhook-id',
|
||||||
|
'cloudhook_id': 'mock-cloud-id',
|
||||||
|
'cloudhook_url': 'https://hooks.nabu.casa/ZXCZCXZ',
|
||||||
|
}
|
||||||
|
|
||||||
|
assert hook == await mock_cloudhooks.async_create('mock-webhook-id')
|
||||||
|
|
||||||
|
assert mock_cloudhooks.cloud.prefs.cloudhooks == {
|
||||||
|
'mock-webhook-id': hook
|
||||||
|
}
|
||||||
|
|
||||||
|
assert len(mock_cloudhooks.cloud.iot.async_send_message.mock_calls) == 0
|
||||||
|
@ -5,6 +5,7 @@ from unittest.mock import patch, MagicMock, mock_open
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
from homeassistant.setup import async_setup_component
|
||||||
from homeassistant.components import cloud
|
from homeassistant.components import cloud
|
||||||
from homeassistant.util.dt import utcnow
|
from homeassistant.util.dt import utcnow
|
||||||
|
|
||||||
@ -175,3 +176,71 @@ def test_subscription_not_expired(hass):
|
|||||||
patch('homeassistant.util.dt.utcnow',
|
patch('homeassistant.util.dt.utcnow',
|
||||||
return_value=utcnow().replace(year=2017, month=11, day=9)):
|
return_value=utcnow().replace(year=2017, month=11, day=9)):
|
||||||
assert not cl.subscription_expired
|
assert not cl.subscription_expired
|
||||||
|
|
||||||
|
|
||||||
|
async def test_create_cloudhook_no_login(hass):
|
||||||
|
"""Test create cloudhook when not logged in."""
|
||||||
|
assert await async_setup_component(hass, 'cloud', {})
|
||||||
|
coro = mock_coro({'yo': 'hey'})
|
||||||
|
with patch('homeassistant.components.cloud.cloudhooks.'
|
||||||
|
'Cloudhooks.async_create', return_value=coro) as mock_create, \
|
||||||
|
pytest.raises(cloud.CloudNotAvailable):
|
||||||
|
await hass.components.cloud.async_create_cloudhook('hello')
|
||||||
|
|
||||||
|
assert len(mock_create.mock_calls) == 0
|
||||||
|
|
||||||
|
|
||||||
|
async def test_delete_cloudhook_no_login(hass):
|
||||||
|
"""Test delete cloudhook when not logged in."""
|
||||||
|
assert await async_setup_component(hass, 'cloud', {})
|
||||||
|
coro = mock_coro({'yo': 'hey'})
|
||||||
|
with patch('homeassistant.components.cloud.cloudhooks.'
|
||||||
|
'Cloudhooks.async_delete', return_value=coro) as mock_delete, \
|
||||||
|
pytest.raises(cloud.CloudNotAvailable):
|
||||||
|
await hass.components.cloud.async_delete_cloudhook('hello')
|
||||||
|
|
||||||
|
assert len(mock_delete.mock_calls) == 0
|
||||||
|
|
||||||
|
|
||||||
|
async def test_create_cloudhook(hass):
|
||||||
|
"""Test create cloudhook."""
|
||||||
|
assert await async_setup_component(hass, 'cloud', {})
|
||||||
|
coro = mock_coro({'yo': 'hey'})
|
||||||
|
with patch('homeassistant.components.cloud.cloudhooks.'
|
||||||
|
'Cloudhooks.async_create', return_value=coro) as mock_create, \
|
||||||
|
patch('homeassistant.components.cloud.async_is_logged_in',
|
||||||
|
return_value=True):
|
||||||
|
result = await hass.components.cloud.async_create_cloudhook('hello')
|
||||||
|
|
||||||
|
assert result == {'yo': 'hey'}
|
||||||
|
assert len(mock_create.mock_calls) == 1
|
||||||
|
|
||||||
|
|
||||||
|
async def test_delete_cloudhook(hass):
|
||||||
|
"""Test delete cloudhook."""
|
||||||
|
assert await async_setup_component(hass, 'cloud', {})
|
||||||
|
coro = mock_coro({'yo': 'hey'})
|
||||||
|
with patch('homeassistant.components.cloud.cloudhooks.'
|
||||||
|
'Cloudhooks.async_delete', return_value=coro) as mock_delete, \
|
||||||
|
patch('homeassistant.components.cloud.async_is_logged_in',
|
||||||
|
return_value=True):
|
||||||
|
result = await hass.components.cloud.async_delete_cloudhook('hello')
|
||||||
|
|
||||||
|
assert result == {'yo': 'hey'}
|
||||||
|
assert len(mock_delete.mock_calls) == 1
|
||||||
|
|
||||||
|
|
||||||
|
async def test_async_logged_in(hass):
|
||||||
|
"""Test if is_logged_in works."""
|
||||||
|
# Cloud not loaded
|
||||||
|
assert hass.components.cloud.async_is_logged_in() is False
|
||||||
|
|
||||||
|
assert await async_setup_component(hass, 'cloud', {})
|
||||||
|
|
||||||
|
# Cloud loaded, not logged in
|
||||||
|
assert hass.components.cloud.async_is_logged_in() is False
|
||||||
|
|
||||||
|
hass.data['cloud'].id_token = "some token"
|
||||||
|
|
||||||
|
# Cloud loaded, logged in
|
||||||
|
assert hass.components.cloud.async_is_logged_in() is True
|
||||||
|
Loading…
x
Reference in New Issue
Block a user