123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713 |
- import { describe, it } from "mocha";
- import { RPCServer, RPCSocket } from '../Index'
- import { RPCExporter } from "../src/Interfaces";
- import { ConnectedSocket } from "../src/Types";
- import * as log from 'why-is-node-running';
-
- const add = (...args: number[]) => { return args.reduce((a, b) => a + b, 0) }
- function makeServer() {
- let subcallback
- return new RPCServer(21010, [{
- name: 'test',
- exportRPCs: () => [
- {
- name: 'echo',
- call: async (s: string) => s,
- }, {
- name: 'simpleSubscribe',
- hook: async (callback) => {
- subcallback = callback
- return { topic: "test" }
- },
- onClose: (res) => { }
- }, {
- name: 'subscribe',
- hook: async (callback) => {
- subcallback = callback
- return { topic: "test" }
- },
- onClose: (res, rpc) => {
- console.log("onClose", rpc.name === 'subscribe' && res ? "OK" : "")
- subcallback = null
- },
- onCallback: (...args: any) => {
- console.log("onCallback", args[0] === "test" && args[1] === "callback" ? "OK" : "")
- }
- },
- add,
- function triggerCallback(...messages: any[]): number { return subcallback.apply({}, messages) },
- ]
- }], {
- connectionHandler: (socket) => { },
- closeHandler: (socket) => { },
- errorHandler: (socket, err) => { throw err }
- })
- }
-
-
- describe('RPCServer', () => {
- let client, server
- const echo = (x) => x
-
- before(done => {
- server = new RPCServer(21003, [{
- name: 'HelloWorldRPCGroup',
- exportRPCs: () => [
- echo, //named function variable
- function echof(x) { return x }, //named function
- {
- name: 'echoExplicit', //describing object
- call: async (x, y, z) => [x, y, z]
- }
- ]
- }])
-
- client = new RPCSocket(21003, 'localhost')
- done()
- })
-
- after(done => {
- client.destroy()
- server.destroy()
-
- done()
- })
-
- it('should be able to use all kinds of RPC definitions', (done) => {
- client.connect().then(async () => {
- const r0 = await client['HelloWorldRPCGroup'].echo('Hello')
- const r1 = await client['HelloWorldRPCGroup'].echof('World')
- const r2 = await client['HelloWorldRPCGroup'].echoExplicit('R', 'P', 'C!')
-
-
- if (r0 === 'Hello' && r1 === 'World' && r2.join('') === 'RPC!') {
- done()
- }else{
- done(new Error("Bad response"))
- }
- })
- })
-
- it('new RPCServer() should fail on bad RPC', (done) => {
- try {
- new RPCServer(20001, [{
- name: 'bad',
- exportRPCs: () => [
- (aaa, bbb, ccc) => { return aaa + bbb + ccc }
- ]
- }])
- done(new Error("Didn't fail with bad RPC"))
- } catch (badRPCError) {
- done()
- }
- })
- })
-
-
- describe('RPCSocket', () => {
- let client: RPCSocket
- let server: RPCServer
-
- before(async () => {
- server = makeServer()
- client = new RPCSocket(21010, "localhost")
- return await client.connect()
- })
-
- after(() => {
- client.destroy()
- server.destroy()
- })
-
-
- it('should have rpc echo', (done) => {
- client['test'].echo("x").then(x => {
- if (x === 'x')
- done()
- else
- done(new Error('echo RPC response did not match'))
- })
- })
-
- it('should add up to 6', (done) => {
- client['test'].add(1, 2, 3).then(x => {
- if (x === 6)
- done()
- else
- done(new Error('add RPC response did not match'))
- })
- })
-
- it('should subscribe with success', (done) => {
- client['test'].simpleSubscribe(console.log).then(res => {
- if (res.topic === 'test') {
- done()
- } else {
- console.error(res)
- done(new Error('Subscribe did not return success'))
- }
- })
- })
-
- it('subscribe should call back', (done) => {
- client['test'].subscribe((...args: any) => {
- if (args[0] === "test" && args[1] === "callback")
- done()
- else
- done(new Error("Bad callback value " + args))
- }).then(async () => {
- await client['test'].triggerCallback("test", "callback")
- })
- })
-
- it('simpleSubscribe should call back', (done) => {
- client['test'].simpleSubscribe((...args: any) => {
- if (args[0] === "test_" && args[1] === "callback_")
- done()
- else
- done(new Error("Bad callback value " + args))
- }).then(async () => {
- await client['test'].triggerCallback("test_", "callback_")
- })
- })
- })
-
- describe('It should do unhook', () => {
- const yesCandy = "OK"
- const noCandy = "stolen"
- let candy = yesCandy
- let cb: Function
- let cb2: Function
- let client: RPCSocket
- let server: RPCServer
-
- before(async () => {
- server = new RPCServer(21010, [{
- name: "test",
- exportRPCs: () => [{
- name: 'subscribe',
- hook: async (callback): Promise<void> => {
- cb = <Function>callback
- return
- }
- },
- {
- name: 'subscribeWithParam',
- hook: async (param, callback): Promise<{ uuid: string }> => {
-
- if (param != "OK") {
- console.log("param was" + param);
- return {
- uuid: "no",
- }
- }
- cb2 = <Function>callback
- return {
- uuid: "OK",
- }
- }
- },
- function publish(): string { cb(candy); return candy },
- function unsubscribe(): string { candy = noCandy; cb(candy); cb = () => { }; return candy }
- ]
- }], {
- connectionHandler: (socket) => { },
- closeHandler: (socket) => { },
- errorHandler: (socket, err) => { throw err }
- })
- client = new RPCSocket(21010, "localhost")
- return await client.connect()
- })
-
- after(() => {
- client.destroy()
- server.destroy()
- })
-
- it('Subscribe with param', (done) => {
- client['test'].subscribeWithParam("OK", c => { }).then(async (res) => {
- if (res.uuid === candy) {
- done()
- } else
- done(new Error("Results did not match " + res.uuid))
- })
- })
-
- let run = 0
- const expected = [yesCandy, noCandy, noCandy, noCandy]
-
- it('Unhook+unsubscribe should stop callbacks', (done) => {
-
- client['test'].subscribe(function myCallback(c){
- if(run == 1)
- (myCallback as any).destroy()
-
- if (c !== expected[run++]) {
- done(new Error(`Wrong candy '${c}' in iteration '${run - 1}'`))
- }
- }).then(async function(res){
- const r1 = await client['test'].publish()
- const r3 = await client['test'].unsubscribe()
- const r2 = await client['test'].publish()
- const r4 = await client['test'].publish()
-
- if (r1 === yesCandy && r3 === noCandy && r2 === noCandy && r4 === noCandy)
- done()
- else
- done(new Error("Results did not match: " + [r1, r2, r3, r4]))
- })
- })
- })
-
- type topicDTO = { topic: string; }
-
- type SesameTestIfc = {
- test: {
- checkCandy: () => Promise<string>
- subscribe: (callback: Function) => Promise<topicDTO>
- manyParams: <A=string,B=number,C=boolean,D=Object>(a:A, b:B, c:C, d:D) => Promise<[A, B, C, D]>
- }
-
- other: {
- echo: (x:any) => Promise<any>
- }
- }
-
- describe('Sesame should unlock the socket', () => {
- let candy = "OK"
- let client: ConnectedSocket<SesameTestIfc>
- let server: RPCServer<SesameTestIfc>
- let cb: Function = (...args) => { }
-
- before((done) => {
- server = new RPCServer<SesameTestIfc>(21004, [{
- name: "test",
- exportRPCs: () => [
- {
- name: 'subscribe',
- hook: async (callback) => {
- cb = callback
- return {
- topic: 'test'
- }
- },
- onClose: (a) => { }
- },
- async function checkCandy() { cb(candy); cb = () => { }; return candy },
- async function manyParams(a, b, c, d) { return [a, b, c, d] }
- ],
- },{
- name: 'other',
- exportRPCs: () => [
- async function echo(x){return x}
- ]
-
- }], {
- sesame: (_sesame) => _sesame === 'sesame!'
- })
- const sock = new RPCSocket<SesameTestIfc>(21004, "localhost")
- sock.connect('sesame!').then(cli => {
- client = cli
- done()
- })
- })
-
- after(() => {
- client.destroy()
- server.destroy()
- })
-
- it('should work with sesame', (done) => {
- client.test.checkCandy().then(c => done())
- })
-
- it('should work with multiple params', (done) => {
- client.test['manyParams']('a', 'b', 'c', 'd').then(c => {
- if (c[0] == 'a' && c[1] === 'b' && c[2] === 'c' && c[3] === 'd')
- done()
- })
- })
-
- it('should not work without sesame', (done) => {
- const sock = new RPCSocket(21004, "localhost")
- sock.connect<SesameTestIfc>( /* no sesame */).then(async (cli) => {
- if (!cli.test)
- done()
- else {
- done(new Error("Function supposed to be removed without sesame"))
- }
- cli.destroy()
- sock.destroy()
- })
- })
-
- it('should fail with wrong sesame', (done) => {
- const sock = new RPCSocket(21004, "localhost")
- sock.connect<SesameTestIfc>('abasd').then(async (cli) => {
- if (!cli.test)
- done()
- else {
- done(new Error("Function supposed to be removed without sesame"))
- }
- cli.destroy()
- sock.destroy()
- })
- })
-
- it('callback should work with sesame', (done) => {
- client.test.subscribe((c) => {
- if (c === candy) {
- done()
- }
- }).then(d => {
- if (d.topic !== 'test')
- done('unexpected invalid response')
-
- client.test.checkCandy()
- })
- })
- })
-
-
- describe('Error handling', () => {
- const errtxt = "BAD BAD BAD"
-
- let createUser = async (user: { a: any, b: any }) => {
- throw new Error(errtxt)
- }
-
- it("RPC throws on client without handler", (done) => {
- let server = new RPCServer(21004, [{
- name: "createUser",
- exportRPCs: () => [{
- name: 'createUser' as 'createUser',
- call: createUser
- }]
- }], {
-
- })
-
- let sock = new RPCSocket(21004, 'localhost')
- sock.connect().then((cli) => {
- cli["createUser"]["createUser"]({
- a: 'a',
- b: 'b'
- })
- .then(r => {
- if (r != null)
- done(new Error("UNEXPECTED RESULT " + r))
- })
- .catch((e) => {
- if (e.message === errtxt)
- done()
- else
- done(e)
- })
- .finally(() => {
- cli.destroy()
- sock.destroy()
- server.destroy()
- })
- })
- })
-
- it("RPC throws on server with handler", (done) => {
- let server = new RPCServer(21004, [{
- name: "createUser",
- exportRPCs: () => [{
- name: 'createUser' as 'createUser',
- call: createUser
- }]
- }], {
- errorHandler: (socket, e, rpcName, args) => {
- done()
- }
- })
-
- let sock = new RPCSocket(21004, 'localhost')
- sock.connect().then((cli) => {
- cli["createUser"]["createUser"]({
- a: 'a',
- b: 'b'
- })
- .then(r => {
- if (r != null)
- done("UNEXPECTED RESULT " + r)
- })
- .catch((e) => {
- done("UNEXPECTED CLIENT ERROR " + e)
- done(e)
- })
- .finally(() => {
- cli.destroy()
- sock.destroy()
- server.destroy()
- })
- })
- })
- })
-
-
- describe("Errorhandler functionality", () => {
- const errtxt = "BAD BAD BAD"
-
- let createUser = async (user: { a: any, b: any }) => {
- throw new Error(errtxt)
- }
-
- it("correct values are passed to the handler", (done) => {
- let server = new RPCServer(21004, [{
- name: "createUser",
- exportRPCs: () => [{
- name: 'createUser' as 'createUser',
- call: createUser
- }]
- }], {
- errorHandler: (socket, e, rpcName, args) => {
- if (e.message === errtxt && rpcName === "createUser" && args[0]['a'] === 'a' && args[0]['b'] === 'b')
- done()
- }
- })
-
- let sock = new RPCSocket(21004, 'localhost')
- sock.connect().then((cli) => {
- cli["createUser"]["createUser"]({
- a: 'a',
- b: 'b'
- })
- .then(r => {
- if (r != null)
- done("UNEXPECTED RESULT " + r)
- })
- .catch((e) => {
- done(new Error("UNEXPECTED CLIENT ERROR " + e.message))
- })
- .finally(() => {
- cli.destroy()
- sock.destroy()
- server.destroy()
- })
- })
- })
-
- it("handler sees sesame", (done) => {
- let sesame = "AAAAAAAAAAAAAAA"
- let server = new RPCServer(21004, [{
- name: "createUser" as "createUser",
- exportRPCs: () => [{
- name: 'createUser' as 'createUser',
- call: createUser
- }]
- }], {
- sesame: sesame,
- errorHandler: (socket, e, rpcName, args) => {
- if (e.message === errtxt && rpcName === "createUser" && args[0] === sesame && args[1]['a'] === 'a' && args[1]['b'] === 'b')
- done()
- }
-
- })
-
- let sock = new RPCSocket(21004, 'localhost')
- sock.connect(sesame).then((cli) => {
- cli["createUser"]["createUser"]({
- a: 'a',
- b: 'b'
- })
- .then(r => {
- if (r != null)
- done("UNEXPECTED RESULT " + r)
- })
- .catch((e) => {
- done("UNEXPECTED CLIENT ERROR " + e)
- done(e)
- })
- .finally(() => {
- cli.destroy()
- sock.destroy()
- server.destroy()
- })
- })
- })
- })
-
- type myExporterIfc = {
- MyExporter: {
- myRPC: () => Promise<string>
- }
- }
-
-
- describe("Class binding", () => {
-
- let exporter1: MyExporter
- let serv: RPCServer<myExporterIfc>
- let sock: RPCSocket & myExporterIfc
- let allowed = true
-
- class MyExporter implements RPCExporter<myExporterIfc>{
- name = "MyExporter" as "MyExporter"
- exportRPCs = () => [
- this.myRPC
- ]
-
- myRPC = async () => {
- //serv.setExporters([new MyOtherExporter])
- return "Hello World"
- }
- }
-
- class MyOtherExporter implements RPCExporter<myExporterIfc>{
- name = "MyExporter" as "MyExporter"
- exportRPCs = () => [
- this.myRPC
- ]
-
- myRPC = async () => {
- return "Hello Borld"
- }
-
- }
-
- before(done => {
- exporter1 = new MyExporter()
- serv = new RPCServer<myExporterIfc>(21004, [exporter1], {
- accessFilter: async (sesame, exporter) => {
- if(exporter.name === 'MyExporter'){
- if (!allowed) return false
- allowed = false
- return sesame === 'xxx';
- }else{
- return false
- }
- },
- sesame: "xxx"
- })
- done()
- })
-
- beforeEach((done) => {
- const s = new RPCSocket(21004, 'localhost')
- s.connect<myExporterIfc>("xxx").then(conn => {
- sock = conn
- done()
- })
- })
-
- afterEach((done) => {
- sock.destroy()
- done()
- })
-
- after(() => {
- serv.destroy()
- })
-
- /* The server-side socket will enter a 30s timeout if destroyed by a RPC.
- to mitigate the impact on testing time these are not run.
-
- it("binds correctly", function(done){
- this.timeout(1000)
- sock['MyExporter'].myRPC().then((res) => {
- done(new Error(res))
- }).catch(e => {
- //job will time out because of setExporters
- allowed = true
- done()
- })
- })
-
- it("changes exporters", (done) => {
-
- sock['MyExporter'].myRPC().then((res) => {
- if (res === "Hello Borld")
- done()
- else
- done(new Error(res))
- })
- })
- */
-
-
- it("use sesameFilter for available", (done) => {
- if (sock['MyExporter']){
- allowed = false
- done()
- }
- else done(new Error("RPC supposed to be here"))
- })
-
- it("use sesameFilter", (done) => {
- if (!sock['MyExporter']) done()
- else done(new Error("RPC supposed to be gone"))
- })
- })
-
-
- describe("attaching handlers before connecting", () => {
- it("fires error if server is unreachable", (done) => {
- const sock = new RPCSocket(21004, 'localhost')
- let errorHandleCount = 0
-
- sock.on('error', (err) => {
- //attached listener fires first
- if (errorHandleCount != 0) {
- console.log("Error handler didn't fire first");
- } else {
- errorHandleCount++
- }
- })
-
- sock.connect().then(_ => {
- console.log("Unexpected successful connect")
- }).catch(e => {
- //catch clause fires second
- if (errorHandleCount != 1) {
- console.log("catch clause didn't fire second");
- } else {
- sock.destroy()
- done()
- }
- })
- })
-
- it("fires error if call is unknown", (done) => {
- const serv = new RPCServer(21004)
- const sock = new RPCSocket(21004, 'localhost')
-
- sock.on('error', (err) => {
- sock.destroy()
- serv.destroy()
- done()
- })
-
- sock.connect().then(_ => {
- sock.call("unknownRPC123", "AAAAA").catch(e => { /* ignore */ })
- }).catch(e => {
- console.log("unexpected connect catch clause");
- done(e)
- })
- })
-
- it("demands catch on method invocation if call is unknown", (done) => {
- const serv = new RPCServer(21004)
- const sock = new RPCSocket(21004, 'localhost')
-
- sock.connect().then(_ => {
- sock.call("unknownRPC123", "AAAAA").catch(e => {
- sock.destroy()
- serv.destroy()
- done()
- })
- }).catch(e => {
- console.log("unexpected connect catch clause");
- done(e)
- })
- })
-
- })
-
- describe('finally', () => {
- it('print open handles (Ignore `DNSCHANNEL` and `Immediate`)', () => {
- log()
- })
- })
|