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.
		
		
		
		
		
			
		
			
				
	
	
		
			290 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			TypeScript
		
	
			
		
		
	
	
			290 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			TypeScript
		
	
/* eslint-disable consistent-return */
 | 
						|
/* eslint-disable no-unused-expressions */
 | 
						|
/* eslint-disable more/no-then */
 | 
						|
/* eslint-disable no-loop-func */
 | 
						|
/* eslint-disable @typescript-eslint/no-floating-promises */
 | 
						|
/* eslint-disable no-await-in-loop */
 | 
						|
/* eslint-disable no-unreachable-loop */
 | 
						|
/* eslint-disable no-restricted-syntax */
 | 
						|
import { randomBytes } from 'crypto';
 | 
						|
 | 
						|
import chai from 'chai';
 | 
						|
import chaiAsPromised from 'chai-as-promised';
 | 
						|
import { describe } from 'mocha';
 | 
						|
import Sinon, * as sinon from 'sinon';
 | 
						|
 | 
						|
import { ContentMessage } from '../../../../session/messages/outgoing';
 | 
						|
import { ClosedGroupMessage } from '../../../../session/messages/outgoing/controlMessage/group/ClosedGroupMessage';
 | 
						|
import { MessageSender } from '../../../../session/sending';
 | 
						|
import { MessageQueue } from '../../../../session/sending/MessageQueue';
 | 
						|
import { PubKey } from '../../../../session/types';
 | 
						|
import { GroupUtils, PromiseUtils, UserUtils } from '../../../../session/utils';
 | 
						|
import { TestUtils } from '../../../test-utils';
 | 
						|
import { PendingMessageCacheStub } from '../../../test-utils/stubs';
 | 
						|
 | 
						|
import { SnodeNamespaces } from '../../../../session/apis/snode_api/namespaces';
 | 
						|
import { MessageSentHandler } from '../../../../session/sending/MessageSentHandler';
 | 
						|
import { stubData } from '../../../test-utils/utils';
 | 
						|
 | 
						|
chai.use(chaiAsPromised as any);
 | 
						|
chai.should();
 | 
						|
 | 
						|
const { expect } = chai;
 | 
						|
 | 
						|
describe('MessageQueue', () => {
 | 
						|
  // Initialize new stubbed cache
 | 
						|
  const ourDevice = TestUtils.generateFakePubKey();
 | 
						|
  const ourNumber = ourDevice.key;
 | 
						|
 | 
						|
  // Initialize new stubbed queue
 | 
						|
  let pendingMessageCache: PendingMessageCacheStub;
 | 
						|
  let messageSentHandlerFailedStub: sinon.SinonStub;
 | 
						|
  let messageSentHandlerSuccessStub: sinon.SinonStub;
 | 
						|
  let messageSentPublicHandlerSuccessStub: sinon.SinonStub;
 | 
						|
  let messageQueueStub: MessageQueue;
 | 
						|
 | 
						|
  // Message Sender Stubs
 | 
						|
  let sendStub: sinon.SinonStub;
 | 
						|
 | 
						|
  beforeEach(() => {
 | 
						|
    // Utils Stubs
 | 
						|
    Sinon.stub(UserUtils, 'getOurPubKeyStrFromCache').returns(ourNumber);
 | 
						|
 | 
						|
    // Message Sender Stubs
 | 
						|
    sendStub = Sinon.stub(MessageSender, 'send');
 | 
						|
    messageSentHandlerFailedStub = Sinon.stub(
 | 
						|
      MessageSentHandler,
 | 
						|
      'handleMessageSentFailure'
 | 
						|
    ).resolves();
 | 
						|
    messageSentHandlerSuccessStub = Sinon.stub(
 | 
						|
      MessageSentHandler,
 | 
						|
      'handleMessageSentSuccess'
 | 
						|
    ).resolves();
 | 
						|
    messageSentPublicHandlerSuccessStub = Sinon.stub(
 | 
						|
      MessageSentHandler,
 | 
						|
      'handlePublicMessageSentSuccess'
 | 
						|
    ).resolves();
 | 
						|
 | 
						|
    // Init Queue
 | 
						|
    pendingMessageCache = new PendingMessageCacheStub();
 | 
						|
    messageQueueStub = new MessageQueue(pendingMessageCache);
 | 
						|
    TestUtils.stubWindowLog();
 | 
						|
  });
 | 
						|
 | 
						|
  afterEach(() => {
 | 
						|
    Sinon.restore();
 | 
						|
  });
 | 
						|
 | 
						|
  describe('processPending', () => {
 | 
						|
    it('will send messages', done => {
 | 
						|
      const device = TestUtils.generateFakePubKey();
 | 
						|
 | 
						|
      const waitForMessageSentEvent = new Promise(resolve => {
 | 
						|
        resolve(true);
 | 
						|
        done();
 | 
						|
      });
 | 
						|
 | 
						|
      void pendingMessageCache
 | 
						|
        .add(device, TestUtils.generateVisibleMessage(), waitForMessageSentEvent as any)
 | 
						|
        .then(async () => {
 | 
						|
          return messageQueueStub.processPending(device);
 | 
						|
        })
 | 
						|
        .then(() => {
 | 
						|
          expect(waitForMessageSentEvent).to.be.fulfilled;
 | 
						|
        });
 | 
						|
    });
 | 
						|
 | 
						|
    it('should remove message from cache', async () => {
 | 
						|
      const events = ['sendSuccess', 'sendFail'];
 | 
						|
      for (const event of events) {
 | 
						|
        if (event === 'sendSuccess') {
 | 
						|
          sendStub.resolves();
 | 
						|
        } else {
 | 
						|
          sendStub.throws(new Error('fail'));
 | 
						|
        }
 | 
						|
 | 
						|
        const device = TestUtils.generateFakePubKey();
 | 
						|
        await pendingMessageCache.add(
 | 
						|
          device,
 | 
						|
          TestUtils.generateVisibleMessage(),
 | 
						|
          SnodeNamespaces.UserMessages
 | 
						|
        );
 | 
						|
 | 
						|
        const initialMessages = await pendingMessageCache.getForDevice(device);
 | 
						|
        expect(initialMessages).to.have.length(1);
 | 
						|
        await messageQueueStub.processPending(device);
 | 
						|
 | 
						|
        const promise = PromiseUtils.waitUntil(async () => {
 | 
						|
          const messages = await pendingMessageCache.getForDevice(device);
 | 
						|
          return messages.length === 0;
 | 
						|
        }, 100);
 | 
						|
        return promise.should.be.fulfilled;
 | 
						|
      }
 | 
						|
    }).timeout(15000);
 | 
						|
 | 
						|
    describe('events', () => {
 | 
						|
      it('should send a success event if message was sent', done => {
 | 
						|
        stubData('getMessageById').resolves();
 | 
						|
        const message = TestUtils.generateVisibleMessage();
 | 
						|
 | 
						|
        sendStub.resolves({ effectiveTimestamp: Date.now(), wrappedEnvelope: randomBytes(10) });
 | 
						|
        const device = TestUtils.generateFakePubKey();
 | 
						|
        Sinon.stub(MessageSender, 'getMinRetryTimeout').returns(10);
 | 
						|
        const waitForMessageSentEvent = async () =>
 | 
						|
          new Promise<void>(resolve => {
 | 
						|
            resolve();
 | 
						|
            try {
 | 
						|
              expect(messageSentHandlerSuccessStub.callCount).to.be.equal(1);
 | 
						|
              expect(messageSentHandlerSuccessStub.lastCall.args[0].identifier).to.be.equal(
 | 
						|
                message.identifier
 | 
						|
              );
 | 
						|
              done();
 | 
						|
            } catch (e) {
 | 
						|
              done(e);
 | 
						|
            }
 | 
						|
          });
 | 
						|
 | 
						|
        void pendingMessageCache
 | 
						|
          .add(device, message, SnodeNamespaces.UserMessages, waitForMessageSentEvent)
 | 
						|
          .then(() => messageQueueStub.processPending(device));
 | 
						|
      });
 | 
						|
 | 
						|
      it('should send a fail event if something went wrong while sending', async () => {
 | 
						|
        sendStub.throws(new Error('failure'));
 | 
						|
 | 
						|
        const device = TestUtils.generateFakePubKey();
 | 
						|
        const message = TestUtils.generateVisibleMessage();
 | 
						|
        void pendingMessageCache
 | 
						|
          .add(device, message, SnodeNamespaces.UserMessages)
 | 
						|
          .then(() => messageQueueStub.processPending(device));
 | 
						|
        // The cb is only invoke is all reties fails. Here we poll until the messageSentHandlerFailed was invoked as this is what we want to do
 | 
						|
 | 
						|
        return PromiseUtils.poll(done => {
 | 
						|
          if (messageSentHandlerFailedStub.callCount === 1) {
 | 
						|
            try {
 | 
						|
              expect(messageSentHandlerFailedStub.callCount).to.be.equal(1);
 | 
						|
              expect(messageSentHandlerFailedStub.lastCall.args[0].identifier).to.be.equal(
 | 
						|
                message.identifier
 | 
						|
              );
 | 
						|
              expect(messageSentHandlerFailedStub.lastCall.args[1].message).to.equal('failure');
 | 
						|
              done();
 | 
						|
            } catch (e) {
 | 
						|
              done(e);
 | 
						|
            }
 | 
						|
          }
 | 
						|
        });
 | 
						|
      });
 | 
						|
    });
 | 
						|
  });
 | 
						|
 | 
						|
  describe('sendToPubKey', () => {
 | 
						|
    it('should send the message to the device', async () => {
 | 
						|
      const device = TestUtils.generateFakePubKey();
 | 
						|
      const stub = Sinon.stub(messageQueueStub as any, 'process').resolves();
 | 
						|
 | 
						|
      const message = TestUtils.generateVisibleMessage();
 | 
						|
      await messageQueueStub.sendToPubKey(device, message, SnodeNamespaces.UserMessages);
 | 
						|
 | 
						|
      const args = stub.lastCall.args as [Array<PubKey>, ContentMessage];
 | 
						|
      expect(args[0]).to.be.equal(device);
 | 
						|
      expect(args[1]).to.equal(message);
 | 
						|
    });
 | 
						|
  });
 | 
						|
 | 
						|
  describe('sendToGroup', () => {
 | 
						|
    it('should throw an error if invalid non-group message was passed', async () => {
 | 
						|
      const chatMessage = TestUtils.generateVisibleMessage();
 | 
						|
      return expect(
 | 
						|
        messageQueueStub.sendToGroup({
 | 
						|
          message: chatMessage as any,
 | 
						|
          namespace: SnodeNamespaces.ClosedGroupMessage,
 | 
						|
        })
 | 
						|
      ).to.be.rejectedWith('Invalid group message passed in sendToGroup.');
 | 
						|
    });
 | 
						|
 | 
						|
    describe('closed groups', () => {
 | 
						|
      it('can send to closed group', async () => {
 | 
						|
        const members = TestUtils.generateFakePubKeys(4);
 | 
						|
        Sinon.stub(GroupUtils, 'getGroupMembers').returns(members);
 | 
						|
 | 
						|
        const send = Sinon.stub(messageQueueStub, 'sendToPubKey').resolves();
 | 
						|
 | 
						|
        const message = TestUtils.generateClosedGroupMessage();
 | 
						|
        await messageQueueStub.sendToGroup({
 | 
						|
          message,
 | 
						|
          namespace: SnodeNamespaces.ClosedGroupMessage,
 | 
						|
        });
 | 
						|
        expect(send.callCount).to.equal(1);
 | 
						|
 | 
						|
        const arg = send.getCall(0).args;
 | 
						|
        expect(arg[1] instanceof ClosedGroupMessage).to.equal(
 | 
						|
          true,
 | 
						|
          'message sent to group member was not a ClosedGroupMessage'
 | 
						|
        );
 | 
						|
      });
 | 
						|
 | 
						|
      describe('open groupsv2', () => {
 | 
						|
        let sendToOpenGroupV2Stub: sinon.SinonStub;
 | 
						|
        beforeEach(() => {
 | 
						|
          sendToOpenGroupV2Stub = Sinon.stub(MessageSender, 'sendToOpenGroupV2').resolves(
 | 
						|
            TestUtils.generateOpenGroupMessageV2()
 | 
						|
          );
 | 
						|
        });
 | 
						|
 | 
						|
        it('can send to open group', async () => {
 | 
						|
          const message = TestUtils.generateOpenGroupVisibleMessage();
 | 
						|
          const roomInfos = TestUtils.generateOpenGroupV2RoomInfos();
 | 
						|
 | 
						|
          await messageQueueStub.sendToOpenGroupV2({
 | 
						|
            message,
 | 
						|
            roomInfos,
 | 
						|
            blinded: false,
 | 
						|
            filesToLink: [],
 | 
						|
          });
 | 
						|
          expect(sendToOpenGroupV2Stub.callCount).to.equal(1);
 | 
						|
        });
 | 
						|
 | 
						|
        it('should emit a success event when send was successful', async () => {
 | 
						|
          sendToOpenGroupV2Stub.resolves({
 | 
						|
            serverId: 5125,
 | 
						|
            sentTimestamp: 5127,
 | 
						|
          });
 | 
						|
 | 
						|
          const message = TestUtils.generateOpenGroupVisibleMessage();
 | 
						|
          const roomInfos = TestUtils.generateOpenGroupV2RoomInfos();
 | 
						|
          await messageQueueStub.sendToOpenGroupV2({
 | 
						|
            message,
 | 
						|
            roomInfos,
 | 
						|
            blinded: false,
 | 
						|
            filesToLink: [],
 | 
						|
          });
 | 
						|
 | 
						|
          expect(messageSentPublicHandlerSuccessStub.callCount).to.equal(1);
 | 
						|
          expect(messageSentPublicHandlerSuccessStub.lastCall.args[0]).to.equal(message.identifier);
 | 
						|
          expect(messageSentPublicHandlerSuccessStub.lastCall.args[1].serverId).to.equal(5125);
 | 
						|
          expect(messageSentPublicHandlerSuccessStub.lastCall.args[1].serverTimestamp).to.equal(
 | 
						|
            5127
 | 
						|
          );
 | 
						|
        });
 | 
						|
 | 
						|
        it('should emit a fail event if something went wrong', async () => {
 | 
						|
          sendToOpenGroupV2Stub.resolves({ serverId: -1, serverTimestamp: -1 });
 | 
						|
          const message = TestUtils.generateOpenGroupVisibleMessage();
 | 
						|
          const roomInfos = TestUtils.generateOpenGroupV2RoomInfos();
 | 
						|
 | 
						|
          await messageQueueStub.sendToOpenGroupV2({
 | 
						|
            message,
 | 
						|
            roomInfos,
 | 
						|
            blinded: false,
 | 
						|
            filesToLink: [],
 | 
						|
          });
 | 
						|
          expect(messageSentHandlerFailedStub.callCount).to.equal(1);
 | 
						|
          expect(messageSentHandlerFailedStub.lastCall.args[0].identifier).to.equal(
 | 
						|
            message.identifier
 | 
						|
          );
 | 
						|
        });
 | 
						|
      });
 | 
						|
    });
 | 
						|
  });
 | 
						|
});
 |