From ea5da950c01a47d973fb10d7e4d7abb97365d136 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 20 Aug 2025 12:09:26 -0500 Subject: [PATCH 1/3] [core] Improve error reporting for entity name conflicts with non-ASCII characters --- esphome/core/entity_helpers.py | 10 ++++++++ tests/unit_tests/core/test_entity_helpers.py | 25 ++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/esphome/core/entity_helpers.py b/esphome/core/entity_helpers.py index 1ccc3e2683..c0759e3bb6 100644 --- a/esphome/core/entity_helpers.py +++ b/esphome/core/entity_helpers.py @@ -236,10 +236,20 @@ def entity_duplicate_validator(platform: str) -> Callable[[ConfigType], ConfigTy if existing_component != "unknown": conflict_msg += f" from component '{existing_component}'" + # Show both original names and their ASCII-only versions if they differ + sanitized_msg = "" + if entity_name != name_key or existing_name != name_key: + sanitized_msg = ( + f"\n Original names: '{entity_name}' and '{existing_name}'" + f"\n Both convert to ASCII ID: '{name_key}'" + f"\n To fix: Add unique ASCII characters (e.g., '1', '2', or 'A', 'B') to distinguish them" + ) + raise cv.Invalid( f"Duplicate {platform} entity with name '{entity_name}' found{device_prefix}. " f"{conflict_msg}. " f"Each entity on a device must have a unique name within its platform." + f"{sanitized_msg}" ) # Store metadata about this entity diff --git a/tests/unit_tests/core/test_entity_helpers.py b/tests/unit_tests/core/test_entity_helpers.py index db99243a1a..005728dca8 100644 --- a/tests/unit_tests/core/test_entity_helpers.py +++ b/tests/unit_tests/core/test_entity_helpers.py @@ -705,3 +705,28 @@ def test_empty_or_null_device_id_on_entity() -> None: config2 = {CONF_NAME: "Temperature", CONF_DEVICE_ID: None} validated2 = validator(config2) assert validated2 == config2 + + +def test_entity_duplicate_validator_non_ascii_names() -> None: + """Test that non-ASCII names show helpful error messages.""" + # Create validator for binary_sensor platform + validator = entity_duplicate_validator("binary_sensor") + + # First Russian sensor should pass + config1 = {CONF_NAME: "Датчик открытия основного крана"} + validated1 = validator(config1) + assert validated1 == config1 + + # Second Russian sensor with different text but same ASCII conversion should fail + config2 = {CONF_NAME: "Датчик закрытия основного крана"} + with pytest.raises( + Invalid, + match=re.compile( + r"Duplicate binary_sensor entity with name 'Датчик закрытия основного крана' found.*" + r"Original names: 'Датчик закрытия основного крана' and 'Датчик открытия основного крана'.*" + r"Both convert to ASCII ID: '_______________________________'.*" + r"To fix: Add unique ASCII characters \(e\.g\., '1', '2', or 'A', 'B'\)", + re.DOTALL, + ), + ): + validator(config2) From d182ce8bf6f4fdd1c92fbc3fbb662b1ded563765 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 20 Aug 2025 12:12:39 -0500 Subject: [PATCH 2/3] preen --- esphome/core/entity_helpers.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/esphome/core/entity_helpers.py b/esphome/core/entity_helpers.py index c0759e3bb6..447c894495 100644 --- a/esphome/core/entity_helpers.py +++ b/esphome/core/entity_helpers.py @@ -242,13 +242,14 @@ def entity_duplicate_validator(platform: str) -> Callable[[ConfigType], ConfigTy sanitized_msg = ( f"\n Original names: '{entity_name}' and '{existing_name}'" f"\n Both convert to ASCII ID: '{name_key}'" - f"\n To fix: Add unique ASCII characters (e.g., '1', '2', or 'A', 'B') to distinguish them" + "\n To fix: Add unique ASCII characters (e.g., '1', '2', or 'A', 'B')" + "\n to distinguish them" ) raise cv.Invalid( f"Duplicate {platform} entity with name '{entity_name}' found{device_prefix}. " f"{conflict_msg}. " - f"Each entity on a device must have a unique name within its platform." + "Each entity on a device must have a unique name within its platform." f"{sanitized_msg}" ) From 86c3812174ea7907e10d1e84486401d0ef02bdef Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 20 Aug 2025 12:15:54 -0500 Subject: [PATCH 3/3] preen --- esphome/core/entity_helpers.py | 4 ++-- tests/unit_tests/core/test_entity_helpers.py | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/esphome/core/entity_helpers.py b/esphome/core/entity_helpers.py index 447c894495..e1b2a8264b 100644 --- a/esphome/core/entity_helpers.py +++ b/esphome/core/entity_helpers.py @@ -238,12 +238,12 @@ def entity_duplicate_validator(platform: str) -> Callable[[ConfigType], ConfigTy # Show both original names and their ASCII-only versions if they differ sanitized_msg = "" - if entity_name != name_key or existing_name != name_key: + if entity_name != existing_name: sanitized_msg = ( f"\n Original names: '{entity_name}' and '{existing_name}'" f"\n Both convert to ASCII ID: '{name_key}'" "\n To fix: Add unique ASCII characters (e.g., '1', '2', or 'A', 'B')" - "\n to distinguish them" + "\n to distinguish them" ) raise cv.Invalid( diff --git a/tests/unit_tests/core/test_entity_helpers.py b/tests/unit_tests/core/test_entity_helpers.py index 005728dca8..9ba5367413 100644 --- a/tests/unit_tests/core/test_entity_helpers.py +++ b/tests/unit_tests/core/test_entity_helpers.py @@ -730,3 +730,23 @@ def test_entity_duplicate_validator_non_ascii_names() -> None: ), ): validator(config2) + + +def test_entity_duplicate_validator_same_name_no_enhanced_message() -> None: + """Test that identical names don't show the enhanced message.""" + # Create validator for sensor platform + validator = entity_duplicate_validator("sensor") + + # First entity should pass + config1 = {CONF_NAME: "Temperature"} + validated1 = validator(config1) + assert validated1 == config1 + + # Second entity with exact same name should fail without enhanced message + config2 = {CONF_NAME: "Temperature"} + with pytest.raises( + Invalid, + match=r"Duplicate sensor entity with name 'Temperature' found.*" + r"Each entity on a device must have a unique name within its platform\.$", + ): + validator(config2)