UNPKG

fbp-client

Version:

Higher-level client library for the FBP protocol

383 lines (378 loc) 13 kB
const { expect } = require('chai'); const { spawn } = require('child_process'); const path = require('path'); const fbpClient = require('../lib/client'); const { Graph: fbpGraph } = require('fbp-graph'); describe('FBP Client with noflo-nodejs', () => { let nofloNodejs = null; let client = null; before(function (done) { this.timeout(60 * 1000); const nofloPath = path.resolve(__dirname, '../node_modules/.bin/noflo-nodejs'); const baseDir = path.resolve(__dirname, '../'); nofloNodejs = spawn('node', [ nofloPath, '--host=localhost', '--port=3570', '--secret=fbp-client', '--open=false', `--base-dir=${baseDir}`, ]); nofloNodejs.stdout.on('data', (data) => { const message = data.toString('utf8'); if (message.indexOf('listening') !== -1) { done(); } }); }); after(function (done) { this.timeout(60 * 1000); if (!nofloNodejs) { done(); return; } nofloNodejs.on('close', () => { done(); }); nofloNodejs.kill(); }); describe('when instantiated', () => { it('should be able to connect', () => { return fbpClient({ address: 'ws://localhost:3570', protocol: 'websocket', secret: 'fbp-client', }) .then((c) => { client = c; return c.connect(); }); }); it('should be marked as connected', () => { expect(client.isConnected()).to.equal(true); }); it('should have updated runtime definition type', () => { expect(client.definition.type).to.equal('noflo-nodejs'); }); it('should be able to connect again without side-effects', () => { client.adapter.__spec = true; return client.connect() .then(() => { expect(client.adapter.__spec).to.equal(true); delete client.adapter.__spec; }); }); }); describe('when connected', () => { it('should be possible to get graph sources', () => { return client.protocol.component.getsource({ name: client.definition.graph, }) .then((res) => { expect(`${res.library}/${res.name}`).to.equal(client.definition.graph); expect(res.language).to.equal('json'); }); }); it('should fail when adding a node to a non-existing graph', () => { return client.protocol.graph.addnode({ id: 'foo', component: 'bar', graph: 'not-existing', }) .then(() => { throw new Error('Unexpected success') }) .catch((err) => { expect(err).to.be.an('error'); expect(err.message).to.contain('graph not found'); }); }); it('should be possible to list components', () => { return client.protocol.component.list() .then((components) => { expect(components).to.be.an('array'); expect(components.length).to.be.above(5); }); }); }); describe('setting up a project', () => { let signals = []; it('should be possible to send a custom component', () => { const code = ` const noflo = require('noflo'); const plusOne = function(val) { return parseInt(val, 10) + 1; } exports.getComponent = () => noflo.asComponent(plusOne); `; return client.protocol.component.source({ name: 'PlusOne', language: 'javascript', library: 'foo', code, }) .then((res) => { expect(res.name).to.equal('foo/PlusOne'); expect(res.inPorts.length).to.equal(1); expect(res.outPorts.length).to.equal(2); }); }); it('should be possible to send a graph', () => { const graph = new fbpGraph('one-plus-one'); graph.addNode('repeat', 'core/Repeat'); graph.addNode('plus', 'foo/PlusOne'); graph.addNode('output', 'core/Output'); graph.addEdge('repeat', 'out', 'plus', 'val'); graph.addEdge('plus', 'out', 'output', 'in'); graph.addInitial(1, 'repeat', 'in'); return client.protocol.graph.send(graph, true); }); it('should be possible to start the graph', () => { const observer = client.observe(['network:*']); return client.protocol.network.start({ graph: 'one-plus-one', }) .then(() => observer.until(['network:stopped'], ['network:error', 'network:processerror'])) .then((s) => { signals = s; }); }); it('should tell that the network has finished', () => { return client.protocol.network.getstatus({ graph: 'one-plus-one', }) .then((status) => { expect(status.started).to.equal(false); expect(status.running).to.equal(false); }); }); it('should have emitted packet events as signals', () => { const packets = signals.filter(s => (s.protocol === 'network' && s.command === 'data')); expect(packets.length).to.equal(3); // IIP expect(packets[0].payload.src).to.be.an('undefined'); expect(packets[0].payload.data).to.equal('1'); // repeat -> plus expect(packets[1].payload.src).to.eql({ node: 'repeat', port: 'out', }); expect(packets[1].payload.data).to.equal('1'); // repeat -> plus expect(packets[2].payload.src).to.eql({ node: 'plus', port: 'out', }); expect(packets[2].payload.data).to.equal('2'); }); it('should be possible to stop', () => { return client.protocol.network.stop({ graph: 'one-plus-one', }); }); }); describe('setting up a project with a failing component', () => { let observerResult = null; it('should be possible to send a custom component', () => { const code = ` const noflo = require('noflo'); exports.getComponent = () => { const c = new noflo.Component(); c.inPorts.add('in'); c.outPorts.add('out'); c.outPorts.add('error'); c.process((input, output) => { const data = input.getData('in'); setTimeout(() => { output.done(new Error('Unexpected data ' + data)); }, 1); }); return c; }; `; return client.protocol.component.source({ name: 'Errorer', language: 'javascript', library: 'foo', code, }) .then((res) => { expect(res.name).to.equal('foo/Errorer'); expect(res.inPorts.length).to.equal(1); expect(res.outPorts.length).to.equal(2); }); }); it('should be possible to send a graph', () => { const graph = new fbpGraph('one-plus-error'); graph.addNode('repeat', 'core/Repeat'); graph.addNode('plus', 'foo/Errorer'); graph.addNode('output', 'core/Output'); graph.addEdge('repeat', 'out', 'plus', 'in'); graph.addEdge('plus', 'out', 'output', 'in'); graph.addEdge('plus', 'error', 'output', 'in'); graph.addInitial(1, 'repeat', 'in'); return client.protocol.graph.send(graph, true); }); it('should be possible to start the graph', () => { observer = client.observe(['network:*']); return client.protocol.network.start({ graph: 'one-plus-error', }) .then(() => observer.until(['network:stopped'], (s) => s.protocol === 'network' && s.command === 'data' && s.payload.src && s.payload.src.port === 'error')) .then(() => { throw new Error('Unexpected success') }) .catch((err) => { observerResult = err; }); }); it('should fail the observer with a readable error', () => { expect(observerResult).to.be.an('error'); expect(observerResult.message).to.contain('Unexpected data 1'); }); }); describe('when creating graph with missing components', () => { it('should be possible to send a graph', () => { const graph = new fbpGraph('one-plus-two'); graph.addNode('repeat', 'core/Repeat'); graph.addNode('plus', 'foo/PlusTwo'); graph.addNode('output', 'core/Output'); graph.addEdge('repeat', 'out', 'plus', 'val'); graph.addEdge('plus', 'out', 'output', 'in'); graph.addInitial(1, 'repeat', 'in'); return client.protocol.graph.send(graph, true); }); it('should be possible to start the graph', () => { return client.protocol.network.start({ graph: 'one-plus-two', }) .then(() => { throw new Error('Unexpected success') }) .catch((err) => { expect(err).to.be.an('error'); expect(err.message).to.contain('Component foo/PlusTwo not available'); }); }); }); describe('when creating graph with crashing component', () => { let observer = null; it('should be possible to send a custom component', () => { const code = ` const noflo = require('noflo'); exports.getComponent = () => { const c = new noflo.Component(); c.inPorts.add('in'); c.outPorts.add('out'); c.process((input, output) => { // Typo here on purpose inpts.hasData('foo'); }); return c; }; `; return client.protocol.component.source({ name: 'PlusThree', language: 'javascript', library: 'foo', code, }) .then((res) => { expect(res.name).to.equal('foo/PlusThree'); expect(res.inPorts.length).to.equal(1); expect(res.outPorts.length).to.equal(1); }); }); it('should be possible to send a graph', () => { const graph = new fbpGraph('one-plus-three'); graph.addNode('repeat', 'core/Repeat'); graph.addNode('plus', 'foo/PlusThree'); graph.addNode('output', 'core/Output'); graph.addEdge('repeat', 'out', 'plus', 'in'); graph.addEdge('plus', 'out', 'output', 'in'); graph.addInitial(1, 'repeat', 'in'); return client.protocol.graph.send(graph, true); }); it('should be possible to start the graph', () => { observer = client.observe((signal) => signal.protocol === 'network' && signal.payload.graph === 'one-plus-three'); return client.protocol.network.start({ graph: 'one-plus-three', }); }); it('should register failure in observer', () => { return observer.until(['network:stopped'], ['network:error', 'network:processerror']) .then(() => { throw new Error('Unexpected success') }) .catch((err) => { expect(err).to.be.an('error'); expect(err.message).to.contain('inpts is not defined'); expect(err.signature).to.equal('network:processerror'); }); }); }); describe('when creating graph with exported ports', () => { let observer = null; it('should be possible to send a graph', () => { const graph = new fbpGraph('exported-plus-one'); graph.addNode('repeat', 'core/RepeatAsync'); graph.addNode('plus', 'foo/PlusOne'); graph.addEdge('repeat', 'out', 'plus', 'val'); graph.addInport('in', 'repeat', 'in'); graph.addOutport('out', 'plus', 'out'); graph.addOutport('error', 'plus', 'error'); return client.protocol.graph.send(graph, true); }); it('starting the graph should expose its ports', () => { const obs = client.observe(['runtime:ports']); return client.protocol.network.start({ graph: 'exported-plus-one', }) .then(() => obs.until((s) => s.payload.graph === 'exported-plus-one', [])); }); it('should be possible to send a packet', () => { observer = client.observe(['runtime:packet']); return client.protocol.runtime.packet({ graph: 'exported-plus-one', event: 'data', port: 'in', payload: 1, }); }); it('should result in an output packet', () => { return observer.until(['runtime:packet'], []) .then((packets) => { expect(packets.length).to.equal(1); expect(packets[0].payload.port).to.equal('out'); expect(packets[0].payload.payload).to.equal(2); }); }); it('it should be possible to stop the network', () => { return client.protocol.network.stop({ graph: 'exported-plus-one', }); }); }); describe('when disconnecting', () => { it('should be able to disconnect', () => { return client.disconnect(); }); it('should be marked as disconnected', () => { expect(client.isConnected()).to.not.equal(true); }); it('should be able to disconnect again without side-effects', () => { client.adapter.__spec = true; return client.disconnect() .then(() => { expect(client.adapter.__spec).to.equal(true); delete client.adapter.__spec; }); }); }); describe('registering commands after being disconnected', () => { after(() => { return client.disconnect(); }); it('should process commands sent while offline after reconnected', (done) => { client.protocol.component.getsource({ name: client.definition.graph, }) .then(() => { done(); }, done); client.connect(); }); }); });