introduce update-handler

This commit is contained in:
Felix Hammerl 2014-03-11 16:06:19 +01:00
parent 45e6b7834a
commit aa7827554b
5 changed files with 289 additions and 2 deletions

View File

@ -43,7 +43,8 @@ define(function(require) {
checkOutboxInterval: 5000,
iconPath: '/img/icon.png',
verificationUrl: '/verify/',
verificationUuidLength: 36
verificationUuidLength: 36,
dbVersion: 1
};
/**

View File

@ -0,0 +1,90 @@
define(function(require) {
'use strict';
var cfg = require('js/app-config').config,
updateV1 = require('js/util/update/update-v1');
/**
* Handles database migration
*/
var UpdateHandler = function(appConfigStorage, userStorage) {
this._appConfigStorage = appConfigStorage;
this._userStorage = userStorage;
this._updateScripts = [updateV1];
};
/**
* Executes all the necessary updates
* @param {Function} callback(error) Invoked when all the database updates were executed, or if an error occurred
*/
UpdateHandler.prototype.update = function(callback) {
var self = this,
currentVersion = 0,
targetVersion = cfg.dbVersion,
versionDbType = 'dbVersion';
self._appConfigStorage.listItems(versionDbType, 0, null, function(err, items) {
if (err) {
callback(err);
return;
}
// parse the database version number
if (items && items.length > 0) {
currentVersion = parseInt(items[0], 10);
}
self._applyUpdate({
currentVersion: currentVersion,
targetVersion: targetVersion
}, callback);
});
};
/**
* Schedules necessary updates and executes thom in order
*/
UpdateHandler.prototype._applyUpdate = function(options, callback) {
var self = this,
storage,
queue = [];
if (options.currentVersion >= options.targetVersion) {
// the current database version is up to date
callback();
return;
}
storage = {
appConfigStorage: self._appConfigStorage,
userStorage: self._userStorage
};
// add all the necessary database updates to the queue
for (var i = options.currentVersion; i < options.targetVersion; i++) {
queue.push(self._updateScripts[i]);
}
// takes the next update from the queue and executes it
function executeNextUpdate(err) {
if (err) {
callback(err);
return;
}
if (queue.length < 1) {
// we're done
callback();
return;
}
// process next update
var script = queue.shift();
script(storage, executeNextUpdate);
}
executeNextUpdate();
};
return UpdateHandler;
});

View File

@ -0,0 +1,29 @@
define(function() {
'use strict';
/**
* Update handler for transition databasae version 0 -> 1
*
* In database version 1, the stored email objects have to be purged, otherwise
* every non-prefixed mail in the IMAP folders would be nuked due to the implementation
* of the delta sync.
*/
function updateV1(options, callback) {
var emailDbType = 'email_',
versionDbType = 'dbVersion',
postUpdateDbVersion = 1;
// remove the emails
options.userStorage.removeList(emailDbType, function(err) {
if (err) {
callback(err);
return;
}
// update the database version to postUpdateDbVersion
options.appConfigStorage.storeList([postUpdateDbVersion], versionDbType, callback);
});
}
return updateV1;
});

View File

@ -49,7 +49,8 @@ function startTests() {
'test/new-unit/mail-list-ctrl-test',
'test/new-unit/write-ctrl-test',
'test/new-unit/outbox-bo-test',
'test/new-unit/invitation-dao-test'
'test/new-unit/invitation-dao-test',
'test/new-unit/update-handler-test'
], function() {
//Tests loaded, run tests
mocha.run();

View File

@ -0,0 +1,166 @@
define(function(require) {
'use strict';
var DeviceStorageDAO = require('js/dao/devicestorage-dao'),
cfg = require('js/app-config').config,
UpdateHandler = require('js/util/update/update-handler'),
expect = chai.expect;
chai.Assertion.includeStack = true;
describe('UpdateHandler', function() {
var updateHandler, appConfigStorageStub, userStorageStub, origDbVersion;
beforeEach(function() {
origDbVersion = cfg.dbVersion;
appConfigStorageStub = sinon.createStubInstance(DeviceStorageDAO);
userStorageStub = sinon.createStubInstance(DeviceStorageDAO);
updateHandler = new UpdateHandler(appConfigStorageStub, userStorageStub);
});
afterEach(function() {
cfg.dbVersion = origDbVersion;
});
describe('#constructor', function() {
it('should create instance', function() {
expect(updateHandler).to.exist;
expect(updateHandler._appConfigStorage).to.equal(appConfigStorageStub);
expect(updateHandler._userStorage).to.equal(userStorageStub);
// the update handler must contain as many db update sripts as there are database versions
expect(updateHandler._updateScripts.length).to.equal(cfg.dbVersion);
});
});
describe('#update', function() {
var versionDbType = 'dbVersion';
it('should not update when up to date', function(done) {
cfg.dbVersion = 3; // app requires database version 3
appConfigStorageStub.listItems.withArgs(versionDbType).yieldsAsync(null, '3'); // database version is 3
updateHandler.update(function(error) {
expect(error).to.not.exist;
expect(appConfigStorageStub.listItems.calledOnce).to.be.true;
done();
});
});
describe('dummy updates for v0 through v4', function() {
var updateCounter;
beforeEach(function() {
updateCounter = 0;
appConfigStorageStub.listItems.withArgs(versionDbType).yieldsAsync(); // database version is 0
});
afterEach(function() {
expect(appConfigStorageStub.listItems.calledOnce).to.be.true;
});
it('should work', function(done) {
cfg.dbVersion = 4; // app requires database version 4
// a simple dummy update to executed that only increments the update counter
function dummyUpdate(options, callback) {
updateCounter++;
callback();
}
// inject the dummy updates instead of live ones
updateHandler._updateScripts = [dummyUpdate, dummyUpdate, dummyUpdate, dummyUpdate];
// execute test
updateHandler.update(function(error) {
expect(error).to.not.exist;
expect(updateCounter).to.equal(4);
done();
});
});
it('should fail while updating to v3', function(done) {
cfg.dbVersion = 4; // app requires database version 4
function dummyUpdate(options, callback) {
updateCounter++;
callback();
}
function failingUpdate(options, callback) {
updateCounter++;
callback({});
}
// inject the dummy updates instead of live ones
updateHandler._updateScripts = [dummyUpdate, dummyUpdate, failingUpdate, dummyUpdate];
// execute test
updateHandler.update(function(error) {
expect(error).to.exist;
expect(updateCounter).to.equal(3);
done();
});
});
});
describe('v0 -> v1', function() {
var emailDbType = 'email_';
beforeEach(function() {
cfg.dbVersion = 1; // app requires database version 1
appConfigStorageStub.listItems.withArgs(versionDbType).yieldsAsync(); // database version is 0
});
afterEach(function() {
// database version is only queried for version checking prior to the update script
// so no need to check this in case-specific tests
expect(appConfigStorageStub.listItems.calledOnce).to.be.true;
});
it('should work', function(done) {
userStorageStub.removeList.withArgs(emailDbType).yieldsAsync();
appConfigStorageStub.storeList.withArgs([1], versionDbType).yieldsAsync();
updateHandler.update(function(error) {
expect(error).to.not.exist;
expect(userStorageStub.removeList.calledOnce).to.be.true;
expect(appConfigStorageStub.storeList.calledOnce).to.be.true;
done();
});
});
it('should fail when persisting database version fails', function(done) {
userStorageStub.removeList.yieldsAsync();
appConfigStorageStub.storeList.yieldsAsync({});
updateHandler.update(function(error) {
expect(error).to.exist;
expect(userStorageStub.removeList.calledOnce).to.be.true;
expect(appConfigStorageStub.storeList.calledOnce).to.be.true;
done();
});
});
it('should fail when wiping emails from database fails', function(done) {
userStorageStub.removeList.yieldsAsync({});
updateHandler.update(function(error) {
expect(error).to.exist;
expect(userStorageStub.removeList.calledOnce).to.be.true;
expect(appConfigStorageStub.storeList.called).to.be.false;
done();
});
});
});
});
});
});