1 | var assert = require('assert');
|
2 | var http = require('http');
|
3 | var WebSocket = require('ws');
|
4 | var zetta = require('../');
|
5 | var zettacluster = require('zetta-cluster');
|
6 | var Scout = require('./fixture/example_scout');
|
7 | var VirtualDevice = require('../lib/virtual_device');
|
8 | var LedJSON = require('./fixture/virtual_device.json');
|
9 |
|
10 | var mockSocket = {
|
11 | on: function(){},
|
12 | subscribe: function(topic, cb){
|
13 | if(cb) {
|
14 | cb();
|
15 | }
|
16 | },
|
17 | unsubscribe: function(){}
|
18 | };
|
19 |
|
20 | describe('Virtual Device', function() {
|
21 | var base = null;
|
22 | var cluster = null;
|
23 | var device = null;
|
24 | var socket = null;
|
25 | var deviceJson = null;
|
26 | var vdevice = null;
|
27 |
|
28 | var startPort = 2600;
|
29 |
|
30 | beforeEach(function(done) {
|
31 | cluster = zettacluster({ zetta: zetta })
|
32 | .server('cloud')
|
33 | .server('detroit1', [Scout], ['cloud'])
|
34 | .on('ready', function() {
|
35 | socket = cluster.servers['cloud'].httpServer.peers['detroit1'];
|
36 | if (!socket) {
|
37 | done(new Error('socket not found'));
|
38 | }
|
39 |
|
40 | var did = Object.keys(cluster.servers['detroit1'].runtime._jsDevices)[0];
|
41 | device = cluster.servers['detroit1'].runtime._jsDevices[did];
|
42 | var id = cluster.servers['detroit1'].id;
|
43 | base = 'localhost:' + cluster.servers['cloud']._testPort + '/servers/' + cluster.servers['cloud'].locatePeer(id) + '/devices/' + did;
|
44 |
|
45 | http.get('http://' + base, function(res) {
|
46 | var buffer = [];
|
47 | var len = 0;
|
48 | res.on('readable', function() {
|
49 | var data;
|
50 | while (data = res.read()) {
|
51 | buffer.push(data);
|
52 | len += data.length;
|
53 | }
|
54 | });
|
55 | res.on('end', function() {
|
56 | var buf = Buffer.concat(buffer, len);
|
57 | deviceJson = JSON.parse(buf.toString());
|
58 | vdevice = new VirtualDevice(deviceJson, socket);
|
59 | vdevice.on('ready', function() {
|
60 | setTimeout(done, 100);
|
61 | });
|
62 | });
|
63 | res.on('error', function(err) {
|
64 | done(err);
|
65 | });
|
66 | })
|
67 | })
|
68 | .run(function(err){
|
69 | if (err) {
|
70 | return done(err);
|
71 | }
|
72 | });
|
73 | });
|
74 |
|
75 | afterEach(function(done) {
|
76 | cluster.stop();
|
77 | setTimeout(done, 10);
|
78 | });
|
79 |
|
80 | describe('.call method', function() {
|
81 |
|
82 | it('call should work without a callback function', function(done) {
|
83 | vdevice.call('change')
|
84 | var timer = setTimeout(function() {
|
85 | done(new Error('Faied to recv transition call on detroit device'));
|
86 | }, 100);
|
87 |
|
88 | device.on('change', function() {
|
89 | clearTimeout(timer);
|
90 | done();
|
91 | });
|
92 | });
|
93 |
|
94 | it('_update should always be called with data.actions in proper format', function(done) {
|
95 | var called = 0;
|
96 | var orig = vdevice._update;
|
97 | vdevice._update = function(data) {
|
98 | called++;
|
99 | assert(Array.isArray(data.actions));
|
100 | data.actions.forEach(function(action) {
|
101 | assert(action.class);
|
102 | assert(action.name);
|
103 | assert(action.method);
|
104 | assert(action.href);
|
105 | assert(action.fields);
|
106 | });
|
107 | orig.apply(vdevice, arguments);
|
108 |
|
109 |
|
110 |
|
111 | if (called === 2) {
|
112 | done();
|
113 | }
|
114 | };
|
115 |
|
116 | vdevice.call('change');
|
117 | });
|
118 |
|
119 | it('call should work without arguments', function(done) {
|
120 | vdevice.call('change', function(err) {
|
121 | assert.equal(err, null);
|
122 | });
|
123 | var timer = setTimeout(function() {
|
124 | done(new Error('Faied to recv transition call on detroit device'));
|
125 | }, 100);
|
126 |
|
127 | device.on('change', function() {
|
128 | clearTimeout(timer);
|
129 | done();
|
130 | });
|
131 | });
|
132 |
|
133 | it('call should work with arguments', function(done) {
|
134 | vdevice.call('test', 321, function(err) {
|
135 | assert.equal(err, null);
|
136 | });
|
137 | var timer = setTimeout(function() {
|
138 | done(new Error('Faied to recv transition call on detroit device'));
|
139 | }, 100);
|
140 |
|
141 | device.on('test', function() {
|
142 | clearTimeout(timer);
|
143 | assert.equal(device.value, 321);
|
144 | done();
|
145 | });
|
146 | });
|
147 |
|
148 | it('call should work with arguments, after peer reconnects', function(done) {
|
149 |
|
150 | var timer = setTimeout(function() {
|
151 | done(new Error('Faied to recv transition call on detroit device'));
|
152 | }, 1500);
|
153 |
|
154 | vdevice.call('test', 999, function(err) {
|
155 | assert.equal(err, null);
|
156 |
|
157 | clearTimeout(timer);
|
158 | assert.equal(device.value, 999);
|
159 |
|
160 | var socket = cluster.servers['cloud'].httpServer.peers['detroit1'];
|
161 | socket.close();
|
162 |
|
163 | setTimeout(function() {
|
164 | vdevice.call('test', 222, function(err) {
|
165 | assert.equal(err, null);
|
166 | });
|
167 | var timer = setTimeout(function() {
|
168 | done(new Error('Faied to recv transition call on detroit device'));
|
169 | }, 1500);
|
170 |
|
171 | device.on('test', function() {
|
172 | clearTimeout(timer);
|
173 | assert.equal(device.value, 222);
|
174 | done();
|
175 | });
|
176 | }, 1500);
|
177 | });
|
178 |
|
179 | });
|
180 | });
|
181 |
|
182 | describe('Device log monitor stream', function() {
|
183 |
|
184 | it('should update virtual devices state when detroit device updates', function(done) {
|
185 | assert.equal(vdevice.state, 'ready');
|
186 | device.call('change', function() {
|
187 | assert.equal(device.state, 'changed');
|
188 | setTimeout(function() {
|
189 | assert.equal(vdevice.state, 'changed');
|
190 | done();
|
191 | }, 100);
|
192 | });
|
193 | });
|
194 |
|
195 | it('should update virtual devices state when virtual device calls transition', function(done) {
|
196 | assert.equal(vdevice.state, 'ready');
|
197 | vdevice.call('change', function() {
|
198 | assert.equal(device.state, 'changed');
|
199 | setTimeout(function() {
|
200 | assert.equal(vdevice.state, 'changed');
|
201 | done();
|
202 | }, 100);
|
203 | });
|
204 | });
|
205 |
|
206 | });
|
207 |
|
208 |
|
209 |
|
210 | describe('Device monitor streams on properties', function() {
|
211 |
|
212 | it('should update virtual device when value increments locally', function(done) {
|
213 | assert.equal(vdevice.bar, 0);
|
214 | assert.equal(device.bar, 0);
|
215 | device.incrementStreamValue();
|
216 | assert.equal(device.bar, 1);
|
217 | setTimeout(function() {
|
218 | assert.equal(vdevice.bar, 1);
|
219 | done();
|
220 | }, 100);
|
221 | });
|
222 |
|
223 | it('should implement .createReadStream() for object stream', function(done) {
|
224 | vdevice.createReadStream('bar').on('data', function(msg) {
|
225 | assert.equal(msg.data, 1);
|
226 | done();
|
227 | });
|
228 |
|
229 | setTimeout(function(){
|
230 | device.incrementStreamValue();
|
231 | }, 10);
|
232 | })
|
233 |
|
234 | it('should implement .createReadStream() for binary stream', function(done) {
|
235 | vdevice.createReadStream('foobar').on('data', function(buf) {
|
236 | assert.deepEqual(buf, new Buffer([1]));
|
237 | done();
|
238 | });
|
239 | setTimeout(function(){
|
240 | device.incrementFooBar();
|
241 | }, 10);
|
242 | })
|
243 |
|
244 | it('should recv data event after a client ws disconnected on the same topic', function(done) {
|
245 |
|
246 | var url = 'ws://localhost:' + cluster.servers['cloud']._testPort + '/servers/detroit1/events?topic=testdriver%2F' + device.id + '%2Fbar';
|
247 |
|
248 | var recv = 0;
|
249 | var wsRecv = 0;
|
250 | vdevice.streams.bar.on('data', function(data) {
|
251 | recv++;
|
252 | });
|
253 |
|
254 | device.incrementStreamValue();
|
255 |
|
256 | setTimeout(function() {
|
257 | assert.equal(recv, 1);
|
258 | var socket = new WebSocket(url);
|
259 | socket.on('message', function() {
|
260 | wsRecv++;
|
261 | });
|
262 | socket.on('open', function() {
|
263 | device.incrementStreamValue();
|
264 | setTimeout(function() {
|
265 | assert.equal(recv, 2);
|
266 | assert.equal(wsRecv, 1);
|
267 |
|
268 | socket.close();
|
269 | device.incrementStreamValue();
|
270 | setTimeout(function() {
|
271 | assert.equal(recv, 3);
|
272 | assert.equal(wsRecv, 1);
|
273 | done();
|
274 | }, 300);
|
275 | },300);
|
276 | });
|
277 | socket.on('error', done);
|
278 |
|
279 | }, 300);
|
280 | });
|
281 |
|
282 | });
|
283 |
|
284 | describe('Device binary streams', function() {
|
285 |
|
286 | it('should only subscribe to a binary stream if used', function(done) {
|
287 | var topic = device.type + '/' + device.id + '/foobar';
|
288 | assert.equal(cluster.servers['detroit1'].pubsub._listeners[topic], undefined);
|
289 | vdevice.streams.foobar.on('data', function() {});
|
290 | setTimeout(function() {
|
291 | assert.notEqual(cluster.servers['detroit1'].pubsub._listeners[topic], undefined);
|
292 | done();
|
293 | }, 100);
|
294 | });
|
295 |
|
296 | it('should pass binary data from local device to virtual', function(done) {
|
297 | var recv = 0;
|
298 | vdevice.streams.foobar.on('data', function(data) {
|
299 | recv++;
|
300 | assert.deepEqual(data, new Buffer([recv]));
|
301 | });
|
302 |
|
303 | setTimeout(function() {
|
304 | device.incrementFooBar();
|
305 | device.incrementFooBar();
|
306 | device.incrementFooBar();
|
307 |
|
308 | setTimeout(function() {
|
309 | assert.equal(recv, 3);
|
310 | done();
|
311 | }, 100);
|
312 | }, 100);
|
313 | });
|
314 |
|
315 | });
|
316 |
|
317 |
|
318 |
|
319 | describe('basic unit tests', function() {
|
320 |
|
321 | var device = null;
|
322 | beforeEach(function() {
|
323 | device = new VirtualDevice(LedJSON , mockSocket);
|
324 | });
|
325 |
|
326 | it('wires up logs, properties, and actions', function() {
|
327 | assert.equal(device.state, 'off');
|
328 | assert.equal(Object.keys(device.streams).length, 2);
|
329 | });
|
330 |
|
331 | it('will change properties with update.', function() {
|
332 | device._update({ properties: {state: 'on'}});
|
333 | assert.equal(device.state, 'on');
|
334 | });
|
335 |
|
336 | it('will return the proper action given a name', function() {
|
337 | var action = device._getAction('turn-on');
|
338 | assert.ok(action);
|
339 | assert.equal(action.name, 'turn-on');
|
340 | assert.equal(action.fields.length, 1);
|
341 | });
|
342 |
|
343 | it('will return link given a title', function() {
|
344 | var link = device._getLinkWithTitle('state');
|
345 | assert.ok(link);
|
346 | assert.equal(link.title, 'state');
|
347 | assert.equal(link.rel[0], 'monitor');
|
348 | assert.equal(link.rel[1], 'http://rels.zettajs.io/object-stream');
|
349 | });
|
350 |
|
351 | it('will return an array of links if searched for by rel', function() {
|
352 | var links = device._getLinksWithRel('http://rels.zettajs.io/object-stream');
|
353 | assert.ok(links);
|
354 | assert.equal(links.length, 2);
|
355 | assert.ok(Array.isArray(links));
|
356 | });
|
357 |
|
358 | it('will parse out a topic for a particular link', function() {
|
359 | var link = device._getLinkWithTitle('state');
|
360 | var topic = device._getTopic(link);
|
361 | assert.equal(topic, 'led/0eaf8607-5b8c-45ee-afae-9a5f9e1f34e2/state');
|
362 | });
|
363 |
|
364 | it('will encode transition arguments into an object', function() {
|
365 | var action = device._getAction('turn-on');
|
366 | var data = device._encodeData(action, {});
|
367 | assert.ok(data);
|
368 | assert.equal(Object.keys(data)[0], 'action');
|
369 | assert.equal(data.action, 'turn-on');
|
370 | });
|
371 |
|
372 | it('exposes .available() method', function() {
|
373 | assert.equal(typeof device.available, 'function');
|
374 | assert.equal(device.available('turn-on'), true);
|
375 | assert.equal(device.available('turn-off'), false);
|
376 | });
|
377 | });
|
378 |
|
379 | });
|
380 |
|