diff --git a/homeassistant/components/hassio.py b/homeassistant/components/hassio.py new file mode 100644 index 00000000000000..154be0917bbe40 --- /dev/null +++ b/homeassistant/components/hassio.py @@ -0,0 +1,272 @@ +""" +Exposes regular rest commands as services. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/hassio/ +""" +import asyncio +import logging +import os + +import aiohttp +from aiohttp import web +from aiohttp.web_exceptions import HTTPBadGateway +import async_timeout +import voluptuous as vol + +from homeassistant.config import load_yaml_config_file +from homeassistant.components.http import HomeAssistantView +from homeassistant.helpers.aiohttp_client import async_get_clientsession +import homeassistant.helpers.config_validation as cv + +DOMAIN = 'hassio' +DEPENDENCIES = ['http'] + +_LOGGER = logging.getLogger(__name__) + +LONG_TASK_TIMEOUT = 900 +DEFAULT_TIMEOUT = 10 + +SERVICE_HOST_SHUTDOWN = 'host_shutdown' +SERVICE_HOST_REBOOT = 'host_reboot' + +SERVICE_HOST_UPDATE = 'host_update' +SERVICE_SUPERVISOR_UPDATE = 'supervisor_update' +SERVICE_HOMEASSISTANT_UPDATE = 'homeassistant_update' + +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_SUPERVISOR_UPDATE: SCHEMA_SERVICE_UPDATE, + SERVICE_HOMEASSISTANT_UPDATE: SCHEMA_SERVICE_UPDATE, + 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, +} + + +@asyncio.coroutine +def async_setup(hass, config): + """Setup the hassio component.""" + try: + host = os.environ['HASSIO'] + except KeyError: + _LOGGER.error("No HassIO supervisor detect!") + return False + + websession = async_get_clientsession(hass) + hassio = HassIO(hass.loop, websession, host) + + api_ok = yield from hassio.is_connected() + if not api_ok: + _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)) + + # 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_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)) + 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) + + return True + + +class HassIO(object): + """Small API wrapper for HassIO.""" + + def __init__(self, loop, websession, ip): + """Initialze HassIO api.""" + self.loop = loop + self.websession = websession + self._ip = ip + + def is_connected(self): + """Return True if it connected to HassIO supervisor. + + Return 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) + if 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): + request = yield from self.websession.get( + "http://{}{}".format(self._ip, cmd), + timeout=None, json=payload + ) + + if request.status != 200: + _LOGGER.error("%s return code %d.", cmd, request.status) + return + + return (yield from request.json()) + + except asyncio.TimeoutError: + _LOGGER.error("Timeout on api request %s.", cmd) + + except aiohttp.ClientError: + _LOGGER.error("Client error on api request %s.", cmd) + + +class HassIOBaseView(HomeAssistantView): + """HassIO view to handle base part.""" + + 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.""" + 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) + + @asyncio.coroutine + def post(self, request, addon): + """Set options on host.""" + data = yield from request.json() + + response = yield from self.hassio.send_raw( + "/addons/{}/options".format(addon), payload=data) + if not response: + raise HTTPBadGateway() + return web.json_response(response) diff --git a/homeassistant/components/services.yaml b/homeassistant/components/services.yaml index a28a95969fb40a..cf5999200d8927 100644 --- a/homeassistant/components/services.yaml +++ b/homeassistant/components/services.yaml @@ -316,3 +316,72 @@ ffmpeg: logger: set_level: description: Set log level for components. + +hassio: + host_reboot: + description: Reboot host computer. + + host_shutdown: + description: Poweroff host computer. + + host_update: + description: Update host computer. + fields: + version: + description: Optional or it will be use the latest version. + example: '0.3' + + supervisor_update: + description: Update HassIO supervisor. + fields: + version: + description: Optional or it will be use the latest version. + example: '0.3' + + homeassistant_update: + description: Update HomeAssistant docker image. + fields: + version: + description: Optional or it will be use the latest version. + example: '0.40.1' + + addon_install: + description: Install a HassIO docker addon. + fields: + addon: + description: Name of addon. + example: 'smb_config' + version: + description: Optional or it will be use the latest version. + example: '0.2' + + addon_uninstall: + description: Uninstall a HassIO docker addon. + fields: + addon: + description: Name of addon. + example: 'smb_config' + + addon_update: + description: Update a HassIO docker addon. + fields: + addon: + description: Name of addon. + example: 'smb_config' + version: + description: Optional or it will be use the latest version. + example: '0.2' + + addon_start: + description: Start a HassIO docker addon. + fields: + addon: + description: Name of addon. + example: 'smb_config' + + addon_stop: + description: Stop a HassIO docker addon. + fields: + addon: + description: Name of addon. + example: 'smb_config' diff --git a/tests/components/test_hassio.py b/tests/components/test_hassio.py new file mode 100644 index 00000000000000..bde419c410421f --- /dev/null +++ b/tests/components/test_hassio.py @@ -0,0 +1,543 @@ +"""The tests for the hassio component.""" +import asyncio +import os + +import aiohttp + +import homeassistant.components.hassio as ho +from homeassistant.setup import setup_component, async_setup_component + +from tests.common import ( + get_test_home_assistant, assert_setup_component) + + +class TestHassIOSetup(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" + + 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_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_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 + + +@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' + + +@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' + + +@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" + + 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'] + + +@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" + + 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' + + +@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" + + 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/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 + assert resp.status == 200 + assert data['name'] == 'SMB Config' + assert data['state'] == 'running' + assert data['boot'] == 'auto' + assert not data['options']['bla'] + + aioclient_mock.get('http://127.0.0.1/addons/smb_config/options', json={ + 'result': 'ok', + '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 + assert resp.status == 200 + assert aioclient_mock.mock_calls[-1][2]['boot'] == 'manual' + assert aioclient_mock.mock_calls[-1][2]['options']['bla'] diff --git a/tests/test_util/aiohttp.py b/tests/test_util/aiohttp.py index 23e24cac0cd741..39e926ab7e7076 100644 --- a/tests/test_util/aiohttp.py +++ b/tests/test_util/aiohttp.py @@ -75,8 +75,10 @@ def clear_requests(self): @asyncio.coroutine # pylint: disable=unused-variable def match_request(self, method, url, *, data=None, auth=None, params=None, - headers=None, allow_redirects=None): + headers=None, allow_redirects=None, timeout=None, + json=None): """Match a request against pre-registered requests.""" + data = data or json for response in self._mocks: if response.match_request(method, url, params): self.mock_calls.append((method, url, data))