1
0
mirror of https://github.com/esphome/esphome.git synced 2025-09-28 16:12:24 +01:00

rename to workaround the test conflict

This commit is contained in:
J. Nick Koston
2025-09-26 16:13:38 -05:00
parent b134f40201
commit f5bba6f8cc
2 changed files with 96 additions and 475 deletions

View File

@@ -1,122 +0,0 @@
"""Unit tests for esphome.config module."""
from collections.abc import Generator
from pathlib import Path
from unittest.mock import MagicMock, Mock, patch
import pytest
from esphome import config, yaml_util
from esphome.core import CORE
@pytest.fixture
def mock_get_component() -> Generator[Mock, None, None]:
"""Fixture for mocking get_component."""
with patch("esphome.config.get_component") as mock_get_component:
yield mock_get_component
@pytest.fixture
def mock_get_platform() -> Generator[Mock, None, None]:
"""Fixture for mocking get_platform."""
with patch("esphome.config.get_platform") as mock_get_platform:
# Default mock platform
mock_get_platform.return_value = MagicMock()
yield mock_get_platform
@pytest.fixture
def fixtures_dir() -> Path:
"""Get the fixtures directory."""
return Path(__file__).parent / "fixtures"
def test_ota_component_configs_with_proper_platform_list(
mock_get_component: Mock,
mock_get_platform: Mock,
) -> None:
"""Test iter_component_configs handles OTA properly configured as a list."""
test_config = {
"ota": [
{"platform": "esphome", "password": "test123", "id": "my_ota"},
],
}
mock_get_component.return_value = MagicMock(
is_platform_component=True, multi_conf=False
)
configs = list(config.iter_component_configs(test_config))
assert len(configs) == 2
assert configs[0][0] == "ota"
assert configs[0][2] == test_config["ota"] # The list itself
assert configs[1][0] == "ota.esphome"
assert configs[1][2]["platform"] == "esphome"
assert configs[1][2]["password"] == "test123"
def test_iter_component_configs_with_multi_conf(mock_get_component: Mock) -> None:
"""Test that iter_component_configs handles multi_conf components correctly."""
test_config = {
"switch": [
{"name": "Switch 1"},
{"name": "Switch 2"},
],
}
mock_get_component.return_value = MagicMock(
is_platform_component=False, multi_conf=True
)
configs = list(config.iter_component_configs(test_config))
assert len(configs) == 2
for domain, component, conf in configs:
assert domain == "switch"
assert "name" in conf
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."""
CORE.config_path = fixtures_dir / "dummy.yaml"
config_file = fixtures_dir / "ota_no_platform.yaml"
raw_config = yaml_util.load_yaml(config_file)
result = config.validate_config(raw_config, {})
assert "ota" in result
assert isinstance(result["ota"], list), f"Expected list, got {type(result['ota'])}"
platforms = {p.get("platform") for p in result["ota"]}
assert "web_server" in platforms, f"Expected web_server platform in {platforms}"
def test_ota_empty_dict_with_captive_portal(fixtures_dir: Path) -> None:
"""Test OTA with empty dict ({}) gets normalized when captive_portal auto-loads."""
CORE.config_path = fixtures_dir / "dummy.yaml"
config_file = fixtures_dir / "ota_empty_dict.yaml"
raw_config = yaml_util.load_yaml(config_file)
result = config.validate_config(raw_config, {})
assert "ota" in result
assert isinstance(result["ota"], list), f"Expected list, got {type(result['ota'])}"
platforms = {p.get("platform") for p in result["ota"]}
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:
"""Test OTA with proper platform list remains valid when captive_portal auto-loads."""
CORE.config_path = fixtures_dir / "dummy.yaml"
config_file = fixtures_dir / "ota_with_platform_list.yaml"
raw_config = yaml_util.load_yaml(config_file)
result = config.validate_config(raw_config, {})
assert "ota" in result
assert isinstance(result["ota"], list), f"Expected list, got {type(result['ota'])}"
platforms = {p.get("platform") for p in result["ota"]}
assert "esphome" in platforms, f"Expected esphome platform in {platforms}"
assert "web_server" in platforms, f"Expected web_server platform in {platforms}"

View File

@@ -1,379 +1,122 @@
import string
"""Unit tests for esphome.config module."""
from collections.abc import Generator
from pathlib import Path
from unittest.mock import MagicMock, Mock, patch
from hypothesis import example, given
from hypothesis.strategies import builds, integers, ip_addresses, one_of, text
import pytest
from esphome import config_validation
from esphome.components.esp32.const import (
VARIANT_ESP32,
VARIANT_ESP32C2,
VARIANT_ESP32C3,
VARIANT_ESP32C6,
VARIANT_ESP32H2,
VARIANT_ESP32S2,
VARIANT_ESP32S3,
)
from esphome.config_validation import Invalid
from esphome.const import (
PLATFORM_BK72XX,
PLATFORM_ESP32,
PLATFORM_ESP8266,
PLATFORM_HOST,
PLATFORM_LN882X,
PLATFORM_RP2040,
PLATFORM_RTL87XX,
)
from esphome.core import CORE, HexInt, Lambda
from esphome import config, yaml_util
from esphome.core import CORE
def test_check_not_templatable__invalid():
with pytest.raises(Invalid, match="This option is not templatable!"):
config_validation.check_not_templatable(Lambda(""))
@pytest.fixture
def mock_get_component() -> Generator[Mock, None, None]:
"""Fixture for mocking get_component."""
with patch("esphome.config.get_component") as mock_get_component:
yield mock_get_component
@pytest.mark.parametrize("value", ("foo", 1, "D12", False))
def test_alphanumeric__valid(value):
actual = config_validation.alphanumeric(value)
assert actual == str(value)
@pytest.fixture
def mock_get_platform() -> Generator[Mock, None, None]:
"""Fixture for mocking get_platform."""
with patch("esphome.config.get_platform") as mock_get_platform:
# Default mock platform
mock_get_platform.return_value = MagicMock()
yield mock_get_platform
@pytest.mark.parametrize("value", ("£23", "Foo!"))
def test_alphanumeric__invalid(value):
with pytest.raises(Invalid):
config_validation.alphanumeric(value)
@pytest.fixture
def fixtures_dir() -> Path:
"""Get the fixtures directory."""
return Path(__file__).parent / "fixtures"
@given(value=text(alphabet=string.ascii_lowercase + string.digits + "-_"))
def test_valid_name__valid(value):
actual = config_validation.valid_name(value)
assert actual == value
@pytest.mark.parametrize("value", ("foo bar", "FooBar", "foo::bar"))
def test_valid_name__invalid(value):
with pytest.raises(Invalid):
config_validation.valid_name(value)
@pytest.mark.parametrize("value", ("${name}", "${NAME}", "$NAME", "${name}_name"))
def test_valid_name__substitution_valid(value):
CORE.vscode = True
actual = config_validation.valid_name(value)
assert actual == value
CORE.vscode = False
with pytest.raises(Invalid):
actual = config_validation.valid_name(value)
@pytest.mark.parametrize("value", ("{NAME}", "${A NAME}"))
def test_valid_name__substitution_like_invalid(value):
with pytest.raises(Invalid):
config_validation.valid_name(value)
@pytest.mark.parametrize("value", ("myid", "anID", "SOME_ID_test", "MYID_99"))
def test_validate_id_name__valid(value):
actual = config_validation.validate_id_name(value)
assert actual == value
@pytest.mark.parametrize("value", ("id of mine", "id-4", "{name_id}", "id::name"))
def test_validate_id_name__invalid(value):
with pytest.raises(Invalid):
config_validation.validate_id_name(value)
@pytest.mark.parametrize("value", ("${id}", "${ID}", "${ID}_test_1", "$MYID"))
def test_validate_id_name__substitution_valid(value):
CORE.vscode = True
actual = config_validation.validate_id_name(value)
assert actual == value
CORE.vscode = False
with pytest.raises(Invalid):
config_validation.validate_id_name(value)
@given(one_of(integers(), text()))
def test_string__valid(value):
actual = config_validation.string(value)
assert actual == str(value)
@pytest.mark.parametrize("value", ({}, [], True, False, None))
def test_string__invalid(value):
with pytest.raises(Invalid):
config_validation.string(value)
@given(text())
def test_strict_string__valid(value):
actual = config_validation.string_strict(value)
assert actual == value
@pytest.mark.parametrize("value", (None, 123))
def test_string_string__invalid(value):
with pytest.raises(Invalid, match="Must be string, got"):
config_validation.string_strict(value)
@given(
builds(
lambda v: "mdi:" + v,
text(
alphabet=string.ascii_letters + string.digits + "-_",
min_size=1,
max_size=20,
),
)
)
@example("")
def test_icon__valid(value):
actual = config_validation.icon(value)
assert actual == value
def test_icon__invalid():
with pytest.raises(Invalid, match="Icons must match the format "):
config_validation.icon("foo")
@pytest.mark.parametrize("value", ("True", "YES", "on", "enAblE", True))
def test_boolean__valid_true(value):
assert config_validation.boolean(value) is True
@pytest.mark.parametrize("value", ("False", "NO", "off", "disAblE", False))
def test_boolean__valid_false(value):
assert config_validation.boolean(value) is False
@pytest.mark.parametrize("value", (None, 1, 0, "foo"))
def test_boolean__invalid(value):
with pytest.raises(Invalid, match="Expected boolean value"):
config_validation.boolean(value)
@given(value=ip_addresses(v=4).map(str))
def test_ipv4__valid(value):
config_validation.ipv4address(value)
@pytest.mark.parametrize("value", ("127.0.0", "localhost", ""))
def test_ipv4__invalid(value):
with pytest.raises(Invalid, match="is not a valid IPv4 address"):
config_validation.ipv4address(value)
@given(value=ip_addresses(v=6).map(str))
def test_ipv6__valid(value):
config_validation.ipaddress(value)
@pytest.mark.parametrize("value", ("127.0.0", "localhost", "", "2001:db8::2::3"))
def test_ipv6__invalid(value):
with pytest.raises(Invalid, match="is not a valid IP address"):
config_validation.ipaddress(value)
# TODO: ensure_list
@given(integers())
def hex_int__valid(value):
actual = config_validation.hex_int(value)
assert isinstance(actual, HexInt)
assert actual == value
@pytest.mark.parametrize(
"framework, platform, variant, full, idf, arduino, simple",
[
("arduino", PLATFORM_ESP8266, None, "1", "1", "1", "1"),
("arduino", PLATFORM_ESP32, VARIANT_ESP32, "3", "2", "3", "2"),
("esp-idf", PLATFORM_ESP32, VARIANT_ESP32, "4", "4", "2", "2"),
("arduino", PLATFORM_ESP32, VARIANT_ESP32C2, "3", "2", "3", "2"),
("esp-idf", PLATFORM_ESP32, VARIANT_ESP32C2, "4", "4", "2", "2"),
("arduino", PLATFORM_ESP32, VARIANT_ESP32S2, "6", "5", "6", "5"),
("esp-idf", PLATFORM_ESP32, VARIANT_ESP32S2, "7", "7", "5", "5"),
("arduino", PLATFORM_ESP32, VARIANT_ESP32S3, "9", "8", "9", "8"),
("esp-idf", PLATFORM_ESP32, VARIANT_ESP32S3, "10", "10", "8", "8"),
("arduino", PLATFORM_ESP32, VARIANT_ESP32C3, "12", "11", "12", "11"),
("esp-idf", PLATFORM_ESP32, VARIANT_ESP32C3, "13", "13", "11", "11"),
("arduino", PLATFORM_ESP32, VARIANT_ESP32C6, "15", "14", "15", "14"),
("esp-idf", PLATFORM_ESP32, VARIANT_ESP32C6, "16", "16", "14", "14"),
("arduino", PLATFORM_ESP32, VARIANT_ESP32H2, "18", "17", "18", "17"),
("esp-idf", PLATFORM_ESP32, VARIANT_ESP32H2, "19", "19", "17", "17"),
("arduino", PLATFORM_RP2040, None, "20", "20", "20", "20"),
("arduino", PLATFORM_BK72XX, None, "21", "21", "21", "21"),
("arduino", PLATFORM_RTL87XX, None, "22", "22", "22", "22"),
("arduino", PLATFORM_LN882X, None, "23", "23", "23", "23"),
("host", PLATFORM_HOST, None, "24", "24", "24", "24"),
def test_ota_component_configs_with_proper_platform_list(
mock_get_component: Mock,
mock_get_platform: Mock,
) -> None:
"""Test iter_component_configs handles OTA properly configured as a list."""
test_config = {
"ota": [
{"platform": "esphome", "password": "test123", "id": "my_ota"},
],
)
def test_split_default(framework, platform, variant, full, idf, arduino, simple):
from esphome.components.esp32.const import KEY_ESP32
from esphome.const import (
KEY_CORE,
KEY_TARGET_FRAMEWORK,
KEY_TARGET_PLATFORM,
KEY_VARIANT,
}
mock_get_component.return_value = MagicMock(
is_platform_component=True, multi_conf=False
)
CORE.data[KEY_CORE] = {}
CORE.data[KEY_CORE][KEY_TARGET_PLATFORM] = platform
CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK] = framework
if platform == PLATFORM_ESP32:
CORE.data[KEY_ESP32] = {}
CORE.data[KEY_ESP32][KEY_VARIANT] = variant
configs = list(config.iter_component_configs(test_config))
assert len(configs) == 2
common_mappings = {
"esp8266": "1",
"esp32": "2",
"esp32_s2": "5",
"esp32_s3": "8",
"esp32_c3": "11",
"esp32_c6": "14",
"esp32_h2": "17",
"rp2040": "20",
"bk72xx": "21",
"rtl87xx": "22",
"ln882x": "23",
"host": "24",
}
assert configs[0][0] == "ota"
assert configs[0][2] == test_config["ota"] # The list itself
idf_mappings = {
"esp32_idf": "4",
"esp32_s2_idf": "7",
"esp32_s3_idf": "10",
"esp32_c3_idf": "13",
"esp32_c6_idf": "16",
"esp32_h2_idf": "19",
}
arduino_mappings = {
"esp32_arduino": "3",
"esp32_s2_arduino": "6",
"esp32_s3_arduino": "9",
"esp32_c3_arduino": "12",
"esp32_c6_arduino": "15",
"esp32_h2_arduino": "18",
}
schema = config_validation.Schema(
{
config_validation.SplitDefault(
"full", **common_mappings, **idf_mappings, **arduino_mappings
): str,
config_validation.SplitDefault(
"idf", **common_mappings, **idf_mappings
): str,
config_validation.SplitDefault(
"arduino", **common_mappings, **arduino_mappings
): str,
config_validation.SplitDefault("simple", **common_mappings): str,
}
)
assert schema({}).get("full") == full
assert schema({}).get("idf") == idf
assert schema({}).get("arduino") == arduino
assert schema({}).get("simple") == simple
assert configs[1][0] == "ota.esphome"
assert configs[1][2]["platform"] == "esphome"
assert configs[1][2]["password"] == "test123"
@pytest.mark.parametrize(
"framework, platform, message",
[
("esp-idf", PLATFORM_ESP32, "ESP32 using esp-idf framework"),
("arduino", PLATFORM_ESP32, "ESP32 using arduino framework"),
("arduino", PLATFORM_ESP8266, "ESP8266 using arduino framework"),
("arduino", PLATFORM_RP2040, "RP2040 using arduino framework"),
("arduino", PLATFORM_BK72XX, "BK72XX using arduino framework"),
("host", PLATFORM_HOST, "HOST using host framework"),
def test_iter_component_configs_with_multi_conf(mock_get_component: Mock) -> None:
"""Test that iter_component_configs handles multi_conf components correctly."""
test_config = {
"switch": [
{"name": "Switch 1"},
{"name": "Switch 2"},
],
)
def test_require_framework_version(framework, platform, message):
import voluptuous as vol
}
from esphome.const import (
KEY_CORE,
KEY_FRAMEWORK_VERSION,
KEY_TARGET_FRAMEWORK,
KEY_TARGET_PLATFORM,
mock_get_component.return_value = MagicMock(
is_platform_component=False, multi_conf=True
)
CORE.data[KEY_CORE] = {}
CORE.data[KEY_CORE][KEY_TARGET_PLATFORM] = platform
CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK] = framework
CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] = config_validation.Version(1, 0, 0)
configs = list(config.iter_component_configs(test_config))
assert len(configs) == 2
assert (
config_validation.require_framework_version(
esp_idf=config_validation.Version(0, 5, 0),
esp32_arduino=config_validation.Version(0, 5, 0),
esp8266_arduino=config_validation.Version(0, 5, 0),
rp2040_arduino=config_validation.Version(0, 5, 0),
bk72xx_arduino=config_validation.Version(0, 5, 0),
host=config_validation.Version(0, 5, 0),
extra_message="test 1",
)("test")
== "test"
)
for domain, component, conf in configs:
assert domain == "switch"
assert "name" in conf
with pytest.raises(
vol.error.Invalid,
match="This feature requires at least framework version 2.0.0. test 2",
):
config_validation.require_framework_version(
esp_idf=config_validation.Version(2, 0, 0),
esp32_arduino=config_validation.Version(2, 0, 0),
esp8266_arduino=config_validation.Version(2, 0, 0),
rp2040_arduino=config_validation.Version(2, 0, 0),
bk72xx_arduino=config_validation.Version(2, 0, 0),
host=config_validation.Version(2, 0, 0),
extra_message="test 2",
)("test")
assert (
config_validation.require_framework_version(
esp_idf=config_validation.Version(1, 5, 0),
esp32_arduino=config_validation.Version(1, 5, 0),
esp8266_arduino=config_validation.Version(1, 5, 0),
rp2040_arduino=config_validation.Version(1, 5, 0),
bk72xx_arduino=config_validation.Version(1, 5, 0),
host=config_validation.Version(1, 5, 0),
max_version=True,
extra_message="test 3",
)("test")
== "test"
)
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."""
CORE.config_path = fixtures_dir / "dummy.yaml"
with pytest.raises(
vol.error.Invalid,
match="This feature requires framework version 0.5.0 or lower. test 4",
):
config_validation.require_framework_version(
esp_idf=config_validation.Version(0, 5, 0),
esp32_arduino=config_validation.Version(0, 5, 0),
esp8266_arduino=config_validation.Version(0, 5, 0),
rp2040_arduino=config_validation.Version(0, 5, 0),
bk72xx_arduino=config_validation.Version(0, 5, 0),
host=config_validation.Version(0, 5, 0),
max_version=True,
extra_message="test 4",
)("test")
config_file = fixtures_dir / "ota_no_platform.yaml"
raw_config = yaml_util.load_yaml(config_file)
result = config.validate_config(raw_config, {})
with pytest.raises(
vol.error.Invalid, match=f"This feature is incompatible with {message}. test 5"
):
config_validation.require_framework_version(
extra_message="test 5",
)("test")
assert "ota" in result
assert isinstance(result["ota"], list), f"Expected list, got {type(result['ota'])}"
platforms = {p.get("platform") for p in result["ota"]}
assert "web_server" in platforms, f"Expected web_server platform in {platforms}"
def test_ota_empty_dict_with_captive_portal(fixtures_dir: Path) -> None:
"""Test OTA with empty dict ({}) gets normalized when captive_portal auto-loads."""
CORE.config_path = fixtures_dir / "dummy.yaml"
config_file = fixtures_dir / "ota_empty_dict.yaml"
raw_config = yaml_util.load_yaml(config_file)
result = config.validate_config(raw_config, {})
assert "ota" in result
assert isinstance(result["ota"], list), f"Expected list, got {type(result['ota'])}"
platforms = {p.get("platform") for p in result["ota"]}
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:
"""Test OTA with proper platform list remains valid when captive_portal auto-loads."""
CORE.config_path = fixtures_dir / "dummy.yaml"
config_file = fixtures_dir / "ota_with_platform_list.yaml"
raw_config = yaml_util.load_yaml(config_file)
result = config.validate_config(raw_config, {})
assert "ota" in result
assert isinstance(result["ota"], list), f"Expected list, got {type(result['ota'])}"
platforms = {p.get("platform") for p in result["ota"]}
assert "esphome" in platforms, f"Expected esphome platform in {platforms}"
assert "web_server" in platforms, f"Expected web_server platform in {platforms}"