Send localization info on websocket_api script errors (#104638)
* Send localization info on script errors * Use connection exception hander * Keep HomeAssistantError is unknown_error * Move specific exception handling
This commit is contained in:
parent
7dbaf40f48
commit
efd330f182
6 changed files with 106 additions and 3 deletions
|
@ -17,6 +17,7 @@ from .const import ( # noqa: F401
|
||||||
ERR_INVALID_FORMAT,
|
ERR_INVALID_FORMAT,
|
||||||
ERR_NOT_FOUND,
|
ERR_NOT_FOUND,
|
||||||
ERR_NOT_SUPPORTED,
|
ERR_NOT_SUPPORTED,
|
||||||
|
ERR_SERVICE_VALIDATION_ERROR,
|
||||||
ERR_TEMPLATE_ERROR,
|
ERR_TEMPLATE_ERROR,
|
||||||
ERR_TIMEOUT,
|
ERR_TIMEOUT,
|
||||||
ERR_UNAUTHORIZED,
|
ERR_UNAUTHORIZED,
|
||||||
|
|
|
@ -778,7 +778,25 @@ async def handle_execute_script(
|
||||||
|
|
||||||
context = connection.context(msg)
|
context = connection.context(msg)
|
||||||
script_obj = Script(hass, script_config, f"{const.DOMAIN} script", const.DOMAIN)
|
script_obj = Script(hass, script_config, f"{const.DOMAIN} script", const.DOMAIN)
|
||||||
script_result = await script_obj.async_run(msg.get("variables"), context=context)
|
try:
|
||||||
|
script_result = await script_obj.async_run(
|
||||||
|
msg.get("variables"), context=context
|
||||||
|
)
|
||||||
|
except ServiceValidationError as err:
|
||||||
|
connection.logger.error(err)
|
||||||
|
connection.logger.debug("", exc_info=err)
|
||||||
|
connection.send_error(
|
||||||
|
msg["id"],
|
||||||
|
const.ERR_SERVICE_VALIDATION_ERROR,
|
||||||
|
str(err),
|
||||||
|
translation_domain=err.translation_domain,
|
||||||
|
translation_key=err.translation_key,
|
||||||
|
translation_placeholders=err.translation_placeholders,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
except Exception as exc: # pylint: disable=broad-except
|
||||||
|
connection.async_handle_exception(msg, exc)
|
||||||
|
return
|
||||||
connection.send_result(
|
connection.send_result(
|
||||||
msg["id"],
|
msg["id"],
|
||||||
{
|
{
|
||||||
|
|
|
@ -255,7 +255,10 @@ class ActiveConnection:
|
||||||
log_handler = self.logger.error
|
log_handler = self.logger.error
|
||||||
|
|
||||||
code = const.ERR_UNKNOWN_ERROR
|
code = const.ERR_UNKNOWN_ERROR
|
||||||
err_message = None
|
err_message: str | None = None
|
||||||
|
translation_domain: str | None = None
|
||||||
|
translation_key: str | None = None
|
||||||
|
translation_placeholders: dict[str, Any] | None = None
|
||||||
|
|
||||||
if isinstance(err, Unauthorized):
|
if isinstance(err, Unauthorized):
|
||||||
code = const.ERR_UNAUTHORIZED
|
code = const.ERR_UNAUTHORIZED
|
||||||
|
@ -268,6 +271,10 @@ class ActiveConnection:
|
||||||
err_message = "Timeout"
|
err_message = "Timeout"
|
||||||
elif isinstance(err, HomeAssistantError):
|
elif isinstance(err, HomeAssistantError):
|
||||||
err_message = str(err)
|
err_message = str(err)
|
||||||
|
code = const.ERR_UNKNOWN_ERROR
|
||||||
|
translation_domain = err.translation_domain
|
||||||
|
translation_key = err.translation_key
|
||||||
|
translation_placeholders = err.translation_placeholders
|
||||||
|
|
||||||
# This if-check matches all other errors but also matches errors which
|
# This if-check matches all other errors but also matches errors which
|
||||||
# result in an empty message. In that case we will also log the stack
|
# result in an empty message. In that case we will also log the stack
|
||||||
|
@ -276,7 +283,16 @@ class ActiveConnection:
|
||||||
err_message = "Unknown error"
|
err_message = "Unknown error"
|
||||||
log_handler = self.logger.exception
|
log_handler = self.logger.exception
|
||||||
|
|
||||||
self.send_message(messages.error_message(msg["id"], code, err_message))
|
self.send_message(
|
||||||
|
messages.error_message(
|
||||||
|
msg["id"],
|
||||||
|
code,
|
||||||
|
err_message,
|
||||||
|
translation_domain=translation_domain,
|
||||||
|
translation_key=translation_key,
|
||||||
|
translation_placeholders=translation_placeholders,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
if code:
|
if code:
|
||||||
err_message += f" ({code})"
|
err_message += f" ({code})"
|
||||||
|
|
|
@ -297,6 +297,7 @@ def async_mock_service(
|
||||||
schema: vol.Schema | None = None,
|
schema: vol.Schema | None = None,
|
||||||
response: ServiceResponse = None,
|
response: ServiceResponse = None,
|
||||||
supports_response: SupportsResponse | None = None,
|
supports_response: SupportsResponse | None = None,
|
||||||
|
raise_exception: Exception | None = None,
|
||||||
) -> list[ServiceCall]:
|
) -> list[ServiceCall]:
|
||||||
"""Set up a fake service & return a calls log list to this service."""
|
"""Set up a fake service & return a calls log list to this service."""
|
||||||
calls = []
|
calls = []
|
||||||
|
@ -305,6 +306,8 @@ def async_mock_service(
|
||||||
def mock_service_log(call): # pylint: disable=unnecessary-lambda
|
def mock_service_log(call): # pylint: disable=unnecessary-lambda
|
||||||
"""Mock service call."""
|
"""Mock service call."""
|
||||||
calls.append(call)
|
calls.append(call)
|
||||||
|
if raise_exception is not None:
|
||||||
|
raise raise_exception
|
||||||
return response
|
return response
|
||||||
|
|
||||||
if supports_response is None:
|
if supports_response is None:
|
||||||
|
|
|
@ -2317,6 +2317,65 @@ async def test_execute_script(
|
||||||
assert call.context.as_dict() == msg_var["result"]["context"]
|
assert call.context.as_dict() == msg_var["result"]["context"]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("raise_exception", "err_code"),
|
||||||
|
[
|
||||||
|
(
|
||||||
|
HomeAssistantError(
|
||||||
|
"Some error",
|
||||||
|
translation_domain="test",
|
||||||
|
translation_key="test_error",
|
||||||
|
translation_placeholders={"option": "bla"},
|
||||||
|
),
|
||||||
|
"unknown_error",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
ServiceValidationError(
|
||||||
|
"Some error",
|
||||||
|
translation_domain="test",
|
||||||
|
translation_key="test_error",
|
||||||
|
translation_placeholders={"option": "bla"},
|
||||||
|
),
|
||||||
|
"service_validation_error",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_execute_script_err_localization(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
websocket_client: MockHAClientWebSocket,
|
||||||
|
raise_exception: HomeAssistantError,
|
||||||
|
err_code: str,
|
||||||
|
) -> None:
|
||||||
|
"""Test testing a condition."""
|
||||||
|
async_mock_service(
|
||||||
|
hass, "domain_test", "test_service", raise_exception=raise_exception
|
||||||
|
)
|
||||||
|
|
||||||
|
await websocket_client.send_json(
|
||||||
|
{
|
||||||
|
"id": 5,
|
||||||
|
"type": "execute_script",
|
||||||
|
"sequence": [
|
||||||
|
{
|
||||||
|
"service": "domain_test.test_service",
|
||||||
|
"data": {"hello": "world"},
|
||||||
|
},
|
||||||
|
{"stop": "done", "response_variable": "service_result"},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
msg = await websocket_client.receive_json()
|
||||||
|
assert msg["id"] == 5
|
||||||
|
assert msg["type"] == const.TYPE_RESULT
|
||||||
|
assert msg["success"] is False
|
||||||
|
assert msg["error"]["code"] == err_code
|
||||||
|
assert msg["error"]["message"] == "Some error"
|
||||||
|
assert msg["error"]["translation_key"] == "test_error"
|
||||||
|
assert msg["error"]["translation_domain"] == "test"
|
||||||
|
assert msg["error"]["translation_placeholders"] == {"option": "bla"}
|
||||||
|
|
||||||
|
|
||||||
async def test_execute_script_complex_response(
|
async def test_execute_script_complex_response(
|
||||||
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
|
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
|
@ -43,6 +43,12 @@ from tests.common import MockUser
|
||||||
"Failed to do X",
|
"Failed to do X",
|
||||||
"Error handling message: Failed to do X (unknown_error) Mock User from 127.0.0.42 (Browser)",
|
"Error handling message: Failed to do X (unknown_error) Mock User from 127.0.0.42 (Browser)",
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
exceptions.ServiceValidationError("Failed to do X"),
|
||||||
|
websocket_api.ERR_UNKNOWN_ERROR,
|
||||||
|
"Failed to do X",
|
||||||
|
"Error handling message: Failed to do X (unknown_error) Mock User from 127.0.0.42 (Browser)",
|
||||||
|
),
|
||||||
(
|
(
|
||||||
ValueError("Really bad"),
|
ValueError("Really bad"),
|
||||||
websocket_api.ERR_UNKNOWN_ERROR,
|
websocket_api.ERR_UNKNOWN_ERROR,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue