parent
ceffa1e13b
commit
9492fdc51e
@ -0,0 +1,367 @@
|
|||||||
|
import { expect } from 'chai';
|
||||||
|
import { PubkeyType } 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 { GetNetworkTime } from '../../../../../../session/apis/snode_api/getNetworkTime';
|
||||||
|
import {
|
||||||
|
SnodeNamespaces,
|
||||||
|
UserConfigNamespaces,
|
||||||
|
} 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';
|
||||||
|
import { UserUtils } from '../../../../../../session/utils';
|
||||||
|
import { RunJobResult } from '../../../../../../session/utils/job_runners/PersistedJob';
|
||||||
|
import { UserSync } from '../../../../../../session/utils/job_runners/jobs/UserSyncJob';
|
||||||
|
import {
|
||||||
|
LibSessionUtil,
|
||||||
|
PendingChangesForUs,
|
||||||
|
UserDestinationChanges,
|
||||||
|
UserSuccessfulChange,
|
||||||
|
} from '../../../../../../session/utils/libsession/libsession_utils';
|
||||||
|
import { GenericWrapperActions } from '../../../../../../webworker/workers/browser/libsession_worker_interface';
|
||||||
|
import { TestUtils } from '../../../../../test-utils';
|
||||||
|
import { TypedStub, stubConfigDumpData } from '../../../../../test-utils/utils';
|
||||||
|
|
||||||
|
function userChange(
|
||||||
|
sodium: LibSodiumWrappers,
|
||||||
|
namespace: UserConfigNamespaces,
|
||||||
|
seqno: number
|
||||||
|
): PendingChangesForUs {
|
||||||
|
return {
|
||||||
|
ciphertext: sodium.randombytes_buf(120),
|
||||||
|
namespace,
|
||||||
|
seqno: Long.fromNumber(seqno),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('UserSyncJob run()', () => {
|
||||||
|
afterEach(() => {
|
||||||
|
Sinon.restore();
|
||||||
|
});
|
||||||
|
it('throws if no user keys', async () => {
|
||||||
|
const job = new UserSync.UserSyncJob({});
|
||||||
|
|
||||||
|
const func = async () => job.run();
|
||||||
|
await expect(func()).to.be.eventually.rejected;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws if our pubkey is set but not valid', async () => {
|
||||||
|
const job = new UserSync.UserSyncJob({});
|
||||||
|
Sinon.stub(UserUtils, 'getOurPubKeyStrFromCache').returns({ something: false } as any);
|
||||||
|
Sinon.stub(UserUtils, 'getUserED25519KeyPairBytes').resolves({ something: true } as any);
|
||||||
|
Sinon.stub(ConvoHub.use(), 'get').resolves({}); // anything not falsy
|
||||||
|
|
||||||
|
const func = async () => job.run();
|
||||||
|
await expect(func()).to.be.eventually.rejected;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('permanent failure if user has no ed keypair', async () => {
|
||||||
|
Sinon.stub(UserUtils, 'getOurPubKeyStrFromCache').returns(TestUtils.generateFakePubKeyStr());
|
||||||
|
Sinon.stub(UserUtils, 'getUserED25519KeyPairBytes').resolves(undefined);
|
||||||
|
Sinon.stub(ConvoHub.use(), 'get').resolves({}); // anything not falsy
|
||||||
|
const job = new UserSync.UserSyncJob({});
|
||||||
|
const result = await job.run();
|
||||||
|
expect(result).to.be.eq(RunJobResult.PermanentFailure);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('permanent failure if user has no own conversation', async () => {
|
||||||
|
Sinon.stub(UserUtils, 'getOurPubKeyStrFromCache').returns(TestUtils.generateFakePubKeyStr());
|
||||||
|
Sinon.stub(UserUtils, 'getUserED25519KeyPairBytes').resolves({} as any); // anything not falsy
|
||||||
|
Sinon.stub(ConvoHub.use(), 'get').returns(undefined as any);
|
||||||
|
const job = new UserSync.UserSyncJob({});
|
||||||
|
const result = await job.run();
|
||||||
|
expect(result).to.be.eq(RunJobResult.PermanentFailure);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls pushChangesToUserSwarmIfNeeded if preconditions are fine', async () => {
|
||||||
|
Sinon.stub(UserUtils, 'getOurPubKeyStrFromCache').returns(TestUtils.generateFakePubKeyStr());
|
||||||
|
Sinon.stub(UserUtils, 'getUserED25519KeyPairBytes').resolves({} as any); // anything not falsy
|
||||||
|
const taskedRun = Sinon.stub(UserSync, 'pushChangesToUserSwarmIfNeeded').resolves(
|
||||||
|
RunJobResult.Success
|
||||||
|
);
|
||||||
|
Sinon.stub(ConvoHub.use(), 'get').returns({} as any); // anything not falsy
|
||||||
|
const job = new UserSync.UserSyncJob({});
|
||||||
|
const result = await job.run();
|
||||||
|
expect(result).to.be.eq(RunJobResult.Success);
|
||||||
|
expect(taskedRun.callCount).to.be.eq(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('UserSyncJob resultsToSuccessfulChange', () => {
|
||||||
|
let sodium: LibSodiumWrappers;
|
||||||
|
beforeEach(async () => {
|
||||||
|
sodium = await getSodiumNode();
|
||||||
|
});
|
||||||
|
it('no or empty results return empty array', () => {
|
||||||
|
expect(
|
||||||
|
LibSessionUtil.batchResultsToUserSuccessfulChange(null, {
|
||||||
|
allOldHashes: new Set(),
|
||||||
|
messages: [],
|
||||||
|
})
|
||||||
|
).to.be.deep.eq([]);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
LibSessionUtil.batchResultsToUserSuccessfulChange([] as any as NotEmptyArrayOfBatchResults, {
|
||||||
|
allOldHashes: new Set(),
|
||||||
|
messages: [],
|
||||||
|
})
|
||||||
|
).to.be.deep.eq([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('extract one result with 200 and messagehash', () => {
|
||||||
|
const profile = userChange(sodium, SnodeNamespaces.UserProfile, 321);
|
||||||
|
const contact = userChange(sodium, SnodeNamespaces.UserContacts, 123);
|
||||||
|
const batchResults: NotEmptyArrayOfBatchResults = [{ code: 200, body: { hash: 'hash1' } }];
|
||||||
|
const request: UserDestinationChanges = {
|
||||||
|
allOldHashes: new Set(),
|
||||||
|
messages: [profile, contact],
|
||||||
|
};
|
||||||
|
const results = LibSessionUtil.batchResultsToUserSuccessfulChange(batchResults, request);
|
||||||
|
expect(results).to.be.deep.eq([
|
||||||
|
{
|
||||||
|
updatedHash: 'hash1',
|
||||||
|
pushed: profile,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('extract two results with 200 and messagehash', () => {
|
||||||
|
const profile = userChange(sodium, SnodeNamespaces.UserProfile, 321);
|
||||||
|
const contact = userChange(sodium, SnodeNamespaces.UserContacts, 123);
|
||||||
|
const batchResults: NotEmptyArrayOfBatchResults = [
|
||||||
|
{ code: 200, body: { hash: 'hash1' } },
|
||||||
|
{ code: 200, body: { hash: 'hash2' } },
|
||||||
|
];
|
||||||
|
const request: UserDestinationChanges = {
|
||||||
|
allOldHashes: new Set(),
|
||||||
|
messages: [contact, profile],
|
||||||
|
};
|
||||||
|
const results = LibSessionUtil.batchResultsToUserSuccessfulChange(batchResults, request);
|
||||||
|
expect(results).to.be.deep.eq([
|
||||||
|
{
|
||||||
|
updatedHash: 'hash1',
|
||||||
|
pushed: contact,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
updatedHash: 'hash2',
|
||||||
|
pushed: profile,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('skip message hashes not a string', () => {
|
||||||
|
const profile = userChange(sodium, SnodeNamespaces.UserProfile, 321);
|
||||||
|
const contact = userChange(sodium, SnodeNamespaces.UserContacts, 123);
|
||||||
|
const batchResults: NotEmptyArrayOfBatchResults = [
|
||||||
|
{ code: 200, body: { hash: 123 as any as string } },
|
||||||
|
{ code: 200, body: { hash: 'hash2' } },
|
||||||
|
];
|
||||||
|
const request: UserDestinationChanges = {
|
||||||
|
allOldHashes: new Set(),
|
||||||
|
messages: [profile, contact],
|
||||||
|
};
|
||||||
|
const results = LibSessionUtil.batchResultsToUserSuccessfulChange(batchResults, request);
|
||||||
|
expect(results).to.be.deep.eq([
|
||||||
|
{
|
||||||
|
updatedHash: 'hash2',
|
||||||
|
pushed: contact,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('skip request item without data', () => {
|
||||||
|
const profile = userChange(sodium, SnodeNamespaces.UserProfile, 321);
|
||||||
|
const contact = userChange(sodium, SnodeNamespaces.UserContacts, 123);
|
||||||
|
const profileNoData = omit(profile, 'ciphertext');
|
||||||
|
const batchResults: NotEmptyArrayOfBatchResults = [
|
||||||
|
{ code: 200, body: { hash: 'hash1' } },
|
||||||
|
{ code: 200, body: { hash: 'hash2' } },
|
||||||
|
];
|
||||||
|
const request: UserDestinationChanges = {
|
||||||
|
allOldHashes: new Set(),
|
||||||
|
messages: [profileNoData as any as PendingChangesForUs, contact],
|
||||||
|
};
|
||||||
|
const results = LibSessionUtil.batchResultsToUserSuccessfulChange(batchResults, request);
|
||||||
|
expect(results).to.be.deep.eq([
|
||||||
|
{
|
||||||
|
updatedHash: 'hash2',
|
||||||
|
pushed: contact,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('skip request item without 200 code', () => {
|
||||||
|
const profile = userChange(sodium, SnodeNamespaces.UserProfile, 321);
|
||||||
|
const contact = userChange(sodium, SnodeNamespaces.UserContacts, 123);
|
||||||
|
const batchResults: NotEmptyArrayOfBatchResults = [
|
||||||
|
{ code: 200, body: { hash: 'hash1' } },
|
||||||
|
{ code: 401, body: { hash: 'hash2' } },
|
||||||
|
];
|
||||||
|
const request: UserDestinationChanges = {
|
||||||
|
allOldHashes: new Set(),
|
||||||
|
messages: [profile, contact],
|
||||||
|
};
|
||||||
|
const results = LibSessionUtil.batchResultsToUserSuccessfulChange(batchResults, request);
|
||||||
|
expect(results).to.be.deep.eq([
|
||||||
|
{
|
||||||
|
updatedHash: 'hash1',
|
||||||
|
pushed: profile,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
// another test swapping the results
|
||||||
|
batchResults[0].code = 401;
|
||||||
|
batchResults[1].code = 200;
|
||||||
|
const results2 = LibSessionUtil.batchResultsToUserSuccessfulChange(batchResults, request);
|
||||||
|
expect(results2).to.be.deep.eq([
|
||||||
|
{
|
||||||
|
updatedHash: 'hash2',
|
||||||
|
pushed: contact,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('UserSyncJob pushChangesToUserSwarmIfNeeded', () => {
|
||||||
|
let sessionId: PubkeyType;
|
||||||
|
let userkeys: TestUtils.TestUserKeyPairs;
|
||||||
|
let sodium: LibSodiumWrappers;
|
||||||
|
|
||||||
|
let sendStub: TypedStub<typeof MessageSender, 'sendEncryptedDataToSnode'>;
|
||||||
|
let pendingChangesForUsStub: TypedStub<typeof LibSessionUtil, 'pendingChangesForUs'>;
|
||||||
|
let dump: TypedStub<typeof GenericWrapperActions, 'dump'>;
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
window.Whisper = {};
|
||||||
|
window.Whisper.events = {};
|
||||||
|
window.Whisper.events.trigger = Sinon.mock();
|
||||||
|
stubConfigDumpData('saveConfigDump').resolves();
|
||||||
|
|
||||||
|
pendingChangesForUsStub = Sinon.stub(LibSessionUtil, 'pendingChangesForUs');
|
||||||
|
dump = Sinon.stub(GenericWrapperActions, 'dump').resolves(new Uint8Array());
|
||||||
|
sendStub = Sinon.stub(MessageSender, 'sendEncryptedDataToSnode');
|
||||||
|
});
|
||||||
|
afterEach(() => {
|
||||||
|
Sinon.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('call savesDumpToDb even if no changes are required on the serverside', async () => {
|
||||||
|
Sinon.stub(GenericWrapperActions, 'needsDump').resolves(true);
|
||||||
|
const result = await UserSync.pushChangesToUserSwarmIfNeeded();
|
||||||
|
|
||||||
|
pendingChangesForUsStub.resolves(undefined);
|
||||||
|
expect(result).to.be.eq(RunJobResult.Success);
|
||||||
|
expect(sendStub.callCount).to.be.eq(0);
|
||||||
|
expect(pendingChangesForUsStub.callCount).to.be.eq(1);
|
||||||
|
expect(dump.callCount).to.be.eq(4);
|
||||||
|
expect(dump.getCalls().map(m => m.args)).to.be.deep.eq([
|
||||||
|
['UserConfig'],
|
||||||
|
['ContactsConfig'],
|
||||||
|
['UserGroupsConfig'],
|
||||||
|
['ConvoInfoVolatileConfig'],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls sendEncryptedDataToSnode with the right data x2 and retry if network returned nothing', async () => {
|
||||||
|
Sinon.stub(GenericWrapperActions, 'needsDump').resolves(false);
|
||||||
|
|
||||||
|
const profile = userChange(sodium, SnodeNamespaces.UserProfile, 321);
|
||||||
|
const contact = userChange(sodium, SnodeNamespaces.UserContacts, 123);
|
||||||
|
const networkTimestamp = 4444;
|
||||||
|
const ttl = TTL_DEFAULT.TTL_CONFIG;
|
||||||
|
Sinon.stub(GetNetworkTime, 'getNowWithNetworkOffset').returns(networkTimestamp);
|
||||||
|
pendingChangesForUsStub.resolves({
|
||||||
|
messages: [profile, contact],
|
||||||
|
allOldHashes: new Set('123'),
|
||||||
|
});
|
||||||
|
const result = await UserSync.pushChangesToUserSwarmIfNeeded();
|
||||||
|
|
||||||
|
sendStub.resolves(undefined);
|
||||||
|
expect(result).to.be.eq(RunJobResult.RetryJobIfPossible); // not returning anything in the sendstub so network issue happened
|
||||||
|
expect(sendStub.callCount).to.be.eq(1);
|
||||||
|
expect(pendingChangesForUsStub.callCount).to.be.eq(1);
|
||||||
|
expect(dump.callCount).to.be.eq(0);
|
||||||
|
|
||||||
|
function expected(details: any) {
|
||||||
|
return {
|
||||||
|
namespace: details.namespace,
|
||||||
|
data: details.ciphertext,
|
||||||
|
ttl,
|
||||||
|
networkTimestamp,
|
||||||
|
pubkey: sessionId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
throw null; //
|
||||||
|
// this test is not right. check for dump logic and make sure this matches
|
||||||
|
|
||||||
|
const expectedProfile = expected(profile);
|
||||||
|
const expectedContact = expected(contact);
|
||||||
|
expect(sendStub.firstCall.args).to.be.deep.eq([
|
||||||
|
[expectedProfile, expectedContact],
|
||||||
|
sessionId,
|
||||||
|
new Set('123'),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls sendEncryptedDataToSnode with the right data x3 and retry if network returned nothing then success', async () => {
|
||||||
|
const profile = userChange(sodium, SnodeNamespaces.UserProfile, 321);
|
||||||
|
const contact = userChange(sodium, SnodeNamespaces.UserContacts, 123);
|
||||||
|
const groups = userChange(sodium, SnodeNamespaces.UserGroups, 111);
|
||||||
|
|
||||||
|
throw null; //
|
||||||
|
// this test is not right. check for dump logic and make sure this matches
|
||||||
|
pendingChangesForUsStub.resolves({
|
||||||
|
messages: [profile, contact, groups],
|
||||||
|
allOldHashes: new Set('123'),
|
||||||
|
});
|
||||||
|
const changes: Array<UserSuccessfulChange> = [
|
||||||
|
{
|
||||||
|
pushed: profile,
|
||||||
|
updatedHash: 'hashprofile',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pushed: contact,
|
||||||
|
updatedHash: 'hashcontact',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pushed: groups,
|
||||||
|
updatedHash: 'hashgroup',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
Sinon.stub(LibSessionUtil, 'batchResultsToUserSuccessfulChange').returns(changes);
|
||||||
|
const confirmPushed = Sinon.stub(GenericWrapperActions, 'confirmPushed').resolves();
|
||||||
|
|
||||||
|
const needsDump = Sinon.stub(GenericWrapperActions, 'needsDump').resolves();
|
||||||
|
|
||||||
|
sendStub.resolves([
|
||||||
|
{ code: 200, body: { hash: 'hashprofile' } },
|
||||||
|
{ code: 200, body: { hash: 'hashcontact' } },
|
||||||
|
{ code: 200, body: { hash: 'hashgroup' } },
|
||||||
|
{ code: 200, body: {} }, // because we are giving a set of allOldHashes
|
||||||
|
]);
|
||||||
|
const result = await UserSync.pushChangesToUserSwarmIfNeeded();
|
||||||
|
|
||||||
|
expect(sendStub.callCount).to.be.eq(1);
|
||||||
|
expect(pendingChangesForUsStub.callCount).to.be.eq(1);
|
||||||
|
expect(dump.callCount).to.be.eq(2);
|
||||||
|
expect(dump.firstCall.args).to.be.deep.eq([sessionId]);
|
||||||
|
expect(needsDump.callCount).to.be.eq(4);
|
||||||
|
expect(needsDump.getCalls().map(m => m.args)).to.be.deep.eq([[sessionId, []]]);
|
||||||
|
|
||||||
|
expect(confirmPushed.callCount).to.be.eq(4);
|
||||||
|
expect(confirmPushed.getCalls().map(m => m.args)).to.be.deep.eq([[sessionId, [123, 'hash1']]]);
|
||||||
|
expect(result).to.be.eq(RunJobResult.Success);
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue