UNPKG

47.4 kBJavaScriptView Raw
1bus = require('../statebus')()
2util = require('util')
3fs = require('fs')
4bus.label = 'bus'
5statelog_indent++
6
7// Include the test functions
8var {test, run_tests, log, assert, delay} = require('../statebus').testing
9
10// Make sure we have all the npm packages installed
11try {
12 var reqs = 'sockjs chokidar websocket bcrypt-nodejs'.split(' ')
13 for (var i=0; i<reqs.length; i++) require(reqs[i])
14} catch (e) {
15 console.log(e)
16 console.warn('#### Yo! You need to run "npm install sockjs chokidar websocket bcrypt-nodejs"')
17 process.exit()
18}
19
20
21// Equality tests
22test(function equality (done) {
23 var equality_tests = [
24 [1, 1, true],
25 [1, 3, false],
26 [NaN, NaN, true],
27 [NaN, undefined, false],
28 [null, {}, false],
29 [null, null, true],
30 [[], [], true],
31 [{}, [], false],
32 [{}, {}, true],
33 [[1], [], false],
34 [[1], [1], true],
35 [[1], [1, 1], false],
36 [[{}], [{}], true],
37 [{a:[]}, {a:[]}, true],
38 [{a:[]}, {}, false],
39 [[[[]]], [[[]]], true],
40 [[[[]]], [[[[]]]], false],
41 [[[{a:3,b:4}]], [[{b:4,a:3}]], true],
42 [[[{a:3,b:4}]], [[{b:4,a:4}]], false],
43 [function () {}, function () {}, false],
44 [require, require, true],
45 [require, function () {}, false],
46 [{key:'f'}, {key:'f'}, true]
47 ]
48
49 for (var i=0; i<equality_tests.length; i++) {
50 assert(bus.deep_equals(equality_tests[i][0],
51 equality_tests[i][1])
52 === equality_tests[i][2],
53 'Equality test failed forward', equality_tests[i])
54
55 assert(bus.deep_equals(equality_tests[i][1],
56 equality_tests[i][0])
57 === equality_tests[i][2],
58 'Equality test failed backward', equality_tests[i])
59 }
60
61 done()
62})
63
64test(function validation (done) {
65 var v_tests = [
66 ['something', 'string', true],
67 ['something', 'something', true],
68 ['something', 3, false],
69 [3, 3, true],
70 [3, 'number', true],
71 [3, 'string', false],
72 [3, {}, false],
73 [3, [], false],
74 [{}, {}, true],
75 [{}, {'key': 'string'}, false],
76 [{}, {'?key': 'string'}, true],
77 [{key: 3}, {'?key': 'string'}, false],
78 [{key: '/foo'}, {key: 'string'}, true],
79 [{a: 2, b: [], c: 'foo'}, {a: 'number', b: 'array', c: 'string'}, true],
80 [{a: 2, b: [], c: 'foo', d: false}, {a: 'number', b: 'array', c: 'string'}, false],
81 [{a: false}, {a: 'boolean'}, true],
82 [{a: 1, b: 2}, {a: 1}, false],
83 [{a: 1, b: 2}, {a: 1, '*':'*'}, true],
84 [{a: 2, b: 2}, {a: 1, '*':'*'}, false]
85 ]
86
87 for (var i=0; i<v_tests.length; i++)
88 assert(bus.validate(v_tests[i][0], v_tests[i][1]) === v_tests[i][2],
89 'Validation test failed', v_tests[i])
90
91 done()
92})
93
94test(function applying_patches (done) {
95 var tests = [
96 ['0', '[0] = "1"', '1'],
97 [['0'], '[0] = "1"', ['1']],
98 [['0'], '[0] = [1]', [[1]]],
99 ['', '[0:0] = "something"', 'something'],
100 ['hello', '[-0:-0] = " there"', 'hello there'],
101 [{}, '.foo = "bar"', {foo: 'bar'}],
102 [{a:1}, '.a = true', {a: true}],
103 [[1,2,3], '[1:1] = [4, 5, 6]', [1, 4, 5, 6, 2, 3]],
104 [[1,2,3], '[-0:-0] = 0', [1, 2, 3, 0]],
105 [[1,2,3], '[-1] = 0', [1, 2, 0]],
106 [[1,2,3], '[-1:-0] = [0]', [1, 2, 0]],
107 [[1,2,3], '[-1:-0] = [0, 0, 0]', [1, 2, 0, 0, 0]]
108 ]
109
110 for (var i=0; i<tests.length; i++) {
111 var x = bus.apply_patch(tests[i][0], tests[i][1])
112 assert(bus.deep_equals(x, tests[i][2]),
113 `Patch applied wrong ${JSON.stringify(tests[i])} got ${x}`)
114 }
115 done()
116})
117
118test(function prune (done) {
119 var boose = require('../statebus')()
120 boose.save({key: 'nark', _: 333666})
121 var a = {key: 'funny',
122 b: {key: 'farty', booger: 3}}
123 assert(!boose.prune(a).b.booger)
124 var b = {key: 'farty',
125 a: a,
126 arr: [1, 3, a, {key: 'nark', ___: 999}]}
127 done()
128})
129
130test(function auto_vars (done) {
131 var n = require('../statebus')()
132 n('r/*').to_fetch = function (rest, o) {return {rest: rest, o: o}}
133 log(n.fetch('r/3'))
134 assert(n.fetch('r/3').rest === '3')
135 assert(n.fetch('r/3').o === undefined)
136
137 n('v/*').to_fetch = function (vars, star) {return {vars: vars, rest: star}}
138 log(n.fetch('v/[3,9 4]').rest)
139 log(n.fetch('v/[3,9 4]').rest.match(/$Bad/))
140 assert(n.fetch('v/[3,9 4]').rest === '[3,9 4]')
141
142 log(n.fetch('v/[3,9 4]').vars)
143 log(n.fetch('v/[3,9 4]').vars.match(/^Bad/))
144 assert(n.fetch('v/[3,9 4]').vars.match(/^Bad/))
145 assert(Array.isArray(n.fetch('v/[3,4]').vars))
146
147 n('a/*').to_save = function (t, k, obj) {
148 log('k:', k, 't:', t, 'o:', obj)
149 assert(k === 'a/foo')
150 assert(typeof obj === 'object')
151 assert(obj.i >= 0 && obj.i <= 4)
152 assert(Object.keys(obj).length === 2)
153 assert(t.version)
154 t.done()
155 }
156 for (var i=0; i<4; i++)
157 n.save({key: 'a/foo', i:i})
158
159 log('Forgetting things now')
160 n('v/*').to_forget = function (vars, star) {log('(from auto_vars) forgot v/' + star)}
161 n.forget('v/[3,9 4]')
162 n.forget('v/[3,4]')
163
164 done()
165})
166
167test(function serve_options (done) {
168 var filename = 'test1.db', backups = 'test1.backups', certs = 'testcerts'
169
170 // Remove old stuff if there
171 try {
172 // rm db
173 fs.unlinkSync(filename)
174
175 // rm -r backups
176 fs.readdirSync(backups).forEach((f) => {
177 fs.unlinkSync(backups + "/" + f);
178 })
179 fs.rmdirSync(backups)
180
181 // // rm -r certs
182 // fs.readdirSync(certs).forEach((f) => {
183 // fs.unlinkSync(certs + "/" + f);
184 // })
185 // fs.rmdirSync(certs)
186 } catch (e) {log(e)}
187
188 var b = require('../statebus').serve({
189 port: 31829,
190 //file_store: false,
191 file_store: {filename: filename, save_delay: 0, backup_dir: backups},
192 certs: {private_key: certs+'/pk', certificate: certs+'/cert'},
193 })
194
195 b.save({key: 'foo', body: 'is this on disk?'})
196 setTimeout(() => done(), 60)
197})
198
199test(function transactions (done) {
200 var bus = require('../statebus')()
201 bus.honk = 'statelog'
202 bus.label = 'tranny'
203
204 // Test to_fetch handlers with t.done()
205 bus('foo1').to_fetch = function (t) {
206 log('to_fetching foo1')
207 setTimeout(()=>{
208 log('returning something for foo1')
209 t.done({something: 'yeah'})
210 }, 0)
211 }
212 var foo1 = bus.fetch('foo1')
213 assert(!foo1.something)
214 setTimeout(() => { log('test foo1'); assert(foo1.something === 'yeah') }, 10)
215
216 // And return a value directly
217 bus('foo2').to_fetch = (t) => { return {something: 'yeah'} }
218 var foo2 = bus.fetch('foo2')
219 setTimeout(() => { log('test foo2'); assert(foo2.something === 'yeah') }, 10)
220
221
222 // Set up some rocks
223 log('Set up some rocks')
224 bus.save({key: 'rock1', a:1})
225 bus.save({key: 'rock2', a:1})
226 bus.save({key: 'softrock1', a:1})
227 bus.save({key: 'softrock2', a:1})
228
229 // Test to_save handlers with t.done(o), t.abort, 'done', 'abort'
230 bus('rock1').to_save = (t) => {setTimeout(()=>{ t.abort() }, 0)}
231 bus('rock2').to_save = ( ) => {return 'abort'}
232 bus('softrock1').to_save = (t) => {setTimeout(()=>{ t.done() }, 0)}
233 bus('softrock2').to_save = ( ) => {return 'done'}
234
235 //bus.honk = true
236 setTimeout(() => {
237 log('Save some changes')
238 bus.save({key: 'rock1', a:2})
239 bus.save({key: 'rock2', a:2})
240 bus.save({key: 'softrock1', a:2})
241 bus.save({key: 'softrock2', a:2})
242
243 setTimeout(() => {
244 log('Check if the saves worked...')
245 assert(bus.cache.rock1.a == 1)
246 assert(bus.cache.rock2.a == 1)
247 assert(bus.cache.softrock1.a == 2)
248 assert(bus.cache.softrock2.a == 2)
249
250 // Test the delete handlers with t.done(o), t.abort, 'done', 'abort'
251 log("Now let's delete")
252 bus('rock1').to_delete = (t) => {setTimeout(()=>{ t.abort() }, 0)}
253 bus('rock2').to_delete = ( ) => {return 'abort'}
254 bus('softrock1').to_delete = (t) => {setTimeout(()=>{ t.done() }, 0)}
255 bus('softrock2').to_delete = ( ) => {return 'done'}
256
257 log('Delete some rocks')
258 bus.delete('rock1')
259 bus.delete('rock2')
260 bus.delete('softrock1')
261 bus.delete('softrock2')
262
263 setTimeout(() => {
264 log("Test if the deletes worked")
265 assert(bus.cache.rock1)
266 assert(bus.cache.rock2)
267 assert(!bus.cache.softrock1)
268 assert(!bus.cache.softrock2)
269 done()
270 }, 10)
271
272 }, 10)
273 }, 20)
274})
275
276test(function url_translation (done) {
277 var tests = [
278 ['foo', 'foo'],
279 [['a', 'b'], ['a', 'b']],
280 [{key: 'a'}, {key: '/a'}],
281 [{key: 'a', f: '3'}, {key: '/a', f: '3'}],
282 [{a: {b: {key: 'a'}}}, {a: {b: {key: '/a'}}}],
283 [{a: {b: {_key: ['a', 'b']}}}, {a: {b: {_key: ['/a', '/b']}}}],
284 [{a: {b: {bombs_key: ['a', 1]}}}, {a: {b: {bombs_key: ['/a', 1]}}}],
285 [{a: {b: {_key: ['a', {_key: 'b'}]}}}, {a: {b: {_key: ['/a', {_key: '/b'}]}}}],
286 [{a: ['a', {_key: 'b'}]}, {a: ['a', {_key: '/b'}]}],
287 ]
288 for (var i=0; i<tests.length; i++) {
289 log('Testing', i, JSON.stringify(tests[i][0]))
290 var trans = bus.translate_keys(tests[i][0], function (k) {return '/' + k})
291 assert(bus.deep_equals(trans, tests[i][1]),
292 'Bad translation: ' + JSON.stringify(trans))
293 }
294 done()
295})
296
297test(function basics (done) {
298 bus('basic wait').to_fetch = function () {
299 setTimeout(function () {bus.save.fire({key:'basic wait', a:1})},
300 30)
301 }
302
303 var count = 0
304 bus(function () {
305 var v = bus.fetch('basic wait')
306 log('On round', count, 'we see', v)
307 if (count == 0)
308 assert(!v.a)
309 if (count == 1) {
310 assert(v.a)
311 bus.forget()
312 done()
313 }
314 count++
315 })
316})
317
318// Multi-handlers
319test(function multiple_handlers1 (done) {
320 var cuss = require('../statebus')()
321 cuss('foo').to_fetch = () => {log('do nothing 1')}
322 cuss('foo').to_fetch = () => {log('do nothing 2')}
323 cuss('foo').to_fetch = () => (log('doing something'),{b: 3})
324 cuss.fetch('foo', (o) => {
325 log('Multi-handle got', o)
326 cuss.forget()
327 setTimeout(()=>{done()})
328 })
329})
330
331test(function multiple_handlers2 (done) {
332 var cuss = require('../statebus')()
333 cuss('foo').to_save = (o) => {log('do nothing 1')}
334 cuss('foo').to_save = (o) => {log('do nothing 2')}
335 cuss('foo').to_save = (o) => {log('doin something'); cuss.save.fire(o)}
336 //cuss('foo').to_save = (o) => {log('doin abortion'); cuss.save.abort(o)}
337 cuss.save({key: 'foo', b: 55})
338 log('over and out')
339 setTimeout(()=>{done()})
340})
341
342// Callbacks are reactive
343test(function fetch_with_callback (done) {
344 var count = 0
345
346 function bbs() {
347 return bus.bindings('bar', 'on_save').map(
348 function (f){return bus.funk_name(f)})
349 }
350 function cb (o) {
351 count++
352 // log(bbs().length + ' bindings in cb before fetch')
353 var bar = bus.fetch('bar')
354 log('cb called', count, 'times', 'bar is', bar, 'foo is', o)
355 // log(bbs().length + ' bindings in cb after fetch')
356 }
357
358 // log(bbs().length+ ' bindings to start')
359
360 // Fetch a foo
361 bus.fetch('foo', cb) // Call 1
362
363 // Fire a foo
364 setTimeout(function () {
365 assert(count === 1, '1!=' + count)
366 // log(bbs().length + ' bindings after first fetch')
367
368 log('firing a new foo')
369 // log(bbs().length + ' bindings')
370 bus.save.fire({key: 'foo', count:count}) // Call 2
371 }, 30)
372
373 // Fire a bar, which the callback depends on
374 setTimeout(function () {
375 log('firing a new bar')
376 // log(bbs().length+ ' bindings')
377 assert(count === 2, '2!=' + count)
378 bus.honk = true
379 bus.save.fire({key: 'bar', count:count}) // Call 3
380 log('fired the new bar')
381 //log(bus.bindings('bar', 'on_save'))
382 }, 50)
383
384 // Done
385 setTimeout(function () {
386 assert(count === 2, '2!=' + count)
387 bus.forget('foo', cb)
388 //bus.forget('bar', cb)
389 done()
390 }, 100)
391})
392
393test(function fetch_once (done) {
394 var calls = 0
395 bus.fetch('fetch_once', function cb (o) {
396 calls++
397 log('Fetch_once called', calls)
398 assert(calls < 2, 'Fetch-once called twice')
399 bus.forget('fetch_once', cb)
400 })
401 bus.save.fire({key: 'fetch_once', _: 0})
402 setTimeout(()=> { bus.save.fire({key: 'fetch_once', _: 1}) }, 10)
403 setTimeout(()=> { bus.save.fire({key: 'fetch_once', _: 2}) }, 20)
404 setTimeout(()=> { done() }, 30 )
405})
406
407test(function once (done) {
408 bus('takeawhile').to_fetch = (t) => {
409 setTimeout(_=> t.return({_: 3}), 150)
410 }
411 bus.once(_=> {
412 var x = bus.fetch('takeawhile')
413 var y = bus.fetch('changing')
414 log('running the once func! loading is', bus.loading())
415 assert(!bus.cache.certified)
416 bus.save({key: 'whendone', certified: true})
417 })
418
419 delay(50, _=> assert(!bus.fetch('whendone').certified))
420 delay(10, _=> bus.save({key: 'changing', _: 1}))
421 delay(10, _=> bus.save({key: 'changing', _: 2}))
422 delay(150, _=> assert(bus.fetch('whendone').certified))
423 delay(10, _=> bus.save({key: 'changing', _: 3}))
424 delay(10, _=> bus.save({key: 'changing', _: 4}))
425 delay(30, _=> done())
426})
427
428// If there's an on_fetch handler, the callback doesn't return
429// until the handler fires a value
430test(function fetch_remote (done) {
431 var count = 0
432
433 // The moon responds in 30ms
434 bus('moon').to_fetch =
435 function (k) { setTimeout(function () {bus.save.fire({key:k})},30) }
436 function cb (o) {
437 count++
438 var moon = bus.fetch('hey over there')
439 log('cb called', count, 'times')
440 }
441
442 // Fetch a moon
443 bus.fetch('moon', cb) // Doesn't call back yet
444 assert(count === 0, '0!=' + count)
445
446 // There should be a moonshot by now
447 setTimeout(function () {
448 assert(count === 1, '1!=' + count)
449 bus.forget('moon', cb)
450 done()
451 }, 50)
452})
453
454// Multiple batched fires might trigger duplicate reactions
455test(function duplicate_fires (done) {
456 var calls = new Set()
457 var count = 0
458 var dupes = []
459 function cb (o) {
460 count++
461 if (calls.has(o.n)) dupes.push(o.n)
462 calls.add(o.n)
463 log('cb called', count, 'times with', calls)
464 }
465
466 // Fetch a foo
467 bus.fetch('foo', cb) // Call 1
468 assert(count === 1, '1!=' + count)
469
470 // Fire a foo
471 setTimeout(function () {
472 log('Firing a few new foos')
473 bus.save.fire({key: 'foo', n:0}) // Skipped
474 bus.save.fire({key: 'foo', n:1}) // Skipped
475 bus.save.fire({key: 'foo', n:2}) // Skipped
476 bus.save.fire({key: 'foo', n:3}) // Call 2
477 log("ok, now let's see what happens.")
478 }, 30)
479
480 // Done
481 setTimeout(function () {
482 assert(count === 2, '2!=' + count)
483 assert(dupes.length === 0, 'CB got duplicate calls', dupes)
484 log('Well, that went smoothly!')
485 bus.forget('foo', cb)
486 //bus.forget('bar', cb)
487 done()
488 }, 60)
489})
490
491// Identity fires shouldn't infinite loop
492test(function identity (done) {
493 var key = 'kooder'
494 var count = 0
495 function fire () { bus.save.fire({key: 'kooder', count: count}) }
496 bus(key).to_fetch = function () { setTimeout(fire, 10) }
497 function cb() {
498 count++
499 log('cb called', count, 'times')
500 bus.save.fire(bus.fetch('new'))
501 }
502 bus.fetch(key, cb)
503
504 // Done
505 setTimeout(function () {
506 // Calls:
507 // 1. Initial call
508 // 2. First return from pending fetch
509 assert(count === 1, 'cb called '+count+'!=1 times')
510 bus.forget(key, cb)
511 bus(key).to_fetch.delete(fire)
512 done()
513 }, 40)
514})
515
516
517// bus.forget() within a callback
518test(function forgetting (done) {
519 var key = 'kooder'
520 var count = 0
521 function fire () { log('firing!'); bus.save.fire({key: key, count: count}) }
522 bus(key).to_fetch = function () { setTimeout(fire, 10) }
523
524 function cb (o) {
525 count++
526 log('cb2 called', count, 'times', 'on', o)
527
528 if (count > 2) assert(false, 'cb2 too many calls')
529 if (count > 1) {
530 log('cb2 forgetting', key)
531 bus.forget(key, cb)
532 log('forgot.')
533 }
534 }
535
536 bus.fetch(key, cb)
537 setTimeout(fire, 70)
538 setTimeout(fire, 80)
539
540 // Done
541 setTimeout(function () {
542 assert(count === 2, "Count should be 2 but is", count)
543 bus(key).to_fetch.delete(fire)
544 done()
545 }, 100)
546})
547
548// Can we return an object that fetches another?
549test(function nested_fetch (done) {
550 function outer () { return {inner: bus.fetch('inner') } }
551 bus('outer').to_fetch = outer
552 log('fetching the outer wrapper')
553 var obj = bus.fetch('outer')
554 log('Ok, we fetched:', obj)
555 assert(obj.inner.key === 'inner')
556 bus.save({key: 'inner', c: 1})
557 assert(obj.inner.c === 1)
558
559 log('vvv Grey line follows (cause outer fetched inner) vvv')
560
561 // Done
562 setTimeout(function () {
563 bus('outer').to_fetch.delete(outer)
564 done()
565 }, 10)
566})
567
568// Russian dolls
569test(function russian_doll_nesting (done) {
570 var nothing = 3
571 function big () { return {middle: bus.fetch('middle') } }
572 function middle () { return {small: bus.fetch('small') } }
573 function small () { return {nothing: nothing} }
574 bus('big').to_fetch = big
575 bus('middle').to_fetch = middle
576 bus('small').to_fetch = small
577
578 log('fetching')
579 var obj = bus.fetch('big')
580 log('we got', obj)
581
582 setTimeout(function () {
583 bus.fetch('big', function (o) {
584 nothing = 5
585 log('About to update small')
586 bus.save.fire({key: 'small', something: nothing})
587 log('We did it.')
588 })}, 10)
589
590 setTimeout(function () {
591 bus.fetch('big', function ruskie (o) {
592 nothing = 50
593 var small = bus.fetch('small')
594 log()
595 log('Second try. Small starts as', small)
596 bus.save.fire({key: 'small', something: nothing})
597 log('Now it is', bus.fetch('small'))
598 })}, 15)
599
600
601 // Done
602 setTimeout(function () {
603 bus('big').to_fetch.delete(big)
604 bus('middle').to_fetch.delete(middle)
605 bus('small').to_fetch.delete(small)
606 done()
607 }, 50)
608})
609
610test(function some_handlers_suicide (done) {
611 // These handlers stop reacting after they successfully complete:
612 //
613 // .on_save
614 // .to_save
615 // .to_forget
616 // .to_delete
617 //
618 // Ok, that's everyting except for a .to_fetch handler, which
619 // runs until its key has been forget()ed.
620
621 // XXX todo
622 done()
623})
624
625test(function uncallback (done) {
626 try {
627 var chokidar = require('chokidar')
628 } catch (e) {
629 console.warn('#### Yo! You need to run "npm install chokidar"')
630 process.exit()
631 }
632
633 var watchers = {}
634 function read_file (filename, cb) {
635 fs.readFile(filename, function (err, result) {
636 if (err) throw err
637 else cb(null, result + '')
638 })
639 }
640
641 read_file = bus.uncallback(read_file, {
642 start_watching: (args, dirty, del) => {
643 var filename = args[0]
644 assert(!(filename in watchers), 'WTF... the file ' + filename + ' is already being watched?')
645 watchers[filename] = chokidar.watch(filename)
646 watchers[filename].on('change', () => { bus.dirty(this.key) })
647 },
648 stop_watching: (json) => {
649 filename = json[0]
650 log('unwatching', filename)
651 //watchers[filename].unwatch(filename)
652 watchers[filename].close()
653 delete watchers[filename]
654 }})
655
656 fs.writeFileSync('/tmp/blah', 'foo')
657
658 bus.honk = 3
659 var runs = 0
660 bus(() => {
661 runs++
662 var result = read_file('/tmp/blah')
663 log('read file as', JSON.stringify(result && result.substr(0,50)))
664 //bus.forget(); return;
665 if (bus.loading())
666 log('Still loading!')
667 else
668 if (result == '1' || result == '2' || result == '3') {
669 log('done. forgetting')
670 bus.forget()
671 }
672
673 switch(runs) {
674 case 1:
675 console.assert(result == undefined); break
676 case 2:
677 console.assert(result == 'foo'); break
678 case 3:
679 case 4:
680 console.assert(result == '1' || result == '2' || result == '3'); break;
681 }
682 })
683
684 delay(10, () => {log('* MODIFY 1'); fs.writeFileSync('/tmp/blah', '1')})
685 delay(200, () => {log('* MODIFY 2'); fs.writeFileSync('/tmp/blah', '2')})
686 delay(200, () => {log('* MODIFY 3'); fs.writeFileSync('/tmp/blah', '3')})
687
688 delay(200, () => {console.assert(runs < 4, 'There were', runs, 'runs'); done()})
689})
690
691test(function readfile (done) {
692 var b = require('../statebus')(),
693 count = 0
694
695 fs.writeFileSync('/tmp/blah', '1')
696 b(() => {
697 var r = b.read_file('/tmp/blah')
698 log('Got', r, 'at count', count)
699 switch(count++) {
700 case 0:
701 console.assert(r === undefined); break
702 case 1:
703 console.assert(r === '1'); break
704 case 2:
705 console.assert(r === '2'); b.forget(); break
706 case 3:
707 console.assert(false); break
708 }
709 })
710
711 delay(300, () => {log('* MODIFY 2'); fs.writeFileSync('/tmp/blah', '2')})
712 delay(300, () => {log('* MODIFY 3'); fs.writeFileSync('/tmp/blah', '3')})
713 delay(200, () => {done()})
714})
715
716test(function proxies (done) {
717 var bus = require('../statebus')()
718 db = bus.sb
719 db.foo.a = 3
720 assert(db.foo.a == 3)
721 db.foo.a = [1,3,5, 'hello']
722 db.foo.a.push({key: 'bar', 4:4})
723
724 // Todo: make sure that push() and splice() etc. trigger updates to
725 // state properly. They should trigger save() when they modify the
726 // array, and not when they don't.
727
728 log('Now foo is', db.foo)
729 log('And bar is', db.bar)
730
731 db.f = 3
732 assert(bus.validate(bus.cache['f'], {key: 'f', _: 3}),
733 bus.cache['f'], 'should be', {key: 'f', _: 3})
734 assert(db.f === 3)
735
736 db.g = [1,2,4,5]
737 assert(bus.cache['g']._.length = 4)
738
739 done()
740})
741
742test(function proxies2 (done) {
743 var bus = require('../statebus')()
744 var state = bus.state
745
746 assert(state.array === undefined)
747 assert(state.bar === undefined)
748
749 state.array = []
750 log('array:', state.array)
751 assert(state.array.length === 0)
752 state.array[0] = 1
753 log('array:', state.array)
754 assert(state.array.length === 1)
755 state.bar = {}
756 log('bar:', state.bar)
757 state.bar = {a: 1}
758 log('bar:', state.bar)
759 assert(state.bar.a === 1)
760 state.bar.a = state.array
761 log('bar:', state.bar)
762 state.array[1] = 2
763 log('array:', state.array)
764 log('bar:', state.bar)
765 assert(state.bar.a[1] === 2,
766 "Array ref didn't link.\n\t"
767 + JSON.stringify(bus.cache.bar) + '\n\t'
768 + JSON.stringify(bus.cache.array))
769
770 state.undefining = undefined
771 assert(state.undefining === undefined)
772 state.undefining = {a: undefined}
773 log('state.undefining =', state.undefining)
774 // assert(state.undefining.a === undefined)
775 // TODO: fix https://github.com/invisible-college/statebus/issues/34
776 state.undefining = {}
777 assert(!('a' in state.undefining))
778 state.undefining.a = undefined
779 assert('a' in state.undefining)
780 assert(state.undefining.a === undefined)
781
782 state.b = {a: undefined}
783 assert('a' in state.b)
784 assert(state.b.a === undefined)
785 delete state.b.a
786 // TODO: https://github.com/invisible-college/statebus/issues/34
787 assert(!('a' in state.b))
788 assert(state.b.a === undefined)
789
790 return done()
791
792 /*
793 Things I want to test:
794
795 - Setting nested items
796 - Escaping their fields
797 - Calling fetch on them
798 - Converting state[..] to keyed objects internally
799 - has() potentially doing a fetch, or loading()
800 - set() returning a proxy object
801 - console output
802 - node AND chrome
803 - having nice colors and distinctions and shit
804 */
805
806 state.foo = 3
807 // This should set into _
808 console.assert(bus.validate(bus.cache.foo,
809 {key: 'foo', _: 3}))
810
811
812 state.foo = {a: 5}
813 // This should set directly on it
814 console.assert(bus.validate(bus.cache.foo,
815 {key: 'foo', a: 5}))
816 state.foo.b = 6
817 console.assert(bus.validate(bus.cache.foo,
818 {key: 'foo', b: 6}))
819
820 state.bar = [3]
821 state.foo = {a: 3, bar: state.bar}
822
823 bus(() => {
824 state.foo // foo triggers re-render
825 state.foo.bar // bar triggers re-render
826 state.foo.a // triggers re-render too
827 })
828
829 // Getting a linked item should do a fetch
830 bus(() => {
831 state.bar
832 })
833
834 // Getting a normal property should do a fetch
835})
836
837test(function only_one (done) {
838 bus('only_one/*').to_fetch = function (k) {
839 var id = k[k.length-1]
840 return {selected: bus.fetch('selector').choice == id}
841 }
842
843 assert(!bus.fetch('only_one/1').selected)
844 assert(!bus.fetch('only_one/2').selected)
845 assert(!bus.fetch('only_one/3').selected)
846
847 bus.save({key: 'selector', choice: 1})
848
849 setTimeout(function () {
850 assert( bus.fetch('only_one/1').selected)
851 assert(!bus.fetch('only_one/2').selected)
852 assert(!bus.fetch('only_one/3').selected)
853
854 bus.save({key: 'selector', choice: 2})
855 }, 10)
856
857 setTimeout(function () {
858 assert(!bus.fetch('only_one/1').selected)
859 assert( bus.fetch('only_one/2').selected)
860 assert(!bus.fetch('only_one/3').selected)
861
862 bus.save({key: 'selector', choice: 3})
863 }, 20)
864
865 setTimeout(function () {
866 assert(!bus.fetch('only_one/1').selected)
867 assert(!bus.fetch('only_one/2').selected)
868 assert( bus.fetch('only_one/3').selected)
869 done()
870 }, 30)
871})
872
873test(function save_can_trigger_tofetch (done) {
874 // bus.honk = true
875 bus('save_trigger_tofetch').to_fetch =
876 function (k, old) {
877 old.yes = Math.random()
878 return old
879 }
880
881 var obj
882 var triggered = 0
883 bus(() => {
884 log('Aight! Fetching save_trigger_fetch')
885 obj = bus.fetch('save_trigger_tofetch')
886 if (bus.loading()) return
887 triggered++
888 log('Triggered', triggered, 'times', obj)
889
890 // XXX todo: because of a bug in how to_fetch is handled, this triggers 4 times instead of 3
891 assert(triggered <= 4)
892 log('GGGGGGGGGGGG')
893 })
894
895 delay(30, () => { log('savin 1!'); obj.a = 1; bus.save(obj) })
896 delay(30, () => { log('savin 2!'); obj.a = 2; bus.save(obj) })
897 delay(30, done)
898})
899
900test(function rollback_savefire (done) {
901 var count = 0
902 var error = false
903 var phase = 0
904
905 function wait () { setTimeout(function () {
906 assert(phase++ === 1)
907 log('Firing wait')
908 bus.save.fire({key: 'wait', count: count})
909 }, 50) }
910 bus('wait').to_fetch = wait
911
912 // Initialize
913 bus.save.fire({key: 'undo me', state: 'start'})
914
915 // Now start the reactive function
916 bus(function () {
917 log('Reaction', ++count, 'starting with state',
918 bus.fetch('undo me').state, 'and loading =', bus.loading())
919
920 // Fetch something that we have to wait for
921 var wait = bus.fetch('wait')
922
923 // Save some middling state
924 bus.save.fire({key: 'undo me', state: 'progressing'})
925
926 if (count === 1 && !bus.loading()) {
927 log('### Error! We should be loading!')
928 error = true
929 }
930 log('Done with this reaction')
931 })
932
933 assert(!error)
934
935 var state = bus.cache['undo me'].state
936 log('After first reaction, the state is', state)
937 assert(state === 'start', 'The state did not roll back.')
938
939 // The state should still be "start" until 50ms
940 setTimeout(function () {
941 assert(bus.cache['undo me'].state === 'start')
942 assert(phase++ === 0, phase)
943 },
944 40)
945
946 // The state should finally progress after 50ms
947 setTimeout(function () {
948 log('state is', bus.cache['undo me'].state)
949 assert(bus.cache['undo me'].state === 'progressing')
950 assert(phase++ === 2)
951 },
952 60)
953
954 setTimeout(function () {
955 bus('wait').to_fetch.delete(wait)
956 assert(phase === 3)
957 done()
958 }, 80)
959})
960
961test(function rollback_del (done) {
962 bus('wait forever').to_fetch = function () {} // shooting blanks
963 bus.save.fire({key: 'kill me', alive: true})
964
965 // First do a del that will roll back
966 bus(function () {
967 log('Doing a rollback on', bus.cache['kill me'])
968 bus.fetch('wait forever') // Never finishes loading
969 bus.del('kill me') // Will roll back
970 })
971 assert(bus.cache['kill me'].alive === true)
972
973 // Now a del that goes through
974 bus(function () {
975 log('Doing a real delete on', bus.cache['kill me'])
976 bus.del('kill me') // Will not roll back
977 })
978 assert(!('kill me' in bus.cache))
979 log('Now kill me is', bus.cache['kill me'])
980 done()
981})
982
983test(function rollback_save (done) {
984 var saves = []
985 var all_done = false
986 bus('candy').to_save = function (o) {saves.push(o); bus.save.fire(o)}
987 bus.save.fire({key: 'candy', flavor: 'lemon'})
988
989 log('Trying some rollbacks starting with', bus.cache['candy'])
990
991 // First do a save that will roll back
992 bus(function () { if (all_done) return;
993 log('Doing a rollback on bananafied candy')
994 bus.fetch('wait forever') // Never finishes loading
995 bus.save({key:'candy', flavor: 'banana'}) // Will roll back
996 log('...and the candy is', bus.cache['candy'])
997 //forget('candy')
998 })
999 assert(bus.cache['candy'].flavor === 'lemon')
1000 assert(saves.length === 0)
1001
1002 // Try rolling back another style of save
1003 bus(function () { if (all_done) return
1004 log("Now we'll First we licoricize the", bus.cache['candy'])
1005 bus.fetch('wait forever') // Never finishes loading
1006 var candy = bus.fetch('candy')
1007 candy.flavor = 'licorice'
1008 log('...the candy has become', bus.cache['candy'])
1009 bus.save(candy) // Will roll back
1010 log('...and now it\'s rolled back to', bus.cache['candy'])
1011 bus.forget('candy')
1012 })
1013 assert(bus.cache['candy'].flavor === 'lemon')
1014 assert(saves.length === 0)
1015
1016 // Now a save that goes through
1017 bus(function () {
1018 log('Doing a real save on', bus.cache['candy'])
1019 bus.save({key:'candy', flavor: 'orangina'}) // Will go through
1020 })
1021 assert(bus.cache['candy'].flavor = 'orangina')
1022 assert(saves.length === 1, 'Saves.length 1 != '+saves.length)
1023
1024 log('Now candy is', bus.cache['candy'])
1025 all_done = true
1026 done()
1027})
1028
1029test(function rollback_abort (done) {
1030 var bus = require('../statebus')()
1031 bus('foo').to_save = (o, t) => {t.abort()}
1032 bus(()=> {
1033 var o = bus.fetch('foo')
1034 o.bar = 3
1035 bus.save(o)
1036 })
1037 setTimeout(() => {
1038 assert(!bus.cache.foo.bar)
1039 done()
1040 }, 40)
1041})
1042
1043test(function loading_quirk (done) {
1044 // Make sure a function that called loading() gets re-run even
1045 // if the return from a fetch didn't actually change state
1046
1047 // First define a delayed save.fire
1048 bus('wait a sec').to_fetch = function (k) {
1049 setTimeout(function () { bus.save.fire({key: k}) }, 50)
1050 }
1051
1052 // Now run the test
1053 var loaded = false
1054 var num_calls = 0
1055 bus(function () {
1056 num_calls++
1057 log('called', num_calls, 'times')
1058 bus.fetch('wait a sec')
1059 loaded = !bus.loading()
1060 })
1061
1062 // Finish
1063 setTimeout(function () {
1064 assert(loaded, 'We never got loaded.')
1065 assert(num_calls == 2,
1066 'We got called '+num_calls+'!=2 times')
1067 done()
1068 }, 100)
1069})
1070
1071test(function requires (done) {
1072 try {
1073 require.resolve('sockjs') // Will throw error if not found
1074 require.resolve('websocket')
1075 } catch (e) {
1076 console.warn('#### Yo! You need to run "npm install sockjs websocket"')
1077 process.exit()
1078 }
1079 log('Ok good, we have the goods.')
1080 done()
1081})
1082
1083test(function default_route (done) {
1084 var b1 = require('../statebus')()
1085 var b2 = require('../statebus')()
1086 b1.shadows(b2)
1087 b2.save({key: 'foo', bar: 3})
1088 console.assert(b1.fetch('foo').bar)
1089 log(b1.fetch('foo'))
1090 log(b2.fetch('foo'))
1091 b1.delete('foo')
1092 log(b1.fetch('foo'))
1093 log(b2.fetch('foo'))
1094 console.assert(!b1.fetch('foo').bar)
1095 console.assert(!b2.fetch('foo').bar)
1096 done()
1097})
1098
1099test(function setup_server (done) {
1100 s = require('../statebus').serve({port: 3948, file_store: false})
1101 s.label = 's'
1102 log('Saving /far on server')
1103 s.save({key: 'far', away:'is this'})
1104
1105 c = require('../statebus')()
1106 c.label = 'c'
1107 c.ws_client('/*', 'statei://localhost:3948')
1108
1109 // s.honk = true
1110 // c.honk = true
1111
1112 setTimeout(function () {
1113 log('Fetching /far on client')
1114 var count = 0
1115 c.fetch('/far', function (o) {
1116 log('cb !!!!!!!!! --- ^_^')
1117 c.fetch('/far')
1118 if (o.away === 'is this') {
1119 log('We got '+o.key+' from the server!')
1120
1121 c.forget()
1122 console.assert(count++ === 0, "Forget didn't work.")
1123
1124 setTimeout(function () {done()})
1125 }
1126 })
1127 }, 100)
1128
1129 var matches = new Set()
1130 for (var k in s.busses) {
1131 log("Checking unique bus id", k, 'with name', s.busses[k].toString())
1132 console.assert(!matches.has(k), 'duplicate bus id', k)
1133 matches.add(k)
1134 }
1135})
1136
1137test(function login (done) {
1138 s.save({key: 'users',
1139 all: [ { key: 'user/1',
1140 name: 'mike',
1141 email: 'toomim@gmail.com',
1142 admin: true,
1143 pass: '$2a$10$Ti7BgAZS8sB0Z62o2NKsIuCdmU3q9xP7jexVccTcG19Y8qpBpl/1y' }
1144
1145 ,{ key: 'user/2',
1146 name: 'j',
1147 email: 'jtoomim@gmail.com',
1148 admin: true,
1149 pass: '$2a$10$Ti7BgAZS8sB0Z62o2NKsIuCdmU3q9xP7jexVccTcG19Y8qpBpl/1y' }
1150
1151 ,{ key: 'user/3',
1152 name: 'boo',
1153 email: 'boo@gmail.com',
1154 admin: false,
1155 pass: '$2a$10$4UTjzf5OOGdkrCEsT.hO/.csKqf7u8mZ23ZT6stamBAWNV7u5WJuu' } ] })
1156
1157 c(function () {
1158 var u = c.fetch('/current_user')
1159 if (u.logged_in) {
1160 log('Yay! We are logged in as', u.user.name)
1161 forget()
1162 setTimeout(function () {done()})
1163 } else
1164 log("Ok... we aren't logged in yet. We be patient.")
1165 })
1166 // c.honk = user0.honk = true
1167 var u = c.fetch('/current_user')
1168 u.login_as = {name: 'mike', pass: 'yeah'}
1169 log('Logging in')
1170 c.save(u)
1171})
1172
1173test(function wrong_password (done) {
1174 var u = c.fetch('/current_user')
1175 assert(u.logged_in && u.user.name == 'mike')
1176 u.login_as = {name: 'j', pass: 'nah'}
1177 c.save(u)
1178 setTimeout(function () {
1179 assert(!u.login_as, 'Aborted login needs to abort')
1180 log('Good, the login failed.')
1181 done()
1182 }, 300)
1183})
1184
1185test(function create_account (done) {
1186 assert(c.fetch('/current_user').logged_in)
1187 var count = 0
1188 c(function () {
1189 count++
1190 var u = c.fetch('/current_user')
1191
1192 log('Phase', count, '(logged '+(u.logged_in?'in)':'out)'))
1193
1194 switch (count) {
1195 case 1:
1196 log('In 1 - Logging out')
1197 assert(u.logged_in, '1 not logged in')
1198 u.logout = true; c.save(u)
1199 break
1200 case 2: break
1201 case 3:
1202 // These are triggering a weird race condition bug, where the
1203 // server sends a duplicate {user: {key: 'user/bob', name:
1204 // 'bob', email: 'b@o.b'}, logged_in: true, key:
1205 // 'current_user'} event, triggering a re-render at 7 before
1206 // the logout has transpired.
1207 // c.honk = true
1208 // s.honk = true
1209 log('In 3 - Creating bob, logging in as bob')
1210 assert(!u.logged_in, '3 logged in')
1211 u.create_account = {name: 'bob', email: 'b@o.b', pass: 'boob'}
1212 c.save(u)
1213
1214 // Note: I hope this done line isn't necessary in the future!
1215 delete u.create_account
1216
1217 u.login_as = {name: 'bob', pass: 'boob'}
1218 c.save(u)
1219 break
1220 case 4: break
1221 case 5:
1222 log('In 5 - Logging out')
1223 assert(u.logged_in)
1224 assert(u.user.name === 'bob'
1225 && u.user.email === 'b@o.b'
1226 && u.user.pass === undefined
1227 && u.user.key.match(/\/user\/.*/),
1228 'Bad user', u)
1229 // Now let's log out
1230 log('Almost done 5')
1231 u.logout = true; c.save(u)
1232 log('Done 5')
1233 break
1234 case 6: break
1235 case 7:
1236 log('In 7 - Logging back in as boob')
1237 assert(!u.logged_in, '7. still logged in, as', u)
1238 u.login_as = {name: 'bob', pass:'boob'}
1239 c.save(u)
1240 break
1241 case 8: break
1242 case 9:
1243 log('In 9 - Forget and finish.')
1244 assert(u.logged_in, '9 not logged in')
1245 forget()
1246 setTimeout(function () {done()})
1247 break
1248 default:
1249 assert(false)
1250 break
1251 }
1252 })
1253})
1254
1255function connections_helper (done, port, options) {
1256 // Setup a server
1257 var s = require('../statebus').serve({port: port,
1258 file_store: false,
1259 connections: options})
1260 s.label = 's'
1261
1262 // Connect two clients
1263 var c1 = require('../statebus')()
1264 c1.label = 'c1'
1265 c1.ws_client('/*', 'statei://localhost:' + port)
1266
1267 var c2 = require('../statebus')()
1268 c2.label = 'c2'
1269 c2.ws_client('/*', 'statei://localhost:' + port)
1270
1271 // Load the basic connections
1272 c1.c = c1.fetch('/connection')
1273 c2.c = c2.fetch('/connection')
1274 c1.all = c1.fetch('/connections')
1275
1276 delay(50, _=> {
1277 // Test
1278 log('c1.c:', c1.c)
1279 log('c2.c:', c2.c)
1280 log('all:', c1.all)
1281 assert(c1.c.id)
1282 assert(c2.c.id)
1283
1284 // Load the connections inside
1285 c1.c1 = c1.fetch('/connection/' + c1.c.id)
1286 c1.c2 = c1.fetch('/connection/' + c2.c.id)
1287 c2.c1 = c2.fetch('/connection/' + c1.c.id)
1288 c2.c2 = c2.fetch('/connection/' + c2.c.id)
1289 })
1290
1291 delay(50, _=> {
1292 // Test
1293 assert(c1.validate(c1.c1, {key: 'string', client: 'string', id: 'string'}))
1294 log('c1.c1', JSON.stringify(c1.c1))
1295 log('c2.c2', JSON.stringify(c2.c2))
1296 assert(c1.c1.id === c1.c.id)
1297 assert(c1.c2.id === c2.c.id)
1298 assert(c2.c1.id === c1.c.id)
1299 assert(c2.c2.id === c2.c.id)
1300
1301 // Modify a connection
1302 c1.c.foo = 'bar'; c1.save(c1.c)
1303 c2.c2.fuzz = 'buzz'; c2.save(c2.c2)
1304 })
1305
1306 delay(50, _=> {
1307 // Test
1308 assert(c1.c.foo === 'bar')
1309 assert(c1.c1.foo === 'bar')
1310 assert(c2.c1.foo === 'bar')
1311
1312 assert(c2.c.fuzz === 'buzz')
1313 assert(c2.c2.fuzz === 'buzz')
1314 assert(c1.c2.fuzz === 'buzz')
1315
1316 // Modify someone else's connection
1317 c1.c2.fuzz = 'fart'; c1.save(c1.c2)
1318 c2.c1.free = 'willy'; c2.save(c2.c1)
1319 })
1320
1321 delay(50, _=> {
1322 // Test
1323 if (options.edit_others) {
1324 assert(c1.c2.fuzz === 'fart')
1325 assert(c2.c2.fuzz === 'fart')
1326 assert(c1.c1.free === 'willy')
1327 assert(c2.c1.free === 'willy')
1328 } else {
1329 assert(c1.c2.fuzz !== 'fart')
1330 assert(c2.c2.fuzz !== 'fart')
1331 assert(c1.c1.free !== 'willy')
1332 assert(c2.c1.free !== 'willy')
1333 }
1334 })
1335
1336 // Todo: test the inclusion of users
1337
1338 delay(50, _=> done())
1339}
1340
1341test(function connections_1 (done) {
1342 connections_helper(done, 3951, {include_users: true, edit_others: true})
1343})
1344
1345test(function connections_2 (done) {
1346 connections_helper(done, 3952, {include_users: false, edit_others: false})
1347})
1348
1349test(function flashbacks (done) {
1350 // We have an echo canceler. If you save state, it shouldn't send the
1351 // same state back to you, but it should send it to everyone else. But if
1352 // the state is changed in a to_save handler, it *should* send you
1353 // changes.
1354
1355 var port = 3873
1356
1357 // Setup a server
1358 var s = require('../statebus').serve({port: port, file_store: false})
1359 s.label = 's'
1360
1361 s('x').to_save = (o, t) => {
1362 log('Saving x with', o)
1363 o.x++ // Change the value of o.x a little
1364 t.done(o)
1365 }
1366
1367 // Connect two clients
1368 var c1 = require('../statebus')()
1369 c1.label = 'c1'
1370 c1.ws_client('*', 'statei://localhost:' + port)
1371
1372 var c2 = require('../statebus')()
1373 c2.label = 'c2'
1374 c2.ws_client('*', 'statei://localhost:' + port)
1375
1376 c1.x = c1.fetch('x')
1377 c2.x = c2.fetch('x')
1378
1379 // Change stuff
1380 delay(50, _=> {
1381 c1.x.x = 1
1382 c1.save(c1.x)
1383 })
1384
1385 // Test stuff
1386 delay(50, _=> {
1387 assert(c2.x.x === 2, 'c2 didn\'t get it')
1388 assert(c1.x.x === 2, 'c1 didn\'t get it')
1389 log('complete!', c1.x, c2.x)
1390 done()
1391 })
1392})
1393
1394test(function email_read_permissions (done) {
1395 var phase = -1
1396 var u, user1, user2, user3
1397 var tmp1
1398
1399 var states = function () { return [
1400 // Phase 0
1401 [true,
1402 function () {
1403 log('Logging in as mike')
1404 //s.honk=true
1405 u.login_as = {name: 'mike', pass: 'yeah'}; c.save(u)
1406 }],
1407
1408 // Phase 1
1409 // Logged in as mike
1410 [(u.logged_in
1411 && u.user.name === 'mike'
1412 && u.user.key === '/user/1'
1413
1414 // We can see our email
1415 && u.user.email
1416 && user1.email
1417
1418 // We can't see other emails
1419 && !user2.email
1420 && !user3.email),
1421
1422 function () {
1423 !tmp1 && log('Logging in as j')
1424 setTimeout(function () {
1425 if (tmp1) return
1426 tmp1 = true
1427 log('Firing the actual j login')
1428 //s.userbus.honk = true
1429 u.login_as = {name: 'j', pass: 'yeah'}; c.save(u)
1430 log('We just logged in as j. now user is:', u.user.name)
1431 }, 10)
1432 }],
1433
1434 // Phase 2
1435 // Logged in as j
1436 [(u.logged_in
1437 && u.user.name === 'j'
1438 && u.user.key === '/user/2'
1439
1440 // We can see j's email
1441 && u.user.email
1442 && user2.email
1443
1444 // We can't see other emails
1445 && !user1.email
1446 && !user3.email),
1447
1448 // That's all, Doc
1449 function () { log("That's all, Doc."); setTimeout(function () {done()}) }]
1450 ]}
1451
1452 c('/current_user').on_save = function (o) {
1453 //if (o.user && o.user.name === 'j') {
1454 // log(s.userbus.deps('/current_user'))
1455 // log(s.userbus.deps('/user/2'))
1456 //}
1457 }
1458 c('/user/*').on_save = function (o) {
1459 //log('-> Got new', o.key, o.email ? 'with email' : '')
1460 }
1461 c('/current_user').on_save = function (o) {
1462 //log('-> Got new /current_user')
1463 }
1464 c(function loop () {
1465 u = c.fetch('/current_user')
1466 user1 = c.fetch('/user/1')
1467 user2 = c.fetch('/user/2')
1468 user3 = c.fetch('/user/3')
1469 var st = states()
1470
1471 if (phase===1)
1472 log('\n\tcurr u:\t',u.user, '\n\t1:\t', user1,'\n\t2:\t', user2,'\n\t3:\t', user3)
1473
1474 if (phase >= st.length) {
1475 loop.forget()
1476 return
1477 }
1478
1479 if (phase + 1 < st.length && st[phase + 1][0]) {
1480 phase++
1481 log()
1482 log('## Shifting to phase', phase)
1483 }
1484
1485 //log('Phase', phase, 'logged_in:', u.logged_in && u.user.name)
1486 st[phase][1]()
1487 })
1488})
1489
1490test(function closet_space (done) {
1491 var s = require('../statebus').serve({port: 3949,
1492 file_store: false,
1493 client: (c)=>{c.honk=true}})
1494 s.label = 's'
1495
1496 var c = require('../statebus')()
1497 c.label = 'c'
1498 c.ws_client('/*', 'statei://localhost:3949')
1499
1500 // Make stuff as user A
1501 var cu = c.fetch('/current_user')
1502 c.save({key: '/current_user', create_account: {name: 'a', pass: 'a'}})
1503 c.save({key: '/current_user', login_as: {name: 'a', pass: 'a'}})
1504 var a_closet = c.fetch('/user/a/foo')
1505 var a_private = c.fetch('/user/a/private/foo')
1506
1507 delay(400, () => {
1508 c.save({key: '/user/a/foo', _: 3})
1509 c.save({key: '/user/a/private/foo', _: 4})
1510 })
1511
1512 // User A can see it
1513 delay(450, ()=> {
1514 log('1. Now curr user is', cu)
1515 console.assert(cu.logged_in == true, 'not logged in')
1516 log('closet is', a_closet)
1517 console.assert(a_closet._ === 3, 'closet not right')
1518 console.assert(a_private._ === 4, 'private not right')
1519
1520 // Set up User B
1521 c.save({key: '/current_user', create_account: {name: 'b', pass: 'b'}})
1522 c.save({key: '/current_user', login_as: {name: 'b', pass: 'b'}})
1523 })
1524
1525 // User B can't see private stuff
1526 delay(450, ()=> {
1527 log('3. Now curr user is', cu)
1528 log('closet is', {closet:a_closet, private:a_private})
1529 console.assert(a_closet._ === 3, 'A\'s closet is not visible')
1530 console.assert(a_private._ !== 4, 'damn can still see private')
1531
1532 // User B tries editing the first closet
1533 a_closet._ = 5; c.save(a_closet)
1534 })
1535
1536 // User B could not edit that
1537 delay(350, ()=> {
1538 console.assert(a_closet._ === 3, 'damn he could edit it')
1539 })
1540
1541 delay(50, ()=>done())
1542})
1543
1544test(function ambiguous_ordering (done) {
1545 // Not fully implemented yet
1546
1547 /*
1548 Let's save within an on-save handler. Which will trigger
1549 first... the dirty(), or the new save()? Hm, do we really
1550 care?
1551 */
1552
1553 var user = 3
1554 bus('user').to_fetch =
1555 function (k) {
1556 return {user: user}
1557 }
1558
1559 bus('user').to_save =
1560 function (o) {
1561 if (o.funny)
1562 bus.save({key: 'user', user: 'funny'})
1563
1564 user = o.user
1565 bus.dirty('user')
1566 }
1567
1568 log("Eh, nevermind.")
1569 done()
1570})
1571
1572run_tests()
\No newline at end of file