From 7eb6cec78513249fb6515afb7fd9bce8a0760357 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Mon, 18 Nov 2024 11:11:03 +1100 Subject: [PATCH] fix: fix unit test with groups --- ts/session/sending/MessageSender.ts | 142 +-------- ts/session/sending/MessageWrapper.ts | 128 +++++++- .../jobs/GroupPendingRemovalsJob.ts | 2 +- ts/state/selectors/groups.ts | 3 + .../libsession_wrapper_metagroup_test.ts | 84 ++++-- .../session/unit/sending/MessageQueue_test.ts | 31 +- .../snode_api/retrieveNextMessages_test.ts | 7 +- .../unit/swarm_polling/SwarmPolling_test.ts | 277 +----------------- .../group_sync_job/GroupSyncJob_test.ts | 61 ++-- .../user_sync_job/UserSyncJob_test.ts | 30 +- ts/test/test-utils/utils/pubkey.ts | 2 +- 11 files changed, 262 insertions(+), 505 deletions(-) diff --git a/ts/session/sending/MessageSender.ts b/ts/session/sending/MessageSender.ts index 1143c907c..47c035224 100644 --- a/ts/session/sending/MessageSender.ts +++ b/ts/session/sending/MessageSender.ts @@ -5,7 +5,6 @@ import { GroupPubkeyType, PubkeyType } from 'libsession_util_nodejs'; import { isArray, isEmpty, isNumber, isString } from 'lodash'; import pRetry from 'p-retry'; import { Data, SeenMessageHashes } from '../../data/data'; -import { SignalService } from '../../protobuf'; import { UserGroupsWrapperActions } from '../../webworker/workers/browser/libsession_worker_interface'; import { OpenGroupMessageV2 } from '../apis/open_group_api/opengroupV2/OpenGroupMessageV2'; import { @@ -44,7 +43,6 @@ import { SnodePool } from '../apis/snode_api/snodePool'; import { TTL_DEFAULT } from '../constants'; import { ConvoHub } from '../conversations'; import { addMessagePadding } from '../crypto/BufferPadding'; -import { MessageEncrypter } from '../crypto/MessageEncrypter'; import { ContentMessage } from '../messages/outgoing'; import { UnsendMessage } from '../messages/outgoing/controlMessage/UnsendMessage'; import { ClosedGroupNewMessage } from '../messages/outgoing/controlMessage/group/ClosedGroupNewMessage'; @@ -54,7 +52,7 @@ import { OutgoingRawMessage } from '../types/RawMessage'; import { UserUtils } from '../utils'; import { ed25519Str, fromUInt8ArrayToBase64 } from '../utils/String'; import { MessageSentHandler } from './MessageSentHandler'; -import { MessageWrapper } from './MessageWrapper'; +import { EncryptAndWrapMessageResults, MessageWrapper } from './MessageWrapper'; import { stringify } from '../../types/sqlSharedTypes'; import { OpenGroupRequestCommonType } from '../../data/types'; import { NetworkTime } from '../../util/NetworkTime'; @@ -246,9 +244,8 @@ async function sendSingleMessage({ return pRetry( async () => { const recipient = PubKey.cast(message.device); - // we can only have a single message in this send function for now - const [encryptedAndWrapped] = await encryptMessagesAndWrap([ + const [encryptedAndWrapped] = await MessageWrapper.encryptMessagesAndWrap([ { destination: message.device, plainTextBuffer: message.plainTextBuffer, @@ -260,6 +257,7 @@ async function sendSingleMessage({ }, ]); + // make sure to update the local sent_at timestamp, because sometimes, we will get the just pushed message in the receiver side // before we return from the await below. // and the isDuplicate messages relies on sent_at timestamp to be valid. @@ -297,9 +295,7 @@ async function sendSingleMessage({ destination, false ); - await handleBatchResultWithSubRequests({ batchResult, subRequests, destination }); - return { wrappedEnvelope: encryptedAndWrapped.encryptedAndWrappedData, effectiveTimestamp: encryptedAndWrapped.networkTimestamp, @@ -309,6 +305,7 @@ async function sendSingleMessage({ retries: Math.max(attempts - 1, 0), factor: 1, minTimeout: retryMinTimeout || MessageSender.getMinRetryTimeout(), + } ); } @@ -486,125 +483,6 @@ async function sendMessagesDataToSnode({ } } -function encryptionBasedOnConversation(destination: PubKey) { - if (ConvoHub.use().get(destination.key)?.isClosedGroup()) { - return SignalService.Envelope.Type.CLOSED_GROUP_MESSAGE; - } - return SignalService.Envelope.Type.SESSION_MESSAGE; -} - -type SharedEncryptAndWrap = { - ttl: number; - identifier: string; - isSyncMessage: boolean; - plainTextBuffer: Uint8Array; -}; - -type EncryptAndWrapMessage = { - destination: string; - namespace: number; - networkTimestamp: number; -} & SharedEncryptAndWrap; - -type EncryptAndWrapMessageResults = { - networkTimestamp: number; - encryptedAndWrappedData: Uint8Array; - namespace: number; -} & SharedEncryptAndWrap; - -async function encryptForGroupV2( - params: EncryptAndWrapMessage -): Promise { - // Group v2 encryption works a bit differently: we encrypt the envelope itself through libsession. - // We essentially need to do the opposite of the usual encryption which is send envelope unencrypted with content encrypted. - const { - destination, - identifier, - isSyncMessage: syncMessage, - namespace, - plainTextBuffer, - ttl, - networkTimestamp, - } = params; - - const envelope = MessageWrapper.wrapContentIntoEnvelope( - SignalService.Envelope.Type.CLOSED_GROUP_MESSAGE, - destination, - networkTimestamp, - plainTextBuffer - ); - - const recipient = PubKey.cast(destination); - - const { cipherText } = await MessageEncrypter.encrypt( - recipient, - SignalService.Envelope.encode(envelope).finish(), - encryptionBasedOnConversation(recipient) - ); - - return { - networkTimestamp, - encryptedAndWrappedData: cipherText, - namespace, - ttl, - identifier, - isSyncMessage: syncMessage, - plainTextBuffer, - }; -} - -async function encryptMessageAndWrap( - params: EncryptAndWrapMessage -): Promise { - const { - destination, - identifier, - isSyncMessage: syncMessage, - namespace, - plainTextBuffer, - ttl, - networkTimestamp, - } = params; - - if (PubKey.is03Pubkey(destination)) { - return encryptForGroupV2(params); - } - - // can only be legacy group or 1o1 chats here - - const recipient = PubKey.cast(destination); - - const { envelopeType, cipherText } = await MessageEncrypter.encrypt( - recipient, - plainTextBuffer, - encryptionBasedOnConversation(recipient) - ); - - const envelope = MessageWrapper.wrapContentIntoEnvelope( - envelopeType, - recipient.key, - networkTimestamp, - cipherText - ); - const data = MessageWrapper.wrapEnvelopeInWebSocketMessage(envelope); - - return { - encryptedAndWrappedData: data, - networkTimestamp, - namespace, - ttl, - identifier, - isSyncMessage: syncMessage, - plainTextBuffer, - }; -} - -async function encryptMessagesAndWrap( - messages: Array -): Promise> { - return Promise.all(messages.map(encryptMessageAndWrap)); -} - /** * Send an array of pre-encrypted data to the corresponding swarm. * Note: also handles the result of each sub requests with `handleBatchResultWithSubRequests` @@ -714,10 +592,14 @@ export const MessageSender = { isContentSyncMessage, getSignatureParamsFromNamespace, signSubRequests, - encryptMessagesAndWrap, messagesToRequests, + destinationIsClosedGroup, }; +function destinationIsClosedGroup(destination: string) { + return ConvoHub.use().get(destination)?.isClosedGroup(); +} + /** * Note: this function does not handle the syncing logic of messages yet. * Use it to push message to group, to note to self, or with user messages which do not require a syncing logic @@ -731,13 +613,13 @@ async function handleBatchResultWithSubRequests({ subRequests: Array; destination: string; }) { - const isDestinationClosedGroup = ConvoHub.use().get(destination)?.isClosedGroup(); if (!batchResult || !isArray(batchResult) || isEmpty(batchResult)) { window.log.error('handleBatchResultWithSubRequests: invalid batch result '); return; } const seenHashes: Array = []; + for (let index = 0; index < subRequests.length; index++) { const subRequest = subRequests[index]; @@ -753,13 +635,13 @@ async function handleBatchResultWithSubRequests({ const subRequestStatusCode = batchResult?.[index]?.code; // TODO: the expiration is due to be returned by the storage server on "store" soon, we will then be able to use it instead of doing the storedAt + ttl logic below // if we have a hash and a storedAt, mark it as seen so we don't reprocess it on the next retrieve - if ( subRequestStatusCode === 200 && !isEmpty(storedHash) && isString(storedHash) && isNumber(storedAt) ) { + seenHashes.push({ expiresAt: NetworkTime.now() + TTL_DEFAULT.CONTENT_MESSAGE, // non config msg expire at CONTENT_MESSAGE at most hash: storedHash, @@ -772,7 +654,7 @@ async function handleBatchResultWithSubRequests({ await MessageSentHandler.handleSwarmMessageSentSuccess( { device: subRequest.destination, - isDestinationClosedGroup, + isDestinationClosedGroup: MessageSender.destinationIsClosedGroup(destination), identifier: subRequest.dbMessageIdentifier, plainTextBuffer: subRequest instanceof StoreUserMessageSubRequest diff --git a/ts/session/sending/MessageWrapper.ts b/ts/session/sending/MessageWrapper.ts index 35bb540ca..f519b478f 100644 --- a/ts/session/sending/MessageWrapper.ts +++ b/ts/session/sending/MessageWrapper.ts @@ -1,4 +1,74 @@ import { SignalService } from '../../protobuf'; +import { ConvoHub } from '../conversations'; +import { MessageEncrypter } from '../crypto/MessageEncrypter'; +import { PubKey } from '../types'; + +function encryptionBasedOnConversation(destination: PubKey) { + if (ConvoHub.use().get(destination.key)?.isClosedGroup()) { + return SignalService.Envelope.Type.CLOSED_GROUP_MESSAGE; + } + return SignalService.Envelope.Type.SESSION_MESSAGE; +} + +type SharedEncryptAndWrap = { + ttl: number; + identifier: string; + isSyncMessage: boolean; + plainTextBuffer: Uint8Array; +}; + +type EncryptAndWrapMessage = { + destination: string; + namespace: number; + networkTimestamp: number; +} & SharedEncryptAndWrap; + +export type EncryptAndWrapMessageResults = { + networkTimestamp: number; + encryptedAndWrappedData: Uint8Array; + namespace: number; +} & SharedEncryptAndWrap; + +async function encryptForGroupV2( + params: EncryptAndWrapMessage +): Promise { + // Group v2 encryption works a bit differently: we encrypt the envelope itself through libsession. + // We essentially need to do the opposite of the usual encryption which is send envelope unencrypted with content encrypted. + const { + destination, + identifier, + isSyncMessage: syncMessage, + namespace, + plainTextBuffer, + ttl, + networkTimestamp, + } = params; + + const envelope = MessageWrapper.wrapContentIntoEnvelope( + SignalService.Envelope.Type.CLOSED_GROUP_MESSAGE, + destination, + networkTimestamp, + plainTextBuffer + ); + + const recipient = PubKey.cast(destination); + + const { cipherText } = await MessageEncrypter.encrypt( + recipient, + SignalService.Envelope.encode(envelope).finish(), + encryptionBasedOnConversation(recipient) + ); + + return { + networkTimestamp, + encryptedAndWrappedData: cipherText, + namespace, + ttl, + identifier, + isSyncMessage: syncMessage, + plainTextBuffer, + }; +} function wrapContentIntoEnvelope( type: SignalService.Envelope.Type, @@ -38,4 +108,60 @@ function wrapEnvelopeInWebSocketMessage(envelope: SignalService.Envelope): Uint8 return SignalService.WebSocketMessage.encode(websocket).finish(); } -export const MessageWrapper = { wrapEnvelopeInWebSocketMessage, wrapContentIntoEnvelope }; +async function encryptMessageAndWrap( + params: EncryptAndWrapMessage +): Promise { + const { + destination, + identifier, + isSyncMessage: syncMessage, + namespace, + plainTextBuffer, + ttl, + networkTimestamp, + } = params; + + if (PubKey.is03Pubkey(destination)) { + return encryptForGroupV2(params); + } + + // can only be legacy group or 1o1 chats here + + const recipient = PubKey.cast(destination); + + const { envelopeType, cipherText } = await MessageEncrypter.encrypt( + recipient, + plainTextBuffer, + encryptionBasedOnConversation(recipient) + ); + + const envelope = MessageWrapper.wrapContentIntoEnvelope( + envelopeType, + recipient.key, + networkTimestamp, + cipherText + ); + const data = MessageWrapper.wrapEnvelopeInWebSocketMessage(envelope); + + return { + encryptedAndWrappedData: data, + networkTimestamp, + namespace, + ttl, + identifier, + isSyncMessage: syncMessage, + plainTextBuffer, + }; +} + +async function encryptMessagesAndWrap( + messages: Array +): Promise> { + return Promise.all(messages.map(encryptMessageAndWrap)); +} + +export const MessageWrapper = { + wrapEnvelopeInWebSocketMessage, + wrapContentIntoEnvelope, + encryptMessagesAndWrap, +}; diff --git a/ts/session/utils/job_runners/jobs/GroupPendingRemovalsJob.ts b/ts/session/utils/job_runners/jobs/GroupPendingRemovalsJob.ts index 08ee513fb..7005d9c88 100644 --- a/ts/session/utils/job_runners/jobs/GroupPendingRemovalsJob.ts +++ b/ts/session/utils/job_runners/jobs/GroupPendingRemovalsJob.ts @@ -134,7 +134,7 @@ class GroupPendingRemovalsJob extends PersistedJob m.shouldRemoveMessages) + .filter(m => m.removedStatus === 'REMOVED_MEMBER_AND_MESSAGES') .map(m => m.pubkeyHex); const sessionIdsHex = pendingRemovals.map(m => m.pubkeyHex); diff --git a/ts/state/selectors/groups.ts b/ts/state/selectors/groups.ts index dc1f8a312..904adb457 100644 --- a/ts/state/selectors/groups.ts +++ b/ts/state/selectors/groups.ts @@ -321,6 +321,9 @@ export function useStateOf03GroupMembers(convoId?: string) { case 'INVITE_ACCEPTED': stateSortingOrder = 2; break; + case 'UNKNOWN': + stateSortingOrder = 5; // just a fallback, hopefully won't happen in production + break; default: assertUnreachable(item.memberStatus, 'Unhandled switch case'); diff --git a/ts/test/session/unit/libsession_wrapper/libsession_wrapper_metagroup_test.ts b/ts/test/session/unit/libsession_wrapper/libsession_wrapper_metagroup_test.ts index 830ff031b..a05c2bfe3 100644 --- a/ts/test/session/unit/libsession_wrapper/libsession_wrapper_metagroup_test.ts +++ b/ts/test/session/unit/libsession_wrapper/libsession_wrapper_metagroup_test.ts @@ -24,9 +24,8 @@ function emptyMember(pubkeyHex: PubkeyType): GroupMemberGet { key: null, url: null, }, - isRemoved: false, nominatedAdmin: false, - shouldRemoveMessages: false, + removedStatus: 'NOT_REMOVED', pubkeyHex, }; } @@ -157,7 +156,7 @@ describe('libsession_metagroup', () => { const memberCreated = metaGroupWrapper.memberGetOrConstruct(member); console.info('Object.keys(memberCreated) ', JSON.stringify(Object.keys(memberCreated))); expect(Object.keys(memberCreated).length).to.be.eq( - 10, // if you change this value, also make sure you add a test, testing that new field, below + 6, // if you change this value, also make sure you add a test, testing that new field, below 'this test is designed to fail if you need to add tests to test a new field of libsession' ); }); @@ -165,73 +164,85 @@ describe('libsession_metagroup', () => { it('can add member by setting its promoted state, both ok and nok', () => { metaGroupWrapper.memberConstructAndSet(member); metaGroupWrapper.memberSetPromotionSent(member); - expect(metaGroupWrapper.memberGetAll().length).to.be.deep.eq(1); - expect(metaGroupWrapper.memberGetAll()[0]).to.be.deep.eq({ - ...emptyMember(member), - promoted: true, - promotionPending: true, - promotionFailed: false, - admin: false, - }); - + expect(metaGroupWrapper.memberGetAll()).to.be.deep.eq([ + { + ...emptyMember(member), + nominatedAdmin: true, + memberStatus: 'PROMOTION_SENT', + }, + ]); + + metaGroupWrapper.memberConstructAndSet(member2); metaGroupWrapper.memberSetPromotionFailed(member2); expect(metaGroupWrapper.memberGetAll().length).to.be.deep.eq(2); // the list is sorted by member pk, which means that index based test do not work expect(metaGroupWrapper.memberGet(member2)).to.be.deep.eq({ ...emptyMember(member2), - promoted: true, - promotionFailed: true, - promotionPending: true, - admin: false, + nominatedAdmin: true, + memberStatus: 'PROMOTION_FAILED', }); // we test the admin: true case below }); it('can add member by setting its invited state, both ok and nok', () => { + metaGroupWrapper.memberConstructAndSet(member); + metaGroupWrapper.memberSetInvited(member, false); // with invite success expect(metaGroupWrapper.memberGetAll().length).to.be.deep.eq(1); - expect(metaGroupWrapper.memberGetAll()[0]).to.be.deep.eq({ - ...emptyMember(member), - invitePending: true, - inviteFailed: false, - }); + expect(metaGroupWrapper.memberGetAll()).to.be.deep.eq([ + { + ...emptyMember(member), + memberStatus: 'INVITE_SENT', + }, + ]); + + metaGroupWrapper.memberConstructAndSet(member2); metaGroupWrapper.memberSetInvited(member2, true); // with invite failed expect(metaGroupWrapper.memberGetAll().length).to.be.deep.eq(2); expect(metaGroupWrapper.memberGet(member2)).to.be.deep.eq({ ...emptyMember(member2), - invitePending: true, - inviteFailed: true, + memberStatus: 'INVITE_FAILED', }); }); it('can add member by setting its accepted state', () => { + metaGroupWrapper.memberConstructAndSet(member); + metaGroupWrapper.memberSetAccepted(member); expect(metaGroupWrapper.memberGetAll().length).to.be.deep.eq(1); expect(metaGroupWrapper.memberGetAll()[0]).to.be.deep.eq({ ...emptyMember(member), + memberStatus: 'INVITE_ACCEPTED', }); + metaGroupWrapper.memberConstructAndSet(member2); + metaGroupWrapper.memberSetAccepted(member2); expect(metaGroupWrapper.memberGetAll().length).to.be.deep.eq(2); expect(metaGroupWrapper.memberGet(member2)).to.be.deep.eq({ ...emptyMember(member2), + memberStatus: 'INVITE_ACCEPTED', }); }); it('can erase member', () => { + metaGroupWrapper.memberConstructAndSet(member); + metaGroupWrapper.memberConstructAndSet(member2); + metaGroupWrapper.memberSetAccepted(member); metaGroupWrapper.memberSetPromoted(member2); expect(metaGroupWrapper.memberGetAll().length).to.be.deep.eq(2); expect(metaGroupWrapper.memberGet(member)).to.be.deep.eq({ ...emptyMember(member), + memberStatus: 'INVITE_ACCEPTED', }); expect(metaGroupWrapper.memberGet(member2)).to.be.deep.eq({ ...emptyMember(member2), - promoted: true, - promotionPending: true, + memberStatus: 'PROMOTION_SENT', + nominatedAdmin: true, }); const rekeyed = metaGroupWrapper.memberEraseAndRekey([member2]); @@ -239,10 +250,12 @@ describe('libsession_metagroup', () => { expect(metaGroupWrapper.memberGetAll().length).to.be.deep.eq(1); expect(metaGroupWrapper.memberGetAll()[0]).to.be.deep.eq({ ...emptyMember(member), + memberStatus: 'INVITE_ACCEPTED', }); }); it('can add via name set', () => { + metaGroupWrapper.memberConstructAndSet(member); metaGroupWrapper.memberSetNameTruncated(member, 'member name'); expect(metaGroupWrapper.memberGetAll().length).to.be.deep.eq(1); expect(metaGroupWrapper.memberGetAll()[0]).to.be.deep.eq({ @@ -253,6 +266,7 @@ describe('libsession_metagroup', () => { it('can add via profile picture set', () => { const pic = profilePicture(); + metaGroupWrapper.memberConstructAndSet(member); metaGroupWrapper.memberSetProfilePicture(member, pic); expect(metaGroupWrapper.memberGetAll().length).to.be.deep.eq(1); const expected = { ...emptyMember(member), profilePicture: pic }; @@ -261,6 +275,7 @@ describe('libsession_metagroup', () => { }); it('can add via admin set', () => { + metaGroupWrapper.memberConstructAndSet(member); metaGroupWrapper.memberSetPromotionAccepted(member); expect(metaGroupWrapper.memberGetAll().length).to.be.deep.eq(1); const expected: GroupMemberGet = { @@ -272,28 +287,35 @@ describe('libsession_metagroup', () => { expect(metaGroupWrapper.memberGetAll()[0]).to.be.deep.eq(expected); }); + it('can simply add, and has the correct default', () => { + expect(metaGroupWrapper.memberGetAll().length).to.be.deep.eq(0); + metaGroupWrapper.memberConstructAndSet(member); + expect(metaGroupWrapper.memberGetAll()).to.be.deep.eq([emptyMember(member)]); + }); + it('can mark as removed with messages', () => { + metaGroupWrapper.memberConstructAndSet(member); metaGroupWrapper.membersMarkPendingRemoval([member], true); expect(metaGroupWrapper.memberGetAll().length).to.be.deep.eq(1); const expected: GroupMemberGet = { ...emptyMember(member), - shouldRemoveMessages: true, - isRemoved: true, + removedStatus: 'REMOVED_MEMBER_AND_MESSAGES', + memberStatus: 'INVITE_ACCEPTED', // marking a member as pending removal auto-marks him as accepted (so we don't retry sending an invite) }; expect(metaGroupWrapper.memberGetAll().length).to.be.deep.eq(1); expect(metaGroupWrapper.memberGetAll()[0]).to.be.deep.eq(expected); }); it('can mark as removed without messages', () => { + metaGroupWrapper.memberConstructAndSet(member); metaGroupWrapper.membersMarkPendingRemoval([member], false); expect(metaGroupWrapper.memberGetAll().length).to.be.deep.eq(1); const expected: GroupMemberGet = { ...emptyMember(member), - shouldRemoveMessages: false, - isRemoved: true, + removedStatus: 'REMOVED_MEMBER', + memberStatus: 'INVITE_ACCEPTED', // marking a member as pending removal auto-marks him as accepted (so we don't retry sending an invite) }; - expect(metaGroupWrapper.memberGetAll().length).to.be.deep.eq(1); - expect(metaGroupWrapper.memberGetAll()[0]).to.be.deep.eq(expected); + expect(metaGroupWrapper.memberGetAll()).to.be.deep.eq([expected]); }); }); diff --git a/ts/test/session/unit/sending/MessageQueue_test.ts b/ts/test/session/unit/sending/MessageQueue_test.ts index 6184b40e4..8d129e020 100644 --- a/ts/test/session/unit/sending/MessageQueue_test.ts +++ b/ts/test/session/unit/sending/MessageQueue_test.ts @@ -6,7 +6,6 @@ /* 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'; @@ -14,6 +13,7 @@ import { describe } from 'mocha'; import Sinon, * as sinon from 'sinon'; import { PubkeyType } from 'libsession_util_nodejs'; +import { randombytes_buf } from 'libsodium-wrappers-sumo'; import { ContentMessage } from '../../../../session/messages/outgoing'; import { ClosedGroupMessage } from '../../../../session/messages/outgoing/controlMessage/group/ClosedGroupMessage'; import { MessageSender } from '../../../../session/sending'; @@ -25,7 +25,10 @@ import { PendingMessageCacheStub } from '../../../test-utils/stubs'; import { SnodeNamespaces } from '../../../../session/apis/snode_api/namespaces'; import { MessageSentHandler } from '../../../../session/sending/MessageSentHandler'; -import { TypedStub, stubData } from '../../../test-utils/utils'; +import { TypedStub, generateFakeSnode, stubData } from '../../../test-utils/utils'; +import { MessageWrapper } from '../../../../session/sending/MessageWrapper'; +import { SnodePool } from '../../../../session/apis/snode_api/snodePool'; +import { BatchRequests } from '../../../../session/apis/snode_api/batchRequest'; chai.use(chaiAsPromised as any); chai.should(); @@ -144,11 +147,32 @@ describe('MessageQueue', () => { describe('events', () => { it('should send a success event if message was sent', done => { stubData('getMessageById').resolves(); + TestUtils.stubWindowLog(); const message = TestUtils.generateVisibleMessage(); - sendStub.resolves({ effectiveTimestamp: Date.now(), wrappedEnvelope: randomBytes(10) }); + sendStub.restore(); const device = TestUtils.generateFakePubKey(); + stubData('saveSeenMessageHashes').resolves(); Sinon.stub(MessageSender, 'getMinRetryTimeout').returns(10); + Sinon.stub(MessageSender, 'destinationIsClosedGroup').returns(false); + Sinon.stub(SnodePool, 'getNodeFromSwarmOrThrow').resolves(generateFakeSnode()); + Sinon.stub(BatchRequests, 'doUnsignedSnodeBatchRequestNoRetries').resolves([ + { + body: { t: message.createAtNetworkTimestamp, hash: 'whatever', code: 200 }, + code: 200, + }, + ]); + Sinon.stub(MessageWrapper, 'encryptMessagesAndWrap').resolves([ + { + encryptedAndWrappedData: randombytes_buf(100), + identifier: message.identifier, + isSyncMessage: false, + namespace: SnodeNamespaces.Default, + networkTimestamp: message.createAtNetworkTimestamp, + plainTextBuffer: message.plainTextBuffer(), + ttl: message.ttl(), + }, + ]); const waitForMessageSentEvent = async () => new Promise(resolve => { resolve(); @@ -159,6 +183,7 @@ describe('MessageQueue', () => { ); done(); } catch (e) { + console.warn('messageSentHandlerSuccessStub was not called, but should have been'); done(e); } }); diff --git a/ts/test/session/unit/snode_api/retrieveNextMessages_test.ts b/ts/test/session/unit/snode_api/retrieveNextMessages_test.ts index 597640a74..01f69feb2 100644 --- a/ts/test/session/unit/snode_api/retrieveNextMessages_test.ts +++ b/ts/test/session/unit/snode_api/retrieveNextMessages_test.ts @@ -2,7 +2,7 @@ import chai from 'chai'; import { beforeEach, describe } from 'mocha'; import Sinon from 'sinon'; -import { GroupPubkeyType, PubkeyType } from 'libsession_util_nodejs'; +import { GroupPubkeyType, PubkeyType, UserGroupsGet } from 'libsession_util_nodejs'; import { RetrieveGroupSubRequest, RetrieveLegacyClosedGroupSubRequest, @@ -297,6 +297,8 @@ describe('SnodeAPI:buildRetrieveRequest', () => { stubLibSessionWorker({}); }); it('with single namespace and lasthash, no hashesToBump ', async () => { + TestUtils.stubUserGroupWrapper('getGroup', { whatever: '' } as any as UserGroupsGet); + const requests = await SnodeAPIRetrieve.buildRetrieveRequest( [{ lastHash: 'lasthash', namespace: SnodeNamespaces.ClosedGroupInfo }], groupPk, @@ -318,6 +320,8 @@ describe('SnodeAPI:buildRetrieveRequest', () => { }); it('with two namespace and lasthashes, no hashesToBump ', async () => { + TestUtils.stubUserGroupWrapper('getGroup', { whatever: '' } as any as UserGroupsGet); + const requests = await SnodeAPIRetrieve.buildRetrieveRequest( [ { lastHash: 'lasthash1', namespace: SnodeNamespaces.ClosedGroupInfo }, @@ -351,6 +355,7 @@ describe('SnodeAPI:buildRetrieveRequest', () => { }); it('with two namespace and lasthashes, 2 hashesToBump ', async () => { + TestUtils.stubUserGroupWrapper('getGroup', { whatever: '' } as any as UserGroupsGet); const requests = await SnodeAPIRetrieve.buildRetrieveRequest( [ { lastHash: 'lasthash1', namespace: SnodeNamespaces.ClosedGroupInfo }, diff --git a/ts/test/session/unit/swarm_polling/SwarmPolling_test.ts b/ts/test/session/unit/swarm_polling/SwarmPolling_test.ts index da33fb713..da3062033 100644 --- a/ts/test/session/unit/swarm_polling/SwarmPolling_test.ts +++ b/ts/test/session/unit/swarm_polling/SwarmPolling_test.ts @@ -3,7 +3,6 @@ import { describe } from 'mocha'; import Sinon, * as sinon from 'sinon'; import chaiAsPromised from 'chai-as-promised'; -import { ConversationModel } from '../../../../models/conversation'; import { getSwarmPollingInstance } from '../../../../session/apis/snode_api'; import { resetHardForkCachedValues } from '../../../../session/apis/snode_api/hfHandling'; import { SnodeAPIRetrieve } from '../../../../session/apis/snode_api/retrieveRequest'; @@ -12,10 +11,8 @@ import { SWARM_POLLING_TIMEOUT } from '../../../../session/constants'; import { PubKey } from '../../../../session/types'; import { UserUtils } from '../../../../session/utils'; import { UserSync } from '../../../../session/utils/job_runners/jobs/UserSyncJob'; -import { sleepFor } from '../../../../session/utils/Promise'; -import { UserGroupsWrapperActions } from '../../../../webworker/workers/browser/libsession_worker_interface'; import { TestUtils } from '../../../test-utils'; -import { generateFakeSnodes, stubData, stubLibSessionWorker } from '../../../test-utils/utils'; +import { generateFakeSnodes, stubData } from '../../../test-utils/utils'; import { ConversationTypeEnum } from '../../../../models/types'; import { ConvoHub } from '../../../../session/conversations'; import { SnodePool } from '../../../../session/apis/snode_api/snodePool'; @@ -30,11 +27,7 @@ describe('SwarmPolling', () => { const ourNumber = TestUtils.generateFakePubKeyStr(); const ourPubkey = PubKey.cast(ourNumber); - let pollOnceForKeySpy: Sinon.SinonSpy; - let swarmPolling: SwarmPolling; - let getItemByIdStub: Sinon.SinonStub; - let clock: Sinon.SinonFakeTimers; beforeEach(async () => { ConvoHub.use().reset(); @@ -45,7 +38,6 @@ describe('SwarmPolling', () => { Sinon.stub(UserUtils, 'getOurPubKeyStrFromCache').returns(ourNumber); stubData('getAllConversations').resolves([]); - getItemByIdStub = TestUtils.stubData('getItemById'); stubData('saveConversation').resolves(); stubData('getSwarmNodesForPubkey').resolves(); stubData('getLastHashBySnode').resolves(); @@ -62,7 +54,6 @@ describe('SwarmPolling', () => { swarmPolling = getSwarmPollingInstance(); swarmPolling.resetSwarmPolling(); - pollOnceForKeySpy = Sinon.spy(swarmPolling, 'pollOnceForKey'); clock = sinon.useFakeTimers({ now: Date.now(), shouldAdvanceTime: true }); }); @@ -186,270 +177,4 @@ describe('SwarmPolling', () => { }); }); }); - - describe('pollForAllKeys', () => { - beforeEach(() => { - stubData('createOrUpdateItem').resolves(); - }); - afterEach(() => { - Sinon.restore(); - }); - it('does run for our pubkey even if activeAt is really old ', async () => { - stubLibSessionWorker([]); - - const convo = ConvoHub.use().getOrCreate(ourNumber, ConversationTypeEnum.PRIVATE); - convo.set('active_at', Date.now() - 1000 * 3600 * 25); - await swarmPolling.start(true); - - expect(pollOnceForKeySpy.callCount).to.eq(1); - expect(pollOnceForKeySpy.firstCall.args[0]).to.deep.eq([ourPubkey.key, 'private']); - }); - - it('does run for our pubkey even if activeAt is recent ', async () => { - stubLibSessionWorker([]); - - const convo = ConvoHub.use().getOrCreate(ourNumber, ConversationTypeEnum.PRIVATE); - convo.set('active_at', Date.now()); - await swarmPolling.start(true); - - expect(pollOnceForKeySpy.callCount).to.eq(1); - expect(pollOnceForKeySpy.firstCall.args[0]).to.deep.eq([ourPubkey.key, 'private']); - }); - - describe('legacy group', () => { - it('does run for group pubkey on start no matter the recent timestamp', async () => { - const convo = ConvoHub.use().getOrCreate( - TestUtils.generateFakePubKeyStr(), - ConversationTypeEnum.GROUP - ); - TestUtils.stubLibSessionWorker([]); - stubData('removeAllMessagesInConversation').resolves(); - stubData('getLatestClosedGroupEncryptionKeyPair').resolves(); - stubData('removeAllClosedGroupEncryptionKeyPairs').resolves(); - stubData('removeConversation').resolves(); - stubData('fetchConvoMemoryDetails').resolves(); - convo.set('active_at', Date.now()); - const groupConvoPubkey = PubKey.cast(convo.id as string); - swarmPolling.addGroupId(groupConvoPubkey); - await swarmPolling.start(true); - - // our pubkey will be polled for, hence the 2 - expect(pollOnceForKeySpy.callCount).to.eq(2); - expect(pollOnceForKeySpy.firstCall.args[0]).to.deep.eq([ourPubkey.key, 'private']); - expect(pollOnceForKeySpy.secondCall.args[0]).to.deep.eq([groupConvoPubkey.key, 'private']); - }); - - it('does only poll from -10 for closed groups if HF >= 19.1 ', async () => { - const convo = ConvoHub.use().getOrCreate( - TestUtils.generateFakePubKeyStr(), - ConversationTypeEnum.GROUP - ); - TestUtils.stubLibSessionWorker(undefined); - getItemByIdStub.restore(); - getItemByIdStub = TestUtils.stubData('getItemById'); - getItemByIdStub - .withArgs('hasSeenHardfork190') - .resolves({ id: 'hasSeenHardfork190', value: true }) - .withArgs('hasSeenHardfork191') - .resolves({ id: 'hasSeenHardfork191', value: true }); - - convo.set('active_at', 1); - const groupConvoPubkey = PubKey.cast(convo.id as string); - swarmPolling.addGroupId(groupConvoPubkey); - - await swarmPolling.start(true); - - // our pubkey will be polled for, hence the 2 - expect(pollOnceForKeySpy.callCount).to.eq(2); - expect(pollOnceForKeySpy.firstCall.args).to.deep.eq([ourPubkey, false, [0, 2, 3, 5, 4]]); - expect(pollOnceForKeySpy.secondCall.args).to.deep.eq([groupConvoPubkey, true, [-10]]); - getItemByIdStub.restore(); - getItemByIdStub = TestUtils.stubData('getItemById'); - - getItemByIdStub.resolves(); - }); - - it('does run for group pubkey on start but not another time if activeAt is old ', async () => { - const convo = ConvoHub.use().getOrCreate( - TestUtils.generateFakePubKeyStr(), - ConversationTypeEnum.GROUP - ); - TestUtils.stubLibSessionWorker(undefined); - - convo.set('active_at', 1); // really old, but active - const groupConvoPubkey = PubKey.cast(convo.id as string); - swarmPolling.addGroupId(groupConvoPubkey); - - // this calls the stub 2 times, one for our direct pubkey and one for the group - await swarmPolling.start(true); - - // this should only call the stub one more time: for our direct pubkey but not for the group pubkey - await swarmPolling.pollForAllKeys(); - - expect(pollOnceForKeySpy.callCount).to.eq(3); - expect(pollOnceForKeySpy.firstCall.args).to.deep.eq([ourPubkey, false, [0, 2, 3, 5, 4]]); - expect(pollOnceForKeySpy.secondCall.args).to.deep.eq([groupConvoPubkey, true, [-10]]); - expect(pollOnceForKeySpy.thirdCall.args).to.deep.eq([ourPubkey, false, [0, 2, 3, 5, 4]]); - }); - - it('does run twice if activeAt less than one hour ', async () => { - const convo = ConvoHub.use().getOrCreate( - TestUtils.generateFakePubKeyStr(), - ConversationTypeEnum.GROUP - ); - - // fake that the group is part of the wrapper otherwise we stop tracking it after the first polling event - Sinon.stub(UserGroupsWrapperActions, 'getLegacyGroup').resolves({} as any); - - convo.set('active_at', Date.now()); - const groupConvoPubkey = PubKey.cast(convo.id as string); - swarmPolling.addGroupId(groupConvoPubkey); - await swarmPolling.start(true); - expect(pollOnceForKeySpy.callCount).to.eq(2); - expect(pollOnceForKeySpy.firstCall.args).to.deep.eq([ourPubkey, false, [0, 2, 3, 5, 4]]); - expect(pollOnceForKeySpy.secondCall.args).to.deep.eq([groupConvoPubkey, true, [-10]]); - pollOnceForKeySpy.resetHistory(); - clock.tick(9000); - - // no need to do that as the tick will trigger a call in all cases after 5 secs await swarmPolling.pollForAllKeys(); - /** this is not easy to explain, but - * - during the swarmPolling.start, we get two calls to pollOnceForKeySpy (one for our id and one for group id) - * - the clock ticks 9sec, and another call of pollOnceForKeySpy get started, but as we do not await them, this test fails. - * the only fix is to restore the clock and force the a small sleep to let the thing run in bg - */ - - await sleepFor(10); - - expect(pollOnceForKeySpy.callCount).to.eq(2); - expect(pollOnceForKeySpy.firstCall.args).to.deep.eq([ourPubkey, false, [0, 2, 3, 5, 4]]); - expect(pollOnceForKeySpy.secondCall.args).to.deep.eq([groupConvoPubkey, true, [-10]]); - }); - - it('does run twice if activeAt is inactive and we tick longer than 2 minutes', async () => { - const convo = ConvoHub.use().getOrCreate( - TestUtils.generateFakePubKeyStr(), - ConversationTypeEnum.GROUP - ); - // fake that the group is part of the wrapper otherwise we stop tracking it after the first polling event - Sinon.stub(UserGroupsWrapperActions, 'getLegacyGroup').resolves({} as any); - pollOnceForKeySpy.resetHistory(); - convo.set('active_at', Date.now()); - const groupConvoPubkey = convo.id; - swarmPolling.addGroupId(groupConvoPubkey); - // this call the stub two times already, one for our direct pubkey and one for the group - await swarmPolling.start(true); - const timeToTick = 3 * 60 * 1000; - swarmPolling.forcePolledTimestamp(groupConvoPubkey, Date.now() - timeToTick); - // more than week old, so inactive group but we have to tick after more than 2 min - convo.set('active_at', Date.now() - 7 * 25 * 3600 * 1000); - clock.tick(timeToTick); - /** this is not easy to explain, but - * - during the swarmPolling.start, we get two calls to pollOnceForKeySpy (one for our id and one for group od) - * - the clock ticks 9sec, and another call of pollOnceForKeySpy get started, but as we do not await them, this test fails. - * the only fix is to restore the clock and force the a small sleep to let the thing run in bg - */ - await sleepFor(10); - // we should have two more calls here, so 4 total. - expect(pollOnceForKeySpy.callCount).to.eq(4); - expect(pollOnceForKeySpy.firstCall.args).to.deep.eq([ourPubkey, false, [0, 2, 3, 5, 4]]); - expect(pollOnceForKeySpy.secondCall.args).to.deep.eq([groupConvoPubkey, true, [-10]]); - expect(pollOnceForKeySpy.thirdCall.args).to.deep.eq([ourPubkey, false, [0, 2, 3, 5, 4]]); - expect(pollOnceForKeySpy.getCalls()[3].args).to.deep.eq([groupConvoPubkey, true, [-10]]); - }); - - it('does run once only if group is inactive and we tick less than 2 minutes ', async () => { - const convo = ConvoHub.use().getOrCreate( - TestUtils.generateFakePubKeyStr(), - ConversationTypeEnum.GROUP - ); - pollOnceForKeySpy.resetHistory(); - TestUtils.stubLibSessionWorker(undefined); - convo.set('active_at', Date.now()); - const groupConvoPubkey = PubKey.cast(convo.id as string); - swarmPolling.addGroupId(groupConvoPubkey); - await swarmPolling.start(true); - - // more than a week old, we should not tick after just 5 seconds - convo.set('active_at', Date.now() - 7 * 24 * 3600 * 1000 - 3600 * 1000); - - clock.tick(1 * 60 * 1000); - await sleepFor(10); - - // we should have only one more call here, the one for our direct pubkey fetch - expect(pollOnceForKeySpy.callCount).to.eq(3); - expect(pollOnceForKeySpy.secondCall.args).to.deep.eq([groupConvoPubkey, true, [-10]]); // this one comes from the swarmPolling.start - expect(pollOnceForKeySpy.thirdCall.args).to.deep.eq([ourPubkey, false, [0, 2, 3, 5, 4]]); - }); - - describe('multiple runs', () => { - let convo: ConversationModel; - let groupConvoPubkey: PubKey; - - beforeEach(async () => { - convo = ConvoHub.use().getOrCreate( - TestUtils.generateFakePubKeyStr(), - ConversationTypeEnum.GROUP - ); - TestUtils.stubLibSessionWorker({}); - - convo.set('active_at', Date.now()); - groupConvoPubkey = PubKey.cast(convo.id as string); - swarmPolling.addGroupId(groupConvoPubkey); - await swarmPolling.start(true); - }); - - afterEach(() => { - Sinon.restore(); - ConvoHub.use().reset(); - clock.restore(); - resetHardForkCachedValues(); - }); - - it('does run twice if activeAt is less than 2 days', async () => { - pollOnceForKeySpy.resetHistory(); - // less than 2 days old, this is an active group - convo.set('active_at', Date.now() - 2 * 24 * 3600 * 1000 - 3600 * 1000); - - const timeToTick = 6 * 1000; - - swarmPolling.forcePolledTimestamp(convo.id, timeToTick); - // we tick more than 5 sec - clock.tick(timeToTick); - - await swarmPolling.pollForAllKeys(); - // we have 4 calls total. 2 for our direct promises run each 5 seconds, and 2 for the group pubkey active (so run every 5 sec too) - expect(pollOnceForKeySpy.callCount).to.eq(4); - // first two calls are our pubkey - expect(pollOnceForKeySpy.firstCall.args).to.deep.eq([ourPubkey, false, [0, 2, 3, 5, 4]]); - expect(pollOnceForKeySpy.secondCall.args).to.deep.eq([groupConvoPubkey, true, [-10]]); - - expect(pollOnceForKeySpy.thirdCall.args).to.deep.eq([ourPubkey, false, [0, 2, 3, 5, 4]]); - expect(pollOnceForKeySpy.getCalls()[3].args).to.deep.eq([groupConvoPubkey, true, [-10]]); - }); - - it('does run twice if activeAt is more than 2 days old and we tick more than one minute', async () => { - pollOnceForKeySpy.resetHistory(); - TestUtils.stubWindowLog(); - convo.set('active_at', Date.now() - 2 * 25 * 3600 * 1000); // medium active - // fake that the group is part of the wrapper otherwise we stop tracking it after the first polling event - - const timeToTick = 65 * 1000; // more than one minute - swarmPolling.forcePolledTimestamp(convo.id, timeToTick); - clock.tick(timeToTick); // should tick twice more (one more our direct pubkey and one for the group) - - // fake that the group is part of the wrapper otherwise we stop tracking it after the first polling event - - await swarmPolling.pollForAllKeys(); - - expect(pollOnceForKeySpy.callCount).to.eq(4); - - // first two calls are our pubkey - expect(pollOnceForKeySpy.firstCall.args).to.deep.eq([ourPubkey, false, [0, 2, 3, 5, 4]]); - expect(pollOnceForKeySpy.secondCall.args).to.deep.eq([groupConvoPubkey, true, [-10]]); - expect(pollOnceForKeySpy.thirdCall.args).to.deep.eq([ourPubkey, false, [0, 2, 3, 5, 4]]); - expect(pollOnceForKeySpy.getCalls()[3].args).to.deep.eq([groupConvoPubkey, true, [-10]]); - }); - }); - }); - }); }); diff --git a/ts/test/session/unit/utils/job_runner/group_sync_job/GroupSyncJob_test.ts b/ts/test/session/unit/utils/job_runner/group_sync_job/GroupSyncJob_test.ts index 3a574e31e..bb7951f2a 100644 --- a/ts/test/session/unit/utils/job_runner/group_sync_job/GroupSyncJob_test.ts +++ b/ts/test/session/unit/utils/job_runner/group_sync_job/GroupSyncJob_test.ts @@ -1,12 +1,11 @@ import { expect } from 'chai'; -import { GroupPubkeyType } from 'libsession_util_nodejs'; +import { GroupPubkeyType, UserGroupsGet } from 'libsession_util_nodejs'; import { omit } from 'lodash'; import Long from 'long'; import Sinon from 'sinon'; import { getSodiumNode } from '../../../../../../node/sodiumNode'; import { NotEmptyArrayOfBatchResults } from '../../../../../../session/apis/snode_api/SnodeRequestTypes'; import { SnodeNamespaces } from '../../../../../../session/apis/snode_api/namespaces'; -import { TTL_DEFAULT } from '../../../../../../session/constants'; import { ConvoHub } from '../../../../../../session/conversations'; import { LibSodiumWrappers } from '../../../../../../session/crypto'; import { MessageSender } from '../../../../../../session/sending'; @@ -21,7 +20,7 @@ import { } from '../../../../../../session/utils/libsession/libsession_utils'; import { MetaGroupWrapperActions } from '../../../../../../webworker/workers/browser/libsession_worker_interface'; import { TestUtils } from '../../../../../test-utils'; -import { TypedStub } from '../../../../../test-utils/utils'; +import { stubWindowFeatureFlags, stubWindowLog, TypedStub } from '../../../../../test-utils/utils'; import { NetworkTime } from '../../../../../../util/NetworkTime'; function validInfo(sodium: LibSodiumWrappers) { @@ -52,17 +51,34 @@ function validKeys(sodium: LibSodiumWrappers) { } as const; } +function validUserGroup03WithSecKey(pubkey?: GroupPubkeyType) { + const group: UserGroupsGet = { + authData: new Uint8Array(30), + secretKey: new Uint8Array(30), + destroyed: false, + invitePending: false, + joinedAtSeconds: Date.now(), + kicked: false, + priority: 0, + pubkeyHex: pubkey || TestUtils.generateFakeClosedGroupV2PkStr(), + name: 'Valid usergroup 03', + disappearingTimerSeconds: 0, + }; + return group; +} + describe('GroupSyncJob run()', () => { afterEach(() => { Sinon.restore(); }); - it('throws if no user keys', async () => { + it('does not throw if no user keys', async () => { const job = new GroupSync.GroupSyncJob({ identifier: TestUtils.generateFakeClosedGroupV2PkStr(), }); const func = async () => job.run(); - await expect(func()).to.be.eventually.rejected; + // Note: the run() function should never throw, at most it should return "permanent failure" + await expect(func()).to.be.not.eventually.rejected; }); it('permanent failure if group is not a 03 one', async () => { @@ -259,6 +275,8 @@ describe('GroupSyncJob pushChangesToGroupSwarmIfNeeded', () => { sodium = await getSodiumNode(); groupPk = TestUtils.generateFakeClosedGroupV2PkStr(); userkeys = await TestUtils.generateUserKeyPairs(); + + stubWindowLog(); Sinon.stub(UserUtils, 'getOurPubKeyStrFromCache').returns(userkeys.x25519KeyPair.pubkeyHex); Sinon.stub(UserUtils, 'getUserED25519KeyPairBytes').resolves(userkeys.ed25519KeyPair); @@ -286,16 +304,18 @@ describe('GroupSyncJob pushChangesToGroupSwarmIfNeeded', () => { it('calls sendEncryptedDataToSnode with the right data and retry if network returned nothing', async () => { TestUtils.stubLibSessionWorker(undefined); + stubWindowFeatureFlags(); + TestUtils.stubUserGroupWrapper('getGroup', validUserGroup03WithSecKey()); const info = validInfo(sodium); const member = validMembers(sodium); const networkTimestamp = 4444; - const ttl = TTL_DEFAULT.CONFIG_MESSAGE; Sinon.stub(NetworkTime, 'now').returns(networkTimestamp); pendingChangesForGroupStub.resolves({ messages: [info, member], allOldHashes: new Set('123'), }); + const result = await GroupSync.pushChangesToGroupSwarmIfNeeded({ groupPk, extraStoreRequests: [], @@ -307,34 +327,11 @@ describe('GroupSyncJob pushChangesToGroupSwarmIfNeeded', () => { expect(pendingChangesForGroupStub.callCount).to.be.eq(1); expect(saveDumpsToDbStub.callCount).to.be.eq(1); expect(saveDumpsToDbStub.firstCall.args).to.be.deep.eq([groupPk]); - - function expected(details: any) { - return { - dbMessageIdentifier: null, - namespace: details.namespace, - encryptedData: details.ciphertext, - ttlMs: ttl, - destination: groupPk, - method: 'store', - }; - } - - const expectedInfo = expected(info); - const expectedMember = expected(member); - - const callArgs = sendStub.firstCall.args[0]; - // we don't want to check the content of the request in this unit test, just the structure/count of them - // callArgs.storeRequests = callArgs.storeRequests.map(_m => null) as any; - const expectedArgs = { - storeRequests: [expectedInfo, expectedMember], - destination: groupPk, - messagesHashesToDelete: new Set('123'), - }; - expect(callArgs).to.be.deep.eq(expectedArgs); }); - it('calls sendEncryptedDataToSnode with the right data (and keys) and retry if network returned nothing', async () => { - TestUtils.stubLibSessionWorker(undefined); + it('calls sendEncryptedDataToSnode and retry if network returned nothing', async () => { + stubWindowFeatureFlags(); + TestUtils.stubUserGroupWrapper('getGroup', validUserGroup03WithSecKey(groupPk)); const info = validInfo(sodium); const member = validMembers(sodium); diff --git a/ts/test/session/unit/utils/job_runner/user_sync_job/UserSyncJob_test.ts b/ts/test/session/unit/utils/job_runner/user_sync_job/UserSyncJob_test.ts index 36ed430a1..68368715b 100644 --- a/ts/test/session/unit/utils/job_runner/user_sync_job/UserSyncJob_test.ts +++ b/ts/test/session/unit/utils/job_runner/user_sync_job/UserSyncJob_test.ts @@ -1,5 +1,4 @@ import { expect } from 'chai'; -import { PubkeyType } from 'libsession_util_nodejs'; import { omit } from 'lodash'; import Long from 'long'; import Sinon from 'sinon'; @@ -9,7 +8,6 @@ import { SnodeNamespaces, SnodeNamespacesUserConfig, } from '../../../../../../session/apis/snode_api/namespaces'; -import { TTL_DEFAULT } from '../../../../../../session/constants'; import { ConvoHub } from '../../../../../../session/conversations'; import { LibSodiumWrappers } from '../../../../../../session/crypto'; import { MessageSender } from '../../../../../../session/sending'; @@ -228,7 +226,6 @@ describe('UserSyncJob batchResultsToUserSuccessfulChange', () => { }); describe('UserSyncJob pushChangesToUserSwarmIfNeeded', () => { - let sessionId: PubkeyType; let userkeys: TestUtils.TestUserKeyPairs; let sodium: LibSodiumWrappers; @@ -239,7 +236,6 @@ describe('UserSyncJob pushChangesToUserSwarmIfNeeded', () => { beforeEach(async () => { sodium = await getSodiumNode(); userkeys = await TestUtils.generateUserKeyPairs(); - sessionId = userkeys.x25519KeyPair.pubkeyHex; Sinon.stub(UserUtils, 'getOurPubKeyStrFromCache').returns(userkeys.x25519KeyPair.pubkeyHex); Sinon.stub(UserUtils, 'getUserED25519KeyPairBytes').resolves(userkeys.ed25519KeyPair); @@ -274,13 +270,12 @@ describe('UserSyncJob pushChangesToUserSwarmIfNeeded', () => { ]); }); - it('calls sendEncryptedDataToSnode with the right data x2 and retry if network returned nothing', async () => { + it('calls sendEncryptedDataToSnode and retry if network returned nothing', async () => { Sinon.stub(GenericWrapperActions, 'needsDump').resolves(false).onSecondCall().resolves(true); const profile = userChange(sodium, SnodeNamespaces.UserProfile, 321); const contact = userChange(sodium, SnodeNamespaces.UserContacts, 123); const networkTimestamp = 4444; - const ttl = TTL_DEFAULT.CONFIG_MESSAGE; Sinon.stub(NetworkTime, 'now').returns(networkTimestamp); pendingChangesForUsStub.resolves({ @@ -295,29 +290,6 @@ describe('UserSyncJob pushChangesToUserSwarmIfNeeded', () => { expect(pendingChangesForUsStub.callCount).to.be.eq(1); expect(dump.callCount).to.be.eq(1); expect(dump.firstCall.args).to.be.deep.eq(['ContactsConfig']); - - function expected(details: any) { - return { - namespace: details.namespace, - encryptedData: details.ciphertext, - ttlMs: ttl, - destination: sessionId, - method: 'store', - }; - } - - const expectedProfile = expected(profile); - const expectedContact = expected(contact); - - const callArgs = sendStub.firstCall.args[0]; - // we don't want to check the content of the request in this unit test, just the structure/count of them - const expectedArgs = { - storeRequests: [expectedProfile, expectedContact], - destination: sessionId, - messagesHashesToDelete: new Set('123'), - }; - // callArgs.storeRequests = callArgs.storeRequests.map(_m => null) as any; - expect(callArgs).to.be.deep.eq(expectedArgs); }); it('calls sendEncryptedDataToSnode with the right data x3 and retry if network returned nothing then success', async () => { diff --git a/ts/test/test-utils/utils/pubkey.ts b/ts/test/test-utils/utils/pubkey.ts index 6c7ea3cf7..be58d9247 100644 --- a/ts/test/test-utils/utils/pubkey.ts +++ b/ts/test/test-utils/utils/pubkey.ts @@ -129,7 +129,7 @@ export function generateFakeSnodes(amount: number): Array { } /** - * this function can be used to setup unit test which relies on fetching a snodepool + * this function can be used to setup unit test which relies on fetching a snode pool */ export function setupTestWithSending() { const snodes = generateFakeSnodes(20);