Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/base/test/common/jsonRpcProtocol.test.ts
13397 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import assert from 'assert';
7
import { DeferredPromise } from '../../common/async.js';
8
import { CancellationToken, CancellationTokenSource } from '../../common/cancellation.js';
9
import { CancellationError } from '../../common/errors.js';
10
import { IJsonRpcNotification, IJsonRpcProtocolHandlers, IJsonRpcRequest, JsonRpcError, JsonRpcMessage, JsonRpcProtocol } from '../../common/jsonRpcProtocol.js';
11
import { ensureNoDisposablesAreLeakedInTestSuite } from './utils.js';
12
13
suite('JsonRpcProtocol', () => {
14
15
const store = ensureNoDisposablesAreLeakedInTestSuite();
16
17
const createProtocol = (handlers: IJsonRpcProtocolHandlers = {}) => {
18
const sentMessages: JsonRpcMessage[] = [];
19
const protocol = new JsonRpcProtocol(message => sentMessages.push(message), handlers);
20
store.add(protocol);
21
return { protocol, sentMessages };
22
};
23
24
test('sendNotification adds jsonrpc envelope', () => {
25
const { protocol, sentMessages } = createProtocol();
26
27
protocol.sendNotification({ method: 'notify', params: { value: 1 } });
28
29
assert.deepStrictEqual(sentMessages, [{
30
jsonrpc: '2.0',
31
method: 'notify',
32
params: { value: 1 }
33
}]);
34
});
35
36
test('sendRequest resolves on success response', async () => {
37
const { protocol, sentMessages } = createProtocol();
38
39
const requestPromise = protocol.sendRequest<string>({ method: 'echo', params: { value: 'ok' } });
40
const outgoingRequest = sentMessages[0] as IJsonRpcRequest;
41
42
const replies = await protocol.handleMessage({
43
jsonrpc: '2.0',
44
id: outgoingRequest.id,
45
result: 'done'
46
});
47
48
const result = await requestPromise;
49
assert.strictEqual(result, 'done');
50
assert.deepStrictEqual(replies, []);
51
});
52
53
test('sendRequest rejects on error response', async () => {
54
const { protocol, sentMessages } = createProtocol();
55
56
const requestPromise = protocol.sendRequest({ method: 'fail' });
57
const outgoingRequest = sentMessages[0] as IJsonRpcRequest;
58
59
await protocol.handleMessage({
60
jsonrpc: '2.0',
61
id: outgoingRequest.id,
62
error: {
63
code: 123,
64
message: 'Failure',
65
data: { source: 'test' }
66
}
67
});
68
69
await assert.rejects(requestPromise, error => {
70
assert.ok(error instanceof JsonRpcError);
71
assert.strictEqual(error.code, 123);
72
assert.strictEqual(error.message, 'Failure');
73
assert.deepStrictEqual(error.data, { source: 'test' });
74
return true;
75
});
76
});
77
78
test('sendRequest honors cancellation token and invokes onCancel', async () => {
79
const { protocol, sentMessages } = createProtocol();
80
const cts = new CancellationTokenSource();
81
let canceledId: string | number | undefined;
82
83
const requestPromise = protocol.sendRequest(
84
{ method: 'cancel-me' },
85
cts.token,
86
id => canceledId = id,
87
);
88
const outgoingRequest = sentMessages[0] as IJsonRpcRequest;
89
90
cts.cancel();
91
92
await assert.rejects(requestPromise, error => error instanceof CancellationError);
93
assert.strictEqual(canceledId, outgoingRequest.id);
94
95
cts.dispose(true);
96
});
97
98
test('cancelPendingRequest rejects active request', async () => {
99
const { protocol, sentMessages } = createProtocol();
100
101
const requestPromise = protocol.sendRequest({ method: 'pending' });
102
const outgoingRequest = sentMessages[0] as IJsonRpcRequest;
103
protocol.cancelPendingRequest(outgoingRequest.id);
104
105
await assert.rejects(requestPromise, error => error instanceof CancellationError);
106
});
107
108
test('handleRequest responds with method not found without handler', async () => {
109
const { protocol, sentMessages } = createProtocol();
110
111
const replies = await protocol.handleMessage({
112
jsonrpc: '2.0',
113
id: 7,
114
method: 'unknown'
115
});
116
117
const expected = [{
118
jsonrpc: '2.0',
119
id: 7,
120
error: {
121
code: -32601,
122
message: 'Method not found: unknown'
123
}
124
}];
125
assert.deepStrictEqual(sentMessages, expected);
126
assert.deepStrictEqual(replies, expected);
127
});
128
129
test('handleRequest responds with result and passes cancellation token', async () => {
130
let receivedToken: CancellationToken | undefined;
131
let wasCanceledDuringHandler: boolean | undefined;
132
const { protocol, sentMessages } = createProtocol({
133
handleRequest: async (request, token) => {
134
receivedToken = token;
135
wasCanceledDuringHandler = token.isCancellationRequested;
136
return `${request.method}:ok`;
137
}
138
});
139
140
const replies = await protocol.handleMessage({
141
jsonrpc: '2.0',
142
id: 9,
143
method: 'compute'
144
});
145
146
assert.ok(receivedToken);
147
assert.strictEqual(wasCanceledDuringHandler, false);
148
const expected = [{
149
jsonrpc: '2.0',
150
id: 9,
151
result: 'compute:ok'
152
}];
153
assert.deepStrictEqual(sentMessages, expected);
154
assert.deepStrictEqual(replies, expected);
155
});
156
157
test('handleRequest serializes JsonRpcError and returns it', async () => {
158
const { protocol, sentMessages } = createProtocol({
159
handleRequest: () => {
160
throw new JsonRpcError(88, 'bad request', { detail: true });
161
}
162
});
163
164
const replies = await protocol.handleMessage({
165
jsonrpc: '2.0',
166
id: 'a',
167
method: 'boom'
168
});
169
170
const expected = [{
171
jsonrpc: '2.0',
172
id: 'a',
173
error: {
174
code: 88,
175
message: 'bad request',
176
data: { detail: true }
177
}
178
}];
179
assert.deepStrictEqual(sentMessages, expected);
180
assert.deepStrictEqual(replies, expected);
181
});
182
183
test('handleRequest maps unknown errors to internal error and returns it', async () => {
184
const { protocol, sentMessages } = createProtocol({
185
handleRequest: () => {
186
throw new Error('unexpected');
187
}
188
});
189
190
const replies = await protocol.handleMessage({
191
jsonrpc: '2.0',
192
id: 'b',
193
method: 'explode'
194
});
195
196
const expected = [{
197
jsonrpc: '2.0',
198
id: 'b',
199
error: {
200
code: -32603,
201
message: 'unexpected'
202
}
203
}];
204
assert.deepStrictEqual(sentMessages, expected);
205
assert.deepStrictEqual(replies, expected);
206
});
207
208
test('handleMessage processes batch sequentially', async () => {
209
const sequence: string[] = [];
210
const gate = new DeferredPromise<void>();
211
const { protocol } = createProtocol({
212
handleRequest: async () => {
213
sequence.push('request:start');
214
await gate.p;
215
sequence.push('request:end');
216
return true;
217
},
218
handleNotification: () => {
219
sequence.push('notification');
220
}
221
});
222
223
const request: IJsonRpcRequest = {
224
jsonrpc: '2.0',
225
id: 1,
226
method: 'first'
227
};
228
const notification: IJsonRpcNotification = {
229
jsonrpc: '2.0',
230
method: 'second'
231
};
232
233
const handlingPromise = protocol.handleMessage([request, notification]);
234
assert.deepStrictEqual(sequence, ['request:start']);
235
236
gate.complete();
237
const replies = await handlingPromise;
238
239
assert.deepStrictEqual(sequence, ['request:start', 'request:end', 'notification']);
240
assert.deepStrictEqual(replies, [{ jsonrpc: '2.0', id: 1, result: true }]);
241
});
242
});
243
244