Merge remote-tracking branch 'upstream/clearnet' into message-sending-refactor
commit
da1edab63d
@ -1,36 +1,135 @@
|
||||
import { RawMessage } from '../types/RawMessage';
|
||||
import { createOrUpdateItem, getItemById } from '../../../js/modules/data';
|
||||
import { PartialRawMessage, RawMessage } from '../types/RawMessage';
|
||||
import { ContentMessage } from '../messages/outgoing';
|
||||
import { PubKey } from '../types';
|
||||
import * as MessageUtils from '../utils';
|
||||
|
||||
// TODO: We should be able to import functions straight from the db here without going through the window object
|
||||
// This is an abstraction for storing pending messages.
|
||||
// Ideally we want to store pending messages in the database so that
|
||||
// on next launch we can re-send the pending messages, but we don't want
|
||||
// to constantly fetch pending messages from the database.
|
||||
// Thus we have an intermediary cache which will store pending messagesin
|
||||
// memory and sync its state with the database on modification (add or remove).
|
||||
|
||||
export class PendingMessageCache {
|
||||
private readonly cachedMessages: Array<RawMessage> = [];
|
||||
public readonly isReady: Promise<boolean>;
|
||||
private cache: Array<RawMessage>;
|
||||
|
||||
constructor() {
|
||||
// TODO: We should load pending messages from db here
|
||||
// Load pending messages from the database
|
||||
// You should await isReady on making a new PendingMessageCache
|
||||
// if you'd like to have instant access to the cache
|
||||
this.cache = [];
|
||||
|
||||
this.isReady = new Promise(async resolve => {
|
||||
await this.loadFromDB();
|
||||
resolve(true);
|
||||
});
|
||||
}
|
||||
|
||||
public getAllPending(): Array<RawMessage> {
|
||||
// Get all pending from cache, sorted with oldest first
|
||||
return [...this.cache].sort((a, b) => a.timestamp - b.timestamp);
|
||||
}
|
||||
|
||||
public getForDevice(device: PubKey): Array<RawMessage> {
|
||||
return this.getAllPending().filter(m => m.device === device.key);
|
||||
}
|
||||
|
||||
public getDevices(): Array<PubKey> {
|
||||
// Gets all unique devices with pending messages
|
||||
const pubkeyStrings = [...new Set(this.cache.map(m => m.device))];
|
||||
|
||||
return pubkeyStrings.map(PubKey.from).filter((k): k is PubKey => !!k);
|
||||
}
|
||||
|
||||
public addPendingMessage(
|
||||
device: string,
|
||||
public async add(
|
||||
device: PubKey,
|
||||
message: ContentMessage
|
||||
): RawMessage {
|
||||
// TODO: Maybe have a util for converting OutgoingContentMessage to RawMessage?
|
||||
// TODO: Raw message has uuid, how are we going to set that? maybe use a different identifier?
|
||||
// One could be device + timestamp would make a unique identifier
|
||||
// TODO: Return previous pending message if it exists
|
||||
return {} as RawMessage;
|
||||
): Promise<RawMessage> {
|
||||
const rawMessage = MessageUtils.toRawMessage(device, message);
|
||||
|
||||
// Does it exist in cache already?
|
||||
if (this.find(rawMessage)) {
|
||||
return rawMessage;
|
||||
}
|
||||
|
||||
this.cache.push(rawMessage);
|
||||
await this.saveToDB();
|
||||
|
||||
return rawMessage;
|
||||
}
|
||||
|
||||
public async remove(
|
||||
message: RawMessage
|
||||
): Promise<Array<RawMessage> | undefined> {
|
||||
// Should only be called after message is processed
|
||||
|
||||
// Return if message doesn't exist in cache
|
||||
if (!this.find(message)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove item from cache and sync with database
|
||||
const updatedCache = this.cache.filter(
|
||||
m => m.identifier !== message.identifier
|
||||
);
|
||||
this.cache = updatedCache;
|
||||
await this.saveToDB();
|
||||
|
||||
return updatedCache;
|
||||
}
|
||||
|
||||
public find(message: RawMessage): RawMessage | undefined {
|
||||
// Find a message in the cache
|
||||
return this.cache.find(
|
||||
m => m.device === message.device && m.timestamp === message.timestamp
|
||||
);
|
||||
}
|
||||
|
||||
public removePendingMessage(message: RawMessage) {
|
||||
// TODO: implement
|
||||
public async clear() {
|
||||
// Clears the cache and syncs to DB
|
||||
this.cache = [];
|
||||
await this.saveToDB();
|
||||
}
|
||||
|
||||
public getPendingDevices(): Array<String> {
|
||||
// TODO: this should return all devices which have pending messages
|
||||
return [];
|
||||
public async loadFromDB() {
|
||||
const messages = await this.getFromStorage();
|
||||
this.cache = messages;
|
||||
}
|
||||
|
||||
public getPendingMessages(device: string): Array<RawMessage> {
|
||||
return [];
|
||||
private async getFromStorage(): Promise<Array<RawMessage>> {
|
||||
const data = await getItemById('pendingMessages');
|
||||
if (!data || !data.value) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const barePending = JSON.parse(String(data.value)) as Array<
|
||||
PartialRawMessage
|
||||
>;
|
||||
|
||||
// Rebuild plainTextBuffer
|
||||
return barePending.map((message: PartialRawMessage) => {
|
||||
return {
|
||||
...message,
|
||||
plainTextBuffer: new Uint8Array(message.plainTextBuffer),
|
||||
} as RawMessage;
|
||||
});
|
||||
}
|
||||
|
||||
private async saveToDB() {
|
||||
// For each plainTextBuffer in cache, save in as a simple Array<number> to avoid
|
||||
// Node issues with JSON stringifying Buffer without strict typing
|
||||
const encodedCache = [...this.cache].map(item => {
|
||||
const plainTextBuffer = Array.from(item.plainTextBuffer);
|
||||
|
||||
return { ...item, plainTextBuffer };
|
||||
});
|
||||
|
||||
const encodedPendingMessages = JSON.stringify(encodedCache) || '[]';
|
||||
await createOrUpdateItem({
|
||||
id: 'pendingMessages',
|
||||
value: encodedPendingMessages,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,28 @@
|
||||
export class PubKey {
|
||||
public static readonly PUBKEY_LEN = 66;
|
||||
private static readonly regex: string = `^05[0-9a-fA-F]{${PubKey.PUBKEY_LEN -
|
||||
2}}$`;
|
||||
public readonly key: string;
|
||||
|
||||
constructor(pubkeyString: string) {
|
||||
PubKey.validate(pubkeyString);
|
||||
this.key = pubkeyString;
|
||||
}
|
||||
|
||||
public static from(pubkeyString: string): PubKey | undefined {
|
||||
// Returns a new instance if the pubkey is valid
|
||||
if (PubKey.validate(pubkeyString)) {
|
||||
return new PubKey(pubkeyString);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public static validate(pubkeyString: string): boolean {
|
||||
if (pubkeyString.match(PubKey.regex)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
export * from './EncryptionType';
|
||||
export * from './RawMessage';
|
||||
export * from './PubKey';
|
@ -0,0 +1,24 @@
|
||||
import { RawMessage } from '../types/RawMessage';
|
||||
import { ContentMessage } from '../messages/outgoing';
|
||||
import { EncryptionType, PubKey } from '../types';
|
||||
|
||||
export function toRawMessage(
|
||||
device: PubKey,
|
||||
message: ContentMessage
|
||||
): RawMessage {
|
||||
const ttl = message.ttl();
|
||||
const timestamp = message.timestamp;
|
||||
const plainTextBuffer = message.plainTextBuffer();
|
||||
|
||||
// tslint:disable-next-line: no-unnecessary-local-variable
|
||||
const rawMessage: RawMessage = {
|
||||
identifier: message.identifier,
|
||||
plainTextBuffer,
|
||||
timestamp,
|
||||
device: device.key,
|
||||
ttl,
|
||||
encryption: EncryptionType.Signal,
|
||||
};
|
||||
|
||||
return rawMessage;
|
||||
}
|
@ -1,2 +1,3 @@
|
||||
export * from './TypedEmitter';
|
||||
export * from './JobQueue';
|
||||
export * from './Messages';
|
||||
|
@ -0,0 +1,179 @@
|
||||
import { expect } from 'chai';
|
||||
import * as crypto from 'crypto';
|
||||
import * as sinon from 'sinon';
|
||||
import { toNumber } from 'lodash';
|
||||
import { MessageSender } from '../../../session/sending';
|
||||
import LokiMessageAPI from '../../../../js/modules/loki_message_api';
|
||||
import { TestUtils } from '../../test-utils';
|
||||
import { UserUtil } from '../../../util';
|
||||
import { MessageEncrypter } from '../../../session/crypto';
|
||||
import { SignalService } from '../../../protobuf';
|
||||
import LokiPublicChatFactoryAPI from '../../../../js/modules/loki_public_chat_api';
|
||||
import { OpenGroupMessage } from '../../../session/messages/outgoing';
|
||||
import { LokiPublicChannelAPI } from '../../../../js/modules/loki_app_dot_net_api';
|
||||
import { EncryptionType } from '../../../session/types/EncryptionType';
|
||||
|
||||
describe('MessageSender', () => {
|
||||
const sandbox = sinon.createSandbox();
|
||||
|
||||
afterEach(() => {
|
||||
sandbox.restore();
|
||||
TestUtils.restoreStubs();
|
||||
});
|
||||
|
||||
describe('send', () => {
|
||||
const ourNumber = 'ourNumber';
|
||||
let lokiMessageAPIStub: sinon.SinonStubbedInstance<LokiMessageAPI>;
|
||||
let messageEncyrptReturnEnvelopeType =
|
||||
SignalService.Envelope.Type.CIPHERTEXT;
|
||||
|
||||
beforeEach(() => {
|
||||
// We can do this because LokiMessageAPI has a module export in it
|
||||
lokiMessageAPIStub = sandbox.createStubInstance(LokiMessageAPI, {
|
||||
sendMessage: sandbox.stub(),
|
||||
});
|
||||
TestUtils.stubWindow('lokiMessageAPI', lokiMessageAPIStub);
|
||||
|
||||
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 () => {
|
||||
const device = '0';
|
||||
const timestamp = Date.now();
|
||||
const ttl = 100;
|
||||
|
||||
await MessageSender.send({
|
||||
identifier: '1',
|
||||
device,
|
||||
plainTextBuffer: crypto.randomBytes(10),
|
||||
encryption: EncryptionType.Signal,
|
||||
timestamp,
|
||||
ttl,
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
it('should correctly build the envelope', async () => {
|
||||
messageEncyrptReturnEnvelopeType = SignalService.Envelope.Type.CIPHERTEXT;
|
||||
|
||||
// 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.CIPHERTEXT);
|
||||
expect(envelope.source).to.equal(ourNumber);
|
||||
expect(envelope.sourceDevice).to.equal(1);
|
||||
expect(toNumber(envelope.timestamp)).to.equal(timestamp);
|
||||
expect(envelope.content).to.deep.equal(plainTextBuffer);
|
||||
});
|
||||
|
||||
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'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('sendToOpenGroup', () => {
|
||||
it('should send the message to the correct server and channel', async () => {
|
||||
// We can do this because LokiPublicChatFactoryAPI has a module export in it
|
||||
const stub = sandbox.createStubInstance(LokiPublicChatFactoryAPI, {
|
||||
findOrCreateChannel: sandbox.stub().resolves({
|
||||
sendMessage: sandbox.stub(),
|
||||
} as LokiPublicChannelAPI) as any,
|
||||
});
|
||||
TestUtils.stubWindow('lokiPublicChatAPI', stub);
|
||||
|
||||
const group = {
|
||||
server: 'server',
|
||||
channel: 1,
|
||||
conversationId: '0',
|
||||
};
|
||||
|
||||
const message = new OpenGroupMessage({
|
||||
timestamp: Date.now(),
|
||||
group,
|
||||
});
|
||||
|
||||
await MessageSender.sendToOpenGroup(message);
|
||||
|
||||
const [
|
||||
server,
|
||||
channel,
|
||||
conversationId,
|
||||
] = stub.findOrCreateChannel.getCall(0).args;
|
||||
expect(server).to.equal(group.server);
|
||||
expect(channel).to.equal(group.channel);
|
||||
expect(conversationId).to.equal(group.conversationId);
|
||||
});
|
||||
});
|
||||
});
|
@ -0,0 +1,259 @@
|
||||
import { expect } from 'chai';
|
||||
import * as _ from 'lodash';
|
||||
import * as MessageUtils from '../../../session/utils';
|
||||
import { TestUtils } from '../../../test/test-utils';
|
||||
import { PendingMessageCache } from '../../../session/sending/PendingMessageCache';
|
||||
|
||||
// Equivalent to Data.StorageItem
|
||||
interface StorageItem {
|
||||
id: string;
|
||||
value: any;
|
||||
}
|
||||
|
||||
describe('PendingMessageCache', () => {
|
||||
// Initialize new stubbed cache
|
||||
let data: StorageItem;
|
||||
let pendingMessageCacheStub: PendingMessageCache;
|
||||
|
||||
beforeEach(async () => {
|
||||
// Stub out methods which touch the database
|
||||
const storageID = 'pendingMessages';
|
||||
data = {
|
||||
id: storageID,
|
||||
value: '[]',
|
||||
};
|
||||
|
||||
TestUtils.stubData('getItemById')
|
||||
.withArgs('pendingMessages')
|
||||
.callsFake(async () => {
|
||||
return data;
|
||||
});
|
||||
|
||||
TestUtils.stubData('createOrUpdateItem').callsFake((item: StorageItem) => {
|
||||
if (item.id === storageID) {
|
||||
data = item;
|
||||
}
|
||||
});
|
||||
|
||||
pendingMessageCacheStub = new PendingMessageCache();
|
||||
await pendingMessageCacheStub.isReady;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
TestUtils.restoreStubs();
|
||||
});
|
||||
|
||||
it('can initialize cache', async () => {
|
||||
const cache = pendingMessageCacheStub.getAllPending();
|
||||
|
||||
// We expect the cache to initialise as an empty array
|
||||
expect(cache).to.be.instanceOf(Array);
|
||||
expect(cache).to.have.length(0);
|
||||
});
|
||||
|
||||
it('can add to cache', async () => {
|
||||
const device = TestUtils.generateFakePubkey();
|
||||
const message = TestUtils.generateUniqueChatMessage();
|
||||
const rawMessage = MessageUtils.toRawMessage(device, message);
|
||||
|
||||
await pendingMessageCacheStub.add(device, message);
|
||||
|
||||
// Verify that the message is in the cache
|
||||
const finalCache = pendingMessageCacheStub.getAllPending();
|
||||
|
||||
expect(finalCache).to.have.length(1);
|
||||
|
||||
const addedMessage = finalCache[0];
|
||||
expect(addedMessage.device).to.deep.equal(rawMessage.device);
|
||||
expect(addedMessage.timestamp).to.deep.equal(rawMessage.timestamp);
|
||||
});
|
||||
|
||||
it('can remove from cache', async () => {
|
||||
const device = TestUtils.generateFakePubkey();
|
||||
const message = TestUtils.generateUniqueChatMessage();
|
||||
const rawMessage = MessageUtils.toRawMessage(device, message);
|
||||
|
||||
await pendingMessageCacheStub.add(device, message);
|
||||
|
||||
const initialCache = pendingMessageCacheStub.getAllPending();
|
||||
expect(initialCache).to.have.length(1);
|
||||
|
||||
// Remove the message
|
||||
await pendingMessageCacheStub.remove(rawMessage);
|
||||
|
||||
const finalCache = pendingMessageCacheStub.getAllPending();
|
||||
|
||||
// Verify that the message was removed
|
||||
expect(finalCache).to.have.length(0);
|
||||
});
|
||||
|
||||
it('can get devices', async () => {
|
||||
const cacheItems = [
|
||||
{
|
||||
device: TestUtils.generateFakePubkey(),
|
||||
message: TestUtils.generateUniqueChatMessage(),
|
||||
},
|
||||
{
|
||||
device: TestUtils.generateFakePubkey(),
|
||||
message: TestUtils.generateUniqueChatMessage(),
|
||||
},
|
||||
{
|
||||
device: TestUtils.generateFakePubkey(),
|
||||
message: TestUtils.generateUniqueChatMessage(),
|
||||
},
|
||||
];
|
||||
|
||||
cacheItems.forEach(async item => {
|
||||
await pendingMessageCacheStub.add(item.device, item.message);
|
||||
});
|
||||
|
||||
const cache = pendingMessageCacheStub.getAllPending();
|
||||
expect(cache).to.have.length(cacheItems.length);
|
||||
|
||||
// Get list of devices
|
||||
const devicesKeys = cacheItems.map(item => item.device.key);
|
||||
const pulledDevices = pendingMessageCacheStub.getDevices();
|
||||
const pulledDevicesKeys = pulledDevices.map(d => d.key);
|
||||
|
||||
// Verify that device list from cache is equivalent to devices added
|
||||
expect(pulledDevicesKeys).to.have.members(devicesKeys);
|
||||
});
|
||||
|
||||
it('can get pending for device', async () => {
|
||||
const cacheItems = [
|
||||
{
|
||||
device: TestUtils.generateFakePubkey(),
|
||||
message: TestUtils.generateUniqueChatMessage(),
|
||||
},
|
||||
{
|
||||
device: TestUtils.generateFakePubkey(),
|
||||
message: TestUtils.generateUniqueChatMessage(),
|
||||
},
|
||||
];
|
||||
|
||||
cacheItems.forEach(async item => {
|
||||
await pendingMessageCacheStub.add(item.device, item.message);
|
||||
});
|
||||
|
||||
const initialCache = pendingMessageCacheStub.getAllPending();
|
||||
expect(initialCache).to.have.length(cacheItems.length);
|
||||
|
||||
// Get pending for each specific device
|
||||
cacheItems.forEach(item => {
|
||||
const pendingForDevice = pendingMessageCacheStub.getForDevice(
|
||||
item.device
|
||||
);
|
||||
expect(pendingForDevice).to.have.length(1);
|
||||
expect(pendingForDevice[0].device).to.equal(item.device.key);
|
||||
});
|
||||
});
|
||||
|
||||
it('can find nothing when empty', async () => {
|
||||
const device = TestUtils.generateFakePubkey();
|
||||
const message = TestUtils.generateUniqueChatMessage();
|
||||
const rawMessage = MessageUtils.toRawMessage(device, message);
|
||||
|
||||
const foundMessage = pendingMessageCacheStub.find(rawMessage);
|
||||
expect(foundMessage, 'a message was found in empty cache').to.be.undefined;
|
||||
});
|
||||
|
||||
it('can find message in cache', async () => {
|
||||
const device = TestUtils.generateFakePubkey();
|
||||
const message = TestUtils.generateUniqueChatMessage();
|
||||
const rawMessage = MessageUtils.toRawMessage(device, message);
|
||||
|
||||
await pendingMessageCacheStub.add(device, message);
|
||||
|
||||
const finalCache = pendingMessageCacheStub.getAllPending();
|
||||
expect(finalCache).to.have.length(1);
|
||||
|
||||
const foundMessage = pendingMessageCacheStub.find(rawMessage);
|
||||
expect(foundMessage, 'message not found in cache').to.be.ok;
|
||||
foundMessage && expect(foundMessage.device).to.equal(device.key);
|
||||
});
|
||||
|
||||
it('can clear cache', async () => {
|
||||
const cacheItems = [
|
||||
{
|
||||
device: TestUtils.generateFakePubkey(),
|
||||
message: TestUtils.generateUniqueChatMessage(),
|
||||
},
|
||||
{
|
||||
device: TestUtils.generateFakePubkey(),
|
||||
message: TestUtils.generateUniqueChatMessage(),
|
||||
},
|
||||
{
|
||||
device: TestUtils.generateFakePubkey(),
|
||||
message: TestUtils.generateUniqueChatMessage(),
|
||||
},
|
||||
];
|
||||
|
||||
cacheItems.forEach(async item => {
|
||||
await pendingMessageCacheStub.add(item.device, item.message);
|
||||
});
|
||||
|
||||
const initialCache = pendingMessageCacheStub.getAllPending();
|
||||
expect(initialCache).to.have.length(cacheItems.length);
|
||||
|
||||
// Clear cache
|
||||
await pendingMessageCacheStub.clear();
|
||||
|
||||
const finalCache = pendingMessageCacheStub.getAllPending();
|
||||
expect(finalCache).to.have.length(0);
|
||||
});
|
||||
|
||||
it('can restore from db', async () => {
|
||||
const cacheItems = [
|
||||
{
|
||||
device: TestUtils.generateFakePubkey(),
|
||||
message: TestUtils.generateUniqueChatMessage(),
|
||||
},
|
||||
{
|
||||
device: TestUtils.generateFakePubkey(),
|
||||
message: TestUtils.generateUniqueChatMessage(),
|
||||
},
|
||||
{
|
||||
device: TestUtils.generateFakePubkey(),
|
||||
message: TestUtils.generateUniqueChatMessage(),
|
||||
},
|
||||
];
|
||||
|
||||
cacheItems.forEach(async item => {
|
||||
await pendingMessageCacheStub.add(item.device, item.message);
|
||||
});
|
||||
|
||||
const addedMessages = pendingMessageCacheStub.getAllPending();
|
||||
expect(addedMessages).to.have.length(cacheItems.length);
|
||||
|
||||
// Rebuild from DB
|
||||
const freshCache = new PendingMessageCache();
|
||||
await freshCache.isReady;
|
||||
|
||||
// Verify messages
|
||||
const rebuiltMessages = freshCache.getAllPending();
|
||||
|
||||
rebuiltMessages.forEach((message, index) => {
|
||||
const addedMessage = addedMessages[index];
|
||||
|
||||
// Pull out plainTextBuffer for a separate check
|
||||
const buffersCompare =
|
||||
Buffer.compare(
|
||||
message.plainTextBuffer,
|
||||
addedMessage.plainTextBuffer
|
||||
) === 0;
|
||||
expect(buffersCompare).to.equal(
|
||||
true,
|
||||
'buffers were not loaded properly from database'
|
||||
);
|
||||
|
||||
// Compare all other valures
|
||||
const trimmedAdded = _.omit(addedMessage, ['plainTextBuffer']);
|
||||
const trimmedRebuilt = _.omit(message, ['plainTextBuffer']);
|
||||
|
||||
expect(_.isEqual(trimmedAdded, trimmedRebuilt)).to.equal(
|
||||
true,
|
||||
'cached messages were not rebuilt properly'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue