ZeroOmega/omega-target/test/options_sync.coffee

208 lines
6.7 KiB
CoffeeScript
Raw Normal View History

chai = require 'chai'
should = chai.should()
sinon = require 'sinon'
chai.use require('sinon-chai')
describe 'OptionsSync', ->
OptionsSync = require '../src/options_sync'
Storage = require '../src/storage'
Log = require '../src/log'
Promise = require 'bluebird'
before ->
# Silence storage and sync logging.
sinon.stub(Log, 'log')
after ->
Log.log.restore()
# coffeelint: disable=missing_fat_arrows
hookPostBasic = (func, hook) -> ->
result = func.apply(this, arguments)
hook.apply(this, arguments)
return result
# coffeelint: enable=missing_fat_arrows
hookPost = (args...) ->
if args.length == 2
[func, hook] = args
hostPostBasic(func, hook)
else
[obj, method, hook] = args
obj[method] = hookPostBasic(obj[method], hook)
describe '#merge', ->
sync = new OptionsSync()
it 'should choose the one with newer revision', ->
newVal = {revision: '2'}
oldVal = {revision: '1'}
sync.merge('example', newVal, oldVal).should.equal(newVal)
it 'should use oldVal when sync is disabled in newVal', ->
newVal = {revision: '2', is: 'newVal', syncOptions: 'disabled'}
oldVal = {revision: '1', is: 'oldVal'}
sync.merge('example', newVal, oldVal).should.equal(oldVal)
it 'should use oldVal when sync is disabled in oldVal', ->
newVal = {revision: '2', is: 'newVal'}
oldVal = {revision: '1', is: 'oldVal', syncOptions: 'disabled'}
sync.merge('example', newVal, oldVal).should.equal(oldVal)
it 'should favor oldVal when revisions are equal', ->
newVal = {revision: '1', is: 'newVal'}
oldVal = {revision: '1', is: 'oldVal'}
sync.merge('example', newVal, oldVal).should.equal(oldVal)
it 'should favor oldVal when newVal deeply equals oldVal', ->
newVal = {they: 'are', the: 'same'}
oldVal = {they: 'are', the: 'same'}
sync.merge('example', newVal, oldVal).should.equal(oldVal)
it 'should choose newVal when newVal is different', ->
newVal = {they: 'are', not: 'equal'}
oldVal = {they: 'are', not: 'identical'}
sync.merge('example', newVal, oldVal).should.equal(newVal)
describe '#requestPush', ->
unlimited = new OptionsSync.TokenBucket()
it 'should store pendingChanges', ->
sync = new OptionsSync()
sync.enabled = false
sync.requestPush({a: 1})
sync.pendingChanges().should.eql({a: 1})
it 'should schedule storage write', (done) ->
check = ->
return if storage.set.callCount == 0 or storage.remove.callCount == 0
storage.set.should.have.been.calledOnce.and.calledWith({b: 1})
storage.remove.should.have.been.calledOnce.and.calledWith(['a'])
done()
storage = new Storage()
storage.set({a: 1})
hookPost storage, 'set', check
hookPost storage, 'remove', check
sinon.spy(storage, 'set')
sinon.spy(storage, 'remove')
sync = new OptionsSync(storage, unlimited)
sync.debounce = 0
sync.requestPush({a: undefined, b: 1})
it 'should combine multiple write operations', (done) ->
check = ->
return if storage.set.callCount == 0 or storage.remove.callCount == 0
storage.set.should.have.been.calledOnce.and.calledWith({c: 1, d: 1})
storage.remove.should.have.been.calledOnce.and.calledWith(['a', 'b'])
done()
storage = new Storage()
storage.set({a: 1, b: 1})
hookPost storage, 'set', check
hookPost storage, 'remove', check
sinon.spy(storage, 'set')
sinon.spy(storage, 'remove')
sync = new OptionsSync(storage, unlimited)
sync.debounce = 0
sync.requestPush({a: undefined})
sync.requestPush({b: 2})
sync.requestPush({b: undefined})
sync.requestPush({c: 1})
sync.requestPush({d: 1})
sync.requestPush({e: 1})
sync.requestPush({e: undefined})
it 'should disable syncing for the profiles if quota is exceeded', (done) ->
options = {'+a': {is: 'a', oversized: true}, b: {is: 'b'}}
storage = new Storage()
storage.set = (changes) ->
for key, value of changes
if value.oversized
err = new Storage.QuotaExceededError()
err.perItem = true
return Promise.reject(err)
storage.set.should.have.been.calledTwice
storage.set.should.have.been.calledWith(options)
storage.set.should.have.been.calledWith({b: {is: 'b'}})
options['+a'].syncOptions.should.equal('disabled')
options['+a'].syncError.reason.should.equal('quotaPerItem')
done()
Promise.resolve()
sinon.spy(storage, 'set')
sync = new OptionsSync(storage, unlimited)
sync.debounce = 0
sync.requestPush(options)
describe '#copyTo', ->
it 'should fetch all items from remote storage', (done) ->
remote = new Storage()
remote.set({a: 1, b: 2, c: 3})
storage = new Storage()
hookPost storage, 'set', ->
storage.set.should.have.been.calledOnce.and.calledWith(
{a: 1, b: 2, c: 3}
)
done()
sinon.spy(storage, 'set')
sync = new OptionsSync(remote)
sync.copyTo(storage)
it 'should merge with local as base', (done) ->
check = ->
return if storage.set.callCount == 0 or storage.remove.callCount == 0
storage.set.should.have.been.calledOnce.and.calledWith({b: 2, c: 3})
storage.remove.should.have.been.calledOnce.and.calledWith(['d'])
done()
remote = new Storage()
remote.set({a: 1, b: 2, c: 3, d: undefined})
storage = new Storage()
storage.set({a: 1, b: 0, d: 4})
hookPost storage, 'set', check
hookPost storage, 'remove', check
sinon.spy(storage, 'set')
sinon.spy(storage, 'remove')
sync = new OptionsSync(remote)
sync.copyTo(storage)
describe '#watchAndPull', ->
it 'should pull changes into local when remote changes', (done) ->
check = ->
return if storage.set.callCount == 0 or storage.remove.callCount == 0
remote.watch.should.have.been.calledOnce
storage.set.should.have.been.calledOnce.and.calledWith({b: 2, c: 3})
storage.remove.should.have.been.calledOnce.and.calledWith(['d'])
done()
remote = new Storage()
hookPost remote, 'watch', (_, callback) ->
setTimeout (->
callback({a: 1})
callback({b: 2})
callback({c: 3})
callback({d: undefined})
), 10
sinon.spy(remote, 'watch')
storage = new Storage()
storage.set({a: 1, b: 0, d: 4})
hookPost storage, 'set', check
hookPost storage, 'remove', check
sinon.spy(storage, 'set')
sinon.spy(storage, 'remove')
sync = new OptionsSync(remote)
sync.pullThrottle = 0
sync.watchAndPull(storage)