-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathintegration.test.ts
239 lines (205 loc) · 6.36 KB
/
integration.test.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
import { jest } from "@jest/globals";
import express from "express";
import {
A2AClient,
A2AServer,
InMemoryTaskStore,
TaskContext,
TaskYieldUpdate,
configureLogger,
} from "../src/index.js";
// Set a reasonable timeout for all tests
jest.setTimeout(10000);
configureLogger({ level: "silent" });
/**
* Simple echo task handler for testing
*/
async function* echoHandler(
context: TaskContext
): AsyncGenerator<TaskYieldUpdate, void, unknown> {
// Extract user text
const userText = context.userMessage.parts
.filter((part) => part.type === "text")
.map((part) => (part as any).text)
.join(" ");
// Send working status
yield {
state: "working",
message: {
role: "agent",
parts: [{ type: "text", text: "Processing..." }],
},
};
// Check cancellation
if (context.isCancelled()) {
yield {
state: "canceled",
message: {
role: "agent",
parts: [{ type: "text", text: "Task was canceled." }],
},
};
return;
}
// Create a response
const response = `You said: "${userText}"`;
// Create an artifact
yield {
name: "echo.txt",
parts: [{ type: "text", text: response }],
};
// Complete the task
yield {
state: "completed",
message: {
role: "agent",
parts: [{ type: "text", text: response }],
},
};
}
describe("Client-Server Integration Tests", () => {
let server: A2AServer;
let app: express.Express;
let expressServer: any;
let port: number;
let client: A2AClient;
beforeEach(async () => {
// Create a simple server
server = new A2AServer({
handler: echoHandler,
taskStore: new InMemoryTaskStore(),
port: 0,
});
app = server.start();
// Get the actual port
expressServer = app.listen(0);
port = (expressServer.address() as any).port;
// Create client
client = new A2AClient(`http://localhost:${port}`);
});
afterEach(async () => {
// Force close any open connections
return new Promise<void>((resolve) => {
// Close the express server gracefully
server.stop().then(() => {
// Allow some time for connections to fully close
setTimeout(resolve, 100);
});
expressServer.close(() => {
resolve();
});
});
});
test("client can retrieve agent card", async () => {
const card = await client.agentCard();
expect(card).toBeDefined();
expect(card.name).toBe("A2A Server");
expect(card.capabilities.streaming).toBe(true);
});
test("client can send task and get response", async () => {
const testMessage = "Hello, A2A!";
const task = await client.sendTask({
id: "test-task-1",
message: {
role: "user",
parts: [{ type: "text", text: testMessage }],
},
});
expect(task).toBeDefined();
expect(task!.id).toBe("test-task-1");
expect(task!.status.state).toBe("completed");
// Check if the response message contains our echo
const responseText = task!.status.message?.parts
.filter((part) => part.type === "text")
.map((part) => (part as any).text)
.join(" ");
expect(responseText).toContain(testMessage);
// Check if artifact was created
expect(task!.artifacts).toBeDefined();
expect(task!.artifacts!.length).toBe(1);
expect(task!.artifacts![0].name).toBe("echo.txt");
});
test("client can stream task updates", async () => {
const testMessage = "Test streaming";
const stream = client.sendTaskSubscribe({
id: "test-stream-task",
message: {
role: "user",
parts: [{ type: "text", text: testMessage }],
},
});
const updates: any[] = [];
for await (const update of stream) {
updates.push(update);
}
// We should have at least 3 updates:
// 1. "submitted" status (initial state from server)
// 2. "working" status (from our handler)
// 3. artifact
// 4. "completed" status
expect(updates.length).toBeGreaterThanOrEqual(3);
// First update should be "submitted" status
expect(updates[0].status?.state).toBe("submitted");
// Second update should be "working" status
if (updates.length > 1) {
expect(updates[1].status?.state).toBe("working");
}
// Check for artifact update
const artifactUpdate = updates.find((u) => u.artifact);
expect(artifactUpdate).toBeDefined();
expect(artifactUpdate.artifact.name).toBe("echo.txt");
// Last update should be "completed" status
const lastUpdate = updates[updates.length - 1];
expect(lastUpdate.status?.state).toBe("completed");
// Verify response text contains our message
const responseText = lastUpdate.status.message?.parts
.filter((part: any) => part.type === "text")
.map((part: any) => part.text)
.join(" ");
expect(responseText).toContain(testMessage);
});
test("client can cancel a task", async () => {
// First send a task to create it
const task = await client.sendTask({
id: "cancel-task-test",
message: {
role: "user",
parts: [{ type: "text", text: "Task to be canceled" }],
},
});
try {
// Now try to cancel it (note: in a real scenario this would need to be a long-running task)
const canceledTask = await client.cancelTask({
id: "cancel-task-test",
});
console.log("canceledTask", canceledTask);
expect(canceledTask).toBeDefined();
// Should be in canceled state
expect(canceledTask!.status.state).toBe("canceled");
} catch (error: any) {
// The task might complete too quickly to be canceled, resulting in a "cannot be canceled" error
// This is also a valid test scenario
// The error message should contain the task cannot be canceled text
// Not checking the exact error code because it may be wrapped in an internal error
expect(error.code).toBe(-32002);
expect(error.message).toContain("Task cannot be canceled");
}
});
test("client can get task by ID", async () => {
// First create a task
await client.sendTask({
id: "get-task-test",
message: {
role: "user",
parts: [{ type: "text", text: "Task to be retrieved" }],
},
});
// Now retrieve it
const task = await client.getTask({
id: "get-task-test",
});
expect(task).toBeDefined();
expect(task!.id).toBe("get-task-test");
expect(task!.status.state).toBe("completed");
});
});