mirror of
https://github.com/esphome/esphome.git
synced 2026-10-05 10:31:29 +00:00
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:
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user