From c195ba2630a424791a2e0877907daba5a86077ad Mon Sep 17 00:00:00 2001 From: Scott Nonnenberg Date: Fri, 1 Dec 2017 13:35:39 -0800 Subject: [PATCH] Save prekeys optimistically, track confirms, new clean behavior (#1846) * Re-enable libtextsecure unit tests, get passing, run in CI * Save prekeys optimistically, track confirmed, new clean behavior * Eliminate potential conflicts when rotating on startup * Remove last symlink: get libtextsecure tests running on windows --- Gruntfile.js | 135 ++++++++------- config/test-lib.json | 5 + js/background.js | 8 +- js/libtextsecure.js | 110 ++++++++---- js/rotate_signed_prekey_listener.js | 11 +- js/signal_protocol_store.js | 11 +- libtextsecure/account_manager.js | 110 ++++++++---- libtextsecure/test/_test.js | 3 + libtextsecure/test/account_manager_test.js | 156 ++++++++++++++++++ libtextsecure/test/index.html | 13 +- libtextsecure/test/protos | 1 - libtextsecure/test/test.js | 3 + .../test/websocket-resources_test.js | 6 +- main.js | 8 +- 14 files changed, 433 insertions(+), 147 deletions(-) create mode 100644 config/test-lib.json create mode 100644 libtextsecure/test/account_manager_test.js delete mode 120000 libtextsecure/test/protos diff --git a/Gruntfile.js b/Gruntfile.js index 1458fbd6a..524b2b706 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -335,71 +335,82 @@ module.exports = function(grunt) { }); }); - grunt.registerTask('unit-tests', 'Run unit tests inside Electron', function() { - var environment = grunt.option('env') || 'test'; - var done = this.async(); - var failure; - - var Application = require('spectron').Application; - var electronBinary = process.platform === 'win32' ? 'electron.cmd' : 'electron'; - var app = new Application({ - path: path.join(__dirname, 'node_modules', '.bin', electronBinary), - args: [path.join(__dirname, 'main.js')], - env: { - NODE_ENV: environment - } - }); - - function getMochaResults() { - return window.mochaResults; + function runTests(environment, cb) { + var failure; + var Application = require('spectron').Application; + var electronBinary = process.platform === 'win32' ? 'electron.cmd' : 'electron'; + var app = new Application({ + path: path.join(__dirname, 'node_modules', '.bin', electronBinary), + args: [path.join(__dirname, 'main.js')], + env: { + NODE_ENV: environment } + }); - app.start().then(function() { - return app.client.waitUntil(function() { - return app.client.execute(getMochaResults).then(function(data) { - return Boolean(data.value); - }); - }, 10000, 'Expected to find window.mochaResults set!'); - }).then(function() { - return app.client.execute(getMochaResults); - }).then(function(data) { - var results = data.value; - if (results.failures > 0) { - console.error(results.reports); - failure = function() { - grunt.fail.fatal('Found ' + results.failures + ' failing unit tests.'); - }; - return app.client.log('browser'); - } else { - grunt.log.ok(results.passes + ' tests passed.'); - } - }).then(function(logs) { - if (logs) { - console.error(); - console.error('Because tests failed, printing browser logs:'); - console.error(logs); - } - }).catch(function (error) { + function getMochaResults() { + return window.mochaResults; + } + + app.start().then(function() { + return app.client.waitUntil(function() { + return app.client.execute(getMochaResults).then(function(data) { + return Boolean(data.value); + }); + }, 10000, 'Expected to find window.mochaResults set!'); + }).then(function() { + return app.client.execute(getMochaResults); + }).then(function(data) { + var results = data.value; + if (results.failures > 0) { + console.error(results.reports); failure = function() { - grunt.fail.fatal('Something went wrong: ' + error.message + ' ' + error.stack); + grunt.fail.fatal('Found ' + results.failures + ' failing unit tests.'); }; - }).then(function () { - // We need to use the failure variable and this early stop to clean up before - // shutting down. Grunt's fail methods are the only way to set the return value, - // but they shut the process down immediately! - return app.stop(); - }).then(function() { - if (failure) { - failure(); - } - done(); - }).catch(function (error) { - console.error('Second-level error:', error.message, error.stack); - if (failure) { - failure(); - } - done(); - }); + return app.client.log('browser'); + } else { + grunt.log.ok(results.passes + ' tests passed.'); + } + }).then(function(logs) { + if (logs) { + console.error(); + console.error('Because tests failed, printing browser logs:'); + console.error(logs); + } + }).catch(function (error) { + failure = function() { + grunt.fail.fatal('Something went wrong: ' + error.message + ' ' + error.stack); + }; + }).then(function () { + // We need to use the failure variable and this early stop to clean up before + // shutting down. Grunt's fail methods are the only way to set the return value, + // but they shut the process down immediately! + return app.stop(); + }).then(function() { + if (failure) { + failure(); + } + cb(); + }).catch(function (error) { + console.error('Second-level error:', error.message, error.stack); + if (failure) { + failure(); + } + cb(); + }); + } + + grunt.registerTask('unit-tests', 'Run unit tests w/Electron', function() { + var environment = grunt.option('env') || 'test'; + var done = this.async(); + + runTests(environment, done); + }); + + grunt.registerTask('lib-unit-tests', 'Run libtextsecure unit tests w/Electron', function() { + var environment = grunt.option('env') || 'test-lib'; + var done = this.async(); + + runTests(environment, done); }); grunt.registerMultiTask('test-release', 'Test packaged releases', function() { @@ -473,7 +484,7 @@ module.exports = function(grunt) { grunt.registerTask('tx', ['exec:tx-pull', 'locale-patch']); grunt.registerTask('dev', ['default', 'watch']); - grunt.registerTask('test', ['jshint', 'jscs', 'unit-tests']); + grunt.registerTask('test', ['jshint', 'jscs', 'unit-tests', 'lib-unit-tests']); grunt.registerTask('copy_dist', ['gitinfo', 'copy:res', 'copy:src']); grunt.registerTask('date', ['gitinfo', 'getExpireTime']); grunt.registerTask('prep-release', ['gitinfo', 'clean-release', 'fetch-release']); diff --git a/config/test-lib.json b/config/test-lib.json new file mode 100644 index 000000000..d0e5c25d8 --- /dev/null +++ b/config/test-lib.json @@ -0,0 +1,5 @@ +{ + "storageProfile": "test", + "disableAutoUpdate": true, + "openDevTools": false +} diff --git a/js/background.js b/js/background.js index b0aa1268c..a7c852792 100644 --- a/js/background.js +++ b/js/background.js @@ -79,11 +79,11 @@ function start() { var currentVersion = window.config.version; var lastVersion = storage.get('version'); + var newVersion = !lastVersion || currentVersion !== lastVersion; storage.put('version', currentVersion); - if (!lastVersion || currentVersion !== lastVersion) { + if (newVersion) { console.log('New version detected:', currentVersion); - getAccountManager().rotateSignedPreKey(); } window.dispatchEvent(new Event('storage_ready')); @@ -91,7 +91,7 @@ console.log('listening for registration events'); Whisper.events.on('registration_done', function() { console.log('handling registration event'); - Whisper.RotateSignedPreKeyListener.init(Whisper.events); + Whisper.RotateSignedPreKeyListener.init(Whisper.events, newVersion); connect(true); }); @@ -104,7 +104,7 @@ console.log('Import was interrupted, showing import error screen'); appView.openImporter(); } else if (Whisper.Registration.everDone()) { - Whisper.RotateSignedPreKeyListener.init(Whisper.events); + Whisper.RotateSignedPreKeyListener.init(Whisper.events, newVersion); connect(); appView.openInbox({ initialLoadComplete: initialLoadComplete diff --git a/js/libtextsecure.js b/js/libtextsecure.js index 220bfb818..930589e48 100644 --- a/js/libtextsecure.js +++ b/js/libtextsecure.js @@ -37988,26 +37988,35 @@ var TextSecureServer = (function() { rotateSignedPreKey: function() { return this.queueTask(function() { var signedKeyId = textsecure.storage.get('signedKeyId', 1); - if (typeof signedKeyId != 'number') { throw new Error('Invalid signedKeyId'); } + var store = textsecure.storage.protocol; var server = this.server; var cleanSignedPreKeys = this.cleanSignedPreKeys; + return store.getIdentityKeyPair().then(function(identityKey) { return libsignal.KeyHelper.generateSignedPreKey(identityKey, signedKeyId); }).then(function(res) { - return server.setSignedPreKey({ - keyId : res.keyId, - publicKey : res.keyPair.pubKey, - signature : res.signature + console.log('Saving new signed prekey', res.keyId); + return Promise.all([ + textsecure.storage.put('signedKeyId', signedKeyId + 1), + store.storeSignedPreKey(res.keyId, res.keyPair), + server.setSignedPreKey({ + keyId : res.keyId, + publicKey : res.keyPair.pubKey, + signature : res.signature + }), + ]).then(function() { + var confirmed = true; + console.log('Confirming new signed prekey', res.keyId); + return Promise.all([ + textsecure.storage.remove('signedKeyRotationRejected'), + store.storeSignedPreKey(res.keyId, res.keyPair, confirmed), + ]); }).then(function() { - textsecure.storage.put('signedKeyId', signedKeyId + 1); - textsecure.storage.remove('signedKeyRotationRejected'); - return store.storeSignedPreKey(res.keyId, res.keyPair).then(function() { - return cleanSignedPreKeys(); - }); + return cleanSignedPreKeys(); }); }).catch(function(e) { console.log( @@ -38030,35 +38039,72 @@ var TextSecureServer = (function() { return this.pending = this.pending.then(taskWithTimeout, taskWithTimeout); }, cleanSignedPreKeys: function() { - var nextSignedKeyId = textsecure.storage.get('signedKeyId'); - if (typeof nextSignedKeyId != 'number') { - return Promise.resolve(); - } - var activeSignedPreKeyId = nextSignedKeyId - 1; - + var MINIMUM_KEYS = 3; var store = textsecure.storage.protocol; - return store.loadSignedPreKeys().then(function(allRecords) { - var oldRecords = allRecords.filter(function(record) { - return record.keyId !== activeSignedPreKeyId; - }); - oldRecords.sort(function(a, b) { + return store.loadSignedPreKeys().then(function(allKeys) { + allKeys.sort(function(a, b) { return (a.created_at || 0) - (b.created_at || 0); }); + allKeys.reverse(); // we want the most recent first + var confirmed = allKeys.filter(function(key) { + return key.confirmed; + }); + var unconfirmed = allKeys.filter(function(key) { + return !key.confirmed; + }); + + var recent = allKeys[0] ? allKeys[0].keyId : 'none'; + var recentConfirmed = confirmed[0] ? confirmed[0].keyId : 'none'; + console.log('Most recent signed key: ' + recent); + console.log('Most recent confirmed signed key: ' + recentConfirmed); + console.log( + 'Total signed key count:', + allKeys.length, + '-', + confirmed.length, + 'confirmed' + ); + + var confirmedCount = confirmed.length; + + // Keep MINIMUM_KEYS confirmed keys, then drop if older than a week + confirmed = confirmed.forEach(function(key, index) { + if (index < MINIMUM_KEYS) { + return; + } + var created_at = key.created_at || 0; + var age = Date.now() - created_at; + if (age > ARCHIVE_AGE) { + console.log( + 'Removing confirmed signed prekey:', + key.keyId, + 'with timestamp:', + created_at + ); + store.removeSignedPreKey(key.keyId); + confirmedCount--; + } + }); - console.log("Active signed prekey: " + activeSignedPreKeyId); - console.log("Old signed prekey record count: " + oldRecords.length); + var stillNeeded = MINIMUM_KEYS - confirmedCount; - oldRecords.forEach(function(oldRecord) { - if ( oldRecord.keyId >= activeSignedPreKeyId - 3 ) { - // keep at least the last 3 signed keys + // If we still don't have enough total keys, we keep as many unconfirmed + // keys as necessary. If not necessary, and over a week old, we drop. + unconfirmed.forEach(function(key, index) { + if (index < stillNeeded) { return; } - var created_at = oldRecord.created_at || 0; - var archiveDuration = Date.now() - created_at; - if (archiveDuration > ARCHIVE_AGE) { - console.log("Removing signed prekey record:", - oldRecord.keyId, "with timestamp:", created_at); - store.removeSignedPreKey(oldRecord.keyId); + + var created_at = key.created_at || 0; + var age = Date.now() - created_at; + if (age > ARCHIVE_AGE) { + console.log( + 'Removing unconfirmed signed prekey:', + key.keyId, + 'with timestamp:', + created_at + ); + store.removeSignedPreKey(key.keyId); } }); }); diff --git a/js/rotate_signed_prekey_listener.js b/js/rotate_signed_prekey_listener.js index a52ece8e3..e353e71a7 100644 --- a/js/rotate_signed_prekey_listener.js +++ b/js/rotate_signed_prekey_listener.js @@ -58,20 +58,19 @@ var initComplete; Whisper.RotateSignedPreKeyListener = { - init: function(events) { + init: function(events, newVersion) { if (initComplete) { console.log('Rotate signed prekey listener: Already initialized'); return; } initComplete = true; - if (Whisper.Registration.isDone()) { + if (newVersion) { + runWhenOnline(); + } else { setTimeoutForNextRun(); } - events.on('registration_done', function() { - scheduleNextRotation(); - setTimeoutForNextRun(); - }); + events.on('timetravel', function() { if (Whisper.Registration.isDone()) { setTimeoutForNextRun(); diff --git a/js/signal_protocol_store.js b/js/signal_protocol_store.js index 3aacab0ea..f9a5403df 100644 --- a/js/signal_protocol_store.js +++ b/js/signal_protocol_store.js @@ -240,7 +240,8 @@ pubKey : prekey.get('publicKey'), privKey : prekey.get('privateKey'), created_at : prekey.get('created_at'), - keyId : prekey.get('id') + keyId : prekey.get('id'), + confirmed : prekey.get('confirmed'), }); }).fail(function() { console.log('Failed to load signed prekey:', keyId); @@ -260,18 +261,20 @@ pubKey : prekey.get('publicKey'), privKey : prekey.get('privateKey'), created_at : prekey.get('created_at'), - keyId : prekey.get('id') + keyId : prekey.get('id'), + confirmed : prekey.get('confirmed'), }; })); }); }); }, - storeSignedPreKey: function(keyId, keyPair) { + storeSignedPreKey: function(keyId, keyPair, confirmed) { var prekey = new SignedPreKey({ id : keyId, publicKey : keyPair.pubKey, privateKey : keyPair.privKey, - created_at : Date.now() + created_at : Date.now(), + confirmed : Boolean(confirmed), }); return new Promise(function(resolve) { prekey.save().always(function() { diff --git a/libtextsecure/account_manager.js b/libtextsecure/account_manager.js index 320480915..69f7c2c8a 100644 --- a/libtextsecure/account_manager.js +++ b/libtextsecure/account_manager.js @@ -118,26 +118,35 @@ rotateSignedPreKey: function() { return this.queueTask(function() { var signedKeyId = textsecure.storage.get('signedKeyId', 1); - if (typeof signedKeyId != 'number') { throw new Error('Invalid signedKeyId'); } + var store = textsecure.storage.protocol; var server = this.server; var cleanSignedPreKeys = this.cleanSignedPreKeys; + return store.getIdentityKeyPair().then(function(identityKey) { return libsignal.KeyHelper.generateSignedPreKey(identityKey, signedKeyId); }).then(function(res) { - return server.setSignedPreKey({ - keyId : res.keyId, - publicKey : res.keyPair.pubKey, - signature : res.signature + console.log('Saving new signed prekey', res.keyId); + return Promise.all([ + textsecure.storage.put('signedKeyId', signedKeyId + 1), + store.storeSignedPreKey(res.keyId, res.keyPair), + server.setSignedPreKey({ + keyId : res.keyId, + publicKey : res.keyPair.pubKey, + signature : res.signature + }), + ]).then(function() { + var confirmed = true; + console.log('Confirming new signed prekey', res.keyId); + return Promise.all([ + textsecure.storage.remove('signedKeyRotationRejected'), + store.storeSignedPreKey(res.keyId, res.keyPair, confirmed), + ]); }).then(function() { - textsecure.storage.put('signedKeyId', signedKeyId + 1); - textsecure.storage.remove('signedKeyRotationRejected'); - return store.storeSignedPreKey(res.keyId, res.keyPair).then(function() { - return cleanSignedPreKeys(); - }); + return cleanSignedPreKeys(); }); }).catch(function(e) { console.log( @@ -160,35 +169,72 @@ return this.pending = this.pending.then(taskWithTimeout, taskWithTimeout); }, cleanSignedPreKeys: function() { - var nextSignedKeyId = textsecure.storage.get('signedKeyId'); - if (typeof nextSignedKeyId != 'number') { - return Promise.resolve(); - } - var activeSignedPreKeyId = nextSignedKeyId - 1; - + var MINIMUM_KEYS = 3; var store = textsecure.storage.protocol; - return store.loadSignedPreKeys().then(function(allRecords) { - var oldRecords = allRecords.filter(function(record) { - return record.keyId !== activeSignedPreKeyId; - }); - oldRecords.sort(function(a, b) { + return store.loadSignedPreKeys().then(function(allKeys) { + allKeys.sort(function(a, b) { return (a.created_at || 0) - (b.created_at || 0); }); + allKeys.reverse(); // we want the most recent first + var confirmed = allKeys.filter(function(key) { + return key.confirmed; + }); + var unconfirmed = allKeys.filter(function(key) { + return !key.confirmed; + }); + + var recent = allKeys[0] ? allKeys[0].keyId : 'none'; + var recentConfirmed = confirmed[0] ? confirmed[0].keyId : 'none'; + console.log('Most recent signed key: ' + recent); + console.log('Most recent confirmed signed key: ' + recentConfirmed); + console.log( + 'Total signed key count:', + allKeys.length, + '-', + confirmed.length, + 'confirmed' + ); + + var confirmedCount = confirmed.length; + + // Keep MINIMUM_KEYS confirmed keys, then drop if older than a week + confirmed = confirmed.forEach(function(key, index) { + if (index < MINIMUM_KEYS) { + return; + } + var created_at = key.created_at || 0; + var age = Date.now() - created_at; + if (age > ARCHIVE_AGE) { + console.log( + 'Removing confirmed signed prekey:', + key.keyId, + 'with timestamp:', + created_at + ); + store.removeSignedPreKey(key.keyId); + confirmedCount--; + } + }); - console.log("Active signed prekey: " + activeSignedPreKeyId); - console.log("Old signed prekey record count: " + oldRecords.length); + var stillNeeded = MINIMUM_KEYS - confirmedCount; - oldRecords.forEach(function(oldRecord) { - if ( oldRecord.keyId >= activeSignedPreKeyId - 3 ) { - // keep at least the last 3 signed keys + // If we still don't have enough total keys, we keep as many unconfirmed + // keys as necessary. If not necessary, and over a week old, we drop. + unconfirmed.forEach(function(key, index) { + if (index < stillNeeded) { return; } - var created_at = oldRecord.created_at || 0; - var archiveDuration = Date.now() - created_at; - if (archiveDuration > ARCHIVE_AGE) { - console.log("Removing signed prekey record:", - oldRecord.keyId, "with timestamp:", created_at); - store.removeSignedPreKey(oldRecord.keyId); + + var created_at = key.created_at || 0; + var age = Date.now() - created_at; + if (age > ARCHIVE_AGE) { + console.log( + 'Removing unconfirmed signed prekey:', + key.keyId, + 'with timestamp:', + created_at + ); + store.removeSignedPreKey(key.keyId); } }); }); diff --git a/libtextsecure/test/_test.js b/libtextsecure/test/_test.js index d4d557b80..dfbc95ee2 100644 --- a/libtextsecure/test/_test.js +++ b/libtextsecure/test/_test.js @@ -1,5 +1,6 @@ mocha.setup("bdd"); window.assert = chai.assert; +window.PROTO_ROOT = '../../protos'; (function() { var OriginalReporter = mocha._reporter; @@ -52,3 +53,5 @@ function hexToArrayBuffer(str) { array[i] = parseInt(str.substr(i*2, 2), 16); return ret; }; + +window.MockSocket.prototype.addEventListener = function() {}; diff --git a/libtextsecure/test/account_manager_test.js b/libtextsecure/test/account_manager_test.js new file mode 100644 index 000000000..309ed52c9 --- /dev/null +++ b/libtextsecure/test/account_manager_test.js @@ -0,0 +1,156 @@ +'use strict'; + +describe("AccountManager", function() { + let accountManager; + let originalServer; + + before(function() { + originalServer = window.TextSecureServer; + window.TextSecureServer = function() {}; + }); + after(function() { + window.TextSecureServer = originalServer; + }); + + beforeEach(function() { + accountManager = new window.textsecure.AccountManager(); + }); + + describe('#cleanSignedPreKeys', function() { + let originalProtocolStorage; + let signedPreKeys; + const DAY = 1000 * 60 * 60 * 24; + + beforeEach(function() { + originalProtocolStorage = window.textsecure.storage.protocol; + window.textsecure.storage.protocol = { + loadSignedPreKeys: function() { + return Promise.resolve(signedPreKeys); + }, + }; + }); + afterEach(function() { + window.textsecure.storage.protocol = originalProtocolStorage; + }); + + it('keeps three confirmed keys even if over a week old', function() { + const now = Date.now(); + signedPreKeys = [{ + keyId: 1, + created_at: now - DAY * 21, + confirmed: true, + }, { + keyId: 2, + created_at: now - DAY * 14, + confirmed: true, + }, { + keyId: 3, + created_at: now - DAY * 18, + confirmed: true, + }]; + + // should be no calls to store.removeSignedPreKey, would cause crash + return accountManager.cleanSignedPreKeys(); + }); + + it('eliminates confirmed keys over a week old, if more than three', function() { + const now = Date.now(); + signedPreKeys = [{ + keyId: 1, + created_at: now - DAY * 21, + confirmed: true, + }, { + keyId: 2, + created_at: now - DAY * 14, + confirmed: true, + }, { + keyId: 3, + created_at: now - DAY * 4, + confirmed: true, + }, { + keyId: 4, + created_at: now - DAY * 18, + confirmed: true, + }, { + keyId: 5, + created_at: now - DAY, + confirmed: true, + }]; + + let count = 0; + window.textsecure.storage.protocol.removeSignedPreKey = function(keyId) { + if (keyId !== 1 && keyId !== 4) { + throw new Error('Wrong keys were eliminated! ' + keyId); + } + + count++; + }; + + return accountManager.cleanSignedPreKeys().then(function() { + assert.strictEqual(count, 2); + }); + }); + + it('keeps at least three unconfirmed keys if no confirmed', function() { + const now = Date.now(); + signedPreKeys = [{ + keyId: 1, + created_at: now - DAY * 14, + }, { + keyId: 2, + created_at: now - DAY * 21, + }, { + keyId: 3, + created_at: now - DAY * 18, + }, { + keyId: 4, + created_at: now - DAY + }]; + + let count = 0; + window.textsecure.storage.protocol.removeSignedPreKey = function(keyId) { + if (keyId !== 2) { + throw new Error('Wrong keys were eliminated! ' + keyId); + } + + count++; + }; + + return accountManager.cleanSignedPreKeys().then(function() { + assert.strictEqual(count, 1); + }); + }); + + it('if some confirmed keys, keeps unconfirmed to addd up to three total', function() { + const now = Date.now(); + signedPreKeys = [{ + keyId: 1, + created_at: now - DAY * 21, + confirmed: true, + }, { + keyId: 2, + created_at: now - DAY * 14, + confirmed: true, + }, { + keyId: 3, + created_at: now - DAY * 12, + }, { + keyId: 4, + created_at: now - DAY * 8, + }]; + + let count = 0; + window.textsecure.storage.protocol.removeSignedPreKey = function(keyId) { + if (keyId !== 3) { + throw new Error('Wrong keys were eliminated! ' + keyId); + } + + count++; + }; + + return accountManager.cleanSignedPreKeys().then(function() { + assert.strictEqual(count, 1); + }); + }); + }); +}); diff --git a/libtextsecure/test/index.html b/libtextsecure/test/index.html index 5c8cd7b88..4064ed9e0 100644 --- a/libtextsecure/test/index.html +++ b/libtextsecure/test/index.html @@ -1,6 +1,7 @@ + libTextSecure test runner @@ -12,7 +13,6 @@ - @@ -23,12 +23,12 @@ + - @@ -42,5 +42,14 @@ + + + + + + + diff --git a/libtextsecure/test/protos b/libtextsecure/test/protos deleted file mode 120000 index 3d021e597..000000000 --- a/libtextsecure/test/protos +++ /dev/null @@ -1 +0,0 @@ -../../protos/ \ No newline at end of file diff --git a/libtextsecure/test/test.js b/libtextsecure/test/test.js index bb1cc7a0d..660b25b2b 100644 --- a/libtextsecure/test/test.js +++ b/libtextsecure/test/test.js @@ -22054,6 +22054,7 @@ Library.prototype.test = function(obj, type) { }); mocha.setup("bdd"); window.assert = chai.assert; +window.PROTO_ROOT = '../../protos'; (function() { var OriginalReporter = mocha._reporter; @@ -22106,3 +22107,5 @@ function hexToArrayBuffer(str) { array[i] = parseInt(str.substr(i*2, 2), 16); return ret; }; + +window.MockSocket.prototype.addEventListener = function() {}; diff --git a/libtextsecure/test/websocket-resources_test.js b/libtextsecure/test/websocket-resources_test.js index 92755cfbf..c110cfca2 100644 --- a/libtextsecure/test/websocket-resources_test.js +++ b/libtextsecure/test/websocket-resources_test.js @@ -18,7 +18,8 @@ assert.strictEqual(message.response.status, 200); assert.strictEqual(message.response.id.toString(), request_id); done(); - } + }, + addEventListener: function() {}, }; // actual test @@ -58,7 +59,8 @@ assert.strictEqual(message.request.path, '/some/path'); assertEqualArrayBuffers(message.request.body.toArrayBuffer(), new Uint8Array([1,2,3]).buffer); request_id = message.request.id; - } + }, + addEventListener: function() {}, }; // actual test diff --git a/main.js b/main.js index d8f1013e8..c087ccd6a 100644 --- a/main.js +++ b/main.js @@ -207,6 +207,8 @@ function createWindow () { if (config.environment === 'test') { mainWindow.loadURL(prepareURL([__dirname, 'test', 'index.html'])); + } else if (config.environment === 'test-lib') { + mainWindow.loadURL(prepareURL([__dirname, 'libtextsecure', 'test', 'index.html'])); } else { mainWindow.loadURL(prepareURL([__dirname, 'background.html'])); } @@ -225,7 +227,9 @@ function createWindow () { // Emitted when the window is about to be closed. mainWindow.on('close', function (e) { - if (process.platform === 'darwin' && !windowState.shouldQuit() && config.environment !== 'test') { + if (process.platform === 'darwin' && !windowState.shouldQuit() + && config.environment !== 'test' && config.environment !== 'test-lib') { + e.preventDefault(); mainWindow.hide(); } @@ -356,7 +360,7 @@ app.on('before-quit', function() { app.on('window-all-closed', function () { // On OS X it is common for applications and their menu bar // to stay active until the user quits explicitly with Cmd + Q - if (process.platform !== 'darwin' || config.environment === 'test') { + if (process.platform !== 'darwin' || config.environment === 'test' || config.environment === 'test-lib') { app.quit() } })