diff --git a/homeassistant/components/frontend/__init__.py b/homeassistant/components/frontend/__init__.py index 3f2f9ded22a8cc0d482b653cc86854b2b8b7169e..f9ace910481e1971f7748db9da5629e62b928252 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -96,6 +96,15 @@ WS_TYPE_GET_PANELS = 'get_panels' SCHEMA_GET_PANELS = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ vol.Required('type'): WS_TYPE_GET_PANELS, }) +WS_TYPE_GET_THEMES = 'frontend/get_themes' +SCHEMA_GET_THEMES = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ + vol.Required('type'): WS_TYPE_GET_THEMES, +}) +WS_TYPE_GET_TRANSLATIONS = 'frontend/get_translations' +SCHEMA_GET_TRANSLATIONS = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ + vol.Required('type'): WS_TYPE_GET_TRANSLATIONS, + vol.Required('language'): str, +}) class Panel: @@ -195,7 +204,12 @@ async def async_setup(hass, config): client = None hass.components.websocket_api.async_register_command( - WS_TYPE_GET_PANELS, websocket_handle_get_panels, SCHEMA_GET_PANELS) + WS_TYPE_GET_PANELS, websocket_get_panels, SCHEMA_GET_PANELS) + hass.components.websocket_api.async_register_command( + WS_TYPE_GET_THEMES, websocket_get_themes, SCHEMA_GET_THEMES) + hass.components.websocket_api.async_register_command( + WS_TYPE_GET_TRANSLATIONS, websocket_get_translations, + SCHEMA_GET_TRANSLATIONS) hass.http.register_view(ManifestJSONView) conf = config.get(DOMAIN, {}) @@ -262,16 +276,14 @@ async def async_setup(hass, config): for url in conf.get(CONF_EXTRA_HTML_URL_ES5, []): add_extra_html_url(hass, url, True) - async_setup_themes(hass, conf.get(CONF_THEMES)) - - hass.http.register_view(TranslationsView) + _async_setup_themes(hass, conf.get(CONF_THEMES)) return True -def async_setup_themes(hass, themes): +@callback +def _async_setup_themes(hass, themes): """Set up themes data and services.""" - hass.http.register_view(ThemesView) hass.data[DATA_DEFAULT_THEME] = DEFAULT_THEME if themes is None: hass.data[DATA_THEMES] = {} @@ -400,40 +412,6 @@ class ManifestJSONView(HomeAssistantView): return web.Response(text=msg, content_type="application/manifest+json") -class ThemesView(HomeAssistantView): - """View to return defined themes.""" - - requires_auth = False - url = '/api/themes' - name = 'api:themes' - - @callback - def get(self, request): - """Return themes.""" - hass = request.app['hass'] - - return self.json({ - 'themes': hass.data[DATA_THEMES], - 'default_theme': hass.data[DATA_DEFAULT_THEME], - }) - - -class TranslationsView(HomeAssistantView): - """View to return backend defined translations.""" - - url = '/api/translations/{language}' - name = 'api:translations' - - async def get(self, request, language): - """Return translations.""" - hass = request.app['hass'] - - resources = await async_get_translations(hass, language) - return self.json({ - 'resources': resources, - }) - - def _is_latest(js_option, request): """ Return whether we should serve latest untranspiled code. @@ -467,7 +445,7 @@ def _is_latest(js_option, request): @callback -def websocket_handle_get_panels(hass, connection, msg): +def websocket_get_panels(hass, connection, msg): """Handle get panels command. Async friendly. @@ -480,3 +458,33 @@ def websocket_handle_get_panels(hass, connection, msg): connection.to_write.put_nowait(websocket_api.result_message( msg['id'], panels)) + + +@callback +def websocket_get_themes(hass, connection, msg): + """Handle get themes command. + + Async friendly. + """ + connection.to_write.put_nowait(websocket_api.result_message(msg['id'], { + 'themes': hass.data[DATA_THEMES], + 'default_theme': hass.data[DATA_DEFAULT_THEME], + })) + + +@callback +def websocket_get_translations(hass, connection, msg): + """Handle get translations command. + + Async friendly. + """ + async def send_translations(): + """Send a camera still.""" + resources = await async_get_translations(hass, msg['language']) + connection.send_message_outside(websocket_api.result_message( + msg['id'], { + 'resources': resources, + } + )) + + hass.async_add_job(send_translations()) diff --git a/homeassistant/components/websocket_api.py b/homeassistant/components/websocket_api.py index 11094acd3e2a00931ecd9b8ae1b545c0a89ce18e..e16e5524f95456e1469b428ceacbe8ffb572b5ad 100644 --- a/homeassistant/components/websocket_api.py +++ b/homeassistant/components/websocket_api.py @@ -38,6 +38,7 @@ MAX_PENDING_MSG = 512 ERR_ID_REUSE = 1 ERR_INVALID_FORMAT = 2 ERR_NOT_FOUND = 3 +ERR_UNKNOWN_COMMAND = 4 TYPE_AUTH = 'auth' TYPE_AUTH_INVALID = 'auth_invalid' @@ -353,8 +354,11 @@ class ActiveConnection: 'Identifier values have to increase.')) elif msg['type'] not in handlers: - # Unknown command - break + self.log_error( + 'Received invalid command: {}'.format(msg['type'])) + self.to_write.put_nowait(error_message( + cur_id, ERR_UNKNOWN_COMMAND, + 'Unknown command.')) else: handler, schema = handlers[msg['type']] diff --git a/tests/components/test_frontend.py b/tests/components/test_frontend.py index 2f83d923e2bd92b623e17e9c9167a95e142b998c..2f118f24ef0933a549da6ac8e9d580c1a4b51daf 100644 --- a/tests/components/test_frontend.py +++ b/tests/components/test_frontend.py @@ -11,6 +11,19 @@ from homeassistant.components.frontend import ( CONF_EXTRA_HTML_URL_ES5) from homeassistant.components import websocket_api as wapi +from tests.common import mock_coro + + +CONFIG_THEMES = { + DOMAIN: { + CONF_THEMES: { + 'happy': { + 'primary-color': 'red' + } + } + } +} + @pytest.fixture def mock_http_client(hass, aiohttp_client): @@ -101,68 +114,109 @@ def test_states_routes(mock_http_client): assert resp.status == 200 -@asyncio.coroutine -def test_themes_api(mock_http_client_with_themes): +async def test_themes_api(hass, hass_ws_client): """Test that /api/themes returns correct data.""" - resp = yield from mock_http_client_with_themes.get('/api/themes') - json = yield from resp.json() - assert json['default_theme'] == 'default' - assert json['themes'] == {'happy': {'primary-color': 'red'}} + assert await async_setup_component(hass, 'frontend', CONFIG_THEMES) + client = await hass_ws_client(hass) + + await client.send_json({ + 'id': 5, + 'type': 'frontend/get_themes', + }) + msg = await client.receive_json() + assert msg['result']['default_theme'] == 'default' + assert msg['result']['themes'] == {'happy': {'primary-color': 'red'}} -@asyncio.coroutine -def test_themes_set_theme(hass, mock_http_client_with_themes): + +async def test_themes_set_theme(hass, hass_ws_client): """Test frontend.set_theme service.""" - yield from hass.services.async_call(DOMAIN, 'set_theme', {'name': 'happy'}) - yield from hass.async_block_till_done() - resp = yield from mock_http_client_with_themes.get('/api/themes') - json = yield from resp.json() - assert json['default_theme'] == 'happy' + assert await async_setup_component(hass, 'frontend', CONFIG_THEMES) + client = await hass_ws_client(hass) - yield from hass.services.async_call( - DOMAIN, 'set_theme', {'name': 'default'}) - yield from hass.async_block_till_done() - resp = yield from mock_http_client_with_themes.get('/api/themes') - json = yield from resp.json() - assert json['default_theme'] == 'default' + await hass.services.async_call( + DOMAIN, 'set_theme', {'name': 'happy'}, blocking=True) + await client.send_json({ + 'id': 5, + 'type': 'frontend/get_themes', + }) + msg = await client.receive_json() -@asyncio.coroutine -def test_themes_set_theme_wrong_name(hass, mock_http_client_with_themes): + assert msg['result']['default_theme'] == 'happy' + + await hass.services.async_call( + DOMAIN, 'set_theme', {'name': 'default'}, blocking=True) + + await client.send_json({ + 'id': 6, + 'type': 'frontend/get_themes', + }) + msg = await client.receive_json() + + assert msg['result']['default_theme'] == 'default' + + +async def test_themes_set_theme_wrong_name(hass, hass_ws_client): """Test frontend.set_theme service called with wrong name.""" - yield from hass.services.async_call(DOMAIN, 'set_theme', {'name': 'wrong'}) - yield from hass.async_block_till_done() - resp = yield from mock_http_client_with_themes.get('/api/themes') - json = yield from resp.json() - assert json['default_theme'] == 'default' + assert await async_setup_component(hass, 'frontend', CONFIG_THEMES) + client = await hass_ws_client(hass) + await hass.services.async_call( + DOMAIN, 'set_theme', {'name': 'wrong'}, blocking=True) -@asyncio.coroutine -def test_themes_reload_themes(hass, mock_http_client_with_themes): + await client.send_json({ + 'id': 5, + 'type': 'frontend/get_themes', + }) + + msg = await client.receive_json() + + assert msg['result']['default_theme'] == 'default' + + +async def test_themes_reload_themes(hass, hass_ws_client): """Test frontend.reload_themes service.""" + assert await async_setup_component(hass, 'frontend', CONFIG_THEMES) + client = await hass_ws_client(hass) + with patch('homeassistant.components.frontend.load_yaml_config_file', return_value={DOMAIN: { CONF_THEMES: { 'sad': {'primary-color': 'blue'} }}}): - yield from hass.services.async_call(DOMAIN, 'set_theme', - {'name': 'happy'}) - yield from hass.services.async_call(DOMAIN, 'reload_themes') - yield from hass.async_block_till_done() - resp = yield from mock_http_client_with_themes.get('/api/themes') - json = yield from resp.json() - assert json['themes'] == {'sad': {'primary-color': 'blue'}} - assert json['default_theme'] == 'default' + await hass.services.async_call( + DOMAIN, 'set_theme', {'name': 'happy'}, blocking=True) + await hass.services.async_call(DOMAIN, 'reload_themes', blocking=True) + await client.send_json({ + 'id': 5, + 'type': 'frontend/get_themes', + }) -@asyncio.coroutine -def test_missing_themes(mock_http_client): + msg = await client.receive_json() + + assert msg['result']['themes'] == {'sad': {'primary-color': 'blue'}} + assert msg['result']['default_theme'] == 'default' + + +async def test_missing_themes(hass, hass_ws_client): """Test that themes API works when themes are not defined.""" - resp = yield from mock_http_client.get('/api/themes') - assert resp.status == 200 - json = yield from resp.json() - assert json['default_theme'] == 'default' - assert json['themes'] == {} + await async_setup_component(hass, 'frontend') + + client = await hass_ws_client(hass) + await client.send_json({ + 'id': 5, + 'type': 'frontend/get_themes', + }) + + msg = await client.receive_json() + + assert msg['id'] == 5 + assert msg['type'] == wapi.TYPE_RESULT + assert msg['success'] + assert msg['result']['default_theme'] == 'default' + assert msg['result']['themes'] == {} @asyncio.coroutine @@ -204,3 +258,23 @@ async def test_get_panels(hass, hass_ws_client): assert msg['result']['map']['url_path'] == 'map' assert msg['result']['map']['icon'] == 'mdi:account-location' assert msg['result']['map']['title'] == 'Map' + + +async def test_get_translations(hass, hass_ws_client): + """Test get_translations command.""" + await async_setup_component(hass, 'frontend') + client = await hass_ws_client(hass) + + with patch('homeassistant.components.frontend.async_get_translations', + side_effect=lambda hass, lang: mock_coro({'lang': lang})): + await client.send_json({ + 'id': 5, + 'type': 'frontend/get_translations', + 'language': 'nl', + }) + msg = await client.receive_json() + + assert msg['id'] == 5 + assert msg['type'] == wapi.TYPE_RESULT + assert msg['success'] + assert msg['result'] == {'resources': {'lang': 'nl'}} diff --git a/tests/components/test_websocket_api.py b/tests/components/test_websocket_api.py index cff103142b0b92e976ee32315aaad19a95512ca8..fbd8584a7d14ae74c832d72580e073a0f35d4dac 100644 --- a/tests/components/test_websocket_api.py +++ b/tests/components/test_websocket_api.py @@ -311,8 +311,9 @@ def test_unknown_command(websocket_client): 'type': 'unknown_command', }) - msg = yield from websocket_client.receive() - assert msg.type == WSMsgType.close + msg = yield from websocket_client.receive_json() + assert not msg['success'] + assert msg['error']['code'] == wapi.ERR_UNKNOWN_COMMAND async def test_auth_with_token(hass, aiohttp_client, hass_access_token):