2021-02-21 13:54:36 +00:00
|
|
|
"""Test to verify that Home Assistant exceptions work."""
|
2024-03-08 15:36:11 +00:00
|
|
|
|
2022-11-23 19:30:32 +00:00
|
|
|
from __future__ import annotations
|
|
|
|
|
2024-03-16 21:56:48 +00:00
|
|
|
from typing import Any
|
|
|
|
from unittest.mock import patch
|
|
|
|
|
2022-11-23 19:30:32 +00:00
|
|
|
import pytest
|
|
|
|
|
2024-03-16 21:56:48 +00:00
|
|
|
from homeassistant.core import HomeAssistant
|
2021-02-21 13:54:36 +00:00
|
|
|
from homeassistant.exceptions import (
|
|
|
|
ConditionErrorContainer,
|
|
|
|
ConditionErrorIndex,
|
|
|
|
ConditionErrorMessage,
|
2024-03-16 21:56:48 +00:00
|
|
|
HomeAssistantError,
|
2022-11-23 19:30:32 +00:00
|
|
|
TemplateError,
|
2021-02-21 13:54:36 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
|
2022-11-23 19:30:32 +00:00
|
|
|
def test_conditionerror_format() -> None:
|
2021-02-21 13:54:36 +00:00
|
|
|
"""Test ConditionError stringifiers."""
|
|
|
|
error1 = ConditionErrorMessage("test", "A test error")
|
|
|
|
assert str(error1) == "In 'test' condition: A test error"
|
|
|
|
|
|
|
|
error2 = ConditionErrorMessage("test", "Another error")
|
|
|
|
assert str(error2) == "In 'test' condition: Another error"
|
|
|
|
|
|
|
|
error_pos1 = ConditionErrorIndex("box", index=0, total=2, error=error1)
|
|
|
|
assert (
|
|
|
|
str(error_pos1)
|
|
|
|
== """In 'box' (item 1 of 2):
|
|
|
|
In 'test' condition: A test error"""
|
|
|
|
)
|
|
|
|
|
|
|
|
error_pos2 = ConditionErrorIndex("box", index=1, total=2, error=error2)
|
|
|
|
assert (
|
|
|
|
str(error_pos2)
|
|
|
|
== """In 'box' (item 2 of 2):
|
|
|
|
In 'test' condition: Another error"""
|
|
|
|
)
|
|
|
|
|
|
|
|
error_container1 = ConditionErrorContainer("box", errors=[error_pos1, error_pos2])
|
|
|
|
assert (
|
|
|
|
str(error_container1)
|
|
|
|
== """In 'box' (item 1 of 2):
|
|
|
|
In 'test' condition: A test error
|
|
|
|
In 'box' (item 2 of 2):
|
|
|
|
In 'test' condition: Another error"""
|
|
|
|
)
|
|
|
|
|
|
|
|
error_pos3 = ConditionErrorIndex("box", index=0, total=1, error=error1)
|
|
|
|
assert (
|
|
|
|
str(error_pos3)
|
|
|
|
== """In 'box':
|
|
|
|
In 'test' condition: A test error"""
|
|
|
|
)
|
2022-11-23 19:30:32 +00:00
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize(
|
2023-02-15 13:09:50 +00:00
|
|
|
("arg", "expected"),
|
2022-11-23 19:30:32 +00:00
|
|
|
[
|
|
|
|
("message", "message"),
|
|
|
|
(Exception("message"), "Exception: message"),
|
|
|
|
],
|
|
|
|
)
|
|
|
|
def test_template_message(arg: str | Exception, expected: str) -> None:
|
|
|
|
"""Ensure we can create TemplateError."""
|
|
|
|
template_error = TemplateError(arg)
|
|
|
|
assert str(template_error) == expected
|
2024-03-16 21:56:48 +00:00
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
("exception_args", "exception_kwargs", "args_base_class", "message"),
|
|
|
|
[
|
|
|
|
((), {}, (), ""),
|
|
|
|
(("bla",), {}, ("bla",), "bla"),
|
|
|
|
((None,), {}, (None,), "None"),
|
|
|
|
((type_error_bla := TypeError("bla"),), {}, (type_error_bla,), "bla"),
|
|
|
|
(
|
|
|
|
(),
|
|
|
|
{"translation_domain": "test", "translation_key": "test"},
|
|
|
|
("test",),
|
|
|
|
"test",
|
|
|
|
),
|
|
|
|
(
|
|
|
|
(),
|
|
|
|
{"translation_domain": "test", "translation_key": "bla"},
|
|
|
|
("bla",),
|
|
|
|
"{bla} from cache",
|
|
|
|
),
|
|
|
|
(
|
|
|
|
(),
|
|
|
|
{
|
|
|
|
"translation_domain": "test",
|
|
|
|
"translation_key": "bla",
|
|
|
|
"translation_placeholders": {"bla": "Bla"},
|
|
|
|
},
|
|
|
|
("bla",),
|
|
|
|
"Bla from cache",
|
|
|
|
),
|
|
|
|
],
|
|
|
|
)
|
|
|
|
async def test_home_assistant_error(
|
|
|
|
hass: HomeAssistant,
|
2024-04-23 14:02:16 +00:00
|
|
|
exception_args: tuple[Any, ...],
|
2024-03-16 21:56:48 +00:00
|
|
|
exception_kwargs: dict[str, Any],
|
|
|
|
args_base_class: tuple[Any],
|
|
|
|
message: str,
|
|
|
|
) -> None:
|
|
|
|
"""Test edge cases with HomeAssistantError."""
|
|
|
|
|
|
|
|
with patch(
|
|
|
|
"homeassistant.helpers.translation.async_get_cached_translations",
|
|
|
|
return_value={"component.test.exceptions.bla.message": "{bla} from cache"},
|
|
|
|
):
|
|
|
|
with pytest.raises(HomeAssistantError) as exc:
|
|
|
|
raise HomeAssistantError(*exception_args, **exception_kwargs)
|
|
|
|
assert exc.value.args == args_base_class
|
|
|
|
assert str(exc.value) == message
|
|
|
|
# Get string of exception again from the cache
|
|
|
|
assert str(exc.value) == message
|
2024-03-21 22:12:25 +00:00
|
|
|
|
|
|
|
|
|
|
|
async def test_home_assistant_error_subclass(hass: HomeAssistant) -> None:
|
|
|
|
"""Test __str__ method on an HomeAssistantError subclass."""
|
|
|
|
|
|
|
|
class _SubExceptionDefault(HomeAssistantError):
|
|
|
|
"""Sub class, default with generated message."""
|
|
|
|
|
|
|
|
class _SubExceptionConstructor(HomeAssistantError):
|
|
|
|
"""Sub class with constructor, no generated message."""
|
|
|
|
|
|
|
|
def __init__(
|
|
|
|
self,
|
|
|
|
custom_arg: str,
|
|
|
|
translation_domain: str | None = None,
|
|
|
|
translation_key: str | None = None,
|
|
|
|
translation_placeholders: dict[str, str] | None = None,
|
|
|
|
) -> None:
|
|
|
|
super().__init__(
|
|
|
|
translation_domain=translation_domain,
|
|
|
|
translation_key=translation_key,
|
|
|
|
translation_placeholders=translation_placeholders,
|
|
|
|
)
|
|
|
|
self.custom_arg = custom_arg
|
|
|
|
|
|
|
|
class _SubExceptionConstructorGenerate(HomeAssistantError):
|
|
|
|
"""Sub class with constructor, with generated message."""
|
|
|
|
|
|
|
|
generate_message: bool = True
|
|
|
|
|
|
|
|
def __init__(
|
|
|
|
self,
|
|
|
|
custom_arg: str,
|
|
|
|
translation_domain: str | None = None,
|
|
|
|
translation_key: str | None = None,
|
|
|
|
translation_placeholders: dict[str, str] | None = None,
|
|
|
|
) -> None:
|
|
|
|
super().__init__(
|
|
|
|
translation_domain=translation_domain,
|
|
|
|
translation_key=translation_key,
|
|
|
|
translation_placeholders=translation_placeholders,
|
|
|
|
)
|
|
|
|
self.custom_arg = custom_arg
|
|
|
|
|
|
|
|
class _SubExceptionGenerate(HomeAssistantError):
|
|
|
|
"""Sub class, no generated message."""
|
|
|
|
|
|
|
|
generate_message: bool = True
|
|
|
|
|
|
|
|
class _SubClassWithExceptionGroup(HomeAssistantError, BaseExceptionGroup):
|
|
|
|
"""Sub class with exception group, no generated message."""
|
|
|
|
|
|
|
|
class _SubClassWithExceptionGroupGenerate(HomeAssistantError, BaseExceptionGroup):
|
|
|
|
"""Sub class with exception group and generated message."""
|
|
|
|
|
|
|
|
generate_message: bool = True
|
|
|
|
|
|
|
|
with patch(
|
|
|
|
"homeassistant.helpers.translation.async_get_cached_translations",
|
|
|
|
return_value={"component.test.exceptions.bla.message": "{bla} from cache"},
|
|
|
|
):
|
|
|
|
# A subclass without a constructor generates a message by default
|
|
|
|
with pytest.raises(HomeAssistantError) as exc:
|
|
|
|
raise _SubExceptionDefault(
|
|
|
|
translation_domain="test",
|
|
|
|
translation_key="bla",
|
|
|
|
translation_placeholders={"bla": "Bla"},
|
|
|
|
)
|
|
|
|
assert str(exc.value) == "Bla from cache"
|
|
|
|
|
|
|
|
# A subclass with a constructor that does not parse `args` to the super class
|
|
|
|
with pytest.raises(HomeAssistantError) as exc:
|
|
|
|
raise _SubExceptionConstructor(
|
|
|
|
"custom arg",
|
|
|
|
translation_domain="test",
|
|
|
|
translation_key="bla",
|
|
|
|
translation_placeholders={"bla": "Bla"},
|
|
|
|
)
|
2024-03-22 12:39:36 +00:00
|
|
|
assert str(exc.value) == "Bla from cache"
|
2024-03-21 22:12:25 +00:00
|
|
|
with pytest.raises(HomeAssistantError) as exc:
|
|
|
|
raise _SubExceptionConstructor(
|
|
|
|
"custom arg",
|
|
|
|
)
|
2024-03-22 12:39:36 +00:00
|
|
|
assert str(exc.value) == ""
|
2024-03-21 22:12:25 +00:00
|
|
|
|
|
|
|
# A subclass with a constructor that generates the message
|
|
|
|
with pytest.raises(HomeAssistantError) as exc:
|
|
|
|
raise _SubExceptionConstructorGenerate(
|
|
|
|
"custom arg",
|
|
|
|
translation_domain="test",
|
|
|
|
translation_key="bla",
|
|
|
|
translation_placeholders={"bla": "Bla"},
|
|
|
|
)
|
|
|
|
assert str(exc.value) == "Bla from cache"
|
|
|
|
|
|
|
|
# A subclass without overridden constructors and passed args
|
|
|
|
# defaults to the passed args
|
|
|
|
with pytest.raises(HomeAssistantError) as exc:
|
|
|
|
raise _SubExceptionDefault(
|
|
|
|
ValueError("wrong value"),
|
|
|
|
translation_domain="test",
|
|
|
|
translation_key="bla",
|
|
|
|
translation_placeholders={"bla": "Bla"},
|
|
|
|
)
|
|
|
|
assert str(exc.value) == "wrong value"
|
|
|
|
|
|
|
|
# A subclass without overridden constructors and passed args
|
|
|
|
# and generate_message = True, generates a message
|
|
|
|
with pytest.raises(HomeAssistantError) as exc:
|
|
|
|
raise _SubExceptionGenerate(
|
|
|
|
ValueError("wrong value"),
|
|
|
|
translation_domain="test",
|
|
|
|
translation_key="bla",
|
|
|
|
translation_placeholders={"bla": "Bla"},
|
|
|
|
)
|
|
|
|
assert str(exc.value) == "Bla from cache"
|
|
|
|
|
2024-03-22 12:39:36 +00:00
|
|
|
# A subclass with an ExceptionGroup subclass requires a message to be passed.
|
2024-03-21 22:12:25 +00:00
|
|
|
# As we pass args, we will not generate the message.
|
|
|
|
# The __str__ constructor defaults to that of the super class.
|
|
|
|
with pytest.raises(HomeAssistantError) as exc:
|
|
|
|
raise _SubClassWithExceptionGroup(
|
|
|
|
"group message",
|
|
|
|
[ValueError("wrong value"), TypeError("wrong type")],
|
|
|
|
translation_domain="test",
|
|
|
|
translation_key="bla",
|
|
|
|
translation_placeholders={"bla": "Bla"},
|
|
|
|
)
|
|
|
|
assert str(exc.value) == "group message (2 sub-exceptions)"
|
|
|
|
with pytest.raises(HomeAssistantError) as exc:
|
|
|
|
raise _SubClassWithExceptionGroup(
|
|
|
|
"group message",
|
|
|
|
[ValueError("wrong value"), TypeError("wrong type")],
|
|
|
|
)
|
|
|
|
assert str(exc.value) == "group message (2 sub-exceptions)"
|
|
|
|
|
2024-03-22 12:39:36 +00:00
|
|
|
# A subclass with an ExceptionGroup subclass requires a message to be passed.
|
2024-03-21 22:12:25 +00:00
|
|
|
# The `generate_message` flag is set.`
|
|
|
|
# The __str__ constructor will return the generated message.
|
|
|
|
with pytest.raises(HomeAssistantError) as exc:
|
|
|
|
raise _SubClassWithExceptionGroupGenerate(
|
|
|
|
"group message",
|
|
|
|
[ValueError("wrong value"), TypeError("wrong type")],
|
|
|
|
translation_domain="test",
|
|
|
|
translation_key="bla",
|
|
|
|
translation_placeholders={"bla": "Bla"},
|
|
|
|
)
|
|
|
|
assert str(exc.value) == "Bla from cache"
|