Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713
  1. import { describe, it } from "mocha";
  2. import { RPCServer, RPCSocket } from '../Index'
  3. import { RPCExporter } from "../src/Interfaces";
  4. import { ConnectedSocket } from "../src/Types";
  5. import * as log from 'why-is-node-running';
  6. const add = (...args: number[]) => { return args.reduce((a, b) => a + b, 0) }
  7. function makeServer() {
  8. let subcallback
  9. return new RPCServer(21010, [{
  10. name: 'test',
  11. exportRPCs: () => [
  12. {
  13. name: 'echo',
  14. call: async (s: string) => s,
  15. }, {
  16. name: 'simpleSubscribe',
  17. hook: async (callback) => {
  18. subcallback = callback
  19. return { topic: "test" }
  20. },
  21. onClose: (res) => { }
  22. }, {
  23. name: 'subscribe',
  24. hook: async (callback) => {
  25. subcallback = callback
  26. return { topic: "test" }
  27. },
  28. onClose: (res, rpc) => {
  29. console.log("onClose", rpc.name === 'subscribe' && res ? "OK" : "")
  30. subcallback = null
  31. },
  32. onCallback: (...args: any) => {
  33. console.log("onCallback", args[0] === "test" && args[1] === "callback" ? "OK" : "")
  34. }
  35. },
  36. add,
  37. function triggerCallback(...messages: any[]): number { return subcallback.apply({}, messages) },
  38. ]
  39. }], {
  40. connectionHandler: (socket) => { },
  41. closeHandler: (socket) => { },
  42. errorHandler: (socket, err) => { throw err }
  43. })
  44. }
  45. describe('RPCServer', () => {
  46. let client, server
  47. const echo = (x) => x
  48. before(done => {
  49. server = new RPCServer(21003, [{
  50. name: 'HelloWorldRPCGroup',
  51. exportRPCs: () => [
  52. echo, //named function variable
  53. function echof(x) { return x }, //named function
  54. {
  55. name: 'echoExplicit', //describing object
  56. call: async (x, y, z) => [x, y, z]
  57. }
  58. ]
  59. }])
  60. client = new RPCSocket(21003, 'localhost')
  61. done()
  62. })
  63. after(done => {
  64. client.destroy()
  65. server.destroy()
  66. done()
  67. })
  68. it('should be able to use all kinds of RPC definitions', (done) => {
  69. client.connect().then(async () => {
  70. const r0 = await client['HelloWorldRPCGroup'].echo('Hello')
  71. const r1 = await client['HelloWorldRPCGroup'].echof('World')
  72. const r2 = await client['HelloWorldRPCGroup'].echoExplicit('R', 'P', 'C!')
  73. if (r0 === 'Hello' && r1 === 'World' && r2.join('') === 'RPC!') {
  74. done()
  75. }else{
  76. done(new Error("Bad response"))
  77. }
  78. })
  79. })
  80. it('new RPCServer() should fail on bad RPC', (done) => {
  81. try {
  82. new RPCServer(20001, [{
  83. name: 'bad',
  84. exportRPCs: () => [
  85. (aaa, bbb, ccc) => { return aaa + bbb + ccc }
  86. ]
  87. }])
  88. done(new Error("Didn't fail with bad RPC"))
  89. } catch (badRPCError) {
  90. done()
  91. }
  92. })
  93. })
  94. describe('RPCSocket', () => {
  95. let client: RPCSocket
  96. let server: RPCServer
  97. before(async () => {
  98. server = makeServer()
  99. client = new RPCSocket(21010, "localhost")
  100. return await client.connect()
  101. })
  102. after(() => {
  103. client.destroy()
  104. server.destroy()
  105. })
  106. it('should have rpc echo', (done) => {
  107. client['test'].echo("x").then(x => {
  108. if (x === 'x')
  109. done()
  110. else
  111. done(new Error('echo RPC response did not match'))
  112. })
  113. })
  114. it('should add up to 6', (done) => {
  115. client['test'].add(1, 2, 3).then(x => {
  116. if (x === 6)
  117. done()
  118. else
  119. done(new Error('add RPC response did not match'))
  120. })
  121. })
  122. it('should subscribe with success', (done) => {
  123. client['test'].simpleSubscribe(console.log).then(res => {
  124. if (res.topic === 'test') {
  125. done()
  126. } else {
  127. console.error(res)
  128. done(new Error('Subscribe did not return success'))
  129. }
  130. })
  131. })
  132. it('subscribe should call back', (done) => {
  133. client['test'].subscribe((...args: any) => {
  134. if (args[0] === "test" && args[1] === "callback")
  135. done()
  136. else
  137. done(new Error("Bad callback value " + args))
  138. }).then(async () => {
  139. await client['test'].triggerCallback("test", "callback")
  140. })
  141. })
  142. it('simpleSubscribe should call back', (done) => {
  143. client['test'].simpleSubscribe((...args: any) => {
  144. if (args[0] === "test_" && args[1] === "callback_")
  145. done()
  146. else
  147. done(new Error("Bad callback value " + args))
  148. }).then(async () => {
  149. await client['test'].triggerCallback("test_", "callback_")
  150. })
  151. })
  152. })
  153. describe('It should do unhook', () => {
  154. const yesCandy = "OK"
  155. const noCandy = "stolen"
  156. let candy = yesCandy
  157. let cb: Function
  158. let cb2: Function
  159. let client: RPCSocket
  160. let server: RPCServer
  161. before(async () => {
  162. server = new RPCServer(21010, [{
  163. name: "test",
  164. exportRPCs: () => [{
  165. name: 'subscribe',
  166. hook: async (callback): Promise<void> => {
  167. cb = <Function>callback
  168. return
  169. }
  170. },
  171. {
  172. name: 'subscribeWithParam',
  173. hook: async (param, callback): Promise<{ uuid: string }> => {
  174. if (param != "OK") {
  175. console.log("param was" + param);
  176. return {
  177. uuid: "no",
  178. }
  179. }
  180. cb2 = <Function>callback
  181. return {
  182. uuid: "OK",
  183. }
  184. }
  185. },
  186. function publish(): string { cb(candy); return candy },
  187. function unsubscribe(): string { candy = noCandy; cb(candy); cb = () => { }; return candy }
  188. ]
  189. }], {
  190. connectionHandler: (socket) => { },
  191. closeHandler: (socket) => { },
  192. errorHandler: (socket, err) => { throw err }
  193. })
  194. client = new RPCSocket(21010, "localhost")
  195. return await client.connect()
  196. })
  197. after(() => {
  198. client.destroy()
  199. server.destroy()
  200. })
  201. it('Subscribe with param', (done) => {
  202. client['test'].subscribeWithParam("OK", c => { }).then(async (res) => {
  203. if (res.uuid === candy) {
  204. done()
  205. } else
  206. done(new Error("Results did not match " + res.uuid))
  207. })
  208. })
  209. let run = 0
  210. const expected = [yesCandy, noCandy, noCandy, noCandy]
  211. it('Unhook+unsubscribe should stop callbacks', (done) => {
  212. client['test'].subscribe(function myCallback(c){
  213. if(run == 1)
  214. (myCallback as any).destroy()
  215. if (c !== expected[run++]) {
  216. done(new Error(`Wrong candy '${c}' in iteration '${run - 1}'`))
  217. }
  218. }).then(async function(res){
  219. const r1 = await client['test'].publish()
  220. const r3 = await client['test'].unsubscribe()
  221. const r2 = await client['test'].publish()
  222. const r4 = await client['test'].publish()
  223. if (r1 === yesCandy && r3 === noCandy && r2 === noCandy && r4 === noCandy)
  224. done()
  225. else
  226. done(new Error("Results did not match: " + [r1, r2, r3, r4]))
  227. })
  228. })
  229. })
  230. type topicDTO = { topic: string; }
  231. type SesameTestIfc = {
  232. test: {
  233. checkCandy: () => Promise<string>
  234. subscribe: (callback: Function) => Promise<topicDTO>
  235. manyParams: <A=string,B=number,C=boolean,D=Object>(a:A, b:B, c:C, d:D) => Promise<[A, B, C, D]>
  236. }
  237. other: {
  238. echo: (x:any) => Promise<any>
  239. }
  240. }
  241. describe('Sesame should unlock the socket', () => {
  242. let candy = "OK"
  243. let client: ConnectedSocket<SesameTestIfc>
  244. let server: RPCServer<SesameTestIfc>
  245. let cb: Function = (...args) => { }
  246. before((done) => {
  247. server = new RPCServer<SesameTestIfc>(21004, [{
  248. name: "test",
  249. exportRPCs: () => [
  250. {
  251. name: 'subscribe',
  252. hook: async (callback) => {
  253. cb = callback
  254. return {
  255. topic: 'test'
  256. }
  257. },
  258. onClose: (a) => { }
  259. },
  260. async function checkCandy() { cb(candy); cb = () => { }; return candy },
  261. async function manyParams(a, b, c, d) { return [a, b, c, d] }
  262. ],
  263. },{
  264. name: 'other',
  265. exportRPCs: () => [
  266. async function echo(x){return x}
  267. ]
  268. }], {
  269. sesame: (_sesame) => _sesame === 'sesame!'
  270. })
  271. const sock = new RPCSocket<SesameTestIfc>(21004, "localhost")
  272. sock.connect('sesame!').then(cli => {
  273. client = cli
  274. done()
  275. })
  276. })
  277. after(() => {
  278. client.destroy()
  279. server.destroy()
  280. })
  281. it('should work with sesame', (done) => {
  282. client.test.checkCandy().then(c => done())
  283. })
  284. it('should work with multiple params', (done) => {
  285. client.test['manyParams']('a', 'b', 'c', 'd').then(c => {
  286. if (c[0] == 'a' && c[1] === 'b' && c[2] === 'c' && c[3] === 'd')
  287. done()
  288. })
  289. })
  290. it('should not work without sesame', (done) => {
  291. const sock = new RPCSocket(21004, "localhost")
  292. sock.connect( /* no sesame */).then(async (cli) => {
  293. if (!cli.test)
  294. done()
  295. else {
  296. done(new Error("Function supposed to be removed without sesame"))
  297. }
  298. cli.destroy()
  299. sock.destroy()
  300. })
  301. })
  302. it('should fail with wrong sesame', (done) => {
  303. const sock = new RPCSocket(21004, "localhost")
  304. sock.connect('abasd').then(async (cli) => {
  305. if (!cli.test)
  306. done()
  307. else {
  308. done(new Error("Function supposed to be removed without sesame"))
  309. }
  310. cli.destroy()
  311. sock.destroy()
  312. })
  313. })
  314. it('callback should work with sesame', (done) => {
  315. client.test.subscribe((c) => {
  316. if (c === candy) {
  317. done()
  318. }
  319. }).then(d => {
  320. if (d.topic !== 'test')
  321. done('unexpected invalid response')
  322. client.test.checkCandy()
  323. })
  324. })
  325. })
  326. describe('Error handling', () => {
  327. const errtxt = "BAD BAD BAD"
  328. let createUser = async (user: { a: any, b: any }) => {
  329. throw new Error(errtxt)
  330. }
  331. it("RPC throws on client without handler", (done) => {
  332. let server = new RPCServer(21004, [{
  333. name: "createUser",
  334. exportRPCs: () => [{
  335. name: 'createUser' as 'createUser',
  336. call: createUser
  337. }]
  338. }], {
  339. })
  340. let sock = new RPCSocket(21004, 'localhost')
  341. sock.connect().then((cli) => {
  342. cli["createUser"]["createUser"]({
  343. a: 'a',
  344. b: 'b'
  345. })
  346. .then(r => {
  347. if (r != null)
  348. done(new Error("UNEXPECTED RESULT " + r))
  349. })
  350. .catch((e) => {
  351. if (e.message === errtxt)
  352. done()
  353. else
  354. done(e)
  355. })
  356. .finally(() => {
  357. cli.destroy()
  358. sock.destroy()
  359. server.destroy()
  360. })
  361. })
  362. })
  363. it("RPC throws on server with handler", (done) => {
  364. let server = new RPCServer(21004, [{
  365. name: "createUser",
  366. exportRPCs: () => [{
  367. name: 'createUser' as 'createUser',
  368. call: createUser
  369. }]
  370. }], {
  371. errorHandler: (socket, e, rpcName, args) => {
  372. done()
  373. }
  374. })
  375. let sock = new RPCSocket(21004, 'localhost')
  376. sock.connect().then((cli) => {
  377. cli["createUser"]["createUser"]({
  378. a: 'a',
  379. b: 'b'
  380. })
  381. .then(r => {
  382. if (r != null)
  383. done("UNEXPECTED RESULT " + r)
  384. })
  385. .catch((e) => {
  386. done("UNEXPECTED CLIENT ERROR " + e)
  387. done(e)
  388. })
  389. .finally(() => {
  390. cli.destroy()
  391. sock.destroy()
  392. server.destroy()
  393. })
  394. })
  395. })
  396. })
  397. describe("Errorhandler functionality", () => {
  398. const errtxt = "BAD BAD BAD"
  399. let createUser = async (user: { a: any, b: any }) => {
  400. throw new Error(errtxt)
  401. }
  402. it("correct values are passed to the handler", (done) => {
  403. let server = new RPCServer(21004, [{
  404. name: "createUser",
  405. exportRPCs: () => [{
  406. name: 'createUser' as 'createUser',
  407. call: createUser
  408. }]
  409. }], {
  410. errorHandler: (socket, e, rpcName, args) => {
  411. if (e.message === errtxt && rpcName === "createUser" && args[0]['a'] === 'a' && args[0]['b'] === 'b')
  412. done()
  413. }
  414. })
  415. let sock = new RPCSocket(21004, 'localhost')
  416. sock.connect().then((cli) => {
  417. cli["createUser"]["createUser"]({
  418. a: 'a',
  419. b: 'b'
  420. })
  421. .then(r => {
  422. if (r != null)
  423. done("UNEXPECTED RESULT " + r)
  424. })
  425. .catch((e) => {
  426. done(new Error("UNEXPECTED CLIENT ERROR " + e.message))
  427. })
  428. .finally(() => {
  429. cli.destroy()
  430. sock.destroy()
  431. server.destroy()
  432. })
  433. })
  434. })
  435. it("handler sees sesame", (done) => {
  436. let sesame = "AAAAAAAAAAAAAAA"
  437. let server = new RPCServer(21004, [{
  438. name: "createUser" as "createUser",
  439. exportRPCs: () => [{
  440. name: 'createUser' as 'createUser',
  441. call: createUser
  442. }]
  443. }], {
  444. sesame: sesame,
  445. errorHandler: (socket, e, rpcName, args) => {
  446. if (e.message === errtxt && rpcName === "createUser" && args[0] === sesame && args[1]['a'] === 'a' && args[1]['b'] === 'b')
  447. done()
  448. }
  449. })
  450. let sock = new RPCSocket(21004, 'localhost')
  451. sock.connect(sesame).then((cli) => {
  452. cli["createUser"]["createUser"]({
  453. a: 'a',
  454. b: 'b'
  455. })
  456. .then(r => {
  457. if (r != null)
  458. done("UNEXPECTED RESULT " + r)
  459. })
  460. .catch((e) => {
  461. done("UNEXPECTED CLIENT ERROR " + e)
  462. done(e)
  463. })
  464. .finally(() => {
  465. cli.destroy()
  466. sock.destroy()
  467. server.destroy()
  468. })
  469. })
  470. })
  471. })
  472. type myExporterIfc = {
  473. MyExporter: {
  474. myRPC: () => Promise<string>
  475. }
  476. }
  477. describe("Class binding", () => {
  478. let exporter1: MyExporter
  479. let serv: RPCServer<myExporterIfc>
  480. let sock: RPCSocket & myExporterIfc
  481. let allowed = true
  482. class MyExporter implements RPCExporter<myExporterIfc>{
  483. name = "MyExporter" as "MyExporter"
  484. exportRPCs = () => [
  485. this.myRPC
  486. ]
  487. myRPC = async () => {
  488. //serv.setExporters([new MyOtherExporter])
  489. return "Hello World"
  490. }
  491. }
  492. class MyOtherExporter implements RPCExporter<myExporterIfc>{
  493. name = "MyExporter" as "MyExporter"
  494. exportRPCs = () => [
  495. this.myRPC
  496. ]
  497. myRPC = async () => {
  498. return "Hello Borld"
  499. }
  500. }
  501. before(done => {
  502. exporter1 = new MyExporter()
  503. serv = new RPCServer<myExporterIfc>(21004, [exporter1], {
  504. accessFilter: async (sesame, exporter) => {
  505. if(exporter.name === 'MyExporter'){
  506. if (!allowed) return false
  507. allowed = false
  508. return sesame === 'xxx';
  509. }else{
  510. return false
  511. }
  512. },
  513. sesame: "xxx"
  514. })
  515. done()
  516. })
  517. beforeEach((done) => {
  518. const s = new RPCSocket<myExporterIfc>(21004, 'localhost')
  519. s.connect("xxx").then(conn => {
  520. sock = conn
  521. done()
  522. })
  523. })
  524. afterEach((done) => {
  525. sock.destroy()
  526. done()
  527. })
  528. after(() => {
  529. serv.destroy()
  530. })
  531. /* The server-side socket will enter a 30s timeout if destroyed by a RPC.
  532. to mitigate the impact on testing time these are not run.
  533. it("binds correctly", function(done){
  534. this.timeout(1000)
  535. sock['MyExporter'].myRPC().then((res) => {
  536. done(new Error(res))
  537. }).catch(e => {
  538. //job will time out because of setExporters
  539. allowed = true
  540. done()
  541. })
  542. })
  543. it("changes exporters", (done) => {
  544. sock['MyExporter'].myRPC().then((res) => {
  545. if (res === "Hello Borld")
  546. done()
  547. else
  548. done(new Error(res))
  549. })
  550. })
  551. */
  552. it("use sesameFilter for available", (done) => {
  553. if (sock['MyExporter']){
  554. allowed = false
  555. done()
  556. }
  557. else done(new Error("RPC supposed to be here"))
  558. })
  559. it("use sesameFilter", (done) => {
  560. if (!sock['MyExporter']) done()
  561. else done(new Error("RPC supposed to be gone"))
  562. })
  563. })
  564. describe("attaching handlers before connecting", () => {
  565. it("fires error if server is unreachable", (done) => {
  566. const sock = new RPCSocket<myExporterIfc>(21004, 'localhost')
  567. let errorHandleCount = 0
  568. sock.on('error', (err) => {
  569. //attached listener fires first
  570. if (errorHandleCount != 0) {
  571. console.log("Error handler didn't fire first");
  572. } else {
  573. errorHandleCount++
  574. }
  575. })
  576. sock.connect().then(_ => {
  577. console.log("Unexpected successful connect")
  578. }).catch(e => {
  579. //catch clause fires second
  580. if (errorHandleCount != 1) {
  581. console.log("catch clause didn't fire second");
  582. } else {
  583. sock.destroy()
  584. done()
  585. }
  586. })
  587. })
  588. it("fires error if call is unknown", (done) => {
  589. const serv = new RPCServer(21004)
  590. const sock = new RPCSocket(21004, 'localhost')
  591. sock.on('error', (err) => {
  592. sock.destroy()
  593. serv.destroy()
  594. done()
  595. })
  596. sock.connect().then(_ => {
  597. sock.call("unknownRPC123", "AAAAA").catch(e => { /* ignore */ })
  598. }).catch(e => {
  599. console.log("unexpected connect catch clause");
  600. done(e)
  601. })
  602. })
  603. it("demands catch on method invocation if call is unknown", (done) => {
  604. const serv = new RPCServer(21004)
  605. const sock = new RPCSocket(21004, 'localhost')
  606. sock.connect().then(_ => {
  607. sock.call("unknownRPC123", "AAAAA").catch(e => {
  608. sock.destroy()
  609. serv.destroy()
  610. done()
  611. })
  612. }).catch(e => {
  613. console.log("unexpected connect catch clause");
  614. done(e)
  615. })
  616. })
  617. })
  618. describe('finally', () => {
  619. it('print open handles (Ignore `DNSCHANNEL` and `Immediate`)', () => {
  620. log()
  621. })
  622. })