diff --git a/homeassistant/components/config/__init__.py b/homeassistant/components/config/__init__.py new file mode 100644 index 00000000000..dc8946776f5 --- /dev/null +++ b/homeassistant/components/config/__init__.py @@ -0,0 +1,28 @@ +"""Component to interact with Hassbian tools.""" +import asyncio + +from homeassistant.bootstrap import async_prepare_setup_platform +from homeassistant.components.frontend import register_built_in_panel + +DOMAIN = 'config' +DEPENDENCIES = ['http'] + + +@asyncio.coroutine +def async_setup(hass, config): + """Setup the hassbian component.""" + register_built_in_panel(hass, 'config', 'Configuration', 'mdi:settings') + + for panel_name in ('hassbian',): + panel = yield from async_prepare_setup_platform(hass, config, DOMAIN, + panel_name) + + if not panel: + continue + + success = yield from panel.async_setup(hass) + + if success: + hass.config.components.add('{}.{}'.format(DOMAIN, panel_name)) + + return True diff --git a/homeassistant/components/config/hassbian.py b/homeassistant/components/config/hassbian.py new file mode 100644 index 00000000000..c90583c5278 --- /dev/null +++ b/homeassistant/components/config/hassbian.py @@ -0,0 +1,118 @@ +"""Component to interact with Hassbian tools.""" +import asyncio +import json +import os + +from homeassistant.components.http import HomeAssistantView + + +_TEST_OUTPUT = """ +{ + "suites": [ + { + "openzwave": [ + { + "state": "installed" + }, + { + "description": "This is the description of the Open Z-Wave suite." + } + ] + }, + { + "openelec": [ + { + "state": "not_installed" + }, + { + "description": + "OpenElec is amazing. It allows you to control the TV." + } + ] + }, + { + "mosquitto": [ + { + "state": "installing" + }, + { + "description": + "Mosquitto is an MQTT broker." + } + ] + } + ] +} +""" + + +@asyncio.coroutine +def async_setup(hass): + """Setup the hassbian config.""" + # Test if is hassbian + test_mode = 'FORCE_HASSBIAN' in os.environ + is_hassbian = test_mode + + if not is_hassbian: + return False + + hass.http.register_view(HassbianSuitesView(test_mode)) + hass.http.register_view(HassbianSuiteInstallView(test_mode)) + + return True + + +@asyncio.coroutine +def hassbian_status(hass, test_mode=False): + """Query for the Hassbian status.""" + # fetch real output when not in test mode + if test_mode: + return json.loads(_TEST_OUTPUT) + + raise Exception('Real mode not implemented yet.') + + +class HassbianSuitesView(HomeAssistantView): + """Hassbian packages endpoint.""" + + url = '/api/config/hassbian/suites' + name = 'api:config:hassbian:suites' + + def __init__(self, test_mode): + """Initialize suites view.""" + self._test_mode = test_mode + + @asyncio.coroutine + def get(self, request): + """Request suite status.""" + inp = yield from hassbian_status(request.app['hass'], self._test_mode) + + # Flatten the structure a bit + suites = {} + + for suite in inp['suites']: + key = next(iter(suite)) + info = suites[key] = {} + + for item in suite[key]: + item_key = next(iter(item)) + info[item_key] = item[item_key] + + return self.json(suites) + + +class HassbianSuiteInstallView(HomeAssistantView): + """Hassbian packages endpoint.""" + + url = '/api/config/hassbian/suites/{suite}/install' + name = 'api:config:hassbian:suite' + + def __init__(self, test_mode): + """Initialize suite view.""" + self._test_mode = test_mode + + @asyncio.coroutine + def post(self, request, suite): + """Request suite status.""" + # do real install if not in test mode + return self.json({"status": "ok"}) diff --git a/homeassistant/components/frontend/__init__.py b/homeassistant/components/frontend/__init__.py index 4d9fb8624d8..1b4602d35b4 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -19,7 +19,7 @@ DOMAIN = 'frontend' DEPENDENCIES = ['api', 'websocket_api'] URL_PANEL_COMPONENT = '/frontend/panels/{}.html' URL_PANEL_COMPONENT_FP = '/frontend/panels/{}-{}.html' -STATIC_PATH = os.path.join(os.path.dirname(__file__), 'www_static') +STATIC_PATH = os.path.join(os.path.dirname(__file__), 'www_static/') MANIFEST_JSON = { "background_color": "#FFFFFF", "description": "Open-source home automation platform running on Python 3.", @@ -51,17 +51,22 @@ _LOGGER = logging.getLogger(__name__) def register_built_in_panel(hass, component_name, sidebar_title=None, sidebar_icon=None, url_path=None, config=None): """Register a built-in panel.""" - path = 'panels/ha-panel-{}.html'.format(component_name) + nondev_path = 'panels/ha-panel-{}.html'.format(component_name) if hass.http.development: url = ('/static/home-assistant-polymer/panels/' '{0}/ha-panel-{0}.html'.format(component_name)) + path = os.path.join( + STATIC_PATH, 'home-assistant-polymer/panels/', + '{0}/ha-panel-{0}.html'.format(component_name)) else: url = None # use default url generate mechanism + path = os.path.join(STATIC_PATH, nondev_path) - register_panel(hass, component_name, os.path.join(STATIC_PATH, path), - FINGERPRINTS[path], sidebar_title, sidebar_icon, url_path, - url, config) + # Fingerprint doesn't exist when adding new built-in panel + register_panel(hass, component_name, path, + FINGERPRINTS.get(nondev_path, 'dev'), sidebar_title, + sidebar_icon, url_path, url, config) def register_panel(hass, component_name, path, md5=None, sidebar_title=None, diff --git a/homeassistant/components/frontend/version.py b/homeassistant/components/frontend/version.py index a186e440f50..1832564b98f 100644 --- a/homeassistant/components/frontend/version.py +++ b/homeassistant/components/frontend/version.py @@ -5,6 +5,7 @@ FINGERPRINTS = { "frontend.html": "cfd75c944ab14912cfbb4fdd9027579c", "mdi.html": "c1dde43ccf5667f687c418fc8daf9668", "micromarkdown-js.html": "93b5ec4016f0bba585521cf4d18dec1a", + "panels/ha-panel-config.html": "fb50a25da4b4cfbb0d9a74bb22a31db9", "panels/ha-panel-dev-event.html": "5c82300b3cf543a92cf4297506e450e7", "panels/ha-panel-dev-info.html": "0469024d94d6270a8680df2be44ba916", "panels/ha-panel-dev-service.html": "9f749635e518a4ca7991975bdefdb10a", diff --git a/homeassistant/components/frontend/www_static/home-assistant-polymer b/homeassistant/components/frontend/www_static/home-assistant-polymer index 518d0b38da4..ba48e40a2bb 160000 --- a/homeassistant/components/frontend/www_static/home-assistant-polymer +++ b/homeassistant/components/frontend/www_static/home-assistant-polymer @@ -1 +1 @@ -Subproject commit 518d0b38da47fa5923c84a963d11e9ed59424c2f +Subproject commit ba48e40a2bba273b1cdcdf637ecbef4f4db14284 diff --git a/homeassistant/components/frontend/www_static/panels/ha-panel-config.html b/homeassistant/components/frontend/www_static/panels/ha-panel-config.html new file mode 100644 index 00000000000..aa49194bfbc --- /dev/null +++ b/homeassistant/components/frontend/www_static/panels/ha-panel-config.html @@ -0,0 +1,22 @@ + \ No newline at end of file diff --git a/homeassistant/components/frontend/www_static/panels/ha-panel-config.html.gz b/homeassistant/components/frontend/www_static/panels/ha-panel-config.html.gz new file mode 100644 index 00000000000..0680d628cc0 Binary files /dev/null and b/homeassistant/components/frontend/www_static/panels/ha-panel-config.html.gz differ diff --git a/tests/common.py b/tests/common.py index 70afae75155..5527a26b63e 100644 --- a/tests/common.py +++ b/tests/common.py @@ -210,9 +210,9 @@ def mock_state_change_event(hass, new_state, old_state=None): hass.bus.fire(EVENT_STATE_CHANGED, event_data) -def mock_http_component(hass): +def mock_http_component(hass, api_password=None): """Mock the HTTP component.""" - hass.http = MagicMock() + hass.http = MagicMock(api_password=api_password) hass.config.components.add('http') hass.http.views = {} @@ -229,7 +229,8 @@ def mock_http_component(hass): def mock_http_component_app(hass, api_password=None): """Create an aiohttp.web.Application instance for testing.""" - hass.http = MagicMock(api_password=api_password) + if 'http' not in hass.config.components: + mock_http_component(hass, api_password) app = web.Application(middlewares=[auth_middleware], loop=hass.loop) app['hass'] = hass app[KEY_USE_X_FORWARDED_FOR] = False diff --git a/tests/components/config/__init__.py b/tests/components/config/__init__.py new file mode 100644 index 00000000000..53629c7e8f7 --- /dev/null +++ b/tests/components/config/__init__.py @@ -0,0 +1 @@ +"""Tests for the config component.""" diff --git a/tests/components/config/test_hassbian.py b/tests/components/config/test_hassbian.py new file mode 100644 index 00000000000..5ed48fa7794 --- /dev/null +++ b/tests/components/config/test_hassbian.py @@ -0,0 +1,70 @@ +"""Test hassbian config.""" +import asyncio +import os +from unittest.mock import patch + +from homeassistant.bootstrap import async_setup_component +from homeassistant.components.config.hassbian import ( + HassbianSuitesView, HassbianSuiteInstallView) +from tests.common import ( + mock_http_component, mock_http_component_app) + + +def test_setup_check_env_prevents_load(hass, loop): + """Test it does not set up hassbian if environment var not present.""" + mock_http_component(hass) + with patch.dict(os.environ, clear=True): + loop.run_until_complete(async_setup_component(hass, 'config', {})) + assert 'config' in hass.config.components + assert HassbianSuitesView.name not in hass.http.views + assert HassbianSuiteInstallView.name not in hass.http.views + + +def test_setup_check_env_works(hass, loop): + """Test it sets up hassbian if environment var present.""" + mock_http_component(hass) + with patch.dict(os.environ, {'FORCE_HASSBIAN': '1'}): + loop.run_until_complete(async_setup_component(hass, 'config', {})) + assert 'config' in hass.config.components + assert HassbianSuitesView.name in hass.http.views + assert HassbianSuiteInstallView.name in hass.http.views + + +@asyncio.coroutine +def test_get_suites(hass, test_client): + """Test getting suites.""" + app = mock_http_component_app(hass) + + with patch.dict(os.environ, {'FORCE_HASSBIAN': '1'}): + yield from async_setup_component(hass, 'config', {}) + + hass.http.views[HassbianSuitesView.name].register(app.router) + + client = yield from test_client(app) + resp = yield from client.get('/api/config/hassbian/suites') + assert resp.status == 200 + result = yield from resp.json() + + assert 'mosquitto' in result + info = result['mosquitto'] + assert info['state'] == 'installing' + assert info['description'] == 'Mosquitto is an MQTT broker.' + + +@asyncio.coroutine +def test_install_suite(hass, test_client): + """Test getting suites.""" + app = mock_http_component_app(hass) + + with patch.dict(os.environ, {'FORCE_HASSBIAN': '1'}): + yield from async_setup_component(hass, 'config', {}) + + hass.http.views[HassbianSuiteInstallView.name].register(app.router) + + client = yield from test_client(app) + resp = yield from client.post( + '/api/config/hassbian/suites/openzwave/install') + assert resp.status == 200 + result = yield from resp.json() + + assert result == {"status": "ok"} diff --git a/tests/components/config/test_init.py b/tests/components/config/test_init.py new file mode 100644 index 00000000000..1194c6c2b3d --- /dev/null +++ b/tests/components/config/test_init.py @@ -0,0 +1,18 @@ +"""Test config init.""" +import pytest + +from homeassistant.bootstrap import async_setup_component + +from tests.common import mock_http_component + + +@pytest.fixture(autouse=True) +def stub_http(hass): + """Stub the HTTP component.""" + mock_http_component(hass) + + +def test_config_setup(hass, loop): + """Test it sets up hassbian.""" + loop.run_until_complete(async_setup_component(hass, 'config', {})) + assert 'config' in hass.config.components