Merge branch 'clearnet' into refactor-polling
commit
6fcc1f7ae4
@ -0,0 +1,63 @@
|
||||
import chai from 'chai';
|
||||
import { TestUtils } from '../../test-utils/';
|
||||
import { MessageUtils } from '../../../session/utils/';
|
||||
import { PubKey } from '../../../session/types/';
|
||||
|
||||
// tslint:disable-next-line: no-require-imports no-var-requires
|
||||
const chaiAsPromised = require('chai-as-promised');
|
||||
chai.use(chaiAsPromised);
|
||||
|
||||
const { expect } = chai;
|
||||
|
||||
describe('Message Utils', () => {
|
||||
describe('toRawMessage', () => {
|
||||
it('can convert to raw message', async () => {
|
||||
const device = TestUtils.generateFakePubKey();
|
||||
const message = TestUtils.generateChatMessage();
|
||||
|
||||
const rawMessage = MessageUtils.toRawMessage(device, message);
|
||||
|
||||
expect(Object.keys(rawMessage)).to.have.length(6);
|
||||
expect(rawMessage.identifier).to.exist;
|
||||
expect(rawMessage.device).to.exist;
|
||||
expect(rawMessage.encryption).to.exist;
|
||||
expect(rawMessage.plainTextBuffer).to.exist;
|
||||
expect(rawMessage.timestamp).to.exist;
|
||||
expect(rawMessage.ttl).to.exist;
|
||||
});
|
||||
|
||||
it('should generate valid plainTextBuffer', async () => {
|
||||
const device = TestUtils.generateFakePubKey();
|
||||
const message = TestUtils.generateChatMessage();
|
||||
|
||||
const rawMessage = MessageUtils.toRawMessage(device, message);
|
||||
|
||||
const rawBuffer = rawMessage.plainTextBuffer;
|
||||
const rawBufferJSON = JSON.stringify(rawBuffer);
|
||||
const messageBufferJSON = JSON.stringify(message.plainTextBuffer());
|
||||
|
||||
expect(rawBuffer instanceof Uint8Array).to.equal(
|
||||
true,
|
||||
'raw message did not contain a plainTextBuffer'
|
||||
);
|
||||
expect(rawBufferJSON).to.equal(
|
||||
messageBufferJSON,
|
||||
'plainTextBuffer was not converted correctly'
|
||||
);
|
||||
});
|
||||
|
||||
it('should maintain pubkey', async () => {
|
||||
const device = TestUtils.generateFakePubKey();
|
||||
const message = TestUtils.generateChatMessage();
|
||||
|
||||
const rawMessage = MessageUtils.toRawMessage(device, message);
|
||||
const derivedPubKey = PubKey.from(rawMessage.device);
|
||||
|
||||
expect(derivedPubKey).to.exist;
|
||||
expect(derivedPubKey?.isEqual(device)).to.equal(
|
||||
true,
|
||||
'pubkey of message was not converted correctly'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
@ -0,0 +1,141 @@
|
||||
import chai from 'chai';
|
||||
import * as sinon from 'sinon';
|
||||
|
||||
import { PromiseUtils } from '../../../session/utils/';
|
||||
|
||||
// tslint:disable-next-line: no-require-imports no-var-requires
|
||||
const chaiAsPromised = require('chai-as-promised');
|
||||
chai.use(chaiAsPromised);
|
||||
|
||||
const { expect } = chai;
|
||||
|
||||
describe('Promise Utils', () => {
|
||||
const sandbox = sinon.createSandbox();
|
||||
let pollSpy: sinon.SinonSpy<
|
||||
[
|
||||
(done: (arg: any) => void) => Promise<void> | void,
|
||||
(Partial<PromiseUtils.PollOptions> | undefined)?
|
||||
],
|
||||
Promise<void>
|
||||
>;
|
||||
let waitForTaskSpy: sinon.SinonSpy<
|
||||
[(done: (arg: any) => void) => Promise<void> | void, (number | undefined)?],
|
||||
Promise<unknown>
|
||||
>;
|
||||
let waitUntilSpy: sinon.SinonSpy<
|
||||
[() => Promise<boolean> | boolean, (number | undefined)?],
|
||||
Promise<void>
|
||||
>;
|
||||
|
||||
beforeEach(() => {
|
||||
pollSpy = sandbox.spy(PromiseUtils, 'poll');
|
||||
waitForTaskSpy = sandbox.spy(PromiseUtils, 'waitForTask');
|
||||
waitUntilSpy = sandbox.spy(PromiseUtils, 'waitUntil');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
describe('poll', () => {
|
||||
it('will call done on finished', async () => {
|
||||
// completionSpy will be called on done
|
||||
const completionSpy = sandbox.spy();
|
||||
|
||||
// tslint:disable-next-line: mocha-unneeded-done
|
||||
const task = (done: any) => {
|
||||
completionSpy();
|
||||
done();
|
||||
};
|
||||
|
||||
const promise = PromiseUtils.poll(task, {});
|
||||
|
||||
await expect(promise).to.be.fulfilled;
|
||||
expect(pollSpy.callCount).to.equal(1);
|
||||
expect(completionSpy.callCount).to.equal(1);
|
||||
});
|
||||
|
||||
it('can timeout a task', async () => {
|
||||
// completionSpy will be called on done
|
||||
const completionSpy = sandbox.spy();
|
||||
const task = (_done: any) => undefined;
|
||||
|
||||
const promise = PromiseUtils.poll(task, { timeout: 1 });
|
||||
|
||||
await expect(promise).to.be.rejectedWith('Periodic check timeout');
|
||||
expect(pollSpy.callCount).to.equal(1);
|
||||
expect(completionSpy.callCount).to.equal(0);
|
||||
});
|
||||
|
||||
it('will recur according to interval option', async () => {
|
||||
const expectedRecurrences = 4;
|
||||
const timeout = 1000;
|
||||
const interval = timeout / expectedRecurrences;
|
||||
|
||||
const recurrenceSpy = sandbox.spy();
|
||||
const task = (done: any) => {
|
||||
recurrenceSpy();
|
||||
|
||||
// Done after we've been called `expectedRecurrences` times
|
||||
if (recurrenceSpy.callCount === expectedRecurrences) {
|
||||
done();
|
||||
}
|
||||
};
|
||||
|
||||
const promise = PromiseUtils.poll(task, { timeout, interval });
|
||||
|
||||
await expect(promise).to.be.fulfilled;
|
||||
expect(pollSpy.callCount).to.equal(1);
|
||||
expect(recurrenceSpy.callCount).to.equal(expectedRecurrences);
|
||||
});
|
||||
});
|
||||
|
||||
describe('waitForTask', () => {
|
||||
it('can wait for a task', async () => {
|
||||
// completionSpy will be called on done
|
||||
const completionSpy = sandbox.spy();
|
||||
|
||||
// tslint:disable-next-line: mocha-unneeded-done
|
||||
const task = (done: any) => {
|
||||
completionSpy();
|
||||
done();
|
||||
};
|
||||
|
||||
const promise = PromiseUtils.waitForTask(task);
|
||||
|
||||
await expect(promise).to.be.fulfilled;
|
||||
expect(waitForTaskSpy.callCount).to.equal(1);
|
||||
expect(completionSpy.callCount).to.equal(1);
|
||||
});
|
||||
|
||||
it('can timeout a task', async () => {
|
||||
// completionSpy will be called on done
|
||||
const completionSpy = sandbox.spy();
|
||||
const task = async (_done: any) => undefined;
|
||||
|
||||
const promise = PromiseUtils.waitForTask(task, 1);
|
||||
|
||||
await expect(promise).to.be.rejectedWith('Task timed out.');
|
||||
expect(waitForTaskSpy.callCount).to.equal(1);
|
||||
expect(completionSpy.callCount).to.equal(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('waitUntil', () => {
|
||||
it('can wait for check', async () => {
|
||||
const check = () => true;
|
||||
const promise = PromiseUtils.waitUntil(check);
|
||||
|
||||
await expect(promise).to.be.fulfilled;
|
||||
expect(waitUntilSpy.callCount).to.equal(1);
|
||||
});
|
||||
|
||||
it('can timeout a check', async () => {
|
||||
const check = () => false;
|
||||
const promise = PromiseUtils.waitUntil(check, 1);
|
||||
|
||||
await expect(promise).to.be.rejectedWith('Periodic check timeout');
|
||||
expect(waitUntilSpy.callCount).to.equal(1);
|
||||
});
|
||||
});
|
||||
});
|
@ -0,0 +1,227 @@
|
||||
import chai from 'chai';
|
||||
import ByteBuffer from 'bytebuffer';
|
||||
|
||||
// Can't import type as StringUtils.Encoding
|
||||
import { Encoding } from '../../../session/utils/String';
|
||||
import { StringUtils } from '../../../session/utils/';
|
||||
|
||||
// tslint:disable-next-line: no-require-imports no-var-requires
|
||||
const chaiAsPromised = require('chai-as-promised');
|
||||
chai.use(chaiAsPromised);
|
||||
|
||||
const { expect } = chai;
|
||||
|
||||
describe('String Utils', () => {
|
||||
describe('encode', () => {
|
||||
it('can encode to base64', async () => {
|
||||
const testString = 'AAAAAAAAAA';
|
||||
const encoded = StringUtils.encode(testString, 'base64');
|
||||
|
||||
expect(encoded instanceof ArrayBuffer).to.equal(
|
||||
true,
|
||||
'a buffer was not returned from `encode`'
|
||||
);
|
||||
expect(encoded.byteLength).to.be.greaterThan(0);
|
||||
});
|
||||
|
||||
it('can encode to hex', async () => {
|
||||
const testString = 'AAAAAAAAAA';
|
||||
const encoded = StringUtils.encode(testString, 'hex');
|
||||
|
||||
expect(encoded instanceof ArrayBuffer).to.equal(
|
||||
true,
|
||||
'a buffer was not returned from `encode`'
|
||||
);
|
||||
expect(encoded.byteLength).to.be.greaterThan(0);
|
||||
});
|
||||
|
||||
it('wont encode invalid hex', async () => {
|
||||
const testString = 'ZZZZZZZZZZ';
|
||||
const encoded = StringUtils.encode(testString, 'hex');
|
||||
|
||||
expect(encoded.byteLength).to.equal(0);
|
||||
});
|
||||
|
||||
it('can encode to binary', async () => {
|
||||
const testString = 'AAAAAAAAAA';
|
||||
const encoded = StringUtils.encode(testString, 'binary');
|
||||
|
||||
expect(encoded instanceof ArrayBuffer).to.equal(
|
||||
true,
|
||||
'a buffer was not returned from `encode`'
|
||||
);
|
||||
expect(encoded.byteLength).to.be.greaterThan(0);
|
||||
});
|
||||
|
||||
it('can encode to utf8', async () => {
|
||||
const testString = 'AAAAAAAAAA';
|
||||
const encoded = StringUtils.encode(testString, 'binary');
|
||||
|
||||
expect(encoded instanceof ArrayBuffer).to.equal(
|
||||
true,
|
||||
'a buffer was not returned from `encode`'
|
||||
);
|
||||
expect(encoded.byteLength).to.be.greaterThan(0);
|
||||
});
|
||||
|
||||
it('can encode empty string', async () => {
|
||||
const testString = '';
|
||||
expect(testString).to.have.length(0);
|
||||
|
||||
const allEncodedings = (['base64', 'hex', 'binary', 'utf8'] as Array<
|
||||
Encoding
|
||||
>).map(e => StringUtils.encode(testString, e));
|
||||
|
||||
allEncodedings.forEach(encoded => {
|
||||
expect(encoded instanceof ArrayBuffer).to.equal(
|
||||
true,
|
||||
'a buffer was not returned from `encode`'
|
||||
);
|
||||
expect(encoded.byteLength).to.equal(0);
|
||||
});
|
||||
});
|
||||
|
||||
it('can encode huge string', async () => {
|
||||
const stringSize = Math.pow(2, 16);
|
||||
const testString = Array(stringSize)
|
||||
.fill('0')
|
||||
.join('');
|
||||
|
||||
const allEncodedings = (['base64', 'hex', 'binary', 'utf8'] as Array<
|
||||
Encoding
|
||||
>).map(e => StringUtils.encode(testString, e));
|
||||
|
||||
allEncodedings.forEach(encoded => {
|
||||
expect(encoded instanceof ArrayBuffer).to.equal(
|
||||
true,
|
||||
'a buffer was not returned from `encode`'
|
||||
);
|
||||
expect(encoded.byteLength).to.be.greaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
it("won't encode illegal string length in hex", async () => {
|
||||
const testString = 'A';
|
||||
const encode = () => StringUtils.encode(testString, 'hex');
|
||||
|
||||
// Ensure string is odd length
|
||||
expect(testString.length % 2).to.equal(1);
|
||||
expect(encode).to.throw('Illegal str: Length not a multiple of 2');
|
||||
});
|
||||
|
||||
it('can encode obscure string', async () => {
|
||||
const testString =
|
||||
'↓←¶ᶑᵶ⅑⏕→⅓ᵹ⅙ᵰᶎ⅔⅗↔ᶈ⅞⸜ᶊᵴᶉ↉¥ᶖᶋᶃᶓ⏦ᵾᶂᶆ↕⸝ᶔᶐ⏔£⏙⅐⅒ᶌ⁁ᶘᶄᶒᶸ⅘⅚⅛ᶙᶇᶕᶀ↑ᵿ⏠ᶍᵯ⏖⏗⅜ᶚᶏ⁊ᶁᶗᵽᵼ⅝⏘⅖⅕⏡';
|
||||
|
||||
// Not valid hex format; try test the others
|
||||
const encodings = ['base64', 'binary', 'utf8'] as Array<Encoding>;
|
||||
|
||||
encodings.forEach(encoding => {
|
||||
const encoded = StringUtils.encode(testString, encoding);
|
||||
expect(encoded instanceof ArrayBuffer).to.equal(
|
||||
true,
|
||||
`a buffer was not returned using encoding: '${encoding}'`
|
||||
);
|
||||
expect(encoded.byteLength).to.be.greaterThan(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('decode', () => {
|
||||
it('can decode empty buffer', async () => {
|
||||
const buffer = new ByteBuffer(0);
|
||||
|
||||
const encodings = ['base64', 'hex', 'binary', 'utf8'] as Array<Encoding>;
|
||||
|
||||
// Each encoding should be valid
|
||||
encodings.forEach(encoding => {
|
||||
const decoded = StringUtils.decode(buffer, encoding);
|
||||
|
||||
expect(decoded).to.exist;
|
||||
expect(typeof decoded === String.name.toLowerCase());
|
||||
expect(decoded).to.have.length(0);
|
||||
});
|
||||
});
|
||||
|
||||
it('can decode huge buffer', async () => {
|
||||
const bytes = Math.pow(2, 16);
|
||||
const bufferString = Array(bytes)
|
||||
.fill('A')
|
||||
.join('');
|
||||
const buffer = ByteBuffer.fromUTF8(bufferString);
|
||||
|
||||
const encodings = ['base64', 'hex', 'binary', 'utf8'] as Array<Encoding>;
|
||||
|
||||
// Each encoding should be valid
|
||||
encodings.forEach(encoding => {
|
||||
const decoded = StringUtils.decode(buffer, encoding);
|
||||
|
||||
expect(decoded).to.exist;
|
||||
expect(typeof decoded === String.name.toLowerCase());
|
||||
expect(decoded).to.have.length.greaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
it('can decode from ByteBuffer', async () => {
|
||||
const buffer = ByteBuffer.fromUTF8('AAAAAAAAAA');
|
||||
|
||||
const encodings = ['base64', 'hex', 'binary', 'utf8'] as Array<Encoding>;
|
||||
|
||||
// Each encoding should be valid
|
||||
encodings.forEach(encoding => {
|
||||
const decoded = StringUtils.decode(buffer, encoding);
|
||||
|
||||
expect(decoded).to.exist;
|
||||
expect(typeof decoded === String.name.toLowerCase());
|
||||
expect(decoded).to.have.length.greaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
it('can decode from Buffer', async () => {
|
||||
const arrayBuffer = new ArrayBuffer(10);
|
||||
const buffer = Buffer.from(arrayBuffer);
|
||||
buffer.writeUInt8(0, 0);
|
||||
|
||||
const encodings = ['base64', 'hex', 'binary', 'utf8'] as Array<Encoding>;
|
||||
|
||||
// Each encoding should be valid
|
||||
encodings.forEach(encoding => {
|
||||
const decoded = StringUtils.decode(buffer, encoding);
|
||||
|
||||
expect(decoded).to.exist;
|
||||
expect(typeof decoded === String.name.toLowerCase());
|
||||
expect(decoded).to.have.length.greaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
it('can decode from ArrayBuffer', async () => {
|
||||
const buffer = new ArrayBuffer(10);
|
||||
|
||||
const encodings = ['base64', 'hex', 'binary', 'utf8'] as Array<Encoding>;
|
||||
|
||||
// Each encoding should be valid
|
||||
encodings.forEach(encoding => {
|
||||
const decoded = StringUtils.decode(buffer, encoding);
|
||||
|
||||
expect(decoded).to.exist;
|
||||
expect(typeof decoded === String.name.toLowerCase());
|
||||
expect(decoded).to.have.length.greaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
it('can decode from Uint8Array', async () => {
|
||||
const buffer = new Uint8Array(10);
|
||||
|
||||
const encodings = ['base64', 'hex', 'binary', 'utf8'] as Array<Encoding>;
|
||||
|
||||
// Each encoding should be valid
|
||||
encodings.forEach(encoding => {
|
||||
const decoded = StringUtils.decode(buffer, encoding);
|
||||
|
||||
expect(decoded).to.exist;
|
||||
expect(typeof decoded === String.name.toLowerCase());
|
||||
expect(decoded).to.have.length.greaterThan(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -0,0 +1,117 @@
|
||||
import chai from 'chai';
|
||||
import * as sinon from 'sinon';
|
||||
|
||||
import { SyncMessageUtils } from '../../../session/utils/';
|
||||
import { TestUtils } from '../../test-utils';
|
||||
import { UserUtil } from '../../../util';
|
||||
import { MultiDeviceProtocol } from '../../../session/protocols';
|
||||
import { SyncMessage } from '../../../session/messages/outgoing';
|
||||
|
||||
// tslint:disable-next-line: no-require-imports no-var-requires
|
||||
const chaiAsPromised = require('chai-as-promised');
|
||||
chai.use(chaiAsPromised);
|
||||
|
||||
const { expect } = chai;
|
||||
|
||||
describe('Sync Message Utils', () => {
|
||||
describe('getSyncContacts', () => {
|
||||
let getAllConversationsStub: sinon.SinonStub;
|
||||
let getOrCreateAndWaitStub: sinon.SinonStub;
|
||||
let getOrCreatAndWaitItem: any;
|
||||
|
||||
// Fill half with secondaries, half with primaries
|
||||
const numConversations = 20;
|
||||
const primaryConversations = new Array(numConversations / 2)
|
||||
.fill({})
|
||||
.map(
|
||||
() =>
|
||||
new TestUtils.MockConversation({
|
||||
type: TestUtils.MockConversationType.Primary,
|
||||
})
|
||||
);
|
||||
const secondaryConversations = new Array(numConversations / 2)
|
||||
.fill({})
|
||||
.map(
|
||||
() =>
|
||||
new TestUtils.MockConversation({
|
||||
type: TestUtils.MockConversationType.Secondary,
|
||||
})
|
||||
);
|
||||
const conversations = [...primaryConversations, ...secondaryConversations];
|
||||
|
||||
const sandbox = sinon.createSandbox();
|
||||
const ourDevice = TestUtils.generateFakePubKey();
|
||||
const ourNumber = ourDevice.key;
|
||||
|
||||
const ourPrimaryDevice = TestUtils.generateFakePubKey();
|
||||
|
||||
beforeEach(async () => {
|
||||
// Util Stubs
|
||||
TestUtils.stubWindow('Whisper', {
|
||||
ConversationCollection: sandbox.stub(),
|
||||
});
|
||||
|
||||
getAllConversationsStub = TestUtils.stubData(
|
||||
'getAllConversations'
|
||||
).resolves(conversations);
|
||||
|
||||
// Scale result in sync with secondaryConversations on callCount
|
||||
getOrCreateAndWaitStub = sandbox.stub().callsFake(() => {
|
||||
const item =
|
||||
secondaryConversations[getOrCreateAndWaitStub.callCount - 1];
|
||||
|
||||
// Make the item a primary device to match the call in SyncMessage under secondaryContactsPromise
|
||||
getOrCreatAndWaitItem = {
|
||||
...item,
|
||||
getPrimaryDevicePubKey: () => item.id,
|
||||
attributes: {
|
||||
secondaryStatus: false,
|
||||
},
|
||||
};
|
||||
|
||||
return getOrCreatAndWaitItem;
|
||||
});
|
||||
|
||||
TestUtils.stubWindow('ConversationController', {
|
||||
getOrCreateAndWait: getOrCreateAndWaitStub,
|
||||
});
|
||||
|
||||
// Stubs
|
||||
sandbox.stub(UserUtil, 'getCurrentDevicePubKey').resolves(ourNumber);
|
||||
sandbox
|
||||
.stub(MultiDeviceProtocol, 'getPrimaryDevice')
|
||||
.resolves(ourPrimaryDevice);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
sandbox.restore();
|
||||
TestUtils.restoreStubs();
|
||||
});
|
||||
|
||||
it('can get sync contacts with only primary contacts', async () => {
|
||||
getAllConversationsStub.resolves(primaryConversations);
|
||||
|
||||
const contacts = await SyncMessageUtils.getSyncContacts();
|
||||
expect(getAllConversationsStub.callCount).to.equal(1);
|
||||
|
||||
// Each contact should be a primary device
|
||||
expect(contacts).to.have.length(numConversations / 2);
|
||||
expect(contacts?.find(c => c.attributes.secondaryStatus)).to.not.exist;
|
||||
});
|
||||
|
||||
it('can get sync contacts of assorted primaries and secondaries', async () => {
|
||||
// Map secondary contacts to stub resolution
|
||||
const contacts = await SyncMessageUtils.getSyncContacts();
|
||||
expect(getAllConversationsStub.callCount).to.equal(1);
|
||||
|
||||
// We should have numConversations unique contacts
|
||||
expect(contacts).to.have.length(numConversations);
|
||||
|
||||
// All contacts should be primary; half of which some from secondaries in secondaryContactsPromise
|
||||
expect(contacts?.find(c => c.attributes.secondaryStatus)).to.not.exist;
|
||||
expect(contacts?.filter(c => c.isPrimary)).to.have.length(
|
||||
numConversations / 2
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue