"""Tests for Alexa Devices services.""" from unittest.mock import AsyncMock import pytest from syrupy.assertion import SnapshotAssertion from homeassistant.components.alexa_devices.const import DOMAIN from homeassistant.components.alexa_devices.services import ( ATTR_INFO_SKILL, ATTR_SOUND, ATTR_TEXT_COMMAND, ) from homeassistant.config_entries import ConfigEntryState from homeassistant.const import ATTR_DEVICE_ID from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant from homeassistant.exceptions import ServiceValidationError from homeassistant.helpers import device_registry as dr from . import setup_integration from .const import TEST_DEVICE_1_ID, TEST_DEVICE_1_SN from tests.common import MockConfigEntry, mock_device_registry async def test_setup_services( hass: HomeAssistant, mock_amazon_devices_client: AsyncMock, mock_config_entry: MockConfigEntry, ) -> None: """Test setup of Alexa Devices services.""" await setup_integration(hass, mock_config_entry) assert (services := hass.services.async_services_for_domain(DOMAIN)) assert "send_text_command" in services assert "send_sound" in services assert "send_info_skill" in services async def test_info_skill_service( hass: HomeAssistant, device_registry: dr.DeviceRegistry, mock_amazon_devices_client: AsyncMock, mock_config_entry: MockConfigEntry, snapshot: SnapshotAssertion, ) -> None: """Test info skill service.""" await setup_integration(hass, mock_config_entry) device_entry = device_registry.async_get_device_by_identifier( (DOMAIN, TEST_DEVICE_1_SN), mock_config_entry.entry_id ) assert device_entry await hass.services.async_call( DOMAIN, "send_info_skill", { ATTR_INFO_SKILL: "tell_joke", ATTR_DEVICE_ID: device_entry.id, }, blocking=True, ) assert mock_amazon_devices_client.call_alexa_info_skill.call_count == 1 assert mock_amazon_devices_client.call_alexa_info_skill.call_args == snapshot async def test_send_sound_service( hass: HomeAssistant, device_registry: dr.DeviceRegistry, mock_amazon_devices_client: AsyncMock, mock_config_entry: MockConfigEntry, snapshot: SnapshotAssertion, ) -> None: """Test send sound service.""" await setup_integration(hass, mock_config_entry) device_entry = device_registry.async_get_device_by_identifier( (DOMAIN, TEST_DEVICE_1_SN), mock_config_entry.entry_id ) assert device_entry await hass.services.async_call( DOMAIN, "send_sound", { ATTR_SOUND: "bell_02", ATTR_DEVICE_ID: device_entry.id, }, blocking=True, ) assert mock_amazon_devices_client.call_alexa_sound.call_count == 1 assert mock_amazon_devices_client.call_alexa_sound.call_args == snapshot async def test_send_text_service( hass: HomeAssistant, device_registry: dr.DeviceRegistry, mock_amazon_devices_client: AsyncMock, mock_config_entry: MockConfigEntry, snapshot: SnapshotAssertion, ) -> None: """Test send text service.""" await setup_integration(hass, mock_config_entry) device_entry = device_registry.async_get_device_by_identifier( (DOMAIN, TEST_DEVICE_1_SN), mock_config_entry.entry_id ) assert device_entry await hass.services.async_call( DOMAIN, "send_text_command", { ATTR_TEXT_COMMAND: "Play B.B.C. radio on TuneIn", ATTR_DEVICE_ID: device_entry.id, }, blocking=True, ) assert mock_amazon_devices_client.call_alexa_text_command.call_count == 1 assert mock_amazon_devices_client.call_alexa_text_command.call_args == snapshot @pytest.mark.parametrize( ( "sound", "device_id", "translation_domain", "translation_key", "translation_placeholders", ), [ ( "bell_02", "fake_device_id", HOMEASSISTANT_DOMAIN, "service_device_not_found", {"device_id": "fake_device_id"}, ), ( "wrong_sound_name", TEST_DEVICE_1_ID, DOMAIN, "invalid_sound_value", { "sound": "wrong_sound_name", }, ), ], ) async def test_invalid_parameters( hass: HomeAssistant, mock_amazon_devices_client: AsyncMock, mock_config_entry: MockConfigEntry, sound: str, device_id: str, translation_domain: str, translation_key: str, translation_placeholders: dict[str, str], ) -> None: """Test invalid service parameters.""" device_entry = dr.DeviceEntry( config_entry_id=mock_config_entry.entry_id, id=TEST_DEVICE_1_ID, identifiers={(DOMAIN, TEST_DEVICE_1_SN)}, ) mock_device_registry( hass, {device_entry.id: device_entry}, ) await setup_integration(hass, mock_config_entry) # Call Service with pytest.raises(ServiceValidationError) as exc_info: await hass.services.async_call( DOMAIN, "send_sound", { ATTR_SOUND: sound, ATTR_DEVICE_ID: device_id, }, blocking=True, ) assert exc_info.value.translation_domain == translation_domain assert exc_info.value.translation_key == translation_key assert exc_info.value.translation_placeholders == translation_placeholders @pytest.mark.parametrize( ( "info_skill", "device_id", "translation_domain", "translation_key", "translation_placeholders", ), [ ( "tell_joke", "fake_device_id", HOMEASSISTANT_DOMAIN, "service_device_not_found", {"device_id": "fake_device_id"}, ), ( "wrong_info_skill_name", TEST_DEVICE_1_ID, DOMAIN, "invalid_info_skill_value", { "info_skill": "wrong_info_skill_name", }, ), ], ) async def test_invalid_info_skillparameters( hass: HomeAssistant, mock_amazon_devices_client: AsyncMock, mock_config_entry: MockConfigEntry, info_skill: str, device_id: str, translation_domain: str, translation_key: str, translation_placeholders: dict[str, str], ) -> None: """Test invalid info skill service parameters.""" device_entry = dr.DeviceEntry( config_entry_id=mock_config_entry.entry_id, id=TEST_DEVICE_1_ID, identifiers={(DOMAIN, TEST_DEVICE_1_SN)}, ) mock_device_registry( hass, {device_entry.id: device_entry}, ) await setup_integration(hass, mock_config_entry) # Call Service with pytest.raises(ServiceValidationError) as exc_info: await hass.services.async_call( DOMAIN, "send_info_skill", { ATTR_INFO_SKILL: info_skill, ATTR_DEVICE_ID: device_id, }, blocking=True, ) assert exc_info.value.translation_domain == translation_domain assert exc_info.value.translation_key == translation_key assert exc_info.value.translation_placeholders == translation_placeholders async def test_config_entry_not_loaded( hass: HomeAssistant, device_registry: dr.DeviceRegistry, mock_amazon_devices_client: AsyncMock, mock_config_entry: MockConfigEntry, ) -> None: """Test config entry not loaded.""" await setup_integration(hass, mock_config_entry) device_entry = device_registry.async_get_device_by_identifier( (DOMAIN, TEST_DEVICE_1_SN), mock_config_entry.entry_id ) assert device_entry await hass.config_entries.async_unload(mock_config_entry.entry_id) await hass.async_block_till_done() assert mock_config_entry.state is ConfigEntryState.NOT_LOADED # Call Service with pytest.raises(ServiceValidationError) as exc_info: await hass.services.async_call( DOMAIN, "send_sound", { ATTR_SOUND: "bell_02", ATTR_DEVICE_ID: device_entry.id, }, blocking=True, ) assert exc_info.value.translation_domain == HOMEASSISTANT_DOMAIN assert exc_info.value.translation_key == "service_config_entry_not_loaded" assert exc_info.value.translation_placeholders == { "domain": DOMAIN, "entry_title": mock_config_entry.title, } async def test_invalid_config_entry( hass: HomeAssistant, mock_amazon_devices_client: AsyncMock, mock_config_entry: MockConfigEntry, ) -> None: """Test that a device pointing to a non-existing config entry ID is skipped.""" device_entry = dr.DeviceEntry( config_entry_id="non_existing_entry_id", id=TEST_DEVICE_1_ID, identifiers={(DOMAIN, TEST_DEVICE_1_SN)}, ) mock_device_registry( hass, {device_entry.id: device_entry}, ) await setup_integration(hass, mock_config_entry) with pytest.raises(ServiceValidationError) as exc_info: await hass.services.async_call( DOMAIN, "send_sound", { ATTR_SOUND: "bell_02", ATTR_DEVICE_ID: TEST_DEVICE_1_ID, }, blocking=True, ) assert exc_info.value.translation_domain == HOMEASSISTANT_DOMAIN assert exc_info.value.translation_key == "service_device_wrong_domain" assert exc_info.value.translation_placeholders == { "device_name": TEST_DEVICE_1_ID, "domain": DOMAIN, } async def test_missing_config_entry( hass: HomeAssistant, device_registry: dr.DeviceRegistry, mock_amazon_devices_client: AsyncMock, mock_config_entry: MockConfigEntry, ) -> None: """Test that a device not owned by an Alexa config entry is rejected.""" await setup_integration(hass, mock_config_entry) device_entry = device_registry.async_get_device_by_identifier( (DOMAIN, TEST_DEVICE_1_SN), mock_config_entry.entry_id ) assert device_entry # Move the device to a config entry from a different integration other_entry = MockConfigEntry(domain="other_domain", data={}) other_entry.add_to_hass(hass) device_registry.async_update_device( device_entry.id, new_config_entry_id=other_entry.entry_id ) # Call Service with pytest.raises(ServiceValidationError) as exc_info: await hass.services.async_call( DOMAIN, "send_sound", { ATTR_SOUND: "bell_02", ATTR_DEVICE_ID: device_entry.id, }, blocking=True, ) assert exc_info.value.translation_domain == HOMEASSISTANT_DOMAIN assert exc_info.value.translation_key == "service_device_wrong_domain" assert exc_info.value.translation_placeholders == { "device_name": device_entry.name, "domain": DOMAIN, }