Skip to content

Commit 15fa456

Browse files
authored
feat: add ArtifactUtils utility class and corresponding tests (#303) (#350)
## feat: add `ArtifactUtils` utility class and corresponding unit tests ### Description This PR introduces the `ArtifactUtils` utility class, which provides a set of static helper methods for creating `Artifact` instances in a standardized and concise way. It simplifies the creation of artifacts with different content types (`TextPart`, `DataPart`, etc.) while ensuring consistent ID generation and description handling. ### Details - Added `ArtifactUtils` as a `final` utility class with a private constructor to prevent instantiation. - Implemented multiple overloaded factory methods for flexible artifact creation: - `newArtifact(List<Part<?>> parts, String name, String description)` - `newArtifact(List<Part<?>> parts, String name)` - `newTextArtifact(String name, String text, String description)` - `newTextArtifact(String name, String text)` - `newDataArtifact(String name, Map<String, Object> data, String description)` - `newDataArtifact(String name, Map<String, Object> data)` - Each artifact automatically receives a unique `artifactId` via `UUID.randomUUID()`. - Uses `List.of()` for concise creation of single-part artifacts (`TextPart`, `DataPart`). ### Tests Added comprehensive unit tests in `ArtifactUtilsTest` to verify: - UUID generation through mocking - Proper assignment of `name`, `description`, and `parts` - Correct creation of both text-based and data-based artifacts - Non-null and non-empty `artifactId` values ### Example Usage ```java Artifact textArtifact = ArtifactUtils.newTextArtifact("User Profile", "Basic user info"); Artifact dataArtifact = ArtifactUtils.newDataArtifact("Config", Map.of("version", "1.0")); ``` Fixes:#303
1 parent 6f1b5b2 commit 15fa456

File tree

2 files changed

+257
-0
lines changed

2 files changed

+257
-0
lines changed
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package io.a2a.server.util;
2+
3+
import java.util.List;
4+
import java.util.Map;
5+
import java.util.UUID;
6+
7+
import io.a2a.spec.Artifact;
8+
import io.a2a.spec.DataPart;
9+
import io.a2a.spec.Part;
10+
import io.a2a.spec.TextPart;
11+
12+
/**
13+
* Utility functions for creating A2A Artifact objects.
14+
*/
15+
public final class ArtifactUtils {
16+
17+
private ArtifactUtils() {
18+
// Utility class - prevent instantiation
19+
}
20+
21+
/**
22+
* Creates a new Artifact object.
23+
*
24+
* @param name The human-readable name of the artifact.
25+
* @param parts The list of {@code Part} objects forming the artifact's content.
26+
* @param description An optional description of the artifact.
27+
* @return A new {@code Artifact} object with a generated artifact_id.
28+
*/
29+
public static Artifact newArtifact(String name, List<Part<?>> parts, String description) {
30+
return new Artifact(
31+
UUID.randomUUID().toString(),
32+
name,
33+
description,
34+
parts,
35+
null
36+
);
37+
}
38+
39+
/**
40+
* Creates a new Artifact object with empty description.
41+
*
42+
* @param name The human-readable name of the artifact.
43+
* @param parts The list of {@code Part} objects forming the artifact's content.
44+
* @return A new {@code Artifact} object with a generated artifact_id.
45+
*/
46+
public static Artifact newArtifact(String name, List<Part<?>> parts) {
47+
return newArtifact(name, parts, null);
48+
}
49+
50+
/**
51+
* Creates a new Artifact object containing only a single TextPart.
52+
*
53+
* @param name The human-readable name of the artifact.
54+
* @param text The text content of the artifact.
55+
* @param description An optional description of the artifact.
56+
* @return A new {@code Artifact} object with a generated artifact_id.
57+
*/
58+
public static Artifact newTextArtifact(String name, String text, String description) {
59+
return newArtifact(
60+
name,
61+
List.of(new TextPart(text)),
62+
description
63+
);
64+
}
65+
66+
/**
67+
* Creates a new Artifact object containing only a single TextPart with empty description.
68+
*
69+
* @param name The human-readable name of the artifact.
70+
* @param text The text content of the artifact.
71+
* @return A new {@code Artifact} object with a generated artifact_id.
72+
*/
73+
public static Artifact newTextArtifact(String name, String text) {
74+
return newTextArtifact(name, text, null);
75+
}
76+
77+
/**
78+
* Creates a new Artifact object containing only a single DataPart.
79+
*
80+
* @param name The human-readable name of the artifact.
81+
* @param data The structured data content of the artifact.
82+
* @param description An optional description of the artifact.
83+
* @return A new {@code Artifact} object with a generated artifact_id.
84+
*/
85+
public static Artifact newDataArtifact(String name, Map<String, Object> data, String description) {
86+
return newArtifact(
87+
name,
88+
List.of(new DataPart(data)),
89+
description
90+
);
91+
}
92+
93+
/**
94+
* Creates a new Artifact object containing only a single DataPart with empty description.
95+
*
96+
* @param name The human-readable name of the artifact.
97+
* @param data The structured data content of the artifact.
98+
* @return A new {@code Artifact} object with a generated artifact_id.
99+
*/
100+
public static Artifact newDataArtifact(String name, Map<String, Object> data) {
101+
return newDataArtifact(name, data, null);
102+
}
103+
}
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
package io.a2a.server.util;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertFalse;
5+
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
6+
import static org.junit.jupiter.api.Assertions.assertNotNull;
7+
import static org.junit.jupiter.api.Assertions.assertTrue;
8+
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
9+
10+
import java.util.List;
11+
import java.util.Map;
12+
import java.util.UUID;
13+
14+
import org.junit.jupiter.api.Test;
15+
16+
import io.a2a.spec.Artifact;
17+
import io.a2a.spec.DataPart;
18+
import io.a2a.spec.Part;
19+
import io.a2a.spec.TextPart;
20+
class ArtifactUtilsTest {
21+
22+
@Test
23+
void testNewArtifactAssignsPartsNameDescription() {
24+
// Given
25+
List<Part<?>> parts = List.of(new TextPart("Sample text"));
26+
String name = "My Artifact";
27+
String description = "This is a test artifact.";
28+
29+
// When
30+
Artifact artifact = ArtifactUtils.newArtifact(name, parts, description);
31+
32+
// Then
33+
assertEquals(parts, artifact.parts());
34+
assertEquals(name, artifact.name());
35+
assertEquals(description, artifact.description());
36+
37+
// Then
38+
assertNotNull(artifact.artifactId());
39+
assertFalse(artifact.artifactId().isBlank());
40+
assertDoesNotThrow(() -> UUID.fromString(artifact.artifactId()));
41+
}
42+
43+
@Test
44+
void testNewArtifactEmptyDescriptionIfNotProvided() {
45+
// Given
46+
List<Part<?>> parts = List.of(new TextPart("Another sample"));
47+
String name = "Artifact_No_Desc";
48+
49+
// When
50+
Artifact artifact = ArtifactUtils.newArtifact(name, parts);
51+
52+
// Then
53+
assertEquals(null, artifact.description()); }
54+
55+
@Test
56+
void testNewTextArtifactCreatesSingleTextPart() {
57+
// Given
58+
String text = "This is a text artifact.";
59+
String name = "Text_Artifact";
60+
61+
// When
62+
Artifact artifact = ArtifactUtils.newTextArtifact(name, text);
63+
64+
// Then
65+
assertEquals(1, artifact.parts().size());
66+
assertInstanceOf(TextPart.class, artifact.parts().get(0));
67+
}
68+
69+
@Test
70+
void testNewTextArtifactPartContainsProvidedText() {
71+
// Given
72+
String text = "Hello, world!";
73+
String name = "Greeting_Artifact";
74+
75+
// When
76+
Artifact artifact = ArtifactUtils.newTextArtifact(name, text);
77+
78+
// Then
79+
TextPart textPart = (TextPart) artifact.parts().get(0);
80+
assertEquals(text, textPart.getText());
81+
}
82+
83+
@Test
84+
void testNewTextArtifactAssignsNameDescription() {
85+
// Given
86+
String text = "Some content.";
87+
String name = "Named_Text_Artifact";
88+
String description = "Description for text artifact.";
89+
90+
// When
91+
Artifact artifact = ArtifactUtils.newTextArtifact(name, text, description);
92+
93+
// Then
94+
assertEquals(name, artifact.name());
95+
assertEquals(description, artifact.description());
96+
}
97+
98+
@Test
99+
void testNewDataArtifactCreatesSingleDataPart() {
100+
// Given
101+
Map<String, Object> sampleData = Map.of("key", "value", "number", 123);
102+
String name = "Data_Artifact";
103+
104+
// When
105+
Artifact artifact = ArtifactUtils.newDataArtifact(name, sampleData);
106+
107+
// Then
108+
assertEquals(1, artifact.parts().size());
109+
assertInstanceOf(DataPart.class, artifact.parts().get(0));
110+
}
111+
112+
@Test
113+
void testNewDataArtifactPartContainsProvidedData() {
114+
// Given
115+
Map<String, Object> sampleData = Map.of("content", "test_data", "is_valid", true);
116+
String name = "Structured_Data_Artifact";
117+
118+
// When
119+
Artifact artifact = ArtifactUtils.newDataArtifact(name, sampleData);
120+
121+
// Then
122+
DataPart dataPart = (DataPart) artifact.parts().get(0);
123+
assertEquals(sampleData, dataPart.getData());
124+
}
125+
126+
@Test
127+
void testNewDataArtifactAssignsNameDescription() {
128+
// Given
129+
Map<String, Object> sampleData = Map.of("info", "some details");
130+
String name = "Named_Data_Artifact";
131+
String description = "Description for data artifact.";
132+
133+
// When
134+
Artifact artifact = ArtifactUtils.newDataArtifact(name, sampleData, description);
135+
136+
// Then
137+
assertEquals(name, artifact.name());
138+
assertEquals(description, artifact.description());
139+
}
140+
141+
@Test
142+
void testArtifactIdIsNotNull() {
143+
// Given
144+
List<Part<?>> parts = List.of(new TextPart("Test"));
145+
String name = "Test_Artifact";
146+
147+
// When
148+
Artifact artifact = ArtifactUtils.newArtifact(name, parts);
149+
150+
// Then
151+
assertNotNull(artifact.artifactId());
152+
assertTrue(artifact.artifactId().length() > 0);
153+
}
154+
}

0 commit comments

Comments
 (0)