From 0ecc2c30e1c17a052cd3fc3dc810840510b5cc14 Mon Sep 17 00:00:00 2001 From: HardMax71 Date: Sat, 3 Oct 2026 15:04:15 +0200 Subject: [PATCH] fix(abstractions): raise ValueError for out-of-range timedelta strings parse_timedelta_string and parse_timedelta_from_iso_format let OverflowError through when a duration is past timedelta.max, such as "50001140846:00021" or "P1000000000D". The callers only catch ValueError, so a typed timedelta property in JSON, form additional data and the JSON writer's string check raised OverflowError instead of treating the value as invalid. Both functions now raise ValueError from the OverflowError. Fixes #480. --- .../kiota_abstractions/date_utils.py | 22 ++++++++++++------- .../abstractions/tests/test_date_utils.py | 12 ++++++++++ .../form/tests/unit/test_form_parse_node.py | 7 ++++++ .../json/tests/unit/test_json_parse_node.py | 6 +++++ .../unit/test_json_serialization_writer.py | 7 ++++++ 5 files changed, 46 insertions(+), 8 deletions(-) diff --git a/packages/abstractions/kiota_abstractions/date_utils.py b/packages/abstractions/kiota_abstractions/date_utils.py index 05e9572d..eb0877fd 100644 --- a/packages/abstractions/kiota_abstractions/date_utils.py +++ b/packages/abstractions/kiota_abstractions/date_utils.py @@ -48,13 +48,16 @@ def parse_timedelta_from_iso_format(text: str) -> timedelta: raise ValueError("Combining weeks with other date/time parts is not supported") _total_days = (years * 365) + (months * 30) + days - return timedelta( - days=_total_days, - hours=hours, - minutes=minutes, - seconds=seconds, - weeks=weeks, - ) + try: + return timedelta( + days=_total_days, + hours=hours, + minutes=minutes, + seconds=seconds, + weeks=weeks, + ) + except OverflowError as exc: + raise ValueError(f"ISO8601 duration out of range: {text}") from exc _TIMEDELTA_PATTERN = re.compile(r"^(?P\d+):(?P\d+)(?::(?P\d+))?$") @@ -73,7 +76,10 @@ def parse_timedelta_string(text: str) -> timedelta: hours = int(m.group("hours")) minutes = int(m.group("minutes")) seconds = int(m.group("seconds") or 0) - return timedelta(hours=hours, minutes=minutes, seconds=seconds) + try: + return timedelta(hours=hours, minutes=minutes, seconds=seconds) + except OverflowError as overflow: + raise ValueError(f"Timedelta string out of range: {text}") from overflow _TIME_REPLACEMENT_PATTERN = re.compile(r'(\d)([.,])(\d+)') diff --git a/packages/abstractions/tests/test_date_utils.py b/packages/abstractions/tests/test_date_utils.py index 640a2393..df9fc495 100644 --- a/packages/abstractions/tests/test_date_utils.py +++ b/packages/abstractions/tests/test_date_utils.py @@ -84,3 +84,15 @@ def test_parse_timedelta_string_valid(text:str, expected_hours:int): result = parse_timedelta_string(text) assert result.days == 0 assert result.seconds == expected_hours * 3600 + + +@pytest.mark.parametrize("text", ["P1000000000D", "PT24000000000H", "P" + "9" * 400 + "D"]) +def test_parse_timedelta_from_iso_format_out_of_range(text: str): + with pytest.raises(ValueError, match="out of range"): + parse_timedelta_from_iso_format(text) + + +@pytest.mark.parametrize("text", ["50001140846:00021", "P1000000000D"]) +def test_parse_timedelta_string_out_of_range(text: str): + with pytest.raises(ValueError): + parse_timedelta_string(text) diff --git a/packages/serialization/form/tests/unit/test_form_parse_node.py b/packages/serialization/form/tests/unit/test_form_parse_node.py index 94dff9c4..51b4faf5 100644 --- a/packages/serialization/form/tests/unit/test_form_parse_node.py +++ b/packages/serialization/form/tests/unit/test_form_parse_node.py @@ -175,6 +175,13 @@ def test_get_object_value(): assert result.additional_data["mobilePhone"] assert result.additional_data["accountEnabled"] == "true" assert result.additional_data["jobTitle"] == "Auditor" + + +@pytest.mark.parametrize("value", ["50001140846:00021", "P1000000000D"]) +def test_get_object_value_keeps_out_of_range_duration_as_string(value): + parse_node = FormParseNode(f"jobTitle=Auditor&extra={value}") + result = parse_node.get_object_value(TestEntity) + assert result.additional_data["extra"] == value def returns_default_if_child_node_does_not_exist(): parse_node = FormParseNode(TEST_USER_FORM) diff --git a/packages/serialization/json/tests/unit/test_json_parse_node.py b/packages/serialization/json/tests/unit/test_json_parse_node.py index 4bc54713..c5d59dc8 100644 --- a/packages/serialization/json/tests/unit/test_json_parse_node.py +++ b/packages/serialization/json/tests/unit/test_json_parse_node.py @@ -105,6 +105,12 @@ def test_get_timedelta_value(): assert str(result) == "0:00:30" +@pytest.mark.parametrize("value", ["50001140846:00021", "P1000000000D"]) +def test_get_timedelta_value_out_of_range(value): + parse_node = JsonParseNode(value) + assert parse_node.get_timedelta_value() is None + + def test_get_collection_of_primitive_values(): parse_node = JsonParseNode([12.1, 12.2, 12.3, 12.4, 12.5]) result = parse_node.get_collection_of_primitive_values(float) diff --git a/packages/serialization/json/tests/unit/test_json_serialization_writer.py b/packages/serialization/json/tests/unit/test_json_serialization_writer.py index 207136c0..d115c1bc 100644 --- a/packages/serialization/json/tests/unit/test_json_serialization_writer.py +++ b/packages/serialization/json/tests/unit/test_json_serialization_writer.py @@ -150,6 +150,13 @@ def test_write_timedelta_value_invalid_string(): assert "Invalid timedelta string value found for property diff" in str(excinfo.value) +@pytest.mark.parametrize("value", ["50001140846:00021", "P1000000000D"]) +def test_write_timedelta_value_out_of_range_string(value): + json_serialization_writer = JsonSerializationWriter() + with pytest.raises(ValueError, match="Invalid timedelta string value found for property diff"): + json_serialization_writer.write_timedelta_value("diff", value) + + def test_write_date_value(): json_serialization_writer = JsonSerializationWriter() json_serialization_writer.write_date_value("birthday", date(2000,9,4))