Path: blob/main/src/vs/base/test/common/jsonRpcProtocol.test.ts
13397 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import assert from 'assert';6import { DeferredPromise } from '../../common/async.js';7import { CancellationToken, CancellationTokenSource } from '../../common/cancellation.js';8import { CancellationError } from '../../common/errors.js';9import { IJsonRpcNotification, IJsonRpcProtocolHandlers, IJsonRpcRequest, JsonRpcError, JsonRpcMessage, JsonRpcProtocol } from '../../common/jsonRpcProtocol.js';10import { ensureNoDisposablesAreLeakedInTestSuite } from './utils.js';1112suite('JsonRpcProtocol', () => {1314const store = ensureNoDisposablesAreLeakedInTestSuite();1516const createProtocol = (handlers: IJsonRpcProtocolHandlers = {}) => {17const sentMessages: JsonRpcMessage[] = [];18const protocol = new JsonRpcProtocol(message => sentMessages.push(message), handlers);19store.add(protocol);20return { protocol, sentMessages };21};2223test('sendNotification adds jsonrpc envelope', () => {24const { protocol, sentMessages } = createProtocol();2526protocol.sendNotification({ method: 'notify', params: { value: 1 } });2728assert.deepStrictEqual(sentMessages, [{29jsonrpc: '2.0',30method: 'notify',31params: { value: 1 }32}]);33});3435test('sendRequest resolves on success response', async () => {36const { protocol, sentMessages } = createProtocol();3738const requestPromise = protocol.sendRequest<string>({ method: 'echo', params: { value: 'ok' } });39const outgoingRequest = sentMessages[0] as IJsonRpcRequest;4041const replies = await protocol.handleMessage({42jsonrpc: '2.0',43id: outgoingRequest.id,44result: 'done'45});4647const result = await requestPromise;48assert.strictEqual(result, 'done');49assert.deepStrictEqual(replies, []);50});5152test('sendRequest rejects on error response', async () => {53const { protocol, sentMessages } = createProtocol();5455const requestPromise = protocol.sendRequest({ method: 'fail' });56const outgoingRequest = sentMessages[0] as IJsonRpcRequest;5758await protocol.handleMessage({59jsonrpc: '2.0',60id: outgoingRequest.id,61error: {62code: 123,63message: 'Failure',64data: { source: 'test' }65}66});6768await assert.rejects(requestPromise, error => {69assert.ok(error instanceof JsonRpcError);70assert.strictEqual(error.code, 123);71assert.strictEqual(error.message, 'Failure');72assert.deepStrictEqual(error.data, { source: 'test' });73return true;74});75});7677test('sendRequest honors cancellation token and invokes onCancel', async () => {78const { protocol, sentMessages } = createProtocol();79const cts = new CancellationTokenSource();80let canceledId: string | number | undefined;8182const requestPromise = protocol.sendRequest(83{ method: 'cancel-me' },84cts.token,85id => canceledId = id,86);87const outgoingRequest = sentMessages[0] as IJsonRpcRequest;8889cts.cancel();9091await assert.rejects(requestPromise, error => error instanceof CancellationError);92assert.strictEqual(canceledId, outgoingRequest.id);9394cts.dispose(true);95});9697test('cancelPendingRequest rejects active request', async () => {98const { protocol, sentMessages } = createProtocol();99100const requestPromise = protocol.sendRequest({ method: 'pending' });101const outgoingRequest = sentMessages[0] as IJsonRpcRequest;102protocol.cancelPendingRequest(outgoingRequest.id);103104await assert.rejects(requestPromise, error => error instanceof CancellationError);105});106107test('handleRequest responds with method not found without handler', async () => {108const { protocol, sentMessages } = createProtocol();109110const replies = await protocol.handleMessage({111jsonrpc: '2.0',112id: 7,113method: 'unknown'114});115116const expected = [{117jsonrpc: '2.0',118id: 7,119error: {120code: -32601,121message: 'Method not found: unknown'122}123}];124assert.deepStrictEqual(sentMessages, expected);125assert.deepStrictEqual(replies, expected);126});127128test('handleRequest responds with result and passes cancellation token', async () => {129let receivedToken: CancellationToken | undefined;130let wasCanceledDuringHandler: boolean | undefined;131const { protocol, sentMessages } = createProtocol({132handleRequest: async (request, token) => {133receivedToken = token;134wasCanceledDuringHandler = token.isCancellationRequested;135return `${request.method}:ok`;136}137});138139const replies = await protocol.handleMessage({140jsonrpc: '2.0',141id: 9,142method: 'compute'143});144145assert.ok(receivedToken);146assert.strictEqual(wasCanceledDuringHandler, false);147const expected = [{148jsonrpc: '2.0',149id: 9,150result: 'compute:ok'151}];152assert.deepStrictEqual(sentMessages, expected);153assert.deepStrictEqual(replies, expected);154});155156test('handleRequest serializes JsonRpcError and returns it', async () => {157const { protocol, sentMessages } = createProtocol({158handleRequest: () => {159throw new JsonRpcError(88, 'bad request', { detail: true });160}161});162163const replies = await protocol.handleMessage({164jsonrpc: '2.0',165id: 'a',166method: 'boom'167});168169const expected = [{170jsonrpc: '2.0',171id: 'a',172error: {173code: 88,174message: 'bad request',175data: { detail: true }176}177}];178assert.deepStrictEqual(sentMessages, expected);179assert.deepStrictEqual(replies, expected);180});181182test('handleRequest maps unknown errors to internal error and returns it', async () => {183const { protocol, sentMessages } = createProtocol({184handleRequest: () => {185throw new Error('unexpected');186}187});188189const replies = await protocol.handleMessage({190jsonrpc: '2.0',191id: 'b',192method: 'explode'193});194195const expected = [{196jsonrpc: '2.0',197id: 'b',198error: {199code: -32603,200message: 'unexpected'201}202}];203assert.deepStrictEqual(sentMessages, expected);204assert.deepStrictEqual(replies, expected);205});206207test('handleMessage processes batch sequentially', async () => {208const sequence: string[] = [];209const gate = new DeferredPromise<void>();210const { protocol } = createProtocol({211handleRequest: async () => {212sequence.push('request:start');213await gate.p;214sequence.push('request:end');215return true;216},217handleNotification: () => {218sequence.push('notification');219}220});221222const request: IJsonRpcRequest = {223jsonrpc: '2.0',224id: 1,225method: 'first'226};227const notification: IJsonRpcNotification = {228jsonrpc: '2.0',229method: 'second'230};231232const handlingPromise = protocol.handleMessage([request, notification]);233assert.deepStrictEqual(sequence, ['request:start']);234235gate.complete();236const replies = await handlingPromise;237238assert.deepStrictEqual(sequence, ['request:start', 'request:end', 'notification']);239assert.deepStrictEqual(replies, [{ jsonrpc: '2.0', id: 1, result: true }]);240});241});242243244