1 | var assert = require('assert');
|
2 | var util = require('util');
|
3 | var fs = require('fs');
|
4 | var https = require('https');
|
5 | var zetta = require('../zetta');
|
6 | var WebSocket = require('ws');
|
7 | var MemRegistry = require('./fixture/mem_registry');
|
8 | var MemPeerRegistry = require('./fixture/mem_peer_registry');
|
9 |
|
10 | var Device = require('zetta-device');
|
11 | var HttpDevice = require('zetta-http-device');
|
12 | var Scout = require('zetta-scout');
|
13 | var ExampleDevice = require('./fixture/example_driver');
|
14 | var Query = require('calypso').Query;
|
15 |
|
16 | describe('Zetta', function() {
|
17 | var reg = null;
|
18 | var peerRegistry = null;
|
19 |
|
20 | beforeEach(function() {
|
21 | reg = new MemRegistry();
|
22 | peerRegistry = new MemPeerRegistry();
|
23 | });
|
24 |
|
25 | it('should be attached to the zetta as a function', function() {
|
26 | assert.equal(typeof zetta, 'function');
|
27 | });
|
28 |
|
29 | it('has the name set using the name() function.', function() {
|
30 | var z = zetta({ registry: reg, peerRegistry: peerRegistry }).name('local').silent();
|
31 | assert.equal(z._name, 'local');
|
32 | });
|
33 |
|
34 | it('should throw error if setting name to *', function() {
|
35 | assert.throws(function() {
|
36 | var z = zetta({ registry: reg, peerRegistry: peerRegistry }).name('*').silent();
|
37 | }, Error);
|
38 | });
|
39 |
|
40 | it('has the silent() function to suppress logging.', function() {
|
41 | var z = zetta({ registry: reg, peerRegistry: peerRegistry }).name('local').silent();
|
42 | });
|
43 |
|
44 | it('errors thrown in zetta apps should propagate.', function(done) {
|
45 | var d = require('domain').create();
|
46 | d.on('error', function(err) {
|
47 | assert.equal(err.message, '123');
|
48 | d.dispose()
|
49 | done();
|
50 | });
|
51 | d.run(function() {
|
52 | zetta()
|
53 | .silent()
|
54 | .use(ExampleDevice)
|
55 | .use(function(server) {
|
56 | var ledQuery = server.where({ type: 'testdriver' });
|
57 | server.observe(ledQuery, function(led) {
|
58 | throw new Error('123');
|
59 | })
|
60 | })
|
61 | .listen(0);
|
62 | });
|
63 | });
|
64 |
|
65 | it('support tls options for https server', function(done) {
|
66 | var options = {
|
67 | key: fs.readFileSync(__dirname + '/fixture/keys/key.pem'),
|
68 | cert: fs.readFileSync(__dirname + '/fixture/keys/cert.pem')
|
69 | };
|
70 |
|
71 | var z = zetta({ registry: reg, peerRegistry: peerRegistry, tls: options })
|
72 | .silent()
|
73 | .listen(0, function(err) {
|
74 | if (err) return done(err);
|
75 |
|
76 | var port = z.httpServer.server.address().port;
|
77 | var req = https.get({
|
78 | host: 'localhost',
|
79 | port: port,
|
80 | path: '/',
|
81 | rejectUnauthorized: false
|
82 | }, function(res) {
|
83 | assert.equal(res.statusCode, 200);
|
84 | done();
|
85 | });
|
86 | req.on('error', done);
|
87 | });
|
88 | });
|
89 |
|
90 | it('has the logger() function to pass in custom logging.', function(done) {
|
91 | var z = zetta({ registry: reg, peerRegistry: peerRegistry });
|
92 | z.logger(function(log) {
|
93 | log.on('message', function(level, event, msg, data) {
|
94 | assert.equal(level, 'info');
|
95 | assert.equal(event, 'custom');
|
96 | assert.equal(msg, 'some message');
|
97 | assert.equal(data.data, 1);
|
98 | done();
|
99 | });
|
100 |
|
101 | z.log.info('custom', 'some message', {data: 1});
|
102 | });
|
103 | });
|
104 |
|
105 |
|
106 | it('will load an app with the load() function', function(done) {
|
107 | zetta({ registry: reg, peerRegistry: peerRegistry })
|
108 | .silent()
|
109 | .load(function(server) {
|
110 | assert.ok(server);
|
111 | done();
|
112 | })
|
113 | ._initApps(function(){});
|
114 | });
|
115 |
|
116 | it('will load an app with the use() function', function(done) {
|
117 | zetta({ registry: reg, peerRegistry: peerRegistry })
|
118 | .silent()
|
119 | .use(function(server) {
|
120 | assert.ok(server);
|
121 | done();
|
122 | })
|
123 | ._initApps(function(){});
|
124 | });
|
125 |
|
126 | it('will load an app with the use() function and additional arguments', function(done) {
|
127 | var app = function(server, opts) {
|
128 | assert.ok(server);
|
129 | assert.ok(opts);
|
130 | assert.equal(opts.foo, 1);
|
131 | done();
|
132 | }
|
133 |
|
134 | zetta({ registry: reg, peerRegistry: peerRegistry })
|
135 | .silent()
|
136 | .use(app, { foo: 1})
|
137 | ._initApps(function() {
|
138 |
|
139 | });
|
140 |
|
141 | });
|
142 |
|
143 | it('will load an app with the use() function and additional arguments', function(done) {
|
144 | var app = function(server, foo, bar) {
|
145 | assert.ok(server);
|
146 | assert.equal(foo, 1);
|
147 | assert.equal(bar, 2);
|
148 | done();
|
149 | }
|
150 |
|
151 | zetta({ registry: reg, peerRegistry: peerRegistry })
|
152 | .silent()
|
153 | .use(app, 1, 2)
|
154 | ._initApps(function() {
|
155 |
|
156 | });
|
157 |
|
158 | });
|
159 | it('will load a driver with the use() function', function() {
|
160 | var z = zetta({ registry: reg, peerRegistry: peerRegistry }).silent();
|
161 | function TestDriver() {
|
162 | Device.call(this);
|
163 | }
|
164 | util.inherits(TestDriver, Device);
|
165 |
|
166 | TestDriver.prototype.init = function() {};
|
167 |
|
168 | z.use(TestDriver);
|
169 | var s = z._scouts[0];
|
170 | assert.equal(s.server, z.runtime);
|
171 | });
|
172 |
|
173 | it('will load an HTTP driver with the use() function', function() {
|
174 | var z = zetta({ registry: reg, peerRegistry: peerRegistry }).silent();
|
175 | function TestDriver() {
|
176 | HttpDevice.call(this);
|
177 | }
|
178 | util.inherits(TestDriver, HttpDevice);
|
179 |
|
180 | TestDriver.prototype.init = function() {};
|
181 |
|
182 | z.use(TestDriver);
|
183 | var s = z._scouts[0];
|
184 | assert.equal(s.server, z.runtime);
|
185 | });
|
186 |
|
187 | it('will load a scout with the use() function', function() {
|
188 | var z = zetta({ registry: reg, peerRegistry: peerRegistry }).silent();
|
189 | function TestScout() {
|
190 | Scout.call(this);
|
191 | }
|
192 | util.inherits(TestScout, Scout);
|
193 | z.use(TestScout);
|
194 | assert.equal(z._scouts.length, 2);
|
195 | var s = z._scouts[0];
|
196 | assert.equal(s.server, z.runtime);
|
197 | });
|
198 |
|
199 | it('will set the what query is used for expose()', function() {
|
200 | var z = zetta({ registry: reg, peerRegistry: peerRegistry }).silent();
|
201 | z.expose('*');
|
202 |
|
203 | assert.ok(z._exposeQuery);
|
204 | });
|
205 |
|
206 | it('will call init on the server prototype to ensure everything is wired up correctly.', function(done) {
|
207 | function MockHttp(){}
|
208 | MockHttp.prototype.init = function() {
|
209 | done();
|
210 | };
|
211 | MockHttp.prototype.listen = function(port) {};
|
212 |
|
213 | var z = zetta({ registry: reg, peerRegistry: peerRegistry }).silent();
|
214 | z.httpServer = new MockHttp();
|
215 | z.listen(0);
|
216 |
|
217 | });
|
218 |
|
219 | it('will apply arguments to httpServer when listen() is called', function(done) {
|
220 | function MockHttp(){}
|
221 | MockHttp.prototype.init = function(){};
|
222 | MockHttp.prototype.listen = function(port) {
|
223 | assert.equal(port, 0);
|
224 | done();
|
225 | };
|
226 |
|
227 | var z = zetta({ registry: reg, peerRegistry: peerRegistry }).silent();
|
228 | z.httpServer = new MockHttp();
|
229 | z.listen(0);
|
230 |
|
231 | });
|
232 |
|
233 | it('will correctly apply the callback to httpServer when listen() is called', function(done) {
|
234 | function MockHttp(){}
|
235 | MockHttp.prototype.init = function(){};
|
236 | MockHttp.prototype.listen = function(port, cb) {
|
237 | assert.equal(port, 0);
|
238 | cb(null);
|
239 | };
|
240 |
|
241 | var z = zetta({ registry: reg, peerRegistry: peerRegistry }).silent();
|
242 | z.httpServer = new MockHttp();
|
243 | z.listen(0, function(err) {
|
244 | assert.ok(!err);
|
245 | done();
|
246 | });
|
247 | });
|
248 |
|
249 | it('should initialize device with proper properties set.', function(done) {
|
250 | var z = zetta({ registry: reg, peerRegistry: peerRegistry })
|
251 | .silent()
|
252 | .use(ExampleDevice, 1, 'a')
|
253 | ._run(function(err) {
|
254 | if (err) {
|
255 | return done(err);
|
256 | }
|
257 |
|
258 | var device = z.runtime._jsDevices[Object.keys(z.runtime._jsDevices)[0]];
|
259 | device.call('change', done);
|
260 | });
|
261 | });
|
262 |
|
263 | it('should initialize 3 devices with correct params when using multiple use', function(done) {
|
264 | var z = zetta({ registry: reg, peerRegistry: peerRegistry })
|
265 | .silent()
|
266 | .use(ExampleDevice, 1, 'a')
|
267 | .use(ExampleDevice, 2, 'b')
|
268 | .use(ExampleDevice, 3, 'c')
|
269 | ._run(function(err) {
|
270 | if (err) {
|
271 | return done(err);
|
272 | }
|
273 |
|
274 | var find = function(x, y) {
|
275 | return Object.keys(z.runtime._jsDevices).some(function(key){
|
276 | var device = z.runtime._jsDevices[key];
|
277 | return device._x === x && device._y === y;
|
278 | });
|
279 | };
|
280 |
|
281 | assert(find(1, 'a'));
|
282 | assert(find(2, 'b'));
|
283 | assert(find(3, 'c'));
|
284 |
|
285 | done();
|
286 | });
|
287 | });
|
288 |
|
289 |
|
290 |
|
291 | it('should provision 3 devices already in registry with correct params when using multiple use', function(done) {
|
292 | var z = zetta({ registry: reg, peerRegistry: peerRegistry })
|
293 | .silent()
|
294 | .use(ExampleDevice, 1, 'a')
|
295 | .use(ExampleDevice, 2, 'b')
|
296 | .use(ExampleDevice, 3, 'c')
|
297 | ._run(function(err) {
|
298 | if (err) {
|
299 | return done(err);
|
300 | }
|
301 |
|
302 | var find = function(x, y) {
|
303 | var id = null;
|
304 | Object.keys(z.runtime._jsDevices).some(function(key){
|
305 | var device = z.runtime._jsDevices[key];
|
306 | if (device._x === x && device._y === y) {
|
307 | id = device.id;
|
308 | return true;
|
309 | }
|
310 | });
|
311 |
|
312 | return id;
|
313 | };
|
314 |
|
315 | assert(find(1, 'a'));
|
316 | assert(find(2, 'b'));
|
317 | assert(find(3, 'c'));
|
318 |
|
319 | var z2 = zetta({ registry: reg, peerRegistry: peerRegistry })
|
320 | .silent()
|
321 | .use(ExampleDevice, 1, 'a')
|
322 | .use(ExampleDevice, 2, 'b')
|
323 | .use(ExampleDevice, 3, 'c')
|
324 | ._run(function(err) {
|
325 | if (err) {
|
326 | return done(err);
|
327 | }
|
328 |
|
329 | var find2 = function(id, x, y) {
|
330 | return Object.keys(z2.runtime._jsDevices).some(function(key){
|
331 | var device = z2.runtime._jsDevices[key];
|
332 | return device.id === id && device._x === x && device._y === y;
|
333 | });
|
334 | };
|
335 |
|
336 | assert(find2(find(1, 'a'), 1, 'a'));
|
337 | assert(find2(find(2, 'b'), 2, 'b'));
|
338 | assert(find2(find(3, 'c'), 3, 'c'));
|
339 | done();
|
340 | });
|
341 | });
|
342 | });
|
343 |
|
344 | it('should only call .init once on a device driver with .use(Device)', function(done) {
|
345 | var called = 0;
|
346 | var oldInit = ExampleDevice.prototype.init;
|
347 | ExampleDevice.prototype.init = function(config) {
|
348 | called++;
|
349 | return oldInit.call(this, config);
|
350 | };
|
351 |
|
352 | var app = zetta({ peerRegistry: peerRegistry, registry: reg });
|
353 | app.silent();
|
354 | app.use(ExampleDevice);
|
355 | app.listen(0);
|
356 | setTimeout(function() {
|
357 | ExampleDevice.prototype.init = oldInit;
|
358 | assert.equal(called, 1);
|
359 | done();
|
360 | }, 10);
|
361 | });
|
362 |
|
363 | describe('peering', function() {
|
364 | it('.link should add to peers', function(done){
|
365 | var app = zetta({ peerRegistry: peerRegistry, registry: reg });
|
366 | app.silent();
|
367 | app.link('http://example.com/');
|
368 | app._initPeers(app._peers, function(err) {
|
369 | setTimeout(function() {
|
370 | assert.equal(app._peerClients.length, 1);
|
371 | done();
|
372 | }, 100);
|
373 | });
|
374 | });
|
375 |
|
376 | it('.link should not add to peers', function(done){
|
377 |
|
378 | peerRegistry.db.put('1234567', JSON.stringify({id: '1234567', direction: 'initiator', url: 'http://example.com/', fromLink: true}), function(err){
|
379 | var app = zetta({ peerRegistry: peerRegistry, registry: reg });
|
380 | app.silent();
|
381 | app._initPeers(app._peers, function(err) {
|
382 | setTimeout(function() {
|
383 | assert.equal(app._peerClients.length, 0);
|
384 | done();
|
385 | }, 100);
|
386 | });
|
387 | });
|
388 | });
|
389 |
|
390 | it('will delete fromLink peers in the registry', function(done) {
|
391 | peerRegistry.db.put('1234567', JSON.stringify({ id:'1234567', direction: 'initiator', url: 'http://example.com/', fromLink: true}), function(err) {
|
392 | var app = zetta({ peerRegistry: peerRegistry, registry: reg });
|
393 | app._initPeers(app._peers, function(err) {
|
394 | setTimeout(function(){
|
395 | assert.equal(app._peerClients.length, 0);
|
396 | peerRegistry.find(Query.of('peers'), function(err, results) {
|
397 | assert.equal(results.length, 0);
|
398 | done();
|
399 | });
|
400 | }, 100);
|
401 | });
|
402 | });
|
403 |
|
404 | });
|
405 |
|
406 | it('will init API peers.', function(done){
|
407 |
|
408 | peerRegistry.db.put('1234567', JSON.stringify({id: '1234567', direction: 'initiator', url: 'http://example.com/'}), function(err){
|
409 | var app = zetta({ peerRegistry: peerRegistry, registry: reg });
|
410 | app.silent();
|
411 | app._initPeers(app._peers, function(err) {
|
412 | setTimeout(function() {
|
413 | assert.equal(app._peerClients.length, 1);
|
414 | done();
|
415 | }, 100);
|
416 | });
|
417 | });
|
418 | });
|
419 |
|
420 | });
|
421 |
|
422 | it('has the properties() function to add custom properties to the api.', function() {
|
423 | var z = zetta({ registry: reg, peerRegistry: peerRegistry });
|
424 | assert(typeof z.properties, 'function');
|
425 | z.properties({ test: 'abc' });
|
426 | });
|
427 |
|
428 | it('.getProperties() returns properties.', function() {
|
429 | var z = zetta({ registry: reg, peerRegistry: peerRegistry }).name('test');
|
430 | z.properties({ someKey: 123 });
|
431 | assert.deepEqual(z.getProperties(), { name: 'test', someKey: 123 });
|
432 | });
|
433 |
|
434 |
|
435 | describe('HTTP Server Websocket connect hooks', function() {
|
436 | it('peer connect hook will fire when peer connects', function(done) {
|
437 | var fired = false;
|
438 | var z = zetta({ registry: new MemRegistry(), peerRegistry: new MemPeerRegistry() });
|
439 | z.silent();
|
440 | z.use(function(server) {
|
441 | server.httpServer.onPeerConnect(function(request, socket, head, next) {
|
442 | fired = true;
|
443 | next();
|
444 | })
|
445 | })
|
446 | z.listen(0, function() {
|
447 | var port = z.httpServer.server.address().port;
|
448 | zetta({ registry: new MemRegistry(), peerRegistry: new MemPeerRegistry() })
|
449 | .silent()
|
450 | .use(function(server) {
|
451 | server.pubsub.subscribe('_peer/connect', function(topic, data) {
|
452 | assert.equal(fired, true);
|
453 | done();
|
454 | })
|
455 | })
|
456 | .link('http://localhost:' + port)
|
457 | .listen(0);
|
458 | })
|
459 | })
|
460 |
|
461 | it('websocket connect hook will fire when clients connects', function(done) {
|
462 | var fired = false;
|
463 | var z = zetta({ registry: new MemRegistry(), peerRegistry: new MemPeerRegistry() });
|
464 | z.silent();
|
465 | z.use(function(server) {
|
466 | server.httpServer.onEventWebsocketConnect(function(request, socket, head, next) {
|
467 | fired = true;
|
468 | next();
|
469 | })
|
470 | })
|
471 | z.listen(0, function() {
|
472 | var port = z.httpServer.server.address().port;
|
473 | var ws = new WebSocket('ws://localhost:' + port + '/events');
|
474 | ws.once('open', function() {
|
475 | assert.equal(fired, true);
|
476 | done();
|
477 | })
|
478 | });
|
479 | })
|
480 |
|
481 | it('multiple hooks will fire in order for peer connects', function(done) {
|
482 | var fired = [];
|
483 | var z = zetta({ registry: new MemRegistry(), peerRegistry: new MemPeerRegistry() });
|
484 | z.silent();
|
485 | z.use(function(server) {
|
486 | server.httpServer.onPeerConnect(function(request, socket, head, next) {
|
487 | fired.push(1);
|
488 | next();
|
489 | })
|
490 | server.httpServer.onPeerConnect(function(request, socket, head, next) {
|
491 | fired.push(2);
|
492 | next();
|
493 | })
|
494 | })
|
495 | z.listen(0, function() {
|
496 | var port = z.httpServer.server.address().port;
|
497 | zetta({ registry: new MemRegistry(), peerRegistry: new MemPeerRegistry() })
|
498 | .silent()
|
499 | .use(function(server) {
|
500 | server.pubsub.subscribe('_peer/connect', function(topic, data) {
|
501 | assert.deepEqual(fired, [1, 2]);
|
502 | done();
|
503 | })
|
504 | })
|
505 | .link('http://localhost:' + port)
|
506 | .listen(0);
|
507 | })
|
508 | })
|
509 |
|
510 | it('multiple hooks will fire in order for websocket connects', function(done) {
|
511 | var fired = [];
|
512 | var z = zetta({ registry: new MemRegistry(), peerRegistry: new MemPeerRegistry() });
|
513 | z.silent();
|
514 | z.use(function(server) {
|
515 | server.httpServer.onEventWebsocketConnect(function(request, socket, head, next) {
|
516 | fired.push(1);
|
517 | next();
|
518 | })
|
519 | server.httpServer.onEventWebsocketConnect(function(request, socket, head, next) {
|
520 | fired.push(2);
|
521 | next();
|
522 | })
|
523 | })
|
524 | z.listen(0, function() {
|
525 | var port = z.httpServer.server.address().port;
|
526 | var ws = new WebSocket('ws://localhost:' + port + '/events');
|
527 | ws.once('open', function() {
|
528 | assert.deepEqual(fired, [1, 2]);
|
529 | done();
|
530 | })
|
531 | });
|
532 | })
|
533 |
|
534 | it('returning an error from hook will result in a 500 on peer connect', function(done) {
|
535 | var z = zetta({ registry: new MemRegistry(), peerRegistry: new MemPeerRegistry() });
|
536 | z.silent();
|
537 | z.use(function(server) {
|
538 | server.httpServer.onPeerConnect(function(request, socket, head, next) {
|
539 | next(new Error('Error 123'));
|
540 | })
|
541 | })
|
542 | z.listen(0, function() {
|
543 | var port = z.httpServer.server.address().port;
|
544 | zetta({ registry: new MemRegistry(), peerRegistry: new MemPeerRegistry() })
|
545 | .silent()
|
546 | .use(function(server) {
|
547 | server.onPeerResponse(function(req) {
|
548 | return req.map(function(env) {
|
549 | assert.equal(env.response.statusCode, 500);
|
550 | done();
|
551 | return env;
|
552 | });
|
553 | });
|
554 | })
|
555 | .link('http://localhost:' + port)
|
556 | .listen(0);
|
557 | })
|
558 | })
|
559 |
|
560 | it('returning an error from hook will result in a 500 on websocket connect', function(done) {
|
561 | var z = zetta({ registry: new MemRegistry(), peerRegistry: new MemPeerRegistry() });
|
562 | z.silent();
|
563 | z.use(function(server) {
|
564 | server.httpServer.onEventWebsocketConnect(function(request, socket, head, next) {
|
565 | next(new Error('test error'));
|
566 | })
|
567 | })
|
568 | z.listen(0, function() {
|
569 | var port = z.httpServer.server.address().port;
|
570 | var ws = new WebSocket('ws://localhost:' + port + '/events');
|
571 | ws.once('error', function(err) {
|
572 | assert.equal(err.message, 'unexpected server response (500)');
|
573 | done();
|
574 | })
|
575 | });
|
576 | })
|
577 | });
|
578 |
|
579 | });
|