Merge pull request #1180 from Mikunj/multi-device-protocol-test
Added multi device protocol testspull/1182/head
commit
48fc60f0d6
@ -0,0 +1,18 @@
|
|||||||
|
/**
|
||||||
|
* Convert base64 string into a Uint8Array.
|
||||||
|
*
|
||||||
|
* The reason for this function is to avoid a very weird issue when converting to and from base64.
|
||||||
|
* ```
|
||||||
|
* const base64 = <base64string>;
|
||||||
|
* const arrayBuffer = Buffer.from(base64, 'base64').buffer;
|
||||||
|
* const reconstructedBase64 = Buffer.from(arrayBuffer).toString('base64');
|
||||||
|
* expect(base64 === reconstructedBase64) // This returns false!!
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* I have no idea why that doesn't work but a work around is to wrap the original base64 buffer in a Uin8Array before calling `buffer` on it.
|
||||||
|
*
|
||||||
|
* @param base64 The base 64 string.
|
||||||
|
*/
|
||||||
|
export function base64toUint8Array(base64: string): Uint8Array {
|
||||||
|
return new Uint8Array(Buffer.from(base64, 'base64'));
|
||||||
|
}
|
@ -1,8 +1,9 @@
|
|||||||
import * as MessageUtils from './Messages';
|
import * as MessageUtils from './Messages';
|
||||||
import * as GroupUtils from './Groups';
|
import * as GroupUtils from './Groups';
|
||||||
import * as SyncMessageUtils from './SyncMessageUtils';
|
import * as SyncMessageUtils from './SyncMessageUtils';
|
||||||
|
import * as BufferUtils from './Buffer';
|
||||||
|
|
||||||
export * from './TypedEmitter';
|
export * from './TypedEmitter';
|
||||||
export * from './JobQueue';
|
export * from './JobQueue';
|
||||||
|
|
||||||
export { MessageUtils, SyncMessageUtils, GroupUtils };
|
export { MessageUtils, SyncMessageUtils, GroupUtils, BufferUtils };
|
||||||
|
@ -0,0 +1,290 @@
|
|||||||
|
import { expect } from 'chai';
|
||||||
|
import * as sinon from 'sinon';
|
||||||
|
import { TestUtils, timeout } from '../../test-utils';
|
||||||
|
import { PairingAuthorisation } from '../../../../js/modules/data';
|
||||||
|
import { MultiDeviceProtocol } from '../../../session/protocols';
|
||||||
|
import { PubKey } from '../../../session/types';
|
||||||
|
import { UserUtil } from '../../../util';
|
||||||
|
|
||||||
|
function generateFakeAuthorisations(
|
||||||
|
primary: PubKey,
|
||||||
|
otherDevices: Array<PubKey>
|
||||||
|
): Array<PairingAuthorisation> {
|
||||||
|
return otherDevices.map(
|
||||||
|
device =>
|
||||||
|
({
|
||||||
|
primaryDevicePubKey: primary.key,
|
||||||
|
secondaryDevicePubKey: device.key,
|
||||||
|
requestSignature: new Uint8Array(0),
|
||||||
|
grantSignature: new Uint8Array(1),
|
||||||
|
} as PairingAuthorisation)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('MultiDeviceProtocol', () => {
|
||||||
|
const sandbox = sinon.createSandbox();
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
TestUtils.restoreStubs();
|
||||||
|
sandbox.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getPairingAuthorisations', () => {
|
||||||
|
let fetchPairingStub: sinon.SinonStub<[PubKey], Promise<void>>;
|
||||||
|
beforeEach(() => {
|
||||||
|
fetchPairingStub = sandbox
|
||||||
|
.stub(MultiDeviceProtocol, 'fetchPairingAuthorisationsIfNeeded')
|
||||||
|
.resolves();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fetch pairing authorisations before getting authorisations from the database', async () => {
|
||||||
|
const dataStub = TestUtils.stubData(
|
||||||
|
'getPairingAuthorisationsFor'
|
||||||
|
).resolves([]);
|
||||||
|
await MultiDeviceProtocol.getPairingAuthorisations(
|
||||||
|
TestUtils.generateFakePubkey()
|
||||||
|
);
|
||||||
|
expect(fetchPairingStub.called).to.equal(true, 'Pairing is not fetched.');
|
||||||
|
expect(fetchPairingStub.calledBefore(dataStub)).to.equal(
|
||||||
|
true,
|
||||||
|
'Database result was fetched before network result'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the authorisations from the database', async () => {
|
||||||
|
const device1 = TestUtils.generateFakePubkey();
|
||||||
|
const device2 = TestUtils.generateFakePubkey();
|
||||||
|
const pairing: PairingAuthorisation = {
|
||||||
|
primaryDevicePubKey: device1.key,
|
||||||
|
secondaryDevicePubKey: device2.key,
|
||||||
|
requestSignature: new Uint8Array(1),
|
||||||
|
grantSignature: new Uint8Array(2),
|
||||||
|
};
|
||||||
|
TestUtils.stubData('getPairingAuthorisationsFor').resolves([pairing]);
|
||||||
|
const a1 = await MultiDeviceProtocol.getPairingAuthorisations(device1);
|
||||||
|
expect(a1).to.deep.equal([pairing]);
|
||||||
|
|
||||||
|
const a2 = await MultiDeviceProtocol.getPairingAuthorisations(device2);
|
||||||
|
expect(a2).to.deep.equal([pairing]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('fetchPairingAuthorisations', () => {
|
||||||
|
it('should throw if lokiFileServerAPI does not exist', async () => {
|
||||||
|
TestUtils.stubWindow('lokiFileServerAPI', undefined);
|
||||||
|
expect(
|
||||||
|
MultiDeviceProtocol.fetchPairingAuthorisations(
|
||||||
|
TestUtils.generateFakePubkey()
|
||||||
|
)
|
||||||
|
).to.be.rejectedWith('lokiFileServerAPI is not initialised.');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the authorisations', async () => {
|
||||||
|
const networkAuth = {
|
||||||
|
primaryDevicePubKey:
|
||||||
|
'05caa6310a490415df45f8f4ad1b3655ad7a11e722257887a30cf71601d679720b',
|
||||||
|
secondaryDevicePubKey:
|
||||||
|
'051296b9588641eea268d60ad6636eecb53a95150e91c0531a00203e01a2c16a39',
|
||||||
|
requestSignature:
|
||||||
|
'+knEdlenTV+MooRqlFsZRPWW8s9pcjKwB40fY5o0GJmAi2RPZtaVGRTqgApTIn2zPBTE4GQlmPD7uxcczHDjAg==',
|
||||||
|
grantSignature:
|
||||||
|
'eKzcOWMEVetybkuiVK2u18B9en5pywohn2Hn25/VOVTMrIsKSCW4xXpqwipfqvgvi62WtUt6SA9bCEB5Ngcyiw==',
|
||||||
|
};
|
||||||
|
|
||||||
|
const stub = sinon.stub().resolves({
|
||||||
|
isPrimary: false,
|
||||||
|
authorisations: [networkAuth],
|
||||||
|
});
|
||||||
|
TestUtils.stubWindow('lokiFileServerAPI', {
|
||||||
|
getUserDeviceMapping: stub,
|
||||||
|
});
|
||||||
|
|
||||||
|
const authorisations = await MultiDeviceProtocol.fetchPairingAuthorisations(
|
||||||
|
TestUtils.generateFakePubkey()
|
||||||
|
);
|
||||||
|
expect(authorisations.length).to.equal(1);
|
||||||
|
|
||||||
|
const {
|
||||||
|
primaryDevicePubKey,
|
||||||
|
secondaryDevicePubKey,
|
||||||
|
requestSignature,
|
||||||
|
grantSignature,
|
||||||
|
} = authorisations[0];
|
||||||
|
expect(primaryDevicePubKey).to.equal(networkAuth.primaryDevicePubKey);
|
||||||
|
expect(secondaryDevicePubKey).to.equal(networkAuth.secondaryDevicePubKey);
|
||||||
|
expect(Buffer.from(requestSignature).toString('base64')).to.equal(
|
||||||
|
networkAuth.requestSignature
|
||||||
|
);
|
||||||
|
expect(grantSignature).to.not.equal(
|
||||||
|
undefined,
|
||||||
|
'Grant signature should not be undefined.'
|
||||||
|
);
|
||||||
|
// tslint:disable-next-line: no-non-null-assertion
|
||||||
|
expect(Buffer.from(grantSignature!).toString('base64')).to.equal(
|
||||||
|
networkAuth.grantSignature
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('fetchPairingAuthorisationIfNeeded', () => {
|
||||||
|
let fetchPairingAuthorisationStub: sinon.SinonStub<
|
||||||
|
[PubKey],
|
||||||
|
Promise<Array<PairingAuthorisation>>
|
||||||
|
>;
|
||||||
|
let currentDevice: PubKey;
|
||||||
|
let device: PubKey;
|
||||||
|
beforeEach(() => {
|
||||||
|
MultiDeviceProtocol.resetFetchCache();
|
||||||
|
|
||||||
|
fetchPairingAuthorisationStub = sandbox
|
||||||
|
.stub(MultiDeviceProtocol, 'fetchPairingAuthorisations')
|
||||||
|
.resolves([]);
|
||||||
|
currentDevice = TestUtils.generateFakePubkey();
|
||||||
|
device = TestUtils.generateFakePubkey();
|
||||||
|
sandbox
|
||||||
|
.stub(UserUtil, 'getCurrentDevicePubKey')
|
||||||
|
.resolves(currentDevice.key);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not fetch authorisations for our devices', async () => {
|
||||||
|
const otherDevices = TestUtils.generateFakePubKeys(2);
|
||||||
|
const authorisations = generateFakeAuthorisations(
|
||||||
|
currentDevice,
|
||||||
|
otherDevices
|
||||||
|
);
|
||||||
|
sandbox
|
||||||
|
.stub(MultiDeviceProtocol, 'getPairingAuthorisations')
|
||||||
|
.resolves(authorisations);
|
||||||
|
|
||||||
|
for (const ourDevice of [currentDevice, ...otherDevices]) {
|
||||||
|
// Ensure cache is not getting in our way
|
||||||
|
MultiDeviceProtocol.resetFetchCache();
|
||||||
|
|
||||||
|
await MultiDeviceProtocol.fetchPairingAuthorisationsIfNeeded(ourDevice);
|
||||||
|
expect(fetchPairingAuthorisationStub.called).to.equal(
|
||||||
|
false,
|
||||||
|
'Pairing should not be fetched from the server'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fetch if it has not fetched before', async () => {
|
||||||
|
await MultiDeviceProtocol.fetchPairingAuthorisationsIfNeeded(device);
|
||||||
|
expect(fetchPairingAuthorisationStub.calledWith(device)).to.equal(
|
||||||
|
true,
|
||||||
|
'Device does not match'
|
||||||
|
);
|
||||||
|
expect(fetchPairingAuthorisationStub.called).to.equal(
|
||||||
|
true,
|
||||||
|
'Pairing should be fetched from the server'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not fetch if the refresh delay has not been met', async () => {
|
||||||
|
await MultiDeviceProtocol.fetchPairingAuthorisationsIfNeeded(device);
|
||||||
|
await timeout(100);
|
||||||
|
await MultiDeviceProtocol.fetchPairingAuthorisationsIfNeeded(device);
|
||||||
|
expect(fetchPairingAuthorisationStub.callCount).to.equal(
|
||||||
|
1,
|
||||||
|
'Pairing should only be fetched once every refresh delay'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fetch again if time since last fetch is more than refresh delay', async () => {
|
||||||
|
const clock = sandbox.useFakeTimers();
|
||||||
|
await MultiDeviceProtocol.fetchPairingAuthorisationsIfNeeded(device);
|
||||||
|
clock.tick(MultiDeviceProtocol.refreshDelay + 10);
|
||||||
|
await MultiDeviceProtocol.fetchPairingAuthorisationsIfNeeded(device);
|
||||||
|
expect(fetchPairingAuthorisationStub.callCount).to.equal(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fetch again if something went wrong while fetching', async () => {
|
||||||
|
fetchPairingAuthorisationStub.throws(new Error('42'));
|
||||||
|
await MultiDeviceProtocol.fetchPairingAuthorisationsIfNeeded(device);
|
||||||
|
await timeout(100);
|
||||||
|
await MultiDeviceProtocol.fetchPairingAuthorisationsIfNeeded(device);
|
||||||
|
expect(fetchPairingAuthorisationStub.callCount).to.equal(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fetch only once if called rapidly', async () => {
|
||||||
|
fetchPairingAuthorisationStub.callsFake(async () => {
|
||||||
|
await timeout(200);
|
||||||
|
return [];
|
||||||
|
});
|
||||||
|
|
||||||
|
void MultiDeviceProtocol.fetchPairingAuthorisationsIfNeeded(device);
|
||||||
|
await timeout(10);
|
||||||
|
void MultiDeviceProtocol.fetchPairingAuthorisationsIfNeeded(device);
|
||||||
|
await timeout(200);
|
||||||
|
expect(fetchPairingAuthorisationStub.callCount).to.equal(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should save the fetched authorisations', async () => {
|
||||||
|
const saveStub = sandbox
|
||||||
|
.stub(MultiDeviceProtocol, 'savePairingAuthorisation')
|
||||||
|
.resolves();
|
||||||
|
const authorisations = generateFakeAuthorisations(
|
||||||
|
device,
|
||||||
|
TestUtils.generateFakePubKeys(3)
|
||||||
|
);
|
||||||
|
fetchPairingAuthorisationStub.resolves(authorisations);
|
||||||
|
await MultiDeviceProtocol.fetchPairingAuthorisationsIfNeeded(device);
|
||||||
|
expect(saveStub.callCount).to.equal(authorisations.length);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getAllDevices', () => {
|
||||||
|
it('should return all devices', async () => {
|
||||||
|
const primary = TestUtils.generateFakePubkey();
|
||||||
|
const otherDevices = TestUtils.generateFakePubKeys(2);
|
||||||
|
const authorisations = generateFakeAuthorisations(primary, otherDevices);
|
||||||
|
sandbox
|
||||||
|
.stub(MultiDeviceProtocol, 'getPairingAuthorisations')
|
||||||
|
.resolves(authorisations);
|
||||||
|
|
||||||
|
const devices = [primary, ...otherDevices];
|
||||||
|
for (const device of devices) {
|
||||||
|
const allDevices = await MultiDeviceProtocol.getAllDevices(device);
|
||||||
|
const allDevicePubKeys = allDevices.map(p => p.key);
|
||||||
|
expect(allDevicePubKeys).to.have.same.members(devices.map(d => d.key));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getPrimaryDevice', () => {
|
||||||
|
it('should return the primary device', async () => {
|
||||||
|
const primary = TestUtils.generateFakePubkey();
|
||||||
|
const otherDevices = TestUtils.generateFakePubKeys(2);
|
||||||
|
const authorisations = generateFakeAuthorisations(primary, otherDevices);
|
||||||
|
sandbox
|
||||||
|
.stub(MultiDeviceProtocol, 'getPairingAuthorisations')
|
||||||
|
.resolves(authorisations);
|
||||||
|
|
||||||
|
const devices = [primary, ...otherDevices];
|
||||||
|
for (const device of devices) {
|
||||||
|
const actual = await MultiDeviceProtocol.getPrimaryDevice(device);
|
||||||
|
expect(actual.key).to.equal(primary.key);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getSecondaryDevices', () => {
|
||||||
|
it('should return the secondary devices', async () => {
|
||||||
|
const primary = TestUtils.generateFakePubkey();
|
||||||
|
const otherDevices = TestUtils.generateFakePubKeys(2);
|
||||||
|
const authorisations = generateFakeAuthorisations(primary, otherDevices);
|
||||||
|
sandbox
|
||||||
|
.stub(MultiDeviceProtocol, 'getPairingAuthorisations')
|
||||||
|
.resolves(authorisations);
|
||||||
|
|
||||||
|
const devices = [primary, ...otherDevices];
|
||||||
|
for (const device of devices) {
|
||||||
|
const secondaryDevices = await MultiDeviceProtocol.getSecondaryDevices(
|
||||||
|
device
|
||||||
|
);
|
||||||
|
const pubKeys = secondaryDevices.map(p => p.key);
|
||||||
|
expect(pubKeys).to.have.same.members(otherDevices.map(d => d.key));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue