Skip to content

Commit 8ea66e7

Browse files
committed
Tests for protocol version negotiation
Resolves #44.
1 parent 0a7461c commit 8ea66e7

File tree

2 files changed

+285
-6
lines changed

2 files changed

+285
-6
lines changed

src/client/index.test.ts

Lines changed: 122 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,130 @@
33
/* eslint-disable @typescript-eslint/no-unused-expressions */
44
import { Client } from "./index.js";
55
import { z } from "zod";
6-
import { RequestSchema, NotificationSchema, ResultSchema } from "../types.js";
6+
import {
7+
RequestSchema,
8+
NotificationSchema,
9+
ResultSchema,
10+
LATEST_PROTOCOL_VERSION,
11+
SUPPORTED_PROTOCOL_VERSIONS,
12+
} from "../types.js";
13+
import { Transport } from "../shared/transport.js";
14+
15+
test("should initialize with matching protocol version", async () => {
16+
const clientTransport: Transport = {
17+
start: jest.fn().mockResolvedValue(undefined),
18+
close: jest.fn().mockResolvedValue(undefined),
19+
send: jest.fn().mockImplementation((message) => {
20+
if (message.method === "initialize") {
21+
clientTransport.onmessage?.({
22+
jsonrpc: "2.0",
23+
id: message.id,
24+
result: {
25+
protocolVersion: LATEST_PROTOCOL_VERSION,
26+
capabilities: {},
27+
serverInfo: {
28+
name: "test",
29+
version: "1.0",
30+
},
31+
},
32+
});
33+
}
34+
return Promise.resolve();
35+
}),
36+
};
37+
38+
const client = new Client({
39+
name: "test client",
40+
version: "1.0",
41+
});
42+
43+
await client.connect(clientTransport);
44+
45+
// Should have sent initialize with latest version
46+
expect(clientTransport.send).toHaveBeenCalledWith(
47+
expect.objectContaining({
48+
method: "initialize",
49+
params: expect.objectContaining({
50+
protocolVersion: LATEST_PROTOCOL_VERSION,
51+
}),
52+
}),
53+
);
54+
});
55+
56+
test("should initialize with supported older protocol version", async () => {
57+
const OLD_VERSION = SUPPORTED_PROTOCOL_VERSIONS[1];
58+
const clientTransport: Transport = {
59+
start: jest.fn().mockResolvedValue(undefined),
60+
close: jest.fn().mockResolvedValue(undefined),
61+
send: jest.fn().mockImplementation((message) => {
62+
if (message.method === "initialize") {
63+
clientTransport.onmessage?.({
64+
jsonrpc: "2.0",
65+
id: message.id,
66+
result: {
67+
protocolVersion: OLD_VERSION,
68+
capabilities: {},
69+
serverInfo: {
70+
name: "test",
71+
version: "1.0",
72+
},
73+
},
74+
});
75+
}
76+
return Promise.resolve();
77+
}),
78+
};
79+
80+
const client = new Client({
81+
name: "test client",
82+
version: "1.0",
83+
});
84+
85+
await client.connect(clientTransport);
86+
87+
// Connection should succeed with the older version
88+
expect(client.getServerVersion()).toEqual({
89+
name: "test",
90+
version: "1.0",
91+
});
92+
});
93+
94+
test("should reject unsupported protocol version", async () => {
95+
const clientTransport: Transport = {
96+
start: jest.fn().mockResolvedValue(undefined),
97+
close: jest.fn().mockResolvedValue(undefined),
98+
send: jest.fn().mockImplementation((message) => {
99+
if (message.method === "initialize") {
100+
clientTransport.onmessage?.({
101+
jsonrpc: "2.0",
102+
id: message.id,
103+
result: {
104+
protocolVersion: "invalid-version",
105+
capabilities: {},
106+
serverInfo: {
107+
name: "test",
108+
version: "1.0",
109+
},
110+
},
111+
});
112+
}
113+
return Promise.resolve();
114+
}),
115+
};
116+
117+
const client = new Client({
118+
name: "test client",
119+
version: "1.0",
120+
});
121+
122+
await expect(client.connect(clientTransport)).rejects.toThrow(
123+
"Server's protocol version is not supported: invalid-version",
124+
);
125+
});
7126

8127
/*
9-
Test that custom request/notification/result schemas can be used with the Client class.
10-
*/
128+
Test that custom request/notification/result schemas can be used with the Client class.
129+
*/
11130
test("should typecheck", () => {
12131
const GetWeatherRequestSchema = RequestSchema.extend({
13132
method: z.literal("weather/get"),

src/server/index.test.ts

Lines changed: 163 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,171 @@
33
/* eslint-disable @typescript-eslint/no-unused-expressions */
44
import { Server } from "./index.js";
55
import { z } from "zod";
6-
import { RequestSchema, NotificationSchema, ResultSchema } from "../types.js";
6+
import {
7+
RequestSchema,
8+
NotificationSchema,
9+
ResultSchema,
10+
LATEST_PROTOCOL_VERSION,
11+
SUPPORTED_PROTOCOL_VERSIONS,
12+
InitializeRequestSchema,
13+
InitializeResultSchema,
14+
} from "../types.js";
15+
import { Transport } from "../shared/transport.js";
16+
17+
test("should accept latest protocol version", async () => {
18+
let sendPromiseResolve: (value: unknown) => void;
19+
const sendPromise = new Promise((resolve) => {
20+
sendPromiseResolve = resolve;
21+
});
22+
23+
const serverTransport: Transport = {
24+
start: jest.fn().mockResolvedValue(undefined),
25+
close: jest.fn().mockResolvedValue(undefined),
26+
send: jest.fn().mockImplementation((message) => {
27+
if (message.id === 1 && message.result) {
28+
expect(message.result).toEqual({
29+
protocolVersion: LATEST_PROTOCOL_VERSION,
30+
capabilities: expect.any(Object),
31+
serverInfo: {
32+
name: "test server",
33+
version: "1.0",
34+
},
35+
});
36+
sendPromiseResolve(undefined);
37+
}
38+
return Promise.resolve();
39+
}),
40+
};
41+
42+
const server = new Server({
43+
name: "test server",
44+
version: "1.0",
45+
});
46+
47+
await server.connect(serverTransport);
48+
49+
// Simulate initialize request with latest version
50+
serverTransport.onmessage?.({
51+
jsonrpc: "2.0",
52+
id: 1,
53+
method: "initialize",
54+
params: {
55+
protocolVersion: LATEST_PROTOCOL_VERSION,
56+
capabilities: {},
57+
clientInfo: {
58+
name: "test client",
59+
version: "1.0",
60+
},
61+
},
62+
});
63+
64+
await expect(sendPromise).resolves.toBeUndefined();
65+
});
66+
67+
test("should accept supported older protocol version", async () => {
68+
const OLD_VERSION = SUPPORTED_PROTOCOL_VERSIONS[1];
69+
let sendPromiseResolve: (value: unknown) => void;
70+
const sendPromise = new Promise((resolve) => {
71+
sendPromiseResolve = resolve;
72+
});
73+
74+
const serverTransport: Transport = {
75+
start: jest.fn().mockResolvedValue(undefined),
76+
close: jest.fn().mockResolvedValue(undefined),
77+
send: jest.fn().mockImplementation((message) => {
78+
if (message.id === 1 && message.result) {
79+
expect(message.result).toEqual({
80+
protocolVersion: OLD_VERSION,
81+
capabilities: expect.any(Object),
82+
serverInfo: {
83+
name: "test server",
84+
version: "1.0",
85+
},
86+
});
87+
sendPromiseResolve(undefined);
88+
}
89+
return Promise.resolve();
90+
}),
91+
};
92+
93+
const server = new Server({
94+
name: "test server",
95+
version: "1.0",
96+
});
97+
98+
await server.connect(serverTransport);
99+
100+
// Simulate initialize request with older version
101+
serverTransport.onmessage?.({
102+
jsonrpc: "2.0",
103+
id: 1,
104+
method: "initialize",
105+
params: {
106+
protocolVersion: OLD_VERSION,
107+
capabilities: {},
108+
clientInfo: {
109+
name: "test client",
110+
version: "1.0",
111+
},
112+
},
113+
});
114+
115+
await expect(sendPromise).resolves.toBeUndefined();
116+
});
117+
118+
test("should handle unsupported protocol version", async () => {
119+
let sendPromiseResolve: (value: unknown) => void;
120+
const sendPromise = new Promise((resolve) => {
121+
sendPromiseResolve = resolve;
122+
});
123+
124+
const serverTransport: Transport = {
125+
start: jest.fn().mockResolvedValue(undefined),
126+
close: jest.fn().mockResolvedValue(undefined),
127+
send: jest.fn().mockImplementation((message) => {
128+
if (message.id === 1 && message.result) {
129+
expect(message.result).toEqual({
130+
protocolVersion: LATEST_PROTOCOL_VERSION,
131+
capabilities: expect.any(Object),
132+
serverInfo: {
133+
name: "test server",
134+
version: "1.0",
135+
},
136+
});
137+
sendPromiseResolve(undefined);
138+
}
139+
return Promise.resolve();
140+
}),
141+
};
142+
143+
const server = new Server({
144+
name: "test server",
145+
version: "1.0",
146+
});
147+
148+
await server.connect(serverTransport);
149+
150+
// Simulate initialize request with unsupported version
151+
serverTransport.onmessage?.({
152+
jsonrpc: "2.0",
153+
id: 1,
154+
method: "initialize",
155+
params: {
156+
protocolVersion: "invalid-version",
157+
capabilities: {},
158+
clientInfo: {
159+
name: "test client",
160+
version: "1.0",
161+
},
162+
},
163+
});
164+
165+
await expect(sendPromise).resolves.toBeUndefined();
166+
});
7167

8168
/*
9-
Test that custom request/notification/result schemas can be used with the Server class.
10-
*/
169+
Test that custom request/notification/result schemas can be used with the Server class.
170+
*/
11171
test("should typecheck", () => {
12172
const GetWeatherRequestSchema = RequestSchema.extend({
13173
method: z.literal("weather/get"),

0 commit comments

Comments
 (0)