mirror of
https://github.com/esphome/esphome.git
synced 2025-08-10 12:27:46 +00:00
cleanup
This commit is contained in:
0
tests/unit_tests/core/__init__.py
Normal file
0
tests/unit_tests/core/__init__.py
Normal file
33
tests/unit_tests/core/common.py
Normal file
33
tests/unit_tests/core/common.py
Normal file
@@ -0,0 +1,33 @@
|
||||
"""Common test utilities for core unit tests."""
|
||||
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
from esphome import config, yaml_util
|
||||
from esphome.config import Config
|
||||
from esphome.core import CORE
|
||||
|
||||
|
||||
def load_config_from_yaml(
|
||||
yaml_file: Callable[[str], str], yaml_content: str
|
||||
) -> Config | None:
|
||||
"""Load configuration from YAML content."""
|
||||
yaml_path = yaml_file(yaml_content)
|
||||
parsed_yaml = yaml_util.load_yaml(yaml_path)
|
||||
|
||||
# Mock yaml_util.load_yaml to return our parsed content
|
||||
with (
|
||||
patch.object(yaml_util, "load_yaml", return_value=parsed_yaml),
|
||||
patch.object(CORE, "config_path", yaml_path),
|
||||
):
|
||||
return config.read_config({})
|
||||
|
||||
|
||||
def load_config_from_fixture(
|
||||
yaml_file: Callable[[str], str], fixture_name: str, fixtures_dir: Path
|
||||
) -> Config | None:
|
||||
"""Load configuration from a fixture file."""
|
||||
fixture_path = fixtures_dir / fixture_name
|
||||
yaml_content = fixture_path.read_text()
|
||||
return load_config_from_yaml(yaml_file, yaml_content)
|
@@ -3,43 +3,18 @@
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from esphome import config, config_validation as cv, core, yaml_util
|
||||
from esphome.config import Config
|
||||
from esphome import config_validation as cv, core
|
||||
from esphome.const import CONF_AREA, CONF_AREAS, CONF_DEVICES
|
||||
from esphome.core import CORE
|
||||
from esphome.core.config import Area, validate_area_config
|
||||
|
||||
from .common import load_config_from_fixture
|
||||
|
||||
FIXTURES_DIR = Path(__file__).parent.parent / "fixtures" / "core" / "config"
|
||||
|
||||
|
||||
def load_config_from_yaml(
|
||||
yaml_file: Callable[[str], str], yaml_content: str
|
||||
) -> Config | None:
|
||||
"""Load configuration from YAML content."""
|
||||
yaml_path = yaml_file(yaml_content)
|
||||
parsed_yaml = yaml_util.load_yaml(yaml_path)
|
||||
|
||||
# Mock yaml_util.load_yaml to return our parsed content
|
||||
with (
|
||||
patch.object(yaml_util, "load_yaml", return_value=parsed_yaml),
|
||||
patch.object(CORE, "config_path", yaml_path),
|
||||
):
|
||||
return config.read_config({})
|
||||
|
||||
|
||||
def load_config_from_fixture(
|
||||
yaml_file: Callable[[str], str], fixture_name: str
|
||||
) -> Config | None:
|
||||
"""Load configuration from a fixture file."""
|
||||
fixture_path = FIXTURES_DIR / fixture_name
|
||||
yaml_content = fixture_path.read_text()
|
||||
return load_config_from_yaml(yaml_file, yaml_content)
|
||||
|
||||
|
||||
def test_validate_area_config_with_string() -> None:
|
||||
"""Test that string area config is converted to structured format."""
|
||||
result = validate_area_config("Living Room")
|
||||
@@ -70,7 +45,7 @@ def test_validate_area_config_with_dict() -> None:
|
||||
|
||||
def test_device_with_valid_area_id(yaml_file: Callable[[str], str]) -> None:
|
||||
"""Test that device with valid area_id works correctly."""
|
||||
result = load_config_from_fixture(yaml_file, "valid_area_device.yaml")
|
||||
result = load_config_from_fixture(yaml_file, "valid_area_device.yaml", FIXTURES_DIR)
|
||||
assert result is not None
|
||||
|
||||
esphome_config = result["esphome"]
|
||||
@@ -93,7 +68,9 @@ def test_device_with_valid_area_id(yaml_file: Callable[[str], str]) -> None:
|
||||
|
||||
def test_multiple_areas_and_devices(yaml_file: Callable[[str], str]) -> None:
|
||||
"""Test multiple areas and devices configuration."""
|
||||
result = load_config_from_fixture(yaml_file, "multiple_areas_devices.yaml")
|
||||
result = load_config_from_fixture(
|
||||
yaml_file, "multiple_areas_devices.yaml", FIXTURES_DIR
|
||||
)
|
||||
assert result is not None
|
||||
|
||||
esphome_config = result["esphome"]
|
||||
@@ -129,7 +106,9 @@ def test_legacy_string_area(
|
||||
yaml_file: Callable[[str], str], caplog: pytest.LogCaptureFixture
|
||||
) -> None:
|
||||
"""Test legacy string area configuration with deprecation warning."""
|
||||
result = load_config_from_fixture(yaml_file, "legacy_string_area.yaml")
|
||||
result = load_config_from_fixture(
|
||||
yaml_file, "legacy_string_area.yaml", FIXTURES_DIR
|
||||
)
|
||||
assert result is not None
|
||||
|
||||
esphome_config = result["esphome"]
|
||||
@@ -148,7 +127,7 @@ def test_area_id_collision(
|
||||
yaml_file: Callable[[str], str], capsys: pytest.CaptureFixture[str]
|
||||
) -> None:
|
||||
"""Test that duplicate area IDs are detected."""
|
||||
result = load_config_from_fixture(yaml_file, "area_id_collision.yaml")
|
||||
result = load_config_from_fixture(yaml_file, "area_id_collision.yaml", FIXTURES_DIR)
|
||||
assert result is None
|
||||
|
||||
# Check for the specific error message in stdout
|
||||
@@ -159,7 +138,9 @@ def test_area_id_collision(
|
||||
|
||||
def test_device_without_area(yaml_file: Callable[[str], str]) -> None:
|
||||
"""Test that devices without area_id work correctly."""
|
||||
result = load_config_from_fixture(yaml_file, "device_without_area.yaml")
|
||||
result = load_config_from_fixture(
|
||||
yaml_file, "device_without_area.yaml", FIXTURES_DIR
|
||||
)
|
||||
assert result is not None
|
||||
|
||||
esphome_config = result["esphome"]
|
||||
@@ -181,7 +162,9 @@ def test_device_with_invalid_area_id(
|
||||
yaml_file: Callable[[str], str], capsys: pytest.CaptureFixture[str]
|
||||
) -> None:
|
||||
"""Test that device with non-existent area_id fails validation."""
|
||||
result = load_config_from_fixture(yaml_file, "device_invalid_area.yaml")
|
||||
result = load_config_from_fixture(
|
||||
yaml_file, "device_invalid_area.yaml", FIXTURES_DIR
|
||||
)
|
||||
assert result is None
|
||||
|
||||
# Check for the specific error message in stdout
|
||||
@@ -196,7 +179,9 @@ def test_device_id_hash_collision(
|
||||
yaml_file: Callable[[str], str], capsys: pytest.CaptureFixture[str]
|
||||
) -> None:
|
||||
"""Test that device IDs with hash collisions are detected."""
|
||||
result = load_config_from_fixture(yaml_file, "device_id_collision.yaml")
|
||||
result = load_config_from_fixture(
|
||||
yaml_file, "device_id_collision.yaml", FIXTURES_DIR
|
||||
)
|
||||
assert result is None
|
||||
|
||||
# Check for the specific error message about hash collision
|
||||
@@ -212,7 +197,9 @@ def test_area_id_hash_collision(
|
||||
yaml_file: Callable[[str], str], capsys: pytest.CaptureFixture[str]
|
||||
) -> None:
|
||||
"""Test that area IDs with hash collisions are detected."""
|
||||
result = load_config_from_fixture(yaml_file, "area_id_hash_collision.yaml")
|
||||
result = load_config_from_fixture(
|
||||
yaml_file, "area_id_hash_collision.yaml", FIXTURES_DIR
|
||||
)
|
||||
assert result is None
|
||||
|
||||
# Check for the specific error message about hash collision
|
||||
@@ -228,7 +215,9 @@ def test_device_duplicate_id(
|
||||
yaml_file: Callable[[str], str], capsys: pytest.CaptureFixture[str]
|
||||
) -> None:
|
||||
"""Test that duplicate device IDs are detected by IDPassValidationStep."""
|
||||
result = load_config_from_fixture(yaml_file, "device_duplicate_id.yaml")
|
||||
result = load_config_from_fixture(
|
||||
yaml_file, "device_duplicate_id.yaml", FIXTURES_DIR
|
||||
)
|
||||
assert result is None
|
||||
|
||||
# Check for the specific error message from IDPassValidationStep
|
||||
|
@@ -1,6 +1,7 @@
|
||||
"""Test get_base_entity_object_id function matches C++ behavior."""
|
||||
|
||||
from collections.abc import Generator
|
||||
from collections.abc import Callable, Generator
|
||||
from pathlib import Path
|
||||
import re
|
||||
from typing import Any
|
||||
|
||||
@@ -13,9 +14,13 @@ from esphome.core.entity_helpers import get_base_entity_object_id, setup_entity
|
||||
from esphome.cpp_generator import MockObj
|
||||
from esphome.helpers import sanitize, snake_case
|
||||
|
||||
from .common import load_config_from_yaml
|
||||
|
||||
# Pre-compiled regex pattern for extracting object IDs from expressions
|
||||
OBJECT_ID_PATTERN = re.compile(r'\.set_object_id\(["\'](.*?)["\']\)')
|
||||
|
||||
FIXTURES_DIR = Path(__file__).parent.parent / "fixtures" / "core" / "entity_helpers"
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def restore_core_state() -> Generator[None, None, None]:
|
||||
@@ -548,3 +553,104 @@ def test_entity_duplicate_validator_with_devices() -> None:
|
||||
match=r"Duplicate sensor entity with name 'Temperature' found on device 'device1'",
|
||||
):
|
||||
validator(config3)
|
||||
|
||||
|
||||
def test_duplicate_entity_yaml_validation(
|
||||
yaml_file: Callable[[str], str], capsys: pytest.CaptureFixture[str]
|
||||
) -> None:
|
||||
"""Test that duplicate entity names are caught during YAML config validation."""
|
||||
yaml_content = """
|
||||
esphome:
|
||||
name: test-duplicate
|
||||
|
||||
esp32:
|
||||
board: esp32dev
|
||||
|
||||
sensor:
|
||||
- platform: template
|
||||
name: "Temperature"
|
||||
lambda: return 21.0;
|
||||
- platform: template
|
||||
name: "Temperature" # Duplicate - should fail
|
||||
lambda: return 22.0;
|
||||
"""
|
||||
result = load_config_from_yaml(yaml_file, yaml_content)
|
||||
assert result is None
|
||||
|
||||
# Check for the duplicate entity error message
|
||||
captured = capsys.readouterr()
|
||||
assert "Duplicate sensor entity with name 'Temperature' found" in captured.out
|
||||
|
||||
|
||||
def test_duplicate_entity_with_devices_yaml_validation(
|
||||
yaml_file: Callable[[str], str], capsys: pytest.CaptureFixture[str]
|
||||
) -> None:
|
||||
"""Test duplicate entity validation with devices."""
|
||||
yaml_content = """
|
||||
esphome:
|
||||
name: test-duplicate-devices
|
||||
devices:
|
||||
- id: device1
|
||||
name: "Device 1"
|
||||
- id: device2
|
||||
name: "Device 2"
|
||||
|
||||
esp32:
|
||||
board: esp32dev
|
||||
|
||||
sensor:
|
||||
# Same name on different devices - should pass
|
||||
- platform: template
|
||||
device_id: device1
|
||||
name: "Temperature"
|
||||
lambda: return 21.0;
|
||||
- platform: template
|
||||
device_id: device2
|
||||
name: "Temperature"
|
||||
lambda: return 22.0;
|
||||
# Duplicate on same device - should fail
|
||||
- platform: template
|
||||
device_id: device1
|
||||
name: "Temperature"
|
||||
lambda: return 23.0;
|
||||
"""
|
||||
result = load_config_from_yaml(yaml_file, yaml_content)
|
||||
assert result is None
|
||||
|
||||
# Check for the duplicate entity error message with device
|
||||
captured = capsys.readouterr()
|
||||
assert (
|
||||
"Duplicate sensor entity with name 'Temperature' found on device 'device1'"
|
||||
in captured.out
|
||||
)
|
||||
|
||||
|
||||
def test_entity_different_platforms_yaml_validation(
|
||||
yaml_file: Callable[[str], str],
|
||||
) -> None:
|
||||
"""Test that same entity name on different platforms is allowed."""
|
||||
yaml_content = """
|
||||
esphome:
|
||||
name: test-different-platforms
|
||||
|
||||
esp32:
|
||||
board: esp32dev
|
||||
|
||||
sensor:
|
||||
- platform: template
|
||||
name: "Status"
|
||||
lambda: return 1.0;
|
||||
|
||||
binary_sensor:
|
||||
- platform: template
|
||||
name: "Status" # Same name, different platform - should pass
|
||||
lambda: return true;
|
||||
|
||||
text_sensor:
|
||||
- platform: template
|
||||
name: "Status" # Same name, different platform - should pass
|
||||
lambda: return {"OK"};
|
||||
"""
|
||||
result = load_config_from_yaml(yaml_file, yaml_content)
|
||||
# This should succeed
|
||||
assert result is not None
|
||||
|
Reference in New Issue
Block a user