picorpc
Version:
A tiny RPC library and spec, inspired by JSON-RPC 2.0 and tRPC.
355 lines (238 loc) • 9.74 kB
JavaScript
/* IMPORT */
import {describe} from 'fava';
import {createMemoryClient, createMemoryServer} from '../dist/index.js';
import {createHttpClient, createHttpServer} from '../dist/index.js';
/* HELPERS */
const procedures = {
ctx () {
return this.value;
},
sum ( a, b ) {
return a + b;
},
powerOfTwo ( a ) {
return a ** 2;
},
throwCustom () {
const error = new Error ( 'Some custom error' );
error.code = 1;
error.data = 'Some data';
throw error;
},
throwGeneral () {
const error = new Error ( 'Some general error' );
throw error;
}
};
const createTestHttpClient = context => {
const port = 5173;
const url = `http://localhost:${port}`;
const server = createHttpServer ({ port, procedures });
const client = createHttpClient ({ context, url });
return {client, server};
};
const createTestMemoryClient = context => {
const server = createMemoryServer ({ procedures });
const client = createMemoryClient ({ context, server });
return {client, server};
};
/* MAIN */
describe ( 'PicoRPC', () => {
describe ( 'protocol', it => {
it ( 'errors on invalid request, string', async t => {
const {server} = createTestMemoryClient ();
const result = ( await server.handle ( '' ) ).valueOf ();
t.is ( result.version, '1.0.0' );
t.is ( result.id, '' );
t.is ( result.error.code, -1 );
t.is ( result.error.message, 'Invalid request' );
t.is ( result.result, undefined );
});
it ( 'errors on invalid version', async t => {
const {server} = createTestMemoryClient ();
const result = ( await server.handle ( { version: 2, id: '1' } ) ).valueOf ();
t.is ( result.version, '1.0.0' );
t.is ( result.id, '1' );
t.is ( result.error.code, -2 );
t.is ( result.error.message, 'Invalid version' );
t.is ( result.result, undefined );
});
it ( 'errors on invalid version, missing', async t => {
const {server} = createTestMemoryClient ();
const result = ( await server.handle ( { id: '1' } ) ).valueOf ();
t.is ( result.version, '1.0.0' );
t.is ( result.id, '1' );
t.is ( result.error.code, -2 );
t.is ( result.error.message, 'Invalid version' );
t.is ( result.result, undefined );
});
it ( 'errors on unsupported version', async t => {
const {server} = createTestMemoryClient ();
const result = ( await server.handle ( { version: '2.0.0', id: '1' } ) ).valueOf ();
t.is ( result.version, '1.0.0' );
t.is ( result.id, '1' );
t.is ( result.error.code, -3 );
t.is ( result.error.message, 'Unsupported version' );
t.is ( result.result, undefined );
});
it ( 'errors on unsupported version, major.minor.path', async t => {
const {server} = createTestMemoryClient ();
const result = ( await server.handle ( { version: '1.0', id: '1' } ) ).valueOf ();
t.is ( result.version, '1.0.0' );
t.is ( result.id, '1' );
t.is ( result.error.code, -3 );
t.is ( result.error.message, 'Unsupported version' );
t.is ( result.result, undefined );
});
it ( 'errors on invalid id', async t => {
const {server} = createTestMemoryClient ();
const result = ( await server.handle ( { version: '1.0.0', id: 1 } ) ).valueOf ();
t.is ( result.version, '1.0.0' );
t.is ( result.id, '' );
t.is ( result.error.code, -4 );
t.is ( result.error.message, 'Invalid id' );
t.is ( result.result, undefined );
});
it ( 'errors on invalid id, missing', async t => {
const {server} = createTestMemoryClient ();
const result = ( await server.handle ( { version: '1.0.0' } ) ).valueOf ();
t.is ( result.version, '1.0.0' );
t.is ( result.id, '' );
t.is ( result.error.code, -4 );
t.is ( result.error.message, 'Invalid id' );
t.is ( result.result, undefined );
});
it ( 'errors on invalid id, different case', async t => {
const {server} = createTestMemoryClient ();
const result = ( await server.handle ( { 'ID': '1' } ) ).valueOf ();
t.is ( result.version, '1.0.0' );
t.is ( result.id, '' );
t.is ( result.error.code, -4 );
t.is ( result.error.message, 'Invalid id' );
t.is ( result.result, undefined );
});
it ( 'errors on invalid method', async t => {
const {server} = createTestMemoryClient ();
const result = ( await server.handle ( { version: '1.0.0', id: '1', method: 'missing' } ) ).valueOf ();
t.is ( result.version, '1.0.0' );
t.is ( result.id, '1' );
t.is ( result.error.code, -5 );
t.is ( result.error.message, 'Invalid method' );
t.is ( result.result, undefined );
});
it ( 'errors on invalid method, missing', async t => {
const {server} = createTestMemoryClient ();
const result = ( await server.handle ( { version: '1.0.0', id: '1' } ) ).valueOf ();
t.is ( result.version, '1.0.0' );
t.is ( result.id, '1' );
t.is ( result.error.code, -5 );
t.is ( result.error.message, 'Invalid method' );
t.is ( result.result, undefined );
});
it ( 'errors on invalid params', async t => {
const {server} = createTestMemoryClient ();
const result = ( await server.handle ( { version: '1.0.0', id: '1', method: 'sum', params: {} } ) ).valueOf ();
t.is ( result.version, '1.0.0' );
t.is ( result.id, '1' );
t.is ( result.error.code, -6 );
t.is ( result.error.message, 'Invalid params' );
t.is ( result.result, undefined );
});
it ( 'errors on invalid context', async t => {
const {server} = createTestMemoryClient ();
const result = ( await server.handle ( { version: '1.0.0', id: '1', method: 'sum', params: [], context: [] } ) ).valueOf ();
t.is ( result.version, '1.0.0' );
t.is ( result.id, '1' );
t.is ( result.error.code, -7 );
t.is ( result.error.message, 'Invalid context' );
t.is ( result.result, undefined );
});
it ( 'errors on failed execution, with a custom error', async t => {
const {server} = createTestMemoryClient ();
const result = ( await server.handle ( { version: '1.0.0', id: '1', method: 'throwCustom' } ) ).valueOf ();
t.is ( result.version, '1.0.0' );
t.is ( result.id, '1' );
t.is ( result.error.code, 1 );
t.is ( result.error.message, 'Some custom error' );
t.is ( result.error.data, 'Some data' );
t.is ( result.result, undefined );
});
it ( 'errors on failed execution, with a general error', async t => {
const {server} = createTestMemoryClient ();
const result = ( await server.handle ( { version: '1.0.0', id: '1', method: 'throwGeneral' } ) ).valueOf ();
t.is ( result.version, '1.0.0' );
t.is ( result.id, '1' );
t.is ( result.error.code, -8 );
t.is ( result.error.message, 'Some general error' );
t.is ( result.error.data, undefined );
t.is ( result.result, undefined );
});
it ( 'succeeds on valid request', async t => {
const {server} = createTestMemoryClient ();
const result = ( await server.handle ( { version: '1.0.0', id: 'foo', method: 'sum', params: [1, 2] } ) ).valueOf ();
t.is ( result.version, '1.0.0' );
t.is ( result.id, 'foo' );
t.is ( result.error, undefined );
t.is ( result.result, 3 );
});
it ( 'succeeds on valid request, using the context', async t => {
const {server} = createTestMemoryClient ();
const result = ( await server.handle ( { version: '1.0.0', id: 'foo', method: 'ctx', context: { value: 123 } } ) ).valueOf ();
t.is ( result.version, '1.0.0' );
t.is ( result.id, 'foo' );
t.is ( result.error, undefined );
t.is ( result.result, 123 );
});
});
describe ( 'roudtrip', () => {
describe ( 'memory', it => {
it ( 'errors on invalid request', async t => {
const {client} = createTestMemoryClient ();
try {
await client.throwCustom ();
} catch ( error ) {
t.is ( error.code, 1 );
t.is ( error.message, 'Some custom error' );
t.is ( error.data, 'Some data' );
}
});
it ( 'succeeds on valid request', async t => {
const {client} = createTestMemoryClient ();
const result = await client.sum ( 1, 2 );
t.is ( result, 3 );
});
it ( 'succeeds on valid request, using the context', async t => {
const context = () => ({ value: 123 });
const {client} = createTestMemoryClient ( context );
const result = await client.ctx ()
t.is ( result, 123 );
});
});
describe ( 'http', it => {
it ( 'errors on invalid request', async t => {
const {client, server} = createTestHttpClient ();
try {
await client.throwCustom ();
} catch ( error ) {
t.is ( error.code, 1 );
t.is ( error.message, 'Some custom error' );
t.is ( error.data, 'Some data' );
server.close ();
}
});
it ( 'succeeds on valid request', async t => {
const {client, server} = createTestHttpClient ();
const result = await client.sum ( 1, 2 );
t.is ( result, 3 );
server.close ();
});
it ( 'succeeds on valid request, using the context', async t => {
const context = () => ({ value: 123 });
const {client, server} = createTestHttpClient ( context );
const result = await client.ctx ()
t.is ( result, 123 );
server.close ();
});
});
});
});