Add tests for types and functions for type conversions in templates (#100807)

Co-authored-by: Robert Resch <robert@resch.dev>
pull/98968/head
Raman Gupta 2023-10-25 07:20:34 -04:00 committed by GitHub
parent 8ca5df6fcc
commit 35d18a9a3e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 209 additions and 0 deletions

View File

@ -1956,6 +1956,41 @@ def is_number(value):
return True
def _is_list(value: Any) -> bool:
"""Return whether a value is a list."""
return isinstance(value, list)
def _is_set(value: Any) -> bool:
"""Return whether a value is a set."""
return isinstance(value, set)
def _is_tuple(value: Any) -> bool:
"""Return whether a value is a tuple."""
return isinstance(value, tuple)
def _to_set(value: Any) -> set[Any]:
"""Convert value to set."""
return set(value)
def _to_tuple(value):
"""Convert value to tuple."""
return tuple(value)
def _is_datetime(value: Any) -> bool:
"""Return whether a value is a datetime."""
return isinstance(value, datetime)
def _is_string_like(value: Any) -> bool:
"""Return whether a value is a string or string like object."""
return isinstance(value, (str, bytes, bytearray))
def regex_match(value, find="", ignorecase=False):
"""Match value using regex."""
if not isinstance(value, str):
@ -2387,6 +2422,8 @@ class TemplateEnvironment(ImmutableSandboxedEnvironment):
self.globals["max"] = min_max_from_filter(self.filters["max"], "max")
self.globals["min"] = min_max_from_filter(self.filters["min"], "min")
self.globals["is_number"] = is_number
self.globals["set"] = _to_set
self.globals["tuple"] = _to_tuple
self.globals["int"] = forgiving_int
self.globals["pack"] = struct_pack
self.globals["unpack"] = struct_unpack
@ -2395,6 +2432,11 @@ class TemplateEnvironment(ImmutableSandboxedEnvironment):
self.globals["bool"] = forgiving_boolean
self.globals["version"] = version
self.tests["is_number"] = is_number
self.tests["list"] = _is_list
self.tests["set"] = _is_set
self.tests["tuple"] = _is_tuple
self.tests["datetime"] = _is_datetime
self.tests["string_like"] = _is_string_like
self.tests["match"] = regex_match
self.tests["search"] = regex_search
self.tests["contains"] = contains

View File

@ -7,6 +7,7 @@ import json
import logging
import math
import random
from types import MappingProxyType
from typing import Any
from unittest.mock import patch
@ -43,6 +44,7 @@ from homeassistant.helpers.json import json_dumps
from homeassistant.helpers.typing import TemplateVarsType
from homeassistant.setup import async_setup_component
import homeassistant.util.dt as dt_util
from homeassistant.util.read_only_dict import ReadOnlyDict
from homeassistant.util.unit_system import UnitSystem
from tests.common import MockConfigEntry, async_fire_time_changed
@ -475,6 +477,171 @@ def test_isnumber(hass: HomeAssistant, value, expected) -> None:
)
@pytest.mark.parametrize(
("value", "expected"),
[
([1, 2], True),
({1, 2}, False),
({"a": 1, "b": 2}, False),
(ReadOnlyDict({"a": 1, "b": 2}), False),
(MappingProxyType({"a": 1, "b": 2}), False),
("abc", False),
(b"abc", False),
((1, 2), False),
(datetime(2024, 1, 1, 0, 0, 0), False),
],
)
def test_is_list(hass: HomeAssistant, value: Any, expected: bool) -> None:
"""Test is list."""
assert (
template.Template("{{ value is list }}", hass).async_render({"value": value})
== expected
)
@pytest.mark.parametrize(
("value", "expected"),
[
([1, 2], False),
({1, 2}, True),
({"a": 1, "b": 2}, False),
(ReadOnlyDict({"a": 1, "b": 2}), False),
(MappingProxyType({"a": 1, "b": 2}), False),
("abc", False),
(b"abc", False),
((1, 2), False),
(datetime(2024, 1, 1, 0, 0, 0), False),
],
)
def test_is_set(hass: HomeAssistant, value: Any, expected: bool) -> None:
"""Test is set."""
assert (
template.Template("{{ value is set }}", hass).async_render({"value": value})
== expected
)
@pytest.mark.parametrize(
("value", "expected"),
[
([1, 2], False),
({1, 2}, False),
({"a": 1, "b": 2}, False),
(ReadOnlyDict({"a": 1, "b": 2}), False),
(MappingProxyType({"a": 1, "b": 2}), False),
("abc", False),
(b"abc", False),
((1, 2), True),
(datetime(2024, 1, 1, 0, 0, 0), False),
],
)
def test_is_tuple(hass: HomeAssistant, value: Any, expected: bool) -> None:
"""Test is tuple."""
assert (
template.Template("{{ value is tuple }}", hass).async_render({"value": value})
== expected
)
@pytest.mark.parametrize(
("value", "expected"),
[
([1, 2], {1, 2}),
({1, 2}, {1, 2}),
({"a": 1, "b": 2}, {"a", "b"}),
(ReadOnlyDict({"a": 1, "b": 2}), {"a", "b"}),
(MappingProxyType({"a": 1, "b": 2}), {"a", "b"}),
("abc", {"a", "b", "c"}),
(b"abc", {97, 98, 99}),
((1, 2), {1, 2}),
],
)
def test_set(hass: HomeAssistant, value: Any, expected: bool) -> None:
"""Test convert to set function."""
assert (
template.Template("{{ set(value) }}", hass).async_render({"value": value})
== expected
)
@pytest.mark.parametrize(
("value", "expected"),
[
([1, 2], (1, 2)),
({1, 2}, (1, 2)),
({"a": 1, "b": 2}, ("a", "b")),
(ReadOnlyDict({"a": 1, "b": 2}), ("a", "b")),
(MappingProxyType({"a": 1, "b": 2}), ("a", "b")),
("abc", ("a", "b", "c")),
(b"abc", (97, 98, 99)),
((1, 2), (1, 2)),
],
)
def test_tuple(hass: HomeAssistant, value: Any, expected: bool) -> None:
"""Test convert to tuple function."""
assert (
template.Template("{{ tuple(value) }}", hass).async_render({"value": value})
== expected
)
def test_converting_datetime_to_iterable(hass: HomeAssistant) -> None:
"""Test converting a datetime to an iterable raises an error."""
dt_ = datetime(2020, 1, 1, 0, 0, 0)
with pytest.raises(TemplateError):
template.Template("{{ tuple(value) }}", hass).async_render({"value": dt_})
with pytest.raises(TemplateError):
template.Template("{{ set(value) }}", hass).async_render({"value": dt_})
@pytest.mark.parametrize(
("value", "expected"),
[
([1, 2], False),
({1, 2}, False),
({"a": 1, "b": 2}, False),
(ReadOnlyDict({"a": 1, "b": 2}), False),
(MappingProxyType({"a": 1, "b": 2}), False),
("abc", False),
(b"abc", False),
((1, 2), False),
(datetime(2024, 1, 1, 0, 0, 0), True),
],
)
def test_is_datetime(hass: HomeAssistant, value, expected) -> None:
"""Test is datetime."""
assert (
template.Template("{{ value is datetime }}", hass).async_render(
{"value": value}
)
== expected
)
@pytest.mark.parametrize(
("value", "expected"),
[
([1, 2], False),
({1, 2}, False),
({"a": 1, "b": 2}, False),
(ReadOnlyDict({"a": 1, "b": 2}), False),
(MappingProxyType({"a": 1, "b": 2}), False),
("abc", True),
(b"abc", True),
((1, 2), False),
(datetime(2024, 1, 1, 0, 0, 0), False),
],
)
def test_is_string_like(hass: HomeAssistant, value, expected) -> None:
"""Test is string_like."""
assert (
template.Template("{{ value is string_like }}", hass).async_render(
{"value": value}
)
== expected
)
def test_rounding_value(hass: HomeAssistant) -> None:
"""Test rounding value."""
hass.states.async_set("sensor.temperature", 12.78)