import { expect } from 'chai'; import * as sinon from 'sinon'; import { GroupUtils } from '../../../session/utils'; import { Stubs, TestUtils } from '../../../test/test-utils'; import { MessageQueue } from '../../../session/sending/MessageQueue'; import { OpenGroupMessage } from '../../../session/messages/outgoing'; import { PubKey, RawMessage } from '../../../session/types'; import { UserUtil } from '../../../util'; import { MessageSender } from '../../../session/sending'; import { toRawMessage } from '../../../session/utils/Messages'; import { SessionProtocol } from '../../../session/protocols'; import { PendingMessageCache } from '../../../session/sending/PendingMessageCache'; // Equivalent to Data.StorageItem interface StorageItem { id: string; value: any; } describe('MessageQueue', () => { // Initialize new stubbed cache let data: StorageItem; const sandbox = sinon.createSandbox(); const ourNumber = TestUtils.generateFakePubkey().key; // Initialize new stubbed cache let messageQueueStub: MessageQueue; // Message Sender Stubs let sendStub: sinon.SinonStub<[RawMessage, (number | undefined)?]>; let sendToOpenGroupStub: sinon.SinonStub<[OpenGroupMessage]>; // Group Utils Stubs let groupMembersStub: sinon.SinonStub; // Session Protocol Stubs let hasSessionStub: sinon.SinonStub<[PubKey]>; let sendSessionRequestIfNeededStub: sinon.SinonStub<[PubKey]>; let sessionRequestCalled: boolean; beforeEach(async () => { // Stub out methods which touch the database const storageID = 'pendingMessages'; data = { id: storageID, value: '[]', }; // Pending Message Cache Data Stubs TestUtils.stubData('getItemById') .withArgs('pendingMessages') .callsFake(async () => { return data; }); TestUtils.stubData('createOrUpdateItem').callsFake((item: StorageItem) => { if (item.id === storageID) { data = item; } }); // Utils Stubs sandbox.stub(UserUtil, 'getCurrentDevicePubKey').resolves(ourNumber); TestUtils.stubData('getPairedDevicesFor').callsFake(async () => { return TestUtils.generateMemberList(2); }); TestUtils.stubWindow('libsignal', { SignalProtocolAddress: sandbox.stub(), SessionCipher: Stubs.SessionCipherStub, } as any); // Message Sender Stubs sendStub = sandbox.stub(MessageSender, 'send').resolves(); sendToOpenGroupStub = sandbox .stub(MessageSender, 'sendToOpenGroup') .resolves(true); // Group Utils Stubs sandbox .stub(GroupUtils, 'isMediumGroup') .returns(false); groupMembersStub = sandbox .stub(GroupUtils, 'getGroupMembers' as any) .callsFake(async () => TestUtils.generateMemberList(10)); // Session Protocol Stubs // sessionRequestCalled used instead of sendSessionRequestIfNeededStub.callCount because the call couunt was not registered from methods inside the stubbed MessageQueue. sendSessionRequestIfNeededStub = sandbox.stub(SessionProtocol, 'sendSessionRequestIfNeeded').callsFake(async (pubkey: PubKey) => { pubkey; sessionRequestCalled = true; }); sandbox.stub(SessionProtocol, 'sendSessionRequest').resolves(); hasSessionStub = sandbox.stub(SessionProtocol, 'hasSession').resolves(true); // Pending Mesage Cache Stubs const chatMessages = Array.from({ length: 10 }, TestUtils.generateChatMessage); const rawMessage = toRawMessage( TestUtils.generateFakePubkey(), TestUtils.generateChatMessage() ); sandbox.stub(PendingMessageCache.prototype, 'add').resolves(rawMessage); sandbox.stub(PendingMessageCache.prototype, 'remove').resolves(); sandbox .stub(PendingMessageCache.prototype, 'getDevices') .returns(TestUtils.generateMemberList(10)); sandbox .stub(PendingMessageCache.prototype, 'getForDevice') .returns(chatMessages.map(m => toRawMessage(TestUtils.generateFakePubkey(), m))); messageQueueStub = new MessageQueue(); }); afterEach(() => { sessionRequestCalled = false; TestUtils.restoreStubs(); sandbox.restore(); }); describe('send', () => { }) it('can send to a single device', async () => { const device = TestUtils.generateFakePubkey(); const message = TestUtils.generateChatMessage(); const promise = messageQueueStub.send(device, message); await expect(promise).to.be.fulfilled; }); it('can send to many devices', async () => { const devices = TestUtils.generateMemberList(10); const message = TestUtils.generateChatMessage(); const promise = messageQueueStub.sendMessageToDevices(devices, message); await expect(promise).to.be.fulfilled; }); it('can send using multidevice', async () => { const device = TestUtils.generateFakePubkey(); const message = TestUtils.generateChatMessage(); const promise = messageQueueStub.sendUsingMultiDevice(device, message); await expect(promise).to.be.fulfilled; }); it('can send to open group', async () => { const message = TestUtils.generateOpenGroupMessage(); const success = await messageQueueStub.sendToGroup(message); expect(success).to.equal(true, 'sending to group failed'); }); it('can send to closed group', async () => { const message = TestUtils.generateClosedGroupMessage(); const success = await messageQueueStub.sendToGroup(message); expect(success).to.equal(true, 'sending to group failed'); }); it('wont send message to empty closed group', async () => { groupMembersStub.callsFake(async () => TestUtils.generateMemberList(0)); const message = TestUtils.generateClosedGroupMessage(); const response = await messageQueueStub.sendToGroup(message); expect(response).to.equal( false, 'sendToGroup send a message to an empty group' ); }); it('wont send invalid message type to group', async () => { // Regular chat message should return false const message = TestUtils.generateChatMessage(); const response = await messageQueueStub.sendToGroup(message); expect(response).to.equal( false, 'sendToGroup considered an invalid message type as valid' ); }); it('will send sync message if no session', async () => { hasSessionStub.resolves(false); const device = TestUtils.generateFakePubkey(); const promise = messageQueueStub.processPending(device); expect(promise).to.be.fulfilled; expect(sessionRequestCalled).to.equal(true, 'Session request not sent for !isMediumGroup && !hasSession'); }); it('can send sync message', async () => { const devices = TestUtils.generateMemberList(3); const message = TestUtils.generateChatMessage(); const promise = messageQueueStub.sendSyncMessage(message, devices); expect(promise).to.be.fulfilled; }); });