From 22893db6a351c05335d9913ce3f61d633e4d5ae7 Mon Sep 17 00:00:00 2001 From: RKS Date: Sun, 11 Oct 2026 13:50:45 -0400 Subject: [PATCH] fix(serialization-form): decode collection elements exactly once --- .../form_parse_node.py | 5 +- .../form/tests/unit/test_form_parse_node.py | 46 ++++++++++++++++++- 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/packages/serialization/form/kiota_serialization_form/form_parse_node.py b/packages/serialization/form/kiota_serialization_form/form_parse_node.py index d2f00747..13bcd4b8 100644 --- a/packages/serialization/form/kiota_serialization_form/form_parse_node.py +++ b/packages/serialization/form/kiota_serialization_form/form_parse_node.py @@ -137,8 +137,9 @@ def get_collection_of_primitive_values(self, primitive_type: type[T]) -> Optiona converter = converters.get(primitive_type) if converter is None: raise Exception(f"Encountered an unknown type during deserialization {primitive_type}") + values = self._raw_value.split(',') return [ - converter(unquote_plus(item)) for item in self._node.split(',') # type: ignore[misc] + converter(unquote_plus(item)) for item in values # type: ignore[misc] ] def get_collection_of_object_values(self, factory: ParsableFactory[U]) -> Optional[list[U]]: @@ -149,7 +150,7 @@ def get_collection_of_enum_values(self, enum_class: K) -> Optional[list[K]]: Returns: list[K]: The collection of enum values """ - values = self._node.split(',') + values = self._raw_value.split(',') if values: return list( map( 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 51b4faf5..d25269fc 100644 --- a/packages/serialization/form/tests/unit/test_form_parse_node.py +++ b/packages/serialization/form/tests/unit/test_form_parse_node.py @@ -1,10 +1,13 @@ import base64 from datetime import date, datetime, time, timedelta +from enum import Enum from uuid import UUID import pytest from kiota_serialization_form.form_parse_node import FormParseNode +from kiota_serialization_form.form_parse_node_factory import FormParseNodeFactory +from kiota_serialization_form.form_serialization_writer import FormSerializationWriter from ..helpers import TestEntity, TestEnum TEST_USER_FORM: str = ( @@ -29,6 +32,47 @@ "id=48d31887-5fad-4d73-a9f5-3c356e68a038" ) + +@pytest.mark.parametrize("encoded, expected", [ + ("a%2Bb", ["a+b"]), + ("%252F", ["%2F"]), + ("a%2Cb", ["a,b"]), + ("a,b", ["a", "b"]), + ("Megan+Bowen", ["Megan Bowen"]), + ("caf%C3%A9", ["café"]), +]) +def test_primitive_collection_decodes_each_encoded_value_once(encoded, expected): + assert FormParseNode(encoded).get_collection_of_primitive_values(str) == expected + + +def test_primitive_collection_round_trips_repeated_encoded_fields(): + values = ["a+b", "%2F", "a,b", "plain", "Megan Bowen", "café", "a+b"] + writer = FormSerializationWriter() + writer.write_collection_of_primitive_values("tags", values) + root = FormParseNodeFactory().get_root_parse_node( + "application/x-www-form-urlencoded", writer.get_serialized_content() + ) + node = root.get_child_node("tags") + assert node is not None + assert node.get_collection_of_primitive_values(str) == values + + +def test_enum_collection_round_trips_encoded_values(): + class EncodedEnum(Enum): + Plus = "a+b" + Percent = "%2F" + Comma = "a,b" + + values = [EncodedEnum.Plus, EncodedEnum.Percent, EncodedEnum.Comma, EncodedEnum.Plus] + writer = FormSerializationWriter() + writer.write_collection_of_enum_values("tags", values) + root = FormParseNodeFactory().get_root_parse_node( + "application/x-www-form-urlencoded", writer.get_serialized_content() + ) + node = root.get_child_node("tags") + assert node is not None + assert node.get_collection_of_enum_values(EncodedEnum) == values + def test_get_str_value(): parse_node = FormParseNode("Megan+Bowen") result = parse_node.get_str_value() @@ -186,4 +230,4 @@ def test_get_object_value_keeps_out_of_range_duration_as_string(value): def returns_default_if_child_node_does_not_exist(): parse_node = FormParseNode(TEST_USER_FORM) result = parse_node.get_child_node("nonExistent") - assert result == None \ No newline at end of file + assert result == None