fix: validate rollover time with ascii-bounded pattern
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
"""Options flow for NestQuest post-setup settings."""
|
"""Options flow for NestQuest post-setup settings."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import re
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
@@ -18,20 +19,13 @@ from .const import (
|
|||||||
|
|
||||||
MIN_HORIZON_DAYS = 1
|
MIN_HORIZON_DAYS = 1
|
||||||
MIN_PANEL_IDLE_TIMEOUT = 30
|
MIN_PANEL_IDLE_TIMEOUT = 30
|
||||||
_TIME_PART_LENGTH = 2
|
|
||||||
|
_TIME_PATTERN = re.compile(r"(?:[01]\d|2[0-3]):[0-5]\d")
|
||||||
|
|
||||||
|
|
||||||
def _parse_time(value: str) -> bool:
|
def _parse_time(value: str) -> bool:
|
||||||
"""Return True when value is a valid 24-hour HH:MM string."""
|
"""Return True when value is a valid 24-hour HH:MM string."""
|
||||||
parts = value.split(":")
|
return _TIME_PATTERN.fullmatch(value) is not None
|
||||||
if len(parts) != 2:
|
|
||||||
return False
|
|
||||||
hours, minutes = parts
|
|
||||||
if len(hours) != _TIME_PART_LENGTH or len(minutes) != _TIME_PART_LENGTH:
|
|
||||||
return False
|
|
||||||
if not hours.isdigit() or not minutes.isdigit():
|
|
||||||
return False
|
|
||||||
return int(hours) <= 23 and int(minutes) <= 59
|
|
||||||
|
|
||||||
|
|
||||||
def _async_validate(user_input: dict[str, Any]) -> dict[str, str]:
|
def _async_validate(user_input: dict[str, Any]) -> dict[str, str]:
|
||||||
|
|||||||
@@ -375,6 +375,16 @@ def test_options_flow_rejects_malformed_time_out_of_range() -> None:
|
|||||||
assert result["errors"] == {CONF_DAY_ROLLOVER_TIME: "invalid_time"}
|
assert result["errors"] == {CONF_DAY_ROLLOVER_TIME: "invalid_time"}
|
||||||
|
|
||||||
|
|
||||||
|
def test_options_flow_rejects_pasted_unicode_superscript_time() -> None:
|
||||||
|
"""Pasted Unicode digits like '²²:²²' yield a field-level error, no exception."""
|
||||||
|
flow = _make_flow(_make_entry())
|
||||||
|
result = _run(
|
||||||
|
flow.async_step_init({**VALID_INPUT, CONF_DAY_ROLLOVER_TIME: "²²:²²"})
|
||||||
|
)
|
||||||
|
assert result["type"] == "form"
|
||||||
|
assert result["errors"] == {CONF_DAY_ROLLOVER_TIME: "invalid_time"}
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("bad_timeout", ["abc", 0, -10, 10])
|
@pytest.mark.parametrize("bad_timeout", ["abc", 0, -10, 10])
|
||||||
def test_options_flow_rejects_invalid_idle_timeout(bad_timeout) -> None:
|
def test_options_flow_rejects_invalid_idle_timeout(bad_timeout) -> None:
|
||||||
"""Non-numeric or too-low idle timeouts yield a field-level error."""
|
"""Non-numeric or too-low idle timeouts yield a field-level error."""
|
||||||
|
|||||||
Reference in New Issue
Block a user