mirror of
https://github.com/esphome/esphome.git
synced 2026-10-01 17:00:21 +00:00
Merge branch 'dev' into std-optional
This commit is contained in:
@@ -11,4 +11,4 @@ def test_sensor_device_class_set(generate_main):
|
||||
main_cpp = generate_main("tests/component_tests/sensor/test_sensor.yaml")
|
||||
|
||||
# Then
|
||||
assert 's_1->set_device_class("voltage");' in main_cpp
|
||||
assert "s_1->set_entity_strings(" in main_cpp
|
||||
|
||||
@@ -54,5 +54,5 @@ def test_text_sensor_device_class_set(generate_main):
|
||||
main_cpp = generate_main("tests/component_tests/text_sensor/test_text_sensor.yaml")
|
||||
|
||||
# Then
|
||||
assert 'ts_2->set_device_class("timestamp");' in main_cpp
|
||||
assert 'ts_3->set_device_class("date");' in main_cpp
|
||||
assert "ts_2->set_entity_strings(" in main_cpp
|
||||
assert "ts_3->set_entity_strings(" in main_cpp
|
||||
|
||||
@@ -13,3 +13,4 @@ openthread:
|
||||
force_dataset: true
|
||||
use_address: open-thread-test.local
|
||||
poll_period: 20sec
|
||||
output_power: 1dBm
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
<<: !include common.yaml
|
||||
|
||||
media_player:
|
||||
- platform: speaker
|
||||
id: speaker_media_player_id
|
||||
announcement_pipeline:
|
||||
speaker: speaker_id
|
||||
buffer_size: 1000000
|
||||
volume_increment: 0.02
|
||||
volume_max: 0.95
|
||||
volume_min: 0.0
|
||||
task_stack_in_psram: true
|
||||
on_turn_on:
|
||||
then:
|
||||
- logger.log: "Turn On Media Player"
|
||||
on_turn_off:
|
||||
then:
|
||||
- logger.log: "Turn Off Media Player"
|
||||
@@ -0,0 +1,9 @@
|
||||
substitutions:
|
||||
scl_pin: GPIO16
|
||||
sda_pin: GPIO17
|
||||
i2s_bclk_pin: GPIO27
|
||||
i2s_lrclk_pin: GPIO26
|
||||
i2s_mclk_pin: GPIO25
|
||||
i2s_dout_pin: GPIO23
|
||||
|
||||
<<: !include common-media_player_off_on.yaml
|
||||
@@ -1,6 +1,7 @@
|
||||
from esphome import automation
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import uart
|
||||
from esphome.components.const import CONF_DATA_BITS, CONF_PARITY, CONF_STOP_BITS
|
||||
from esphome.components.uart import (
|
||||
CONF_RX_FULL_THRESHOLD,
|
||||
CONF_RX_TIMEOUT,
|
||||
@@ -12,14 +13,11 @@ import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_BAUD_RATE,
|
||||
CONF_DATA,
|
||||
CONF_DATA_BITS,
|
||||
CONF_DEBUG,
|
||||
CONF_DELAY,
|
||||
CONF_ID,
|
||||
CONF_INTERVAL,
|
||||
CONF_PARITY,
|
||||
CONF_RX_BUFFER_SIZE,
|
||||
CONF_STOP_BITS,
|
||||
CONF_TRIGGER_ID,
|
||||
)
|
||||
from esphome.core import ID
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
esphome:
|
||||
name: micros-to-millis-test
|
||||
platformio_options:
|
||||
build_flags:
|
||||
- "-DDEBUG"
|
||||
on_boot:
|
||||
- lambda: |-
|
||||
using esphome::micros_to_millis;
|
||||
const char *TAG = "MTM";
|
||||
int pass = 0, fail = 0;
|
||||
|
||||
auto check = [&](const char *name, uint64_t us) {
|
||||
uint32_t got = micros_to_millis(us);
|
||||
uint32_t want = (uint32_t)(us / 1000ULL);
|
||||
if (got == want) { pass++; }
|
||||
else { ESP_LOGE(TAG, "%s FAILED: got=%u want=%u", name, got, want); fail++; }
|
||||
};
|
||||
|
||||
// Basic values
|
||||
check("zero", 0);
|
||||
check("below_1ms", 999);
|
||||
check("exactly_1ms", 1000);
|
||||
check("above_1ms", 1001);
|
||||
|
||||
// Shift boundary (1000 = 8 * 125, exercises the >>3 shift)
|
||||
check("shift_7999", 7999);
|
||||
check("shift_8000", 8000);
|
||||
check("shift_8001", 8001);
|
||||
|
||||
// 32-bit boundary
|
||||
check("u32max_minus1", 0xFFFFFFFEULL);
|
||||
check("u32max", 0xFFFFFFFFULL);
|
||||
check("u32max_plus1", 0x100000000ULL);
|
||||
|
||||
// Realistic uptimes
|
||||
check("30_days", 2592000000000ULL);
|
||||
check("1_year", 31536000000000ULL);
|
||||
|
||||
// Carry path: construct x = us>>3 with specific hi/lo that trigger adj overflow
|
||||
{ uint64_t x = (603ULL << 32) | 0xFFFFFFFFU; check("carry_603", x << 3); }
|
||||
{ uint64_t x = (5000ULL << 32) | 0xFFFFFFFFU; check("carry_5000", x << 3); }
|
||||
|
||||
// Carry boundary: exact transition where adj overflows (hi=1000, R=46)
|
||||
{
|
||||
uint32_t hi = 1000;
|
||||
uint32_t thr = 0xFFFFFFFFU - hi * 46U;
|
||||
uint64_t h = (uint64_t)hi << 32;
|
||||
check("carry_before", (h | (thr - 1)) << 3);
|
||||
check("carry_at", (h | thr) << 3);
|
||||
check("carry_after", (h | (thr + 1)) << 3);
|
||||
}
|
||||
|
||||
// Mod-8 variations (exercises the >>3 truncation)
|
||||
for (int i = 0; i < 8; i++) { check("mod8", 2592000000000ULL + i); }
|
||||
|
||||
if (fail == 0) { ESP_LOGI(TAG, "ALL_PASSED %d tests", pass); }
|
||||
else { ESP_LOGE(TAG, "%d FAILED out of %d", fail, pass + fail); }
|
||||
|
||||
host:
|
||||
api:
|
||||
logger:
|
||||
@@ -0,0 +1,46 @@
|
||||
"""Integration test for micros_to_millis Euclidean decomposition."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import re
|
||||
|
||||
import pytest
|
||||
|
||||
from .types import APIClientConnectedFactory, RunCompiledFunction
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_micros_to_millis(
|
||||
yaml_config: str,
|
||||
run_compiled: RunCompiledFunction,
|
||||
api_client_connected: APIClientConnectedFactory,
|
||||
) -> None:
|
||||
"""Test that micros_to_millis matches reference uint64 division."""
|
||||
|
||||
all_passed = asyncio.Event()
|
||||
failures: list[str] = []
|
||||
|
||||
def on_log_line(line: str) -> None:
|
||||
clean_line = re.sub(r"\x1b\[[0-9;]*m", "", line)
|
||||
if "ALL_PASSED" in clean_line:
|
||||
all_passed.set()
|
||||
elif "FAILED" in clean_line and "[MTM" in clean_line:
|
||||
failures.append(clean_line)
|
||||
|
||||
async with (
|
||||
run_compiled(yaml_config, line_callback=on_log_line),
|
||||
api_client_connected() as client,
|
||||
):
|
||||
device_info = await client.device_info()
|
||||
assert device_info is not None
|
||||
assert device_info.name == "micros-to-millis-test"
|
||||
|
||||
try:
|
||||
await asyncio.wait_for(all_passed.wait(), timeout=2.0)
|
||||
except TimeoutError:
|
||||
if failures:
|
||||
pytest.fail(f"micros_to_millis failures: {failures}")
|
||||
pytest.fail("micros_to_millis test timed out")
|
||||
|
||||
assert not failures, f"micros_to_millis failures: {failures}"
|
||||
@@ -17,10 +17,10 @@ async def test_oversized_payload_plaintext(
|
||||
) -> None:
|
||||
"""Test that oversized payloads (>32768 bytes) from client cause disconnection without crashing."""
|
||||
process_exited = False
|
||||
helper_log_found = False
|
||||
helper_log_event = asyncio.Event()
|
||||
|
||||
def check_logs(line: str) -> None:
|
||||
nonlocal process_exited, helper_log_found
|
||||
nonlocal process_exited
|
||||
# Check for signs that the process exited/crashed
|
||||
if "Segmentation fault" in line or "core dumped" in line:
|
||||
process_exited = True
|
||||
@@ -30,7 +30,7 @@ async def test_oversized_payload_plaintext(
|
||||
and "Bad packet: message size" in line
|
||||
and "exceeds maximum" in line
|
||||
):
|
||||
helper_log_found = True
|
||||
helper_log_event.set()
|
||||
|
||||
async with run_compiled(yaml_config, line_callback=check_logs):
|
||||
async with api_client_connected_with_disconnect() as (client, disconnect_event):
|
||||
@@ -54,10 +54,13 @@ async def test_oversized_payload_plaintext(
|
||||
|
||||
# After disconnection, verify process didn't crash
|
||||
assert not process_exited, "ESPHome process should not crash"
|
||||
# Verify we saw the expected HELPER_LOG message
|
||||
assert helper_log_found, (
|
||||
"Expected to see HELPER_LOG about message size exceeding maximum"
|
||||
)
|
||||
# Wait for the expected log message (may arrive after disconnect event)
|
||||
try:
|
||||
await asyncio.wait_for(helper_log_event.wait(), timeout=2.0)
|
||||
except TimeoutError:
|
||||
pytest.fail(
|
||||
"Expected to see HELPER_LOG about message size exceeding maximum"
|
||||
)
|
||||
|
||||
# Try to reconnect to verify the process is still running
|
||||
async with api_client_connected_with_disconnect() as (client2, _):
|
||||
@@ -77,10 +80,10 @@ async def test_oversized_protobuf_message_id_plaintext(
|
||||
This tests the message type limit - message IDs must fit in a uint16_t (0-65535).
|
||||
"""
|
||||
process_exited = False
|
||||
helper_log_found = False
|
||||
helper_log_event = asyncio.Event()
|
||||
|
||||
def check_logs(line: str) -> None:
|
||||
nonlocal process_exited, helper_log_found
|
||||
nonlocal process_exited
|
||||
# Check for signs that the process exited/crashed
|
||||
if "Segmentation fault" in line or "core dumped" in line:
|
||||
process_exited = True
|
||||
@@ -90,7 +93,7 @@ async def test_oversized_protobuf_message_id_plaintext(
|
||||
and "Bad packet: message type" in line
|
||||
and "exceeds maximum" in line
|
||||
):
|
||||
helper_log_found = True
|
||||
helper_log_event.set()
|
||||
|
||||
async with run_compiled(yaml_config, line_callback=check_logs):
|
||||
async with api_client_connected_with_disconnect() as (client, disconnect_event):
|
||||
@@ -114,10 +117,13 @@ async def test_oversized_protobuf_message_id_plaintext(
|
||||
|
||||
# After disconnection, verify process didn't crash
|
||||
assert not process_exited, "ESPHome process should not crash"
|
||||
# Verify we saw the expected HELPER_LOG message
|
||||
assert helper_log_found, (
|
||||
"Expected to see HELPER_LOG about message type exceeding maximum"
|
||||
)
|
||||
# Wait for the expected log message (may arrive after disconnect event)
|
||||
try:
|
||||
await asyncio.wait_for(helper_log_event.wait(), timeout=2.0)
|
||||
except TimeoutError:
|
||||
pytest.fail(
|
||||
"Expected to see HELPER_LOG about message type exceeding maximum"
|
||||
)
|
||||
|
||||
# Try to reconnect to verify the process is still running
|
||||
async with api_client_connected_with_disconnect() as (client2, _):
|
||||
@@ -135,10 +141,10 @@ async def test_oversized_payload_noise(
|
||||
"""Test that oversized payloads from client cause disconnection without crashing with noise encryption."""
|
||||
noise_key = "N4Yle5YirwZhPiHHsdZLdOA73ndj/84veVaLhTvxCuU="
|
||||
process_exited = False
|
||||
helper_log_found = False
|
||||
helper_log_event = asyncio.Event()
|
||||
|
||||
def check_logs(line: str) -> None:
|
||||
nonlocal process_exited, helper_log_found
|
||||
nonlocal process_exited
|
||||
# Check for signs that the process exited/crashed
|
||||
if "Segmentation fault" in line or "core dumped" in line:
|
||||
process_exited = True
|
||||
@@ -149,7 +155,7 @@ async def test_oversized_payload_noise(
|
||||
and "Bad packet: message size" in line
|
||||
and "exceeds maximum" in line
|
||||
):
|
||||
helper_log_found = True
|
||||
helper_log_event.set()
|
||||
|
||||
async with run_compiled(yaml_config, line_callback=check_logs):
|
||||
async with api_client_connected_with_disconnect(noise_psk=noise_key) as (
|
||||
@@ -177,10 +183,13 @@ async def test_oversized_payload_noise(
|
||||
|
||||
# After disconnection, verify process didn't crash
|
||||
assert not process_exited, "ESPHome process should not crash"
|
||||
# Verify we saw the expected HELPER_LOG message
|
||||
assert helper_log_found, (
|
||||
"Expected to see HELPER_LOG about message size exceeding maximum"
|
||||
)
|
||||
# Wait for the expected log message (may arrive after disconnect event)
|
||||
try:
|
||||
await asyncio.wait_for(helper_log_event.wait(), timeout=2.0)
|
||||
except TimeoutError:
|
||||
pytest.fail(
|
||||
"Expected to see HELPER_LOG about message size exceeding maximum"
|
||||
)
|
||||
|
||||
# Try to reconnect to verify the process is still running
|
||||
async with api_client_connected_with_disconnect(noise_psk=noise_key) as (
|
||||
@@ -274,10 +283,10 @@ async def test_noise_corrupt_encrypted_frame(
|
||||
"""
|
||||
noise_key = "N4Yle5YirwZhPiHHsdZLdOA73ndj/84veVaLhTvxCuU="
|
||||
process_exited = False
|
||||
cipherstate_failed = False
|
||||
cipherstate_event = asyncio.Event()
|
||||
|
||||
def check_logs(line: str) -> None:
|
||||
nonlocal process_exited, cipherstate_failed
|
||||
nonlocal process_exited
|
||||
# Check for signs that the process exited/crashed
|
||||
if "Segmentation fault" in line or "core dumped" in line:
|
||||
process_exited = True
|
||||
@@ -290,7 +299,7 @@ async def test_noise_corrupt_encrypted_frame(
|
||||
"[W][api.connection" in line
|
||||
and "Reading failed CIPHERSTATE_DECRYPT_FAILED" in line
|
||||
):
|
||||
cipherstate_failed = True
|
||||
cipherstate_event.set()
|
||||
|
||||
async with run_compiled(yaml_config, line_callback=check_logs):
|
||||
async with api_client_connected_with_disconnect(noise_psk=noise_key) as (
|
||||
@@ -326,10 +335,14 @@ async def test_noise_corrupt_encrypted_frame(
|
||||
assert not process_exited, (
|
||||
"ESPHome process should not crash on corrupt encrypted frames"
|
||||
)
|
||||
# Verify we saw the expected log message about decryption failure
|
||||
assert cipherstate_failed, (
|
||||
"Expected to see log about noise_cipherstate_decrypt failure or CIPHERSTATE_DECRYPT_FAILED"
|
||||
)
|
||||
# Wait for the expected log message (may arrive after disconnect event)
|
||||
try:
|
||||
await asyncio.wait_for(cipherstate_event.wait(), timeout=2.0)
|
||||
except TimeoutError:
|
||||
pytest.fail(
|
||||
"Expected to see log about noise_cipherstate_decrypt failure"
|
||||
" or CIPHERSTATE_DECRYPT_FAILED"
|
||||
)
|
||||
|
||||
# Verify we can still reconnect after handling the corrupt frame
|
||||
async with api_client_connected_with_disconnect(noise_psk=noise_key) as (
|
||||
|
||||
@@ -11,6 +11,7 @@ from esphome.config_validation import Invalid
|
||||
from esphome.const import (
|
||||
CONF_DEVICE_ID,
|
||||
CONF_DISABLED_BY_DEFAULT,
|
||||
CONF_ENTITY_CATEGORY,
|
||||
CONF_ICON,
|
||||
CONF_ID,
|
||||
CONF_INTERNAL,
|
||||
@@ -18,6 +19,8 @@ from esphome.const import (
|
||||
)
|
||||
from esphome.core import CORE, ID, entity_helpers
|
||||
from esphome.core.entity_helpers import (
|
||||
_register_string,
|
||||
_setup_entity_impl,
|
||||
entity_duplicate_validator,
|
||||
get_base_entity_object_id,
|
||||
setup_entity,
|
||||
@@ -305,7 +308,7 @@ async def test_setup_entity_no_duplicates(setup_test_environment: list[str]) ->
|
||||
CONF_NAME: "Temperature",
|
||||
CONF_DISABLED_BY_DEFAULT: False,
|
||||
}
|
||||
await setup_entity(var1, config1, "sensor")
|
||||
await _setup_entity_impl(var1, config1, "sensor")
|
||||
|
||||
# Get object ID from first entity
|
||||
object_id1 = extract_object_id_from_expressions(added_expressions)
|
||||
@@ -319,7 +322,7 @@ async def test_setup_entity_no_duplicates(setup_test_environment: list[str]) ->
|
||||
CONF_NAME: "Humidity",
|
||||
CONF_DISABLED_BY_DEFAULT: False,
|
||||
}
|
||||
await setup_entity(var2, config2, "sensor")
|
||||
await _setup_entity_impl(var2, config2, "sensor")
|
||||
|
||||
# Get object ID from second entity
|
||||
object_id2 = extract_object_id_from_expressions(added_expressions)
|
||||
@@ -354,7 +357,7 @@ async def test_setup_entity_different_platforms(
|
||||
object_ids: list[str] = []
|
||||
for var, platform in platforms:
|
||||
added_expressions.clear()
|
||||
await setup_entity(var, config, platform)
|
||||
await _setup_entity_impl(var, config, platform)
|
||||
object_id = extract_object_id_from_expressions(added_expressions)
|
||||
object_ids.append(object_id)
|
||||
|
||||
@@ -416,7 +419,7 @@ async def test_setup_entity_with_devices(
|
||||
object_ids: list[str] = []
|
||||
for var, config in [(sensor1, config1), (sensor2, config2)]:
|
||||
added_expressions.clear()
|
||||
await setup_entity(var, config, "sensor")
|
||||
await _setup_entity_impl(var, config, "sensor")
|
||||
object_id = extract_object_id_from_expressions(added_expressions)
|
||||
object_ids.append(object_id)
|
||||
|
||||
@@ -438,7 +441,7 @@ async def test_setup_entity_empty_name(setup_test_environment: list[str]) -> Non
|
||||
CONF_DISABLED_BY_DEFAULT: False,
|
||||
}
|
||||
|
||||
await setup_entity(var, config, "sensor")
|
||||
await _setup_entity_impl(var, config, "sensor")
|
||||
|
||||
object_id = extract_object_id_from_expressions(added_expressions)
|
||||
# Should use friendly name
|
||||
@@ -460,7 +463,7 @@ async def test_setup_entity_special_characters(
|
||||
CONF_DISABLED_BY_DEFAULT: False,
|
||||
}
|
||||
|
||||
await setup_entity(var, config, "sensor")
|
||||
await _setup_entity_impl(var, config, "sensor")
|
||||
object_id = extract_object_id_from_expressions(added_expressions)
|
||||
|
||||
# Special characters should be sanitized
|
||||
@@ -471,7 +474,7 @@ async def test_setup_entity_special_characters(
|
||||
async def test_setup_entity_with_icon(setup_test_environment: list[str]) -> None:
|
||||
"""Test setup_entity sets icon correctly."""
|
||||
|
||||
added_expressions = setup_test_environment
|
||||
setup_test_environment # noqa: F841 - fixture initializes CORE state
|
||||
|
||||
var = MockObj("sensor1")
|
||||
|
||||
@@ -481,12 +484,10 @@ async def test_setup_entity_with_icon(setup_test_environment: list[str]) -> None
|
||||
CONF_ICON: "mdi:thermometer",
|
||||
}
|
||||
|
||||
await setup_entity(var, config, "sensor")
|
||||
await _setup_entity_impl(var, config, "sensor")
|
||||
|
||||
# Check icon was set
|
||||
assert any(
|
||||
'sensor1.set_icon("mdi:thermometer")' in expr for expr in added_expressions
|
||||
)
|
||||
# Check icon index was stored in config for finalize_entity_strings
|
||||
assert config.get("_entity_icon_idx", 0) > 0
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -504,7 +505,7 @@ async def test_setup_entity_disabled_by_default(
|
||||
CONF_DISABLED_BY_DEFAULT: True,
|
||||
}
|
||||
|
||||
await setup_entity(var, config, "sensor")
|
||||
await _setup_entity_impl(var, config, "sensor")
|
||||
|
||||
# Check disabled_by_default was set
|
||||
assert any(
|
||||
@@ -790,7 +791,7 @@ async def test_setup_entity_empty_name_with_device(
|
||||
CONF_DEVICE_ID: device_id,
|
||||
}
|
||||
|
||||
await setup_entity(var, config, "sensor")
|
||||
await _setup_entity_impl(var, config, "sensor")
|
||||
|
||||
entity_helpers.get_variable = original_get_variable
|
||||
|
||||
@@ -826,7 +827,7 @@ async def test_setup_entity_empty_name_with_mac_suffix(
|
||||
CONF_DISABLED_BY_DEFAULT: False,
|
||||
}
|
||||
|
||||
await setup_entity(var, config, "sensor")
|
||||
await _setup_entity_impl(var, config, "sensor")
|
||||
|
||||
# For empty-name entities, Python passes 0 - C++ calculates hash at runtime
|
||||
assert any('set_name("", 0)' in expr for expr in added_expressions), (
|
||||
@@ -858,7 +859,7 @@ async def test_setup_entity_empty_name_with_mac_suffix_no_friendly_name(
|
||||
CONF_DISABLED_BY_DEFAULT: False,
|
||||
}
|
||||
|
||||
await setup_entity(var, config, "sensor")
|
||||
await _setup_entity_impl(var, config, "sensor")
|
||||
|
||||
# For empty-name entities, Python passes 0 - C++ calculates hash at runtime
|
||||
assert any('set_name("", 0)' in expr for expr in added_expressions), (
|
||||
@@ -891,9 +892,84 @@ async def test_setup_entity_empty_name_no_mac_suffix_no_friendly_name(
|
||||
CONF_DISABLED_BY_DEFAULT: False,
|
||||
}
|
||||
|
||||
await setup_entity(var, config, "sensor")
|
||||
await _setup_entity_impl(var, config, "sensor")
|
||||
|
||||
# For empty-name entities, Python passes 0 - C++ calculates hash at runtime
|
||||
assert any('set_name("", 0)' in expr for expr in added_expressions), (
|
||||
f"Expected set_name with hash 0, got {added_expressions}"
|
||||
)
|
||||
|
||||
|
||||
def test_register_string_overflow() -> None:
|
||||
"""Test _register_string raises ValueError when max count is exceeded."""
|
||||
category: dict[str, int] = {}
|
||||
for i in range(3):
|
||||
_register_string(f"val_{i}", category, 3, "test")
|
||||
with pytest.raises(ValueError, match="Too many unique test values"):
|
||||
_register_string("overflow", category, 3, "test")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_setup_entity_with_entity_category(
|
||||
setup_test_environment: list[str],
|
||||
) -> None:
|
||||
"""Test setup_entity sets entity_category correctly."""
|
||||
added_expressions = setup_test_environment
|
||||
var = MockObj("sensor1")
|
||||
config = {
|
||||
CONF_NAME: "Temperature",
|
||||
CONF_DISABLED_BY_DEFAULT: False,
|
||||
CONF_ENTITY_CATEGORY: "diagnostic",
|
||||
}
|
||||
await _setup_entity_impl(var, config, "sensor")
|
||||
assert any(
|
||||
'set_entity_category("diagnostic")' in expr for expr in added_expressions
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_setup_entity_direct_call(setup_test_environment: list[str]) -> None:
|
||||
"""Test setup_entity in direct call mode (legacy / backward compat)."""
|
||||
added_expressions = setup_test_environment
|
||||
|
||||
var = MockObj("camera1")
|
||||
config = {
|
||||
CONF_NAME: "My Camera",
|
||||
CONF_DISABLED_BY_DEFAULT: False,
|
||||
CONF_ICON: "mdi:camera",
|
||||
}
|
||||
|
||||
# Direct call mode: await setup_entity(var, config, "camera")
|
||||
await setup_entity(var, config, "camera")
|
||||
|
||||
# Should have called set_name
|
||||
object_id = extract_object_id_from_expressions(added_expressions)
|
||||
assert object_id == "my_camera"
|
||||
|
||||
# Icon index should have been stored and finalized
|
||||
assert config.get("_entity_icon_idx", 0) > 0
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_setup_entity_decorator_mode(setup_test_environment: list[str]) -> None:
|
||||
"""Test setup_entity in decorator mode."""
|
||||
added_expressions = setup_test_environment
|
||||
|
||||
body_called = False
|
||||
|
||||
@setup_entity("sensor")
|
||||
async def my_setup(var, config):
|
||||
nonlocal body_called
|
||||
body_called = True
|
||||
|
||||
var = MockObj("sensor1")
|
||||
config = {
|
||||
CONF_NAME: "Temperature",
|
||||
CONF_DISABLED_BY_DEFAULT: False,
|
||||
}
|
||||
|
||||
await my_setup(var, config)
|
||||
|
||||
assert body_called
|
||||
object_id = extract_object_id_from_expressions(added_expressions)
|
||||
assert object_id == "temperature"
|
||||
|
||||
Reference in New Issue
Block a user