From 3e75397213a25fe419bef03a75b22f3ea3a04188 Mon Sep 17 00:00:00 2001 From: Tankred Hase Date: Tue, 29 Oct 2013 12:19:27 +0100 Subject: [PATCH] cleanup and refactor qunit tests to mocha --- Gruntfile.js | 5 +- src/js/app-controller.js | 14 +- src/js/dao/cloudstorage-dao.js | 200 -------------- src/js/dao/devicestorage-dao.js | 41 +-- src/js/dao/email-dao.js | 6 +- src/js/dao/keychain-dao.js | 175 ++++++------- src/js/dao/lawnchair-dao.js | 128 ++++++--- src/js/dao/publickey-dao.js | 83 ++++++ src/js/dao/rest-dao.js | 82 ++++++ test/integration/cloudstorage-dao-test.js | 224 ---------------- test/integration/email-dao-test.js | 16 +- test/new-unit/cloudstorage-dao-test.js | 20 -- test/new-unit/devicestorage-dao-test.js | 105 ++++++++ test/new-unit/email-dao-test.js | 20 +- test/new-unit/keychain-dao-test.js | 302 ++++++++++++++++++++++ test/new-unit/lawnchair-dao-test.js | 152 +++++++++++ test/new-unit/main.js | 7 +- test/new-unit/publickey-dao-test.js | 130 ++++++++++ test/new-unit/rest-dao-test.js | 117 +++++++++ test/unit/devicestorage-dao-test.js | 4 +- test/unit/keychain-dao-test.js | 29 +-- test/unit/lawnchair-dao-test.js | 87 ------- test/unit/main.js | 3 +- 23 files changed, 1224 insertions(+), 726 deletions(-) delete mode 100644 src/js/dao/cloudstorage-dao.js create mode 100644 src/js/dao/publickey-dao.js create mode 100644 src/js/dao/rest-dao.js delete mode 100644 test/integration/cloudstorage-dao-test.js delete mode 100644 test/new-unit/cloudstorage-dao-test.js create mode 100644 test/new-unit/devicestorage-dao-test.js create mode 100644 test/new-unit/keychain-dao-test.js create mode 100644 test/new-unit/lawnchair-dao-test.js create mode 100644 test/new-unit/publickey-dao-test.js create mode 100644 test/new-unit/rest-dao-test.js delete mode 100644 test/unit/lawnchair-dao-test.js diff --git a/Gruntfile.js b/Gruntfile.js index 1063a35..dd960de 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -62,7 +62,8 @@ module.exports = function(grunt) { all: { options: { urls: ['http://localhost:<%= connect.test.options.port %>/test/new-unit/index.html'], - run: false + run: false, + reporter: 'Spec' } } }, @@ -222,7 +223,7 @@ module.exports = function(grunt) { // Test/Dev tasks grunt.registerTask('dev', ['connect:dev']); - grunt.registerTask('test', ['jshint', 'connect:test', 'mocha', 'qunit']); + grunt.registerTask('test', ['jshint', 'connect:test', 'mocha']); grunt.registerTask('prod', ['connect:prod']); }; \ No newline at end of file diff --git a/src/js/app-controller.js b/src/js/app-controller.js index ccc3007..202d4f1 100644 --- a/src/js/app-controller.js +++ b/src/js/app-controller.js @@ -8,8 +8,10 @@ define(function(require) { ImapClient = require('imap-client'), SmtpClient = require('smtp-client'), EmailDAO = require('js/dao/email-dao'), + RestDAO = require('js/dao/rest-dao'), + PublicKeyDAO = require('js/dao/publickey-dao'), + LawnchairDAO = require('js/dao/lawnchair-dao'), KeychainDAO = require('js/dao/keychain-dao'), - cloudstorage = require('js/dao/cloudstorage-dao'), DeviceStorageDAO = require('js/dao/devicestorage-dao'), PGP = require('js/crypto/pgp'), config = require('js/app-config').config; @@ -33,7 +35,7 @@ define(function(require) { function onDeviceReady() { console.log('Starting app.'); // init app config storage - self._appConfigStore = new DeviceStorageDAO(); + self._appConfigStore = new DeviceStorageDAO(new LawnchairDAO()); self._appConfigStore.init('app-config', callback); } }; @@ -129,6 +131,7 @@ define(function(require) { */ self.init = function(userId, token, callback) { var auth, imapOptions, smtpOptions, + lawnchairDao, restDao, pubkeyDao, keychain, imapClient, smtpClient, pgp, userStorage; // create mail credentials objects for imap/smtp @@ -153,11 +156,14 @@ define(function(require) { }; // init objects and inject dependencies - keychain = new KeychainDAO(cloudstorage); + restDao = new RestDAO(); + pubkeyDao = new PublicKeyDAO(restDao); + lawnchairDao = new LawnchairDAO(); + keychain = new KeychainDAO(lawnchairDao, pubkeyDao); imapClient = new ImapClient(imapOptions); smtpClient = new SmtpClient(smtpOptions); pgp = new PGP(); - userStorage = new DeviceStorageDAO(); + userStorage = new DeviceStorageDAO(lawnchairDao); self._emailDao = new EmailDAO(keychain, imapClient, smtpClient, pgp, userStorage); // init email dao diff --git a/src/js/dao/cloudstorage-dao.js b/src/js/dao/cloudstorage-dao.js deleted file mode 100644 index 7ce4ed7..0000000 --- a/src/js/dao/cloudstorage-dao.js +++ /dev/null @@ -1,200 +0,0 @@ -/** - * High level storage api for handling syncing of data to - * and from the cloud. - */ -define(['jquery', 'js/app-config'], function($, app) { - 'use strict'; - - var self = {}; - - // - // Generic Ajax helper functions - // - - /** - * GET (read) request - */ - self.get = function(uri, callback) { - $.ajax({ - url: uri, - type: 'GET', - dataType: 'json', - headers: { - 'Accept': 'application/json', - }, - success: function(res) { - callback(null, res); - }, - error: function(xhr, textStatus, err) { - callback({ - code: xhr.status, - errMsg: xhr.statusText, - err: err - }); - } - }); - }; - - /** - * PUT (update) request - */ - self.put = function(item, uri, callback) { - $.ajax({ - url: uri, - type: 'PUT', - data: JSON.stringify(item), - contentType: 'application/json', - success: function() { - callback(); - }, - error: function(xhr, textStatus, err) { - callback({ - code: xhr.status, - errMsg: xhr.statusText, - err: err - }); - } - }); - }; - - /** - * DELETE (remove) request - */ - self.remove = function(uri, callback) { - $.ajax({ - url: uri, - type: 'DELETE', - success: function() { - callback(); - }, - error: function(xhr, textStatus, err) { - callback({ - code: xhr.status, - errMsg: xhr.statusText, - err: err - }); - } - }); - }; - - // - // Encrypted Mail storage - // - - /** - * Pushes an encrypted item to the user's cloud storage - * @param type [String] The type of item e.g. 'email' - */ - self.putEncryptedItem = function(item, type, emailAddress, folderName, callback) { - var uri = app.config.cloudUrl + '/' + type + '/user/' + emailAddress + '/folder/' + folderName + '/' + item.id; - self.put(item, uri, callback); - }; - - /** - * Deliver an email to the user's outbox - */ - self.deliverEmail = function(email, from, to, callback) { - var uri = app.config.cloudUrl + '/email/user/' + from + '/folder/outbox/' + email.id + '?to=' + to; - self.put(email, uri, callback); - }; - - /** - * Delete an encrypted item from the cloud - * @param type [String] The type of item e.g. 'email' - */ - self.deleteEncryptedItem = function(id, type, emailAddress, folderName, callback) { - var uri = app.config.cloudUrl + '/' + type + '/user/' + emailAddress + '/folder/' + folderName + '/' + id; - self.remove(uri, callback); - }; - - /** - * Lists the encrypted items - * @param type [String] The type of item e.g. 'email' - * @param offset [Number] The offset of items to fetch (0 is the last stored item) - * @param num [Number] The number of items to fetch (null means fetch all) - */ - self.listEncryptedItems = function(type, emailAddress, folderName, callback) { - var uri = app.config.cloudUrl + '/' + type + '/user/' + emailAddress + '/folder/' + folderName; - self.get(uri, callback); - }; - - // - // Public Key - // - - /** - * Find the user's corresponding public key - */ - self.getPublicKey = function(keyId, callback) { - var uri = app.config.cloudUrl + '/publickey/key/' + keyId; - - self.get(uri, function(err, key) { - if (err) { - callback(err); - return; - } - - if (!key || !key._id) { - callback({ - errMsg: 'No public key for that user!' - }); - return; - } - - callback(null, key); - }); - }; - - /** - * Find the user's corresponding public key by email - */ - self.getPublicKeyByUserId = function(userId, callback) { - var uri = app.config.cloudUrl + '/publickey/user/' + userId; - - self.get(uri, function(err, keys) { - // not found - if (err && err.code === 404) { - callback(); - return; - } - - if (err) { - callback(err); - return; - } - - if (!keys || keys.length < 1) { - // 'No public key for that user!' - callback(); - return; - } - - if (keys.length > 1) { - callback({ - errMsg: 'That user has multiple public keys!' - }); - return; - } - - callback(null, keys[0]); - }); - }; - - /** - * Persist the user's publc key - */ - self.putPublicKey = function(pubkey, callback) { - var uri = app.config.cloudUrl + '/publickey/user/' + pubkey.userId + '/key/' + pubkey._id; - self.put(pubkey, uri, callback); - }; - - /** - * Delete the public key from the cloud storage service - */ - self.removePublicKey = function(keyId, callback) { - var uri = app.config.cloudUrl + '/publickey/key/' + keyId; - self.remove(uri, callback); - }; - - return self; -}); \ No newline at end of file diff --git a/src/js/dao/devicestorage-dao.js b/src/js/dao/devicestorage-dao.js index e3c1a9c..b7e352b 100644 --- a/src/js/dao/devicestorage-dao.js +++ b/src/js/dao/devicestorage-dao.js @@ -4,17 +4,15 @@ * through transparent encryption. If not, the crypto API is * used to encrypt data on the fly before persisting via a JSON store. */ -define(function(require) { +define(function() { 'use strict'; - var jsonDao = require('js/dao/lawnchair-dao'); - - var DeviceStorageDAO = function() { - + var DeviceStorageDAO = function(localDbDao) { + this._localDbDao = localDbDao; }; DeviceStorageDAO.prototype.init = function(emailAddress, callback) { - jsonDao.init(emailAddress, callback); + this._localDbDao.init(emailAddress, callback); }; /** @@ -48,26 +46,14 @@ define(function(require) { }); }); - jsonDao.batch(items, function() { - callback(); - }); + this._localDbDao.batch(items, callback); }; /** * Deletes items of a certain type from storage */ DeviceStorageDAO.prototype.removeList = function(type, callback) { - // validate type - if (!type) { - callback({ - errMsg: 'Type is not set!' - }); - return; - } - - jsonDao.removeList(type, function() { - callback(); - }); + this._localDbDao.removeList(type, callback); }; /** @@ -77,26 +63,15 @@ define(function(require) { * @param num [Number] The number of items to fetch (null means fetch all) */ DeviceStorageDAO.prototype.listItems = function(type, offset, num, callback) { - - // validate type - if (!type || typeof offset === 'undefined' || typeof num === 'undefined') { - callback({ - errMsg: 'Args not is not set!' - }); - return; - } - // fetch all items of a certain type from the data-store - jsonDao.list(type, offset, num, function(matchingList) { - callback(null, matchingList); - }); + this._localDbDao.list(type, offset, num, callback); }; /** * Clear the whole device data-store */ DeviceStorageDAO.prototype.clear = function(callback) { - jsonDao.clear(callback); + this._localDbDao.clear(callback); }; // diff --git a/src/js/dao/email-dao.js b/src/js/dao/email-dao.js index e479210..44c1d23 100644 --- a/src/js/dao/email-dao.js +++ b/src/js/dao/email-dao.js @@ -56,7 +56,7 @@ define(function(require) { EmailDAO.prototype.unlock = function(keypair, passphrase, callback) { var self = this; - + if (keypair && keypair.privateKey && keypair.publicKey) { // import existing key pair into crypto module self._crypto.importKeys({ @@ -264,7 +264,7 @@ define(function(require) { list.forEach(function(i) { // gather public keys required to verify signatures var sender = i.from[0].address; - self._keychain.getReveiverPublicKey(sender, function(err, senderPubkey) { + self._keychain.getReceiverPublicKey(sender, function(err, senderPubkey) { // decrypt and verfiy signatures self._crypto.decrypt(i.body, senderPubkey.publicKey, function(err, decrypted) { @@ -488,7 +488,7 @@ define(function(require) { // only support single recipient for e-2-e encryption // check if receiver has a public key - self._keychain.getReveiverPublicKey(email.to[0].address, function(err, receiverPubkey) { + self._keychain.getReceiverPublicKey(email.to[0].address, function(err, receiverPubkey) { if (err) { callback(err); return; diff --git a/src/js/dao/keychain-dao.js b/src/js/dao/keychain-dao.js index 50ad085..ce2e681 100644 --- a/src/js/dao/keychain-dao.js +++ b/src/js/dao/keychain-dao.js @@ -2,13 +2,14 @@ * A high-level Data-Access Api for handling Keypair synchronization * between the cloud service and the device's local storage */ -define(['underscore', 'js/dao/lawnchair-dao'], function(_, jsonDao) { +define(function(require) { 'use strict'; - var KeychainDAO = function(cloudstorage) { - var self = this; + var _ = require('underscore'); - self._cloudstorage = cloudstorage; + var KeychainDAO = function(localDbDao, publicKeyDao) { + this._localDbDao = localDbDao; + this._publicKeyDao = publicKeyDao; }; /** @@ -60,47 +61,54 @@ define(['underscore', 'js/dao/lawnchair-dao'], function(_, jsonDao) { * Look up a reveiver's public key by user id * @param userId [String] the receiver's email address */ - KeychainDAO.prototype.getReveiverPublicKey = function(userId, callback) { + KeychainDAO.prototype.getReceiverPublicKey = function(userId, callback) { var self = this; // search local keyring for public key - jsonDao.list('publickey', 0, null, function(allPubkeys) { + self._localDbDao.list('publickey', 0, null, function(err, allPubkeys) { + if (err) { + callback(err); + return; + } + var pubkey = _.findWhere(allPubkeys, { userId: userId }); - if (!pubkey || !pubkey._id) { - // no public key by that user id in storage - // find from cloud by email address - self._cloudstorage.getPublicKeyByUserId(userId, function(err, cloudPubkey) { - if (err || !cloudPubkey) { - callback(); - return; - } - - if (cloudPubkey && cloudPubkey._id) { - // there is a public key for that user already in the cloud... - // save to local storage - self.saveLocalPublicKey(cloudPubkey, function(err) { - if (err) { - callback(err); - return; - } - - callback(null, cloudPubkey); - }); - } else { - // no public key for that user - callback(); - return; - } - }); - - } else { + if (pubkey && pubkey._id) { // that user's public key is already in local storage callback(null, pubkey); + return; } + + // no public key by that user id in storage + // find from cloud by email address + self._publicKeyDao.getByUserId(userId, onKeyGotten); }); + + function onKeyGotten(err, cloudPubkey) { + if (err) { + callback(err); + return; + } + + if (!cloudPubkey) { + // no public key for that user + callback(); + return; + } + + // there is a public key for that user already in the cloud... + // save to local storage + self.saveLocalPublicKey(cloudPubkey, function(err) { + if (err) { + callback(err); + return; + } + + callback(null, cloudPubkey); + }); + } }; /** @@ -113,36 +121,41 @@ define(['underscore', 'js/dao/lawnchair-dao'], function(_, jsonDao) { var self = this; // search for user's public key locally - jsonDao.list('publickey', 0, null, function(allPubkeys) { + self._localDbDao.list('publickey', 0, null, function(err, allPubkeys) { + if (err) { + callback(err); + return; + } + var pubkey = _.findWhere(allPubkeys, { userId: userId }); - if (!pubkey || !pubkey._id) { - // no public key by that user id in storage - // find from cloud by email address - self._cloudstorage.getPublicKeyByUserId(userId, function(err, cloudPubkey) { - if (err) { - callback(err); - return; - } - - if (cloudPubkey && cloudPubkey._id) { - // there is a public key for that user already in the cloud... - // sync keypair to local storage - syncKeypair(cloudPubkey._id); - } else { - // continue without keypair... generate in crypto.js - callback(); - return; - } - }); - - } else { + if (pubkey && pubkey._id) { // that user's public key is already in local storage... // sync keypair to the cloud syncKeypair(pubkey._id); + return; } + + // no public key by that user id in storage + // find from cloud by email address + self._publicKeyDao.getByUserId(userId, function(err, cloudPubkey) { + if (err) { + callback(err); + return; + } + + if (cloudPubkey && cloudPubkey._id) { + // there is a public key for that user already in the cloud... + // sync keypair to local storage + syncKeypair(cloudPubkey._id); + return; + } + + // continue without keypair... generate in crypto.js + callback(); + }); }); function syncKeypair(keypairId) { @@ -165,7 +178,7 @@ define(['underscore', 'js/dao/lawnchair-dao'], function(_, jsonDao) { if (savedPubkey && savedPubkey.publicKey) { keys.publicKey = savedPubkey; } - + if (savedPrivkey && savedPrivkey.encryptedKey) { keys.privateKey = savedPrivkey; } @@ -200,7 +213,7 @@ define(['underscore', 'js/dao/lawnchair-dao'], function(_, jsonDao) { } // persist public key in cloud storage - self._cloudstorage.putPublicKey(keypair.publicKey, function(err) { + self._publicKeyDao.put(keypair.publicKey, function(err) { // validate result if (err) { callback(err); @@ -220,11 +233,23 @@ define(['underscore', 'js/dao/lawnchair-dao'], function(_, jsonDao) { KeychainDAO.prototype.lookupPublicKey = function(id, callback) { var self = this; + if (!id) { + callback({ + errMsg: 'ID must be set for public key query!' + }); + return; + } + // lookup in local storage - jsonDao.read('publickey_' + id, function(pubkey) { + self._localDbDao.read('publickey_' + id, function(err, pubkey) { + if (err) { + callback(err); + return; + } + if (!pubkey) { // fetch from cloud storage - self._cloudstorage.getPublicKey(id, function(err, cloudPubkey) { + self._publicKeyDao.get(id, function(err, cloudPubkey) { if (err) { callback(err); return; @@ -249,43 +274,19 @@ define(['underscore', 'js/dao/lawnchair-dao'], function(_, jsonDao) { KeychainDAO.prototype.lookupPrivateKey = function(id, callback) { // lookup in local storage - jsonDao.read('privatekey_' + id, function(privkey) { - callback(null, privkey); - }); + this._localDbDao.read('privatekey_' + id, callback); }; KeychainDAO.prototype.saveLocalPublicKey = function(pubkey, callback) { // persist public key (email, _id) var pkLookupKey = 'publickey_' + pubkey._id; - - jsonDao.persist(pkLookupKey, pubkey, function(res1) { - // validate result - if (res1.key !== pkLookupKey) { - callback({ - errMsg: 'Persisting public key in local storage went wrong!' - }); - return; - } - - callback(); - }); + this._localDbDao.persist(pkLookupKey, pubkey, callback); }; KeychainDAO.prototype.saveLocalPrivateKey = function(privkey, callback) { // persist private key (email, _id) var prkLookupKey = 'privatekey_' + privkey._id; - - jsonDao.persist(prkLookupKey, privkey, function(res1) { - // validate result - if (res1.key !== prkLookupKey) { - callback({ - errMsg: 'Persisting private key in local storage went wrong!' - }); - return; - } - - callback(); - }); + this._localDbDao.persist(prkLookupKey, privkey, callback); }; return KeychainDAO; diff --git a/src/js/dao/lawnchair-dao.js b/src/js/dao/lawnchair-dao.js index 2eb124e..bee484f 100644 --- a/src/js/dao/lawnchair-dao.js +++ b/src/js/dao/lawnchair-dao.js @@ -1,27 +1,34 @@ /** * Handles generic caching of JSON objects in a lawnchair adapter */ -define(['underscore', 'lawnchair', 'lawnchairSQL', 'lawnchairIDB'], function(_, Lawnchair) { +define(function(require) { 'use strict'; - var self = {}, - db; + var _ = require('underscore'), + Lawnchair = require('lawnchair'); + require('lawnchairSQL'); + require('lawnchairIDB'); - self.init = function(dbName, callback) { - var temp; + var LawnchairDAO = function() {}; + LawnchairDAO.prototype.init = function(dbName, callback) { if (!dbName) { - throw new Error('Lawnchair DB name must be specified!'); + callback({ + errMsg: 'Lawnchair DB name must be specified!' + }); + return; } - temp = new Lawnchair({ + this._db = new Lawnchair({ name: dbName }, function(lc) { if (!lc) { - throw new Error('Lawnchair init failed!'); + callback({ + errMsg: 'Lawnchair init failed!' + }); + return; } - db = lc; callback(); }); }; @@ -29,27 +36,66 @@ define(['underscore', 'lawnchair', 'lawnchairSQL', 'lawnchairIDB'], function(_, /** * Create or update an object */ - self.persist = function(key, object, callback) { - db.save({ + LawnchairDAO.prototype.persist = function(key, object, callback) { + if (!key || !object) { + callback({ + errMsg: 'Key and Object must be set!' + }); + return; + } + + this._db.save({ key: key, object: object - }, callback); + }, function(persisted) { + if (persisted.key !== key) { + callback({ + errMsg: 'Persisting failed!' + }); + return; + } + + callback(); + }); }; /** * Persist a bunch of items at once */ - self.batch = function(list, callback) { - db.batch(list, callback); + LawnchairDAO.prototype.batch = function(list, callback) { + if (!(list instanceof Array)) { + callback({ + errMsg: 'Input must be of type Array!' + }); + return; + } + + this._db.batch(list, function(res) { + if (!res) { + callback({ + errMsg: 'Persisting batch failed!' + }); + return; + } + + callback(); + }); }; /** * Read a single item by its key */ - self.read = function(key, callback) { - db.get(key, function(o) { + LawnchairDAO.prototype.read = function(key, callback) { + if (!key) { + callback({ + errMsg: 'Key must be specified!' + }); + return; + } + + this._db.get(key, function(o) { if (o) { - callback(o.object); + callback(null, o.object); } else { callback(); } @@ -62,14 +108,23 @@ define(['underscore', 'lawnchair', 'lawnchairSQL', 'lawnchairIDB'], function(_, * @param offset [Number] The offset of items to fetch (0 is the last stored item) * @param num [Number] The number of items to fetch (null means fetch all) */ - self.list = function(type, offset, num, callback) { - var i, from, to, + LawnchairDAO.prototype.list = function(type, offset, num, callback) { + var self = this, + i, from, to, matchingKeys = [], intervalKeys = [], list = []; + // validate input + if (!type || typeof offset === 'undefined' || typeof num === 'undefined') { + callback({ + errMsg: 'Args not is not set!' + }); + return; + } + // get all keys - db.keys(function(keys) { + self._db.keys(function(keys) { // check if key begins with type keys.forEach(function(key) { @@ -94,18 +149,18 @@ define(['underscore', 'lawnchair', 'lawnchairSQL', 'lawnchairIDB'], function(_, // return if there are no matching keys if (intervalKeys.length === 0) { - callback(list); + callback(null, list); return; } // fetch all items from data-store with matching key - db.get(intervalKeys, function(intervalList) { + self._db.get(intervalKeys, function(intervalList) { intervalList.forEach(function(item) { list.push(item.object); }); // return only the interval between offset and num - callback(list); + callback(null, list); }); }); @@ -114,19 +169,28 @@ define(['underscore', 'lawnchair', 'lawnchairSQL', 'lawnchairIDB'], function(_, /** * Removes an object liter from local storage by its key (delete) */ - self.remove = function(key, callback) { - db.remove(key, callback); + LawnchairDAO.prototype.remove = function(key, callback) { + this._db.remove(key, callback); }; /** * Removes an object liter from local storage by its key (delete) */ - self.removeList = function(type, callback) { - var matchingKeys = [], + LawnchairDAO.prototype.removeList = function(type, callback) { + var self = this, + matchingKeys = [], after; + // validate type + if (!type) { + callback({ + errMsg: 'Type is not set!' + }); + return; + } + // get all keys - db.keys(function(keys) { + self._db.keys(function(keys) { // check if key begins with type keys.forEach(function(key) { if (key.indexOf(type) === 0) { @@ -142,7 +206,7 @@ define(['underscore', 'lawnchair', 'lawnchairSQL', 'lawnchairIDB'], function(_, // remove all matching keys after = _.after(matchingKeys.length, callback); _.each(matchingKeys, function(key) { - db.remove(key, after); + self._db.remove(key, after); }); }); }; @@ -150,9 +214,9 @@ define(['underscore', 'lawnchair', 'lawnchairSQL', 'lawnchairIDB'], function(_, /** * Clears the whole local storage cache */ - self.clear = function(callback) { - db.nuke(callback); + LawnchairDAO.prototype.clear = function(callback) { + this._db.nuke(callback); }; - return self; + return LawnchairDAO; }); \ No newline at end of file diff --git a/src/js/dao/publickey-dao.js b/src/js/dao/publickey-dao.js new file mode 100644 index 0000000..aae6520 --- /dev/null +++ b/src/js/dao/publickey-dao.js @@ -0,0 +1,83 @@ +define(function() { + 'use strict'; + + var PublicKeyDAO = function(restDao) { + this._restDao = restDao; + }; + + /** + * Find the user's corresponding public key + */ + PublicKeyDAO.prototype.get = function(keyId, callback) { + var uri = '/publickey/key/' + keyId; + + this._restDao.get(uri, function(err, key) { + if (err) { + callback(err); + return; + } + + if (!key || !key._id) { + callback({ + errMsg: 'No public key for that user!' + }); + return; + } + + callback(null, key); + }); + }; + + /** + * Find the user's corresponding public key by email + */ + PublicKeyDAO.prototype.getByUserId = function(userId, callback) { + var uri = '/publickey/user/' + userId; + + this._restDao.get(uri, function(err, keys) { + // not found + if (err && err.code === 404) { + callback(); + return; + } + + if (err) { + callback(err); + return; + } + + if (!keys || keys.length < 1) { + // 'No public key for that user!' + callback(); + return; + } + + if (keys.length > 1) { + callback({ + errMsg: 'That user has multiple public keys!' + }); + return; + } + + callback(null, keys[0]); + }); + }; + + /** + * Persist the user's publc key + */ + PublicKeyDAO.prototype.put = function(pubkey, callback) { + var uri = '/publickey/user/' + pubkey.userId + '/key/' + pubkey._id; + this._restDao.put(pubkey, uri, callback); + }; + + /** + * Delete the public key from the cloud storage service + */ + PublicKeyDAO.prototype.remove = function(keyId, callback) { + var uri = '/publickey/key/' + keyId; + this._restDao.remove(uri, callback); + }; + + return PublicKeyDAO; +}); \ No newline at end of file diff --git a/src/js/dao/rest-dao.js b/src/js/dao/rest-dao.js new file mode 100644 index 0000000..f9d3779 --- /dev/null +++ b/src/js/dao/rest-dao.js @@ -0,0 +1,82 @@ +define(function(require) { + 'use strict'; + + var $ = require('jquery'), + config = require('js/app-config').config; + + var RestDAO = function(options) { + if (options && options.baseUri) { + this._baseUri = options.baseUri; + } else { + this._baseUri = config.cloudUrl; + } + }; + + /** + * GET (read) request + */ + RestDAO.prototype.get = function(uri, callback) { + $.ajax({ + url: this._baseUri + uri, + type: 'GET', + dataType: 'json', + headers: { + 'Accept': 'application/json', + }, + success: function(res) { + callback(null, res); + }, + error: function(xhr, textStatus, err) { + callback({ + code: xhr.status, + errMsg: xhr.statusText, + err: err + }); + } + }); + }; + + /** + * PUT (create/update) request + */ + RestDAO.prototype.put = function(item, uri, callback) { + $.ajax({ + url: this._baseUri + uri, + type: 'PUT', + data: JSON.stringify(item), + contentType: 'application/json', + success: function() { + callback(); + }, + error: function(xhr, textStatus, err) { + callback({ + code: xhr.status, + errMsg: xhr.statusText, + err: err + }); + } + }); + }; + + /** + * DELETE (remove) request + */ + RestDAO.prototype.remove = function(uri, callback) { + $.ajax({ + url: this._baseUri + uri, + type: 'DELETE', + success: function() { + callback(); + }, + error: function(xhr, textStatus, err) { + callback({ + code: xhr.status, + errMsg: xhr.statusText, + err: err + }); + } + }); + }; + + return RestDAO; +}); \ No newline at end of file diff --git a/test/integration/cloudstorage-dao-test.js b/test/integration/cloudstorage-dao-test.js deleted file mode 100644 index 67730a2..0000000 --- a/test/integration/cloudstorage-dao-test.js +++ /dev/null @@ -1,224 +0,0 @@ -define(['js/dao/email-dao', 'js/dao/keychain-dao', 'js/dao/lawnchair-dao', - 'js/dao/cloudstorage-dao', 'js/app-config' -], function(EmailDAO, KeychainDAO, jsonDao, cloudstorage, app) { - 'use strict'; - - module("CloudStorage DAO"); - - var cloudstoragedaoTest = { - user: 'email.dao.it.test@mail.whiteout.io', - password: 'Xoza76645', - keySize: 128, - ivSize: 128, - rsaKeySize: 1024 - }; - - asyncTest("Init", 1, function() { - - // test keys - var pk = "-----BEGIN PUBLIC KEY-----\r\n" + - "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDTIupLSuRD5gh6wHx1f4Q2Qv61\r\n" + - "trOWgqfi/eJUtheoOWkW6KGoLqo5xdklPVIqyP9702PDQtf1upwVB8MCGSiYMDyj\r\n" + - "Fr0XlYJnJM2ERVrSGkDNSI2+6bVq1k2TB4YeZoMVhel/igCr5Rbr8LyNswCQMIXl\r\n" + - "oiMEqmiN/YtLwD1z+QIDAQAB\r\n" + - "-----END PUBLIC KEY-----"; - cloudstoragedaoTest.keypair = { - publicKey: { - _id: "01ca6e54-a6b3-4b5f-bb43-ede30aaccc9e", - userId: cloudstoragedaoTest.user, - publicKey: pk - }, - privateKey: { - _id: "01ca6e54-a6b3-4b5f-bb43-ede30aaccc9e", - userId: cloudstoragedaoTest.user, - encryptedKey: "zXBmmR7fz6sfR0AIeOzvwKOb6BrBQBgyweJ4c0LZS9h7C18SgPSMcvpSgBIwJadi577DPmwfXPl6zCNtwoqlLqD6xdS6P/bDY6rIWbaGbRrWzs/KXJ7UjWq0uyZSUFQK8w/woHkyQ4eLqdwj+duPZzrerDyi1XX8XXCcNDpDwR+1L2TxWlDzShN7IiA4OUeFsDbgqN3lKUBSHw5USnassv7nRwWlSNWPVaIlx3YT2T/VIaNoBbX5jDDwhDT4h/1fOOEbxTIBEN65mGGNW9GPLbi/PVgKibrF6l8bHwW5FjIkoYZdzgPe5nowhbFb2FB7mWX4gXMzqT3wuOP9fCOCEj4muxYkV7daedAGFRKl5mTPd9Cu+vSY+SnZX55m1yXQixn55J50AgW+aY/LXV+UqYwVObp7o5qs0B+OhQIRxH2Sp6IjRRFAqsQgBoRXS1qWPLCGVLMoSUkOSOCQd6rsr70fGXxGpguJFigAMWDXAzuPH98UFaB7kCiohhFLZ25vMhd/cOz1MXTKKPQJXfpBr8uX/hhhdsZReVfqLFKpvs1MzdFoV6FiTwZwyDyRnANYRHnqnP148q5s0JOkFcHaHqYdbLvVU6jm/B3QYJ/IO/uKyHoIhVobSSUBLzLDV0Eila9LhCdfWVXIVfFNUr5ut1YyOoJ23G5ItBSq5VFaZv79lNIhWjw/effq1IJd4eKeBe2X2DoYv85FZifAf/kUN38g0rTfAmKedsTGGhMLo+3Aa12MzRF93aTOMyKpHSA0G/5dA5PSVSlgDd/hwn4AlKzfo8M2PF1fh+9q5YtWqs4+mSOmyvYKxg9g+ZBhAvHbVBUN2emoNoJTC6JToB9jeRvksl1iehx+3C8nHUzXsxqApA3a79RJ+izRT2f0GguEAlAz4B6EozFRJwjNyRL2Fe7bgtadJxTNZfcG+oCgCFNCOyOvSgcpkjvj2DlFdPvw5BXXodV5D0jIg+OnszWcgLUDLFMkPPJgYrx9smRqdPjFnjWvnm6bRVZbxaU+FXKFvplmOG3XK1sR9g91bg5nnKDHRf6OuwBBgX0AyzOz2ohO3NVuIcppHjecUEY8t9QgezGal/R1PepW/uNPn/zJgGthTb4rK/KrXZTEsvC3XI55VlSnhORfNJvjn5Up/iusKeKXEGb/lhyc058GZY5UCmoIsV30TYgzXeuj6VZBEtcvAvza0mYmGvXf91ebVZR+", - iv: "XE4c3X134YNkapbeSXP6GA==" - } - }; - - // init dependencies - jsonDao.init(cloudstoragedaoTest.user); - cloudstoragedaoTest.keychain = new KeychainDAO(cloudstorage); - cloudstoragedaoTest.emailDao = new EmailDAO(cloudstorage, cloudstoragedaoTest.keychain); - - // clear db before tests - jsonDao.clear(function(err) { - ok(!err, 'DB cleared. Error status: ' + err); - - start(); - }); - }); - - asyncTest("Put public key to cloud", 1, function() { - cloudstorage.putPublicKey(cloudstoragedaoTest.keypair.publicKey, function(err) { - ok(!err, 'Persist key to cloud'); - - start(); - }); - }); - - asyncTest("Get Public key from cloud by id", 2, function() { - cloudstorage.getPublicKey(cloudstoragedaoTest.keypair.publicKey._id, function(err, data) { - ok(!err && data && data.publicKey, 'Get public key from cloud'); - deepEqual(data, cloudstoragedaoTest.keypair.publicKey, 'Public key is equal'); - - start(); - }); - }); - - asyncTest("Get Public key from cloud by email", 2, function() { - cloudstorage.getPublicKeyByUserId(cloudstoragedaoTest.keypair.publicKey.userId, function(err, data) { - ok(!err && data && data.publicKey, 'Get public key from cloud'); - deepEqual(data, cloudstoragedaoTest.keypair.publicKey, 'Public key is equal'); - - start(); - }); - }); - - asyncTest("Delete Public key from cloud", 1, function() { - cloudstorage.removePublicKey(cloudstoragedaoTest.keypair.publicKey._id, function(err) { - ok(!err, 'Delete public key from cloud'); - - start(); - }); - }); - - asyncTest("Put private key to cloud", 1, function() { - cloudstorage.putPrivateKey(cloudstoragedaoTest.keypair.privateKey, function(err) { - ok(!err, 'Persist key to cloud'); - - start(); - }); - }); - - asyncTest("Get Private key from cloud", 2, function() { - cloudstorage.getPrivateKey(cloudstoragedaoTest.keypair.privateKey._id, function(err, data) { - ok(!err && data && data.encryptedKey, 'Get private key from cloud'); - deepEqual(data, cloudstoragedaoTest.keypair.privateKey, 'Private key is equal'); - - start(); - }); - }); - - asyncTest("Delete Private key from cloud", 1, function() { - cloudstorage.removePrivateKey(cloudstoragedaoTest.keypair.privateKey._id, function(err) { - ok(!err, 'Delete private key from cloud'); - - start(); - }); - }); - - - - module("Keychain DAO"); - - asyncTest("Put User Keypair", 1, function() { - cloudstoragedaoTest.keychain.putUserKeyPair(cloudstoragedaoTest.keypair, function(err) { - ok(!err); - - start(); - }); - }); - - asyncTest("Get User Keypair", 2, function() { - cloudstoragedaoTest.keychain.getUserKeyPair(cloudstoragedaoTest.user, function(err, keypair) { - ok(!err); - ok(keypair && keypair.publicKey && keypair.privateKey); - - start(); - }); - }); - - asyncTest("Get Public Keys", 2, function() { - var pubkeyIds = [{ - _id: cloudstoragedaoTest.keypair.publicKey._id - } - ]; - cloudstoragedaoTest.keychain.getPublicKeys(pubkeyIds, function(err, pubkeys) { - ok(!err); - deepEqual(pubkeys[0], cloudstoragedaoTest.keypair.publicKey, "Fetch public key"); - - start(); - }); - }); - - - - module("Email DAO"); - - asyncTest("Init", 1, function() { - var account = new app.model.Account({ - emailAddress: cloudstoragedaoTest.user, - symKeySize: cloudstoragedaoTest.keySize, - symIvSize: cloudstoragedaoTest.ivSize, - asymKeySize: cloudstoragedaoTest.rsaKeySize - }); - - cloudstoragedaoTest.emailDao.init(account, cloudstoragedaoTest.password, function(err) { - ok(!err, 'Init complete'); - - start(); - }); - }); - - asyncTest("Send e-2-e Encrypted Email item", 1, function() { - var email = { - subject: 'Client Email DAO Test', // Subject line - body: 'Hello world' // plaintext body - }; - email.from = [{ - address: cloudstoragedaoTest.user - } - ]; - email.to = [{ - address: cloudstoragedaoTest.user - } - ]; - - cloudstoragedaoTest.emailDao.sendEmail(email, function(err) { - ok(!err, 'Email sent'); - - start(); - }); - }); - - asyncTest("Send Plaintext Email item", 1, function() { - var email = { - subject: 'Client Email DAO Test', // Subject line - body: 'Hello world' // plaintext body - }; - email.from = [{ - address: cloudstoragedaoTest.user - } - ]; - email.to = [{ - address: 'safewithme.testuser@gmail.com' - } - ]; - - cloudstoragedaoTest.emailDao.sendEmail(email, function(err) { - ok(!err, 'Email sent'); - - start(); - }); - }); - - asyncTest("Sync emails from cloud", 1, function() { - cloudstoragedaoTest.emailDao.syncFromCloud('inbox', function(err) { - ok(!err, 'Synced items'); - - start(); - }); - }); - - asyncTest("List emails from cloud", 2, function() { - - cloudstoragedaoTest.emailDao.listItems('inbox', 0, null, function(err, gotten) { - ok(!err); - ok(gotten.length > 0, 'Read synced items'); - - start(); - }); - }); - -}); \ No newline at end of file diff --git a/test/integration/email-dao-test.js b/test/integration/email-dao-test.js index 416b2eb..e22c6c8 100644 --- a/test/integration/email-dao-test.js +++ b/test/integration/email-dao-test.js @@ -27,13 +27,21 @@ define(function(require) { appController.start(function(err) { expect(err).to.not.exist; - appController.fetchOAuthToken(test.passphrase, function(err) { + appController.fetchOAuthToken(function(err, auth) { expect(err).to.not.exist; - emailDao = appController._emailDao; - emailDao.imapLogin(function(err) { + appController.init(auth.emailAddress, auth.token, function(err, availableKeys) { expect(err).to.not.exist; - done(); + + emailDao = appController._emailDao; + emailDao.unlock(availableKeys, test.passphrase, function(err) { + expect(err).to.not.exist; + + emailDao.imapLogin(function(err) { + expect(err).to.not.exist; + done(); + }); + }); }); }); }); diff --git a/test/new-unit/cloudstorage-dao-test.js b/test/new-unit/cloudstorage-dao-test.js deleted file mode 100644 index 739bc2f..0000000 --- a/test/new-unit/cloudstorage-dao-test.js +++ /dev/null @@ -1,20 +0,0 @@ -define(function() { - 'use strict'; - - var expect = chai.expect; - - describe('Cloudstorage DAO unit tests', function() { - - beforeEach(function() {}); - - afterEach(function() {}); - - describe('init', function() { - it('should not explode', function() { - expect(true).to.be.ok; - }); - }); - - }); - -}); \ No newline at end of file diff --git a/test/new-unit/devicestorage-dao-test.js b/test/new-unit/devicestorage-dao-test.js new file mode 100644 index 0000000..f593e39 --- /dev/null +++ b/test/new-unit/devicestorage-dao-test.js @@ -0,0 +1,105 @@ +define(function(require) { + 'use strict'; + + var LawnchairDAO = require('js/dao/lawnchair-dao'), + DeviceStorageDAO = require('js/dao/devicestorage-dao'), + expect = chai.expect; + + var testUser = 'test@example.com'; + + describe('Device Storage DAO unit tests', function() { + + var storageDao, lawnchairDaoStub; + + beforeEach(function() { + lawnchairDaoStub = sinon.createStubInstance(LawnchairDAO); + storageDao = new DeviceStorageDAO(lawnchairDaoStub); + }); + + afterEach(function() {}); + + describe('init', function() { + it('should work', function(done) { + lawnchairDaoStub.init.yields(); + + storageDao.init(testUser, function(err) { + expect(err).to.not.exist; + expect(lawnchairDaoStub.init.calledOnce).to.be.true; + done(); + }); + }); + }); + + describe('store list', function() { + it('should fail', function(done) { + var list = [{}]; + + storageDao.storeList(list, '', function(err) { + expect(err).to.exist; + done(); + }); + }); + + it('should work with empty list', function(done) { + var list = []; + + storageDao.storeList(list, 'email', function(err) { + expect(err).to.not.exist; + done(); + }); + }); + + it('should work', function(done) { + lawnchairDaoStub.batch.yields(); + + var list = [{ + foo: 'bar' + }]; + + storageDao.storeList(list, 'email', function(err) { + expect(err).to.not.exist; + expect(lawnchairDaoStub.batch.calledOnce).to.be.true; + done(); + }); + }); + }); + + describe('remove list', function() { + it('should work', function(done) { + lawnchairDaoStub.removeList.yields(); + + storageDao.removeList('email', function(err) { + expect(err).to.not.exist; + expect(lawnchairDaoStub.removeList.calledOnce).to.be.true; + done(); + }); + }); + }); + + describe('list items', function() { + it('should work', function(done) { + lawnchairDaoStub.list.yields(); + + storageDao.listItems('email', 0, null, function(err) { + expect(err).to.not.exist; + expect(lawnchairDaoStub.list.calledOnce).to.be.true; + done(); + }); + }); + }); + + describe('clear', function() { + it('should work', function(done) { + lawnchairDaoStub.clear.yields(); + + storageDao.clear(function(err) { + expect(err).to.not.exist; + expect(lawnchairDaoStub.clear.calledOnce).to.be.true; + done(); + }); + }); + }); + + }); + +}); \ No newline at end of file diff --git a/test/new-unit/email-dao-test.js b/test/new-unit/email-dao-test.js index 54fb894..27bd70e 100644 --- a/test/new-unit/email-dao-test.js +++ b/test/new-unit/email-dao-test.js @@ -235,7 +235,7 @@ define(function(require) { it('should fail due to bad input', function(done) { emailDao.smtpSend({}, function(err) { expect(smtpClientStub.send.called).to.be.false; - expect(keychainStub.getReveiverPublicKey.called).to.be.false; + expect(keychainStub.getReceiverPublicKey.called).to.be.false; expect(err).to.exist; done(); }); @@ -247,18 +247,18 @@ define(function(require) { }]; emailDao.smtpSend(dummyMail, function(err) { expect(smtpClientStub.send.called).to.be.false; - expect(keychainStub.getReveiverPublicKey.called).to.be.false; + expect(keychainStub.getReceiverPublicKey.called).to.be.false; expect(err).to.exist; done(); }); }); it('should work for a new user', function(done) { - keychainStub.getReveiverPublicKey.yields(null, null); + keychainStub.getReceiverPublicKey.yields(null, null); smtpClientStub.send.yields(); emailDao.smtpSend(dummyMail, function(err) { - expect(keychainStub.getReveiverPublicKey.calledOnce).to.be.true; + expect(keychainStub.getReceiverPublicKey.calledOnce).to.be.true; // expect(smtpClientStub.send.called).to.be.true; // smtpClientStub.send.calledWith(sinon.match(function(o) { // return typeof o.attachments === 'undefined'; @@ -269,7 +269,7 @@ define(function(require) { }); it('should work without attachments', function(done) { - keychainStub.getReveiverPublicKey.yields(null, { + keychainStub.getReceiverPublicKey.yields(null, { _id: "fcf8b4aa-5d09-4089-8b4f-e3bc5091daf3", userId: "safewithme.testuser@gmail.com", publicKey: publicKey @@ -279,7 +279,7 @@ define(function(require) { smtpClientStub.send.yields(); emailDao.smtpSend(dummyMail, function(err) { - expect(keychainStub.getReveiverPublicKey.calledOnce).to.be.true; + expect(keychainStub.getReceiverPublicKey.calledOnce).to.be.true; expect(pgpStub.exportKeys.calledOnce).to.be.true; expect(pgpStub.encrypt.calledOnce).to.be.true; expect(smtpClientStub.send.calledOnce).to.be.true; @@ -297,7 +297,7 @@ define(function(require) { // contentType: 'text/plain', // binStr: 'barbarbarbarbar' // }]; - // keychainStub.getReveiverPublicKey.yields(null, { + // keychainStub.getReceiverPublicKey.yields(null, { // _id: "fcf8b4aa-5d09-4089-8b4f-e3bc5091daf3", // userId: "safewithme.testuser@gmail.com", // publicKey: publicKey @@ -307,7 +307,7 @@ define(function(require) { // smtpClientStub.send.yields(); // emailDao.smtpSend(dummyMail, function(err) { - // expect(keychainStub.getReveiverPublicKey.calledOnce).to.be.true; + // expect(keychainStub.getReceiverPublicKey.calledOnce).to.be.true; // expect(pgpStub.exportKeys.calledOnce).to.be.true; // expect(pgpStub.encrypt.calledOnce).to.be.true; // expect(smtpClientStub.send.calledOnce).to.be.true; @@ -577,7 +577,7 @@ define(function(require) { it('should work', function(done) { dummyMail.body = app.string.cryptPrefix + btoa('asdf') + app.string.cryptSuffix; devicestorageStub.listItems.yields(null, [dummyMail, dummyMail]); - keychainStub.getReveiverPublicKey.yields(null, { + keychainStub.getReceiverPublicKey.yields(null, { _id: "fcf8b4aa-5d09-4089-8b4f-e3bc5091daf3", userId: "safewithme.testuser@gmail.com", publicKey: publicKey @@ -593,7 +593,7 @@ define(function(require) { num: 2 }, function(err, emails) { expect(devicestorageStub.listItems.calledOnce).to.be.true; - expect(keychainStub.getReveiverPublicKey.calledTwice).to.be.true; + expect(keychainStub.getReceiverPublicKey.calledTwice).to.be.true; expect(pgpStub.decrypt.calledTwice).to.be.true; expect(err).to.not.exist; expect(emails.length).to.equal(2); diff --git a/test/new-unit/keychain-dao-test.js b/test/new-unit/keychain-dao-test.js new file mode 100644 index 0000000..c67b80e --- /dev/null +++ b/test/new-unit/keychain-dao-test.js @@ -0,0 +1,302 @@ +define(function(require) { + 'use strict'; + + var LawnchairDAO = require('js/dao/lawnchair-dao'), + PublicKeyDAO = require('js/dao/publickey-dao'), + KeychainDAO = require('js/dao/keychain-dao'), + expect = chai.expect; + + var testUser = 'test@example.com'; + + describe('Keychain DAO unit tests', function() { + + var keychainDao, lawnchairDaoStub, pubkeyDaoStub; + + beforeEach(function() { + lawnchairDaoStub = sinon.createStubInstance(LawnchairDAO); + pubkeyDaoStub = sinon.createStubInstance(PublicKeyDAO); + keychainDao = new KeychainDAO(lawnchairDaoStub, pubkeyDaoStub); + }); + + afterEach(function() {}); + + describe('lookup public key', function() { + it('should fail', function(done) { + keychainDao.lookupPublicKey(undefined, function(err, key) { + expect(err).to.exist; + expect(key).to.not.exist; + done(); + }); + }); + + it('should fail', function(done) { + lawnchairDaoStub.read.yields(42); + + keychainDao.lookupPublicKey('12345', function(err, key) { + expect(err).to.exist; + expect(key).to.not.exist; + expect(lawnchairDaoStub.read.calledOnce).to.be.true; + done(); + }); + }); + + it('should work from local storage', function(done) { + lawnchairDaoStub.read.yields(null, { + _id: '12345', + publicKey: 'asdf' + }); + + keychainDao.lookupPublicKey('12345', function(err, key) { + expect(err).to.not.exist; + expect(key).to.exist; + expect(lawnchairDaoStub.read.calledOnce).to.be.true; + done(); + }); + }); + + it('should work from cloud', function(done) { + lawnchairDaoStub.read.yields(); + pubkeyDaoStub.get.yields(null, { + _id: '12345', + publicKey: 'asdf' + }); + lawnchairDaoStub.persist.yields(); + + keychainDao.lookupPublicKey('12345', function(err, key) { + expect(err).to.not.exist; + expect(key).to.exist; + expect(key._id).to.equal('12345'); + expect(lawnchairDaoStub.read.calledOnce).to.be.true; + expect(pubkeyDaoStub.get.calledOnce).to.be.true; + expect(lawnchairDaoStub.persist.calledOnce).to.be.true; + done(); + }); + }); + }); + + describe('get public keys by id', function() { + it('should fail', function(done) { + keychainDao.getPublicKeys([], function(err, keys) { + expect(err).to.not.exist; + expect(keys.length).to.equal(0); + done(); + }); + }); + + it('should fail', function(done) { + lawnchairDaoStub.read.yields(42); + + var ids = [{ + _id: '12345' + }]; + keychainDao.getPublicKeys(ids, function(err, keys) { + expect(err).to.exist; + expect(keys).to.not.exist; + expect(lawnchairDaoStub.read.calledOnce).to.be.true; + done(); + }); + }); + + it('should work from local storage', function(done) { + lawnchairDaoStub.read.yields(null, { + _id: '12345', + publicKey: 'asdf' + }); + + var ids = [{ + _id: '12345' + }]; + keychainDao.getPublicKeys(ids, function(err, keys) { + expect(err).to.not.exist; + expect(keys.length).to.equal(1); + expect(keys[0]._id).to.equal('12345'); + expect(lawnchairDaoStub.read.calledOnce).to.be.true; + done(); + }); + }); + }); + + describe('get receiver public key', function() { + it('should fail due to error in lawnchair list', function(done) { + lawnchairDaoStub.list.yields(42); + + keychainDao.getReceiverPublicKey(testUser, function(err, key) { + expect(err).to.exist; + expect(key).to.not.exist; + expect(lawnchairDaoStub.list.calledOnce).to.be.true; + done(); + }); + }); + + it('should work from lawnchair list', function(done) { + lawnchairDaoStub.list.yields(null, [{ + _id: '12345', + userId: testUser, + publicKey: 'asdf' + }]); + + keychainDao.getReceiverPublicKey(testUser, function(err, key) { + expect(err).to.not.exist; + expect(key).to.exist; + expect(key._id).to.equal('12345'); + expect(lawnchairDaoStub.list.calledOnce).to.be.true; + done(); + }); + }); + + it('should fail due to error in pubkey dao', function(done) { + lawnchairDaoStub.list.yields(); + pubkeyDaoStub.getByUserId.yields(42); + + keychainDao.getReceiverPublicKey(testUser, function(err, key) { + expect(err).to.exist; + expect(key).to.not.exist; + expect(lawnchairDaoStub.list.calledOnce).to.be.true; + expect(pubkeyDaoStub.getByUserId.calledOnce).to.be.true; + done(); + }); + }); + + it('should work from pubkey dao with empty result', function(done) { + lawnchairDaoStub.list.yields(); + pubkeyDaoStub.getByUserId.yields(); + + keychainDao.getReceiverPublicKey(testUser, function(err, key) { + expect(err).to.not.exist; + expect(key).to.not.exist; + expect(lawnchairDaoStub.list.calledOnce).to.be.true; + expect(pubkeyDaoStub.getByUserId.calledOnce).to.be.true; + done(); + }); + }); + + it('should work from pubkey dao', function(done) { + lawnchairDaoStub.list.yields(); + pubkeyDaoStub.getByUserId.yields(null, { + _id: '12345', + publicKey: 'asdf' + }); + lawnchairDaoStub.persist.yields(); + + keychainDao.getReceiverPublicKey(testUser, function(err, key) { + expect(err).to.not.exist; + expect(key).to.exist; + expect(key._id).to.equal('12345'); + expect(lawnchairDaoStub.list.calledOnce).to.be.true; + expect(pubkeyDaoStub.getByUserId.calledOnce).to.be.true; + expect(lawnchairDaoStub.persist.calledOnce).to.be.true; + done(); + }); + }); + }); + + describe('get user key pair', function() { + it('should work if local keys are already present', function(done) { + lawnchairDaoStub.list.yields(null, [{ + _id: '12345', + userId: testUser, + publicKey: 'asdf' + }]); + lawnchairDaoStub.read.yields(null, { + _id: '12345', + publicKey: 'asdf', + encryptedKey: 'qwer' + }); + + keychainDao.getUserKeyPair(testUser, function(err, keys) { + expect(err).to.not.exist; + expect(keys).to.exist; + expect(keys.publicKey).to.exist; + expect(keys.privateKey).to.exist; + expect(lawnchairDaoStub.list.calledOnce).to.be.true; + expect(lawnchairDaoStub.read.calledTwice).to.be.true; + done(); + }); + }); + + it('should work if local keys are not already present', function(done) { + lawnchairDaoStub.list.yields(); + pubkeyDaoStub.getByUserId.yields(); + + keychainDao.getUserKeyPair(testUser, function(err, keys) { + expect(err).to.not.exist; + expect(keys).to.not.exist; + expect(lawnchairDaoStub.list.calledOnce).to.be.true; + expect(pubkeyDaoStub.getByUserId.calledOnce).to.be.true; + done(); + }); + }); + + it('should work if local keys are not already present', function(done) { + lawnchairDaoStub.list.yields(); + pubkeyDaoStub.getByUserId.yields(null, { + _id: '12345', + publicKey: 'asdf' + }); + lawnchairDaoStub.read.yields(null, { + _id: '12345', + publicKey: 'asdf', + encryptedKey: 'qwer' + }); + + keychainDao.getUserKeyPair(testUser, function(err, keys) { + expect(err).to.not.exist; + expect(keys).to.exist; + expect(keys.publicKey).to.exist; + expect(keys.privateKey).to.exist; + expect(lawnchairDaoStub.list.calledOnce).to.be.true; + expect(lawnchairDaoStub.read.calledTwice).to.be.true; + done(); + }); + }); + }); + + describe('put user keypair', function() { + it('should fail', function(done) { + var keypair = { + publicKey: { + _id: '12345', + userId: testUser, + publicKey: 'asdf' + }, + privateKey: { + _id: '12345', + encryptedKey: 'qwer' + } + }; + + keychainDao.putUserKeyPair(keypair, function(err) { + expect(err).to.exist; + done(); + }); + }); + + it('should work', function(done) { + var keypair = { + publicKey: { + _id: '12345', + userId: testUser, + publicKey: 'asdf' + }, + privateKey: { + _id: '12345', + userId: testUser, + encryptedKey: 'qwer' + } + }; + + lawnchairDaoStub.persist.yields(); + pubkeyDaoStub.put.yields(); + + keychainDao.putUserKeyPair(keypair, function(err) { + expect(err).to.not.exist; + expect(lawnchairDaoStub.persist.calledTwice).to.be.true; + expect(pubkeyDaoStub.put.calledOnce).to.be.true; + done(); + }); + }); + }); + + }); + +}); \ No newline at end of file diff --git a/test/new-unit/lawnchair-dao-test.js b/test/new-unit/lawnchair-dao-test.js new file mode 100644 index 0000000..fd59c46 --- /dev/null +++ b/test/new-unit/lawnchair-dao-test.js @@ -0,0 +1,152 @@ +define(function(require) { + 'use strict'; + + var LawnchairDAO = require('js/dao/lawnchair-dao'), + expect = chai.expect; + + + var dbName = 'lawnchair@test.com'; + + var key = 'type_1'; + var data = { + name: 'testName1', + type: 'testType1' + }; + + var key2 = 'type_2'; + var data2 = { + name: 'testName2', + type: 'testType2' + }; + + describe('Lawnchair DAO unit tests', function() { + var lawnchairDao; + + beforeEach(function(done) { + lawnchairDao = new LawnchairDAO(); + lawnchairDao.init(dbName, function(err) { + expect(err).to.not.exist; + expect(lawnchairDao._db).to.exist; + done(); + }); + }); + + afterEach(function(done) { + lawnchairDao.clear(function(err) { + expect(err).to.not.exist; + done(); + }); + }); + + describe('read', function() { + it('should fail', function(done) { + lawnchairDao.read(undefined, function(err) { + expect(err).to.exist; + done(); + }); + }); + }); + + describe('list', function() { + it('should fail', function(done) { + lawnchairDao.list(undefined, 0, null, function(err) { + expect(err).to.exist; + done(); + }); + }); + }); + + describe('remove list', function() { + it('should fail', function(done) { + lawnchairDao.removeList(undefined, function(err) { + expect(err).to.exist; + done(); + }); + }); + }); + + describe('persist/read/remove', function() { + it('should fail', function(done) { + lawnchairDao.persist(undefined, data, function(err) { + expect(err).to.exist; + done(); + }); + }); + it('should fail', function(done) { + lawnchairDao.persist('1234', undefined, function(err) { + expect(err).to.exist; + done(); + }); + }); + + it('should work', function(done) { + lawnchairDao.persist(key, data, function(err) { + expect(err).to.not.exist; + lawnchairDao.read(key, onRead); + }); + + function onRead(err, fetched) { + expect(err).to.not.exist; + expect(fetched).to.deep.equal(data); + lawnchairDao.remove(key, onRemove); + } + + function onRemove(err) { + expect(err).to.not.exist; + lawnchairDao.read(key, onReadAgain); + } + + function onReadAgain(err, fetched) { + expect(err).to.not.exist; + expect(fetched).to.not.exist; + done(); + } + }); + }); + + describe('batch/list/removeList', function() { + it('should fails', function(done) { + lawnchairDao.batch({}, function(err) { + expect(err).to.exist; + done(); + }); + }); + + it('should work', function(done) { + var list = [{ + key: key, + object: data + }, { + key: key2, + object: data2 + }]; + + lawnchairDao.batch(list, function(err) { + expect(err).to.not.exist; + lawnchairDao.list('type', 0, null, onList); + }); + + function onList(err, fetched) { + expect(err).to.not.exist; + expect(fetched.length).to.equal(2); + expect(fetched[0]).to.deep.equal(list[0].object); + lawnchairDao.removeList('type', onRemoveList); + } + + function onRemoveList(err) { + expect(err).to.not.exist; + lawnchairDao.list('type', 0, null, onListAgain); + } + + function onListAgain(err, fetched) { + expect(err).to.not.exist; + expect(fetched).to.exist; + expect(fetched.length).to.equal(0); + done(); + } + }); + }); + + }); + +}); \ No newline at end of file diff --git a/test/new-unit/main.js b/test/new-unit/main.js index 5ec8f43..0fc6400 100644 --- a/test/new-unit/main.js +++ b/test/new-unit/main.js @@ -22,7 +22,12 @@ function startTests() { [ 'test/new-unit/email-dao-test', 'test/new-unit/app-controller-test', - 'test/new-unit/pgp-test' + 'test/new-unit/pgp-test', + 'test/new-unit/rest-dao-test', + 'test/new-unit/publickey-dao-test', + 'test/new-unit/lawnchair-dao-test', + 'test/new-unit/keychain-dao-test', + 'test/new-unit/devicestorage-dao-test' ], function() { //Tests loaded, run tests mocha.run(); diff --git a/test/new-unit/publickey-dao-test.js b/test/new-unit/publickey-dao-test.js new file mode 100644 index 0000000..c82d730 --- /dev/null +++ b/test/new-unit/publickey-dao-test.js @@ -0,0 +1,130 @@ +define(function(require) { + 'use strict'; + + var RestDAO = require('js/dao/rest-dao'), + PublicKeyDAO = require('js/dao/publickey-dao'), + expect = chai.expect; + + describe('Public Key DAO unit tests', function() { + + var pubkeyDao, restDaoStub; + + beforeEach(function() { + restDaoStub = sinon.createStubInstance(RestDAO); + pubkeyDao = new PublicKeyDAO(restDaoStub); + }); + + afterEach(function() {}); + + describe('get', function() { + it('should fail', function(done) { + restDaoStub.get.yields(42); + + pubkeyDao.get('id', function(err, key) { + expect(err).to.exist; + expect(key).to.not.exist; + expect(restDaoStub.get.calledOnce).to.be.true; + done(); + }); + }); + + it('should work', function(done) { + restDaoStub.get.yields(null, { + _id: '12345', + publicKey: 'asdf' + }); + + pubkeyDao.get('id', function(err, key) { + expect(err).to.not.exist; + expect(key).to.exist; + expect(key._id).to.exist; + expect(key.publicKey).to.exist; + expect(restDaoStub.get.calledOnce).to.be.true; + done(); + }); + }); + }); + + describe('get by userId', function() { + it('should fail', function(done) { + restDaoStub.get.yields(42); + + pubkeyDao.getByUserId('userId', function(err, key) { + expect(err).to.exist; + expect(key).to.not.exist; + expect(restDaoStub.get.calledOnce).to.be.true; + done(); + }); + }); + + it('should react to 404', function(done) { + restDaoStub.get.yields({ + code: 404 + }); + + pubkeyDao.getByUserId('userId', function(err, key) { + expect(err).to.not.exist; + expect(key).to.not.exist; + expect(restDaoStub.get.calledOnce).to.be.true; + done(); + }); + }); + + it('should return empty array', function(done) { + restDaoStub.get.yields(null, []); + + pubkeyDao.getByUserId('userId', function(err, key) { + expect(err).to.not.exist; + expect(key).to.not.exist; + expect(restDaoStub.get.calledOnce).to.be.true; + done(); + }); + }); + + it('should work', function(done) { + restDaoStub.get.yields(null, [{ + _id: '12345', + publicKey: 'asdf' + }]); + + pubkeyDao.getByUserId('userId', function(err, key) { + expect(err).to.not.exist; + expect(key).to.exist; + expect(key._id).to.exist; + expect(key.publicKey).to.exist; + expect(restDaoStub.get.calledOnce).to.be.true; + done(); + }); + }); + }); + + describe('put', function() { + it('should fail', function(done) { + restDaoStub.put.yields(); + + pubkeyDao.put({ + _id: '12345', + publicKey: 'asdf' + }, function(err) { + expect(err).to.not.exist; + expect(restDaoStub.put.calledOnce).to.be.true; + done(); + }); + }); + }); + + describe('remove', function() { + it('should fail', function(done) { + restDaoStub.remove.yields(); + + pubkeyDao.remove('12345', function(err) { + expect(err).to.not.exist; + expect(restDaoStub.remove.calledOnce).to.be.true; + done(); + }); + }); + }); + + }); + +}); \ No newline at end of file diff --git a/test/new-unit/rest-dao-test.js b/test/new-unit/rest-dao-test.js new file mode 100644 index 0000000..9e004e9 --- /dev/null +++ b/test/new-unit/rest-dao-test.js @@ -0,0 +1,117 @@ +define(function(require) { + 'use strict'; + + var RestDAO = require('js/dao/rest-dao'), + $ = require('jquery'), + expect = chai.expect; + + describe('Rest DAO unit tests', function() { + + var restDao; + + beforeEach(function() { + sinon.stub($, 'ajax').yieldsTo('success', { + foo: 'bar' + }); + restDao = new RestDAO(); + }); + + afterEach(function() { + $.ajax.restore(); + }); + + describe('contructor', function() { + it('should set default base uri', function() { + restDao = new RestDAO(); + expect(restDao).to.exist; + expect(restDao._baseUri).to.exist; + }); + + it('should accept default base uri', function() { + var baseUri = 'http://custom.com'; + + restDao = new RestDAO({ + baseUri: baseUri + }); + expect(restDao).to.exist; + expect(restDao._baseUri).to.equal(baseUri); + }); + }); + + describe('get', function() { + it('should fail', function(done) { + $.ajax.restore(); + sinon.stub($, 'ajax').yieldsTo('error', { + status: 500 + }, { + statusText: 'Internal error' + }, {}); + + restDao.get('/asdf', function(err, data) { + expect(err).to.exist; + expect(err.code).to.equal(500); + expect(data).to.not.exist; + done(); + }); + }); + + it('should work', function(done) { + restDao.get('/asdf', function(err, data) { + expect(err).to.not.exist; + expect(data.foo).to.equal('bar'); + done(); + }); + }); + }); + + describe('put', function() { + it('should fail', function(done) { + $.ajax.restore(); + sinon.stub($, 'ajax').yieldsTo('error', { + status: 500 + }, { + statusText: 'Internal error' + }, {}); + + restDao.put('/asdf', {}, function(err) { + expect(err).to.exist; + expect(err.code).to.equal(500); + done(); + }); + }); + + it('should work', function(done) { + restDao.put('/asdf', {}, function(err) { + expect(err).to.not.exist; + done(); + }); + }); + }); + + describe('remove', function() { + it('should fail', function(done) { + $.ajax.restore(); + sinon.stub($, 'ajax').yieldsTo('error', { + status: 500 + }, { + statusText: 'Internal error' + }, {}); + + restDao.remove('/asdf', function(err) { + expect(err).to.exist; + expect(err.code).to.equal(500); + done(); + }); + }); + + it('should work', function(done) { + restDao.remove('/asdf', function(err) { + expect(err).to.not.exist; + done(); + }); + }); + }); + + }); + +}); \ No newline at end of file diff --git a/test/unit/devicestorage-dao-test.js b/test/unit/devicestorage-dao-test.js index d3416b6..627918f 100644 --- a/test/unit/devicestorage-dao-test.js +++ b/test/unit/devicestorage-dao-test.js @@ -1,4 +1,4 @@ -define(['underscore', 'cryptoLib/util', 'js/crypto/crypto', 'js/dao/devicestorage-dao', 'test/test-data'], function(_, util, Crypto, DeviceStorageDAO, testData) { +define(['underscore', 'cryptoLib/util', 'js/crypto/crypto', 'js/dao/devicestorage-dao', 'test/test-data', 'js/dao/lawnchair-dao'], function(_, util, Crypto, DeviceStorageDAO, testData, LawnchairDAO) { 'use strict'; module("DeviceStorage"); @@ -15,7 +15,7 @@ define(['underscore', 'cryptoLib/util', 'js/crypto/crypto', 'js/dao/devicestorag asyncTest("Init", 3, function() { // init dependencies - storage = new DeviceStorageDAO(); + storage = new DeviceStorageDAO(new LawnchairDAO()); storage.init(devicestorageTest.user, function() { ok(storage, 'DeviceStorageDAO'); diff --git a/test/unit/keychain-dao-test.js b/test/unit/keychain-dao-test.js index 8424c3b..c29d8c8 100644 --- a/test/unit/keychain-dao-test.js +++ b/test/unit/keychain-dao-test.js @@ -1,30 +1,29 @@ -define(['js/dao/keychain-dao', 'js/dao/lawnchair-dao'], function(KeychainDAO, jsonDao) { +define(['js/dao/keychain-dao', 'js/dao/lawnchair-dao'], function(KeychainDAO, LawnchairDAO) { 'use strict'; module("Keychain DAO"); - var keychaindaoTest = { - user: 'keychaindao_test@example.com', - password: 'Password', - keySize: 128, - ivSize: 128, - rsaKeySize: 512 - }; + var jsonDao, + keychaindaoTest = { + user: 'keychaindao_test@example.com', + password: 'Password', + keySize: 128, + ivSize: 128, + rsaKeySize: 512 + }; asyncTest("Init", 2, function() { // stubbing - var cloudstorageStub = { - putPublicKey: function(pk, callback) { - callback(); - }, - putPrivateKey: function(prk, callback) { + var pubkeyDaoStub = { + put: function(pk, callback) { callback(); } }; // module instancing - keychaindaoTest.keychainDao = new KeychainDAO(cloudstorageStub); + jsonDao = new LawnchairDAO(); + keychaindaoTest.keychainDao = new KeychainDAO(jsonDao, pubkeyDaoStub); ok(keychaindaoTest.keychainDao); // init and clear db before test @@ -82,7 +81,7 @@ define(['js/dao/keychain-dao', 'js/dao/lawnchair-dao'], function(KeychainDAO, js }); asyncTest("Get User Keypair", 2, function() { - keychaindaoTest.keychainDao.getReveiverPublicKey(keychaindaoTest.user, function(err, pubkey) { + keychaindaoTest.keychainDao.getReceiverPublicKey(keychaindaoTest.user, function(err, pubkey) { ok(!err); ok(pubkey && pubkey.publicKey); diff --git a/test/unit/lawnchair-dao-test.js b/test/unit/lawnchair-dao-test.js deleted file mode 100644 index daabdcd..0000000 --- a/test/unit/lawnchair-dao-test.js +++ /dev/null @@ -1,87 +0,0 @@ -define(['js/dao/lawnchair-dao'], function(jsonDao) { - 'use strict'; - - module("Lawnchair DAO"); - - var lawnchairdaoTest = { - user: 'lawnchair@test.com' - }; - - asyncTest("Init", 2, function() { - // init dependencies - jsonDao.init(lawnchairdaoTest.user, function() { - ok(true, 'init db'); - - // clear db before test - jsonDao.clear(function() { - ok(true, 'cleared db'); - - start(); - }); - }); - }); - - asyncTest("CRUD object literal", 5, function() { - - var key = 'type_1'; - var data = { - name: 'testName1', - type: 'testType1' - }; - - var key2 = 'type_2'; - var data2 = { - name: 'testName2', - type: 'testType2' - }; - - // create - jsonDao.persist(key, data, function() { - - // read - jsonDao.read(key, function(read) { - equal(data.name, read.name, 'Create, Read'); - - // list all - jsonDao.list('type', 0, null, function(list) { - ok(list.length === 1, 'List'); - - // update - var newName = 'updatedName'; - read.name = newName; - jsonDao.persist(key, read, function() { - - // read again - jsonDao.read(key, function(updated) { - equal(updated.name, newName, 'Update'); - - // persist 2nd type - jsonDao.persist(key2, data2, function() { - - // delete all items of 2nd type - jsonDao.removeList(key2, function() { - - jsonDao.list('type', 0, null, function(newList) { - ok(newList.length === 1, 'List'); - - // delete - jsonDao.remove(key, function() { - - // should read empty - jsonDao.read(key, function(lastRead) { - equal(lastRead, undefined, 'Delete'); - - start(); - }); - }); - }); - }); - }); - }); - }); - }); - }); - }); - }); - -}); \ No newline at end of file diff --git a/test/unit/main.js b/test/unit/main.js index fcca131..918d69e 100644 --- a/test/unit/main.js +++ b/test/unit/main.js @@ -10,7 +10,7 @@ require(['../../src/require-config'], function() { require(['js/app-config', 'cordova'], function(app) { // clear session storage of failed tests, so async order is correct after fail & refresh window.sessionStorage.clear(); - window.Worker = undefined; + //window.Worker = undefined; app.config.workerPath = '../../src/js'; @@ -24,7 +24,6 @@ function startTests() { 'test/unit/forge-test', 'test/unit/aes-test', 'test/unit/rsa-test', - 'test/unit/lawnchair-dao-test', 'test/unit/keychain-dao-test', 'test/unit/crypto-test', 'test/unit/devicestorage-dao-test'