[climate] Add integration test for ControlAction

This commit is contained in:
J. Nick Koston
2026-04-27 05:03:57 -05:00
parent 1a02f7e41f
commit 66115c117a
2 changed files with 162 additions and 0 deletions
@@ -0,0 +1,90 @@
esphome:
name: climate-control-action-test
host:
api:
logger:
level: DEBUG
globals:
- id: test_target_temp
type: float
initial_value: "21.5"
sensor:
- platform: template
id: temp_sensor
name: "Temp"
lambda: 'return 20.0;'
update_interval: 60s
climate:
- platform: thermostat
id: test_climate
name: "Test Climate"
sensor: temp_sensor
min_idle_time: 30s
min_heating_off_time: 300s
min_heating_run_time: 300s
min_cooling_off_time: 300s
min_cooling_run_time: 300s
heat_action:
- logger.log: heating
idle_action:
- logger.log: idle
cool_action:
- logger.log: cooling
preset:
- name: Default
default_target_temperature_low: 18 °C
default_target_temperature_high: 22 °C
visual:
min_temperature: 10 °C
max_temperature: 30 °C
button:
# Test 1: mode only (mask 1)
- platform: template
id: btn_mode
name: "Set Mode Heat"
on_press:
- climate.control:
id: test_climate
mode: HEAT
# Test 2: mode + low + high (mask 0b1101 = 13)
- platform: template
id: btn_mode_temps
name: "Set Mode Temps"
on_press:
- climate.control:
id: test_climate
mode: HEAT_COOL
target_temperature_low: 19.0 °C
target_temperature_high: 23.0 °C
# Test 3: just target_temp_low (mask 0b0100 = 4)
- platform: template
id: btn_low_only
name: "Set Low Only"
on_press:
- climate.control:
id: test_climate
target_temperature_low: 17.5 °C
# Test 4: lambda for target_temperature_high (exercises lambda path)
- platform: template
id: btn_lambda_high
name: "Lambda High"
on_press:
- climate.control:
id: test_climate
target_temperature_high: !lambda "return id(test_target_temp);"
# Test 5: turn off via mode
- platform: template
id: btn_off
name: "Set Off"
on_press:
- climate.control:
id: test_climate
mode: "OFF"
@@ -0,0 +1,72 @@
"""Integration test for climate ControlAction.
Tests that climate.control automation actions work correctly with the
per-instance bitmask field storage. Exercises multiple field combinations
to cover different bitmask variants and the lambda path.
"""
import asyncio
from typing import Any
import pytest
from .types import APIClientConnectedFactory, RunCompiledFunction
@pytest.mark.asyncio
async def test_climate_control_action(
yaml_config: str,
run_compiled: RunCompiledFunction,
api_client_connected: APIClientConnectedFactory,
) -> None:
"""Test climate ControlAction with constants and lambdas."""
async with run_compiled(yaml_config), api_client_connected() as client:
state_futures: dict[int, asyncio.Future[Any]] = {}
def on_state(state: Any) -> None:
if state.key in state_futures and not state_futures[state.key].done():
state_futures[state.key].set_result(state)
client.subscribe_states(on_state)
entities = await client.list_entities_services()
climate = next(e for e in entities[0] if e.object_id == "test_climate")
buttons = {e.name: e for e in entities[0] if hasattr(e, "name")}
async def wait_for_state(key: int, timeout: float = 5.0) -> Any:
loop = asyncio.get_running_loop()
state_futures[key] = loop.create_future()
try:
return await asyncio.wait_for(state_futures[key], timeout)
finally:
state_futures.pop(key, None)
async def press_and_wait(button_name: str) -> Any:
btn = buttons[button_name]
client.button_command(btn.key)
return await wait_for_state(climate.key)
# Test 1: mode only (mask 1) — set HEAT
state = await press_and_wait("Set Mode Heat")
# ClimateMode.CLIMATE_MODE_HEAT == 3
assert state.mode == 3
# Test 2: mode + low + high (mask 13) — HEAT_COOL with both temps
state = await press_and_wait("Set Mode Temps")
# CLIMATE_MODE_HEAT_COOL == 1
assert state.mode == 1
assert state.target_temperature_low == pytest.approx(19.0, abs=0.5)
assert state.target_temperature_high == pytest.approx(23.0, abs=0.5)
# Test 3: low only (mask 4)
state = await press_and_wait("Set Low Only")
assert state.target_temperature_low == pytest.approx(17.5, abs=0.5)
# Test 4: lambda high — global is 21.5
state = await press_and_wait("Lambda High")
assert state.target_temperature_high == pytest.approx(21.5, abs=0.5)
# Test 5: turn off via mode (mask 1)
state = await press_and_wait("Set Off")
# CLIMATE_MODE_OFF == 0
assert state.mode == 0