mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 15:12:06 +00:00 
			
		
		
		
	cleanup
This commit is contained in:
		| @@ -12,7 +12,7 @@ from esphome.const import ( | |||||||
| ) | ) | ||||||
| from esphome.core import CORE, ID | from esphome.core import CORE, ID | ||||||
| from esphome.cpp_generator import MockObj, add, get_variable | from esphome.cpp_generator import MockObj, add, get_variable | ||||||
| from esphome.helpers import sanitize, snake_case | from esphome.helpers import fnv1a_32bit_hash, sanitize, snake_case | ||||||
| from esphome.types import ConfigType | from esphome.types import ConfigType | ||||||
|  |  | ||||||
| _LOGGER = logging.getLogger(__name__) | _LOGGER = logging.getLogger(__name__) | ||||||
| @@ -81,7 +81,6 @@ async def setup_entity(var: MockObj, config: ConfigType, platform: str) -> None: | |||||||
|         device: MockObj = await get_variable(device_id_obj) |         device: MockObj = await get_variable(device_id_obj) | ||||||
|         add(var.set_device(device)) |         add(var.set_device(device)) | ||||||
|         # Use the device's ID hash as device_id |         # Use the device's ID hash as device_id | ||||||
|         from esphome.helpers import fnv1a_32bit_hash |  | ||||||
|  |  | ||||||
|         device_id = fnv1a_32bit_hash(device_id_obj.id) |         device_id = fnv1a_32bit_hash(device_id_obj.id) | ||||||
|         # Get device name for object ID calculation |         # Get device name for object ID calculation | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| """Test get_base_entity_object_id function matches C++ behavior.""" | """Test get_base_entity_object_id function matches C++ behavior.""" | ||||||
|  |  | ||||||
| from collections.abc import Generator | from collections.abc import Generator | ||||||
|  | import re | ||||||
| from typing import Any | from typing import Any | ||||||
|  |  | ||||||
| import pytest | import pytest | ||||||
| @@ -107,9 +108,9 @@ def test_edge_cases() -> None: | |||||||
|     assert get_base_entity_object_id(long_name, None) == expected |     assert get_base_entity_object_id(long_name, None) == expected | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_matches_cpp_helpers() -> None: | @pytest.mark.parametrize( | ||||||
|     """Test that the logic matches using snake_case and sanitize directly.""" |     ("name", "expected"), | ||||||
|     test_cases = [ |     [ | ||||||
|         ("Temperature Sensor", "temperature_sensor"), |         ("Temperature Sensor", "temperature_sensor"), | ||||||
|         ("Living Room Light", "living_room_light"), |         ("Living Room Light", "living_room_light"), | ||||||
|         ("Test-Device_123", "test-device_123"), |         ("Test-Device_123", "test-device_123"), | ||||||
| @@ -118,13 +119,17 @@ def test_matches_cpp_helpers() -> None: | |||||||
|         ("lowercase name", "lowercase_name"), |         ("lowercase name", "lowercase_name"), | ||||||
|         ("Mixed Case Name", "mixed_case_name"), |         ("Mixed Case Name", "mixed_case_name"), | ||||||
|         ("   Spaces   ", "___spaces___"), |         ("   Spaces   ", "___spaces___"), | ||||||
|     ] |     ], | ||||||
|  | ) | ||||||
|  | def test_matches_cpp_helpers(name: str, expected: str) -> None: | ||||||
|  |     """Test that the logic matches using snake_case and sanitize directly.""" | ||||||
|  |     # For non-empty names, verify our function produces same result as direct snake_case + sanitize | ||||||
|  |     assert get_base_entity_object_id(name, None) == sanitize(snake_case(name)) | ||||||
|  |     assert get_base_entity_object_id(name, None) == expected | ||||||
|  |  | ||||||
|     for name, expected in test_cases: |  | ||||||
|         # For non-empty names, verify our function produces same result as direct snake_case + sanitize |  | ||||||
|         assert get_base_entity_object_id(name, None) == sanitize(snake_case(name)) |  | ||||||
|         assert get_base_entity_object_id(name, None) == expected |  | ||||||
|  |  | ||||||
|  | def test_empty_name_fallback() -> None: | ||||||
|  |     """Test empty name handling which falls back to friendly_name or CORE.name.""" | ||||||
|     # Empty name is handled specially - it doesn't just use sanitize(snake_case("")) |     # Empty name is handled specially - it doesn't just use sanitize(snake_case("")) | ||||||
|     # Instead it falls back to friendly_name or CORE.name |     # Instead it falls back to friendly_name or CORE.name | ||||||
|     assert sanitize(snake_case("")) == ""  # Direct conversion gives empty string |     assert sanitize(snake_case("")) == ""  # Direct conversion gives empty string | ||||||
| @@ -169,10 +174,9 @@ def test_priority_order() -> None: | |||||||
|     assert get_base_entity_object_id("", None, None) == "core-device" |     assert get_base_entity_object_id("", None, None) == "core-device" | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_real_world_examples() -> None: | @pytest.mark.parametrize( | ||||||
|     """Test real-world entity naming scenarios.""" |     ("name", "friendly_name", "device_name", "expected"), | ||||||
|     # Common ESPHome entity names |     [ | ||||||
|     test_cases = [ |  | ||||||
|         # name, friendly_name, device_name, expected |         # name, friendly_name, device_name, expected | ||||||
|         ("Living Room Light", None, None, "living_room_light"), |         ("Living Room Light", None, None, "living_room_light"), | ||||||
|         ("", "Kitchen Controller", None, "kitchen_controller"), |         ("", "Kitchen Controller", None, "kitchen_controller"), | ||||||
| @@ -186,13 +190,14 @@ def test_real_world_examples() -> None: | |||||||
|         ("WiFi Signal", "My Device", None, "wifi_signal"), |         ("WiFi Signal", "My Device", None, "wifi_signal"), | ||||||
|         ("", None, "esp32_node", "esp32_node"), |         ("", None, "esp32_node", "esp32_node"), | ||||||
|         ("Front Door Sensor", "Home Assistant", "door_controller", "front_door_sensor"), |         ("Front Door Sensor", "Home Assistant", "door_controller", "front_door_sensor"), | ||||||
|     ] |     ], | ||||||
|  | ) | ||||||
|     for name, friendly_name, device_name, expected in test_cases: | def test_real_world_examples( | ||||||
|         result = get_base_entity_object_id(name, friendly_name, device_name) |     name: str, friendly_name: str | None, device_name: str | None, expected: str | ||||||
|         assert result == expected, ( | ) -> None: | ||||||
|             f"Failed for {name=}, {friendly_name=}, {device_name=}: {result=}, {expected=}" |     """Test real-world entity naming scenarios.""" | ||||||
|         ) |     result = get_base_entity_object_id(name, friendly_name, device_name) | ||||||
|  |     assert result == expected | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_issue_6953_scenarios() -> None: | def test_issue_6953_scenarios() -> None: | ||||||
| @@ -226,15 +231,14 @@ def test_issue_6953_scenarios() -> None: | |||||||
| @pytest.fixture | @pytest.fixture | ||||||
| def setup_test_environment() -> Generator[list[str], None, None]: | def setup_test_environment() -> Generator[list[str], None, None]: | ||||||
|     """Set up test environment for setup_entity tests.""" |     """Set up test environment for setup_entity tests.""" | ||||||
|     # Reset CORE state |     # Set CORE state for tests | ||||||
|     CORE.reset() |  | ||||||
|     CORE.name = "test-device" |     CORE.name = "test-device" | ||||||
|     CORE.friendly_name = "Test Device" |     CORE.friendly_name = "Test Device" | ||||||
|     # Store original add function |     # Store original add function | ||||||
|  |  | ||||||
|     original_add = entity.add |     original_add = entity.add | ||||||
|     # Track what gets added |     # Track what gets added | ||||||
|     added_expressions = [] |     added_expressions: list[str] = [] | ||||||
|  |  | ||||||
|     def mock_add(expression: Any) -> Any: |     def mock_add(expression: Any) -> Any: | ||||||
|         added_expressions.append(str(expression)) |         added_expressions.append(str(expression)) | ||||||
| @@ -245,19 +249,16 @@ def setup_test_environment() -> Generator[list[str], None, None]: | |||||||
|     yield added_expressions |     yield added_expressions | ||||||
|     # Clean up |     # Clean up | ||||||
|     entity.add = original_add |     entity.add = original_add | ||||||
|     CORE.reset() |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def extract_object_id_from_expressions(expressions: list[str]) -> str | None: | def extract_object_id_from_expressions(expressions: list[str]) -> str | None: | ||||||
|     """Extract the object ID that was set from the generated expressions.""" |     """Extract the object ID that was set from the generated expressions.""" | ||||||
|     for expr in expressions: |     for expr in expressions: | ||||||
|         # Look for set_object_id calls |         # Look for set_object_id calls with regex to handle various formats | ||||||
|         if ".set_object_id(" in expr: |         # Matches: var.set_object_id("temperature_2") or var.set_object_id('temperature_2') | ||||||
|             # Extract the ID from something like: var.set_object_id("temperature_2") |         match = re.search(r'\.set_object_id\(["\'](.*?)["\']\)', expr) | ||||||
|             start = expr.find('"') + 1 |         if match: | ||||||
|             end = expr.rfind('"') |             return match.group(1) | ||||||
|             if start > 0 and end > start: |  | ||||||
|                 return expr[start:end] |  | ||||||
|     return None |     return None | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -312,7 +313,7 @@ async def test_setup_entity_with_duplicates(setup_test_environment: list[str]) - | |||||||
|         CONF_DISABLED_BY_DEFAULT: False, |         CONF_DISABLED_BY_DEFAULT: False, | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     object_ids = [] |     object_ids: list[str] = [] | ||||||
|     for var in entities: |     for var in entities: | ||||||
|         added_expressions.clear() |         added_expressions.clear() | ||||||
|         await setup_entity(var, config, "sensor") |         await setup_entity(var, config, "sensor") | ||||||
| @@ -351,7 +352,7 @@ async def test_setup_entity_different_platforms( | |||||||
|         (text_sensor, "text_sensor"), |         (text_sensor, "text_sensor"), | ||||||
|     ] |     ] | ||||||
|  |  | ||||||
|     object_ids = [] |     object_ids: list[str] = [] | ||||||
|     for var, platform in platforms: |     for var, platform in platforms: | ||||||
|         added_expressions.clear() |         added_expressions.clear() | ||||||
|         await setup_entity(var, config, platform) |         await setup_entity(var, config, platform) | ||||||
| @@ -362,62 +363,67 @@ async def test_setup_entity_different_platforms( | |||||||
|     assert all(obj_id == "status" for obj_id in object_ids) |     assert all(obj_id == "status" for obj_id in object_ids) | ||||||
|  |  | ||||||
|  |  | ||||||
| @pytest.mark.asyncio | @pytest.fixture | ||||||
| async def test_setup_entity_with_devices(setup_test_environment: list[str]) -> None: | def mock_get_variable() -> Generator[dict[ID, MockObj], None, None]: | ||||||
|     """Test that same name on different devices doesn't conflict.""" |     """Mock get_variable to return test devices.""" | ||||||
|  |     devices = {} | ||||||
|  |     original_get_variable = entity.get_variable | ||||||
|  |  | ||||||
|  |     async def _mock_get_variable(device_id: ID) -> MockObj: | ||||||
|  |         if device_id in devices: | ||||||
|  |             return devices[device_id] | ||||||
|  |         return await original_get_variable(device_id) | ||||||
|  |  | ||||||
|  |     entity.get_variable = _mock_get_variable | ||||||
|  |     yield devices | ||||||
|  |     # Clean up | ||||||
|  |     entity.get_variable = original_get_variable | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @pytest.mark.asyncio | ||||||
|  | async def test_setup_entity_with_devices( | ||||||
|  |     setup_test_environment: list[str], mock_get_variable: dict[ID, MockObj] | ||||||
|  | ) -> None: | ||||||
|  |     """Test that same name on different devices doesn't conflict.""" | ||||||
|     added_expressions = setup_test_environment |     added_expressions = setup_test_environment | ||||||
|  |  | ||||||
|     # Create mock devices |     # Create mock devices | ||||||
|     device1_id = ID("device1", type="Device") |     device1_id = ID("device1", type="Device") | ||||||
|     device2_id = ID("device2", type="Device") |     device2_id = ID("device2", type="Device") | ||||||
|  |  | ||||||
|     device1 = MockObj("device1_obj") |     device1 = MockObj("device1_obj") | ||||||
|     device2 = MockObj("device2_obj") |     device2 = MockObj("device2_obj") | ||||||
|  |  | ||||||
|     # Mock get_variable to return our devices |     # Register devices with the mock | ||||||
|     original_get_variable = entity.get_variable |     mock_get_variable[device1_id] = device1 | ||||||
|  |     mock_get_variable[device2_id] = device2 | ||||||
|  |  | ||||||
|     async def mock_get_variable(device_id: ID) -> MockObj: |     # Create sensors with same name on different devices | ||||||
|         if device_id == device1_id: |     sensor1 = MockObj("sensor1") | ||||||
|             return device1 |     sensor2 = MockObj("sensor2") | ||||||
|         elif device_id == device2_id: |  | ||||||
|             return device2 |  | ||||||
|         return await original_get_variable(device_id) |  | ||||||
|  |  | ||||||
|     entity.get_variable = mock_get_variable |     config1 = { | ||||||
|  |         CONF_NAME: "Temperature", | ||||||
|  |         CONF_DEVICE_ID: device1_id, | ||||||
|  |         CONF_DISABLED_BY_DEFAULT: False, | ||||||
|  |     } | ||||||
|  |  | ||||||
|     try: |     config2 = { | ||||||
|         # Create sensors with same name on different devices |         CONF_NAME: "Temperature", | ||||||
|         sensor1 = MockObj("sensor1") |         CONF_DEVICE_ID: device2_id, | ||||||
|         sensor2 = MockObj("sensor2") |         CONF_DISABLED_BY_DEFAULT: False, | ||||||
|  |     } | ||||||
|  |  | ||||||
|         config1 = { |     # Get object IDs | ||||||
|             CONF_NAME: "Temperature", |     object_ids: list[str] = [] | ||||||
|             CONF_DEVICE_ID: device1_id, |     for var, config in [(sensor1, config1), (sensor2, config2)]: | ||||||
|             CONF_DISABLED_BY_DEFAULT: False, |         added_expressions.clear() | ||||||
|         } |         await setup_entity(var, config, "sensor") | ||||||
|  |         object_id = extract_object_id_from_expressions(added_expressions) | ||||||
|  |         object_ids.append(object_id) | ||||||
|  |  | ||||||
|         config2 = { |     # Both should get base object ID without suffix (different devices) | ||||||
|             CONF_NAME: "Temperature", |     assert object_ids[0] == "temperature" | ||||||
|             CONF_DEVICE_ID: device2_id, |     assert object_ids[1] == "temperature" | ||||||
|             CONF_DISABLED_BY_DEFAULT: False, |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         # Get object IDs |  | ||||||
|         object_ids = [] |  | ||||||
|         for var, config in [(sensor1, config1), (sensor2, config2)]: |  | ||||||
|             added_expressions.clear() |  | ||||||
|             await setup_entity(var, config, "sensor") |  | ||||||
|             object_id = extract_object_id_from_expressions(added_expressions) |  | ||||||
|             object_ids.append(object_id) |  | ||||||
|  |  | ||||||
|         # Both should get base object ID without suffix (different devices) |  | ||||||
|         assert object_ids[0] == "temperature" |  | ||||||
|         assert object_ids[1] == "temperature" |  | ||||||
|  |  | ||||||
|     finally: |  | ||||||
|         entity.get_variable = original_get_variable |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @pytest.mark.asyncio | @pytest.mark.asyncio | ||||||
| @@ -455,7 +461,7 @@ async def test_setup_entity_empty_name_duplicates( | |||||||
|         CONF_DISABLED_BY_DEFAULT: False, |         CONF_DISABLED_BY_DEFAULT: False, | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     object_ids = [] |     object_ids: list[str] = [] | ||||||
|     for var in entities: |     for var in entities: | ||||||
|         added_expressions.clear() |         added_expressions.clear() | ||||||
|         await setup_entity(var, config, "sensor") |         await setup_entity(var, config, "sensor") | ||||||
| @@ -483,7 +489,7 @@ async def test_setup_entity_special_characters( | |||||||
|         CONF_DISABLED_BY_DEFAULT: False, |         CONF_DISABLED_BY_DEFAULT: False, | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     object_ids = [] |     object_ids: list[str] = [] | ||||||
|     for var in entities: |     for var in entities: | ||||||
|         added_expressions.clear() |         added_expressions.clear() | ||||||
|         await setup_entity(var, config, "sensor") |         await setup_entity(var, config, "sensor") | ||||||
| @@ -513,10 +519,9 @@ async def test_setup_entity_with_icon(setup_test_environment: list[str]) -> None | |||||||
|     await setup_entity(var, config, "sensor") |     await setup_entity(var, config, "sensor") | ||||||
|  |  | ||||||
|     # Check icon was set |     # Check icon was set | ||||||
|     icon_set = any( |     assert any( | ||||||
|         ".set_icon(" in expr and "mdi:thermometer" in expr for expr in added_expressions |         'sensor1.set_icon("mdi:thermometer")' in expr for expr in added_expressions | ||||||
|     ) |     ) | ||||||
|     assert icon_set |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @pytest.mark.asyncio | @pytest.mark.asyncio | ||||||
| @@ -537,10 +542,9 @@ async def test_setup_entity_disabled_by_default( | |||||||
|     await setup_entity(var, config, "sensor") |     await setup_entity(var, config, "sensor") | ||||||
|  |  | ||||||
|     # Check disabled_by_default was set |     # Check disabled_by_default was set | ||||||
|     disabled_set = any( |     assert any( | ||||||
|         ".set_disabled_by_default(true)" in expr.lower() for expr in added_expressions |         "sensor1.set_disabled_by_default(true)" in expr for expr in added_expressions | ||||||
|     ) |     ) | ||||||
|     assert disabled_set |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @pytest.mark.asyncio | @pytest.mark.asyncio | ||||||
| @@ -550,7 +554,7 @@ async def test_setup_entity_mixed_duplicates(setup_test_environment: list[str]) | |||||||
|     added_expressions = setup_test_environment |     added_expressions = setup_test_environment | ||||||
|  |  | ||||||
|     # Track results |     # Track results | ||||||
|     results = [] |     results: list[tuple[str, str]] = [] | ||||||
|  |  | ||||||
|     # 3 sensors named "Status" |     # 3 sensors named "Status" | ||||||
|     for i in range(3): |     for i in range(3): | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user