mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-25 05:03:52 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			291 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			291 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| """Integration test for all light call combinations.
 | |
| 
 | |
| Tests that LightCall handles all possible light operations correctly
 | |
| including RGB, color temperature, effects, transitions, and flash.
 | |
| """
 | |
| 
 | |
| import asyncio
 | |
| from typing import Any
 | |
| 
 | |
| from aioesphomeapi import LightState
 | |
| import pytest
 | |
| 
 | |
| from .types import APIClientConnectedFactory, RunCompiledFunction
 | |
| 
 | |
| 
 | |
| @pytest.mark.asyncio
 | |
| async def test_light_calls(
 | |
|     yaml_config: str,
 | |
|     run_compiled: RunCompiledFunction,
 | |
|     api_client_connected: APIClientConnectedFactory,
 | |
| ) -> None:
 | |
|     """Test all possible LightCall operations and combinations."""
 | |
|     async with run_compiled(yaml_config), api_client_connected() as client:
 | |
|         # Track state changes with futures
 | |
|         state_futures: dict[int, asyncio.Future[Any]] = {}
 | |
|         states: dict[int, Any] = {}
 | |
| 
 | |
|         def on_state(state: Any) -> None:
 | |
|             states[state.key] = state
 | |
|             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)
 | |
| 
 | |
|         # Get the light entities
 | |
|         entities = await client.list_entities_services()
 | |
|         lights = [e for e in entities[0] if e.object_id.startswith("test_")]
 | |
|         assert len(lights) >= 2  # Should have RGBCW and RGB lights
 | |
| 
 | |
|         rgbcw_light = next(light for light in lights if "RGBCW" in light.name)
 | |
|         rgb_light = next(light for light in lights if "RGB Light" in light.name)
 | |
| 
 | |
|         async def wait_for_state_change(key: int, timeout: float = 1.0) -> Any:
 | |
|             """Wait for a state change for the given entity key."""
 | |
|             loop = asyncio.get_event_loop()
 | |
|             state_futures[key] = loop.create_future()
 | |
|             try:
 | |
|                 return await asyncio.wait_for(state_futures[key], timeout)
 | |
|             finally:
 | |
|                 state_futures.pop(key, None)
 | |
| 
 | |
|         # Test all individual parameters first
 | |
| 
 | |
|         # Test 1: state only
 | |
|         client.light_command(key=rgbcw_light.key, state=True)
 | |
|         state = await wait_for_state_change(rgbcw_light.key)
 | |
|         assert state.state is True
 | |
| 
 | |
|         # Test 2: brightness only
 | |
|         client.light_command(key=rgbcw_light.key, brightness=0.5)
 | |
|         state = await wait_for_state_change(rgbcw_light.key)
 | |
|         assert state.brightness == pytest.approx(0.5)
 | |
| 
 | |
|         # Test 3: color_brightness only
 | |
|         client.light_command(key=rgbcw_light.key, color_brightness=0.8)
 | |
|         state = await wait_for_state_change(rgbcw_light.key)
 | |
|         assert state.color_brightness == pytest.approx(0.8)
 | |
| 
 | |
|         # Test 4-7: RGB values must be set together via rgb parameter
 | |
|         client.light_command(key=rgbcw_light.key, rgb=(0.7, 0.3, 0.9))
 | |
|         state = await wait_for_state_change(rgbcw_light.key)
 | |
|         assert state.red == pytest.approx(0.7, abs=0.1)
 | |
|         assert state.green == pytest.approx(0.3, abs=0.1)
 | |
|         assert state.blue == pytest.approx(0.9, abs=0.1)
 | |
| 
 | |
|         # Test 7: white value
 | |
|         client.light_command(key=rgbcw_light.key, white=0.6)
 | |
|         state = await wait_for_state_change(rgbcw_light.key)
 | |
|         # White might need more tolerance or might not be directly settable
 | |
|         if isinstance(state, LightState) and state.white is not None:
 | |
|             assert state.white == pytest.approx(0.6, abs=0.1)
 | |
| 
 | |
|         # Test 8: color_temperature only
 | |
|         client.light_command(key=rgbcw_light.key, color_temperature=300)
 | |
|         state = await wait_for_state_change(rgbcw_light.key)
 | |
|         assert state.color_temperature == pytest.approx(300)
 | |
| 
 | |
|         # Test 9: cold_white only
 | |
|         client.light_command(key=rgbcw_light.key, cold_white=0.8)
 | |
|         state = await wait_for_state_change(rgbcw_light.key)
 | |
|         assert state.cold_white == pytest.approx(0.8)
 | |
| 
 | |
|         # Test 10: warm_white only
 | |
|         client.light_command(key=rgbcw_light.key, warm_white=0.2)
 | |
|         state = await wait_for_state_change(rgbcw_light.key)
 | |
|         assert state.warm_white == pytest.approx(0.2)
 | |
| 
 | |
|         # Test 11: transition_length with state change
 | |
|         client.light_command(key=rgbcw_light.key, state=False, transition_length=0.1)
 | |
|         state = await wait_for_state_change(rgbcw_light.key)
 | |
|         assert state.state is False
 | |
| 
 | |
|         # Test 12: flash_length
 | |
|         client.light_command(key=rgbcw_light.key, state=True, flash_length=0.2)
 | |
|         state = await wait_for_state_change(rgbcw_light.key)
 | |
|         # Flash starts
 | |
|         assert state.state is True
 | |
|         # Wait for flash to end
 | |
|         state = await wait_for_state_change(rgbcw_light.key)
 | |
| 
 | |
|         # Test 13: effect only - test all random effects
 | |
|         # First ensure light is on
 | |
|         client.light_command(key=rgbcw_light.key, state=True)
 | |
|         state = await wait_for_state_change(rgbcw_light.key)
 | |
| 
 | |
|         # Test 13a: Default random effect (no name, gets default name "Random")
 | |
|         client.light_command(key=rgbcw_light.key, effect="Random")
 | |
|         state = await wait_for_state_change(rgbcw_light.key)
 | |
|         assert state.effect == "Random"
 | |
| 
 | |
|         # Test 13b: Slow random effect with long name
 | |
|         client.light_command(
 | |
|             key=rgbcw_light.key, effect="My Very Slow Random Effect With Long Name"
 | |
|         )
 | |
|         state = await wait_for_state_change(rgbcw_light.key)
 | |
|         assert state.effect == "My Very Slow Random Effect With Long Name"
 | |
| 
 | |
|         # Test 13c: Fast random effect with long name
 | |
|         client.light_command(
 | |
|             key=rgbcw_light.key, effect="My Fast Random Effect That Changes Quickly"
 | |
|         )
 | |
|         state = await wait_for_state_change(rgbcw_light.key)
 | |
|         assert state.effect == "My Fast Random Effect That Changes Quickly"
 | |
| 
 | |
|         # Test 13d: Random effect with medium length name
 | |
|         client.light_command(
 | |
|             key=rgbcw_light.key, effect="Random Effect With Medium Length Name Here"
 | |
|         )
 | |
|         state = await wait_for_state_change(rgbcw_light.key)
 | |
|         assert state.effect == "Random Effect With Medium Length Name Here"
 | |
| 
 | |
|         # Test 13e: Another random effect
 | |
|         client.light_command(
 | |
|             key=rgbcw_light.key,
 | |
|             effect="Another Random Effect With Different Parameters",
 | |
|         )
 | |
|         state = await wait_for_state_change(rgbcw_light.key)
 | |
|         assert state.effect == "Another Random Effect With Different Parameters"
 | |
| 
 | |
|         # Test 13f: Yet another random effect
 | |
|         client.light_command(
 | |
|             key=rgbcw_light.key, effect="Yet Another Random Effect To Test Memory"
 | |
|         )
 | |
|         state = await wait_for_state_change(rgbcw_light.key)
 | |
|         assert state.effect == "Yet Another Random Effect To Test Memory"
 | |
| 
 | |
|         # Test 14: stop effect
 | |
|         client.light_command(key=rgbcw_light.key, effect="None")
 | |
|         state = await wait_for_state_change(rgbcw_light.key)
 | |
|         assert state.effect == "None"
 | |
| 
 | |
|         # Test 15: color_mode parameter
 | |
|         client.light_command(
 | |
|             key=rgbcw_light.key, state=True, color_mode=5
 | |
|         )  # COLD_WARM_WHITE
 | |
|         state = await wait_for_state_change(rgbcw_light.key)
 | |
|         assert state.state is True
 | |
| 
 | |
|         # Now test common combinations
 | |
| 
 | |
|         # Test 16: RGB combination (set_rgb) - RGB values get normalized
 | |
|         client.light_command(key=rgbcw_light.key, rgb=(1.0, 0.0, 0.5))
 | |
|         state = await wait_for_state_change(rgbcw_light.key)
 | |
|         # RGB values get normalized - in this case red is already 1.0
 | |
|         assert state.red == pytest.approx(1.0, abs=0.1)
 | |
|         assert state.green == pytest.approx(0.0, abs=0.1)
 | |
|         assert state.blue == pytest.approx(0.5, abs=0.1)
 | |
| 
 | |
|         # Test 17: Multiple RGB changes to test transitions
 | |
|         client.light_command(key=rgbcw_light.key, rgb=(0.2, 0.8, 0.4))
 | |
|         state = await wait_for_state_change(rgbcw_light.key)
 | |
|         # RGB values get normalized so green (highest) becomes 1.0
 | |
|         # Expected: (0.2/0.8, 0.8/0.8, 0.4/0.8) = (0.25, 1.0, 0.5)
 | |
|         assert state.red == pytest.approx(0.25, abs=0.01)
 | |
|         assert state.green == pytest.approx(1.0, abs=0.01)
 | |
|         assert state.blue == pytest.approx(0.5, abs=0.01)
 | |
| 
 | |
|         # Test 18: State + brightness + transition
 | |
|         client.light_command(
 | |
|             key=rgbcw_light.key, state=True, brightness=0.7, transition_length=0.1
 | |
|         )
 | |
|         state = await wait_for_state_change(rgbcw_light.key)
 | |
|         assert state.state is True
 | |
|         assert state.brightness == pytest.approx(0.7)
 | |
| 
 | |
|         # Test 19: RGB + brightness + color_brightness
 | |
|         client.light_command(
 | |
|             key=rgb_light.key,
 | |
|             state=True,
 | |
|             brightness=0.8,
 | |
|             color_brightness=0.9,
 | |
|             rgb=(0.2, 0.4, 0.6),
 | |
|         )
 | |
|         state = await wait_for_state_change(rgb_light.key)
 | |
|         assert state.state is True
 | |
|         assert state.brightness == pytest.approx(0.8)
 | |
| 
 | |
|         # Test 20: Color temp + cold/warm white
 | |
|         client.light_command(
 | |
|             key=rgbcw_light.key, color_temperature=250, cold_white=0.7, warm_white=0.3
 | |
|         )
 | |
|         state = await wait_for_state_change(rgbcw_light.key)
 | |
|         assert state.color_temperature == pytest.approx(250)
 | |
| 
 | |
|         # Test 21: Turn RGB light off
 | |
|         client.light_command(key=rgb_light.key, state=False)
 | |
|         state = await wait_for_state_change(rgb_light.key)
 | |
|         assert state.state is False
 | |
| 
 | |
|         # Test color mode combinations to verify get_suitable_color_modes optimization
 | |
| 
 | |
|         # Test 22: White only mode
 | |
|         client.light_command(key=rgbcw_light.key, state=True, white=0.5)
 | |
|         state = await wait_for_state_change(rgbcw_light.key)
 | |
|         assert state.state is True
 | |
| 
 | |
|         # Test 23: Color temperature only mode
 | |
|         client.light_command(key=rgbcw_light.key, state=True, color_temperature=300)
 | |
|         state = await wait_for_state_change(rgbcw_light.key)
 | |
|         assert state.color_temperature == pytest.approx(300)
 | |
| 
 | |
|         # Test 24: Cold/warm white only mode
 | |
|         client.light_command(
 | |
|             key=rgbcw_light.key, state=True, cold_white=0.6, warm_white=0.4
 | |
|         )
 | |
|         state = await wait_for_state_change(rgbcw_light.key)
 | |
|         assert state.cold_white == pytest.approx(0.6)
 | |
|         assert state.warm_white == pytest.approx(0.4)
 | |
| 
 | |
|         # Test 25: RGB only mode
 | |
|         client.light_command(key=rgb_light.key, state=True, rgb=(0.5, 0.5, 0.5))
 | |
|         state = await wait_for_state_change(rgb_light.key)
 | |
|         assert state.state is True
 | |
| 
 | |
|         # Test 26: RGB + white combination
 | |
|         client.light_command(
 | |
|             key=rgbcw_light.key, state=True, rgb=(0.3, 0.3, 0.3), white=0.5
 | |
|         )
 | |
|         state = await wait_for_state_change(rgbcw_light.key)
 | |
|         assert state.state is True
 | |
| 
 | |
|         # Test 27: RGB + color temperature combination
 | |
|         client.light_command(
 | |
|             key=rgbcw_light.key, state=True, rgb=(0.4, 0.4, 0.4), color_temperature=280
 | |
|         )
 | |
|         state = await wait_for_state_change(rgbcw_light.key)
 | |
|         assert state.state is True
 | |
| 
 | |
|         # Test 28: RGB + cold/warm white combination
 | |
|         client.light_command(
 | |
|             key=rgbcw_light.key,
 | |
|             state=True,
 | |
|             rgb=(0.2, 0.2, 0.2),
 | |
|             cold_white=0.5,
 | |
|             warm_white=0.5,
 | |
|         )
 | |
|         state = await wait_for_state_change(rgbcw_light.key)
 | |
|         assert state.state is True
 | |
| 
 | |
|         # Test 29: White + color temperature combination
 | |
|         client.light_command(
 | |
|             key=rgbcw_light.key, state=True, white=0.6, color_temperature=320
 | |
|         )
 | |
|         state = await wait_for_state_change(rgbcw_light.key)
 | |
|         assert state.state is True
 | |
| 
 | |
|         # Test 30: No specific color parameters (tests default mode selection)
 | |
|         client.light_command(key=rgbcw_light.key, state=True, brightness=0.75)
 | |
|         state = await wait_for_state_change(rgbcw_light.key)
 | |
|         assert state.state is True
 | |
|         assert state.brightness == pytest.approx(0.75)
 | |
| 
 | |
|         # Final cleanup - turn all lights off
 | |
|         for light in lights:
 | |
|             client.light_command(
 | |
|                 key=light.key,
 | |
|                 state=False,
 | |
|             )
 | |
|             state = await wait_for_state_change(light.key)
 | |
|             assert state.state is False
 |