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.
This commit is contained in:
J. Nick Koston
2026-03-06 09:51:26 -10:00
parent c37f1481ce
commit 54a8f558d6
+82 -65
View File
@@ -26,14 +26,6 @@ from esphome.core.entity_helpers import (
_ENTITY_CATEGORY_SHIFT, _ENTITY_CATEGORY_SHIFT,
_ICON_SHIFT, _ICON_SHIFT,
_INTERNAL_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, _UOM_SHIFT,
_register_string, _register_string,
_setup_entity_impl, _setup_entity_impl,
@@ -42,7 +34,9 @@ from esphome.core.entity_helpers import (
get_base_entity_object_id, get_base_entity_object_id,
register_device_class, register_device_class,
register_icon, register_icon,
setup_device_class,
setup_entity, setup_entity,
setup_unit_of_measurement,
) )
from esphome.cpp_generator import MockObj from esphome.cpp_generator import MockObj
from esphome.helpers import sanitize, snake_case 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( async def test_setup_entity_with_entity_category(
setup_test_environment: list[str], setup_test_environment: list[str],
) -> None: ) -> 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") var = MockObj("sensor1")
config = { config = {
CONF_NAME: "Temperature", CONF_NAME: "Temperature",
@@ -949,9 +944,9 @@ async def test_setup_entity_with_entity_category(
CONF_ENTITY_CATEGORY: "diagnostic", CONF_ENTITY_CATEGORY: "diagnostic",
} }
await _setup_entity_impl(var, config, "sensor") await _setup_entity_impl(var, config, "sensor")
# entity_category is now packed into config for configure_entity_() finalize_entity_strings(var, config)
# "diagnostic" maps to integer value 2 packed = _extract_packed_value(added_expressions)
assert config.get("_entity_category") == 2 assert (packed >> _ENTITY_CATEGORY_SHIFT) & 0x3 == 2
@pytest.mark.asyncio @pytest.mark.asyncio
@@ -1016,131 +1011,147 @@ def _extract_packed_value(expressions: list[str]) -> int:
raise AssertionError("No configure_entity_ call found") raise AssertionError("No configure_entity_ call found")
def test_finalize_entity_strings_no_flags(setup_test_environment: list[str]) -> None: @pytest.mark.asyncio
"""Test finalize_entity_strings with no flags set — no comment emitted.""" 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 added_expressions = setup_test_environment
var = MockObj("sensor1") var = MockObj("sensor1")
config = { config = {
_KEY_ENTITY_NAME: "Test", CONF_NAME: "Test",
_KEY_OBJECT_ID_HASH: 12345, CONF_DISABLED_BY_DEFAULT: False,
} }
await _setup_entity_impl(var, config, "sensor")
finalize_entity_strings(var, config) finalize_entity_strings(var, config)
packed = _extract_packed_value(added_expressions) packed = _extract_packed_value(added_expressions)
assert packed == 0 assert packed == 0
# No comment when all flags are default
assert "//" not in added_expressions[0] assert "//" not in added_expressions[0]
def test_finalize_entity_strings_internal(setup_test_environment: list[str]) -> None: @pytest.mark.asyncio
"""Test finalize_entity_strings with internal=True.""" 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 added_expressions = setup_test_environment
var = MockObj("sensor1") var = MockObj("sensor1")
config = { config = {
_KEY_ENTITY_NAME: "Test", CONF_NAME: "Test",
_KEY_OBJECT_ID_HASH: 12345, CONF_DISABLED_BY_DEFAULT: False,
_KEY_INTERNAL: 1, CONF_INTERNAL: True,
} }
await _setup_entity_impl(var, config, "sensor")
finalize_entity_strings(var, config) finalize_entity_strings(var, config)
packed = _extract_packed_value(added_expressions) packed = _extract_packed_value(added_expressions)
assert packed & (1 << _INTERNAL_SHIFT) != 0 assert packed & (1 << _INTERNAL_SHIFT) != 0
# No other flags set
assert packed == (1 << _INTERNAL_SHIFT) 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], setup_test_environment: list[str],
) -> None: ) -> 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 added_expressions = setup_test_environment
var = MockObj("sensor1") var = MockObj("sensor1")
config = { config = {
_KEY_ENTITY_NAME: "Test", CONF_NAME: "Test",
_KEY_OBJECT_ID_HASH: 12345, CONF_DISABLED_BY_DEFAULT: True,
_KEY_DISABLED_BY_DEFAULT: 1,
} }
await _setup_entity_impl(var, config, "sensor")
finalize_entity_strings(var, config) finalize_entity_strings(var, config)
packed = _extract_packed_value(added_expressions) packed = _extract_packed_value(added_expressions)
assert packed & (1 << _DISABLED_BY_DEFAULT_SHIFT) != 0 assert packed & (1 << _DISABLED_BY_DEFAULT_SHIFT) != 0
assert packed == (1 << _DISABLED_BY_DEFAULT_SHIFT) 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], setup_test_environment: list[str],
) -> None: ) -> None:
"""Test finalize_entity_strings with entity_category values.""" """Test entity_category values (diagnostic=2, config=1) are packed."""
added_expressions = setup_test_environment added_expressions = setup_test_environment
var = MockObj("sensor1") var = MockObj("sensor1")
# Test diagnostic (value 2) # Test diagnostic (value 2)
config = { config = {
_KEY_ENTITY_NAME: "Test", CONF_NAME: "Test",
_KEY_OBJECT_ID_HASH: 12345, CONF_DISABLED_BY_DEFAULT: False,
_KEY_ENTITY_CATEGORY: 2, CONF_ENTITY_CATEGORY: "diagnostic",
} }
await _setup_entity_impl(var, config, "sensor")
finalize_entity_strings(var, config) finalize_entity_strings(var, config)
packed = _extract_packed_value(added_expressions) packed = _extract_packed_value(added_expressions)
assert (packed >> _ENTITY_CATEGORY_SHIFT) & 0x3 == 2 assert (packed >> _ENTITY_CATEGORY_SHIFT) & 0x3 == 2
# Test config (value 1) # Test config (value 1)
added_expressions.clear() added_expressions.clear()
config[_KEY_ENTITY_CATEGORY] = 1 config2 = {
finalize_entity_strings(var, config) 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) packed = _extract_packed_value(added_expressions)
assert (packed >> _ENTITY_CATEGORY_SHIFT) & 0x3 == 1 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], setup_test_environment: list[str],
) -> None: ) -> 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 added_expressions = setup_test_environment
var = MockObj("sensor1") var = MockObj("sensor1")
config = { config = {
_KEY_ENTITY_NAME: "Test", CONF_NAME: "Test",
_KEY_OBJECT_ID_HASH: 12345, CONF_DISABLED_BY_DEFAULT: False,
_KEY_DC_IDX: 3, CONF_DEVICE_CLASS: "temperature",
_KEY_UOM_IDX: 5, CONF_UNIT_OF_MEASUREMENT: "°C",
_KEY_ICON_IDX: 7, 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) finalize_entity_strings(var, config)
packed = _extract_packed_value(added_expressions) packed = _extract_packed_value(added_expressions)
assert (packed >> _DC_SHIFT) & 0xFF == 3 # All three string indices should be non-zero
assert (packed >> _UOM_SHIFT) & 0xFF == 5 assert (packed >> _DC_SHIFT) & 0xFF != 0
assert (packed >> _ICON_SHIFT) & 0xFF == 7 assert (packed >> _UOM_SHIFT) & 0xFF != 0
assert (packed >> _ICON_SHIFT) & 0xFF != 0
# No flags set # No flags set
assert (packed >> _INTERNAL_SHIFT) & 1 == 0 assert (packed >> _INTERNAL_SHIFT) & 1 == 0
assert (packed >> _DISABLED_BY_DEFAULT_SHIFT) & 1 == 0 assert (packed >> _DISABLED_BY_DEFAULT_SHIFT) & 1 == 0
assert (packed >> _ENTITY_CATEGORY_SHIFT) & 0x3 == 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], setup_test_environment: list[str],
) -> None: ) -> None:
"""Test finalize_entity_strings with all fields set.""" """Test all fields set: flags, string indices, and comment."""
added_expressions = setup_test_environment added_expressions = setup_test_environment
var = MockObj("sensor1") var = MockObj("sensor1")
config = { config = {
_KEY_ENTITY_NAME: "Test", CONF_NAME: "Test",
_KEY_OBJECT_ID_HASH: 12345, CONF_DISABLED_BY_DEFAULT: True,
_KEY_DC_IDX: 1, CONF_INTERNAL: True,
_KEY_UOM_IDX: 2, CONF_ENTITY_CATEGORY: "diagnostic",
_KEY_ICON_IDX: 3,
_KEY_INTERNAL: 1,
_KEY_DISABLED_BY_DEFAULT: 1,
_KEY_ENTITY_CATEGORY: 2, # diagnostic
CONF_DEVICE_CLASS: "temperature", CONF_DEVICE_CLASS: "temperature",
CONF_UNIT_OF_MEASUREMENT: "°C", CONF_UNIT_OF_MEASUREMENT: "°C",
CONF_ICON: "mdi:thermometer", 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) finalize_entity_strings(var, config)
packed = _extract_packed_value(added_expressions) packed = _extract_packed_value(added_expressions)
# Verify all fields # Verify flags
assert (packed >> _DC_SHIFT) & 0xFF == 1
assert (packed >> _UOM_SHIFT) & 0xFF == 2
assert (packed >> _ICON_SHIFT) & 0xFF == 3
assert (packed >> _INTERNAL_SHIFT) & 1 == 1 assert (packed >> _INTERNAL_SHIFT) & 1 == 1
assert (packed >> _DISABLED_BY_DEFAULT_SHIFT) & 1 == 1 assert (packed >> _DISABLED_BY_DEFAULT_SHIFT) & 1 == 1
assert (packed >> _ENTITY_CATEGORY_SHIFT) & 0x3 == 2 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 # Verify comment contains all flags with actual string values
comment_line = added_expressions[0] comment_line = added_expressions[0]
assert ( 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], setup_test_environment: list[str],
) -> None: ) -> None:
"""Test that user strings in comments are sanitized against injection.""" """Test that user strings in comments are sanitized against injection."""
added_expressions = setup_test_environment added_expressions = setup_test_environment
var = MockObj("sensor1") var = MockObj("sensor1")
config = { config = {
_KEY_ENTITY_NAME: "Test", CONF_NAME: "Test",
_KEY_OBJECT_ID_HASH: 12345, CONF_DISABLED_BY_DEFAULT: False,
_KEY_ICON_IDX: 1,
# Backslash at end would cause line splice eating next code line # Backslash at end would cause line splice eating next code line
CONF_ICON: "mdi:evil\\", CONF_ICON: "mdi:evil\\",
} }
await _setup_entity_impl(var, config, "sensor")
finalize_entity_strings(var, config) finalize_entity_strings(var, config)
comment_line = added_expressions[0] comment_line = added_expressions[0]
# Backslash must be replaced to prevent line splice # 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 assert "mdi:evil/" in comment_line
added_expressions.clear() added_expressions.clear()
config[CONF_ICON] = "mdi:evil\nINJECTED_CODE();" config2 = {
finalize_entity_strings(var, config) 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] comment_line = added_expressions[0]
# Newline must be replaced to prevent breaking out of comment # Newline must be replaced to prevent breaking out of comment
assert "\n" not in comment_line assert "\n" not in comment_line