You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			331 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			TypeScript
		
	
			
		
		
	
	
			331 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			TypeScript
		
	
// tslint:disable: no-implicit-dependencies max-func-body-length no-unused-expression
 | 
						|
import { expect } from 'chai';
 | 
						|
import Sinon from 'sinon';
 | 
						|
import * as _ from 'lodash';
 | 
						|
import { MessageUtils } from '../../../../session/utils';
 | 
						|
import { TestUtils } from '../../../../test/test-utils';
 | 
						|
import { PendingMessageCache } from '../../../../session/sending/PendingMessageCache';
 | 
						|
import { SnodeNamespaces } from '../../../../session/apis/snode_api/namespaces';
 | 
						|
 | 
						|
// Equivalent to Data.StorageItem
 | 
						|
interface StorageItem {
 | 
						|
  id: string;
 | 
						|
  value: any;
 | 
						|
}
 | 
						|
 | 
						|
describe('PendingMessageCache', () => {
 | 
						|
  // Initialize new stubbed cache
 | 
						|
  let data: StorageItem;
 | 
						|
  let pendingMessageCacheStub: PendingMessageCache;
 | 
						|
 | 
						|
  beforeEach(() => {
 | 
						|
    // 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();
 | 
						|
  });
 | 
						|
 | 
						|
  afterEach(() => {
 | 
						|
    Sinon.restore();
 | 
						|
  });
 | 
						|
 | 
						|
  it('can initialize cache', async () => {
 | 
						|
    const cache = await 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.generateVisibleMessage();
 | 
						|
    const rawMessage = await MessageUtils.toRawMessage(
 | 
						|
      device,
 | 
						|
      message,
 | 
						|
      SnodeNamespaces.UserMessages
 | 
						|
    );
 | 
						|
 | 
						|
    await pendingMessageCacheStub.add(device, message, SnodeNamespaces.UserMessages);
 | 
						|
 | 
						|
    // Verify that the message is in the cache
 | 
						|
    const finalCache = await pendingMessageCacheStub.getAllPending();
 | 
						|
 | 
						|
    expect(finalCache).to.have.length(1);
 | 
						|
 | 
						|
    const addedMessage = finalCache[0];
 | 
						|
    expect(addedMessage.device).to.deep.equal(rawMessage.device);
 | 
						|
  });
 | 
						|
 | 
						|
  it('can add multiple messages belonging to the same user', async () => {
 | 
						|
    const device = TestUtils.generateFakePubKey();
 | 
						|
 | 
						|
    await pendingMessageCacheStub.add(
 | 
						|
      device,
 | 
						|
      TestUtils.generateVisibleMessage(),
 | 
						|
      SnodeNamespaces.UserMessages
 | 
						|
    );
 | 
						|
    // We have to timeout here otherwise it's processed too fast and messages start having the same timestamp
 | 
						|
    await TestUtils.timeout(5);
 | 
						|
    await pendingMessageCacheStub.add(
 | 
						|
      device,
 | 
						|
      TestUtils.generateVisibleMessage(),
 | 
						|
      SnodeNamespaces.UserMessages
 | 
						|
    );
 | 
						|
    await TestUtils.timeout(5);
 | 
						|
    await pendingMessageCacheStub.add(
 | 
						|
      device,
 | 
						|
      TestUtils.generateVisibleMessage(),
 | 
						|
      SnodeNamespaces.UserMessages
 | 
						|
    );
 | 
						|
 | 
						|
    // Verify that the message is in the cache
 | 
						|
    const finalCache = await pendingMessageCacheStub.getAllPending();
 | 
						|
 | 
						|
    expect(finalCache).to.have.length(3);
 | 
						|
  });
 | 
						|
 | 
						|
  it('can remove from cache', async () => {
 | 
						|
    const device = TestUtils.generateFakePubKey();
 | 
						|
    const message = TestUtils.generateVisibleMessage();
 | 
						|
    const rawMessage = await MessageUtils.toRawMessage(
 | 
						|
      device,
 | 
						|
      message,
 | 
						|
      SnodeNamespaces.UserMessages
 | 
						|
    );
 | 
						|
 | 
						|
    await pendingMessageCacheStub.add(device, message, SnodeNamespaces.UserMessages);
 | 
						|
 | 
						|
    const initialCache = await pendingMessageCacheStub.getAllPending();
 | 
						|
    expect(initialCache).to.have.length(1);
 | 
						|
 | 
						|
    // Remove the message
 | 
						|
    await pendingMessageCacheStub.remove(rawMessage);
 | 
						|
 | 
						|
    const finalCache = await pendingMessageCacheStub.getAllPending();
 | 
						|
 | 
						|
    // Verify that the message was removed
 | 
						|
    expect(finalCache).to.have.length(0);
 | 
						|
  });
 | 
						|
 | 
						|
  it('should only remove messages with different identifier and device', async () => {
 | 
						|
    const device = TestUtils.generateFakePubKey();
 | 
						|
    const message = TestUtils.generateVisibleMessage();
 | 
						|
    const rawMessage = await MessageUtils.toRawMessage(
 | 
						|
      device,
 | 
						|
      message,
 | 
						|
      SnodeNamespaces.UserMessages
 | 
						|
    );
 | 
						|
 | 
						|
    await pendingMessageCacheStub.add(device, message, SnodeNamespaces.UserMessages);
 | 
						|
    await TestUtils.timeout(5);
 | 
						|
    const one = await pendingMessageCacheStub.add(
 | 
						|
      device,
 | 
						|
      TestUtils.generateVisibleMessage(),
 | 
						|
      SnodeNamespaces.UserMessages
 | 
						|
    );
 | 
						|
    const two = await pendingMessageCacheStub.add(
 | 
						|
      TestUtils.generateFakePubKey(),
 | 
						|
      message,
 | 
						|
      SnodeNamespaces.UserMessages
 | 
						|
    );
 | 
						|
 | 
						|
    const initialCache = await pendingMessageCacheStub.getAllPending();
 | 
						|
    expect(initialCache).to.have.length(3);
 | 
						|
 | 
						|
    // Remove the message
 | 
						|
    await pendingMessageCacheStub.remove(rawMessage);
 | 
						|
 | 
						|
    const finalCache = await pendingMessageCacheStub.getAllPending();
 | 
						|
 | 
						|
    // Verify that the message was removed
 | 
						|
    expect(finalCache).to.have.length(2);
 | 
						|
    expect(finalCache).to.have.deep.members([one, two]);
 | 
						|
  });
 | 
						|
 | 
						|
  it('can get devices', async () => {
 | 
						|
    const cacheItems = [
 | 
						|
      {
 | 
						|
        device: TestUtils.generateFakePubKey(),
 | 
						|
        message: TestUtils.generateVisibleMessage(),
 | 
						|
      },
 | 
						|
      {
 | 
						|
        device: TestUtils.generateFakePubKey(),
 | 
						|
        message: TestUtils.generateVisibleMessage(),
 | 
						|
      },
 | 
						|
      {
 | 
						|
        device: TestUtils.generateFakePubKey(),
 | 
						|
        message: TestUtils.generateVisibleMessage(),
 | 
						|
      },
 | 
						|
    ];
 | 
						|
 | 
						|
    for (const item of cacheItems) {
 | 
						|
      await pendingMessageCacheStub.add(item.device, item.message, SnodeNamespaces.UserMessages);
 | 
						|
    }
 | 
						|
 | 
						|
    const cache = await pendingMessageCacheStub.getAllPending();
 | 
						|
    expect(cache).to.have.length(cacheItems.length);
 | 
						|
 | 
						|
    // Get list of devices
 | 
						|
    const devicesKeys = cacheItems.map(item => item.device.key);
 | 
						|
    const pulledDevices = await 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.generateVisibleMessage(),
 | 
						|
      },
 | 
						|
      {
 | 
						|
        device: TestUtils.generateFakePubKey(),
 | 
						|
        message: TestUtils.generateVisibleMessage(),
 | 
						|
      },
 | 
						|
    ];
 | 
						|
 | 
						|
    for (const item of cacheItems) {
 | 
						|
      await pendingMessageCacheStub.add(item.device, item.message, SnodeNamespaces.UserMessages);
 | 
						|
    }
 | 
						|
 | 
						|
    const initialCache = await pendingMessageCacheStub.getAllPending();
 | 
						|
    expect(initialCache).to.have.length(cacheItems.length);
 | 
						|
 | 
						|
    // Get pending for each specific device
 | 
						|
    for (const item of cacheItems) {
 | 
						|
      const pendingForDevice = await 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.generateVisibleMessage();
 | 
						|
    const rawMessage = await MessageUtils.toRawMessage(
 | 
						|
      device,
 | 
						|
      message,
 | 
						|
      SnodeNamespaces.UserMessages
 | 
						|
    );
 | 
						|
 | 
						|
    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.generateVisibleMessage();
 | 
						|
    const rawMessage = await MessageUtils.toRawMessage(
 | 
						|
      device,
 | 
						|
      message,
 | 
						|
      SnodeNamespaces.UserMessages
 | 
						|
    );
 | 
						|
 | 
						|
    await pendingMessageCacheStub.add(device, message, SnodeNamespaces.UserMessages);
 | 
						|
 | 
						|
    const finalCache = await 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.generateVisibleMessage(),
 | 
						|
      },
 | 
						|
      {
 | 
						|
        device: TestUtils.generateFakePubKey(),
 | 
						|
        message: TestUtils.generateVisibleMessage(),
 | 
						|
      },
 | 
						|
      {
 | 
						|
        device: TestUtils.generateFakePubKey(),
 | 
						|
        message: TestUtils.generateVisibleMessage(),
 | 
						|
      },
 | 
						|
    ];
 | 
						|
 | 
						|
    for (const item of cacheItems) {
 | 
						|
      await pendingMessageCacheStub.add(item.device, item.message, SnodeNamespaces.UserMessages);
 | 
						|
    }
 | 
						|
 | 
						|
    const initialCache = await pendingMessageCacheStub.getAllPending();
 | 
						|
    expect(initialCache).to.have.length(cacheItems.length);
 | 
						|
 | 
						|
    // Clear cache
 | 
						|
    await pendingMessageCacheStub.clear();
 | 
						|
 | 
						|
    const finalCache = await pendingMessageCacheStub.getAllPending();
 | 
						|
    expect(finalCache).to.have.length(0);
 | 
						|
  });
 | 
						|
 | 
						|
  it('can restore from db', async () => {
 | 
						|
    const cacheItems = [
 | 
						|
      {
 | 
						|
        device: TestUtils.generateFakePubKey(),
 | 
						|
        message: TestUtils.generateVisibleMessage(),
 | 
						|
      },
 | 
						|
      {
 | 
						|
        device: TestUtils.generateFakePubKey(),
 | 
						|
        message: TestUtils.generateVisibleMessage(),
 | 
						|
      },
 | 
						|
      {
 | 
						|
        device: TestUtils.generateFakePubKey(),
 | 
						|
        message: TestUtils.generateVisibleMessage(),
 | 
						|
      },
 | 
						|
    ];
 | 
						|
 | 
						|
    for (const item of cacheItems) {
 | 
						|
      await pendingMessageCacheStub.add(item.device, item.message, SnodeNamespaces.UserMessages);
 | 
						|
    }
 | 
						|
 | 
						|
    const addedMessages = await pendingMessageCacheStub.getAllPending();
 | 
						|
    expect(addedMessages).to.have.length(cacheItems.length);
 | 
						|
 | 
						|
    // Rebuild from DB
 | 
						|
    const freshCache = new PendingMessageCache();
 | 
						|
 | 
						|
    // Verify messages
 | 
						|
    const rebuiltMessages = await freshCache.getAllPending();
 | 
						|
 | 
						|
    for (const [index, message] of rebuiltMessages.entries()) {
 | 
						|
      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'
 | 
						|
      );
 | 
						|
    }
 | 
						|
  });
 | 
						|
});
 |