Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package org.a2aproject.sdk.compat03.conversion.mappers.domain;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

import java.util.HashMap;
import java.util.Map;

import org.a2aproject.sdk.compat03.spec.TaskState_v0_3;
import org.a2aproject.sdk.compat03.spec.TaskStatusUpdateEvent_v0_3;
import org.a2aproject.sdk.compat03.spec.TaskStatus_v0_3;
import org.a2aproject.sdk.spec.TaskState;
import org.a2aproject.sdk.spec.TaskStatus;
import org.a2aproject.sdk.spec.TaskStatusUpdateEvent;
import org.junit.jupiter.api.Test;

class TaskStatusUpdateEventMapper_v0_3_Test {

@Test
void toV10_preservesMetadataAndDefensiveCopy() {
Map<String, Object> mutableMetadata = new HashMap<>();
mutableMetadata.put("source", "sensor");

TaskStatusUpdateEvent_v0_3 v03 = new TaskStatusUpdateEvent_v0_3.Builder()
.taskId("task-123")
.status(new TaskStatus_v0_3(TaskState_v0_3.WORKING))
.contextId("context-456")
.isFinal(false)
.metadata(mutableMetadata)
.build();

TaskStatusUpdateEvent v10 = TaskStatusUpdateEventMapper_v0_3.INSTANCE.toV10(v03);
mutableMetadata.put("write", "should-not-leak");

assertEquals("task-123", v10.taskId());
assertEquals(TaskState.TASK_STATE_WORKING, v10.status().state());
assertEquals("context-456", v10.contextId());
assertEquals(Map.of("source", "sensor"), v10.metadata());
assertThrows(UnsupportedOperationException.class, () -> v10.metadata().put("another", "value"));
}

@Test
void fromV10_preservesMetadata() {
TaskStatusUpdateEvent v10 = new TaskStatusUpdateEvent(
"task-123",
new TaskStatus(TaskState.TASK_STATE_WORKING),
"context-456",
Map.of("source", "sensor"));

TaskStatusUpdateEvent_v0_3 v03 = TaskStatusUpdateEventMapper_v0_3.INSTANCE.fromV10(v10);

assertEquals("task-123", v03.taskId());
assertEquals(TaskState_v0_3.WORKING, v03.status().state());
assertEquals("context-456", v03.contextId());
assertEquals(Map.of("source", "sensor"), v03.metadata());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package org.a2aproject.sdk.server.tasks;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.util.List;
import java.util.Map;

import org.a2aproject.sdk.spec.Artifact;
import org.a2aproject.sdk.spec.ListTasksParams;
import org.a2aproject.sdk.spec.Message;
import org.a2aproject.sdk.spec.Task;
import org.a2aproject.sdk.spec.TaskState;
import org.a2aproject.sdk.spec.TaskStatus;
import org.a2aproject.sdk.spec.TextPart;
import org.junit.jupiter.api.Test;

public class InMemoryTaskStoreTest {

@Test
public void testSaveAndGet() {
InMemoryTaskStore store = new InMemoryTaskStore();
Task task = sampleTask("task-abc");

store.save(task, false);

Task retrieved = store.get(task.id());
assertSame(task, retrieved);
}

@Test
public void testGetNonExistent() {
InMemoryTaskStore store = new InMemoryTaskStore();

Task retrieved = store.get("nonexistent");
assertNull(retrieved);
}

@Test
public void testDelete() {
InMemoryTaskStore store = new InMemoryTaskStore();
Task task = sampleTask("task-abc");

store.save(task, false);
store.delete(task.id());

Task retrieved = store.get(task.id());
assertNull(retrieved);
}

@Test
public void testDeleteNonExistent() {
InMemoryTaskStore store = new InMemoryTaskStore();

store.delete("non-existent");
}

@Test
public void testListTransformsHistoryAndArtifacts() {
InMemoryTaskStore store = new InMemoryTaskStore();
Task task = Task.builder()
.id("task-abc")
.contextId("session-xyz")
.status(new TaskStatus(TaskState.TASK_STATE_WORKING))
.history(List.of(sampleMessage("msg-1"), sampleMessage("msg-2")))
.artifacts(List.of(sampleArtifact("artifact-1")))
.metadata(Map.of("origin", "test"))
.build();

store.save(task, false);

ListTasksParams params = ListTasksParams.builder().build();
List<Task> tasks = store.list(params, null).tasks();

assertEquals(1, tasks.size());
Task listed = tasks.get(0);
assertEquals(task.id(), listed.id());
assertEquals(task.contextId(), listed.contextId());
assertTrue(listed.history().isEmpty(), "Default list() should omit history");
assertTrue(listed.artifacts().isEmpty(), "Default list() should omit artifacts");
assertEquals(task.metadata(), listed.metadata());
}

private static Task sampleTask(String id) {
return Task.builder()
.id(id)
.contextId("session-xyz")
.status(new TaskStatus(TaskState.TASK_STATE_SUBMITTED))
.build();
}

private static Message sampleMessage(String messageId) {
return Message.builder()
.role(Message.Role.ROLE_USER)
.parts(List.of(new TextPart("content")))
.messageId(messageId)
.build();
}

private static Artifact sampleArtifact(String artifactId) {
return Artifact.builder()
.artifactId(artifactId)
.parts(List.of(new TextPart("artifact content")))
.build();
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,6 @@ public void testSaveTaskEventStatusUpdate() throws A2AServerException {

assertEquals(initialTask.id(), updated.id());
assertEquals(initialTask.contextId(), updated.contextId());
// TODO type does not get unmarshalled
//assertEquals(initialTask.getType(), updated.getType());
assertSame(newStatus, updated.status());
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.a2aproject.sdk.spec;

import java.util.List;

import org.jspecify.annotations.Nullable;

/**
Expand Down Expand Up @@ -35,6 +36,10 @@ public record AgentCapabilities(boolean streaming,
boolean extendedAgentCard,
@Nullable List<AgentExtension> extensions) {

public AgentCapabilities {
extensions = extensions == null ? null : List.copyOf(extensions);
}

/**
* Create a new Builder
*
Expand Down
8 changes: 8 additions & 0 deletions spec/src/main/java/org/a2aproject/sdk/spec/AgentCard.java
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,14 @@ public record AgentCard(
Assert.checkNotNullParam("skills", skills);
Assert.checkNotNullParam("supportedInterfaces", supportedInterfaces);
Assert.checkNotNullParam("version", version);
defaultInputModes = List.copyOf(defaultInputModes);
defaultOutputModes = List.copyOf(defaultOutputModes);
skills = List.copyOf(skills);
securitySchemes = securitySchemes == null ? null : Map.copyOf(securitySchemes);
securityRequirements = securityRequirements == null ? null : List.copyOf(securityRequirements);
supportedInterfaces = List.copyOf(supportedInterfaces);
signatures = signatures == null ? null : List.copyOf(signatures);
additionalInterfaces = additionalInterfaces == null ? null : List.copyOf(additionalInterfaces);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ public record AgentCardSignature(@Nullable Map<String, Object> header, @Serializ
public AgentCardSignature {
Assert.checkNotNullParam("protectedHeader", protectedHeader);
Assert.checkNotNullParam("signature", signature);
header = header == null ? null : Map.copyOf(header);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ public record AgentExtension (@Nullable String description, @Nullable Map<String
*/
public AgentExtension {
Assert.checkNotNullParam("uri", uri);
params = params == null ? null : Map.copyOf(params);
}

/**
Expand Down
5 changes: 5 additions & 0 deletions spec/src/main/java/org/a2aproject/sdk/spec/AgentSkill.java
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@ public record AgentSkill(String id, String name, String description, List<String
Assert.checkNotNullParam("name", name);
Assert.checkNotNullParam("description", description);
Assert.checkNotNullParam("tags", tags);
tags = List.copyOf(tags);
examples = examples == null ? null : List.copyOf(examples);
inputModes = inputModes == null ? null : List.copyOf(inputModes);
outputModes = outputModes == null ? null : List.copyOf(outputModes);
securityRequirements = securityRequirements == null ? null : List.copyOf(securityRequirements);
}

/**
Expand Down
3 changes: 3 additions & 0 deletions spec/src/main/java/org/a2aproject/sdk/spec/Artifact.java
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ public record Artifact(String artifactId, @Nullable String name, @Nullable Strin
if (parts.isEmpty()) {
throw new IllegalArgumentException("Parts cannot be empty");
}
parts = List.copyOf(parts);
metadata = metadata == null ? null : Map.copyOf(metadata);
extensions = extensions == null ? null : List.copyOf(extensions);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,6 @@ public record AuthorizationCodeOAuthFlow(String authorizationUrl, String refresh
Assert.checkNotNullParam("authorizationUrl", authorizationUrl);
Assert.checkNotNullParam("scopes", scopes);
Assert.checkNotNullParam("tokenUrl", tokenUrl);
scopes = Map.copyOf(scopes);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public record CancelTaskParams(String id, @Nullable String tenant, Map<String, O
*/
public CancelTaskParams {
Assert.checkNotNullParam("id", id);
metadata = Map.copyOf(metadata);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public record ClientCredentialsOAuthFlow(String refreshUrl, Map<String, String>
public ClientCredentialsOAuthFlow {
Assert.checkNotNullParam("scopes", scopes);
Assert.checkNotNullParam("tokenUrl", tokenUrl);
scopes = Map.copyOf(scopes);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public record MessageSendConfiguration(@Nullable List<String> acceptedOutputMode
* @throws IllegalArgumentException if historyLength is negative
*/
public MessageSendConfiguration {
acceptedOutputModes = acceptedOutputModes == null ? null : List.copyOf(acceptedOutputModes);
if (historyLength != null && historyLength < 0) {
throw new IllegalArgumentException("Invalid history length");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ public record MessageSendParams(Message message, @Nullable MessageSendConfigurat
*/
public MessageSendParams {
Assert.checkNotNullParam("message", message);
metadata = metadata == null ? null : Map.copyOf(metadata);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,11 @@ public record SecurityRequirement(Map<String, List<String>> schemes) {
*/
public SecurityRequirement {
Assert.checkNotNullParam("schemes", schemes);
schemes = unmodifiableMap(new LinkedHashMap<>(schemes));
LinkedHashMap<String, List<String>> copiedSchemes = new LinkedHashMap<>();
for (Map.Entry<String, List<String>> entry : schemes.entrySet()) {
copiedSchemes.put(entry.getKey(), List.copyOf(entry.getValue()));
}
schemes = unmodifiableMap(copiedSchemes);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ public record TaskArtifactUpdateEvent(String taskId, Artifact artifact, String c
Assert.checkNotNullParam("taskId", taskId);
Assert.checkNotNullParam("artifact", artifact);
Assert.checkNotNullParam("contextId", contextId);
metadata = metadata == null ? null : Map.copyOf(metadata);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ public record TaskStatusUpdateEvent(String taskId, TaskStatus status, String con
Assert.checkNotNullParam("taskId", taskId);
Assert.checkNotNullParam("status", status);
Assert.checkNotNullParam("contextId", contextId);
metadata = metadata != null ? Map.copyOf(metadata) : null;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package org.a2aproject.sdk.spec;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.junit.jupiter.api.Test;

class AgentCapabilitiesTest {

@Test
void testConstruction_defensivelyCopiesExtensions() {
List<AgentExtension> extensions = new ArrayList<>();
extensions.add(new AgentExtension("Custom auth", Map.of("authType", "bearer"), true, "https://example.com/ext"));

AgentCapabilities capabilities = new AgentCapabilities(true, false, true, extensions);
extensions.add(new AgentExtension("Another", Map.of("x", "y"), false, "https://example.com/ext2"));

assertEquals(1, capabilities.extensions().size());
assertThrows(UnsupportedOperationException.class, () -> capabilities.extensions().add(
new AgentExtension("Mutated", Map.of(), false, "https://example.com/ext3")));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.a2aproject.sdk.spec;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

import java.util.HashMap;
import java.util.Map;

import org.junit.jupiter.api.Test;

class AgentCardSignatureTest {

@Test
void testConstruction_defensivelyCopiesHeader() {
Map<String, Object> header = new HashMap<>();
header.put("kid", "2024-01");

AgentCardSignature signature = new AgentCardSignature(header, "protected", "signature");
header.put("alg", "ES256");

assertEquals(Map.of("kid", "2024-01"), signature.header());
assertThrows(UnsupportedOperationException.class, () -> signature.header().put("x", "y"));
}
}
Loading