Add retrying and tests

pull/1170/head
Mikunj 5 years ago
parent f710606909
commit 090f0e2c38

@ -6,6 +6,7 @@ import { SignalService } from '../../protobuf';
import { UserUtil } from '../../util'; import { UserUtil } from '../../util';
import { MessageEncrypter } from '../crypto'; import { MessageEncrypter } from '../crypto';
import { lokiMessageAPI, lokiPublicChatAPI } from '../../window'; import { lokiMessageAPI, lokiPublicChatAPI } from '../../window';
import pRetry from 'p-retry';
// ================ Regular ================ // ================ Regular ================
@ -14,13 +15,10 @@ export function canSendToSnode(): boolean {
return Boolean(lokiMessageAPI); return Boolean(lokiMessageAPI);
} }
export async function send({ export async function send(
device, { device, plainTextBuffer, encryption, timestamp, ttl }: RawMessage,
plainTextBuffer, retries: number = 3
encryption, ): Promise<void> {
timestamp,
ttl,
}: RawMessage): Promise<void> {
if (!canSendToSnode()) { if (!canSendToSnode()) {
throw new Error('lokiMessageAPI is not initialized.'); throw new Error('lokiMessageAPI is not initialized.');
} }
@ -33,8 +31,14 @@ export async function send({
const envelope = await buildEnvelope(envelopeType, timestamp, cipherText); const envelope = await buildEnvelope(envelopeType, timestamp, cipherText);
const data = wrapEnvelope(envelope); const data = wrapEnvelope(envelope);
// TODO: Somehow differentiate between Retryable and Regular erros // pRetry doesn't count the first call as a retry
return lokiMessageAPI.sendMessage(device, data, timestamp, ttl); return pRetry(
async () => lokiMessageAPI.sendMessage(device, data, timestamp, ttl),
{
retries: retries - 1,
factor: 1,
}
);
} }
async function buildEnvelope( async function buildEnvelope(

@ -21,11 +21,25 @@ describe('MessageSender', () => {
TestUtils.restoreStubs(); TestUtils.restoreStubs();
}); });
describe('canSendToSnode', () => {
it('should return the correct value', () => {
const stub = TestUtils.stubWindow('lokiMessageAPI', undefined);
expect(MessageSender.canSendToSnode()).to.equal(
false,
'We cannot send if lokiMessageAPI is not set'
);
stub.set(sandbox.createStubInstance(LokiMessageAPI));
expect(MessageSender.canSendToSnode()).to.equal(
true,
'We can send if lokiMessageAPI is set'
);
});
});
describe('send', () => { describe('send', () => {
const ourNumber = 'ourNumber'; const ourNumber = 'ourNumber';
let lokiMessageAPIStub: sinon.SinonStubbedInstance<LokiMessageAPI>; let lokiMessageAPIStub: sinon.SinonStubbedInstance<LokiMessageAPI>;
let messageEncyrptReturnEnvelopeType = let encryptStub: sinon.SinonStub<[string, Uint8Array, EncryptionType]>;
SignalService.Envelope.Type.CIPHERTEXT;
beforeEach(() => { beforeEach(() => {
// We can do this because LokiMessageAPI has a module export in it // We can do this because LokiMessageAPI has a module export in it
@ -34,76 +48,87 @@ describe('MessageSender', () => {
}); });
TestUtils.stubWindow('lokiMessageAPI', lokiMessageAPIStub); TestUtils.stubWindow('lokiMessageAPI', lokiMessageAPIStub);
encryptStub = sandbox.stub(MessageEncrypter, 'encrypt').resolves({
envelopeType: SignalService.Envelope.Type.CIPHERTEXT,
cipherText: crypto.randomBytes(10),
});
sandbox.stub(UserUtil, 'getCurrentDevicePubKey').resolves(ourNumber); sandbox.stub(UserUtil, 'getCurrentDevicePubKey').resolves(ourNumber);
sandbox
.stub(MessageEncrypter, 'encrypt')
.callsFake(async (_device, plainTextBuffer, _type) => ({
envelopeType: messageEncyrptReturnEnvelopeType,
cipherText: plainTextBuffer,
}));
}); });
it('should pass the correct values to lokiMessageAPI', async () => { describe('retry', () => {
const device = '0'; const rawMessage = {
const timestamp = Date.now();
const ttl = 100;
await MessageSender.send({
identifier: '1', identifier: '1',
device, device: '0',
plainTextBuffer: crypto.randomBytes(10), plainTextBuffer: crypto.randomBytes(10),
encryption: EncryptionType.Signal, encryption: EncryptionType.Signal,
timestamp, timestamp: Date.now(),
ttl, ttl: 100,
};
it('should not retry if an error occurred during encryption', async () => {
encryptStub.throws(new Error('Failed to encrypt.'));
const promise = MessageSender.send(rawMessage);
await expect(promise).is.rejectedWith('Failed to encrypt.');
expect(lokiMessageAPIStub.sendMessage.callCount).to.equal(0);
}); });
const args = lokiMessageAPIStub.sendMessage.getCall(0).args; it('should only call lokiMessageAPI once if no errors occured', async () => {
expect(args[0]).to.equal(device); await MessageSender.send(rawMessage);
expect(args[2]).to.equal(timestamp); expect(lokiMessageAPIStub.sendMessage.callCount).to.equal(1);
expect(args[3]).to.equal(ttl); });
});
it('should correctly build the envelope', async () => { it('should only retry the specified amount of times before throwing', async () => {
messageEncyrptReturnEnvelopeType = SignalService.Envelope.Type.CIPHERTEXT; lokiMessageAPIStub.sendMessage.throws(new Error('API error'));
const retries = 2;
const promise = MessageSender.send(rawMessage, retries);
await expect(promise).is.rejectedWith('API error');
expect(lokiMessageAPIStub.sendMessage.callCount).to.equal(retries);
});
// This test assumes the encryption stub returns the plainText passed into it. it('should not throw error if successful send occurs within the retry limit', async () => {
const plainTextBuffer = crypto.randomBytes(10); lokiMessageAPIStub.sendMessage
const timestamp = Date.now(); .onFirstCall()
.throws(new Error('API error'));
await MessageSender.send(rawMessage, 3);
expect(lokiMessageAPIStub.sendMessage.callCount).to.equal(2);
});
});
await MessageSender.send({ describe('logic', () => {
identifier: '1', let messageEncyrptReturnEnvelopeType =
device: '0', SignalService.Envelope.Type.CIPHERTEXT;
plainTextBuffer,
encryption: EncryptionType.Signal, beforeEach(() => {
timestamp, encryptStub.callsFake(async (_device, plainTextBuffer, _type) => ({
ttl: 1, envelopeType: messageEncyrptReturnEnvelopeType,
cipherText: plainTextBuffer,
}));
}); });
const data = lokiMessageAPIStub.sendMessage.getCall(0).args[1]; it('should pass the correct values to lokiMessageAPI', async () => {
const webSocketMessage = SignalService.WebSocketMessage.decode(data); const device = '0';
expect(webSocketMessage.request?.body).to.not.equal( const timestamp = Date.now();
undefined, const ttl = 100;
'Request body should not be undefined'
);
expect(webSocketMessage.request?.body).to.not.equal(
null,
'Request body should not be null'
);
const envelope = SignalService.Envelope.decode( await MessageSender.send({
webSocketMessage.request?.body as Uint8Array identifier: '1',
); device,
expect(envelope.type).to.equal(SignalService.Envelope.Type.CIPHERTEXT); plainTextBuffer: crypto.randomBytes(10),
expect(envelope.source).to.equal(ourNumber); encryption: EncryptionType.Signal,
expect(envelope.sourceDevice).to.equal(1); timestamp,
expect(toNumber(envelope.timestamp)).to.equal(timestamp); ttl,
expect(envelope.content).to.deep.equal(plainTextBuffer); });
});
const args = lokiMessageAPIStub.sendMessage.getCall(0).args;
expect(args[0]).to.equal(device);
expect(args[2]).to.equal(timestamp);
expect(args[3]).to.equal(ttl);
});
describe('UNIDENTIFIED_SENDER', () => { it('should correctly build the envelope', async () => {
it('should set the envelope source to be empty', async () => {
messageEncyrptReturnEnvelopeType = messageEncyrptReturnEnvelopeType =
SignalService.Envelope.Type.UNIDENTIFIED_SENDER; SignalService.Envelope.Type.CIPHERTEXT;
// This test assumes the encryption stub returns the plainText passed into it. // This test assumes the encryption stub returns the plainText passed into it.
const plainTextBuffer = crypto.randomBytes(10); const plainTextBuffer = crypto.randomBytes(10);
@ -132,13 +157,53 @@ describe('MessageSender', () => {
const envelope = SignalService.Envelope.decode( const envelope = SignalService.Envelope.decode(
webSocketMessage.request?.body as Uint8Array webSocketMessage.request?.body as Uint8Array
); );
expect(envelope.type).to.equal( expect(envelope.type).to.equal(SignalService.Envelope.Type.CIPHERTEXT);
SignalService.Envelope.Type.UNIDENTIFIED_SENDER expect(envelope.source).to.equal(ourNumber);
); expect(envelope.sourceDevice).to.equal(1);
expect(envelope.source).to.equal( expect(toNumber(envelope.timestamp)).to.equal(timestamp);
'', expect(envelope.content).to.deep.equal(plainTextBuffer);
'envelope source should be empty in UNIDENTIFIED_SENDER' });
);
describe('UNIDENTIFIED_SENDER', () => {
it('should set the envelope source to be empty', async () => {
messageEncyrptReturnEnvelopeType =
SignalService.Envelope.Type.UNIDENTIFIED_SENDER;
// This test assumes the encryption stub returns the plainText passed into it.
const plainTextBuffer = crypto.randomBytes(10);
const timestamp = Date.now();
await MessageSender.send({
identifier: '1',
device: '0',
plainTextBuffer,
encryption: EncryptionType.Signal,
timestamp,
ttl: 1,
});
const data = lokiMessageAPIStub.sendMessage.getCall(0).args[1];
const webSocketMessage = SignalService.WebSocketMessage.decode(data);
expect(webSocketMessage.request?.body).to.not.equal(
undefined,
'Request body should not be undefined'
);
expect(webSocketMessage.request?.body).to.not.equal(
null,
'Request body should not be null'
);
const envelope = SignalService.Envelope.decode(
webSocketMessage.request?.body as Uint8Array
);
expect(envelope.type).to.equal(
SignalService.Envelope.Type.UNIDENTIFIED_SENDER
);
expect(envelope.source).to.equal(
'',
'envelope source should be empty in UNIDENTIFIED_SENDER'
);
});
}); });
}); });
}); });

Loading…
Cancel
Save