Merge pull request #560 from daidi/main

fix: Ensure _meta object is not lost when onprogress option is passed
This commit is contained in:
ihrpr 2025-05-29 15:02:51 +01:00 • committed by GitHub
commit b2b426fb67
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 130 additions and 1 deletions

View file

@ -27,9 +27,11 @@ class MockTransport implements Transport {
describe("protocol tests", () => {
let protocol: Protocol<Request, Notification, Result>;
let transport: MockTransport;
let sendSpy: jest.SpyInstance;
beforeEach(() => {
transport = new MockTransport();
sendSpy = jest.spyOn(transport, 'send');
protocol = new (class extends Protocol<Request, Notification, Result> {
protected assertCapabilityForMethod(): void {}
protected assertNotificationCapability(): void {}
@ -63,6 +65,130 @@ describe("protocol tests", () => {
expect(oncloseMock).toHaveBeenCalled();
});
describe("_meta preservation with onprogress", () => {
test("should preserve existing _meta when adding progressToken", async () => {
await protocol.connect(transport);
const request = {
method: "example",
params: {
data: "test",
_meta: {
customField: "customValue",
anotherField: 123
}
}
};
const mockSchema: ZodType<{ result: string }> = z.object({
result: z.string(),
});
const onProgressMock = jest.fn();
protocol.request(request, mockSchema, {
onprogress: onProgressMock,
});
expect(sendSpy).toHaveBeenCalledWith(expect.objectContaining({
method: "example",
params: {
data: "test",
_meta: {
customField: "customValue",
anotherField: 123,
progressToken: expect.any(Number)
}
},
jsonrpc: "2.0",
id: expect.any(Number)
}), expect.any(Object));
});
test("should create _meta with progressToken when no _meta exists", async () => {
await protocol.connect(transport);
const request = {
method: "example",
params: {
data: "test"
}
};
const mockSchema: ZodType<{ result: string }> = z.object({
result: z.string(),
});
const onProgressMock = jest.fn();
protocol.request(request, mockSchema, {
onprogress: onProgressMock,
});
expect(sendSpy).toHaveBeenCalledWith(expect.objectContaining({
method: "example",
params: {
data: "test",
_meta: {
progressToken: expect.any(Number)
}
},
jsonrpc: "2.0",
id: expect.any(Number)
}), expect.any(Object));
});
test("should not modify _meta when onprogress is not provided", async () => {
await protocol.connect(transport);
const request = {
method: "example",
params: {
data: "test",
_meta: {
customField: "customValue"
}
}
};
const mockSchema: ZodType<{ result: string }> = z.object({
result: z.string(),
});
protocol.request(request, mockSchema);
expect(sendSpy).toHaveBeenCalledWith(expect.objectContaining({
method: "example",
params: {
data: "test",
_meta: {
customField: "customValue"
}
},
jsonrpc: "2.0",
id: expect.any(Number)
}), expect.any(Object));
});
test("should handle params being undefined with onprogress", async () => {
await protocol.connect(transport);
const request = {
method: "example"
};
const mockSchema: ZodType<{ result: string }> = z.object({
result: z.string(),
});
const onProgressMock = jest.fn();
protocol.request(request, mockSchema, {
onprogress: onProgressMock,
});
expect(sendSpy).toHaveBeenCalledWith(expect.objectContaining({
method: "example",
params: {
_meta: {
progressToken: expect.any(Number)
}
},
jsonrpc: "2.0",
id: expect.any(Number)
}), expect.any(Object));
});
});
describe("progress notification timeout behavior", () => {
beforeEach(() => {
jest.useFakeTimers();

View file

@ -541,7 +541,10 @@ export abstract class Protocol<
this._progressHandlers.set(messageId, options.onprogress);
jsonrpcRequest.params = {
...request.params,
_meta: { progressToken: messageId },
_meta: {
...(request.params?._meta || {}),
progressToken: messageId
},
};
}