mirror of
https://github.com/home-assistant/core.git
synced 2025-04-24 09:17:53 +00:00
Differentiate between warnings and errors in check_config helper (#102902)
* Differentiate between warnings and errors in check_config helper * Update tests * Treat configuration errors in frontend and its dependencies as errors * Improve test coverage * Address review comments * Improve test coverage * Improve test coverage * Address review comments * Add comment
This commit is contained in:
parent
936956a430
commit
3ba8a82243
@ -7,9 +7,8 @@ import voluptuous as vol
|
||||
from homeassistant.components import websocket_api
|
||||
from homeassistant.components.http import HomeAssistantView, require_admin
|
||||
from homeassistant.components.sensor import async_update_suggested_units
|
||||
from homeassistant.config import async_check_ha_config_file
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers import check_config, config_validation as cv
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.util import location, unit_system
|
||||
|
||||
@ -31,11 +30,18 @@ class CheckConfigView(HomeAssistantView):
|
||||
@require_admin
|
||||
async def post(self, request):
|
||||
"""Validate configuration and return results."""
|
||||
errors = await async_check_ha_config_file(request.app["hass"])
|
||||
|
||||
state = "invalid" if errors else "valid"
|
||||
res = await check_config.async_check_ha_config_file(request.app["hass"])
|
||||
|
||||
return self.json({"result": state, "errors": errors})
|
||||
state = "invalid" if res.errors else "valid"
|
||||
|
||||
return self.json(
|
||||
{
|
||||
"result": state,
|
||||
"errors": res.error_str or None,
|
||||
"warnings": res.warning_str or None,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@websocket_api.require_admin
|
||||
|
@ -48,6 +48,7 @@ class HomeAssistantConfig(OrderedDict):
|
||||
"""Initialize HA config."""
|
||||
super().__init__()
|
||||
self.errors: list[CheckConfigError] = []
|
||||
self.warnings: list[CheckConfigError] = []
|
||||
|
||||
def add_error(
|
||||
self,
|
||||
@ -55,15 +56,30 @@ class HomeAssistantConfig(OrderedDict):
|
||||
domain: str | None = None,
|
||||
config: ConfigType | None = None,
|
||||
) -> Self:
|
||||
"""Add a single error."""
|
||||
"""Add an error."""
|
||||
self.errors.append(CheckConfigError(str(message), domain, config))
|
||||
return self
|
||||
|
||||
@property
|
||||
def error_str(self) -> str:
|
||||
"""Return errors as a string."""
|
||||
"""Concatenate all errors to a string."""
|
||||
return "\n".join([err.message for err in self.errors])
|
||||
|
||||
def add_warning(
|
||||
self,
|
||||
message: str,
|
||||
domain: str | None = None,
|
||||
config: ConfigType | None = None,
|
||||
) -> Self:
|
||||
"""Add a warning."""
|
||||
self.warnings.append(CheckConfigError(str(message), domain, config))
|
||||
return self
|
||||
|
||||
@property
|
||||
def warning_str(self) -> str:
|
||||
"""Concatenate all warnings to a string."""
|
||||
return "\n".join([err.message for err in self.warnings])
|
||||
|
||||
|
||||
async def async_check_ha_config_file( # noqa: C901
|
||||
hass: HomeAssistant,
|
||||
@ -82,11 +98,36 @@ async def async_check_ha_config_file( # noqa: C901
|
||||
message = f"Package {package} setup failed. Component {component} {message}"
|
||||
domain = f"homeassistant.packages.{package}.{component}"
|
||||
pack_config = core_config[CONF_PACKAGES].get(package, config)
|
||||
result.add_error(message, domain, pack_config)
|
||||
result.add_warning(message, domain, pack_config)
|
||||
|
||||
def _comp_error(ex: Exception, domain: str, config: ConfigType) -> None:
|
||||
"""Handle errors from components: async_log_exception."""
|
||||
result.add_error(_format_config_error(ex, domain, config)[0], domain, config)
|
||||
if domain in frontend_dependencies:
|
||||
result.add_error(
|
||||
_format_config_error(ex, domain, config)[0], domain, config
|
||||
)
|
||||
else:
|
||||
result.add_warning(
|
||||
_format_config_error(ex, domain, config)[0], domain, config
|
||||
)
|
||||
|
||||
async def _get_integration(
|
||||
hass: HomeAssistant, domain: str
|
||||
) -> loader.Integration | None:
|
||||
"""Get an integration."""
|
||||
integration: loader.Integration | None = None
|
||||
try:
|
||||
integration = await async_get_integration_with_requirements(hass, domain)
|
||||
except loader.IntegrationNotFound as ex:
|
||||
# We get this error if an integration is not found. In recovery mode and
|
||||
# safe mode, this currently happens for all custom integrations. Don't
|
||||
# show errors for a missing integration in recovery mode or safe mode to
|
||||
# not confuse the user.
|
||||
if not hass.config.recovery_mode and not hass.config.safe_mode:
|
||||
result.add_warning(f"Integration error: {domain} - {ex}")
|
||||
except RequirementsNotFound as ex:
|
||||
result.add_warning(f"Integration error: {domain} - {ex}")
|
||||
return integration
|
||||
|
||||
# Load configuration.yaml
|
||||
config_path = hass.config.path(YAML_CONFIG_FILE)
|
||||
@ -122,22 +163,22 @@ async def async_check_ha_config_file( # noqa: C901
|
||||
# Filter out repeating config sections
|
||||
components = {key.partition(" ")[0] for key in config}
|
||||
|
||||
frontend_dependencies: set[str] = set()
|
||||
if "frontend" in components or "default_config" in components:
|
||||
frontend = await _get_integration(hass, "frontend")
|
||||
if frontend:
|
||||
await frontend.resolve_dependencies()
|
||||
frontend_dependencies = frontend.all_dependencies | {"frontend"}
|
||||
|
||||
# Process and validate config
|
||||
for domain in components:
|
||||
try:
|
||||
integration = await async_get_integration_with_requirements(hass, domain)
|
||||
except loader.IntegrationNotFound as ex:
|
||||
if not hass.config.recovery_mode and not hass.config.safe_mode:
|
||||
result.add_error(f"Integration error: {domain} - {ex}")
|
||||
continue
|
||||
except RequirementsNotFound as ex:
|
||||
result.add_error(f"Integration error: {domain} - {ex}")
|
||||
if not (integration := await _get_integration(hass, domain)):
|
||||
continue
|
||||
|
||||
try:
|
||||
component = integration.get_component()
|
||||
except ImportError as ex:
|
||||
result.add_error(f"Component error: {domain} - {ex}")
|
||||
result.add_warning(f"Component error: {domain} - {ex}")
|
||||
continue
|
||||
|
||||
# Check if the integration has a custom config validator
|
||||
@ -216,14 +257,18 @@ async def async_check_ha_config_file( # noqa: C901
|
||||
)
|
||||
platform = p_integration.get_platform(domain)
|
||||
except loader.IntegrationNotFound as ex:
|
||||
# We get this error if an integration is not found. In recovery mode and
|
||||
# safe mode, this currently happens for all custom integrations. Don't
|
||||
# show errors for a missing integration in recovery mode or safe mode to
|
||||
# not confuse the user.
|
||||
if not hass.config.recovery_mode and not hass.config.safe_mode:
|
||||
result.add_error(f"Platform error {domain}.{p_name} - {ex}")
|
||||
result.add_warning(f"Platform error {domain}.{p_name} - {ex}")
|
||||
continue
|
||||
except (
|
||||
RequirementsNotFound,
|
||||
ImportError,
|
||||
) as ex:
|
||||
result.add_error(f"Platform error {domain}.{p_name} - {ex}")
|
||||
result.add_warning(f"Platform error {domain}.{p_name} - {ex}")
|
||||
continue
|
||||
|
||||
# Validate platform specific schema
|
||||
|
@ -40,6 +40,7 @@ PATCHES: dict[str, Any] = {}
|
||||
|
||||
C_HEAD = "bold"
|
||||
ERROR_STR = "General Errors"
|
||||
WARNING_STR = "General Warnings"
|
||||
|
||||
|
||||
def color(the_color, *args, reset=None):
|
||||
@ -116,6 +117,18 @@ def run(script_args: list) -> int:
|
||||
dump_dict(config, reset="red")
|
||||
print(color("reset"))
|
||||
|
||||
if res["warn"]:
|
||||
print(color("bold_white", "Incorrect config"))
|
||||
for domain, config in res["warn"].items():
|
||||
domain_info.append(domain)
|
||||
print(
|
||||
" ",
|
||||
color("bold_yellow", domain + ":"),
|
||||
color("yellow", "", reset="yellow"),
|
||||
)
|
||||
dump_dict(config, reset="yellow")
|
||||
print(color("reset"))
|
||||
|
||||
if domain_info:
|
||||
if "all" in domain_info:
|
||||
print(color("bold_white", "Successful config (all)"))
|
||||
@ -160,7 +173,8 @@ def check(config_dir, secrets=False):
|
||||
res: dict[str, Any] = {
|
||||
"yaml_files": OrderedDict(), # yaml_files loaded
|
||||
"secrets": OrderedDict(), # secret cache and secrets loaded
|
||||
"except": OrderedDict(), # exceptions raised (with config)
|
||||
"except": OrderedDict(), # critical exceptions raised (with config)
|
||||
"warn": OrderedDict(), # non critical exceptions raised (with config)
|
||||
#'components' is a HomeAssistantConfig # noqa: E265
|
||||
"secret_cache": {},
|
||||
}
|
||||
@ -215,6 +229,12 @@ def check(config_dir, secrets=False):
|
||||
if err.config:
|
||||
res["except"].setdefault(domain, []).append(err.config)
|
||||
|
||||
for err in res["components"].warnings:
|
||||
domain = err.domain or WARNING_STR
|
||||
res["warn"].setdefault(domain, []).append(err.message)
|
||||
if err.config:
|
||||
res["warn"].setdefault(domain, []).append(err.config)
|
||||
|
||||
except Exception as err: # pylint: disable=broad-except
|
||||
print(color("red", "Fatal error while loading config:"), str(err))
|
||||
res["except"].setdefault(ERROR_STR, []).append(str(err))
|
||||
|
@ -1,6 +1,6 @@
|
||||
"""Test core config."""
|
||||
from http import HTTPStatus
|
||||
from unittest.mock import patch
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
@ -37,9 +37,14 @@ async def test_validate_config_ok(
|
||||
|
||||
client = await hass_client()
|
||||
|
||||
no_error = Mock()
|
||||
no_error.errors = None
|
||||
no_error.error_str = ""
|
||||
no_error.warning_str = ""
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.config.core.async_check_ha_config_file",
|
||||
return_value=None,
|
||||
"homeassistant.components.config.core.check_config.async_check_ha_config_file",
|
||||
return_value=no_error,
|
||||
):
|
||||
resp = await client.post("/api/config/core/check_config")
|
||||
|
||||
@ -47,10 +52,16 @@ async def test_validate_config_ok(
|
||||
result = await resp.json()
|
||||
assert result["result"] == "valid"
|
||||
assert result["errors"] is None
|
||||
assert result["warnings"] is None
|
||||
|
||||
error_warning = Mock()
|
||||
error_warning.errors = ["beer"]
|
||||
error_warning.error_str = "beer"
|
||||
error_warning.warning_str = "milk"
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.config.core.async_check_ha_config_file",
|
||||
return_value="beer",
|
||||
"homeassistant.components.config.core.check_config.async_check_ha_config_file",
|
||||
return_value=error_warning,
|
||||
):
|
||||
resp = await client.post("/api/config/core/check_config")
|
||||
|
||||
@ -58,6 +69,24 @@ async def test_validate_config_ok(
|
||||
result = await resp.json()
|
||||
assert result["result"] == "invalid"
|
||||
assert result["errors"] == "beer"
|
||||
assert result["warnings"] == "milk"
|
||||
|
||||
warning = Mock()
|
||||
warning.errors = None
|
||||
warning.error_str = ""
|
||||
warning.warning_str = "milk"
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.config.core.check_config.async_check_ha_config_file",
|
||||
return_value=warning,
|
||||
):
|
||||
resp = await client.post("/api/config/core/check_config")
|
||||
|
||||
assert resp.status == HTTPStatus.OK
|
||||
result = await resp.json()
|
||||
assert result["result"] == "valid"
|
||||
assert result["errors"] is None
|
||||
assert result["warnings"] == "milk"
|
||||
|
||||
|
||||
async def test_validate_config_requires_admin(
|
||||
|
@ -2,10 +2,13 @@
|
||||
import logging
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant.config import YAML_CONFIG_FILE
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.check_config import (
|
||||
CheckConfigError,
|
||||
HomeAssistantConfig,
|
||||
async_check_ha_config_file,
|
||||
)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
@ -40,6 +43,28 @@ def log_ha_config(conf):
|
||||
_LOGGER.debug("error[%s] = %s", cnt, err)
|
||||
|
||||
|
||||
def _assert_warnings_errors(
|
||||
res: HomeAssistantConfig,
|
||||
expected_warnings: list[CheckConfigError],
|
||||
expected_errors: list[CheckConfigError],
|
||||
) -> None:
|
||||
assert len(res.warnings) == len(expected_warnings)
|
||||
assert len(res.errors) == len(expected_errors)
|
||||
|
||||
expected_warning_str = ""
|
||||
expected_error_str = ""
|
||||
|
||||
for idx, expected_warning in enumerate(expected_warnings):
|
||||
assert res.warnings[idx] == expected_warning
|
||||
expected_warning_str += expected_warning.message
|
||||
assert res.warning_str == expected_warning_str
|
||||
|
||||
for idx, expected_error in enumerate(expected_errors):
|
||||
assert res.errors[idx] == expected_error
|
||||
expected_error_str += expected_error.message
|
||||
assert res.error_str == expected_error_str
|
||||
|
||||
|
||||
async def test_bad_core_config(hass: HomeAssistant) -> None:
|
||||
"""Test a bad core config setup."""
|
||||
files = {YAML_CONFIG_FILE: BAD_CORE_CONFIG}
|
||||
@ -47,13 +72,12 @@ async def test_bad_core_config(hass: HomeAssistant) -> None:
|
||||
res = await async_check_ha_config_file(hass)
|
||||
log_ha_config(res)
|
||||
|
||||
assert isinstance(res.errors[0].message, str)
|
||||
assert res.errors[0].domain == "homeassistant"
|
||||
assert res.errors[0].config == {"unit_system": "bad"}
|
||||
|
||||
# Only 1 error expected
|
||||
res.errors.pop(0)
|
||||
assert not res.errors
|
||||
error = CheckConfigError(
|
||||
"not a valid value for dictionary value @ data['unit_system']",
|
||||
"homeassistant",
|
||||
{"unit_system": "bad"},
|
||||
)
|
||||
_assert_warnings_errors(res, [], [error])
|
||||
|
||||
|
||||
async def test_config_platform_valid(hass: HomeAssistant) -> None:
|
||||
@ -65,7 +89,7 @@ async def test_config_platform_valid(hass: HomeAssistant) -> None:
|
||||
|
||||
assert res.keys() == {"homeassistant", "light"}
|
||||
assert res["light"] == [{"platform": "demo"}]
|
||||
assert not res.errors
|
||||
_assert_warnings_errors(res, [], [])
|
||||
|
||||
|
||||
async def test_component_platform_not_found(hass: HomeAssistant) -> None:
|
||||
@ -77,13 +101,10 @@ async def test_component_platform_not_found(hass: HomeAssistant) -> None:
|
||||
log_ha_config(res)
|
||||
|
||||
assert res.keys() == {"homeassistant"}
|
||||
assert res.errors[0] == CheckConfigError(
|
||||
warning = CheckConfigError(
|
||||
"Integration error: beer - Integration 'beer' not found.", None, None
|
||||
)
|
||||
|
||||
# Only 1 error expected
|
||||
res.errors.pop(0)
|
||||
assert not res.errors
|
||||
_assert_warnings_errors(res, [warning], [])
|
||||
|
||||
|
||||
async def test_component_requirement_not_found(hass: HomeAssistant) -> None:
|
||||
@ -98,7 +119,7 @@ async def test_component_requirement_not_found(hass: HomeAssistant) -> None:
|
||||
log_ha_config(res)
|
||||
|
||||
assert res.keys() == {"homeassistant"}
|
||||
assert res.errors[0] == CheckConfigError(
|
||||
warning = CheckConfigError(
|
||||
(
|
||||
"Integration error: test_custom_component - Requirements for"
|
||||
" test_custom_component not found: ['any']."
|
||||
@ -106,10 +127,7 @@ async def test_component_requirement_not_found(hass: HomeAssistant) -> None:
|
||||
None,
|
||||
None,
|
||||
)
|
||||
|
||||
# Only 1 error expected
|
||||
res.errors.pop(0)
|
||||
assert not res.errors
|
||||
_assert_warnings_errors(res, [warning], [])
|
||||
|
||||
|
||||
async def test_component_not_found_recovery_mode(hass: HomeAssistant) -> None:
|
||||
@ -122,7 +140,7 @@ async def test_component_not_found_recovery_mode(hass: HomeAssistant) -> None:
|
||||
log_ha_config(res)
|
||||
|
||||
assert res.keys() == {"homeassistant"}
|
||||
assert not res.errors
|
||||
_assert_warnings_errors(res, [], [])
|
||||
|
||||
|
||||
async def test_component_not_found_safe_mode(hass: HomeAssistant) -> None:
|
||||
@ -135,7 +153,55 @@ async def test_component_not_found_safe_mode(hass: HomeAssistant) -> None:
|
||||
log_ha_config(res)
|
||||
|
||||
assert res.keys() == {"homeassistant"}
|
||||
assert not res.errors
|
||||
_assert_warnings_errors(res, [], [])
|
||||
|
||||
|
||||
async def test_component_import_error(hass: HomeAssistant) -> None:
|
||||
"""Test errors if component with a requirement not found not found."""
|
||||
# Make sure they don't exist
|
||||
files = {YAML_CONFIG_FILE: BASE_CONFIG + "light:"}
|
||||
with patch(
|
||||
"homeassistant.loader.Integration.get_component",
|
||||
side_effect=ImportError("blablabla"),
|
||||
), patch("os.path.isfile", return_value=True), patch_yaml_files(files):
|
||||
res = await async_check_ha_config_file(hass)
|
||||
log_ha_config(res)
|
||||
|
||||
assert res.keys() == {"homeassistant"}
|
||||
warning = CheckConfigError(
|
||||
"Component error: light - blablabla",
|
||||
None,
|
||||
None,
|
||||
)
|
||||
_assert_warnings_errors(res, [warning], [])
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("component", "errors", "warnings", "message"),
|
||||
[
|
||||
("frontend", 1, 0, "[blah] is an invalid option for [frontend]"),
|
||||
("http", 1, 0, "[blah] is an invalid option for [http]"),
|
||||
("logger", 0, 1, "[blah] is an invalid option for [logger]"),
|
||||
],
|
||||
)
|
||||
async def test_component_schema_error(
|
||||
hass: HomeAssistant, component: str, errors: int, warnings: int, message: str
|
||||
) -> None:
|
||||
"""Test schema error in component."""
|
||||
# Make sure they don't exist
|
||||
files = {YAML_CONFIG_FILE: BASE_CONFIG + f"frontend:\n{component}:\n blah:"}
|
||||
hass.config.safe_mode = True
|
||||
with patch("os.path.isfile", return_value=True), patch_yaml_files(files):
|
||||
res = await async_check_ha_config_file(hass)
|
||||
log_ha_config(res)
|
||||
|
||||
assert len(res.errors) == errors
|
||||
assert len(res.warnings) == warnings
|
||||
|
||||
for err in res.errors:
|
||||
assert message in err.message
|
||||
for warn in res.warnings:
|
||||
assert message in warn.message
|
||||
|
||||
|
||||
async def test_component_platform_not_found_2(hass: HomeAssistant) -> None:
|
||||
@ -149,13 +215,10 @@ async def test_component_platform_not_found_2(hass: HomeAssistant) -> None:
|
||||
assert res.keys() == {"homeassistant", "light"}
|
||||
assert res["light"] == []
|
||||
|
||||
assert res.errors[0] == CheckConfigError(
|
||||
warning = CheckConfigError(
|
||||
"Platform error light.beer - Integration 'beer' not found.", None, None
|
||||
)
|
||||
|
||||
# Only 1 error expected
|
||||
res.errors.pop(0)
|
||||
assert not res.errors
|
||||
_assert_warnings_errors(res, [warning], [])
|
||||
|
||||
|
||||
async def test_platform_not_found_recovery_mode(hass: HomeAssistant) -> None:
|
||||
@ -170,7 +233,7 @@ async def test_platform_not_found_recovery_mode(hass: HomeAssistant) -> None:
|
||||
assert res.keys() == {"homeassistant", "light"}
|
||||
assert res["light"] == []
|
||||
|
||||
assert not res.errors
|
||||
_assert_warnings_errors(res, [], [])
|
||||
|
||||
|
||||
async def test_platform_not_found_safe_mode(hass: HomeAssistant) -> None:
|
||||
@ -185,7 +248,47 @@ async def test_platform_not_found_safe_mode(hass: HomeAssistant) -> None:
|
||||
assert res.keys() == {"homeassistant", "light"}
|
||||
assert res["light"] == []
|
||||
|
||||
assert not res.errors
|
||||
_assert_warnings_errors(res, [], [])
|
||||
|
||||
|
||||
async def test_component_config_platform_import_error(hass: HomeAssistant) -> None:
|
||||
"""Test errors if config platform fails to import."""
|
||||
# Make sure they don't exist
|
||||
files = {YAML_CONFIG_FILE: BASE_CONFIG + "light:\n platform: beer"}
|
||||
with patch(
|
||||
"homeassistant.loader.Integration.get_platform",
|
||||
side_effect=ImportError("blablabla"),
|
||||
), patch("os.path.isfile", return_value=True), patch_yaml_files(files):
|
||||
res = await async_check_ha_config_file(hass)
|
||||
log_ha_config(res)
|
||||
|
||||
assert res.keys() == {"homeassistant"}
|
||||
error = CheckConfigError(
|
||||
"Error importing config platform light: blablabla",
|
||||
None,
|
||||
None,
|
||||
)
|
||||
_assert_warnings_errors(res, [], [error])
|
||||
|
||||
|
||||
async def test_component_platform_import_error(hass: HomeAssistant) -> None:
|
||||
"""Test errors if component or platform not found."""
|
||||
# Make sure they don't exist
|
||||
files = {YAML_CONFIG_FILE: BASE_CONFIG + "light:\n platform: demo"}
|
||||
with patch(
|
||||
"homeassistant.loader.Integration.get_platform",
|
||||
side_effect=[None, ImportError("blablabla")],
|
||||
), patch("os.path.isfile", return_value=True), patch_yaml_files(files):
|
||||
res = await async_check_ha_config_file(hass)
|
||||
log_ha_config(res)
|
||||
|
||||
assert res.keys() == {"homeassistant", "light"}
|
||||
warning = CheckConfigError(
|
||||
"Platform error light.demo - blablabla",
|
||||
None,
|
||||
None,
|
||||
)
|
||||
_assert_warnings_errors(res, [warning], [])
|
||||
|
||||
|
||||
async def test_package_invalid(hass: HomeAssistant) -> None:
|
||||
@ -195,27 +298,32 @@ async def test_package_invalid(hass: HomeAssistant) -> None:
|
||||
res = await async_check_ha_config_file(hass)
|
||||
log_ha_config(res)
|
||||
|
||||
assert res.errors[0].domain == "homeassistant.packages.p1.group"
|
||||
assert res.errors[0].config == {"group": ["a"]}
|
||||
# Only 1 error expected
|
||||
res.errors.pop(0)
|
||||
assert not res.errors
|
||||
|
||||
assert res.keys() == {"homeassistant"}
|
||||
|
||||
warning = CheckConfigError(
|
||||
(
|
||||
"Package p1 setup failed. Component group cannot be merged. Expected a "
|
||||
"dict."
|
||||
),
|
||||
"homeassistant.packages.p1.group",
|
||||
{"group": ["a"]},
|
||||
)
|
||||
_assert_warnings_errors(res, [warning], [])
|
||||
|
||||
async def test_bootstrap_error(hass: HomeAssistant) -> None:
|
||||
"""Test a valid platform setup."""
|
||||
|
||||
async def test_missing_included_file(hass: HomeAssistant) -> None:
|
||||
"""Test missing included file."""
|
||||
files = {YAML_CONFIG_FILE: BASE_CONFIG + "automation: !include no.yaml"}
|
||||
with patch("os.path.isfile", return_value=True), patch_yaml_files(files):
|
||||
res = await async_check_ha_config_file(hass)
|
||||
log_ha_config(res)
|
||||
|
||||
assert res.errors[0].domain is None
|
||||
assert len(res.errors) == 1
|
||||
assert len(res.warnings) == 0
|
||||
|
||||
# Only 1 error expected
|
||||
res.errors.pop(0)
|
||||
assert not res.errors
|
||||
assert res.errors[0].message.startswith("Error loading")
|
||||
assert res.errors[0].domain is None
|
||||
assert res.errors[0].config is None
|
||||
|
||||
|
||||
async def test_automation_config_platform(hass: HomeAssistant) -> None:
|
||||
@ -251,6 +359,7 @@ action:
|
||||
res = await async_check_ha_config_file(hass)
|
||||
assert len(res.get("automation", [])) == 1
|
||||
assert len(res.errors) == 0
|
||||
assert len(res.warnings) == 0
|
||||
assert "input_datetime" in res
|
||||
|
||||
|
||||
@ -270,11 +379,12 @@ bla:
|
||||
}
|
||||
with patch("os.path.isfile", return_value=True), patch_yaml_files(files):
|
||||
res = await async_check_ha_config_file(hass)
|
||||
assert len(res.errors) == 1
|
||||
err = res.errors[0]
|
||||
assert err.domain == "bla"
|
||||
assert err.message == "Unexpected error calling config validator: Broken"
|
||||
assert err.config == {"value": 1}
|
||||
error = CheckConfigError(
|
||||
"Unexpected error calling config validator: Broken",
|
||||
"bla",
|
||||
{"value": 1},
|
||||
)
|
||||
_assert_warnings_errors(res, [], [error])
|
||||
|
||||
|
||||
async def test_removed_yaml_support(hass: HomeAssistant) -> None:
|
||||
@ -292,3 +402,4 @@ async def test_removed_yaml_support(hass: HomeAssistant) -> None:
|
||||
log_ha_config(res)
|
||||
|
||||
assert res.keys() == {"homeassistant"}
|
||||
_assert_warnings_errors(res, [], [])
|
||||
|
@ -49,6 +49,7 @@ def test_bad_core_config(mock_is_file, event_loop, mock_hass_config_yaml: None)
|
||||
res = check_config.check(get_test_config_dir())
|
||||
assert res["except"].keys() == {"homeassistant"}
|
||||
assert res["except"]["homeassistant"][1] == {"unit_system": "bad"}
|
||||
assert res["warn"] == {}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("hass_config_yaml", [BASE_CONFIG + "light:\n platform: demo"])
|
||||
@ -62,6 +63,7 @@ def test_config_platform_valid(
|
||||
assert res["except"] == {}
|
||||
assert res["secret_cache"] == {}
|
||||
assert res["secrets"] == {}
|
||||
assert res["warn"] == {}
|
||||
assert len(res["yaml_files"]) == 1
|
||||
|
||||
|
||||
@ -87,9 +89,10 @@ def test_component_platform_not_found(
|
||||
# Make sure they don't exist
|
||||
res = check_config.check(get_test_config_dir())
|
||||
assert res["components"].keys() == platforms
|
||||
assert res["except"] == {check_config.ERROR_STR: [error]}
|
||||
assert res["except"] == {}
|
||||
assert res["secret_cache"] == {}
|
||||
assert res["secrets"] == {}
|
||||
assert res["warn"] == {check_config.WARNING_STR: [error]}
|
||||
assert len(res["yaml_files"]) == 1
|
||||
|
||||
|
||||
@ -123,6 +126,7 @@ def test_secrets(mock_is_file, event_loop, mock_hass_config_yaml: None) -> None:
|
||||
get_test_config_dir("secrets.yaml"): {"http_pw": "http://google.com"}
|
||||
}
|
||||
assert res["secrets"] == {"http_pw": "http://google.com"}
|
||||
assert res["warn"] == {}
|
||||
assert normalize_yaml_files(res) == [
|
||||
".../configuration.yaml",
|
||||
".../secrets.yaml",
|
||||
@ -136,13 +140,12 @@ def test_package_invalid(mock_is_file, event_loop, mock_hass_config_yaml: None)
|
||||
"""Test an invalid package."""
|
||||
res = check_config.check(get_test_config_dir())
|
||||
|
||||
assert res["except"].keys() == {"homeassistant.packages.p1.group"}
|
||||
assert res["except"]["homeassistant.packages.p1.group"][1] == {"group": ["a"]}
|
||||
assert len(res["except"]) == 1
|
||||
assert res["except"] == {}
|
||||
assert res["components"].keys() == {"homeassistant"}
|
||||
assert len(res["components"]) == 1
|
||||
assert res["secret_cache"] == {}
|
||||
assert res["secrets"] == {}
|
||||
assert res["warn"].keys() == {"homeassistant.packages.p1.group"}
|
||||
assert res["warn"]["homeassistant.packages.p1.group"][1] == {"group": ["a"]}
|
||||
assert len(res["yaml_files"]) == 1
|
||||
|
||||
|
||||
@ -158,4 +161,5 @@ def test_bootstrap_error(event_loop, mock_hass_config_yaml: None) -> None:
|
||||
assert res["components"] == {} # No components, load failed
|
||||
assert res["secret_cache"] == {}
|
||||
assert res["secrets"] == {}
|
||||
assert res["warn"] == {}
|
||||
assert res["yaml_files"] == {}
|
||||
|
Loading…
x
Reference in New Issue
Block a user