mirror of
https://github.com/esphome/esphome.git
synced 2025-09-29 08:32:26 +01:00
[core] Fix platform component normalization happening too late in validation pipeline
This commit is contained in:
@@ -36,14 +36,11 @@ def test_iter_components_handles_non_list_platform_component(
|
|||||||
mock_get_component: Mock,
|
mock_get_component: Mock,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test that iter_components handles platform components that have been normalized to empty list."""
|
"""Test that iter_components handles platform components that have been normalized to empty list."""
|
||||||
# After LoadValidationStep normalization, platform components without config
|
|
||||||
# are converted to empty list
|
|
||||||
test_config = {
|
test_config = {
|
||||||
"ota": [], # Normalized from None/dict to empty list by LoadValidationStep
|
"ota": [], # Normalized from None/dict to empty list by LoadValidationStep
|
||||||
"wifi": {"ssid": "test"},
|
"wifi": {"ssid": "test"},
|
||||||
}
|
}
|
||||||
|
|
||||||
# Set up mock components
|
|
||||||
components = {
|
components = {
|
||||||
"ota": MagicMock(is_platform_component=True),
|
"ota": MagicMock(is_platform_component=True),
|
||||||
"wifi": MagicMock(is_platform_component=False),
|
"wifi": MagicMock(is_platform_component=False),
|
||||||
@@ -53,10 +50,7 @@ def test_iter_components_handles_non_list_platform_component(
|
|||||||
domain, MagicMock(is_platform_component=False)
|
domain, MagicMock(is_platform_component=False)
|
||||||
)
|
)
|
||||||
|
|
||||||
# This should not raise TypeError
|
|
||||||
components_list = list(config.iter_components(test_config))
|
components_list = list(config.iter_components(test_config))
|
||||||
|
|
||||||
# Verify we got the expected components
|
|
||||||
assert len(components_list) == 2 # ota and wifi (ota has no platforms)
|
assert len(components_list) == 2 # ota and wifi (ota has no platforms)
|
||||||
|
|
||||||
|
|
||||||
@@ -65,33 +59,22 @@ def test_iter_component_configs_handles_non_list_platform_component(
|
|||||||
mock_get_platform: Mock,
|
mock_get_platform: Mock,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test that iter_component_configs handles platform components that have been normalized."""
|
"""Test that iter_component_configs handles platform components that have been normalized."""
|
||||||
|
|
||||||
# After LoadValidationStep normalization
|
|
||||||
test_config = {
|
test_config = {
|
||||||
"ota": [], # Normalized from None/dict to empty list by LoadValidationStep
|
"ota": [], # Normalized from None/dict to empty list by LoadValidationStep
|
||||||
"one_wire": [ # List config for platform component
|
"one_wire": [{"platform": "gpio", "pin": 10}],
|
||||||
{"platform": "gpio", "pin": 10}
|
|
||||||
],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Set up mock components
|
|
||||||
components: dict[str, Mock] = {
|
components: dict[str, Mock] = {
|
||||||
"ota": MagicMock(is_platform_component=True, multi_conf=False),
|
"ota": MagicMock(is_platform_component=True, multi_conf=False),
|
||||||
"one_wire": MagicMock(is_platform_component=True, multi_conf=False),
|
"one_wire": MagicMock(is_platform_component=True, multi_conf=False),
|
||||||
}
|
}
|
||||||
|
|
||||||
# Default mock for unknown components
|
|
||||||
default_mock = MagicMock(is_platform_component=False, multi_conf=False)
|
default_mock = MagicMock(is_platform_component=False, multi_conf=False)
|
||||||
|
|
||||||
mock_get_component.side_effect = lambda domain: components.get(domain, default_mock)
|
mock_get_component.side_effect = lambda domain: components.get(domain, default_mock)
|
||||||
|
|
||||||
# This should not raise TypeError
|
|
||||||
configs = list(config.iter_component_configs(test_config))
|
configs = list(config.iter_component_configs(test_config))
|
||||||
|
|
||||||
# Should have 3 items: ota (empty list), one_wire, and one_wire.gpio
|
|
||||||
assert len(configs) == 3
|
assert len(configs) == 3
|
||||||
|
|
||||||
# Check the domains
|
|
||||||
domains = [c[0] for c in configs]
|
domains = [c[0] for c in configs]
|
||||||
assert "ota" in domains
|
assert "ota" in domains
|
||||||
assert "one_wire" in domains
|
assert "one_wire" in domains
|
||||||
@@ -103,12 +86,9 @@ def test_iter_components_with_valid_platform_list(
|
|||||||
mock_get_platform: Mock,
|
mock_get_platform: Mock,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test that iter_components works correctly with valid platform component list."""
|
"""Test that iter_components works correctly with valid platform component list."""
|
||||||
|
|
||||||
# Create a mock component that is a platform component
|
|
||||||
mock_component = MagicMock()
|
mock_component = MagicMock()
|
||||||
mock_component.is_platform_component = True
|
mock_component.is_platform_component = True
|
||||||
|
|
||||||
# Create test config with proper list format
|
|
||||||
test_config = {
|
test_config = {
|
||||||
"sensor": [
|
"sensor": [
|
||||||
{"platform": "dht", "pin": 5},
|
{"platform": "dht", "pin": 5},
|
||||||
@@ -118,13 +98,9 @@ def test_iter_components_with_valid_platform_list(
|
|||||||
|
|
||||||
mock_get_component.return_value = mock_component
|
mock_get_component.return_value = mock_component
|
||||||
|
|
||||||
# Get all components
|
|
||||||
components = list(config.iter_components(test_config))
|
components = list(config.iter_components(test_config))
|
||||||
|
|
||||||
# Should have 3 items: sensor, sensor.dht, sensor.bme280
|
|
||||||
assert len(components) == 3
|
assert len(components) == 3
|
||||||
|
|
||||||
# Check the domains
|
|
||||||
domains = [c[0] for c in components]
|
domains = [c[0] for c in components]
|
||||||
assert "sensor" in domains
|
assert "sensor" in domains
|
||||||
assert "sensor.dht" in domains
|
assert "sensor.dht" in domains
|
||||||
@@ -136,8 +112,6 @@ def test_ota_with_proper_platform_list(
|
|||||||
mock_get_platform: Mock,
|
mock_get_platform: Mock,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test that OTA component works correctly when configured as a list with platforms."""
|
"""Test that OTA component works correctly when configured as a list with platforms."""
|
||||||
|
|
||||||
# Create test config where ota is properly configured as a list
|
|
||||||
test_config = {
|
test_config = {
|
||||||
"ota": [
|
"ota": [
|
||||||
{"platform": "esphome", "password": "test123"},
|
{"platform": "esphome", "password": "test123"},
|
||||||
@@ -145,14 +119,9 @@ def test_ota_with_proper_platform_list(
|
|||||||
}
|
}
|
||||||
|
|
||||||
mock_get_component.return_value = MagicMock(is_platform_component=True)
|
mock_get_component.return_value = MagicMock(is_platform_component=True)
|
||||||
|
|
||||||
# This should work without TypeError
|
|
||||||
components = list(config.iter_components(test_config))
|
components = list(config.iter_components(test_config))
|
||||||
|
|
||||||
# Should have 2 items: ota and ota.esphome
|
|
||||||
assert len(components) == 2
|
assert len(components) == 2
|
||||||
|
|
||||||
# Check the domains
|
|
||||||
domains = [c[0] for c in components]
|
domains = [c[0] for c in components]
|
||||||
assert "ota" in domains
|
assert "ota" in domains
|
||||||
assert "ota.esphome" in domains
|
assert "ota.esphome" in domains
|
||||||
@@ -163,8 +132,6 @@ def test_ota_component_configs_with_proper_platform_list(
|
|||||||
mock_get_platform: Mock,
|
mock_get_platform: Mock,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test that iter_component_configs handles OTA properly configured as a list."""
|
"""Test that iter_component_configs handles OTA properly configured as a list."""
|
||||||
|
|
||||||
# Create test config where ota is properly configured as a list
|
|
||||||
test_config = {
|
test_config = {
|
||||||
"ota": [
|
"ota": [
|
||||||
{"platform": "esphome", "password": "test123", "id": "my_ota"},
|
{"platform": "esphome", "password": "test123", "id": "my_ota"},
|
||||||
@@ -175,13 +142,9 @@ def test_ota_component_configs_with_proper_platform_list(
|
|||||||
is_platform_component=True, multi_conf=False
|
is_platform_component=True, multi_conf=False
|
||||||
)
|
)
|
||||||
|
|
||||||
# This should work without TypeError
|
|
||||||
configs = list(config.iter_component_configs(test_config))
|
configs = list(config.iter_component_configs(test_config))
|
||||||
|
|
||||||
# Should have 2 items: ota config and ota.esphome platform config
|
|
||||||
assert len(configs) == 2
|
assert len(configs) == 2
|
||||||
|
|
||||||
# Check the domains and configs
|
|
||||||
assert configs[0][0] == "ota"
|
assert configs[0][0] == "ota"
|
||||||
assert configs[0][2] == test_config["ota"] # The list itself
|
assert configs[0][2] == test_config["ota"] # The list itself
|
||||||
|
|
||||||
@@ -192,7 +155,6 @@ def test_ota_component_configs_with_proper_platform_list(
|
|||||||
|
|
||||||
def test_iter_component_configs_with_multi_conf(mock_get_component: Mock) -> None:
|
def test_iter_component_configs_with_multi_conf(mock_get_component: Mock) -> None:
|
||||||
"""Test that iter_component_configs handles multi_conf components correctly."""
|
"""Test that iter_component_configs handles multi_conf components correctly."""
|
||||||
# Create test config
|
|
||||||
test_config = {
|
test_config = {
|
||||||
"switch": [
|
"switch": [
|
||||||
{"name": "Switch 1"},
|
{"name": "Switch 1"},
|
||||||
@@ -200,18 +162,13 @@ def test_iter_component_configs_with_multi_conf(mock_get_component: Mock) -> Non
|
|||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
# Set up mock component with multi_conf
|
|
||||||
mock_get_component.return_value = MagicMock(
|
mock_get_component.return_value = MagicMock(
|
||||||
is_platform_component=False, multi_conf=True
|
is_platform_component=False, multi_conf=True
|
||||||
)
|
)
|
||||||
|
|
||||||
# Get all configs
|
|
||||||
configs = list(config.iter_component_configs(test_config))
|
configs = list(config.iter_component_configs(test_config))
|
||||||
|
|
||||||
# Should have 2 items (one for each switch)
|
|
||||||
assert len(configs) == 2
|
assert len(configs) == 2
|
||||||
|
|
||||||
# Both should be for "switch" domain
|
|
||||||
for domain, component, conf in configs:
|
for domain, component, conf in configs:
|
||||||
assert domain == "switch"
|
assert domain == "switch"
|
||||||
assert "name" in conf
|
assert "name" in conf
|
||||||
@@ -219,51 +176,40 @@ def test_iter_component_configs_with_multi_conf(mock_get_component: Mock) -> Non
|
|||||||
|
|
||||||
def test_ota_no_platform_with_captive_portal(fixtures_dir: Path) -> None:
|
def test_ota_no_platform_with_captive_portal(fixtures_dir: Path) -> None:
|
||||||
"""Test OTA with no platform (ota:) gets normalized when captive_portal auto-loads."""
|
"""Test OTA with no platform (ota:) gets normalized when captive_portal auto-loads."""
|
||||||
# Set up CORE config path
|
|
||||||
CORE.config_path = fixtures_dir / "dummy.yaml"
|
CORE.config_path = fixtures_dir / "dummy.yaml"
|
||||||
|
|
||||||
# Load config with OTA having no value (ota:)
|
|
||||||
config_file = fixtures_dir / "ota_no_platform.yaml"
|
config_file = fixtures_dir / "ota_no_platform.yaml"
|
||||||
raw_config = yaml_util.load_yaml(config_file)
|
raw_config = yaml_util.load_yaml(config_file)
|
||||||
result = config.validate_config(raw_config, {})
|
result = config.validate_config(raw_config, {})
|
||||||
|
|
||||||
# Check that OTA was normalized to a list and captive_portal added web_server
|
|
||||||
assert "ota" in result
|
assert "ota" in result
|
||||||
assert isinstance(result["ota"], list), f"Expected list, got {type(result['ota'])}"
|
assert isinstance(result["ota"], list), f"Expected list, got {type(result['ota'])}"
|
||||||
# After captive_portal auto-loads, OTA should have web_server platform
|
|
||||||
platforms = {p.get("platform") for p in result["ota"]}
|
platforms = {p.get("platform") for p in result["ota"]}
|
||||||
assert "web_server" in platforms, f"Expected web_server platform in {platforms}"
|
assert "web_server" in platforms, f"Expected web_server platform in {platforms}"
|
||||||
|
|
||||||
|
|
||||||
def test_ota_empty_dict_with_captive_portal(fixtures_dir: Path) -> None:
|
def test_ota_empty_dict_with_captive_portal(fixtures_dir: Path) -> None:
|
||||||
"""Test OTA with empty dict ({}) gets normalized when captive_portal auto-loads."""
|
"""Test OTA with empty dict ({}) gets normalized when captive_portal auto-loads."""
|
||||||
# Set up CORE config path
|
|
||||||
CORE.config_path = fixtures_dir / "dummy.yaml"
|
CORE.config_path = fixtures_dir / "dummy.yaml"
|
||||||
|
|
||||||
# Load config with OTA having empty dict (ota: {})
|
|
||||||
config_file = fixtures_dir / "ota_empty_dict.yaml"
|
config_file = fixtures_dir / "ota_empty_dict.yaml"
|
||||||
raw_config = yaml_util.load_yaml(config_file)
|
raw_config = yaml_util.load_yaml(config_file)
|
||||||
result = config.validate_config(raw_config, {})
|
result = config.validate_config(raw_config, {})
|
||||||
|
|
||||||
# Check that OTA was normalized to a list and captive_portal added web_server
|
|
||||||
assert "ota" in result
|
assert "ota" in result
|
||||||
assert isinstance(result["ota"], list), f"Expected list, got {type(result['ota'])}"
|
assert isinstance(result["ota"], list), f"Expected list, got {type(result['ota'])}"
|
||||||
# The empty dict gets normalized and web_server is added
|
|
||||||
platforms = {p.get("platform") for p in result["ota"]}
|
platforms = {p.get("platform") for p in result["ota"]}
|
||||||
assert "web_server" in platforms, f"Expected web_server platform in {platforms}"
|
assert "web_server" in platforms, f"Expected web_server platform in {platforms}"
|
||||||
|
|
||||||
|
|
||||||
def test_ota_with_platform_list_and_captive_portal(fixtures_dir: Path) -> None:
|
def test_ota_with_platform_list_and_captive_portal(fixtures_dir: Path) -> None:
|
||||||
"""Test OTA with proper platform list remains valid when captive_portal auto-loads."""
|
"""Test OTA with proper platform list remains valid when captive_portal auto-loads."""
|
||||||
# Set up CORE config path
|
|
||||||
CORE.config_path = fixtures_dir / "dummy.yaml"
|
CORE.config_path = fixtures_dir / "dummy.yaml"
|
||||||
|
|
||||||
# Load config with OTA having proper list format
|
|
||||||
config_file = fixtures_dir / "ota_with_platform_list.yaml"
|
config_file = fixtures_dir / "ota_with_platform_list.yaml"
|
||||||
raw_config = yaml_util.load_yaml(config_file)
|
raw_config = yaml_util.load_yaml(config_file)
|
||||||
result = config.validate_config(raw_config, {})
|
result = config.validate_config(raw_config, {})
|
||||||
|
|
||||||
# Check that OTA remains a list with both esphome and web_server platforms
|
|
||||||
assert "ota" in result
|
assert "ota" in result
|
||||||
assert isinstance(result["ota"], list), f"Expected list, got {type(result['ota'])}"
|
assert isinstance(result["ota"], list), f"Expected list, got {type(result['ota'])}"
|
||||||
platforms = {p.get("platform") for p in result["ota"]}
|
platforms = {p.get("platform") for p in result["ota"]}
|
||||||
|
Reference in New Issue
Block a user