diff --git a/pylint/plugins/hass_enforce_type_hints.py b/pylint/plugins/hass_enforce_type_hints.py index b117a427646..5ee0489e8c4 100644 --- a/pylint/plugins/hass_enforce_type_hints.py +++ b/pylint/plugins/hass_enforce_type_hints.py @@ -109,6 +109,9 @@ _TEST_FIXTURES: dict[str, list[str] | str] = { "hass_admin_user": "MockUser", "hass_client": "ClientSessionGenerator", "hass_client_no_auth": "ClientSessionGenerator", + "hass_config": "ConfigType | None", + "hass_config_yaml": "str | None", + "hass_config_yaml_files": "dict[str, str] | None", "hass_owner_user": "MockUser", "hass_read_only_access_token": "str", "hass_read_only_user": "MockUser", @@ -126,6 +129,8 @@ _TEST_FIXTURES: dict[str, list[str] | str] = { "mock_bluetooth_adapters": "None", "mock_device_tracker_conf": "list[Device]", "mock_get_source_ip": "None", + "mock_hass_config": "None", + "mock_hass_config_yaml": "None", "mock_zeroconf": "None", "mqtt_client_mock": "MqttMockPahoClient", "mqtt_mock": "MqttMockHAClient", diff --git a/tests/components/homeassistant/triggers/test_homeassistant.py b/tests/components/homeassistant/triggers/test_homeassistant.py index 231b81e3862..1bc28a92ba4 100644 --- a/tests/components/homeassistant/triggers/test_homeassistant.py +++ b/tests/components/homeassistant/triggers/test_homeassistant.py @@ -1,29 +1,39 @@ """The tests for the Event automation.""" -from unittest.mock import AsyncMock, patch +from unittest.mock import patch + +import pytest import homeassistant.components.automation as automation -from homeassistant.core import CoreState +from homeassistant.core import CoreState, HomeAssistant +from homeassistant.helpers.typing import ConfigType from homeassistant.setup import async_setup_component from tests.common import async_mock_service -async def test_if_fires_on_hass_start(hass): +@pytest.mark.parametrize( + "hass_config", + [ + { + automation.DOMAIN: { + "alias": "hello", + "trigger": {"platform": "homeassistant", "event": "start"}, + "action": { + "service": "test.automation", + "data_template": {"id": "{{ trigger.id}}"}, + }, + } + } + ], +) +async def test_if_fires_on_hass_start( + hass: HomeAssistant, mock_hass_config: None, hass_config: ConfigType | None +) -> None: """Test the firing when Home Assistant starts.""" calls = async_mock_service(hass, "test", "automation") hass.state = CoreState.not_running - config = { - automation.DOMAIN: { - "alias": "hello", - "trigger": {"platform": "homeassistant", "event": "start"}, - "action": { - "service": "test.automation", - "data_template": {"id": "{{ trigger.id}}"}, - }, - } - } - assert await async_setup_component(hass, automation.DOMAIN, config) + assert await async_setup_component(hass, automation.DOMAIN, hass_config) assert automation.is_on(hass, "automation.hello") assert len(calls) == 0 @@ -32,13 +42,9 @@ async def test_if_fires_on_hass_start(hass): assert automation.is_on(hass, "automation.hello") assert len(calls) == 1 - with patch( - "homeassistant.config.async_hass_config_yaml", - AsyncMock(return_value=config), - ): - await hass.services.async_call( - automation.DOMAIN, automation.SERVICE_RELOAD, blocking=True - ) + await hass.services.async_call( + automation.DOMAIN, automation.SERVICE_RELOAD, blocking=True + ) assert automation.is_on(hass, "automation.hello") assert len(calls) == 1 diff --git a/tests/components/knx/test_diagnostic.py b/tests/components/knx/test_diagnostic.py index 0c690c4f241..99ce4399891 100644 --- a/tests/components/knx/test_diagnostic.py +++ b/tests/components/knx/test_diagnostic.py @@ -1,6 +1,6 @@ """Tests for the diagnostics data provided by the KNX integration.""" -from unittest.mock import patch +import pytest from xknx.io import DEFAULT_MCAST_GRP, DEFAULT_MCAST_PORT from homeassistant.components.knx.const import ( @@ -29,37 +29,40 @@ from tests.components.diagnostics import get_diagnostics_for_config_entry from tests.typing import ClientSessionGenerator +@pytest.mark.parametrize("hass_config", [{}]) async def test_diagnostics( hass: HomeAssistant, hass_client: ClientSessionGenerator, mock_config_entry: MockConfigEntry, knx: KNXTestKit, + mock_hass_config: None, ) -> None: """Test diagnostics.""" await knx.setup_integration({}) - with patch("homeassistant.config.async_hass_config_yaml", return_value={}): - # Overwrite the version for this test since we don't want to change this with every library bump - knx.xknx.version = "1.0.0" - assert await get_diagnostics_for_config_entry( - hass, hass_client, mock_config_entry - ) == { - "config_entry_data": { - "connection_type": "automatic", - "individual_address": "0.0.240", - "multicast_group": "224.0.23.12", - "multicast_port": 3671, - "rate_limit": 0, - "state_updater": True, - }, - "configuration_error": None, - "configuration_yaml": None, - "xknx": {"current_address": "0.0.0", "version": "1.0.0"}, - } + # Overwrite the version for this test since we don't want to change this with every library bump + knx.xknx.version = "1.0.0" + assert await get_diagnostics_for_config_entry( + hass, hass_client, mock_config_entry + ) == { + "config_entry_data": { + "connection_type": "automatic", + "individual_address": "0.0.240", + "multicast_group": "224.0.23.12", + "multicast_port": 3671, + "rate_limit": 0, + "state_updater": True, + }, + "configuration_error": None, + "configuration_yaml": None, + "xknx": {"current_address": "0.0.0", "version": "1.0.0"}, + } +@pytest.mark.parametrize("hass_config", [{"knx": {"wrong_key": {}}}]) async def test_diagnostic_config_error( hass: HomeAssistant, + mock_hass_config: None, hass_client: ClientSessionGenerator, mock_config_entry: MockConfigEntry, knx: KNXTestKit, @@ -67,32 +70,30 @@ async def test_diagnostic_config_error( """Test diagnostics.""" await knx.setup_integration({}) - with patch( - "homeassistant.config.async_hass_config_yaml", - return_value={"knx": {"wrong_key": {}}}, - ): - # Overwrite the version for this test since we don't want to change this with every library bump - knx.xknx.version = "1.0.0" - assert await get_diagnostics_for_config_entry( - hass, hass_client, mock_config_entry - ) == { - "config_entry_data": { - "connection_type": "automatic", - "individual_address": "0.0.240", - "multicast_group": "224.0.23.12", - "multicast_port": 3671, - "rate_limit": 0, - "state_updater": True, - }, - "configuration_error": "extra keys not allowed @ data['knx']['wrong_key']", - "configuration_yaml": {"wrong_key": {}}, - "xknx": {"current_address": "0.0.0", "version": "1.0.0"}, - } + # Overwrite the version for this test since we don't want to change this with every library bump + knx.xknx.version = "1.0.0" + assert await get_diagnostics_for_config_entry( + hass, hass_client, mock_config_entry + ) == { + "config_entry_data": { + "connection_type": "automatic", + "individual_address": "0.0.240", + "multicast_group": "224.0.23.12", + "multicast_port": 3671, + "rate_limit": 0, + "state_updater": True, + }, + "configuration_error": "extra keys not allowed @ data['knx']['wrong_key']", + "configuration_yaml": {"wrong_key": {}}, + "xknx": {"current_address": "0.0.0", "version": "1.0.0"}, + } +@pytest.mark.parametrize("hass_config", [{}]) async def test_diagnostic_redact( hass: HomeAssistant, hass_client: ClientSessionGenerator, + mock_hass_config: None, ) -> None: """Test diagnostics redacting data.""" mock_config_entry: MockConfigEntry = MockConfigEntry( @@ -114,25 +115,24 @@ async def test_diagnostic_redact( knx: KNXTestKit = KNXTestKit(hass, mock_config_entry) await knx.setup_integration({}) - with patch("homeassistant.config.async_hass_config_yaml", return_value={}): - # Overwrite the version for this test since we don't want to change this with every library bump - knx.xknx.version = "1.0.0" - assert await get_diagnostics_for_config_entry( - hass, hass_client, mock_config_entry - ) == { - "config_entry_data": { - "connection_type": "automatic", - "individual_address": "0.0.240", - "multicast_group": "224.0.23.12", - "multicast_port": 3671, - "rate_limit": 0, - "state_updater": True, - "knxkeys_password": "**REDACTED**", - "user_password": "**REDACTED**", - "device_authentication": "**REDACTED**", - "backbone_key": "**REDACTED**", - }, - "configuration_error": None, - "configuration_yaml": None, - "xknx": {"current_address": "0.0.0", "version": "1.0.0"}, - } + # Overwrite the version for this test since we don't want to change this with every library bump + knx.xknx.version = "1.0.0" + assert await get_diagnostics_for_config_entry( + hass, hass_client, mock_config_entry + ) == { + "config_entry_data": { + "connection_type": "automatic", + "individual_address": "0.0.240", + "multicast_group": "224.0.23.12", + "multicast_port": 3671, + "rate_limit": 0, + "state_updater": True, + "knxkeys_password": "**REDACTED**", + "user_password": "**REDACTED**", + "device_authentication": "**REDACTED**", + "backbone_key": "**REDACTED**", + }, + "configuration_error": None, + "configuration_yaml": None, + "xknx": {"current_address": "0.0.0", "version": "1.0.0"}, + } diff --git a/tests/conftest.py b/tests/conftest.py index 5a46971730b..2717edf7021 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -44,6 +44,7 @@ from homeassistant.components.websocket_api.auth import ( TYPE_AUTH_REQUIRED, ) from homeassistant.components.websocket_api.http import URL +from homeassistant.config import YAML_CONFIG_FILE from homeassistant.config_entries import ConfigEntry from homeassistant.const import HASSIO_USER_NAME from homeassistant.core import CoreState, HomeAssistant @@ -90,6 +91,7 @@ from .common import ( # noqa: E402, isort:skip get_test_home_assistant, init_recorder_component, mock_storage, + patch_yaml_files, ) from .test_util.aiohttp import ( # noqa: E402, isort:skip AiohttpClientMocker, @@ -917,6 +919,66 @@ async def _mqtt_mock_entry( yield _setup_mqtt_entry +@pytest.fixture +def hass_config() -> ConfigType | None: + """Fixture to parametrize the content of main configuration using mock_hass_config. + + To set a configuration, tests can be marked with: + @pytest.mark.parametrize("hass_config", [{integration: {...}}]) + Add the `mock_hass_config: None` fixture to the test. + """ + return None + + +@pytest.fixture +def mock_hass_config( + hass: HomeAssistant, hass_config: ConfigType | None +) -> Generator[None, None, None]: + """Fixture to mock the content of main configuration. + + Patches homeassistant.config.load_yaml_config_file with `hass_config` parameterized as content. + """ + with patch("homeassistant.config.load_yaml_config_file", return_value=hass_config): + yield + + +@pytest.fixture +def hass_config_yaml() -> str | None: + """Fixture to parametrize the content of configuration.yaml file. + + To set yaml content, tests can be marked with: + @pytest.mark.parametrize("hass_config_yaml", ["..."]) + Add the `mock_hass_config_yaml: None` fixture to the test. + """ + return None + + +@pytest.fixture +def hass_config_yaml_files(hass_config_yaml: str | None) -> dict[str, str] | None: + """Fixture to parametrize multiple yaml configuration files. + + To set the YAML files to patch, tests can be marked with: + @pytest.mark.parametrize( + "hass_config_yaml_files", [{"configuration.yaml": "..."}] + ) + Add the `mock_hass_config_yaml: None` fixture to the test. + """ + return None if hass_config_yaml is None else {YAML_CONFIG_FILE: hass_config_yaml} + + +@pytest.fixture +def mock_hass_config_yaml( + hass: HomeAssistant, hass_config_yaml_files: dict[str, str] | None +) -> Generator[None, None, None]: + """Fixture to mock the content of the yaml configuration files. + + Patches yaml configuration files using the `hass_config_yaml` + and `hass_config_yaml_files` fixtures. + """ + with patch_yaml_files(hass_config_yaml_files): + yield + + @pytest.fixture async def mqtt_mock_entry_no_yaml_config( hass: HomeAssistant, diff --git a/tests/scripts/test_check_config.py b/tests/scripts/test_check_config.py index 1e6f1c184be..44a4d55d545 100644 --- a/tests/scripts/test_check_config.py +++ b/tests/scripts/test_check_config.py @@ -6,7 +6,7 @@ import pytest from homeassistant.config import YAML_CONFIG_FILE import homeassistant.scripts.check_config as check_config -from tests.common import get_test_config_dir, patch_yaml_files +from tests.common import get_test_config_dir BASE_CONFIG = ( "homeassistant:\n" @@ -43,114 +43,118 @@ def normalize_yaml_files(check_dict): return [key.replace(root, "...") for key in sorted(check_dict["yaml_files"].keys())] -def test_bad_core_config(mock_is_file, event_loop) -> None: +@pytest.mark.parametrize("hass_config_yaml", [BAD_CORE_CONFIG]) +def test_bad_core_config(mock_is_file, event_loop, mock_hass_config_yaml: None) -> None: """Test a bad core config setup.""" - files = {YAML_CONFIG_FILE: BAD_CORE_CONFIG} - with patch_yaml_files(files): - res = check_config.check(get_test_config_dir()) - assert res["except"].keys() == {"homeassistant"} - assert res["except"]["homeassistant"][1] == {"unit_system": "bad"} + res = check_config.check(get_test_config_dir()) + assert res["except"].keys() == {"homeassistant"} + assert res["except"]["homeassistant"][1] == {"unit_system": "bad"} -def test_config_platform_valid(mock_is_file, event_loop) -> None: +@pytest.mark.parametrize("hass_config_yaml", [BASE_CONFIG + "light:\n platform: demo"]) +def test_config_platform_valid( + mock_is_file, event_loop, mock_hass_config_yaml: None +) -> None: """Test a valid platform setup.""" - files = {YAML_CONFIG_FILE: BASE_CONFIG + "light:\n platform: demo"} - with patch_yaml_files(files): - res = check_config.check(get_test_config_dir()) - assert res["components"].keys() == {"homeassistant", "light"} - assert res["components"]["light"] == [{"platform": "demo"}] - assert res["except"] == {} - assert res["secret_cache"] == {} - assert res["secrets"] == {} - assert len(res["yaml_files"]) == 1 + res = check_config.check(get_test_config_dir()) + assert res["components"].keys() == {"homeassistant", "light"} + assert res["components"]["light"] == [{"platform": "demo"}] + assert res["except"] == {} + assert res["secret_cache"] == {} + assert res["secrets"] == {} + assert len(res["yaml_files"]) == 1 -def test_component_platform_not_found(mock_is_file, event_loop) -> None: +@pytest.mark.parametrize( + ("hass_config_yaml", "platforms", "error"), + [ + ( + BASE_CONFIG + "beer:", + {"homeassistant"}, + "Integration error: beer - Integration 'beer' not found.", + ), + ( + BASE_CONFIG + "light:\n platform: beer", + {"homeassistant", "light"}, + "Platform error light.beer - Integration 'beer' not found.", + ), + ], +) +def test_component_platform_not_found( + mock_is_file, event_loop, mock_hass_config_yaml: None, platforms, error +) -> None: """Test errors if component or platform not found.""" # Make sure they don't exist - files = {YAML_CONFIG_FILE: BASE_CONFIG + "beer:"} - with patch_yaml_files(files): - res = check_config.check(get_test_config_dir()) - assert res["components"].keys() == {"homeassistant"} - assert res["except"] == { - check_config.ERROR_STR: [ - "Integration error: beer - Integration 'beer' not found." - ] + res = check_config.check(get_test_config_dir()) + assert res["components"].keys() == platforms + assert res["except"] == {check_config.ERROR_STR: [error]} + assert res["secret_cache"] == {} + assert res["secrets"] == {} + assert len(res["yaml_files"]) == 1 + + +@pytest.mark.parametrize( + "hass_config_yaml_files", + [ + { + get_test_config_dir(YAML_CONFIG_FILE): BASE_CONFIG + + "http:\n cors_allowed_origins: !secret http_pw", + get_test_config_dir( + "secrets.yaml" + ): "logger: debug\nhttp_pw: http://google.com", } - assert res["secret_cache"] == {} - assert res["secrets"] == {} - assert len(res["yaml_files"]) == 1 - - files = {YAML_CONFIG_FILE: BASE_CONFIG + "light:\n platform: beer"} - with patch_yaml_files(files): - res = check_config.check(get_test_config_dir()) - assert res["components"].keys() == {"homeassistant", "light"} - assert res["components"]["light"] == [] - assert res["except"] == { - check_config.ERROR_STR: [ - "Platform error light.beer - Integration 'beer' not found." - ] - } - assert res["secret_cache"] == {} - assert res["secrets"] == {} - assert len(res["yaml_files"]) == 1 - - -def test_secrets(mock_is_file, event_loop) -> None: + ], +) +def test_secrets(mock_is_file, event_loop, mock_hass_config_yaml: None) -> None: """Test secrets config checking method.""" - secrets_path = get_test_config_dir("secrets.yaml") + res = check_config.check(get_test_config_dir(), True) - files = { - get_test_config_dir(YAML_CONFIG_FILE): BASE_CONFIG - + "http:\n cors_allowed_origins: !secret http_pw", - secrets_path: "logger: debug\nhttp_pw: http://google.com", + assert res["except"] == {} + assert res["components"].keys() == {"homeassistant", "http"} + assert res["components"]["http"] == { + "cors_allowed_origins": ["http://google.com"], + "ip_ban_enabled": True, + "login_attempts_threshold": -1, + "server_port": 8123, + "ssl_profile": "modern", } - - with patch_yaml_files(files): - res = check_config.check(get_test_config_dir(), True) - - assert res["except"] == {} - assert res["components"].keys() == {"homeassistant", "http"} - assert res["components"]["http"] == { - "cors_allowed_origins": ["http://google.com"], - "ip_ban_enabled": True, - "login_attempts_threshold": -1, - "server_port": 8123, - "ssl_profile": "modern", - } - assert res["secret_cache"] == {secrets_path: {"http_pw": "http://google.com"}} - assert res["secrets"] == {"http_pw": "http://google.com"} - assert normalize_yaml_files(res) == [ - ".../configuration.yaml", - ".../secrets.yaml", - ] + assert res["secret_cache"] == { + get_test_config_dir("secrets.yaml"): {"http_pw": "http://google.com"} + } + assert res["secrets"] == {"http_pw": "http://google.com"} + assert normalize_yaml_files(res) == [ + ".../configuration.yaml", + ".../secrets.yaml", + ] -def test_package_invalid(mock_is_file, event_loop) -> None: +@pytest.mark.parametrize( + "hass_config_yaml", [BASE_CONFIG + ' packages:\n p1:\n group: ["a"]'] +) +def test_package_invalid(mock_is_file, event_loop, mock_hass_config_yaml: None) -> None: """Test an invalid package.""" - files = {YAML_CONFIG_FILE: BASE_CONFIG + ' packages:\n p1:\n group: ["a"]'} - with patch_yaml_files(files): - res = check_config.check(get_test_config_dir()) + res = check_config.check(get_test_config_dir()) - assert res["except"].keys() == {"homeassistant.packages.p1.group"} - assert res["except"]["homeassistant.packages.p1.group"][1] == {"group": ["a"]} - assert len(res["except"]) == 1 - assert res["components"].keys() == {"homeassistant"} - assert len(res["components"]) == 1 - assert res["secret_cache"] == {} - assert res["secrets"] == {} - assert len(res["yaml_files"]) == 1 + assert res["except"].keys() == {"homeassistant.packages.p1.group"} + assert res["except"]["homeassistant.packages.p1.group"][1] == {"group": ["a"]} + assert len(res["except"]) == 1 + assert res["components"].keys() == {"homeassistant"} + assert len(res["components"]) == 1 + assert res["secret_cache"] == {} + assert res["secrets"] == {} + assert len(res["yaml_files"]) == 1 -def test_bootstrap_error(event_loop) -> None: +@pytest.mark.parametrize( + "hass_config_yaml", [BASE_CONFIG + "automation: !include no.yaml"] +) +def test_bootstrap_error(event_loop, mock_hass_config_yaml: None) -> None: """Test a valid platform setup.""" - files = {YAML_CONFIG_FILE: BASE_CONFIG + "automation: !include no.yaml"} - with patch_yaml_files(files): - res = check_config.check(get_test_config_dir(YAML_CONFIG_FILE)) - err = res["except"].pop(check_config.ERROR_STR) - assert len(err) == 1 - assert res["except"] == {} - assert res["components"] == {} # No components, load failed - assert res["secret_cache"] == {} - assert res["secrets"] == {} - assert res["yaml_files"] == {} + res = check_config.check(get_test_config_dir(YAML_CONFIG_FILE)) + err = res["except"].pop(check_config.ERROR_STR) + assert len(err) == 1 + assert res["except"] == {} + assert res["components"] == {} # No components, load failed + assert res["secret_cache"] == {} + assert res["secrets"] == {} + assert res["yaml_files"] == {} diff --git a/tests/test_bootstrap.py b/tests/test_bootstrap.py index 48d161d5b57..bf10aa732a5 100644 --- a/tests/test_bootstrap.py +++ b/tests/test_bootstrap.py @@ -454,7 +454,9 @@ def mock_ensure_config_exists(): yield ensure_config_exists +@pytest.mark.parametrize("hass_config", [{"browser": {}, "frontend": {}}]) async def test_setup_hass( + mock_hass_config: None, mock_enable_logging, mock_is_virtual_env, mock_mount_local_lib_path, @@ -462,17 +464,14 @@ async def test_setup_hass( mock_process_ha_config_upgrade, caplog, event_loop, -): +) -> None: """Test it works.""" verbose = Mock() log_rotate_days = Mock() log_file = Mock() log_no_color = Mock() - with patch( - "homeassistant.config.async_hass_config_yaml", - return_value={"browser": {}, "frontend": {}}, - ), patch.object(bootstrap, "LOG_SLOW_STARTUP_INTERVAL", 5000): + with patch.object(bootstrap, "LOG_SLOW_STARTUP_INTERVAL", 5000): hass = await bootstrap.async_setup_hass( runner.RuntimeConfig( config_dir=get_test_config_dir(), @@ -505,7 +504,9 @@ async def test_setup_hass( assert hass == async_get_hass() +@pytest.mark.parametrize("hass_config", [{"browser": {}, "frontend": {}}]) async def test_setup_hass_takes_longer_than_log_slow_startup( + mock_hass_config: None, mock_enable_logging, mock_is_virtual_env, mock_mount_local_lib_path, @@ -513,7 +514,7 @@ async def test_setup_hass_takes_longer_than_log_slow_startup( mock_process_ha_config_upgrade, caplog, event_loop, -): +) -> None: """Test it works.""" verbose = Mock() log_rotate_days = Mock() @@ -524,10 +525,7 @@ async def test_setup_hass_takes_longer_than_log_slow_startup( await asyncio.sleep(0.6) return True - with patch( - "homeassistant.config.async_hass_config_yaml", - return_value={"browser": {}, "frontend": {}}, - ), patch.object(bootstrap, "LOG_SLOW_STARTUP_INTERVAL", 0.3), patch.object( + with patch.object(bootstrap, "LOG_SLOW_STARTUP_INTERVAL", 0.3), patch.object( bootstrap, "SLOW_STARTUP_CHECK_INTERVAL", 0.05 ), patch( "homeassistant.components.frontend.async_setup", @@ -555,7 +553,7 @@ async def test_setup_hass_invalid_yaml( mock_ensure_config_exists, mock_process_ha_config_upgrade, event_loop, -): +) -> None: """Test it works.""" with patch( "homeassistant.config.async_hass_config_yaml", side_effect=HomeAssistantError @@ -636,70 +634,71 @@ async def test_setup_hass_safe_mode( assert len(browser_setup.mock_calls) == 0 +@pytest.mark.parametrize("hass_config", [{"homeassistant": {"non-existing": 1}}]) async def test_setup_hass_invalid_core_config( + mock_hass_config: None, mock_enable_logging, mock_is_virtual_env, mock_mount_local_lib_path, mock_ensure_config_exists, mock_process_ha_config_upgrade, event_loop, -): +) -> None: """Test it works.""" - with patch( - "homeassistant.config.async_hass_config_yaml", - return_value={"homeassistant": {"non-existing": 1}}, - ): - hass = await bootstrap.async_setup_hass( - runner.RuntimeConfig( - config_dir=get_test_config_dir(), - verbose=False, - log_rotate_days=10, - log_file="", - log_no_color=False, - skip_pip=True, - safe_mode=False, - ), - ) + hass = await bootstrap.async_setup_hass( + runner.RuntimeConfig( + config_dir=get_test_config_dir(), + verbose=False, + log_rotate_days=10, + log_file="", + log_no_color=False, + skip_pip=True, + safe_mode=False, + ), + ) assert "safe_mode" in hass.config.components -async def test_setup_safe_mode_if_no_frontend( - mock_enable_logging, - mock_is_virtual_env, - mock_mount_local_lib_path, - mock_ensure_config_exists, - mock_process_ha_config_upgrade, - event_loop, -): - """Test we setup safe mode if frontend didn't load.""" - verbose = Mock() - log_rotate_days = Mock() - log_file = Mock() - log_no_color = Mock() - - with patch( - "homeassistant.config.async_hass_config_yaml", - return_value={ +@pytest.mark.parametrize( + "hass_config", + [ + { "homeassistant": { "internal_url": "http://192.168.1.100:8123", "external_url": "https://abcdef.ui.nabu.casa", }, "map": {}, "person": {"invalid": True}, - }, - ): - hass = await bootstrap.async_setup_hass( - runner.RuntimeConfig( - config_dir=get_test_config_dir(), - verbose=verbose, - log_rotate_days=log_rotate_days, - log_file=log_file, - log_no_color=log_no_color, - skip_pip=True, - safe_mode=False, - ), - ) + } + ], +) +async def test_setup_safe_mode_if_no_frontend( + mock_hass_config: None, + mock_enable_logging, + mock_is_virtual_env, + mock_mount_local_lib_path, + mock_ensure_config_exists, + mock_process_ha_config_upgrade, + event_loop, +) -> None: + """Test we setup safe mode if frontend didn't load.""" + verbose = Mock() + log_rotate_days = Mock() + log_file = Mock() + log_no_color = Mock() + + hass = await bootstrap.async_setup_hass( + runner.RuntimeConfig( + config_dir=get_test_config_dir(), + verbose=verbose, + log_rotate_days=log_rotate_days, + log_file=log_file, + log_no_color=log_no_color, + skip_pip=True, + safe_mode=False, + ), + ) assert "safe_mode" in hass.config.components assert hass.config.config_dir == get_test_config_dir() diff --git a/tests/test_config.py b/tests/test_config.py index 606770d4923..5dfd674dafe 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -40,7 +40,7 @@ from homeassistant.util.unit_system import ( ) from homeassistant.util.yaml import SECRET_YAML -from .common import MockUser, get_test_config_dir, patch_yaml_files +from .common import MockUser, get_test_config_dir CONFIG_DIR = get_test_config_dir() YAML_PATH = os.path.join(CONFIG_DIR, config_util.YAML_CONFIG_FILE) @@ -736,20 +736,25 @@ async def test_check_ha_config_file_wrong(mock_check, hass): assert await config_util.async_check_ha_config_file(hass) == "bad" -@patch("homeassistant.config.os.path.isfile", mock.Mock(return_value=True)) -async def test_async_hass_config_yaml_merge(merge_log_err, hass): +@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: """Test merge during async config reload.""" - config = { - config_util.CONF_CORE: { - config_util.CONF_PACKAGES: {"pack_dict": {"input_boolean": {"ib1": None}}} - }, - "input_boolean": {"ib2": None}, - "light": {"platform": "test"}, - } - - files = {config_util.YAML_CONFIG_FILE: yaml.dump(config)} - with patch_yaml_files(files, True): - conf = await config_util.async_hass_config_yaml(hass) + conf = await config_util.async_hass_config_yaml(hass) assert merge_log_err.call_count == 0 assert conf[config_util.CONF_CORE].get(config_util.CONF_PACKAGES) is not None diff --git a/tests/util/yaml/test_init.py b/tests/util/yaml/test_init.py index aa8276ea2fd..18aca8ea5f5 100644 --- a/tests/util/yaml/test_init.py +++ b/tests/util/yaml/test_init.py @@ -3,6 +3,7 @@ import importlib import io import os import pathlib +from typing import Any import unittest from unittest.mock import patch @@ -67,17 +68,17 @@ def test_simple_dict(try_both_loaders): assert doc["key"] == "value" -def test_unhashable_key() -> None: +@pytest.mark.parametrize("hass_config_yaml", ["message:\n {{ states.state }}"]) +def test_unhashable_key(mock_hass_config_yaml: None) -> None: """Test an unhashable key.""" - files = {YAML_CONFIG_FILE: "message:\n {{ states.state }}"} - with pytest.raises(HomeAssistantError), patch_yaml_files(files): + with pytest.raises(HomeAssistantError): load_yaml_config_file(YAML_CONFIG_FILE) -def test_no_key(try_both_loaders): +@pytest.mark.parametrize("hass_config_yaml", ["a: a\nnokeyhere"]) +def test_no_key(try_both_loaders, mock_hass_config_yaml: None) -> None: """Test item without a key.""" - files = {YAML_CONFIG_FILE: "a: a\nnokeyhere"} - with pytest.raises(HomeAssistantError), patch_yaml_files(files): + with pytest.raises(HomeAssistantError): yaml.load_yaml(YAML_CONFIG_FILE) @@ -106,35 +107,50 @@ def test_invalid_environment_variable(try_both_loaders): yaml_loader.yaml.load(file, Loader=yaml_loader.SafeLineLoader) -def test_include_yaml(try_both_loaders): +@pytest.mark.parametrize( + ("hass_config_yaml_files", "value"), + [({"test.yaml": "value"}, "value"), ({"test.yaml": None}, {})], +) +def test_include_yaml( + try_both_loaders, mock_hass_config_yaml: None, value: Any +) -> None: """Test include yaml.""" - with patch_yaml_files({"test.yaml": "value"}): - conf = "key: !include test.yaml" - with io.StringIO(conf) as file: - doc = yaml_loader.yaml.load(file, Loader=yaml_loader.SafeLineLoader) - assert doc["key"] == "value" - - with patch_yaml_files({"test.yaml": None}): - conf = "key: !include test.yaml" - with io.StringIO(conf) as file: - doc = yaml_loader.yaml.load(file, Loader=yaml_loader.SafeLineLoader) - assert doc["key"] == {} + conf = "key: !include test.yaml" + with io.StringIO(conf) as file: + doc = yaml_loader.yaml.load(file, Loader=yaml_loader.SafeLineLoader) + assert doc["key"] == value @patch("homeassistant.util.yaml.loader.os.walk") -def test_include_dir_list(mock_walk, try_both_loaders): +@pytest.mark.parametrize( + "hass_config_yaml_files", [{"/test/one.yaml": "one", "/test/two.yaml": "two"}] +) +def test_include_dir_list( + mock_walk, try_both_loaders, mock_hass_config_yaml: None +) -> None: """Test include dir list yaml.""" mock_walk.return_value = [["/test", [], ["two.yaml", "one.yaml"]]] - with patch_yaml_files({"/test/one.yaml": "one", "/test/two.yaml": "two"}): - conf = "key: !include_dir_list /test" - with io.StringIO(conf) as file: - doc = yaml_loader.yaml.load(file, Loader=yaml_loader.SafeLineLoader) - assert doc["key"] == sorted(["one", "two"]) + conf = "key: !include_dir_list /test" + with io.StringIO(conf) as file: + doc = yaml_loader.yaml.load(file, Loader=yaml_loader.SafeLineLoader) + assert doc["key"] == sorted(["one", "two"]) @patch("homeassistant.util.yaml.loader.os.walk") -def test_include_dir_list_recursive(mock_walk, try_both_loaders): +@pytest.mark.parametrize( + "hass_config_yaml_files", + [ + { + "/test/zero.yaml": "zero", + "/test/tmp2/one.yaml": "one", + "/test/tmp2/two.yaml": "two", + } + ], +) +def test_include_dir_list_recursive( + mock_walk, try_both_loaders, mock_hass_config_yaml: None +) -> None: """Test include dir recursive list yaml.""" mock_walk.return_value = [ ["/test", ["tmp2", ".ignore", "ignore"], ["zero.yaml"]], @@ -142,41 +158,49 @@ def test_include_dir_list_recursive(mock_walk, try_both_loaders): ["/test/ignore", [], [".ignore.yaml"]], ] - with patch_yaml_files( - { - "/test/zero.yaml": "zero", - "/test/tmp2/one.yaml": "one", - "/test/tmp2/two.yaml": "two", - } - ): - conf = "key: !include_dir_list /test" - with io.StringIO(conf) as file: - assert ( - ".ignore" in mock_walk.return_value[0][1] - ), "Expecting .ignore in here" - doc = yaml_loader.yaml.load(file, Loader=yaml_loader.SafeLineLoader) - assert "tmp2" in mock_walk.return_value[0][1] - assert ".ignore" not in mock_walk.return_value[0][1] - assert sorted(doc["key"]) == sorted(["zero", "one", "two"]) + conf = "key: !include_dir_list /test" + with io.StringIO(conf) as file: + assert ".ignore" in mock_walk.return_value[0][1], "Expecting .ignore in here" + doc = yaml_loader.yaml.load(file, Loader=yaml_loader.SafeLineLoader) + assert "tmp2" in mock_walk.return_value[0][1] + assert ".ignore" not in mock_walk.return_value[0][1] + assert sorted(doc["key"]) == sorted(["zero", "one", "two"]) @patch("homeassistant.util.yaml.loader.os.walk") -def test_include_dir_named(mock_walk, try_both_loaders): +@pytest.mark.parametrize( + "hass_config_yaml_files", + [{"/test/first.yaml": "one", "/test/second.yaml": "two"}], +) +def test_include_dir_named( + mock_walk, try_both_loaders, mock_hass_config_yaml: None +) -> None: """Test include dir named yaml.""" mock_walk.return_value = [ ["/test", [], ["first.yaml", "second.yaml", "secrets.yaml"]] ] - with patch_yaml_files({"/test/first.yaml": "one", "/test/second.yaml": "two"}): - conf = "key: !include_dir_named /test" - correct = {"first": "one", "second": "two"} - with io.StringIO(conf) as file: - doc = yaml_loader.yaml.load(file, Loader=yaml_loader.SafeLineLoader) - assert doc["key"] == correct + conf = "key: !include_dir_named /test" + correct = {"first": "one", "second": "two"} + with io.StringIO(conf) as file: + doc = yaml_loader.yaml.load(file, Loader=yaml_loader.SafeLineLoader) + assert doc["key"] == correct @patch("homeassistant.util.yaml.loader.os.walk") -def test_include_dir_named_recursive(mock_walk, try_both_loaders): +@pytest.mark.parametrize( + "hass_config_yaml_files", + [ + { + "/test/first.yaml": "one", + "/test/tmp2/second.yaml": "two", + "/test/tmp2/third.yaml": "three", + } + ], +) +def test_include_dir_named_recursive( + mock_walk, try_both_loaders, mock_hass_config_yaml: None +) -> None: """Test include dir named yaml.""" mock_walk.return_value = [ ["/test", ["tmp2", ".ignore", "ignore"], ["first.yaml"]], @@ -184,85 +208,99 @@ def test_include_dir_named_recursive(mock_walk, try_both_loaders): ["/test/ignore", [], [".ignore.yaml"]], ] - with patch_yaml_files( - { - "/test/first.yaml": "one", - "/test/tmp2/second.yaml": "two", - "/test/tmp2/third.yaml": "three", - } - ): - conf = "key: !include_dir_named /test" - correct = {"first": "one", "second": "two", "third": "three"} - with io.StringIO(conf) as file: - assert ( - ".ignore" in mock_walk.return_value[0][1] - ), "Expecting .ignore in here" - doc = yaml_loader.yaml.load(file, Loader=yaml_loader.SafeLineLoader) - assert "tmp2" in mock_walk.return_value[0][1] - assert ".ignore" not in mock_walk.return_value[0][1] - assert doc["key"] == correct + conf = "key: !include_dir_named /test" + correct = {"first": "one", "second": "two", "third": "three"} + with io.StringIO(conf) as file: + assert ".ignore" in mock_walk.return_value[0][1], "Expecting .ignore in here" + doc = yaml_loader.yaml.load(file, Loader=yaml_loader.SafeLineLoader) + assert "tmp2" in mock_walk.return_value[0][1] + assert ".ignore" not in mock_walk.return_value[0][1] + assert doc["key"] == correct @patch("homeassistant.util.yaml.loader.os.walk") -def test_include_dir_merge_list(mock_walk, try_both_loaders): +@pytest.mark.parametrize( + "hass_config_yaml_files", + [{"/test/first.yaml": "- one", "/test/second.yaml": "- two\n- three"}], +) +def test_include_dir_merge_list( + mock_walk, try_both_loaders, mock_hass_config_yaml: None +) -> None: """Test include dir merge list yaml.""" mock_walk.return_value = [["/test", [], ["first.yaml", "second.yaml"]]] - with patch_yaml_files( - {"/test/first.yaml": "- one", "/test/second.yaml": "- two\n- three"} - ): - conf = "key: !include_dir_merge_list /test" - with io.StringIO(conf) as file: - doc = yaml_loader.yaml.load(file, Loader=yaml_loader.SafeLineLoader) - assert sorted(doc["key"]) == sorted(["one", "two", "three"]) + conf = "key: !include_dir_merge_list /test" + with io.StringIO(conf) as file: + doc = yaml_loader.yaml.load(file, Loader=yaml_loader.SafeLineLoader) + assert sorted(doc["key"]) == sorted(["one", "two", "three"]) @patch("homeassistant.util.yaml.loader.os.walk") -def test_include_dir_merge_list_recursive(mock_walk, try_both_loaders): - """Test include dir merge list yaml.""" - mock_walk.return_value = [ - ["/test", ["tmp2", ".ignore", "ignore"], ["first.yaml"]], - ["/test/tmp2", [], ["second.yaml", "third.yaml"]], - ["/test/ignore", [], [".ignore.yaml"]], - ] - - with patch_yaml_files( +@pytest.mark.parametrize( + "hass_config_yaml_files", + [ { "/test/first.yaml": "- one", "/test/tmp2/second.yaml": "- two", "/test/tmp2/third.yaml": "- three\n- four", } - ): - conf = "key: !include_dir_merge_list /test" - with io.StringIO(conf) as file: - assert ( - ".ignore" in mock_walk.return_value[0][1] - ), "Expecting .ignore in here" - doc = yaml_loader.yaml.load(file, Loader=yaml_loader.SafeLineLoader) - assert "tmp2" in mock_walk.return_value[0][1] - assert ".ignore" not in mock_walk.return_value[0][1] - assert sorted(doc["key"]) == sorted(["one", "two", "three", "four"]) + ], +) +def test_include_dir_merge_list_recursive( + mock_walk, try_both_loaders, mock_hass_config_yaml: None +) -> None: + """Test include dir merge list yaml.""" + mock_walk.return_value = [ + ["/test", ["tmp2", ".ignore", "ignore"], ["first.yaml"]], + ["/test/tmp2", [], ["second.yaml", "third.yaml"]], + ["/test/ignore", [], [".ignore.yaml"]], + ] + + conf = "key: !include_dir_merge_list /test" + with io.StringIO(conf) as file: + assert ".ignore" in mock_walk.return_value[0][1], "Expecting .ignore in here" + doc = yaml_loader.yaml.load(file, Loader=yaml_loader.SafeLineLoader) + assert "tmp2" in mock_walk.return_value[0][1] + assert ".ignore" not in mock_walk.return_value[0][1] + assert sorted(doc["key"]) == sorted(["one", "two", "three", "four"]) @patch("homeassistant.util.yaml.loader.os.walk") -def test_include_dir_merge_named(mock_walk, try_both_loaders): +@pytest.mark.parametrize( + "hass_config_yaml_files", + [ + { + "/test/first.yaml": "key1: one", + "/test/second.yaml": "key2: two\nkey3: three", + } + ], +) +def test_include_dir_merge_named( + mock_walk, try_both_loaders, mock_hass_config_yaml: None +) -> None: """Test include dir merge named yaml.""" mock_walk.return_value = [["/test", [], ["first.yaml", "second.yaml"]]] - files = { - "/test/first.yaml": "key1: one", - "/test/second.yaml": "key2: two\nkey3: three", - } - - with patch_yaml_files(files): - conf = "key: !include_dir_merge_named /test" - with io.StringIO(conf) as file: - doc = yaml_loader.yaml.load(file, Loader=yaml_loader.SafeLineLoader) - assert doc["key"] == {"key1": "one", "key2": "two", "key3": "three"} + conf = "key: !include_dir_merge_named /test" + with io.StringIO(conf) as file: + doc = yaml_loader.yaml.load(file, Loader=yaml_loader.SafeLineLoader) + assert doc["key"] == {"key1": "one", "key2": "two", "key3": "three"} @patch("homeassistant.util.yaml.loader.os.walk") -def test_include_dir_merge_named_recursive(mock_walk, try_both_loaders): +@pytest.mark.parametrize( + "hass_config_yaml_files", + [ + { + "/test/first.yaml": "key1: one", + "/test/tmp2/second.yaml": "key2: two", + "/test/tmp2/third.yaml": "key3: three\nkey4: four", + } + ], +) +def test_include_dir_merge_named_recursive( + mock_walk, try_both_loaders, mock_hass_config_yaml: None +) -> None: """Test include dir merge named yaml.""" mock_walk.return_value = [ ["/test", ["tmp2", ".ignore", "ignore"], ["first.yaml"]], @@ -270,27 +308,18 @@ def test_include_dir_merge_named_recursive(mock_walk, try_both_loaders): ["/test/ignore", [], [".ignore.yaml"]], ] - with patch_yaml_files( - { - "/test/first.yaml": "key1: one", - "/test/tmp2/second.yaml": "key2: two", - "/test/tmp2/third.yaml": "key3: three\nkey4: four", + conf = "key: !include_dir_merge_named /test" + with io.StringIO(conf) as file: + assert ".ignore" in mock_walk.return_value[0][1], "Expecting .ignore in here" + doc = yaml_loader.yaml.load(file, Loader=yaml_loader.SafeLineLoader) + assert "tmp2" in mock_walk.return_value[0][1] + assert ".ignore" not in mock_walk.return_value[0][1] + assert doc["key"] == { + "key1": "one", + "key2": "two", + "key3": "three", + "key4": "four", } - ): - conf = "key: !include_dir_merge_named /test" - with io.StringIO(conf) as file: - assert ( - ".ignore" in mock_walk.return_value[0][1] - ), "Expecting .ignore in here" - doc = yaml_loader.yaml.load(file, Loader=yaml_loader.SafeLineLoader) - assert "tmp2" in mock_walk.return_value[0][1] - assert ".ignore" not in mock_walk.return_value[0][1] - assert doc["key"] == { - "key1": "one", - "key2": "two", - "key3": "three", - "key4": "four", - } @patch("homeassistant.util.yaml.loader.open", create=True) @@ -452,26 +481,31 @@ class TestSecrets(unittest.TestCase): ) -def test_representing_yaml_loaded_data(try_both_dumpers): +@pytest.mark.parametrize("hass_config_yaml", ['key: [1, "2", 3]']) +def test_representing_yaml_loaded_data( + try_both_dumpers, mock_hass_config_yaml: None +) -> None: """Test we can represent YAML loaded data.""" - files = {YAML_CONFIG_FILE: 'key: [1, "2", 3]'} - with patch_yaml_files(files): - data = load_yaml_config_file(YAML_CONFIG_FILE) + data = load_yaml_config_file(YAML_CONFIG_FILE) assert yaml.dump(data) == "key:\n- 1\n- '2'\n- 3\n" -def test_duplicate_key(caplog, try_both_loaders): +@pytest.mark.parametrize("hass_config_yaml", ["key: thing1\nkey: thing2"]) +def test_duplicate_key(caplog, try_both_loaders, mock_hass_config_yaml: None) -> None: """Test duplicate dict keys.""" - files = {YAML_CONFIG_FILE: "key: thing1\nkey: thing2"} - with patch_yaml_files(files): - load_yaml_config_file(YAML_CONFIG_FILE) + load_yaml_config_file(YAML_CONFIG_FILE) assert "contains duplicate key" in caplog.text -def test_no_recursive_secrets(caplog, try_both_loaders): +@pytest.mark.parametrize( + "hass_config_yaml_files", + [{YAML_CONFIG_FILE: "key: !secret a", yaml.SECRET_YAML: "a: 1\nb: !secret a"}], +) +def test_no_recursive_secrets( + caplog, try_both_loaders, mock_hass_config_yaml: None +) -> None: """Test that loading of secrets from the secrets file fails correctly.""" - files = {YAML_CONFIG_FILE: "key: !secret a", yaml.SECRET_YAML: "a: 1\nb: !secret a"} - with patch_yaml_files(files), pytest.raises(HomeAssistantError) as e: + with pytest.raises(HomeAssistantError) as e: load_yaml_config_file(YAML_CONFIG_FILE) assert e.value.args == ("Secrets not supported in this YAML file",)