You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Test.ts 32KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132
  1. import { describe, it } from "mocha";
  2. import { RPCServer, RPCSocket, Serializable } from '../Index'
  3. import { RPCExporter, Socket } from "../src/Interfaces";
  4. import { ConnectedSocket, Callback, GenericFunction } from "../src/Types";
  5. import * as log from 'why-is-node-running';
  6. import * as http from 'http';
  7. import * as express from 'express';
  8. import * as fetch from 'node-fetch';
  9. import { PromiseIO } from "../src/PromiseIO/Server";
  10. import { PromiseIOClient } from "../src/PromiseIO/Client";
  11. import { assert, expect } from 'chai';
  12. import { CLASSNAME_ATTRIBUTE, USER_DEFINED_TIMEOUT } from "../src/Strings";
  13. var should = require('chai').should();
  14. var chai = require("chai");
  15. var chaiAsPromised = require("chai-as-promised");
  16. chai.use(chaiAsPromised);
  17. const noop = (...args) => { }
  18. const add = (...args: number[]) => { return args.reduce((a, b) => a + b, 0) }
  19. function makeServer(onCallback = noop, connectionHandler = noop, hookCloseHandler = noop, closeHandler = noop, errorHandler = noop) {
  20. let subcallback
  21. const serv = new RPCServer([{
  22. name: 'test',
  23. RPCs: [
  24. {
  25. name: 'echo',
  26. call: async (s: string) => s,
  27. }, {
  28. name: 'complexSignature',
  29. call: async ([a, b]) => {
  30. return [b, a]
  31. }
  32. }, {
  33. name: 'simpleSubscribe',
  34. hook: async (callback) => {
  35. subcallback = callback
  36. return { topic: "test" }
  37. },
  38. onDestroy: hookCloseHandler
  39. }, {
  40. name: 'subscribe',
  41. hook: async (callback) => {
  42. subcallback = callback
  43. return { topic: "test" }
  44. },
  45. onDestroy: hookCloseHandler,
  46. onCallback: onCallback
  47. },
  48. add,
  49. function triggerCallback(...messages: any[]): number { return subcallback.apply({}, messages) },
  50. function brokenRPC() { throw new Error("Intended error") }
  51. ]
  52. }],
  53. {
  54. connectionHandler: connectionHandler,
  55. closeHandler: closeHandler,
  56. errorHandler: errorHandler
  57. })
  58. serv.listen(21010)
  59. return serv
  60. }
  61. describe('PromiseIO', () => {
  62. it("bind + fire", (done) => {
  63. const server = new PromiseIO()
  64. server.attach(new http.Server())
  65. server.on("socket", clientSocket => {
  66. clientSocket.bind("test123", (p1, p2) => {
  67. server.close()
  68. if (p1 === "p1" && p2 === "p2")
  69. done()
  70. })
  71. });
  72. server.listen(21003)
  73. PromiseIOClient.connect(21003, "localhost", { protocol: 'http' }).then(cli => {
  74. cli.fire("test123", "p1", "p2")
  75. cli.close()
  76. })
  77. })
  78. it("hook + call", (done) => {
  79. const server = new PromiseIO()
  80. server.attach(new http.Server())
  81. server.on("socket", clientSocket => {
  82. clientSocket.hook("test123", (p1, p2) => {
  83. if (p1 === "p1" && p2 === "p2")
  84. return "OK"
  85. })
  86. });
  87. server.listen(21003)
  88. PromiseIOClient.connect(21003, "localhost", { protocol: 'http' }).then(cli => {
  89. cli.call("test123", "p1", "p2").then(resp => {
  90. cli.close()
  91. server.close()
  92. if (resp === "OK")
  93. done()
  94. })
  95. })
  96. })
  97. it("on + emit", (done) => {
  98. const server = new PromiseIO()
  99. server.attach(new http.Server())
  100. server.on("socket", clientSocket => {
  101. clientSocket.on("test123", (p1, p2) => {
  102. server.close()
  103. if (p1 === "p1" && p2 === "p2")
  104. done()
  105. })
  106. });
  107. server.listen(21003)
  108. PromiseIOClient.connect(21003, "localhost", { protocol: 'http' }).then(cli => {
  109. cli.emit("test123", "p1", "p2")
  110. cli.close()
  111. })
  112. })
  113. })
  114. describe('RPCServer', () => {
  115. let client, server
  116. const echo = (x) => x
  117. before(done => {
  118. server = new RPCServer([{
  119. name: 'HelloWorldRPCGroup',
  120. RPCs: () => [
  121. echo, //named function variable
  122. function echof(x) { return x }, //named function
  123. {
  124. name: 'echoExplicit', //describing object
  125. call: async (x, y, z) => [x, y, z]
  126. }
  127. ]
  128. }])
  129. server.listen(21003)
  130. client = new RPCSocket(21003, 'localhost')
  131. done()
  132. })
  133. after(done => {
  134. client.close()
  135. server.close()
  136. done()
  137. })
  138. it('should be able to use all kinds of RPC definitions', async () => {
  139. await client.connect()
  140. const r0 = await client['HelloWorldRPCGroup'].echo('Hello')
  141. const r1 = await client['HelloWorldRPCGroup'].echof('World')
  142. const r2 = await client['HelloWorldRPCGroup'].echoExplicit('R', 'P', 'C!')
  143. expect(r0).to.be.equal('Hello')
  144. expect(r1).to.be.equal('World')
  145. expect(r2.join('')).to.be.equal('RPC!')
  146. })
  147. it('new RPCServer() should fail on unnamed RPC', async () => {
  148. expect(() => {
  149. const sv = new RPCServer([{
  150. name: 'bad',
  151. RPCs: () => [
  152. (aaa, bbb, ccc) => { return aaa + bbb + ccc }
  153. ]
  154. }])
  155. }).to.throw()
  156. })
  157. })
  158. describe('RPCServer with premade http server', () => {
  159. const echo = (x) => x
  160. const RPCs = [
  161. echo, //named function variable
  162. function echof(x) { return x }, //named function
  163. {
  164. name: 'echoExplicit', //describing object
  165. call: async (x, y, z) => [x, y, z]
  166. }
  167. ]
  168. const RPCExporters = [
  169. {
  170. name: 'HelloWorldRPCGroup',
  171. RPCs: RPCs,
  172. }
  173. ]
  174. const RPCExporters2 = [
  175. {
  176. name: 'Grp2',
  177. RPCs: [
  178. function test() { return "test" }
  179. ],
  180. }
  181. ]
  182. let client: RPCSocket, server: RPCServer
  183. before(done => {
  184. const expressServer = express()
  185. const httpServer = new http.Server(expressServer)
  186. expressServer.get('/REST_ping', (req, res) => {
  187. return res
  188. .send('REST_pong')
  189. .status(200)
  190. })
  191. server = new RPCServer(
  192. RPCExporters,
  193. )
  194. server.attach(httpServer)
  195. httpServer.listen(8080)
  196. client = new RPCSocket(8080, 'localhost')
  197. done()
  198. })
  199. after(done => {
  200. client.close()
  201. server.close()
  202. done()
  203. })
  204. it('should serve REST', async () => {
  205. const response = await fetch('http://localhost:8080/REST_ping')
  206. const text = await response.text()
  207. expect(text).to.be.equal("REST_pong")
  208. })
  209. it('should be able to use all kinds of RPC definitions', async () => {
  210. await client.connect()
  211. const r0 = await client['HelloWorldRPCGroup'].echo('Hello')
  212. const r1 = await client['HelloWorldRPCGroup'].echof('World')
  213. const r2 = await client['HelloWorldRPCGroup'].echoExplicit('R', 'P', 'C!')
  214. expect(r0).to.be.equal('Hello')
  215. expect(r1).to.be.equal('World')
  216. expect(r2.join('')).to.be.equal('RPC!')
  217. })
  218. })
  219. describe('should be able to attach to non-standard path', () => {
  220. let client: RPCSocket, server: RPCServer
  221. const echo = (x) => x
  222. before(done => {
  223. server = new RPCServer([{
  224. name: 'HelloWorldRPCGroup',
  225. RPCs: () => [
  226. echo, //named function variable
  227. function echof(x) { return x }, //named function
  228. {
  229. name: 'echoExplicit', //describing object
  230. call: async (x, y, z) => [x, y, z]
  231. }
  232. ]
  233. }])
  234. server.listen(21003, { path: '/test' })
  235. client = new RPCSocket(21003, 'localhost', { path: '/test' })
  236. done()
  237. })
  238. after(done => {
  239. client.close()
  240. server.close()
  241. done()
  242. })
  243. it('should be able to use all kinds of RPC definitions', async () => {
  244. await client.connect()
  245. const r0 = await client['HelloWorldRPCGroup'].echo('Hello')
  246. const r1 = await client['HelloWorldRPCGroup'].echof('World')
  247. const r2 = await client['HelloWorldRPCGroup'].echoExplicit('R', 'P', 'C!')
  248. expect(r0).to.be.equal('Hello')
  249. expect(r1).to.be.equal('World')
  250. expect(r2.join('')).to.be.equal('RPC!')
  251. })
  252. })
  253. describe('can attach multiple RPCServers to same http server', () => {
  254. const echo = (x) => x
  255. const RPCs = [
  256. echo, //named function variable
  257. function echof(x) { return x }, //named function
  258. {
  259. name: 'echoExplicit', //describing object
  260. call: async (x, y, z) => [x, y, z]
  261. }
  262. ]
  263. const RPCExporters = [
  264. {
  265. name: 'HelloWorldRPCGroup',
  266. RPCs: RPCs,
  267. }
  268. ]
  269. const RPCExporters2 = [
  270. {
  271. name: 'Grp2',
  272. RPCs: [
  273. function test() { return "/test" }
  274. ],
  275. }
  276. ]
  277. const callTimeout = 100;
  278. let client: RPCSocket, client2: RPCSocket, server: RPCServer, server2: RPCServer
  279. before(done => {
  280. const expressServer = express()
  281. const httpServer = new http.Server(expressServer)
  282. server = new RPCServer(
  283. RPCExporters,
  284. )
  285. server2 = new RPCServer(
  286. RPCExporters2
  287. )
  288. server.attach(httpServer)
  289. server2.attach(httpServer, {
  290. path: "test"
  291. })
  292. httpServer.listen(8080)
  293. new RPCSocket(8080, 'localhost').connect().then(sock => {
  294. client = sock
  295. new RPCSocket(8080, 'localhost', { path: "test", callTimeoutMs: callTimeout }).connect().then(sock2 => {
  296. client2 = sock2
  297. done()
  298. })
  299. })
  300. })
  301. after(done => {
  302. client.close()
  303. client2.close()
  304. server.close()
  305. server2.close()
  306. done()
  307. })
  308. it('both servers should answer', async () => {
  309. const res = await client['HelloWorldRPCGroup'].echo("test")
  310. expect(res).to.equal('test')
  311. const res2 = await client2['Grp2'].test()
  312. expect(res2).to.equal('/test')
  313. })
  314. it('server1 should answer after server2 is closed', async () => {
  315. server2.close()
  316. const res = await client['HelloWorldRPCGroup'].echo("test")
  317. expect(res).to.equal('test')
  318. return client2['Grp2'].test().should.eventually.be.rejectedWith(USER_DEFINED_TIMEOUT(callTimeout))
  319. })
  320. })
  321. describe("can attach second RPCServer if first is already running", () => {
  322. const RPCExporters = [
  323. {
  324. name: 'HelloWorldRPCGroup',
  325. RPCs: [
  326. function echo(x) { return x }, //named function variable
  327. function echof(x) { return x }, //named function
  328. {
  329. name: 'echoExplicit', //describing object
  330. call: async (x, y, z) => [x, y, z]
  331. }
  332. ],
  333. }
  334. ]
  335. const RPCExporters2 = [
  336. {
  337. name: 'Grp2',
  338. RPCs: [
  339. function test() { return "/test" }
  340. ],
  341. }
  342. ]
  343. it("attaches correctly", async () => {
  344. const expressServer = express()
  345. const httpServer = new http.Server(expressServer)
  346. const server = new RPCServer(
  347. RPCExporters,
  348. )
  349. const server2 = new RPCServer(
  350. RPCExporters2
  351. )
  352. server.attach(httpServer)
  353. httpServer.listen(8080)
  354. server2.attach(httpServer, {
  355. path: "test"
  356. })
  357. const sock = await new RPCSocket(8080, 'localhost').connect()
  358. const sock2 = await new RPCSocket(8080, 'localhost', { path: "test" }).connect()
  359. const resp = await sock2.Grp2.test()
  360. expect(resp).to.be.equal("/test")
  361. server.close()
  362. server2.close()
  363. sock.close()
  364. sock2.close()
  365. })
  366. })
  367. describe('Serverside Triggers', () => {
  368. let server, client
  369. const closerFunction = (done) => () => {
  370. client.close()
  371. server.close()
  372. done()
  373. }
  374. it('trigger onCallback', (done) => {
  375. server = makeServer(closerFunction(done))
  376. client = new RPCSocket(21010, "localhost")
  377. client.connect().then(_ => {
  378. client['test'].subscribe(noop).then(_ => client['test'].triggerCallback())
  379. })
  380. })
  381. /* testing framework has trouble terminating on this one
  382. it('trigger connectionHandler', (done) => {
  383. server = makeServer(undefined, closerFunction(done))
  384. client = new RPCSocket(21010, "localhost")
  385. client.connect()
  386. })
  387. */
  388. it('trigger hook closeHandler', (done) => {
  389. server = makeServer(undefined, undefined, closerFunction(done))
  390. client = new RPCSocket(21010, "localhost")
  391. client.connect().then(_ => {
  392. client['test'].subscribe(function cb() {
  393. cb['destroy']()
  394. }).then(_ => client['test'].triggerCallback())
  395. })
  396. })
  397. it('trigger global closeHandler', (done) => {
  398. server = makeServer(undefined, undefined, undefined, () => {
  399. server.close()
  400. done()
  401. })
  402. client = new RPCSocket(21010, "localhost")
  403. client.connect().then(_ => {
  404. client['test'].subscribe(noop).then(_ => client.close())
  405. })
  406. })
  407. })
  408. describe('RPCSocket', () => {
  409. let client: RPCSocket
  410. let server: RPCServer
  411. before(async () => {
  412. server = makeServer()
  413. client = new RPCSocket(21010, "localhost")
  414. return await client.connect()
  415. })
  416. after(() => {
  417. client.close()
  418. server.close()
  419. })
  420. it('should have rpc echo', async () => {
  421. const x = await client['test'].echo("x")
  422. expect(x).to.be.equal('x')
  423. })
  424. it('should add up to 6', async () => {
  425. const sum = await client['test'].add(1, 2, 3)
  426. expect(sum).to.be.equal(6)
  427. })
  428. it('should subscribe with success', async () => {
  429. const res = await client['test'].simpleSubscribe(noop)
  430. expect(res.topic).to.be.equal('test')
  431. })
  432. it('subscribe should call back', (done) => {
  433. client['test'].subscribe((...args: any) => {
  434. if (args[0] === "test" && args[1] === "callback")
  435. done()
  436. else
  437. done(new Error("Bad callback value " + args))
  438. }).then(async () => {
  439. await client['test'].triggerCallback("test", "callback")
  440. })
  441. })
  442. it('simpleSubscribe should call back', (done) => {
  443. client['test'].simpleSubscribe((...args: any) => {
  444. if (args[0] === "test_" && args[1] === "callback_")
  445. done()
  446. else
  447. done(new Error("Bad callback value " + args))
  448. }).then(async () => {
  449. await client['test'].triggerCallback("test_", "callback_")
  450. })
  451. })
  452. })
  453. describe('It should do unhook', () => {
  454. const yesCandy = "OK"
  455. const noCandy = "stolen"
  456. let candy = yesCandy
  457. let cb: Function
  458. let cb2: Function
  459. let client: RPCSocket
  460. let server: RPCServer
  461. before(async () => {
  462. server = new RPCServer([{
  463. name: "test",
  464. RPCs: () => [{
  465. name: 'subscribe',
  466. hook: async (callback): Promise<void> => {
  467. cb = <Function>callback
  468. return
  469. }
  470. },
  471. {
  472. name: 'subscribeWithParam',
  473. hook: async (param, callback): Promise<{ uuid: string }> => {
  474. if (param != "OK") {
  475. console.log("param was" + param);
  476. return {
  477. uuid: "no",
  478. }
  479. }
  480. cb2 = <Function>callback
  481. return {
  482. uuid: "OK",
  483. }
  484. }
  485. },
  486. function publish(): string { cb(candy); return candy },
  487. function unsubscribe(): string { candy = noCandy; cb(candy); cb = () => { }; return candy }
  488. ]
  489. }],
  490. {
  491. connectionHandler: noop,
  492. closeHandler: noop,
  493. errorHandler: (socket, err) => { throw err }
  494. })
  495. server.listen(21010)
  496. client = new RPCSocket(21010, "localhost")
  497. return await client.connect()
  498. })
  499. after(() => {
  500. client.close()
  501. server.close()
  502. })
  503. it('Subscribe with param', async () => {
  504. const res = await client['test'].subscribeWithParam("OK", noop)
  505. expect(res.uuid).to.be.equal(candy)
  506. })
  507. let run = 0
  508. const expected = [yesCandy, noCandy, noCandy, noCandy]
  509. it('Unhook+unsubscribe should stop callbacks', async () => {
  510. await client['test'].subscribe(function myCallback(c) {
  511. if (run == 1)
  512. (myCallback as any).destroy()
  513. expect(c).to.equal(expected[run++])
  514. })
  515. const r1 = await client['test'].publish()
  516. const r3 = await client['test'].unsubscribe()
  517. const r2 = await client['test'].publish()
  518. const r4 = await client['test'].publish()
  519. expect(r1).to.be.equal(yesCandy)
  520. expect(r2).to.be.equal(noCandy)
  521. expect(r3).to.be.equal(noCandy)
  522. expect(r4).to.be.equal(noCandy)
  523. })
  524. })
  525. type topicDTO = { topic: string; }
  526. type SesameTestIfc = {
  527. test: {
  528. checkCandy: () => Promise<string>
  529. subscribe: (callback: Callback<[string]>) => Promise<topicDTO>
  530. manyParams: <A = string, B = number, C = boolean, D = Object>(a: A, b: B, c: C, d: D) => Promise<[A, B, C, D]>
  531. }
  532. }
  533. describe('Sesame should unlock the socket', () => {
  534. let candy = "OK"
  535. let client: ConnectedSocket<SesameTestIfc>
  536. let server: RPCServer<SesameTestIfc>
  537. let cb: Function = (...args) => { }
  538. before((done) => {
  539. server = new RPCServer<SesameTestIfc>([{
  540. name: "test",
  541. RPCs: () => [
  542. {
  543. name: 'subscribe',
  544. hook: async (callback) => {
  545. cb = callback
  546. return {
  547. topic: 'test'
  548. }
  549. },
  550. onDestroy: noop
  551. },
  552. async function checkCandy() { cb(candy); cb = noop; return candy },
  553. async function manyParams(a, b, c, d) { return [a, b, c, d] }
  554. ],
  555. }], {
  556. sesame: (_sesame) => _sesame === 'sesame!'
  557. })
  558. server.listen(21004)
  559. const sock = new RPCSocket<SesameTestIfc>(21004, "localhost")
  560. sock.connect('sesame!').then(cli => {
  561. client = cli
  562. done()
  563. })
  564. })
  565. after(() => {
  566. client.close()
  567. server.close()
  568. })
  569. it('should work with sesame', async () => {
  570. const c = client.test.checkCandy()
  571. expect(c).to.exist
  572. })
  573. it('should work with multiple params', async () => {
  574. const c = await client.test['manyParams']('a', 'b', 'c', 'd')
  575. expect(c[0]).to.be.equal('a')
  576. expect(c[1]).to.be.equal('b')
  577. expect(c[2]).to.be.equal('c')
  578. expect(c[3]).to.be.equal('d')
  579. })
  580. it('should not work without sesame', async () => {
  581. const sock = new RPCSocket(21004, "localhost")
  582. const cli = await sock.connect()
  583. expect(cli.test).to.not.exist
  584. cli.close()
  585. sock.close()
  586. })
  587. it('should fail with wrong sesame', async () => {
  588. const sock = new RPCSocket(21004, "localhost")
  589. const cli = await sock.connect('iamwrong')
  590. expect(cli.test).to.not.exist
  591. cli.close()
  592. sock.close()
  593. })
  594. it('callback should work with sesame', (done) => {
  595. client.test.subscribe(function (c) {
  596. if (c === candy) {
  597. done()
  598. }
  599. }).then(d => {
  600. if (d.topic !== 'test')
  601. done('unexpected invalid response')
  602. client.test.checkCandy()
  603. })
  604. })
  605. })
  606. describe('Error handling', () => {
  607. const errtxt = "BAD BAD BAD"
  608. let createUser = async (user: { a: any, b: any }) => {
  609. throw new Error(errtxt)
  610. }
  611. it("RPC throws on client without handler", (done) => {
  612. let server = new RPCServer([{
  613. name: "createUser",
  614. RPCs: () => [{
  615. name: 'createUser' as 'createUser',
  616. call: createUser
  617. }]
  618. }])
  619. server.listen(21004)
  620. let sock = new RPCSocket(21004, 'localhost')
  621. sock.connect().then((cli) => {
  622. cli["createUser"]["createUser"]({
  623. a: 'a',
  624. b: 'b'
  625. })
  626. .then(r => {
  627. if (r != null)
  628. done(new Error("UNEXPECTED RESULT " + r))
  629. })
  630. .catch((e) => {
  631. if (e.message === errtxt)
  632. done()
  633. else
  634. done(e)
  635. })
  636. .finally(() => {
  637. cli.close()
  638. sock.close()
  639. server.close()
  640. })
  641. })
  642. })
  643. it("RPC throws on server with handler", (done) => {
  644. let server = new RPCServer([{
  645. name: "createUser",
  646. RPCs: () => [{
  647. name: 'createUser' as 'createUser',
  648. call: createUser
  649. }]
  650. }], {
  651. errorHandler: (socket, e, rpcName, args) => {
  652. done()
  653. }
  654. })
  655. server.listen(21004)
  656. let sock = new RPCSocket(21004, 'localhost')
  657. sock.connect().then((cli) => {
  658. cli["createUser"]["createUser"]({
  659. a: 'a',
  660. b: 'b'
  661. })
  662. .then(r => {
  663. if (r != null)
  664. done("UNEXPECTED RESULT " + r)
  665. })
  666. .catch((e) => {
  667. done("UNEXPECTED CLIENT ERROR " + e)
  668. done(e)
  669. })
  670. .finally(() => {
  671. cli.close()
  672. sock.close()
  673. server.close()
  674. })
  675. })
  676. })
  677. })
  678. describe("Errorhandler functionality", () => {
  679. const errtxt = "BAD BAD BAD"
  680. let createUser = async (user: { a: any, b: any }) => {
  681. throw new Error(errtxt)
  682. }
  683. it("correct values are passed to the handler", (done) => {
  684. let server = new RPCServer([{
  685. name: "createUser",
  686. RPCs: () => [{
  687. name: 'createUser' as 'createUser',
  688. call: createUser
  689. }]
  690. }], {
  691. errorHandler: (socket, e, rpcName, args) => {
  692. if (e.message === errtxt && rpcName === "createUser" && args[0]['a'] === 'a' && args[0]['b'] === 'b')
  693. done()
  694. }
  695. })
  696. server.listen(21004)
  697. let sock = new RPCSocket(21004, 'localhost')
  698. sock.connect().then((cli) => {
  699. cli["createUser"]["createUser"]({
  700. a: 'a',
  701. b: 'b'
  702. })
  703. .then(r => {
  704. if (r != null)
  705. done(new Error("UNEXPECTED RESULT " + r))
  706. })
  707. .catch((e) => {
  708. done(new Error("UNEXPECTED CLIENT ERROR " + e.message))
  709. })
  710. .finally(() => {
  711. cli.close()
  712. sock.close()
  713. server.close()
  714. })
  715. })
  716. })
  717. it("handler sees sesame", (done) => {
  718. let sesame = "AAAAAAAAAAAAAAA"
  719. let server = new RPCServer([{
  720. name: "createUser" as "createUser",
  721. RPCs: () => [{
  722. name: 'createUser' as 'createUser',
  723. call: createUser
  724. }]
  725. }], {
  726. sesame: sesame,
  727. errorHandler: (socket, e, rpcName, args) => {
  728. if (e.message === errtxt && rpcName === "createUser" && args[0] === sesame && args[1]['a'] === 'a' && args[1]['b'] === 'b')
  729. done()
  730. }
  731. })
  732. server.listen(21004)
  733. let sock = new RPCSocket(21004, 'localhost')
  734. sock.connect(sesame).then((cli) => {
  735. cli["createUser"]["createUser"]({
  736. a: 'a',
  737. b: 'b'
  738. })
  739. .then(r => {
  740. if (r != null)
  741. done(new Error("UNEXPECTED RESULT " + r))
  742. })
  743. .catch((e) => {
  744. done(new Error("UNEXPECTED CLIENT ERROR " + e))
  745. })
  746. .finally(() => {
  747. cli.close()
  748. sock.close()
  749. server.close()
  750. })
  751. })
  752. })
  753. })
  754. type myExporterIfc = {
  755. MyExporter: {
  756. myRPC: () => Promise<string>
  757. }
  758. }
  759. describe("Class binding", () => {
  760. let exporter1: MyExporter
  761. let serv: RPCServer<myExporterIfc>
  762. let sock: RPCSocket & myExporterIfc
  763. let allowed = true
  764. const SESAME = 'xyz'
  765. class MyExporter implements RPCExporter<myExporterIfc>{
  766. name = "MyExporter" as "MyExporter"
  767. RPCs = () => [
  768. this.myRPC
  769. ]
  770. myRPC = async () => {
  771. //serv.setExporters([new MyOtherExporter])
  772. return "Hello World"
  773. }
  774. }
  775. class MyOtherExporter implements RPCExporter<myExporterIfc>{
  776. name = "MyExporter" as "MyExporter"
  777. RPCs = () => [
  778. this.myRPC
  779. ]
  780. myRPC = async () => {
  781. return "Hello Borld"
  782. }
  783. }
  784. before(done => {
  785. exporter1 = new MyExporter()
  786. serv = new RPCServer<myExporterIfc>(
  787. [exporter1],
  788. {
  789. accessFilter: async (sesame, exporter) => {
  790. if (exporter.name === 'MyExporter') {
  791. if (!allowed) return false
  792. allowed = false
  793. return sesame === SESAME;
  794. } else {
  795. return false
  796. }
  797. },
  798. sesame: SESAME
  799. })
  800. serv.listen(21004)
  801. done()
  802. })
  803. beforeEach((done) => {
  804. const s = new RPCSocket<myExporterIfc>(21004, 'localhost')
  805. s.connect(SESAME).then(conn => {
  806. sock = conn
  807. done()
  808. })
  809. })
  810. afterEach((done) => {
  811. sock.close()
  812. done()
  813. })
  814. after(() => {
  815. serv.close()
  816. })
  817. it("use sesameFilter for available", (done) => {
  818. if (sock['MyExporter']) {
  819. allowed = false
  820. done()
  821. }
  822. else done(new Error("RPC supposed to be here"))
  823. })
  824. it("use sesameFilter", (done) => {
  825. if (!sock['MyExporter']) done()
  826. else done(new Error("RPC supposed to be gone"))
  827. })
  828. })
  829. /*
  830. describe('finally', () => {
  831. it('print open handles (Ignore `DNSCHANNEL` and `Immediate`)', () => {
  832. //log(console)
  833. })
  834. })
  835. */
  836. describe("attaching handlers before connecting", () => {
  837. it("fires error if server is unreachable", (done) => {
  838. const sock = new RPCSocket<myExporterIfc>(21004, 'localhost')
  839. let errorHandleCount = 0
  840. sock.on('error', (err) => {
  841. //attached listener fires first
  842. if (errorHandleCount != 0) {
  843. console.log("Error handler didn't fire first");
  844. } else {
  845. errorHandleCount++
  846. }
  847. })
  848. sock.connect().then(_ => {
  849. console.log("Unexpected successful connect")
  850. }).catch(e => {
  851. //catch clause fires second
  852. if (errorHandleCount != 1) {
  853. console.log("catch clause didn't fire second", errorHandleCount);
  854. } else {
  855. sock.close()
  856. done()
  857. }
  858. })
  859. })
  860. it("fires error if call is unknown", (done) => {
  861. const serv = new RPCServer().listen(21004)
  862. const sock = new RPCSocket(21004, 'localhost')
  863. sock.on('error', (err) => {
  864. sock.close()
  865. serv.close()
  866. done()
  867. })
  868. sock.connect().then(_ => {
  869. sock.call("unknownRPC123", "AAAAA").catch(e => { }).then(x => {
  870. console.log("X", x);
  871. })
  872. }).catch(e => {
  873. console.log("unexpected connect catch clause");
  874. done(e)
  875. })
  876. })
  877. })
  878. describe("class (de-)serialization", () => {
  879. @Serializable()
  880. class SubClass {
  881. fString = "F"
  882. }
  883. @Serializable()
  884. class TestClass {
  885. aString = "A"
  886. aNumber = 46
  887. aObject = {
  888. x: "x",
  889. y: undefined,
  890. sub: new SubClass()
  891. }
  892. aClassObject = new SubClass()
  893. public returnOK() {
  894. return "OK"
  895. }
  896. }
  897. const verifyObject = (obj: any) => {
  898. expect(obj).to.be.an.instanceOf(TestClass)
  899. expect(obj.aString).to.be.a('string')
  900. expect(obj.aNumber).to.be.a('number')
  901. expect(obj.aObject).to.be.a('object')
  902. expect(obj.aObject.x).to.be.a('string')
  903. expect(obj.aObject.y).to.be.undefined
  904. expect(obj.aObject.sub).to.be.an.instanceOf(SubClass)
  905. expect(obj.aClassObject).to.be.an.instanceOf(SubClass)
  906. expect(obj).to.not.have.key(CLASSNAME_ATTRIBUTE)
  907. expect(obj.aObject.sub).to.not.have.key(CLASSNAME_ATTRIBUTE)
  908. expect(obj.aClassObject).to.not.have.key(CLASSNAME_ATTRIBUTE)
  909. expect(obj.returnOK()).to.be.equal('OK')
  910. }
  911. describe("Responses", () => {
  912. type TestIfc = {
  913. Test: {
  914. returnClass: () => Promise<TestClass>
  915. classCallback: (callback: Callback<[TestClass]>) => Promise<TestClass>
  916. }
  917. }
  918. let myServer: RPCServer<TestIfc>;
  919. let mySocket: ConnectedSocket<TestIfc>;
  920. before(function (done) {
  921. myServer = new RPCServer<TestIfc>([{
  922. name: "Test",
  923. RPCs: [
  924. async function returnClass() {
  925. return new TestClass()
  926. }, {
  927. name: "classCallback",
  928. hook: async function (callback) {
  929. setTimeout(_ => callback(new TestClass()), 250)
  930. return new TestClass()
  931. }
  932. }
  933. ]
  934. }])
  935. myServer.listen(8084)
  936. new RPCSocket<TestIfc>(8084, 'localhost').connect().then(connsock => {
  937. mySocket = connsock
  938. done()
  939. })
  940. })
  941. after(function (done) {
  942. mySocket.close()
  943. myServer.close()
  944. done()
  945. })
  946. it("receives class object in call response", async () => {
  947. const obj: TestClass = await mySocket['Test'].returnClass()
  948. verifyObject(obj)
  949. })
  950. it("receives class object in hook response", async function () {
  951. const obj: TestClass = await mySocket.Test.classCallback(noop)
  952. verifyObject(obj)
  953. })
  954. it("receives class object in callback", function (done) {
  955. mySocket.Test.classCallback(function (cbValue) {
  956. verifyObject(cbValue)
  957. done()
  958. }).then(verifyObject)
  959. })
  960. })
  961. describe("Parameters", () => {
  962. it("Class object in call", function(done){
  963. const server = new RPCServer([
  964. {
  965. name: "Test",
  966. RPCs: [
  967. function callWithClass(testObj: TestClass){
  968. verifyObject(testObj)
  969. done()
  970. }
  971. ]
  972. }
  973. ]).listen(8086)
  974. new RPCSocket(8086, 'localhost').connect().then(sock => {
  975. sock['Test'].callWithClass(new TestClass()).then(_ => {
  976. sock.close()
  977. server.close()
  978. })
  979. })
  980. })
  981. })
  982. })