mirror of
https://github.com/home-assistant/core.git
synced 2026-04-20 16:39:02 +02:00
Ignore incorrect themes (#151794)
This commit is contained in:
@@ -38,6 +38,8 @@ from homeassistant.util.hass_dict import HassKey
|
||||
|
||||
from .storage import async_setup_frontend_storage
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DOMAIN = "frontend"
|
||||
CONF_THEMES = "themes"
|
||||
CONF_THEMES_MODES = "modes"
|
||||
@@ -73,7 +75,23 @@ VALUE_NO_THEME = "none"
|
||||
|
||||
PRIMARY_COLOR = "primary-color"
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
LEGACY_THEME_SCHEMA = vol.Any(
|
||||
# Legacy theme scheme
|
||||
{cv.string: cv.string},
|
||||
# New extended schema with mode support
|
||||
{
|
||||
# Theme variables that apply to all modes
|
||||
cv.string: cv.string,
|
||||
# Mode specific theme variables
|
||||
vol.Optional(CONF_THEMES_MODES): vol.Schema(
|
||||
{
|
||||
vol.Optional(CONF_THEMES_LIGHT): vol.Schema({cv.string: cv.string}),
|
||||
vol.Optional(CONF_THEMES_DARK): vol.Schema({cv.string: cv.string}),
|
||||
}
|
||||
),
|
||||
},
|
||||
)
|
||||
|
||||
THEME_SCHEMA = vol.Schema(
|
||||
{
|
||||
@@ -90,14 +108,28 @@ THEME_SCHEMA = vol.Schema(
|
||||
}
|
||||
)
|
||||
|
||||
THEMES_SCHEMA = vol.Schema({cv.string: THEME_SCHEMA})
|
||||
|
||||
def _validate_themes(themes: dict) -> dict[str, Any]:
|
||||
"""Validate themes."""
|
||||
validated_themes = {}
|
||||
for theme_name, theme in themes.items():
|
||||
theme_name = cv.string(theme_name)
|
||||
LEGACY_THEME_SCHEMA(theme)
|
||||
|
||||
try:
|
||||
validated_themes[theme_name] = THEME_SCHEMA(theme)
|
||||
except vol.Invalid as err:
|
||||
_LOGGER.error("Theme %s is invalid: %s", theme_name, err)
|
||||
|
||||
return validated_themes
|
||||
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema(
|
||||
{
|
||||
DOMAIN: vol.Schema(
|
||||
{
|
||||
vol.Optional(CONF_FRONTEND_REPO): cv.isdir,
|
||||
vol.Optional(CONF_THEMES): THEMES_SCHEMA,
|
||||
vol.Optional(CONF_THEMES): vol.All(dict, _validate_themes),
|
||||
vol.Optional(CONF_EXTRA_MODULE_URL): vol.All(
|
||||
cv.ensure_list, [cv.string]
|
||||
),
|
||||
@@ -536,7 +568,7 @@ async def _async_setup_themes(
|
||||
new_themes = config.get(DOMAIN, {}).get(CONF_THEMES, {})
|
||||
|
||||
try:
|
||||
THEMES_SCHEMA(new_themes)
|
||||
new_themes = _validate_themes(new_themes)
|
||||
except vol.Invalid as err:
|
||||
raise HomeAssistantError(f"Failed to reload themes: {err}") from err
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""The tests for Home Assistant frontend."""
|
||||
|
||||
from collections.abc import Generator
|
||||
from contextlib import nullcontext
|
||||
from http import HTTPStatus
|
||||
from pathlib import Path
|
||||
import re
|
||||
@@ -411,13 +412,14 @@ async def test_themes_reload_themes(
|
||||
|
||||
@pytest.mark.usefixtures("frontend")
|
||||
@pytest.mark.parametrize(
|
||||
("invalid_theme", "error"),
|
||||
("invalid_theme", "error", "log"),
|
||||
[
|
||||
(
|
||||
{
|
||||
"invalid0": "blue",
|
||||
},
|
||||
"expected a dictionary",
|
||||
None,
|
||||
),
|
||||
(
|
||||
{
|
||||
@@ -426,13 +428,15 @@ async def test_themes_reload_themes(
|
||||
"modes": "light:{} dark:{}",
|
||||
}
|
||||
},
|
||||
"expected a dictionary.*modes",
|
||||
None,
|
||||
"expected a dictionary",
|
||||
),
|
||||
(
|
||||
{
|
||||
"invalid2": None,
|
||||
},
|
||||
"expected a dictionary",
|
||||
None,
|
||||
),
|
||||
(
|
||||
{
|
||||
@@ -441,7 +445,8 @@ async def test_themes_reload_themes(
|
||||
"modes": {},
|
||||
}
|
||||
},
|
||||
"at least one of light, dark.*modes",
|
||||
None,
|
||||
"must contain at least one of light, dark",
|
||||
),
|
||||
(
|
||||
{
|
||||
@@ -450,7 +455,8 @@ async def test_themes_reload_themes(
|
||||
"modes": None,
|
||||
}
|
||||
},
|
||||
"expected a dictionary.*modes",
|
||||
"string value is None for dictionary value",
|
||||
None,
|
||||
),
|
||||
(
|
||||
{
|
||||
@@ -460,6 +466,7 @@ async def test_themes_reload_themes(
|
||||
}
|
||||
},
|
||||
"extra keys not allowed.*dank",
|
||||
None,
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -467,7 +474,9 @@ async def test_themes_reload_invalid(
|
||||
hass: HomeAssistant,
|
||||
themes_ws_client: MockHAClientWebSocket,
|
||||
invalid_theme: dict,
|
||||
error: str,
|
||||
error: str | None,
|
||||
log: str | None,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""Test frontend.reload_themes service with an invalid theme."""
|
||||
|
||||
@@ -482,15 +491,24 @@ async def test_themes_reload_invalid(
|
||||
"homeassistant.components.frontend.async_hass_config_yaml",
|
||||
return_value={DOMAIN: {CONF_THEMES: invalid_theme}},
|
||||
),
|
||||
pytest.raises(HomeAssistantError, match=rf"Failed to reload themes.*{error}"),
|
||||
pytest.raises(HomeAssistantError, match=rf"Failed to reload themes.*{error}")
|
||||
if error is not None
|
||||
else nullcontext(),
|
||||
):
|
||||
await hass.services.async_call(DOMAIN, "reload_themes", blocking=True)
|
||||
|
||||
if log is not None:
|
||||
assert log in caplog.text
|
||||
|
||||
await themes_ws_client.send_json({"id": 5, "type": "frontend/get_themes"})
|
||||
|
||||
msg = await themes_ws_client.receive_json()
|
||||
|
||||
assert msg["result"]["themes"] == {"happy": {"primary-color": "pink"}}
|
||||
expected_themes = {"happy": {"primary-color": "pink"}}
|
||||
if error is None:
|
||||
expected_themes = {}
|
||||
|
||||
assert msg["result"]["themes"] == expected_themes
|
||||
assert msg["result"]["default_theme"] == "default"
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user