Merge pull request #7335 from home-assistant/release-0-43-2

0.43.2
pull/7435/head 0.43.2
Paulus Schoutsen 2017-04-27 12:42:25 -07:00 committed by GitHub
commit 6902e522b9
30 changed files with 517 additions and 773 deletions

View File

@ -17,7 +17,7 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.event import async_track_time_interval
REQUIREMENTS = ['aioautomatic==0.1.1']
REQUIREMENTS = ['aioautomatic==0.2.1']
_LOGGER = logging.getLogger(__name__)
@ -27,6 +27,8 @@ CONF_DEVICES = 'devices'
DEFAULT_TIMEOUT = 5
SCOPE = ['location', 'vehicle:profile', 'trip']
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_CLIENT_ID): cv.string,
vol.Required(CONF_SECRET): cv.string,
@ -49,7 +51,7 @@ def async_setup_scanner(hass, config, async_see, discovery_info=None):
request_kwargs={'timeout': DEFAULT_TIMEOUT})
try:
session = yield from client.create_session_from_password(
config[CONF_USERNAME], config[CONF_PASSWORD])
SCOPE, config[CONF_USERNAME], config[CONF_PASSWORD])
data = AutomaticData(hass, session, config[CONF_DEVICES], async_see)
except aioautomatic.exceptions.AutomaticError as err:
_LOGGER.error(str(err))

View File

@ -3,19 +3,20 @@
FINGERPRINTS = {
"compatibility.js": "83d9c77748dafa9db49ae77d7f3d8fb0",
"core.js": "5d08475f03adb5969bd31855d5ca0cfd",
"frontend.html": "8264c0ee8dafb09785ec7b934795d3b1",
"mdi.html": "d86ee142ae2476f49384bfe866a2885e",
"frontend.html": "1533f44c55927e814294de757cd7eada",
"mdi.html": "1cc8593d3684f7f6f3b3854403216f77",
"micromarkdown-js.html": "93b5ec4016f0bba585521cf4d18dec1a",
"panels/ha-panel-config.html": "0b42cb4e709ce35ad2666ffeca6f9b14",
"panels/ha-panel-config.html": "39f00f769faa63ee61f1fe6fc85d67f7",
"panels/ha-panel-dev-event.html": "2db9c218065ef0f61d8d08db8093cad2",
"panels/ha-panel-dev-info.html": "61610e015a411cfc84edd2c4d489e71d",
"panels/ha-panel-dev-service.html": "415552027cb083badeff5f16080410ed",
"panels/ha-panel-dev-state.html": "d70314913b8923d750932367b1099750",
"panels/ha-panel-dev-template.html": "567fbf86735e1b891e40c2f4060fec9b",
"panels/ha-panel-history.html": "be115906882752d220199abbaddc53e5",
"panels/ha-panel-hassio.html": "1d954cfe5f47c4be3cf4f6f5db9a83b2",
"panels/ha-panel-history.html": "89062c48c76206cad1cec14ddbb1cbb1",
"panels/ha-panel-iframe.html": "d920f0aa3c903680f2f8795e2255daab",
"panels/ha-panel-logbook.html": "bf29de0c586a598113c6cc09ead12b00",
"panels/ha-panel-logbook.html": "6dd6a16f52117318b202e60f98400163",
"panels/ha-panel-map.html": "31c592c239636f91e07c7ac232a5ebc4",
"panels/ha-panel-zwave.html": "f52d0c001f48e0c7b33a172f3a71b547",
"panels/ha-panel-zwave.html": "a81f82b48439da80286798558f414a2e",
"websocket_test.html": "575de64b431fe11c3785bf96d7813450"
}

File diff suppressed because one or more lines are too long

@ -1 +1 @@
Subproject commit 3fdba359865823805e8ea756c8500d3913976158
Subproject commit 863ccb548616236faafa3b3393a1f51429bb8afd

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -7,70 +7,53 @@ https://home-assistant.io/components/hassio/
import asyncio
import logging
import os
import re
import aiohttp
from aiohttp import web
from aiohttp.web_exceptions import HTTPBadGateway
from aiohttp.web_exceptions import (
HTTPBadGateway, HTTPNotFound, HTTPMethodNotAllowed)
from aiohttp.hdrs import CONTENT_TYPE
import async_timeout
import voluptuous as vol
from homeassistant.config import load_yaml_config_file
from homeassistant.const import CONTENT_TYPE_TEXT_PLAIN
from homeassistant.components.http import HomeAssistantView
from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv
from homeassistant.components.frontend import register_built_in_panel
DOMAIN = 'hassio'
DEPENDENCIES = ['http']
_LOGGER = logging.getLogger(__name__)
LONG_TASK_TIMEOUT = 900
DEFAULT_TIMEOUT = 10
TIMEOUT = 10
SERVICE_HOST_SHUTDOWN = 'host_shutdown'
SERVICE_HOST_REBOOT = 'host_reboot'
HASSIO_REST_COMMANDS = {
'host/shutdown': ['POST'],
'host/reboot': ['POST'],
'host/update': ['GET'],
'host/info': ['GET'],
'supervisor/info': ['GET'],
'supervisor/update': ['POST'],
'supervisor/options': ['POST'],
'supervisor/reload': ['POST'],
'supervisor/logs': ['GET'],
'homeassistant/info': ['GET'],
'homeassistant/update': ['POST'],
'homeassistant/logs': ['GET'],
'network/info': ['GET'],
'network/options': ['GET'],
}
SERVICE_HOST_UPDATE = 'host_update'
SERVICE_HOMEASSISTANT_UPDATE = 'homeassistant_update'
SERVICE_SUPERVISOR_UPDATE = 'supervisor_update'
SERVICE_SUPERVISOR_RELOAD = 'supervisor_reload'
SERVICE_ADDON_INSTALL = 'addon_install'
SERVICE_ADDON_UNINSTALL = 'addon_uninstall'
SERVICE_ADDON_UPDATE = 'addon_update'
SERVICE_ADDON_START = 'addon_start'
SERVICE_ADDON_STOP = 'addon_stop'
ATTR_ADDON = 'addon'
ATTR_VERSION = 'version'
SCHEMA_SERVICE_UPDATE = vol.Schema({
vol.Optional(ATTR_VERSION): cv.string,
})
SCHEMA_SERVICE_ADDONS = vol.Schema({
vol.Required(ATTR_ADDON): cv.slug,
})
SCHEMA_SERVICE_ADDONS_VERSION = SCHEMA_SERVICE_ADDONS.extend({
vol.Optional(ATTR_VERSION): cv.string,
})
SERVICE_MAP = {
SERVICE_HOST_SHUTDOWN: None,
SERVICE_HOST_REBOOT: None,
SERVICE_HOST_UPDATE: SCHEMA_SERVICE_UPDATE,
SERVICE_HOMEASSISTANT_UPDATE: SCHEMA_SERVICE_UPDATE,
SERVICE_SUPERVISOR_UPDATE: SCHEMA_SERVICE_UPDATE,
SERVICE_SUPERVISOR_RELOAD: None,
SERVICE_ADDON_INSTALL: SCHEMA_SERVICE_ADDONS_VERSION,
SERVICE_ADDON_UNINSTALL: SCHEMA_SERVICE_ADDONS,
SERVICE_ADDON_START: SCHEMA_SERVICE_ADDONS,
SERVICE_ADDON_STOP: SCHEMA_SERVICE_ADDONS,
SERVICE_ADDON_UPDATE: SCHEMA_SERVICE_ADDONS_VERSION,
ADDON_REST_COMMANDS = {
'install': ['POST'],
'uninstall': ['POST'],
'start': ['POST'],
'stop': ['POST'],
'update': ['POST'],
'options': ['POST'],
'info': ['GET'],
'logs': ['GET'],
}
@ -91,67 +74,11 @@ def async_setup(hass, config):
_LOGGER.error("Not connected with HassIO!")
return False
# register base api views
for base in ('host', 'homeassistant'):
hass.http.register_view(HassIOBaseView(hassio, base))
for base in ('supervisor', 'network'):
hass.http.register_view(HassIOBaseEditView(hassio, base))
hass.http.register_view(HassIOView(hassio))
# register view for addons
hass.http.register_view(HassIOAddonsView(hassio))
@asyncio.coroutine
def async_service_handler(service):
"""Handle HassIO service calls."""
addon = service.data.get(ATTR_ADDON)
if ATTR_VERSION in service.data:
version = {ATTR_VERSION: service.data[ATTR_VERSION]}
else:
version = None
# map to api call
if service.service == SERVICE_HOST_UPDATE:
yield from hassio.send_command(
"/host/update", payload=version)
elif service.service == SERVICE_HOST_REBOOT:
yield from hassio.send_command("/host/reboot")
elif service.service == SERVICE_HOST_SHUTDOWN:
yield from hassio.send_command("/host/shutdown")
elif service.service == SERVICE_SUPERVISOR_UPDATE:
yield from hassio.send_command(
"/supervisor/update", payload=version)
elif service.service == SERVICE_SUPERVISOR_RELOAD:
yield from hassio.send_command(
"/supervisor/reload", timeout=LONG_TASK_TIMEOUT)
elif service.service == SERVICE_HOMEASSISTANT_UPDATE:
yield from hassio.send_command(
"/homeassistant/update", payload=version,
timeout=LONG_TASK_TIMEOUT)
elif service.service == SERVICE_ADDON_INSTALL:
yield from hassio.send_command(
"/addons/{}/install".format(addon), payload=version,
timeout=LONG_TASK_TIMEOUT)
elif service.service == SERVICE_ADDON_UNINSTALL:
yield from hassio.send_command(
"/addons/{}/uninstall".format(addon))
elif service.service == SERVICE_ADDON_START:
yield from hassio.send_command("/addons/{}/start".format(addon))
elif service.service == SERVICE_ADDON_STOP:
yield from hassio.send_command(
"/addons/{}/stop".format(addon), timeout=LONG_TASK_TIMEOUT)
elif service.service == SERVICE_ADDON_UPDATE:
yield from hassio.send_command(
"/addons/{}/update".format(addon), payload=version,
timeout=LONG_TASK_TIMEOUT)
descriptions = yield from hass.loop.run_in_executor(
None, load_yaml_config_file, os.path.join(
os.path.dirname(__file__), 'services.yaml'))
for service, schema in SERVICE_MAP.items():
hass.services.async_register(
DOMAIN, service, async_service_handler,
descriptions[DOMAIN][service], schema=schema)
if 'frontend' in hass.config.components:
register_built_in_panel(hass, 'hassio', 'Hass.io',
'mdi:access-point-network')
return True
@ -165,117 +92,122 @@ class HassIO(object):
self.websession = websession
self._ip = ip
@asyncio.coroutine
def is_connected(self):
"""Return True if it connected to HassIO supervisor.
Return a coroutine.
This method is a coroutine.
"""
return self.send_command("/supervisor/ping")
@asyncio.coroutine
def send_command(self, cmd, payload=None, timeout=DEFAULT_TIMEOUT):
"""Send request to API."""
answer = yield from self.send_raw(
cmd, payload=payload, timeout=timeout
)
if answer and answer['result'] == 'ok':
return answer['data'] if answer['data'] else True
_LOGGER.error("%s return error %s.", cmd, answer['message'])
return False
@asyncio.coroutine
def send_raw(self, cmd, payload=None, timeout=DEFAULT_TIMEOUT):
"""Send raw request to API."""
try:
with async_timeout.timeout(timeout, loop=self.loop):
with async_timeout.timeout(TIMEOUT, loop=self.loop):
request = yield from self.websession.get(
"http://{}{}".format(self._ip, cmd),
timeout=None, json=payload
"http://{}{}".format(self._ip, "/supervisor/ping")
)
if request.status != 200:
_LOGGER.error("%s return code %d.", cmd, request.status)
return
_LOGGER.error("Ping return code %d.", request.status)
return False
return (yield from request.json())
answer = yield from request.json()
return answer and answer['result'] == 'ok'
except asyncio.TimeoutError:
_LOGGER.error("Timeout on api request %s.", cmd)
_LOGGER.error("Timeout on ping request")
except aiohttp.ClientError:
_LOGGER.error("Client error on api request %s.", cmd)
except aiohttp.ClientError as err:
_LOGGER.error("Client error on ping request %s", err)
return False
@asyncio.coroutine
def command_proxy(self, path, request):
"""Return a client request with proxy origin for HassIO supervisor.
This method is a coroutine.
"""
try:
data = None
headers = None
with async_timeout.timeout(TIMEOUT, loop=self.loop):
data = yield from request.read()
if data:
headers = {CONTENT_TYPE: request.content_type}
else:
data = None
method = getattr(self.websession, request.method.lower())
client = yield from method(
"http://{}/{}".format(self._ip, path), data=data,
headers=headers
)
return client
except aiohttp.ClientError as err:
_LOGGER.error("Client error on api %s request %s.", path, err)
except asyncio.TimeoutError:
_LOGGER.error("Client timeout error on api request %s.", path)
raise HTTPBadGateway()
class HassIOBaseView(HomeAssistantView):
class HassIOView(HomeAssistantView):
"""HassIO view to handle base part."""
name = "api:hassio"
url = "/api/hassio/{path:.+}"
requires_auth = True
def __init__(self, hassio, base):
"""Initialize a hassio base view."""
self.hassio = hassio
self._url_info = "/{}/info".format(base)
self.url = "/api/hassio/{}".format(base)
self.name = "api:hassio:{}".format(base)
@asyncio.coroutine
def get(self, request):
"""Get base data."""
data = yield from self.hassio.send_command(self._url_info)
if not data:
raise HTTPBadGateway()
return web.json_response(data)
class HassIOBaseEditView(HassIOBaseView):
"""HassIO view to handle base with options support."""
def __init__(self, hassio, base):
"""Initialize a hassio base edit view."""
super().__init__(hassio, base)
self._url_options = "/{}/options".format(base)
@asyncio.coroutine
def post(self, request):
"""Set options on host."""
data = yield from request.json()
response = yield from self.hassio.send_raw(
self._url_options, payload=data)
if not response:
raise HTTPBadGateway()
return web.json_response(response)
class HassIOAddonsView(HomeAssistantView):
"""HassIO view to handle addons part."""
requires_auth = True
url = "/api/hassio/addons/{addon}"
name = "api:hassio:addons"
def __init__(self, hassio):
"""Initialize a hassio addon view."""
"""Initialize a hassio base view."""
self.hassio = hassio
@asyncio.coroutine
def get(self, request, addon):
"""Get addon data."""
data = yield from self.hassio.send_command(
"/addons/{}/info".format(addon))
if not data:
raise HTTPBadGateway()
return web.json_response(data)
def _handle(self, request, path):
"""Route data to hassio."""
if path.startswith('addons/'):
parts = path.split('/')
@asyncio.coroutine
def post(self, request, addon):
"""Set options on host."""
data = yield from request.json()
if len(parts) != 3:
raise HTTPNotFound()
response = yield from self.hassio.send_raw(
"/addons/{}/options".format(addon), payload=data)
if not response:
raise HTTPBadGateway()
return web.json_response(response)
allowed_methods = ADDON_REST_COMMANDS.get(parts[-1])
else:
allowed_methods = HASSIO_REST_COMMANDS.get(path)
if allowed_methods is None:
raise HTTPNotFound()
if request.method not in allowed_methods:
raise HTTPMethodNotAllowed(request.method, allowed_methods)
client = yield from self.hassio.command_proxy(path, request)
data = yield from client.read()
if path.endswith('/logs'):
return _create_response_log(client, data)
return _create_response(client, data)
get = _handle
post = _handle
def _create_response(client, data):
"""Convert a response from client request."""
return web.Response(
body=data,
status=client.status,
content_type=client.content_type,
)
def _create_response_log(client, data):
"""Convert a response from client request."""
# Remove color codes
log = re.sub(r"\x1b(\[.*?[@-~]|\].*?(\x07|\x1b\\))", "", data.decode())
return web.Response(
text=log,
status=client.status,
content_type=CONTENT_TYPE_TEXT_PLAIN,
)

View File

@ -129,7 +129,8 @@ class Tradfri(Light):
"""Fetch new state data for this light."""
self._light.update()
# Handle Hue lights paired with the gatway
if self._light_data.hex_color is not None:
# Handle Hue lights paired with the gateway
# hex_color is 0 when bulb is unreachable
if self._light_data.hex_color not in (None, '0'):
self._rgb_color = color_util.rgb_hex_to_rgb_list(
self._light_data.hex_color)

View File

@ -25,7 +25,8 @@ from homeassistant.components.http import HomeAssistantView
from homeassistant.components.frontend import add_manifest_json_key
from homeassistant.helpers import config_validation as cv
REQUIREMENTS = ['pywebpush==0.6.1', 'PyJWT==1.4.2']
# pyelliptic is dependency of pywebpush and 1.5.8 contains a breaking change
REQUIREMENTS = ['pywebpush==0.6.1', 'PyJWT==1.4.2', 'pyelliptic==1.5.7']
DEPENDENCIES = ['frontend']

View File

@ -19,7 +19,7 @@ from homeassistant.const import (
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['python-telegram-bot==5.3.0']
REQUIREMENTS = ['python-telegram-bot==5.3.1']
ATTR_PHOTO = 'photo'
ATTR_KEYBOARD = 'keyboard'
@ -47,7 +47,7 @@ def get_service(hass, config, discovery_info=None):
api_key = config.get(CONF_API_KEY)
bot = telegram.Bot(token=api_key)
username = bot.getMe()['username']
_LOGGER.info("Telegram bot is '%s'", username)
_LOGGER.debug("Telegram bot is '%s'", username)
except urllib.error.HTTPError:
_LOGGER.error("Please check your access token")
return None
@ -59,7 +59,7 @@ def load_data(url=None, file=None, username=None, password=None):
"""Load photo/document into ByteIO/File container from a source."""
try:
if url is not None:
# load photo from url
# Load photo from URL
if username is not None and password is not None:
req = requests.get(url, auth=(username, password), timeout=15)
else:
@ -67,7 +67,7 @@ def load_data(url=None, file=None, username=None, password=None):
return io.BytesIO(req.content)
elif file is not None:
# load photo from file
# Load photo from file
return open(file, "rb")
else:
_LOGGER.warning("Can't load photo no photo found in params!")
@ -96,7 +96,7 @@ class TelegramNotificationService(BaseNotificationService):
title = kwargs.get(ATTR_TITLE)
data = kwargs.get(ATTR_DATA)
# exists data for send a photo/location
# Exists data for send a photo/location
if data is not None and ATTR_PHOTO in data:
photos = data.get(ATTR_PHOTO, None)
photos = photos if isinstance(photos, list) else [photos]
@ -120,11 +120,10 @@ class TelegramNotificationService(BaseNotificationService):
parse_mode = telegram.parsemode.ParseMode.MARKDOWN
# send message
# Send message
try:
self.bot.sendMessage(chat_id=self._chat_id,
text=text,
parse_mode=parse_mode)
self.bot.sendMessage(
chat_id=self._chat_id, text=text, parse_mode=parse_mode)
except telegram.error.TelegramError:
_LOGGER.exception("Error sending message")
@ -135,8 +134,8 @@ class TelegramNotificationService(BaseNotificationService):
keyboard = telegram.ReplyKeyboardMarkup([
[key.strip() for key in row.split(",")] for row in keys])
try:
self.bot.sendMessage(chat_id=self._chat_id, text=message,
reply_markup=keyboard)
self.bot.sendMessage(
chat_id=self._chat_id, text=message, reply_markup=keyboard)
except telegram.error.TelegramError:
_LOGGER.exception("Error sending message")
@ -145,7 +144,7 @@ class TelegramNotificationService(BaseNotificationService):
import telegram
caption = data.get(ATTR_CAPTION)
# send photo
# Send photo
try:
photo = load_data(
url=data.get(ATTR_URL),
@ -153,8 +152,8 @@ class TelegramNotificationService(BaseNotificationService):
username=data.get(ATTR_USERNAME),
password=data.get(ATTR_PASSWORD),
)
self.bot.sendPhoto(chat_id=self._chat_id,
photo=photo, caption=caption)
self.bot.sendPhoto(
chat_id=self._chat_id, photo=photo, caption=caption)
except telegram.error.TelegramError:
_LOGGER.exception("Error sending photo")
@ -171,8 +170,8 @@ class TelegramNotificationService(BaseNotificationService):
username=data.get(ATTR_USERNAME),
password=data.get(ATTR_PASSWORD),
)
self.bot.sendDocument(chat_id=self._chat_id,
document=document, caption=caption)
self.bot.sendDocument(
chat_id=self._chat_id, document=document, caption=caption)
except telegram.error.TelegramError:
_LOGGER.exception("Error sending document")
@ -182,9 +181,9 @@ class TelegramNotificationService(BaseNotificationService):
latitude = float(gps.get(ATTR_LATITUDE, 0.0))
longitude = float(gps.get(ATTR_LONGITUDE, 0.0))
# send location
# Send location
try:
self.bot.sendLocation(chat_id=self._chat_id,
latitude=latitude, longitude=longitude)
self.bot.sendLocation(
chat_id=self._chat_id, latitude=latitude, longitude=longitude)
except telegram.error.TelegramError:
_LOGGER.exception("Error sending location")

View File

@ -16,7 +16,7 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['python-telegram-bot==5.3.0']
REQUIREMENTS = ['python-telegram-bot==5.3.1']
PLATFORM_SCHEMA = PLATFORM_SCHEMA

View File

@ -21,7 +21,7 @@ from homeassistant.const import CONF_API_KEY
from homeassistant.components.http.util import get_real_ip
DEPENDENCIES = ['http']
REQUIREMENTS = ['python-telegram-bot==5.3.0']
REQUIREMENTS = ['python-telegram-bot==5.3.1']
_LOGGER = logging.getLogger(__name__)

View File

@ -21,7 +21,7 @@ DOMAIN = 'tradfri'
CONFIG_FILE = 'tradfri.conf'
KEY_CONFIG = 'tradfri_configuring'
KEY_GATEWAY = 'tradfri_gateway'
REQUIREMENTS = ['pytradfri==1.0']
REQUIREMENTS = ['pytradfri==1.1']
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({

View File

@ -2,7 +2,7 @@
"""Constants used by Home Assistant components."""
MAJOR_VERSION = 0
MINOR_VERSION = 43
PATCH_VERSION = '1'
PATCH_VERSION = '2'
__short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION)
__version__ = '{}.{}'.format(__short_version__, PATCH_VERSION)
REQUIRED_PYTHON_VER = (3, 4, 2)

View File

@ -38,7 +38,7 @@ SoCo==0.12
TwitterAPI==2.4.5
# homeassistant.components.device_tracker.automatic
aioautomatic==0.1.1
aioautomatic==0.2.1
# homeassistant.components.sensor.dnsip
aiodns==1.1.1
@ -520,6 +520,9 @@ pydroid-ipcam==0.8
# homeassistant.components.sensor.ebox
pyebox==0.1.0
# homeassistant.components.notify.html5
pyelliptic==1.5.7
# homeassistant.components.media_player.emby
pyemby==1.2
@ -648,7 +651,7 @@ python-synology==0.1.0
# homeassistant.components.notify.telegram
# homeassistant.components.telegram_bot.polling
# homeassistant.components.telegram_bot.webhooks
python-telegram-bot==5.3.0
python-telegram-bot==5.3.1
# homeassistant.components.sensor.twitch
python-twitch==1.3.0
@ -663,7 +666,7 @@ python-wink==1.2.3
pytrackr==0.0.5
# homeassistant.components.tradfri
pytradfri==1.0
pytradfri==1.1
# homeassistant.components.device_tracker.unifi
pyunifi==2.0

View File

@ -1,561 +1,184 @@
"""The tests for the hassio component."""
import asyncio
import os
from unittest.mock import patch, Mock, MagicMock
import aiohttp
import pytest
import homeassistant.components.hassio as ho
from homeassistant.setup import setup_component, async_setup_component
from homeassistant.setup import async_setup_component
from tests.common import (
get_test_home_assistant, assert_setup_component)
from tests.common import mock_coro, mock_http_component_app
class TestHassIOSetup(object):
"""Test the hassio component."""
@pytest.fixture
def hassio_env():
"""Fixture to inject hassio env."""
with patch.dict(os.environ, {'HASSIO': "127.0.0.1"}), \
patch('homeassistant.components.hassio.HassIO.is_connected',
Mock(return_value=mock_coro(True))):
yield
def setup_method(self):
"""Setup things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.config = {
ho.DOMAIN: {},
}
os.environ['HASSIO'] = "127.0.0.1"
def teardown_method(self):
"""Stop everything that was started."""
self.hass.stop()
def test_setup_component(self, aioclient_mock):
"""Test setup component."""
aioclient_mock.get("http://127.0.0.1/supervisor/ping", json={
'result': 'ok', 'data': {}
})
with assert_setup_component(0, ho.DOMAIN):
setup_component(self.hass, ho.DOMAIN, self.config)
def test_setup_component_test_service(self, aioclient_mock):
"""Test setup component and check if service exits."""
aioclient_mock.get("http://127.0.0.1/supervisor/ping", json={
'result': 'ok', 'data': {}
})
with assert_setup_component(0, ho.DOMAIN):
setup_component(self.hass, ho.DOMAIN, self.config)
assert self.hass.services.has_service(
ho.DOMAIN, ho.SERVICE_HOST_REBOOT)
assert self.hass.services.has_service(
ho.DOMAIN, ho.SERVICE_HOST_SHUTDOWN)
assert self.hass.services.has_service(
ho.DOMAIN, ho.SERVICE_HOST_UPDATE)
assert self.hass.services.has_service(
ho.DOMAIN, ho.SERVICE_SUPERVISOR_UPDATE)
assert self.hass.services.has_service(
ho.DOMAIN, ho.SERVICE_SUPERVISOR_RELOAD)
assert self.hass.services.has_service(
ho.DOMAIN, ho.SERVICE_ADDON_INSTALL)
assert self.hass.services.has_service(
ho.DOMAIN, ho.SERVICE_ADDON_UNINSTALL)
assert self.hass.services.has_service(
ho.DOMAIN, ho.SERVICE_ADDON_UPDATE)
assert self.hass.services.has_service(
ho.DOMAIN, ho.SERVICE_ADDON_START)
assert self.hass.services.has_service(
ho.DOMAIN, ho.SERVICE_ADDON_STOP)
class TestHassIOComponent(object):
"""Test the HassIO component."""
def setup_method(self):
"""Setup things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.config = {
ho.DOMAIN: {},
}
os.environ['HASSIO'] = "127.0.0.1"
self.url = "http://127.0.0.1/{}"
self.error_msg = {
'result': 'error',
'message': 'Test error',
}
self.ok_msg = {
'result': 'ok',
'data': {},
}
def teardown_method(self):
"""Stop everything that was started."""
self.hass.stop()
def test_rest_command_timeout(self, aioclient_mock):
"""Call a hassio with timeout."""
aioclient_mock.get(
"http://127.0.0.1/supervisor/ping", json=self.ok_msg)
with assert_setup_component(0, ho.DOMAIN):
setup_component(self.hass, ho.DOMAIN, self.config)
aioclient_mock.get(
self.url.format("host/update"), exc=asyncio.TimeoutError())
self.hass.services.call(ho.DOMAIN, ho.SERVICE_HOST_UPDATE, {})
self.hass.block_till_done()
assert len(aioclient_mock.mock_calls) == 2
def test_rest_command_aiohttp_error(self, aioclient_mock):
"""Call a hassio with aiohttp exception."""
aioclient_mock.get(
"http://127.0.0.1/supervisor/ping", json=self.ok_msg)
with assert_setup_component(0, ho.DOMAIN):
setup_component(self.hass, ho.DOMAIN, self.config)
aioclient_mock.get(
self.url.format("host/update"), exc=aiohttp.ClientError())
self.hass.services.call(ho.DOMAIN, ho.SERVICE_HOST_UPDATE, {})
self.hass.block_till_done()
assert len(aioclient_mock.mock_calls) == 2
def test_rest_command_http_error(self, aioclient_mock):
"""Call a hassio with status code 503."""
aioclient_mock.get(
"http://127.0.0.1/supervisor/ping", json=self.ok_msg)
with assert_setup_component(0, ho.DOMAIN):
setup_component(self.hass, ho.DOMAIN, self.config)
aioclient_mock.get(
self.url.format("host/update"), status=503)
self.hass.services.call(ho.DOMAIN, ho.SERVICE_HOST_UPDATE, {})
self.hass.block_till_done()
assert len(aioclient_mock.mock_calls) == 2
def test_rest_command_http_error_api(self, aioclient_mock):
"""Call a hassio with status code 503."""
aioclient_mock.get(
"http://127.0.0.1/supervisor/ping", json=self.ok_msg)
with assert_setup_component(0, ho.DOMAIN):
setup_component(self.hass, ho.DOMAIN, self.config)
aioclient_mock.get(
self.url.format("host/update"), json=self.error_msg)
self.hass.services.call(ho.DOMAIN, ho.SERVICE_HOST_UPDATE, {})
self.hass.block_till_done()
assert len(aioclient_mock.mock_calls) == 2
def test_rest_command_http_host_reboot(self, aioclient_mock):
"""Call a hassio for host reboot."""
aioclient_mock.get(
"http://127.0.0.1/supervisor/ping", json=self.ok_msg)
with assert_setup_component(0, ho.DOMAIN):
setup_component(self.hass, ho.DOMAIN, self.config)
aioclient_mock.get(
self.url.format("host/reboot"), json=self.ok_msg)
self.hass.services.call(ho.DOMAIN, ho.SERVICE_HOST_REBOOT, {})
self.hass.block_till_done()
assert len(aioclient_mock.mock_calls) == 2
def test_rest_command_http_host_shutdown(self, aioclient_mock):
"""Call a hassio for host shutdown."""
aioclient_mock.get(
"http://127.0.0.1/supervisor/ping", json=self.ok_msg)
with assert_setup_component(0, ho.DOMAIN):
setup_component(self.hass, ho.DOMAIN, self.config)
aioclient_mock.get(
self.url.format("host/shutdown"), json=self.ok_msg)
self.hass.services.call(ho.DOMAIN, ho.SERVICE_HOST_SHUTDOWN, {})
self.hass.block_till_done()
assert len(aioclient_mock.mock_calls) == 2
def test_rest_command_http_host_update(self, aioclient_mock):
"""Call a hassio for host update."""
aioclient_mock.get(
"http://127.0.0.1/supervisor/ping", json=self.ok_msg)
with assert_setup_component(0, ho.DOMAIN):
setup_component(self.hass, ho.DOMAIN, self.config)
aioclient_mock.get(
self.url.format("host/update"), json=self.ok_msg)
self.hass.services.call(
ho.DOMAIN, ho.SERVICE_HOST_UPDATE, {'version': '0.4'})
self.hass.block_till_done()
assert len(aioclient_mock.mock_calls) == 2
assert aioclient_mock.mock_calls[-1][2]['version'] == '0.4'
def test_rest_command_http_supervisor_update(self, aioclient_mock):
"""Call a hassio for supervisor update."""
aioclient_mock.get(
"http://127.0.0.1/supervisor/ping", json=self.ok_msg)
with assert_setup_component(0, ho.DOMAIN):
setup_component(self.hass, ho.DOMAIN, self.config)
aioclient_mock.get(
self.url.format("supervisor/update"), json=self.ok_msg)
self.hass.services.call(
ho.DOMAIN, ho.SERVICE_SUPERVISOR_UPDATE, {'version': '0.4'})
self.hass.block_till_done()
assert len(aioclient_mock.mock_calls) == 2
assert aioclient_mock.mock_calls[-1][2]['version'] == '0.4'
def test_rest_command_http_supervisor_reload(self, aioclient_mock):
"""Call a hassio for supervisor reload."""
aioclient_mock.get(
"http://127.0.0.1/supervisor/ping", json=self.ok_msg)
with assert_setup_component(0, ho.DOMAIN):
setup_component(self.hass, ho.DOMAIN, self.config)
aioclient_mock.get(
self.url.format("supervisor/reload"), json=self.ok_msg)
self.hass.services.call(
ho.DOMAIN, ho.SERVICE_SUPERVISOR_RELOAD, {})
self.hass.block_till_done()
assert len(aioclient_mock.mock_calls) == 2
def test_rest_command_http_homeassistant_update(self, aioclient_mock):
"""Call a hassio for homeassistant update."""
aioclient_mock.get(
"http://127.0.0.1/supervisor/ping", json=self.ok_msg)
with assert_setup_component(0, ho.DOMAIN):
setup_component(self.hass, ho.DOMAIN, self.config)
aioclient_mock.get(
self.url.format("homeassistant/update"), json=self.ok_msg)
self.hass.services.call(
ho.DOMAIN, ho.SERVICE_HOMEASSISTANT_UPDATE, {'version': '0.4'})
self.hass.block_till_done()
assert len(aioclient_mock.mock_calls) == 2
assert aioclient_mock.mock_calls[-1][2]['version'] == '0.4'
def test_rest_command_http_addon_install(self, aioclient_mock):
"""Call a hassio for addon install."""
aioclient_mock.get(
"http://127.0.0.1/supervisor/ping", json=self.ok_msg)
with assert_setup_component(0, ho.DOMAIN):
setup_component(self.hass, ho.DOMAIN, self.config)
aioclient_mock.get(
self.url.format("addons/smb_config/install"), json=self.ok_msg)
self.hass.services.call(
ho.DOMAIN, ho.SERVICE_ADDON_INSTALL, {
'addon': 'smb_config',
'version': '0.4'
})
self.hass.block_till_done()
assert len(aioclient_mock.mock_calls) == 2
assert aioclient_mock.mock_calls[-1][2]['version'] == '0.4'
def test_rest_command_http_addon_uninstall(self, aioclient_mock):
"""Call a hassio for addon uninstall."""
aioclient_mock.get(
"http://127.0.0.1/supervisor/ping", json=self.ok_msg)
with assert_setup_component(0, ho.DOMAIN):
setup_component(self.hass, ho.DOMAIN, self.config)
aioclient_mock.get(
self.url.format("addons/smb_config/uninstall"), json=self.ok_msg)
self.hass.services.call(
ho.DOMAIN, ho.SERVICE_ADDON_UNINSTALL, {
'addon': 'smb_config'
})
self.hass.block_till_done()
assert len(aioclient_mock.mock_calls) == 2
def test_rest_command_http_addon_update(self, aioclient_mock):
"""Call a hassio for addon update."""
aioclient_mock.get(
"http://127.0.0.1/supervisor/ping", json=self.ok_msg)
with assert_setup_component(0, ho.DOMAIN):
setup_component(self.hass, ho.DOMAIN, self.config)
aioclient_mock.get(
self.url.format("addons/smb_config/update"), json=self.ok_msg)
self.hass.services.call(
ho.DOMAIN, ho.SERVICE_ADDON_UPDATE, {
'addon': 'smb_config',
'version': '0.4'
})
self.hass.block_till_done()
assert len(aioclient_mock.mock_calls) == 2
assert aioclient_mock.mock_calls[-1][2]['version'] == '0.4'
def test_rest_command_http_addon_start(self, aioclient_mock):
"""Call a hassio for addon start."""
aioclient_mock.get(
"http://127.0.0.1/supervisor/ping", json=self.ok_msg)
with assert_setup_component(0, ho.DOMAIN):
setup_component(self.hass, ho.DOMAIN, self.config)
aioclient_mock.get(
self.url.format("addons/smb_config/start"), json=self.ok_msg)
self.hass.services.call(
ho.DOMAIN, ho.SERVICE_ADDON_START, {
'addon': 'smb_config',
})
self.hass.block_till_done()
assert len(aioclient_mock.mock_calls) == 2
def test_rest_command_http_addon_stop(self, aioclient_mock):
"""Call a hassio for addon stop."""
aioclient_mock.get(
"http://127.0.0.1/supervisor/ping", json=self.ok_msg)
with assert_setup_component(0, ho.DOMAIN):
setup_component(self.hass, ho.DOMAIN, self.config)
aioclient_mock.get(
self.url.format("addons/smb_config/stop"), json=self.ok_msg)
self.hass.services.call(
ho.DOMAIN, ho.SERVICE_ADDON_STOP, {
'addon': 'smb_config'
})
self.hass.block_till_done()
assert len(aioclient_mock.mock_calls) == 2
@pytest.fixture
def hassio_client(hassio_env, hass, test_client):
"""Create mock hassio http client."""
app = mock_http_component_app(hass)
hass.loop.run_until_complete(async_setup_component(hass, 'hassio', {}))
hass.http.views['api:hassio'].register(app.router)
yield hass.loop.run_until_complete(test_client(app))
@asyncio.coroutine
def test_async_hassio_host_view(aioclient_mock, hass, test_client):
"""Test that it fetches the given url."""
os.environ['HASSIO'] = "127.0.0.1"
aioclient_mock.get("http://127.0.0.1/supervisor/ping", json={
'result': 'ok', 'data': {}
})
result = yield from async_setup_component(hass, ho.DOMAIN, {ho.DOMAIN: {}})
assert result, 'Failed to setup hasio'
client = yield from test_client(hass.http.app)
aioclient_mock.get('http://127.0.0.1/host/info', json={
'result': 'ok',
'data': {
'os': 'resinos',
'version': '0.3',
'current': '0.4',
'level': 16,
'hostname': 'test',
}
})
resp = yield from client.get('/api/hassio/host')
data = yield from resp.json()
assert len(aioclient_mock.mock_calls) == 2
assert resp.status == 200
assert data['os'] == 'resinos'
assert data['version'] == '0.3'
assert data['current'] == '0.4'
assert data['level'] == 16
assert data['hostname'] == 'test'
def test_fail_setup_without_environ_var(hass):
"""Fail setup if no environ variable set."""
with patch.dict(os.environ, {}, clear=True):
result = yield from async_setup_component(hass, 'hassio', {})
assert not result
@asyncio.coroutine
def test_async_hassio_homeassistant_view(aioclient_mock, hass, test_client):
"""Test that it fetches the given url."""
os.environ['HASSIO'] = "127.0.0.1"
aioclient_mock.get("http://127.0.0.1/supervisor/ping", json={
'result': 'ok', 'data': {}
})
result = yield from async_setup_component(hass, ho.DOMAIN, {ho.DOMAIN: {}})
assert result, 'Failed to setup hasio'
client = yield from test_client(hass.http.app)
aioclient_mock.get('http://127.0.0.1/homeassistant/info', json={
'result': 'ok',
'data': {
'version': '0.41',
'current': '0.41.1',
}
})
resp = yield from client.get('/api/hassio/homeassistant')
data = yield from resp.json()
assert len(aioclient_mock.mock_calls) == 2
assert resp.status == 200
assert data['version'] == '0.41'
assert data['current'] == '0.41.1'
def test_fail_setup_cannot_connect(hass):
"""Fail setup if cannot connect."""
with patch.dict(os.environ, {'HASSIO': "127.0.0.1"}), \
patch('homeassistant.components.hassio.HassIO.is_connected',
Mock(return_value=mock_coro(False))):
result = yield from async_setup_component(hass, 'hassio', {})
assert not result
@asyncio.coroutine
def test_async_hassio_supervisor_view(aioclient_mock, hass, test_client):
"""Test that it fetches the given url."""
os.environ['HASSIO'] = "127.0.0.1"
def test_invalid_path(hassio_client):
"""Test requesting invalid path."""
with patch.dict(ho.HASSIO_REST_COMMANDS, {}, clear=True):
resp = yield from hassio_client.post('/api/hassio/beer')
aioclient_mock.get("http://127.0.0.1/supervisor/ping", json={
'result': 'ok', 'data': {}
})
result = yield from async_setup_component(hass, ho.DOMAIN, {ho.DOMAIN: {}})
assert result, 'Failed to setup hasio'
client = yield from test_client(hass.http.app)
aioclient_mock.get('http://127.0.0.1/supervisor/info', json={
'result': 'ok',
'data': {
'version': '0.3',
'current': '0.4',
'beta': False,
}
})
resp = yield from client.get('/api/hassio/supervisor')
data = yield from resp.json()
assert len(aioclient_mock.mock_calls) == 2
assert resp.status == 200
assert data['version'] == '0.3'
assert data['current'] == '0.4'
assert not data['beta']
aioclient_mock.get('http://127.0.0.1/supervisor/options', json={
'result': 'ok',
'data': {},
})
resp = yield from client.post('/api/hassio/supervisor', json={
'beta': True,
})
data = yield from resp.json()
assert len(aioclient_mock.mock_calls) == 3
assert resp.status == 200
assert aioclient_mock.mock_calls[-1][2]['beta']
assert resp.status == 404
@asyncio.coroutine
def test_async_hassio_network_view(aioclient_mock, hass, test_client):
"""Test that it fetches the given url."""
os.environ['HASSIO'] = "127.0.0.1"
def test_invalid_method(hassio_client):
"""Test requesting path with invalid method."""
with patch.dict(ho.HASSIO_REST_COMMANDS, {'beer': ['POST']}):
resp = yield from hassio_client.get('/api/hassio/beer')
aioclient_mock.get("http://127.0.0.1/supervisor/ping", json={
'result': 'ok', 'data': {}
})
result = yield from async_setup_component(hass, ho.DOMAIN, {ho.DOMAIN: {}})
assert result, 'Failed to setup hasio'
client = yield from test_client(hass.http.app)
aioclient_mock.get('http://127.0.0.1/network/info', json={
'result': 'ok',
'data': {
'mode': 'dhcp',
'ssid': 'my_wlan',
'password': '123456',
}
})
resp = yield from client.get('/api/hassio/network')
data = yield from resp.json()
assert len(aioclient_mock.mock_calls) == 2
assert resp.status == 200
assert data['mode'] == 'dhcp'
assert data['ssid'] == 'my_wlan'
assert data['password'] == '123456'
aioclient_mock.get('http://127.0.0.1/network/options', json={
'result': 'ok',
'data': {},
})
resp = yield from client.post('/api/hassio/network', json={
'mode': 'dhcp',
'ssid': 'my_wlan2',
'password': '654321',
})
data = yield from resp.json()
assert len(aioclient_mock.mock_calls) == 3
assert resp.status == 200
assert aioclient_mock.mock_calls[-1][2]['ssid'] == 'my_wlan2'
assert aioclient_mock.mock_calls[-1][2]['password'] == '654321'
assert resp.status == 405
@asyncio.coroutine
def test_async_hassio_addon_view(aioclient_mock, hass, test_client):
"""Test that it fetches the given url."""
os.environ['HASSIO'] = "127.0.0.1"
def test_forward_normal_path(hassio_client):
"""Test fetching normal path."""
response = MagicMock()
response.read.return_value = mock_coro('data')
aioclient_mock.get("http://127.0.0.1/supervisor/ping", json={
'result': 'ok', 'data': {}
})
result = yield from async_setup_component(hass, ho.DOMAIN, {ho.DOMAIN: {}})
assert result, 'Failed to setup hasio'
with patch.dict(ho.HASSIO_REST_COMMANDS, {'beer': ['POST']}), \
patch('homeassistant.components.hassio.HassIO.command_proxy',
Mock(return_value=mock_coro(response))), \
patch('homeassistant.components.hassio._create_response') as mresp:
mresp.return_value = 'response'
resp = yield from hassio_client.post('/api/hassio/beer')
client = yield from test_client(hass.http.app)
aioclient_mock.get('http://127.0.0.1/addons/smb_config/info', json={
'result': 'ok',
'data': {
'name': 'SMB Config',
'state': 'running',
'boot': 'auto',
'options': {
'bla': False,
}
}
})
resp = yield from client.get('/api/hassio/addons/smb_config')
data = yield from resp.json()
assert len(aioclient_mock.mock_calls) == 2
# Check we got right response
assert resp.status == 200
assert data['name'] == 'SMB Config'
assert data['state'] == 'running'
assert data['boot'] == 'auto'
assert not data['options']['bla']
body = yield from resp.text()
assert body == 'response'
aioclient_mock.get('http://127.0.0.1/addons/smb_config/options', json={
'result': 'ok',
'data': {},
})
# Check we forwarded command
assert len(mresp.mock_calls) == 1
assert mresp.mock_calls[0][1] == (response, 'data')
resp = yield from client.post('/api/hassio/addons/smb_config', json={
'boot': 'manual',
'options': {
'bla': True,
}
})
data = yield from resp.json()
assert len(aioclient_mock.mock_calls) == 3
@asyncio.coroutine
def test_forward_normal_log_path(hassio_client):
"""Test fetching normal log path."""
response = MagicMock()
response.read.return_value = mock_coro('data')
with patch.dict(ho.HASSIO_REST_COMMANDS, {'beer/logs': ['GET']}), \
patch('homeassistant.components.hassio.HassIO.command_proxy',
Mock(return_value=mock_coro(response))), \
patch('homeassistant.components.hassio.'
'_create_response_log') as mresp:
mresp.return_value = 'response'
resp = yield from hassio_client.get('/api/hassio/beer/logs')
# Check we got right response
assert resp.status == 200
assert aioclient_mock.mock_calls[-1][2]['boot'] == 'manual'
assert aioclient_mock.mock_calls[-1][2]['options']['bla']
body = yield from resp.text()
assert body == 'response'
# Check we forwarded command
assert len(mresp.mock_calls) == 1
assert mresp.mock_calls[0][1] == (response, 'data')
@asyncio.coroutine
def test_forward_addon_path(hassio_client):
"""Test fetching addon path."""
response = MagicMock()
response.read.return_value = mock_coro('data')
with patch.dict(ho.ADDON_REST_COMMANDS, {'install': ['POST']}), \
patch('homeassistant.components.hassio.'
'HassIO.command_proxy') as proxy_command, \
patch('homeassistant.components.hassio._create_response') as mresp:
proxy_command.return_value = mock_coro(response)
mresp.return_value = 'response'
resp = yield from hassio_client.post('/api/hassio/addons/beer/install')
# Check we got right response
assert resp.status == 200
body = yield from resp.text()
assert body == 'response'
assert proxy_command.mock_calls[0][1][0] == 'addons/beer/install'
# Check we forwarded command
assert len(mresp.mock_calls) == 1
assert mresp.mock_calls[0][1] == (response, 'data')
@asyncio.coroutine
def test_forward_addon_log_path(hassio_client):
"""Test fetching addon log path."""
response = MagicMock()
response.read.return_value = mock_coro('data')
with patch.dict(ho.ADDON_REST_COMMANDS, {'logs': ['GET']}), \
patch('homeassistant.components.hassio.'
'HassIO.command_proxy') as proxy_command, \
patch('homeassistant.components.hassio.'
'_create_response_log') as mresp:
proxy_command.return_value = mock_coro(response)
mresp.return_value = 'response'
resp = yield from hassio_client.get('/api/hassio/addons/beer/logs')
# Check we got right response
assert resp.status == 200
body = yield from resp.text()
assert body == 'response'
assert proxy_command.mock_calls[0][1][0] == 'addons/beer/logs'
# Check we forwarded command
assert len(mresp.mock_calls) == 1
assert mresp.mock_calls[0][1] == (response, 'data')
@asyncio.coroutine
def test_bad_request_when_wrong_addon_url(hassio_client):
"""Test we cannot mess with addon url."""
resp = yield from hassio_client.get('/api/hassio/addons/../../info')
assert resp.status == 404
resp = yield from hassio_client.get('/api/hassio/addons/info')
assert resp.status == 404
@asyncio.coroutine
def test_bad_gateway_when_cannot_find_supervisor(hassio_client):
"""Test we get a bad gateway error if we can't find supervisor."""
with patch('homeassistant.components.hassio.async_timeout.timeout',
side_effect=asyncio.TimeoutError):
resp = yield from hassio_client.get('/api/hassio/addons/test/info')
assert resp.status == 502

View File

@ -16,14 +16,12 @@ cd build
if [ -d python-openzwave ]; then
cd python-openzwave
git pull --recurse-submodules=yes
git submodule update --init --recursive
git checkout v0.3.3
else
git clone --branch python3 --recursive --depth 1 https://github.com/OpenZWave/python-openzwave.git
git clone --branch v0.3.3 --recursive --depth 1 https://github.com/OpenZWave/python-openzwave.git
cd python-openzwave
fi
git checkout python3
pip3 install --upgrade cython==0.24.1
PYTHON_EXEC=`which python3` make build
PYTHON_EXEC=`which python3` make install