2016-03-09 09:25:50 +00:00
|
|
|
"""Test config utils."""
|
2019-12-09 15:52:24 +00:00
|
|
|
from collections import OrderedDict
|
2022-06-13 18:44:46 +00:00
|
|
|
import contextlib
|
2019-05-20 18:02:36 +00:00
|
|
|
import copy
|
2023-11-13 06:25:58 +00:00
|
|
|
import logging
|
2016-06-27 16:02:45 +00:00
|
|
|
import os
|
2023-02-21 08:27:13 +00:00
|
|
|
from typing import Any
|
2020-02-13 21:43:07 +00:00
|
|
|
from unittest import mock
|
2021-01-01 21:31:56 +00:00
|
|
|
from unittest.mock import AsyncMock, Mock, patch
|
2015-04-26 17:05:01 +00:00
|
|
|
|
2016-03-28 01:48:51 +00:00
|
|
|
import pytest
|
2023-11-13 06:25:58 +00:00
|
|
|
from syrupy.assertion import SnapshotAssertion
|
2020-03-20 03:45:26 +00:00
|
|
|
import voluptuous as vol
|
2019-12-09 15:52:24 +00:00
|
|
|
from voluptuous import Invalid, MultipleInvalid
|
2018-12-03 09:56:26 +00:00
|
|
|
import yaml
|
2016-03-28 01:48:51 +00:00
|
|
|
|
2015-04-26 17:05:01 +00:00
|
|
|
import homeassistant.config as config_util
|
|
|
|
from homeassistant.const import (
|
2019-12-09 15:52:24 +00:00
|
|
|
ATTR_ASSUMED_STATE,
|
2019-07-31 19:25:30 +00:00
|
|
|
ATTR_FRIENDLY_NAME,
|
2019-12-09 15:52:24 +00:00
|
|
|
CONF_AUTH_MFA_MODULES,
|
|
|
|
CONF_AUTH_PROVIDERS,
|
|
|
|
CONF_CUSTOMIZE,
|
2019-07-31 19:25:30 +00:00
|
|
|
CONF_LATITUDE,
|
|
|
|
CONF_LONGITUDE,
|
|
|
|
CONF_NAME,
|
2019-12-09 15:52:24 +00:00
|
|
|
CONF_UNIT_SYSTEM,
|
|
|
|
CONF_UNIT_SYSTEM_IMPERIAL,
|
|
|
|
CONF_UNIT_SYSTEM_METRIC,
|
|
|
|
__version__,
|
2019-07-31 19:25:30 +00:00
|
|
|
)
|
2022-10-19 11:31:08 +00:00
|
|
|
from homeassistant.core import ConfigSource, HomeAssistant, HomeAssistantError
|
2023-11-24 16:34:45 +00:00
|
|
|
from homeassistant.exceptions import ConfigValidationError
|
2022-11-08 06:21:09 +00:00
|
|
|
from homeassistant.helpers import config_validation as cv, issue_registry as ir
|
2019-12-09 15:52:24 +00:00
|
|
|
import homeassistant.helpers.check_config as check_config
|
|
|
|
from homeassistant.helpers.entity import Entity
|
2023-11-15 12:11:33 +00:00
|
|
|
from homeassistant.helpers.typing import ConfigType
|
2023-11-13 10:21:37 +00:00
|
|
|
from homeassistant.loader import Integration, async_get_integration
|
2022-10-19 11:31:08 +00:00
|
|
|
from homeassistant.util.unit_system import (
|
|
|
|
_CONF_UNIT_SYSTEM_US_CUSTOMARY,
|
|
|
|
METRIC_SYSTEM,
|
|
|
|
US_CUSTOMARY_SYSTEM,
|
|
|
|
UnitSystem,
|
|
|
|
)
|
2017-10-05 16:10:29 +00:00
|
|
|
from homeassistant.util.yaml import SECRET_YAML
|
2015-04-26 17:05:01 +00:00
|
|
|
|
2023-11-13 06:25:58 +00:00
|
|
|
from .common import (
|
|
|
|
MockModule,
|
|
|
|
MockPlatform,
|
|
|
|
MockUser,
|
|
|
|
get_test_config_dir,
|
|
|
|
mock_integration,
|
|
|
|
mock_platform,
|
|
|
|
)
|
2015-04-26 17:05:01 +00:00
|
|
|
|
|
|
|
CONFIG_DIR = get_test_config_dir()
|
|
|
|
YAML_PATH = os.path.join(CONFIG_DIR, config_util.YAML_CONFIG_FILE)
|
2017-10-05 16:10:29 +00:00
|
|
|
SECRET_PATH = os.path.join(CONFIG_DIR, SECRET_YAML)
|
2016-07-04 01:24:17 +00:00
|
|
|
VERSION_PATH = os.path.join(CONFIG_DIR, config_util.VERSION_FILE)
|
2019-10-15 23:15:26 +00:00
|
|
|
AUTOMATIONS_PATH = os.path.join(CONFIG_DIR, config_util.AUTOMATION_CONFIG_PATH)
|
|
|
|
SCRIPTS_PATH = os.path.join(CONFIG_DIR, config_util.SCRIPT_CONFIG_PATH)
|
2019-11-04 20:38:18 +00:00
|
|
|
SCENES_PATH = os.path.join(CONFIG_DIR, config_util.SCENE_CONFIG_PATH)
|
2023-10-24 12:47:58 +00:00
|
|
|
SAFE_MODE_PATH = os.path.join(CONFIG_DIR, config_util.SAFE_MODE_FILENAME)
|
2015-04-26 17:05:01 +00:00
|
|
|
|
|
|
|
|
|
|
|
def create_file(path):
|
2016-03-09 09:25:50 +00:00
|
|
|
"""Create an empty file."""
|
2019-07-31 19:25:30 +00:00
|
|
|
with open(path, "w"):
|
2015-04-26 17:05:01 +00:00
|
|
|
pass
|
|
|
|
|
|
|
|
|
2022-02-08 09:03:27 +00:00
|
|
|
@pytest.fixture(autouse=True)
|
2019-04-30 16:20:38 +00:00
|
|
|
def teardown():
|
|
|
|
"""Clean up."""
|
2022-02-08 09:03:27 +00:00
|
|
|
yield
|
|
|
|
|
2019-04-30 16:20:38 +00:00
|
|
|
if os.path.isfile(YAML_PATH):
|
|
|
|
os.remove(YAML_PATH)
|
2016-10-27 07:16:23 +00:00
|
|
|
|
2019-04-30 16:20:38 +00:00
|
|
|
if os.path.isfile(SECRET_PATH):
|
|
|
|
os.remove(SECRET_PATH)
|
2016-06-27 16:02:45 +00:00
|
|
|
|
2019-04-30 16:20:38 +00:00
|
|
|
if os.path.isfile(VERSION_PATH):
|
|
|
|
os.remove(VERSION_PATH)
|
2015-04-26 17:05:01 +00:00
|
|
|
|
2019-04-30 16:20:38 +00:00
|
|
|
if os.path.isfile(AUTOMATIONS_PATH):
|
|
|
|
os.remove(AUTOMATIONS_PATH)
|
2016-07-04 01:24:17 +00:00
|
|
|
|
2019-04-30 16:20:38 +00:00
|
|
|
if os.path.isfile(SCRIPTS_PATH):
|
|
|
|
os.remove(SCRIPTS_PATH)
|
2017-02-24 06:53:16 +00:00
|
|
|
|
2019-11-04 20:38:18 +00:00
|
|
|
if os.path.isfile(SCENES_PATH):
|
|
|
|
os.remove(SCENES_PATH)
|
|
|
|
|
2023-10-24 12:47:58 +00:00
|
|
|
if os.path.isfile(SAFE_MODE_PATH):
|
|
|
|
os.remove(SAFE_MODE_PATH)
|
|
|
|
|
2017-08-29 20:40:08 +00:00
|
|
|
|
2023-11-13 10:21:37 +00:00
|
|
|
IOT_DOMAIN_PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend({vol.Remove("old"): str})
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
|
async def mock_iot_domain_integration(hass: HomeAssistant) -> Integration:
|
|
|
|
"""Mock an integration which provides an IoT domain."""
|
|
|
|
comp_platform_schema = cv.PLATFORM_SCHEMA.extend({vol.Remove("old"): str})
|
|
|
|
comp_platform_schema_base = comp_platform_schema.extend({}, extra=vol.ALLOW_EXTRA)
|
|
|
|
|
|
|
|
return mock_integration(
|
|
|
|
hass,
|
|
|
|
MockModule(
|
|
|
|
"iot_domain",
|
|
|
|
platform_schema_base=comp_platform_schema_base,
|
|
|
|
platform_schema=comp_platform_schema,
|
|
|
|
),
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
2023-11-14 16:14:34 +00:00
|
|
|
async def mock_iot_domain_integration_with_docs(hass: HomeAssistant) -> Integration:
|
|
|
|
"""Mock an integration which provides an IoT domain."""
|
|
|
|
comp_platform_schema = cv.PLATFORM_SCHEMA.extend({vol.Remove("old"): str})
|
|
|
|
comp_platform_schema_base = comp_platform_schema.extend({}, extra=vol.ALLOW_EXTRA)
|
|
|
|
|
|
|
|
return mock_integration(
|
|
|
|
hass,
|
|
|
|
MockModule(
|
|
|
|
"iot_domain",
|
|
|
|
platform_schema_base=comp_platform_schema_base,
|
|
|
|
platform_schema=comp_platform_schema,
|
|
|
|
partial_manifest={
|
|
|
|
"documentation": "https://www.home-assistant.io/integrations/iot_domain"
|
|
|
|
},
|
|
|
|
),
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
|
async def mock_non_adr_0007_integration(hass: HomeAssistant) -> None:
|
2023-11-13 10:21:37 +00:00
|
|
|
"""Mock a non-ADR-0007 compliant integration with iot_domain platform.
|
|
|
|
|
|
|
|
The integration allows setting up iot_domain entities under the iot_domain's
|
|
|
|
configuration key
|
|
|
|
"""
|
|
|
|
|
2023-11-14 14:08:20 +00:00
|
|
|
test_platform_schema = IOT_DOMAIN_PLATFORM_SCHEMA.extend(
|
|
|
|
{vol.Required("option1"): str, vol.Optional("option2"): str}
|
|
|
|
)
|
2023-11-13 10:21:37 +00:00
|
|
|
mock_platform(
|
|
|
|
hass,
|
|
|
|
"non_adr_0007.iot_domain",
|
|
|
|
MockPlatform(platform_schema=test_platform_schema),
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
2023-11-14 16:14:34 +00:00
|
|
|
async def mock_non_adr_0007_integration_with_docs(hass: HomeAssistant) -> None:
|
|
|
|
"""Mock a non-ADR-0007 compliant integration with iot_domain platform.
|
|
|
|
|
|
|
|
The integration allows setting up iot_domain entities under the iot_domain's
|
|
|
|
configuration key
|
|
|
|
"""
|
|
|
|
|
|
|
|
mock_integration(
|
|
|
|
hass,
|
|
|
|
MockModule(
|
|
|
|
"non_adr_0007",
|
|
|
|
partial_manifest={
|
|
|
|
"documentation": "https://www.home-assistant.io/integrations/non_adr_0007"
|
|
|
|
},
|
|
|
|
),
|
|
|
|
)
|
|
|
|
test_platform_schema = IOT_DOMAIN_PLATFORM_SCHEMA.extend(
|
|
|
|
{vol.Required("option1"): str, vol.Optional("option2"): str}
|
|
|
|
)
|
|
|
|
mock_platform(
|
|
|
|
hass,
|
|
|
|
"non_adr_0007.iot_domain",
|
|
|
|
MockPlatform(platform_schema=test_platform_schema),
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
|
async def mock_adr_0007_integrations(hass: HomeAssistant) -> list[Integration]:
|
2023-11-13 10:21:37 +00:00
|
|
|
"""Mock ADR-0007 compliant integrations."""
|
|
|
|
integrations = []
|
2023-11-14 14:08:20 +00:00
|
|
|
for domain in [
|
|
|
|
"adr_0007_1",
|
|
|
|
"adr_0007_2",
|
|
|
|
"adr_0007_3",
|
|
|
|
"adr_0007_4",
|
|
|
|
"adr_0007_5",
|
|
|
|
]:
|
2023-11-13 10:21:37 +00:00
|
|
|
adr_0007_config_schema = vol.Schema(
|
|
|
|
{
|
|
|
|
domain: vol.Schema(
|
|
|
|
{
|
|
|
|
vol.Required("host"): str,
|
2023-11-15 12:11:33 +00:00
|
|
|
vol.Optional("port", default=8080): int,
|
2023-11-13 10:21:37 +00:00
|
|
|
}
|
|
|
|
)
|
|
|
|
},
|
|
|
|
extra=vol.ALLOW_EXTRA,
|
|
|
|
)
|
|
|
|
integrations.append(
|
|
|
|
mock_integration(
|
|
|
|
hass,
|
|
|
|
MockModule(domain, config_schema=adr_0007_config_schema),
|
|
|
|
)
|
|
|
|
)
|
|
|
|
return integrations
|
|
|
|
|
|
|
|
|
2023-11-14 16:14:34 +00:00
|
|
|
@pytest.fixture
|
|
|
|
async def mock_adr_0007_integrations_with_docs(
|
|
|
|
hass: HomeAssistant,
|
|
|
|
) -> list[Integration]:
|
|
|
|
"""Mock ADR-0007 compliant integrations."""
|
|
|
|
integrations = []
|
|
|
|
for domain in [
|
|
|
|
"adr_0007_1",
|
|
|
|
"adr_0007_2",
|
|
|
|
"adr_0007_3",
|
|
|
|
"adr_0007_4",
|
|
|
|
"adr_0007_5",
|
|
|
|
]:
|
|
|
|
adr_0007_config_schema = vol.Schema(
|
|
|
|
{
|
|
|
|
domain: vol.Schema(
|
|
|
|
{
|
|
|
|
vol.Required("host"): str,
|
2023-11-15 12:11:33 +00:00
|
|
|
vol.Optional("port", default=8080): int,
|
2023-11-14 16:14:34 +00:00
|
|
|
}
|
|
|
|
)
|
|
|
|
},
|
|
|
|
extra=vol.ALLOW_EXTRA,
|
|
|
|
)
|
|
|
|
integrations.append(
|
|
|
|
mock_integration(
|
|
|
|
hass,
|
|
|
|
MockModule(
|
|
|
|
domain,
|
|
|
|
config_schema=adr_0007_config_schema,
|
|
|
|
partial_manifest={
|
|
|
|
"documentation": f"https://www.home-assistant.io/integrations/{domain}"
|
|
|
|
},
|
|
|
|
),
|
|
|
|
)
|
|
|
|
)
|
|
|
|
return integrations
|
|
|
|
|
|
|
|
|
2023-11-15 12:11:33 +00:00
|
|
|
@pytest.fixture
|
|
|
|
async def mock_custom_validator_integrations(hass: HomeAssistant) -> list[Integration]:
|
|
|
|
"""Mock integrations with custom validator."""
|
|
|
|
integrations = []
|
|
|
|
|
|
|
|
for domain in ("custom_validator_ok_1", "custom_validator_ok_2"):
|
|
|
|
|
|
|
|
def gen_async_validate_config(domain):
|
|
|
|
schema = vol.Schema(
|
|
|
|
{
|
|
|
|
domain: vol.Schema(
|
|
|
|
{
|
|
|
|
vol.Required("host"): str,
|
|
|
|
vol.Optional("port", default=8080): int,
|
|
|
|
}
|
|
|
|
)
|
|
|
|
},
|
|
|
|
extra=vol.ALLOW_EXTRA,
|
|
|
|
)
|
|
|
|
|
|
|
|
async def async_validate_config(
|
|
|
|
hass: HomeAssistant, config: ConfigType
|
|
|
|
) -> ConfigType:
|
|
|
|
"""Validate config."""
|
|
|
|
return schema(config)
|
|
|
|
|
|
|
|
return async_validate_config
|
|
|
|
|
|
|
|
integrations.append(mock_integration(hass, MockModule(domain)))
|
|
|
|
mock_platform(
|
|
|
|
hass,
|
|
|
|
f"{domain}.config",
|
|
|
|
Mock(async_validate_config=gen_async_validate_config(domain)),
|
|
|
|
)
|
|
|
|
|
|
|
|
for domain, exception in [
|
|
|
|
("custom_validator_bad_1", HomeAssistantError("broken")),
|
|
|
|
("custom_validator_bad_2", ValueError("broken")),
|
|
|
|
]:
|
|
|
|
integrations.append(mock_integration(hass, MockModule(domain)))
|
|
|
|
mock_platform(
|
|
|
|
hass,
|
|
|
|
f"{domain}.config",
|
|
|
|
Mock(async_validate_config=AsyncMock(side_effect=exception)),
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2023-11-15 18:09:49 +00:00
|
|
|
@pytest.fixture
|
|
|
|
async def mock_custom_validator_integrations_with_docs(
|
|
|
|
hass: HomeAssistant,
|
|
|
|
) -> list[Integration]:
|
|
|
|
"""Mock integrations with custom validator."""
|
|
|
|
integrations = []
|
|
|
|
|
|
|
|
for domain in ("custom_validator_ok_1", "custom_validator_ok_2"):
|
|
|
|
|
|
|
|
def gen_async_validate_config(domain):
|
|
|
|
schema = vol.Schema(
|
|
|
|
{
|
|
|
|
domain: vol.Schema(
|
|
|
|
{
|
|
|
|
vol.Required("host"): str,
|
|
|
|
vol.Optional("port", default=8080): int,
|
|
|
|
}
|
|
|
|
)
|
|
|
|
},
|
|
|
|
extra=vol.ALLOW_EXTRA,
|
|
|
|
)
|
|
|
|
|
|
|
|
async def async_validate_config(
|
|
|
|
hass: HomeAssistant, config: ConfigType
|
|
|
|
) -> ConfigType:
|
|
|
|
"""Validate config."""
|
|
|
|
return schema(config)
|
|
|
|
|
|
|
|
return async_validate_config
|
|
|
|
|
|
|
|
integrations.append(
|
|
|
|
mock_integration(
|
|
|
|
hass,
|
|
|
|
MockModule(
|
|
|
|
domain,
|
|
|
|
partial_manifest={
|
|
|
|
"documentation": f"https://www.home-assistant.io/integrations/{domain}"
|
|
|
|
},
|
|
|
|
),
|
|
|
|
)
|
|
|
|
)
|
|
|
|
mock_platform(
|
|
|
|
hass,
|
|
|
|
f"{domain}.config",
|
|
|
|
Mock(async_validate_config=gen_async_validate_config(domain)),
|
|
|
|
)
|
|
|
|
|
|
|
|
for domain, exception in [
|
|
|
|
("custom_validator_bad_1", HomeAssistantError("broken")),
|
|
|
|
("custom_validator_bad_2", ValueError("broken")),
|
|
|
|
]:
|
|
|
|
integrations.append(
|
|
|
|
mock_integration(
|
|
|
|
hass,
|
|
|
|
MockModule(
|
|
|
|
domain,
|
|
|
|
partial_manifest={
|
|
|
|
"documentation": f"https://www.home-assistant.io/integrations/{domain}"
|
|
|
|
},
|
|
|
|
),
|
|
|
|
)
|
|
|
|
)
|
|
|
|
mock_platform(
|
|
|
|
hass,
|
|
|
|
f"{domain}.config",
|
|
|
|
Mock(async_validate_config=AsyncMock(side_effect=exception)),
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2023-02-08 07:51:43 +00:00
|
|
|
async def test_create_default_config(hass: HomeAssistant) -> None:
|
2019-04-30 16:20:38 +00:00
|
|
|
"""Test creation of default config."""
|
2022-02-08 09:03:27 +00:00
|
|
|
assert not os.path.isfile(YAML_PATH)
|
|
|
|
assert not os.path.isfile(SECRET_PATH)
|
|
|
|
assert not os.path.isfile(VERSION_PATH)
|
|
|
|
assert not os.path.isfile(AUTOMATIONS_PATH)
|
|
|
|
|
2020-01-14 21:03:02 +00:00
|
|
|
await config_util.async_create_default_config(hass)
|
2017-08-26 17:02:32 +00:00
|
|
|
|
2019-04-30 16:20:38 +00:00
|
|
|
assert os.path.isfile(YAML_PATH)
|
|
|
|
assert os.path.isfile(SECRET_PATH)
|
|
|
|
assert os.path.isfile(VERSION_PATH)
|
|
|
|
assert os.path.isfile(AUTOMATIONS_PATH)
|
2016-06-27 16:02:45 +00:00
|
|
|
|
2015-04-26 17:05:01 +00:00
|
|
|
|
2023-02-08 07:51:43 +00:00
|
|
|
async def test_ensure_config_exists_creates_config(hass: HomeAssistant) -> None:
|
2019-04-30 16:20:38 +00:00
|
|
|
"""Test that calling ensure_config_exists.
|
2015-04-26 17:05:01 +00:00
|
|
|
|
2019-04-30 16:20:38 +00:00
|
|
|
If not creates a new config file.
|
|
|
|
"""
|
2022-02-08 09:03:27 +00:00
|
|
|
assert not os.path.isfile(YAML_PATH)
|
2020-04-30 20:29:50 +00:00
|
|
|
with patch("builtins.print") as mock_print:
|
2020-01-14 21:03:02 +00:00
|
|
|
await config_util.async_ensure_config_exists(hass)
|
2015-04-26 17:05:01 +00:00
|
|
|
|
2019-04-30 16:20:38 +00:00
|
|
|
assert os.path.isfile(YAML_PATH)
|
|
|
|
assert mock_print.called
|
2015-04-26 17:05:01 +00:00
|
|
|
|
2019-04-30 16:20:38 +00:00
|
|
|
|
2023-02-08 07:51:43 +00:00
|
|
|
async def test_ensure_config_exists_uses_existing_config(hass: HomeAssistant) -> None:
|
2019-04-30 16:20:38 +00:00
|
|
|
"""Test that calling ensure_config_exists uses existing config."""
|
|
|
|
create_file(YAML_PATH)
|
2020-01-14 21:03:02 +00:00
|
|
|
await config_util.async_ensure_config_exists(hass)
|
2019-04-30 16:20:38 +00:00
|
|
|
|
2020-04-06 10:51:48 +00:00
|
|
|
with open(YAML_PATH) as fp:
|
|
|
|
content = fp.read()
|
2019-04-30 16:20:38 +00:00
|
|
|
|
|
|
|
# File created with create_file are empty
|
2019-07-31 19:25:30 +00:00
|
|
|
assert content == ""
|
2019-04-30 16:20:38 +00:00
|
|
|
|
|
|
|
|
2023-02-08 07:51:43 +00:00
|
|
|
async def test_ensure_existing_files_is_not_overwritten(hass: HomeAssistant) -> None:
|
2021-04-28 15:15:04 +00:00
|
|
|
"""Test that calling async_create_default_config does not overwrite existing files."""
|
|
|
|
create_file(SECRET_PATH)
|
|
|
|
|
|
|
|
await config_util.async_create_default_config(hass)
|
|
|
|
|
|
|
|
with open(SECRET_PATH) as fp:
|
|
|
|
content = fp.read()
|
|
|
|
|
|
|
|
# File created with create_file are empty
|
|
|
|
assert content == ""
|
|
|
|
|
|
|
|
|
2023-02-07 13:20:06 +00:00
|
|
|
def test_load_yaml_config_converts_empty_files_to_dict() -> None:
|
2019-04-30 16:20:38 +00:00
|
|
|
"""Test that loading an empty file returns an empty dict."""
|
|
|
|
create_file(YAML_PATH)
|
|
|
|
|
|
|
|
assert isinstance(config_util.load_yaml_config_file(YAML_PATH), dict)
|
|
|
|
|
|
|
|
|
2023-02-07 13:20:06 +00:00
|
|
|
def test_load_yaml_config_raises_error_if_not_dict() -> None:
|
2019-04-30 16:20:38 +00:00
|
|
|
"""Test error raised when YAML file is not a dict."""
|
2020-04-06 10:51:48 +00:00
|
|
|
with open(YAML_PATH, "w") as fp:
|
|
|
|
fp.write("5")
|
2019-04-30 16:20:38 +00:00
|
|
|
|
|
|
|
with pytest.raises(HomeAssistantError):
|
|
|
|
config_util.load_yaml_config_file(YAML_PATH)
|
|
|
|
|
|
|
|
|
2023-02-07 13:20:06 +00:00
|
|
|
def test_load_yaml_config_raises_error_if_malformed_yaml() -> None:
|
2019-04-30 16:20:38 +00:00
|
|
|
"""Test error raised if invalid YAML."""
|
2020-04-06 10:51:48 +00:00
|
|
|
with open(YAML_PATH, "w") as fp:
|
2022-06-13 18:44:46 +00:00
|
|
|
fp.write(":-")
|
2019-04-30 16:20:38 +00:00
|
|
|
|
|
|
|
with pytest.raises(HomeAssistantError):
|
|
|
|
config_util.load_yaml_config_file(YAML_PATH)
|
|
|
|
|
|
|
|
|
2023-02-07 13:20:06 +00:00
|
|
|
def test_load_yaml_config_raises_error_if_unsafe_yaml() -> None:
|
2019-04-30 16:20:38 +00:00
|
|
|
"""Test error raised if unsafe YAML."""
|
2020-04-06 10:51:48 +00:00
|
|
|
with open(YAML_PATH, "w") as fp:
|
2022-06-13 18:44:46 +00:00
|
|
|
fp.write("- !!python/object/apply:os.system []")
|
2019-04-30 16:20:38 +00:00
|
|
|
|
2022-06-13 18:44:46 +00:00
|
|
|
with patch.object(os, "system") as system_mock, contextlib.suppress(
|
|
|
|
HomeAssistantError
|
|
|
|
):
|
2019-04-30 16:20:38 +00:00
|
|
|
config_util.load_yaml_config_file(YAML_PATH)
|
|
|
|
|
2022-06-13 18:44:46 +00:00
|
|
|
assert len(system_mock.mock_calls) == 0
|
|
|
|
|
|
|
|
# Here we validate that the test above is a good test
|
|
|
|
# since previously the syntax was not valid
|
|
|
|
with open(YAML_PATH) as fp, patch.object(os, "system") as system_mock:
|
|
|
|
list(yaml.unsafe_load_all(fp))
|
|
|
|
|
|
|
|
assert len(system_mock.mock_calls) == 1
|
|
|
|
|
2019-04-30 16:20:38 +00:00
|
|
|
|
2023-02-07 13:20:06 +00:00
|
|
|
def test_load_yaml_config_preserves_key_order() -> None:
|
2019-04-30 16:20:38 +00:00
|
|
|
"""Test removal of library."""
|
2020-04-06 10:51:48 +00:00
|
|
|
with open(YAML_PATH, "w") as fp:
|
|
|
|
fp.write("hello: 2\n")
|
|
|
|
fp.write("world: 1\n")
|
2019-04-30 16:20:38 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
assert [("hello", 2), ("world", 1)] == list(
|
|
|
|
config_util.load_yaml_config_file(YAML_PATH).items()
|
|
|
|
)
|
2019-04-30 16:20:38 +00:00
|
|
|
|
|
|
|
|
2023-02-08 07:51:43 +00:00
|
|
|
async def test_create_default_config_returns_none_if_write_error(
|
|
|
|
hass: HomeAssistant,
|
|
|
|
) -> None:
|
2019-04-30 16:20:38 +00:00
|
|
|
"""Test the writing of a default configuration.
|
|
|
|
|
|
|
|
Non existing folder returns None.
|
|
|
|
"""
|
2020-01-14 21:03:02 +00:00
|
|
|
hass.config.config_dir = os.path.join(CONFIG_DIR, "non_existing_dir/")
|
2020-04-30 20:29:50 +00:00
|
|
|
with patch("builtins.print") as mock_print:
|
2020-01-14 21:03:02 +00:00
|
|
|
assert await config_util.async_create_default_config(hass) is False
|
2019-04-30 16:20:38 +00:00
|
|
|
assert mock_print.called
|
|
|
|
|
|
|
|
|
2023-02-07 13:20:06 +00:00
|
|
|
def test_core_config_schema() -> None:
|
2019-04-30 16:20:38 +00:00
|
|
|
"""Test core config schema."""
|
|
|
|
for value in (
|
2019-07-31 19:25:30 +00:00
|
|
|
{CONF_UNIT_SYSTEM: "K"},
|
|
|
|
{"time_zone": "non-exist"},
|
|
|
|
{"latitude": "91"},
|
|
|
|
{"longitude": -181},
|
2020-05-08 00:29:47 +00:00
|
|
|
{"external_url": "not an url"},
|
|
|
|
{"internal_url": "not an url"},
|
2021-07-28 06:55:58 +00:00
|
|
|
{"currency", 100},
|
2019-07-31 19:25:30 +00:00
|
|
|
{"customize": "bla"},
|
|
|
|
{"customize": {"light.sensor": 100}},
|
|
|
|
{"customize": {"entity_id": []}},
|
2022-11-24 22:25:50 +00:00
|
|
|
{"country": "xx"},
|
|
|
|
{"language": "xx"},
|
2019-04-30 16:20:38 +00:00
|
|
|
):
|
|
|
|
with pytest.raises(MultipleInvalid):
|
|
|
|
config_util.CORE_CONFIG_SCHEMA(value)
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
config_util.CORE_CONFIG_SCHEMA(
|
|
|
|
{
|
|
|
|
"name": "Test name",
|
|
|
|
"latitude": "-23.45",
|
|
|
|
"longitude": "123.45",
|
2020-05-08 00:29:47 +00:00
|
|
|
"external_url": "https://www.example.com",
|
|
|
|
"internal_url": "http://example.local",
|
2019-07-31 19:25:30 +00:00
|
|
|
CONF_UNIT_SYSTEM: CONF_UNIT_SYSTEM_METRIC,
|
2021-07-28 06:55:58 +00:00
|
|
|
"currency": "USD",
|
2019-07-31 19:25:30 +00:00
|
|
|
"customize": {"sensor.temperature": {"hidden": True}},
|
2022-11-24 22:25:50 +00:00
|
|
|
"country": "SE",
|
|
|
|
"language": "sv",
|
2019-07-31 19:25:30 +00:00
|
|
|
}
|
|
|
|
)
|
2019-04-30 16:20:38 +00:00
|
|
|
|
2016-06-27 16:02:45 +00:00
|
|
|
|
2023-02-21 08:27:13 +00:00
|
|
|
def test_core_config_schema_internal_external_warning(
|
|
|
|
caplog: pytest.LogCaptureFixture,
|
|
|
|
) -> None:
|
2021-08-09 07:38:09 +00:00
|
|
|
"""Test that we warn for internal/external URL with path."""
|
|
|
|
config_util.CORE_CONFIG_SCHEMA(
|
|
|
|
{
|
|
|
|
"external_url": "https://www.example.com/bla",
|
|
|
|
"internal_url": "http://example.local/yo",
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
assert "Invalid external_url set" in caplog.text
|
|
|
|
assert "Invalid internal_url set" in caplog.text
|
|
|
|
|
|
|
|
|
2023-02-07 13:20:06 +00:00
|
|
|
def test_customize_dict_schema() -> None:
|
2019-04-30 16:20:38 +00:00
|
|
|
"""Test basic customize config validation."""
|
2020-06-23 22:05:32 +00:00
|
|
|
values = ({ATTR_FRIENDLY_NAME: None}, {ATTR_ASSUMED_STATE: "2"})
|
2019-04-30 16:20:38 +00:00
|
|
|
|
|
|
|
for val in values:
|
2017-10-17 07:59:33 +00:00
|
|
|
with pytest.raises(MultipleInvalid):
|
2019-04-30 16:20:38 +00:00
|
|
|
config_util.CUSTOMIZE_DICT_SCHEMA(val)
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
assert config_util.CUSTOMIZE_DICT_SCHEMA(
|
2020-06-23 22:05:32 +00:00
|
|
|
{ATTR_FRIENDLY_NAME: 2, ATTR_ASSUMED_STATE: "0"}
|
|
|
|
) == {ATTR_FRIENDLY_NAME: "2", ATTR_ASSUMED_STATE: False}
|
2019-04-30 16:20:38 +00:00
|
|
|
|
|
|
|
|
2023-02-07 13:20:06 +00:00
|
|
|
def test_customize_glob_is_ordered() -> None:
|
2019-04-30 16:20:38 +00:00
|
|
|
"""Test that customize_glob preserves order."""
|
2019-07-31 19:25:30 +00:00
|
|
|
conf = config_util.CORE_CONFIG_SCHEMA({"customize_glob": OrderedDict()})
|
|
|
|
assert isinstance(conf["customize_glob"], OrderedDict)
|
2019-04-30 16:20:38 +00:00
|
|
|
|
|
|
|
|
|
|
|
async def _compute_state(hass, config):
|
|
|
|
await config_util.async_process_ha_core_config(hass, config)
|
|
|
|
|
|
|
|
entity = Entity()
|
2019-07-31 19:25:30 +00:00
|
|
|
entity.entity_id = "test.test"
|
2019-04-30 16:20:38 +00:00
|
|
|
entity.hass = hass
|
|
|
|
entity.schedule_update_ha_state()
|
|
|
|
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
return hass.states.get("test.test")
|
2019-04-30 16:20:38 +00:00
|
|
|
|
|
|
|
|
2023-02-08 07:51:43 +00:00
|
|
|
async def test_entity_customization(hass: HomeAssistant) -> None:
|
2019-04-30 16:20:38 +00:00
|
|
|
"""Test entity customization through configuration."""
|
2019-07-31 19:25:30 +00:00
|
|
|
config = {
|
|
|
|
CONF_LATITUDE: 50,
|
|
|
|
CONF_LONGITUDE: 50,
|
|
|
|
CONF_NAME: "Test",
|
|
|
|
CONF_CUSTOMIZE: {"test.test": {"hidden": True}},
|
|
|
|
}
|
2019-04-30 16:20:38 +00:00
|
|
|
|
|
|
|
state = await _compute_state(hass, config)
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
assert state.attributes["hidden"]
|
2019-04-30 16:20:38 +00:00
|
|
|
|
|
|
|
|
2020-04-30 20:29:50 +00:00
|
|
|
@patch("homeassistant.config.shutil")
|
|
|
|
@patch("homeassistant.config.os")
|
|
|
|
@patch("homeassistant.config.is_docker_env", return_value=False)
|
2023-02-21 08:27:13 +00:00
|
|
|
def test_remove_lib_on_upgrade(
|
|
|
|
mock_docker, mock_os, mock_shutil, hass: HomeAssistant
|
|
|
|
) -> None:
|
2019-04-30 16:20:38 +00:00
|
|
|
"""Test removal of library on upgrade from before 0.50."""
|
2019-07-31 19:25:30 +00:00
|
|
|
ha_version = "0.49.0"
|
2019-04-30 16:20:38 +00:00
|
|
|
mock_os.path.isdir = mock.Mock(return_value=True)
|
|
|
|
mock_open = mock.mock_open()
|
2020-04-30 20:29:50 +00:00
|
|
|
with patch("homeassistant.config.open", mock_open, create=True):
|
2019-04-30 16:20:38 +00:00
|
|
|
opened_file = mock_open.return_value
|
|
|
|
opened_file.readline.return_value = ha_version
|
|
|
|
hass.config.path = mock.Mock()
|
|
|
|
config_util.process_ha_config_upgrade(hass)
|
2019-06-03 19:37:27 +00:00
|
|
|
hass_path = hass.config.path.return_value
|
|
|
|
|
|
|
|
assert mock_os.path.isdir.call_count == 1
|
|
|
|
assert mock_os.path.isdir.call_args == mock.call(hass_path)
|
|
|
|
assert mock_shutil.rmtree.call_count == 1
|
|
|
|
assert mock_shutil.rmtree.call_args == mock.call(hass_path)
|
|
|
|
|
|
|
|
|
2020-04-30 20:29:50 +00:00
|
|
|
@patch("homeassistant.config.shutil")
|
|
|
|
@patch("homeassistant.config.os")
|
|
|
|
@patch("homeassistant.config.is_docker_env", return_value=True)
|
2023-02-21 08:27:13 +00:00
|
|
|
def test_remove_lib_on_upgrade_94(
|
|
|
|
mock_docker, mock_os, mock_shutil, hass: HomeAssistant
|
|
|
|
) -> None:
|
2019-06-03 19:37:27 +00:00
|
|
|
"""Test removal of library on upgrade from before 0.94 and in Docker."""
|
2019-07-31 19:25:30 +00:00
|
|
|
ha_version = "0.93.0.dev0"
|
2019-06-03 19:37:27 +00:00
|
|
|
mock_os.path.isdir = mock.Mock(return_value=True)
|
|
|
|
mock_open = mock.mock_open()
|
2020-04-30 20:29:50 +00:00
|
|
|
with patch("homeassistant.config.open", mock_open, create=True):
|
2019-06-03 19:37:27 +00:00
|
|
|
opened_file = mock_open.return_value
|
|
|
|
opened_file.readline.return_value = ha_version
|
|
|
|
hass.config.path = mock.Mock()
|
|
|
|
config_util.process_ha_config_upgrade(hass)
|
2019-04-30 16:20:38 +00:00
|
|
|
hass_path = hass.config.path.return_value
|
|
|
|
|
|
|
|
assert mock_os.path.isdir.call_count == 1
|
|
|
|
assert mock_os.path.isdir.call_args == mock.call(hass_path)
|
|
|
|
assert mock_shutil.rmtree.call_count == 1
|
|
|
|
assert mock_shutil.rmtree.call_args == mock.call(hass_path)
|
|
|
|
|
|
|
|
|
2023-02-07 14:01:16 +00:00
|
|
|
def test_process_config_upgrade(hass: HomeAssistant) -> None:
|
2019-04-30 16:20:38 +00:00
|
|
|
"""Test update of version on upgrade."""
|
2019-07-31 19:25:30 +00:00
|
|
|
ha_version = "0.92.0"
|
2019-04-30 16:20:38 +00:00
|
|
|
|
|
|
|
mock_open = mock.mock_open()
|
2020-04-30 20:29:50 +00:00
|
|
|
with patch("homeassistant.config.open", mock_open, create=True), patch.object(
|
|
|
|
config_util, "__version__", "0.91.0"
|
|
|
|
):
|
2019-04-30 16:20:38 +00:00
|
|
|
opened_file = mock_open.return_value
|
|
|
|
opened_file.readline.return_value = ha_version
|
|
|
|
|
|
|
|
config_util.process_ha_config_upgrade(hass)
|
|
|
|
|
|
|
|
assert opened_file.write.call_count == 1
|
2019-07-31 19:25:30 +00:00
|
|
|
assert opened_file.write.call_args == mock.call("0.91.0")
|
2019-04-30 16:20:38 +00:00
|
|
|
|
|
|
|
|
2023-02-07 14:01:16 +00:00
|
|
|
def test_config_upgrade_same_version(hass: HomeAssistant) -> None:
|
2019-04-30 16:20:38 +00:00
|
|
|
"""Test no update of version on no upgrade."""
|
|
|
|
ha_version = __version__
|
|
|
|
|
|
|
|
mock_open = mock.mock_open()
|
2020-04-30 20:29:50 +00:00
|
|
|
with patch("homeassistant.config.open", mock_open, create=True):
|
2019-04-30 16:20:38 +00:00
|
|
|
opened_file = mock_open.return_value
|
|
|
|
opened_file.readline.return_value = ha_version
|
|
|
|
|
|
|
|
config_util.process_ha_config_upgrade(hass)
|
|
|
|
|
|
|
|
assert opened_file.write.call_count == 0
|
|
|
|
|
|
|
|
|
2023-02-07 14:01:16 +00:00
|
|
|
def test_config_upgrade_no_file(hass: HomeAssistant) -> None:
|
2019-04-30 16:20:38 +00:00
|
|
|
"""Test update of version on upgrade, with no version file."""
|
|
|
|
mock_open = mock.mock_open()
|
2019-07-31 19:25:30 +00:00
|
|
|
mock_open.side_effect = [FileNotFoundError(), mock.DEFAULT, mock.DEFAULT]
|
2020-04-30 20:29:50 +00:00
|
|
|
with patch("homeassistant.config.open", mock_open, create=True):
|
2019-04-30 16:20:38 +00:00
|
|
|
opened_file = mock_open.return_value
|
|
|
|
config_util.process_ha_config_upgrade(hass)
|
|
|
|
assert opened_file.write.call_count == 1
|
|
|
|
assert opened_file.write.call_args == mock.call(__version__)
|
|
|
|
|
|
|
|
|
2023-02-21 08:27:13 +00:00
|
|
|
async def test_loading_configuration_from_storage(
|
|
|
|
hass: HomeAssistant, hass_storage: dict[str, Any]
|
|
|
|
) -> None:
|
2019-05-16 14:27:53 +00:00
|
|
|
"""Test loading core config onto hass object."""
|
2019-05-23 00:24:46 +00:00
|
|
|
hass_storage["core.config"] = {
|
2019-07-31 19:25:30 +00:00
|
|
|
"data": {
|
|
|
|
"elevation": 10,
|
|
|
|
"latitude": 55,
|
|
|
|
"location_name": "Home",
|
|
|
|
"longitude": 13,
|
|
|
|
"time_zone": "Europe/Copenhagen",
|
|
|
|
"unit_system": "metric",
|
2020-05-08 00:29:47 +00:00
|
|
|
"external_url": "https://www.example.com",
|
|
|
|
"internal_url": "http://example.local",
|
2021-07-28 06:55:58 +00:00
|
|
|
"currency": "EUR",
|
2022-11-24 22:25:50 +00:00
|
|
|
"country": "SE",
|
|
|
|
"language": "sv",
|
2019-07-31 19:25:30 +00:00
|
|
|
},
|
|
|
|
"key": "core.config",
|
|
|
|
"version": 1,
|
2022-11-24 22:25:50 +00:00
|
|
|
"minor_version": 3,
|
2019-05-16 14:27:53 +00:00
|
|
|
}
|
|
|
|
await config_util.async_process_ha_core_config(
|
2020-07-13 15:43:11 +00:00
|
|
|
hass, {"allowlist_external_dirs": "/etc"}
|
2019-07-31 19:25:30 +00:00
|
|
|
)
|
2019-05-16 14:27:53 +00:00
|
|
|
|
|
|
|
assert hass.config.latitude == 55
|
|
|
|
assert hass.config.longitude == 13
|
|
|
|
assert hass.config.elevation == 10
|
2019-07-31 19:25:30 +00:00
|
|
|
assert hass.config.location_name == "Home"
|
2023-01-27 11:09:26 +00:00
|
|
|
assert hass.config.units is METRIC_SYSTEM
|
2021-05-08 05:46:26 +00:00
|
|
|
assert hass.config.time_zone == "Europe/Copenhagen"
|
2020-05-08 00:29:47 +00:00
|
|
|
assert hass.config.external_url == "https://www.example.com"
|
|
|
|
assert hass.config.internal_url == "http://example.local"
|
2021-07-28 06:55:58 +00:00
|
|
|
assert hass.config.currency == "EUR"
|
2022-11-24 22:25:50 +00:00
|
|
|
assert hass.config.country == "SE"
|
|
|
|
assert hass.config.language == "sv"
|
2020-09-04 15:16:29 +00:00
|
|
|
assert len(hass.config.allowlist_external_dirs) == 3
|
2020-07-13 15:43:11 +00:00
|
|
|
assert "/etc" in hass.config.allowlist_external_dirs
|
2021-12-19 17:02:52 +00:00
|
|
|
assert hass.config.config_source is ConfigSource.STORAGE
|
2019-05-20 18:02:36 +00:00
|
|
|
|
|
|
|
|
2023-02-21 08:27:13 +00:00
|
|
|
async def test_loading_configuration_from_storage_with_yaml_only(
|
|
|
|
hass: HomeAssistant, hass_storage: dict[str, Any]
|
|
|
|
) -> None:
|
2020-10-05 20:09:47 +00:00
|
|
|
"""Test loading core and YAML config onto hass object."""
|
|
|
|
hass_storage["core.config"] = {
|
|
|
|
"data": {
|
|
|
|
"elevation": 10,
|
|
|
|
"latitude": 55,
|
|
|
|
"location_name": "Home",
|
|
|
|
"longitude": 13,
|
|
|
|
"time_zone": "Europe/Copenhagen",
|
|
|
|
"unit_system": "metric",
|
|
|
|
},
|
|
|
|
"key": "core.config",
|
|
|
|
"version": 1,
|
|
|
|
}
|
|
|
|
await config_util.async_process_ha_core_config(
|
|
|
|
hass, {"media_dirs": {"mymedia": "/usr"}, "allowlist_external_dirs": "/etc"}
|
|
|
|
)
|
|
|
|
|
|
|
|
assert hass.config.latitude == 55
|
|
|
|
assert hass.config.longitude == 13
|
|
|
|
assert hass.config.elevation == 10
|
|
|
|
assert hass.config.location_name == "Home"
|
2023-01-27 11:09:26 +00:00
|
|
|
assert hass.config.units is METRIC_SYSTEM
|
2021-05-08 05:46:26 +00:00
|
|
|
assert hass.config.time_zone == "Europe/Copenhagen"
|
2020-10-05 20:09:47 +00:00
|
|
|
assert len(hass.config.allowlist_external_dirs) == 3
|
|
|
|
assert "/etc" in hass.config.allowlist_external_dirs
|
|
|
|
assert hass.config.media_dirs == {"mymedia": "/usr"}
|
2021-12-19 17:02:52 +00:00
|
|
|
assert hass.config.config_source is ConfigSource.STORAGE
|
2020-10-05 20:09:47 +00:00
|
|
|
|
|
|
|
|
2023-02-21 08:27:13 +00:00
|
|
|
async def test_migration_and_updating_configuration(
|
|
|
|
hass: HomeAssistant, hass_storage: dict[str, Any]
|
|
|
|
) -> None:
|
2019-05-20 18:02:36 +00:00
|
|
|
"""Test updating configuration stores the new configuration."""
|
|
|
|
core_data = {
|
2019-07-31 19:25:30 +00:00
|
|
|
"data": {
|
|
|
|
"elevation": 10,
|
|
|
|
"latitude": 55,
|
|
|
|
"location_name": "Home",
|
|
|
|
"longitude": 13,
|
|
|
|
"time_zone": "Europe/Copenhagen",
|
2022-10-19 11:31:08 +00:00
|
|
|
"unit_system": "imperial",
|
2020-05-08 00:29:47 +00:00
|
|
|
"external_url": "https://www.example.com",
|
|
|
|
"internal_url": "http://example.local",
|
2021-07-28 06:55:58 +00:00
|
|
|
"currency": "BTC",
|
2019-07-31 19:25:30 +00:00
|
|
|
},
|
|
|
|
"key": "core.config",
|
|
|
|
"version": 1,
|
2021-11-18 16:15:40 +00:00
|
|
|
"minor_version": 1,
|
2019-05-20 18:02:36 +00:00
|
|
|
}
|
2019-05-23 00:24:46 +00:00
|
|
|
hass_storage["core.config"] = dict(core_data)
|
2019-05-20 18:02:36 +00:00
|
|
|
await config_util.async_process_ha_core_config(
|
2020-07-13 15:43:11 +00:00
|
|
|
hass, {"allowlist_external_dirs": "/etc"}
|
2019-07-31 19:25:30 +00:00
|
|
|
)
|
2021-07-28 06:55:58 +00:00
|
|
|
await hass.config.async_update(latitude=50, currency="USD")
|
2019-05-20 18:02:36 +00:00
|
|
|
|
2022-10-19 11:31:08 +00:00
|
|
|
expected_new_core_data = copy.deepcopy(core_data)
|
|
|
|
# From async_update above
|
|
|
|
expected_new_core_data["data"]["latitude"] = 50
|
|
|
|
expected_new_core_data["data"]["currency"] = "USD"
|
|
|
|
# 1.1 -> 1.2 store migration with migrated unit system
|
|
|
|
expected_new_core_data["data"]["unit_system_v2"] = "us_customary"
|
2022-11-24 22:25:50 +00:00
|
|
|
expected_new_core_data["minor_version"] = 3
|
|
|
|
# defaults for country and language
|
|
|
|
expected_new_core_data["data"]["country"] = None
|
|
|
|
expected_new_core_data["data"]["language"] = "en"
|
2022-10-19 11:31:08 +00:00
|
|
|
assert hass_storage["core.config"] == expected_new_core_data
|
2019-05-20 18:02:36 +00:00
|
|
|
assert hass.config.latitude == 50
|
2021-07-28 06:55:58 +00:00
|
|
|
assert hass.config.currency == "USD"
|
2022-11-24 22:25:50 +00:00
|
|
|
assert hass.config.country is None
|
|
|
|
assert hass.config.language == "en"
|
2019-05-16 14:27:53 +00:00
|
|
|
|
|
|
|
|
2023-02-21 08:27:13 +00:00
|
|
|
async def test_override_stored_configuration(
|
|
|
|
hass: HomeAssistant, hass_storage: dict[str, Any]
|
|
|
|
) -> None:
|
2019-05-16 14:27:53 +00:00
|
|
|
"""Test loading core and YAML config onto hass object."""
|
2019-05-23 00:24:46 +00:00
|
|
|
hass_storage["core.config"] = {
|
2019-07-31 19:25:30 +00:00
|
|
|
"data": {
|
|
|
|
"elevation": 10,
|
|
|
|
"latitude": 55,
|
|
|
|
"location_name": "Home",
|
|
|
|
"longitude": 13,
|
|
|
|
"time_zone": "Europe/Copenhagen",
|
|
|
|
"unit_system": "metric",
|
|
|
|
},
|
|
|
|
"key": "core.config",
|
|
|
|
"version": 1,
|
2019-05-16 14:27:53 +00:00
|
|
|
}
|
2019-07-31 19:25:30 +00:00
|
|
|
await config_util.async_process_ha_core_config(
|
2020-07-13 15:43:11 +00:00
|
|
|
hass, {"latitude": 60, "allowlist_external_dirs": "/etc"}
|
2019-07-31 19:25:30 +00:00
|
|
|
)
|
2019-05-16 14:27:53 +00:00
|
|
|
|
|
|
|
assert hass.config.latitude == 60
|
|
|
|
assert hass.config.longitude == 13
|
|
|
|
assert hass.config.elevation == 10
|
2019-07-31 19:25:30 +00:00
|
|
|
assert hass.config.location_name == "Home"
|
2023-01-27 11:09:26 +00:00
|
|
|
assert hass.config.units is METRIC_SYSTEM
|
2021-05-08 05:46:26 +00:00
|
|
|
assert hass.config.time_zone == "Europe/Copenhagen"
|
2020-09-04 15:16:29 +00:00
|
|
|
assert len(hass.config.allowlist_external_dirs) == 3
|
2020-07-13 15:43:11 +00:00
|
|
|
assert "/etc" in hass.config.allowlist_external_dirs
|
2021-12-19 17:02:52 +00:00
|
|
|
assert hass.config.config_source is ConfigSource.YAML
|
2019-05-16 14:27:53 +00:00
|
|
|
|
|
|
|
|
2023-02-08 07:51:43 +00:00
|
|
|
async def test_loading_configuration(hass: HomeAssistant) -> None:
|
2019-04-30 16:20:38 +00:00
|
|
|
"""Test loading core config onto hass object."""
|
2019-07-31 19:25:30 +00:00
|
|
|
await config_util.async_process_ha_core_config(
|
|
|
|
hass,
|
|
|
|
{
|
|
|
|
"latitude": 60,
|
|
|
|
"longitude": 50,
|
|
|
|
"elevation": 25,
|
|
|
|
"name": "Huis",
|
|
|
|
CONF_UNIT_SYSTEM: CONF_UNIT_SYSTEM_IMPERIAL,
|
|
|
|
"time_zone": "America/New_York",
|
2020-07-13 15:43:11 +00:00
|
|
|
"allowlist_external_dirs": "/etc",
|
2020-05-08 00:29:47 +00:00
|
|
|
"external_url": "https://www.example.com",
|
|
|
|
"internal_url": "http://example.local",
|
2020-09-16 13:28:25 +00:00
|
|
|
"media_dirs": {"mymedia": "/usr"},
|
2020-10-06 22:05:52 +00:00
|
|
|
"legacy_templates": True,
|
2021-07-28 06:55:58 +00:00
|
|
|
"currency": "EUR",
|
2022-11-24 22:25:50 +00:00
|
|
|
"country": "SE",
|
|
|
|
"language": "sv",
|
2019-07-31 19:25:30 +00:00
|
|
|
},
|
|
|
|
)
|
2019-04-30 16:20:38 +00:00
|
|
|
|
|
|
|
assert hass.config.latitude == 60
|
|
|
|
assert hass.config.longitude == 50
|
|
|
|
assert hass.config.elevation == 25
|
2019-07-31 19:25:30 +00:00
|
|
|
assert hass.config.location_name == "Huis"
|
2023-01-27 11:09:26 +00:00
|
|
|
assert hass.config.units is US_CUSTOMARY_SYSTEM
|
2021-05-08 05:46:26 +00:00
|
|
|
assert hass.config.time_zone == "America/New_York"
|
2020-05-08 00:29:47 +00:00
|
|
|
assert hass.config.external_url == "https://www.example.com"
|
|
|
|
assert hass.config.internal_url == "http://example.local"
|
2020-09-04 15:16:29 +00:00
|
|
|
assert len(hass.config.allowlist_external_dirs) == 3
|
2020-07-13 15:43:11 +00:00
|
|
|
assert "/etc" in hass.config.allowlist_external_dirs
|
2020-09-16 13:28:25 +00:00
|
|
|
assert "/usr" in hass.config.allowlist_external_dirs
|
|
|
|
assert hass.config.media_dirs == {"mymedia": "/usr"}
|
2021-12-19 17:02:52 +00:00
|
|
|
assert hass.config.config_source is ConfigSource.YAML
|
2020-10-06 22:05:52 +00:00
|
|
|
assert hass.config.legacy_templates is True
|
2021-07-28 06:55:58 +00:00
|
|
|
assert hass.config.currency == "EUR"
|
2022-11-24 22:25:50 +00:00
|
|
|
assert hass.config.country == "SE"
|
|
|
|
assert hass.config.language == "sv"
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize(
|
2023-02-15 13:09:50 +00:00
|
|
|
("minor_version", "users", "user_data", "default_language"),
|
2022-11-24 22:25:50 +00:00
|
|
|
(
|
|
|
|
(2, (), {}, "en"),
|
|
|
|
(2, ({"is_owner": True},), {}, "en"),
|
|
|
|
(
|
|
|
|
2,
|
|
|
|
({"id": "user1", "is_owner": True},),
|
|
|
|
{"user1": {"language": {"language": "sv"}}},
|
|
|
|
"sv",
|
|
|
|
),
|
|
|
|
(
|
|
|
|
2,
|
|
|
|
({"id": "user1", "is_owner": False},),
|
|
|
|
{"user1": {"language": {"language": "sv"}}},
|
|
|
|
"en",
|
|
|
|
),
|
|
|
|
(3, (), {}, "en"),
|
|
|
|
(3, ({"is_owner": True},), {}, "en"),
|
|
|
|
(
|
|
|
|
3,
|
|
|
|
({"id": "user1", "is_owner": True},),
|
|
|
|
{"user1": {"language": {"language": "sv"}}},
|
|
|
|
"en",
|
|
|
|
),
|
|
|
|
(
|
|
|
|
3,
|
|
|
|
({"id": "user1", "is_owner": False},),
|
|
|
|
{"user1": {"language": {"language": "sv"}}},
|
|
|
|
"en",
|
|
|
|
),
|
|
|
|
),
|
|
|
|
)
|
|
|
|
async def test_language_default(
|
2023-02-21 08:27:13 +00:00
|
|
|
hass: HomeAssistant,
|
|
|
|
hass_storage: dict[str, Any],
|
|
|
|
minor_version,
|
|
|
|
users,
|
|
|
|
user_data,
|
|
|
|
default_language,
|
|
|
|
) -> None:
|
2022-11-24 22:25:50 +00:00
|
|
|
"""Test language config default to owner user's language during migration.
|
|
|
|
|
|
|
|
This should only happen if the core store version < 1.3
|
|
|
|
"""
|
|
|
|
core_data = {
|
|
|
|
"data": {},
|
|
|
|
"key": "core.config",
|
|
|
|
"version": 1,
|
|
|
|
"minor_version": minor_version,
|
|
|
|
}
|
|
|
|
hass_storage["core.config"] = dict(core_data)
|
|
|
|
|
|
|
|
for user_config in users:
|
|
|
|
user = MockUser(**user_config).add_to_hass(hass)
|
|
|
|
if user.id not in user_data:
|
|
|
|
continue
|
|
|
|
storage_key = f"frontend.user_data_{user.id}"
|
|
|
|
hass_storage[storage_key] = {
|
|
|
|
"key": storage_key,
|
|
|
|
"version": 1,
|
|
|
|
"data": user_data[user.id],
|
|
|
|
}
|
|
|
|
|
|
|
|
await config_util.async_process_ha_core_config(
|
|
|
|
hass,
|
|
|
|
{},
|
|
|
|
)
|
|
|
|
assert hass.config.language == default_language
|
2019-04-30 16:20:38 +00:00
|
|
|
|
|
|
|
|
2023-02-08 07:51:43 +00:00
|
|
|
async def test_loading_configuration_default_media_dirs_docker(
|
|
|
|
hass: HomeAssistant,
|
|
|
|
) -> None:
|
2020-09-16 13:28:25 +00:00
|
|
|
"""Test loading core config onto hass object."""
|
|
|
|
with patch("homeassistant.config.is_docker_env", return_value=True):
|
|
|
|
await config_util.async_process_ha_core_config(
|
|
|
|
hass,
|
|
|
|
{
|
|
|
|
"name": "Huis",
|
|
|
|
},
|
|
|
|
)
|
|
|
|
|
|
|
|
assert hass.config.location_name == "Huis"
|
|
|
|
assert len(hass.config.allowlist_external_dirs) == 2
|
|
|
|
assert "/media" in hass.config.allowlist_external_dirs
|
2020-09-16 19:38:40 +00:00
|
|
|
assert hass.config.media_dirs == {"local": "/media"}
|
2020-09-16 13:28:25 +00:00
|
|
|
|
|
|
|
|
2023-02-08 07:51:43 +00:00
|
|
|
async def test_loading_configuration_from_packages(hass: HomeAssistant) -> None:
|
2019-04-30 16:20:38 +00:00
|
|
|
"""Test loading packages config onto hass object config."""
|
2019-07-31 19:25:30 +00:00
|
|
|
await config_util.async_process_ha_core_config(
|
|
|
|
hass,
|
|
|
|
{
|
|
|
|
"latitude": 39,
|
|
|
|
"longitude": -1,
|
|
|
|
"elevation": 500,
|
|
|
|
"name": "Huis",
|
2022-10-14 11:03:17 +00:00
|
|
|
CONF_UNIT_SYSTEM: CONF_UNIT_SYSTEM_METRIC,
|
2019-07-31 19:25:30 +00:00
|
|
|
"time_zone": "Europe/Madrid",
|
2020-05-08 00:29:47 +00:00
|
|
|
"external_url": "https://www.example.com",
|
|
|
|
"internal_url": "http://example.local",
|
2019-07-31 19:25:30 +00:00
|
|
|
"packages": {
|
|
|
|
"package_1": {"wake_on_lan": None},
|
|
|
|
"package_2": {
|
|
|
|
"light": {"platform": "hue"},
|
|
|
|
"media_extractor": None,
|
|
|
|
"sun": None,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
)
|
2019-04-30 16:20:38 +00:00
|
|
|
|
|
|
|
# Empty packages not allowed
|
|
|
|
with pytest.raises(MultipleInvalid):
|
2019-07-31 19:25:30 +00:00
|
|
|
await config_util.async_process_ha_core_config(
|
|
|
|
hass,
|
|
|
|
{
|
|
|
|
"latitude": 39,
|
|
|
|
"longitude": -1,
|
|
|
|
"elevation": 500,
|
|
|
|
"name": "Huis",
|
2022-10-14 11:03:17 +00:00
|
|
|
CONF_UNIT_SYSTEM: CONF_UNIT_SYSTEM_METRIC,
|
2019-07-31 19:25:30 +00:00
|
|
|
"time_zone": "Europe/Madrid",
|
|
|
|
"packages": {"empty_package": None},
|
|
|
|
},
|
|
|
|
)
|
2019-04-30 16:20:38 +00:00
|
|
|
|
|
|
|
|
2022-10-19 11:31:08 +00:00
|
|
|
@pytest.mark.parametrize(
|
2023-02-15 13:09:50 +00:00
|
|
|
("unit_system_name", "expected_unit_system"),
|
2022-10-19 11:31:08 +00:00
|
|
|
[
|
|
|
|
(CONF_UNIT_SYSTEM_METRIC, METRIC_SYSTEM),
|
|
|
|
(CONF_UNIT_SYSTEM_IMPERIAL, US_CUSTOMARY_SYSTEM),
|
|
|
|
(_CONF_UNIT_SYSTEM_US_CUSTOMARY, US_CUSTOMARY_SYSTEM),
|
|
|
|
],
|
|
|
|
)
|
|
|
|
async def test_loading_configuration_unit_system(
|
|
|
|
hass: HomeAssistant, unit_system_name: str, expected_unit_system: UnitSystem
|
|
|
|
) -> None:
|
|
|
|
"""Test backward compatibility when loading core config."""
|
|
|
|
await config_util.async_process_ha_core_config(
|
|
|
|
hass,
|
|
|
|
{
|
|
|
|
"latitude": 60,
|
|
|
|
"longitude": 50,
|
|
|
|
"elevation": 25,
|
|
|
|
"name": "Huis",
|
|
|
|
"unit_system": unit_system_name,
|
|
|
|
"time_zone": "America/New_York",
|
|
|
|
"external_url": "https://www.example.com",
|
|
|
|
"internal_url": "http://example.local",
|
|
|
|
},
|
|
|
|
)
|
|
|
|
|
|
|
|
assert hass.config.units is expected_unit_system
|
|
|
|
|
|
|
|
|
2020-04-30 20:29:50 +00:00
|
|
|
@patch("homeassistant.helpers.check_config.async_check_ha_config_file")
|
2023-02-21 08:27:13 +00:00
|
|
|
async def test_check_ha_config_file_correct(mock_check, hass: HomeAssistant) -> None:
|
2019-04-30 16:20:38 +00:00
|
|
|
"""Check that restart propagates to stop."""
|
|
|
|
mock_check.return_value = check_config.HomeAssistantConfig()
|
|
|
|
assert await config_util.async_check_ha_config_file(hass) is None
|
|
|
|
|
|
|
|
|
2020-04-30 20:29:50 +00:00
|
|
|
@patch("homeassistant.helpers.check_config.async_check_ha_config_file")
|
2023-02-21 08:27:13 +00:00
|
|
|
async def test_check_ha_config_file_wrong(mock_check, hass: HomeAssistant) -> None:
|
2019-04-30 16:20:38 +00:00
|
|
|
"""Check that restart with a bad config doesn't propagate to stop."""
|
|
|
|
mock_check.return_value = check_config.HomeAssistantConfig()
|
|
|
|
mock_check.return_value.add_error("bad")
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
assert await config_util.async_check_ha_config_file(hass) == "bad"
|
2017-02-08 17:17:52 +00:00
|
|
|
|
2017-01-14 06:01:47 +00:00
|
|
|
|
2023-02-20 15:57:12 +00:00
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"hass_config",
|
|
|
|
[
|
|
|
|
{
|
|
|
|
config_util.CONF_CORE: {
|
|
|
|
config_util.CONF_PACKAGES: {
|
|
|
|
"pack_dict": {"input_boolean": {"ib1": None}}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
"input_boolean": {"ib2": None},
|
|
|
|
"light": {"platform": "test"},
|
|
|
|
}
|
|
|
|
],
|
|
|
|
)
|
|
|
|
async def test_async_hass_config_yaml_merge(
|
|
|
|
merge_log_err, hass: HomeAssistant, mock_hass_config: None
|
|
|
|
) -> None:
|
2018-12-03 09:56:26 +00:00
|
|
|
"""Test merge during async config reload."""
|
2023-02-20 15:57:12 +00:00
|
|
|
conf = await config_util.async_hass_config_yaml(hass)
|
2018-12-03 09:56:26 +00:00
|
|
|
|
|
|
|
assert merge_log_err.call_count == 0
|
2019-07-31 19:25:30 +00:00
|
|
|
assert conf[config_util.CONF_CORE].get(config_util.CONF_PACKAGES) is not None
|
2018-12-03 09:56:26 +00:00
|
|
|
assert len(conf) == 3
|
2019-07-31 19:25:30 +00:00
|
|
|
assert len(conf["input_boolean"]) == 2
|
|
|
|
assert len(conf["light"]) == 1
|
2018-12-03 09:56:26 +00:00
|
|
|
|
|
|
|
|
2017-01-14 06:01:47 +00:00
|
|
|
@pytest.fixture
|
|
|
|
def merge_log_err(hass):
|
|
|
|
"""Patch _merge_log_error from packages."""
|
2020-04-30 20:29:50 +00:00
|
|
|
with patch("homeassistant.config._LOGGER.error") as logerr:
|
2017-01-14 06:01:47 +00:00
|
|
|
yield logerr
|
|
|
|
|
|
|
|
|
2023-02-21 08:27:13 +00:00
|
|
|
async def test_merge(merge_log_err, hass: HomeAssistant) -> None:
|
2017-01-14 06:01:47 +00:00
|
|
|
"""Test if we can merge packages."""
|
|
|
|
packages = {
|
2019-07-31 19:25:30 +00:00
|
|
|
"pack_dict": {"input_boolean": {"ib1": None}},
|
|
|
|
"pack_11": {"input_select": {"is1": None}},
|
|
|
|
"pack_list": {"light": {"platform": "test"}},
|
|
|
|
"pack_list2": {"light": [{"platform": "test"}]},
|
|
|
|
"pack_none": {"wake_on_lan": None},
|
2021-08-05 20:11:01 +00:00
|
|
|
"pack_special": {
|
|
|
|
"automation": [{"some": "yay"}],
|
|
|
|
"script": {"a_script": "yay"},
|
|
|
|
"template": [{"some": "yay"}],
|
|
|
|
},
|
2017-01-14 06:01:47 +00:00
|
|
|
}
|
|
|
|
config = {
|
|
|
|
config_util.CONF_CORE: {config_util.CONF_PACKAGES: packages},
|
2019-07-31 19:25:30 +00:00
|
|
|
"input_boolean": {"ib2": None},
|
|
|
|
"light": {"platform": "test"},
|
2021-08-05 20:11:01 +00:00
|
|
|
"automation": [],
|
|
|
|
"script": {},
|
|
|
|
"template": [],
|
2017-01-14 06:01:47 +00:00
|
|
|
}
|
2019-04-14 14:23:01 +00:00
|
|
|
await config_util.merge_packages_config(hass, config, packages)
|
2017-01-14 06:01:47 +00:00
|
|
|
|
|
|
|
assert merge_log_err.call_count == 0
|
2021-08-05 20:11:01 +00:00
|
|
|
assert len(config) == 8
|
2019-07-31 19:25:30 +00:00
|
|
|
assert len(config["input_boolean"]) == 2
|
|
|
|
assert len(config["input_select"]) == 1
|
|
|
|
assert len(config["light"]) == 3
|
2021-08-05 20:11:01 +00:00
|
|
|
assert len(config["automation"]) == 1
|
|
|
|
assert len(config["script"]) == 1
|
|
|
|
assert len(config["template"]) == 1
|
2019-07-31 19:25:30 +00:00
|
|
|
assert isinstance(config["wake_on_lan"], OrderedDict)
|
2017-01-14 06:01:47 +00:00
|
|
|
|
|
|
|
|
2023-02-21 08:27:13 +00:00
|
|
|
async def test_merge_try_falsy(merge_log_err, hass: HomeAssistant) -> None:
|
2020-01-31 16:33:00 +00:00
|
|
|
"""Ensure we don't add falsy items like empty OrderedDict() to list."""
|
2018-03-30 00:13:08 +00:00
|
|
|
packages = {
|
2019-07-31 19:25:30 +00:00
|
|
|
"pack_falsy_to_lst": {"automation": OrderedDict()},
|
|
|
|
"pack_list2": {"light": OrderedDict()},
|
2018-03-30 00:13:08 +00:00
|
|
|
}
|
|
|
|
config = {
|
|
|
|
config_util.CONF_CORE: {config_util.CONF_PACKAGES: packages},
|
2019-07-31 19:25:30 +00:00
|
|
|
"automation": {"do": "something"},
|
|
|
|
"light": {"some": "light"},
|
2018-03-30 00:13:08 +00:00
|
|
|
}
|
2019-04-14 14:23:01 +00:00
|
|
|
await config_util.merge_packages_config(hass, config, packages)
|
2018-03-30 00:13:08 +00:00
|
|
|
|
|
|
|
assert merge_log_err.call_count == 0
|
|
|
|
assert len(config) == 3
|
2019-07-31 19:25:30 +00:00
|
|
|
assert len(config["automation"]) == 1
|
|
|
|
assert len(config["light"]) == 1
|
2018-03-30 00:13:08 +00:00
|
|
|
|
|
|
|
|
2023-02-21 08:27:13 +00:00
|
|
|
async def test_merge_new(merge_log_err, hass: HomeAssistant) -> None:
|
2017-01-14 06:01:47 +00:00
|
|
|
"""Test adding new components to outer scope."""
|
|
|
|
packages = {
|
2019-07-31 19:25:30 +00:00
|
|
|
"pack_1": {"light": [{"platform": "one"}]},
|
|
|
|
"pack_11": {"input_select": {"ib1": None}},
|
|
|
|
"pack_2": {
|
|
|
|
"light": {"platform": "one"},
|
|
|
|
"panel_custom": {"pan1": None},
|
|
|
|
"api": {},
|
|
|
|
},
|
2017-01-14 06:01:47 +00:00
|
|
|
}
|
2019-07-31 19:25:30 +00:00
|
|
|
config = {config_util.CONF_CORE: {config_util.CONF_PACKAGES: packages}}
|
2019-04-14 14:23:01 +00:00
|
|
|
await config_util.merge_packages_config(hass, config, packages)
|
2017-01-14 06:01:47 +00:00
|
|
|
|
|
|
|
assert merge_log_err.call_count == 0
|
2019-07-31 19:25:30 +00:00
|
|
|
assert "api" in config
|
2017-01-14 06:01:47 +00:00
|
|
|
assert len(config) == 5
|
2019-07-31 19:25:30 +00:00
|
|
|
assert len(config["light"]) == 2
|
|
|
|
assert len(config["panel_custom"]) == 1
|
2017-01-14 06:01:47 +00:00
|
|
|
|
|
|
|
|
2023-02-21 08:27:13 +00:00
|
|
|
async def test_merge_type_mismatch(merge_log_err, hass: HomeAssistant) -> None:
|
2017-01-14 06:01:47 +00:00
|
|
|
"""Test if we have a type mismatch for packages."""
|
|
|
|
packages = {
|
2019-07-31 19:25:30 +00:00
|
|
|
"pack_1": {"input_boolean": [{"ib1": None}]},
|
|
|
|
"pack_11": {"input_select": {"ib1": None}},
|
|
|
|
"pack_2": {"light": {"ib1": None}}, # light gets merged - ensure_list
|
2017-01-14 06:01:47 +00:00
|
|
|
}
|
|
|
|
config = {
|
|
|
|
config_util.CONF_CORE: {config_util.CONF_PACKAGES: packages},
|
2019-07-31 19:25:30 +00:00
|
|
|
"input_boolean": {"ib2": None},
|
|
|
|
"input_select": [{"ib2": None}],
|
|
|
|
"light": [{"platform": "two"}],
|
2017-01-14 06:01:47 +00:00
|
|
|
}
|
2019-04-14 14:23:01 +00:00
|
|
|
await config_util.merge_packages_config(hass, config, packages)
|
2017-01-14 06:01:47 +00:00
|
|
|
|
|
|
|
assert merge_log_err.call_count == 2
|
|
|
|
assert len(config) == 4
|
2019-07-31 19:25:30 +00:00
|
|
|
assert len(config["input_boolean"]) == 1
|
|
|
|
assert len(config["light"]) == 2
|
2017-01-14 06:01:47 +00:00
|
|
|
|
|
|
|
|
2023-02-21 08:27:13 +00:00
|
|
|
async def test_merge_once_only_keys(merge_log_err, hass: HomeAssistant) -> None:
|
2018-05-25 20:41:50 +00:00
|
|
|
"""Test if we have a merge for a comp that may occur only once. Keys."""
|
2019-07-31 19:25:30 +00:00
|
|
|
packages = {"pack_2": {"api": None}}
|
|
|
|
config = {config_util.CONF_CORE: {config_util.CONF_PACKAGES: packages}, "api": None}
|
2019-04-14 14:23:01 +00:00
|
|
|
await config_util.merge_packages_config(hass, config, packages)
|
2019-07-31 19:25:30 +00:00
|
|
|
assert config["api"] == OrderedDict()
|
2018-06-16 10:55:32 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
packages = {"pack_2": {"api": {"key_3": 3}}}
|
2018-05-25 20:41:50 +00:00
|
|
|
config = {
|
|
|
|
config_util.CONF_CORE: {config_util.CONF_PACKAGES: packages},
|
2019-07-31 19:25:30 +00:00
|
|
|
"api": {"key_1": 1, "key_2": 2},
|
2017-01-14 06:01:47 +00:00
|
|
|
}
|
2019-04-14 14:23:01 +00:00
|
|
|
await config_util.merge_packages_config(hass, config, packages)
|
2019-07-31 19:25:30 +00:00
|
|
|
assert config["api"] == {"key_1": 1, "key_2": 2, "key_3": 3}
|
2018-05-25 20:41:50 +00:00
|
|
|
|
|
|
|
# Duplicate keys error
|
2019-07-31 19:25:30 +00:00
|
|
|
packages = {"pack_2": {"api": {"key": 2}}}
|
2017-01-14 06:01:47 +00:00
|
|
|
config = {
|
|
|
|
config_util.CONF_CORE: {config_util.CONF_PACKAGES: packages},
|
2019-07-31 19:25:30 +00:00
|
|
|
"api": {"key": 1},
|
2017-01-14 06:01:47 +00:00
|
|
|
}
|
2019-04-14 14:23:01 +00:00
|
|
|
await config_util.merge_packages_config(hass, config, packages)
|
2017-04-30 07:42:19 +00:00
|
|
|
assert merge_log_err.call_count == 1
|
2018-05-25 20:41:50 +00:00
|
|
|
|
|
|
|
|
2023-02-08 07:51:43 +00:00
|
|
|
async def test_merge_once_only_lists(hass: HomeAssistant) -> None:
|
2018-05-25 20:41:50 +00:00
|
|
|
"""Test if we have a merge for a comp that may occur only once. Lists."""
|
2019-07-31 19:25:30 +00:00
|
|
|
packages = {
|
|
|
|
"pack_2": {
|
2019-08-10 23:30:33 +00:00
|
|
|
"api": {"list_1": ["item_2", "item_3"], "list_2": ["item_4"], "list_3": []}
|
2019-07-31 19:25:30 +00:00
|
|
|
}
|
|
|
|
}
|
2018-05-25 20:41:50 +00:00
|
|
|
config = {
|
|
|
|
config_util.CONF_CORE: {config_util.CONF_PACKAGES: packages},
|
2019-07-31 19:25:30 +00:00
|
|
|
"api": {"list_1": ["item_1"]},
|
2018-05-25 20:41:50 +00:00
|
|
|
}
|
2019-04-14 14:23:01 +00:00
|
|
|
await config_util.merge_packages_config(hass, config, packages)
|
2019-07-31 19:25:30 +00:00
|
|
|
assert config["api"] == {
|
|
|
|
"list_1": ["item_1", "item_2", "item_3"],
|
2019-08-10 23:30:33 +00:00
|
|
|
"list_2": ["item_4"],
|
|
|
|
"list_3": [],
|
2018-05-25 20:41:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2023-02-08 07:51:43 +00:00
|
|
|
async def test_merge_once_only_dictionaries(hass: HomeAssistant) -> None:
|
2018-05-25 20:41:50 +00:00
|
|
|
"""Test if we have a merge for a comp that may occur only once. Dicts."""
|
2019-07-31 19:25:30 +00:00
|
|
|
packages = {
|
|
|
|
"pack_2": {
|
|
|
|
"api": {
|
|
|
|
"dict_1": {"key_2": 2, "dict_1.1": {"key_1.2": 1.2}},
|
|
|
|
"dict_2": {"key_1": 1},
|
|
|
|
"dict_3": {},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-05-25 20:41:50 +00:00
|
|
|
config = {
|
|
|
|
config_util.CONF_CORE: {config_util.CONF_PACKAGES: packages},
|
2019-07-31 19:25:30 +00:00
|
|
|
"api": {"dict_1": {"key_1": 1, "dict_1.1": {"key_1.1": 1.1}}},
|
2018-05-25 20:41:50 +00:00
|
|
|
}
|
2019-04-14 14:23:01 +00:00
|
|
|
await config_util.merge_packages_config(hass, config, packages)
|
2019-07-31 19:25:30 +00:00
|
|
|
assert config["api"] == {
|
|
|
|
"dict_1": {
|
|
|
|
"key_1": 1,
|
|
|
|
"key_2": 2,
|
|
|
|
"dict_1.1": {"key_1.1": 1.1, "key_1.2": 1.2},
|
2018-05-25 20:41:50 +00:00
|
|
|
},
|
2019-07-31 19:25:30 +00:00
|
|
|
"dict_2": {"key_1": 1},
|
2018-05-25 20:41:50 +00:00
|
|
|
}
|
2017-01-14 06:01:47 +00:00
|
|
|
|
|
|
|
|
2023-02-08 07:51:43 +00:00
|
|
|
async def test_merge_id_schema(hass: HomeAssistant) -> None:
|
2017-01-14 06:01:47 +00:00
|
|
|
"""Test if we identify the config schemas correctly."""
|
|
|
|
types = {
|
2019-07-31 19:25:30 +00:00
|
|
|
"panel_custom": "list",
|
|
|
|
"group": "dict",
|
|
|
|
"input_boolean": "dict",
|
|
|
|
"shell_command": "dict",
|
|
|
|
"qwikswitch": "dict",
|
2017-01-14 06:01:47 +00:00
|
|
|
}
|
2019-04-14 14:23:01 +00:00
|
|
|
for domain, expected_type in types.items():
|
|
|
|
integration = await async_get_integration(hass, domain)
|
|
|
|
module = integration.get_component()
|
2020-03-20 20:34:56 +00:00
|
|
|
typ = config_util._identify_config_schema(module)
|
2020-01-03 13:47:06 +00:00
|
|
|
assert typ == expected_type, f"{domain} expected {expected_type}, got {typ}"
|
2017-01-14 06:01:47 +00:00
|
|
|
|
|
|
|
|
2023-02-21 08:27:13 +00:00
|
|
|
async def test_merge_duplicate_keys(merge_log_err, hass: HomeAssistant) -> None:
|
2017-01-14 06:01:47 +00:00
|
|
|
"""Test if keys in dicts are duplicates."""
|
2019-07-31 19:25:30 +00:00
|
|
|
packages = {"pack_1": {"input_select": {"ib1": None}}}
|
2017-01-14 06:01:47 +00:00
|
|
|
config = {
|
|
|
|
config_util.CONF_CORE: {config_util.CONF_PACKAGES: packages},
|
2019-07-31 19:25:30 +00:00
|
|
|
"input_select": {"ib1": 1},
|
2017-01-14 06:01:47 +00:00
|
|
|
}
|
2019-04-14 14:23:01 +00:00
|
|
|
await config_util.merge_packages_config(hass, config, packages)
|
2017-01-14 06:01:47 +00:00
|
|
|
|
|
|
|
assert merge_log_err.call_count == 1
|
|
|
|
assert len(config) == 2
|
2019-07-31 19:25:30 +00:00
|
|
|
assert len(config["input_select"]) == 1
|
2017-01-27 06:26:49 +00:00
|
|
|
|
|
|
|
|
2023-02-08 07:51:43 +00:00
|
|
|
async def test_merge_customize(hass: HomeAssistant) -> None:
|
2017-01-27 06:26:49 +00:00
|
|
|
"""Test loading core config onto hass object."""
|
|
|
|
core_config = {
|
2019-07-31 19:25:30 +00:00
|
|
|
"latitude": 60,
|
|
|
|
"longitude": 50,
|
|
|
|
"elevation": 25,
|
|
|
|
"name": "Huis",
|
2017-01-27 06:26:49 +00:00
|
|
|
CONF_UNIT_SYSTEM: CONF_UNIT_SYSTEM_IMPERIAL,
|
2019-07-31 19:25:30 +00:00
|
|
|
"time_zone": "GMT",
|
|
|
|
"customize": {"a.a": {"friendly_name": "A"}},
|
|
|
|
"packages": {
|
|
|
|
"pkg1": {"homeassistant": {"customize": {"b.b": {"friendly_name": "BB"}}}}
|
|
|
|
},
|
2017-01-27 06:26:49 +00:00
|
|
|
}
|
2020-04-07 16:33:23 +00:00
|
|
|
await config_util.async_process_ha_core_config(hass, core_config)
|
2017-01-27 06:26:49 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
assert hass.data[config_util.DATA_CUSTOMIZE].get("b.b") == {"friendly_name": "BB"}
|
2018-07-17 17:36:33 +00:00
|
|
|
|
|
|
|
|
2023-02-08 07:51:43 +00:00
|
|
|
async def test_auth_provider_config(hass: HomeAssistant) -> None:
|
2018-07-17 17:36:33 +00:00
|
|
|
"""Test loading auth provider config onto hass object."""
|
|
|
|
core_config = {
|
2019-07-31 19:25:30 +00:00
|
|
|
"latitude": 60,
|
|
|
|
"longitude": 50,
|
|
|
|
"elevation": 25,
|
|
|
|
"name": "Huis",
|
2018-07-17 17:36:33 +00:00
|
|
|
CONF_UNIT_SYSTEM: CONF_UNIT_SYSTEM_IMPERIAL,
|
2019-07-31 19:25:30 +00:00
|
|
|
"time_zone": "GMT",
|
2018-07-17 17:36:33 +00:00
|
|
|
CONF_AUTH_PROVIDERS: [
|
2019-07-31 19:25:30 +00:00
|
|
|
{"type": "homeassistant"},
|
|
|
|
{"type": "legacy_api_password", "api_password": "some-pass"},
|
2018-08-26 20:38:52 +00:00
|
|
|
],
|
2019-07-31 19:25:30 +00:00
|
|
|
CONF_AUTH_MFA_MODULES: [{"type": "totp"}, {"type": "totp", "id": "second"}],
|
2018-07-17 17:36:33 +00:00
|
|
|
}
|
2019-07-31 19:25:30 +00:00
|
|
|
if hasattr(hass, "auth"):
|
2018-07-17 17:36:33 +00:00
|
|
|
del hass.auth
|
|
|
|
await config_util.async_process_ha_core_config(hass, core_config)
|
|
|
|
|
|
|
|
assert len(hass.auth.auth_providers) == 2
|
2019-07-31 19:25:30 +00:00
|
|
|
assert hass.auth.auth_providers[0].type == "homeassistant"
|
|
|
|
assert hass.auth.auth_providers[1].type == "legacy_api_password"
|
2018-08-26 20:38:52 +00:00
|
|
|
assert len(hass.auth.auth_mfa_modules) == 2
|
2019-07-31 19:25:30 +00:00
|
|
|
assert hass.auth.auth_mfa_modules[0].id == "totp"
|
|
|
|
assert hass.auth.auth_mfa_modules[1].id == "second"
|
2018-08-23 11:38:08 +00:00
|
|
|
|
|
|
|
|
2023-02-08 07:51:43 +00:00
|
|
|
async def test_auth_provider_config_default(hass: HomeAssistant) -> None:
|
2018-08-23 11:38:08 +00:00
|
|
|
"""Test loading default auth provider config."""
|
|
|
|
core_config = {
|
2019-07-31 19:25:30 +00:00
|
|
|
"latitude": 60,
|
|
|
|
"longitude": 50,
|
|
|
|
"elevation": 25,
|
|
|
|
"name": "Huis",
|
2018-08-23 11:38:08 +00:00
|
|
|
CONF_UNIT_SYSTEM: CONF_UNIT_SYSTEM_IMPERIAL,
|
2019-07-31 19:25:30 +00:00
|
|
|
"time_zone": "GMT",
|
2018-08-23 11:38:08 +00:00
|
|
|
}
|
2019-07-31 19:25:30 +00:00
|
|
|
if hasattr(hass, "auth"):
|
2018-08-23 11:38:08 +00:00
|
|
|
del hass.auth
|
|
|
|
await config_util.async_process_ha_core_config(hass, core_config)
|
|
|
|
|
|
|
|
assert len(hass.auth.auth_providers) == 1
|
2019-07-31 19:25:30 +00:00
|
|
|
assert hass.auth.auth_providers[0].type == "homeassistant"
|
2018-08-26 20:38:52 +00:00
|
|
|
assert len(hass.auth.auth_mfa_modules) == 1
|
2019-07-31 19:25:30 +00:00
|
|
|
assert hass.auth.auth_mfa_modules[0].id == "totp"
|
2018-08-23 11:38:08 +00:00
|
|
|
|
|
|
|
|
2023-02-08 07:51:43 +00:00
|
|
|
async def test_disallowed_auth_provider_config(hass: HomeAssistant) -> None:
|
2018-07-17 17:36:33 +00:00
|
|
|
"""Test loading insecure example auth provider is disallowed."""
|
|
|
|
core_config = {
|
2019-07-31 19:25:30 +00:00
|
|
|
"latitude": 60,
|
|
|
|
"longitude": 50,
|
|
|
|
"elevation": 25,
|
|
|
|
"name": "Huis",
|
2018-07-17 17:36:33 +00:00
|
|
|
CONF_UNIT_SYSTEM: CONF_UNIT_SYSTEM_IMPERIAL,
|
2019-07-31 19:25:30 +00:00
|
|
|
"time_zone": "GMT",
|
|
|
|
CONF_AUTH_PROVIDERS: [
|
|
|
|
{
|
|
|
|
"type": "insecure_example",
|
|
|
|
"users": [
|
|
|
|
{
|
|
|
|
"username": "test-user",
|
|
|
|
"password": "test-pass",
|
|
|
|
"name": "Test Name",
|
|
|
|
}
|
|
|
|
],
|
|
|
|
}
|
|
|
|
],
|
2018-08-28 18:54:01 +00:00
|
|
|
}
|
|
|
|
with pytest.raises(Invalid):
|
|
|
|
await config_util.async_process_ha_core_config(hass, core_config)
|
|
|
|
|
|
|
|
|
2023-02-08 07:51:43 +00:00
|
|
|
async def test_disallowed_duplicated_auth_provider_config(hass: HomeAssistant) -> None:
|
2018-08-28 18:54:01 +00:00
|
|
|
"""Test loading insecure example auth provider is disallowed."""
|
|
|
|
core_config = {
|
2019-07-31 19:25:30 +00:00
|
|
|
"latitude": 60,
|
|
|
|
"longitude": 50,
|
|
|
|
"elevation": 25,
|
|
|
|
"name": "Huis",
|
2018-08-28 18:54:01 +00:00
|
|
|
CONF_UNIT_SYSTEM: CONF_UNIT_SYSTEM_IMPERIAL,
|
2019-07-31 19:25:30 +00:00
|
|
|
"time_zone": "GMT",
|
|
|
|
CONF_AUTH_PROVIDERS: [{"type": "homeassistant"}, {"type": "homeassistant"}],
|
2018-08-28 18:54:01 +00:00
|
|
|
}
|
|
|
|
with pytest.raises(Invalid):
|
|
|
|
await config_util.async_process_ha_core_config(hass, core_config)
|
|
|
|
|
|
|
|
|
2023-02-08 07:51:43 +00:00
|
|
|
async def test_disallowed_auth_mfa_module_config(hass: HomeAssistant) -> None:
|
2018-08-28 18:54:01 +00:00
|
|
|
"""Test loading insecure example auth mfa module is disallowed."""
|
|
|
|
core_config = {
|
2019-07-31 19:25:30 +00:00
|
|
|
"latitude": 60,
|
|
|
|
"longitude": 50,
|
|
|
|
"elevation": 25,
|
|
|
|
"name": "Huis",
|
2018-08-28 18:54:01 +00:00
|
|
|
CONF_UNIT_SYSTEM: CONF_UNIT_SYSTEM_IMPERIAL,
|
2019-07-31 19:25:30 +00:00
|
|
|
"time_zone": "GMT",
|
|
|
|
CONF_AUTH_MFA_MODULES: [
|
|
|
|
{
|
|
|
|
"type": "insecure_example",
|
|
|
|
"data": [{"user_id": "mock-user", "pin": "test-pin"}],
|
|
|
|
}
|
|
|
|
],
|
2018-08-28 18:54:01 +00:00
|
|
|
}
|
|
|
|
with pytest.raises(Invalid):
|
|
|
|
await config_util.async_process_ha_core_config(hass, core_config)
|
|
|
|
|
|
|
|
|
2023-02-08 07:51:43 +00:00
|
|
|
async def test_disallowed_duplicated_auth_mfa_module_config(
|
|
|
|
hass: HomeAssistant,
|
|
|
|
) -> None:
|
2018-08-28 18:54:01 +00:00
|
|
|
"""Test loading insecure example auth mfa module is disallowed."""
|
|
|
|
core_config = {
|
2019-07-31 19:25:30 +00:00
|
|
|
"latitude": 60,
|
|
|
|
"longitude": 50,
|
|
|
|
"elevation": 25,
|
|
|
|
"name": "Huis",
|
2018-08-28 18:54:01 +00:00
|
|
|
CONF_UNIT_SYSTEM: CONF_UNIT_SYSTEM_IMPERIAL,
|
2019-07-31 19:25:30 +00:00
|
|
|
"time_zone": "GMT",
|
|
|
|
CONF_AUTH_MFA_MODULES: [{"type": "totp"}, {"type": "totp"}],
|
2018-07-17 17:36:33 +00:00
|
|
|
}
|
|
|
|
with pytest.raises(Invalid):
|
|
|
|
await config_util.async_process_ha_core_config(hass, core_config)
|
2018-09-24 08:17:24 +00:00
|
|
|
|
|
|
|
|
2023-02-08 07:51:43 +00:00
|
|
|
async def test_merge_split_component_definition(hass: HomeAssistant) -> None:
|
2018-09-24 08:17:24 +00:00
|
|
|
"""Test components with trailing description in packages are merged."""
|
|
|
|
packages = {
|
2019-07-31 19:25:30 +00:00
|
|
|
"pack_1": {"light one": {"l1": None}},
|
|
|
|
"pack_2": {"light two": {"l2": None}, "light three": {"l3": None}},
|
2018-09-24 08:17:24 +00:00
|
|
|
}
|
2019-07-31 19:25:30 +00:00
|
|
|
config = {config_util.CONF_CORE: {config_util.CONF_PACKAGES: packages}}
|
2019-04-14 14:23:01 +00:00
|
|
|
await config_util.merge_packages_config(hass, config, packages)
|
2018-09-24 08:17:24 +00:00
|
|
|
|
|
|
|
assert len(config) == 4
|
2019-07-31 19:25:30 +00:00
|
|
|
assert len(config["light one"]) == 1
|
|
|
|
assert len(config["light two"]) == 1
|
|
|
|
assert len(config["light three"]) == 1
|
2020-02-13 21:43:07 +00:00
|
|
|
|
|
|
|
|
2023-02-21 08:27:13 +00:00
|
|
|
async def test_component_config_exceptions(
|
|
|
|
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
|
|
|
|
) -> None:
|
2020-02-13 21:43:07 +00:00
|
|
|
"""Test unexpected exceptions validating component config."""
|
|
|
|
# Config validator
|
2023-11-24 16:34:45 +00:00
|
|
|
test_integration = Mock(
|
|
|
|
domain="test_domain",
|
2024-03-02 23:56:25 +00:00
|
|
|
async_get_component=AsyncMock(),
|
|
|
|
async_get_platform=AsyncMock(
|
2023-11-24 16:34:45 +00:00
|
|
|
return_value=Mock(
|
|
|
|
async_validate_config=AsyncMock(side_effect=ValueError("broken"))
|
|
|
|
)
|
|
|
|
),
|
|
|
|
)
|
2020-02-13 21:43:07 +00:00
|
|
|
assert (
|
2023-11-24 16:34:45 +00:00
|
|
|
await config_util.async_process_component_and_handle_errors(
|
|
|
|
hass, {}, integration=test_integration
|
2020-02-13 21:43:07 +00:00
|
|
|
)
|
|
|
|
is None
|
|
|
|
)
|
|
|
|
assert "ValueError: broken" in caplog.text
|
|
|
|
assert "Unknown error calling test_domain config validator" in caplog.text
|
2023-11-24 16:34:45 +00:00
|
|
|
caplog.clear()
|
|
|
|
with pytest.raises(HomeAssistantError) as ex:
|
|
|
|
await config_util.async_process_component_and_handle_errors(
|
|
|
|
hass, {}, integration=test_integration, raise_on_failure=True
|
|
|
|
)
|
|
|
|
assert "ValueError: broken" in caplog.text
|
|
|
|
assert "Unknown error calling test_domain config validator" in caplog.text
|
|
|
|
assert str(ex.value) == "Unknown error calling test_domain config validator"
|
|
|
|
|
|
|
|
test_integration = Mock(
|
|
|
|
domain="test_domain",
|
2024-03-02 23:56:25 +00:00
|
|
|
async_get_platform=AsyncMock(
|
2023-11-24 16:34:45 +00:00
|
|
|
return_value=Mock(
|
|
|
|
async_validate_config=AsyncMock(
|
|
|
|
side_effect=HomeAssistantError("broken")
|
|
|
|
)
|
|
|
|
)
|
|
|
|
),
|
2024-03-02 23:56:25 +00:00
|
|
|
async_get_component=AsyncMock(return_value=Mock(spec=["PLATFORM_SCHEMA_BASE"])),
|
2023-11-24 16:34:45 +00:00
|
|
|
)
|
|
|
|
caplog.clear()
|
|
|
|
assert (
|
|
|
|
await config_util.async_process_component_and_handle_errors(
|
|
|
|
hass, {}, integration=test_integration, raise_on_failure=False
|
|
|
|
)
|
|
|
|
is None
|
|
|
|
)
|
|
|
|
assert "Invalid config for 'test_domain': broken" in caplog.text
|
|
|
|
with pytest.raises(HomeAssistantError) as ex:
|
|
|
|
await config_util.async_process_component_and_handle_errors(
|
|
|
|
hass, {}, integration=test_integration, raise_on_failure=True
|
|
|
|
)
|
|
|
|
assert "Invalid config for 'test_domain': broken" in str(ex.value)
|
2020-02-13 21:43:07 +00:00
|
|
|
|
|
|
|
# component.CONFIG_SCHEMA
|
|
|
|
caplog.clear()
|
2023-11-24 16:34:45 +00:00
|
|
|
test_integration = Mock(
|
|
|
|
domain="test_domain",
|
2024-03-02 23:56:25 +00:00
|
|
|
async_get_platform=AsyncMock(return_value=None),
|
|
|
|
async_get_component=AsyncMock(
|
2023-11-24 16:34:45 +00:00
|
|
|
return_value=Mock(CONFIG_SCHEMA=Mock(side_effect=ValueError("broken")))
|
|
|
|
),
|
|
|
|
)
|
2020-02-13 21:43:07 +00:00
|
|
|
assert (
|
2023-11-24 16:34:45 +00:00
|
|
|
await config_util.async_process_component_and_handle_errors(
|
2020-02-13 21:43:07 +00:00
|
|
|
hass,
|
|
|
|
{},
|
2023-11-24 16:34:45 +00:00
|
|
|
integration=test_integration,
|
|
|
|
raise_on_failure=False,
|
2020-02-13 21:43:07 +00:00
|
|
|
)
|
|
|
|
is None
|
|
|
|
)
|
|
|
|
assert "Unknown error calling test_domain CONFIG_SCHEMA" in caplog.text
|
2023-11-24 16:34:45 +00:00
|
|
|
with pytest.raises(HomeAssistantError) as ex:
|
|
|
|
await config_util.async_process_component_and_handle_errors(
|
|
|
|
hass,
|
|
|
|
{},
|
|
|
|
integration=test_integration,
|
|
|
|
raise_on_failure=True,
|
|
|
|
)
|
|
|
|
assert "Unknown error calling test_domain CONFIG_SCHEMA" in caplog.text
|
|
|
|
assert str(ex.value) == "Unknown error calling test_domain CONFIG_SCHEMA"
|
2020-02-13 21:43:07 +00:00
|
|
|
|
|
|
|
# component.PLATFORM_SCHEMA
|
|
|
|
caplog.clear()
|
2023-11-24 16:34:45 +00:00
|
|
|
test_integration = Mock(
|
|
|
|
domain="test_domain",
|
2024-03-02 23:56:25 +00:00
|
|
|
async_get_platform=AsyncMock(return_value=None),
|
|
|
|
async_get_component=AsyncMock(
|
2023-11-24 16:34:45 +00:00
|
|
|
return_value=Mock(
|
|
|
|
spec=["PLATFORM_SCHEMA_BASE"],
|
|
|
|
PLATFORM_SCHEMA_BASE=Mock(side_effect=ValueError("broken")),
|
|
|
|
)
|
|
|
|
),
|
|
|
|
)
|
|
|
|
assert await config_util.async_process_component_and_handle_errors(
|
2022-02-05 13:19:37 +00:00
|
|
|
hass,
|
|
|
|
{"test_domain": {"platform": "test_platform"}},
|
2023-11-24 16:34:45 +00:00
|
|
|
integration=test_integration,
|
|
|
|
raise_on_failure=False,
|
2022-02-05 13:19:37 +00:00
|
|
|
) == {"test_domain": []}
|
2020-02-13 21:43:07 +00:00
|
|
|
assert "ValueError: broken" in caplog.text
|
|
|
|
assert (
|
2023-11-24 16:34:45 +00:00
|
|
|
"Unknown error validating config for test_platform platform "
|
|
|
|
"for test_domain component with PLATFORM_SCHEMA"
|
2023-01-20 12:52:46 +00:00
|
|
|
) in caplog.text
|
2023-11-24 16:34:45 +00:00
|
|
|
caplog.clear()
|
|
|
|
with pytest.raises(HomeAssistantError) as ex:
|
|
|
|
await config_util.async_process_component_and_handle_errors(
|
|
|
|
hass,
|
|
|
|
{"test_domain": {"platform": "test_platform"}},
|
|
|
|
integration=test_integration,
|
|
|
|
raise_on_failure=True,
|
|
|
|
)
|
|
|
|
assert (
|
|
|
|
"Unknown error validating config for test_platform platform "
|
|
|
|
"for test_domain component with PLATFORM_SCHEMA"
|
|
|
|
) in caplog.text
|
|
|
|
assert str(ex.value) == (
|
|
|
|
"Unknown error validating config for test_platform platform "
|
|
|
|
"for test_domain component with PLATFORM_SCHEMA"
|
|
|
|
)
|
2020-02-13 21:43:07 +00:00
|
|
|
|
|
|
|
# platform.PLATFORM_SCHEMA
|
|
|
|
caplog.clear()
|
2023-11-24 16:34:45 +00:00
|
|
|
test_integration = Mock(
|
|
|
|
domain="test_domain",
|
2024-03-02 23:56:25 +00:00
|
|
|
async_get_platform=AsyncMock(return_value=None),
|
|
|
|
async_get_component=AsyncMock(return_value=Mock(spec=["PLATFORM_SCHEMA_BASE"])),
|
2023-11-24 16:34:45 +00:00
|
|
|
)
|
2020-02-13 21:43:07 +00:00
|
|
|
with patch(
|
|
|
|
"homeassistant.config.async_get_integration_with_requirements",
|
|
|
|
return_value=Mock( # integration that owns platform
|
2024-03-02 23:56:25 +00:00
|
|
|
async_get_platform=AsyncMock(
|
2020-02-13 21:43:07 +00:00
|
|
|
return_value=Mock( # platform
|
|
|
|
PLATFORM_SCHEMA=Mock(side_effect=ValueError("broken"))
|
|
|
|
)
|
|
|
|
)
|
|
|
|
),
|
|
|
|
):
|
2023-11-24 16:34:45 +00:00
|
|
|
assert await config_util.async_process_component_and_handle_errors(
|
2022-02-05 13:19:37 +00:00
|
|
|
hass,
|
|
|
|
{"test_domain": {"platform": "test_platform"}},
|
2023-11-24 16:34:45 +00:00
|
|
|
integration=test_integration,
|
|
|
|
raise_on_failure=False,
|
2022-02-05 13:19:37 +00:00
|
|
|
) == {"test_domain": []}
|
2020-02-13 21:43:07 +00:00
|
|
|
assert "ValueError: broken" in caplog.text
|
2023-11-24 16:34:45 +00:00
|
|
|
assert (
|
|
|
|
"Unknown error validating config for test_platform platform for test_domain"
|
|
|
|
" component with PLATFORM_SCHEMA"
|
|
|
|
) in caplog.text
|
|
|
|
caplog.clear()
|
|
|
|
with pytest.raises(HomeAssistantError) as ex:
|
|
|
|
assert await config_util.async_process_component_and_handle_errors(
|
|
|
|
hass,
|
|
|
|
{"test_domain": {"platform": "test_platform"}},
|
|
|
|
integration=test_integration,
|
|
|
|
raise_on_failure=True,
|
|
|
|
)
|
|
|
|
assert (
|
|
|
|
"Unknown error validating config for test_platform platform for test_domain"
|
|
|
|
" component with PLATFORM_SCHEMA"
|
|
|
|
) in str(ex.value)
|
|
|
|
assert "ValueError: broken" in caplog.text
|
2020-02-13 21:43:07 +00:00
|
|
|
assert (
|
2023-01-20 12:52:46 +00:00
|
|
|
"Unknown error validating config for test_platform platform for test_domain"
|
|
|
|
" component with PLATFORM_SCHEMA" in caplog.text
|
2020-02-13 21:43:07 +00:00
|
|
|
)
|
2023-11-24 16:34:45 +00:00
|
|
|
# Test multiple platform failures
|
|
|
|
assert await config_util.async_process_component_and_handle_errors(
|
|
|
|
hass,
|
|
|
|
{
|
|
|
|
"test_domain": [
|
|
|
|
{"platform": "test_platform1"},
|
|
|
|
{"platform": "test_platform2"},
|
|
|
|
]
|
|
|
|
},
|
|
|
|
integration=test_integration,
|
|
|
|
raise_on_failure=False,
|
|
|
|
) == {"test_domain": []}
|
|
|
|
assert "ValueError: broken" in caplog.text
|
|
|
|
assert (
|
|
|
|
"Unknown error validating config for test_platform1 platform "
|
|
|
|
"for test_domain component with PLATFORM_SCHEMA"
|
|
|
|
) in caplog.text
|
|
|
|
assert (
|
|
|
|
"Unknown error validating config for test_platform2 platform "
|
|
|
|
"for test_domain component with PLATFORM_SCHEMA"
|
|
|
|
) in caplog.text
|
|
|
|
caplog.clear()
|
|
|
|
with pytest.raises(HomeAssistantError) as ex:
|
|
|
|
assert await config_util.async_process_component_and_handle_errors(
|
|
|
|
hass,
|
|
|
|
{
|
|
|
|
"test_domain": [
|
|
|
|
{"platform": "test_platform1"},
|
|
|
|
{"platform": "test_platform2"},
|
|
|
|
]
|
|
|
|
},
|
|
|
|
integration=test_integration,
|
|
|
|
raise_on_failure=True,
|
|
|
|
)
|
|
|
|
assert (
|
|
|
|
"Failed to process component config for integration test_domain"
|
|
|
|
" due to multiple errors (2), check the logs for more information."
|
|
|
|
) in str(ex.value)
|
|
|
|
assert "ValueError: broken" in caplog.text
|
|
|
|
assert (
|
|
|
|
"Unknown error validating config for test_platform1 platform "
|
|
|
|
"for test_domain component with PLATFORM_SCHEMA"
|
|
|
|
) in caplog.text
|
|
|
|
assert (
|
|
|
|
"Unknown error validating config for test_platform2 platform "
|
|
|
|
"for test_domain component with PLATFORM_SCHEMA"
|
|
|
|
) in caplog.text
|
|
|
|
|
2024-03-02 23:56:25 +00:00
|
|
|
# async_get_platform("domain") raising on ImportError
|
2023-11-24 16:34:45 +00:00
|
|
|
caplog.clear()
|
|
|
|
test_integration = Mock(
|
|
|
|
domain="test_domain",
|
2024-03-02 23:56:25 +00:00
|
|
|
async_get_platform=AsyncMock(return_value=None),
|
|
|
|
async_get_component=AsyncMock(return_value=Mock(spec=["PLATFORM_SCHEMA_BASE"])),
|
2023-11-24 16:34:45 +00:00
|
|
|
)
|
|
|
|
import_error = ImportError(
|
|
|
|
("ModuleNotFoundError: No module named 'not_installed_something'"),
|
|
|
|
name="not_installed_something",
|
|
|
|
)
|
|
|
|
with patch(
|
|
|
|
"homeassistant.config.async_get_integration_with_requirements",
|
|
|
|
return_value=Mock( # integration that owns platform
|
2024-03-02 23:56:25 +00:00
|
|
|
async_get_platform=AsyncMock(side_effect=import_error)
|
2023-11-24 16:34:45 +00:00
|
|
|
),
|
|
|
|
):
|
|
|
|
assert await config_util.async_process_component_and_handle_errors(
|
|
|
|
hass,
|
|
|
|
{"test_domain": {"platform": "test_platform"}},
|
|
|
|
integration=test_integration,
|
|
|
|
raise_on_failure=False,
|
|
|
|
) == {"test_domain": []}
|
|
|
|
assert (
|
|
|
|
"ImportError: ModuleNotFoundError: No module named "
|
|
|
|
"'not_installed_something'" in caplog.text
|
|
|
|
)
|
|
|
|
caplog.clear()
|
|
|
|
with pytest.raises(HomeAssistantError) as ex:
|
|
|
|
assert await config_util.async_process_component_and_handle_errors(
|
|
|
|
hass,
|
|
|
|
{"test_domain": {"platform": "test_platform"}},
|
|
|
|
integration=test_integration,
|
|
|
|
raise_on_failure=True,
|
|
|
|
)
|
|
|
|
assert (
|
|
|
|
"ImportError: ModuleNotFoundError: No module named "
|
|
|
|
"'not_installed_something'" in caplog.text
|
|
|
|
)
|
|
|
|
assert (
|
|
|
|
"Platform error: test_domain - ModuleNotFoundError: "
|
|
|
|
"No module named 'not_installed_something'"
|
|
|
|
) in caplog.text
|
|
|
|
assert (
|
|
|
|
"Platform error: test_domain - ModuleNotFoundError: "
|
|
|
|
"No module named 'not_installed_something'"
|
|
|
|
) in str(ex.value)
|
2020-03-20 03:45:26 +00:00
|
|
|
|
2024-03-02 23:56:25 +00:00
|
|
|
# async_get_platform("config") raising
|
2020-10-19 15:09:57 +00:00
|
|
|
caplog.clear()
|
2023-11-24 16:34:45 +00:00
|
|
|
test_integration = Mock(
|
|
|
|
pkg_path="homeassistant.components.test_domain",
|
|
|
|
domain="test_domain",
|
2024-03-02 23:56:25 +00:00
|
|
|
async_get_component=AsyncMock(),
|
|
|
|
async_get_platform=AsyncMock(
|
2023-11-24 16:34:45 +00:00
|
|
|
side_effect=ImportError(
|
|
|
|
("ModuleNotFoundError: No module named 'not_installed_something'"),
|
|
|
|
name="not_installed_something",
|
|
|
|
)
|
|
|
|
),
|
|
|
|
)
|
2020-10-19 15:09:57 +00:00
|
|
|
assert (
|
2023-11-24 16:34:45 +00:00
|
|
|
await config_util.async_process_component_and_handle_errors(
|
2020-10-19 15:09:57 +00:00
|
|
|
hass,
|
|
|
|
{"test_domain": {}},
|
2023-11-24 16:34:45 +00:00
|
|
|
integration=test_integration,
|
|
|
|
raise_on_failure=False,
|
2020-10-19 15:09:57 +00:00
|
|
|
)
|
|
|
|
is None
|
|
|
|
)
|
|
|
|
assert (
|
2023-11-24 16:34:45 +00:00
|
|
|
"Error importing config platform test_domain: ModuleNotFoundError: "
|
|
|
|
"No module named 'not_installed_something'" in caplog.text
|
|
|
|
)
|
|
|
|
with pytest.raises(HomeAssistantError) as ex:
|
|
|
|
await config_util.async_process_component_and_handle_errors(
|
|
|
|
hass,
|
|
|
|
{"test_domain": {}},
|
|
|
|
integration=test_integration,
|
|
|
|
raise_on_failure=True,
|
|
|
|
)
|
|
|
|
assert (
|
|
|
|
"Error importing config platform test_domain: ModuleNotFoundError: "
|
|
|
|
"No module named 'not_installed_something'" in caplog.text
|
|
|
|
)
|
|
|
|
assert (
|
|
|
|
"Error importing config platform test_domain: ModuleNotFoundError: "
|
|
|
|
"No module named 'not_installed_something'" in str(ex.value)
|
2020-10-19 15:09:57 +00:00
|
|
|
)
|
|
|
|
|
2024-03-02 23:56:25 +00:00
|
|
|
# async_get_component raising
|
2021-02-20 06:22:48 +00:00
|
|
|
caplog.clear()
|
2023-11-24 16:34:45 +00:00
|
|
|
test_integration = Mock(
|
|
|
|
pkg_path="homeassistant.components.test_domain",
|
|
|
|
domain="test_domain",
|
2024-03-02 23:56:25 +00:00
|
|
|
async_get_component=AsyncMock(
|
2023-11-24 16:34:45 +00:00
|
|
|
side_effect=FileNotFoundError("No such file or directory: b'liblibc.a'")
|
|
|
|
),
|
|
|
|
)
|
2021-02-20 06:22:48 +00:00
|
|
|
assert (
|
2023-11-24 16:34:45 +00:00
|
|
|
await config_util.async_process_component_and_handle_errors(
|
2021-02-20 06:22:48 +00:00
|
|
|
hass,
|
|
|
|
{"test_domain": {}},
|
2023-11-24 16:34:45 +00:00
|
|
|
integration=test_integration,
|
|
|
|
raise_on_failure=False,
|
2021-02-20 06:22:48 +00:00
|
|
|
)
|
|
|
|
is None
|
|
|
|
)
|
|
|
|
assert "Unable to import test_domain: No such file or directory" in caplog.text
|
2023-11-24 16:34:45 +00:00
|
|
|
with pytest.raises(HomeAssistantError) as ex:
|
|
|
|
await config_util.async_process_component_and_handle_errors(
|
|
|
|
hass,
|
|
|
|
{"test_domain": {}},
|
|
|
|
integration=test_integration,
|
|
|
|
raise_on_failure=True,
|
|
|
|
)
|
|
|
|
assert "Unable to import test_domain: No such file or directory" in caplog.text
|
|
|
|
assert "Unable to import test_domain: No such file or directory" in str(ex.value)
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
("exception_info_list", "error", "messages", "show_stack_trace", "translation_key"),
|
|
|
|
[
|
|
|
|
(
|
|
|
|
[
|
|
|
|
config_util.ConfigExceptionInfo(
|
|
|
|
ImportError("bla"),
|
|
|
|
"component_import_err",
|
|
|
|
"test_domain",
|
|
|
|
{"test_domain": []},
|
|
|
|
"https://example.com",
|
|
|
|
)
|
|
|
|
],
|
|
|
|
"bla",
|
|
|
|
["Unable to import test_domain: bla", "bla"],
|
|
|
|
False,
|
|
|
|
"component_import_err",
|
|
|
|
),
|
|
|
|
(
|
|
|
|
[
|
|
|
|
config_util.ConfigExceptionInfo(
|
|
|
|
HomeAssistantError("bla"),
|
|
|
|
"config_validation_err",
|
|
|
|
"test_domain",
|
|
|
|
{"test_domain": []},
|
|
|
|
"https://example.com",
|
|
|
|
)
|
|
|
|
],
|
|
|
|
"bla",
|
|
|
|
[
|
|
|
|
"Invalid config for 'test_domain': bla, "
|
|
|
|
"please check the docs at https://example.com",
|
|
|
|
"bla",
|
|
|
|
],
|
|
|
|
True,
|
|
|
|
"config_validation_err",
|
|
|
|
),
|
|
|
|
(
|
|
|
|
[
|
|
|
|
config_util.ConfigExceptionInfo(
|
|
|
|
vol.Invalid("bla", ["path"]),
|
|
|
|
"config_validation_err",
|
|
|
|
"test_domain",
|
|
|
|
{"test_domain": []},
|
|
|
|
"https://example.com",
|
|
|
|
)
|
|
|
|
],
|
|
|
|
"bla @ data['path']",
|
|
|
|
[
|
|
|
|
"Invalid config for 'test_domain': bla 'path', got None, "
|
|
|
|
"please check the docs at https://example.com",
|
|
|
|
"bla",
|
|
|
|
],
|
|
|
|
False,
|
|
|
|
"config_validation_err",
|
|
|
|
),
|
|
|
|
(
|
|
|
|
[
|
|
|
|
config_util.ConfigExceptionInfo(
|
|
|
|
vol.Invalid("bla", ["path"]),
|
|
|
|
"platform_config_validation_err",
|
|
|
|
"test_domain",
|
|
|
|
{"test_domain": []},
|
|
|
|
"https://alt.example.com",
|
|
|
|
)
|
|
|
|
],
|
|
|
|
"bla @ data['path']",
|
|
|
|
[
|
|
|
|
"Invalid config for 'test_domain': bla 'path', got None, "
|
|
|
|
"please check the docs at https://alt.example.com",
|
|
|
|
"bla",
|
|
|
|
],
|
|
|
|
False,
|
|
|
|
"platform_config_validation_err",
|
|
|
|
),
|
|
|
|
(
|
|
|
|
[
|
|
|
|
config_util.ConfigExceptionInfo(
|
|
|
|
ImportError("bla"),
|
|
|
|
"platform_component_load_err",
|
|
|
|
"test_domain",
|
|
|
|
{"test_domain": []},
|
|
|
|
"https://example.com",
|
|
|
|
)
|
|
|
|
],
|
|
|
|
"bla",
|
|
|
|
["Platform error: test_domain - bla", "bla"],
|
|
|
|
False,
|
|
|
|
"platform_component_load_err",
|
|
|
|
),
|
|
|
|
],
|
|
|
|
)
|
|
|
|
async def test_component_config_error_processing(
|
|
|
|
hass: HomeAssistant,
|
|
|
|
caplog: pytest.LogCaptureFixture,
|
|
|
|
error: str,
|
|
|
|
exception_info_list: list[config_util.ConfigExceptionInfo],
|
|
|
|
messages: list[str],
|
|
|
|
show_stack_trace: bool,
|
|
|
|
translation_key: str,
|
|
|
|
) -> None:
|
|
|
|
"""Test component config error processing."""
|
|
|
|
test_integration = Mock(
|
|
|
|
domain="test_domain",
|
|
|
|
documentation="https://example.com",
|
|
|
|
get_platform=Mock(
|
|
|
|
return_value=Mock(
|
|
|
|
async_validate_config=AsyncMock(side_effect=ValueError("broken"))
|
|
|
|
)
|
|
|
|
),
|
|
|
|
)
|
|
|
|
with patch(
|
|
|
|
"homeassistant.config.async_process_component_config",
|
|
|
|
return_value=config_util.IntegrationConfigInfo(None, exception_info_list),
|
|
|
|
), pytest.raises(ConfigValidationError) as ex:
|
|
|
|
await config_util.async_process_component_and_handle_errors(
|
|
|
|
hass, {}, test_integration, raise_on_failure=True
|
|
|
|
)
|
|
|
|
records = [record for record in caplog.records if record.msg == messages[0]]
|
|
|
|
assert len(records) == 1
|
|
|
|
assert (records[0].exc_info is not None) == show_stack_trace
|
|
|
|
assert str(ex.value) == messages[0]
|
|
|
|
assert ex.value.translation_key == translation_key
|
|
|
|
assert ex.value.translation_domain == "homeassistant"
|
|
|
|
assert ex.value.translation_placeholders["domain"] == "test_domain"
|
|
|
|
assert all(message in caplog.text for message in messages)
|
|
|
|
|
|
|
|
caplog.clear()
|
|
|
|
with patch(
|
|
|
|
"homeassistant.config.async_process_component_config",
|
|
|
|
return_value=config_util.IntegrationConfigInfo(None, exception_info_list),
|
|
|
|
):
|
|
|
|
await config_util.async_process_component_and_handle_errors(
|
|
|
|
hass, {}, test_integration
|
|
|
|
)
|
|
|
|
assert all(message in caplog.text for message in messages)
|
2021-02-20 06:22:48 +00:00
|
|
|
|
2020-03-20 03:45:26 +00:00
|
|
|
|
|
|
|
@pytest.mark.parametrize(
|
2023-02-15 13:09:50 +00:00
|
|
|
("domain", "schema", "expected"),
|
2020-03-20 03:45:26 +00:00
|
|
|
[
|
2020-03-21 03:27:37 +00:00
|
|
|
("zone", vol.Schema({vol.Optional("zone", default=list): [int]}), "list"),
|
|
|
|
("zone", vol.Schema({vol.Optional("zone", default=[]): [int]}), "list"),
|
|
|
|
(
|
|
|
|
"zone",
|
|
|
|
vol.Schema({vol.Optional("zone", default={}): {vol.Optional("hello"): 1}}),
|
|
|
|
"dict",
|
|
|
|
),
|
|
|
|
(
|
|
|
|
"zone",
|
|
|
|
vol.Schema(
|
|
|
|
{vol.Optional("zone", default=dict): {vol.Optional("hello"): 1}}
|
|
|
|
),
|
|
|
|
"dict",
|
|
|
|
),
|
2020-03-20 20:34:56 +00:00
|
|
|
("zone", vol.Schema({vol.Optional("zone"): int}), None),
|
|
|
|
("zone", vol.Schema({"zone": int}), None),
|
2020-08-27 11:56:20 +00:00
|
|
|
(
|
|
|
|
"not_existing",
|
|
|
|
vol.Schema({vol.Optional("zone", default=dict): dict}),
|
|
|
|
None,
|
|
|
|
),
|
2020-03-20 20:34:56 +00:00
|
|
|
("non_existing", vol.Schema({"zone": int}), None),
|
|
|
|
("zone", vol.Schema({}), None),
|
2020-04-24 16:52:23 +00:00
|
|
|
("plex", vol.Schema(vol.All({"plex": {"host": str}})), "dict"),
|
2020-12-12 21:24:16 +00:00
|
|
|
("openuv", cv.deprecated("openuv"), None),
|
2020-03-20 03:45:26 +00:00
|
|
|
],
|
|
|
|
)
|
2023-02-21 08:27:13 +00:00
|
|
|
def test_identify_config_schema(domain, schema, expected) -> None:
|
2020-03-20 03:45:26 +00:00
|
|
|
"""Test identify config schema."""
|
|
|
|
assert (
|
2020-03-20 20:34:56 +00:00
|
|
|
config_util._identify_config_schema(Mock(DOMAIN=domain, CONFIG_SCHEMA=schema))
|
2020-03-20 03:45:26 +00:00
|
|
|
== expected
|
|
|
|
)
|
2022-11-08 06:21:09 +00:00
|
|
|
|
|
|
|
|
2023-02-08 07:51:43 +00:00
|
|
|
async def test_core_config_schema_historic_currency(hass: HomeAssistant) -> None:
|
2022-11-08 06:21:09 +00:00
|
|
|
"""Test core config schema."""
|
2022-11-10 16:28:19 +00:00
|
|
|
await config_util.async_process_ha_core_config(hass, {"currency": "LTT"})
|
|
|
|
|
|
|
|
issue_registry = ir.async_get(hass)
|
|
|
|
issue = issue_registry.async_get_issue("homeassistant", "historic_currency")
|
|
|
|
assert issue
|
|
|
|
assert issue.translation_placeholders == {"currency": "LTT"}
|
|
|
|
|
|
|
|
|
2023-02-21 08:27:13 +00:00
|
|
|
async def test_core_store_historic_currency(
|
|
|
|
hass: HomeAssistant, hass_storage: dict[str, Any]
|
|
|
|
) -> None:
|
2022-11-10 16:28:19 +00:00
|
|
|
"""Test core config store."""
|
|
|
|
core_data = {
|
|
|
|
"data": {
|
2022-11-08 06:21:09 +00:00
|
|
|
"currency": "LTT",
|
2022-11-10 16:28:19 +00:00
|
|
|
},
|
|
|
|
"key": "core.config",
|
|
|
|
"version": 1,
|
|
|
|
"minor_version": 1,
|
|
|
|
}
|
|
|
|
hass_storage["core.config"] = dict(core_data)
|
|
|
|
await config_util.async_process_ha_core_config(hass, {})
|
2022-11-08 06:21:09 +00:00
|
|
|
|
|
|
|
issue_registry = ir.async_get(hass)
|
2022-11-28 08:54:13 +00:00
|
|
|
issue_id = "historic_currency"
|
|
|
|
issue = issue_registry.async_get_issue("homeassistant", issue_id)
|
2022-11-08 06:21:09 +00:00
|
|
|
assert issue
|
|
|
|
assert issue.translation_placeholders == {"currency": "LTT"}
|
2022-11-28 08:54:13 +00:00
|
|
|
|
|
|
|
await hass.config.async_update(**{"currency": "EUR"})
|
|
|
|
issue = issue_registry.async_get_issue("homeassistant", issue_id)
|
|
|
|
assert not issue
|
|
|
|
|
|
|
|
|
2023-02-08 07:51:43 +00:00
|
|
|
async def test_core_config_schema_no_country(hass: HomeAssistant) -> None:
|
2022-11-28 08:54:13 +00:00
|
|
|
"""Test core config schema."""
|
|
|
|
await config_util.async_process_ha_core_config(hass, {})
|
|
|
|
|
|
|
|
issue_registry = ir.async_get(hass)
|
|
|
|
issue = issue_registry.async_get_issue("homeassistant", "country_not_configured")
|
|
|
|
assert issue
|
|
|
|
|
|
|
|
|
2023-12-21 23:42:16 +00:00
|
|
|
@pytest.mark.parametrize(
|
|
|
|
("config", "expected_issue"),
|
|
|
|
[
|
|
|
|
({}, None),
|
|
|
|
({"legacy_templates": True}, "legacy_templates_true"),
|
|
|
|
({"legacy_templates": False}, "legacy_templates_false"),
|
|
|
|
],
|
|
|
|
)
|
|
|
|
async def test_core_config_schema_legacy_template(
|
|
|
|
hass: HomeAssistant, config: dict[str, Any], expected_issue: str | None
|
|
|
|
) -> None:
|
|
|
|
"""Test legacy_template core config schema."""
|
|
|
|
await config_util.async_process_ha_core_config(hass, config)
|
|
|
|
|
|
|
|
issue_registry = ir.async_get(hass)
|
|
|
|
for issue_id in {"legacy_templates_true", "legacy_templates_false"}:
|
|
|
|
issue = issue_registry.async_get_issue("homeassistant", issue_id)
|
|
|
|
assert issue if issue_id == expected_issue else not issue
|
|
|
|
|
|
|
|
await config_util.async_process_ha_core_config(hass, {})
|
|
|
|
for issue_id in {"legacy_templates_true", "legacy_templates_false"}:
|
|
|
|
assert not issue_registry.async_get_issue("homeassistant", issue_id)
|
|
|
|
|
|
|
|
|
2023-02-21 08:27:13 +00:00
|
|
|
async def test_core_store_no_country(
|
|
|
|
hass: HomeAssistant, hass_storage: dict[str, Any]
|
|
|
|
) -> None:
|
2022-11-28 08:54:13 +00:00
|
|
|
"""Test core config store."""
|
|
|
|
core_data = {
|
|
|
|
"data": {},
|
|
|
|
"key": "core.config",
|
|
|
|
"version": 1,
|
|
|
|
"minor_version": 1,
|
|
|
|
}
|
|
|
|
hass_storage["core.config"] = dict(core_data)
|
|
|
|
await config_util.async_process_ha_core_config(hass, {})
|
|
|
|
|
|
|
|
issue_registry = ir.async_get(hass)
|
|
|
|
issue_id = "country_not_configured"
|
|
|
|
issue = issue_registry.async_get_issue("homeassistant", issue_id)
|
|
|
|
assert issue
|
|
|
|
|
|
|
|
await hass.config.async_update(**{"country": "SE"})
|
|
|
|
issue = issue_registry.async_get_issue("homeassistant", issue_id)
|
|
|
|
assert not issue
|
2023-10-24 12:47:58 +00:00
|
|
|
|
|
|
|
|
|
|
|
async def test_safe_mode(hass: HomeAssistant) -> None:
|
|
|
|
"""Test safe mode."""
|
|
|
|
assert config_util.safe_mode_enabled(hass.config.config_dir) is False
|
|
|
|
assert config_util.safe_mode_enabled(hass.config.config_dir) is False
|
|
|
|
await config_util.async_enable_safe_mode(hass)
|
|
|
|
assert config_util.safe_mode_enabled(hass.config.config_dir) is True
|
|
|
|
assert config_util.safe_mode_enabled(hass.config.config_dir) is False
|
2023-11-13 06:25:58 +00:00
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"config_dir",
|
|
|
|
[
|
|
|
|
"basic",
|
|
|
|
"basic_include",
|
|
|
|
"include_dir_list",
|
|
|
|
"include_dir_merge_list",
|
|
|
|
"packages",
|
|
|
|
"packages_include_dir_named",
|
|
|
|
],
|
|
|
|
)
|
|
|
|
async def test_component_config_validation_error(
|
|
|
|
hass: HomeAssistant,
|
|
|
|
caplog: pytest.LogCaptureFixture,
|
|
|
|
config_dir: str,
|
2023-11-13 10:21:37 +00:00
|
|
|
mock_iot_domain_integration: Integration,
|
|
|
|
mock_non_adr_0007_integration: None,
|
|
|
|
mock_adr_0007_integrations: list[Integration],
|
2023-11-15 12:11:33 +00:00
|
|
|
mock_custom_validator_integrations: list[Integration],
|
2023-11-13 06:25:58 +00:00
|
|
|
snapshot: SnapshotAssertion,
|
|
|
|
) -> None:
|
|
|
|
"""Test schema error in component."""
|
|
|
|
|
|
|
|
base_path = os.path.dirname(__file__)
|
|
|
|
hass.config.config_dir = os.path.join(
|
2023-11-13 10:21:37 +00:00
|
|
|
base_path, "fixtures", "core", "config", "component_validation", config_dir
|
2023-11-13 06:25:58 +00:00
|
|
|
)
|
|
|
|
config = await config_util.async_hass_config_yaml(hass)
|
|
|
|
|
2023-11-20 13:15:29 +00:00
|
|
|
for domain_with_label in config:
|
|
|
|
integration = await async_get_integration(
|
2023-12-05 14:07:32 +00:00
|
|
|
hass, cv.domain_key(domain_with_label)
|
2023-11-20 13:15:29 +00:00
|
|
|
)
|
2023-11-24 16:34:45 +00:00
|
|
|
await config_util.async_process_component_and_handle_errors(
|
2023-11-14 16:14:34 +00:00
|
|
|
hass,
|
|
|
|
config,
|
|
|
|
integration=integration,
|
|
|
|
)
|
|
|
|
|
|
|
|
error_records = [
|
2023-11-15 12:11:33 +00:00
|
|
|
{
|
2023-11-16 09:56:47 +00:00
|
|
|
"message": record.message,
|
2023-11-15 12:11:33 +00:00
|
|
|
"has_exc_info": bool(record.exc_info),
|
|
|
|
}
|
2023-11-14 16:14:34 +00:00
|
|
|
for record in caplog.get_records("call")
|
|
|
|
if record.levelno == logging.ERROR
|
|
|
|
]
|
|
|
|
assert error_records == snapshot
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"config_dir",
|
|
|
|
[
|
|
|
|
"basic",
|
|
|
|
],
|
|
|
|
)
|
|
|
|
async def test_component_config_validation_error_with_docs(
|
|
|
|
hass: HomeAssistant,
|
|
|
|
caplog: pytest.LogCaptureFixture,
|
|
|
|
config_dir: str,
|
|
|
|
mock_iot_domain_integration_with_docs: Integration,
|
|
|
|
mock_non_adr_0007_integration_with_docs: None,
|
|
|
|
mock_adr_0007_integrations_with_docs: list[Integration],
|
2023-11-15 18:09:49 +00:00
|
|
|
mock_custom_validator_integrations_with_docs: list[Integration],
|
2023-11-14 16:14:34 +00:00
|
|
|
snapshot: SnapshotAssertion,
|
|
|
|
) -> None:
|
|
|
|
"""Test schema error in component."""
|
|
|
|
|
|
|
|
base_path = os.path.dirname(__file__)
|
|
|
|
hass.config.config_dir = os.path.join(
|
|
|
|
base_path, "fixtures", "core", "config", "component_validation", config_dir
|
|
|
|
)
|
|
|
|
config = await config_util.async_hass_config_yaml(hass)
|
|
|
|
|
2023-11-20 13:15:29 +00:00
|
|
|
for domain_with_label in config:
|
|
|
|
integration = await async_get_integration(
|
2023-12-05 14:07:32 +00:00
|
|
|
hass, cv.domain_key(domain_with_label)
|
2023-11-20 13:15:29 +00:00
|
|
|
)
|
2023-11-24 16:34:45 +00:00
|
|
|
await config_util.async_process_component_and_handle_errors(
|
2023-11-13 06:25:58 +00:00
|
|
|
hass,
|
|
|
|
config,
|
|
|
|
integration=integration,
|
|
|
|
)
|
|
|
|
|
|
|
|
error_records = [
|
2023-11-16 09:56:47 +00:00
|
|
|
record.message
|
2023-11-13 06:25:58 +00:00
|
|
|
for record in caplog.get_records("call")
|
|
|
|
if record.levelno == logging.ERROR
|
|
|
|
]
|
|
|
|
assert error_records == snapshot
|
2023-11-13 10:21:37 +00:00
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"config_dir",
|
|
|
|
["packages", "packages_include_dir_named"],
|
|
|
|
)
|
|
|
|
async def test_package_merge_error(
|
|
|
|
hass: HomeAssistant,
|
|
|
|
caplog: pytest.LogCaptureFixture,
|
|
|
|
config_dir: str,
|
|
|
|
mock_iot_domain_integration: Integration,
|
|
|
|
mock_non_adr_0007_integration: None,
|
|
|
|
mock_adr_0007_integrations: list[Integration],
|
|
|
|
snapshot: SnapshotAssertion,
|
|
|
|
) -> None:
|
|
|
|
"""Test schema error in component."""
|
|
|
|
base_path = os.path.dirname(__file__)
|
|
|
|
hass.config.config_dir = os.path.join(
|
|
|
|
base_path, "fixtures", "core", "config", "package_errors", config_dir
|
|
|
|
)
|
|
|
|
await config_util.async_hass_config_yaml(hass)
|
|
|
|
|
|
|
|
error_records = [
|
2023-11-16 09:56:47 +00:00
|
|
|
record.message
|
2023-11-13 10:21:37 +00:00
|
|
|
for record in caplog.get_records("call")
|
|
|
|
if record.levelno == logging.ERROR
|
|
|
|
]
|
|
|
|
assert error_records == snapshot
|
2023-11-13 13:04:58 +00:00
|
|
|
|
|
|
|
|
2023-11-16 08:08:47 +00:00
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"error",
|
|
|
|
[
|
|
|
|
FileNotFoundError("No such file or directory: b'liblibc.a'"),
|
|
|
|
ImportError(
|
|
|
|
("ModuleNotFoundError: No module named 'not_installed_something'"),
|
|
|
|
name="not_installed_something",
|
|
|
|
),
|
|
|
|
],
|
|
|
|
)
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"config_dir",
|
|
|
|
["packages", "packages_include_dir_named"],
|
|
|
|
)
|
|
|
|
async def test_package_merge_exception(
|
|
|
|
hass: HomeAssistant,
|
|
|
|
caplog: pytest.LogCaptureFixture,
|
|
|
|
config_dir: str,
|
|
|
|
error: Exception,
|
|
|
|
snapshot: SnapshotAssertion,
|
|
|
|
) -> None:
|
|
|
|
"""Test exception when merging packages."""
|
|
|
|
base_path = os.path.dirname(__file__)
|
|
|
|
hass.config.config_dir = os.path.join(
|
|
|
|
base_path, "fixtures", "core", "config", "package_exceptions", config_dir
|
|
|
|
)
|
|
|
|
with patch(
|
|
|
|
"homeassistant.config.async_get_integration_with_requirements",
|
|
|
|
side_effect=error,
|
|
|
|
):
|
|
|
|
await config_util.async_hass_config_yaml(hass)
|
|
|
|
|
|
|
|
error_records = [
|
2023-11-16 09:56:47 +00:00
|
|
|
record.message
|
2023-11-16 08:08:47 +00:00
|
|
|
for record in caplog.get_records("call")
|
|
|
|
if record.levelno == logging.ERROR
|
|
|
|
]
|
|
|
|
assert error_records == snapshot
|
|
|
|
|
|
|
|
|
2023-11-13 13:04:58 +00:00
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"config_dir",
|
|
|
|
[
|
|
|
|
"basic",
|
|
|
|
"basic_include",
|
|
|
|
"include_dir_list",
|
|
|
|
"include_dir_merge_list",
|
|
|
|
"packages_include_dir_named",
|
|
|
|
],
|
|
|
|
)
|
|
|
|
async def test_yaml_error(
|
|
|
|
hass: HomeAssistant,
|
|
|
|
caplog: pytest.LogCaptureFixture,
|
|
|
|
config_dir: str,
|
|
|
|
mock_iot_domain_integration: Integration,
|
|
|
|
mock_non_adr_0007_integration: None,
|
|
|
|
mock_adr_0007_integrations: list[Integration],
|
|
|
|
snapshot: SnapshotAssertion,
|
|
|
|
) -> None:
|
|
|
|
"""Test schema error in component."""
|
|
|
|
|
|
|
|
base_path = os.path.dirname(__file__)
|
|
|
|
hass.config.config_dir = os.path.join(
|
|
|
|
base_path, "fixtures", "core", "config", "yaml_errors", config_dir
|
|
|
|
)
|
|
|
|
with pytest.raises(HomeAssistantError) as exc_info:
|
|
|
|
await config_util.async_hass_config_yaml(hass)
|
|
|
|
assert str(exc_info.value).replace(base_path, "<BASE_PATH>") == snapshot
|
|
|
|
|
|
|
|
error_records = [
|
|
|
|
record.message.replace(base_path, "<BASE_PATH>")
|
|
|
|
for record in caplog.get_records("call")
|
|
|
|
if record.levelno == logging.ERROR
|
|
|
|
]
|
|
|
|
assert error_records == snapshot
|
2023-12-04 11:48:49 +00:00
|
|
|
|
|
|
|
|
2024-02-10 19:16:20 +00:00
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"config_dir",
|
|
|
|
[
|
|
|
|
"packages_dict",
|
|
|
|
"packages_slug",
|
|
|
|
"packages_include_dir_named_dict",
|
|
|
|
"packages_include_dir_named_slug",
|
|
|
|
],
|
|
|
|
)
|
|
|
|
async def test_individual_packages_schema_validation_errors(
|
|
|
|
hass: HomeAssistant,
|
|
|
|
caplog: pytest.LogCaptureFixture,
|
|
|
|
config_dir: str,
|
|
|
|
mock_iot_domain_integration: Integration,
|
|
|
|
snapshot: SnapshotAssertion,
|
|
|
|
) -> None:
|
|
|
|
"""Tests syntactic errors in individual packages."""
|
|
|
|
|
|
|
|
base_path = os.path.dirname(__file__)
|
|
|
|
hass.config.config_dir = os.path.join(
|
|
|
|
base_path, "fixtures", "core", "config", "package_schema_validation", config_dir
|
|
|
|
)
|
|
|
|
|
|
|
|
config = await config_util.async_hass_config_yaml(hass)
|
|
|
|
|
|
|
|
error_records = [
|
|
|
|
record.message
|
|
|
|
for record in caplog.get_records("call")
|
|
|
|
if record.levelno == logging.ERROR
|
|
|
|
]
|
|
|
|
assert error_records == snapshot
|
|
|
|
|
|
|
|
assert len(config["iot_domain"]) == 1
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"config_dir",
|
|
|
|
[
|
|
|
|
"packages_is_a_list",
|
|
|
|
"packages_is_a_value",
|
|
|
|
"packages_is_null",
|
|
|
|
],
|
|
|
|
)
|
|
|
|
async def test_packages_schema_validation_error(
|
|
|
|
hass: HomeAssistant,
|
|
|
|
caplog: pytest.LogCaptureFixture,
|
|
|
|
config_dir: str,
|
|
|
|
snapshot: SnapshotAssertion,
|
|
|
|
) -> None:
|
|
|
|
"""Ensure that global package schema validation errors are logged."""
|
|
|
|
|
|
|
|
base_path = os.path.dirname(__file__)
|
|
|
|
hass.config.config_dir = os.path.join(
|
|
|
|
base_path,
|
|
|
|
"fixtures",
|
|
|
|
"core",
|
|
|
|
"config",
|
|
|
|
"package_schema_errors",
|
|
|
|
config_dir,
|
|
|
|
)
|
|
|
|
|
|
|
|
config = await config_util.async_hass_config_yaml(hass)
|
|
|
|
|
|
|
|
error_records = [
|
|
|
|
record.message
|
|
|
|
for record in caplog.get_records("call")
|
|
|
|
if record.levelno == logging.ERROR
|
|
|
|
]
|
|
|
|
assert error_records == snapshot
|
|
|
|
|
|
|
|
assert len(config[config_util.CONF_CORE][config_util.CONF_PACKAGES]) == 0
|
|
|
|
|
|
|
|
|
2023-12-04 11:48:49 +00:00
|
|
|
def test_extract_domain_configs() -> None:
|
|
|
|
"""Test the extraction of domain configuration."""
|
|
|
|
config = {
|
|
|
|
"zone": None,
|
|
|
|
"zoner": None,
|
|
|
|
"zone ": None,
|
|
|
|
"zone Hallo": None,
|
|
|
|
"zone 100": None,
|
|
|
|
}
|
|
|
|
|
|
|
|
assert {"zone", "zone Hallo", "zone 100"} == set(
|
|
|
|
config_util.extract_domain_configs(config, "zone")
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def test_config_per_platform() -> None:
|
|
|
|
"""Test config per platform method."""
|
|
|
|
config = OrderedDict(
|
|
|
|
[
|
|
|
|
("zone", {"platform": "hello"}),
|
|
|
|
("zoner", None),
|
|
|
|
("zone Hallo", [1, {"platform": "hello 2"}]),
|
|
|
|
("zone 100", None),
|
|
|
|
]
|
|
|
|
)
|
|
|
|
|
|
|
|
assert [
|
|
|
|
("hello", config["zone"]),
|
|
|
|
(None, 1),
|
|
|
|
("hello 2", config["zone Hallo"][1]),
|
|
|
|
] == list(config_util.config_per_platform(config, "zone"))
|
2024-03-03 09:03:35 +00:00
|
|
|
|
|
|
|
|
|
|
|
def test_extract_platform_integrations() -> None:
|
|
|
|
"""Test extract_platform_integrations."""
|
|
|
|
config = OrderedDict(
|
|
|
|
[
|
|
|
|
(b"zone", {"platform": "not str"}),
|
|
|
|
("zone", {"platform": "hello"}),
|
|
|
|
("zonex", []),
|
|
|
|
("zoney", ""),
|
|
|
|
("notzone", {"platform": "nothello"}),
|
|
|
|
("zoner", None),
|
|
|
|
("zone Hallo", [1, {"platform": "hello 2"}]),
|
|
|
|
("zone 100", None),
|
|
|
|
("i n v a-@@", None),
|
|
|
|
("i n v a-@@", {"platform": "hello"}),
|
|
|
|
("zoneq", "pig"),
|
|
|
|
("zoneempty", {"platform": ""}),
|
|
|
|
]
|
|
|
|
)
|
|
|
|
assert config_util.extract_platform_integrations(config, {"zone"}) == {
|
|
|
|
"hello",
|
|
|
|
"hello 2",
|
|
|
|
}
|
|
|
|
assert config_util.extract_platform_integrations(config, {"zonex"}) == set()
|
|
|
|
assert config_util.extract_platform_integrations(config, {"zoney"}) == set()
|
|
|
|
assert config_util.extract_platform_integrations(
|
|
|
|
config, {"zone", "not_valid", "notzone"}
|
|
|
|
) == {"hello", "hello 2", "nothello"}
|
|
|
|
assert config_util.extract_platform_integrations(config, {"zoneq"}) == set()
|
|
|
|
assert config_util.extract_platform_integrations(config, {"zoneempty"}) == set()
|