"""The tests for Home Assistant frontend.""" import re from asynctest import patch import pytest from homeassistant.components.frontend import ( CONF_EXTRA_HTML_URL, CONF_EXTRA_HTML_URL_ES5, CONF_JS_VERSION, CONF_THEMES, DOMAIN, EVENT_PANELS_UPDATED, ) from homeassistant.components.websocket_api.const import TYPE_RESULT from homeassistant.setup import async_setup_component from tests.common import async_capture_events, mock_coro CONFIG_THEMES = {DOMAIN: {CONF_THEMES: {"happy": {"primary-color": "red"}}}} @pytest.fixture def mock_http_client(hass, aiohttp_client): """Start the Home Assistant HTTP component.""" hass.loop.run_until_complete(async_setup_component(hass, "frontend", {})) return hass.loop.run_until_complete(aiohttp_client(hass.http.app)) @pytest.fixture def mock_http_client_with_themes(hass, aiohttp_client): """Start the Home Assistant HTTP component.""" hass.loop.run_until_complete( async_setup_component( hass, "frontend", {DOMAIN: {CONF_THEMES: {"happy": {"primary-color": "red"}}}}, ) ) return hass.loop.run_until_complete(aiohttp_client(hass.http.app)) @pytest.fixture def mock_http_client_with_urls(hass, aiohttp_client): """Start the Home Assistant HTTP component.""" hass.loop.run_until_complete( async_setup_component( hass, "frontend", { DOMAIN: { CONF_JS_VERSION: "auto", CONF_EXTRA_HTML_URL: ["https://domain.com/my_extra_url.html"], CONF_EXTRA_HTML_URL_ES5: [ "https://domain.com/my_extra_url_es5.html" ], } }, ) ) return hass.loop.run_until_complete(aiohttp_client(hass.http.app)) @pytest.fixture def mock_onboarded(): """Mock that we're onboarded.""" with patch( "homeassistant.components.onboarding.async_is_onboarded", return_value=True ): yield async def test_frontend_and_static(mock_http_client, mock_onboarded): """Test if we can get the frontend.""" resp = await mock_http_client.get("") assert resp.status == 200 assert "cache-control" not in resp.headers text = await resp.text() # Test we can retrieve frontend.js frontendjs = re.search(r"(?P\/frontend_es5\/app.[A-Za-z0-9]{8}.js)", text) assert frontendjs is not None, text resp = await mock_http_client.get(frontendjs.groups(0)[0]) assert resp.status == 200 assert "public" in resp.headers.get("cache-control") async def test_dont_cache_service_worker(mock_http_client): """Test that we don't cache the service worker.""" resp = await mock_http_client.get("/service_worker.js") assert resp.status == 200 assert "cache-control" not in resp.headers async def test_404(mock_http_client): """Test for HTTP 404 error.""" resp = await mock_http_client.get("/not-existing") assert resp.status == 404 async def test_we_cannot_POST_to_root(mock_http_client): """Test that POST is not allow to root.""" resp = await mock_http_client.post("/") assert resp.status == 405 async def test_themes_api(hass, hass_ws_client): """Test that /api/themes returns correct data.""" 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"}} # safe mode hass.config.safe_mode = True await client.send_json({"id": 6, "type": "frontend/get_themes"}) msg = await client.receive_json() assert msg["result"]["default_theme"] == "safe_mode" assert msg["result"]["themes"] == { "safe_mode": {"primary-color": "#db4437", "accent-color": "#eeee02"} } async def test_themes_set_theme(hass, hass_ws_client): """Test frontend.set_theme service.""" assert await async_setup_component(hass, "frontend", CONFIG_THEMES) client = await hass_ws_client(hass) 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() 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.""" 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 ) 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.async_hass_config_yaml", return_value={DOMAIN: {CONF_THEMES: {"sad": {"primary-color": "blue"}}}}, ): 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"}) 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.""" 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"] == TYPE_RESULT assert msg["success"] assert msg["result"]["default_theme"] == "default" assert msg["result"]["themes"] == {} async def test_extra_urls(mock_http_client_with_urls, mock_onboarded): """Test that extra urls are loaded.""" resp = await mock_http_client_with_urls.get("/lovelace?latest") assert resp.status == 200 text = await resp.text() assert text.find('href="https://domain.com/my_extra_url.html"') >= 0 async def test_get_panels(hass, hass_ws_client, mock_http_client): """Test get_panels command.""" events = async_capture_events(hass, EVENT_PANELS_UPDATED) resp = await mock_http_client.get("/map") assert resp.status == 404 hass.components.frontend.async_register_built_in_panel( "map", "Map", "mdi:tooltip-account", require_admin=True ) resp = await mock_http_client.get("/map") assert resp.status == 200 assert len(events) == 1 client = await hass_ws_client(hass) await client.send_json({"id": 5, "type": "get_panels"}) msg = await client.receive_json() assert msg["id"] == 5 assert msg["type"] == TYPE_RESULT assert msg["success"] assert msg["result"]["map"]["component_name"] == "map" assert msg["result"]["map"]["url_path"] == "map" assert msg["result"]["map"]["icon"] == "mdi:tooltip-account" assert msg["result"]["map"]["title"] == "Map" assert msg["result"]["map"]["require_admin"] is True hass.components.frontend.async_remove_panel("map") resp = await mock_http_client.get("/map") assert resp.status == 404 assert len(events) == 2 async def test_get_panels_non_admin(hass, hass_ws_client, hass_admin_user): """Test get_panels command.""" hass_admin_user.groups = [] await async_setup_component(hass, "frontend", {}) hass.components.frontend.async_register_built_in_panel( "map", "Map", "mdi:tooltip-account", require_admin=True ) hass.components.frontend.async_register_built_in_panel( "history", "History", "mdi:history" ) client = await hass_ws_client(hass) await client.send_json({"id": 5, "type": "get_panels"}) msg = await client.receive_json() assert msg["id"] == 5 assert msg["type"] == TYPE_RESULT assert msg["success"] assert "history" in msg["result"] assert "map" not in msg["result"] 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"] == TYPE_RESULT assert msg["success"] assert msg["result"] == {"resources": {"lang": "nl"}} async def test_auth_load(mock_http_client, mock_onboarded): """Test auth component loaded by default.""" resp = await mock_http_client.get("/auth/providers") assert resp.status == 200 async def test_onboarding_load(mock_http_client): """Test onboarding component loaded by default.""" resp = await mock_http_client.get("/api/onboarding") assert resp.status == 200 async def test_auth_authorize(mock_http_client): """Test the authorize endpoint works.""" resp = await mock_http_client.get( "/auth/authorize?response_type=code&client_id=https://localhost/&" "redirect_uri=https://localhost/&state=123%23456" ) assert resp.status == 200 # No caching of auth page. assert "cache-control" not in resp.headers text = await resp.text() # Test we can retrieve authorize.js authorizejs = re.search( r"(?P\/frontend_latest\/authorize.[A-Za-z0-9]{8}.js)", text ) assert authorizejs is not None, text resp = await mock_http_client.get(authorizejs.groups(0)[0]) assert resp.status == 200 assert "public" in resp.headers.get("cache-control")