Skip to content

Commit 5c07636

Browse files
Merge pull request #152 from Ozamatash/feature/progress-timeout-reset
Add timeout reset on progress notifications
2 parents d5906b7 + b09240b commit 5c07636

File tree

2 files changed

+270
-50
lines changed

2 files changed

+270
-50
lines changed

src/shared/protocol.test.ts

+156
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,162 @@ describe("protocol tests", () => {
6262
await transport.close();
6363
expect(oncloseMock).toHaveBeenCalled();
6464
});
65+
66+
describe("progress notification timeout behavior", () => {
67+
beforeEach(() => {
68+
jest.useFakeTimers();
69+
});
70+
afterEach(() => {
71+
jest.useRealTimers();
72+
});
73+
74+
test("should reset timeout when progress notification is received", async () => {
75+
await protocol.connect(transport);
76+
const request = { method: "example", params: {} };
77+
const mockSchema: ZodType<{ result: string }> = z.object({
78+
result: z.string(),
79+
});
80+
const onProgressMock = jest.fn();
81+
const requestPromise = protocol.request(request, mockSchema, {
82+
timeout: 1000,
83+
resetTimeoutOnProgress: true,
84+
onprogress: onProgressMock,
85+
});
86+
jest.advanceTimersByTime(800);
87+
if (transport.onmessage) {
88+
transport.onmessage({
89+
jsonrpc: "2.0",
90+
method: "notifications/progress",
91+
params: {
92+
progressToken: 0,
93+
progress: 50,
94+
total: 100,
95+
},
96+
});
97+
}
98+
await Promise.resolve();
99+
expect(onProgressMock).toHaveBeenCalledWith({
100+
progress: 50,
101+
total: 100,
102+
});
103+
jest.advanceTimersByTime(800);
104+
if (transport.onmessage) {
105+
transport.onmessage({
106+
jsonrpc: "2.0",
107+
id: 0,
108+
result: { result: "success" },
109+
});
110+
}
111+
await Promise.resolve();
112+
await expect(requestPromise).resolves.toEqual({ result: "success" });
113+
});
114+
115+
test("should respect maxTotalTimeout", async () => {
116+
await protocol.connect(transport);
117+
const request = { method: "example", params: {} };
118+
const mockSchema: ZodType<{ result: string }> = z.object({
119+
result: z.string(),
120+
});
121+
const onProgressMock = jest.fn();
122+
const requestPromise = protocol.request(request, mockSchema, {
123+
timeout: 1000,
124+
maxTotalTimeout: 150,
125+
resetTimeoutOnProgress: true,
126+
onprogress: onProgressMock,
127+
});
128+
129+
// First progress notification should work
130+
jest.advanceTimersByTime(80);
131+
if (transport.onmessage) {
132+
transport.onmessage({
133+
jsonrpc: "2.0",
134+
method: "notifications/progress",
135+
params: {
136+
progressToken: 0,
137+
progress: 50,
138+
total: 100,
139+
},
140+
});
141+
}
142+
await Promise.resolve();
143+
expect(onProgressMock).toHaveBeenCalledWith({
144+
progress: 50,
145+
total: 100,
146+
});
147+
jest.advanceTimersByTime(80);
148+
if (transport.onmessage) {
149+
transport.onmessage({
150+
jsonrpc: "2.0",
151+
method: "notifications/progress",
152+
params: {
153+
progressToken: 0,
154+
progress: 75,
155+
total: 100,
156+
},
157+
});
158+
}
159+
await expect(requestPromise).rejects.toThrow("Maximum total timeout exceeded");
160+
expect(onProgressMock).toHaveBeenCalledTimes(1);
161+
});
162+
163+
test("should timeout if no progress received within timeout period", async () => {
164+
await protocol.connect(transport);
165+
const request = { method: "example", params: {} };
166+
const mockSchema: ZodType<{ result: string }> = z.object({
167+
result: z.string(),
168+
});
169+
const requestPromise = protocol.request(request, mockSchema, {
170+
timeout: 100,
171+
resetTimeoutOnProgress: true,
172+
});
173+
jest.advanceTimersByTime(101);
174+
await expect(requestPromise).rejects.toThrow("Request timed out");
175+
});
176+
177+
test("should handle multiple progress notifications correctly", async () => {
178+
await protocol.connect(transport);
179+
const request = { method: "example", params: {} };
180+
const mockSchema: ZodType<{ result: string }> = z.object({
181+
result: z.string(),
182+
});
183+
const onProgressMock = jest.fn();
184+
const requestPromise = protocol.request(request, mockSchema, {
185+
timeout: 1000,
186+
resetTimeoutOnProgress: true,
187+
onprogress: onProgressMock,
188+
});
189+
190+
// Simulate multiple progress updates
191+
for (let i = 1; i <= 3; i++) {
192+
jest.advanceTimersByTime(800);
193+
if (transport.onmessage) {
194+
transport.onmessage({
195+
jsonrpc: "2.0",
196+
method: "notifications/progress",
197+
params: {
198+
progressToken: 0,
199+
progress: i * 25,
200+
total: 100,
201+
},
202+
});
203+
}
204+
await Promise.resolve();
205+
expect(onProgressMock).toHaveBeenNthCalledWith(i, {
206+
progress: i * 25,
207+
total: 100,
208+
});
209+
}
210+
if (transport.onmessage) {
211+
transport.onmessage({
212+
jsonrpc: "2.0",
213+
id: 0,
214+
result: { result: "success" },
215+
});
216+
}
217+
await Promise.resolve();
218+
await expect(requestPromise).resolves.toEqual({ result: "success" });
219+
});
220+
});
65221
});
66222

67223
describe("mergeCapabilities", () => {

0 commit comments

Comments
 (0)