mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-23 04:03:52 +01:00 
			
		
		
		
	Co-authored-by: J. Nick Koston <nick@koston.org> Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Co-authored-by: J. Nick Koston <nick+github@koston.org>
		
			
				
	
	
		
			226 lines
		
	
	
		
			7.3 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			226 lines
		
	
	
		
			7.3 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| """Unit tests for core config functionality including areas and devices."""
 | |
| 
 | |
| from collections.abc import Callable
 | |
| from pathlib import Path
 | |
| from typing import Any
 | |
| 
 | |
| import pytest
 | |
| 
 | |
| from esphome import config_validation as cv, core
 | |
| from esphome.const import CONF_AREA, CONF_AREAS, CONF_DEVICES
 | |
| 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 test_validate_area_config_with_string() -> None:
 | |
|     """Test that string area config is converted to structured format."""
 | |
|     result = validate_area_config("Living Room")
 | |
| 
 | |
|     assert isinstance(result, dict)
 | |
|     assert "id" in result
 | |
|     assert "name" in result
 | |
|     assert result["name"] == "Living Room"
 | |
|     assert isinstance(result["id"], core.ID)
 | |
|     assert result["id"].is_declaration
 | |
|     assert not result["id"].is_manual
 | |
| 
 | |
| 
 | |
| def test_validate_area_config_with_dict() -> None:
 | |
|     """Test that structured area config passes through unchanged."""
 | |
|     area_id = cv.declare_id(Area)("test_area")
 | |
|     input_config: dict[str, Any] = {
 | |
|         "id": area_id,
 | |
|         "name": "Test Area",
 | |
|     }
 | |
| 
 | |
|     result = validate_area_config(input_config)
 | |
| 
 | |
|     assert result == input_config
 | |
|     assert result["id"] == area_id
 | |
|     assert result["name"] == "Test Area"
 | |
| 
 | |
| 
 | |
| 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", FIXTURES_DIR)
 | |
|     assert result is not None
 | |
| 
 | |
|     esphome_config = result["esphome"]
 | |
| 
 | |
|     # Verify areas were parsed correctly
 | |
|     assert CONF_AREAS in esphome_config
 | |
|     areas = esphome_config[CONF_AREAS]
 | |
|     assert len(areas) == 1
 | |
|     assert areas[0]["id"].id == "bedroom_area"
 | |
|     assert areas[0]["name"] == "Bedroom"
 | |
| 
 | |
|     # Verify devices were parsed correctly
 | |
|     assert CONF_DEVICES in esphome_config
 | |
|     devices = esphome_config[CONF_DEVICES]
 | |
|     assert len(devices) == 1
 | |
|     assert devices[0]["id"].id == "test_device"
 | |
|     assert devices[0]["name"] == "Test Device"
 | |
|     assert devices[0]["area_id"].id == "bedroom_area"
 | |
| 
 | |
| 
 | |
| 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", FIXTURES_DIR
 | |
|     )
 | |
|     assert result is not None
 | |
| 
 | |
|     esphome_config = result["esphome"]
 | |
| 
 | |
|     # Verify main area
 | |
|     assert CONF_AREA in esphome_config
 | |
|     main_area = esphome_config[CONF_AREA]
 | |
|     assert main_area["id"].id == "main_area"
 | |
|     assert main_area["name"] == "Main Area"
 | |
| 
 | |
|     # Verify additional areas
 | |
|     assert CONF_AREAS in esphome_config
 | |
|     areas = esphome_config[CONF_AREAS]
 | |
|     assert len(areas) == 2
 | |
|     area_ids = {area["id"].id for area in areas}
 | |
|     assert area_ids == {"area1", "area2"}
 | |
| 
 | |
|     # Verify devices
 | |
|     assert CONF_DEVICES in esphome_config
 | |
|     devices = esphome_config[CONF_DEVICES]
 | |
|     assert len(devices) == 3
 | |
| 
 | |
|     # Check device-area associations
 | |
|     device_area_map = {dev["id"].id: dev["area_id"].id for dev in devices}
 | |
|     assert device_area_map == {
 | |
|         "device1": "main_area",
 | |
|         "device2": "area1",
 | |
|         "device3": "area2",
 | |
|     }
 | |
| 
 | |
| 
 | |
| 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", FIXTURES_DIR
 | |
|     )
 | |
|     assert result is not None
 | |
| 
 | |
|     esphome_config = result["esphome"]
 | |
| 
 | |
|     # Verify the string was converted to structured format
 | |
|     assert CONF_AREA in esphome_config
 | |
|     area = esphome_config[CONF_AREA]
 | |
|     assert isinstance(area, dict)
 | |
|     assert area["name"] == "Living Room"
 | |
|     assert isinstance(area["id"], core.ID)
 | |
|     assert area["id"].is_declaration
 | |
|     assert not area["id"].is_manual
 | |
| 
 | |
| 
 | |
| 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", FIXTURES_DIR)
 | |
|     assert result is None
 | |
| 
 | |
|     # Check for the specific error message in stdout
 | |
|     captured = capsys.readouterr()
 | |
|     # Exact duplicates are now caught by IDPassValidationStep
 | |
|     assert "ID duplicate_id redefined! Check esphome->area->id." in captured.out
 | |
| 
 | |
| 
 | |
| 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", FIXTURES_DIR
 | |
|     )
 | |
|     assert result is not None
 | |
| 
 | |
|     esphome_config = result["esphome"]
 | |
| 
 | |
|     # Verify device was parsed
 | |
|     assert CONF_DEVICES in esphome_config
 | |
|     devices = esphome_config[CONF_DEVICES]
 | |
|     assert len(devices) == 1
 | |
| 
 | |
|     device = devices[0]
 | |
|     assert device["id"].id == "test_device"
 | |
|     assert device["name"] == "Test Device"
 | |
| 
 | |
|     # Verify no area_id is present
 | |
|     assert "area_id" not in device
 | |
| 
 | |
| 
 | |
| 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", FIXTURES_DIR
 | |
|     )
 | |
|     assert result is None
 | |
| 
 | |
|     # Check for the specific error message in stdout
 | |
|     captured = capsys.readouterr()
 | |
|     assert (
 | |
|         "Couldn't find ID 'nonexistent_area'. Please check you have defined an ID with that name in your configuration."
 | |
|         in captured.out
 | |
|     )
 | |
| 
 | |
| 
 | |
| 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", FIXTURES_DIR
 | |
|     )
 | |
|     assert result is None
 | |
| 
 | |
|     # Check for the specific error message about hash collision
 | |
|     captured = capsys.readouterr()
 | |
|     # The error message shows the ID that collides and includes the hash value
 | |
|     assert (
 | |
|         "Device ID 'd6ka' with hash 3082558663 collides with existing device ID 'test_2258'"
 | |
|         in captured.out
 | |
|     )
 | |
| 
 | |
| 
 | |
| 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", FIXTURES_DIR
 | |
|     )
 | |
|     assert result is None
 | |
| 
 | |
|     # Check for the specific error message about hash collision
 | |
|     captured = capsys.readouterr()
 | |
|     # The error message shows the ID that collides and includes the hash value
 | |
|     assert (
 | |
|         "Area ID 'd6ka' with hash 3082558663 collides with existing area ID 'test_2258'"
 | |
|         in captured.out
 | |
|     )
 | |
| 
 | |
| 
 | |
| 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", FIXTURES_DIR
 | |
|     )
 | |
|     assert result is None
 | |
| 
 | |
|     # Check for the specific error message from IDPassValidationStep
 | |
|     captured = capsys.readouterr()
 | |
|     assert "ID duplicate_device redefined!" in captured.out
 |