"""Test device template functions.""" import pytest from homeassistant.core import HomeAssistant from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers.template import TemplateError from tests.common import MockConfigEntry from tests.helpers.template.helpers import assert_result_info, render_to_info async def test_device_entities( hass: HomeAssistant, device_registry: dr.DeviceRegistry, entity_registry: er.EntityRegistry, ) -> None: """Test device_entities function.""" config_entry = MockConfigEntry(domain="light") config_entry.add_to_hass(hass) # Test non existing device ids info = render_to_info(hass, "{{ device_entities('abc123') }}") assert_result_info(info, []) assert info.rate_limit is None info = render_to_info(hass, "{{ device_entities(56) }}") assert_result_info(info, []) assert info.rate_limit is None # Test device without entities device_entry = device_registry.async_get_or_create( config_entry_id=config_entry.entry_id, connections={(dr.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")}, ) info = render_to_info(hass, f"{{{{ device_entities('{device_entry.id}') }}}}") assert_result_info(info, []) assert info.rate_limit is None # Test device with single entity, which has no state entity_entry = entity_registry.async_get_or_create( "light", "hue", "5678", config_entry=config_entry, device_id=device_entry.id, ) info = render_to_info(hass, f"{{{{ device_entities('{device_entry.id}') }}}}") assert_result_info(info, [entity_entry.entity_id], []) assert info.rate_limit is None info = render_to_info( hass, ( f"{{{{ device_entities('{device_entry.id}') | expand " "| sort(attribute='entity_id') | map(attribute='entity_id') | join(', ') }}" ), ) assert_result_info(info, "", [entity_entry.entity_id]) assert info.rate_limit is None # Test device with single entity, with state hass.states.async_set(entity_entry.entity_id, "happy") info = render_to_info( hass, ( f"{{{{ device_entities('{device_entry.id}') | expand " "| sort(attribute='entity_id') | map(attribute='entity_id') | join(', ') }}" ), ) assert_result_info(info, entity_entry.entity_id, [entity_entry.entity_id]) assert info.rate_limit is None # Test device with multiple entities, which have a state entity_entry_2 = entity_registry.async_get_or_create( "light", "hue", "ABCD", config_entry=config_entry, device_id=device_entry.id, ) hass.states.async_set(entity_entry_2.entity_id, "camper") info = render_to_info(hass, f"{{{{ device_entities('{device_entry.id}') }}}}") assert_result_info(info, [entity_entry.entity_id, entity_entry_2.entity_id], []) assert info.rate_limit is None info = render_to_info( hass, ( f"{{{{ device_entities('{device_entry.id}') | expand " "| sort(attribute='entity_id') | map(attribute='entity_id') | join(', ') }}" ), ) assert_result_info( info, f"{entity_entry.entity_id}, {entity_entry_2.entity_id}", [entity_entry.entity_id, entity_entry_2.entity_id], ) assert info.rate_limit is None async def test_device_id( hass: HomeAssistant, device_registry: dr.DeviceRegistry, entity_registry: er.EntityRegistry, ) -> None: """Test device_id function.""" config_entry = MockConfigEntry(domain="light") config_entry.add_to_hass(hass) device_entry = device_registry.async_get_or_create( config_entry_id=config_entry.entry_id, connections={(dr.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")}, model="test", name="test", ) entity_entry = entity_registry.async_get_or_create( "sensor", "test", "test", suggested_object_id="test", device_id=device_entry.id ) entity_entry_no_device = entity_registry.async_get_or_create( "sensor", "test", "test_no_device", suggested_object_id="test" ) info = render_to_info(hass, "{{ 'sensor.fail' | device_id }}") assert_result_info(info, None) assert info.rate_limit is None info = render_to_info(hass, "{{ 56 | device_id }}") assert_result_info(info, None) info = render_to_info(hass, "{{ 'not_a_real_entity_id' | device_id }}") assert_result_info(info, None) info = render_to_info( hass, f"{{{{ device_id('{entity_entry_no_device.entity_id}') }}}}" ) assert_result_info(info, None) assert info.rate_limit is None info = render_to_info(hass, f"{{{{ device_id('{entity_entry.entity_id}') }}}}") assert_result_info(info, device_entry.id) assert info.rate_limit is None info = render_to_info(hass, "{{ device_id('test') }}") assert_result_info(info, device_entry.id) assert info.rate_limit is None async def test_device_name( hass: HomeAssistant, device_registry: dr.DeviceRegistry, entity_registry: er.EntityRegistry, ) -> None: """Test device_name function.""" config_entry = MockConfigEntry(domain="light") config_entry.add_to_hass(hass) # Test non existing entity id info = render_to_info(hass, "{{ device_name('sensor.fake') }}") assert_result_info(info, None) assert info.rate_limit is None # Test non existing device id info = render_to_info(hass, "{{ device_name('1234567890') }}") assert_result_info(info, None) assert info.rate_limit is None # Test wrong value type info = render_to_info(hass, "{{ device_name(56) }}") assert_result_info(info, None) assert info.rate_limit is None # Test device with single entity device_entry = device_registry.async_get_or_create( config_entry_id=config_entry.entry_id, connections={(dr.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")}, name="A light", ) entity_entry = entity_registry.async_get_or_create( "light", "hue", "5678", config_entry=config_entry, device_id=device_entry.id, ) info = render_to_info(hass, f"{{{{ device_name('{device_entry.id}') }}}}") assert_result_info(info, device_entry.name) assert info.rate_limit is None info = render_to_info(hass, f"{{{{ device_name('{entity_entry.entity_id}') }}}}") assert_result_info(info, device_entry.name) assert info.rate_limit is None # Test device after renaming device_entry = device_registry.async_update_device( device_entry.id, name_by_user="My light", ) info = render_to_info(hass, f"{{{{ device_name('{device_entry.id}') }}}}") assert_result_info(info, device_entry.name_by_user) assert info.rate_limit is None info = render_to_info(hass, f"{{{{ device_name('{entity_entry.entity_id}') }}}}") assert_result_info(info, device_entry.name_by_user) assert info.rate_limit is None async def test_device_attr( hass: HomeAssistant, device_registry: dr.DeviceRegistry, entity_registry: er.EntityRegistry, ) -> None: """Test device_attr and is_device_attr functions.""" config_entry = MockConfigEntry(domain="light") config_entry.add_to_hass(hass) # Test non existing device ids (device_attr) info = render_to_info(hass, "{{ device_attr('abc123', 'id') }}") assert_result_info(info, None) assert info.rate_limit is None info = render_to_info(hass, "{{ device_attr(56, 'id') }}") with pytest.raises(TemplateError): assert_result_info(info, None) # Test non existing device ids (is_device_attr) info = render_to_info(hass, "{{ is_device_attr('abc123', 'id', 'test') }}") assert_result_info(info, False) assert info.rate_limit is None info = render_to_info(hass, "{{ is_device_attr(56, 'id', 'test') }}") with pytest.raises(TemplateError): assert_result_info(info, False) # Test non existing entity id (device_attr) info = render_to_info(hass, "{{ device_attr('entity.test', 'id') }}") assert_result_info(info, None) assert info.rate_limit is None # Test non existing entity id (is_device_attr) info = render_to_info(hass, "{{ is_device_attr('entity.test', 'id', 'test') }}") assert_result_info(info, False) assert info.rate_limit is None device_entry = device_registry.async_get_or_create( config_entry_id=config_entry.entry_id, connections={(dr.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")}, model="test", ) entity_entry = entity_registry.async_get_or_create( "sensor", "test", "test", suggested_object_id="test", device_id=device_entry.id ) # Test non existent device attribute (device_attr) info = render_to_info( hass, f"{{{{ device_attr('{device_entry.id}', 'invalid_attr') }}}}" ) assert_result_info(info, None) assert info.rate_limit is None # Test non existent device attribute (is_device_attr) info = render_to_info( hass, f"{{{{ is_device_attr('{device_entry.id}', 'invalid_attr', 'test') }}}}" ) assert_result_info(info, False) assert info.rate_limit is None # Test None device attribute (device_attr) info = render_to_info( hass, f"{{{{ device_attr('{device_entry.id}', 'manufacturer') }}}}" ) assert_result_info(info, None) assert info.rate_limit is None # Test None device attribute mismatch (is_device_attr) info = render_to_info( hass, f"{{{{ is_device_attr('{device_entry.id}', 'manufacturer', 'test') }}}}" ) assert_result_info(info, False) assert info.rate_limit is None # Test None device attribute match (is_device_attr) info = render_to_info( hass, f"{{{{ is_device_attr('{device_entry.id}', 'manufacturer', None) }}}}" ) assert_result_info(info, True) assert info.rate_limit is None # Test valid device attribute match (device_attr) info = render_to_info(hass, f"{{{{ device_attr('{device_entry.id}', 'model') }}}}") assert_result_info(info, "test") assert info.rate_limit is None # Test valid device attribute match (device_attr) info = render_to_info( hass, f"{{{{ device_attr('{entity_entry.entity_id}', 'model') }}}}" ) assert_result_info(info, "test") assert info.rate_limit is None # Test valid device attribute mismatch (is_device_attr) info = render_to_info( hass, f"{{{{ is_device_attr('{device_entry.id}', 'model', 'fail') }}}}" ) assert_result_info(info, False) assert info.rate_limit is None # Test valid device attribute match (is_device_attr) info = render_to_info( hass, f"{{{{ is_device_attr('{device_entry.id}', 'model', 'test') }}}}" ) assert_result_info(info, True) assert info.rate_limit is None # Test filter syntax (device_attr) info = render_to_info( hass, f"{{{{ '{entity_entry.entity_id}' | device_attr('model') }}}}" ) assert_result_info(info, "test") assert info.rate_limit is None # Test test syntax (is_device_attr) info = render_to_info( hass, ( f"{{{{ ['{device_entry.id}'] | select('is_device_attr', 'model', 'test') " "| list }}" ), ) assert_result_info(info, [device_entry.id]) assert info.rate_limit is None async def test_device_functions_with_child_devices( hass: HomeAssistant, device_registry: dr.DeviceRegistry, ) -> None: """Test device functions with child devices.""" config_entry = MockConfigEntry(domain="test") config_entry.add_to_hass(hass) parent = device_registry.async_get_or_create( config_entry_id=config_entry.entry_id, identifiers={("test", "strip")}, name="Power strip", ) child_device = device_registry.async_get_or_create_child( config_entry_id=config_entry.entry_id, identifiers={("test", "strip_outlet_1")}, parent_device_id=parent.id, name="Outlet 1", ) # device_id finds a child device by name info = render_to_info(hass, "{{ device_id('Outlet 1') }}") assert_result_info(info, child_device.id) # device_name resolves a child device info = render_to_info(hass, f"{{{{ device_name('{child_device.id}') }}}}") assert_result_info(info, "Outlet 1") # device_attr returns None for attributes a child device does not have info = render_to_info( hass, f"{{{{ device_attr('{child_device.id}', 'manufacturer') }}}}" ) assert_result_info(info, None) info = render_to_info( hass, f"{{{{ device_attr('{child_device.id}', 'parent_device_id') }}}}" ) assert_result_info(info, child_device.parent_device_id)