Skip to content

Commit 0fb4115

Browse files
test: improve TaskManager test coverage (#181)
# Description Thank you for opening a Pull Request! Before submitting your PR, there are a few things you can do to make sure it goes smoothly: - [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 - [x] Appropriate READMEs were updated (if necessary) Fixes #46 🦕 --------- Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
1 parent bbfa07d commit 0fb4115

File tree

1 file changed

+359
-5
lines changed

1 file changed

+359
-5
lines changed

server-common/src/test/java/io/a2a/server/tasks/TaskManagerTest.java

Lines changed: 359 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
11
package io.a2a.server.tasks;
22

3+
import java.util.Collections;
4+
import java.util.HashMap;
5+
import java.util.List;
6+
37
import static org.junit.jupiter.api.Assertions.assertEquals;
8+
import static org.junit.jupiter.api.Assertions.assertNotNull;
49
import static org.junit.jupiter.api.Assertions.assertNotSame;
510
import static org.junit.jupiter.api.Assertions.assertNull;
611
import static org.junit.jupiter.api.Assertions.assertSame;
7-
8-
import java.util.Collections;
9-
import java.util.HashMap;
12+
import static org.junit.jupiter.api.Assertions.assertThrows;
13+
import static org.junit.jupiter.api.Assertions.assertTrue;
14+
import org.junit.jupiter.api.BeforeEach;
15+
import org.junit.jupiter.api.Test;
1016

1117
import io.a2a.spec.A2AServerException;
1218
import io.a2a.spec.Artifact;
@@ -18,8 +24,6 @@
1824
import io.a2a.spec.TaskStatusUpdateEvent;
1925
import io.a2a.spec.TextPart;
2026
import io.a2a.util.Utils;
21-
import org.junit.jupiter.api.BeforeEach;
22-
import org.junit.jupiter.api.Test;
2327

2428
public class TaskManagerTest {
2529
private static final String TASK_JSON = """
@@ -175,4 +179,354 @@ public void testGetTaskNoTaskId() {
175179
Task retrieved = taskManagerWithoutId.getTask();
176180
assertNull(retrieved);
177181
}
182+
183+
// Additional tests for missing coverage scenarios
184+
185+
@Test
186+
public void testTaskArtifactUpdateEventAppendTrueWithExistingArtifact() throws A2AServerException {
187+
// Setup: Create a task with an existing artifact
188+
Task initialTask = minimalTask;
189+
Artifact existingArtifact = new Artifact.Builder()
190+
.artifactId("artifact-id")
191+
.name("artifact-1")
192+
.parts(Collections.singletonList(new TextPart("existing content")))
193+
.build();
194+
Task taskWithArtifact = new Task.Builder(initialTask)
195+
.artifacts(Collections.singletonList(existingArtifact))
196+
.build();
197+
taskStore.save(taskWithArtifact);
198+
199+
// Test: Append new parts to existing artifact
200+
Artifact newArtifact = new Artifact.Builder()
201+
.artifactId("artifact-id")
202+
.name("artifact-1")
203+
.parts(Collections.singletonList(new TextPart("new content")))
204+
.build();
205+
TaskArtifactUpdateEvent event = new TaskArtifactUpdateEvent.Builder()
206+
.taskId(minimalTask.getId())
207+
.contextId(minimalTask.getContextId())
208+
.artifact(newArtifact)
209+
.append(true)
210+
.build();
211+
212+
Task updatedTask = taskManager.saveTaskEvent(event);
213+
214+
assertEquals(1, updatedTask.getArtifacts().size());
215+
Artifact updatedArtifact = updatedTask.getArtifacts().get(0);
216+
assertEquals("artifact-id", updatedArtifact.artifactId());
217+
assertEquals(2, updatedArtifact.parts().size());
218+
assertEquals("existing content", ((TextPart) updatedArtifact.parts().get(0)).getText());
219+
assertEquals("new content", ((TextPart) updatedArtifact.parts().get(1)).getText());
220+
}
221+
222+
@Test
223+
public void testTaskArtifactUpdateEventAppendTrueWithoutExistingArtifact() throws A2AServerException {
224+
// Setup: Create a task without artifacts
225+
Task initialTask = minimalTask;
226+
taskStore.save(initialTask);
227+
228+
// Test: Try to append to non-existent artifact (should be ignored)
229+
Artifact newArtifact = new Artifact.Builder()
230+
.artifactId("artifact-id")
231+
.name("artifact-1")
232+
.parts(Collections.singletonList(new TextPart("new content")))
233+
.build();
234+
TaskArtifactUpdateEvent event = new TaskArtifactUpdateEvent.Builder()
235+
.taskId(minimalTask.getId())
236+
.contextId(minimalTask.getContextId())
237+
.artifact(newArtifact)
238+
.append(true)
239+
.build();
240+
241+
Task saved = taskManager.saveTaskEvent(event);
242+
Task updatedTask = taskManager.getTask();
243+
244+
// Should have no artifacts since append was ignored
245+
assertEquals(0, updatedTask.getArtifacts().size());
246+
}
247+
248+
@Test
249+
public void testTaskArtifactUpdateEventAppendFalseWithExistingArtifact() throws A2AServerException {
250+
// Setup: Create a task with an existing artifact
251+
Task initialTask = minimalTask;
252+
Artifact existingArtifact = new Artifact.Builder()
253+
.artifactId("artifact-id")
254+
.name("artifact-1")
255+
.parts(Collections.singletonList(new TextPart("existing content")))
256+
.build();
257+
Task taskWithArtifact = new Task.Builder(initialTask)
258+
.artifacts(Collections.singletonList(existingArtifact))
259+
.build();
260+
taskStore.save(taskWithArtifact);
261+
262+
// Test: Replace existing artifact (append=false)
263+
Artifact newArtifact = new Artifact.Builder()
264+
.artifactId("artifact-id")
265+
.name("artifact-1")
266+
.parts(Collections.singletonList(new TextPart("replacement content")))
267+
.build();
268+
TaskArtifactUpdateEvent event = new TaskArtifactUpdateEvent.Builder()
269+
.taskId(minimalTask.getId())
270+
.contextId(minimalTask.getContextId())
271+
.artifact(newArtifact)
272+
.append(false)
273+
.build();
274+
275+
Task saved = taskManager.saveTaskEvent(event);
276+
Task updatedTask = taskManager.getTask();
277+
278+
assertEquals(1, updatedTask.getArtifacts().size());
279+
Artifact updatedArtifact = updatedTask.getArtifacts().get(0);
280+
assertEquals("artifact-id", updatedArtifact.artifactId());
281+
assertEquals(1, updatedArtifact.parts().size());
282+
assertEquals("replacement content", ((TextPart) updatedArtifact.parts().get(0)).getText());
283+
}
284+
285+
@Test
286+
public void testTaskArtifactUpdateEventAppendNullWithExistingArtifact() throws A2AServerException {
287+
// Setup: Create a task with an existing artifact
288+
Task initialTask = minimalTask;
289+
Artifact existingArtifact = new Artifact.Builder()
290+
.artifactId("artifact-id")
291+
.name("artifact-1")
292+
.parts(Collections.singletonList(new TextPart("existing content")))
293+
.build();
294+
Task taskWithArtifact = new Task.Builder(initialTask)
295+
.artifacts(Collections.singletonList(existingArtifact))
296+
.build();
297+
taskStore.save(taskWithArtifact);
298+
299+
// Test: Replace existing artifact (append=null, defaults to false)
300+
Artifact newArtifact = new Artifact.Builder()
301+
.artifactId("artifact-id")
302+
.name("artifact-1")
303+
.parts(Collections.singletonList(new TextPart("replacement content")))
304+
.build();
305+
TaskArtifactUpdateEvent event = new TaskArtifactUpdateEvent.Builder()
306+
.taskId(minimalTask.getId())
307+
.contextId(minimalTask.getContextId())
308+
.artifact(newArtifact)
309+
.build(); // append is null
310+
311+
Task saved = taskManager.saveTaskEvent(event);
312+
Task updatedTask = taskManager.getTask();
313+
314+
assertEquals(1, updatedTask.getArtifacts().size());
315+
Artifact updatedArtifact = updatedTask.getArtifacts().get(0);
316+
assertEquals("artifact-id", updatedArtifact.artifactId());
317+
assertEquals(1, updatedArtifact.parts().size());
318+
assertEquals("replacement content", ((TextPart) updatedArtifact.parts().get(0)).getText());
319+
}
320+
321+
@Test
322+
public void testAddingTaskWithDifferentIdFails() {
323+
// Test that adding a task with a different id from the taskmanager's taskId fails
324+
TaskManager taskManagerWithId = new TaskManager("task-abc", "session-xyz", taskStore, null);
325+
326+
Task differentTask = new Task.Builder()
327+
.id("different-task-id")
328+
.contextId("session-xyz")
329+
.status(new TaskStatus(TaskState.SUBMITTED))
330+
.build();
331+
332+
assertThrows(A2AServerException.class, () -> {
333+
taskManagerWithId.saveTaskEvent(differentTask);
334+
});
335+
}
336+
337+
@Test
338+
public void testAddingTaskWithDifferentIdViaStatusUpdateFails() {
339+
// Test that adding a status update with different taskId fails
340+
TaskManager taskManagerWithId = new TaskManager("task-abc", "session-xyz", taskStore, null);
341+
342+
TaskStatusUpdateEvent event = new TaskStatusUpdateEvent.Builder()
343+
.taskId("different-task-id")
344+
.contextId("session-xyz")
345+
.status(new TaskStatus(TaskState.WORKING))
346+
.isFinal(false)
347+
.build();
348+
349+
assertThrows(A2AServerException.class, () -> {
350+
taskManagerWithId.saveTaskEvent(event);
351+
});
352+
}
353+
354+
@Test
355+
public void testAddingTaskWithDifferentIdViaArtifactUpdateFails() {
356+
// Test that adding an artifact update with different taskId fails
357+
TaskManager taskManagerWithId = new TaskManager("task-abc", "session-xyz", taskStore, null);
358+
359+
Artifact artifact = new Artifact.Builder()
360+
.artifactId("artifact-id")
361+
.name("artifact-1")
362+
.parts(Collections.singletonList(new TextPart("content")))
363+
.build();
364+
TaskArtifactUpdateEvent event = new TaskArtifactUpdateEvent.Builder()
365+
.taskId("different-task-id")
366+
.contextId("session-xyz")
367+
.artifact(artifact)
368+
.build();
369+
370+
assertThrows(A2AServerException.class, () -> {
371+
taskManagerWithId.saveTaskEvent(event);
372+
});
373+
}
374+
375+
@Test
376+
public void testTaskWithNoMessageUsesInitialMessage() throws A2AServerException {
377+
// Test that adding a task with no message, and there is a TaskManager.initialMessage,
378+
// the initialMessage gets used
379+
Message initialMessage = new Message.Builder()
380+
.role(Message.Role.USER)
381+
.parts(Collections.singletonList(new TextPart("initial message")))
382+
.messageId("initial-msg-id")
383+
.build();
384+
385+
TaskManager taskManagerWithInitialMessage = new TaskManager(null, null, taskStore, initialMessage);
386+
387+
// Use a status update event instead of a Task to trigger createTask
388+
TaskStatusUpdateEvent event = new TaskStatusUpdateEvent.Builder()
389+
.taskId("new-task-id")
390+
.contextId("some-context")
391+
.status(new TaskStatus(TaskState.SUBMITTED))
392+
.isFinal(false)
393+
.build();
394+
395+
Task saved = taskManagerWithInitialMessage.saveTaskEvent(event);
396+
Task retrieved = taskManagerWithInitialMessage.getTask();
397+
398+
// Check that the task has the initial message in its history
399+
assertNotNull(retrieved.getHistory());
400+
assertEquals(1, retrieved.getHistory().size());
401+
Message historyMessage = retrieved.getHistory().get(0);
402+
assertEquals(initialMessage.getMessageId(), historyMessage.getMessageId());
403+
assertEquals(initialMessage.getRole(), historyMessage.getRole());
404+
assertEquals("initial message", ((TextPart) historyMessage.getParts().get(0)).getText());
405+
}
406+
407+
@Test
408+
public void testTaskWithMessageDoesNotUseInitialMessage() throws A2AServerException {
409+
// Test that adding a task with a message does not use the initial message
410+
Message initialMessage = new Message.Builder()
411+
.role(Message.Role.USER)
412+
.parts(Collections.singletonList(new TextPart("initial message")))
413+
.messageId("initial-msg-id")
414+
.build();
415+
416+
TaskManager taskManagerWithInitialMessage = new TaskManager(null, null, taskStore, initialMessage);
417+
418+
Message taskMessage = new Message.Builder()
419+
.role(Message.Role.AGENT)
420+
.parts(Collections.singletonList(new TextPart("task message")))
421+
.messageId("task-msg-id")
422+
.build();
423+
424+
// Use TaskStatusUpdateEvent to trigger the creation of a task, which will check if the initialMessage is used.
425+
TaskStatusUpdateEvent event = new TaskStatusUpdateEvent.Builder()
426+
.taskId("new-task-id")
427+
.contextId("some-context")
428+
.status(new TaskStatus(TaskState.SUBMITTED, taskMessage, null))
429+
.isFinal(false)
430+
.build();
431+
432+
Task saved = taskManagerWithInitialMessage.saveTaskEvent(event);
433+
Task retrieved = taskManagerWithInitialMessage.getTask();
434+
435+
// There should now be a history containing the initialMessage
436+
// But the current message (taskMessage) should be in the state, not in the history
437+
assertNotNull(retrieved.getHistory());
438+
assertEquals(1, retrieved.getHistory().size());
439+
assertEquals("initial message", ((TextPart) retrieved.getHistory().get(0).getParts().get(0)).getText());
440+
441+
// The message in the current state should be taskMessage
442+
assertNotNull(retrieved.getStatus().message());
443+
assertEquals("task message", ((TextPart) retrieved.getStatus().message().getParts().get(0)).getText());
444+
}
445+
446+
@Test
447+
public void testMultipleArtifactsWithSameArtifactId() throws A2AServerException {
448+
// Test handling of multiple artifacts with the same artifactId
449+
Task initialTask = minimalTask;
450+
taskStore.save(initialTask);
451+
452+
// Add first artifact
453+
Artifact artifact1 = new Artifact.Builder()
454+
.artifactId("artifact-id")
455+
.name("artifact-1")
456+
.parts(Collections.singletonList(new TextPart("content 1")))
457+
.build();
458+
TaskArtifactUpdateEvent event1 = new TaskArtifactUpdateEvent.Builder()
459+
.taskId(minimalTask.getId())
460+
.contextId(minimalTask.getContextId())
461+
.artifact(artifact1)
462+
.build();
463+
taskManager.saveTaskEvent(event1);
464+
465+
// Add second artifact with same artifactId (should replace the first)
466+
Artifact artifact2 = new Artifact.Builder()
467+
.artifactId("artifact-id")
468+
.name("artifact-2")
469+
.parts(Collections.singletonList(new TextPart("content 2")))
470+
.build();
471+
TaskArtifactUpdateEvent event2 = new TaskArtifactUpdateEvent.Builder()
472+
.taskId(minimalTask.getId())
473+
.contextId(minimalTask.getContextId())
474+
.artifact(artifact2)
475+
.build();
476+
taskManager.saveTaskEvent(event2);
477+
478+
Task updatedTask = taskManager.getTask();
479+
assertEquals(1, updatedTask.getArtifacts().size());
480+
Artifact finalArtifact = updatedTask.getArtifacts().get(0);
481+
assertEquals("artifact-id", finalArtifact.artifactId());
482+
assertEquals("artifact-2", finalArtifact.name());
483+
assertEquals("content 2", ((TextPart) finalArtifact.parts().get(0)).getText());
484+
}
485+
486+
@Test
487+
public void testMultipleArtifactsWithDifferentArtifactIds() throws A2AServerException {
488+
// Test handling of multiple artifacts with different artifactIds
489+
Task initialTask = minimalTask;
490+
taskStore.save(initialTask);
491+
492+
// Add first artifact
493+
Artifact artifact1 = new Artifact.Builder()
494+
.artifactId("artifact-id-1")
495+
.name("artifact-1")
496+
.parts(Collections.singletonList(new TextPart("content 1")))
497+
.build();
498+
TaskArtifactUpdateEvent event1 = new TaskArtifactUpdateEvent.Builder()
499+
.taskId(minimalTask.getId())
500+
.contextId(minimalTask.getContextId())
501+
.artifact(artifact1)
502+
.build();
503+
taskManager.saveTaskEvent(event1);
504+
505+
// Add second artifact with different artifactId (should be added)
506+
Artifact artifact2 = new Artifact.Builder()
507+
.artifactId("artifact-id-2")
508+
.name("artifact-2")
509+
.parts(Collections.singletonList(new TextPart("content 2")))
510+
.build();
511+
TaskArtifactUpdateEvent event2 = new TaskArtifactUpdateEvent.Builder()
512+
.taskId(minimalTask.getId())
513+
.contextId(minimalTask.getContextId())
514+
.artifact(artifact2)
515+
.build();
516+
taskManager.saveTaskEvent(event2);
517+
518+
Task updatedTask = taskManager.getTask();
519+
assertEquals(2, updatedTask.getArtifacts().size());
520+
521+
// Verify both artifacts are present
522+
List<Artifact> artifacts = updatedTask.getArtifacts();
523+
assertTrue(artifacts.stream()
524+
.anyMatch(a -> "artifact-id-1".equals(a.artifactId())
525+
&& "content 1".equals(((TextPart) a.parts().get(0)).getText()))
526+
, "Artifact 1 should be present");
527+
assertTrue(artifacts.stream()
528+
.anyMatch(a -> "artifact-id-2".equals(a.artifactId())
529+
&& "content 2".equals(((TextPart) a.parts().get(0)).getText()))
530+
, "Artifact 2 should be present");
531+
}
178532
}

0 commit comments

Comments
 (0)