UNPKG

18.8 kBtext/coffeescriptView Raw
1chai = require 'chai'
2path = require 'path'
3# spawn = require('child_process').spawn
4shelljs = require 'shelljs'
5WebSocketClient = require('websocket').client
6
7check = (done, f) ->
8 try
9 f()
10 catch e
11 done e
12
13exports.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 # TODO:
331 # ports:
332 # addinport / removeinport / renameinport
333 # addoutport / removeoutport / renameoutport
334 # groups:
335 # addgroup / removegroup / renamegroup / changegroup
336
337 describe 'Network protocol', ->
338 # Set up a clean graph
339 beforeEach (done) ->
340 waitFor = 5 # set this to the number of commands below
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 # getstatus does not return a status when the network has not been started: seems like a bug
378 # describe "on requesting a graph's status", ->
379 # it 'should provide the status', (done) ->
380 # expects = [
381 # protocol: 'network'
382 # command: 'status'
383 # payload:
384 # graph: 'bar'
385 # running: false
386 # started: false
387 # ]
388 # receive expects, done
389 # send 'network', 'getstatus',
390 # graph: 'bar'
391 describe 'on starting the network', ->
392 it 'should process the nodes and stop when it completes', (done) ->
393 # send 'network', 'debug',
394 # graph: 'bar'
395 # enable: true
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 # describe 'on console output', ->
495 # it 'should be able to capture and transmit it', (done) ->
496 # listener = (message) ->
497 # check done, ->
498 # rt.stopCapture()
499 # chai.expect(message.utf8Data).to.be.a 'string'
500 # msg = JSON.parse message.utf8Data
501 # chai.expect(msg.protocol).to.equal 'network'
502 # chai.expect(msg.command).to.equal 'output'
503 # chai.expect(msg.payload).to.be.an 'object'
504 # chai.expect(msg.payload.message).to.equal 'Hello, World!'
505 # done()
506 # connection.once 'message', listener
507 # rt.startCapture()
508 # console.log 'Hello, World!'
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 # listener = (message) ->
547 # check done, ->
548 # chai.expect(message.utf8Data).to.be.a 'string'
549 # msg = JSON.parse message.utf8Data
550 # chai.expect(msg.protocol).to.equal 'component'
551 # chai.expect(msg.payload).to.be.an 'object'
552 # unless msg.payload.name is "#{collection}/Output"
553 # connection.once 'message', listener
554 # else
555 # expectedInPorts = [
556 # id: 'in'
557 # type: 'all'
558 # required: false
559 # addressable: false
560 # description: 'Packet to be printed through console.log'
561 # ,
562 # id: 'options'
563 # type: 'object'
564 # required: false
565 # addressable: false
566 # description: 'Options to be passed to console.log'
567 # ]
568 # # order matters
569 # chai.expect(msg.payload.inPorts).to.eql expectedInPorts
570 # chai.expect(msg.payload.outPorts).to.eql [
571 # id: 'out'
572 # type: 'all'
573 # required: false
574 # addressable: false
575 # ]
576 # done()
577 # connection.once 'message', listener
578 send 'component', 'list', process.cwd()
579
580 # TODO:
581 # getsource => source
582
583exports.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 )