Signal-Desktop/ts/test-both/services/ourProfileKey_test.ts

183 lines
5.2 KiB
TypeScript

// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { assert } from 'chai';
import * as sinon from 'sinon';
import { noop } from 'lodash';
import { sleep } from '../../util/sleep';
import { constantTimeEqual } from '../../Crypto';
import { OurProfileKeyService } from '../../services/ourProfileKey';
describe('"our profile key" service', () => {
const createFakeStorage = () => ({
get: sinon.stub(),
put: sinon.stub().resolves(),
remove: sinon.stub().resolves(),
onready: sinon.stub().callsArg(0),
});
describe('get', () => {
it("fetches the key from storage if it's there", async () => {
const fakeProfileKey = new Uint8Array(2);
const fakeStorage = createFakeStorage();
fakeStorage.get.withArgs('profileKey').returns(fakeProfileKey);
const service = new OurProfileKeyService();
service.initialize(fakeStorage);
const profileKey = await service.get();
assert.isTrue(
profileKey && constantTimeEqual(profileKey, fakeProfileKey)
);
});
it('resolves with undefined if the key is not in storage', async () => {
const service = new OurProfileKeyService();
service.initialize(createFakeStorage());
assert.isUndefined(await service.get());
});
it("doesn't grab the profile key from storage until storage is ready", async () => {
let onReadyCallback = noop;
const fakeStorage = {
...createFakeStorage(),
get: sinon.stub().returns(new Uint8Array(2)),
onready: sinon.stub().callsFake(callback => {
onReadyCallback = callback;
}),
};
const service = new OurProfileKeyService();
service.initialize(fakeStorage);
const getPromise = service.get();
// We want to make sure this isn't called even after a tick of the event loop.
await sleep(1);
sinon.assert.notCalled(fakeStorage.get);
onReadyCallback();
await getPromise;
sinon.assert.calledOnce(fakeStorage.get);
});
it("doesn't grab the profile key until all blocking promises are ready", async () => {
const fakeStorage = createFakeStorage();
const service = new OurProfileKeyService();
service.initialize(fakeStorage);
let resolve1 = noop;
service.blockGetWithPromise(
new Promise<void>(resolve => {
resolve1 = resolve;
})
);
let reject2 = noop;
service.blockGetWithPromise(
new Promise<void>((_resolve, reject) => {
reject2 = reject;
})
);
let reject3 = noop;
service.blockGetWithPromise(
new Promise<void>((_resolve, reject) => {
reject3 = reject;
})
);
const getPromise = service.get();
resolve1();
await sleep(1);
sinon.assert.notCalled(fakeStorage.get);
reject2(new Error('uh oh'));
await sleep(1);
sinon.assert.notCalled(fakeStorage.get);
reject3(new Error('oh no'));
await getPromise;
sinon.assert.calledOnce(fakeStorage.get);
});
it("if there are blocking promises, doesn't grab the profile key from storage more than once (in other words, subsequent calls piggyback)", async () => {
const fakeStorage = createFakeStorage();
fakeStorage.get.returns(new Uint8Array(2));
const service = new OurProfileKeyService();
service.initialize(fakeStorage);
let resolve = noop;
service.blockGetWithPromise(
new Promise<void>(innerResolve => {
resolve = innerResolve;
})
);
const getPromises = [service.get(), service.get(), service.get()];
resolve();
const results = await Promise.all(getPromises);
assert(new Set(results).size === 1, 'All results should be the same');
sinon.assert.calledOnce(fakeStorage.get);
});
it('removes all of the blocking promises after waiting for them once', async () => {
const fakeStorage = createFakeStorage();
const service = new OurProfileKeyService();
service.initialize(fakeStorage);
let resolve = noop;
service.blockGetWithPromise(
new Promise<void>(innerResolve => {
resolve = innerResolve;
})
);
const getPromise = service.get();
sinon.assert.notCalled(fakeStorage.get);
resolve();
await getPromise;
sinon.assert.calledOnce(fakeStorage.get);
await service.get();
sinon.assert.calledTwice(fakeStorage.get);
});
});
describe('set', () => {
it('updates the key in storage', async () => {
const fakeProfileKey = new Uint8Array(2);
const fakeStorage = createFakeStorage();
const service = new OurProfileKeyService();
service.initialize(fakeStorage);
await service.set(fakeProfileKey);
sinon.assert.calledOnce(fakeStorage.put);
sinon.assert.calledWith(fakeStorage.put, 'profileKey', fakeProfileKey);
});
it('clears the key in storage', async () => {
const fakeStorage = createFakeStorage();
const service = new OurProfileKeyService();
service.initialize(fakeStorage);
await service.set(undefined);
sinon.assert.calledOnce(fakeStorage.remove);
sinon.assert.calledWith(fakeStorage.remove, 'profileKey');
});
});
});