chore: fixed unit tests

pull/2873/head
Audric Ackermann 2 years ago
parent d12071f3c8
commit e69c5c4b35

@ -144,7 +144,7 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
public throttledBumpTyping: () => void;
public throttledNotify: (message: MessageModel) => void;
public markConversationRead: (newestUnreadDate: number, readAt?: number) => void;
public initialPromise: any;
public initialPromise: Promise<ConversationModel | void>;
private typingRefreshTimer?: NodeJS.Timeout | null;
private typingPauseTimer?: NodeJS.Timeout | null;
@ -918,7 +918,7 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
public async commit() {
perfStart(`conversationCommit-${this.id}`);
await commitConversationAndRefreshWrapper(this.id);
await Convo.commitConversationAndRefreshWrapper(this.id);
perfEnd(`conversationCommit-${this.id}`, 'conversationCommit');
}
@ -2334,7 +2334,9 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
}
}
export async function commitConversationAndRefreshWrapper(id: string) {
export const Convo = { commitConversationAndRefreshWrapper };
async function commitConversationAndRefreshWrapper(id: string) {
const convo = ConvoHub.use().get(id);
if (!convo) {
return;

@ -244,7 +244,6 @@ export class SwarmPolling {
const { toPollDetails, groupsToLeave, legacyGroupsToLeave } = await this.getPollingDetails(
this.groupPolling
);
// first, leave anything which shouldn't be there anymore
await Promise.all(
concat(groupsToLeave, legacyGroupsToLeave).map(m =>
@ -262,12 +261,61 @@ export class SwarmPolling {
}
}
public async updateLastPollTimestampForPubkey({
countMessages,
pubkey,
type,
}: {
type: ConversationTypeEnum;
countMessages: number;
pubkey: string;
}) {
// if all snodes returned an error (null), no need to update the lastPolledTimestamp
if (type === ConversationTypeEnum.GROUP || type === ConversationTypeEnum.GROUPV3) {
window?.log?.debug(
`Polled for group(${ed25519Str(pubkey)}):, got ${countMessages} messages back.`
);
let lastPolledTimestamp = Date.now();
if (countMessages >= 95) {
// if we get 95 messages or more back, it means there are probably more than this
// so make sure to retry the polling in the next 5sec by marking the last polled timestamp way before that it is really
// this is a kind of hack
lastPolledTimestamp = Date.now() - SWARM_POLLING_TIMEOUT.INACTIVE - 5 * 1000;
} // update the last fetched timestamp
this.forcePolledTimestamp(pubkey, lastPolledTimestamp);
}
}
public async handleUserOrGroupConfMessages({
confMessages,
pubkey,
type,
}: {
type: ConversationTypeEnum;
pubkey: string;
confMessages: Array<RetrieveMessageItemWithNamespace> | null;
}) {
if (!confMessages) {
return;
}
// first make sure to handle the shared user config message first
if (type === ConversationTypeEnum.PRIVATE && UserUtils.isUsFromCache(pubkey)) {
// this does not throw, no matter what happens
await SwarmPollingUserConfig.handleUserSharedConfigMessages(confMessages);
return;
}
if (type === ConversationTypeEnum.GROUPV3 && PubKey.isClosedGroupV2(pubkey)) {
await SwarmPollingGroupConfig.handleGroupSharedConfigMessages(confMessages, pubkey);
}
}
/**
* Only exposed as public for testing
*/
public async pollOnceForKey([pubkey, type]: PollForUs | PollForLegacy | PollForGroup) {
const namespaces = this.getNamespacesToPollFrom(type);
const swarmSnodes = await snodePool.getSwarmFor(pubkey);
// Select nodes for which we already have lastHashes
@ -291,59 +339,32 @@ export class SwarmPolling {
}
if (!resultsFromAllNamespaces?.length) {
// not a single message from any of the polled namespace was retrieve. nothing else to do
// Not a single message from any of the polled namespace was retrieve.
// We must still mark the current pubkey as "was just polled"
await this.updateLastPollTimestampForPubkey({
countMessages: 0,
pubkey,
type,
});
return;
}
const { confMessages, otherMessages } = filterMessagesPerTypeOfConvo(
type,
resultsFromAllNamespaces
);
// first make sure to handle the shared user config message first
if (
type === ConversationTypeEnum.PRIVATE &&
confMessages?.length &&
UserUtils.isUsFromCache(pubkey)
) {
// this does not throw, no matter what happens
await SwarmPollingUserConfig.handleUserSharedConfigMessages(confMessages);
} else if (
type === ConversationTypeEnum.GROUPV3 &&
confMessages?.length &&
PubKey.isClosedGroupV2(pubkey)
) {
await SwarmPollingGroupConfig.handleGroupSharedConfigMessages(confMessages, pubkey);
}
// We always handle the config messages first (for groups 03 or our own messages)
await this.handleUserOrGroupConfMessages({ confMessages, pubkey, type });
// Merge results into one list of unique messages
const uniqOtherMsgs = uniqBy(otherMessages, x => x.hash);
if (uniqOtherMsgs.length) {
window.log.debug(`received otherMessages: ${otherMessages.length} for type: ${type}`);
}
// if all snodes returned an error (null), no need to update the lastPolledTimestamp
if (type === ConversationTypeEnum.GROUP || type === ConversationTypeEnum.GROUPV3) {
window?.log?.debug(
`Polled for group(${ed25519Str(pubkey)}):, got ${uniqOtherMsgs.length} messages back.`
);
let lastPolledTimestamp = Date.now();
if (uniqOtherMsgs.length >= 95) {
// if we get 95 messages or more back, it means there are probably more than this
// so make sure to retry the polling in the next 5sec by marking the last polled timestamp way before that it is really
// this is a kind of hack
lastPolledTimestamp = Date.now() - SWARM_POLLING_TIMEOUT.INACTIVE - 5 * 1000;
}
// update the last fetched timestamp
this.groupPolling = this.groupPolling.map(group => {
if (PubKey.isEqual(pubkey, group.pubkey)) {
return {
...group,
lastPolledTimestamp,
};
}
return group;
});
}
await this.updateLastPollTimestampForPubkey({
countMessages: uniqOtherMsgs.length,
pubkey,
type,
});
perfStart(`handleSeenMessages-${pubkey}`);
const newMessages = await this.handleSeenMessages(uniqOtherMsgs);

@ -12,7 +12,6 @@ import {
} from '../../state/ducks/conversations';
import { BlockedNumberController } from '../../util';
import { getOpenGroupManager } from '../apis/open_group_api/opengroupV2/OpenGroupManagerV2';
import { getSwarmFor } from '../apis/snode_api/snodePool';
import { PubKey } from '../types';
import { getMessageQueue } from '..';
@ -36,24 +35,24 @@ import { SessionUtilContact } from '../utils/libsession/libsession_utils_contact
import { SessionUtilConvoInfoVolatile } from '../utils/libsession/libsession_utils_convo_info_volatile';
import { SessionUtilUserGroups } from '../utils/libsession/libsession_utils_user_groups';
let instance: ConversationController | null;
let instance: ConvoController | null;
const getConversationController = () => {
const getConvoHub = () => {
if (instance) {
return instance;
}
instance = new ConversationController();
instance = new ConvoController();
return instance;
};
class ConversationController {
class ConvoController {
private readonly conversations: ConversationCollection;
private _initialFetchComplete: boolean = false;
private _initialPromise?: Promise<any>;
private _convoHubInitialPromise?: Promise<any>;
/**
* Do not call this constructor. You get the ConversationController through ConvoHub.use() only
* Do not call this constructor. You get the ConvoHub through ConvoHub.use() only
*/
constructor() {
this.conversations = new ConversationCollection();
@ -139,11 +138,6 @@ class ConversationController {
})
);
if (!conversation.isPublic() && conversation.isActive()) {
// NOTE: we request snodes updating the cache, but ignore the result
void getSwarmFor(id);
}
return conversation;
};
@ -164,21 +158,21 @@ class ConversationController {
id: string | PubKey,
type: ConversationTypeEnum
): Promise<ConversationModel> {
const initialPromise =
this._initialPromise !== undefined ? this._initialPromise : Promise.resolve();
return initialPromise.then(() => {
if (!id) {
return Promise.reject(new Error('getOrCreateAndWait: invalid id passed.'));
}
const pubkey = id && (id as any).key ? (id as any).key : id;
const conversation = this.getOrCreate(pubkey, type);
const convoHubInitialPromise =
this._convoHubInitialPromise !== undefined ? this._convoHubInitialPromise : Promise.resolve();
await convoHubInitialPromise;
if (conversation) {
return conversation.initialPromise.then(() => conversation);
}
if (!id) {
throw new Error('getOrCreateAndWait: invalid id passed.');
}
const pubkey = id && (id as any).key ? (id as any).key : id;
const conversation = this.getOrCreate(pubkey, type);
return Promise.reject(new Error('getOrCreateAndWait: did not get conversation'));
});
if (conversation) {
return conversation.initialPromise.then(() => conversation);
}
return Promise.reject(new Error('getOrCreateAndWait: did not get conversation'));
}
/**
@ -312,17 +306,12 @@ class ConversationController {
}
public async load() {
window.log.warn(`plop1`);
if (this.conversations.length) {
throw new Error('ConversationController: Already loaded!');
}
window.log.warn(`plop1`);
const load = async () => {
try {
window.log.warn(`plop2`);
const startLoad = Date.now();
const convoModels = await Data.getAllConversations();
@ -379,17 +368,17 @@ class ConversationController {
}
};
this._initialPromise = load();
this._convoHubInitialPromise = load();
return this._initialPromise;
return this._convoHubInitialPromise;
}
public loadPromise() {
return this._initialPromise;
return this._convoHubInitialPromise;
}
public reset() {
this._initialPromise = Promise.resolve();
this._convoHubInitialPromise = Promise.resolve();
this._initialFetchComplete = false;
if (window?.inboxStore) {
window.inboxStore?.dispatch(conversationActions.removeAllConversations());
@ -569,4 +558,4 @@ async function removeCommunityFromWrappers(conversationId: string) {
}
}
export const ConvoHub = { use: getConversationController };
export const ConvoHub = { use: getConvoHub };

@ -514,12 +514,12 @@ describe('knownBlindedKeys', () => {
it('does iterate over all the conversations and find the first one matching (passes)', async () => {
await loadKnownBlindedKeys();
// adding a private conversation with a known match of the blinded pubkey we have
await ConvoHub.use().getOrCreateAndWait(realSessionId, ConversationTypeEnum.PRIVATE);
ConvoHub.use().getOrCreate(realSessionId, ConversationTypeEnum.PRIVATE);
const convo = await ConvoHub.use().getOrCreateAndWait(
knownBlindingMatch.realSessionId,
ConversationTypeEnum.PRIVATE
);
await ConvoHub.use().getOrCreateAndWait(realSessionId2, ConversationTypeEnum.PRIVATE);
ConvoHub.use().getOrCreate(realSessionId2, ConversationTypeEnum.PRIVATE);
convo.set({ isApproved: true });
const real = await findCachedBlindedMatchOrLookItUp(
knownBlindingMatch.blindedId,

@ -2,8 +2,8 @@ import chai from 'chai';
import { describe } from 'mocha';
import Sinon, * as sinon from 'sinon';
import { UserGroupsGet } from 'libsession_util_nodejs';
import { ConversationModel } from '../../../../models/conversation';
import { GroupPubkeyType, LegacyGroupInfo, UserGroupsGet } from 'libsession_util_nodejs';
import { ConversationModel, Convo } from '../../../../models/conversation';
import { ConversationTypeEnum } from '../../../../models/conversationAttributes';
import { SnodePool, getSwarmPollingInstance } from '../../../../session/apis/snode_api';
import { resetHardForkCachedValues } from '../../../../session/apis/snode_api/hfHandling';
@ -24,6 +24,16 @@ const pollOnceForGroupLegacyArgs = (groupLegacy: string) => [
[groupLegacy, ConversationTypeEnum.GROUP],
];
function stubWithLegacyGroups(pubkeys: Array<string>) {
const groups = pubkeys.map(m => ({ pubkeyHex: m }) as LegacyGroupInfo);
TestUtils.stubUserGroupWrapper('getAllLegacyGroups', groups);
}
function stubWithGroups(pubkeys: Array<GroupPubkeyType>) {
const groups = pubkeys.map(m => ({ pubkeyHex: m }) as UserGroupsGet);
TestUtils.stubUserGroupWrapper('getAllGroups', groups);
}
describe('SwarmPolling:pollForAllKeys', () => {
const ourPubkey = TestUtils.generateFakePubKey();
const ourNumber = ourPubkey.key;
@ -39,6 +49,7 @@ describe('SwarmPolling:pollForAllKeys', () => {
beforeEach(async () => {
ConvoHub.use().reset();
TestUtils.stubWindowFeatureFlags();
TestUtils.stubWindowLog();
Sinon.stub(ConfigurationSync, 'queueNewJobIfNeeded').resolves();
// Utils Stubs
@ -50,13 +61,16 @@ describe('SwarmPolling:pollForAllKeys', () => {
stubData('getSwarmNodesForPubkey').resolves();
stubData('getLastHashBySnode').resolves();
Sinon.stub(Convo, 'commitConversationAndRefreshWrapper').resolves();
TestUtils.stubLibSessionWorker(undefined);
Sinon.stub(SnodePool, 'getSwarmFor').resolves(generateFakeSnodes(5));
Sinon.stub(SnodeAPIRetrieve, 'retrieveNextMessages').resolves([]);
TestUtils.stubWindow('inboxStore', undefined);
TestUtils.stubWindow('getGlobalOnlineStatus', () => true);
TestUtils.stubWindowLog();
TestUtils.stubUserGroupWrapper('getAllLegacyGroups', []);
TestUtils.stubUserGroupWrapper('getAllGroups', []);
const convoController = ConvoHub.use();
await convoController.load();
@ -78,8 +92,8 @@ describe('SwarmPolling:pollForAllKeys', () => {
});
it('does run for our pubkey even if activeAt is really old ', async () => {
TestUtils.stubLibSessionWorker(undefined);
stubWithGroups([]);
stubWithLegacyGroups([]);
const convo = ConvoHub.use().getOrCreate(ourNumber, ConversationTypeEnum.PRIVATE);
convo.set('active_at', Date.now() - 1000 * 3600 * 25);
await swarmPolling.start(true);
@ -89,8 +103,8 @@ describe('SwarmPolling:pollForAllKeys', () => {
});
it('does run for our pubkey even if activeAt is recent ', async () => {
TestUtils.stubLibSessionWorker(undefined);
stubWithGroups([]);
stubWithLegacyGroups([]);
const convo = ConvoHub.use().getOrCreate(ourNumber, ConversationTypeEnum.PRIVATE);
convo.set('active_at', Date.now());
await swarmPolling.start(true);
@ -103,10 +117,8 @@ describe('SwarmPolling:pollForAllKeys', () => {
it('does run for group pubkey on start no matter the recent timestamp', async () => {
const groupPk = TestUtils.generateFakePubKeyStr();
const convo = ConvoHub.use().getOrCreate(groupPk, ConversationTypeEnum.GROUP);
const group = {
pubkeyHex: groupPk,
} as UserGroupsGet;
TestUtils.stubLibSessionWorker([group]);
stubWithLegacyGroups([groupPk]);
stubWithGroups([]);
convo.set('active_at', Date.now());
const groupConvoPubkey = PubKey.cast(groupPk);
swarmPolling.addGroupId(groupConvoPubkey);
@ -122,11 +134,9 @@ describe('SwarmPolling:pollForAllKeys', () => {
it('does only poll from -10 for closed groups', async () => {
const groupPk = TestUtils.generateFakePubKeyStr();
const convo = ConvoHub.use().getOrCreate(groupPk, ConversationTypeEnum.GROUP);
const group = {
pubkeyHex: groupPk,
} as UserGroupsGet;
TestUtils.stubLibSessionWorker([group]);
stubWithLegacyGroups([groupPk]);
stubWithGroups([]);
convo.set('active_at', 1);
swarmPolling.addGroupId(PubKey.cast(groupPk));
@ -144,36 +154,31 @@ describe('SwarmPolling:pollForAllKeys', () => {
it('does run for group pubkey on start but not another time if activeAt is old ', async () => {
const groupPk = TestUtils.generateFakePubKeyStr();
const convo = ConvoHub.use().getOrCreate(groupPk, ConversationTypeEnum.GROUP);
const groupConvo = ConvoHub.use().getOrCreate(groupPk, ConversationTypeEnum.GROUP);
const group = {
pubkeyHex: groupPk,
} as UserGroupsGet;
TestUtils.stubLibSessionWorker([group]);
convo.set('active_at', 1); // really old, but active
swarmPolling.addGroupId(groupPk);
stubWithLegacyGroups([groupPk]);
stubWithGroups([]);
groupConvo.set('active_at', 1); // really old, but active
swarmPolling.addGroupId(groupPk);
// this calls the stub 2 times, one for our direct pubkey and one for the group
await swarmPolling.start(true);
expect(pollOnceForKeySpy.callCount).to.eq(2);
expect(pollOnceForKeySpy.firstCall.args).to.deep.eq(pollOnceForUsArgs(ourPubkey.key));
expect(pollOnceForKeySpy.secondCall.args).to.deep.eq(pollOnceForGroupLegacyArgs(groupPk));
// 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(pollOnceForUsArgs(ourPubkey.key));
expect(pollOnceForKeySpy.secondCall.args).to.deep.eq(pollOnceForGroupLegacyArgs(groupPk));
expect(pollOnceForKeySpy.thirdCall.args).to.deep.eq(pollOnceForUsArgs(ourPubkey.key));
});
it('does run twice if activeAt less than one hour ', async () => {
const groupPk = TestUtils.generateFakePubKeyStr();
const convo = ConvoHub.use().getOrCreate(groupPk, ConversationTypeEnum.GROUP);
const convo = ConvoHub.use().getOrCreate(groupPk, ConversationTypeEnum.GROUP);
// fake that the group is part of the wrapper otherwise we stop tracking it after the first polling event
const group = {
pubkeyHex: groupPk,
} as UserGroupsGet;
TestUtils.stubLibSessionWorker([group]);
stubWithLegacyGroups([groupPk]);
stubWithGroups([]);
convo.set('active_at', Date.now());
swarmPolling.addGroupId(groupPk);
@ -203,10 +208,9 @@ describe('SwarmPolling:pollForAllKeys', () => {
const convo = ConvoHub.use().getOrCreate(groupPk, ConversationTypeEnum.GROUP);
// fake that the group is part of the wrapper otherwise we stop tracking it after the first polling event
const group = {
pubkeyHex: groupPk,
} as UserGroupsGet;
TestUtils.stubLibSessionWorker([group]);
stubWithLegacyGroups([groupPk]);
stubWithGroups([]);
pollOnceForKeySpy.resetHistory();
convo.set('active_at', Date.now());
swarmPolling.addGroupId(groupPk);
@ -236,10 +240,10 @@ describe('SwarmPolling:pollForAllKeys', () => {
const convo = ConvoHub.use().getOrCreate(groupPk, ConversationTypeEnum.GROUP);
pollOnceForKeySpy.resetHistory();
const group = {
pubkeyHex: groupPk,
} as UserGroupsGet;
TestUtils.stubLibSessionWorker([group]);
stubWithLegacyGroups([groupPk]);
stubWithGroups([]);
convo.set('active_at', Date.now());
swarmPolling.addGroupId(groupPk);
await swarmPolling.start(true);
@ -265,7 +269,9 @@ describe('SwarmPolling:pollForAllKeys', () => {
TestUtils.generateFakePubKeyStr(),
ConversationTypeEnum.GROUP
);
TestUtils.stubLibSessionWorker({});
stubWithLegacyGroups([convo.id]);
stubWithGroups([]);
convo.set('active_at', Date.now());
groupConvoPubkey = PubKey.cast(convo.id as string);
@ -334,4 +340,6 @@ describe('SwarmPolling:pollForAllKeys', () => {
});
});
});
it.skip('do the same for neww groups');
});

@ -1,6 +1,8 @@
import path from 'path';
import { readFileSync } from 'fs-extra';
import { isEmpty } from 'lodash';
import { expect } from 'chai';
import { enableLogRedirect } from '../../../test-utils/utils';
describe('Updater', () => {
it.skip('isUpdateAvailable', () => {});
@ -16,4 +18,10 @@ describe('Updater', () => {
);
}
});
it('stubWindowLog is set to false before pushing', () => {
expect(enableLogRedirect).to.be.eq(
false,
'If you see this message, just set `enableLogRedirect` to false in `ts/test/test-utils/utils/stubbing.ts'
);
});
});

@ -1,6 +1,6 @@
import chai from 'chai';
import Sinon, * as sinon from 'sinon';
import chaiAsPromised from 'chai-as-promised';
import Sinon, * as sinon from 'sinon';
import { PromiseUtils } from '../../../../session/utils';
@ -10,7 +10,6 @@ import {
sleepFor,
} from '../../../../session/utils/Promise';
import { TestUtils } from '../../../test-utils';
import { enableLogRedirect } from '../../../test-utils/utils';
chai.use(chaiAsPromised as any);
chai.should();
@ -207,11 +206,4 @@ describe('Promise Utils', () => {
expect(hasAlreadyOneAtaTimeMatching('testing2')).to.be.eq(false, 'should be false');
});
});
it('stubWindowLog is set to false before pushing', () => {
expect(
enableLogRedirect,
'If you see this message, just set `enableLogRedirect` to false in `ts/test/test-utils/utils/stubbing.ts`'
).to.be.eq(false);
});
});

@ -199,12 +199,11 @@ describe('JobRunner', () => {
expect(runnerMulti.getJobList()).to.deep.eq([job.serializeJob(), job2.serializeJob()]);
expect(runnerMulti.getCurrentJobIdentifier()).to.be.equal(job.persistedData.identifier);
console.info(
'runnerMulti.getJobList() initial',
runnerMulti.getJobList().map(m => m.identifier),
Date.now()
);
console.info('=========== awaiting first job ==========');
// console.info(
// 'runnerMulti.getJobList() initial',
// runnerMulti.getJobList().map(m => m.identifier),
// Date.now()
// );
// each job takes 5s to finish, so let's tick once the first one should be done
clock.tick(5000);
@ -212,20 +211,13 @@ describe('JobRunner', () => {
let awaited = await runnerMulti.waitCurrentJob();
expect(awaited).to.eq('await');
await sleepFor(10);
console.info('=========== awaited first job ==========');
expect(runnerMulti.getCurrentJobIdentifier()).to.be.equal(job2.persistedData.identifier);
console.info('=========== awaiting second job ==========');
clock.tick(5000);
awaited = await runnerMulti.waitCurrentJob();
expect(awaited).to.eq('await');
await sleepFor(10); // those sleep for is just to let the runner the time to finish writing the tests to the DB and exit the handling of the previous test
console.info('=========== awaited second job ==========');
expect(runnerMulti.getCurrentJobIdentifier()).to.eq(null);
expect(runnerMulti.getJobList()).to.deep.eq([]);

@ -1,5 +1,5 @@
import { Data } from '../data/data';
import { commitConversationAndRefreshWrapper } from '../models/conversation';
import { Convo } from '../models/conversation';
import { PubKey } from '../session/types';
import { Storage } from './storage';
@ -38,7 +38,7 @@ export class BlockedNumberController {
if (!this.blockedNumbers.has(toBlock.key)) {
this.blockedNumbers.add(toBlock.key);
await this.saveToDB(BLOCKED_NUMBERS_ID, this.blockedNumbers);
await commitConversationAndRefreshWrapper(toBlock.key);
await Convo.commitConversationAndRefreshWrapper(toBlock.key);
}
}
@ -64,7 +64,7 @@ export class BlockedNumberController {
const user = users[index];
try {
// eslint-disable-next-line no-await-in-loop
await commitConversationAndRefreshWrapper(user);
await Convo.commitConversationAndRefreshWrapper(user);
} catch (e) {
window.log.warn(
'failed to SessionUtilContact.insertContactFromDBIntoWrapperAndRefresh with: ',

Loading…
Cancel
Save