Tests for protocol version negotiation

Resolves #44.
This commit is contained in:
Justin Spahr-Summers 2024-11-11 22:05:13 +00:00
commit 8ea66e7df9
No known key found for this signature in database
2 changed files with 285 additions and 6 deletions

View file

@ -3,11 +3,130 @@
/* eslint-disable @typescript-eslint/no-unused-expressions */
import { Client } from "./index.js";
import { z } from "zod";
import { RequestSchema, NotificationSchema, ResultSchema } from "../types.js";
import {
RequestSchema,
NotificationSchema,
ResultSchema,
LATEST_PROTOCOL_VERSION,
SUPPORTED_PROTOCOL_VERSIONS,
} from "../types.js";
import { Transport } from "../shared/transport.js";
test("should initialize with matching protocol version", async () => {
const clientTransport: Transport = {
start: jest.fn().mockResolvedValue(undefined),
close: jest.fn().mockResolvedValue(undefined),
send: jest.fn().mockImplementation((message) => {
if (message.method === "initialize") {
clientTransport.onmessage?.({
jsonrpc: "2.0",
id: message.id,
result: {
protocolVersion: LATEST_PROTOCOL_VERSION,
capabilities: {},
serverInfo: {
name: "test",
version: "1.0",
},
},
});
}
return Promise.resolve();
}),
};
const client = new Client({
name: "test client",
version: "1.0",
});
await client.connect(clientTransport);
// Should have sent initialize with latest version
expect(clientTransport.send).toHaveBeenCalledWith(
expect.objectContaining({
method: "initialize",
params: expect.objectContaining({
protocolVersion: LATEST_PROTOCOL_VERSION,
}),
}),
);
});
test("should initialize with supported older protocol version", async () => {
const OLD_VERSION = SUPPORTED_PROTOCOL_VERSIONS[1];
const clientTransport: Transport = {
start: jest.fn().mockResolvedValue(undefined),
close: jest.fn().mockResolvedValue(undefined),
send: jest.fn().mockImplementation((message) => {
if (message.method === "initialize") {
clientTransport.onmessage?.({
jsonrpc: "2.0",
id: message.id,
result: {
protocolVersion: OLD_VERSION,
capabilities: {},
serverInfo: {
name: "test",
version: "1.0",
},
},
});
}
return Promise.resolve();
}),
};
const client = new Client({
name: "test client",
version: "1.0",
});
await client.connect(clientTransport);
// Connection should succeed with the older version
expect(client.getServerVersion()).toEqual({
name: "test",
version: "1.0",
});
});
test("should reject unsupported protocol version", async () => {
const clientTransport: Transport = {
start: jest.fn().mockResolvedValue(undefined),
close: jest.fn().mockResolvedValue(undefined),
send: jest.fn().mockImplementation((message) => {
if (message.method === "initialize") {
clientTransport.onmessage?.({
jsonrpc: "2.0",
id: message.id,
result: {
protocolVersion: "invalid-version",
capabilities: {},
serverInfo: {
name: "test",
version: "1.0",
},
},
});
}
return Promise.resolve();
}),
};
const client = new Client({
name: "test client",
version: "1.0",
});
await expect(client.connect(clientTransport)).rejects.toThrow(
"Server's protocol version is not supported: invalid-version",
);
});
/*
Test that custom request/notification/result schemas can be used with the Client class.
*/
Test that custom request/notification/result schemas can be used with the Client class.
*/
test("should typecheck", () => {
const GetWeatherRequestSchema = RequestSchema.extend({
method: z.literal("weather/get"),

View file

@ -3,11 +3,171 @@
/* eslint-disable @typescript-eslint/no-unused-expressions */
import { Server } from "./index.js";
import { z } from "zod";
import { RequestSchema, NotificationSchema, ResultSchema } from "../types.js";
import {
RequestSchema,
NotificationSchema,
ResultSchema,
LATEST_PROTOCOL_VERSION,
SUPPORTED_PROTOCOL_VERSIONS,
InitializeRequestSchema,
InitializeResultSchema,
} from "../types.js";
import { Transport } from "../shared/transport.js";
test("should accept latest protocol version", async () => {
let sendPromiseResolve: (value: unknown) => void;
const sendPromise = new Promise((resolve) => {
sendPromiseResolve = resolve;
});
const serverTransport: Transport = {
start: jest.fn().mockResolvedValue(undefined),
close: jest.fn().mockResolvedValue(undefined),
send: jest.fn().mockImplementation((message) => {
if (message.id === 1 && message.result) {
expect(message.result).toEqual({
protocolVersion: LATEST_PROTOCOL_VERSION,
capabilities: expect.any(Object),
serverInfo: {
name: "test server",
version: "1.0",
},
});
sendPromiseResolve(undefined);
}
return Promise.resolve();
}),
};
const server = new Server({
name: "test server",
version: "1.0",
});
await server.connect(serverTransport);
// Simulate initialize request with latest version
serverTransport.onmessage?.({
jsonrpc: "2.0",
id: 1,
method: "initialize",
params: {
protocolVersion: LATEST_PROTOCOL_VERSION,
capabilities: {},
clientInfo: {
name: "test client",
version: "1.0",
},
},
});
await expect(sendPromise).resolves.toBeUndefined();
});
test("should accept supported older protocol version", async () => {
const OLD_VERSION = SUPPORTED_PROTOCOL_VERSIONS[1];
let sendPromiseResolve: (value: unknown) => void;
const sendPromise = new Promise((resolve) => {
sendPromiseResolve = resolve;
});
const serverTransport: Transport = {
start: jest.fn().mockResolvedValue(undefined),
close: jest.fn().mockResolvedValue(undefined),
send: jest.fn().mockImplementation((message) => {
if (message.id === 1 && message.result) {
expect(message.result).toEqual({
protocolVersion: OLD_VERSION,
capabilities: expect.any(Object),
serverInfo: {
name: "test server",
version: "1.0",
},
});
sendPromiseResolve(undefined);
}
return Promise.resolve();
}),
};
const server = new Server({
name: "test server",
version: "1.0",
});
await server.connect(serverTransport);
// Simulate initialize request with older version
serverTransport.onmessage?.({
jsonrpc: "2.0",
id: 1,
method: "initialize",
params: {
protocolVersion: OLD_VERSION,
capabilities: {},
clientInfo: {
name: "test client",
version: "1.0",
},
},
});
await expect(sendPromise).resolves.toBeUndefined();
});
test("should handle unsupported protocol version", async () => {
let sendPromiseResolve: (value: unknown) => void;
const sendPromise = new Promise((resolve) => {
sendPromiseResolve = resolve;
});
const serverTransport: Transport = {
start: jest.fn().mockResolvedValue(undefined),
close: jest.fn().mockResolvedValue(undefined),
send: jest.fn().mockImplementation((message) => {
if (message.id === 1 && message.result) {
expect(message.result).toEqual({
protocolVersion: LATEST_PROTOCOL_VERSION,
capabilities: expect.any(Object),
serverInfo: {
name: "test server",
version: "1.0",
},
});
sendPromiseResolve(undefined);
}
return Promise.resolve();
}),
};
const server = new Server({
name: "test server",
version: "1.0",
});
await server.connect(serverTransport);
// Simulate initialize request with unsupported version
serverTransport.onmessage?.({
jsonrpc: "2.0",
id: 1,
method: "initialize",
params: {
protocolVersion: "invalid-version",
capabilities: {},
clientInfo: {
name: "test client",
version: "1.0",
},
},
});
await expect(sendPromise).resolves.toBeUndefined();
});
/*
Test that custom request/notification/result schemas can be used with the Server class.
*/
Test that custom request/notification/result schemas can be used with the Server class.
*/
test("should typecheck", () => {
const GetWeatherRequestSchema = RequestSchema.extend({
method: z.literal("weather/get"),