1717from unittest .mock import patch
1818
1919from a2a import types as a2a_types
20+ from google .adk .a2a .converters .part_converter import A2A_DATA_PART_END_TAG
2021from google .adk .a2a .converters .part_converter import A2A_DATA_PART_METADATA_TYPE_CODE_EXECUTION_RESULT
2122from google .adk .a2a .converters .part_converter import A2A_DATA_PART_METADATA_TYPE_EXECUTABLE_CODE
2223from google .adk .a2a .converters .part_converter import A2A_DATA_PART_METADATA_TYPE_FUNCTION_CALL
2324from google .adk .a2a .converters .part_converter import A2A_DATA_PART_METADATA_TYPE_FUNCTION_RESPONSE
2425from google .adk .a2a .converters .part_converter import A2A_DATA_PART_METADATA_TYPE_KEY
26+ from google .adk .a2a .converters .part_converter import A2A_DATA_PART_START_TAG
27+ from google .adk .a2a .converters .part_converter import A2A_DATA_PART_TEXT_MIME_TYPE
2528from google .adk .a2a .converters .part_converter import convert_a2a_part_to_genai_part
2629from google .adk .a2a .converters .part_converter import convert_genai_part_to_a2a_part
2730from google .adk .a2a .converters .utils import _get_adk_metadata_key
@@ -154,12 +157,43 @@ def test_convert_data_part_function_response(self):
154157 "data" : [1 , 2 , 3 ],
155158 }
156159
157- def test_convert_data_part_without_special_metadata (self ):
158- """Test conversion of A2A DataPart without special metadata to text."""
160+ @pytest .mark .parametrize (
161+ "test_name, data, metadata" ,
162+ [
163+ (
164+ "without_special_metadata" ,
165+ {"key" : "value" , "number" : 123 },
166+ {"other" : "metadata" },
167+ ),
168+ (
169+ "no_metadata" ,
170+ {"key" : "value" , "array" : [1 , 2 , 3 ]},
171+ None ,
172+ ),
173+ (
174+ "complex_data" ,
175+ {
176+ "nested" : {
177+ "array" : [1 , 2 , {"inner" : "value" }],
178+ "boolean" : True ,
179+ "null_value" : None ,
180+ },
181+ "unicode" : "Hello 世界 🌍" ,
182+ },
183+ None ,
184+ ),
185+ (
186+ "empty_metadata" ,
187+ {"key" : "value" },
188+ {},
189+ ),
190+ ],
191+ )
192+ def test_convert_data_part_to_inline_data (self , test_name , data , metadata ):
193+ """Test conversion of A2A DataPart to GenAI inline_data Part."""
159194 # Arrange
160- data = {"key" : "value" , "number" : 123 }
161195 a2a_part = a2a_types .Part (
162- root = a2a_types .DataPart (data = data , metadata = { "other" : " metadata" } )
196+ root = a2a_types .DataPart (data = data , metadata = metadata )
163197 )
164198
165199 # Act
@@ -168,21 +202,17 @@ def test_convert_data_part_without_special_metadata(self):
168202 # Assert
169203 assert result is not None
170204 assert isinstance (result , genai_types .Part )
171- assert result .text == json .dumps (data )
172-
173- def test_convert_data_part_no_metadata (self ):
174- """Test conversion of A2A DataPart with no metadata to text."""
175- # Arrange
176- data = {"key" : "value" , "array" : [1 , 2 , 3 ]}
177- a2a_part = a2a_types .Part (root = a2a_types .DataPart (data = data ))
178-
179- # Act
180- result = convert_a2a_part_to_genai_part (a2a_part )
181-
182- # Assert
183- assert result is not None
184- assert isinstance (result , genai_types .Part )
185- assert result .text == json .dumps (data )
205+ assert result .inline_data is not None
206+ assert result .inline_data .mime_type == A2A_DATA_PART_TEXT_MIME_TYPE
207+ assert result .inline_data .data .startswith (A2A_DATA_PART_START_TAG )
208+ assert result .inline_data .data .endswith (A2A_DATA_PART_END_TAG )
209+ converted_data_part = a2a_types .DataPart .model_validate_json (
210+ result .inline_data .data [
211+ len (A2A_DATA_PART_START_TAG ) : - len (A2A_DATA_PART_END_TAG )
212+ ]
213+ )
214+ assert converted_data_part .data == data
215+ assert converted_data_part .metadata == metadata
186216
187217 def test_convert_unsupported_file_type (self ):
188218 """Test handling of unsupported file types."""
@@ -325,6 +355,32 @@ def test_convert_inline_data_part_with_video_metadata(self):
325355 assert result .root .metadata is not None
326356 assert _get_adk_metadata_key ("video_metadata" ) in result .root .metadata
327357
358+ def test_convert_inline_data_part_to_data_part (self ):
359+ """Test conversion of GenAI inline_data Part to A2A DataPart."""
360+ # Arrange
361+ data = {"key" : "value" }
362+ metadata = {"meta" : "data" }
363+ a2a_part_to_convert = a2a_types .DataPart (data = data , metadata = metadata )
364+ json_data = a2a_part_to_convert .model_dump_json (
365+ by_alias = True , exclude_none = True
366+ ).encode ("utf-8" )
367+ genai_part = genai_types .Part (
368+ inline_data = genai_types .Blob (
369+ data = A2A_DATA_PART_START_TAG + json_data + A2A_DATA_PART_END_TAG ,
370+ mime_type = A2A_DATA_PART_TEXT_MIME_TYPE ,
371+ )
372+ )
373+
374+ # Act
375+ result = convert_genai_part_to_a2a_part (genai_part )
376+
377+ # Assert
378+ assert result is not None
379+ assert isinstance (result , a2a_types .Part )
380+ assert isinstance (result .root , a2a_types .DataPart )
381+ assert result .root .data == data
382+ assert result .root .metadata == metadata
383+
328384 def test_convert_function_call_part (self ):
329385 """Test conversion of GenAI function_call Part to A2A Part."""
330386 # Arrange
@@ -596,6 +652,47 @@ def test_executable_code_round_trip(self):
596652 )
597653 assert result_genai_part .executable_code .code == executable_code .code
598654
655+ def test_data_part_round_trip (self ):
656+ """Test round-trip conversion for data parts."""
657+ # Arrange
658+ data = {"key" : "value" }
659+ metadata = {"meta" : "data" }
660+ a2a_part = a2a_types .Part (
661+ root = a2a_types .DataPart (data = data , metadata = metadata )
662+ )
663+
664+ # Act
665+ genai_part = convert_a2a_part_to_genai_part (a2a_part )
666+ result_a2a_part = convert_genai_part_to_a2a_part (genai_part )
667+
668+ # Assert
669+ assert result_a2a_part is not None
670+ assert isinstance (result_a2a_part , a2a_types .Part )
671+ assert isinstance (result_a2a_part .root , a2a_types .DataPart )
672+ assert result_a2a_part .root .data == data
673+ assert result_a2a_part .root .metadata == metadata
674+
675+ def test_data_part_with_mime_type_metadata_round_trip (self ):
676+ """Test round-trip conversion for data parts with 'mime_type' in metadata."""
677+ # Arrange
678+ data = {"content" : "some data" }
679+ metadata = {"meta" : "data" , "mime_type" : "application/json" }
680+ a2a_part = a2a_types .Part (
681+ root = a2a_types .DataPart (data = data , metadata = metadata )
682+ )
683+
684+ # Act
685+ genai_part = convert_a2a_part_to_genai_part (a2a_part )
686+ result_a2a_part = convert_genai_part_to_a2a_part (genai_part )
687+
688+ # Assert
689+ assert result_a2a_part is not None
690+ assert isinstance (result_a2a_part , a2a_types .Part )
691+ assert isinstance (result_a2a_part .root , a2a_types .DataPart )
692+ assert result_a2a_part .root .data == data
693+ # The 'mime_type' key in the metadata should be preserved as is
694+ assert result_a2a_part .root .metadata == metadata
695+
599696
600697class TestEdgeCases :
601698 """Test cases for edge cases and error conditions."""
@@ -612,6 +709,37 @@ def test_empty_text_part(self):
612709 assert result is not None
613710 assert result .text == ""
614711
712+ def test_genai_inline_data_with_mimetype_to_a2a (self ):
713+ """Test conversion of GenAI inline_data with 'mimeType' in DataPart metadata to A2A.
714+
715+ This tests if 'mimeType' in metadata of a DataPart wrapped in inline_data
716+ is correctly handled, ensuring the key casing is preserved.
717+ """
718+ # Arrange
719+ data = {"key" : "value" }
720+ metadata = {"adk_type" : "some_type" , "mimeType" : "image/png" }
721+ a2a_part_inner = a2a_types .DataPart (data = data , metadata = metadata )
722+ json_data = a2a_part_inner .model_dump_json (
723+ by_alias = True , exclude_none = True
724+ ).encode ("utf-8" )
725+ genai_part = genai_types .Part (
726+ inline_data = genai_types .Blob (
727+ data = A2A_DATA_PART_START_TAG + json_data + A2A_DATA_PART_END_TAG ,
728+ mime_type = A2A_DATA_PART_TEXT_MIME_TYPE ,
729+ )
730+ )
731+
732+ # Act
733+ result = convert_genai_part_to_a2a_part (genai_part )
734+
735+ # Assert
736+ assert result is not None
737+ assert isinstance (result , a2a_types .Part )
738+ assert isinstance (result .root , a2a_types .DataPart )
739+ assert result .root .data == data
740+ # The key casing should be preserved from the JSON
741+ assert result .root .metadata == metadata
742+
615743 def test_none_input_a2a_to_genai (self ):
616744 """Test handling of None input for A2A to GenAI conversion."""
617745 # This test depends on how the function handles None input
@@ -626,39 +754,6 @@ def test_none_input_genai_to_a2a(self):
626754 with pytest .raises (AttributeError ):
627755 convert_genai_part_to_a2a_part (None )
628756
629- def test_data_part_with_complex_data (self ):
630- """Test conversion of DataPart with complex nested data."""
631- # Arrange
632- complex_data = {
633- "nested" : {
634- "array" : [1 , 2 , {"inner" : "value" }],
635- "boolean" : True ,
636- "null_value" : None ,
637- },
638- "unicode" : "Hello 世界 🌍" ,
639- }
640- a2a_part = a2a_types .Part (root = a2a_types .DataPart (data = complex_data ))
641-
642- # Act
643- result = convert_a2a_part_to_genai_part (a2a_part )
644-
645- # Assert
646- assert result is not None
647- assert result .text == json .dumps (complex_data )
648-
649- def test_data_part_with_empty_metadata (self ):
650- """Test conversion of DataPart with empty metadata dict."""
651- # Arrange
652- data = {"key" : "value" }
653- a2a_part = a2a_types .Part (root = a2a_types .DataPart (data = data , metadata = {}))
654-
655- # Act
656- result = convert_a2a_part_to_genai_part (a2a_part )
657-
658- # Assert
659- assert result is not None
660- assert result .text == json .dumps (data )
661-
662757
663758class TestNewConstants :
664759 """Test cases for new constants and functionality."""
0 commit comments