From 54a8f558d6d000a12b8a7828126ce788ff9fe522 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 6 Mar 2026 09:51:26 -1000 Subject: [PATCH] Convert finalize tests to end-to-end through public API Tests now go through _setup_entity_impl + setup_device_class/ setup_unit_of_measurement + finalize_entity_strings using real CONF_* keys instead of internal _KEY_* constants. --- tests/unit_tests/core/test_entity_helpers.py | 147 +++++++++++-------- 1 file changed, 82 insertions(+), 65 deletions(-) diff --git a/tests/unit_tests/core/test_entity_helpers.py b/tests/unit_tests/core/test_entity_helpers.py index 4e24a21b68..a68955fe08 100644 --- a/tests/unit_tests/core/test_entity_helpers.py +++ b/tests/unit_tests/core/test_entity_helpers.py @@ -26,14 +26,6 @@ from esphome.core.entity_helpers import ( _ENTITY_CATEGORY_SHIFT, _ICON_SHIFT, _INTERNAL_SHIFT, - _KEY_DC_IDX, - _KEY_DISABLED_BY_DEFAULT, - _KEY_ENTITY_CATEGORY, - _KEY_ENTITY_NAME, - _KEY_ICON_IDX, - _KEY_INTERNAL, - _KEY_OBJECT_ID_HASH, - _KEY_UOM_IDX, _UOM_SHIFT, _register_string, _setup_entity_impl, @@ -42,7 +34,9 @@ from esphome.core.entity_helpers import ( get_base_entity_object_id, register_device_class, register_icon, + setup_device_class, setup_entity, + setup_unit_of_measurement, ) from esphome.cpp_generator import MockObj from esphome.helpers import sanitize, snake_case @@ -941,7 +935,8 @@ def test_register_device_class_max_length() -> None: async def test_setup_entity_with_entity_category( setup_test_environment: list[str], ) -> None: - """Test setup_entity sets entity_category correctly.""" + """Test entity_category is packed correctly through the full setup flow.""" + added_expressions = setup_test_environment var = MockObj("sensor1") config = { CONF_NAME: "Temperature", @@ -949,9 +944,9 @@ async def test_setup_entity_with_entity_category( CONF_ENTITY_CATEGORY: "diagnostic", } await _setup_entity_impl(var, config, "sensor") - # entity_category is now packed into config for configure_entity_() - # "diagnostic" maps to integer value 2 - assert config.get("_entity_category") == 2 + finalize_entity_strings(var, config) + packed = _extract_packed_value(added_expressions) + assert (packed >> _ENTITY_CATEGORY_SHIFT) & 0x3 == 2 @pytest.mark.asyncio @@ -1016,131 +1011,147 @@ def _extract_packed_value(expressions: list[str]) -> int: raise AssertionError("No configure_entity_ call found") -def test_finalize_entity_strings_no_flags(setup_test_environment: list[str]) -> None: - """Test finalize_entity_strings with no flags set — no comment emitted.""" +@pytest.mark.asyncio +async def test_finalize_no_flags(setup_test_environment: list[str]) -> None: + """Test entity with no special flags — packed value is 0, no comment.""" added_expressions = setup_test_environment var = MockObj("sensor1") config = { - _KEY_ENTITY_NAME: "Test", - _KEY_OBJECT_ID_HASH: 12345, + CONF_NAME: "Test", + CONF_DISABLED_BY_DEFAULT: False, } + await _setup_entity_impl(var, config, "sensor") finalize_entity_strings(var, config) packed = _extract_packed_value(added_expressions) assert packed == 0 - # No comment when all flags are default assert "//" not in added_expressions[0] -def test_finalize_entity_strings_internal(setup_test_environment: list[str]) -> None: - """Test finalize_entity_strings with internal=True.""" +@pytest.mark.asyncio +async def test_finalize_internal(setup_test_environment: list[str]) -> None: + """Test entity with internal=True packs the internal bit.""" added_expressions = setup_test_environment var = MockObj("sensor1") config = { - _KEY_ENTITY_NAME: "Test", - _KEY_OBJECT_ID_HASH: 12345, - _KEY_INTERNAL: 1, + CONF_NAME: "Test", + CONF_DISABLED_BY_DEFAULT: False, + CONF_INTERNAL: True, } + await _setup_entity_impl(var, config, "sensor") finalize_entity_strings(var, config) packed = _extract_packed_value(added_expressions) assert packed & (1 << _INTERNAL_SHIFT) != 0 - # No other flags set assert packed == (1 << _INTERNAL_SHIFT) -def test_finalize_entity_strings_disabled_by_default( +@pytest.mark.asyncio +async def test_finalize_disabled_by_default( setup_test_environment: list[str], ) -> None: - """Test finalize_entity_strings with disabled_by_default=True.""" + """Test entity with disabled_by_default=True packs the bit.""" added_expressions = setup_test_environment var = MockObj("sensor1") config = { - _KEY_ENTITY_NAME: "Test", - _KEY_OBJECT_ID_HASH: 12345, - _KEY_DISABLED_BY_DEFAULT: 1, + CONF_NAME: "Test", + CONF_DISABLED_BY_DEFAULT: True, } + await _setup_entity_impl(var, config, "sensor") finalize_entity_strings(var, config) packed = _extract_packed_value(added_expressions) assert packed & (1 << _DISABLED_BY_DEFAULT_SHIFT) != 0 assert packed == (1 << _DISABLED_BY_DEFAULT_SHIFT) -def test_finalize_entity_strings_entity_category( +@pytest.mark.asyncio +async def test_finalize_entity_category( setup_test_environment: list[str], ) -> None: - """Test finalize_entity_strings with entity_category values.""" + """Test entity_category values (diagnostic=2, config=1) are packed.""" added_expressions = setup_test_environment var = MockObj("sensor1") # Test diagnostic (value 2) config = { - _KEY_ENTITY_NAME: "Test", - _KEY_OBJECT_ID_HASH: 12345, - _KEY_ENTITY_CATEGORY: 2, + CONF_NAME: "Test", + CONF_DISABLED_BY_DEFAULT: False, + CONF_ENTITY_CATEGORY: "diagnostic", } + await _setup_entity_impl(var, config, "sensor") finalize_entity_strings(var, config) packed = _extract_packed_value(added_expressions) assert (packed >> _ENTITY_CATEGORY_SHIFT) & 0x3 == 2 # Test config (value 1) added_expressions.clear() - config[_KEY_ENTITY_CATEGORY] = 1 - finalize_entity_strings(var, config) + config2 = { + CONF_NAME: "Test2", + CONF_DISABLED_BY_DEFAULT: False, + CONF_ENTITY_CATEGORY: "config", + } + await _setup_entity_impl(var, config2, "sensor") + finalize_entity_strings(var, config2) packed = _extract_packed_value(added_expressions) assert (packed >> _ENTITY_CATEGORY_SHIFT) & 0x3 == 1 -def test_finalize_entity_strings_string_indices( +@pytest.mark.asyncio +async def test_finalize_string_indices( setup_test_environment: list[str], ) -> None: - """Test finalize_entity_strings packs string indices correctly.""" + """Test device_class, unit_of_measurement, and icon are packed as indices.""" added_expressions = setup_test_environment var = MockObj("sensor1") config = { - _KEY_ENTITY_NAME: "Test", - _KEY_OBJECT_ID_HASH: 12345, - _KEY_DC_IDX: 3, - _KEY_UOM_IDX: 5, - _KEY_ICON_IDX: 7, + CONF_NAME: "Test", + CONF_DISABLED_BY_DEFAULT: False, + CONF_DEVICE_CLASS: "temperature", + CONF_UNIT_OF_MEASUREMENT: "°C", + CONF_ICON: "mdi:thermometer", } + await _setup_entity_impl(var, config, "sensor") + setup_device_class(config) + setup_unit_of_measurement(config) finalize_entity_strings(var, config) packed = _extract_packed_value(added_expressions) - assert (packed >> _DC_SHIFT) & 0xFF == 3 - assert (packed >> _UOM_SHIFT) & 0xFF == 5 - assert (packed >> _ICON_SHIFT) & 0xFF == 7 + # All three string indices should be non-zero + assert (packed >> _DC_SHIFT) & 0xFF != 0 + assert (packed >> _UOM_SHIFT) & 0xFF != 0 + assert (packed >> _ICON_SHIFT) & 0xFF != 0 # No flags set assert (packed >> _INTERNAL_SHIFT) & 1 == 0 assert (packed >> _DISABLED_BY_DEFAULT_SHIFT) & 1 == 0 assert (packed >> _ENTITY_CATEGORY_SHIFT) & 0x3 == 0 -def test_finalize_entity_strings_all_fields( +@pytest.mark.asyncio +async def test_finalize_all_fields( setup_test_environment: list[str], ) -> None: - """Test finalize_entity_strings with all fields set.""" + """Test all fields set: flags, string indices, and comment.""" added_expressions = setup_test_environment var = MockObj("sensor1") config = { - _KEY_ENTITY_NAME: "Test", - _KEY_OBJECT_ID_HASH: 12345, - _KEY_DC_IDX: 1, - _KEY_UOM_IDX: 2, - _KEY_ICON_IDX: 3, - _KEY_INTERNAL: 1, - _KEY_DISABLED_BY_DEFAULT: 1, - _KEY_ENTITY_CATEGORY: 2, # diagnostic + CONF_NAME: "Test", + CONF_DISABLED_BY_DEFAULT: True, + CONF_INTERNAL: True, + CONF_ENTITY_CATEGORY: "diagnostic", CONF_DEVICE_CLASS: "temperature", CONF_UNIT_OF_MEASUREMENT: "°C", CONF_ICON: "mdi:thermometer", } + await _setup_entity_impl(var, config, "sensor") + setup_device_class(config) + setup_unit_of_measurement(config) finalize_entity_strings(var, config) packed = _extract_packed_value(added_expressions) - # Verify all fields - assert (packed >> _DC_SHIFT) & 0xFF == 1 - assert (packed >> _UOM_SHIFT) & 0xFF == 2 - assert (packed >> _ICON_SHIFT) & 0xFF == 3 + # Verify flags assert (packed >> _INTERNAL_SHIFT) & 1 == 1 assert (packed >> _DISABLED_BY_DEFAULT_SHIFT) & 1 == 1 assert (packed >> _ENTITY_CATEGORY_SHIFT) & 0x3 == 2 + # Verify string indices are non-zero + assert (packed >> _DC_SHIFT) & 0xFF != 0 + assert (packed >> _UOM_SHIFT) & 0xFF != 0 + assert (packed >> _ICON_SHIFT) & 0xFF != 0 # Verify comment contains all flags with actual string values comment_line = added_expressions[0] assert ( @@ -1149,19 +1160,20 @@ def test_finalize_entity_strings_all_fields( ) -def test_finalize_entity_strings_comment_sanitization( +@pytest.mark.asyncio +async def test_finalize_comment_sanitization( setup_test_environment: list[str], ) -> None: """Test that user strings in comments are sanitized against injection.""" added_expressions = setup_test_environment var = MockObj("sensor1") config = { - _KEY_ENTITY_NAME: "Test", - _KEY_OBJECT_ID_HASH: 12345, - _KEY_ICON_IDX: 1, + CONF_NAME: "Test", + CONF_DISABLED_BY_DEFAULT: False, # Backslash at end would cause line splice eating next code line CONF_ICON: "mdi:evil\\", } + await _setup_entity_impl(var, config, "sensor") finalize_entity_strings(var, config) comment_line = added_expressions[0] # Backslash must be replaced to prevent line splice @@ -1169,8 +1181,13 @@ def test_finalize_entity_strings_comment_sanitization( assert "mdi:evil/" in comment_line added_expressions.clear() - config[CONF_ICON] = "mdi:evil\nINJECTED_CODE();" - finalize_entity_strings(var, config) + config2 = { + CONF_NAME: "Test2", + CONF_DISABLED_BY_DEFAULT: False, + CONF_ICON: "mdi:evil\nINJECTED_CODE();", + } + await _setup_entity_impl(var, config2, "sensor") + finalize_entity_strings(var, config2) comment_line = added_expressions[0] # Newline must be replaced to prevent breaking out of comment assert "\n" not in comment_line