Skip to content

Commit 8538569

Browse files
authored
test: update A2AClientTest with sending FilePart and DataPart (#130)
# Description for issue task #20 - [x] Follow the [`CONTRIBUTING` Guide](../CONTRIBUTING.md). - [x] Make your Pull Request title in the <https://www.conventionalcommits.org/> specification. - Important Prefixes for [release-please](https://github.com/googleapis/release-please): - `fix:` which represents bug fixes, and correlates to a [SemVer](https://semver.org/) patch. - `feat:` represents a new feature, and correlates to a SemVer minor. - `feat!:`, or `fix!:`, `refactor!:`, etc., which represent a breaking change (indicated by the `!`) and will result in a SemVer major. - [x] Ensure the tests pass - [ ] Appropriate READMEs were updated (if necessary) Fixes #<issue_number_goes_here> 🦕
1 parent 125e111 commit 8538569

File tree

2 files changed

+374
-0
lines changed

2 files changed

+374
-0
lines changed

core/src/test/java/io/a2a/client/A2AClientTest.java

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@
1414
import static io.a2a.client.JsonMessages.SEND_MESSAGE_TEST_RESPONSE;
1515
import static io.a2a.client.JsonMessages.SEND_MESSAGE_TEST_RESPONSE_WITH_MESSAGE_RESPONSE;
1616
import static io.a2a.client.JsonMessages.SEND_MESSAGE_WITH_ERROR_TEST_REQUEST;
17+
import static io.a2a.client.JsonMessages.SEND_MESSAGE_WITH_FILE_PART_TEST_REQUEST;
18+
import static io.a2a.client.JsonMessages.SEND_MESSAGE_WITH_FILE_PART_TEST_RESPONSE;
19+
import static io.a2a.client.JsonMessages.SEND_MESSAGE_WITH_DATA_PART_TEST_REQUEST;
20+
import static io.a2a.client.JsonMessages.SEND_MESSAGE_WITH_DATA_PART_TEST_RESPONSE;
21+
import static io.a2a.client.JsonMessages.SEND_MESSAGE_WITH_MIXED_PARTS_TEST_REQUEST;
22+
import static io.a2a.client.JsonMessages.SEND_MESSAGE_WITH_MIXED_PARTS_TEST_RESPONSE;
1723
import static io.a2a.client.JsonMessages.SET_TASK_PUSH_NOTIFICATION_CONFIG_TEST_REQUEST;
1824
import static io.a2a.client.JsonMessages.SET_TASK_PUSH_NOTIFICATION_CONFIG_TEST_RESPONSE;
1925
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -35,6 +41,7 @@
3541
import io.a2a.spec.AgentSkill;
3642
import io.a2a.spec.Artifact;
3743
import io.a2a.spec.CancelTaskResponse;
44+
import io.a2a.spec.DataPart;
3845
import io.a2a.spec.FileContent;
3946
import io.a2a.spec.FilePart;
4047
import io.a2a.spec.FileWithBytes;
@@ -508,4 +515,183 @@ public void testA2AClientGetAuthenticatedExtendedAgentCard() throws Exception {
508515
assertTrue(agentCard.supportsAuthenticatedExtendedCard());
509516
assertEquals("https://georoute-agent.example.com/icon.png", agentCard.iconUrl());
510517
}
518+
519+
@Test
520+
public void testA2AClientSendMessageWithFilePart() throws Exception {
521+
this.server.when(
522+
request()
523+
.withMethod("POST")
524+
.withPath("/")
525+
.withBody(JsonBody.json(SEND_MESSAGE_WITH_FILE_PART_TEST_REQUEST, MatchType.STRICT))
526+
527+
)
528+
.respond(
529+
response()
530+
.withStatusCode(200)
531+
.withBody(SEND_MESSAGE_WITH_FILE_PART_TEST_RESPONSE)
532+
);
533+
534+
A2AClient client = new A2AClient("http://localhost:4001");
535+
Message message = new Message.Builder()
536+
.role(Message.Role.USER)
537+
.parts(List.of(
538+
new TextPart("analyze this image"),
539+
new FilePart(new FileWithUri("image/jpeg", null, "file:///path/to/image.jpg"))
540+
))
541+
.contextId("context-1234")
542+
.messageId("message-1234-with-file")
543+
.build();
544+
MessageSendConfiguration configuration = new MessageSendConfiguration.Builder()
545+
.acceptedOutputModes(List.of("text"))
546+
.blocking(true)
547+
.build();
548+
MessageSendParams params = new MessageSendParams.Builder()
549+
.message(message)
550+
.configuration(configuration)
551+
.build();
552+
553+
SendMessageResponse response = client.sendMessage("request-1234-with-file", params);
554+
555+
assertEquals("2.0", response.getJsonrpc());
556+
assertNotNull(response.getId());
557+
Object result = response.getResult();
558+
assertInstanceOf(Task.class, result);
559+
Task task = (Task) result;
560+
assertEquals("de38c76d-d54c-436c-8b9f-4c2703648d64", task.getId());
561+
assertNotNull(task.getContextId());
562+
assertEquals(TaskState.COMPLETED, task.getStatus().state());
563+
assertEquals(1, task.getArtifacts().size());
564+
Artifact artifact = task.getArtifacts().get(0);
565+
assertEquals("artifact-1", artifact.artifactId());
566+
assertEquals("image-analysis", artifact.name());
567+
assertEquals(1, artifact.parts().size());
568+
Part<?> part = artifact.parts().get(0);
569+
assertEquals(Part.Kind.TEXT, part.getKind());
570+
assertEquals("This is an image of a cat sitting on a windowsill.", ((TextPart) part).getText());
571+
assertTrue(task.getMetadata().isEmpty());
572+
}
573+
574+
@Test
575+
public void testA2AClientSendMessageWithDataPart() throws Exception {
576+
this.server.when(
577+
request()
578+
.withMethod("POST")
579+
.withPath("/")
580+
.withBody(JsonBody.json(SEND_MESSAGE_WITH_DATA_PART_TEST_REQUEST, MatchType.STRICT))
581+
582+
)
583+
.respond(
584+
response()
585+
.withStatusCode(200)
586+
.withBody(SEND_MESSAGE_WITH_DATA_PART_TEST_RESPONSE)
587+
);
588+
589+
A2AClient client = new A2AClient("http://localhost:4001");
590+
591+
Map<String, Object> data = new HashMap<>();
592+
data.put("temperature", 25.5);
593+
data.put("humidity", 60.2);
594+
data.put("location", "San Francisco");
595+
data.put("timestamp", "2024-01-15T10:30:00Z");
596+
597+
Message message = new Message.Builder()
598+
.role(Message.Role.USER)
599+
.parts(List.of(
600+
new TextPart("process this data"),
601+
new DataPart(data)
602+
))
603+
.contextId("context-1234")
604+
.messageId("message-1234-with-data")
605+
.build();
606+
MessageSendConfiguration configuration = new MessageSendConfiguration.Builder()
607+
.acceptedOutputModes(List.of("text"))
608+
.blocking(true)
609+
.build();
610+
MessageSendParams params = new MessageSendParams.Builder()
611+
.message(message)
612+
.configuration(configuration)
613+
.build();
614+
615+
SendMessageResponse response = client.sendMessage("request-1234-with-data", params);
616+
617+
assertEquals("2.0", response.getJsonrpc());
618+
assertNotNull(response.getId());
619+
Object result = response.getResult();
620+
assertInstanceOf(Task.class, result);
621+
Task task = (Task) result;
622+
assertEquals("de38c76d-d54c-436c-8b9f-4c2703648d64", task.getId());
623+
assertNotNull(task.getContextId());
624+
assertEquals(TaskState.COMPLETED, task.getStatus().state());
625+
assertEquals(1, task.getArtifacts().size());
626+
Artifact artifact = task.getArtifacts().get(0);
627+
assertEquals("artifact-1", artifact.artifactId());
628+
assertEquals("data-analysis", artifact.name());
629+
assertEquals(1, artifact.parts().size());
630+
Part<?> part = artifact.parts().get(0);
631+
assertEquals(Part.Kind.TEXT, part.getKind());
632+
assertEquals("Processed weather data: Temperature is 25.5°C, humidity is 60.2% in San Francisco.", ((TextPart) part).getText());
633+
assertTrue(task.getMetadata().isEmpty());
634+
}
635+
636+
@Test
637+
public void testA2AClientSendMessageWithMixedParts() throws Exception {
638+
this.server.when(
639+
request()
640+
.withMethod("POST")
641+
.withPath("/")
642+
.withBody(JsonBody.json(SEND_MESSAGE_WITH_MIXED_PARTS_TEST_REQUEST, MatchType.STRICT))
643+
644+
)
645+
.respond(
646+
response()
647+
.withStatusCode(200)
648+
.withBody(SEND_MESSAGE_WITH_MIXED_PARTS_TEST_RESPONSE)
649+
);
650+
651+
A2AClient client = new A2AClient("http://localhost:4001");
652+
653+
Map<String, Object> data = new HashMap<>();
654+
data.put("chartType", "bar");
655+
data.put("dataPoints", List.of(10, 20, 30, 40));
656+
data.put("labels", List.of("Q1", "Q2", "Q3", "Q4"));
657+
658+
Message message = new Message.Builder()
659+
.role(Message.Role.USER)
660+
.parts(List.of(
661+
new TextPart("analyze this data and image"),
662+
new FilePart(new FileWithBytes("image/png", "chart.png", "aGVsbG8=")),
663+
new DataPart(data)
664+
))
665+
.contextId("context-1234")
666+
.messageId("message-1234-with-mixed")
667+
.build();
668+
MessageSendConfiguration configuration = new MessageSendConfiguration.Builder()
669+
.acceptedOutputModes(List.of("text"))
670+
.blocking(true)
671+
.build();
672+
MessageSendParams params = new MessageSendParams.Builder()
673+
.message(message)
674+
.configuration(configuration)
675+
.build();
676+
677+
SendMessageResponse response = client.sendMessage("request-1234-with-mixed", params);
678+
679+
assertEquals("2.0", response.getJsonrpc());
680+
assertNotNull(response.getId());
681+
Object result = response.getResult();
682+
assertInstanceOf(Task.class, result);
683+
Task task = (Task) result;
684+
assertEquals("de38c76d-d54c-436c-8b9f-4c2703648d64", task.getId());
685+
assertNotNull(task.getContextId());
686+
assertEquals(TaskState.COMPLETED, task.getStatus().state());
687+
assertEquals(1, task.getArtifacts().size());
688+
Artifact artifact = task.getArtifacts().get(0);
689+
assertEquals("artifact-1", artifact.artifactId());
690+
assertEquals("mixed-analysis", artifact.name());
691+
assertEquals(1, artifact.parts().size());
692+
Part<?> part = artifact.parts().get(0);
693+
assertEquals(Part.Kind.TEXT, part.getKind());
694+
assertEquals("Analyzed chart image and data: Bar chart showing quarterly data with values [10, 20, 30, 40].", ((TextPart) part).getText());
695+
assertTrue(task.getMetadata().isEmpty());
696+
}
511697
}

core/src/test/java/io/a2a/client/JsonMessages.java

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,4 +425,192 @@ public class JsonMessages {
425425
}
426426
""";
427427

428+
static final String SEND_MESSAGE_WITH_FILE_PART_TEST_REQUEST = """
429+
{
430+
"jsonrpc": "2.0",
431+
"id": "request-1234-with-file",
432+
"method": "message/send",
433+
"params": {
434+
"message": {
435+
"role": "user",
436+
"parts": [
437+
{
438+
"kind": "text",
439+
"text": "analyze this image"
440+
},
441+
{
442+
"kind": "file",
443+
"file": {
444+
"uri": "file:///path/to/image.jpg",
445+
"mimeType": "image/jpeg"
446+
}
447+
}
448+
],
449+
"messageId": "message-1234-with-file",
450+
"contextId": "context-1234",
451+
"kind": "message"
452+
},
453+
"configuration": {
454+
"acceptedOutputModes": ["text"],
455+
"blocking": true
456+
}
457+
}
458+
}""";
459+
460+
static final String SEND_MESSAGE_WITH_FILE_PART_TEST_RESPONSE = """
461+
{
462+
"jsonrpc": "2.0",
463+
"id": 1,
464+
"result": {
465+
"id": "de38c76d-d54c-436c-8b9f-4c2703648d64",
466+
"contextId": "c295ea44-7543-4f78-b524-7a38915ad6e4",
467+
"status": {
468+
"state": "completed"
469+
},
470+
"artifacts": [
471+
{
472+
"artifactId": "artifact-1",
473+
"name": "image-analysis",
474+
"parts": [
475+
{
476+
"kind": "text",
477+
"text": "This is an image of a cat sitting on a windowsill."
478+
}
479+
]
480+
}
481+
],
482+
"metadata": {},
483+
"kind": "task"
484+
}
485+
}""";
486+
487+
static final String SEND_MESSAGE_WITH_DATA_PART_TEST_REQUEST = """
488+
{
489+
"jsonrpc": "2.0",
490+
"id": "request-1234-with-data",
491+
"method": "message/send",
492+
"params": {
493+
"message": {
494+
"role": "user",
495+
"parts": [
496+
{
497+
"kind": "text",
498+
"text": "process this data"
499+
},
500+
{
501+
"kind": "data",
502+
"data": {
503+
"temperature": 25.5,
504+
"humidity": 60.2,
505+
"location": "San Francisco",
506+
"timestamp": "2024-01-15T10:30:00Z"
507+
}
508+
}
509+
],
510+
"messageId": "message-1234-with-data",
511+
"contextId": "context-1234",
512+
"kind": "message"
513+
},
514+
"configuration": {
515+
"acceptedOutputModes": ["text"],
516+
"blocking": true
517+
}
518+
}
519+
}""";
520+
521+
static final String SEND_MESSAGE_WITH_DATA_PART_TEST_RESPONSE = """
522+
{
523+
"jsonrpc": "2.0",
524+
"id": 1,
525+
"result": {
526+
"id": "de38c76d-d54c-436c-8b9f-4c2703648d64",
527+
"contextId": "c295ea44-7543-4f78-b524-7a38915ad6e4",
528+
"status": {
529+
"state": "completed"
530+
},
531+
"artifacts": [
532+
{
533+
"artifactId": "artifact-1",
534+
"name": "data-analysis",
535+
"parts": [
536+
{
537+
"kind": "text",
538+
"text": "Processed weather data: Temperature is 25.5°C, humidity is 60.2% in San Francisco."
539+
}
540+
]
541+
}
542+
],
543+
"metadata": {},
544+
"kind": "task"
545+
}
546+
}""";
547+
548+
static final String SEND_MESSAGE_WITH_MIXED_PARTS_TEST_REQUEST = """
549+
{
550+
"jsonrpc": "2.0",
551+
"id": "request-1234-with-mixed",
552+
"method": "message/send",
553+
"params": {
554+
"message": {
555+
"role": "user",
556+
"parts": [
557+
{
558+
"kind": "text",
559+
"text": "analyze this data and image"
560+
},
561+
{
562+
"kind": "file",
563+
"file": {
564+
"bytes": "aGVsbG8=",
565+
"name": "chart.png",
566+
"mimeType": "image/png"
567+
}
568+
},
569+
{
570+
"kind": "data",
571+
"data": {
572+
"chartType": "bar",
573+
"dataPoints": [10, 20, 30, 40],
574+
"labels": ["Q1", "Q2", "Q3", "Q4"]
575+
}
576+
}
577+
],
578+
"messageId": "message-1234-with-mixed",
579+
"contextId": "context-1234",
580+
"kind": "message"
581+
},
582+
"configuration": {
583+
"acceptedOutputModes": ["text"],
584+
"blocking": true
585+
}
586+
}
587+
}""";
588+
589+
static final String SEND_MESSAGE_WITH_MIXED_PARTS_TEST_RESPONSE = """
590+
{
591+
"jsonrpc": "2.0",
592+
"id": 1,
593+
"result": {
594+
"id": "de38c76d-d54c-436c-8b9f-4c2703648d64",
595+
"contextId": "c295ea44-7543-4f78-b524-7a38915ad6e4",
596+
"status": {
597+
"state": "completed"
598+
},
599+
"artifacts": [
600+
{
601+
"artifactId": "artifact-1",
602+
"name": "mixed-analysis",
603+
"parts": [
604+
{
605+
"kind": "text",
606+
"text": "Analyzed chart image and data: Bar chart showing quarterly data with values [10, 20, 30, 40]."
607+
}
608+
]
609+
}
610+
],
611+
"metadata": {},
612+
"kind": "task"
613+
}
614+
}""";
615+
428616
}

0 commit comments

Comments
 (0)