Merge remote-tracking branch 'origin/dev/unittest'

This commit is contained in:
Tankred Hase 2013-11-14 19:30:14 +01:00
commit 61b02c8175
17 changed files with 1440 additions and 24 deletions

View File

@ -151,7 +151,7 @@ module.exports = function(grunt) {
expand: true,
flatten: true,
cwd: 'node_modules/',
src: ['requirejs/require.js', 'mocha/mocha.css', 'mocha/mocha.js', 'chai/chai.js', 'sinon/pkg/sinon.js'],
src: ['requirejs/require.js', 'mocha/mocha.css', 'mocha/mocha.js', 'chai/chai.js', 'sinon/pkg/sinon.js', 'angularjs/src/ngMock/angular-mocks.js'],
dest: 'test/lib/'
},
cryptoLib: {

View File

@ -16,6 +16,7 @@
"requirejs": "2.1.8"
},
"devDependencies": {
"angular": "https://github.com/angular/angular.js/tarball/v1.2.0",
"grunt": "0.4.1",
"mocha": "1.13.0",
"phantomjs": "1.9.1-9",

View File

@ -37,7 +37,7 @@ define(function(require) {
$scope.exportKeyFile = function() {
emailDao._crypto.exportKeys(function(err, keys) {
if (err) {
console.error(err);
$scope.onError(err);
return;
}
@ -46,16 +46,14 @@ define(function(require) {
content: keys.publicKeyArmored + keys.privateKeyArmored,
filename: id + '.asc',
contentType: 'text/plain'
}, onSave);
}, function onSave(err) {
if (err) {
$scope.onError(err);
return;
}
});
});
};
function onSave(err) {
if (err) {
console.error(err);
return;
}
}
};
return AccountCtrl;

View File

@ -31,16 +31,16 @@ define(function(require) {
return;
}
setState(states.PROCESSING);
$scope.setState(states.PROCESSING);
setTimeout(function() {
emailDao.unlock({}, passphrase, function(err) {
if (err) {
console.error(err);
setState(states.IDLE, true);
$scope.setState(states.IDLE, true);
return;
}
setState(states.DONE, true);
$scope.setState(states.DONE, true);
});
}, 500);
};
@ -75,13 +75,13 @@ define(function(require) {
$location.path('/desktop');
};
function setState(state, async) {
$scope.setState = function(state, async) {
$scope.state.ui = state;
if (async) {
$scope.$apply();
}
}
};
};
return LoginInitialCtrl;

View File

@ -27,12 +27,6 @@
angular: {
exports: 'angular'
},
openpgp: {
exports: 'window'
},
iscroll: {
exports: 'IScroll'
},
angularRoute: {
exports: 'angular',
deps: ['angular']
@ -41,6 +35,12 @@
exports: 'angular',
deps: ['angular']
},
openpgp: {
exports: 'window'
},
iscroll: {
exports: 'IScroll'
},
lawnchair: {
exports: 'Lawnchair'
},

View File

@ -29,7 +29,7 @@
<h1>Keypair generated</h1>
<p>Your personal keypair has been generated. You can export it (e.g. to a USB flash drive) to setup whiteout on another computer or as a backup.</p>
<button ng-click="exportKeypair()" class="btn" tabindex="4">Export now</button>
<button ng-click="proceed()" class="btn" tabindex="5">Do it later</button>
<button ng-click="forward()" class="btn" tabindex="5">Do it later</button>
</div>
</div><!--/content-->

View File

@ -0,0 +1,115 @@
define(function(require) {
'use strict';
var expect = chai.expect,
angular = require('angular'),
mocks = require('angularMocks'),
AccountCtrl = require('js/controller/account'),
EmailDAO = require('js/dao/email-dao'),
PGP = require('js/crypto/pgp'),
dl = require('js/util/download'),
appController = require('js/app-controller');
describe('Account Controller unit test', function() {
var scope, accountCtrl, origEmailDao, emailDaoMock,
dummyFingerprint, expectedFingerprint,
dummyKeyId, expectedKeyId,
emailAddress,
keySize,
cryptoMock;
beforeEach(function() {
origEmailDao = appController._emailDao;
cryptoMock = sinon.createStubInstance(PGP);
emailDaoMock = sinon.createStubInstance(EmailDAO);
emailDaoMock._crypto = cryptoMock;
appController._emailDao = emailDaoMock;
dummyFingerprint = '3A2D39B4E1404190B8B949DE7D7E99036E712926';
expectedFingerprint = '3A2D 39B4 E140 4190 B8B9 49DE 7D7E 9903 6E71 2926';
dummyKeyId = '9FEB47936E712926';
expectedKeyId = '6E712926';
cryptoMock.getFingerprint.returns(dummyFingerprint);
cryptoMock.getKeyId.returns(dummyKeyId);
emailAddress = 'fred@foo.com';
keySize = 1234;
emailDaoMock._account = {
emailAddress: emailAddress,
asymKeySize: keySize
};
angular.module('accounttest', []);
mocks.module('accounttest');
mocks.inject(function($rootScope, $controller) {
scope = $rootScope.$new();
scope.state = {};
accountCtrl = $controller(AccountCtrl, {
$scope: scope
});
});
});
afterEach(function() {
// restore the module
appController._emailDao = origEmailDao;
});
describe('scope variables', function() {
it('should be set correctly', function() {
expect(scope.eMail).to.equal(emailAddress);
expect(scope.keyId).to.equal(expectedKeyId);
expect(scope.fingerprint).to.equal(expectedFingerprint);
expect(scope.keysize).to.equal(keySize);
});
});
describe('export to key file', function() {
it('should work', function() {
var createDownloadMock = sinon.stub(dl, 'createDownload');
cryptoMock.exportKeys.yields(null, {
publicKeyArmored: 'a',
privateKeyArmored: 'b',
keyId: dummyKeyId
});
createDownloadMock.withArgs(sinon.match(function(arg) {
return arg.content === 'ab' && arg.filename === expectedKeyId + '.asc' && arg.contentType === 'text/plain';
})).yields();
scope.exportKeyFile();
expect(cryptoMock.exportKeys.calledOnce).to.be.true;
expect(dl.createDownload.calledOnce).to.be.true;
dl.createDownload.restore();
});
it('should not work when key export failed', function(done) {
cryptoMock.exportKeys.yields(new Error('asdasd'));
scope.onError = function() {
expect(cryptoMock.exportKeys.calledOnce).to.be.true;
done();
};
scope.exportKeyFile();
});
it('should not work when create download failed', function(done) {
var createDownloadMock = sinon.stub(dl, 'createDownload');
cryptoMock.exportKeys.yields(null, {
publicKeyArmored: 'a',
privateKeyArmored: 'b',
keyId: dummyKeyId
});
createDownloadMock.withArgs(sinon.match(function(arg) {
return arg.content === 'ab' && arg.filename === expectedKeyId + '.asc' && arg.contentType === 'text/plain';
})).yields(new Error('asdasd'));
scope.onError = function() {
expect(cryptoMock.exportKeys.calledOnce).to.be.true;
expect(dl.createDownload.calledOnce).to.be.true;
dl.createDownload.restore();
done();
};
scope.exportKeyFile();
});
});
});
});

View File

@ -0,0 +1,50 @@
define(function(require) {
'use strict';
var expect = chai.expect,
angular = require('angular'),
mocks = require('angularMocks'),
DialogCtrl = require('js/controller/dialog');
describe('Dialog Controller unit test', function() {
var scope, dialogCtrl;
beforeEach(function() {
angular.module('dialogtest', []);
mocks.module('dialogtest');
mocks.inject(function($rootScope, $controller) {
scope = $rootScope.$new();
scope.state = {
dialog: {}
};
dialogCtrl = $controller(DialogCtrl, {
$scope: scope
});
});
});
afterEach(function() {});
describe('confirm', function() {
it('should work', function(done) {
scope.state.dialog.callback = function(confirmed) {
expect(confirmed).to.be.true;
expect(scope.state.dialog.open).to.be.false;
done();
};
scope.confirm(true);
});
});
describe('cancel', function() {
it('should work', function(done) {
scope.state.dialog.callback = function(confirmed) {
expect(confirmed).to.be.false;
expect(scope.state.dialog.open).to.be.false;
done();
};
scope.confirm(false);
});
});
});
});

View File

@ -0,0 +1,192 @@
define(function(require) {
'use strict';
var expect = chai.expect,
angular = require('angular'),
mocks = require('angularMocks'),
LoginCtrl = require('js/controller/login'),
EmailDAO = require('js/dao/email-dao'),
appController = require('js/app-controller');
describe('Login Controller unit test', function() {
var scope, location, ctrl, origEmailDao, emailDaoMock,
emailAddress = 'fred@foo.com',
oauthToken = 'foobarfoobar',
startAppStub,
checkForUpdateStub,
fetchOAuthStub,
initStub;
describe('initialization', function() {
var hasChrome, hasIdentity;
beforeEach(function() {
hasChrome = !!window.chrome;
hasIdentity = !!window.chrome.identity;
window.chrome = window.chrome || {};
window.chrome.identity = window.chrome.identity || {};
// remember original module to restore later, then replace it
origEmailDao = appController._emailDao;
emailDaoMock = sinon.createStubInstance(EmailDAO);
appController._emailDao = emailDaoMock;
});
afterEach(function() {
// restore the browser
if (!hasIdentity) {
delete window.chrome.identity;
}
if (!hasChrome) {
delete window.chrome;
}
// restore the app controller module
appController._emailDao = origEmailDao;
appController.start.restore && appController.start.restore();
appController.checkForUpdate.restore && appController.checkForUpdate.restore();
appController.fetchOAuthToken.restore && appController.fetchOAuthToken.restore();
appController.init.restore && appController.init.restore();
location.path.restore && location.path.restore();
});
it('should forward to existing user login', function(done) {
startAppStub = sinon.stub(appController, 'start');
startAppStub.yields();
checkForUpdateStub = sinon.stub(appController, 'checkForUpdate');
fetchOAuthStub = sinon.stub(appController, 'fetchOAuthToken');
fetchOAuthStub.yields(null, {
emailAddress: emailAddress,
token: oauthToken
});
initStub = sinon.stub(appController, 'init');
initStub.yields(null, {
privateKey: 'a',
publicKey: 'b'
});
emailDaoMock.imapLogin.yields();
angular.module('logintest', []);
mocks.module('logintest');
mocks.inject(function($controller, $rootScope, $location) {
location = $location;
sinon.stub(location, 'path', function(path) {
expect(path).to.equal('/login-existing');
expect(emailDaoMock.imapLogin.calledOnce).to.be.true;
expect(startAppStub.calledOnce).to.be.true;
expect(checkForUpdateStub.calledOnce).to.be.true;
expect(fetchOAuthStub.calledOnce).to.be.true;
done();
});
scope = $rootScope.$new();
scope.state = {};
ctrl = $controller(LoginCtrl, {
$location: location,
$scope: scope
});
});
});
it('should forward to new device login', function(done) {
startAppStub = sinon.stub(appController, 'start');
startAppStub.yields();
checkForUpdateStub = sinon.stub(appController, 'checkForUpdate');
fetchOAuthStub = sinon.stub(appController, 'fetchOAuthToken');
fetchOAuthStub.yields(null, {
emailAddress: emailAddress,
token: oauthToken
});
initStub = sinon.stub(appController, 'init');
initStub.yields(null, {
publicKey: 'b'
});
emailDaoMock.imapLogin.yields();
angular.module('logintest', []);
mocks.module('logintest');
mocks.inject(function($controller, $rootScope, $location) {
location = $location;
sinon.stub(location, 'path', function(path) {
expect(path).to.equal('/login-new-device');
expect(emailDaoMock.imapLogin.calledOnce).to.be.true;
expect(startAppStub.calledOnce).to.be.true;
expect(checkForUpdateStub.calledOnce).to.be.true;
expect(fetchOAuthStub.calledOnce).to.be.true;
done();
});
scope = $rootScope.$new();
scope.state = {};
ctrl = $controller(LoginCtrl, {
$location: location,
$scope: scope
});
});
});
it('should forward to initial login', function(done) {
startAppStub = sinon.stub(appController, 'start');
startAppStub.yields();
checkForUpdateStub = sinon.stub(appController, 'checkForUpdate');
fetchOAuthStub = sinon.stub(appController, 'fetchOAuthToken');
fetchOAuthStub.yields(null, {
emailAddress: emailAddress,
token: oauthToken
});
initStub = sinon.stub(appController, 'init');
initStub.yields();
emailDaoMock.imapLogin.yields();
angular.module('logintest', []);
mocks.module('logintest');
mocks.inject(function($controller, $rootScope, $location) {
location = $location;
sinon.stub(location, 'path', function(path) {
expect(path).to.equal('/login-initial');
expect(emailDaoMock.imapLogin.calledOnce).to.be.true;
expect(startAppStub.calledOnce).to.be.true;
expect(checkForUpdateStub.calledOnce).to.be.true;
expect(fetchOAuthStub.calledOnce).to.be.true;
done();
});
scope = $rootScope.$new();
scope.state = {};
ctrl = $controller(LoginCtrl, {
$location: location,
$scope: scope
});
});
});
it('should fall back to dev mode', function(done) {
var chromeIdentity;
chromeIdentity = window.chrome.identity;
delete window.chrome.identity;
startAppStub = sinon.stub(appController, 'start');
startAppStub.yields();
angular.module('logintest', []);
mocks.module('logintest');
mocks.inject(function($controller, $rootScope, $location) {
location = $location;
sinon.stub(location, 'path', function(path) {
expect(path).to.equal('/desktop');
window.chrome.identity = chromeIdentity;
done();
});
scope = $rootScope.$new();
scope.state = {};
ctrl = $controller(LoginCtrl, {
$location: location,
$scope: scope
});
});
});
});
});
});

View File

@ -0,0 +1,105 @@
define(function(require) {
'use strict';
var expect = chai.expect,
angular = require('angular'),
mocks = require('angularMocks'),
LoginExistingCtrl = require('js/controller/login-existing'),
EmailDAO = require('js/dao/email-dao'),
KeychainDAO = require('js/dao/keychain-dao'),
appController = require('js/app-controller');
describe('Login (existing user) Controller unit test', function() {
var scope, location, ctrl, origEmailDao, emailDaoMock,
emailAddress = 'fred@foo.com',
passphrase = 'asd',
keychainMock;
beforeEach(function() {
// remember original module to restore later
origEmailDao = appController._emailDao;
emailDaoMock = sinon.createStubInstance(EmailDAO);
appController._emailDao = emailDaoMock;
keychainMock = sinon.createStubInstance(KeychainDAO);
emailDaoMock._keychain = keychainMock;
emailDaoMock._account = {
emailAddress: emailAddress,
};
angular.module('loginexistingtest', []);
mocks.module('loginexistingtest');
mocks.inject(function($rootScope, $controller, $location) {
location = $location;
scope = $rootScope.$new();
scope.state = {};
ctrl = $controller(LoginExistingCtrl, {
$scope: scope
});
});
});
afterEach(function() {
// restore the module
appController._emailDao = origEmailDao;
});
describe('initial state', function() {
it('should be well defined', function() {
expect(scope.buttonEnabled).to.be.true;
expect(scope.incorrect).to.be.false;
expect(scope.change).to.exist;
expect(scope.confirmPassphrase).to.exist;
});
});
describe('functionality', function() {
describe('change', function() {
it('should set incorrect to false', function() {
scope.incorrect = true;
scope.change();
expect(scope.incorrect).to.be.false;
});
});
describe('confirm passphrase', function() {
it('should unlock crypto and start', function() {
var keypair = {},
pathSpy = sinon.spy(location, 'path');
scope.passphrase = passphrase;
keychainMock.getUserKeyPair.withArgs(emailAddress).yields(null, keypair);
emailDaoMock.unlock.withArgs(keypair, passphrase).yields(null);
scope.confirmPassphrase();
expect(keychainMock.getUserKeyPair.calledOnce).to.be.true;
expect(emailDaoMock.unlock.calledOnce).to.be.true;
expect(pathSpy.calledOnce).to.be.true;
expect(pathSpy.calledWith('/desktop')).to.be.true;
});
it('should not do anything without passphrase', function() {
var pathSpy = sinon.spy(location, 'path');
scope.passphrase = '';
scope.confirmPassphrase();
expect(pathSpy.callCount).to.equal(0);
});
it('should not work when keypair unavailable', function(done) {
scope.passphrase = passphrase;
keychainMock.getUserKeyPair.withArgs(emailAddress).yields(new Error('asd'));
scope.confirmPassphrase();
expect(keychainMock.getUserKeyPair.calledOnce).to.be.true;
done();
});
});
});
});
});

View File

@ -0,0 +1,171 @@
define(function(require) {
'use strict';
var expect = chai.expect,
angular = require('angular'),
mocks = require('angularMocks'),
LoginInitialCtrl = require('js/controller/login-initial'),
dl = require('js/util/download'),
PGP = require('js/crypto/pgp'),
EmailDAO = require('js/dao/email-dao'),
appController = require('js/app-controller');
describe('Login (initial user) Controller unit test', function() {
var scope, ctrl, location, origEmailDao, emailDaoMock,
emailAddress = 'fred@foo.com',
passphrase = 'asd',
keyId, expectedKeyId,
cryptoMock;
beforeEach(function() {
// remember original module to restore later
origEmailDao = appController._emailDao;
emailDaoMock = sinon.createStubInstance(EmailDAO);
appController._emailDao = emailDaoMock;
keyId = '9FEB47936E712926';
expectedKeyId = '6E712926';
cryptoMock = sinon.createStubInstance(PGP);
emailDaoMock._crypto = cryptoMock;
emailDaoMock._account = {
emailAddress: emailAddress,
};
angular.module('logininitialtest', []);
mocks.module('logininitialtest');
mocks.inject(function($rootScope, $controller, $location) {
scope = $rootScope.$new();
location = $location;
scope.state = {
ui: {}
};
ctrl = $controller(LoginInitialCtrl, {
$scope: scope
});
});
});
afterEach(function() {
// restore the module
appController._emailDao = origEmailDao;
});
describe('initial state', function() {
it('should be well defined', function() {
expect(scope.proceed).to.exist;
expect(scope.exportKeypair).to.exist;
expect(scope.confirmPassphrase).to.exist;
expect(scope.state.ui).to.equal(1);
});
});
describe('confirm passphrase', function() {
var setStateStub;
it('should unlock crypto', function(done) {
scope.state.passphrase = passphrase;
scope.state.confirmation = passphrase;
emailDaoMock.unlock.withArgs({}, passphrase).yields();
setStateStub = sinon.stub(scope, 'setState', function(state) {
if (setStateStub.calledOnce) {
expect(state).to.equal(2);
} else if (setStateStub.calledTwice) {
expect(state).to.equal(4);
expect(emailDaoMock.unlock.calledOnce).to.be.true;
scope.setState.restore();
done();
}
});
scope.confirmPassphrase();
});
it('should not do anything matching passphrases', function() {
scope.state.passphrase = 'a';
scope.state.confirmation = 'b';
scope.confirmPassphrase();
});
it('should not work when keypair generation fails', function(done) {
scope.state.passphrase = passphrase;
scope.state.confirmation = passphrase;
emailDaoMock.unlock.withArgs({}, passphrase).yields(new Error('asd'));
setStateStub = sinon.stub(scope, 'setState', function(state) {
if (setStateStub.calledOnce) {
expect(state).to.equal(2);
} else if (setStateStub.calledTwice) {
expect(state).to.equal(1);
expect(emailDaoMock.unlock.calledOnce).to.be.true;
scope.setState.restore();
done();
}
});
scope.confirmPassphrase();
});
});
describe('proceed', function() {
it('should forward', function() {
var locationSpy = sinon.spy(location, 'path');
scope.proceed();
expect(locationSpy.calledWith('/desktop')).to.be.true;
});
});
describe('export keypair', function() {
it('should work', function() {
var locationSpy, createDownloadMock;
createDownloadMock = sinon.stub(dl, 'createDownload');
cryptoMock.exportKeys.yields(null, {
publicKeyArmored: 'a',
privateKeyArmored: 'b',
keyId: keyId
});
createDownloadMock.withArgs(sinon.match(function(arg) {
return arg.content === 'ab' && arg.filename === expectedKeyId + '.asc' && arg.contentType === 'text/plain';
})).yields();
locationSpy = sinon.spy(location, 'path');
scope.exportKeypair();
expect(cryptoMock.exportKeys.calledOnce).to.be.true;
expect(createDownloadMock.calledOnce).to.be.true;
expect(locationSpy.calledWith('/desktop')).to.be.true;
dl.createDownload.restore();
});
it('should not work when download fails', function() {
var createDownloadMock = sinon.stub(dl, 'createDownload');
cryptoMock.exportKeys.yields(null, {
publicKeyArmored: 'a',
privateKeyArmored: 'b',
keyId: keyId
});
createDownloadMock.yields({
errMsg: 'snafu.'
});
scope.exportKeypair();
expect(cryptoMock.exportKeys.calledOnce).to.be.true;
expect(createDownloadMock.calledOnce).to.be.true;
dl.createDownload.restore();
});
it('should not work when export fails', function() {
cryptoMock.exportKeys.yields(new Error('snafu.'));
scope.exportKeypair();
expect(cryptoMock.exportKeys.calledOnce).to.be.true;
});
});
});
});

View File

@ -0,0 +1,142 @@
define(function(require) {
'use strict';
var expect = chai.expect,
angular = require('angular'),
mocks = require('angularMocks'),
LoginNewDeviceCtrl = require('js/controller/login-new-device'),
KeychainDAO = require('js/dao/keychain-dao'),
EmailDAO = require('js/dao/email-dao'),
appController = require('js/app-controller');
describe('Login (new device) Controller unit test', function() {
var scope, ctrl, origEmailDao, emailDaoMock,
emailAddress = 'fred@foo.com',
passphrase = 'asd',
keyId,
keychainMock;
beforeEach(function() {
// remember original module to restore later
origEmailDao = appController._emailDao;
emailDaoMock = sinon.createStubInstance(EmailDAO);
appController._emailDao = emailDaoMock;
keyId = '9FEB47936E712926';
keychainMock = sinon.createStubInstance(KeychainDAO);
emailDaoMock._keychain = keychainMock;
emailDaoMock._account = {
emailAddress: emailAddress,
};
angular.module('loginnewdevicetest', []);
mocks.module('loginnewdevicetest');
mocks.inject(function($rootScope, $controller) {
scope = $rootScope.$new();
scope.state = {
ui: {}
};
ctrl = $controller(LoginNewDeviceCtrl, {
$scope: scope
});
});
});
afterEach(function() {
// restore the module
appController._emailDao = origEmailDao;
});
describe('initial state', function() {
it('should be well defined', function() {
expect(scope.incorrect).to.be.false;
expect(scope.confirmPassphrase).to.exist;
});
});
describe('confirm passphrase', function() {
it('should unlock crypto', function() {
scope.passphrase = passphrase;
scope.key = {
privateKeyArmored: 'b'
};
keychainMock.getUserKeyPair.withArgs(emailAddress).yields(null, {
_id: keyId,
publicKey: 'a'
});
emailDaoMock.unlock.withArgs(sinon.match.any, passphrase).yields();
keychainMock.putUserKeyPair.yields();
scope.confirmPassphrase();
expect(emailDaoMock.unlock.calledOnce).to.be.true;
expect(keychainMock.getUserKeyPair.calledOnce).to.be.true;
});
it('should not do anything without passphrase', function() {
scope.state.passphrase = '';
scope.confirmPassphrase();
expect(scope.incorrect).to.be.true;
});
it('should not work when keypair upload fails', function() {
scope.passphrase = passphrase;
scope.key = {
privateKeyArmored: 'b'
};
keychainMock.getUserKeyPair.withArgs(emailAddress).yields(null, {
_id: keyId,
publicKey: 'a'
});
emailDaoMock.unlock.withArgs(sinon.match.any, passphrase).yields();
keychainMock.putUserKeyPair.yields({
errMsg: 'yo mamma.'
});
scope.confirmPassphrase();
expect(keychainMock.getUserKeyPair.calledOnce).to.be.true;
expect(emailDaoMock.unlock.calledOnce).to.be.true;
expect(keychainMock.putUserKeyPair.calledOnce).to.be.true;
});
it('should not work when unlock fails', function() {
scope.passphrase = passphrase;
scope.key = {
privateKeyArmored: 'b'
};
keychainMock.getUserKeyPair.withArgs(emailAddress).yields(null, {
_id: keyId,
publicKey: 'a'
});
emailDaoMock.unlock.withArgs(sinon.match.any, passphrase).yields({
errMsg: 'yo mamma.'
});
scope.confirmPassphrase();
expect(scope.incorrect).to.be.true;
expect(keychainMock.getUserKeyPair.calledOnce).to.be.true;
expect(emailDaoMock.unlock.calledOnce).to.be.true;
});
it('should not work when keypair retrieval', function() {
scope.passphrase = passphrase;
keychainMock.getUserKeyPair.withArgs(emailAddress).yields({
errMsg: 'yo mamma.'
});
scope.confirmPassphrase();
expect(keychainMock.getUserKeyPair.calledOnce).to.be.true;
});
});
});
});

View File

@ -0,0 +1,253 @@
define(function(require) {
'use strict';
var expect = chai.expect,
angular = require('angular'),
mocks = require('angularMocks'),
MailListCtrl = require('js/controller/mail-list'),
EmailDAO = require('js/dao/email-dao'),
DeviceStorageDAO = require('js/dao/devicestorage-dao'),
KeychainDAO = require('js/dao/keychain-dao'),
appController = require('js/app-controller');
describe('Mail List controller unit test', function() {
var scope, ctrl, origEmailDao, emailDaoMock, keychainMock, deviceStorageMock,
emailAddress, notificationClickedHandler,
hasChrome, hasNotifications, hasSocket, hasRuntime, hasIdentity;
beforeEach(function() {
hasChrome = !! window.chrome;
hasNotifications = !! window.chrome.notifications;
hasSocket = !! window.chrome.socket;
hasIdentity = !! window.chrome.identity;
if (!hasChrome) {
window.chrome = {};
}
if (!hasNotifications) {
window.chrome.notifications = {
onClicked: {
addListener: function(handler) {
notificationClickedHandler = handler;
}
},
create: function() {}
};
}
if (!hasSocket) {
window.chrome.socket = {};
}
if (!hasRuntime) {
window.chrome.runtime = {
getURL: function() {}
};
}
if (!hasIdentity) {
window.chrome.identity = {};
}
origEmailDao = appController._emailDao;
emailDaoMock = sinon.createStubInstance(EmailDAO);
appController._emailDao = emailDaoMock;
emailAddress = 'fred@foo.com';
emailDaoMock._account = {
emailAddress: emailAddress,
};
keychainMock = sinon.createStubInstance(KeychainDAO);
emailDaoMock._keychain = keychainMock;
deviceStorageMock = sinon.createStubInstance(DeviceStorageDAO);
emailDaoMock._devicestorage = deviceStorageMock;
angular.module('maillisttest', []);
mocks.module('maillisttest');
mocks.inject(function($rootScope, $controller) {
scope = $rootScope.$new();
scope.state = {};
ctrl = $controller(MailListCtrl, {
$scope: scope
});
});
});
afterEach(function() {
if (!hasNotifications) {
delete window.chrome.notifications;
}
if (!hasSocket) {
delete window.chrome.socket;
}
if (!hasRuntime) {
delete window.chrome.runtime;
}
if (!hasChrome) {
delete window.chrome;
}
if (!hasIdentity) {
delete window.chrome.identity;
}
// restore the module
appController._emailDao = origEmailDao;
});
describe('scope variables', function() {
it('should be set correctly', function() {
expect(scope.select).to.exist;
expect(scope.synchronize).to.exist;
expect(scope.remove).to.exist;
expect(scope.state.mailList).to.exist;
expect(emailDaoMock.onIncomingMessage).to.exist;
});
});
describe('push notification', function() {
it('should focus mail and not mark it read', function(done) {
var uid, mail, currentFolder;
uid = 123;
mail = {
uid: uid,
from: [{
address: 'asd'
}],
subject: '[whiteout] asdasd',
unread: true
};
currentFolder = 'asd';
scope.state.nav = {
currentFolder: currentFolder
};
scope.state.read = {
toggle: function() {}
};
scope.emails = [mail];
emailDaoMock.imapMarkMessageRead.withArgs({
folder: currentFolder,
uid: uid
}).yields();
emailDaoMock.unreadMessages.yieldsAsync(null, 10);
emailDaoMock.imapSync.yieldsAsync();
emailDaoMock.listMessages.yieldsAsync(null, [mail]);
window.chrome.notifications.create = function(id, opts) {
expect(id).to.equal('123');
expect(opts.type).to.equal('basic');
expect(opts.message).to.equal('asdasd');
expect(opts.title).to.equal('asd');
expect(scope.state.mailList.selected).to.deep.equal(mail);
expect(emailDaoMock.imapMarkMessageRead.callCount).to.equal(0);
done();
};
emailDaoMock.onIncomingMessage(mail);
});
});
describe('clicking push notification', function() {
it('should focus mail and mark it read', function() {
var uid, mail, currentFolder;
uid = 123;
mail = {
uid: uid,
from: [{
address: 'asd'
}],
subject: '[whiteout] asdasd',
unread: true
};
currentFolder = 'asd';
scope.state.nav = {
currentFolder: currentFolder
};
scope.state.read = {
toggle: function() {}
};
scope.emails = [mail];
emailDaoMock.imapMarkMessageRead.withArgs({
folder: currentFolder,
uid: uid
}).yields();
notificationClickedHandler('123'); // first select, irrelevant
notificationClickedHandler('123');
expect(scope.state.mailList.selected).to.deep.equal(mail);
expect(emailDaoMock.imapMarkMessageRead.callCount).to.be.at.least(1);
});
});
describe('remove', function() {
it('should not delete without a selected mail', function() {
scope.remove();
expect(emailDaoMock.imapDeleteMessage.called).to.be.false;
});
it('should delete the selected mail from trash folder after clicking ok', function() {
var uid, mail, currentFolder;
uid = 123;
mail = {
uid: uid,
from: [{
address: 'asd'
}],
subject: '[whiteout] asdasd',
unread: true
};
scope.emails = [mail];
currentFolder = {
type: 'Trash'
};
scope.folders = [currentFolder];
scope.state.nav = {
currentFolder: currentFolder
};
emailDaoMock.imapDeleteMessage.yields();
scope.remove(mail);
scope.state.dialog.callback(true);
expect(emailDaoMock.imapDeleteMessage.calledOnce).to.be.true;
expect(scope.state.mailList.selected).to.not.exist;
});
it('should move the selected mail to the trash folder', function() {
var uid, mail, currentFolder, trashFolder;
uid = 123;
mail = {
uid: uid,
from: [{
address: 'asd'
}],
subject: '[whiteout] asdasd',
unread: true
};
scope.emails = [mail];
currentFolder = {
type: 'Inbox',
path: 'INBOX'
};
trashFolder = {
type: 'Trash',
path: 'TRASH'
};
scope.folders = [currentFolder, trashFolder];
scope.state.nav = {
currentFolder: currentFolder
};
emailDaoMock.imapMoveMessage.withArgs({
folder: currentFolder,
uid: uid,
destination: trashFolder.path
}).yields();
scope.remove(mail);
expect(emailDaoMock.imapMoveMessage.calledOnce).to.be.true;
expect(scope.state.mailList.selected).to.not.exist;
});
});
});
});

View File

@ -2,9 +2,19 @@
require(['../../src/require-config'], function() {
require.config({
baseUrl: '../../src/lib'
baseUrl: '../../src/lib',
paths: {
angularMocks: '../../test/lib/angular-mocks'
},
shim: {
angularMocks: {
exports: 'angular.mock',
deps: ['angular']
}
}
});
// Start the main app logic.
require(['js/app-config', 'cordova'], function(app) {
window.Worker = undefined; // disable web workers since mocha doesn't support them
@ -27,7 +37,17 @@ function startTests() {
'test/new-unit/publickey-dao-test',
'test/new-unit/lawnchair-dao-test',
'test/new-unit/keychain-dao-test',
'test/new-unit/devicestorage-dao-test'
'test/new-unit/devicestorage-dao-test',
'test/new-unit/dialog-ctrl-test',
'test/new-unit/account-ctrl-test',
'test/new-unit/login-existing-ctrl-test',
'test/new-unit/login-initial-ctrl-test',
'test/new-unit/login-new-device-ctrl-test',
'test/new-unit/login-ctrl-test',
'test/new-unit/read-ctrl-test',
'test/new-unit/navigation-ctrl-test',
'test/new-unit/mail-list-ctrl-test',
'test/new-unit/write-ctrl-test'
], function() {
//Tests loaded, run tests
mocha.run();

View File

@ -0,0 +1,120 @@
define(function(require) {
'use strict';
var expect = chai.expect,
angular = require('angular'),
mocks = require('angularMocks'),
NavigationCtrl = require('js/controller/navigation'),
EmailDAO = require('js/dao/email-dao'),
DeviceStorageDAO = require('js/dao/devicestorage-dao'),
appController = require('js/app-controller');
describe('Navigation Controller unit test', function() {
var scope, ctrl, origEmailDao, emailDaoMock, deviceStorageMock, tempChrome;
beforeEach(function() {
if (window.chrome.identity) {
tempChrome = window.chrome.identity;
delete window.chrome.identity;
}
// remember original module to restore later
origEmailDao = appController._emailDao;
emailDaoMock = sinon.createStubInstance(EmailDAO);
appController._emailDao = emailDaoMock;
deviceStorageMock = sinon.createStubInstance(DeviceStorageDAO);
appController._emailDao._devicestorage = deviceStorageMock;
angular.module('navigationtest', []);
mocks.module('navigationtest');
mocks.inject(function($rootScope, $controller) {
scope = $rootScope.$new();
scope.state = {};
ctrl = $controller(NavigationCtrl, {
$scope: scope
});
});
});
afterEach(function() {
// restore the module
appController._emailDao = origEmailDao;
if (tempChrome) {
window.chrome.identity = tempChrome;
}
});
describe('initial state', function() {
it('should be well defined', function() {
expect(scope.state).to.exist;
expect(scope.state.nav.open).to.be.false;
expect(scope.folders).to.not.be.empty;
expect(scope.onError).to.exist;
expect(scope.openFolder).to.exist;
expect(scope.emptyOutbox).to.exist;
});
});
describe('open/close nav view', function() {
it('should open/close', function() {
expect(scope.state.nav.open).to.be.false;
scope.state.nav.toggle(true);
expect(scope.state.nav.open).to.be.true;
scope.state.nav.toggle(false);
expect(scope.state.nav.open).to.be.false;
});
});
describe('open folder', function() {
it('should work', function() {
scope.state.nav.open = true;
scope.openFolder('asd');
expect(scope.state.nav.currentFolder).to.equal('asd');
expect(scope.state.nav.open).to.be.false;
});
});
describe('empty outbox', function() {
it('should work', function() {
deviceStorageMock.listItems.yields(null, [{
id: 1
}, {
id: 2
}, {
id: 3
}]);
emailDaoMock.smtpSend.yields();
deviceStorageMock.removeList.yields();
scope.emptyOutbox();
expect(deviceStorageMock.listItems.calledOnce).to.be.true;
expect(emailDaoMock.smtpSend.calledThrice).to.be.true;
expect(deviceStorageMock.removeList.calledThrice).to.be.true;
});
it('should not work when device storage errors', function() {
deviceStorageMock.listItems.yields({errMsg: 'error'});
scope.emptyOutbox();
expect(deviceStorageMock.listItems.calledOnce).to.be.true;
});
it('should not work when smtp send fails', function() {
deviceStorageMock.listItems.yields(null, [{
id: 1
}]);
emailDaoMock.smtpSend.yields({errMsg: 'error'});
scope.emptyOutbox();
expect(deviceStorageMock.listItems.calledOnce).to.be.true;
expect(emailDaoMock.smtpSend.calledOnce).to.be.true;
});
});
});
});

View File

@ -0,0 +1,44 @@
define(function(require) {
'use strict';
var expect = chai.expect,
angular = require('angular'),
mocks = require('angularMocks'),
ReadCtrl = require('js/controller/read');
describe('Read Controller unit test', function() {
var scope, ctrl;
beforeEach(function() {
angular.module('readtest', []);
mocks.module('readtest');
mocks.inject(function($rootScope, $controller) {
scope = $rootScope.$new();
scope.state = {};
ctrl = $controller(ReadCtrl, {
$scope: scope
});
});
});
afterEach(function() {});
describe('scope variables', function() {
it('should be set correctly', function() {
expect(scope.state.read).to.exist;
expect(scope.state.read.open).to.be.false;
expect(scope.state.read.toggle).to.exist;
});
});
describe('open/close read view', function() {
it('should open/close', function() {
expect(scope.state.read.open).to.be.false;
scope.state.read.toggle(true);
expect(scope.state.read.open).to.be.true;
scope.state.read.toggle(false);
expect(scope.state.read.open).to.be.false;
});
});
});
});

View File

@ -0,0 +1,205 @@
define(function(require) {
'use strict';
var expect = chai.expect,
angular = require('angular'),
mocks = require('angularMocks'),
WriteCtrl = require('js/controller/write'),
EmailDAO = require('js/dao/email-dao'),
DeviceStorageDAO = require('js/dao/devicestorage-dao'),
KeychainDAO = require('js/dao/keychain-dao'),
appController = require('js/app-controller');
describe('Write controller unit test', function() {
var ctrl, scope, origEmailDao, emailDaoMock, keychainMock, deviceStorageMock, emailAddress;
beforeEach(function() {
origEmailDao = appController._emailDao;
emailDaoMock = sinon.createStubInstance(EmailDAO);
appController._emailDao = emailDaoMock;
emailAddress = 'fred@foo.com';
emailDaoMock._account = {
emailAddress: emailAddress,
};
keychainMock = sinon.createStubInstance(KeychainDAO);
emailDaoMock._keychain = keychainMock;
deviceStorageMock = sinon.createStubInstance(DeviceStorageDAO);
emailDaoMock._devicestorage = deviceStorageMock;
angular.module('writetest', []);
mocks.module('writetest');
mocks.inject(function($rootScope, $controller) {
scope = $rootScope.$new();
scope.state = {};
ctrl = $controller(WriteCtrl, {
$scope: scope
});
});
});
afterEach(function() {
// restore the module
appController._emailDao = origEmailDao;
});
describe('scope variables', function() {
it('should be set correctly', function() {
expect(scope.state.writer).to.exist;
expect(scope.state.writer.open).to.be.false;
expect(scope.state.writer.write).to.exist;
expect(scope.state.writer.close).to.exist;
expect(scope.verifyTo).to.exist;
expect(scope.updatePreview).to.exist;
expect(scope.sendToOutbox).to.exist;
});
});
describe('close', function() {
it('should close the writer', function() {
scope.state.writer.open = true;
scope.state.writer.close();
expect(scope.state.writer.open).to.be.false;
});
});
describe('write', function() {
it('should prepare write view', function() {
var verifyToMock = sinon.stub(scope, 'verifyTo');
scope.state.writer.write();
expect(scope.writerTitle).to.equal('New email');
expect(scope.to).to.equal('');
expect(scope.subject).to.equal('');
expect(scope.body).to.equal('');
expect(scope.ciphertextPreview).to.equal('');
expect(verifyToMock.calledOnce).to.be.true;
scope.verifyTo.restore();
});
it('should prefill write view for response', function() {
var verifyToMock = sinon.stub(scope, 'verifyTo'),
address = 'pity@dafool',
subject = 'Ermahgerd!',
body = 'so much body!',
re = {
from: [{
address: address
}],
subject: subject,
sentDate: new Date(),
body: body
};
scope.state.writer.write(re);
expect(scope.writerTitle).to.equal('Reply');
expect(scope.to).to.equal(address);
expect(scope.subject).to.equal('Re: ' + subject);
expect(scope.body).to.contain(body);
expect(scope.ciphertextPreview).to.not.be.empty;
expect(verifyToMock.calledOnce).to.be.true;
scope.verifyTo.restore();
});
it('should prevent markup injection', function() {
var address = 'pity@dafool',
subject = 'Ermahgerd!',
body = '<div>markup</div><div>moreMarkup<div>',
re = {
from: [{
address: address
}],
subject: subject,
sentDate: new Date(),
body: body,
html: false
};
sinon.stub(scope, 'verifyTo');
scope.state.writer.write(re);
expect(scope.body).to.contain('<br>markupmoreMarkup');
scope.verifyTo.restore();
});
});
describe('verifyTo', function() {
it('should verify the recipient as secure', function() {
var id = scope.to = 'pity@da.fool';
keychainMock.getReceiverPublicKey.withArgs(id).yields(null, {
userId: id
});
scope.verifyTo();
expect(scope.toSecure).to.be.true;
expect(scope.sendBtnText).to.equal('Send securely');
});
it('should verify the recipient as not secure', function() {
var id = scope.to = 'pity@da.fool';
keychainMock.getReceiverPublicKey.withArgs(id).yields({
errMsg: '404 not found yadda yadda'
});
scope.verifyTo();
expect(scope.toSecure).to.be.false;
expect(scope.sendBtnText).to.equal('Invite & send securely');
});
it('should reset display if there is no recipient', function() {
scope.to = undefined;
scope.verifyTo();
});
});
describe('send to outbox', function() {
it('should work', function(done) {
scope.state.writer.open = true;
scope.to = 'a, b, c';
scope.body = 'asd';
scope.subject = 'yaddablabla';
deviceStorageMock.storeList.withArgs(sinon.match(function(mail) {
return mail[0].from[0].address === emailAddress && mail[0].to.length === 3;
})).yieldsAsync();
scope.emptyOutbox = function() {
expect(scope.state.writer.open).to.be.false;
expect(deviceStorageMock.storeList.calledOnce).to.be.true;
done();
};
scope.sendToOutbox();
});
it('should not work and not close the write view', function() {
scope.state.writer.open = true;
scope.to = 'a, b, c';
scope.body = 'asd';
scope.subject = 'yaddablabla';
deviceStorageMock.storeList.withArgs(sinon.match(function(mail) {
return mail[0].from[0].address === emailAddress && mail[0].to.length === 3;
})).yields({
errMsg: 'snafu'
});
scope.sendToOutbox();
expect(scope.state.writer.open).to.be.true;
expect(deviceStorageMock.storeList.calledOnce).to.be.true;
});
});
});
});