Skip to content

Commit f11ca28

Browse files
google-genai-botcopybara-github
authored andcommitted
test: PartConverter: Improve test coverage
PiperOrigin-RevId: 861695993
1 parent a3f1763 commit f11ca28

File tree

1 file changed

+360
-0
lines changed

1 file changed

+360
-0
lines changed
Lines changed: 360 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,360 @@
1+
package com.google.adk.a2a.converters;
2+
3+
import static com.google.common.truth.Truth.assertThat;
4+
import static java.nio.charset.StandardCharsets.UTF_8;
5+
6+
import com.google.common.collect.ImmutableList;
7+
import com.google.common.collect.ImmutableMap;
8+
import com.google.genai.types.Blob;
9+
import com.google.genai.types.FileData;
10+
import com.google.genai.types.FunctionCall;
11+
import com.google.genai.types.FunctionResponse;
12+
import com.google.genai.types.Part;
13+
import io.a2a.spec.DataPart;
14+
import io.a2a.spec.FilePart;
15+
import io.a2a.spec.FileWithBytes;
16+
import io.a2a.spec.FileWithUri;
17+
import io.a2a.spec.TextPart;
18+
import java.util.Base64;
19+
import java.util.Optional;
20+
import org.junit.Test;
21+
import org.junit.runner.RunWith;
22+
import org.junit.runners.JUnit4;
23+
24+
@RunWith(JUnit4.class)
25+
public class PartConverterTest {
26+
27+
@Test
28+
public void toGenaiPart_withNullPart_returnsEmpty() {
29+
assertThat(PartConverter.toGenaiPart(null)).isEmpty();
30+
}
31+
32+
@Test
33+
public void toGenaiPart_withTextPart_returnsGenaiTextPart() {
34+
TextPart textPart = new TextPart("Hello");
35+
36+
Optional<Part> result = PartConverter.toGenaiPart(textPart);
37+
38+
assertThat(result).isPresent();
39+
assertThat(result.get().text()).hasValue("Hello");
40+
}
41+
42+
@Test
43+
public void toGenaiPart_withFilePartUri_returnsGenaiFilePart() {
44+
FilePart filePart = new FilePart(new FileWithUri("text/plain", "file.txt", "http://file.txt"));
45+
46+
Optional<Part> result = PartConverter.toGenaiPart(filePart);
47+
48+
assertThat(result).isPresent();
49+
assertThat(result.get().fileData()).isPresent();
50+
FileData fileData = result.get().fileData().get();
51+
assertThat(fileData.mimeType()).hasValue("text/plain");
52+
assertThat(fileData.fileUri()).hasValue("http://file.txt");
53+
}
54+
55+
@Test
56+
public void toGenaiPart_withFilePartBytes_returnsGenaiBlobPart() {
57+
byte[] bytes = "file content".getBytes(UTF_8);
58+
String encoded = Base64.getEncoder().encodeToString(bytes);
59+
FilePart filePart = new FilePart(new FileWithBytes("text/plain", "file.txt", encoded));
60+
61+
Optional<Part> result = PartConverter.toGenaiPart(filePart);
62+
63+
assertThat(result).isPresent();
64+
assertThat(result.get().inlineData()).isPresent();
65+
Blob blob = result.get().inlineData().get();
66+
assertThat(blob.mimeType()).hasValue("text/plain");
67+
assertThat(blob.data().get()).isEqualTo(bytes);
68+
}
69+
70+
@Test
71+
public void toGenaiPart_withFilePartBytes_handlesNullBytes() {
72+
FilePart filePart = new FilePart(new FileWithBytes("text/plain", "file.txt", null));
73+
assertThat(PartConverter.toGenaiPart(filePart)).isEmpty();
74+
}
75+
76+
@Test
77+
public void toGenaiPart_withFilePartBytes_handlesInvalidBase64() {
78+
FilePart filePart =
79+
new FilePart(new FileWithBytes("text/plain", "file.txt", "invalid-base64!"));
80+
assertThat(PartConverter.toGenaiPart(filePart)).isEmpty();
81+
}
82+
83+
@Test
84+
public void toGenaiPart_withDataPartFunctionCall_returnsGenaiFunctionCallPart() {
85+
ImmutableMap<String, Object> data =
86+
ImmutableMap.of("name", "func", "id", "1", "args", ImmutableMap.of());
87+
DataPart dataPart =
88+
new DataPart(
89+
data,
90+
ImmutableMap.of(
91+
PartConverter.A2A_DATA_PART_METADATA_TYPE_KEY,
92+
PartConverter.A2A_DATA_PART_METADATA_TYPE_FUNCTION_CALL));
93+
94+
Optional<Part> result = PartConverter.toGenaiPart(dataPart);
95+
96+
assertThat(result).isPresent();
97+
assertThat(result.get().functionCall()).isPresent();
98+
FunctionCall functionCall = result.get().functionCall().get();
99+
assertThat(functionCall.name()).hasValue("func");
100+
assertThat(functionCall.id()).hasValue("1");
101+
assertThat(functionCall.args()).hasValue(ImmutableMap.of());
102+
}
103+
104+
@Test
105+
public void toGenaiPart_withDataPartFunctionCallByNameAndArgs_returnsGenaiFunctionCallPart() {
106+
ImmutableMap<String, Object> data =
107+
ImmutableMap.of("name", "func", "id", "1", "args", ImmutableMap.of("param", "value"));
108+
DataPart dataPart = new DataPart(data, null);
109+
110+
Optional<Part> result = PartConverter.toGenaiPart(dataPart);
111+
112+
assertThat(result).isPresent();
113+
assertThat(result.get().functionCall()).isPresent();
114+
FunctionCall functionCall = result.get().functionCall().get();
115+
assertThat(functionCall.name()).hasValue("func");
116+
assertThat(functionCall.id()).hasValue("1");
117+
assertThat(functionCall.args()).hasValue(ImmutableMap.of("param", "value"));
118+
}
119+
120+
@Test
121+
public void toGenaiPart_withDataPartFunctionResponse_returnsGenaiFunctionResponsePart() {
122+
ImmutableMap<String, Object> data =
123+
ImmutableMap.of("name", "func", "id", "1", "response", ImmutableMap.of());
124+
DataPart dataPart =
125+
new DataPart(
126+
data,
127+
ImmutableMap.of(
128+
PartConverter.A2A_DATA_PART_METADATA_TYPE_KEY,
129+
PartConverter.A2A_DATA_PART_METADATA_TYPE_FUNCTION_RESPONSE));
130+
131+
Optional<Part> result = PartConverter.toGenaiPart(dataPart);
132+
133+
assertThat(result).isPresent();
134+
assertThat(result.get().functionResponse()).isPresent();
135+
FunctionResponse functionResponse = result.get().functionResponse().get();
136+
assertThat(functionResponse.name()).hasValue("func");
137+
assertThat(functionResponse.id()).hasValue("1");
138+
assertThat(functionResponse.response()).hasValue(ImmutableMap.of());
139+
}
140+
141+
@Test
142+
public void
143+
toGenaiPart_withDataPartFunctionResponseByNameAndResponse_returnsGenaiFunctionResponsePart() {
144+
ImmutableMap<String, Object> data =
145+
ImmutableMap.of("name", "func", "id", "1", "response", ImmutableMap.of("result", "value"));
146+
DataPart dataPart = new DataPart(data, null);
147+
148+
Optional<Part> result = PartConverter.toGenaiPart(dataPart);
149+
150+
assertThat(result).isPresent();
151+
assertThat(result.get().functionResponse()).isPresent();
152+
FunctionResponse functionResponse = result.get().functionResponse().get();
153+
assertThat(functionResponse.name()).hasValue("func");
154+
assertThat(functionResponse.id()).hasValue("1");
155+
assertThat(functionResponse.response()).hasValue(ImmutableMap.of("result", "value"));
156+
}
157+
158+
@Test
159+
public void toGenaiPart_withOtherDataPart_returnsGenaiTextPartWithJson() {
160+
ImmutableMap<String, Object> data = ImmutableMap.of("key", "value");
161+
DataPart dataPart = new DataPart(data, null);
162+
163+
Optional<Part> result = PartConverter.toGenaiPart(dataPart);
164+
165+
assertThat(result).isPresent();
166+
assertThat(result.get().text()).hasValue("{\"key\":\"value\"}");
167+
}
168+
169+
@Test
170+
public void toGenaiParts_convertsAllSupportedParts() {
171+
ImmutableList<io.a2a.spec.Part<?>> a2aParts =
172+
ImmutableList.of(
173+
new TextPart("text"),
174+
new FilePart(new FileWithUri("text/plain", "file.txt", "http://file.txt")));
175+
176+
ImmutableList<Part> result = PartConverter.toGenaiParts(a2aParts);
177+
178+
assertThat(result).hasSize(2);
179+
assertThat(result.get(0).text()).hasValue("text");
180+
assertThat(result.get(1).fileData()).isPresent();
181+
}
182+
183+
@Test
184+
public void convertGenaiPartToA2aPart_withNullPart_returnsEmpty() {
185+
assertThat(PartConverter.convertGenaiPartToA2aPart(null)).isEmpty();
186+
}
187+
188+
@Test
189+
public void convertGenaiPartToA2aPart_withTextPart_returnsEmpty() {
190+
Part part = Part.builder().text("text").build();
191+
assertThat(PartConverter.convertGenaiPartToA2aPart(part)).isEmpty();
192+
}
193+
194+
@Test
195+
public void convertGenaiPartToA2aPart_withFunctionCallPart_returnsDataPart() {
196+
Part part =
197+
Part.builder()
198+
.functionCall(
199+
FunctionCall.builder()
200+
.name("func")
201+
.id("1")
202+
.args(ImmutableMap.of("param", "value"))
203+
.build())
204+
.build();
205+
206+
Optional<DataPart> result = PartConverter.convertGenaiPartToA2aPart(part);
207+
208+
assertThat(result).isPresent();
209+
DataPart dataPart = result.get();
210+
assertThat(dataPart.getData())
211+
.containsExactly("name", "func", "id", "1", "args", ImmutableMap.of("param", "value"));
212+
assertThat(dataPart.getMetadata())
213+
.containsEntry(
214+
PartConverter.A2A_DATA_PART_METADATA_TYPE_KEY,
215+
PartConverter.A2A_DATA_PART_METADATA_TYPE_FUNCTION_CALL);
216+
}
217+
218+
@Test
219+
public void convertGenaiPartToA2aPart_withFunctionResponsePart_returnsDataPart() {
220+
Part part =
221+
Part.builder()
222+
.functionResponse(
223+
FunctionResponse.builder()
224+
.name("func")
225+
.id("1")
226+
.response(ImmutableMap.of("result", "value"))
227+
.build())
228+
.build();
229+
230+
Optional<DataPart> result = PartConverter.convertGenaiPartToA2aPart(part);
231+
232+
assertThat(result).isPresent();
233+
DataPart dataPart = result.get();
234+
assertThat(dataPart.getData())
235+
.containsExactly("name", "func", "id", "1", "response", ImmutableMap.of("result", "value"));
236+
assertThat(dataPart.getMetadata())
237+
.containsEntry(
238+
PartConverter.A2A_DATA_PART_METADATA_TYPE_KEY,
239+
PartConverter.A2A_DATA_PART_METADATA_TYPE_FUNCTION_RESPONSE);
240+
}
241+
242+
@Test
243+
public void fromGenaiPart_withNullPart_returnsEmpty() {
244+
assertThat(PartConverter.fromGenaiPart(null)).isEmpty();
245+
}
246+
247+
@Test
248+
public void fromGenaiPart_withTextPart_returnsTextPart() {
249+
Part part = Part.builder().text("text").build();
250+
251+
Optional<io.a2a.spec.Part<?>> result = PartConverter.fromGenaiPart(part);
252+
253+
assertThat(result).isPresent();
254+
assertThat(result.get()).isInstanceOf(TextPart.class);
255+
assertThat(((TextPart) result.get()).getText()).isEqualTo("text");
256+
}
257+
258+
@Test
259+
public void fromGenaiPart_withFileDataPart_returnsFilePartWithUri() {
260+
Part part =
261+
Part.builder()
262+
.fileData(FileData.builder().mimeType("text/plain").fileUri("http://file.txt").build())
263+
.build();
264+
265+
Optional<io.a2a.spec.Part<?>> result = PartConverter.fromGenaiPart(part);
266+
267+
assertThat(result).isPresent();
268+
assertThat(result.get()).isInstanceOf(FilePart.class);
269+
FilePart filePart = (FilePart) result.get();
270+
assertThat(filePart.getFile()).isInstanceOf(FileWithUri.class);
271+
FileWithUri fileWithUri = (FileWithUri) filePart.getFile();
272+
assertThat(fileWithUri.mimeType()).isEqualTo("text/plain");
273+
assertThat(fileWithUri.uri()).isEqualTo("http://file.txt");
274+
}
275+
276+
@Test
277+
public void fromGenaiPart_withInlineDataPart_returnsFilePartWithBytes() {
278+
byte[] bytes = "content".getBytes(UTF_8);
279+
Part part =
280+
Part.builder()
281+
.inlineData(Blob.builder().mimeType("text/plain").data(bytes).build())
282+
.build();
283+
284+
Optional<io.a2a.spec.Part<?>> result = PartConverter.fromGenaiPart(part);
285+
286+
assertThat(result).isPresent();
287+
assertThat(result.get()).isInstanceOf(FilePart.class);
288+
FilePart filePart = (FilePart) result.get();
289+
assertThat(filePart.getFile()).isInstanceOf(FileWithBytes.class);
290+
FileWithBytes fileWithBytes = (FileWithBytes) filePart.getFile();
291+
assertThat(fileWithBytes.mimeType()).isEqualTo("text/plain");
292+
assertThat(Base64.getDecoder().decode(fileWithBytes.bytes())).isEqualTo(bytes);
293+
}
294+
295+
@Test
296+
public void fromGenaiPart_withFunctionCallPart_returnsDataPart() {
297+
Part part =
298+
Part.builder()
299+
.functionCall(
300+
FunctionCall.builder().name("func").id("1").args(ImmutableMap.of()).build())
301+
.build();
302+
303+
Optional<io.a2a.spec.Part<?>> result = PartConverter.fromGenaiPart(part);
304+
305+
assertThat(result).isPresent();
306+
assertThat(result.get()).isInstanceOf(DataPart.class);
307+
DataPart dataPart = (DataPart) result.get();
308+
assertThat(dataPart.getData())
309+
.containsExactly("name", "func", "id", "1", "args", ImmutableMap.of());
310+
assertThat(dataPart.getMetadata())
311+
.containsEntry(
312+
PartConverter.A2A_DATA_PART_METADATA_TYPE_KEY,
313+
PartConverter.A2A_DATA_PART_METADATA_TYPE_FUNCTION_CALL);
314+
}
315+
316+
@Test
317+
public void fromGenaiPart_withFunctionResponsePart_returnsDataPart() {
318+
Part part =
319+
Part.builder()
320+
.functionResponse(
321+
FunctionResponse.builder().name("func").id("1").response(ImmutableMap.of()).build())
322+
.build();
323+
324+
Optional<io.a2a.spec.Part<?>> result = PartConverter.fromGenaiPart(part);
325+
326+
assertThat(result).isPresent();
327+
assertThat(result.get()).isInstanceOf(DataPart.class);
328+
DataPart dataPart = (DataPart) result.get();
329+
assertThat(dataPart.getData())
330+
.containsExactly("name", "func", "id", "1", "response", ImmutableMap.of());
331+
assertThat(dataPart.getMetadata())
332+
.containsEntry(
333+
PartConverter.A2A_DATA_PART_METADATA_TYPE_KEY,
334+
PartConverter.A2A_DATA_PART_METADATA_TYPE_FUNCTION_RESPONSE);
335+
}
336+
337+
@Test
338+
public void toGenaiPart_dataPartWithEmptyStringCoercedToEmptyMap() {
339+
ImmutableMap<String, Object> data = ImmutableMap.of("name", "func", "id", "1", "args", "");
340+
DataPart dataPart = new DataPart(data, null);
341+
342+
Optional<Part> result = PartConverter.toGenaiPart(dataPart);
343+
344+
assertThat(result).isPresent();
345+
assertThat(result.get().functionCall()).isPresent();
346+
assertThat(result.get().functionCall().get().args()).hasValue(ImmutableMap.of());
347+
}
348+
349+
@Test
350+
public void toGenaiPart_dataPartWithNonMapCoercedToMap() {
351+
ImmutableMap<String, Object> data = ImmutableMap.of("name", "func", "id", "1", "args", 123);
352+
DataPart dataPart = new DataPart(data, null);
353+
354+
Optional<Part> result = PartConverter.toGenaiPart(dataPart);
355+
356+
assertThat(result).isPresent();
357+
assertThat(result.get().functionCall()).isPresent();
358+
assertThat(result.get().functionCall().get().args()).hasValue(ImmutableMap.of("value", 123));
359+
}
360+
}

0 commit comments

Comments
 (0)