diff --git a/packages/serialization/multipart/kiota_serialization_multipart/multipart_serialization_writer.py b/packages/serialization/multipart/kiota_serialization_multipart/multipart_serialization_writer.py index b12b7763..b2bfdaf8 100644 --- a/packages/serialization/multipart/kiota_serialization_multipart/multipart_serialization_writer.py +++ b/packages/serialization/multipart/kiota_serialization_multipart/multipart_serialization_writer.py @@ -34,10 +34,14 @@ def __init__(self) -> None: def write_str_value(self, key: Optional[str], value: Optional[str]) -> None: """Writes the specified string value to the stream with an optional given key. + If value is None, no output is written to avoid emitting bare header names. + Args: key (Optional[str]): The key to be used for the written value. May be null. - value (Optional[str]): The string value to be written. + value (Optional[str]): The string value to be written. If None, the value is omitted. """ + if value is None: + return if key: self.writer.write(key) if value: @@ -161,7 +165,10 @@ def write_collection_of_object_values( raise NotImplementedError() def write_object_value( - self, key: Optional[str], value: Optional[U], *additional_values_to_merge: Optional[U] + self, + key: Optional[str], + value: Optional[U], + *additional_values_to_merge: Optional[U], ) -> None: """Writes the specified model object to the stream with an optional given key. Args: @@ -170,7 +177,8 @@ def write_object_value( additional_values_to_merge (tuple[Parsable]): The additional values to merge to the main value when serializing an intersection wrapper. """ - temp_writer = self._create_new_writer() + if value is None: + return if not isinstance(value, MultipartBody): raise ValueError(f"Expected a MultipartBody instance but got {type(value)}") @@ -260,13 +268,13 @@ def on_start_object_serialization( """ self._on_start_object_serialization = value - def _serialize_value(self, temp_writer: MultipartSerializationWriter, value: U) -> None: + def _serialize_value(self, writer: MultipartSerializationWriter, value: U) -> None: if on_before := self.on_before_object_serialization: on_before(value) if on_start := self.on_start_object_serialization: on_start(value, self) - value.serialize(temp_writer) + value.serialize(writer) def _create_new_writer(self) -> SerializationWriter: writer = MultipartSerializationWriter() diff --git a/packages/serialization/multipart/tests/unit/test_multipart_serialization_writer.py b/packages/serialization/multipart/tests/unit/test_multipart_serialization_writer.py index d6593aa5..498f67c7 100644 --- a/packages/serialization/multipart/tests/unit/test_multipart_serialization_writer.py +++ b/packages/serialization/multipart/tests/unit/test_multipart_serialization_writer.py @@ -1,9 +1,13 @@ -from uuid import UUID +from datetime import date, datetime, time, timedelta from unittest.mock import Mock +from uuid import UUID + import pytest -from datetime import datetime, timedelta, date, time -from kiota_serialization_multipart.multipart_serialization_writer import MultipartSerializationWriter +from kiota_serialization_multipart.multipart_serialization_writer import ( + MultipartSerializationWriter, +) + from ..helpers import TestEntity, TestEnum @@ -33,7 +37,8 @@ def test_not_implemented(): writer.write_collection_of_object_values("users", [TestEntity()]) with pytest.raises(NotImplementedError): writer.write_collection_of_primitive_values("numbers", [1, 2.0, "3"]) - + + def test_write_string_value(): serialization_writer = MultipartSerializationWriter() serialization_writer.write_str_value("message", "Hello world") @@ -41,76 +46,131 @@ def test_write_string_value(): content_string = content.decode('utf-8') assert content_string == "message: Hello world\r\n" + def test_write_bytes_value_bytes(): serialization_writer = MultipartSerializationWriter() serialization_writer.write_bytes_value("", b"Hello world") content = serialization_writer.get_serialized_content() content_string = content.decode('utf-8') assert content_string == "Hello world" - + + def test_write_object_value_raises_exception_on_parseable_object(): serialization_writer = MultipartSerializationWriter() with pytest.raises(ValueError) as excinfo: serialization_writer.write_object_value("user", TestEntity()) assert "Expected a MultipartBody instance but got" in str(excinfo.value) - -def test_write_object_value(user_1, mock_request_adapter, mock_serialization_writer_factory, mock_multipart_body): - mock_request_adapter.get_serialization_writer_factory = Mock(return_value=mock_serialization_writer_factory) + + +def test_write_object_value( + user_1, mock_request_adapter, mock_serialization_writer_factory, mock_multipart_body +): + mock_request_adapter.get_serialization_writer_factory = Mock( + return_value=mock_serialization_writer_factory + ) mock_multipart_body.request_adapter = mock_request_adapter mock_multipart_body.add_or_replace_part("test user", "application/json", user_1) mock_multipart_body.add_or_replace_part("img", "application/octet-stream", b"Hello world") - + serialization_writer = MultipartSerializationWriter() serialization_writer.write_object_value("", mock_multipart_body, None) content = serialization_writer.get_serialized_content() content_string = content.decode('utf-8') - assert content_string == f'--{mock_multipart_body.boundary}'+'\r\nContent-Type: application/json\r\nContent-Disposition: form-data; name="test user"\r\n\r\n{"id": "eac79bd3-fd08-4abf-9df2-2565cf3a3845", "workDuration": "2:00:00", "birthDay": "2017-09-04", "startWorkTime": "00:00:00", "createdDateTime": "2022-01-27T12:59:45", "businessPhones": ["+1 412 555 0109"], "mobilePhone": null, "accountEnabled": false, "jobTitle": "Auditor", "manager": {"id": "eac79bd3-fd08-4abf-9df2-2565cf3a3845"}}\r\n'+f'--{mock_multipart_body.boundary}'+'\r\nContent-Type: application/octet-stream\r\nContent-Disposition: form-data; name="img"\r\n\r\nHello world\r\n'+f'--{mock_multipart_body.boundary}--\r\n' - -def test_write_object_value_inverted(user_1, mock_request_adapter, mock_serialization_writer_factory, mock_multipart_body): - mock_request_adapter.get_serialization_writer_factory = Mock(return_value=mock_serialization_writer_factory) + assert content_string == f'--{mock_multipart_body.boundary}' + '\r\nContent-Type: application/json\r\nContent-Disposition: form-data; name="test user"\r\n\r\n{"id": "eac79bd3-fd08-4abf-9df2-2565cf3a3845", "workDuration": "2:00:00", "birthDay": "2017-09-04", "startWorkTime": "00:00:00", "createdDateTime": "2022-01-27T12:59:45", "businessPhones": ["+1 412 555 0109"], "mobilePhone": null, "accountEnabled": false, "jobTitle": "Auditor", "manager": {"id": "eac79bd3-fd08-4abf-9df2-2565cf3a3845"}}\r\n' + f'--{mock_multipart_body.boundary}' + '\r\nContent-Type: application/octet-stream\r\nContent-Disposition: form-data; name="img"\r\n\r\nHello world\r\n' + f'--{mock_multipart_body.boundary}--\r\n' + + +def test_write_object_value_inverted( + user_1, mock_request_adapter, mock_serialization_writer_factory, mock_multipart_body +): + mock_request_adapter.get_serialization_writer_factory = Mock( + return_value=mock_serialization_writer_factory + ) mock_multipart_body.request_adapter = mock_request_adapter mock_multipart_body.add_or_replace_part("img", "application/octet-stream", b"Hello world") mock_multipart_body.add_or_replace_part("test user", "application/json", user_1) - + serialization_writer = MultipartSerializationWriter() serialization_writer.write_object_value("", mock_multipart_body, None) content = serialization_writer.get_serialized_content() content_string = content.decode('utf-8') - assert content_string == f'--{mock_multipart_body.boundary}'+'\r\nContent-Type: application/octet-stream\r\nContent-Disposition: form-data; name="img"\r\n\r\nHello world\r\n'+f'--{mock_multipart_body.boundary}'+'\r\nContent-Type: application/json\r\nContent-Disposition: form-data; name="test user"\r\n\r\n{"id": "eac79bd3-fd08-4abf-9df2-2565cf3a3845", "workDuration": "2:00:00", "birthDay": "2017-09-04", "startWorkTime": "00:00:00", "createdDateTime": "2022-01-27T12:59:45", "businessPhones": ["+1 412 555 0109"], "mobilePhone": null, "accountEnabled": false, "jobTitle": "Auditor", "manager": {"id": "eac79bd3-fd08-4abf-9df2-2565cf3a3845"}}\r\n'+f'--{mock_multipart_body.boundary}--\r\n' + assert content_string == f'--{mock_multipart_body.boundary}' + '\r\nContent-Type: application/octet-stream\r\nContent-Disposition: form-data; name="img"\r\n\r\nHello world\r\n' + f'--{mock_multipart_body.boundary}' + '\r\nContent-Type: application/json\r\nContent-Disposition: form-data; name="test user"\r\n\r\n{"id": "eac79bd3-fd08-4abf-9df2-2565cf3a3845", "workDuration": "2:00:00", "birthDay": "2017-09-04", "startWorkTime": "00:00:00", "createdDateTime": "2022-01-27T12:59:45", "businessPhones": ["+1 412 555 0109"], "mobilePhone": null, "accountEnabled": false, "jobTitle": "Auditor", "manager": {"id": "eac79bd3-fd08-4abf-9df2-2565cf3a3845"}}\r\n' + f'--{mock_multipart_body.boundary}--\r\n' + -def test_write_object_value_with_filename(user_1, mock_request_adapter, mock_serialization_writer_factory, mock_multipart_body): - mock_request_adapter.get_serialization_writer_factory = Mock(return_value=mock_serialization_writer_factory) +def test_write_object_value_with_filename( + user_1, mock_request_adapter, mock_serialization_writer_factory, mock_multipart_body +): + mock_request_adapter.get_serialization_writer_factory = Mock( + return_value=mock_serialization_writer_factory + ) mock_multipart_body.request_adapter = mock_request_adapter mock_multipart_body.add_or_replace_part("test user", "application/json", user_1) - mock_multipart_body.add_or_replace_part("file", "application/octet-stream", b"Hello world", "file.txt") + mock_multipart_body.add_or_replace_part( + "file", "application/octet-stream", b"Hello world", "file.txt" + ) + + serialization_writer = MultipartSerializationWriter() + serialization_writer.write_object_value("", mock_multipart_body, None) + content = serialization_writer.get_serialized_content() + content_string = content.decode('utf-8') + assert content_string == f'--{mock_multipart_body.boundary}' + '\r\nContent-Type: application/json\r\nContent-Disposition: form-data; name="test user"\r\n\r\n{"id": "eac79bd3-fd08-4abf-9df2-2565cf3a3845", "workDuration": "2:00:00", "birthDay": "2017-09-04", "startWorkTime": "00:00:00", "createdDateTime": "2022-01-27T12:59:45", "businessPhones": ["+1 412 555 0109"], "mobilePhone": null, "accountEnabled": false, "jobTitle": "Auditor", "manager": {"id": "eac79bd3-fd08-4abf-9df2-2565cf3a3845"}}\r\n' + f'--{mock_multipart_body.boundary}' + '\r\nContent-Type: application/octet-stream\r\nContent-Disposition: form-data; name="file"; filename="file.txt"\r\n\r\nHello world\r\n' + f'--{mock_multipart_body.boundary}--\r\n' + + +def test_write_object_value_with_string_part( + mock_request_adapter, mock_serialization_writer_factory, mock_multipart_body +): + mock_request_adapter.get_serialization_writer_factory = Mock( + return_value=mock_serialization_writer_factory + ) + mock_multipart_body.request_adapter = mock_request_adapter + mock_multipart_body.add_or_replace_part("message", "text/plain", "Hello world") serialization_writer = MultipartSerializationWriter() serialization_writer.write_object_value("", mock_multipart_body, None) content = serialization_writer.get_serialized_content() content_string = content.decode('utf-8') - assert content_string == f'--{mock_multipart_body.boundary}'+'\r\nContent-Type: application/json\r\nContent-Disposition: form-data; name="test user"\r\n\r\n{"id": "eac79bd3-fd08-4abf-9df2-2565cf3a3845", "workDuration": "2:00:00", "birthDay": "2017-09-04", "startWorkTime": "00:00:00", "createdDateTime": "2022-01-27T12:59:45", "businessPhones": ["+1 412 555 0109"], "mobilePhone": null, "accountEnabled": false, "jobTitle": "Auditor", "manager": {"id": "eac79bd3-fd08-4abf-9df2-2565cf3a3845"}}\r\n'+f'--{mock_multipart_body.boundary}'+'\r\nContent-Type: application/octet-stream\r\nContent-Disposition: form-data; name="file"; filename="file.txt"\r\n\r\nHello world\r\n'+f'--{mock_multipart_body.boundary}--\r\n' + assert content_string == f'--{mock_multipart_body.boundary}' + '\r\nContent-Type: text/plain\r\nContent-Disposition: form-data; name="message"\r\n\r\nHello world\r\n' + f'--{mock_multipart_body.boundary}--\r\n' -def test_write_object_value_with_string_part(mock_request_adapter, mock_serialization_writer_factory, mock_multipart_body): - mock_request_adapter.get_serialization_writer_factory = Mock(return_value=mock_serialization_writer_factory) - mock_multipart_body.request_adapter = mock_request_adapter - mock_multipart_body.add_or_replace_part("message", "text/plain", "Hello world") +def test_write_object_value_with_multiple_string_parts( + mock_request_adapter, mock_serialization_writer_factory, mock_multipart_body +): + mock_request_adapter.get_serialization_writer_factory = Mock( + return_value=mock_serialization_writer_factory + ) + mock_multipart_body.request_adapter = mock_request_adapter + mock_multipart_body.add_or_replace_part("message1", "text/plain", "Hello world") + mock_multipart_body.add_or_replace_part("message2", "text/plain", "Goodbye world") + + serialization_writer = MultipartSerializationWriter() + serialization_writer.write_object_value("", mock_multipart_body, None) + content = serialization_writer.get_serialized_content() + content_string = content.decode('utf-8') + assert content_string == f'--{mock_multipart_body.boundary}' + '\r\nContent-Type: text/plain\r\nContent-Disposition: form-data; name="message1"\r\n\r\nHello world\r\n' + f'--{mock_multipart_body.boundary}' + '\r\nContent-Type: text/plain\r\nContent-Disposition: form-data; name="message2"\r\n\r\nGoodbye world\r\n' + f'--{mock_multipart_body.boundary}--\r\n' - serialization_writer = MultipartSerializationWriter() - serialization_writer.write_object_value("", mock_multipart_body, None) - content = serialization_writer.get_serialized_content() - content_string = content.decode('utf-8') - assert content_string == f'--{mock_multipart_body.boundary}'+'\r\nContent-Type: text/plain\r\nContent-Disposition: form-data; name="message"\r\n\r\nHello world\r\n'+f'--{mock_multipart_body.boundary}--\r\n' -def test_write_object_value_with_multiple_string_parts(mock_request_adapter, mock_serialization_writer_factory, mock_multipart_body): - mock_request_adapter.get_serialization_writer_factory = Mock(return_value=mock_serialization_writer_factory) - mock_multipart_body.request_adapter = mock_request_adapter - mock_multipart_body.add_or_replace_part("message1", "text/plain", "Hello world") - mock_multipart_body.add_or_replace_part("message2", "text/plain", "Goodbye world") +def test_write_str_value_omits_nil_value(): + serialization_writer = MultipartSerializationWriter() + serialization_writer.write_str_value("Content-Type", None) + content = serialization_writer.get_serialized_content() + assert content == b"" - serialization_writer = MultipartSerializationWriter() - serialization_writer.write_object_value("", mock_multipart_body, None) - content = serialization_writer.get_serialized_content() - content_string = content.decode('utf-8') - assert content_string == f'--{mock_multipart_body.boundary}'+'\r\nContent-Type: text/plain\r\nContent-Disposition: form-data; name="message1"\r\n\r\nHello world\r\n'+f'--{mock_multipart_body.boundary}'+'\r\nContent-Type: text/plain\r\nContent-Disposition: form-data; name="message2"\r\n\r\nGoodbye world\r\n'+f'--{mock_multipart_body.boundary}--\r\n' \ No newline at end of file +def test_write_str_value_omits_both_nil(): + serialization_writer = MultipartSerializationWriter() + serialization_writer.write_str_value(None, None) + content = serialization_writer.get_serialized_content() + assert content == b"" + + +def test_write_object_value_handles_none_gracefully(): + serialization_writer = MultipartSerializationWriter() + serialization_writer.write_object_value("", None) + content = serialization_writer.get_serialized_content() + assert content == b"" + + +def test_write_object_value_none_primary_with_none_additional_values_noop(): + serialization_writer = MultipartSerializationWriter() + serialization_writer.write_object_value("", None, None, None) + assert len(serialization_writer.get_serialized_content()) == 0 +