mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 15:12:06 +00: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