eslintify all test files
This commit is contained in:
parent
884bc9333d
commit
dbf0be2db5
44 changed files with 1469 additions and 1484 deletions
|
@ -3,12 +3,6 @@ components/**
|
||||||
coverage/**
|
coverage/**
|
||||||
dist/**
|
dist/**
|
||||||
|
|
||||||
# these aren't ready yet, pulling files in one-by-one
|
|
||||||
libtextsecure/test/*.js
|
|
||||||
test/*.js
|
|
||||||
test/models/*.js
|
|
||||||
test/views/*.js
|
|
||||||
|
|
||||||
# Generated files
|
# Generated files
|
||||||
js/components.js
|
js/components.js
|
||||||
js/libtextsecure.js
|
js/libtextsecure.js
|
||||||
|
|
25
libtextsecure/test/.eslintrc
Normal file
25
libtextsecure/test/.eslintrc
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
{
|
||||||
|
"env": {
|
||||||
|
"browser": true,
|
||||||
|
"node": false,
|
||||||
|
"mocha": true,
|
||||||
|
},
|
||||||
|
"parserOptions": {
|
||||||
|
"sourceType": "script"
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"strict": "off",
|
||||||
|
"more/no-then": "off",
|
||||||
|
},
|
||||||
|
"globals": {
|
||||||
|
"assert": true,
|
||||||
|
"assertEqualArrayBuffers": true,
|
||||||
|
"dcodeIO": true,
|
||||||
|
"getString": true,
|
||||||
|
"hexToArrayBuffer": true,
|
||||||
|
"MockServer": true,
|
||||||
|
"MockSocket": true,
|
||||||
|
"PROTO_ROOT": true,
|
||||||
|
"stringToArrayBuffer": true,
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,57 +1,59 @@
|
||||||
|
/* global mocha, chai, assert */
|
||||||
|
|
||||||
mocha.setup('bdd');
|
mocha.setup('bdd');
|
||||||
window.assert = chai.assert;
|
window.assert = chai.assert;
|
||||||
window.PROTO_ROOT = '../../protos';
|
window.PROTO_ROOT = '../../protos';
|
||||||
|
|
||||||
(function() {
|
const OriginalReporter = mocha._reporter;
|
||||||
const OriginalReporter = mocha._reporter;
|
|
||||||
|
|
||||||
const SauceReporter = function(runner) {
|
const SauceReporter = runner => {
|
||||||
const failedTests = [];
|
const failedTests = [];
|
||||||
|
|
||||||
runner.on('end', () => {
|
runner.on('end', () => {
|
||||||
window.mochaResults = runner.stats;
|
window.mochaResults = runner.stats;
|
||||||
window.mochaResults.reports = failedTests;
|
window.mochaResults.reports = failedTests;
|
||||||
|
});
|
||||||
|
|
||||||
|
runner.on('fail', (test, err) => {
|
||||||
|
const flattenTitles = item => {
|
||||||
|
const titles = [];
|
||||||
|
while (item.parent.title) {
|
||||||
|
titles.push(item.parent.title);
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
item = item.parent;
|
||||||
|
}
|
||||||
|
return titles.reverse();
|
||||||
|
};
|
||||||
|
failedTests.push({
|
||||||
|
name: test.title,
|
||||||
|
result: false,
|
||||||
|
message: err.message,
|
||||||
|
stack: err.stack,
|
||||||
|
titles: flattenTitles(test),
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
runner.on('fail', (test, err) => {
|
// eslint-disable-next-line no-new
|
||||||
const flattenTitles = function(test) {
|
new OriginalReporter(runner);
|
||||||
const titles = [];
|
};
|
||||||
while (test.parent.title) {
|
|
||||||
titles.push(test.parent.title);
|
|
||||||
test = test.parent;
|
|
||||||
}
|
|
||||||
return titles.reverse();
|
|
||||||
};
|
|
||||||
failedTests.push({
|
|
||||||
name: test.title,
|
|
||||||
result: false,
|
|
||||||
message: err.message,
|
|
||||||
stack: err.stack,
|
|
||||||
titles: flattenTitles(test),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
new OriginalReporter(runner);
|
SauceReporter.prototype = OriginalReporter.prototype;
|
||||||
};
|
|
||||||
|
|
||||||
SauceReporter.prototype = OriginalReporter.prototype;
|
mocha.reporter(SauceReporter);
|
||||||
|
|
||||||
mocha.reporter(SauceReporter);
|
|
||||||
})();
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* global helpers for tests
|
* global helpers for tests
|
||||||
*/
|
*/
|
||||||
function assertEqualArrayBuffers(ab1, ab2) {
|
window.assertEqualArrayBuffers = (ab1, ab2) => {
|
||||||
assert.deepEqual(new Uint8Array(ab1), new Uint8Array(ab2));
|
assert.deepEqual(new Uint8Array(ab1), new Uint8Array(ab2));
|
||||||
}
|
};
|
||||||
|
|
||||||
function hexToArrayBuffer(str) {
|
window.hexToArrayBuffer = str => {
|
||||||
const ret = new ArrayBuffer(str.length / 2);
|
const ret = new ArrayBuffer(str.length / 2);
|
||||||
const array = new Uint8Array(ret);
|
const array = new Uint8Array(ret);
|
||||||
for (let i = 0; i < str.length / 2; i++)
|
for (let i = 0; i < str.length / 2; i += 1)
|
||||||
array[i] = parseInt(str.substr(i * 2, 2), 16);
|
array[i] = parseInt(str.substr(i * 2, 2), 16);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
};
|
||||||
|
|
||||||
window.MockSocket.prototype.addEventListener = function() {};
|
window.MockSocket.prototype.addEventListener = () => null;
|
||||||
|
|
|
@ -46,7 +46,7 @@ describe('AccountManager', () => {
|
||||||
return accountManager.cleanSignedPreKeys();
|
return accountManager.cleanSignedPreKeys();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('eliminates confirmed keys over a week old, if more than three', () => {
|
it('eliminates confirmed keys over a week old, if more than three', async () => {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
signedPreKeys = [
|
signedPreKeys = [
|
||||||
{
|
{
|
||||||
|
@ -77,20 +77,19 @@ describe('AccountManager', () => {
|
||||||
];
|
];
|
||||||
|
|
||||||
let count = 0;
|
let count = 0;
|
||||||
window.textsecure.storage.protocol.removeSignedPreKey = function(keyId) {
|
window.textsecure.storage.protocol.removeSignedPreKey = keyId => {
|
||||||
if (keyId !== 1 && keyId !== 4) {
|
if (keyId !== 1 && keyId !== 4) {
|
||||||
throw new Error(`Wrong keys were eliminated! ${keyId}`);
|
throw new Error(`Wrong keys were eliminated! ${keyId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
count++;
|
count += 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
return accountManager.cleanSignedPreKeys().then(() => {
|
await accountManager.cleanSignedPreKeys();
|
||||||
assert.strictEqual(count, 2);
|
assert.strictEqual(count, 2);
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('keeps at least three unconfirmed keys if no confirmed', () => {
|
it('keeps at least three unconfirmed keys if no confirmed', async () => {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
signedPreKeys = [
|
signedPreKeys = [
|
||||||
{
|
{
|
||||||
|
@ -112,20 +111,19 @@ describe('AccountManager', () => {
|
||||||
];
|
];
|
||||||
|
|
||||||
let count = 0;
|
let count = 0;
|
||||||
window.textsecure.storage.protocol.removeSignedPreKey = function(keyId) {
|
window.textsecure.storage.protocol.removeSignedPreKey = keyId => {
|
||||||
if (keyId !== 2) {
|
if (keyId !== 2) {
|
||||||
throw new Error(`Wrong keys were eliminated! ${keyId}`);
|
throw new Error(`Wrong keys were eliminated! ${keyId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
count++;
|
count += 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
return accountManager.cleanSignedPreKeys().then(() => {
|
await accountManager.cleanSignedPreKeys();
|
||||||
assert.strictEqual(count, 1);
|
assert.strictEqual(count, 1);
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('if some confirmed keys, keeps unconfirmed to addd up to three total', () => {
|
it('if some confirmed keys, keeps unconfirmed to addd up to three total', async () => {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
signedPreKeys = [
|
signedPreKeys = [
|
||||||
{
|
{
|
||||||
|
@ -149,17 +147,16 @@ describe('AccountManager', () => {
|
||||||
];
|
];
|
||||||
|
|
||||||
let count = 0;
|
let count = 0;
|
||||||
window.textsecure.storage.protocol.removeSignedPreKey = function(keyId) {
|
window.textsecure.storage.protocol.removeSignedPreKey = keyId => {
|
||||||
if (keyId !== 3) {
|
if (keyId !== 3) {
|
||||||
throw new Error(`Wrong keys were eliminated! ${keyId}`);
|
throw new Error(`Wrong keys were eliminated! ${keyId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
count++;
|
count += 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
return accountManager.cleanSignedPreKeys().then(() => {
|
await accountManager.cleanSignedPreKeys();
|
||||||
assert.strictEqual(count, 1);
|
assert.strictEqual(count, 1);
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
|
/* global ContactBuffer, GroupBuffer, textsecure */
|
||||||
|
|
||||||
describe('ContactBuffer', () => {
|
describe('ContactBuffer', () => {
|
||||||
function getTestBuffer() {
|
function getTestBuffer() {
|
||||||
const buffer = new dcodeIO.ByteBuffer();
|
const buffer = new dcodeIO.ByteBuffer();
|
||||||
const avatarBuffer = new dcodeIO.ByteBuffer();
|
const avatarBuffer = new dcodeIO.ByteBuffer();
|
||||||
const avatarLen = 255;
|
const avatarLen = 255;
|
||||||
for (var i = 0; i < avatarLen; ++i) {
|
for (let i = 0; i < avatarLen; i += 1) {
|
||||||
avatarBuffer.writeUint8(i);
|
avatarBuffer.writeUint8(i);
|
||||||
}
|
}
|
||||||
avatarBuffer.limit = avatarBuffer.offset;
|
avatarBuffer.limit = avatarBuffer.offset;
|
||||||
|
@ -15,7 +17,7 @@ describe('ContactBuffer', () => {
|
||||||
});
|
});
|
||||||
const contactInfoBuffer = contactInfo.encode().toArrayBuffer();
|
const contactInfoBuffer = contactInfo.encode().toArrayBuffer();
|
||||||
|
|
||||||
for (var i = 0; i < 3; ++i) {
|
for (let i = 0; i < 3; i += 1) {
|
||||||
buffer.writeVarint32(contactInfoBuffer.byteLength);
|
buffer.writeVarint32(contactInfoBuffer.byteLength);
|
||||||
buffer.append(contactInfoBuffer);
|
buffer.append(contactInfoBuffer);
|
||||||
buffer.append(avatarBuffer.clone());
|
buffer.append(avatarBuffer.clone());
|
||||||
|
@ -32,14 +34,14 @@ describe('ContactBuffer', () => {
|
||||||
let contact = contactBuffer.next();
|
let contact = contactBuffer.next();
|
||||||
let count = 0;
|
let count = 0;
|
||||||
while (contact !== undefined) {
|
while (contact !== undefined) {
|
||||||
count++;
|
count += 1;
|
||||||
assert.strictEqual(contact.name, 'Zero Cool');
|
assert.strictEqual(contact.name, 'Zero Cool');
|
||||||
assert.strictEqual(contact.number, '+10000000000');
|
assert.strictEqual(contact.number, '+10000000000');
|
||||||
assert.strictEqual(contact.avatar.contentType, 'image/jpeg');
|
assert.strictEqual(contact.avatar.contentType, 'image/jpeg');
|
||||||
assert.strictEqual(contact.avatar.length, 255);
|
assert.strictEqual(contact.avatar.length, 255);
|
||||||
assert.strictEqual(contact.avatar.data.byteLength, 255);
|
assert.strictEqual(contact.avatar.data.byteLength, 255);
|
||||||
const avatarBytes = new Uint8Array(contact.avatar.data);
|
const avatarBytes = new Uint8Array(contact.avatar.data);
|
||||||
for (let j = 0; j < 255; ++j) {
|
for (let j = 0; j < 255; j += 1) {
|
||||||
assert.strictEqual(avatarBytes[j], j);
|
assert.strictEqual(avatarBytes[j], j);
|
||||||
}
|
}
|
||||||
contact = contactBuffer.next();
|
contact = contactBuffer.next();
|
||||||
|
@ -53,7 +55,7 @@ describe('GroupBuffer', () => {
|
||||||
const buffer = new dcodeIO.ByteBuffer();
|
const buffer = new dcodeIO.ByteBuffer();
|
||||||
const avatarBuffer = new dcodeIO.ByteBuffer();
|
const avatarBuffer = new dcodeIO.ByteBuffer();
|
||||||
const avatarLen = 255;
|
const avatarLen = 255;
|
||||||
for (var i = 0; i < avatarLen; ++i) {
|
for (let i = 0; i < avatarLen; i += 1) {
|
||||||
avatarBuffer.writeUint8(i);
|
avatarBuffer.writeUint8(i);
|
||||||
}
|
}
|
||||||
avatarBuffer.limit = avatarBuffer.offset;
|
avatarBuffer.limit = avatarBuffer.offset;
|
||||||
|
@ -66,7 +68,7 @@ describe('GroupBuffer', () => {
|
||||||
});
|
});
|
||||||
const groupInfoBuffer = groupInfo.encode().toArrayBuffer();
|
const groupInfoBuffer = groupInfo.encode().toArrayBuffer();
|
||||||
|
|
||||||
for (var i = 0; i < 3; ++i) {
|
for (let i = 0; i < 3; i += 1) {
|
||||||
buffer.writeVarint32(groupInfoBuffer.byteLength);
|
buffer.writeVarint32(groupInfoBuffer.byteLength);
|
||||||
buffer.append(groupInfoBuffer);
|
buffer.append(groupInfoBuffer);
|
||||||
buffer.append(avatarBuffer.clone());
|
buffer.append(avatarBuffer.clone());
|
||||||
|
@ -83,7 +85,7 @@ describe('GroupBuffer', () => {
|
||||||
let group = groupBuffer.next();
|
let group = groupBuffer.next();
|
||||||
let count = 0;
|
let count = 0;
|
||||||
while (group !== undefined) {
|
while (group !== undefined) {
|
||||||
count++;
|
count += 1;
|
||||||
assert.strictEqual(group.name, 'Hackers');
|
assert.strictEqual(group.name, 'Hackers');
|
||||||
assertEqualArrayBuffers(
|
assertEqualArrayBuffers(
|
||||||
group.id.toArrayBuffer(),
|
group.id.toArrayBuffer(),
|
||||||
|
@ -94,7 +96,7 @@ describe('GroupBuffer', () => {
|
||||||
assert.strictEqual(group.avatar.length, 255);
|
assert.strictEqual(group.avatar.length, 255);
|
||||||
assert.strictEqual(group.avatar.data.byteLength, 255);
|
assert.strictEqual(group.avatar.data.byteLength, 255);
|
||||||
const avatarBytes = new Uint8Array(group.avatar.data);
|
const avatarBytes = new Uint8Array(group.avatar.data);
|
||||||
for (let j = 0; j < 255; ++j) {
|
for (let j = 0; j < 255; j += 1) {
|
||||||
assert.strictEqual(avatarBytes[j], j);
|
assert.strictEqual(avatarBytes[j], j);
|
||||||
}
|
}
|
||||||
group = groupBuffer.next();
|
group = groupBuffer.next();
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
/* global libsignal, textsecure */
|
||||||
|
|
||||||
describe('encrypting and decrypting profile data', () => {
|
describe('encrypting and decrypting profile data', () => {
|
||||||
const NAME_PADDED_LENGTH = 26;
|
const NAME_PADDED_LENGTH = 26;
|
||||||
describe('encrypting and decrypting profile names', () => {
|
describe('encrypting and decrypting profile names', () => {
|
||||||
|
@ -61,12 +63,12 @@ describe('encrypting and decrypting profile data', () => {
|
||||||
'This is an avatar'
|
'This is an avatar'
|
||||||
).toArrayBuffer();
|
).toArrayBuffer();
|
||||||
const key = libsignal.crypto.getRandomBytes(32);
|
const key = libsignal.crypto.getRandomBytes(32);
|
||||||
const bad_key = libsignal.crypto.getRandomBytes(32);
|
const badKey = libsignal.crypto.getRandomBytes(32);
|
||||||
|
|
||||||
return textsecure.crypto.encryptProfile(buffer, key).then(encrypted => {
|
return textsecure.crypto.encryptProfile(buffer, key).then(encrypted => {
|
||||||
assert(encrypted.byteLength === buffer.byteLength + 16 + 12);
|
assert(encrypted.byteLength === buffer.byteLength + 16 + 12);
|
||||||
return textsecure.crypto
|
return textsecure.crypto
|
||||||
.decryptProfile(encrypted, bad_key)
|
.decryptProfile(encrypted, badKey)
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
assert.strictEqual(error.name, 'ProfileDecryptError');
|
assert.strictEqual(error.name, 'ProfileDecryptError');
|
||||||
});
|
});
|
||||||
|
|
|
@ -22,7 +22,7 @@ const fakeAPI = {
|
||||||
// sendMessages: fakeCall,
|
// sendMessages: fakeCall,
|
||||||
setSignedPreKey: fakeCall,
|
setSignedPreKey: fakeCall,
|
||||||
|
|
||||||
getKeysForNumber(number, deviceId) {
|
getKeysForNumber(number) {
|
||||||
const res = getKeysForNumberMap[number];
|
const res = getKeysForNumberMap[number];
|
||||||
if (res !== undefined) {
|
if (res !== undefined) {
|
||||||
delete getKeysForNumberMap[number];
|
delete getKeysForNumberMap[number];
|
||||||
|
@ -32,14 +32,14 @@ const fakeAPI = {
|
||||||
},
|
},
|
||||||
|
|
||||||
sendMessages(destination, messageArray) {
|
sendMessages(destination, messageArray) {
|
||||||
for (i in messageArray) {
|
for (let i = 0, max = messageArray.length; i < max; i += 1) {
|
||||||
const msg = messageArray[i];
|
const msg = messageArray[i];
|
||||||
if (
|
if (
|
||||||
(msg.type != 1 && msg.type != 3) ||
|
(msg.type !== 1 && msg.type !== 3) ||
|
||||||
msg.destinationDeviceId === undefined ||
|
msg.destinationDeviceId === undefined ||
|
||||||
msg.destinationRegistrationId === undefined ||
|
msg.destinationRegistrationId === undefined ||
|
||||||
msg.body === undefined ||
|
msg.body === undefined ||
|
||||||
msg.timestamp == undefined ||
|
msg.timestamp === undefined ||
|
||||||
msg.relay !== undefined ||
|
msg.relay !== undefined ||
|
||||||
msg.destination !== undefined
|
msg.destination !== undefined
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
describe('Key generation', function() {
|
/* global libsignal, textsecure */
|
||||||
|
|
||||||
|
describe('Key generation', function thisNeeded() {
|
||||||
const count = 10;
|
const count = 10;
|
||||||
this.timeout(count * 2000);
|
this.timeout(count * 2000);
|
||||||
|
|
||||||
|
@ -60,7 +62,7 @@ describe('Key generation', function() {
|
||||||
result = res;
|
result = res;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
for (let i = 1; i <= count; i++) {
|
for (let i = 1; i <= count; i += 1) {
|
||||||
itStoresPreKey(i);
|
itStoresPreKey(i);
|
||||||
}
|
}
|
||||||
itStoresSignedPreKey(1);
|
itStoresSignedPreKey(1);
|
||||||
|
@ -68,12 +70,12 @@ describe('Key generation', function() {
|
||||||
it(`result contains ${count} preKeys`, () => {
|
it(`result contains ${count} preKeys`, () => {
|
||||||
assert.isArray(result.preKeys);
|
assert.isArray(result.preKeys);
|
||||||
assert.lengthOf(result.preKeys, count);
|
assert.lengthOf(result.preKeys, count);
|
||||||
for (let i = 0; i < count; i++) {
|
for (let i = 0; i < count; i += 1) {
|
||||||
assert.isObject(result.preKeys[i]);
|
assert.isObject(result.preKeys[i]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
it('result contains the correct keyIds', () => {
|
it('result contains the correct keyIds', () => {
|
||||||
for (let i = 0; i < count; i++) {
|
for (let i = 0; i < count; i += 1) {
|
||||||
assert.strictEqual(result.preKeys[i].keyId, i + 1);
|
assert.strictEqual(result.preKeys[i].keyId, i + 1);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -93,7 +95,7 @@ describe('Key generation', function() {
|
||||||
result = res;
|
result = res;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
for (let i = 1; i <= 2 * count; i++) {
|
for (let i = 1; i <= 2 * count; i += 1) {
|
||||||
itStoresPreKey(i);
|
itStoresPreKey(i);
|
||||||
}
|
}
|
||||||
itStoresSignedPreKey(1);
|
itStoresSignedPreKey(1);
|
||||||
|
@ -101,12 +103,12 @@ describe('Key generation', function() {
|
||||||
it(`result contains ${count} preKeys`, () => {
|
it(`result contains ${count} preKeys`, () => {
|
||||||
assert.isArray(result.preKeys);
|
assert.isArray(result.preKeys);
|
||||||
assert.lengthOf(result.preKeys, count);
|
assert.lengthOf(result.preKeys, count);
|
||||||
for (let i = 0; i < count; i++) {
|
for (let i = 0; i < count; i += 1) {
|
||||||
assert.isObject(result.preKeys[i]);
|
assert.isObject(result.preKeys[i]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
it('result contains the correct keyIds', () => {
|
it('result contains the correct keyIds', () => {
|
||||||
for (let i = 1; i <= count; i++) {
|
for (let i = 1; i <= count; i += 1) {
|
||||||
assert.strictEqual(result.preKeys[i - 1].keyId, i + count);
|
assert.strictEqual(result.preKeys[i - 1].keyId, i + count);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -126,7 +128,7 @@ describe('Key generation', function() {
|
||||||
result = res;
|
result = res;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
for (let i = 1; i <= 3 * count; i++) {
|
for (let i = 1; i <= 3 * count; i += 1) {
|
||||||
itStoresPreKey(i);
|
itStoresPreKey(i);
|
||||||
}
|
}
|
||||||
itStoresSignedPreKey(2);
|
itStoresSignedPreKey(2);
|
||||||
|
@ -134,12 +136,12 @@ describe('Key generation', function() {
|
||||||
it(`result contains ${count} preKeys`, () => {
|
it(`result contains ${count} preKeys`, () => {
|
||||||
assert.isArray(result.preKeys);
|
assert.isArray(result.preKeys);
|
||||||
assert.lengthOf(result.preKeys, count);
|
assert.lengthOf(result.preKeys, count);
|
||||||
for (let i = 0; i < count; i++) {
|
for (let i = 0; i < count; i += 1) {
|
||||||
assert.isObject(result.preKeys[i]);
|
assert.isObject(result.preKeys[i]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
it('result contains the correct keyIds', () => {
|
it('result contains the correct keyIds', () => {
|
||||||
for (let i = 1; i <= count; i++) {
|
for (let i = 1; i <= count; i += 1) {
|
||||||
assert.strictEqual(result.preKeys[i - 1].keyId, i + 2 * count);
|
assert.strictEqual(result.preKeys[i - 1].keyId, i + 2 * count);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -12,7 +12,6 @@ describe('Helpers', () => {
|
||||||
|
|
||||||
describe('stringToArrayBuffer', () => {
|
describe('stringToArrayBuffer', () => {
|
||||||
it('returns ArrayBuffer when passed string', () => {
|
it('returns ArrayBuffer when passed string', () => {
|
||||||
const StaticArrayBufferProto = new ArrayBuffer().__proto__;
|
|
||||||
const anArrayBuffer = new ArrayBuffer(1);
|
const anArrayBuffer = new ArrayBuffer(1);
|
||||||
const typedArray = new Uint8Array(anArrayBuffer);
|
const typedArray = new Uint8Array(anArrayBuffer);
|
||||||
typedArray[0] = 'a'.charCodeAt(0);
|
typedArray[0] = 'a'.charCodeAt(0);
|
||||||
|
|
|
@ -36,10 +36,10 @@ SignalProtocolStore.prototype = {
|
||||||
|
|
||||||
isTrustedIdentity(identifier, identityKey) {
|
isTrustedIdentity(identifier, identityKey) {
|
||||||
if (identifier === null || identifier === undefined) {
|
if (identifier === null || identifier === undefined) {
|
||||||
throw new error('tried to check identity key for undefined/null key');
|
throw new Error('tried to check identity key for undefined/null key');
|
||||||
}
|
}
|
||||||
if (!(identityKey instanceof ArrayBuffer)) {
|
if (!(identityKey instanceof ArrayBuffer)) {
|
||||||
throw new error('Expected identityKey to be an ArrayBuffer');
|
throw new Error('Expected identityKey to be an ArrayBuffer');
|
||||||
}
|
}
|
||||||
const trusted = this.get(`identityKey${identifier}`);
|
const trusted = this.get(`identityKey${identifier}`);
|
||||||
if (trusted === undefined) {
|
if (trusted === undefined) {
|
||||||
|
@ -96,9 +96,11 @@ SignalProtocolStore.prototype = {
|
||||||
loadSignedPreKeys() {
|
loadSignedPreKeys() {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
const res = [];
|
const res = [];
|
||||||
for (const i in this.store) {
|
const keys = Object.keys(this.store);
|
||||||
if (i.startsWith('25519KeysignedKey')) {
|
for (let i = 0, max = keys.length; i < max; i += 1) {
|
||||||
res.push(this.store[i]);
|
const key = keys[i];
|
||||||
|
if (key.startsWith('25519KeysignedKey')) {
|
||||||
|
res.push(this.store[key]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
resolve(res);
|
resolve(res);
|
||||||
|
@ -127,7 +129,9 @@ SignalProtocolStore.prototype = {
|
||||||
},
|
},
|
||||||
removeAllSessions(identifier) {
|
removeAllSessions(identifier) {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
for (key in this.store) {
|
const keys = Object.keys(this.store);
|
||||||
|
for (let i = 0, max = keys.length; i < max; i += 1) {
|
||||||
|
const key = keys[i];
|
||||||
if (key.match(RegExp(`^session${identifier.replace('+', '\\+')}.+`))) {
|
if (key.match(RegExp(`^session${identifier.replace('+', '\\+')}.+`))) {
|
||||||
delete this.store[key];
|
delete this.store[key];
|
||||||
}
|
}
|
||||||
|
@ -138,9 +142,11 @@ SignalProtocolStore.prototype = {
|
||||||
getDeviceIds(identifier) {
|
getDeviceIds(identifier) {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
const deviceIds = [];
|
const deviceIds = [];
|
||||||
for (key in this.store) {
|
const keys = Object.keys(this.store);
|
||||||
|
for (let i = 0, max = keys.length; i < max; i += 1) {
|
||||||
|
const key = keys[i];
|
||||||
if (key.match(RegExp(`^session${identifier.replace('+', '\\+')}.+`))) {
|
if (key.match(RegExp(`^session${identifier.replace('+', '\\+')}.+`))) {
|
||||||
deviceIds.push(parseInt(key.split('.')[1]));
|
deviceIds.push(parseInt(key.split('.')[1], 10));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
resolve(deviceIds);
|
resolve(deviceIds);
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
|
/* global libsignal, textsecure, SignalProtocolStore */
|
||||||
|
|
||||||
describe('MessageReceiver', () => {
|
describe('MessageReceiver', () => {
|
||||||
textsecure.storage.impl = new SignalProtocolStore();
|
textsecure.storage.impl = new SignalProtocolStore();
|
||||||
const WebSocket = window.WebSocket;
|
const { WebSocket } = window;
|
||||||
const number = '+19999999999';
|
const number = '+19999999999';
|
||||||
const deviceId = 1;
|
const deviceId = 1;
|
||||||
const signalingKey = libsignal.crypto.getRandomBytes(32 + 20);
|
const signalingKey = libsignal.crypto.getRandomBytes(32 + 20);
|
||||||
|
|
||||||
before(() => {
|
before(() => {
|
||||||
window.WebSocket = MockSocket;
|
window.WebSocket = MockSocket;
|
||||||
textsecure.storage.user.setNumberAndDeviceId(number, deviceId, 'name');
|
textsecure.storage.user.setNumberAndDeviceId(number, deviceId, 'name');
|
||||||
|
@ -15,7 +18,6 @@ describe('MessageReceiver', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('connecting', () => {
|
describe('connecting', () => {
|
||||||
const blob = null;
|
|
||||||
const attrs = {
|
const attrs = {
|
||||||
type: textsecure.protobuf.Envelope.Type.CIPHERTEXT,
|
type: textsecure.protobuf.Envelope.Type.CIPHERTEXT,
|
||||||
source: number,
|
source: number,
|
||||||
|
@ -29,14 +31,12 @@ describe('MessageReceiver', () => {
|
||||||
|
|
||||||
before(done => {
|
before(done => {
|
||||||
const signal = new textsecure.protobuf.Envelope(attrs).toArrayBuffer();
|
const signal = new textsecure.protobuf.Envelope(attrs).toArrayBuffer();
|
||||||
const data = new textsecure.protobuf.DataMessage({ body: 'hello' });
|
|
||||||
|
|
||||||
const signaling_key = signalingKey;
|
const aesKey = signalingKey.slice(0, 32);
|
||||||
const aes_key = signaling_key.slice(0, 32);
|
const macKey = signalingKey.slice(32, 32 + 20);
|
||||||
const mac_key = signaling_key.slice(32, 32 + 20);
|
|
||||||
|
|
||||||
window.crypto.subtle
|
window.crypto.subtle
|
||||||
.importKey('raw', aes_key, { name: 'AES-CBC' }, false, ['encrypt'])
|
.importKey('raw', aesKey, { name: 'AES-CBC' }, false, ['encrypt'])
|
||||||
.then(key => {
|
.then(key => {
|
||||||
const iv = libsignal.crypto.getRandomBytes(16);
|
const iv = libsignal.crypto.getRandomBytes(16);
|
||||||
window.crypto.subtle
|
window.crypto.subtle
|
||||||
|
@ -45,14 +45,14 @@ describe('MessageReceiver', () => {
|
||||||
window.crypto.subtle
|
window.crypto.subtle
|
||||||
.importKey(
|
.importKey(
|
||||||
'raw',
|
'raw',
|
||||||
mac_key,
|
macKey,
|
||||||
{ name: 'HMAC', hash: { name: 'SHA-256' } },
|
{ name: 'HMAC', hash: { name: 'SHA-256' } },
|
||||||
false,
|
false,
|
||||||
['sign']
|
['sign']
|
||||||
)
|
)
|
||||||
.then(key => {
|
.then(innerKey => {
|
||||||
window.crypto.subtle
|
window.crypto.subtle
|
||||||
.sign({ name: 'HMAC', hash: 'SHA-256' }, key, signal)
|
.sign({ name: 'HMAC', hash: 'SHA-256' }, innerKey, signal)
|
||||||
.then(mac => {
|
.then(mac => {
|
||||||
const version = new Uint8Array([1]);
|
const version = new Uint8Array([1]);
|
||||||
const message = dcodeIO.ByteBuffer.concat([
|
const message = dcodeIO.ByteBuffer.concat([
|
||||||
|
@ -82,14 +82,19 @@ describe('MessageReceiver', () => {
|
||||||
|
|
||||||
window.addEventListener('textsecure:message', ev => {
|
window.addEventListener('textsecure:message', ev => {
|
||||||
const signal = ev.proto;
|
const signal = ev.proto;
|
||||||
for (const key in attrs) {
|
const keys = Object.keys(attrs);
|
||||||
|
|
||||||
|
for (let i = 0, max = keys.length; i < max; i += 1) {
|
||||||
|
const key = keys[i];
|
||||||
assert.strictEqual(attrs[key], signal[key]);
|
assert.strictEqual(attrs[key], signal[key]);
|
||||||
}
|
}
|
||||||
assert.strictEqual(signal.message.body, 'hello');
|
assert.strictEqual(signal.message.body, 'hello');
|
||||||
server.close();
|
mockServer.close();
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
const messageReceiver = new textsecure.MessageReceiver(
|
|
||||||
|
window.messageReceiver = new textsecure.MessageReceiver(
|
||||||
'username',
|
'username',
|
||||||
'password',
|
'password',
|
||||||
'signalingKey'
|
'signalingKey'
|
||||||
|
|
|
@ -1,32 +1,34 @@
|
||||||
|
/* global textsecure */
|
||||||
|
|
||||||
describe('Protocol', () => {
|
describe('Protocol', () => {
|
||||||
describe('Unencrypted PushMessageProto "decrypt"', () => {
|
describe('Unencrypted PushMessageProto "decrypt"', () => {
|
||||||
// exclusive
|
// exclusive
|
||||||
it('works', done => {
|
it('works', done => {
|
||||||
localStorage.clear();
|
localStorage.clear();
|
||||||
|
|
||||||
const text_message = new textsecure.protobuf.DataMessage();
|
const textMessage = new textsecure.protobuf.DataMessage();
|
||||||
text_message.body = 'Hi Mom';
|
textMessage.body = 'Hi Mom';
|
||||||
const server_message = {
|
const serverMessage = {
|
||||||
type: 4, // unencrypted
|
type: 4, // unencrypted
|
||||||
source: '+19999999999',
|
source: '+19999999999',
|
||||||
timestamp: 42,
|
timestamp: 42,
|
||||||
message: text_message.encode(),
|
message: textMessage.encode(),
|
||||||
};
|
};
|
||||||
|
|
||||||
return textsecure.protocol_wrapper
|
return textsecure.protocol_wrapper
|
||||||
.handleEncryptedMessage(
|
.handleEncryptedMessage(
|
||||||
server_message.source,
|
serverMessage.source,
|
||||||
server_message.source_device,
|
serverMessage.source_device,
|
||||||
server_message.type,
|
serverMessage.type,
|
||||||
server_message.message
|
serverMessage.message
|
||||||
)
|
)
|
||||||
.then(message => {
|
.then(message => {
|
||||||
assert.equal(message.body, text_message.body);
|
assert.equal(message.body, textMessage.body);
|
||||||
assert.equal(
|
assert.equal(
|
||||||
message.attachments.length,
|
message.attachments.length,
|
||||||
text_message.attachments.length
|
textMessage.attachments.length
|
||||||
);
|
);
|
||||||
assert.equal(text_message.attachments.length, 0);
|
assert.equal(textMessage.attachments.length, 0);
|
||||||
})
|
})
|
||||||
.then(done)
|
.then(done)
|
||||||
.catch(done);
|
.catch(done);
|
||||||
|
|
|
@ -1,19 +1,20 @@
|
||||||
describe('Protocol Wrapper', function() {
|
/* global libsignal, textsecure */
|
||||||
|
|
||||||
|
describe('Protocol Wrapper', function thisNeeded() {
|
||||||
const store = textsecure.storage.protocol;
|
const store = textsecure.storage.protocol;
|
||||||
const identifier = '+5558675309';
|
const identifier = '+5558675309';
|
||||||
const another_identifier = '+5555590210';
|
|
||||||
let prekeys, identityKey, testKey;
|
|
||||||
this.timeout(5000);
|
this.timeout(5000);
|
||||||
|
|
||||||
before(done => {
|
before(done => {
|
||||||
localStorage.clear();
|
localStorage.clear();
|
||||||
libsignal.KeyHelper.generateIdentityKeyPair()
|
libsignal.KeyHelper.generateIdentityKeyPair()
|
||||||
.then(identityKey =>
|
.then(key => textsecure.storage.protocol.saveIdentity(identifier, key))
|
||||||
textsecure.storage.protocol.saveIdentity(identifier, identityKey)
|
|
||||||
)
|
|
||||||
.then(() => {
|
.then(() => {
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('processPreKey', () => {
|
describe('processPreKey', () => {
|
||||||
it('rejects if the identity key changes', () => {
|
it('rejects if the identity key changes', () => {
|
||||||
const address = new libsignal.SignalProtocolAddress(identifier, 1);
|
const address = new libsignal.SignalProtocolAddress(identifier, 1);
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
|
/* global libsignal, textsecure */
|
||||||
|
|
||||||
describe('SignalProtocolStore', () => {
|
describe('SignalProtocolStore', () => {
|
||||||
before(() => {
|
before(() => {
|
||||||
localStorage.clear();
|
localStorage.clear();
|
||||||
});
|
});
|
||||||
const store = textsecure.storage.protocol;
|
const store = textsecure.storage.protocol;
|
||||||
const identifier = '+5558675309';
|
const identifier = '+5558675309';
|
||||||
const another_identifier = '+5555590210';
|
|
||||||
const identityKey = {
|
const identityKey = {
|
||||||
pubKey: libsignal.crypto.getRandomBytes(33),
|
pubKey: libsignal.crypto.getRandomBytes(33),
|
||||||
privKey: libsignal.crypto.getRandomBytes(32),
|
privKey: libsignal.crypto.getRandomBytes(32),
|
||||||
|
@ -13,176 +14,121 @@ describe('SignalProtocolStore', () => {
|
||||||
pubKey: libsignal.crypto.getRandomBytes(33),
|
pubKey: libsignal.crypto.getRandomBytes(33),
|
||||||
privKey: libsignal.crypto.getRandomBytes(32),
|
privKey: libsignal.crypto.getRandomBytes(32),
|
||||||
};
|
};
|
||||||
it('retrieves my registration id', done => {
|
it('retrieves my registration id', async () => {
|
||||||
store.put('registrationId', 1337);
|
store.put('registrationId', 1337);
|
||||||
store
|
|
||||||
.getLocalRegistrationId()
|
const reg = await store.getLocalRegistrationId();
|
||||||
.then(reg => {
|
assert.strictEqual(reg, 1337);
|
||||||
assert.strictEqual(reg, 1337);
|
|
||||||
})
|
|
||||||
.then(done, done);
|
|
||||||
});
|
});
|
||||||
it('retrieves my identity key', done => {
|
it('retrieves my identity key', async () => {
|
||||||
store.put('identityKey', identityKey);
|
store.put('identityKey', identityKey);
|
||||||
store
|
const key = await store.getIdentityKeyPair();
|
||||||
.getIdentityKeyPair()
|
assertEqualArrayBuffers(key.pubKey, identityKey.pubKey);
|
||||||
.then(key => {
|
assertEqualArrayBuffers(key.privKey, identityKey.privKey);
|
||||||
assertEqualArrayBuffers(key.pubKey, identityKey.pubKey);
|
});
|
||||||
assertEqualArrayBuffers(key.privKey, identityKey.privKey);
|
it('stores identity keys', async () => {
|
||||||
|
await store.saveIdentity(identifier, testKey.pubKey);
|
||||||
|
const key = await store.loadIdentityKey(identifier);
|
||||||
|
assertEqualArrayBuffers(key, testKey.pubKey);
|
||||||
|
});
|
||||||
|
it('returns whether a key is trusted', async () => {
|
||||||
|
const newIdentity = libsignal.crypto.getRandomBytes(33);
|
||||||
|
await store.saveIdentity(identifier, testKey.pubKey);
|
||||||
|
|
||||||
|
const trusted = await store.isTrustedIdentity(identifier, newIdentity);
|
||||||
|
if (trusted) {
|
||||||
|
throw new Error('Allowed to overwrite identity key');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
it('returns whether a key is untrusted', async () => {
|
||||||
|
await store.saveIdentity(identifier, testKey.pubKey);
|
||||||
|
const trusted = await store.isTrustedIdentity(identifier, testKey.pubKey);
|
||||||
|
|
||||||
|
if (!trusted) {
|
||||||
|
throw new Error('Allowed to overwrite identity key');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
it('stores prekeys', async () => {
|
||||||
|
await store.storePreKey(1, testKey);
|
||||||
|
|
||||||
|
const key = await store.loadPreKey(1);
|
||||||
|
assertEqualArrayBuffers(key.pubKey, testKey.pubKey);
|
||||||
|
assertEqualArrayBuffers(key.privKey, testKey.privKey);
|
||||||
|
});
|
||||||
|
it('deletes prekeys', async () => {
|
||||||
|
await store.storePreKey(2, testKey);
|
||||||
|
await store.removePreKey(2, testKey);
|
||||||
|
|
||||||
|
const key = await store.loadPreKey(2);
|
||||||
|
assert.isUndefined(key);
|
||||||
|
});
|
||||||
|
it('stores signed prekeys', async () => {
|
||||||
|
await store.storeSignedPreKey(3, testKey);
|
||||||
|
|
||||||
|
const key = await store.loadSignedPreKey(3);
|
||||||
|
assertEqualArrayBuffers(key.pubKey, testKey.pubKey);
|
||||||
|
assertEqualArrayBuffers(key.privKey, testKey.privKey);
|
||||||
|
});
|
||||||
|
it('deletes signed prekeys', async () => {
|
||||||
|
await store.storeSignedPreKey(4, testKey);
|
||||||
|
await store.removeSignedPreKey(4, testKey);
|
||||||
|
|
||||||
|
const key = await store.loadSignedPreKey(4);
|
||||||
|
assert.isUndefined(key);
|
||||||
|
});
|
||||||
|
it('stores sessions', async () => {
|
||||||
|
const testRecord = 'an opaque string';
|
||||||
|
const devices = [1, 2, 3].map(deviceId => [identifier, deviceId].join('.'));
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
devices.map(async encodedNumber => {
|
||||||
|
await store.storeSession(encodedNumber, testRecord + encodedNumber);
|
||||||
})
|
})
|
||||||
.then(done, done);
|
);
|
||||||
|
|
||||||
|
const records = await Promise.all(
|
||||||
|
devices.map(store.loadSession.bind(store))
|
||||||
|
);
|
||||||
|
|
||||||
|
for (let i = 0, max = records.length; i < max; i += 1) {
|
||||||
|
assert.strictEqual(records[i], testRecord + devices[i]);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
it('stores identity keys', done => {
|
it('removes all sessions for a number', async () => {
|
||||||
store
|
|
||||||
.saveIdentity(identifier, testKey.pubKey)
|
|
||||||
.then(() =>
|
|
||||||
store.loadIdentityKey(identifier).then(key => {
|
|
||||||
assertEqualArrayBuffers(key, testKey.pubKey);
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.then(done, done);
|
|
||||||
});
|
|
||||||
it('returns whether a key is trusted', done => {
|
|
||||||
const newIdentity = libsignal.crypto.getRandomBytes(33);
|
|
||||||
store.saveIdentity(identifier, testKey.pubKey).then(() => {
|
|
||||||
store
|
|
||||||
.isTrustedIdentity(identifier, newIdentity)
|
|
||||||
.then(trusted => {
|
|
||||||
if (trusted) {
|
|
||||||
done(new Error('Allowed to overwrite identity key'));
|
|
||||||
} else {
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(done);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it('returns whether a key is untrusted', done => {
|
|
||||||
const newIdentity = libsignal.crypto.getRandomBytes(33);
|
|
||||||
store.saveIdentity(identifier, testKey.pubKey).then(() => {
|
|
||||||
store
|
|
||||||
.isTrustedIdentity(identifier, testKey.pubKey)
|
|
||||||
.then(trusted => {
|
|
||||||
if (trusted) {
|
|
||||||
done();
|
|
||||||
} else {
|
|
||||||
done(new Error('Allowed to overwrite identity key'));
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(done);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it('stores prekeys', done => {
|
|
||||||
store
|
|
||||||
.storePreKey(1, testKey)
|
|
||||||
.then(() =>
|
|
||||||
store.loadPreKey(1).then(key => {
|
|
||||||
assertEqualArrayBuffers(key.pubKey, testKey.pubKey);
|
|
||||||
assertEqualArrayBuffers(key.privKey, testKey.privKey);
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.then(done, done);
|
|
||||||
});
|
|
||||||
it('deletes prekeys', done => {
|
|
||||||
before(done => {
|
|
||||||
store.storePreKey(2, testKey).then(done);
|
|
||||||
});
|
|
||||||
store
|
|
||||||
.removePreKey(2, testKey)
|
|
||||||
.then(() =>
|
|
||||||
store.loadPreKey(2).then(key => {
|
|
||||||
assert.isUndefined(key);
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.then(done, done);
|
|
||||||
});
|
|
||||||
it('stores signed prekeys', done => {
|
|
||||||
store
|
|
||||||
.storeSignedPreKey(3, testKey)
|
|
||||||
.then(() =>
|
|
||||||
store.loadSignedPreKey(3).then(key => {
|
|
||||||
assertEqualArrayBuffers(key.pubKey, testKey.pubKey);
|
|
||||||
assertEqualArrayBuffers(key.privKey, testKey.privKey);
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.then(done, done);
|
|
||||||
});
|
|
||||||
it('deletes signed prekeys', done => {
|
|
||||||
before(done => {
|
|
||||||
store.storeSignedPreKey(4, testKey).then(done);
|
|
||||||
});
|
|
||||||
store
|
|
||||||
.removeSignedPreKey(4, testKey)
|
|
||||||
.then(() =>
|
|
||||||
store.loadSignedPreKey(4).then(key => {
|
|
||||||
assert.isUndefined(key);
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.then(done, done);
|
|
||||||
});
|
|
||||||
it('stores sessions', done => {
|
|
||||||
const testRecord = 'an opaque string';
|
const testRecord = 'an opaque string';
|
||||||
const devices = [1, 2, 3].map(deviceId => [identifier, deviceId].join('.'));
|
const devices = [1, 2, 3].map(deviceId => [identifier, deviceId].join('.'));
|
||||||
let promise = Promise.resolve();
|
|
||||||
devices.forEach(encodedNumber => {
|
await Promise.all(
|
||||||
promise = promise.then(() =>
|
devices.map(async encodedNumber => {
|
||||||
store.storeSession(encodedNumber, testRecord + encodedNumber)
|
await store.storeSession(encodedNumber, testRecord + encodedNumber);
|
||||||
);
|
})
|
||||||
});
|
);
|
||||||
promise
|
|
||||||
.then(() =>
|
await store.removeAllSessions(identifier);
|
||||||
Promise.all(devices.map(store.loadSession.bind(store))).then(
|
|
||||||
records => {
|
const records = await Promise.all(
|
||||||
for (const i in records) {
|
devices.map(store.loadSession.bind(store))
|
||||||
assert.strictEqual(records[i], testRecord + devices[i]);
|
);
|
||||||
}
|
|
||||||
}
|
for (let i = 0, max = records.length; i < max; i += 1) {
|
||||||
)
|
assert.isUndefined(records[i]);
|
||||||
)
|
}
|
||||||
.then(done, done);
|
|
||||||
});
|
});
|
||||||
it('removes all sessions for a number', done => {
|
it('returns deviceIds for a number', async () => {
|
||||||
const testRecord = 'an opaque string';
|
const testRecord = 'an opaque string';
|
||||||
const devices = [1, 2, 3].map(deviceId => [identifier, deviceId].join('.'));
|
const devices = [1, 2, 3].map(deviceId => [identifier, deviceId].join('.'));
|
||||||
let promise = Promise.resolve();
|
|
||||||
devices.forEach(encodedNumber => {
|
await Promise.all(
|
||||||
promise = promise.then(() =>
|
devices.map(async encodedNumber => {
|
||||||
store.storeSession(encodedNumber, testRecord + encodedNumber)
|
await store.storeSession(encodedNumber, testRecord + encodedNumber);
|
||||||
);
|
})
|
||||||
});
|
);
|
||||||
promise
|
|
||||||
.then(() =>
|
const deviceIds = await store.getDeviceIds(identifier);
|
||||||
store.removeAllSessions(identifier).then(record =>
|
assert.sameMembers(deviceIds, [1, 2, 3]);
|
||||||
Promise.all(devices.map(store.loadSession.bind(store))).then(
|
|
||||||
records => {
|
|
||||||
for (const i in records) {
|
|
||||||
assert.isUndefined(records[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.then(done, done);
|
|
||||||
});
|
});
|
||||||
it('returns deviceIds for a number', done => {
|
it('returns empty array for a number with no device ids', async () => {
|
||||||
const testRecord = 'an opaque string';
|
const deviceIds = await store.getDeviceIds('foo');
|
||||||
const devices = [1, 2, 3].map(deviceId => [identifier, deviceId].join('.'));
|
assert.sameMembers(deviceIds, []);
|
||||||
let promise = Promise.resolve();
|
|
||||||
devices.forEach(encodedNumber => {
|
|
||||||
promise = promise.then(() =>
|
|
||||||
store.storeSession(encodedNumber, testRecord + encodedNumber)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
promise
|
|
||||||
.then(() =>
|
|
||||||
store.getDeviceIds(identifier).then(deviceIds => {
|
|
||||||
assert.sameMembers(deviceIds, [1, 2, 3]);
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.then(done, done);
|
|
||||||
});
|
});
|
||||||
it('returns empty array for a number with no device ids', () =>
|
|
||||||
store.getDeviceIds('foo').then(deviceIds => {
|
|
||||||
assert.sameMembers(deviceIds, []);
|
|
||||||
}));
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
|
/* global textsecure */
|
||||||
|
|
||||||
describe('createTaskWithTimeout', () => {
|
describe('createTaskWithTimeout', () => {
|
||||||
it('resolves when promise resolves', () => {
|
it('resolves when promise resolves', () => {
|
||||||
const task = function() {
|
const task = () => Promise.resolve('hi!');
|
||||||
return Promise.resolve('hi!');
|
|
||||||
};
|
|
||||||
const taskWithTimeout = textsecure.createTaskWithTimeout(task);
|
const taskWithTimeout = textsecure.createTaskWithTimeout(task);
|
||||||
|
|
||||||
return taskWithTimeout().then(result => {
|
return taskWithTimeout().then(result => {
|
||||||
|
@ -11,26 +11,22 @@ describe('createTaskWithTimeout', () => {
|
||||||
});
|
});
|
||||||
it('flows error from promise back', () => {
|
it('flows error from promise back', () => {
|
||||||
const error = new Error('original');
|
const error = new Error('original');
|
||||||
const task = function() {
|
const task = () => Promise.reject(error);
|
||||||
return Promise.reject(error);
|
|
||||||
};
|
|
||||||
const taskWithTimeout = textsecure.createTaskWithTimeout(task);
|
const taskWithTimeout = textsecure.createTaskWithTimeout(task);
|
||||||
|
|
||||||
return taskWithTimeout().catch(flowedError => {
|
return taskWithTimeout().catch(flowedError => {
|
||||||
assert.strictEqual(error, flowedError);
|
assert.strictEqual(error, flowedError);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('rejects if promise takes too long (this one logs error to console)', function() {
|
it('rejects if promise takes too long (this one logs error to console)', () => {
|
||||||
const error = new Error('original');
|
|
||||||
let complete = false;
|
let complete = false;
|
||||||
const task = function() {
|
const task = () =>
|
||||||
return new Promise(resolve => {
|
new Promise(resolve => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
complete = true;
|
complete = true;
|
||||||
resolve();
|
resolve();
|
||||||
}, 3000);
|
}, 3000);
|
||||||
});
|
});
|
||||||
};
|
|
||||||
const taskWithTimeout = textsecure.createTaskWithTimeout(task, this.name, {
|
const taskWithTimeout = textsecure.createTaskWithTimeout(task, this.name, {
|
||||||
timeout: 10,
|
timeout: 10,
|
||||||
});
|
});
|
||||||
|
@ -45,29 +41,27 @@ describe('createTaskWithTimeout', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
it('resolves if task returns something falsey', () => {
|
it('resolves if task returns something falsey', () => {
|
||||||
const task = function() {};
|
const task = () => {};
|
||||||
const taskWithTimeout = textsecure.createTaskWithTimeout(task);
|
const taskWithTimeout = textsecure.createTaskWithTimeout(task);
|
||||||
return taskWithTimeout();
|
return taskWithTimeout();
|
||||||
});
|
});
|
||||||
it('resolves if task returns a non-promise', () => {
|
it('resolves if task returns a non-promise', () => {
|
||||||
const task = function() {
|
const task = () => 'hi!';
|
||||||
return 'hi!';
|
|
||||||
};
|
|
||||||
const taskWithTimeout = textsecure.createTaskWithTimeout(task);
|
const taskWithTimeout = textsecure.createTaskWithTimeout(task);
|
||||||
return taskWithTimeout().then(result => {
|
return taskWithTimeout().then(result => {
|
||||||
assert.strictEqual(result, 'hi!');
|
assert.strictEqual(result, 'hi!');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('rejects if task throws (and does not log about taking too long)', function() {
|
it('rejects if task throws (and does not log about taking too long)', () => {
|
||||||
const error = new Error('Task is throwing!');
|
const error = new Error('Task is throwing!');
|
||||||
const task = function() {
|
const task = () => {
|
||||||
throw error;
|
throw error;
|
||||||
};
|
};
|
||||||
const taskWithTimeout = textsecure.createTaskWithTimeout(task, this.name, {
|
const taskWithTimeout = textsecure.createTaskWithTimeout(task, this.name, {
|
||||||
timeout: 10,
|
timeout: 10,
|
||||||
});
|
});
|
||||||
return taskWithTimeout().then(
|
return taskWithTimeout().then(
|
||||||
result => {
|
() => {
|
||||||
throw new Error('Overall task should reject!');
|
throw new Error('Overall task should reject!');
|
||||||
},
|
},
|
||||||
flowedError => {
|
flowedError => {
|
||||||
|
|
|
@ -1,208 +1,214 @@
|
||||||
(function() {
|
/* global textsecure, WebSocketResource */
|
||||||
describe('WebSocket-Resource', () => {
|
|
||||||
describe('requests and responses', () => {
|
|
||||||
it('receives requests and sends responses', done => {
|
|
||||||
// mock socket
|
|
||||||
const request_id = '1';
|
|
||||||
const socket = {
|
|
||||||
send(data) {
|
|
||||||
const message = textsecure.protobuf.WebSocketMessage.decode(data);
|
|
||||||
assert.strictEqual(
|
|
||||||
message.type,
|
|
||||||
textsecure.protobuf.WebSocketMessage.Type.RESPONSE
|
|
||||||
);
|
|
||||||
assert.strictEqual(message.response.message, 'OK');
|
|
||||||
assert.strictEqual(message.response.status, 200);
|
|
||||||
assert.strictEqual(message.response.id.toString(), request_id);
|
|
||||||
done();
|
|
||||||
},
|
|
||||||
addEventListener() {},
|
|
||||||
};
|
|
||||||
|
|
||||||
// actual test
|
describe('WebSocket-Resource', () => {
|
||||||
const resource = new WebSocketResource(socket, {
|
describe('requests and responses', () => {
|
||||||
handleRequest(request) {
|
it('receives requests and sends responses', done => {
|
||||||
assert.strictEqual(request.verb, 'PUT');
|
// mock socket
|
||||||
assert.strictEqual(request.path, '/some/path');
|
const requestId = '1';
|
||||||
assertEqualArrayBuffers(
|
const socket = {
|
||||||
request.body.toArrayBuffer(),
|
send(data) {
|
||||||
new Uint8Array([1, 2, 3]).buffer
|
const message = textsecure.protobuf.WebSocketMessage.decode(data);
|
||||||
);
|
assert.strictEqual(
|
||||||
request.respond(200, 'OK');
|
message.type,
|
||||||
},
|
textsecure.protobuf.WebSocketMessage.Type.RESPONSE
|
||||||
});
|
);
|
||||||
|
assert.strictEqual(message.response.message, 'OK');
|
||||||
|
assert.strictEqual(message.response.status, 200);
|
||||||
|
assert.strictEqual(message.response.id.toString(), requestId);
|
||||||
|
done();
|
||||||
|
},
|
||||||
|
addEventListener() {},
|
||||||
|
};
|
||||||
|
|
||||||
// mock socket request
|
// actual test
|
||||||
socket.onmessage({
|
this.resource = new WebSocketResource(socket, {
|
||||||
data: new Blob([
|
handleRequest(request) {
|
||||||
new textsecure.protobuf.WebSocketMessage({
|
assert.strictEqual(request.verb, 'PUT');
|
||||||
type: textsecure.protobuf.WebSocketMessage.Type.REQUEST,
|
assert.strictEqual(request.path, '/some/path');
|
||||||
request: {
|
assertEqualArrayBuffers(
|
||||||
id: request_id,
|
request.body.toArrayBuffer(),
|
||||||
verb: 'PUT',
|
new Uint8Array([1, 2, 3]).buffer
|
||||||
path: '/some/path',
|
);
|
||||||
body: new Uint8Array([1, 2, 3]).buffer,
|
request.respond(200, 'OK');
|
||||||
},
|
},
|
||||||
})
|
|
||||||
.encode()
|
|
||||||
.toArrayBuffer(),
|
|
||||||
]),
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('sends requests and receives responses', done => {
|
// mock socket request
|
||||||
// mock socket and request handler
|
socket.onmessage({
|
||||||
let request_id;
|
data: new Blob([
|
||||||
const socket = {
|
new textsecure.protobuf.WebSocketMessage({
|
||||||
send(data) {
|
type: textsecure.protobuf.WebSocketMessage.Type.REQUEST,
|
||||||
const message = textsecure.protobuf.WebSocketMessage.decode(data);
|
request: {
|
||||||
assert.strictEqual(
|
id: requestId,
|
||||||
message.type,
|
verb: 'PUT',
|
||||||
textsecure.protobuf.WebSocketMessage.Type.REQUEST
|
path: '/some/path',
|
||||||
);
|
body: new Uint8Array([1, 2, 3]).buffer,
|
||||||
assert.strictEqual(message.request.verb, 'PUT');
|
},
|
||||||
assert.strictEqual(message.request.path, '/some/path');
|
})
|
||||||
assertEqualArrayBuffers(
|
.encode()
|
||||||
message.request.body.toArrayBuffer(),
|
.toArrayBuffer(),
|
||||||
new Uint8Array([1, 2, 3]).buffer
|
]),
|
||||||
);
|
|
||||||
request_id = message.request.id;
|
|
||||||
},
|
|
||||||
addEventListener() {},
|
|
||||||
};
|
|
||||||
|
|
||||||
// actual test
|
|
||||||
const resource = new WebSocketResource(socket);
|
|
||||||
resource.sendRequest({
|
|
||||||
verb: 'PUT',
|
|
||||||
path: '/some/path',
|
|
||||||
body: new Uint8Array([1, 2, 3]).buffer,
|
|
||||||
error: done,
|
|
||||||
success(message, status, request) {
|
|
||||||
assert.strictEqual(message, 'OK');
|
|
||||||
assert.strictEqual(status, 200);
|
|
||||||
done();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// mock socket response
|
|
||||||
socket.onmessage({
|
|
||||||
data: new Blob([
|
|
||||||
new textsecure.protobuf.WebSocketMessage({
|
|
||||||
type: textsecure.protobuf.WebSocketMessage.Type.RESPONSE,
|
|
||||||
response: { id: request_id, message: 'OK', status: 200 },
|
|
||||||
})
|
|
||||||
.encode()
|
|
||||||
.toArrayBuffer(),
|
|
||||||
]),
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('close', () => {
|
it('sends requests and receives responses', done => {
|
||||||
before(() => {
|
// mock socket and request handler
|
||||||
window.WebSocket = MockSocket;
|
let requestId;
|
||||||
});
|
const socket = {
|
||||||
after(() => {
|
send(data) {
|
||||||
window.WebSocket = WebSocket;
|
const message = textsecure.protobuf.WebSocketMessage.decode(data);
|
||||||
});
|
assert.strictEqual(
|
||||||
it('closes the connection', done => {
|
message.type,
|
||||||
const mockServer = new MockServer('ws://localhost:8081');
|
textsecure.protobuf.WebSocketMessage.Type.REQUEST
|
||||||
mockServer.on('connection', server => {
|
);
|
||||||
server.on('close', done);
|
assert.strictEqual(message.request.verb, 'PUT');
|
||||||
});
|
assert.strictEqual(message.request.path, '/some/path');
|
||||||
const resource = new WebSocketResource(
|
assertEqualArrayBuffers(
|
||||||
new WebSocket('ws://localhost:8081')
|
message.request.body.toArrayBuffer(),
|
||||||
);
|
new Uint8Array([1, 2, 3]).buffer
|
||||||
resource.close();
|
);
|
||||||
});
|
requestId = message.request.id;
|
||||||
});
|
},
|
||||||
|
addEventListener() {},
|
||||||
|
};
|
||||||
|
|
||||||
describe.skip('with a keepalive config', function() {
|
// actual test
|
||||||
before(() => {
|
const resource = new WebSocketResource(socket);
|
||||||
window.WebSocket = MockSocket;
|
resource.sendRequest({
|
||||||
});
|
verb: 'PUT',
|
||||||
after(() => {
|
path: '/some/path',
|
||||||
window.WebSocket = WebSocket;
|
body: new Uint8Array([1, 2, 3]).buffer,
|
||||||
});
|
error: done,
|
||||||
this.timeout(60000);
|
success(message, status) {
|
||||||
it('sends keepalives once a minute', done => {
|
assert.strictEqual(message, 'OK');
|
||||||
const mockServer = new MockServer('ws://localhost:8081');
|
assert.strictEqual(status, 200);
|
||||||
mockServer.on('connection', server => {
|
done();
|
||||||
server.on('message', data => {
|
},
|
||||||
const message = textsecure.protobuf.WebSocketMessage.decode(data);
|
|
||||||
assert.strictEqual(
|
|
||||||
message.type,
|
|
||||||
textsecure.protobuf.WebSocketMessage.Type.REQUEST
|
|
||||||
);
|
|
||||||
assert.strictEqual(message.request.verb, 'GET');
|
|
||||||
assert.strictEqual(message.request.path, '/v1/keepalive');
|
|
||||||
server.close();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
new WebSocketResource(new WebSocket('ws://localhost:8081'), {
|
|
||||||
keepalive: { path: '/v1/keepalive' },
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('uses / as a default path', done => {
|
// mock socket response
|
||||||
const mockServer = new MockServer('ws://localhost:8081');
|
socket.onmessage({
|
||||||
mockServer.on('connection', server => {
|
data: new Blob([
|
||||||
server.on('message', data => {
|
new textsecure.protobuf.WebSocketMessage({
|
||||||
const message = textsecure.protobuf.WebSocketMessage.decode(data);
|
type: textsecure.protobuf.WebSocketMessage.Type.RESPONSE,
|
||||||
assert.strictEqual(
|
response: { id: requestId, message: 'OK', status: 200 },
|
||||||
message.type,
|
})
|
||||||
textsecure.protobuf.WebSocketMessage.Type.REQUEST
|
.encode()
|
||||||
);
|
.toArrayBuffer(),
|
||||||
assert.strictEqual(message.request.verb, 'GET');
|
]),
|
||||||
assert.strictEqual(message.request.path, '/');
|
|
||||||
server.close();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
new WebSocketResource(new WebSocket('ws://localhost:8081'), {
|
|
||||||
keepalive: true,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('optionally disconnects if no response', function(done) {
|
|
||||||
this.timeout(65000);
|
|
||||||
const mockServer = new MockServer('ws://localhost:8081');
|
|
||||||
const socket = new WebSocket('ws://localhost:8081');
|
|
||||||
mockServer.on('connection', server => {
|
|
||||||
server.on('close', done);
|
|
||||||
});
|
|
||||||
new WebSocketResource(socket, { keepalive: true });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('allows resetting the keepalive timer', function(done) {
|
|
||||||
this.timeout(65000);
|
|
||||||
const mockServer = new MockServer('ws://localhost:8081');
|
|
||||||
const socket = new WebSocket('ws://localhost:8081');
|
|
||||||
const startTime = Date.now();
|
|
||||||
mockServer.on('connection', server => {
|
|
||||||
server.on('message', data => {
|
|
||||||
const message = textsecure.protobuf.WebSocketMessage.decode(data);
|
|
||||||
assert.strictEqual(
|
|
||||||
message.type,
|
|
||||||
textsecure.protobuf.WebSocketMessage.Type.REQUEST
|
|
||||||
);
|
|
||||||
assert.strictEqual(message.request.verb, 'GET');
|
|
||||||
assert.strictEqual(message.request.path, '/');
|
|
||||||
assert(
|
|
||||||
Date.now() > startTime + 60000,
|
|
||||||
'keepalive time should be longer than a minute'
|
|
||||||
);
|
|
||||||
server.close();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
const resource = new WebSocketResource(socket, { keepalive: true });
|
|
||||||
setTimeout(() => {
|
|
||||||
resource.resetKeepAliveTimer();
|
|
||||||
}, 5000);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
})();
|
|
||||||
|
describe('close', () => {
|
||||||
|
before(() => {
|
||||||
|
window.WebSocket = MockSocket;
|
||||||
|
});
|
||||||
|
after(() => {
|
||||||
|
window.WebSocket = WebSocket;
|
||||||
|
});
|
||||||
|
it('closes the connection', done => {
|
||||||
|
const mockServer = new MockServer('ws://localhost:8081');
|
||||||
|
mockServer.on('connection', server => {
|
||||||
|
server.on('close', done);
|
||||||
|
});
|
||||||
|
const resource = new WebSocketResource(
|
||||||
|
new WebSocket('ws://localhost:8081')
|
||||||
|
);
|
||||||
|
resource.close();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe.skip('with a keepalive config', function thisNeeded() {
|
||||||
|
before(() => {
|
||||||
|
window.WebSocket = MockSocket;
|
||||||
|
});
|
||||||
|
after(() => {
|
||||||
|
window.WebSocket = WebSocket;
|
||||||
|
});
|
||||||
|
this.timeout(60000);
|
||||||
|
it('sends keepalives once a minute', done => {
|
||||||
|
const mockServer = new MockServer('ws://localhost:8081');
|
||||||
|
mockServer.on('connection', server => {
|
||||||
|
server.on('message', data => {
|
||||||
|
const message = textsecure.protobuf.WebSocketMessage.decode(data);
|
||||||
|
assert.strictEqual(
|
||||||
|
message.type,
|
||||||
|
textsecure.protobuf.WebSocketMessage.Type.REQUEST
|
||||||
|
);
|
||||||
|
assert.strictEqual(message.request.verb, 'GET');
|
||||||
|
assert.strictEqual(message.request.path, '/v1/keepalive');
|
||||||
|
server.close();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
this.resource = new WebSocketResource(
|
||||||
|
new WebSocket('ws://loc1alhost:8081'),
|
||||||
|
{
|
||||||
|
keepalive: { path: '/v1/keepalive' },
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses / as a default path', done => {
|
||||||
|
const mockServer = new MockServer('ws://localhost:8081');
|
||||||
|
mockServer.on('connection', server => {
|
||||||
|
server.on('message', data => {
|
||||||
|
const message = textsecure.protobuf.WebSocketMessage.decode(data);
|
||||||
|
assert.strictEqual(
|
||||||
|
message.type,
|
||||||
|
textsecure.protobuf.WebSocketMessage.Type.REQUEST
|
||||||
|
);
|
||||||
|
assert.strictEqual(message.request.verb, 'GET');
|
||||||
|
assert.strictEqual(message.request.path, '/');
|
||||||
|
server.close();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
this.resource = new WebSocketResource(
|
||||||
|
new WebSocket('ws://localhost:8081'),
|
||||||
|
{
|
||||||
|
keepalive: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('optionally disconnects if no response', function thisNeeded1(done) {
|
||||||
|
this.timeout(65000);
|
||||||
|
const mockServer = new MockServer('ws://localhost:8081');
|
||||||
|
const socket = new WebSocket('ws://localhost:8081');
|
||||||
|
mockServer.on('connection', server => {
|
||||||
|
server.on('close', done);
|
||||||
|
});
|
||||||
|
this.resource = new WebSocketResource(socket, { keepalive: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('allows resetting the keepalive timer', function thisNeeded2(done) {
|
||||||
|
this.timeout(65000);
|
||||||
|
const mockServer = new MockServer('ws://localhost:8081');
|
||||||
|
const socket = new WebSocket('ws://localhost:8081');
|
||||||
|
const startTime = Date.now();
|
||||||
|
mockServer.on('connection', server => {
|
||||||
|
server.on('message', data => {
|
||||||
|
const message = textsecure.protobuf.WebSocketMessage.decode(data);
|
||||||
|
assert.strictEqual(
|
||||||
|
message.type,
|
||||||
|
textsecure.protobuf.WebSocketMessage.Type.REQUEST
|
||||||
|
);
|
||||||
|
assert.strictEqual(message.request.verb, 'GET');
|
||||||
|
assert.strictEqual(message.request.path, '/');
|
||||||
|
assert(
|
||||||
|
Date.now() > startTime + 60000,
|
||||||
|
'keepalive time should be longer than a minute'
|
||||||
|
);
|
||||||
|
server.close();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
const resource = new WebSocketResource(socket, { keepalive: true });
|
||||||
|
setTimeout(() => {
|
||||||
|
resource.resetKeepAliveTimer();
|
||||||
|
}, 5000);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
/* global TextSecureWebSocket */
|
||||||
|
|
||||||
describe('TextSecureWebSocket', () => {
|
describe('TextSecureWebSocket', () => {
|
||||||
const RealWebSocket = window.WebSocket;
|
const RealWebSocket = window.WebSocket;
|
||||||
before(() => {
|
before(() => {
|
||||||
|
@ -13,19 +15,19 @@ describe('TextSecureWebSocket', () => {
|
||||||
server.close();
|
server.close();
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
var socket = new TextSecureWebSocket('ws://localhost:8080');
|
const socket = new TextSecureWebSocket('ws://localhost:8080');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('sends and receives', done => {
|
it('sends and receives', done => {
|
||||||
const mockServer = new MockServer('ws://localhost:8080');
|
const mockServer = new MockServer('ws://localhost:8080');
|
||||||
mockServer.on('connection', server => {
|
mockServer.on('connection', server => {
|
||||||
server.on('message', data => {
|
server.on('message', () => {
|
||||||
server.send('ack');
|
server.send('ack');
|
||||||
server.close();
|
server.close();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
const socket = new TextSecureWebSocket('ws://localhost:8080');
|
const socket = new TextSecureWebSocket('ws://localhost:8080');
|
||||||
socket.onmessage = function(response) {
|
socket.onmessage = response => {
|
||||||
assert.strictEqual(response.data, 'ack');
|
assert.strictEqual(response.data, 'ack');
|
||||||
socket.close();
|
socket.close();
|
||||||
done();
|
done();
|
||||||
|
@ -40,20 +42,20 @@ describe('TextSecureWebSocket', () => {
|
||||||
server.close();
|
server.close();
|
||||||
socket.close();
|
socket.close();
|
||||||
});
|
});
|
||||||
var socket = new TextSecureWebSocket('ws://localhost:8082');
|
const socket = new TextSecureWebSocket('ws://localhost:8082');
|
||||||
socket.onclose = function() {
|
socket.onclose = () => {
|
||||||
assert.strictEqual(socket.getStatus(), WebSocket.CLOSING);
|
assert.strictEqual(socket.getStatus(), WebSocket.CLOSING);
|
||||||
done();
|
done();
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
it('reconnects', function(done) {
|
it('reconnects', function thisNeeded(done) {
|
||||||
this.timeout(60000);
|
this.timeout(60000);
|
||||||
const mockServer = new MockServer('ws://localhost:8082');
|
const mockServer = new MockServer('ws://localhost:8082');
|
||||||
const socket = new TextSecureWebSocket('ws://localhost:8082');
|
const socket = new TextSecureWebSocket('ws://localhost:8082');
|
||||||
socket.onclose = function() {
|
socket.onclose = () => {
|
||||||
const mockServer = new MockServer('ws://localhost:8082');
|
const secondServer = new MockServer('ws://localhost:8082');
|
||||||
mockServer.on('connection', server => {
|
secondServer.on('connection', server => {
|
||||||
socket.close();
|
socket.close();
|
||||||
server.close();
|
server.close();
|
||||||
done();
|
done();
|
||||||
|
|
|
@ -8,6 +8,15 @@ module.exports = {
|
||||||
|
|
||||||
globals: {
|
globals: {
|
||||||
assert: true,
|
assert: true,
|
||||||
|
assertEqualArrayBuffers: true,
|
||||||
|
clearDatabase: true,
|
||||||
|
dcodeIO: true,
|
||||||
|
getString: true,
|
||||||
|
hexToArrayBuffer: true,
|
||||||
|
MockServer: true,
|
||||||
|
MockSocket: true,
|
||||||
|
PROTO_ROOT: true,
|
||||||
|
stringToArrayBuffer: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
|
@ -25,5 +34,8 @@ module.exports = {
|
||||||
|
|
||||||
// We want to keep each test structured the same, even if its contents are tiny
|
// We want to keep each test structured the same, even if its contents are tiny
|
||||||
'arrow-body-style': 'off',
|
'arrow-body-style': 'off',
|
||||||
|
|
||||||
|
strict: 'off',
|
||||||
|
'more/no-then': 'off',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,43 +1,45 @@
|
||||||
|
/* global chai, Whisper */
|
||||||
|
|
||||||
mocha.setup('bdd');
|
mocha.setup('bdd');
|
||||||
window.assert = chai.assert;
|
window.assert = chai.assert;
|
||||||
window.PROTO_ROOT = '../protos';
|
window.PROTO_ROOT = '../protos';
|
||||||
|
|
||||||
(function() {
|
const OriginalReporter = mocha._reporter;
|
||||||
var OriginalReporter = mocha._reporter;
|
|
||||||
|
|
||||||
var SauceReporter = function(runner) {
|
const SauceReporter = runner => {
|
||||||
var failedTests = [];
|
const failedTests = [];
|
||||||
|
|
||||||
runner.on('end', function() {
|
runner.on('end', () => {
|
||||||
window.mochaResults = runner.stats;
|
window.mochaResults = runner.stats;
|
||||||
window.mochaResults.reports = failedTests;
|
window.mochaResults.reports = failedTests;
|
||||||
|
});
|
||||||
|
|
||||||
|
runner.on('fail', (test, err) => {
|
||||||
|
const flattenTitles = item => {
|
||||||
|
const titles = [];
|
||||||
|
while (item.parent.title) {
|
||||||
|
titles.push(item.parent.title);
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
item = item.parent;
|
||||||
|
}
|
||||||
|
return titles.reverse();
|
||||||
|
};
|
||||||
|
failedTests.push({
|
||||||
|
name: test.title,
|
||||||
|
result: false,
|
||||||
|
message: err.message,
|
||||||
|
stack: err.stack,
|
||||||
|
titles: flattenTitles(test),
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
runner.on('fail', function(test, err) {
|
// eslint-disable-next-line no-new
|
||||||
var flattenTitles = function(test) {
|
new OriginalReporter(runner);
|
||||||
var titles = [];
|
};
|
||||||
while (test.parent.title) {
|
|
||||||
titles.push(test.parent.title);
|
|
||||||
test = test.parent;
|
|
||||||
}
|
|
||||||
return titles.reverse();
|
|
||||||
};
|
|
||||||
failedTests.push({
|
|
||||||
name: test.title,
|
|
||||||
result: false,
|
|
||||||
message: err.message,
|
|
||||||
stack: err.stack,
|
|
||||||
titles: flattenTitles(test),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
new OriginalReporter(runner);
|
SauceReporter.prototype = OriginalReporter.prototype;
|
||||||
};
|
|
||||||
|
|
||||||
SauceReporter.prototype = OriginalReporter.prototype;
|
mocha.reporter(SauceReporter);
|
||||||
|
|
||||||
mocha.reporter(SauceReporter);
|
|
||||||
})();
|
|
||||||
|
|
||||||
// Override the database id.
|
// Override the database id.
|
||||||
window.Whisper = window.Whisper || {};
|
window.Whisper = window.Whisper || {};
|
||||||
|
@ -47,22 +49,22 @@ Whisper.Database.id = 'test';
|
||||||
/*
|
/*
|
||||||
* global helpers for tests
|
* global helpers for tests
|
||||||
*/
|
*/
|
||||||
function assertEqualArrayBuffers(ab1, ab2) {
|
window.assertEqualArrayBuffers = (ab1, ab2) => {
|
||||||
assert.deepEqual(new Uint8Array(ab1), new Uint8Array(ab2));
|
assert.deepEqual(new Uint8Array(ab1), new Uint8Array(ab2));
|
||||||
}
|
};
|
||||||
|
|
||||||
function hexToArrayBuffer(str) {
|
window.hexToArrayBuffer = str => {
|
||||||
var ret = new ArrayBuffer(str.length / 2);
|
const ret = new ArrayBuffer(str.length / 2);
|
||||||
var array = new Uint8Array(ret);
|
const array = new Uint8Array(ret);
|
||||||
for (var i = 0; i < str.length / 2; i++) {
|
for (let i = 0; i < str.length / 2; i += 1) {
|
||||||
array[i] = parseInt(str.substr(i * 2, 2), 16);
|
array[i] = parseInt(str.substr(i * 2, 2), 16);
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
};
|
||||||
|
|
||||||
function deleteDatabase() {
|
function deleteIndexedDB() {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
var idbReq = indexedDB.deleteDatabase('test');
|
const idbReq = indexedDB.deleteIndexedDB('test');
|
||||||
idbReq.onsuccess = resolve;
|
idbReq.onsuccess = resolve;
|
||||||
idbReq.error = reject;
|
idbReq.error = reject;
|
||||||
});
|
});
|
||||||
|
@ -70,10 +72,10 @@ function deleteDatabase() {
|
||||||
|
|
||||||
/* Delete the database before running any tests */
|
/* Delete the database before running any tests */
|
||||||
before(async () => {
|
before(async () => {
|
||||||
await deleteDatabase();
|
await deleteIndexedDB();
|
||||||
await window.Signal.Data.removeAll();
|
await window.Signal.Data.removeAll();
|
||||||
});
|
});
|
||||||
|
|
||||||
async function clearDatabase() {
|
window.clearDatabase = async () => {
|
||||||
await window.Signal.Data.removeAll();
|
await window.Signal.Data.removeAll();
|
||||||
}
|
};
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
/* global textsecure: false */
|
/* global textsecure: false */
|
||||||
/* global _: false */
|
/* global _: false */
|
||||||
|
|
||||||
/* eslint-disable no-unreachable */
|
/* eslint-disable no-unreachable, no-console */
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
|
/* global Whisper */
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
describe('ConversationController', function() {
|
describe('ConversationController', () => {
|
||||||
it('sorts conversations based on timestamp then by intl-friendly title', function() {
|
it('sorts conversations based on timestamp then by intl-friendly title', () => {
|
||||||
var collection = window.getInboxCollection();
|
const collection = window.getInboxCollection();
|
||||||
collection.reset([]);
|
collection.reset([]);
|
||||||
|
|
||||||
collection.add(
|
collection.add(
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
/* global Signal, textsecure */
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
describe('Crypto', () => {
|
describe('Crypto', () => {
|
||||||
|
@ -16,39 +18,36 @@ describe('Crypto', () => {
|
||||||
|
|
||||||
describe('symmetric encryption', () => {
|
describe('symmetric encryption', () => {
|
||||||
it('roundtrips', async () => {
|
it('roundtrips', async () => {
|
||||||
var message = 'this is my message';
|
const message = 'this is my message';
|
||||||
var plaintext = new dcodeIO.ByteBuffer.wrap(
|
const plaintext = dcodeIO.ByteBuffer.wrap(
|
||||||
message,
|
message,
|
||||||
'binary'
|
'binary'
|
||||||
).toArrayBuffer();
|
).toArrayBuffer();
|
||||||
var key = textsecure.crypto.getRandomBytes(32);
|
const key = textsecure.crypto.getRandomBytes(32);
|
||||||
|
|
||||||
var encrypted = await Signal.Crypto.encryptSymmetric(key, plaintext);
|
const encrypted = await Signal.Crypto.encryptSymmetric(key, plaintext);
|
||||||
var decrypted = await Signal.Crypto.decryptSymmetric(key, encrypted);
|
const decrypted = await Signal.Crypto.decryptSymmetric(key, encrypted);
|
||||||
|
|
||||||
var equal = Signal.Crypto.constantTimeEqual(plaintext, decrypted);
|
const equal = Signal.Crypto.constantTimeEqual(plaintext, decrypted);
|
||||||
if (!equal) {
|
if (!equal) {
|
||||||
throw new Error('The output and input did not match!');
|
throw new Error('The output and input did not match!');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('roundtrip fails if nonce is modified', async () => {
|
it('roundtrip fails if nonce is modified', async () => {
|
||||||
var message = 'this is my message';
|
const message = 'this is my message';
|
||||||
var plaintext = new dcodeIO.ByteBuffer.wrap(
|
const plaintext = dcodeIO.ByteBuffer.wrap(
|
||||||
message,
|
message,
|
||||||
'binary'
|
'binary'
|
||||||
).toArrayBuffer();
|
).toArrayBuffer();
|
||||||
var key = textsecure.crypto.getRandomBytes(32);
|
const key = textsecure.crypto.getRandomBytes(32);
|
||||||
|
|
||||||
var encrypted = await Signal.Crypto.encryptSymmetric(key, plaintext);
|
const encrypted = await Signal.Crypto.encryptSymmetric(key, plaintext);
|
||||||
var uintArray = new Uint8Array(encrypted);
|
const uintArray = new Uint8Array(encrypted);
|
||||||
uintArray[2] = 9;
|
uintArray[2] = 9;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var decrypted = await Signal.Crypto.decryptSymmetric(
|
await Signal.Crypto.decryptSymmetric(key, uintArray.buffer);
|
||||||
key,
|
|
||||||
uintArray.buffer
|
|
||||||
);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
error.message,
|
error.message,
|
||||||
|
@ -61,22 +60,19 @@ describe('Crypto', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('roundtrip fails if mac is modified', async () => {
|
it('roundtrip fails if mac is modified', async () => {
|
||||||
var message = 'this is my message';
|
const message = 'this is my message';
|
||||||
var plaintext = new dcodeIO.ByteBuffer.wrap(
|
const plaintext = dcodeIO.ByteBuffer.wrap(
|
||||||
message,
|
message,
|
||||||
'binary'
|
'binary'
|
||||||
).toArrayBuffer();
|
).toArrayBuffer();
|
||||||
var key = textsecure.crypto.getRandomBytes(32);
|
const key = textsecure.crypto.getRandomBytes(32);
|
||||||
|
|
||||||
var encrypted = await Signal.Crypto.encryptSymmetric(key, plaintext);
|
const encrypted = await Signal.Crypto.encryptSymmetric(key, plaintext);
|
||||||
var uintArray = new Uint8Array(encrypted);
|
const uintArray = new Uint8Array(encrypted);
|
||||||
uintArray[uintArray.length - 3] = 9;
|
uintArray[uintArray.length - 3] = 9;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var decrypted = await Signal.Crypto.decryptSymmetric(
|
await Signal.Crypto.decryptSymmetric(key, uintArray.buffer);
|
||||||
key,
|
|
||||||
uintArray.buffer
|
|
||||||
);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
error.message,
|
error.message,
|
||||||
|
@ -89,22 +85,19 @@ describe('Crypto', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('roundtrip fails if encrypted contents are modified', async () => {
|
it('roundtrip fails if encrypted contents are modified', async () => {
|
||||||
var message = 'this is my message';
|
const message = 'this is my message';
|
||||||
var plaintext = new dcodeIO.ByteBuffer.wrap(
|
const plaintext = dcodeIO.ByteBuffer.wrap(
|
||||||
message,
|
message,
|
||||||
'binary'
|
'binary'
|
||||||
).toArrayBuffer();
|
).toArrayBuffer();
|
||||||
var key = textsecure.crypto.getRandomBytes(32);
|
const key = textsecure.crypto.getRandomBytes(32);
|
||||||
|
|
||||||
var encrypted = await Signal.Crypto.encryptSymmetric(key, plaintext);
|
const encrypted = await Signal.Crypto.encryptSymmetric(key, plaintext);
|
||||||
var uintArray = new Uint8Array(encrypted);
|
const uintArray = new Uint8Array(encrypted);
|
||||||
uintArray[35] = 9;
|
uintArray[35] = 9;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var decrypted = await Signal.Crypto.decryptSymmetric(
|
await Signal.Crypto.decryptSymmetric(key, uintArray.buffer);
|
||||||
key,
|
|
||||||
uintArray.buffer
|
|
||||||
);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
error.message,
|
error.message,
|
||||||
|
|
|
@ -1,30 +1,32 @@
|
||||||
|
/* global Whisper */
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
describe('Database', function() {
|
describe('Database', () => {
|
||||||
describe('handleDOMException', function() {
|
describe('handleDOMException', () => {
|
||||||
it('handles null, still calls reject', function() {
|
it('handles null, still calls reject', () => {
|
||||||
var called = 0;
|
let called = 0;
|
||||||
var reject = function() {
|
const reject = () => {
|
||||||
called += 1;
|
called += 1;
|
||||||
};
|
};
|
||||||
var error = null;
|
const error = null;
|
||||||
var prefix = 'something';
|
const prefix = 'something';
|
||||||
|
|
||||||
Whisper.Database.handleDOMException(prefix, error, reject);
|
Whisper.Database.handleDOMException(prefix, error, reject);
|
||||||
|
|
||||||
assert.strictEqual(called, 1);
|
assert.strictEqual(called, 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handles object code and message', function() {
|
it('handles object code and message', () => {
|
||||||
var called = 0;
|
let called = 0;
|
||||||
var reject = function() {
|
const reject = () => {
|
||||||
called += 1;
|
called += 1;
|
||||||
};
|
};
|
||||||
var error = {
|
const error = {
|
||||||
code: 4,
|
code: 4,
|
||||||
message: 'some cryptic error',
|
message: 'some cryptic error',
|
||||||
};
|
};
|
||||||
var prefix = 'something';
|
const prefix = 'something';
|
||||||
|
|
||||||
Whisper.Database.handleDOMException(prefix, error, reject);
|
Whisper.Database.handleDOMException(prefix, error, reject);
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
Whisper.Fixtures = function() {
|
/* global Whisper */
|
||||||
var VERA_ID = '+13016886524'; // nsa
|
|
||||||
var NESTOR_ID = '+17034820623'; // cia
|
|
||||||
var MASHA_ID = '+441242221491'; // gchq
|
|
||||||
var FRED_ID = '+14155537400'; // fbi sf
|
|
||||||
var MICHEL_ID = '+12024561111'; // twh
|
|
||||||
|
|
||||||
var now = Date.now();
|
Whisper.Fixtures = () => {
|
||||||
var conversationCollection = new Whisper.ConversationCollection([
|
const VERA_ID = '+13016886524'; // nsa
|
||||||
|
const NESTOR_ID = '+17034820623'; // cia
|
||||||
|
const MASHA_ID = '+441242221491'; // gchq
|
||||||
|
const FRED_ID = '+14155537400'; // fbi sf
|
||||||
|
const MICHEL_ID = '+12024561111'; // twh
|
||||||
|
|
||||||
|
const now = Date.now();
|
||||||
|
const conversationCollection = new Whisper.ConversationCollection([
|
||||||
{
|
{
|
||||||
name: 'Vera Zasulich',
|
name: 'Vera Zasulich',
|
||||||
id: VERA_ID,
|
id: VERA_ID,
|
||||||
|
@ -51,7 +53,7 @@ Whisper.Fixtures = function() {
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
var Vera = conversationCollection.get(VERA_ID);
|
const Vera = conversationCollection.get(VERA_ID);
|
||||||
Vera.messageCollection.add([
|
Vera.messageCollection.add([
|
||||||
{
|
{
|
||||||
conversationId: VERA_ID,
|
conversationId: VERA_ID,
|
||||||
|
@ -62,7 +64,7 @@ Whisper.Fixtures = function() {
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
var Nestor = conversationCollection.get(NESTOR_ID);
|
const Nestor = conversationCollection.get(NESTOR_ID);
|
||||||
Nestor.messageCollection.add([
|
Nestor.messageCollection.add([
|
||||||
{
|
{
|
||||||
conversationId: NESTOR_ID,
|
conversationId: NESTOR_ID,
|
||||||
|
@ -73,7 +75,7 @@ Whisper.Fixtures = function() {
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
var Fred = conversationCollection.get(FRED_ID);
|
const Fred = conversationCollection.get(FRED_ID);
|
||||||
Fred.messageCollection.add([
|
Fred.messageCollection.add([
|
||||||
{
|
{
|
||||||
conversationId: FRED_ID,
|
conversationId: FRED_ID,
|
||||||
|
@ -85,7 +87,7 @@ Whisper.Fixtures = function() {
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
var Michel = conversationCollection.get(MICHEL_ID);
|
const Michel = conversationCollection.get(MICHEL_ID);
|
||||||
Michel.messageCollection.add([
|
Michel.messageCollection.add([
|
||||||
{
|
{
|
||||||
conversationId: MICHEL_ID,
|
conversationId: MICHEL_ID,
|
||||||
|
@ -118,7 +120,7 @@ Whisper.Fixtures = function() {
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
var Masha = conversationCollection.get(MASHA_ID);
|
const Masha = conversationCollection.get(MASHA_ID);
|
||||||
Masha.messageCollection.add(
|
Masha.messageCollection.add(
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
|
@ -150,7 +152,7 @@ Whisper.Fixtures = function() {
|
||||||
body: "I can't wait to try it!",
|
body: "I can't wait to try it!",
|
||||||
unread: 1,
|
unread: 1,
|
||||||
},
|
},
|
||||||
].map(function(m) {
|
].map((m) => {
|
||||||
return {
|
return {
|
||||||
conversationId: MASHA_ID,
|
conversationId: MASHA_ID,
|
||||||
type: m.type,
|
type: m.type,
|
||||||
|
@ -165,7 +167,7 @@ Whisper.Fixtures = function() {
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
var group = conversationCollection.add({
|
const group = conversationCollection.add({
|
||||||
name: '📖 Book Club',
|
name: '📖 Book Club',
|
||||||
type: 'group',
|
type: 'group',
|
||||||
active_at: now - 100000,
|
active_at: now - 100000,
|
||||||
|
@ -210,7 +212,7 @@ Whisper.Fixtures = function() {
|
||||||
delivered_to: [MICHEL_ID, FRED_ID],
|
delivered_to: [MICHEL_ID, FRED_ID],
|
||||||
sent_to: [NESTOR_ID],
|
sent_to: [NESTOR_ID],
|
||||||
},
|
},
|
||||||
].map(function(m) {
|
].map((m) => {
|
||||||
return Object.assign({}, m, {
|
return Object.assign({}, m, {
|
||||||
conversationId: group.id,
|
conversationId: group.id,
|
||||||
sent_at: m.date,
|
sent_at: m.date,
|
||||||
|
@ -221,16 +223,16 @@ Whisper.Fixtures = function() {
|
||||||
);
|
);
|
||||||
|
|
||||||
function dataURItoBlob(dataURI) {
|
function dataURItoBlob(dataURI) {
|
||||||
var binary = atob(dataURI.split(',')[1]);
|
const binary = atob(dataURI.split(',')[1]);
|
||||||
var array = [];
|
const array = [];
|
||||||
for (var i = 0; i < binary.length; i++) {
|
for (let i = 0; i < binary.length; i += 1) {
|
||||||
array.push(binary.charCodeAt(i));
|
array.push(binary.charCodeAt(i));
|
||||||
}
|
}
|
||||||
return new Uint8Array(array).buffer;
|
return new Uint8Array(array).buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
conversationCollection.saveAll = function() {
|
conversationCollection.saveAll = function thisNeeded() {
|
||||||
return Promise.all(
|
Promise.all(
|
||||||
this.map(async (convo) => {
|
this.map(async (convo) => {
|
||||||
await window.Signal.Data.saveConversation(convo.attributes, {
|
await window.Signal.Data.saveConversation(convo.attributes, {
|
||||||
Conversation: Whisper.Conversation,
|
Conversation: Whisper.Conversation,
|
||||||
|
@ -239,7 +241,7 @@ Whisper.Fixtures = function() {
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
convo.messageCollection.map(async (message) => {
|
convo.messageCollection.map(async (message) => {
|
||||||
const id = await window.Signal.Data.saveMessage(message.attributes, {
|
const id = await window.Signal.Data.saveMessage(message.attributes, {
|
||||||
Message: Whisper.Message
|
Message: Whisper.Message,
|
||||||
});
|
});
|
||||||
message.set({ id });
|
message.set({ id });
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
|
/* global $, ConversationController, textsecure, Whisper */
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
describe('Fixtures', function() {
|
describe('Fixtures', () => {
|
||||||
before(async function() {
|
before(async () => {
|
||||||
// NetworkStatusView checks this method every five seconds while showing
|
// NetworkStatusView checks this method every five seconds while showing
|
||||||
window.getSocketStatus = function() {
|
window.getSocketStatus = () => WebSocket.OPEN;
|
||||||
return WebSocket.OPEN;
|
|
||||||
};
|
|
||||||
|
|
||||||
await clearDatabase();
|
await clearDatabase();
|
||||||
await textsecure.storage.user.setNumberAndDeviceId(
|
await textsecure.storage.user.setNumberAndDeviceId(
|
||||||
|
@ -26,11 +26,11 @@ describe('Fixtures', function() {
|
||||||
ConversationController.reset();
|
ConversationController.reset();
|
||||||
await ConversationController.load();
|
await ConversationController.load();
|
||||||
|
|
||||||
var view = new Whisper.InboxView({ window: window });
|
let view = new Whisper.InboxView({ window });
|
||||||
view.onEmpty();
|
view.onEmpty();
|
||||||
view.$el.prependTo($('#render-light-theme'));
|
view.$el.prependTo($('#render-light-theme'));
|
||||||
|
|
||||||
var view = new Whisper.InboxView({ window: window });
|
view = new Whisper.InboxView({ window });
|
||||||
view.$el.removeClass('light-theme').addClass('dark-theme');
|
view.$el.removeClass('light-theme').addClass('dark-theme');
|
||||||
view.onEmpty();
|
view.onEmpty();
|
||||||
view.$el.prependTo($('#render-dark-theme'));
|
view.$el.prependTo($('#render-dark-theme'));
|
||||||
|
|
|
@ -1,16 +1,18 @@
|
||||||
describe('i18n', function() {
|
/* global i18n */
|
||||||
describe('i18n', function() {
|
|
||||||
it('returns empty string for unknown string', function() {
|
describe('i18n', () => {
|
||||||
|
describe('i18n', () => {
|
||||||
|
it('returns empty string for unknown string', () => {
|
||||||
assert.strictEqual(i18n('random'), '');
|
assert.strictEqual(i18n('random'), '');
|
||||||
});
|
});
|
||||||
it('returns message for given string', function() {
|
it('returns message for given string', () => {
|
||||||
assert.equal(i18n('reportIssue'), 'Report an issue');
|
assert.equal(i18n('reportIssue'), 'Report an issue');
|
||||||
});
|
});
|
||||||
it('returns message with single substitution', function() {
|
it('returns message with single substitution', () => {
|
||||||
const actual = i18n('attemptingReconnection', 5);
|
const actual = i18n('attemptingReconnection', 5);
|
||||||
assert.equal(actual, 'Attempting reconnect in 5 seconds');
|
assert.equal(actual, 'Attempting reconnect in 5 seconds');
|
||||||
});
|
});
|
||||||
it('returns message with multiple substitutions', function() {
|
it('returns message with multiple substitutions', () => {
|
||||||
const actual = i18n('theyChangedTheTimer', ['Someone', '5 minutes']);
|
const actual = i18n('theyChangedTheTimer', ['Someone', '5 minutes']);
|
||||||
assert.equal(
|
assert.equal(
|
||||||
actual,
|
actual,
|
||||||
|
@ -19,8 +21,8 @@ describe('i18n', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getLocale', function() {
|
describe('getLocale', () => {
|
||||||
it('returns a string with length two or greater', function() {
|
it('returns a string with length two or greater', () => {
|
||||||
const locale = i18n.getLocale();
|
const locale = i18n.getLocale();
|
||||||
assert.isAtLeast(locale.trim().length, 2);
|
assert.isAtLeast(locale.trim().length, 2);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,26 +1,28 @@
|
||||||
describe('KeyChangeListener', function() {
|
/* global ConversationController, libsignal, SignalProtocolStore, Whisper */
|
||||||
var phoneNumberWithKeyChange = '+13016886524'; // nsa
|
|
||||||
var address = new libsignal.SignalProtocolAddress(
|
describe('KeyChangeListener', () => {
|
||||||
|
const phoneNumberWithKeyChange = '+13016886524'; // nsa
|
||||||
|
const address = new libsignal.SignalProtocolAddress(
|
||||||
phoneNumberWithKeyChange,
|
phoneNumberWithKeyChange,
|
||||||
1
|
1
|
||||||
);
|
);
|
||||||
var oldKey = libsignal.crypto.getRandomBytes(33);
|
const oldKey = libsignal.crypto.getRandomBytes(33);
|
||||||
var newKey = libsignal.crypto.getRandomBytes(33);
|
const newKey = libsignal.crypto.getRandomBytes(33);
|
||||||
var store;
|
let store;
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(() => {
|
||||||
store = new SignalProtocolStore();
|
store = new SignalProtocolStore();
|
||||||
Whisper.KeyChangeListener.init(store);
|
Whisper.KeyChangeListener.init(store);
|
||||||
return store.saveIdentity(address.toString(), oldKey);
|
return store.saveIdentity(address.toString(), oldKey);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(function() {
|
afterEach(() => {
|
||||||
return store.removeIdentityKey(phoneNumberWithKeyChange);
|
return store.removeIdentityKey(phoneNumberWithKeyChange);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('When we have a conversation with this contact', function() {
|
describe('When we have a conversation with this contact', () => {
|
||||||
let convo;
|
let convo;
|
||||||
before(async function() {
|
before(async () => {
|
||||||
convo = ConversationController.dangerouslyCreateAndAdd({
|
convo = ConversationController.dangerouslyCreateAndAdd({
|
||||||
id: phoneNumberWithKeyChange,
|
id: phoneNumberWithKeyChange,
|
||||||
type: 'private',
|
type: 'private',
|
||||||
|
@ -30,12 +32,12 @@ describe('KeyChangeListener', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
after(async function() {
|
after(async () => {
|
||||||
await convo.destroyMessages();
|
await convo.destroyMessages();
|
||||||
await window.Signal.Data.saveConversation(convo.id);
|
await window.Signal.Data.saveConversation(convo.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('generates a key change notice in the private conversation with this contact', function(done) {
|
it('generates a key change notice in the private conversation with this contact', done => {
|
||||||
convo.once('newmessage', async () => {
|
convo.once('newmessage', async () => {
|
||||||
await convo.fetchMessages();
|
await convo.fetchMessages();
|
||||||
const message = convo.messageCollection.at(0);
|
const message = convo.messageCollection.at(0);
|
||||||
|
@ -46,10 +48,9 @@ describe('KeyChangeListener', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('When we have a group with this contact', function() {
|
describe('When we have a group with this contact', () => {
|
||||||
let convo;
|
let convo;
|
||||||
before(async function() {
|
before(async () => {
|
||||||
console.log('Creating group with contact', phoneNumberWithKeyChange);
|
|
||||||
convo = ConversationController.dangerouslyCreateAndAdd({
|
convo = ConversationController.dangerouslyCreateAndAdd({
|
||||||
id: 'groupId',
|
id: 'groupId',
|
||||||
type: 'group',
|
type: 'group',
|
||||||
|
@ -59,12 +60,12 @@ describe('KeyChangeListener', function() {
|
||||||
Conversation: Whisper.Conversation,
|
Conversation: Whisper.Conversation,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
after(async function() {
|
after(async () => {
|
||||||
await convo.destroyMessages();
|
await convo.destroyMessages();
|
||||||
await window.Signal.Data.saveConversation(convo.id);
|
await window.Signal.Data.saveConversation(convo.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('generates a key change notice in the group conversation with this contact', function(done) {
|
it('generates a key change notice in the group conversation with this contact', done => {
|
||||||
convo.once('newmessage', async () => {
|
convo.once('newmessage', async () => {
|
||||||
await convo.fetchMessages();
|
await convo.fetchMessages();
|
||||||
const message = convo.messageCollection.at(0);
|
const message = convo.messageCollection.at(0);
|
||||||
|
|
|
@ -1,25 +1,26 @@
|
||||||
(function() {
|
/* global libphonenumber */
|
||||||
'use strict';
|
|
||||||
describe('libphonenumber util', function() {
|
'use strict';
|
||||||
describe('parseNumber', function() {
|
|
||||||
it('numbers with + are valid without providing regionCode', function() {
|
describe('libphonenumber util', () => {
|
||||||
var result = libphonenumber.util.parseNumber('+14155555555');
|
describe('parseNumber', () => {
|
||||||
|
it('numbers with + are valid without providing regionCode', () => {
|
||||||
|
const result = libphonenumber.util.parseNumber('+14155555555');
|
||||||
|
assert.isTrue(result.isValidNumber);
|
||||||
|
assert.strictEqual(result.nationalNumber, '4155555555');
|
||||||
|
assert.strictEqual(result.e164, '+14155555555');
|
||||||
|
assert.strictEqual(result.regionCode, 'US');
|
||||||
|
assert.strictEqual(result.countryCode, '1');
|
||||||
|
});
|
||||||
|
it('variant numbers with the right regionCode are valid', () => {
|
||||||
|
['4155555555', '14155555555', '+14155555555'].forEach(number => {
|
||||||
|
const result = libphonenumber.util.parseNumber(number, 'US');
|
||||||
assert.isTrue(result.isValidNumber);
|
assert.isTrue(result.isValidNumber);
|
||||||
assert.strictEqual(result.nationalNumber, '4155555555');
|
assert.strictEqual(result.nationalNumber, '4155555555');
|
||||||
assert.strictEqual(result.e164, '+14155555555');
|
assert.strictEqual(result.e164, '+14155555555');
|
||||||
assert.strictEqual(result.regionCode, 'US');
|
assert.strictEqual(result.regionCode, 'US');
|
||||||
assert.strictEqual(result.countryCode, '1');
|
assert.strictEqual(result.countryCode, '1');
|
||||||
});
|
});
|
||||||
it('variant numbers with the right regionCode are valid', function() {
|
|
||||||
['4155555555', '14155555555', '+14155555555'].forEach(function(number) {
|
|
||||||
var result = libphonenumber.util.parseNumber(number, 'US');
|
|
||||||
assert.isTrue(result.isValidNumber);
|
|
||||||
assert.strictEqual(result.nationalNumber, '4155555555');
|
|
||||||
assert.strictEqual(result.e164, '+14155555555');
|
|
||||||
assert.strictEqual(result.regionCode, 'US');
|
|
||||||
assert.strictEqual(result.countryCode, '1');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
})();
|
});
|
||||||
|
|
|
@ -1,226 +1,213 @@
|
||||||
(function() {
|
/* global storage, textsecure, Whisper */
|
||||||
'use strict';
|
|
||||||
var attributes = {
|
'use strict';
|
||||||
type: 'outgoing',
|
|
||||||
body: 'hi',
|
describe('ConversationCollection', () => {
|
||||||
conversationId: 'foo',
|
|
||||||
attachments: [],
|
|
||||||
timestamp: new Date().getTime(),
|
|
||||||
};
|
|
||||||
var conversation_attributes = {
|
|
||||||
type: 'private',
|
|
||||||
id: '+14155555555',
|
|
||||||
};
|
|
||||||
textsecure.messaging = new textsecure.MessageSender('');
|
textsecure.messaging = new textsecure.MessageSender('');
|
||||||
|
|
||||||
describe('ConversationCollection', function() {
|
before(clearDatabase);
|
||||||
before(clearDatabase);
|
after(clearDatabase);
|
||||||
after(clearDatabase);
|
|
||||||
|
|
||||||
it('should be ordered newest to oldest', function() {
|
it('should be ordered newest to oldest', () => {
|
||||||
var conversations = new Whisper.ConversationCollection();
|
const conversations = new Whisper.ConversationCollection();
|
||||||
// Timestamps
|
// Timestamps
|
||||||
var today = new Date();
|
const today = new Date();
|
||||||
var tomorrow = new Date();
|
const tomorrow = new Date();
|
||||||
tomorrow.setDate(today.getDate() + 1);
|
tomorrow.setDate(today.getDate() + 1);
|
||||||
|
|
||||||
// Add convos
|
// Add convos
|
||||||
conversations.add({ timestamp: today });
|
conversations.add({ timestamp: today });
|
||||||
conversations.add({ timestamp: tomorrow });
|
conversations.add({ timestamp: tomorrow });
|
||||||
|
|
||||||
var models = conversations.models;
|
const { models } = conversations;
|
||||||
var firstTimestamp = models[0].get('timestamp').getTime();
|
const firstTimestamp = models[0].get('timestamp').getTime();
|
||||||
var secondTimestamp = models[1].get('timestamp').getTime();
|
const secondTimestamp = models[1].get('timestamp').getTime();
|
||||||
|
|
||||||
// Compare timestamps
|
// Compare timestamps
|
||||||
assert(firstTimestamp > secondTimestamp);
|
assert(firstTimestamp > secondTimestamp);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Conversation', () => {
|
||||||
|
const attributes = { type: 'private', id: '+18085555555' };
|
||||||
|
before(async () => {
|
||||||
|
const convo = new Whisper.ConversationCollection().add(attributes);
|
||||||
|
await window.Signal.Data.saveConversation(convo.attributes, {
|
||||||
|
Conversation: Whisper.Conversation,
|
||||||
|
});
|
||||||
|
|
||||||
|
const message = convo.messageCollection.add({
|
||||||
|
body: 'hello world',
|
||||||
|
conversationId: convo.id,
|
||||||
|
type: 'outgoing',
|
||||||
|
sent_at: Date.now(),
|
||||||
|
received_at: Date.now(),
|
||||||
|
});
|
||||||
|
await window.Signal.Data.saveMessage(message.attributes, {
|
||||||
|
Message: Whisper.Message,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
after(clearDatabase);
|
||||||
|
|
||||||
describe('Conversation', function() {
|
it('sorts its contacts in an intl-friendly way', () => {
|
||||||
var attributes = { type: 'private', id: '+18085555555' };
|
const convo = new Whisper.Conversation({ id: '+18085555555' });
|
||||||
before(async () => {
|
convo.contactCollection.add(
|
||||||
var convo = new Whisper.ConversationCollection().add(attributes);
|
new Whisper.Conversation({
|
||||||
await window.Signal.Data.saveConversation(convo.attributes, {
|
name: 'C',
|
||||||
Conversation: Whisper.Conversation,
|
})
|
||||||
});
|
);
|
||||||
|
convo.contactCollection.add(
|
||||||
|
new Whisper.Conversation({
|
||||||
|
name: 'B',
|
||||||
|
})
|
||||||
|
);
|
||||||
|
convo.contactCollection.add(
|
||||||
|
new Whisper.Conversation({
|
||||||
|
name: 'Á',
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
var message = convo.messageCollection.add({
|
assert.strictEqual(convo.contactCollection.at('0').get('name'), 'Á');
|
||||||
body: 'hello world',
|
assert.strictEqual(convo.contactCollection.at('1').get('name'), 'B');
|
||||||
conversationId: convo.id,
|
assert.strictEqual(convo.contactCollection.at('2').get('name'), 'C');
|
||||||
type: 'outgoing',
|
|
||||||
sent_at: Date.now(),
|
|
||||||
received_at: Date.now(),
|
|
||||||
});
|
|
||||||
await window.Signal.Data.saveMessage(message.attributes, {
|
|
||||||
Message: Whisper.Message,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
after(clearDatabase);
|
|
||||||
|
|
||||||
it('sorts its contacts in an intl-friendly way', function() {
|
|
||||||
var convo = new Whisper.Conversation({ id: '+18085555555' });
|
|
||||||
convo.contactCollection.add(
|
|
||||||
new Whisper.Conversation({
|
|
||||||
name: 'C',
|
|
||||||
})
|
|
||||||
);
|
|
||||||
convo.contactCollection.add(
|
|
||||||
new Whisper.Conversation({
|
|
||||||
name: 'B',
|
|
||||||
})
|
|
||||||
);
|
|
||||||
convo.contactCollection.add(
|
|
||||||
new Whisper.Conversation({
|
|
||||||
name: 'Á',
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.strictEqual(convo.contactCollection.at('0').get('name'), 'Á');
|
|
||||||
assert.strictEqual(convo.contactCollection.at('1').get('name'), 'B');
|
|
||||||
assert.strictEqual(convo.contactCollection.at('2').get('name'), 'C');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('contains its own messages', async function() {
|
|
||||||
var convo = new Whisper.ConversationCollection().add({
|
|
||||||
id: '+18085555555',
|
|
||||||
});
|
|
||||||
await convo.fetchMessages();
|
|
||||||
assert.notEqual(convo.messageCollection.length, 0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('contains only its own messages', async function() {
|
|
||||||
var convo = new Whisper.ConversationCollection().add({
|
|
||||||
id: '+18085556666',
|
|
||||||
});
|
|
||||||
await convo.fetchMessages();
|
|
||||||
assert.strictEqual(convo.messageCollection.length, 0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('adds conversation to message collection upon leaving group', async function() {
|
|
||||||
var convo = new Whisper.ConversationCollection().add({
|
|
||||||
type: 'group',
|
|
||||||
id: 'a random string',
|
|
||||||
});
|
|
||||||
await convo.leaveGroup();
|
|
||||||
assert.notEqual(convo.messageCollection.length, 0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('has a title', function() {
|
|
||||||
var convos = new Whisper.ConversationCollection();
|
|
||||||
var convo = convos.add(attributes);
|
|
||||||
assert.equal(convo.getTitle(), '+1 808-555-5555');
|
|
||||||
|
|
||||||
convo = convos.add({ type: '' });
|
|
||||||
assert.equal(convo.getTitle(), 'Unknown group');
|
|
||||||
|
|
||||||
convo = convos.add({ name: 'name' });
|
|
||||||
assert.equal(convo.getTitle(), 'name');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns the number', function() {
|
|
||||||
var convos = new Whisper.ConversationCollection();
|
|
||||||
var convo = convos.add(attributes);
|
|
||||||
assert.equal(convo.getNumber(), '+1 808-555-5555');
|
|
||||||
|
|
||||||
convo = convos.add({ type: '' });
|
|
||||||
assert.equal(convo.getNumber(), '');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('has an avatar', function() {
|
|
||||||
var convo = new Whisper.ConversationCollection().add(attributes);
|
|
||||||
var avatar = convo.getAvatar();
|
|
||||||
assert.property(avatar, 'content');
|
|
||||||
assert.property(avatar, 'color');
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('phone number parsing', function() {
|
|
||||||
after(function() {
|
|
||||||
storage.remove('regionCode');
|
|
||||||
});
|
|
||||||
function checkAttributes(number) {
|
|
||||||
var convo = new Whisper.ConversationCollection().add({
|
|
||||||
type: 'private',
|
|
||||||
});
|
|
||||||
convo.set('id', number);
|
|
||||||
convo.validate(convo.attributes);
|
|
||||||
assert.strictEqual(convo.get('id'), '+14155555555', number);
|
|
||||||
}
|
|
||||||
it('processes the phone number when validating', function() {
|
|
||||||
['+14155555555'].forEach(checkAttributes);
|
|
||||||
});
|
|
||||||
it('defaults to the local regionCode', function() {
|
|
||||||
storage.put('regionCode', 'US');
|
|
||||||
['14155555555', '4155555555'].forEach(checkAttributes);
|
|
||||||
});
|
|
||||||
it('works with common phone number formats', function() {
|
|
||||||
storage.put('regionCode', 'US');
|
|
||||||
[
|
|
||||||
'415 555 5555',
|
|
||||||
'415-555-5555',
|
|
||||||
'(415) 555 5555',
|
|
||||||
'(415) 555-5555',
|
|
||||||
'1 415 555 5555',
|
|
||||||
'1 415-555-5555',
|
|
||||||
'1 (415) 555 5555',
|
|
||||||
'1 (415) 555-5555',
|
|
||||||
'+1 415 555 5555',
|
|
||||||
'+1 415-555-5555',
|
|
||||||
'+1 (415) 555 5555',
|
|
||||||
'+1 (415) 555-5555',
|
|
||||||
].forEach(checkAttributes);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Conversation search', function() {
|
it('contains its own messages', async () => {
|
||||||
let convo;
|
const convo = new Whisper.ConversationCollection().add({
|
||||||
|
id: '+18085555555',
|
||||||
|
});
|
||||||
|
await convo.fetchMessages();
|
||||||
|
assert.notEqual(convo.messageCollection.length, 0);
|
||||||
|
});
|
||||||
|
|
||||||
beforeEach(async function() {
|
it('contains only its own messages', async () => {
|
||||||
convo = new Whisper.ConversationCollection().add({
|
const convo = new Whisper.ConversationCollection().add({
|
||||||
id: '+14155555555',
|
id: '+18085556666',
|
||||||
|
});
|
||||||
|
await convo.fetchMessages();
|
||||||
|
assert.strictEqual(convo.messageCollection.length, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('adds conversation to message collection upon leaving group', async () => {
|
||||||
|
const convo = new Whisper.ConversationCollection().add({
|
||||||
|
type: 'group',
|
||||||
|
id: 'a random string',
|
||||||
|
});
|
||||||
|
await convo.leaveGroup();
|
||||||
|
assert.notEqual(convo.messageCollection.length, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('has a title', () => {
|
||||||
|
const convos = new Whisper.ConversationCollection();
|
||||||
|
let convo = convos.add(attributes);
|
||||||
|
assert.equal(convo.getTitle(), '+1 808-555-5555');
|
||||||
|
|
||||||
|
convo = convos.add({ type: '' });
|
||||||
|
assert.equal(convo.getTitle(), 'Unknown group');
|
||||||
|
|
||||||
|
convo = convos.add({ name: 'name' });
|
||||||
|
assert.equal(convo.getTitle(), 'name');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns the number', () => {
|
||||||
|
const convos = new Whisper.ConversationCollection();
|
||||||
|
let convo = convos.add(attributes);
|
||||||
|
assert.equal(convo.getNumber(), '+1 808-555-5555');
|
||||||
|
|
||||||
|
convo = convos.add({ type: '' });
|
||||||
|
assert.equal(convo.getNumber(), '');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('has an avatar', () => {
|
||||||
|
const convo = new Whisper.ConversationCollection().add(attributes);
|
||||||
|
const avatar = convo.getAvatar();
|
||||||
|
assert.property(avatar, 'content');
|
||||||
|
assert.property(avatar, 'color');
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('phone number parsing', () => {
|
||||||
|
after(() => {
|
||||||
|
storage.remove('regionCode');
|
||||||
|
});
|
||||||
|
function checkAttributes(number) {
|
||||||
|
const convo = new Whisper.ConversationCollection().add({
|
||||||
type: 'private',
|
type: 'private',
|
||||||
name: 'John Doe',
|
|
||||||
});
|
});
|
||||||
await window.Signal.Data.saveConversation(convo.attributes, {
|
convo.set('id', number);
|
||||||
Conversation: Whisper.Conversation,
|
convo.validate(convo.attributes);
|
||||||
});
|
assert.strictEqual(convo.get('id'), '+14155555555', number);
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(clearDatabase);
|
|
||||||
|
|
||||||
async function testSearch(queries) {
|
|
||||||
await Promise.all(
|
|
||||||
queries.map(async function(query) {
|
|
||||||
var collection = new Whisper.ConversationCollection();
|
|
||||||
await collection.search(query);
|
|
||||||
|
|
||||||
assert.isDefined(
|
|
||||||
collection.get(convo.id),
|
|
||||||
'no result for "' + query + '"'
|
|
||||||
);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
it('matches by partial phone number', function() {
|
it('processes the phone number when validating', () => {
|
||||||
return testSearch([
|
['+14155555555'].forEach(checkAttributes);
|
||||||
'1',
|
|
||||||
'4',
|
|
||||||
'+1',
|
|
||||||
'415',
|
|
||||||
'4155',
|
|
||||||
'4155555555',
|
|
||||||
'14155555555',
|
|
||||||
'+14155555555',
|
|
||||||
]);
|
|
||||||
});
|
});
|
||||||
it('matches by name', function() {
|
it('defaults to the local regionCode', () => {
|
||||||
return testSearch(['John', 'Doe', 'john', 'doe', 'John Doe', 'john doe']);
|
storage.put('regionCode', 'US');
|
||||||
|
['14155555555', '4155555555'].forEach(checkAttributes);
|
||||||
});
|
});
|
||||||
it('does not match +', async function() {
|
it('works with common phone number formats', () => {
|
||||||
var collection = new Whisper.ConversationCollection();
|
storage.put('regionCode', 'US');
|
||||||
await collection.search('+');
|
[
|
||||||
assert.isUndefined(collection.get(convo.id), 'got result for "+"');
|
'415 555 5555',
|
||||||
|
'415-555-5555',
|
||||||
|
'(415) 555 5555',
|
||||||
|
'(415) 555-5555',
|
||||||
|
'1 415 555 5555',
|
||||||
|
'1 415-555-5555',
|
||||||
|
'1 (415) 555 5555',
|
||||||
|
'1 (415) 555-5555',
|
||||||
|
'+1 415 555 5555',
|
||||||
|
'+1 415-555-5555',
|
||||||
|
'+1 (415) 555 5555',
|
||||||
|
'+1 (415) 555-5555',
|
||||||
|
].forEach(checkAttributes);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
})();
|
});
|
||||||
|
|
||||||
|
describe('Conversation search', () => {
|
||||||
|
let convo;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
convo = new Whisper.ConversationCollection().add({
|
||||||
|
id: '+14155555555',
|
||||||
|
type: 'private',
|
||||||
|
name: 'John Doe',
|
||||||
|
});
|
||||||
|
await window.Signal.Data.saveConversation(convo.attributes, {
|
||||||
|
Conversation: Whisper.Conversation,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(clearDatabase);
|
||||||
|
|
||||||
|
async function testSearch(queries) {
|
||||||
|
await Promise.all(
|
||||||
|
queries.map(async query => {
|
||||||
|
const collection = new Whisper.ConversationCollection();
|
||||||
|
await collection.search(query);
|
||||||
|
|
||||||
|
assert.isDefined(collection.get(convo.id), `no result for "${query}"`);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
it('matches by partial phone number', () => {
|
||||||
|
return testSearch([
|
||||||
|
'1',
|
||||||
|
'4',
|
||||||
|
'+1',
|
||||||
|
'415',
|
||||||
|
'4155',
|
||||||
|
'4155555555',
|
||||||
|
'14155555555',
|
||||||
|
'+14155555555',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
it('matches by name', () => {
|
||||||
|
return testSearch(['John', 'Doe', 'john', 'doe', 'John Doe', 'john doe']);
|
||||||
|
});
|
||||||
|
it('does not match +', async () => {
|
||||||
|
const collection = new Whisper.ConversationCollection();
|
||||||
|
await collection.search('+');
|
||||||
|
assert.isUndefined(collection.get(convo.id), 'got result for "+"');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
@ -1,159 +1,145 @@
|
||||||
(function() {
|
/* global ConversationController, i18n, Whisper */
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var attributes = {
|
'use strict';
|
||||||
type: 'outgoing',
|
|
||||||
body: 'hi',
|
|
||||||
conversationId: 'foo',
|
|
||||||
attachments: [],
|
|
||||||
received_at: new Date().getTime(),
|
|
||||||
};
|
|
||||||
|
|
||||||
var attachment = {
|
const attributes = {
|
||||||
data: 'datasaurus',
|
type: 'outgoing',
|
||||||
contentType: 'plain/text',
|
body: 'hi',
|
||||||
};
|
conversationId: 'foo',
|
||||||
|
attachments: [],
|
||||||
|
received_at: new Date().getTime(),
|
||||||
|
};
|
||||||
|
|
||||||
var source = '+14155555555';
|
const source = '+14155555555';
|
||||||
|
|
||||||
describe('MessageCollection', function() {
|
describe('MessageCollection', () => {
|
||||||
before(async function() {
|
before(async () => {
|
||||||
await clearDatabase();
|
await clearDatabase();
|
||||||
ConversationController.reset();
|
ConversationController.reset();
|
||||||
await ConversationController.load();
|
await ConversationController.load();
|
||||||
});
|
|
||||||
after(function() {
|
|
||||||
return clearDatabase();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('gets outgoing contact', function() {
|
|
||||||
var messages = new Whisper.MessageCollection();
|
|
||||||
var message = messages.add(attributes);
|
|
||||||
message.getContact();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('gets incoming contact', function() {
|
|
||||||
var messages = new Whisper.MessageCollection();
|
|
||||||
var message = messages.add({
|
|
||||||
type: 'incoming',
|
|
||||||
source: source,
|
|
||||||
});
|
|
||||||
message.getContact();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('adds without saving', function() {
|
|
||||||
var messages = new Whisper.MessageCollection();
|
|
||||||
var message = messages.add(attributes);
|
|
||||||
assert.notEqual(messages.length, 0);
|
|
||||||
|
|
||||||
var messages = new Whisper.MessageCollection();
|
|
||||||
assert.strictEqual(messages.length, 0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be ordered oldest to newest', function() {
|
|
||||||
var messages = new Whisper.MessageCollection();
|
|
||||||
// Timestamps
|
|
||||||
var today = new Date();
|
|
||||||
var tomorrow = new Date();
|
|
||||||
tomorrow.setDate(today.getDate() + 1);
|
|
||||||
|
|
||||||
// Add threads
|
|
||||||
messages.add({ received_at: today });
|
|
||||||
messages.add({ received_at: tomorrow });
|
|
||||||
|
|
||||||
var models = messages.models;
|
|
||||||
var firstTimestamp = models[0].get('received_at').getTime();
|
|
||||||
var secondTimestamp = models[1].get('received_at').getTime();
|
|
||||||
|
|
||||||
// Compare timestamps
|
|
||||||
assert(firstTimestamp < secondTimestamp);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('checks if is incoming message', function() {
|
|
||||||
var messages = new Whisper.MessageCollection();
|
|
||||||
var message = messages.add(attributes);
|
|
||||||
assert.notOk(message.isIncoming());
|
|
||||||
message = messages.add({ type: 'incoming' });
|
|
||||||
assert.ok(message.isIncoming());
|
|
||||||
});
|
|
||||||
|
|
||||||
it('checks if is outgoing message', function() {
|
|
||||||
var messages = new Whisper.MessageCollection();
|
|
||||||
var message = messages.add(attributes);
|
|
||||||
assert.ok(message.isOutgoing());
|
|
||||||
message = messages.add({ type: 'incoming' });
|
|
||||||
assert.notOk(message.isOutgoing());
|
|
||||||
});
|
|
||||||
|
|
||||||
it('checks if is group update', function() {
|
|
||||||
var messages = new Whisper.MessageCollection();
|
|
||||||
var message = messages.add(attributes);
|
|
||||||
assert.notOk(message.isGroupUpdate());
|
|
||||||
|
|
||||||
message = messages.add({ group_update: true });
|
|
||||||
assert.ok(message.isGroupUpdate());
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns an accurate description', function() {
|
|
||||||
var messages = new Whisper.MessageCollection();
|
|
||||||
var message = messages.add(attributes);
|
|
||||||
|
|
||||||
assert.equal(
|
|
||||||
message.getDescription(),
|
|
||||||
'hi',
|
|
||||||
'If no group updates or end session flags, return message body.'
|
|
||||||
);
|
|
||||||
|
|
||||||
message = messages.add({ group_update: { left: 'Alice' } });
|
|
||||||
assert.equal(
|
|
||||||
message.getDescription(),
|
|
||||||
'Alice left the group',
|
|
||||||
'Notes one person leaving the group.'
|
|
||||||
);
|
|
||||||
|
|
||||||
message = messages.add({ group_update: { name: 'blerg' } });
|
|
||||||
assert.equal(
|
|
||||||
message.getDescription(),
|
|
||||||
"Title is now 'blerg'",
|
|
||||||
'Returns a single notice if only group_updates.name changes.'
|
|
||||||
);
|
|
||||||
|
|
||||||
message = messages.add({ group_update: { joined: ['Bob'] } });
|
|
||||||
assert.equal(
|
|
||||||
message.getDescription(),
|
|
||||||
'Bob joined the group',
|
|
||||||
'Returns a single notice if only group_updates.joined changes.'
|
|
||||||
);
|
|
||||||
|
|
||||||
message = messages.add({
|
|
||||||
group_update: { joined: ['Bob', 'Alice', 'Eve'] },
|
|
||||||
});
|
|
||||||
assert.equal(
|
|
||||||
message.getDescription(),
|
|
||||||
'Bob, Alice, Eve joined the group',
|
|
||||||
'Notes when >1 person joins the group.'
|
|
||||||
);
|
|
||||||
|
|
||||||
message = messages.add({
|
|
||||||
group_update: { joined: ['Bob'], name: 'blerg' },
|
|
||||||
});
|
|
||||||
assert.equal(
|
|
||||||
message.getDescription(),
|
|
||||||
"Title is now 'blerg', Bob joined the group",
|
|
||||||
'Notes when there are multiple changes to group_updates properties.'
|
|
||||||
);
|
|
||||||
|
|
||||||
message = messages.add({ flags: true });
|
|
||||||
assert.equal(message.getDescription(), i18n('sessionEnded'));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('checks if it is end of the session', function() {
|
|
||||||
var messages = new Whisper.MessageCollection();
|
|
||||||
var message = messages.add(attributes);
|
|
||||||
assert.notOk(message.isEndSession());
|
|
||||||
|
|
||||||
message = messages.add({ flags: true });
|
|
||||||
assert.ok(message.isEndSession());
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
})();
|
after(() => {
|
||||||
|
return clearDatabase();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('gets outgoing contact', () => {
|
||||||
|
const messages = new Whisper.MessageCollection();
|
||||||
|
const message = messages.add(attributes);
|
||||||
|
message.getContact();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('gets incoming contact', () => {
|
||||||
|
const messages = new Whisper.MessageCollection();
|
||||||
|
const message = messages.add({
|
||||||
|
type: 'incoming',
|
||||||
|
source,
|
||||||
|
});
|
||||||
|
message.getContact();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be ordered oldest to newest', () => {
|
||||||
|
const messages = new Whisper.MessageCollection();
|
||||||
|
// Timestamps
|
||||||
|
const today = new Date();
|
||||||
|
const tomorrow = new Date();
|
||||||
|
tomorrow.setDate(today.getDate() + 1);
|
||||||
|
|
||||||
|
// Add threads
|
||||||
|
messages.add({ received_at: today });
|
||||||
|
messages.add({ received_at: tomorrow });
|
||||||
|
|
||||||
|
const { models } = messages;
|
||||||
|
const firstTimestamp = models[0].get('received_at').getTime();
|
||||||
|
const secondTimestamp = models[1].get('received_at').getTime();
|
||||||
|
|
||||||
|
// Compare timestamps
|
||||||
|
assert(firstTimestamp < secondTimestamp);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('checks if is incoming message', () => {
|
||||||
|
const messages = new Whisper.MessageCollection();
|
||||||
|
let message = messages.add(attributes);
|
||||||
|
assert.notOk(message.isIncoming());
|
||||||
|
message = messages.add({ type: 'incoming' });
|
||||||
|
assert.ok(message.isIncoming());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('checks if is outgoing message', () => {
|
||||||
|
const messages = new Whisper.MessageCollection();
|
||||||
|
let message = messages.add(attributes);
|
||||||
|
assert.ok(message.isOutgoing());
|
||||||
|
message = messages.add({ type: 'incoming' });
|
||||||
|
assert.notOk(message.isOutgoing());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('checks if is group update', () => {
|
||||||
|
const messages = new Whisper.MessageCollection();
|
||||||
|
let message = messages.add(attributes);
|
||||||
|
assert.notOk(message.isGroupUpdate());
|
||||||
|
|
||||||
|
message = messages.add({ group_update: true });
|
||||||
|
assert.ok(message.isGroupUpdate());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns an accurate description', () => {
|
||||||
|
const messages = new Whisper.MessageCollection();
|
||||||
|
let message = messages.add(attributes);
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
message.getDescription(),
|
||||||
|
'hi',
|
||||||
|
'If no group updates or end session flags, return message body.'
|
||||||
|
);
|
||||||
|
|
||||||
|
message = messages.add({ group_update: { left: 'Alice' } });
|
||||||
|
assert.equal(
|
||||||
|
message.getDescription(),
|
||||||
|
'Alice left the group',
|
||||||
|
'Notes one person leaving the group.'
|
||||||
|
);
|
||||||
|
|
||||||
|
message = messages.add({ group_update: { name: 'blerg' } });
|
||||||
|
assert.equal(
|
||||||
|
message.getDescription(),
|
||||||
|
"Title is now 'blerg'",
|
||||||
|
'Returns a single notice if only group_updates.name changes.'
|
||||||
|
);
|
||||||
|
|
||||||
|
message = messages.add({ group_update: { joined: ['Bob'] } });
|
||||||
|
assert.equal(
|
||||||
|
message.getDescription(),
|
||||||
|
'Bob joined the group',
|
||||||
|
'Returns a single notice if only group_updates.joined changes.'
|
||||||
|
);
|
||||||
|
|
||||||
|
message = messages.add({
|
||||||
|
group_update: { joined: ['Bob', 'Alice', 'Eve'] },
|
||||||
|
});
|
||||||
|
assert.equal(
|
||||||
|
message.getDescription(),
|
||||||
|
'Bob, Alice, Eve joined the group',
|
||||||
|
'Notes when >1 person joins the group.'
|
||||||
|
);
|
||||||
|
|
||||||
|
message = messages.add({
|
||||||
|
group_update: { joined: ['Bob'], name: 'blerg' },
|
||||||
|
});
|
||||||
|
assert.equal(
|
||||||
|
message.getDescription(),
|
||||||
|
"Title is now 'blerg', Bob joined the group",
|
||||||
|
'Notes when there are multiple changes to group_updates properties.'
|
||||||
|
);
|
||||||
|
|
||||||
|
message = messages.add({ flags: true });
|
||||||
|
assert.equal(message.getDescription(), i18n('sessionEnded'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('checks if it is end of the session', () => {
|
||||||
|
const messages = new Whisper.MessageCollection();
|
||||||
|
let message = messages.add(attributes);
|
||||||
|
assert.notOk(message.isEndSession());
|
||||||
|
|
||||||
|
message = messages.add({ flags: true });
|
||||||
|
assert.ok(message.isEndSession());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
@ -1,28 +1,32 @@
|
||||||
|
/* global Backbone */
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
describe('ReliableTrigger', function() {
|
describe('ReliableTrigger', () => {
|
||||||
describe('trigger', function() {
|
describe('trigger', () => {
|
||||||
var Model, model;
|
let Model;
|
||||||
|
let model;
|
||||||
|
|
||||||
before(function() {
|
before(() => {
|
||||||
Model = Backbone.Model;
|
({ Model } = Backbone);
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(() => {
|
||||||
model = new Model();
|
model = new Model();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns successfully if this._events is falsey', function() {
|
it('returns successfully if this._events is falsey', () => {
|
||||||
model._events = null;
|
model._events = null;
|
||||||
model.trigger('click');
|
model.trigger('click');
|
||||||
});
|
});
|
||||||
it('handles map of events to trigger', function() {
|
it('handles map of events to trigger', () => {
|
||||||
var a = 0,
|
let a = 0;
|
||||||
b = 0;
|
let b = 0;
|
||||||
model.on('a', function(arg) {
|
|
||||||
|
model.on('a', arg => {
|
||||||
a = arg;
|
a = arg;
|
||||||
});
|
});
|
||||||
model.on('b', function(arg) {
|
model.on('b', arg => {
|
||||||
b = arg;
|
b = arg;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -34,13 +38,14 @@ describe('ReliableTrigger', function() {
|
||||||
assert.strictEqual(a, 1);
|
assert.strictEqual(a, 1);
|
||||||
assert.strictEqual(b, 2);
|
assert.strictEqual(b, 2);
|
||||||
});
|
});
|
||||||
it('handles space-separated list of events to trigger', function() {
|
it('handles space-separated list of events to trigger', () => {
|
||||||
var a = false,
|
let a = false;
|
||||||
b = false;
|
let b = false;
|
||||||
model.on('a', function() {
|
|
||||||
|
model.on('a', () => {
|
||||||
a = true;
|
a = true;
|
||||||
});
|
});
|
||||||
model.on('b', function() {
|
model.on('b', () => {
|
||||||
b = true;
|
b = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -49,9 +54,9 @@ describe('ReliableTrigger', function() {
|
||||||
assert.strictEqual(a, true);
|
assert.strictEqual(a, true);
|
||||||
assert.strictEqual(b, true);
|
assert.strictEqual(b, true);
|
||||||
});
|
});
|
||||||
it('calls all clients registered for "all" event', function() {
|
it('calls all clients registered for "all" event', () => {
|
||||||
var count = 0;
|
let count = 0;
|
||||||
model.on('all', function() {
|
model.on('all', () => {
|
||||||
count += 1;
|
count += 1;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -60,13 +65,14 @@ describe('ReliableTrigger', function() {
|
||||||
|
|
||||||
assert.strictEqual(count, 2);
|
assert.strictEqual(count, 2);
|
||||||
});
|
});
|
||||||
it('calls all clients registered for target event', function() {
|
it('calls all clients registered for target event', () => {
|
||||||
var a = false,
|
let a = false;
|
||||||
b = false;
|
let b = false;
|
||||||
model.on('event', function() {
|
|
||||||
|
model.on('event', () => {
|
||||||
a = true;
|
a = true;
|
||||||
});
|
});
|
||||||
model.on('event', function() {
|
model.on('event', () => {
|
||||||
b = true;
|
b = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -75,14 +81,15 @@ describe('ReliableTrigger', function() {
|
||||||
assert.strictEqual(a, true);
|
assert.strictEqual(a, true);
|
||||||
assert.strictEqual(b, true);
|
assert.strictEqual(b, true);
|
||||||
});
|
});
|
||||||
it('successfully returns and calls all clients even if first failed', function() {
|
it('successfully returns and calls all clients even if first failed', () => {
|
||||||
var a = false,
|
let a = false;
|
||||||
b = false;
|
let b = false;
|
||||||
model.on('event', function() {
|
|
||||||
|
model.on('event', () => {
|
||||||
a = true;
|
a = true;
|
||||||
throw new Error('a is set, but exception is thrown');
|
throw new Error('a is set, but exception is thrown');
|
||||||
});
|
});
|
||||||
model.on('event', function() {
|
model.on('event', () => {
|
||||||
b = true;
|
b = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -91,9 +98,9 @@ describe('ReliableTrigger', function() {
|
||||||
assert.strictEqual(a, true);
|
assert.strictEqual(a, true);
|
||||||
assert.strictEqual(b, true);
|
assert.strictEqual(b, true);
|
||||||
});
|
});
|
||||||
it('calls clients with no args', function() {
|
it('calls clients with no args', () => {
|
||||||
var called = false;
|
let called = false;
|
||||||
model.on('event', function() {
|
model.on('event', () => {
|
||||||
called = true;
|
called = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -101,20 +108,20 @@ describe('ReliableTrigger', function() {
|
||||||
|
|
||||||
assert.strictEqual(called, true);
|
assert.strictEqual(called, true);
|
||||||
});
|
});
|
||||||
it('calls clients with 1 arg', function() {
|
it('calls clients with 1 arg', () => {
|
||||||
var args;
|
let args;
|
||||||
model.on('event', function() {
|
model.on('event', (...eventArgs) => {
|
||||||
args = arguments;
|
args = eventArgs;
|
||||||
});
|
});
|
||||||
|
|
||||||
model.trigger('event', 1);
|
model.trigger('event', 1);
|
||||||
|
|
||||||
assert.strictEqual(args[0], 1);
|
assert.strictEqual(args[0], 1);
|
||||||
});
|
});
|
||||||
it('calls clients with 2 args', function() {
|
it('calls clients with 2 args', () => {
|
||||||
var args;
|
let args;
|
||||||
model.on('event', function() {
|
model.on('event', (...eventArgs) => {
|
||||||
args = arguments;
|
args = eventArgs;
|
||||||
});
|
});
|
||||||
|
|
||||||
model.trigger('event', 1, 2);
|
model.trigger('event', 1, 2);
|
||||||
|
@ -122,10 +129,10 @@ describe('ReliableTrigger', function() {
|
||||||
assert.strictEqual(args[0], 1);
|
assert.strictEqual(args[0], 1);
|
||||||
assert.strictEqual(args[1], 2);
|
assert.strictEqual(args[1], 2);
|
||||||
});
|
});
|
||||||
it('calls clients with 3 args', function() {
|
it('calls clients with 3 args', () => {
|
||||||
var args;
|
let args;
|
||||||
model.on('event', function() {
|
model.on('event', (...eventArgs) => {
|
||||||
args = arguments;
|
args = eventArgs;
|
||||||
});
|
});
|
||||||
|
|
||||||
model.trigger('event', 1, 2, 3);
|
model.trigger('event', 1, 2, 3);
|
||||||
|
@ -134,10 +141,10 @@ describe('ReliableTrigger', function() {
|
||||||
assert.strictEqual(args[1], 2);
|
assert.strictEqual(args[1], 2);
|
||||||
assert.strictEqual(args[2], 3);
|
assert.strictEqual(args[2], 3);
|
||||||
});
|
});
|
||||||
it('calls clients with 4+ args', function() {
|
it('calls clients with 4+ args', () => {
|
||||||
var args;
|
let args;
|
||||||
model.on('event', function() {
|
model.on('event', (...eventArgs) => {
|
||||||
args = arguments;
|
args = eventArgs;
|
||||||
});
|
});
|
||||||
|
|
||||||
model.trigger('event', 1, 2, 3, 4);
|
model.trigger('event', 1, 2, 3, 4);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
describe('spellChecker', function() {
|
describe('spellChecker', () => {
|
||||||
it('should work', function() {
|
it('should work', () => {
|
||||||
assert(window.spellChecker.spellCheck('correct'));
|
assert(window.spellChecker.spellCheck('correct'));
|
||||||
assert(!window.spellChecker.spellCheck('fhqwgads'));
|
assert(!window.spellChecker.spellCheck('fhqwgads'));
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,18 +1,14 @@
|
||||||
|
/* global _, textsecure, libsignal, storage */
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
describe('SignalProtocolStore', function() {
|
describe('SignalProtocolStore', () => {
|
||||||
var number = '+5558675309';
|
const number = '+5558675309';
|
||||||
var store;
|
let store;
|
||||||
var identityKey;
|
let identityKey;
|
||||||
var testKey;
|
let testKey;
|
||||||
|
|
||||||
function wrapDeferred(deferred) {
|
before(done => {
|
||||||
return new Promise(function(resolve, reject) {
|
|
||||||
return deferred.then(resolve, reject);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
before(function(done) {
|
|
||||||
store = textsecure.storage.protocol;
|
store = textsecure.storage.protocol;
|
||||||
identityKey = {
|
identityKey = {
|
||||||
pubKey: libsignal.crypto.getRandomBytes(33),
|
pubKey: libsignal.crypto.getRandomBytes(33),
|
||||||
|
@ -28,59 +24,59 @@ describe('SignalProtocolStore', function() {
|
||||||
storage.fetch().then(done, done);
|
storage.fetch().then(done, done);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getLocalRegistrationId', function() {
|
describe('getLocalRegistrationId', () => {
|
||||||
it('retrieves my registration id', async function() {
|
it('retrieves my registration id', async () => {
|
||||||
const id = await store.getLocalRegistrationId();
|
const id = await store.getLocalRegistrationId();
|
||||||
assert.strictEqual(id, 1337);
|
assert.strictEqual(id, 1337);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('getIdentityKeyPair', function() {
|
describe('getIdentityKeyPair', () => {
|
||||||
it('retrieves my identity key', async function() {
|
it('retrieves my identity key', async () => {
|
||||||
const key = await store.getIdentityKeyPair();
|
const key = await store.getIdentityKeyPair();
|
||||||
assertEqualArrayBuffers(key.pubKey, identityKey.pubKey);
|
assertEqualArrayBuffers(key.pubKey, identityKey.pubKey);
|
||||||
assertEqualArrayBuffers(key.privKey, identityKey.privKey);
|
assertEqualArrayBuffers(key.privKey, identityKey.privKey);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('saveIdentity', function() {
|
describe('saveIdentity', () => {
|
||||||
var address = new libsignal.SignalProtocolAddress(number, 1);
|
const address = new libsignal.SignalProtocolAddress(number, 1);
|
||||||
var identifier = address.toString();
|
const identifier = address.toString();
|
||||||
|
|
||||||
it('stores identity keys', async function() {
|
it('stores identity keys', async () => {
|
||||||
await store.saveIdentity(identifier, testKey.pubKey);
|
await store.saveIdentity(identifier, testKey.pubKey);
|
||||||
const key = await store.loadIdentityKey(number);
|
const key = await store.loadIdentityKey(number);
|
||||||
|
|
||||||
assertEqualArrayBuffers(key, testKey.pubKey);
|
assertEqualArrayBuffers(key, testKey.pubKey);
|
||||||
});
|
});
|
||||||
it('allows key changes', async function() {
|
it('allows key changes', async () => {
|
||||||
var newIdentity = libsignal.crypto.getRandomBytes(33);
|
const newIdentity = libsignal.crypto.getRandomBytes(33);
|
||||||
await store.saveIdentity(identifier, testKey.pubKey);
|
await store.saveIdentity(identifier, testKey.pubKey);
|
||||||
await store.saveIdentity(identifier, newIdentity);
|
await store.saveIdentity(identifier, newIdentity);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('When there is no existing key (first use)', function() {
|
describe('When there is no existing key (first use)', () => {
|
||||||
before(async function() {
|
before(async () => {
|
||||||
await store.removeIdentityKey(number);
|
await store.removeIdentityKey(number);
|
||||||
await store.saveIdentity(identifier, testKey.pubKey);
|
await store.saveIdentity(identifier, testKey.pubKey);
|
||||||
});
|
});
|
||||||
it('marks the key firstUse', async function() {
|
it('marks the key firstUse', async () => {
|
||||||
const identity = await window.Signal.Data.getIdentityKeyById(number);
|
const identity = await window.Signal.Data.getIdentityKeyById(number);
|
||||||
assert(identity.firstUse);
|
assert(identity.firstUse);
|
||||||
});
|
});
|
||||||
it('sets the timestamp', async function() {
|
it('sets the timestamp', async () => {
|
||||||
const identity = await window.Signal.Data.getIdentityKeyById(number);
|
const identity = await window.Signal.Data.getIdentityKeyById(number);
|
||||||
assert(identity.timestamp);
|
assert(identity.timestamp);
|
||||||
});
|
});
|
||||||
it('sets the verified status to DEFAULT', async function() {
|
it('sets the verified status to DEFAULT', async () => {
|
||||||
const identity = await window.Signal.Data.getIdentityKeyById(number);
|
const identity = await window.Signal.Data.getIdentityKeyById(number);
|
||||||
assert.strictEqual(identity.verified, store.VerifiedStatus.DEFAULT);
|
assert.strictEqual(identity.verified, store.VerifiedStatus.DEFAULT);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('When there is a different existing key (non first use)', function() {
|
describe('When there is a different existing key (non first use)', () => {
|
||||||
const newIdentity = libsignal.crypto.getRandomBytes(33);
|
const newIdentity = libsignal.crypto.getRandomBytes(33);
|
||||||
const oldTimestamp = Date.now();
|
const oldTimestamp = Date.now();
|
||||||
|
|
||||||
before(async function() {
|
before(async () => {
|
||||||
await window.Signal.Data.createOrUpdateIdentityKey({
|
await window.Signal.Data.createOrUpdateIdentityKey({
|
||||||
id: identifier,
|
id: identifier,
|
||||||
publicKey: testKey.pubKey,
|
publicKey: testKey.pubKey,
|
||||||
|
@ -92,17 +88,17 @@ describe('SignalProtocolStore', function() {
|
||||||
|
|
||||||
await store.saveIdentity(identifier, newIdentity);
|
await store.saveIdentity(identifier, newIdentity);
|
||||||
});
|
});
|
||||||
it('marks the key not firstUse', async function() {
|
it('marks the key not firstUse', async () => {
|
||||||
const identity = await window.Signal.Data.getIdentityKeyById(number);
|
const identity = await window.Signal.Data.getIdentityKeyById(number);
|
||||||
assert(!identity.firstUse);
|
assert(!identity.firstUse);
|
||||||
});
|
});
|
||||||
it('updates the timestamp', async function() {
|
it('updates the timestamp', async () => {
|
||||||
const identity = await window.Signal.Data.getIdentityKeyById(number);
|
const identity = await window.Signal.Data.getIdentityKeyById(number);
|
||||||
assert.notEqual(identity.timestamp, oldTimestamp);
|
assert.notEqual(identity.timestamp, oldTimestamp);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('The previous verified status was DEFAULT', function() {
|
describe('The previous verified status was DEFAULT', () => {
|
||||||
before(async function() {
|
before(async () => {
|
||||||
await window.Signal.Data.createOrUpdateIdentityKey({
|
await window.Signal.Data.createOrUpdateIdentityKey({
|
||||||
id: number,
|
id: number,
|
||||||
publicKey: testKey.pubKey,
|
publicKey: testKey.pubKey,
|
||||||
|
@ -114,13 +110,13 @@ describe('SignalProtocolStore', function() {
|
||||||
|
|
||||||
await store.saveIdentity(identifier, newIdentity);
|
await store.saveIdentity(identifier, newIdentity);
|
||||||
});
|
});
|
||||||
it('sets the new key to default', async function() {
|
it('sets the new key to default', async () => {
|
||||||
const identity = await window.Signal.Data.getIdentityKeyById(number);
|
const identity = await window.Signal.Data.getIdentityKeyById(number);
|
||||||
assert.strictEqual(identity.verified, store.VerifiedStatus.DEFAULT);
|
assert.strictEqual(identity.verified, store.VerifiedStatus.DEFAULT);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('The previous verified status was VERIFIED', function() {
|
describe('The previous verified status was VERIFIED', () => {
|
||||||
before(async function() {
|
before(async () => {
|
||||||
await window.Signal.Data.createOrUpdateIdentityKey({
|
await window.Signal.Data.createOrUpdateIdentityKey({
|
||||||
id: number,
|
id: number,
|
||||||
publicKey: testKey.pubKey,
|
publicKey: testKey.pubKey,
|
||||||
|
@ -131,7 +127,7 @@ describe('SignalProtocolStore', function() {
|
||||||
});
|
});
|
||||||
await store.saveIdentity(identifier, newIdentity);
|
await store.saveIdentity(identifier, newIdentity);
|
||||||
});
|
});
|
||||||
it('sets the new key to unverified', async function() {
|
it('sets the new key to unverified', async () => {
|
||||||
const identity = await window.Signal.Data.getIdentityKeyById(number);
|
const identity = await window.Signal.Data.getIdentityKeyById(number);
|
||||||
|
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
|
@ -140,8 +136,8 @@ describe('SignalProtocolStore', function() {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('The previous verified status was UNVERIFIED', function() {
|
describe('The previous verified status was UNVERIFIED', () => {
|
||||||
before(async function() {
|
before(async () => {
|
||||||
await window.Signal.Data.createOrUpdateIdentityKey({
|
await window.Signal.Data.createOrUpdateIdentityKey({
|
||||||
id: number,
|
id: number,
|
||||||
publicKey: testKey.pubKey,
|
publicKey: testKey.pubKey,
|
||||||
|
@ -153,7 +149,7 @@ describe('SignalProtocolStore', function() {
|
||||||
|
|
||||||
await store.saveIdentity(identifier, newIdentity);
|
await store.saveIdentity(identifier, newIdentity);
|
||||||
});
|
});
|
||||||
it('sets the new key to unverified', async function() {
|
it('sets the new key to unverified', async () => {
|
||||||
const identity = await window.Signal.Data.getIdentityKeyById(number);
|
const identity = await window.Signal.Data.getIdentityKeyById(number);
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
identity.verified,
|
identity.verified,
|
||||||
|
@ -162,9 +158,9 @@ describe('SignalProtocolStore', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('When the key has not changed', function() {
|
describe('When the key has not changed', () => {
|
||||||
var oldTimestamp = Date.now();
|
const oldTimestamp = Date.now();
|
||||||
before(async function() {
|
before(async () => {
|
||||||
await window.Signal.Data.createOrUpdateIdentityKey({
|
await window.Signal.Data.createOrUpdateIdentityKey({
|
||||||
id: number,
|
id: number,
|
||||||
publicKey: testKey.pubKey,
|
publicKey: testKey.pubKey,
|
||||||
|
@ -173,13 +169,13 @@ describe('SignalProtocolStore', function() {
|
||||||
verified: store.VerifiedStatus.DEFAULT,
|
verified: store.VerifiedStatus.DEFAULT,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('If it is marked firstUse', function() {
|
describe('If it is marked firstUse', () => {
|
||||||
before(async function() {
|
before(async () => {
|
||||||
const identity = await window.Signal.Data.getIdentityKeyById(number);
|
const identity = await window.Signal.Data.getIdentityKeyById(number);
|
||||||
identity.firstUse = true;
|
identity.firstUse = true;
|
||||||
await window.Signal.Data.createOrUpdateIdentityKey(identity);
|
await window.Signal.Data.createOrUpdateIdentityKey(identity);
|
||||||
});
|
});
|
||||||
it('nothing changes', async function() {
|
it('nothing changes', async () => {
|
||||||
await store.saveIdentity(identifier, testKey.pubKey, true);
|
await store.saveIdentity(identifier, testKey.pubKey, true);
|
||||||
|
|
||||||
const identity = await window.Signal.Data.getIdentityKeyById(number);
|
const identity = await window.Signal.Data.getIdentityKeyById(number);
|
||||||
|
@ -187,15 +183,15 @@ describe('SignalProtocolStore', function() {
|
||||||
assert.strictEqual(identity.timestamp, oldTimestamp);
|
assert.strictEqual(identity.timestamp, oldTimestamp);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('If it is not marked firstUse', function() {
|
describe('If it is not marked firstUse', () => {
|
||||||
before(async function() {
|
before(async () => {
|
||||||
const identity = await window.Signal.Data.getIdentityKeyById(number);
|
const identity = await window.Signal.Data.getIdentityKeyById(number);
|
||||||
identity.firstUse = false;
|
identity.firstUse = false;
|
||||||
await window.Signal.Data.createOrUpdateIdentityKey(identity);
|
await window.Signal.Data.createOrUpdateIdentityKey(identity);
|
||||||
});
|
});
|
||||||
describe('If nonblocking approval is required', function() {
|
describe('If nonblocking approval is required', () => {
|
||||||
let now;
|
let now;
|
||||||
before(async function() {
|
before(async () => {
|
||||||
now = Date.now();
|
now = Date.now();
|
||||||
const identity = await window.Signal.Data.getIdentityKeyById(
|
const identity = await window.Signal.Data.getIdentityKeyById(
|
||||||
number
|
number
|
||||||
|
@ -203,7 +199,7 @@ describe('SignalProtocolStore', function() {
|
||||||
identity.timestamp = now;
|
identity.timestamp = now;
|
||||||
await window.Signal.Data.createOrUpdateIdentityKey(identity);
|
await window.Signal.Data.createOrUpdateIdentityKey(identity);
|
||||||
});
|
});
|
||||||
it('sets non-blocking approval', async function() {
|
it('sets non-blocking approval', async () => {
|
||||||
await store.saveIdentity(identifier, testKey.pubKey, true);
|
await store.saveIdentity(identifier, testKey.pubKey, true);
|
||||||
|
|
||||||
const identity = await window.Signal.Data.getIdentityKeyById(
|
const identity = await window.Signal.Data.getIdentityKeyById(
|
||||||
|
@ -218,11 +214,11 @@ describe('SignalProtocolStore', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('saveIdentityWithAttributes', function() {
|
describe('saveIdentityWithAttributes', () => {
|
||||||
var now;
|
let now;
|
||||||
var validAttributes;
|
let validAttributes;
|
||||||
|
|
||||||
before(async function() {
|
before(async () => {
|
||||||
now = Date.now();
|
now = Date.now();
|
||||||
validAttributes = {
|
validAttributes = {
|
||||||
publicKey: testKey.pubKey,
|
publicKey: testKey.pubKey,
|
||||||
|
@ -234,35 +230,35 @@ describe('SignalProtocolStore', function() {
|
||||||
|
|
||||||
await store.removeIdentityKey(number);
|
await store.removeIdentityKey(number);
|
||||||
});
|
});
|
||||||
describe('with valid attributes', function() {
|
describe('with valid attributes', () => {
|
||||||
before(async function() {
|
before(async () => {
|
||||||
await store.saveIdentityWithAttributes(number, validAttributes);
|
await store.saveIdentityWithAttributes(number, validAttributes);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('publicKey is saved', async function() {
|
it('publicKey is saved', async () => {
|
||||||
const identity = await window.Signal.Data.getIdentityKeyById(number);
|
const identity = await window.Signal.Data.getIdentityKeyById(number);
|
||||||
assertEqualArrayBuffers(identity.publicKey, testKey.pubKey);
|
assertEqualArrayBuffers(identity.publicKey, testKey.pubKey);
|
||||||
});
|
});
|
||||||
it('firstUse is saved', async function() {
|
it('firstUse is saved', async () => {
|
||||||
const identity = await window.Signal.Data.getIdentityKeyById(number);
|
const identity = await window.Signal.Data.getIdentityKeyById(number);
|
||||||
assert.strictEqual(identity.firstUse, true);
|
assert.strictEqual(identity.firstUse, true);
|
||||||
});
|
});
|
||||||
it('timestamp is saved', async function() {
|
it('timestamp is saved', async () => {
|
||||||
const identity = await window.Signal.Data.getIdentityKeyById(number);
|
const identity = await window.Signal.Data.getIdentityKeyById(number);
|
||||||
assert.strictEqual(identity.timestamp, now);
|
assert.strictEqual(identity.timestamp, now);
|
||||||
});
|
});
|
||||||
it('verified is saved', async function() {
|
it('verified is saved', async () => {
|
||||||
const identity = await window.Signal.Data.getIdentityKeyById(number);
|
const identity = await window.Signal.Data.getIdentityKeyById(number);
|
||||||
assert.strictEqual(identity.verified, store.VerifiedStatus.VERIFIED);
|
assert.strictEqual(identity.verified, store.VerifiedStatus.VERIFIED);
|
||||||
});
|
});
|
||||||
it('nonblockingApproval is saved', async function() {
|
it('nonblockingApproval is saved', async () => {
|
||||||
const identity = await window.Signal.Data.getIdentityKeyById(number);
|
const identity = await window.Signal.Data.getIdentityKeyById(number);
|
||||||
assert.strictEqual(identity.nonblockingApproval, false);
|
assert.strictEqual(identity.nonblockingApproval, false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('with invalid attributes', function() {
|
describe('with invalid attributes', () => {
|
||||||
var attributes;
|
let attributes;
|
||||||
beforeEach(function() {
|
beforeEach(() => {
|
||||||
attributes = _.clone(validAttributes);
|
attributes = _.clone(validAttributes);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -275,38 +271,37 @@ describe('SignalProtocolStore', function() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
it('rejects an invalid publicKey', async function() {
|
it('rejects an invalid publicKey', async () => {
|
||||||
attributes.publicKey = 'a string';
|
attributes.publicKey = 'a string';
|
||||||
await testInvalidAttributes();
|
await testInvalidAttributes();
|
||||||
});
|
});
|
||||||
it('rejects invalid firstUse', async function() {
|
it('rejects invalid firstUse', async () => {
|
||||||
attributes.firstUse = 0;
|
attributes.firstUse = 0;
|
||||||
await testInvalidAttributes();
|
await testInvalidAttributes();
|
||||||
});
|
});
|
||||||
it('rejects invalid timestamp', async function() {
|
it('rejects invalid timestamp', async () => {
|
||||||
attributes.timestamp = NaN;
|
attributes.timestamp = NaN;
|
||||||
await testInvalidAttributes();
|
await testInvalidAttributes();
|
||||||
});
|
});
|
||||||
it('rejects invalid verified', async function() {
|
it('rejects invalid verified', async () => {
|
||||||
attributes.verified = null;
|
attributes.verified = null;
|
||||||
await testInvalidAttributes();
|
await testInvalidAttributes();
|
||||||
});
|
});
|
||||||
it('rejects invalid nonblockingApproval', async function() {
|
it('rejects invalid nonblockingApproval', async () => {
|
||||||
attributes.nonblockingApproval = 0;
|
attributes.nonblockingApproval = 0;
|
||||||
await testInvalidAttributes();
|
await testInvalidAttributes();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('setApproval', function() {
|
describe('setApproval', () => {
|
||||||
it('sets nonblockingApproval', async function() {
|
it('sets nonblockingApproval', async () => {
|
||||||
await store.setApproval(number, true);
|
await store.setApproval(number, true);
|
||||||
const identity = await window.Signal.Data.getIdentityKeyById(number);
|
const identity = await window.Signal.Data.getIdentityKeyById(number);
|
||||||
|
|
||||||
assert.strictEqual(identity.nonblockingApproval, true);
|
assert.strictEqual(identity.nonblockingApproval, true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('setVerified', function() {
|
describe('setVerified', () => {
|
||||||
var record;
|
|
||||||
async function saveRecordDefault() {
|
async function saveRecordDefault() {
|
||||||
await window.Signal.Data.createOrUpdateIdentityKey({
|
await window.Signal.Data.createOrUpdateIdentityKey({
|
||||||
id: number,
|
id: number,
|
||||||
|
@ -317,9 +312,9 @@ describe('SignalProtocolStore', function() {
|
||||||
nonblockingApproval: false,
|
nonblockingApproval: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
describe('with no public key argument', function() {
|
describe('with no public key argument', () => {
|
||||||
before(saveRecordDefault);
|
before(saveRecordDefault);
|
||||||
it('updates the verified status', async function() {
|
it('updates the verified status', async () => {
|
||||||
await store.setVerified(number, store.VerifiedStatus.VERIFIED);
|
await store.setVerified(number, store.VerifiedStatus.VERIFIED);
|
||||||
|
|
||||||
const identity = await window.Signal.Data.getIdentityKeyById(number);
|
const identity = await window.Signal.Data.getIdentityKeyById(number);
|
||||||
|
@ -327,9 +322,9 @@ describe('SignalProtocolStore', function() {
|
||||||
assertEqualArrayBuffers(identity.publicKey, testKey.pubKey);
|
assertEqualArrayBuffers(identity.publicKey, testKey.pubKey);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('with the current public key', function() {
|
describe('with the current public key', () => {
|
||||||
before(saveRecordDefault);
|
before(saveRecordDefault);
|
||||||
it('updates the verified status', async function() {
|
it('updates the verified status', async () => {
|
||||||
await store.setVerified(
|
await store.setVerified(
|
||||||
number,
|
number,
|
||||||
store.VerifiedStatus.VERIFIED,
|
store.VerifiedStatus.VERIFIED,
|
||||||
|
@ -341,10 +336,10 @@ describe('SignalProtocolStore', function() {
|
||||||
assertEqualArrayBuffers(identity.publicKey, testKey.pubKey);
|
assertEqualArrayBuffers(identity.publicKey, testKey.pubKey);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('with a mismatching public key', function() {
|
describe('with a mismatching public key', () => {
|
||||||
var newIdentity = libsignal.crypto.getRandomBytes(33);
|
const newIdentity = libsignal.crypto.getRandomBytes(33);
|
||||||
before(saveRecordDefault);
|
before(saveRecordDefault);
|
||||||
it('does not change the record.', async function() {
|
it('does not change the record.', async () => {
|
||||||
await store.setVerified(
|
await store.setVerified(
|
||||||
number,
|
number,
|
||||||
store.VerifiedStatus.VERIFIED,
|
store.VerifiedStatus.VERIFIED,
|
||||||
|
@ -357,28 +352,27 @@ describe('SignalProtocolStore', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('processContactSyncVerificationState', function() {
|
describe('processContactSyncVerificationState', () => {
|
||||||
var record;
|
const newIdentity = libsignal.crypto.getRandomBytes(33);
|
||||||
var newIdentity = libsignal.crypto.getRandomBytes(33);
|
let keychangeTriggered;
|
||||||
var keychangeTriggered;
|
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(() => {
|
||||||
keychangeTriggered = 0;
|
keychangeTriggered = 0;
|
||||||
store.bind('keychange', function() {
|
store.bind('keychange', () => {
|
||||||
keychangeTriggered++;
|
keychangeTriggered += 1;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
afterEach(function() {
|
afterEach(() => {
|
||||||
store.unbind('keychange');
|
store.unbind('keychange');
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('when the new verified status is DEFAULT', function() {
|
describe('when the new verified status is DEFAULT', () => {
|
||||||
describe('when there is no existing record', function() {
|
describe('when there is no existing record', () => {
|
||||||
before(async function() {
|
before(async () => {
|
||||||
await window.Signal.Data.removeIdentityKeyById(number);
|
await window.Signal.Data.removeIdentityKeyById(number);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does nothing', async function() {
|
it('does nothing', async () => {
|
||||||
await store.processContactSyncVerificationState(
|
await store.processContactSyncVerificationState(
|
||||||
number,
|
number,
|
||||||
store.VerifiedStatus.DEFAULT,
|
store.VerifiedStatus.DEFAULT,
|
||||||
|
@ -398,9 +392,9 @@ describe('SignalProtocolStore', function() {
|
||||||
assert.strictEqual(keychangeTriggered, 0);
|
assert.strictEqual(keychangeTriggered, 0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('when the record exists', function() {
|
describe('when the record exists', () => {
|
||||||
describe('when the existing key is different', function() {
|
describe('when the existing key is different', () => {
|
||||||
before(async function() {
|
before(async () => {
|
||||||
await window.Signal.Data.createOrUpdateIdentityKey({
|
await window.Signal.Data.createOrUpdateIdentityKey({
|
||||||
id: number,
|
id: number,
|
||||||
publicKey: testKey.pubKey,
|
publicKey: testKey.pubKey,
|
||||||
|
@ -411,7 +405,7 @@ describe('SignalProtocolStore', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not save the new identity (because this is a less secure state)', async function() {
|
it('does not save the new identity (because this is a less secure state)', async () => {
|
||||||
await store.processContactSyncVerificationState(
|
await store.processContactSyncVerificationState(
|
||||||
number,
|
number,
|
||||||
store.VerifiedStatus.DEFAULT,
|
store.VerifiedStatus.DEFAULT,
|
||||||
|
@ -430,8 +424,8 @@ describe('SignalProtocolStore', function() {
|
||||||
assert.strictEqual(keychangeTriggered, 0);
|
assert.strictEqual(keychangeTriggered, 0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('when the existing key is the same but VERIFIED', function() {
|
describe('when the existing key is the same but VERIFIED', () => {
|
||||||
before(async function() {
|
before(async () => {
|
||||||
await window.Signal.Data.createOrUpdateIdentityKey({
|
await window.Signal.Data.createOrUpdateIdentityKey({
|
||||||
id: number,
|
id: number,
|
||||||
publicKey: testKey.pubKey,
|
publicKey: testKey.pubKey,
|
||||||
|
@ -442,7 +436,7 @@ describe('SignalProtocolStore', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('updates the verified status', async function() {
|
it('updates the verified status', async () => {
|
||||||
await store.processContactSyncVerificationState(
|
await store.processContactSyncVerificationState(
|
||||||
number,
|
number,
|
||||||
store.VerifiedStatus.DEFAULT,
|
store.VerifiedStatus.DEFAULT,
|
||||||
|
@ -458,8 +452,8 @@ describe('SignalProtocolStore', function() {
|
||||||
assert.strictEqual(keychangeTriggered, 0);
|
assert.strictEqual(keychangeTriggered, 0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('when the existing key is the same and already DEFAULT', function() {
|
describe('when the existing key is the same and already DEFAULT', () => {
|
||||||
before(async function() {
|
before(async () => {
|
||||||
await window.Signal.Data.createOrUpdateIdentityKey({
|
await window.Signal.Data.createOrUpdateIdentityKey({
|
||||||
id: number,
|
id: number,
|
||||||
publicKey: testKey.pubKey,
|
publicKey: testKey.pubKey,
|
||||||
|
@ -470,7 +464,7 @@ describe('SignalProtocolStore', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not hang', async function() {
|
it('does not hang', async () => {
|
||||||
await store.processContactSyncVerificationState(
|
await store.processContactSyncVerificationState(
|
||||||
number,
|
number,
|
||||||
store.VerifiedStatus.DEFAULT,
|
store.VerifiedStatus.DEFAULT,
|
||||||
|
@ -482,13 +476,13 @@ describe('SignalProtocolStore', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('when the new verified status is UNVERIFIED', function() {
|
describe('when the new verified status is UNVERIFIED', () => {
|
||||||
describe('when there is no existing record', function() {
|
describe('when there is no existing record', () => {
|
||||||
before(async function() {
|
before(async () => {
|
||||||
await window.Signal.Data.removeIdentityKeyById(number);
|
await window.Signal.Data.removeIdentityKeyById(number);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('saves the new identity and marks it verified', async function() {
|
it('saves the new identity and marks it verified', async () => {
|
||||||
await store.processContactSyncVerificationState(
|
await store.processContactSyncVerificationState(
|
||||||
number,
|
number,
|
||||||
store.VerifiedStatus.UNVERIFIED,
|
store.VerifiedStatus.UNVERIFIED,
|
||||||
|
@ -505,9 +499,9 @@ describe('SignalProtocolStore', function() {
|
||||||
assert.strictEqual(keychangeTriggered, 0);
|
assert.strictEqual(keychangeTriggered, 0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('when the record exists', function() {
|
describe('when the record exists', () => {
|
||||||
describe('when the existing key is different', function() {
|
describe('when the existing key is different', () => {
|
||||||
before(async function() {
|
before(async () => {
|
||||||
await window.Signal.Data.createOrUpdateIdentityKey({
|
await window.Signal.Data.createOrUpdateIdentityKey({
|
||||||
id: number,
|
id: number,
|
||||||
publicKey: testKey.pubKey,
|
publicKey: testKey.pubKey,
|
||||||
|
@ -518,7 +512,7 @@ describe('SignalProtocolStore', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('saves the new identity and marks it UNVERIFIED', async function() {
|
it('saves the new identity and marks it UNVERIFIED', async () => {
|
||||||
await store.processContactSyncVerificationState(
|
await store.processContactSyncVerificationState(
|
||||||
number,
|
number,
|
||||||
store.VerifiedStatus.UNVERIFIED,
|
store.VerifiedStatus.UNVERIFIED,
|
||||||
|
@ -537,8 +531,8 @@ describe('SignalProtocolStore', function() {
|
||||||
assert.strictEqual(keychangeTriggered, 1);
|
assert.strictEqual(keychangeTriggered, 1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('when the key exists and is DEFAULT', function() {
|
describe('when the key exists and is DEFAULT', () => {
|
||||||
before(async function() {
|
before(async () => {
|
||||||
await window.Signal.Data.createOrUpdateIdentityKey({
|
await window.Signal.Data.createOrUpdateIdentityKey({
|
||||||
id: number,
|
id: number,
|
||||||
publicKey: testKey.pubKey,
|
publicKey: testKey.pubKey,
|
||||||
|
@ -549,7 +543,7 @@ describe('SignalProtocolStore', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('updates the verified status', async function() {
|
it('updates the verified status', async () => {
|
||||||
await store.processContactSyncVerificationState(
|
await store.processContactSyncVerificationState(
|
||||||
number,
|
number,
|
||||||
store.VerifiedStatus.UNVERIFIED,
|
store.VerifiedStatus.UNVERIFIED,
|
||||||
|
@ -567,8 +561,8 @@ describe('SignalProtocolStore', function() {
|
||||||
assert.strictEqual(keychangeTriggered, 0);
|
assert.strictEqual(keychangeTriggered, 0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('when the key exists and is already UNVERIFIED', function() {
|
describe('when the key exists and is already UNVERIFIED', () => {
|
||||||
before(async function() {
|
before(async () => {
|
||||||
await window.Signal.Data.createOrUpdateIdentityKey({
|
await window.Signal.Data.createOrUpdateIdentityKey({
|
||||||
id: number,
|
id: number,
|
||||||
publicKey: testKey.pubKey,
|
publicKey: testKey.pubKey,
|
||||||
|
@ -579,7 +573,7 @@ describe('SignalProtocolStore', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not hang', async function() {
|
it('does not hang', async () => {
|
||||||
await store.processContactSyncVerificationState(
|
await store.processContactSyncVerificationState(
|
||||||
number,
|
number,
|
||||||
store.VerifiedStatus.UNVERIFIED,
|
store.VerifiedStatus.UNVERIFIED,
|
||||||
|
@ -591,13 +585,13 @@ describe('SignalProtocolStore', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('when the new verified status is VERIFIED', function() {
|
describe('when the new verified status is VERIFIED', () => {
|
||||||
describe('when there is no existing record', function() {
|
describe('when there is no existing record', () => {
|
||||||
before(async function() {
|
before(async () => {
|
||||||
await window.Signal.Data.removeIdentityKeyById(number);
|
await window.Signal.Data.removeIdentityKeyById(number);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('saves the new identity and marks it verified', async function() {
|
it('saves the new identity and marks it verified', async () => {
|
||||||
await store.processContactSyncVerificationState(
|
await store.processContactSyncVerificationState(
|
||||||
number,
|
number,
|
||||||
store.VerifiedStatus.VERIFIED,
|
store.VerifiedStatus.VERIFIED,
|
||||||
|
@ -610,9 +604,9 @@ describe('SignalProtocolStore', function() {
|
||||||
assert.strictEqual(keychangeTriggered, 0);
|
assert.strictEqual(keychangeTriggered, 0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('when the record exists', function() {
|
describe('when the record exists', () => {
|
||||||
describe('when the existing key is different', function() {
|
describe('when the existing key is different', () => {
|
||||||
before(async function() {
|
before(async () => {
|
||||||
await window.Signal.Data.createOrUpdateIdentityKey({
|
await window.Signal.Data.createOrUpdateIdentityKey({
|
||||||
id: number,
|
id: number,
|
||||||
publicKey: testKey.pubKey,
|
publicKey: testKey.pubKey,
|
||||||
|
@ -623,7 +617,7 @@ describe('SignalProtocolStore', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('saves the new identity and marks it VERIFIED', async function() {
|
it('saves the new identity and marks it VERIFIED', async () => {
|
||||||
await store.processContactSyncVerificationState(
|
await store.processContactSyncVerificationState(
|
||||||
number,
|
number,
|
||||||
store.VerifiedStatus.VERIFIED,
|
store.VerifiedStatus.VERIFIED,
|
||||||
|
@ -642,8 +636,8 @@ describe('SignalProtocolStore', function() {
|
||||||
assert.strictEqual(keychangeTriggered, 1);
|
assert.strictEqual(keychangeTriggered, 1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('when the existing key is the same but UNVERIFIED', function() {
|
describe('when the existing key is the same but UNVERIFIED', () => {
|
||||||
before(async function() {
|
before(async () => {
|
||||||
await window.Signal.Data.createOrUpdateIdentityKey({
|
await window.Signal.Data.createOrUpdateIdentityKey({
|
||||||
id: number,
|
id: number,
|
||||||
publicKey: testKey.pubKey,
|
publicKey: testKey.pubKey,
|
||||||
|
@ -654,7 +648,7 @@ describe('SignalProtocolStore', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('saves the identity and marks it verified', async function() {
|
it('saves the identity and marks it verified', async () => {
|
||||||
await store.processContactSyncVerificationState(
|
await store.processContactSyncVerificationState(
|
||||||
number,
|
number,
|
||||||
store.VerifiedStatus.VERIFIED,
|
store.VerifiedStatus.VERIFIED,
|
||||||
|
@ -672,8 +666,8 @@ describe('SignalProtocolStore', function() {
|
||||||
assert.strictEqual(keychangeTriggered, 0);
|
assert.strictEqual(keychangeTriggered, 0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('when the existing key is the same and already VERIFIED', function() {
|
describe('when the existing key is the same and already VERIFIED', () => {
|
||||||
before(async function() {
|
before(async () => {
|
||||||
await window.Signal.Data.createOrUpdateIdentityKey({
|
await window.Signal.Data.createOrUpdateIdentityKey({
|
||||||
id: number,
|
id: number,
|
||||||
publicKey: testKey.pubKey,
|
publicKey: testKey.pubKey,
|
||||||
|
@ -684,7 +678,7 @@ describe('SignalProtocolStore', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not hang', async function() {
|
it('does not hang', async () => {
|
||||||
await store.processContactSyncVerificationState(
|
await store.processContactSyncVerificationState(
|
||||||
number,
|
number,
|
||||||
store.VerifiedStatus.VERIFIED,
|
store.VerifiedStatus.VERIFIED,
|
||||||
|
@ -698,8 +692,8 @@ describe('SignalProtocolStore', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('isUntrusted', function() {
|
describe('isUntrusted', () => {
|
||||||
it('returns false if identity key old enough', async function() {
|
it('returns false if identity key old enough', async () => {
|
||||||
await window.Signal.Data.createOrUpdateIdentityKey({
|
await window.Signal.Data.createOrUpdateIdentityKey({
|
||||||
id: number,
|
id: number,
|
||||||
publicKey: testKey.pubKey,
|
publicKey: testKey.pubKey,
|
||||||
|
@ -713,7 +707,7 @@ describe('SignalProtocolStore', function() {
|
||||||
assert.strictEqual(untrusted, false);
|
assert.strictEqual(untrusted, false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns false if new but nonblockingApproval is true', async function() {
|
it('returns false if new but nonblockingApproval is true', async () => {
|
||||||
await window.Signal.Data.createOrUpdateIdentityKey({
|
await window.Signal.Data.createOrUpdateIdentityKey({
|
||||||
id: number,
|
id: number,
|
||||||
publicKey: testKey.pubKey,
|
publicKey: testKey.pubKey,
|
||||||
|
@ -727,7 +721,7 @@ describe('SignalProtocolStore', function() {
|
||||||
assert.strictEqual(untrusted, false);
|
assert.strictEqual(untrusted, false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns false if new but firstUse is true', async function() {
|
it('returns false if new but firstUse is true', async () => {
|
||||||
await window.Signal.Data.createOrUpdateIdentityKey({
|
await window.Signal.Data.createOrUpdateIdentityKey({
|
||||||
id: number,
|
id: number,
|
||||||
publicKey: testKey.pubKey,
|
publicKey: testKey.pubKey,
|
||||||
|
@ -741,7 +735,7 @@ describe('SignalProtocolStore', function() {
|
||||||
assert.strictEqual(untrusted, false);
|
assert.strictEqual(untrusted, false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns true if new, and no flags are set', async function() {
|
it('returns true if new, and no flags are set', async () => {
|
||||||
await window.Signal.Data.createOrUpdateIdentityKey({
|
await window.Signal.Data.createOrUpdateIdentityKey({
|
||||||
id: number,
|
id: number,
|
||||||
publicKey: testKey.pubKey,
|
publicKey: testKey.pubKey,
|
||||||
|
@ -755,21 +749,21 @@ describe('SignalProtocolStore', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getVerified', function() {
|
describe('getVerified', () => {
|
||||||
before(async function() {
|
before(async () => {
|
||||||
await store.setVerified(number, store.VerifiedStatus.VERIFIED);
|
await store.setVerified(number, store.VerifiedStatus.VERIFIED);
|
||||||
});
|
});
|
||||||
it('resolves to the verified status', async function() {
|
it('resolves to the verified status', async () => {
|
||||||
const result = await store.getVerified(number);
|
const result = await store.getVerified(number);
|
||||||
assert.strictEqual(result, store.VerifiedStatus.VERIFIED);
|
assert.strictEqual(result, store.VerifiedStatus.VERIFIED);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('isTrustedIdentity', function() {
|
describe('isTrustedIdentity', () => {
|
||||||
const address = new libsignal.SignalProtocolAddress(number, 1);
|
const address = new libsignal.SignalProtocolAddress(number, 1);
|
||||||
const identifier = address.toString();
|
const identifier = address.toString();
|
||||||
|
|
||||||
describe('When invalid direction is given', function() {
|
describe('When invalid direction is given', () => {
|
||||||
it('should fail', async function() {
|
it('should fail', async () => {
|
||||||
try {
|
try {
|
||||||
await store.isTrustedIdentity(number, testKey.pubKey);
|
await store.isTrustedIdentity(number, testKey.pubKey);
|
||||||
throw new Error('isTrustedIdentity should have failed');
|
throw new Error('isTrustedIdentity should have failed');
|
||||||
|
@ -778,9 +772,9 @@ describe('SignalProtocolStore', function() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('When direction is RECEIVING', function() {
|
describe('When direction is RECEIVING', () => {
|
||||||
it('always returns true', async function() {
|
it('always returns true', async () => {
|
||||||
var newIdentity = libsignal.crypto.getRandomBytes(33);
|
const newIdentity = libsignal.crypto.getRandomBytes(33);
|
||||||
await store.saveIdentity(identifier, testKey.pubKey);
|
await store.saveIdentity(identifier, testKey.pubKey);
|
||||||
|
|
||||||
const trusted = await store.isTrustedIdentity(
|
const trusted = await store.isTrustedIdentity(
|
||||||
|
@ -794,12 +788,12 @@ describe('SignalProtocolStore', function() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('When direction is SENDING', function() {
|
describe('When direction is SENDING', () => {
|
||||||
describe('When there is no existing key (first use)', function() {
|
describe('When there is no existing key (first use)', () => {
|
||||||
before(async function() {
|
before(async () => {
|
||||||
await store.removeIdentityKey(number);
|
await store.removeIdentityKey(number);
|
||||||
});
|
});
|
||||||
it('returns true', async function() {
|
it('returns true', async () => {
|
||||||
const newIdentity = libsignal.crypto.getRandomBytes(33);
|
const newIdentity = libsignal.crypto.getRandomBytes(33);
|
||||||
const trusted = await store.isTrustedIdentity(
|
const trusted = await store.isTrustedIdentity(
|
||||||
identifier,
|
identifier,
|
||||||
|
@ -811,12 +805,12 @@ describe('SignalProtocolStore', function() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('When there is an existing key', function() {
|
describe('When there is an existing key', () => {
|
||||||
before(async function() {
|
before(async () => {
|
||||||
await store.saveIdentity(identifier, testKey.pubKey);
|
await store.saveIdentity(identifier, testKey.pubKey);
|
||||||
});
|
});
|
||||||
describe('When the existing key is different', function() {
|
describe('When the existing key is different', () => {
|
||||||
it('returns false', async function() {
|
it('returns false', async () => {
|
||||||
const newIdentity = libsignal.crypto.getRandomBytes(33);
|
const newIdentity = libsignal.crypto.getRandomBytes(33);
|
||||||
const trusted = await store.isTrustedIdentity(
|
const trusted = await store.isTrustedIdentity(
|
||||||
identifier,
|
identifier,
|
||||||
|
@ -828,12 +822,12 @@ describe('SignalProtocolStore', function() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('When the existing key matches the new key', function() {
|
describe('When the existing key matches the new key', () => {
|
||||||
const newIdentity = libsignal.crypto.getRandomBytes(33);
|
const newIdentity = libsignal.crypto.getRandomBytes(33);
|
||||||
before(async function() {
|
before(async () => {
|
||||||
await store.saveIdentity(identifier, newIdentity);
|
await store.saveIdentity(identifier, newIdentity);
|
||||||
});
|
});
|
||||||
it('returns false if keys match but we just received this new identiy', async function() {
|
it('returns false if keys match but we just received this new identiy', async () => {
|
||||||
const trusted = await store.isTrustedIdentity(
|
const trusted = await store.isTrustedIdentity(
|
||||||
identifier,
|
identifier,
|
||||||
newIdentity,
|
newIdentity,
|
||||||
|
@ -844,7 +838,7 @@ describe('SignalProtocolStore', function() {
|
||||||
throw new Error('isTrusted returned true on untrusted key');
|
throw new Error('isTrusted returned true on untrusted key');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
it('returns true if we have already approved identity', async function() {
|
it('returns true if we have already approved identity', async () => {
|
||||||
await store.saveIdentity(identifier, newIdentity, true);
|
await store.saveIdentity(identifier, newIdentity, true);
|
||||||
|
|
||||||
const trusted = await store.isTrustedIdentity(
|
const trusted = await store.isTrustedIdentity(
|
||||||
|
@ -860,27 +854,27 @@ describe('SignalProtocolStore', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('storePreKey', function() {
|
describe('storePreKey', () => {
|
||||||
it('stores prekeys', async function() {
|
it('stores prekeys', async () => {
|
||||||
await store.storePreKey(1, testKey);
|
await store.storePreKey(1, testKey);
|
||||||
const key = await store.loadPreKey(1);
|
const key = await store.loadPreKey(1);
|
||||||
assertEqualArrayBuffers(key.pubKey, testKey.pubKey);
|
assertEqualArrayBuffers(key.pubKey, testKey.pubKey);
|
||||||
assertEqualArrayBuffers(key.privKey, testKey.privKey);
|
assertEqualArrayBuffers(key.privKey, testKey.privKey);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('removePreKey', function() {
|
describe('removePreKey', () => {
|
||||||
before(async function() {
|
before(async () => {
|
||||||
await store.storePreKey(2, testKey);
|
await store.storePreKey(2, testKey);
|
||||||
});
|
});
|
||||||
it('deletes prekeys', async function() {
|
it('deletes prekeys', async () => {
|
||||||
await store.removePreKey(2, testKey);
|
await store.removePreKey(2, testKey);
|
||||||
|
|
||||||
const key = await store.loadPreKey(2);
|
const key = await store.loadPreKey(2);
|
||||||
assert.isUndefined(key);
|
assert.isUndefined(key);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('storeSignedPreKey', function() {
|
describe('storeSignedPreKey', () => {
|
||||||
it('stores signed prekeys', async function() {
|
it('stores signed prekeys', async () => {
|
||||||
await store.storeSignedPreKey(3, testKey);
|
await store.storeSignedPreKey(3, testKey);
|
||||||
|
|
||||||
const key = await store.loadSignedPreKey(3);
|
const key = await store.loadSignedPreKey(3);
|
||||||
|
@ -888,36 +882,36 @@ describe('SignalProtocolStore', function() {
|
||||||
assertEqualArrayBuffers(key.privKey, testKey.privKey);
|
assertEqualArrayBuffers(key.privKey, testKey.privKey);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('removeSignedPreKey', function() {
|
describe('removeSignedPreKey', () => {
|
||||||
before(async function() {
|
before(async () => {
|
||||||
await store.storeSignedPreKey(4, testKey);
|
await store.storeSignedPreKey(4, testKey);
|
||||||
});
|
});
|
||||||
it('deletes signed prekeys', async function() {
|
it('deletes signed prekeys', async () => {
|
||||||
await store.removeSignedPreKey(4, testKey);
|
await store.removeSignedPreKey(4, testKey);
|
||||||
|
|
||||||
const key = await store.loadSignedPreKey(4);
|
const key = await store.loadSignedPreKey(4);
|
||||||
assert.isUndefined(key);
|
assert.isUndefined(key);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('storeSession', function() {
|
describe('storeSession', () => {
|
||||||
it('stores sessions', async function() {
|
it('stores sessions', async () => {
|
||||||
const testRecord = 'an opaque string';
|
const testRecord = 'an opaque string';
|
||||||
|
|
||||||
await store.storeSession(number + '.1', testRecord);
|
await store.storeSession(`${number}.1`, testRecord);
|
||||||
const record = await store.loadSession(number + '.1');
|
const record = await store.loadSession(`${number}.1`);
|
||||||
|
|
||||||
assert.deepEqual(record, testRecord);
|
assert.deepEqual(record, testRecord);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('removeAllSessions', function() {
|
describe('removeAllSessions', () => {
|
||||||
it('removes all sessions for a number', async function() {
|
it('removes all sessions for a number', async () => {
|
||||||
const testRecord = 'an opaque string';
|
const testRecord = 'an opaque string';
|
||||||
const devices = [1, 2, 3].map(function(deviceId) {
|
const devices = [1, 2, 3].map(deviceId => {
|
||||||
return [number, deviceId].join('.');
|
return [number, deviceId].join('.');
|
||||||
});
|
});
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
devices.map(async function(encodedNumber) {
|
devices.map(async encodedNumber => {
|
||||||
await store.storeSession(encodedNumber, testRecord + encodedNumber);
|
await store.storeSession(encodedNumber, testRecord + encodedNumber);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
@ -927,30 +921,31 @@ describe('SignalProtocolStore', function() {
|
||||||
const records = await Promise.all(
|
const records = await Promise.all(
|
||||||
devices.map(store.loadSession.bind(store))
|
devices.map(store.loadSession.bind(store))
|
||||||
);
|
);
|
||||||
for (var i in records) {
|
|
||||||
|
for (let i = 0, max = records.length; i < max; i += 1) {
|
||||||
assert.isUndefined(records[i]);
|
assert.isUndefined(records[i]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('clearSessionStore', function() {
|
describe('clearSessionStore', () => {
|
||||||
it('clears the session store', async function() {
|
it('clears the session store', async () => {
|
||||||
const testRecord = 'an opaque string';
|
const testRecord = 'an opaque string';
|
||||||
await store.storeSession(number + '.1', testRecord);
|
await store.storeSession(`${number}.1`, testRecord);
|
||||||
await store.clearSessionStore();
|
await store.clearSessionStore();
|
||||||
|
|
||||||
const record = await store.loadSession(number + '.1');
|
const record = await store.loadSession(`${number}.1`);
|
||||||
assert.isUndefined(record);
|
assert.isUndefined(record);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('getDeviceIds', function() {
|
describe('getDeviceIds', () => {
|
||||||
it('returns deviceIds for a number', async function() {
|
it('returns deviceIds for a number', async () => {
|
||||||
const testRecord = 'an opaque string';
|
const testRecord = 'an opaque string';
|
||||||
const devices = [1, 2, 3].map(function(deviceId) {
|
const devices = [1, 2, 3].map(deviceId => {
|
||||||
return [number, deviceId].join('.');
|
return [number, deviceId].join('.');
|
||||||
});
|
});
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
devices.map(async function(encodedNumber) {
|
devices.map(async encodedNumber => {
|
||||||
await store.storeSession(encodedNumber, testRecord + encodedNumber);
|
await store.storeSession(encodedNumber, testRecord + encodedNumber);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
@ -958,20 +953,20 @@ describe('SignalProtocolStore', function() {
|
||||||
const deviceIds = await store.getDeviceIds(number);
|
const deviceIds = await store.getDeviceIds(number);
|
||||||
assert.sameMembers(deviceIds, [1, 2, 3]);
|
assert.sameMembers(deviceIds, [1, 2, 3]);
|
||||||
});
|
});
|
||||||
it('returns empty array for a number with no device ids', async function() {
|
it('returns empty array for a number with no device ids', async () => {
|
||||||
const deviceIds = await store.getDeviceIds('foo');
|
const deviceIds = await store.getDeviceIds('foo');
|
||||||
assert.sameMembers(deviceIds, []);
|
assert.sameMembers(deviceIds, []);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Not yet processed messages', function() {
|
describe('Not yet processed messages', () => {
|
||||||
beforeEach(async function() {
|
beforeEach(async () => {
|
||||||
await store.removeAllUnprocessed();
|
await store.removeAllUnprocessed();
|
||||||
const items = await store.getAllUnprocessed();
|
const items = await store.getAllUnprocessed();
|
||||||
assert.strictEqual(items.length, 0);
|
assert.strictEqual(items.length, 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('adds two and gets them back', async function() {
|
it('adds two and gets them back', async () => {
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
store.addUnprocessed({ id: 2, name: 'second', timestamp: 2 }),
|
store.addUnprocessed({ id: 2, name: 'second', timestamp: 2 }),
|
||||||
store.addUnprocessed({ id: 3, name: 'third', timestamp: 3 }),
|
store.addUnprocessed({ id: 3, name: 'third', timestamp: 3 }),
|
||||||
|
@ -987,9 +982,9 @@ describe('SignalProtocolStore', function() {
|
||||||
assert.strictEqual(items[2].name, 'third');
|
assert.strictEqual(items[2].name, 'third');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('saveUnprocessed successfully updates item', async function() {
|
it('saveUnprocessed successfully updates item', async () => {
|
||||||
const id = 1;
|
const id = 1;
|
||||||
await store.addUnprocessed({ id: id, name: 'first', timestamp: 1 });
|
await store.addUnprocessed({ id, name: 'first', timestamp: 1 });
|
||||||
await store.saveUnprocessed({ id, name: 'updated', timestamp: 1 });
|
await store.saveUnprocessed({ id, name: 'updated', timestamp: 1 });
|
||||||
|
|
||||||
const items = await store.getAllUnprocessed();
|
const items = await store.getAllUnprocessed();
|
||||||
|
@ -998,9 +993,9 @@ describe('SignalProtocolStore', function() {
|
||||||
assert.strictEqual(items[0].timestamp, 1);
|
assert.strictEqual(items[0].timestamp, 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('removeUnprocessed successfully deletes item', async function() {
|
it('removeUnprocessed successfully deletes item', async () => {
|
||||||
const id = 1;
|
const id = 1;
|
||||||
await store.addUnprocessed({ id: id, name: 'first', timestamp: 1 });
|
await store.addUnprocessed({ id, name: 'first', timestamp: 1 });
|
||||||
await store.removeUnprocessed(id);
|
await store.removeUnprocessed(id);
|
||||||
|
|
||||||
const items = await store.getAllUnprocessed();
|
const items = await store.getAllUnprocessed();
|
||||||
|
|
|
@ -1,17 +1,15 @@
|
||||||
/* global assert: false */
|
/* global assert, storage, Whisper */
|
||||||
|
|
||||||
/* global Whisper: false */
|
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
describe('AttachmentView', () => {
|
describe('AttachmentView', () => {
|
||||||
var convo, message;
|
let convo;
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
await clearDatabase();
|
await clearDatabase();
|
||||||
|
|
||||||
convo = new Whisper.Conversation({ id: 'foo' });
|
convo = new Whisper.Conversation({ id: 'foo' });
|
||||||
message = convo.messageCollection.add({
|
convo.messageCollection.add({
|
||||||
conversationId: convo.id,
|
conversationId: convo.id,
|
||||||
body: 'hello world',
|
body: 'hello world',
|
||||||
type: 'outgoing',
|
type: 'outgoing',
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
describe('ConversationSearchView', function() {
|
/* global $, Whisper */
|
||||||
it('should match partial numbers', function() {
|
|
||||||
var $el = $('<div><div class="new-contact contact hide"></div></div>');
|
describe('ConversationSearchView', () => {
|
||||||
var view = new Whisper.ConversationSearchView({
|
it('should match partial numbers', () => {
|
||||||
|
const $el = $('<div><div class="new-contact contact hide"></div></div>');
|
||||||
|
const view = new Whisper.ConversationSearchView({
|
||||||
el: $el,
|
el: $el,
|
||||||
input: $('<input>'),
|
input: $('<input>'),
|
||||||
}).render();
|
}).render();
|
||||||
var maybe_numbers = [
|
const maybeNumbers = [
|
||||||
'+1 415',
|
'+1 415',
|
||||||
'+1415',
|
'+1415',
|
||||||
'+1415',
|
'+1415',
|
||||||
|
@ -19,11 +21,11 @@ describe('ConversationSearchView', function() {
|
||||||
'1 415-123-4567',
|
'1 415-123-4567',
|
||||||
'415-123-4567',
|
'415-123-4567',
|
||||||
];
|
];
|
||||||
maybe_numbers.forEach(function(n) {
|
maybeNumbers.forEach(n => {
|
||||||
assert.ok(view.maybeNumber(n), n);
|
assert.ok(view.maybeNumber(n), n);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('Searching for left groups', function() {
|
describe('Searching for left groups', () => {
|
||||||
let convo;
|
let convo;
|
||||||
|
|
||||||
before(() => {
|
before(() => {
|
||||||
|
@ -39,32 +41,32 @@ describe('ConversationSearchView', function() {
|
||||||
Conversation: Whisper.Conversation,
|
Conversation: Whisper.Conversation,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('with no messages', function() {
|
describe('with no messages', () => {
|
||||||
var input;
|
let input;
|
||||||
var view;
|
let view;
|
||||||
|
|
||||||
before(function(done) {
|
before(done => {
|
||||||
input = $('<input>');
|
input = $('<input>');
|
||||||
view = new Whisper.ConversationSearchView({ input: input }).render();
|
view = new Whisper.ConversationSearchView({ input }).render();
|
||||||
view.$input.val('left');
|
view.$input.val('left');
|
||||||
view.filterContacts();
|
view.filterContacts();
|
||||||
view.typeahead_view.collection.on('reset', function() {
|
view.typeahead_view.collection.on('reset', () => {
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('should not surface left groups with no messages', function() {
|
it('should not surface left groups with no messages', () => {
|
||||||
assert.isUndefined(
|
assert.isUndefined(
|
||||||
view.typeahead_view.collection.get(convo.id),
|
view.typeahead_view.collection.get(convo.id),
|
||||||
'got left group'
|
'got left group'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('with messages', function() {
|
describe('with messages', () => {
|
||||||
var input;
|
let input;
|
||||||
var view;
|
let view;
|
||||||
before(async function() {
|
before(async () => {
|
||||||
input = $('<input>');
|
input = $('<input>');
|
||||||
view = new Whisper.ConversationSearchView({ input: input }).render();
|
view = new Whisper.ConversationSearchView({ input }).render();
|
||||||
convo.set({ id: '2-search-view', left: false });
|
convo.set({ id: '2-search-view', left: false });
|
||||||
|
|
||||||
await window.Signal.Data.saveConversation(convo.attributes, {
|
await window.Signal.Data.saveConversation(convo.attributes, {
|
||||||
|
@ -78,7 +80,7 @@ describe('ConversationSearchView', function() {
|
||||||
view.typeahead_view.collection.on('reset', resolve);
|
view.typeahead_view.collection.on('reset', resolve);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('should surface left groups with messages', function() {
|
it('should surface left groups with messages', () => {
|
||||||
assert.isDefined(
|
assert.isDefined(
|
||||||
view.typeahead_view.collection.get(convo.id),
|
view.typeahead_view.collection.get(convo.id),
|
||||||
'got left group'
|
'got left group'
|
||||||
|
|
|
@ -1,20 +1,22 @@
|
||||||
describe('GroupUpdateView', function() {
|
/* global Whisper */
|
||||||
it('should show new group members', function() {
|
|
||||||
var view = new Whisper.GroupUpdateView({
|
describe('GroupUpdateView', () => {
|
||||||
|
it('should show new group members', () => {
|
||||||
|
const view = new Whisper.GroupUpdateView({
|
||||||
model: { joined: ['Alice', 'Bob'] },
|
model: { joined: ['Alice', 'Bob'] },
|
||||||
}).render();
|
}).render();
|
||||||
assert.match(view.$el.text(), /Alice.*Bob.*joined the group/);
|
assert.match(view.$el.text(), /Alice.*Bob.*joined the group/);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should note updates to the title', function() {
|
it('should note updates to the title', () => {
|
||||||
var view = new Whisper.GroupUpdateView({
|
const view = new Whisper.GroupUpdateView({
|
||||||
model: { name: 'New name' },
|
model: { name: 'New name' },
|
||||||
}).render();
|
}).render();
|
||||||
assert.match(view.$el.text(), /Title is now 'New name'/);
|
assert.match(view.$el.text(), /Title is now 'New name'/);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should say "Updated the group"', function() {
|
it('should say "Updated the group"', () => {
|
||||||
var view = new Whisper.GroupUpdateView({
|
const view = new Whisper.GroupUpdateView({
|
||||||
model: { avatar: 'New avatar' },
|
model: { avatar: 'New avatar' },
|
||||||
}).render();
|
}).render();
|
||||||
assert.match(view.$el.text(), /Updated the group/);
|
assert.match(view.$el.text(), /Updated the group/);
|
||||||
|
|
|
@ -1,24 +1,20 @@
|
||||||
describe('InboxView', function() {
|
/* global ConversationController, textsecure, Whisper */
|
||||||
|
|
||||||
|
describe('InboxView', () => {
|
||||||
let inboxView;
|
let inboxView;
|
||||||
let conversation;
|
let conversation;
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
try {
|
ConversationController.reset();
|
||||||
await ConversationController.load();
|
await ConversationController.load();
|
||||||
} catch (error) {
|
|
||||||
console.log(
|
|
||||||
'InboxView before:',
|
|
||||||
error && error.stack ? error.stack : error
|
|
||||||
);
|
|
||||||
}
|
|
||||||
await ConversationController.getOrCreateAndWait(
|
await ConversationController.getOrCreateAndWait(
|
||||||
textsecure.storage.user.getNumber(),
|
textsecure.storage.user.getNumber(),
|
||||||
'private'
|
'private'
|
||||||
);
|
);
|
||||||
inboxView = new Whisper.InboxView({
|
inboxView = new Whisper.InboxView({
|
||||||
model: {},
|
model: {},
|
||||||
window: window,
|
window,
|
||||||
initialLoadComplete: function() {},
|
initialLoadComplete() {},
|
||||||
}).render();
|
}).render();
|
||||||
|
|
||||||
conversation = new Whisper.Conversation({
|
conversation = new Whisper.Conversation({
|
||||||
|
@ -27,32 +23,32 @@ describe('InboxView', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('the conversation stack', function() {
|
describe('the conversation stack', () => {
|
||||||
it('should be rendered', function() {
|
it('should be rendered', () => {
|
||||||
assert.ok(inboxView.$('.conversation-stack').length === 1);
|
assert.ok(inboxView.$('.conversation-stack').length === 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('opening a conversation', function() {
|
describe('opening a conversation', () => {
|
||||||
var triggeredOpenedCount = 0;
|
let triggeredOpenedCount = 0;
|
||||||
|
|
||||||
before(function() {
|
before(() => {
|
||||||
conversation.on('opened', function() {
|
conversation.on('opened', () => {
|
||||||
triggeredOpenedCount++;
|
triggeredOpenedCount += 1;
|
||||||
});
|
});
|
||||||
|
|
||||||
inboxView.conversation_stack.open(conversation);
|
inboxView.conversation_stack.open(conversation);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should trigger an opened event', function() {
|
it('should trigger an opened event', () => {
|
||||||
assert.ok(triggeredOpenedCount === 1);
|
assert.ok(triggeredOpenedCount === 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('and then opening it again immediately', function() {
|
describe('and then opening it again immediately', () => {
|
||||||
before(function() {
|
before(() => {
|
||||||
inboxView.conversation_stack.open(conversation);
|
inboxView.conversation_stack.open(conversation);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should trigger the opened event again', function() {
|
it('should trigger the opened event again', () => {
|
||||||
assert.ok(triggeredOpenedCount === 2);
|
assert.ok(triggeredOpenedCount === 2);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,22 +1,24 @@
|
||||||
describe('LastSeenIndicatorView', function() {
|
/* global Whisper */
|
||||||
it('renders provided count', function() {
|
|
||||||
var view = new Whisper.LastSeenIndicatorView({ count: 10 });
|
describe('LastSeenIndicatorView', () => {
|
||||||
|
it('renders provided count', () => {
|
||||||
|
const view = new Whisper.LastSeenIndicatorView({ count: 10 });
|
||||||
assert.equal(view.count, 10);
|
assert.equal(view.count, 10);
|
||||||
|
|
||||||
view.render();
|
view.render();
|
||||||
assert.match(view.$el.html(), /10 Unread Messages/);
|
assert.match(view.$el.html(), /10 Unread Messages/);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders count of 1', function() {
|
it('renders count of 1', () => {
|
||||||
var view = new Whisper.LastSeenIndicatorView({ count: 1 });
|
const view = new Whisper.LastSeenIndicatorView({ count: 1 });
|
||||||
assert.equal(view.count, 1);
|
assert.equal(view.count, 1);
|
||||||
|
|
||||||
view.render();
|
view.render();
|
||||||
assert.match(view.$el.html(), /1 Unread Message/);
|
assert.match(view.$el.html(), /1 Unread Message/);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('increments count', function() {
|
it('increments count', () => {
|
||||||
var view = new Whisper.LastSeenIndicatorView({ count: 4 });
|
const view = new Whisper.LastSeenIndicatorView({ count: 4 });
|
||||||
|
|
||||||
assert.equal(view.count, 4);
|
assert.equal(view.count, 4);
|
||||||
view.render();
|
view.render();
|
||||||
|
|
|
@ -1,20 +1,22 @@
|
||||||
describe('ListView', function() {
|
/* global Backbone, Whisper */
|
||||||
var collection;
|
|
||||||
|
|
||||||
beforeEach(function() {
|
describe('ListView', () => {
|
||||||
|
let collection;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
collection = new Backbone.Collection();
|
collection = new Backbone.Collection();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should add children to the list element as they are added to the collection', function() {
|
it('should add children to the list element as they are added to the collection', () => {
|
||||||
var view = new Whisper.ListView({ collection: collection });
|
const view = new Whisper.ListView({ collection });
|
||||||
collection.add('hello');
|
collection.add('hello');
|
||||||
assert.equal(view.$el.children().length, 1);
|
assert.equal(view.$el.children().length, 1);
|
||||||
collection.add('world');
|
collection.add('world');
|
||||||
assert.equal(view.$el.children().length, 2);
|
assert.equal(view.$el.children().length, 2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should add all the children to the list element on reset', function() {
|
it('should add all the children to the list element on reset', () => {
|
||||||
var view = new Whisper.ListView({ collection: collection });
|
const view = new Whisper.ListView({ collection });
|
||||||
collection.reset(['goodbye', 'world']);
|
collection.reset(['goodbye', 'world']);
|
||||||
assert.equal(view.$el.children().length, 2);
|
assert.equal(view.$el.children().length, 2);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,149 +1,143 @@
|
||||||
describe('NetworkStatusView', function() {
|
/* global _, $, Whisper */
|
||||||
describe('getNetworkStatus', function() {
|
|
||||||
var networkStatusView;
|
|
||||||
var socketStatus = WebSocket.OPEN;
|
|
||||||
|
|
||||||
var oldGetSocketStatus;
|
describe('NetworkStatusView', () => {
|
||||||
|
describe('getNetworkStatus', () => {
|
||||||
|
let networkStatusView;
|
||||||
|
let socketStatus = WebSocket.OPEN;
|
||||||
|
|
||||||
|
let oldGetSocketStatus;
|
||||||
|
|
||||||
/* BEGIN stubbing globals */
|
/* BEGIN stubbing globals */
|
||||||
before(function() {
|
before(() => {
|
||||||
oldGetSocketStatus = window.getSocketStatus;
|
oldGetSocketStatus = window.getSocketStatus;
|
||||||
window.getSocketStatus = function() {
|
window.getSocketStatus = () => socketStatus;
|
||||||
return socketStatus;
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
|
|
||||||
after(function() {
|
after(() => {
|
||||||
window.getSocketStatus = oldGetSocketStatus;
|
window.getSocketStatus = oldGetSocketStatus;
|
||||||
|
|
||||||
// It turns out that continued calls to window.getSocketStatus happen
|
// It turns out that continued calls to window.getSocketStatus happen
|
||||||
// because we host NetworkStatusView in three mock interfaces, and the view
|
// because we host NetworkStatusView in three mock interfaces, and the view
|
||||||
// checks every N seconds. That results in infinite errors unless there is
|
// checks every N seconds. That results in infinite errors unless there is
|
||||||
// something to call.
|
// something to call.
|
||||||
window.getSocketStatus = function() {
|
window.getSocketStatus = () => WebSocket.OPEN;
|
||||||
return WebSocket.OPEN;
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
/* END stubbing globals */
|
/* END stubbing globals */
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(() => {
|
||||||
networkStatusView = new Whisper.NetworkStatusView();
|
networkStatusView = new Whisper.NetworkStatusView();
|
||||||
$('.network-status-container').append(networkStatusView.el);
|
$('.network-status-container').append(networkStatusView.el);
|
||||||
});
|
});
|
||||||
afterEach(function() {
|
afterEach(() => {
|
||||||
// prevents huge number of errors on console after running tests
|
// prevents huge number of errors on console after running tests
|
||||||
clearInterval(networkStatusView.renderIntervalHandle);
|
clearInterval(networkStatusView.renderIntervalHandle);
|
||||||
networkStatusView = null;
|
networkStatusView = null;
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('initialization', function() {
|
describe('initialization', () => {
|
||||||
it('should have an empty interval', function() {
|
it('should have an empty interval', () => {
|
||||||
assert.equal(
|
assert.equal(
|
||||||
networkStatusView.socketReconnectWaitDuration.asSeconds(),
|
networkStatusView.socketReconnectWaitDuration.asSeconds(),
|
||||||
0
|
0
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('network status with no connection', function() {
|
describe('network status with no connection', () => {
|
||||||
beforeEach(function() {
|
beforeEach(() => {
|
||||||
networkStatusView.navigatorOnLine = function() {
|
networkStatusView.navigatorOnLine = () => false;
|
||||||
return false;
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
it('should be interrupted', function() {
|
it('should be interrupted', () => {
|
||||||
networkStatusView.update();
|
networkStatusView.update();
|
||||||
var status = networkStatusView.getNetworkStatus();
|
const status = networkStatusView.getNetworkStatus();
|
||||||
assert(status.hasInterruption);
|
assert(status.hasInterruption);
|
||||||
assert.equal(status.instructions, 'Check your network connection.');
|
assert.equal(status.instructions, 'Check your network connection.');
|
||||||
});
|
});
|
||||||
it('should display an offline message', function() {
|
it('should display an offline message', () => {
|
||||||
networkStatusView.update();
|
networkStatusView.update();
|
||||||
assert.match(networkStatusView.$el.text(), /Offline/);
|
assert.match(networkStatusView.$el.text(), /Offline/);
|
||||||
});
|
});
|
||||||
it('should override socket status', function() {
|
it('should override socket status', () => {
|
||||||
_([
|
_([
|
||||||
WebSocket.CONNECTING,
|
WebSocket.CONNECTING,
|
||||||
WebSocket.OPEN,
|
WebSocket.OPEN,
|
||||||
WebSocket.CLOSING,
|
WebSocket.CLOSING,
|
||||||
WebSocket.CLOSED,
|
WebSocket.CLOSED,
|
||||||
]).map(function(socketStatusVal) {
|
]).forEach(socketStatusVal => {
|
||||||
socketStatus = socketStatusVal;
|
socketStatus = socketStatusVal;
|
||||||
networkStatusView.update();
|
networkStatusView.update();
|
||||||
assert.match(networkStatusView.$el.text(), /Offline/);
|
assert.match(networkStatusView.$el.text(), /Offline/);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('should override registration status', function() {
|
it('should override registration status', () => {
|
||||||
Whisper.Registration.remove();
|
Whisper.Registration.remove();
|
||||||
networkStatusView.update();
|
networkStatusView.update();
|
||||||
assert.match(networkStatusView.$el.text(), /Offline/);
|
assert.match(networkStatusView.$el.text(), /Offline/);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('network status when registration is not done', function() {
|
describe('network status when registration is not done', () => {
|
||||||
beforeEach(function() {
|
beforeEach(() => {
|
||||||
Whisper.Registration.remove();
|
Whisper.Registration.remove();
|
||||||
});
|
});
|
||||||
it('should display an unlinked message', function() {
|
it('should display an unlinked message', () => {
|
||||||
networkStatusView.update();
|
networkStatusView.update();
|
||||||
assert.match(networkStatusView.$el.text(), /Relink/);
|
assert.match(networkStatusView.$el.text(), /Relink/);
|
||||||
});
|
});
|
||||||
it('should override socket status', function() {
|
it('should override socket status', () => {
|
||||||
_([
|
_([
|
||||||
WebSocket.CONNECTING,
|
WebSocket.CONNECTING,
|
||||||
WebSocket.OPEN,
|
WebSocket.OPEN,
|
||||||
WebSocket.CLOSING,
|
WebSocket.CLOSING,
|
||||||
WebSocket.CLOSED,
|
WebSocket.CLOSED,
|
||||||
]).map(function(socketStatusVal) {
|
]).forEach(socketStatusVal => {
|
||||||
socketStatus = socketStatusVal;
|
socketStatus = socketStatusVal;
|
||||||
networkStatusView.update();
|
networkStatusView.update();
|
||||||
assert.match(networkStatusView.$el.text(), /Relink/);
|
assert.match(networkStatusView.$el.text(), /Relink/);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('network status when registration is done', function() {
|
describe('network status when registration is done', () => {
|
||||||
beforeEach(function() {
|
beforeEach(() => {
|
||||||
networkStatusView.navigatorOnLine = function() {
|
networkStatusView.navigatorOnLine = () => true;
|
||||||
return true;
|
|
||||||
};
|
|
||||||
Whisper.Registration.markDone();
|
Whisper.Registration.markDone();
|
||||||
networkStatusView.update();
|
networkStatusView.update();
|
||||||
});
|
});
|
||||||
it('should not display an unlinked message', function() {
|
it('should not display an unlinked message', () => {
|
||||||
networkStatusView.update();
|
networkStatusView.update();
|
||||||
assert.notMatch(networkStatusView.$el.text(), /Relink/);
|
assert.notMatch(networkStatusView.$el.text(), /Relink/);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('network status when socket is connecting', function() {
|
describe('network status when socket is connecting', () => {
|
||||||
beforeEach(function() {
|
beforeEach(() => {
|
||||||
Whisper.Registration.markDone();
|
Whisper.Registration.markDone();
|
||||||
socketStatus = WebSocket.CONNECTING;
|
socketStatus = WebSocket.CONNECTING;
|
||||||
networkStatusView.update();
|
networkStatusView.update();
|
||||||
});
|
});
|
||||||
it('it should display a connecting string if connecting and not in the connecting grace period', function() {
|
it('it should display a connecting string if connecting and not in the connecting grace period', () => {
|
||||||
networkStatusView.withinConnectingGracePeriod = false;
|
networkStatusView.withinConnectingGracePeriod = false;
|
||||||
var status = networkStatusView.getNetworkStatus();
|
networkStatusView.getNetworkStatus();
|
||||||
|
|
||||||
assert.match(networkStatusView.$el.text(), /Connecting/);
|
assert.match(networkStatusView.$el.text(), /Connecting/);
|
||||||
});
|
});
|
||||||
it('it should not be interrupted if in connecting grace period', function() {
|
it('it should not be interrupted if in connecting grace period', () => {
|
||||||
assert(networkStatusView.withinConnectingGracePeriod);
|
assert(networkStatusView.withinConnectingGracePeriod);
|
||||||
var status = networkStatusView.getNetworkStatus();
|
const status = networkStatusView.getNetworkStatus();
|
||||||
|
|
||||||
assert.match(networkStatusView.$el.text(), /Connecting/);
|
assert.match(networkStatusView.$el.text(), /Connecting/);
|
||||||
assert(!status.hasInterruption);
|
assert(!status.hasInterruption);
|
||||||
});
|
});
|
||||||
it('it should be interrupted if connecting grace period is over', function() {
|
it('it should be interrupted if connecting grace period is over', () => {
|
||||||
networkStatusView.withinConnectingGracePeriod = false;
|
networkStatusView.withinConnectingGracePeriod = false;
|
||||||
var status = networkStatusView.getNetworkStatus();
|
const status = networkStatusView.getNetworkStatus();
|
||||||
|
|
||||||
assert(status.hasInterruption);
|
assert(status.hasInterruption);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('network status when socket is open', function() {
|
describe('network status when socket is open', () => {
|
||||||
before(function() {
|
before(() => {
|
||||||
socketStatus = WebSocket.OPEN;
|
socketStatus = WebSocket.OPEN;
|
||||||
});
|
});
|
||||||
it('should not be interrupted', function() {
|
it('should not be interrupted', () => {
|
||||||
var status = networkStatusView.getNetworkStatus();
|
const status = networkStatusView.getNetworkStatus();
|
||||||
assert(!status.hasInterruption);
|
assert(!status.hasInterruption);
|
||||||
assert.match(
|
assert.match(
|
||||||
networkStatusView.$el
|
networkStatusView.$el
|
||||||
|
@ -154,23 +148,23 @@ describe('NetworkStatusView', function() {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('network status when socket is closed or closing', function() {
|
describe('network status when socket is closed or closing', () => {
|
||||||
_([WebSocket.CLOSED, WebSocket.CLOSING]).map(function(socketStatusVal) {
|
_([WebSocket.CLOSED, WebSocket.CLOSING]).forEach(socketStatusVal => {
|
||||||
it('should be interrupted', function() {
|
it('should be interrupted', () => {
|
||||||
socketStatus = socketStatusVal;
|
socketStatus = socketStatusVal;
|
||||||
networkStatusView.update();
|
networkStatusView.update();
|
||||||
var status = networkStatusView.getNetworkStatus();
|
const status = networkStatusView.getNetworkStatus();
|
||||||
assert(status.hasInterruption);
|
assert(status.hasInterruption);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('the socket reconnect interval', function() {
|
describe('the socket reconnect interval', () => {
|
||||||
beforeEach(function() {
|
beforeEach(() => {
|
||||||
socketStatus = WebSocket.CLOSED;
|
socketStatus = WebSocket.CLOSED;
|
||||||
networkStatusView.setSocketReconnectInterval(61000);
|
networkStatusView.setSocketReconnectInterval(61000);
|
||||||
networkStatusView.update();
|
networkStatusView.update();
|
||||||
});
|
});
|
||||||
it('should format the message based on the socketReconnectWaitDuration property', function() {
|
it('should format the message based on the socketReconnectWaitDuration property', () => {
|
||||||
assert.equal(
|
assert.equal(
|
||||||
networkStatusView.socketReconnectWaitDuration.asSeconds(),
|
networkStatusView.socketReconnectWaitDuration.asSeconds(),
|
||||||
61
|
61
|
||||||
|
@ -180,7 +174,7 @@ describe('NetworkStatusView', function() {
|
||||||
/Attempting reconnect/
|
/Attempting reconnect/
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
it('should be reset by changing the socketStatus to CONNECTING', function() {});
|
it('should be reset by changing the socketStatus to CONNECTING', () => {});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,28 +1,30 @@
|
||||||
describe('ScrollDownButtonView', function() {
|
/* global Whisper */
|
||||||
it('renders with count = 0', function() {
|
|
||||||
var view = new Whisper.ScrollDownButtonView();
|
describe('ScrollDownButtonView', () => {
|
||||||
|
it('renders with count = 0', () => {
|
||||||
|
const view = new Whisper.ScrollDownButtonView();
|
||||||
view.render();
|
view.render();
|
||||||
assert.equal(view.count, 0);
|
assert.equal(view.count, 0);
|
||||||
assert.match(view.$el.html(), /Scroll to bottom/);
|
assert.match(view.$el.html(), /Scroll to bottom/);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders with count = 1', function() {
|
it('renders with count = 1', () => {
|
||||||
var view = new Whisper.ScrollDownButtonView({ count: 1 });
|
const view = new Whisper.ScrollDownButtonView({ count: 1 });
|
||||||
view.render();
|
view.render();
|
||||||
assert.equal(view.count, 1);
|
assert.equal(view.count, 1);
|
||||||
assert.match(view.$el.html(), /New message below/);
|
assert.match(view.$el.html(), /New message below/);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders with count = 2', function() {
|
it('renders with count = 2', () => {
|
||||||
var view = new Whisper.ScrollDownButtonView({ count: 2 });
|
const view = new Whisper.ScrollDownButtonView({ count: 2 });
|
||||||
view.render();
|
view.render();
|
||||||
assert.equal(view.count, 2);
|
assert.equal(view.count, 2);
|
||||||
|
|
||||||
assert.match(view.$el.html(), /New messages below/);
|
assert.match(view.$el.html(), /New messages below/);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('increments count and re-renders', function() {
|
it('increments count and re-renders', () => {
|
||||||
var view = new Whisper.ScrollDownButtonView();
|
const view = new Whisper.ScrollDownButtonView();
|
||||||
view.render();
|
view.render();
|
||||||
assert.equal(view.count, 0);
|
assert.equal(view.count, 0);
|
||||||
assert.notMatch(view.$el.html(), /New message below/);
|
assert.notMatch(view.$el.html(), /New message below/);
|
||||||
|
|
|
@ -1,17 +1,19 @@
|
||||||
describe('Threads', function() {
|
/* global Whisper */
|
||||||
it('should be ordered newest to oldest', function() {
|
|
||||||
|
describe('Threads', () => {
|
||||||
|
it('should be ordered newest to oldest', () => {
|
||||||
// Timestamps
|
// Timestamps
|
||||||
var today = new Date();
|
const today = new Date();
|
||||||
var tomorrow = new Date();
|
const tomorrow = new Date();
|
||||||
tomorrow.setDate(today.getDate() + 1);
|
tomorrow.setDate(today.getDate() + 1);
|
||||||
|
|
||||||
// Add threads
|
// Add threads
|
||||||
Whisper.Threads.add({ timestamp: today });
|
Whisper.Threads.add({ timestamp: today });
|
||||||
Whisper.Threads.add({ timestamp: tomorrow });
|
Whisper.Threads.add({ timestamp: tomorrow });
|
||||||
|
|
||||||
var models = Whisper.Threads.models;
|
const { models } = Whisper.Threads;
|
||||||
var firstTimestamp = models[0].get('timestamp').getTime();
|
const firstTimestamp = models[0].get('timestamp').getTime();
|
||||||
var secondTimestamp = models[1].get('timestamp').getTime();
|
const secondTimestamp = models[1].get('timestamp').getTime();
|
||||||
|
|
||||||
// Compare timestamps
|
// Compare timestamps
|
||||||
assert(firstTimestamp > secondTimestamp);
|
assert(firstTimestamp > secondTimestamp);
|
||||||
|
|
|
@ -1,38 +1,41 @@
|
||||||
|
/* global moment, Whisper */
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
describe('TimestampView', function() {
|
describe('TimestampView', () => {
|
||||||
it('formats long-ago timestamps correctly', function() {
|
it('formats long-ago timestamps correctly', () => {
|
||||||
var timestamp = Date.now();
|
const timestamp = Date.now();
|
||||||
var brief_view = new Whisper.TimestampView({ brief: true }).render(),
|
const briefView = new Whisper.TimestampView({ brief: true }).render();
|
||||||
ext_view = new Whisper.ExtendedTimestampView().render();
|
const extendedView = new Whisper.ExtendedTimestampView().render();
|
||||||
|
|
||||||
// Helper functions to check absolute and relative timestamps
|
// Helper functions to check absolute and relative timestamps
|
||||||
|
|
||||||
// Helper to check an absolute TS for an exact match
|
// Helper to check an absolute TS for an exact match
|
||||||
var check = function(view, ts, expected) {
|
const check = (view, ts, expected) => {
|
||||||
var result = view.getRelativeTimeSpanString(ts);
|
const result = view.getRelativeTimeSpanString(ts);
|
||||||
assert.strictEqual(result, expected);
|
assert.strictEqual(result, expected);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Helper to check relative times for an exact match against both views
|
// Helper to check relative times for an exact match against both views
|
||||||
var checkDiff = function(sec_ago, expected_brief, expected_ext) {
|
const checkDiff = (secAgo, expectedBrief, expectedExtended) => {
|
||||||
check(brief_view, timestamp - sec_ago * 1000, expected_brief);
|
check(briefView, timestamp - secAgo * 1000, expectedBrief);
|
||||||
check(ext_view, timestamp - sec_ago * 1000, expected_ext);
|
check(extendedView, timestamp - secAgo * 1000, expectedExtended);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Helper to check an absolute TS for an exact match against both views
|
// Helper to check an absolute TS for an exact match against both views
|
||||||
var checkAbs = function(ts, expected_brief, expected_ext) {
|
const checkAbs = (ts, expectedBrief, expectedExtended) => {
|
||||||
if (!expected_ext) {
|
if (!expectedExtended) {
|
||||||
expected_ext = expected_brief;
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
expectedExtended = expectedBrief;
|
||||||
}
|
}
|
||||||
check(brief_view, ts, expected_brief);
|
check(briefView, ts, expectedBrief);
|
||||||
check(ext_view, ts, expected_ext);
|
check(extendedView, ts, expectedExtended);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Helper to check an absolute TS for a match at the beginning against
|
// Helper to check an absolute TS for a match at the beginning against
|
||||||
var checkStartsWith = function(view, ts, expected) {
|
const checkStartsWith = (view, ts, expected) => {
|
||||||
var result = view.getRelativeTimeSpanString(ts);
|
const result = view.getRelativeTimeSpanString(ts);
|
||||||
var regexp = new RegExp('^' + expected);
|
const regexp = new RegExp(`^${expected}`);
|
||||||
assert.match(result, regexp);
|
assert.match(result, regexp);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -48,82 +51,85 @@ describe('TimestampView', function() {
|
||||||
checkDiff(125 * 60, '2 hours', '2 hours ago');
|
checkDiff(125 * 60, '2 hours', '2 hours ago');
|
||||||
|
|
||||||
// set to third of month to avoid problems on the 29th/30th/31st
|
// set to third of month to avoid problems on the 29th/30th/31st
|
||||||
var last_month = moment()
|
const lastMonth = moment()
|
||||||
.subtract(1, 'month')
|
.subtract(1, 'month')
|
||||||
.date(3),
|
.date(3);
|
||||||
months = [
|
const months = [
|
||||||
'Jan',
|
'Jan',
|
||||||
'Feb',
|
'Feb',
|
||||||
'Mar',
|
'Mar',
|
||||||
'Apr',
|
'Apr',
|
||||||
'May',
|
'May',
|
||||||
'Jun',
|
'Jun',
|
||||||
'Jul',
|
'Jul',
|
||||||
'Aug',
|
'Aug',
|
||||||
'Sep',
|
'Sep',
|
||||||
'Oct',
|
'Oct',
|
||||||
'Nov',
|
'Nov',
|
||||||
'Dec',
|
'Dec',
|
||||||
],
|
];
|
||||||
day_of_month = new Date().getDate();
|
check(briefView, lastMonth, `${months[lastMonth.month()]} 3`);
|
||||||
check(brief_view, last_month, months[last_month.month()] + ' 3');
|
checkStartsWith(extendedView, lastMonth, `${months[lastMonth.month()]} 3`);
|
||||||
checkStartsWith(ext_view, last_month, months[last_month.month()] + ' 3');
|
|
||||||
|
|
||||||
// subtract 26 hours to be safe in case of DST stuff
|
// subtract 26 hours to be safe in case of DST stuff
|
||||||
var yesterday = new Date(timestamp - 26 * 60 * 60 * 1000),
|
const yesterday = new Date(timestamp - 26 * 60 * 60 * 1000);
|
||||||
days_of_week = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
const daysOfWeek = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
||||||
check(brief_view, yesterday, days_of_week[yesterday.getDay()]);
|
check(briefView, yesterday, daysOfWeek[yesterday.getDay()]);
|
||||||
checkStartsWith(ext_view, yesterday, days_of_week[yesterday.getDay()]);
|
checkStartsWith(extendedView, yesterday, daysOfWeek[yesterday.getDay()]);
|
||||||
|
|
||||||
// Check something long ago
|
// Check something long ago
|
||||||
// months are zero-indexed in JS for some reason
|
// months are zero-indexed in JS for some reason
|
||||||
check(brief_view, new Date(2012, 4, 5, 17, 30, 0), 'May 5, 2012');
|
check(briefView, new Date(2012, 4, 5, 17, 30, 0), 'May 5, 2012');
|
||||||
checkStartsWith(ext_view, new Date(2012, 4, 5, 17, 30, 0), 'May 5, 2012');
|
checkStartsWith(
|
||||||
|
extendedView,
|
||||||
|
new Date(2012, 4, 5, 17, 30, 0),
|
||||||
|
'May 5, 2012'
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('updates within a minute reasonable intervals', function() {
|
describe('updates within a minute reasonable intervals', () => {
|
||||||
var view;
|
let view;
|
||||||
beforeEach(function() {
|
beforeEach(() => {
|
||||||
view = new Whisper.TimestampView();
|
view = new Whisper.TimestampView();
|
||||||
});
|
});
|
||||||
afterEach(function() {
|
afterEach(() => {
|
||||||
clearTimeout(view.timeout);
|
clearTimeout(view.timeout);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('updates timestamps this minute within a minute', function() {
|
it('updates timestamps this minute within a minute', () => {
|
||||||
var now = Date.now();
|
const now = Date.now();
|
||||||
view.$el.attr('data-timestamp', now - 1000);
|
view.$el.attr('data-timestamp', now - 1000);
|
||||||
view.update();
|
view.update();
|
||||||
assert.isAbove(view.delay, 0); // non zero
|
assert.isAbove(view.delay, 0); // non zero
|
||||||
assert.isBelow(view.delay, 60 * 1000); // < minute
|
assert.isBelow(view.delay, 60 * 1000); // < minute
|
||||||
});
|
});
|
||||||
|
|
||||||
it('updates timestamps from this hour within a minute', function() {
|
it('updates timestamps from this hour within a minute', () => {
|
||||||
var now = Date.now();
|
const now = Date.now();
|
||||||
view.$el.attr('data-timestamp', now - 1000 - 1000 * 60 * 5); // 5 minutes and 1 sec ago
|
view.$el.attr('data-timestamp', now - 1000 - 1000 * 60 * 5); // 5 minutes and 1 sec ago
|
||||||
view.update();
|
view.update();
|
||||||
assert.isAbove(view.delay, 0); // non zero
|
assert.isAbove(view.delay, 0); // non zero
|
||||||
assert.isBelow(view.delay, 60 * 1000); // minute
|
assert.isBelow(view.delay, 60 * 1000); // minute
|
||||||
});
|
});
|
||||||
|
|
||||||
it('updates timestamps from today within an hour', function() {
|
it('updates timestamps from today within an hour', () => {
|
||||||
var now = Date.now();
|
const now = Date.now();
|
||||||
view.$el.attr('data-timestamp', now - 1000 - 1000 * 60 * 60 * 5); // 5 hours and 1 sec ago
|
view.$el.attr('data-timestamp', now - 1000 - 1000 * 60 * 60 * 5); // 5 hours and 1 sec ago
|
||||||
view.update();
|
view.update();
|
||||||
assert.isAbove(view.delay, 60 * 1000); // minute
|
assert.isAbove(view.delay, 60 * 1000); // minute
|
||||||
assert.isBelow(view.delay, 60 * 60 * 1000); // hour
|
assert.isBelow(view.delay, 60 * 60 * 1000); // hour
|
||||||
});
|
});
|
||||||
|
|
||||||
it('updates timestamps from this week within a day', function() {
|
it('updates timestamps from this week within a day', () => {
|
||||||
var now = Date.now();
|
const now = Date.now();
|
||||||
view.$el.attr('data-timestamp', now - 1000 - 6 * 24 * 60 * 60 * 1000); // 6 days and 1 sec ago
|
view.$el.attr('data-timestamp', now - 1000 - 6 * 24 * 60 * 60 * 1000); // 6 days and 1 sec ago
|
||||||
view.update();
|
view.update();
|
||||||
assert.isAbove(view.delay, 60 * 60 * 1000); // hour
|
assert.isAbove(view.delay, 60 * 60 * 1000); // hour
|
||||||
assert.isBelow(view.delay, 36 * 60 * 60 * 1000); // day and a half
|
assert.isBelow(view.delay, 36 * 60 * 60 * 1000); // day and a half
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not updates very old timestamps', function() {
|
it('does not updates very old timestamps', () => {
|
||||||
var now = Date.now();
|
const now = Date.now();
|
||||||
// return falsey value for long ago dates that don't update
|
// return falsey value for long ago dates that don't update
|
||||||
view.$el.attr('data-timestamp', now - 8 * 24 * 60 * 60 * 1000);
|
view.$el.attr('data-timestamp', now - 8 * 24 * 60 * 60 * 1000);
|
||||||
view.update();
|
view.update();
|
||||||
|
|
|
@ -1,35 +1,37 @@
|
||||||
describe('Whisper.View', function() {
|
/* global Whisper */
|
||||||
it('renders a template with render_attributes', function() {
|
|
||||||
var viewClass = Whisper.View.extend({
|
describe('Whisper.View', () => {
|
||||||
|
it('renders a template with render_attributes', () => {
|
||||||
|
const ViewClass = Whisper.View.extend({
|
||||||
template: '<div>{{ variable }}</div>',
|
template: '<div>{{ variable }}</div>',
|
||||||
render_attributes: {
|
render_attributes: {
|
||||||
variable: 'value',
|
variable: 'value',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
var view = new viewClass();
|
const view = new ViewClass();
|
||||||
view.render();
|
view.render();
|
||||||
assert.strictEqual(view.$el.html(), '<div>value</div>');
|
assert.strictEqual(view.$el.html(), '<div>value</div>');
|
||||||
});
|
});
|
||||||
it('renders a template with no render_attributes', function() {
|
it('renders a template with no render_attributes', () => {
|
||||||
var viewClass = Whisper.View.extend({
|
const ViewClass = Whisper.View.extend({
|
||||||
template: '<div>static text</div>',
|
template: '<div>static text</div>',
|
||||||
});
|
});
|
||||||
|
|
||||||
var view = new viewClass();
|
const view = new ViewClass();
|
||||||
view.render();
|
view.render();
|
||||||
assert.strictEqual(view.$el.html(), '<div>static text</div>');
|
assert.strictEqual(view.$el.html(), '<div>static text</div>');
|
||||||
});
|
});
|
||||||
it('renders a template function with render_attributes function', function() {
|
it('renders a template function with render_attributes function', () => {
|
||||||
var viewClass = Whisper.View.extend({
|
const ViewClass = Whisper.View.extend({
|
||||||
template: function() {
|
template() {
|
||||||
return '<div>{{ variable }}</div>';
|
return '<div>{{ variable }}</div>';
|
||||||
},
|
},
|
||||||
render_attributes: function() {
|
render_attributes() {
|
||||||
return { variable: 'value' };
|
return { variable: 'value' };
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
var view = new viewClass();
|
const view = new ViewClass();
|
||||||
view.render();
|
view.render();
|
||||||
assert.strictEqual(view.$el.html(), '<div>value</div>');
|
assert.strictEqual(view.$el.html(), '<div>value</div>');
|
||||||
});
|
});
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue