UNPKG

fox-wamp

Version:

Web Application Message Router/Server WAMP/MQTT

451 lines (372 loc) 16.9 kB
'use strict' const chai = require('chai') const spies = require('chai-spies') const expect = chai.expect const assert = chai.assert const promised = require('chai-as-promised') const { isDataEmpty, deepDataMerge } = require('../lib/realm') const WAMP = require('../lib/wamp/protocol') const { WampGate } = require('../lib/wamp/gate') const FoxRouter = require('../lib/fox_router') const MemKeyValueStorage = require('../lib/mono/memkv').MemKeyValueStorage chai.use(promised) chai.use(spies) describe('20 wamp-realm', () => { let socketHistory, router, gate, realm, mockSocket, ctx, cli, api beforeEach(function () { router = new FoxRouter() realm = router.getRealm('test-realm') api = realm.wampApi() socketHistory = [] mockSocket = { wampPkgWrite: chai.spy(((msg, callback) => socketHistory.push(msg))) } gate = new WampGate(router) cli = router.createSession() ctx = gate.createContext(cli, mockSocket) realm.joinSession(cli) }) afterEach(() => { assert.isFalse(api.hasSendError(), api.firstSendErrorMessage()) assert.isFalse(cli.hasSendError(), cli.firstSendErrorMessage()) }) it('empty cleanup', function () { realm.leaveSession(cli) realm.leaveSession(api) }) it('session-list', function () { let result = realm.getSessionIds() expect(result).to.be.an('array').that.is.not.empty }) it('isDataEmpty', () => { assert.isTrue(isDataEmpty(null)) assert.isTrue(isDataEmpty({args:[]})) assert.isTrue(isDataEmpty({args:[null]})) assert.isTrue(isDataEmpty({kv:null})) assert.isFalse(isDataEmpty({args:[1]})) }) it('deepDataMerge', () => { let result result = deepDataMerge( { args:[{key1: "v1", key2: {key3: "v3"}}] }, { payload: Buffer.from('{"key1":"v1-update","key2":{"key5":"v5"}}') } ) expect(result).to.deep.equal({ kv: { key1: 'v1-update', key2: { key3: 'v3', key5: 'v5' } } }) result = deepDataMerge( { args:[{key1: "v1", key2: {key3: "v3"}}] }, { payload: Buffer.from('{"json-error":<package>') } ) expect(result).to.deep.equal({ args:[{key1: "v1", key2: {key3: "v3"}}] }) }) describe('RPC', function () { it('CALL to RPC not exist', function () { gate.handle(ctx, cli, [WAMP.CALL, 1234, {}, 'any.function.name', []]) expect(mockSocket.wampPkgWrite).to.have.been.called.once() const msg = socketHistory.shift() expect(msg[0]).to.equal(WAMP.ERROR) expect(msg[1]).to.equal(WAMP.CALL) expect(msg[2]).to.equal(1234) expect(msg[4]).to.equal('wamp.error.no_such_procedure') expect(msg[5]).to.deep.equal(['no callee registered for procedure <any.function.name>']) }) it('cleanup RPC API', function () { api.cleanupReg(realm.engine) // clean existing wamp/session/? functions var procSpy = chai.spy(function () {}) api.register('func1', procSpy) expect(api.cleanupReg(realm.engine)).to.equal(1) expect(api.cleanupReg(realm.engine)).to.equal(0) expect(procSpy).to.not.have.been.called() }) it('CALL-to-router', async () => { let procSpy = chai.spy(function (id, args, kwargs) { api.resrpc(id, undefined, [...args, 'result1'], { kres: kwargs }) }) let regId = await api.register('func1', procSpy) gate.handle(ctx, cli, [WAMP.CALL, 1234, {}, 'func1', ['arg1'], { kArg: 'kVal' }]) expect(procSpy, 'RPC delivered').to.have.been.called.once() expect(mockSocket.wampPkgWrite, 'result delivered').to.have.been.called.once() const msg = socketHistory.shift() expect(msg[0]).to.equal(WAMP.RESULT) expect(msg[1]).to.equal(1234) expect(msg[3]).to.deep.equal(['arg1', 'result1']) expect(msg[4]).to.deep.equal({ kres:{ kArg: 'kVal' }}) const subActor = api.getSub(regId) expect(subActor.isAble()).to.equal(true) expect(subActor.getTasksRequestedCount()).to.equal(0) expect(api.unregister(regId)).to.equal('func1') }) it('CALL to router with error', async () => { var procSpy = chai.spy(function (id, args, kwargs) { api.resrpc(id, 'error-message', ['result.1', 'result.2'], { kVal: 'kRes' }) }) let regId = await api.register('func1', procSpy) gate.handle(ctx, cli, [WAMP.CALL, 1234, {}, 'func1', ['arg1', 'arg2'], { kArg: 'kVal' }]) expect(procSpy).to.have.been.called.once() expect(mockSocket.wampPkgWrite).to.have.been.called.once() let msg = socketHistory.shift() expect(msg[0]).to.equal(WAMP.ERROR) expect(msg[1]).to.equal(WAMP.CALL) expect(msg[2]).to.equal(1234) expect(msg[4]).to.equal('wamp.error.callee_failure') expect(msg[5]).to.deep.equal(['error-message']) const subActor = api.getSub(regId) expect(subActor.isAble()).to.equal(true) expect(subActor.getTasksRequestedCount()).to.equal(0) }) it('UNREGISTER error', async () => { gate.handle(ctx, cli, [WAMP.UNREGISTER, 2345, 1234567890]) expect(mockSocket.wampPkgWrite, 'unregistration confirmed').to.have.been.called.once() let msg = socketHistory.shift() expect(msg[0]).to.equal(WAMP.ERROR) expect(msg[1]).to.equal(WAMP.UNREGISTER) expect(msg[2]).to.equal(2345) // 3 options is skipped expect(msg[4]).to.equal('wamp.error.no_such_registration') }) it('UNREGISTER', function () { gate.handle(ctx, cli, [WAMP.REGISTER, 1234, {}, 'func1']) let msg = socketHistory.shift() expect(msg[0]).to.equal(WAMP.REGISTERED) expect(msg[1]).to.equal(1234) let registrationId = msg[2] gate.handle(ctx, cli, [WAMP.UNREGISTER, 2345, registrationId]) msg = socketHistory.shift() expect(msg[0]).to.equal(WAMP.UNREGISTERED) expect(msg[1]).to.equal(2345) expect(mockSocket.wampPkgWrite, 'unregistration confirmed').to.have.been.called.twice() }) it('CALL-to-remote', async () => { gate.handle(ctx, cli, [WAMP.REGISTER, 1234, {}, 'func1']) let msg = socketHistory.shift() expect(msg[0]).to.equal(WAMP.REGISTERED) expect(msg[1]).to.equal(1234) let registrationId = msg[2] const callPromise = api.callrpc('func1', ['arg1'], { v1: 'kRes' }) msg = socketHistory.shift() expect(msg[0]).to.equal(WAMP.INVOCATION) let callId = msg[1] expect(msg[2]).to.equal(registrationId) expect(msg[3]).to.deep.equal({}) // opt expect(msg[4]).to.deep.equal(['arg1']) // args expect(msg[5]).to.deep.equal({ v1: 'kRes' }) // kwargs // return the function result gate.handle(ctx, cli, [WAMP.YIELD, callId, {}, ['result1'], { foo: 'bar' }]) await assert.becomes(callPromise, {args:['result1'], kwargs:{foo: 'bar'}}) }) it('CALL error to remote', async () => { mockSocket.wampPkgWrite = () => {} gate.handle(ctx, cli, [WAMP.REGISTER, 1234, {}, 'func1']) mockSocket.wampPkgWrite = chai.spy((msg, callback) => { let callId = msg[1] gate.handle(ctx, cli, [WAMP.ERROR, WAMP.INVOCATION, callId, {}, 'test-error-text', ['err.detail.1', 'err.detail.2']]) }) let result = await api.callrpc('func1', ['arg.1']).then(() => 'resolve-not-accepted', (reason) => reason) expect(result).to.deep.equal({code:'wamp.error.callee_failure', message:'test-error-text'}) expect(mockSocket.wampPkgWrite, 'invocation received').to.have.been.called.once() }) it('CALL-set-concurrency', function () { gate.handle(ctx, cli, [WAMP.REGISTER, 1234, { concurrency: 2 }, 'func1']) let msg = socketHistory.shift() expect(msg[0]).to.equal(WAMP.REGISTERED) api.callrpc('func1', [], {}) api.callrpc('func1', [], {}) api.callrpc('func1', [], {}) msg = socketHistory.shift() expect(msg[0]).to.equal(WAMP.INVOCATION) msg = socketHistory.shift() expect(msg[0]).to.equal(WAMP.INVOCATION) expect(mockSocket.wampPkgWrite).to.have.been.called.exactly(3) }) it('CALL-concurrency-unlimited', function () { mockSocket.wampPkgWrite = function () {} gate.handle(ctx, cli, [WAMP.REGISTER, 1234, {}, 'func1']) mockSocket.wampPkgWrite = chai.spy((msg, callback) => {}) api.callrpc('func1', [], {}) api.callrpc('func1', [], {}) api.callrpc('func1', [], {}) expect(mockSocket.wampPkgWrite).to.have.been.called.exactly(3) }) it('progress-remote-CALL', async () => { gate.handle(ctx, cli, [WAMP.REGISTER, 1234, {}, 'func1']) socketHistory.shift() let callId = null mockSocket.wampPkgWrite = chai.spy((msg, callback) => { expect(msg[0]).to.equal(WAMP.INVOCATION) callId = msg[1] // qid expect(msg[3]).to.deep.equal({ receive_progress: true }) gate.handle(ctx, cli, [WAMP.YIELD, callId, { progress: true }, ['result.1'], {kv:1}]) gate.handle(ctx, cli, [WAMP.YIELD, callId, { progress: true }, ['result.2'], {kv:2}]) gate.handle(ctx, cli, [WAMP.YIELD, callId, {}, ['result.3.final'], {kv:3}]) }) let progressResult = [] let callResponse = (args, kwargs) => { progressResult.push([args, kwargs]) } let finalResult = await api.callrpc('func1', [], {}, callResponse, { receive_progress: 1 }) expect(mockSocket.wampPkgWrite, 'invocation received').to.have.been.called.once() expect(progressResult).to.deep.equal([ [['result.1'], {kv:1}], [['result.2'], {kv:2}] ]) expect(finalResult).to.deep.equal({args:['result.3.final'], kwargs: { kv: 3 }}) mockSocket.wampPkgWrite = chai.spy((msg, callback) => { expect(msg[0]).to.equal(WAMP.ERROR) }) gate.handle(ctx, cli, [WAMP.YIELD, callId, {}, ['result.response.error']]) expect(mockSocket.wampPkgWrite).to.have.been.called.once() }) }) describe('PUBLISH', function () { it('UNSUBSCRIBE-ERROR', function () { gate.handle(ctx, cli, [WAMP.UNSUBSCRIBE, 2345, 1234567890]) expect(mockSocket.wampPkgWrite, 'unsubscription confirmed').to.have.been.called.once() let msg = socketHistory.shift() expect(msg[0]).to.equal(WAMP.ERROR) expect(msg[1]).to.equal(WAMP.UNSUBSCRIBE) expect(msg[2]).to.equal(2345) // 3 options expect(msg[4]).to.equal('wamp.error.no_such_subscription') }) it('UNSUBSCRIBE-OK', function () { gate.handle(ctx, cli, [WAMP.SUBSCRIBE, 1234, {}, 'topic1']) let msg = socketHistory.shift() expect(msg[0]).to.equal(WAMP.SUBSCRIBED) expect(msg[1]).to.equal(1234) let subscriptionId = msg[2] gate.handle(ctx, cli, [WAMP.UNSUBSCRIBE, 2345, subscriptionId]) msg = socketHistory.shift() expect(msg[0]).to.equal(WAMP.UNSUBSCRIBED) expect(msg[1]).to.equal(2345) expect(mockSocket.wampPkgWrite, 'unsubscription confirmed').to.have.been.called.twice() }) it('cleanup Topic API', function () { let subSpy = chai.spy(function () {}) api.subscribe('topic1', subSpy) expect(api.cleanupTrace(realm.engine)).to.equal(1) expect(api.cleanupTrace(realm.engine)).to.equal(0) expect(subSpy).to.not.have.been.called() }) it('PUBLISH default exclude_me:true', function () { let subSpy = chai.spy(function () {}) api.subscribe('topic1', subSpy) api.publish('topic1', [], {}) expect(subSpy).to.not.have.been.called() }) it('PUBLISH exclude_me:false', async () => { let subSpy = chai.spy(() => {}) await api.subscribe('topic.1', subSpy) await api.publish('topic.1', [], {}, { acknowledge: true, exclude_me: false }) expect(subSpy).to.have.been.called.once() }) it('PUBLISH NULL', async () => { let pubs = [] let subSpy = chai.spy((publicationId, args, kwargs, opt) => { pubs.push([args, kwargs]) }) await api.subscribe('topic.1', subSpy) gate.handle(ctx, cli, [WAMP.PUBLISH, 1234, {}, 'topic.1', null, null /* undefined args & kwargs */]) expect(subSpy).to.have.been.called.once() expect(pubs.shift()).to.deep.equal([null,null]) }) it('PUBLISH-to-pattern', function () { var subSpy = chai.spy(function (a, b, c, d) { // console.log('Publish Event', a,b,c,d) }) api.subscribe('topic1.*.item', subSpy) api.publish('topic1.123.item', ['arg'], {}, { acknowledge: true, exclude_me: false }) expect(subSpy).to.have.been.called.once() }) it('PUBLISH-to-remote', () => { gate.handle(ctx, cli, [WAMP.SUBSCRIBE, 1234, {}, 'topic1']) let msg = socketHistory.shift() expect(msg[0]).to.equal(WAMP.SUBSCRIBED) expect(msg[1]).to.equal(1234) let subscriptionId = msg[2] api.publish('topic1', ['arg1'], { foo: 'bar' }, {retain: true}) msg = socketHistory.shift() expect(msg[0]).to.equal(WAMP.EVENT) expect(msg[1]).to.equal(subscriptionId) // 2 published message Id expect(msg[3]).to.deep.equal({topic:'topic1', publisher: api.getSid()}) expect(msg[4]).to.deep.equal(['arg1']) expect(msg[5]).to.deep.equal({ foo: 'bar' }) expect(mockSocket.wampPkgWrite, 'publication received').to.have.been.called.twice() }) it('SUBSCRIBE-to-remote-wamp', async () => { const publications = [] let subSpy = chai.spy((publicationId, args, kwargs) => { publications.push([args, kwargs]) }) let subId = await api.subscribe('topic1', subSpy) gate.handle(ctx, cli, [WAMP.PUBLISH, 1234, {}, 'topic1', ['arg.11', 'arg.12'], { foo: 'bar1' }]) expect(socketHistory.length).to.equal(0) // no ack sent let pub = publications.shift() expect(pub[0] /* args */ ).to.deep.equal(['arg.11', 'arg.12']) expect(pub[1] /* kwargs */).to.deep.equal({ foo: 'bar1' }) expect(mockSocket.wampPkgWrite, 'ack is not requested').to.not.have.been.called() gate.handle(ctx, cli, [WAMP.PUBLISH, 2345, { acknowledge: true }, 'topic1', ['arg.21', 'arg.22'], { foo: 'bar2' }]) expect(mockSocket.wampPkgWrite, 'ack must be received').to.have.been.called.once() let msg = socketHistory.shift() expect(msg[0]).to.equal(WAMP.PUBLISHED) expect(msg[1]).to.equal(2345) pub = publications.shift() expect(pub[0] /* args */ ).to.deep.equal(['arg.21', 'arg.22']) expect(pub[1] /* kwargs */).to.deep.equal({ foo: 'bar2' }) expect(subSpy, 'publication done').to.have.been.called.twice() expect(api.unsubscribe(subId)).to.equal('topic1') }) it('omit-tasks-of-terminated-sessions', async () => { let uFunc = chai.spy((id, args, kwargs, opt) => {setImmediate(() => { cli.cleanup().then(() => { api.resrpc(id, null, ['any-result']) }) })}) await api.register('func1', uFunc) mockSocket.wampPkgWrite = chai.spy() gate.handle(ctx, cli, [WAMP.CALL, 1231, {}, 'func1', ['call-1']]) gate.handle(ctx, cli, [WAMP.CALL, 1232, {}, 'func1', ['call-2']]) gate.handle(ctx, cli, [WAMP.CALL, 1233, {}, 'func1', ['call-3']]) await api.callrpc('func1', ['call-4']) expect(uFunc).to.have.been.called.twice() expect(mockSocket.wampPkgWrite).to.have.been.called.once() }) }) describe('STORAGE', function () { it('reduce-one', function () { mockSocket.wampPkgWrite = chai.spy((msg, callback) => { // console.log('REDUCE-CALL', msg); }) gate.handle(ctx, cli, [WAMP.REGISTER, 1234, { reducer: true }, 'storage']) api.publish('storage', [{ data: 'init-value', count: 1 }], {}, { retain: true }) api.publish('storage', [{ data: 'value-to-reduce', count: 2 }], {}, { retain: true }) expect(mockSocket.wampPkgWrite).to.have.been.called.exactly(3) }) it('custom-key-value', function () { const app = new MemKeyValueStorage() realm.registerKeyValueEngine(['cache', '*', 'name', '#'], app) api.publish('cache.user.name.john', [{ fullName: 'John Doe' }], {}, { retain: true }) const row = chai.spy((aKey, data) => { expect(aKey).to.deep.equal(['user', 'john']) expect(data).to.deep.equal({ args: [], kwargs: { fullName: 'John Doe' } }) }) app.getKey(['*', 'john'], row) expect(row, 'data has to be saved').to.have.been.called.exactly(1) mockSocket.wampPkgWrite = chai.spy((msg, callback) => { if (msg[1] === WAMP.EVENT) { expect(msg[3]).to.deep.equal({ topic: 'cache.user.name.john', retained: true }) expect(msg[4]).to.deep.equal([]) expect(msg[5]).to.deep.equal({ fullName: 'John Doe' }) } }) gate.handle(ctx, cli, [WAMP.SUBSCRIBE, 1234, { retained: true }, 'cache.*.name.#']) expect(mockSocket.wampPkgWrite).to.have.been.called.exactly(2) }) }) })