Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 49 additions & 41 deletions homeassistant/components/frontend/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,15 @@
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:
Expand Down Expand Up @@ -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, {})
Expand Down Expand Up @@ -262,16 +276,14 @@ def async_finalize_panel(panel):
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] = {}
Expand Down Expand Up @@ -400,40 +412,6 @@ def get(self, request): # pylint: disable=no-self-use
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.
Expand Down Expand Up @@ -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.
Expand All @@ -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())
8 changes: 6 additions & 2 deletions homeassistant/components/websocket_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
ERR_ID_REUSE = 1
ERR_INVALID_FORMAT = 2
ERR_NOT_FOUND = 3
ERR_UNKNOWN_COMMAND = 4

TYPE_AUTH = 'auth'
TYPE_AUTH_INVALID = 'auth_invalid'
Expand Down Expand Up @@ -353,8 +354,11 @@ def handle_hass_stop(event):
'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.'))
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice


else:
handler, schema = handlers[msg['type']]
Expand Down
160 changes: 117 additions & 43 deletions tests/components/test_frontend.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,19 @@
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):
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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'}}
5 changes: 3 additions & 2 deletions tests/components/test_websocket_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down