Skip to content

Commit 5b36049

Browse files
committed
tests: expanding error tests
[ci skip]
1 parent ffb8b57 commit 5b36049

File tree

7 files changed

+248
-15
lines changed

7 files changed

+248
-15
lines changed

src/RPC/RPCServer.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ class RPCServer {
3838
static async createRPCServer({
3939
manifest,
4040
middleware = rpcUtils.defaultServerMiddlewareWrapper(),
41+
sensitive = false,
4142
logger = new Logger(this.name),
4243
}: {
4344
manifest: ServerManifest;
@@ -47,12 +48,14 @@ class RPCServer {
4748
Uint8Array,
4849
JsonRpcResponseResult
4950
>;
51+
sensitive?: boolean;
5052
logger?: Logger;
5153
}): Promise<RPCServer> {
5254
logger.info(`Creating ${this.name}`);
5355
const rpcServer = new this({
5456
manifest,
5557
middleware,
58+
sensitive,
5659
logger,
5760
});
5861
logger.info(`Created ${this.name}`);
@@ -63,6 +66,7 @@ class RPCServer {
6366
protected logger: Logger;
6467
protected handlerMap: Map<string, RawHandlerImplementation> = new Map();
6568
protected activeStreams: Set<PromiseCancellable<void>> = new Set();
69+
protected sensitive: boolean;
6670
protected events: EventTarget = new EventTarget();
6771
protected middleware: MiddlewareFactory<
6872
JsonRpcRequest,
@@ -74,6 +78,7 @@ class RPCServer {
7478
public constructor({
7579
manifest,
7680
middleware,
81+
sensitive,
7782
logger,
7883
}: {
7984
manifest: ServerManifest;
@@ -83,6 +88,7 @@ class RPCServer {
8388
Uint8Array,
8489
JsonRpcResponseResult
8590
>;
91+
sensitive: boolean;
8692
logger: Logger;
8793
}) {
8894
for (const [key, manifestItem] of Object.entries(manifest)) {
@@ -128,6 +134,7 @@ class RPCServer {
128134
never();
129135
}
130136
this.middleware = middleware;
137+
this.sensitive = sensitive;
131138
this.logger = logger;
132139
}
133140

@@ -199,7 +206,7 @@ class RPCServer {
199206
const rpcError: JsonRpcError = {
200207
code: e.exitCode ?? sysexits.UNKNOWN,
201208
message: e.description ?? '',
202-
data: rpcUtils.fromError(e),
209+
data: rpcUtils.fromError(e, this.sensitive),
203210
};
204211
const rpcErrorMessage: JsonRpcResponseError = {
205212
jsonrpc: '2.0',

src/errors.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,3 +80,4 @@ export * from './schema/errors';
8080
export * from './status/errors';
8181
export * from './validation/errors';
8282
export * from './utils/errors';
83+
export * from './RPC/errors';

tests/RPC/RPC.test.ts

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
ServerCaller,
2121
UnaryCaller,
2222
} from '@/RPC/callers';
23+
import * as rpcErrors from '@/RPC/errors';
2324
import * as rpcTestUtils from './utils';
2425

2526
describe('RPC', () => {
@@ -256,4 +257,99 @@ describe('RPC', () => {
256257
await rpcClient.destroy();
257258
},
258259
);
260+
testProp(
261+
'RPC handles and sends errors',
262+
[
263+
rpcTestUtils.safeJsonValueArb,
264+
rpcTestUtils.errorArb(rpcTestUtils.errorArb()),
265+
],
266+
async (value, error) => {
267+
const { clientPair, serverPair } = rpcTestUtils.createTapPairs<
268+
Uint8Array,
269+
Uint8Array
270+
>();
271+
272+
class TestMethod extends UnaryHandler {
273+
public async handle(): Promise<JSONValue> {
274+
throw error;
275+
}
276+
}
277+
const rpcServer = await RPCServer.createRPCServer({
278+
manifest: {
279+
testMethod: new TestMethod({}),
280+
},
281+
logger,
282+
});
283+
rpcServer.handleStream(serverPair, {} as ConnectionInfo);
284+
285+
const rpcClient = await RPCClient.createRPCClient({
286+
manifest: {
287+
testMethod: new UnaryCaller(),
288+
},
289+
streamPairCreateCallback: async () => clientPair,
290+
logger,
291+
});
292+
293+
const callProm = rpcClient.methods.testMethod(value);
294+
await expect(callProm).rejects.toThrow(rpcErrors.ErrorRpcRemoteError);
295+
await expect(
296+
callProm.catch((e) => {
297+
throw e.cause;
298+
}),
299+
).rejects.toThrow(error);
300+
expect(await callProm.catch((e) => JSON.stringify(e.cause))).toInclude(
301+
'stack',
302+
);
303+
await rpcServer.destroy();
304+
await rpcClient.destroy();
305+
},
306+
);
307+
testProp(
308+
'RPC handles and sends sensitive errors',
309+
[
310+
rpcTestUtils.safeJsonValueArb,
311+
rpcTestUtils.errorArb(rpcTestUtils.errorArb()),
312+
],
313+
async (value, error) => {
314+
const { clientPair, serverPair } = rpcTestUtils.createTapPairs<
315+
Uint8Array,
316+
Uint8Array
317+
>();
318+
319+
class TestMethod extends UnaryHandler {
320+
public async handle(): Promise<JSONValue> {
321+
throw error;
322+
}
323+
}
324+
const rpcServer = await RPCServer.createRPCServer({
325+
manifest: {
326+
testMethod: new TestMethod({}),
327+
},
328+
sensitive: true,
329+
logger,
330+
});
331+
rpcServer.handleStream(serverPair, {} as ConnectionInfo);
332+
333+
const rpcClient = await RPCClient.createRPCClient({
334+
manifest: {
335+
testMethod: new UnaryCaller(),
336+
},
337+
streamPairCreateCallback: async () => clientPair,
338+
logger,
339+
});
340+
341+
const callProm = rpcClient.methods.testMethod(value);
342+
await expect(callProm).rejects.toThrow(rpcErrors.ErrorRpcRemoteError);
343+
await expect(
344+
callProm.catch((e) => {
345+
throw e.cause;
346+
}),
347+
).rejects.toThrow(error);
348+
expect(
349+
await callProm.catch((e) => JSON.stringify(e.cause)),
350+
).not.toInclude('stack');
351+
await rpcServer.destroy();
352+
await rpcClient.destroy();
353+
},
354+
);
259355
});

tests/RPC/RPCClient.test.ts

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,80 @@ describe(`${RPCClient.name}`, () => {
234234
'generic duplex caller can throw received error message',
235235
[
236236
fc.array(rpcTestUtils.jsonRpcResponseResultArb()),
237-
rpcTestUtils.jsonRpcResponseErrorArb(),
237+
rpcTestUtils.jsonRpcResponseErrorArb(rpcTestUtils.errorArb()),
238+
],
239+
async (messages, errorMessage) => {
240+
const inputStream = rpcTestUtils.messagesToReadableStream([
241+
...messages,
242+
errorMessage,
243+
]);
244+
const [outputResult, outputStream] =
245+
rpcTestUtils.streamToArray<Uint8Array>();
246+
const streamPair: ReadableWritablePair = {
247+
readable: inputStream,
248+
writable: outputStream,
249+
};
250+
const rpcClient = await RPCClient.createRPCClient({
251+
manifest: {},
252+
streamPairCreateCallback: async () => streamPair,
253+
logger,
254+
});
255+
const callProm = rpcClient.duplexStreamCaller<JSONValue, JSONValue>(
256+
methodName,
257+
async function* (output) {
258+
for await (const _ of output) {
259+
// No touch, just consume
260+
}
261+
},
262+
);
263+
await expect(callProm).rejects.toThrow(rpcErrors.ErrorRpcRemoteError);
264+
await outputResult;
265+
await rpcClient.destroy();
266+
},
267+
);
268+
testProp(
269+
'generic duplex caller can throw received error message with sensitive',
270+
[
271+
fc.array(rpcTestUtils.jsonRpcResponseResultArb()),
272+
rpcTestUtils.jsonRpcResponseErrorArb(rpcTestUtils.errorArb(), true),
273+
],
274+
async (messages, errorMessage) => {
275+
const inputStream = rpcTestUtils.messagesToReadableStream([
276+
...messages,
277+
errorMessage,
278+
]);
279+
const [outputResult, outputStream] =
280+
rpcTestUtils.streamToArray<Uint8Array>();
281+
const streamPair: ReadableWritablePair = {
282+
readable: inputStream,
283+
writable: outputStream,
284+
};
285+
const rpcClient = await RPCClient.createRPCClient({
286+
manifest: {},
287+
streamPairCreateCallback: async () => streamPair,
288+
logger,
289+
});
290+
const callProm = rpcClient.duplexStreamCaller<JSONValue, JSONValue>(
291+
methodName,
292+
async function* (output) {
293+
for await (const _ of output) {
294+
// No touch, just consume
295+
}
296+
},
297+
);
298+
await expect(callProm).rejects.toThrow(rpcErrors.ErrorRpcRemoteError);
299+
await outputResult;
300+
await rpcClient.destroy();
301+
},
302+
);
303+
testProp(
304+
'generic duplex caller can throw received error message with causes',
305+
[
306+
fc.array(rpcTestUtils.jsonRpcResponseResultArb()),
307+
rpcTestUtils.jsonRpcResponseErrorArb(
308+
rpcTestUtils.errorArb(rpcTestUtils.errorArb()),
309+
true,
310+
),
238311
],
239312
async (messages, errorMessage) => {
240313
const inputStream = rpcTestUtils.messagesToReadableStream([

tests/RPC/RPCServer.test.ts

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,6 @@ describe(`${RPCServer.name}`, () => {
4444
maxLength: 10,
4545
},
4646
);
47-
const errorArb = fc.oneof(
48-
fc.constant(new rpcErrors.ErrorRpcParse()),
49-
fc.constant(new rpcErrors.ErrorRpcMessageLength()),
50-
fc.constant(new rpcErrors.ErrorRpcRemoteError()),
51-
);
5247
const validToken = 'VALIDTOKEN';
5348
const invalidTokenMessageArb = rpcTestUtils.jsonRpcRequestMessageArb(
5449
fc.constant('testMethod'),
@@ -377,7 +372,47 @@ describe(`${RPCServer.name}`, () => {
377372
});
378373
testProp(
379374
'should send error message',
380-
[specificMessageArb, errorArb],
375+
[specificMessageArb, rpcTestUtils.errorArb(rpcTestUtils.errorArb())],
376+
async (messages, error) => {
377+
const stream = rpcTestUtils.messagesToReadableStream(messages);
378+
class TestMethod extends DuplexHandler {
379+
public async *handle(): AsyncIterable<JSONValue> {
380+
throw error;
381+
}
382+
}
383+
const rpcServer = await RPCServer.createRPCServer({
384+
manifest: {
385+
testMethod: new TestMethod({}),
386+
},
387+
logger,
388+
});
389+
let resolve, reject;
390+
const errorProm = new Promise((resolve_, reject_) => {
391+
resolve = resolve_;
392+
reject = reject_;
393+
});
394+
rpcServer.addEventListener('error', (thing) => {
395+
resolve(thing);
396+
});
397+
const [outputResult, outputStream] = rpcTestUtils.streamToArray();
398+
const readWriteStream: ReadableWritablePair = {
399+
readable: stream,
400+
writable: outputStream,
401+
};
402+
rpcServer.handleStream(readWriteStream, {} as ConnectionInfo);
403+
const rawErrorMessage = (await outputResult)[0]!.toString();
404+
expect(rawErrorMessage).toInclude('stack');
405+
const errorMessage = JSON.parse(rawErrorMessage);
406+
expect(errorMessage.error.code).toEqual(error.exitCode);
407+
expect(errorMessage.error.message).toEqual(error.description);
408+
reject();
409+
await expect(errorProm).toReject();
410+
await rpcServer.destroy();
411+
},
412+
);
413+
testProp(
414+
'should send error message with sensitive',
415+
[specificMessageArb, rpcTestUtils.errorArb(rpcTestUtils.errorArb())],
381416
async (messages, error) => {
382417
const stream = rpcTestUtils.messagesToReadableStream(messages);
383418
class TestMethod extends DuplexHandler {
@@ -389,6 +424,7 @@ describe(`${RPCServer.name}`, () => {
389424
manifest: {
390425
testMethod: new TestMethod({}),
391426
},
427+
sensitive: true,
392428
logger,
393429
});
394430
let resolve, reject;
@@ -405,7 +441,9 @@ describe(`${RPCServer.name}`, () => {
405441
writable: outputStream,
406442
};
407443
rpcServer.handleStream(readWriteStream, {} as ConnectionInfo);
408-
const errorMessage = JSON.parse((await outputResult)[0]!.toString());
444+
const rawErrorMessage = (await outputResult)[0]!.toString();
445+
expect(rawErrorMessage).not.toInclude('stack');
446+
const errorMessage = JSON.parse(rawErrorMessage);
409447
expect(errorMessage.error.code).toEqual(error.exitCode);
410448
expect(errorMessage.error.message).toEqual(error.description);
411449
reject();

0 commit comments

Comments
 (0)