-
Notifications
You must be signed in to change notification settings - Fork 7
Expand file tree
/
Copy pathserver.e2e.test.ts
More file actions
158 lines (137 loc) · 5 KB
/
server.e2e.test.ts
File metadata and controls
158 lines (137 loc) · 5 KB
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
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
import { existsSync } from "fs";
import { resolve } from "path";
const AGENT_DIR = resolve(import.meta.dir);
const OUTPUT_DIR = resolve(AGENT_DIR, ".aixyz/output");
const SERVER_JS = resolve(OUTPUT_DIR, "server.js");
const PORT = "19879";
const BASE_URL = `http://localhost:${PORT}`;
let serverProcess: ReturnType<typeof Bun.spawn> | null = null;
/**
* Parse an SSE (Server-Sent Events) response body and return all data payloads as parsed JSON.
*/
function parseSseData(text: string): unknown[] {
return text
.split("\n")
.filter((line) => line.startsWith("data: "))
.map((line) => JSON.parse(line.slice("data: ".length)));
}
beforeAll(async () => {
// Build the standalone output from the with-tests example
const build = Bun.spawnSync(["bun", "run", "build"], {
cwd: AGENT_DIR,
stdout: "inherit",
stderr: "inherit",
});
if (build.exitCode !== 0) {
throw new Error("standalone build failed");
}
// Start the built server from the output directory so loadEnvConfig picks up the copied .env files.
// Exclude TEST_ENV_VAR from the inherited env to verify it is loaded from .env at runtime.
const { TEST_ENV_VAR: _, ...envWithout } = process.env;
serverProcess = Bun.spawn(["bun", SERVER_JS], {
cwd: OUTPUT_DIR,
env: { ...envWithout, PORT },
stdout: "inherit",
stderr: "inherit",
});
// Wait for the agent card endpoint to become available (up to 30 s)
for (let i = 0; i < 30; i++) {
await Bun.sleep(1000);
try {
const res = await fetch(`${BASE_URL}/.well-known/agent-card.json`);
if (res.ok) break;
} catch {
// Server not ready yet
}
}
}, 60_000);
afterAll(async () => {
if (serverProcess) {
serverProcess.kill();
await serverProcess.exited;
}
});
describe("standalone output e2e", () => {
test("build produces expected output files", () => {
expect(existsSync(SERVER_JS)).toBe(true);
expect(existsSync(resolve(OUTPUT_DIR, "package.json"))).toBe(true);
expect(existsSync(resolve(OUTPUT_DIR, ".env"))).toBe(true);
});
test("index page responds with agent info", async () => {
const res = await fetch(`${BASE_URL}/`);
expect(res.ok).toBe(true);
const text = await res.text();
expect(text).toContain("Unit Conversion Agent");
});
test("A2A agent card endpoint returns correct data", async () => {
const res = await fetch(`${BASE_URL}/.well-known/agent-card.json`);
expect(res.ok).toBe(true);
expect(res.headers.get("content-type")).toContain("application/json");
const card = (await res.json()) as Record<string, unknown>;
expect(card.name).toBe("Unit Conversion Agent");
expect(card.protocolVersion).toBe("0.3.0");
expect(card.url).toContain("/agent");
expect(card.capabilities).toBeDefined();
});
test("MCP endpoint responds to initialize request", async () => {
const res = await fetch(`${BASE_URL}/mcp`, {
method: "POST",
headers: {
"Content-Type": "application/json",
// StreamableHTTPServerTransport requires both media types in Accept
Accept: "application/json, text/event-stream",
},
body: JSON.stringify({
jsonrpc: "2.0",
method: "initialize",
params: {
protocolVersion: "2025-03-26",
capabilities: {},
clientInfo: { name: "test-client", version: "1.0.0" },
},
id: 1,
}),
});
expect(res.ok).toBe(true);
// The transport responds with SSE; extract the data payload
const text = await res.text();
const [message] = parseSseData(text) as Array<Record<string, unknown>>;
expect(message).toBeDefined();
expect(message.jsonrpc).toBe("2.0");
expect(message.id).toBe(1);
const result = message.result as Record<string, unknown>;
expect(result).toBeDefined();
expect(result.protocolVersion).toBeDefined();
const serverInfo = result.serverInfo as Record<string, unknown>;
expect(serverInfo.name).toBe("Unit Conversion Agent");
expect(serverInfo.version).toBe("0.1.0");
});
test("runtime loads .env files via loadEnvConfig", async () => {
// Call echo-env tool directly — the stateless MCP transport creates a fresh server per request.
const res = await fetch(`${BASE_URL}/mcp`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json, text/event-stream",
},
body: JSON.stringify({
jsonrpc: "2.0",
method: "tools/call",
params: {
name: "echo-env",
arguments: {},
},
id: 1,
}),
});
expect(res.ok).toBe(true);
const text = await res.text();
const [message] = parseSseData(text) as Array<Record<string, unknown>>;
expect(message).toBeDefined();
expect(message.id).toBe(1);
const result = message.result as { content: Array<{ type: string; text: string }> };
const content = JSON.parse(result.content[0].text);
expect(content.value).toBe("hello-agent");
});
});