Handle grpc errors in Google Assistant SDK (#146438)

This commit is contained in:
tronikos 2025-06-10 06:31:32 -07:00 committed by GitHub
parent 7da1671b06
commit b9e8cfb291
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 74 additions and 6 deletions

View File

@ -12,6 +12,7 @@ import aiohttp
from aiohttp import web from aiohttp import web
from gassist_text import TextAssistant from gassist_text import TextAssistant
from google.oauth2.credentials import Credentials from google.oauth2.credentials import Credentials
from grpc import RpcError
from homeassistant.components.http import HomeAssistantView from homeassistant.components.http import HomeAssistantView
from homeassistant.components.media_player import ( from homeassistant.components.media_player import (
@ -25,6 +26,7 @@ from homeassistant.components.media_player import (
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_ENTITY_ID, CONF_ACCESS_TOKEN from homeassistant.const import ATTR_ENTITY_ID, CONF_ACCESS_TOKEN
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.config_entry_oauth2_flow import OAuth2Session from homeassistant.helpers.config_entry_oauth2_flow import OAuth2Session
from homeassistant.helpers.event import async_call_later from homeassistant.helpers.event import async_call_later
@ -83,7 +85,17 @@ async def async_send_text_commands(
) as assistant: ) as assistant:
command_response_list = [] command_response_list = []
for command in commands: for command in commands:
try:
resp = await hass.async_add_executor_job(assistant.assist, command) resp = await hass.async_add_executor_job(assistant.assist, command)
except RpcError as err:
_LOGGER.error(
"Failed to send command '%s' to Google Assistant: %s",
command,
err,
)
raise HomeAssistantError(
translation_domain=DOMAIN, translation_key="grpc_error"
) from err
text_response = resp[0] text_response = resp[0]
_LOGGER.debug("command: %s\nresponse: %s", command, text_response) _LOGGER.debug("command: %s\nresponse: %s", command, text_response)
audio_response = resp[2] audio_response = resp[2]

View File

@ -57,5 +57,10 @@
} }
} }
} }
},
"exceptions": {
"grpc_error": {
"message": "Failed to communicate with Google Assistant"
}
} }
} }

View File

@ -6,6 +6,7 @@ import time
from unittest.mock import call, patch from unittest.mock import call, patch
import aiohttp import aiohttp
from grpc import RpcError
import pytest import pytest
from homeassistant.components import conversation from homeassistant.components import conversation
@ -13,6 +14,7 @@ from homeassistant.components.google_assistant_sdk import DOMAIN
from homeassistant.components.google_assistant_sdk.const import SUPPORTED_LANGUAGE_CODES from homeassistant.components.google_assistant_sdk.const import SUPPORTED_LANGUAGE_CODES
from homeassistant.config_entries import ConfigEntryState from homeassistant.config_entries import ConfigEntryState
from homeassistant.core import Context, HomeAssistant from homeassistant.core import Context, HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.setup import async_setup_component from homeassistant.setup import async_setup_component
from homeassistant.util.dt import utcnow from homeassistant.util.dt import utcnow
@ -231,11 +233,34 @@ async def test_send_text_command_expired_token_refresh_failure(
{"command": "turn on tv"}, {"command": "turn on tv"},
blocking=True, blocking=True,
) )
await hass.async_block_till_done()
assert any(entry.async_get_active_flows(hass, {"reauth"})) == requires_reauth assert any(entry.async_get_active_flows(hass, {"reauth"})) == requires_reauth
async def test_send_text_command_grpc_error(
hass: HomeAssistant,
setup_integration: ComponentSetup,
) -> None:
"""Test service call send_text_command when RpcError is raised."""
await setup_integration()
command = "turn on home assistant unsupported device"
with (
patch(
"homeassistant.components.google_assistant_sdk.helpers.TextAssistant.assist",
side_effect=RpcError(),
) as mock_assist_call,
pytest.raises(HomeAssistantError),
):
await hass.services.async_call(
DOMAIN,
"send_text_command",
{"command": command},
blocking=True,
)
mock_assist_call.assert_called_once_with(command)
async def test_send_text_command_media_player( async def test_send_text_command_media_player(
hass: HomeAssistant, hass: HomeAssistant,
setup_integration: ComponentSetup, setup_integration: ComponentSetup,

View File

@ -2,6 +2,7 @@
from unittest.mock import call, patch from unittest.mock import call, patch
from grpc import RpcError
import pytest import pytest
from homeassistant.components import notify from homeassistant.components import notify
@ -9,6 +10,7 @@ from homeassistant.components.google_assistant_sdk import DOMAIN
from homeassistant.components.google_assistant_sdk.const import SUPPORTED_LANGUAGE_CODES from homeassistant.components.google_assistant_sdk.const import SUPPORTED_LANGUAGE_CODES
from homeassistant.components.google_assistant_sdk.notify import broadcast_commands from homeassistant.components.google_assistant_sdk.notify import broadcast_commands
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from .conftest import ComponentSetup, ExpectedCredentials from .conftest import ComponentSetup, ExpectedCredentials
@ -45,8 +47,8 @@ async def test_broadcast_no_targets(
notify.DOMAIN, notify.DOMAIN,
DOMAIN, DOMAIN,
{notify.ATTR_MESSAGE: message}, {notify.ATTR_MESSAGE: message},
blocking=True,
) )
await hass.async_block_till_done()
mock_text_assistant.assert_called_once_with( mock_text_assistant.assert_called_once_with(
ExpectedCredentials(), language_code, audio_out=False ExpectedCredentials(), language_code, audio_out=False
) )
@ -54,6 +56,30 @@ async def test_broadcast_no_targets(
mock_text_assistant.assert_has_calls([call().__enter__().assist(expected_command)]) mock_text_assistant.assert_has_calls([call().__enter__().assist(expected_command)])
async def test_broadcast_grpc_error(
hass: HomeAssistant,
setup_integration: ComponentSetup,
) -> None:
"""Test broadcast handling when RpcError is raised."""
await setup_integration()
with (
patch(
"homeassistant.components.google_assistant_sdk.helpers.TextAssistant.assist",
side_effect=RpcError(),
) as mock_assist_call,
pytest.raises(HomeAssistantError),
):
await hass.services.async_call(
notify.DOMAIN,
DOMAIN,
{notify.ATTR_MESSAGE: "Dinner is served"},
blocking=True,
)
mock_assist_call.assert_called_once_with("broadcast Dinner is served")
@pytest.mark.parametrize( @pytest.mark.parametrize(
("language_code", "message", "target", "expected_command"), ("language_code", "message", "target", "expected_command"),
[ [
@ -103,8 +129,8 @@ async def test_broadcast_one_target(
notify.DOMAIN, notify.DOMAIN,
DOMAIN, DOMAIN,
{notify.ATTR_MESSAGE: message, notify.ATTR_TARGET: [target]}, {notify.ATTR_MESSAGE: message, notify.ATTR_TARGET: [target]},
blocking=True,
) )
await hass.async_block_till_done()
mock_assist_call.assert_called_once_with(expected_command) mock_assist_call.assert_called_once_with(expected_command)
@ -127,8 +153,8 @@ async def test_broadcast_two_targets(
notify.DOMAIN, notify.DOMAIN,
DOMAIN, DOMAIN,
{notify.ATTR_MESSAGE: message, notify.ATTR_TARGET: [target1, target2]}, {notify.ATTR_MESSAGE: message, notify.ATTR_TARGET: [target1, target2]},
blocking=True,
) )
await hass.async_block_till_done()
mock_assist_call.assert_has_calls( mock_assist_call.assert_has_calls(
[call(expected_command1), call(expected_command2)] [call(expected_command1), call(expected_command2)]
) )
@ -148,8 +174,8 @@ async def test_broadcast_empty_message(
notify.DOMAIN, notify.DOMAIN,
DOMAIN, DOMAIN,
{notify.ATTR_MESSAGE: ""}, {notify.ATTR_MESSAGE: ""},
blocking=True,
) )
await hass.async_block_till_done()
mock_assist_call.assert_not_called() mock_assist_call.assert_not_called()