1 | chai = require 'chai'
|
2 | path = require 'path'
|
3 |
|
4 | shelljs = require 'shelljs'
|
5 | WebSocketClient = require('websocket').client
|
6 |
|
7 | check = (done, f) ->
|
8 | try
|
9 | f()
|
10 | catch e
|
11 | done e
|
12 |
|
13 | exports.testRuntime = (runtimeType, startServer, stopServer, host='localhost', port=8080, collection='core') ->
|
14 | address = "ws://#{host}:#{port}/"
|
15 | describe "#{runtimeType} webSocket network runtime", ->
|
16 | client = null
|
17 | connection = null
|
18 | send = null
|
19 | before (done) ->
|
20 | tries = 5
|
21 | startServer ->
|
22 | client = new WebSocketClient
|
23 | client.on 'connect', (conn) ->
|
24 | connection = conn
|
25 | done()
|
26 | client.on 'connectFailed', (err) ->
|
27 | tries--
|
28 | if tries == 0
|
29 | console.log "failed to connect to runtime after 5 tries"
|
30 | done(err)
|
31 | setTimeout(
|
32 | ->
|
33 | client.connect address, 'noflo'
|
34 | 100
|
35 | )
|
36 | console.log "connecting to", address
|
37 | client.connect address, 'noflo'
|
38 | after stopServer
|
39 |
|
40 | send = (protocol, command, payload) ->
|
41 | connection.sendUTF JSON.stringify
|
42 | protocol: protocol
|
43 | command: command
|
44 | payload: payload
|
45 |
|
46 | receive = (expects, done, ignore=null) ->
|
47 | listener = (message) ->
|
48 | check done, ->
|
49 | chai.expect(message.utf8Data).to.be.a 'string'
|
50 | msg = JSON.parse message.utf8Data
|
51 | if not ignore or not ignore(msg)
|
52 | expected = expects.shift()
|
53 | if expected.payload
|
54 | for key, value of expected.payload
|
55 | type = null
|
56 | if value is String
|
57 | type = 'string'
|
58 | else if value is Number
|
59 | type = 'number'
|
60 | else if value is Array
|
61 | type = 'array'
|
62 | if type
|
63 | chai.expect(msg.payload).to.exist
|
64 | chai.expect(msg.payload[key]).to.be.a type
|
65 | delete expected.payload[key]
|
66 | delete msg.payload[key]
|
67 |
|
68 | chai.expect(msg).to.eql expected
|
69 | if expects.length
|
70 | connection.once 'message', listener
|
71 | else
|
72 | done()
|
73 | connection.once 'message', listener
|
74 |
|
75 | describe 'Runtime Protocol', ->
|
76 | describe 'requesting runtime metadata', ->
|
77 | it 'should provide it back', (done) ->
|
78 | expects = [
|
79 | protocol: 'runtime'
|
80 | command: 'runtime'
|
81 | payload:
|
82 | type: runtimeType
|
83 | version: String
|
84 | capabilities: Array
|
85 | ]
|
86 | receive expects, done
|
87 | send 'runtime', 'getruntime', ''
|
88 |
|
89 | describe 'Graph Protocol', ->
|
90 | describe 'adding a graph and nodes', ->
|
91 | it 'should provide the nodes back', (done) ->
|
92 | expects = [
|
93 | protocol: 'graph'
|
94 | command: 'clear',
|
95 | payload:
|
96 | baseDir: path.resolve __dirname, '../'
|
97 | id: 'foo'
|
98 | main: true
|
99 | name: 'NoFlo runtime'
|
100 | ,
|
101 | protocol: 'graph'
|
102 | command: 'addnode'
|
103 | payload:
|
104 | id: 'Foo'
|
105 | component: "#{collection}/Repeat"
|
106 | metadata:
|
107 | hello: 'World'
|
108 | graph: 'foo'
|
109 | ,
|
110 | protocol: 'graph'
|
111 | command: 'addnode'
|
112 | payload:
|
113 | id: 'Bar'
|
114 | component: "#{collection}/Drop"
|
115 | metadata: {}
|
116 | graph: 'foo'
|
117 | ]
|
118 | receive expects, done
|
119 | send 'graph', 'clear',
|
120 | baseDir: path.resolve __dirname, '../'
|
121 | id: 'foo'
|
122 | main: true
|
123 | send 'graph', 'addnode', expects[1].payload
|
124 | send 'graph', 'addnode', expects[2].payload
|
125 | describe 'adding an edge', ->
|
126 | it 'should provide the edge back', (done) ->
|
127 | expects = [
|
128 | protocol: 'graph'
|
129 | command: 'addedge'
|
130 | payload:
|
131 | src:
|
132 | node: 'Foo'
|
133 | port: 'out'
|
134 | tgt:
|
135 | node: 'Bar'
|
136 | port: 'in'
|
137 | metadata:
|
138 | route: 5
|
139 | graph: 'foo'
|
140 | ]
|
141 | receive expects, done
|
142 | send 'graph', 'addedge', expects[0].payload
|
143 | describe 'adding metadata', ->
|
144 | describe 'to a node with no metadata', ->
|
145 | it 'should add the metadata', (done) ->
|
146 | expects = [
|
147 | protocol: 'graph'
|
148 | command: 'changenode'
|
149 | payload:
|
150 | id: 'Bar'
|
151 | metadata:
|
152 | sort: 1
|
153 | graph: 'foo'
|
154 | ]
|
155 | receive expects, done
|
156 | send 'graph', 'changenode', expects[0].payload
|
157 | describe 'to a node with existing metadata', ->
|
158 | it 'should merge the metadata', (done) ->
|
159 | expects = [
|
160 | protocol: 'graph'
|
161 | command: 'changenode'
|
162 | payload:
|
163 | id: 'Bar'
|
164 | metadata:
|
165 | sort: 1
|
166 | tag: 'awesome'
|
167 | graph: 'foo'
|
168 | ]
|
169 | receive expects, done
|
170 | send 'graph', 'changenode',
|
171 | id: 'Bar'
|
172 | metadata:
|
173 | tag: 'awesome'
|
174 | graph: 'foo'
|
175 | describe 'with no keys to a node with existing metadata', ->
|
176 | it 'should not change the metadata', (done) ->
|
177 | expects = [
|
178 | protocol: 'graph'
|
179 | command: 'changenode'
|
180 | payload:
|
181 | id: 'Bar'
|
182 | metadata:
|
183 | sort: 1
|
184 | tag: 'awesome'
|
185 | graph: 'foo'
|
186 | ]
|
187 | receive expects, done
|
188 | send 'graph', 'changenode',
|
189 | id: 'Bar'
|
190 | metadata: {}
|
191 | graph: 'foo'
|
192 | describe 'will a null value removes it from the node', ->
|
193 | it 'should merge the metadata', (done) ->
|
194 | expects = [
|
195 | protocol: 'graph'
|
196 | command: 'changenode'
|
197 | payload:
|
198 | id: 'Bar'
|
199 | metadata: {}
|
200 | graph: 'foo'
|
201 | ]
|
202 | receive expects, done
|
203 | send 'graph', 'changenode',
|
204 | id: 'Bar'
|
205 | metadata:
|
206 | sort: null
|
207 | tag: null
|
208 | graph: 'foo'
|
209 | describe 'adding an IIP', ->
|
210 | it 'should provide the IIP back', (done) ->
|
211 | expects = [
|
212 | protocol: 'graph'
|
213 | command: 'addinitial'
|
214 | payload:
|
215 | src:
|
216 | data: 'Hello, world!'
|
217 | tgt:
|
218 | node: 'Foo'
|
219 | port: 'in'
|
220 | metadata: {}
|
221 | graph: 'foo'
|
222 | ]
|
223 | receive expects, done
|
224 | send 'graph', 'addinitial', expects[0].payload
|
225 | describe 'removing a node', ->
|
226 | it 'should remove the node and its associated edges', (done) ->
|
227 | expects = [
|
228 | protocol: 'graph'
|
229 | command: 'changeedge'
|
230 | payload:
|
231 | src:
|
232 | node: 'Foo'
|
233 | port: 'out'
|
234 | tgt:
|
235 | node: 'Bar'
|
236 | port: 'in'
|
237 | metadata:
|
238 | route: 5
|
239 | graph: 'foo'
|
240 | ,
|
241 | protocol: 'graph'
|
242 | command: 'removeedge'
|
243 | payload:
|
244 | src:
|
245 | node: 'Foo'
|
246 | port: 'out'
|
247 | tgt:
|
248 | node: 'Bar'
|
249 | port: 'in'
|
250 | metadata:
|
251 | route: 5
|
252 | graph: 'foo'
|
253 | ,
|
254 | protocol: 'graph'
|
255 | command: 'changenode'
|
256 | payload:
|
257 | id: 'Bar'
|
258 | metadata: {}
|
259 | graph: 'foo'
|
260 | ,
|
261 | protocol: 'graph'
|
262 | command: 'removenode'
|
263 | payload:
|
264 | id: 'Bar'
|
265 | component: "#{collection}/Drop"
|
266 | metadata: {}
|
267 | graph: 'foo'
|
268 | ]
|
269 | receive expects, done
|
270 | send 'graph', 'removenode',
|
271 | id: 'Bar'
|
272 | graph: 'foo'
|
273 | describe 'removing an IIP', ->
|
274 | it 'should provide the IIP back', (done) ->
|
275 | expects = [
|
276 | protocol: 'graph'
|
277 | command: 'removeinitial'
|
278 | payload:
|
279 | src:
|
280 | data: 'Hello, world!'
|
281 | tgt:
|
282 | node: 'Foo'
|
283 | port: 'in'
|
284 | metadata: {}
|
285 | graph: 'foo'
|
286 | ]
|
287 | receive expects, done
|
288 | send 'graph', 'removeinitial',
|
289 | tgt:
|
290 | node: 'Foo'
|
291 | port: 'in'
|
292 | graph: 'foo'
|
293 | describe 'renaming a node', ->
|
294 | it 'should send the renamenode event', (done) ->
|
295 | expects = [
|
296 | protocol: 'graph'
|
297 | command: 'renamenode'
|
298 | payload:
|
299 | from: 'Foo'
|
300 | to: 'Baz'
|
301 | graph: 'foo'
|
302 | ]
|
303 | receive expects, done
|
304 | send 'graph', 'renamenode', expects[0].payload
|
305 | describe 'adding a node to a non-existent graph', ->
|
306 | it 'should send an error', (done) ->
|
307 | expects = [
|
308 | protocol: 'graph',
|
309 | command: 'error',
|
310 | payload:
|
311 | message: 'Requested graph not found'
|
312 | ]
|
313 | receive expects, done
|
314 | send 'graph', 'addnode',
|
315 | id: 'Foo'
|
316 | component: "#{collection}/Repeat"
|
317 | graph: 'another-graph'
|
318 | describe 'adding a node without specifying a graph', ->
|
319 | it 'should send an error', (done) ->
|
320 | expects = [
|
321 | protocol: 'graph',
|
322 | command: 'error',
|
323 | payload:
|
324 | message: 'No graph specified'
|
325 | ]
|
326 | receive expects, done
|
327 | send 'graph', 'addnode',
|
328 | id: 'Foo'
|
329 | component: "#{collection}/Repeat"
|
330 |
|
331 |
|
332 |
|
333 |
|
334 |
|
335 |
|
336 |
|
337 | describe 'Network protocol', ->
|
338 |
|
339 | beforeEach (done) ->
|
340 | waitFor = 5
|
341 | listener = (message) ->
|
342 | waitFor--
|
343 | if waitFor
|
344 | connection.once 'message', listener
|
345 | else
|
346 | done()
|
347 | connection.once 'message', listener
|
348 | send 'graph', 'clear',
|
349 | baseDir: path.resolve __dirname, '../'
|
350 | id: 'bar'
|
351 | main: true
|
352 | send 'graph', 'addnode',
|
353 | id: 'Hello'
|
354 | component: "#{collection}/Repeat"
|
355 | metadata: {}
|
356 | graph: 'bar'
|
357 | send 'graph', 'addnode',
|
358 | id: 'World'
|
359 | component: "#{collection}/Drop"
|
360 | metadata: {}
|
361 | graph: 'bar'
|
362 | send 'graph', 'addedge',
|
363 | src:
|
364 | node: 'Hello'
|
365 | port: 'out'
|
366 | tgt:
|
367 | node: 'World'
|
368 | port: 'in'
|
369 | graph: 'bar'
|
370 | send 'graph', 'addinitial',
|
371 | src:
|
372 | data: 'Hello, world!'
|
373 | tgt:
|
374 | node: 'Hello'
|
375 | port: 'in'
|
376 | graph: 'bar'
|
377 |
|
378 |
|
379 |
|
380 |
|
381 |
|
382 |
|
383 |
|
384 |
|
385 |
|
386 |
|
387 |
|
388 |
|
389 |
|
390 |
|
391 | describe 'on starting the network', ->
|
392 | it 'should process the nodes and stop when it completes', (done) ->
|
393 |
|
394 |
|
395 |
|
396 | expects = [
|
397 | protocol: 'network'
|
398 | command: 'started'
|
399 | payload:
|
400 | graph: 'bar'
|
401 | started: true
|
402 | running: true
|
403 | time: String
|
404 | ,
|
405 | protocol: 'network'
|
406 | command: 'connect'
|
407 | payload:
|
408 | id: 'DATA -> IN Hello()'
|
409 | graph: 'bar'
|
410 | tgt: { node: 'Hello', port: 'in' }
|
411 | ,
|
412 | protocol: 'network'
|
413 | command: 'data'
|
414 | payload:
|
415 | id: 'DATA -> IN Hello()'
|
416 | graph: 'bar'
|
417 | tgt: { node: 'Hello', port: 'in' }
|
418 | data: 'Hello, world!'
|
419 | ,
|
420 | protocol: 'network'
|
421 | command: 'connect'
|
422 | payload:
|
423 | id: 'Hello() OUT -> IN World()'
|
424 | graph: 'bar'
|
425 | src: { node: 'Hello', port: 'out' }
|
426 | tgt: { node: 'World', port: 'in' }
|
427 | ,
|
428 | protocol: 'network'
|
429 | command: 'data'
|
430 | payload:
|
431 | id: 'Hello() OUT -> IN World()'
|
432 | graph: 'bar'
|
433 | src: { node: 'Hello', port: 'out' }
|
434 | tgt: { node: 'World', port: 'in' }
|
435 | data: 'Hello, world!'
|
436 | ,
|
437 | protocol: 'network'
|
438 | command: 'disconnect'
|
439 | payload:
|
440 | id: 'DATA -> IN Hello()'
|
441 | graph: 'bar'
|
442 | tgt: { node: 'Hello', port: 'in' }
|
443 | ,
|
444 | protocol: 'network'
|
445 | command: 'disconnect'
|
446 | payload:
|
447 | id: 'Hello() OUT -> IN World()'
|
448 | graph: 'bar'
|
449 | src: { node: 'Hello', port: 'out' }
|
450 | tgt: { node: 'World', port: 'in' }
|
451 | ]
|
452 | receive expects, done
|
453 | send 'network', 'start',
|
454 | graph: 'bar'
|
455 | it "should provide a 'started' status", (done) ->
|
456 | expects = [
|
457 | protocol: 'network'
|
458 | command: 'status'
|
459 | payload:
|
460 | graph: 'bar'
|
461 | running: false
|
462 | started: true
|
463 | ]
|
464 | receive expects, done
|
465 | send 'network', 'getstatus',
|
466 | graph: 'bar'
|
467 | describe 'on stopping the network', ->
|
468 | it 'should be stopped', (done) ->
|
469 | expects = [
|
470 | protocol: 'network'
|
471 | command: 'stopped'
|
472 | payload:
|
473 | graph: 'bar'
|
474 | started: false
|
475 | running: false
|
476 | time: String
|
477 | uptime: Number
|
478 | ]
|
479 | receive expects, done
|
480 | send 'network', 'stop',
|
481 | graph: 'bar'
|
482 | it "should provide a 'stopped' status", (done) ->
|
483 | expects = [
|
484 | protocol: 'network'
|
485 | command: 'status'
|
486 | payload:
|
487 | graph: 'bar'
|
488 | running: false
|
489 | started: false
|
490 | ]
|
491 | receive expects, done
|
492 | send 'network', 'getstatus',
|
493 | graph: 'bar'
|
494 |
|
495 |
|
496 |
|
497 |
|
498 |
|
499 |
|
500 |
|
501 |
|
502 |
|
503 |
|
504 |
|
505 |
|
506 |
|
507 |
|
508 |
|
509 |
|
510 | describe 'Component protocol', ->
|
511 | describe 'on requesting a component list', ->
|
512 | it 'should receive some known components', (done) ->
|
513 | expects = [
|
514 | protocol: 'component'
|
515 | command: 'component'
|
516 | payload:
|
517 | name: "#{collection}/Output"
|
518 | description: "This component receives input on a single inport, and sends the data items directly to console.log"
|
519 | icon: String
|
520 | subgraph: false
|
521 | inPorts:
|
522 | [
|
523 | id: 'in'
|
524 | type: 'all'
|
525 | required: false
|
526 | addressable: false
|
527 | description: 'Packet to be printed through console.log'
|
528 | ,
|
529 | id: 'options'
|
530 | type: 'object'
|
531 | required: false
|
532 | addressable: false
|
533 | description: 'Options to be passed to console.log'
|
534 | ]
|
535 | outPorts:
|
536 | [
|
537 | id: 'out'
|
538 | type: 'all'
|
539 | required: false
|
540 | addressable: false
|
541 | ]
|
542 | ]
|
543 | receive expects, done, (msg) ->
|
544 | return msg.payload.name isnt "#{collection}/Output"
|
545 |
|
546 |
|
547 |
|
548 |
|
549 |
|
550 |
|
551 |
|
552 |
|
553 |
|
554 |
|
555 |
|
556 |
|
557 |
|
558 |
|
559 |
|
560 |
|
561 |
|
562 |
|
563 |
|
564 |
|
565 |
|
566 |
|
567 |
|
568 |
|
569 |
|
570 |
|
571 |
|
572 |
|
573 |
|
574 |
|
575 |
|
576 |
|
577 |
|
578 | send 'component', 'list', process.cwd()
|
579 |
|
580 |
|
581 |
|
582 |
|
583 | exports.testRuntimeCommand = (runtimeType, command, host='localhost', port=8080, collection='core') ->
|
584 | child = null
|
585 | exports.testRuntime( runtimeType,
|
586 | (connectClient) ->
|
587 | child = shelljs.exec command, {async: true}
|
588 | connectClient()
|
589 | ->
|
590 | child.kill "SIGKILL"
|
591 | host
|
592 | port
|
593 | collection
|
594 | )
|