add unit test for backup database

This commit is contained in:
Julian Descottes 2017-06-18 11:31:33 +02:00
parent 372ad1f513
commit d0a32b18c5
5 changed files with 282 additions and 64 deletions

View File

@ -16,41 +16,29 @@
* The BackupDatabase handles all the database interactions related
* to piskel snapshots continuously saved while during the usage of
* Piskel.
*
* @param {Object} options
* - onUpgrade {Function} optional callback called when a DB
* upgrade is performed.
*/
ns.BackupDatabase = function (options) {
options = options || {};
ns.BackupDatabase = function () {
this.db = null;
this.onUpgrade = options.onUpgrade;
};
ns.BackupDatabase.drop = function () {
return _requestPromise(window.indexedDB.deleteDatabase(DB_NAME));
};
/**
* Open and initialize the database.
* Returns a promise that resolves when the databse is opened.
*/
ns.BackupDatabase.prototype.init = function () {
this.initDeferred_ = Q.defer();
var request = window.indexedDB.open(DB_NAME, DB_VERSION);
request.onerror = this.onRequestError_.bind(this);
request.onsuccess = this.onRequestSuccess_.bind(this);
request.onupgradeneeded = this.onUpgradeNeeded_.bind(this);
return this.initDeferred_.promise;
};
ns.BackupDatabase.prototype.onRequestError_ = function (event) {
console.log('Could not initialize the piskel backup database');
this.initDeferred_.reject();
};
ns.BackupDatabase.prototype.onRequestSuccess_ = function (event) {
this.db = event.target.result;
this.initDeferred_.resolve(this.db);
return _requestPromise(request).then(function (event) {
this.db = event.target.result;
return this.db;
}.bind(this)).catch(function (e) {
console.log('Could not initialize the piskel backup database');
});
};
ns.BackupDatabase.prototype.onUpgradeNeeded_ = function (event) {
@ -65,9 +53,7 @@
objectStore.createIndex('session_id, date', ['session_id', 'date'], { unique: false });
objectStore.transaction.oncomplete = function(event) {
if (typeof this.onUpgrade == 'function') {
this.onUpgrade(this.db);
}
// Nothing to do at the moment!
}.bind(this);
};
@ -187,18 +173,18 @@
var sessions = {};
var _createSession = function (snapshot) {
sessions[snapshot.sessionId] = {
sessions[snapshot.session_id] = {
startDate: snapshot.date,
endDate: snapshot.date,
name: snapshot.name,
id: snapshot.sessionId
id: snapshot.session_id
};
};
var _updateSessions = function (snapshot) {
var s = sessions[snapshot.sessionId];
s.startDate = Math.min(s.startDate, snapshot.startDate);
s.endDate = Math.max(s.endDate, snapshot.endDate);
var _updateSession = function (snapshot) {
var s = sessions[snapshot.session_id];
s.startDate = Math.min(s.startDate, snapshot.date);
s.endDate = Math.max(s.endDate, snapshot.date);
if (s.endDate === snapshot.endDate) {
s.name = snapshot.name;
}
@ -212,10 +198,10 @@
if (!snapshot) {
deferred.resolve(sessions);
} else {
if (sessions[snapshot.sessionId]) {
_createSession(snapshot);
if (sessions[snapshot.session_id]) {
_updateSession(snapshot);
} else {
_updateSessions(snapshot);
_createSession(snapshot);
}
cursor.continue();
}
@ -234,7 +220,7 @@
var deferred = Q.defer();
// Open a transaction to the snapshots object store.
var objectStore = this.db.transaction(['snapshots']).objectStore('snapshots');
var objectStore = this.openObjectStore_();
// Loop on all the saved snapshots for the provided piskel id
var index = objectStore.index('session_id');

View File

@ -15,16 +15,9 @@
/**
* The PiskelDatabase handles all the database interactions related
* to the local piskel saved that can be performed in-browser.
*
* @param {Object} options
* - onUpgrade {Function} optional callback called when a DB
* upgrade is performed.
*/
ns.PiskelDatabase = function (options) {
options = options || {};
this.db = null;
this.onUpgrade = options.onUpgrade;
};
ns.PiskelDatabase.prototype.init = function () {
@ -57,9 +50,7 @@
// Create an object store "piskels" with the autoIncrement flag set as true.
var objectStore = this.db.createObjectStore('piskels', { keyPath : 'name' });
objectStore.transaction.oncomplete = function(event) {
if (typeof this.onUpgrade == 'function') {
this.onUpgrade(this.db);
}
pskl.service.storage.migrate.MigrateLocalStorageToIndexed.migrate(this.db);
}.bind(this);
};

View File

@ -1,7 +1,5 @@
(function () {
var ns = $.namespace('pskl.service.storage');
var DB_NAME = 'PiskelDatabase';
var DB_VERSION = 1;
ns.IndexedDbStorageService = function (piskelController) {
this.piskelController = piskelController;

View File

@ -4,11 +4,10 @@
// Simple migration helper to move local storage saves to indexed db.
ns.MigrateLocalStorageToIndexedDb = {};
ns.MigrateLocalStorageToIndexedDb.migrate = function () {
ns.MigrateLocalStorageToIndexedDb.migrate = function (piskelDatabase) {
var deferred = Q.defer();
var localStorageService = pskl.app.localStorageService;
var indexedDbStorageService = pskl.app.indexedDbStorageService;
var localStorageKeys = localStorageService.getKeys();
var migrationData = localStorageKeys.map(function (key) {
@ -28,24 +27,22 @@
if (!data) {
console.log('Data migration from local storage to indexed db finished.');
if (success) {
console.log('Local storage piskels successfully migrated. Old copies will be deleted.');
ns.MigrateLocalStorageToIndexedDb.deleteLocalStoragePiskels();
}
deferred.resolve();
return;
} else {
ns.MigrateLocalStorageToIndexedDb.save_(piskelDatabase, data)
.then(function () {
migrateSprite(index + 1);
})
.catch(function (e) {
var success = false;
console.error('Failed to migrate local storage sprite for name: ' + data.name);
migrateSprite(index + 1);
});
}
indexedDbStorageService.save_(
data.name,
data.description,
data.date,
data.serialized
).then(function () {
migrateSprite(index + 1);
}).catch(function (e) {
var success = false;
console.error('Failed to migrate local storage sprite for name: ' + data.name);
migrateSprite(index + 1);
});
};
// Start the migration.
@ -54,6 +51,17 @@
return deferred.promise;
};
ns.MigrateLocalStorageToIndexedDb.save_ = function (piskelDatabase, piskelData) {
return piskelDatabase.get(piskelData.name).then(function (event) {
var data = event.target.result;
if (typeof data !== 'undefined') {
return piskelDatabase.update(piskelData.name, piskelData.description, piskelData.date, piskelData.serialized);
} else {
return piskelDatabase.create(piskelData.name, piskelData.description, piskelData.date, piskelData.serialized);
}
});
};
ns.MigrateLocalStorageToIndexedDb.deleteLocalStoragePiskels = function () {
var localStorageKeys = pskl.app.localStorageService.getKeys();

View File

@ -0,0 +1,235 @@
describe('BackupDatabase test', function () {
// Test object.
var backupDatabase;
var _toSnapshot = function (session_id, name, description, date, serialized) {
return {
session_id: session_id,
name: name,
description: description,
date: date,
serialized: serialized
};
};
var _checkSnapshot = function (actual, expected) {
expect(actual.session_id).toBe(actual.session_id);
expect(actual.name).toBe(actual.name);
expect(actual.description).toBe(actual.description);
expect(actual.date).toBe(actual.date);
expect(actual.serialized).toBe(actual.serialized);
};
var _addSnapshots = function (snapshots) {
var _add = function (index) {
return backupDatabase.createSnapshot(snapshots[index]).then(function () {
if (snapshots[index + 1]) {
return _add(index + 1);
} else {
return Promise.resolve();
}
})
};
return _add(0);
};
beforeEach(function (done) {
// Drop the database before each test.
pskl.database.BackupDatabase.drop().then(done);
});
afterEach(function () {
// Close the database if it was still open.
if (backupDatabase && backupDatabase.db) {
backupDatabase.db.close();
}
});
it('initializes the DB and returns a promise', function (done) {
backupDatabase = new pskl.database.BackupDatabase();
backupDatabase.init().then(done);
});
it('can add snapshots and retrieve them', function (done) {
var snapshot = _toSnapshot('session_1', 'name', 'desc', 0, 'serialized');
backupDatabase = new pskl.database.BackupDatabase();
backupDatabase.init()
.then(function (db) {
// Create snapshot in backup database
return backupDatabase.createSnapshot(snapshot);
}).then(function () {
// Get snapshots for session_1 in backup database
return backupDatabase.getSnapshotsBySessionId('session_1');
}).then(function (snapshots) {
expect(snapshots.length).toBe(1);
_checkSnapshot(snapshots[0], snapshot);
done();
});
});
it('can update snapshots and retrieve them', function (done) {
var snapshot = _toSnapshot('session_1', 'name', 'desc', 0, 'serialized');
var updated = _toSnapshot('session_1', 'name_updated', 'desc_updated', 10, 'serialized_updated');
backupDatabase = new pskl.database.BackupDatabase();
backupDatabase.init()
.then(function () {
// Create snapshot in backup database
return backupDatabase.createSnapshot(snapshot);
}).then(function () {
// Retrieve snapshots to get the inserted snapshot id
return backupDatabase.getSnapshotsBySessionId('session_1');
}).then(function (snapshots) {
// Update snapshot in backup database
updated.id = snapshots[0].id;
return backupDatabase.updateSnapshot(updated);
}).then(function () {
// Get snapshots for session_1 in backup database
return backupDatabase.getSnapshotsBySessionId('session_1');
}).then(function (snapshots) {
expect(snapshots.length).toBe(1);
_checkSnapshot(snapshots[0], updated);
done();
});
});
it('can delete snapshots', function (done) {
var testSnapshots = [
_toSnapshot('session_1', 'name1', 'desc1', 0, 'serialized1'),
_toSnapshot('session_1', 'name2', 'desc2', 0, 'serialized2'),
_toSnapshot('session_2', 'name3', 'desc3', 0, 'serialized3')
];
backupDatabase = new pskl.database.BackupDatabase();
backupDatabase.init()
.then(function () {
return _addSnapshots(testSnapshots);
}).then(function () {
// Retrieve snapshots to get the inserted snapshot id
return backupDatabase.getSnapshotsBySessionId('session_1');
}).then(function (snapshots) {
expect(snapshots.length).toBe(2);
// Delete snapshot with 'name1' from backup database
var snapshot = snapshots.filter(function (s) { return s.name === 'name1' })[0];
return backupDatabase.deleteSnapshot(snapshot);
}).then(function () {
// Get snapshots for session_1 in backup database
return backupDatabase.getSnapshotsBySessionId('session_1');
}).then(function (snapshots) {
expect(snapshots.length).toBe(1);
_checkSnapshot(snapshots[0], testSnapshots[1]);
done();
});
});
it('returns an empty array when calling getSnapshots for an empty session', function (done) {
var testSnapshots = [
_toSnapshot('session_1', 'name1', 'desc1', 0, 'serialized1'),
_toSnapshot('session_1', 'name2', 'desc2', 0, 'serialized2'),
_toSnapshot('session_2', 'name3', 'desc3', 0, 'serialized3')
];
backupDatabase = new pskl.database.BackupDatabase();
backupDatabase.init()
.then(function () {
return _addSnapshots(testSnapshots);
}).then(function () {
// Retrieve snapshots for a session that doesn't exist
return backupDatabase.getSnapshotsBySessionId('session_3');
}).then(function (snapshots) {
expect(snapshots.length).toBe(0);
done();
});
});
it('can delete all snapshots for a session', function (done) {
var testSnapshots = [
_toSnapshot('session_1', 'name1', 'desc1', 0, 'serialized1'),
_toSnapshot('session_1', 'name2', 'desc2', 0, 'serialized2'),
_toSnapshot('session_2', 'name3', 'desc3', 0, 'serialized3')
];
backupDatabase = new pskl.database.BackupDatabase();
backupDatabase.init()
.then(function () {
return _addSnapshots(testSnapshots);
}).then(function () {
// Retrieve snapshots to get the inserted snapshot id
return backupDatabase.getSnapshotsBySessionId('session_1');
}).then(function (snapshots) {
// Check that we have 2 snapshots for session_1
expect(snapshots.length).toBe(2);
// Delete snapshots for session_1
return backupDatabase.deleteSnapshotsForSession('session_1');
}).then(function () {
// Get snapshots for session_1 in backup database
return backupDatabase.getSnapshotsBySessionId('session_1');
}).then(function (snapshots) {
// All snapshots should have been deleted
expect(snapshots.length).toBe(0);
// Get snapshots for session_2 in backup database
return backupDatabase.getSnapshotsBySessionId('session_2');
}).then(function (snapshots) {
// There should still be the snapshot for session_2
expect(snapshots.length).toBe(1);
_checkSnapshot(snapshots[0], testSnapshots[2]);
done();
});
});
it('does a noop when calling deleteAllSnapshotsForSession for a missing session', function (done) {
backupDatabase = new pskl.database.BackupDatabase();
backupDatabase.init()
.then(function () {
// Delete snapshot with 'name1' from backup database
return backupDatabase.deleteSnapshotsForSession('session_1');
}).then(function () {
done();
});
});
it('returns sessions array when calling getSessions', function (done) {
var testSnapshots = [
_toSnapshot('session_1', 'name1', 'desc1', 5, 'serialized1'),
_toSnapshot('session_1', 'name2', 'desc2', 10, 'serialized2'),
_toSnapshot('session_2', 'name3', 'desc3', 15, 'serialized3')
];
backupDatabase = new pskl.database.BackupDatabase();
backupDatabase.init()
.then(function () {
return _addSnapshots(testSnapshots);
}).then(function () {
return backupDatabase.getSessions();
}).then(function (sessions) {
// Check that we have 2 sessions
expect(sessions.length).toBe(2);
// Get the actual sessions
var session1 = sessions.filter(function (s) { return s.id === 'session_1'; })[0];
var session2 = sessions.filter(function (s) { return s.id === 'session_2'; })[0];
// Check the start/end date were computed properly
expect(session1.startDate).toBe(5);
expect(session1.endDate).toBe(10);
expect(session2.startDate).toBe(15);
expect(session2.endDate).toBe(15);
done();
});
});
it('returns an empty array when calling getSessions on an empty DB', function (done) {
backupDatabase = new pskl.database.BackupDatabase();
backupDatabase.init()
.then(function () {
return backupDatabase.getSessions();
}).then(function (sessions) {
expect(sessions.length).toBe(0);
done();
});
});
});