UNPKG

noflo

Version:

Flow-Based Programming environment for JavaScript

1,643 lines (1,516 loc) 62 kB
if typeof process isnt 'undefined' and process.execPath and process.execPath.match /node|iojs/ chai = require 'chai' unless chai noflo = require '../src/lib/NoFlo.coffee' else noflo = require 'noflo' describe 'Component traits', -> describe 'MapComponent', -> c = null it 'should pass data to the callback', -> c = new noflo.Component c.inPorts.add 'in' c.outPorts.add 'out', required: false noflo.helpers.MapComponent c, (data) -> chai.expect(data).to.equal 1 s = new noflo.internalSocket.createSocket() c.inPorts.in.attach s s.send 1 it 'should pass groups to the callback', -> c = new noflo.Component c.inPorts.add 'in' c.outPorts.add 'out', required: false noflo.helpers.MapComponent c, (data, groups) -> chai.expect(groups).to.eql [ 'one' 'two' ] chai.expect(data).to.equal 1 s = new noflo.internalSocket.createSocket() c.inPorts.in.attach s s.beginGroup 'one' s.beginGroup 'two' s.send 1 it 'should send groups and disconnect through', (done) -> c = new noflo.Component c.inPorts.add 'in' c.outPorts.add 'out', required: false noflo.helpers.MapComponent c, (data, groups, out) -> out.send data * 2 s = new noflo.internalSocket.createSocket() c.inPorts.in.attach s s2 = new noflo.internalSocket.createSocket() c.outPorts.out.attach s2 groups = [] s2.on 'begingroup', (group) -> groups.push group s2.on 'data', (data) -> chai.expect(groups.length).to.equal 2 chai.expect(data).to.equal 6 s2.on 'endgroup', -> groups.pop() s2.on 'disconnect', -> chai.expect(groups.length).to.equal 0 done() s.beginGroup 'one' s.beginGroup 'two' s.send 3 s.endGroup() s.endGroup() s.disconnect() describe 'WirePattern', -> describe 'when grouping by packet groups', -> c = null x = null y = null z = null p = null beforeEach (done) -> c = new noflo.Component c.inPorts.add 'x', required: true datatype: 'int' .add 'y', required: true datatype: 'int' .add 'z', required: true datatype: 'int' c.outPorts.add 'point' x = new noflo.internalSocket.createSocket() y = new noflo.internalSocket.createSocket() z = new noflo.internalSocket.createSocket() p = new noflo.internalSocket.createSocket() c.inPorts.x.attach x c.inPorts.y.attach y c.inPorts.z.attach z c.outPorts.point.attach p done() afterEach -> c.outPorts.point.detach p it 'should pass data and groups to the callback', (done) -> src = 111: {x: 1, y: 2, z: 3} 222: {x: 4, y: 5, z: 6} 333: {x: 7, y: 8, z: 9} noflo.helpers.WirePattern c, in: ['x', 'y', 'z'] out: 'point' group: true forwardGroups: true , (data, groups, out) -> chai.expect(groups.length).to.be.above 0 chai.expect(data).to.deep.equal src[groups[0]] out.send data groups = [] count = 0 p.on 'begingroup', (grp) -> groups.push grp p.on 'endgroup', -> groups.pop() p.on 'data', (data) -> count++ p.on 'disconnect', -> done() if count is 3 and groups.length is 0 for key, grp of src x.beginGroup key y.beginGroup key z.beginGroup key x.send grp.x y.send grp.y z.send grp.z x.endGroup() y.endGroup() z.endGroup() x.disconnect() y.disconnect() z.disconnect() it 'should work without a group provided', (done) -> noflo.helpers.WirePattern c, in: ['x', 'y', 'z'] out: 'point' , (data, groups, out) -> chai.expect(groups.length).to.equal 0 out.send {x: data.x, y: data.y, z: data.z} p.once 'data', (data) -> chai.expect(data).to.deep.equal {x: 123, y: 456, z: 789} done() x.send 123 x.disconnect() y.send 456 y.disconnect() z.send 789 z.disconnect() it 'should process inputs for different groups independently with group: true', (done) -> src = 1: {x: 1, y: 2, z: 3} 2: {x: 4, y: 5, z: 6} 3: {x: 7, y: 8, z: 9} inOrder = [ [ 1, 'x' ] [ 3, 'z' ] [ 2, 'y' ] [ 2, 'x' ] [ 1, 'z' ] [ 2, 'z' ] [ 3, 'x' ] [ 1, 'y' ] [ 3, 'y' ] ] outOrder = [ 2, 1, 3 ] noflo.helpers.WirePattern c, in: ['x', 'y', 'z'] out: 'point' group: true forwardGroups: true , (data, groups, out) -> out.send {x: data.x, y: data.y, z: data.z} groups = [] p.on 'begingroup', (grp) -> groups.push grp p.on 'endgroup', (grp) -> groups.pop() p.on 'data', (data) -> chai.expect(groups.length).to.equal 1 chai.expect(groups[0]).to.equal outOrder[0] chai.expect(data).to.deep.equal src[outOrder[0]] outOrder.shift() done() unless outOrder.length for tuple in inOrder input = null switch tuple[1] when 'x' input = x when 'y' input = y when 'z' input = z input.beginGroup tuple[0] input.send src[tuple[0]][tuple[1]] input.endGroup() input.disconnect() it 'should support asynchronous handlers', (done) -> point = x: 123 y: 456 z: 789 noflo.helpers.WirePattern c, in: ['x', 'y', 'z'] out: 'point' async: true group: true forwardGroups: true , (data, groups, out, callback) -> setTimeout -> out.send {x: data.x, y: data.y, z: data.z} callback() , 100 counter = 0 hadData = false p.on 'begingroup', (grp) -> counter++ p.on 'endgroup', -> counter-- p.once 'data', (data) -> chai.expect(data).to.deep.equal point hadData = true p.once 'disconnect', -> chai.expect(counter).to.equal 0 chai.expect(hadData).to.be.true done() x.beginGroup 'async' y.beginGroup 'async' z.beginGroup 'async' x.send point.x y.send point.y z.send point.z x.endGroup() y.endGroup() z.endGroup() x.disconnect() y.disconnect() z.disconnect() it 'should support asynchronous handlers in legacy mode', (done) -> point = x: 123 y: 456 z: 789 noflo.helpers.WirePattern c, in: ['x', 'y', 'z'] out: 'point' async: true group: true forwardGroups: true legacy: true , (data, groups, out, callback) -> setTimeout -> out.send {x: data.x, y: data.y, z: data.z} callback() , 100 counter = 0 hadData = false p.on 'begingroup', (grp) -> counter++ p.on 'endgroup', -> counter-- p.once 'data', (data) -> chai.expect(data).to.deep.equal point hadData = true p.once 'disconnect', -> chai.expect(counter).to.equal 0 chai.expect(hadData).to.be.true done() x.beginGroup 'async' y.beginGroup 'async' z.beginGroup 'async' x.send point.x y.send point.y z.send point.z x.endGroup() y.endGroup() z.endGroup() x.disconnect() y.disconnect() z.disconnect() it 'should not forward groups if forwarding is off', (done) -> point = x: 123 y: 456 noflo.helpers.WirePattern c, in: ['x', 'y'] out: 'point' , (data, groups, out) -> out.send { x: data.x, y: data.y } counter = 0 hadData = false p.on 'begingroup', (grp) -> counter++ p.on 'data', (data) -> chai.expect(data).to.deep.equal point hadData = true p.once 'disconnect', -> chai.expect(counter).to.equal 0 chai.expect(hadData).to.be.true done() x.beginGroup 'doNotForwardMe' y.beginGroup 'doNotForwardMe' x.send point.x y.send point.y x.endGroup() y.endGroup() x.disconnect() y.disconnect() it 'should forward groups from a specific port only', (done) -> point = x: 123 y: 456 z: 789 refGroups = ['boo'] noflo.helpers.WirePattern c, in: ['x', 'y', 'z'] out: 'point' forwardGroups: 'y' , (data, groups, out) -> out.send { x: data.x, y: data.y, z: data.z } groups = [] p.on 'begingroup', (grp) -> groups.push grp p.on 'data', (data) -> chai.expect(data).to.deep.equal point p.once 'disconnect', -> chai.expect(groups).to.deep.equal refGroups done() x.beginGroup 'foo' y.beginGroup 'boo' z.beginGroup 'bar' x.send point.x y.send point.y z.send point.z x.endGroup() y.endGroup() z.endGroup() x.disconnect() y.disconnect() z.disconnect() it 'should forward groups from selected ports only', (done) -> point = x: 123 y: 456 z: 789 refGroups = ['foo', 'bar'] noflo.helpers.WirePattern c, in: ['x', 'y', 'z'] out: 'point' forwardGroups: [ 'x', 'z' ] , (data, groups, out) -> out.send { x: data.x, y: data.y, z: data.z } groups = [] p.on 'begingroup', (grp) -> groups.push grp p.on 'data', (data) -> chai.expect(data).to.deep.equal point p.once 'disconnect', -> chai.expect(groups).to.deep.equal refGroups done() x.beginGroup 'foo' y.beginGroup 'boo' z.beginGroup 'bar' x.send point.x y.send point.y z.send point.z x.endGroup() y.endGroup() z.endGroup() x.disconnect() y.disconnect() z.disconnect() describe 'when `this` context is important', -> c = new noflo.Component c.inPorts.add 'x', required: true datatype: 'int' .add 'y', required: true datatype: 'int' .add 'z', required: true datatype: 'int' c.outPorts.add 'point' x = new noflo.internalSocket.createSocket() y = new noflo.internalSocket.createSocket() z = new noflo.internalSocket.createSocket() p = new noflo.internalSocket.createSocket() c.inPorts.x.attach x c.inPorts.y.attach y c.inPorts.z.attach z c.outPorts.point.attach p it 'should correctly bind component to `this` context', (done) -> noflo.helpers.WirePattern c, in: ['x', 'y', 'z'] out: 'point' , (data, groups, out) -> chai.expect(this).to.deep.equal c out.send {x: data.x, y: data.y, z: data.z} p.once 'data', (data) -> chai.expect(data).to.deep.equal {x: 123, y: 456, z: 789} done() x.send 123 x.disconnect() y.send 456 y.disconnect() z.send 789 z.disconnect() it 'should correctly bind component to `this` context in async mode', (done) -> noflo.helpers.WirePattern c, in: ['x', 'y', 'z'] async: true out: 'point' , (data, groups, out, callback) -> chai.expect(this).to.deep.equal c out.send {x: data.x, y: data.y, z: data.z} callback() p.once 'data', (data) -> done() x.send 123 x.disconnect() y.send 456 y.disconnect() z.send 789 z.disconnect() describe 'when in async mode and packet order matters', -> c = new noflo.Component c.inPorts.add 'delay', datatype: 'int' .add 'msg', datatype: 'string' c.outPorts.add 'out', datatype: 'object' .add 'load', datatype: 'int' delay = new noflo.internalSocket.createSocket() msg = new noflo.internalSocket.createSocket() out = new noflo.internalSocket.createSocket() load = new noflo.internalSocket.createSocket() c.inPorts.delay.attach delay c.inPorts.msg.attach msg c.outPorts.out.attach out c.outPorts.load.attach load it 'should preserve input order at the output', (done) -> noflo.helpers.WirePattern c, in: ['delay', 'msg'] async: true ordered: true group: false , (data, groups, res, callback) -> setTimeout -> res.send { delay: data.delay, msg: data.msg } callback() , data.delay sample = [ { delay: 30, msg: "one" } { delay: 0, msg: "two" } { delay: 20, msg: "three" } { delay: 10, msg: "four" } ] out.on 'data', (data) -> chai.expect(data).to.deep.equal sample.shift() out.on 'disconnect', -> done() if sample.length is 0 expected = [1, 2, 3, 4, 3, 2, 1, 0] load.on 'data', (data) -> chai.expect(data).to.equal expected.shift() idx = 0 for ip in sample delay.beginGroup idx delay.send ip.delay delay.endGroup() msg.beginGroup idx msg.send ip.msg msg.endGroup() delay.disconnect() msg.disconnect() idx++ it 'should throw if receiveStreams is used', (done) -> f = -> noflo.helpers.WirePattern c, in: ['delay', 'msg'] async: true ordered: true group: false receiveStreams: ['delay', 'msg'] , (data, groups, res, callback) -> callback() chai.expect(f).to.throw Error done() it 'should throw if sendStreams is used', (done) -> f = -> noflo.helpers.WirePattern c, in: ['delay', 'msg'] async: true ordered: true group: false sendStreams: ['out'] , (data, groups, res, callback) -> callback() chai.expect(f).to.throw Error done() # it 'should support complex substreams', (done) -> # out.removeAllListeners() # load.removeAllListeners() # c.cntr = 0 # helpers.WirePattern c, # in: ['delay', 'msg'] # async: true # ordered: true # group: false # receiveStreams: ['delay', 'msg'] # , (data, groups, res, callback) -> # # Substream to object conversion validation # # (the hard way) # chai.expect(data.delay instanceof Substream).to.be.true # chai.expect(data.msg instanceof Substream).to.be.true # delayObj = data.delay.toObject() # msgObj = data.msg.toObject() # index0 = this.cntr.toString() # chai.expect(Object.keys(delayObj)[0]).to.equal index0 # chai.expect(Object.keys(msgObj)[0]).to.equal index0 # subDelay = delayObj[index0] # subMsg = msgObj[index0] # index1 = (10 + this.cntr).toString() # chai.expect(Object.keys(subDelay)[0]).to.equal index1 # chai.expect(Object.keys(subMsg)[0]).to.equal index1 # delayData = subDelay[index1] # msgData = subMsg[index1] # chai.expect(delayData).to.equal sample[c.cntr].delay # chai.expect(msgData).to.equal sample[c.cntr].msg # this.cntr++ # setTimeout -> # # Substream tree traversal (the easy way) # for k0, v0 of msgObj # res.beginGroup k0 # res.send k0 # for k1, v1 of v0 # res.beginGroup k1 # res.send # delay: delayObj[k0][k1] # msg: msgObj[k0][k1] # res.endGroup() # res.send k1 # res.endGroup() # callback() # , data.delay # sample = [ # { delay: 30, msg: "one" } # { delay: 0, msg: "two" } # { delay: 20, msg: "three" } # { delay: 10, msg: "four" } # ] # expected = [ # '0', '0', '10', sample[0], '10' # '1', '1', '11', sample[1], '11' # '2', '2', '12', sample[2], '12' # '3', '3', '13', sample[3], '13' # ] # out.on 'begingroup', (grp) -> # chai.expect(grp).to.equal expected.shift() # out.on 'data', (data) -> # chai.expect(data).to.deep.equal expected.shift() # out.on 'disconnect', -> # done() if expected.length is 0 # for i in [0..3] # delay.beginGroup i # delay.beginGroup 10 + i # delay.send sample[i].delay # delay.endGroup() # delay.endGroup() # msg.beginGroup i # msg.beginGroup 10 + i # msg.send sample[i].msg # msg.endGroup() # msg.endGroup() # delay.disconnect() # msg.disconnect() describe 'when grouping by field', -> c = new noflo.Component c.inPorts.add 'user', datatype: 'object' .add 'message', datatype: 'object' c.outPorts.add 'signedmessage' usr = new noflo.internalSocket.createSocket() msg = new noflo.internalSocket.createSocket() umsg = new noflo.internalSocket.createSocket() c.inPorts.user.attach usr c.inPorts.message.attach msg c.outPorts.signedmessage.attach umsg it 'should match objects by specific field', (done) -> noflo.helpers.WirePattern c, in: ['user', 'message'] out: 'signedmessage' async: true field: 'request' , (data, groups, out, callback) -> setTimeout -> out.send request: data.request user: data.user.name text: data.message.text callback() , 10 users = 14: {request: 14, id: 21, name: 'Josh'} 12: {request: 12, id: 25, name: 'Leo'} 34: {request: 34, id: 84, name: 'Anica'} messages = 34: {request: 34, id: 234, text: 'Hello world'} 12: {request: 12, id: 82, text: 'Aloha amigos'} 14: {request: 14, id: 249, text: 'Node.js ftw'} counter = 0 umsg.on 'data', (data) -> chai.expect(data).to.be.an 'object' chai.expect(data.request).to.be.ok chai.expect(data.user).to.equal users[data.request].name chai.expect(data.text).to.equal messages[data.request].text counter++ done() if counter is 3 # Send input asynchronously with mixed delays for req, user of users do (req, user) -> setTimeout -> usr.send user usr.disconnect() , req for req, mesg of messages do (req, mesg) -> setTimeout -> msg.send mesg msg.disconnect() , req describe 'when there are multiple output routes', -> it 'should send output to one or more of them', (done) -> numbers = ['cero', 'uno', 'dos', 'tres', 'cuatro', 'cinco', 'seis', 'siete', 'ocho', 'nueve'] c = new noflo.Component c.inPorts.add 'num', datatype: 'int' .add 'str', datatype: 'string' c.outPorts.add 'odd', datatype: 'object' .add 'even', datatype: 'object' num = new noflo.internalSocket.createSocket() str = new noflo.internalSocket.createSocket() odd = new noflo.internalSocket.createSocket() even = new noflo.internalSocket.createSocket() c.inPorts.num.attach num c.inPorts.str.attach str c.outPorts.odd.attach odd c.outPorts.even.attach even noflo.helpers.WirePattern c, in: ['num', 'str'] out: ['odd', 'even'] async: true ordered: true forwardGroups: true , (data, groups, outs, callback) -> setTimeout -> if data.num % 2 is 1 outs.odd.send data else outs.even.send data callback() , 0 expected = [] numbers.forEach (n, idx) -> if idx % 2 is 1 port = 'odd' else port = 'even' expected.push "#{port} < #{idx}" expected.push "#{port} DATA #{n}" expected.push "#{port} > #{idx}" received = [] odd.on 'begingroup', (grp) -> received.push "odd < #{grp}" odd.on 'data', (data) -> received.push "odd DATA #{data.str}" odd.on 'endgroup', (grp) -> received.push "odd > #{grp}" odd.on 'disconnect', -> return unless received.length is expected.length chai.expect(received).to.eql expected done() even.on 'begingroup', (grp) -> received.push "even < #{grp}" even.on 'data', (data) -> received.push "even DATA #{data.str}" even.on 'endgroup', (grp) -> received.push "even > #{grp}" even.on 'disconnect', -> return unless received.length >= expected.length chai.expect(received).to.eql expected done() for i in [0...10] num.beginGroup i num.send i num.endGroup i num.disconnect() str.beginGroup i str.send numbers[i] str.endGroup i str.disconnect() it 'should send output to one or more of indexes', (done) -> c = new noflo.Component c.inPorts.add 'num', datatype: 'int' .add 'str', datatype: 'string' c.outPorts.add 'out', datatype: 'object' addressable: true num = new noflo.internalSocket.createSocket() str = new noflo.internalSocket.createSocket() odd = new noflo.internalSocket.createSocket() even = new noflo.internalSocket.createSocket() c.inPorts.num.attach num c.inPorts.str.attach str c.outPorts.out.attach odd c.outPorts.out.attach even numbers = ['cero', 'uno', 'dos', 'tres', 'cuatro', 'cinco', 'seis', 'siete', 'ocho', 'nueve'] noflo.helpers.WirePattern c, in: ['num', 'str'] out: 'out' async: true ordered: true forwardGroups: true , (data, groups, outs, callback) -> setTimeout -> if data.num % 2 is 1 outs.send data, 0 else outs.send data, 1 callback() , 0 expected = [] numbers.forEach (n, idx) -> if idx % 2 is 1 port = 'odd' else port = 'even' expected.push "#{port} < #{idx}" expected.push "#{port} DATA #{n}" expected.push "#{port} > #{idx}" received = [] odd.on 'begingroup', (grp) -> received.push "odd < #{grp}" odd.on 'data', (data) -> received.push "odd DATA #{data.str}" odd.on 'endgroup', (grp) -> received.push "odd > #{grp}" odd.on 'disconnect', -> return unless received.length is expected.length chai.expect(received).to.eql expected done() even.on 'begingroup', (grp) -> received.push "even < #{grp}" even.on 'data', (data) -> received.push "even DATA #{data.str}" even.on 'endgroup', (grp) -> received.push "even > #{grp}" even.on 'disconnect', -> return unless received.length >= expected.length chai.expect(received).to.eql expected done() for i in [0...10] num.beginGroup i num.send i num.endGroup i num.disconnect() str.beginGroup i str.send numbers[i] str.endGroup i str.disconnect() describe 'when there are parameter ports', -> c = null p1 = p2 = p3 = d1 = d2 = out = err = 0 beforeEach -> c = new noflo.Component c.inPorts.add 'param1', datatype: 'string' required: true .add 'param2', datatype: 'int' required: false .add 'param3', datatype: 'int' required: true default: 0 .add 'data1', datatype: 'string' .add 'data2', datatype: 'int' c.outPorts.add 'out', datatype: 'object' .add 'error', datatype: 'object' p1 = new noflo.internalSocket.createSocket() p2 = new noflo.internalSocket.createSocket() p3 = new noflo.internalSocket.createSocket() d1 = new noflo.internalSocket.createSocket() d2 = new noflo.internalSocket.createSocket() out = new noflo.internalSocket.createSocket() err = new noflo.internalSocket.createSocket() c.inPorts.param1.attach p1 c.inPorts.param2.attach p2 c.inPorts.param3.attach p3 c.inPorts.data1.attach d1 c.inPorts.data2.attach d2 c.outPorts.out.attach out c.outPorts.error.attach err it 'should wait for required params without default value', (done) -> noflo.helpers.WirePattern c, in: ['data1', 'data2'] out: 'out' params: ['param1', 'param2', 'param3'] , (input, groups, out) -> res = p1: c.params.param1 p2: c.params.param2 p3: c.params.param3 d1: input.data1 d2: input.data2 out.send res err.on 'data', (data) -> done data out.once 'data', (data) -> chai.expect(data).to.be.an 'object' chai.expect(data.p1).to.equal 'req' chai.expect(data.p2).to.be.undefined chai.expect(data.p3).to.equal 0 chai.expect(data.d1).to.equal 'foo' chai.expect(data.d2).to.equal 123 # And later when second param arrives out.once 'data', (data) -> chai.expect(data).to.be.an 'object' chai.expect(data.p1).to.equal 'req' chai.expect(data.p2).to.equal 568 chai.expect(data.p3).to.equal 800 chai.expect(data.d1).to.equal 'bar' chai.expect(data.d2).to.equal 456 done() d1.send 'foo' d1.disconnect() d2.send 123 d2.disconnect() c.sendDefaults() p1.send 'req' p1.disconnect() # the handler should be triggered here setTimeout -> p2.send 568 p2.disconnect() p3.send 800 p3.disconnect() d1.send 'bar' d1.disconnect() d2.send 456 d2.disconnect() , 10 it 'should work for async procs too', (done) -> noflo.helpers.WirePattern c, in: ['data1', 'data2'] out: 'out' params: ['param1', 'param2', 'param3'] async: true , (input, groups, out, callback) -> delay = if c.params.param2 then c.params.param2 else 10 setTimeout -> res = p1: c.params.param1 p2: c.params.param2 p3: c.params.param3 d1: input.data1 d2: input.data2 out.send res do callback , delay err.on 'data', (data) -> done data out.once 'data', (data) -> chai.expect(data).to.be.an 'object' chai.expect(data.p1).to.equal 'req' chai.expect(data.p2).to.equal 56 chai.expect(data.p3).to.equal 0 chai.expect(data.d1).to.equal 'foo' chai.expect(data.d2).to.equal 123 done() p2.send 56 p2.disconnect() d1.send 'foo' d1.disconnect() d2.send 123 d2.disconnect() c.sendDefaults() p1.send 'req' p1.disconnect() # the handler should be triggered here it 'should reset state if shutdown() is called', (done) -> noflo.helpers.WirePattern c, in: ['data1', 'data2'] out: 'out' params: ['param1', 'param2', 'param3'] , (input, groups, out) -> out.send p1: c.params.param1 p2: c.params.param2 p3: c.params.param3 d1: input.data1 d2: input.data2 d1.send 'boo' d1.disconnect() p2.send 73 p2.disconnect() chai.expect(c.inPorts.data1.getBuffer().length, 'data1 should have a packet').to.be.above 0 chai.expect(c.inPorts.param2.getBuffer().length, 'param2 should have a packet').to.be.above 0 c.shutdown (err) -> return done err if err for portName, port in c.inPorts.ports chai.expect(port.getBuffer()).to.eql [] chai.expect(c.load).to.equal 0 done() it 'should drop premature data if configured to do so', (done) -> noflo.helpers.WirePattern c, in: ['data1', 'data2'] out: 'out' params: ['param1', 'param2', 'param3'] dropInput: true , (input, groups, out) -> res = p1: c.params.param1 p2: c.params.param2 p3: c.params.param3 d1: input.data1 d2: input.data2 out.send res err.on 'data', (data) -> done data out.once 'data', (data) -> chai.expect(data).to.be.an 'object' chai.expect(data.p1).to.equal 'req' chai.expect(data.p2).to.equal 568 chai.expect(data.p3).to.equal 800 chai.expect(data.d1).to.equal 'bar' chai.expect(data.d2).to.equal 456 done() c.sendDefaults() p2.send 568 p2.disconnect() p3.send 800 p3.disconnect() d1.send 'foo' d1.disconnect() d2.send 123 d2.disconnect() # Data is dropped at this point setTimeout -> p1.send 'req' p1.disconnect() d1.send 'bar' d1.disconnect() d2.send 456 d2.disconnect() , 10 describe 'without output ports', -> foo = null sig = null before -> c = new noflo.Component c.inPorts.add 'foo' foo = noflo.internalSocket.createSocket() sig = noflo.internalSocket.createSocket() c.inPorts.foo.attach foo noflo.helpers.WirePattern c, in: 'foo' out: [] async: true , (foo, grp, out, callback) -> setTimeout -> sig.send foo callback() , 20 it 'should be fine still', (done) -> sig.on 'data', (data) -> chai.expect(data).to.equal 'foo' done() foo.send 'foo' foo.disconnect() describe 'with many inputs and groups in async mode', -> ins = noflo.internalSocket.createSocket() msg = noflo.internalSocket.createSocket() rep = noflo.internalSocket.createSocket() pth = noflo.internalSocket.createSocket() tkn = noflo.internalSocket.createSocket() out = noflo.internalSocket.createSocket() err = noflo.internalSocket.createSocket() before -> c = new noflo.Component c.token = null c.inPorts.add 'in', datatype: 'string' .add 'message', datatype: 'string' .add 'repository', datatype: 'string' .add 'path', datatype: 'string' .add 'token', datatype: 'string', (event, payload) -> c.token = payload if event is 'data' c.outPorts.add 'out', datatype: 'string' .add 'error', datatype: 'object' noflo.helpers.WirePattern c, in: ['in', 'message', 'repository', 'path'] out: 'out' async: true forwardGroups: true , (data, groups, out, callback) -> setTimeout -> out.beginGroup data.path out.send data.message out.endGroup() do callback , 300 c.inPorts.in.attach ins c.inPorts.message.attach msg c.inPorts.repository.attach rep c.inPorts.path.attach pth c.inPorts.token.attach tkn c.outPorts.out.attach out c.outPorts.error.attach err it 'should handle mixed flow well', (done) -> groups = [] refGroups = [ 'foo' 'http://techcrunch.com/2013/03/26/embedly-now/' 'path data' ] ends = 0 packets = [] refData = ['message data'] out.on 'begingroup', (grp) -> groups.push grp out.on 'endgroup', -> ends++ out.on 'data', (data) -> packets.push data out.on 'disconnect', -> chai.expect(groups).to.deep.equal refGroups chai.expect(ends).to.equal 3 chai.expect(packets).to.deep.equal refData done() err.on 'data', (data) -> done data rep.beginGroup 'foo' rep.beginGroup 'http://techcrunch.com/2013/03/26/embedly-now/' rep.send 'repo data' rep.endGroup() rep.endGroup() ins.beginGroup 'foo' ins.beginGroup 'http://techcrunch.com/2013/03/26/embedly-now/' ins.send 'ins data' msg.beginGroup 'foo' msg.beginGroup 'http://techcrunch.com/2013/03/26/embedly-now/' msg.send 'message data' msg.endGroup() msg.endGroup() ins.endGroup() ins.endGroup() ins.disconnect() msg.disconnect() pth.beginGroup 'foo' pth.beginGroup 'http://techcrunch.com/2013/03/26/embedly-now/' pth.send 'path data' pth.endGroup() pth.endGroup() pth.disconnect() rep.disconnect() describe 'with many inputs and groups in sync mode', -> ins = noflo.internalSocket.createSocket() msg = noflo.internalSocket.createSocket() rep = noflo.internalSocket.createSocket() pth = noflo.internalSocket.createSocket() tkn = noflo.internalSocket.createSocket() out = noflo.internalSocket.createSocket() err = noflo.internalSocket.createSocket() before -> c = new noflo.Component c.token = null c.inPorts.add 'in', datatype: 'string' .add 'message', datatype: 'string' .add 'repository', datatype: 'string' .add 'path', datatype: 'string' .add 'token', datatype: 'string', (event, payload) -> c.token = payload if event is 'data' c.outPorts.add 'out', datatype: 'string' .add 'error', datatype: 'object' noflo.helpers.WirePattern c, in: ['in', 'message', 'repository', 'path'] out: 'out' async: false forwardGroups: true , (data, groups, out) -> out.beginGroup data.path out.send data.message out.endGroup() c.inPorts.in.attach ins c.inPorts.message.attach msg c.inPorts.repository.attach rep c.inPorts.path.attach pth c.inPorts.token.attach tkn c.outPorts.out.attach out c.outPorts.error.attach err it 'should handle mixed flow well', (done) -> groups = [] refGroups = [ 'foo' 'http://techcrunch.com/2013/03/26/embedly-now/' 'path data' ] ends = 0 packets = [] refData = ['message data'] out.on 'begingroup', (grp) -> groups.push grp out.on 'endgroup', -> ends++ out.on 'data', (data) -> packets.push data out.on 'disconnect', -> chai.expect(groups).to.deep.equal refGroups chai.expect(ends).to.equal 3 chai.expect(packets).to.deep.equal refData done() err.on 'data', (data) -> done data rep.beginGroup 'foo' rep.beginGroup 'http://techcrunch.com/2013/03/26/embedly-now/' rep.send 'repo data' rep.endGroup() rep.endGroup() ins.beginGroup 'foo' ins.beginGroup 'http://techcrunch.com/2013/03/26/embedly-now/' ins.send 'ins data' msg.beginGroup 'foo' msg.beginGroup 'http://techcrunch.com/2013/03/26/embedly-now/' msg.send 'message data' msg.endGroup() msg.endGroup() ins.endGroup() ins.endGroup() ins.disconnect() msg.disconnect() pth.beginGroup 'foo' pth.beginGroup 'http://techcrunch.com/2013/03/26/embedly-now/' pth.send 'path data' pth.endGroup() pth.endGroup() pth.disconnect() rep.disconnect() describe 'for batch processing', -> # Component constructors newGenerator = (name) -> generator = new noflo.Component generator.inPorts.add 'count', datatype: 'int' generator.outPorts.add 'seq', datatype: 'int' noflo.helpers.WirePattern generator, in: 'count' out: 'seq' async: true forwardGroups: true ordered: true , (count, groups, seq, callback) -> sentCount = 0 for i in [1..count] do (i) -> delay = if i > 10 then i % 10 else i setTimeout -> seq.send i sentCount++ if sentCount is count callback() , delay newDoubler = (name) -> doubler = new noflo.Component doubler.inPorts.add 'num', datatype: 'int' doubler.outPorts.add 'out', datatype: 'int' noflo.helpers.WirePattern doubler, in: 'num' out: 'out' forwardGroups: true , (num, groups, out) -> dbl = 2*num out.send dbl newAdder = -> adder = new noflo.Component adder.inPorts.add 'num1', datatype: 'int' adder.inPorts.add 'num2', datatype: 'int' adder.outPorts.add 'sum', datatype: 'int' noflo.helpers.WirePattern adder, in: ['num1', 'num2'] out: 'sum' forwardGroups: true async: true ordered: true , (args, groups, out, callback) -> sum = args.num1 + args.num2 # out.send sum setTimeout -> out.send sum callback() , sum % 10 newSeqsum = -> seqsum = new noflo.Component seqsum.sum = 0 seqsum.inPorts.add 'seq', datatype: 'int', (event, payload) -> switch event when 'data' seqsum.sum += payload when 'disconnect' seqsum.outPorts.sum.send seqsum.sum seqsum.sum = 0 seqsum.outPorts.sum.disconnect() seqsum.outPorts.add 'sum', datatype: 'int' return seqsum cntA = noflo.internalSocket.createSocket() cntB = noflo.internalSocket.createSocket() gen2dblA = noflo.internalSocket.createSocket() gen2dblB = noflo.internalSocket.createSocket() dblA2add = noflo.internalSocket.createSocket() dblB2add = noflo.internalSocket.createSocket() addr2sum = noflo.internalSocket.createSocket() sum = noflo.internalSocket.createSocket() before -> # Wires genA = newGenerator 'A' genB = newGenerator 'B' dblA = newDoubler 'A' dblB = newDoubler 'B' addr = newAdder() sumr = newSeqsum() genA.inPorts.count.attach cntA genB.inPorts.count.attach cntB genA.outPorts.seq.attach gen2dblA genB.outPorts.seq.attach gen2dblB dblA.inPorts.num.attach gen2dblA dblB.inPorts.num.attach gen2dblB dblA.outPorts.out.attach dblA2add dblB.outPorts.out.attach dblB2add addr.inPorts.num1.attach dblA2add addr.inPorts.num2.attach dblB2add addr.outPorts.sum.attach addr2sum sumr.inPorts.seq.attach addr2sum sumr.outPorts.sum.attach sum it 'should process sequences of packets separated by disconnects', (done) -> return @skip 'WirePattern doesn\'t see disconnects because of IP objects' expected = [ 24, 40 ] actual = [] sum.on 'data', (data) -> actual.push data sum.on 'disconnect', -> chai.expect(actual).to.have.length.above 0 chai.expect(expected).to.have.length.above 0 act = actual.shift() exp = expected.shift() chai.expect(act).to.equal exp done() if expected.length is 0 cntA.send 3 cntA.disconnect() cntB.send 3 cntB.disconnect() cntA.send 4 cntB.send 4 cntA.disconnect() cntB.disconnect() describe 'for batch processing with groups', -> c1 = new noflo.Component c1.inPorts.add 'count', datatype: 'int' c1.outPorts.add 'seq', datatype: 'int' c2 = new noflo.Component c2.inPorts.add 'num', datatype: 'int' c2.outPorts.add 'out', datatype: 'int' cnt = noflo.internalSocket.createSocket() c1c2 = noflo.internalSocket.createSocket() out = noflo.internalSocket.createSocket() c1.inPorts.count.attach cnt c1.outPorts.seq.attach c1c2 c2.inPorts.num.attach c1c2 c2.outPorts.out.attach out it 'should wrap entire sequence with groups', (done) -> noflo.helpers.WirePattern c1, in: 'count' out: 'seq' async: true forwardGroups: true , (count, groups, out, callback) -> for i in [0...count] do (i) -> setTimeout -> out.send i , 0 setTimeout -> callback() , 3 noflo.helpers.WirePattern c2, in: 'num' out: 'out' forwardGroups: true , (num, groups, out) -> chai.expect(groups).to.deep.equal ['foo', 'bar'] out.send num expected = ['<foo>', '<bar>', 0, 1, 2, '</bar>', '</foo>'] actual = [] out.on 'begingroup', (grp) -> actual.push "<#{grp}>" out.on 'endgroup', (grp) -> actual.push "</#{grp}>" out.on 'data', (data) -> actual.push data out.on 'disconnect', -> chai.expect(actual).to.deep.equal expected done() cnt.beginGroup 'foo' cnt.beginGroup 'bar' cnt.send 3 cnt.endGroup() cnt.endGroup() cnt.disconnect() describe 'with addressable ports', -> c = null p11 = null p12 = null p13 = null d11 = null d12 = null d13 = null d2 = null out = null err = null beforeEach -> c = new noflo.Component c.inPorts.add 'p1', datatype: 'int' addressable: true required: true .add 'd1', datatype: 'int' addressable: true .add 'd2', datatype: 'string' c.outPorts.add 'out', datatype: 'object' .add 'error', datatype: 'object' p11 = noflo.internalSocket.createSocket() p12 = noflo.internalSocket.createSocket() p13 = noflo.internalSocket.createSocket() d11 = noflo.internalSocket.createSocket() d12 = noflo.internalSocket.createSocket() d13 = noflo.internalSocket.createSocket() d2 = noflo.internalSocket.createSocket() out = noflo.internalSocket.createSocket() err = noflo.internalSocket.createSocket() c.inPorts.p1.attach p11 c.inPorts.p1.attach p12 c.inPorts.p1.attach p13 c.inPorts.d1.attach d11 c.inPorts.d1.attach d12 c.inPorts.d1.attach d13 c.inPorts.d2.attach d2 c.outPorts.out.attach out c.outPorts.error.attach err it 'should wait for all param and any data port values (default)', (done) -> noflo.helpers.WirePattern c, in: ['d1', 'd2'] params: 'p1' out: 'out' arrayPolicy: # default values in: 'any' params: 'all' , (input, groups, out) -> chai.expect(c.params.p1).to.deep.equal { 0: 1, 1: 2, 2: 3 } chai.expect(input.d1).to.deep.equal {0: 1} chai.expect(input.d2).to.equal 'foo' done() d2.send 'foo' d2.disconnect() d11.send 1 d11.disconnect() p11.send 1 p11.disconnect() p12.send 2 p12.disconnect() p13.send 3 p13.disconnect() it 'should wait for any param and all data values', (done) -> noflo.helpers.WirePattern c, in: ['d1', 'd2'] params: 'p1' out: 'out' arrayPolicy: # inversed in: 'all' params: 'any' , (input, groups, out) -> chai.expect(c.params.p1).to.deep.equal {0: 1} chai.expect(input.d1).to.deep.equal { 0: 1, 1: 2, 2: 3 } chai.expect(input.d2).to.equal 'foo' done() out.on 'disconnect', -> console.log 'disc' d2.send 'foo' d2.disconnect() p11.send 1 p11.disconnect() d11.send 1 d11.disconnect() d12.send 2 d12.disconnect() d13.send 3 d13.disconnect() p12.send 2 p12.disconnect() p13.send 3 p13.disconnect() it 'should wait for all indexes of a single input', (done) -> noflo.helpers.WirePattern c, in: 'd1' out: 'out' arrayPolicy: in: 'all' , (input, groups, out) -> chai.expect(input).to.deep.equal { 0: 1, 1: 2, 2: 3 } done() d11.send 1 d11.disconnect() d12.send 2 d12.disconnect() d13.send 3 d13.disconnect() it 'should behave normally with string output from another component', (done) -> c = new noflo.Component c.inPorts.add 'd1', datatype: 'string' addressable: true c.outPorts.add 'out', datatype: 'object' d11 = noflo.internalSocket.createSocket() d12 = noflo.internalSocket.createSocket() d13 = noflo.internalSocket.createSocket() out = noflo.internalSocket.createSocket() c.inPorts.d1.attach d11 c.inPorts.d1.attach d12 c.inPorts.d1.attach d13 c.outPorts.out.attach out c2 = new noflo.Component c2.inPorts.add 'in', datatype: 'string' c2.outPorts.add 'out', datatype: 'string' noflo.helpers.WirePattern c2, in: 'in' out: 'out' forwardGroups: true , (input, groups, out) -> out.send input d3 = noflo.internalSocket.createSocket() c2.inPorts.in.attach d3 c2.outPorts.out.attach d11 noflo.helpers.WirePattern c, in: 'd1' out: 'out' , (input, groups, out) -> chai.expect(input).to.deep.equal {0: 'My string'} done() d3.send 'My string' d3.disconnect() describe 'when grouping requests', -> c = new noflo.Component c.inPorts.add 'x', datatype: 'int' .add 'y', datatype: 'int' c.outPorts.add 'out', datatype: 'object' x = noflo.internalSocket.createSocket() y = noflo.internalSocket.createSocket() out = noflo.internalSocket.createSocket() c.inPorts.x.attach x c.inPorts.y.attach y c.outPorts.out.attach out getUuid = -> 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace /[xy]/g, (c) -> r = Math.random()*16|0 v = if c is 'x' then r else r&0x3|0x8 v.toString 16 isUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i generateRequests = (num) -> reqs = {} for i in [1..num] req = id: getUuid() num: i if i % 3 is 0 req.x = i else if i % 7 is 0 req.y = i else req.x = i req.y = 2*i reqs[req.id] = req reqs sendRequests = (reqs, delay) -> for id, req of reqs do (req) -> setTimeout -> if 'x' of req x.beginGroup req.id x.beginGroup 'x' x.beginGroup req.num x.send req.x x.endGroup() x.endGroup() x.endGroup() x.disconnect() if 'y' of req y.beginGroup req.id y.beginGroup 'y' y.beginGroup req.num y.send req.y y.endGroup() y.endGroup() y.endGroup() y.disconnect() , delay*req.num before -> noflo.helpers.WirePattern c, in: ['x', 'y'] out: 'out'