@@ -3904,3 +3904,239 @@ async def assert_triggers(target: dict[str, list[str]], expected: list[str]) ->
39043904 "switch.turned_on" ,
39053905 ],
39063906 )
3907+
3908+
3909+ @pytest .mark .usefixtures ("target_entities" )
3910+ @patch ("annotatedyaml.loader.load_yaml" )
3911+ @patch .object (Integration , "has_services" , return_value = True )
3912+ async def test_get_services_for_target (
3913+ mock_has_services : Mock ,
3914+ mock_load_yaml : Mock ,
3915+ hass : HomeAssistant ,
3916+ websocket_client : MockHAClientWebSocket ,
3917+ ) -> None :
3918+ """Test get_services_for_target command with mixed target types."""
3919+
3920+ def get_common_service_descriptions (domain : str ):
3921+ return f"""
3922+ turn_on:
3923+ target:
3924+ entity:
3925+ domain: { domain }
3926+ fields:
3927+ behavior:
3928+ required: true
3929+ default: any
3930+ selector:
3931+ select:
3932+ options:
3933+ - first
3934+ - last
3935+ - any
3936+ non_target_service:
3937+ fields:
3938+ behavior:
3939+ required: true
3940+ default: any
3941+ selector:
3942+ select:
3943+ options:
3944+ - first
3945+ - last
3946+ - any
3947+ """
3948+
3949+ component1_service_descriptions = """
3950+ light_message:
3951+ target:
3952+ entity:
3953+ - domain: light
3954+ integration: component1
3955+ - domain: sensor
3956+ integration: component1
3957+ device_class: illuminance
3958+ light_flash:
3959+ target:
3960+ entity:
3961+ domain: light
3962+ integration: component1
3963+ supported_features:
3964+ - light.LightEntityFeature.FLASH
3965+ - light.LightEntityFeature.EFFECT
3966+ light_dance:
3967+ target:
3968+ entity:
3969+ domain: light
3970+ integration: component1
3971+ supported_features: # both required features
3972+ - - light.LightEntityFeature.FLASH
3973+ - light.LightEntityFeature.TRANSITION
3974+ """
3975+
3976+ component2_service_descriptions = """
3977+ match_all:
3978+ target:
3979+
3980+ other_integration_lights:
3981+ target:
3982+ entity:
3983+ - domain: light
3984+ supported_features:
3985+ - light.LightEntityFeature.EFFECT
3986+ - integration: test
3987+ domain: light
3988+ """
3989+
3990+ def _load_yaml (fname , secrets = None ):
3991+ if fname .endswith ("component1/services.yaml" ):
3992+ service_descriptions = component1_service_descriptions
3993+ elif fname .endswith ("component2/services.yaml" ):
3994+ service_descriptions = component2_service_descriptions
3995+ else :
3996+ service_descriptions = get_common_service_descriptions (fname .split ("/" )[- 2 ])
3997+ with io .StringIO (service_descriptions ) as file :
3998+ return parse_yaml (file )
3999+
4000+ mock_load_yaml .side_effect = _load_yaml
4001+ mock_integration (hass , MockModule ("light" ))
4002+ assert await async_setup_component (hass , "light" , {})
4003+ mock_integration (hass , MockModule ("switch" ))
4004+ assert await async_setup_component (hass , "switch" , {})
4005+ mock_integration (hass , MockModule ("sensor" ))
4006+ assert await async_setup_component (hass , "sensor" , {})
4007+ mock_integration (hass , MockModule ("component1" ))
4008+ assert await async_setup_component (hass , "component1" , {})
4009+ mock_integration (hass , MockModule ("component2" ))
4010+ assert await async_setup_component (hass , "component2" , {})
4011+ await hass .async_block_till_done ()
4012+
4013+ hass .services .async_register ("light" , "turn_on" , lambda call : None )
4014+ hass .services .async_register ("light" , "non_target_service" , lambda call : None )
4015+ hass .services .async_register ("switch" , "turn_on" , lambda call : None )
4016+ hass .services .async_register ("switch" , "non_target_service" , lambda call : None )
4017+ hass .services .async_register ("sensor" , "turn_on" , lambda call : None )
4018+ hass .services .async_register ("sensor" , "non_target_service" , lambda call : None )
4019+ hass .services .async_register ("component1" , "light_message" , lambda call : None )
4020+ hass .services .async_register ("component1" , "light_flash" , lambda call : None )
4021+ hass .services .async_register ("component1" , "light_dance" , lambda call : None )
4022+ hass .services .async_register ("component2" , "match_all" , lambda call : None )
4023+ hass .services .async_register (
4024+ "component2" , "other_integration_lights" , lambda call : None
4025+ )
4026+ await hass .async_block_till_done ()
4027+
4028+ async def assert_services (target : dict [str , list [str ]], expected : list [str ]) -> Any :
4029+ """Call the command and assert expected services."""
4030+ await websocket_client .send_json_auto_id (
4031+ {"type" : "get_services_for_target" , "target" : target }
4032+ )
4033+ msg = await websocket_client .receive_json ()
4034+
4035+ assert msg ["type" ] == const .TYPE_RESULT
4036+ assert msg ["success" ]
4037+ assert sorted (msg ["result" ]) == sorted (expected )
4038+
4039+ # Test entity target - unknown entity
4040+ await assert_services ({"entity_id" : ["light.unknown_entity" ]}, [])
4041+
4042+ # Test entity target - entity not in registry
4043+ await assert_services (
4044+ {"entity_id" : ["light.not_registry" ]},
4045+ [
4046+ "component2.match_all" ,
4047+ "component2.other_integration_lights" ,
4048+ "light.turn_on" ,
4049+ ],
4050+ )
4051+
4052+ # Test entity targets
4053+ await assert_services (
4054+ {"entity_id" : ["light.component1_light" , "switch.component1_switch" ]},
4055+ [
4056+ "component1.light_message" ,
4057+ "component2.match_all" ,
4058+ "light.turn_on" ,
4059+ "switch.turn_on" ,
4060+ ],
4061+ )
4062+ await assert_services (
4063+ {"entity_id" : ["light.component1_flash_light" ]},
4064+ [
4065+ "component1.light_flash" ,
4066+ "component1.light_message" ,
4067+ "component2.match_all" ,
4068+ "light.turn_on" ,
4069+ ],
4070+ )
4071+ await assert_services (
4072+ {"entity_id" : ["light.component1_effect_flash_light" ]},
4073+ [
4074+ "component1.light_flash" ,
4075+ "component1.light_message" ,
4076+ "component2.match_all" ,
4077+ "component2.other_integration_lights" ,
4078+ "light.turn_on" ,
4079+ ],
4080+ )
4081+ await assert_services (
4082+ {"entity_id" : ["light.component1_flash_transition_light" ]},
4083+ [
4084+ "component1.light_dance" ,
4085+ "component1.light_flash" ,
4086+ "component1.light_message" ,
4087+ "component2.match_all" ,
4088+ "light.turn_on" ,
4089+ ],
4090+ )
4091+
4092+ # Test device target - multiple devices
4093+ await assert_services (
4094+ {"device_id" : ["device1" , "device2" ]},
4095+ [
4096+ "component1.light_message" ,
4097+ "component2.match_all" ,
4098+ "component2.other_integration_lights" ,
4099+ "light.turn_on" ,
4100+ "sensor.turn_on" ,
4101+ "switch.turn_on" ,
4102+ ],
4103+ )
4104+
4105+ # Test area target - multiple areas
4106+ await assert_services (
4107+ {"area_id" : ["kitchen" , "living_room" ]},
4108+ [
4109+ "component2.match_all" ,
4110+ "component2.other_integration_lights" ,
4111+ "light.turn_on" ,
4112+ "switch.turn_on" ,
4113+ ],
4114+ )
4115+
4116+ # Test label target - multiple labels
4117+ await assert_services (
4118+ {"label_id" : ["label_1" , "label_2" ]},
4119+ [
4120+ "light.turn_on" ,
4121+ "component2.match_all" ,
4122+ "component2.other_integration_lights" ,
4123+ "switch.turn_on" ,
4124+ ],
4125+ )
4126+ # Test mixed target types
4127+ await assert_services (
4128+ {
4129+ "entity_id" : ["light.test1" ],
4130+ "device_id" : ["device2" ],
4131+ "area_id" : ["kitchen" ],
4132+ "label_id" : ["label_1" ],
4133+ },
4134+ [
4135+ "component1.light_message" ,
4136+ "component2.match_all" ,
4137+ "component2.other_integration_lights" ,
4138+ "light.turn_on" ,
4139+ "sensor.turn_on" ,
4140+ "switch.turn_on" ,
4141+ ],
4142+ )
0 commit comments