move taskWithTimeout to typescript
parent
34835ef68f
commit
09d9db38e8
@ -1,23 +0,0 @@
|
|||||||
{
|
|
||||||
"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,
|
|
||||||
"PROTO_ROOT": true,
|
|
||||||
"stringToArrayBuffer": true,
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,61 +0,0 @@
|
|||||||
/* global mocha, chai, assert */
|
|
||||||
|
|
||||||
mocha
|
|
||||||
.setup('bdd')
|
|
||||||
.fullTrace()
|
|
||||||
.timeout(10000);
|
|
||||||
window.assert = chai.assert;
|
|
||||||
window.PROTO_ROOT = '../../protos';
|
|
||||||
|
|
||||||
const OriginalReporter = mocha._reporter;
|
|
||||||
|
|
||||||
const SauceReporter = function Constructor(runner) {
|
|
||||||
const failedTests = [];
|
|
||||||
|
|
||||||
runner.on('end', () => {
|
|
||||||
window.mochaResults = runner.stats;
|
|
||||||
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),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-new
|
|
||||||
new OriginalReporter(runner);
|
|
||||||
};
|
|
||||||
|
|
||||||
SauceReporter.prototype = OriginalReporter.prototype;
|
|
||||||
|
|
||||||
mocha.reporter(SauceReporter);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* global helpers for tests
|
|
||||||
*/
|
|
||||||
window.assertEqualArrayBuffers = (ab1, ab2) => {
|
|
||||||
assert.deepEqual(new Uint8Array(ab1), new Uint8Array(ab2));
|
|
||||||
};
|
|
||||||
|
|
||||||
window.hexToArrayBuffer = str => {
|
|
||||||
const ret = new ArrayBuffer(str.length / 2);
|
|
||||||
const array = new Uint8Array(ret);
|
|
||||||
for (let i = 0; i < str.length / 2; i += 1) {
|
|
||||||
array[i] = parseInt(str.substr(i * 2, 2), 16);
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
};
|
|
File diff suppressed because one or more lines are too long
@ -1,56 +0,0 @@
|
|||||||
/* global libsignal, textsecure */
|
|
||||||
|
|
||||||
describe('encrypting and decrypting profile data', () => {
|
|
||||||
const NAME_PADDED_LENGTH = 26;
|
|
||||||
describe('encrypting and decrypting profile names', () => {
|
|
||||||
it('pads, encrypts, decrypts, and unpads a short string', () => {
|
|
||||||
const name = 'Alice';
|
|
||||||
const buffer = dcodeIO.ByteBuffer.wrap(name).toArrayBuffer();
|
|
||||||
const key = libsignal.crypto.getRandomBytes(32);
|
|
||||||
|
|
||||||
return textsecure.crypto.encryptProfileName(buffer, key).then(encrypted => {
|
|
||||||
assert(encrypted.byteLength === NAME_PADDED_LENGTH + 16 + 12);
|
|
||||||
return textsecure.crypto.decryptProfileName(encrypted, key).then(decrypted => {
|
|
||||||
assert.strictEqual(dcodeIO.ByteBuffer.wrap(decrypted).toString('utf8'), 'Alice');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it('works for empty string', () => {
|
|
||||||
const name = dcodeIO.ByteBuffer.wrap('').toArrayBuffer();
|
|
||||||
const key = libsignal.crypto.getRandomBytes(32);
|
|
||||||
|
|
||||||
return textsecure.crypto.encryptProfileName(name.buffer, key).then(encrypted => {
|
|
||||||
assert(encrypted.byteLength === NAME_PADDED_LENGTH + 16 + 12);
|
|
||||||
return textsecure.crypto.decryptProfileName(encrypted, key).then(decrypted => {
|
|
||||||
assert.strictEqual(decrypted.byteLength, 0);
|
|
||||||
assert.strictEqual(dcodeIO.ByteBuffer.wrap(decrypted).toString('utf8'), '');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
describe('encrypting and decrypting profile avatars', () => {
|
|
||||||
it('encrypts and decrypts', () => {
|
|
||||||
const buffer = dcodeIO.ByteBuffer.wrap('This is an avatar').toArrayBuffer();
|
|
||||||
const key = libsignal.crypto.getRandomBytes(32);
|
|
||||||
|
|
||||||
return textsecure.crypto.encryptProfile(buffer, key).then(encrypted => {
|
|
||||||
assert(encrypted.byteLength === buffer.byteLength + 16 + 12);
|
|
||||||
return textsecure.crypto.decryptProfile(encrypted, key).then(decrypted => {
|
|
||||||
assertEqualArrayBuffers(buffer, decrypted);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it('throws when decrypting with the wrong key', () => {
|
|
||||||
const buffer = dcodeIO.ByteBuffer.wrap('This is an avatar').toArrayBuffer();
|
|
||||||
const key = libsignal.crypto.getRandomBytes(32);
|
|
||||||
const badKey = libsignal.crypto.getRandomBytes(32);
|
|
||||||
|
|
||||||
return textsecure.crypto.encryptProfile(buffer, key).then(encrypted => {
|
|
||||||
assert(encrypted.byteLength === buffer.byteLength + 16 + 12);
|
|
||||||
return textsecure.crypto.decryptProfile(encrypted, badKey).catch(error => {
|
|
||||||
assert.strictEqual(error.name, 'ProfileDecryptError');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,45 +0,0 @@
|
|||||||
<html>
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset='utf-8'>
|
|
||||||
<title>libtextsecure test runner</title>
|
|
||||||
<link rel="stylesheet" href="../../node_modules/mocha/mocha.css" />
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<div id="mocha">
|
|
||||||
</div>
|
|
||||||
<div id="tests">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<script type="text/javascript" src="test.js"></script>
|
|
||||||
|
|
||||||
<script type="text/javascript" src="../components.js"></script>
|
|
||||||
<script type="text/javascript" src="../libsignal-protocol.js"></script>
|
|
||||||
<script type="text/javascript" src="../crypto.js"></script>
|
|
||||||
<script type="text/javascript" src="../protobufs.js" data-cover></script>
|
|
||||||
<script type="text/javascript" src="../errors.js" data-cover></script>
|
|
||||||
<script type="text/javascript" src="../storage.js" data-cover></script>
|
|
||||||
|
|
||||||
<script type="text/javascript" src="../event_target.js" data-cover></script>
|
|
||||||
<script type="text/javascript" src="../helpers.js" data-cover></script>
|
|
||||||
<script type="text/javascript" src="../stringview.js" data-cover></script>
|
|
||||||
<script type="text/javascript" src="../task_with_timeout.js" data-cover></script>
|
|
||||||
|
|
||||||
<script type="text/javascript" src="errors_test.js"></script>
|
|
||||||
<script type="text/javascript" src="helpers_test.js"></script>
|
|
||||||
<script type="text/javascript" src="task_with_timeout_test.js"></script>
|
|
||||||
|
|
||||||
<!-- Comment out to turn off code coverage. Useful for getting real callstacks. -->
|
|
||||||
<!-- NOTE: blanket doesn't support modern syntax and will choke until we find a replacement. :0( -->
|
|
||||||
<!-- <script type="text/javascript" src="blanket_mocha.js"></script> -->
|
|
||||||
|
|
||||||
<!-- Uncomment to start tests without code coverage enabled -->
|
|
||||||
<script type="text/javascript">
|
|
||||||
mocha.run();
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
@ -1,72 +0,0 @@
|
|||||||
/* global textsecure */
|
|
||||||
|
|
||||||
describe('createTaskWithTimeout', () => {
|
|
||||||
it('resolves when promise resolves', () => {
|
|
||||||
const task = () => Promise.resolve('hi!');
|
|
||||||
const taskWithTimeout = textsecure.createTaskWithTimeout(task);
|
|
||||||
|
|
||||||
return taskWithTimeout().then(result => {
|
|
||||||
assert.strictEqual(result, 'hi!');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it('flows error from promise back', () => {
|
|
||||||
const error = new Error('original');
|
|
||||||
const task = () => Promise.reject(error);
|
|
||||||
const taskWithTimeout = textsecure.createTaskWithTimeout(task);
|
|
||||||
|
|
||||||
return taskWithTimeout().catch(flowedError => {
|
|
||||||
assert.strictEqual(error, flowedError);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it('rejects if promise takes too long (this one logs error to console)', () => {
|
|
||||||
let complete = false;
|
|
||||||
const task = () =>
|
|
||||||
new Promise(resolve => {
|
|
||||||
setTimeout(() => {
|
|
||||||
complete = true;
|
|
||||||
resolve();
|
|
||||||
}, 3000);
|
|
||||||
});
|
|
||||||
const taskWithTimeout = textsecure.createTaskWithTimeout(task, this.name, {
|
|
||||||
timeout: 10,
|
|
||||||
});
|
|
||||||
|
|
||||||
return taskWithTimeout().then(
|
|
||||||
() => {
|
|
||||||
throw new Error('it was not supposed to resolve!');
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
assert.strictEqual(complete, false);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
it('resolves if task returns something falsey', () => {
|
|
||||||
const task = () => {};
|
|
||||||
const taskWithTimeout = textsecure.createTaskWithTimeout(task);
|
|
||||||
return taskWithTimeout();
|
|
||||||
});
|
|
||||||
it('resolves if task returns a non-promise', () => {
|
|
||||||
const task = () => 'hi!';
|
|
||||||
const taskWithTimeout = textsecure.createTaskWithTimeout(task);
|
|
||||||
return taskWithTimeout().then(result => {
|
|
||||||
assert.strictEqual(result, 'hi!');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it('rejects if task throws (and does not log about taking too long)', () => {
|
|
||||||
const error = new Error('Task is throwing!');
|
|
||||||
const task = () => {
|
|
||||||
throw error;
|
|
||||||
};
|
|
||||||
const taskWithTimeout = textsecure.createTaskWithTimeout(task, this.name, {
|
|
||||||
timeout: 10,
|
|
||||||
});
|
|
||||||
return taskWithTimeout().then(
|
|
||||||
() => {
|
|
||||||
throw new Error('Overall task should reject!');
|
|
||||||
},
|
|
||||||
flowedError => {
|
|
||||||
assert.strictEqual(flowedError, error);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
@ -0,0 +1,66 @@
|
|||||||
|
export const createTaskWithTimeout = (task: any, id: string, givenTimeout?: number) => {
|
||||||
|
const timeout = givenTimeout || 1000 * 60 * 3; // three minutes
|
||||||
|
|
||||||
|
const errorForStack = new Error('for stack');
|
||||||
|
return async () =>
|
||||||
|
new Promise((resolve, reject) => {
|
||||||
|
let complete = false;
|
||||||
|
let timer: NodeJS.Timeout | null = global.setTimeout(() => {
|
||||||
|
if (!complete) {
|
||||||
|
const message = `${id || ''} task did not complete in time. Calling stack: ${
|
||||||
|
errorForStack.stack
|
||||||
|
}`;
|
||||||
|
|
||||||
|
window?.log?.error(message);
|
||||||
|
reject(new Error(message));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}, timeout);
|
||||||
|
const clearTimer = () => {
|
||||||
|
try {
|
||||||
|
const localTimer = timer;
|
||||||
|
if (localTimer) {
|
||||||
|
timer = null;
|
||||||
|
clearTimeout(localTimer);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
window?.log?.error(
|
||||||
|
id || '',
|
||||||
|
'task ran into problem canceling timer. Calling stack:',
|
||||||
|
errorForStack.stack
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const success = (result: any) => {
|
||||||
|
clearTimer();
|
||||||
|
complete = true;
|
||||||
|
resolve(result);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
const failure = (error: any) => {
|
||||||
|
clearTimer();
|
||||||
|
complete = true;
|
||||||
|
reject(error);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let promise;
|
||||||
|
try {
|
||||||
|
promise = task();
|
||||||
|
} catch (error) {
|
||||||
|
clearTimer();
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
if (!promise || !promise.then) {
|
||||||
|
clearTimer();
|
||||||
|
complete = true;
|
||||||
|
resolve(promise);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return promise.then(success, failure);
|
||||||
|
});
|
||||||
|
};
|
@ -0,0 +1,81 @@
|
|||||||
|
// tslint:disable: no-implicit-dependencies max-func-body-length no-unused-expression
|
||||||
|
|
||||||
|
import chai from 'chai';
|
||||||
|
|
||||||
|
// tslint:disable-next-line: no-require-imports no-var-requires
|
||||||
|
import chaiAsPromised from 'chai-as-promised';
|
||||||
|
import { createTaskWithTimeout } from '../../../../session/utils/TaskWithTimeout';
|
||||||
|
chai.use(chaiAsPromised as any);
|
||||||
|
chai.should();
|
||||||
|
|
||||||
|
const { assert } = chai;
|
||||||
|
|
||||||
|
const taskName = 'whatever';
|
||||||
|
|
||||||
|
describe('createTaskWithTimeout', () => {
|
||||||
|
it('resolves when promise resolves', async () => {
|
||||||
|
const task = () => Promise.resolve('hi!');
|
||||||
|
const taskWithTimeout = createTaskWithTimeout(task, 'task_123');
|
||||||
|
|
||||||
|
await taskWithTimeout().then((result: any) => {
|
||||||
|
assert.strictEqual(result, 'hi!');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('flows error from promise back', async () => {
|
||||||
|
const error = new Error('original');
|
||||||
|
const task = () => Promise.reject(error);
|
||||||
|
const taskWithTimeout = createTaskWithTimeout(task, 'task_123');
|
||||||
|
|
||||||
|
await taskWithTimeout().catch((flowedError: any) => {
|
||||||
|
assert.strictEqual(error, flowedError);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('rejects if promise takes too long (this one logs error to console)', async () => {
|
||||||
|
let complete = false;
|
||||||
|
const task = async () =>
|
||||||
|
new Promise(resolve => {
|
||||||
|
setTimeout(() => {
|
||||||
|
complete = true;
|
||||||
|
resolve(null);
|
||||||
|
}, 3000);
|
||||||
|
});
|
||||||
|
const taskWithTimeout = createTaskWithTimeout(task, taskName, 10);
|
||||||
|
|
||||||
|
await taskWithTimeout().then(
|
||||||
|
() => {
|
||||||
|
throw new Error('it was not supposed to resolve!');
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
assert.strictEqual(complete, false);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('resolves if task returns something falsey', async () => {
|
||||||
|
// tslint:disable-next-line: no-empty
|
||||||
|
const task = () => {};
|
||||||
|
const taskWithTimeout = createTaskWithTimeout(task, taskName);
|
||||||
|
await taskWithTimeout();
|
||||||
|
});
|
||||||
|
it('resolves if task returns a non-promise', async () => {
|
||||||
|
const task = () => 'hi!';
|
||||||
|
const taskWithTimeout = createTaskWithTimeout(task, taskName);
|
||||||
|
await taskWithTimeout().then((result: any) => {
|
||||||
|
assert.strictEqual(result, 'hi!');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('rejects if task throws (and does not log about taking too long)', async () => {
|
||||||
|
const error = new Error('Task is throwing!');
|
||||||
|
const task = () => {
|
||||||
|
throw error;
|
||||||
|
};
|
||||||
|
const taskWithTimeout = createTaskWithTimeout(task, taskName, 10);
|
||||||
|
await taskWithTimeout().then(
|
||||||
|
() => {
|
||||||
|
throw new Error('Overall task should reject!');
|
||||||
|
},
|
||||||
|
(flowedError: any) => {
|
||||||
|
assert.strictEqual(flowedError, error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue