mirror of
https://github.com/esphome/esphome.git
synced 2026-02-18 15:35:59 -07:00
[api] Fix string lifetime issue in Home Assistant service calls with templated values (#9909)
This commit is contained in:
311
tests/integration/fixtures/api_homeassistant.yaml
Normal file
311
tests/integration/fixtures/api_homeassistant.yaml
Normal file
@@ -0,0 +1,311 @@
|
||||
esphome:
|
||||
name: test-ha-api
|
||||
friendly_name: Home Assistant API Test
|
||||
|
||||
host:
|
||||
|
||||
api:
|
||||
services:
|
||||
- service: trigger_all_tests
|
||||
then:
|
||||
- logger.log: "=== Starting Home Assistant API Tests ==="
|
||||
- button.press: test_basic_service
|
||||
- button.press: test_templated_service
|
||||
- button.press: test_empty_string_service
|
||||
- button.press: test_multiple_fields_service
|
||||
- button.press: test_complex_lambda_service
|
||||
- button.press: test_all_empty_service
|
||||
- button.press: test_rapid_service_calls
|
||||
- button.press: test_read_ha_states
|
||||
- number.set:
|
||||
id: ha_number
|
||||
value: 42.5
|
||||
- switch.turn_on: ha_switch
|
||||
- switch.turn_off: ha_switch
|
||||
- logger.log: "=== All tests completed ==="
|
||||
|
||||
logger:
|
||||
level: DEBUG
|
||||
|
||||
# Time component for templated values
|
||||
time:
|
||||
- platform: homeassistant
|
||||
id: homeassistant_time
|
||||
|
||||
# Global variables for testing
|
||||
globals:
|
||||
- id: test_brightness
|
||||
type: int
|
||||
initial_value: '75'
|
||||
- id: test_string
|
||||
type: std::string
|
||||
initial_value: '"test_value"'
|
||||
|
||||
# Sensors for testing state reading
|
||||
sensor:
|
||||
- platform: template
|
||||
name: "Test Sensor"
|
||||
id: test_sensor
|
||||
lambda: return 42.0;
|
||||
update_interval: 0.1s
|
||||
|
||||
# Home Assistant sensor that reads external state
|
||||
- platform: homeassistant
|
||||
name: "HA Temperature"
|
||||
entity_id: sensor.external_temperature
|
||||
id: ha_temperature
|
||||
on_value:
|
||||
then:
|
||||
- logger.log:
|
||||
format: "HA Temperature state updated: %.1f"
|
||||
args: ['x']
|
||||
|
||||
# Test multiple HA state sensors
|
||||
- platform: homeassistant
|
||||
name: "HA Humidity"
|
||||
entity_id: sensor.external_humidity
|
||||
id: ha_humidity
|
||||
on_value:
|
||||
then:
|
||||
- logger.log:
|
||||
format: "HA Humidity state updated: %.1f"
|
||||
args: ['x']
|
||||
|
||||
# Binary sensor from Home Assistant
|
||||
binary_sensor:
|
||||
- platform: homeassistant
|
||||
name: "HA Motion"
|
||||
entity_id: binary_sensor.external_motion
|
||||
id: ha_motion
|
||||
on_state:
|
||||
then:
|
||||
- logger.log:
|
||||
format: "HA Motion state changed: %s"
|
||||
args: ['x ? "ON" : "OFF"']
|
||||
|
||||
# Text sensor from Home Assistant
|
||||
text_sensor:
|
||||
- platform: homeassistant
|
||||
name: "HA Weather"
|
||||
entity_id: weather.home
|
||||
attribute: condition
|
||||
id: ha_weather
|
||||
on_value:
|
||||
then:
|
||||
- logger.log:
|
||||
format: "HA Weather condition updated: %s"
|
||||
args: ['x.c_str()']
|
||||
|
||||
# Test empty state handling
|
||||
- platform: homeassistant
|
||||
name: "HA Empty State"
|
||||
entity_id: sensor.nonexistent_sensor
|
||||
id: ha_empty_state
|
||||
on_value:
|
||||
then:
|
||||
- logger.log:
|
||||
format: "HA Empty state updated: %s"
|
||||
args: ['x.c_str()']
|
||||
|
||||
# Number component for testing HA number control
|
||||
number:
|
||||
- platform: template
|
||||
name: "HA Controlled Number"
|
||||
id: ha_number
|
||||
min_value: 0
|
||||
max_value: 100
|
||||
step: 1
|
||||
optimistic: true
|
||||
set_action:
|
||||
- logger.log:
|
||||
format: "Setting HA number to: %.1f"
|
||||
args: ['x']
|
||||
- homeassistant.action:
|
||||
action: input_number.set_value
|
||||
data:
|
||||
entity_id: input_number.test_number
|
||||
value: !lambda 'return to_string(x);'
|
||||
|
||||
# Switch component for testing HA switch control
|
||||
switch:
|
||||
- platform: template
|
||||
name: "HA Controlled Switch"
|
||||
id: ha_switch
|
||||
optimistic: true
|
||||
turn_on_action:
|
||||
- logger.log: "Toggling HA switch: switch.test_switch ON"
|
||||
- homeassistant.action:
|
||||
action: switch.turn_on
|
||||
data:
|
||||
entity_id: switch.test_switch
|
||||
turn_off_action:
|
||||
- logger.log: "Toggling HA switch: switch.test_switch OFF"
|
||||
- homeassistant.action:
|
||||
action: switch.turn_off
|
||||
data:
|
||||
entity_id: switch.test_switch
|
||||
|
||||
# Buttons for testing various service call scenarios
|
||||
button:
|
||||
# Test 1: Basic service call with static values
|
||||
- platform: template
|
||||
name: "Test Basic Service"
|
||||
id: test_basic_service
|
||||
on_press:
|
||||
- logger.log: "Sending HomeAssistant service call: light.turn_off"
|
||||
- homeassistant.action:
|
||||
action: light.turn_off
|
||||
data:
|
||||
entity_id: light.test_light
|
||||
- logger.log: "Service data: entity_id=light.test_light"
|
||||
|
||||
# Test 2: Service call with templated/lambda values (main bug fix test)
|
||||
- platform: template
|
||||
name: "Test Templated Service"
|
||||
id: test_templated_service
|
||||
on_press:
|
||||
- logger.log: "Testing templated service call"
|
||||
- lambda: |-
|
||||
int brightness_percent = id(test_brightness);
|
||||
std::string computed = to_string(brightness_percent * 255 / 100);
|
||||
ESP_LOGI("test", "Lambda computed value: %s", computed.c_str());
|
||||
- homeassistant.action:
|
||||
action: light.turn_on
|
||||
data:
|
||||
entity_id: light.test_light
|
||||
# This creates a temporary string - the main test case
|
||||
brightness: !lambda 'return to_string(id(test_brightness) * 255 / 100);'
|
||||
data_template:
|
||||
color_name: !lambda 'return id(test_string);'
|
||||
variables:
|
||||
transition: !lambda 'return "2.5";'
|
||||
|
||||
# Test 3: Service call with empty string values
|
||||
- platform: template
|
||||
name: "Test Empty String Service"
|
||||
id: test_empty_string_service
|
||||
on_press:
|
||||
- logger.log: "Testing empty string values"
|
||||
- homeassistant.action:
|
||||
action: notify.test
|
||||
data:
|
||||
message: "Test message"
|
||||
title: ""
|
||||
data_template:
|
||||
target: !lambda 'return "";'
|
||||
variables:
|
||||
sound: !lambda 'return "";'
|
||||
|
||||
- logger.log: "Empty value for key: title"
|
||||
- logger.log: "Empty value for key: target"
|
||||
- logger.log: "Empty value for key: sound"
|
||||
|
||||
# Test 4: Service call with multiple data fields
|
||||
- platform: template
|
||||
name: "Test Multiple Fields Service"
|
||||
id: test_multiple_fields_service
|
||||
on_press:
|
||||
- logger.log: "Testing multiple data fields"
|
||||
- homeassistant.action:
|
||||
action: climate.set_temperature
|
||||
data:
|
||||
entity_id: climate.test_climate
|
||||
temperature: "22"
|
||||
hvac_mode: "heat"
|
||||
data_template:
|
||||
target_temp_high: !lambda 'return "24";'
|
||||
target_temp_low: !lambda 'return "20";'
|
||||
variables:
|
||||
preset_mode: !lambda 'return "comfort";'
|
||||
|
||||
# Test 5: Complex lambda with string operations
|
||||
- platform: template
|
||||
name: "Test Complex Lambda Service"
|
||||
id: test_complex_lambda_service
|
||||
on_press:
|
||||
- logger.log: "Testing complex lambda expressions"
|
||||
- homeassistant.action:
|
||||
action: script.test_script
|
||||
data:
|
||||
entity_id: !lambda |-
|
||||
std::string base = "light.";
|
||||
std::string room = "living_room";
|
||||
return base + room;
|
||||
brightness_pct: !lambda |-
|
||||
float sensor_val = id(test_sensor).state;
|
||||
int pct = (int)(sensor_val * 2.38); // 42 * 2.38 ≈ 100
|
||||
return to_string(pct);
|
||||
data_template:
|
||||
message: !lambda |-
|
||||
char buffer[50];
|
||||
snprintf(buffer, sizeof(buffer), "Sensor: %.1f, Time: %02d:%02d",
|
||||
id(test_sensor).state,
|
||||
id(homeassistant_time).now().hour,
|
||||
id(homeassistant_time).now().minute);
|
||||
return std::string(buffer);
|
||||
|
||||
# Test 6: Service with only empty strings to verify size calculation
|
||||
- platform: template
|
||||
name: "Test All Empty Service"
|
||||
id: test_all_empty_service
|
||||
on_press:
|
||||
- logger.log: "Testing all empty string values"
|
||||
- homeassistant.action:
|
||||
action: test.empty
|
||||
data:
|
||||
field1: ""
|
||||
field2: ""
|
||||
data_template:
|
||||
field3: !lambda 'return "";'
|
||||
variables:
|
||||
field4: !lambda 'return "";'
|
||||
- logger.log: "All empty service call completed"
|
||||
|
||||
# Test 7: Rapid successive service calls
|
||||
- platform: template
|
||||
name: "Test Rapid Service Calls"
|
||||
id: test_rapid_service_calls
|
||||
on_press:
|
||||
- logger.log: "Testing rapid service calls"
|
||||
- repeat:
|
||||
count: 5
|
||||
then:
|
||||
- homeassistant.action:
|
||||
action: counter.increment
|
||||
data:
|
||||
entity_id: counter.test_counter
|
||||
- delay: 10ms
|
||||
- logger.log: "Rapid service calls completed"
|
||||
|
||||
# Test 8: Log current HA states
|
||||
- platform: template
|
||||
name: "Test Read HA States"
|
||||
id: test_read_ha_states
|
||||
on_press:
|
||||
- logger.log: "Reading current HA states"
|
||||
- lambda: |-
|
||||
if (id(ha_temperature).has_state()) {
|
||||
ESP_LOGI("test", "Current HA Temperature: %.1f", id(ha_temperature).state);
|
||||
} else {
|
||||
ESP_LOGI("test", "HA Temperature has no state");
|
||||
}
|
||||
|
||||
if (id(ha_humidity).has_state()) {
|
||||
ESP_LOGI("test", "Current HA Humidity: %.1f", id(ha_humidity).state);
|
||||
} else {
|
||||
ESP_LOGI("test", "HA Humidity has no state");
|
||||
}
|
||||
|
||||
ESP_LOGI("test", "Current HA Motion: %s", id(ha_motion).state ? "ON" : "OFF");
|
||||
|
||||
if (id(ha_weather).has_state()) {
|
||||
ESP_LOGI("test", "Current HA Weather: %s", id(ha_weather).state.c_str());
|
||||
} else {
|
||||
ESP_LOGI("test", "HA Weather has no state");
|
||||
}
|
||||
|
||||
if (id(ha_empty_state).has_state()) {
|
||||
ESP_LOGI("test", "HA Empty State value: %s", id(ha_empty_state).state.c_str());
|
||||
} else {
|
||||
ESP_LOGI("test", "HA Empty State has no value (expected)");
|
||||
}
|
||||
Reference in New Issue
Block a user