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'
 | |
|       );
 | |
|     }
 | |
|   });
 | |
| });
 |