diff --git a/integration_test/settings_test.js b/integration_test/settings_test.js index 50f2b507e..cf463a411 100644 --- a/integration_test/settings_test.js +++ b/integration_test/settings_test.js @@ -31,7 +31,6 @@ describe('Settings', function() { app = await common.startAndStub(appProps); }); - after(async () => { await common.stopApp(app); diff --git a/js/models/conversation.d.ts b/js/models/conversation.d.ts index 02c78688a..137ae18ab 100644 --- a/js/models/conversation.d.ts +++ b/js/models/conversation.d.ts @@ -3,6 +3,7 @@ interface ConversationAttributes { left: boolean; expireTimer: number; profileSharing: boolean; + secondaryStatus: boolean; mentionedUs: boolean; unreadCount: number; isArchived: boolean; diff --git a/js/modules/data.d.ts b/js/modules/data.d.ts index f95dd8a6e..d91ecb45a 100644 --- a/js/modules/data.d.ts +++ b/js/modules/data.d.ts @@ -1,7 +1,7 @@ import { ConversationType } from '../../ts/state/ducks/conversations'; import { Mesasge } from '../../ts/types/Message'; -type IdentityKey = { +export type IdentityKey = { id: string; publicKey: ArrayBuffer; firstUse: boolean; @@ -9,14 +9,14 @@ type IdentityKey = { nonblockingApproval: boolean; }; -type PreKey = { +export type PreKey = { id: number; publicKey: ArrayBuffer; privateKey: ArrayBuffer; recipient: string; }; -type SignedPreKey = { +export type SignedPreKey = { id: number; publicKey: ArrayBuffer; privateKey: ArrayBuffer; @@ -25,14 +25,14 @@ type SignedPreKey = { signature: ArrayBuffer; }; -type ContactPreKey = { +export type ContactPreKey = { id: number; identityKeyString: string; publicKey: ArrayBuffer; keyId: number; }; -type ContactSignedPreKey = { +export type ContactSignedPreKey = { id: number; identityKeyString: string; publicKey: ArrayBuffer; @@ -42,18 +42,18 @@ type ContactSignedPreKey = { confirmed: boolean; }; -type PairingAuthorisation = { +export type PairingAuthorisation = { primaryDevicePubKey: string; secondaryDevicePubKey: string; requestSignature: ArrayBuffer; grantSignature?: ArrayBuffer; }; -type GuardNode = { +export type GuardNode = { ed25519PubKey: string; }; -type SwarmNode = { +export type SwarmNode = { address: string; ip: string; port: string; @@ -61,19 +61,19 @@ type SwarmNode = { pubkey_x25519: string; }; -type StorageItem = { +export type StorageItem = { id: string; value: any; }; -type SessionDataInfo = { +export type SessionDataInfo = { id: string; number: string; deviceId: number; record: string; }; -type ServerToken = { +export type ServerToken = { serverUrl: string; token: string; }; @@ -233,7 +233,7 @@ export function saveSeenMessageHash(data: { hash: string; }): Promise; -// TODO: Strictly type the following +// TODO: Strictly export type the following export function updateLastHash(data: any): Promise; export function saveSeenMessageHashes(data: any): Promise; export function saveLegacyMessage(data: any): Promise; diff --git a/ts/session/utils/SyncMessage.ts b/ts/session/utils/SyncMessage.ts index 033a2241d..5817dc943 100644 --- a/ts/session/utils/SyncMessage.ts +++ b/ts/session/utils/SyncMessage.ts @@ -4,7 +4,9 @@ import { getAllConversations } from '../../../js/modules/data'; import { ContentMessage, SyncMessage } from '../messages/outgoing'; import { MultiDeviceProtocol } from '../protocols'; -export function toSyncMessage(message: ContentMessage): SyncMessage | undefined { +export function toSyncMessage( + message: ContentMessage +): SyncMessage | undefined { if (message instanceof SyncMessage) { return message; } @@ -27,23 +29,12 @@ export async function getSyncContacts(): Promise | undefined> { if (!thisDevice) { return []; } - - console.log('[vince] window.Whisper:', window.Whisper); - console.log('[vince] window.Whisper:', window.Whisper); - console.log('[vince] window.Whisper:', window.Whisper); - console.log('[vince] window.Whisper:', window.Whisper); const primaryDevice = await MultiDeviceProtocol.getPrimaryDevice(thisDevice); const conversations = await getAllConversations({ ConversationCollection: window.Whisper.ConversationCollection, }); - console.log('[vince] conversations:', conversations); - console.log('[vince] conversations:', conversations); - console.log('[vince] conversations:', conversations); - console.log('[vince] conversations:', conversations); - console.log('[vince] conversations:', conversations); - // We are building a set of all contacts const primaryContacts = conversations.filter( @@ -62,20 +53,20 @@ export async function getSyncContacts(): Promise | undefined> { c.attributes.secondaryStatus ); - const seondaryContactsPromise = secondaryContactsPartial.map(async c => + const secondaryContactsPromise = secondaryContactsPartial.map(async c => window.ConversationController.getOrCreateAndWait( c.getPrimaryDevicePubKey(), 'private' ) ); - const secondaryContacts = (await Promise.all(seondaryContactsPromise)) + const secondaryContacts = (await Promise.all(secondaryContactsPromise)) // Filter out our primary key if it was added here .filter(c => c.id !== primaryDevice.key); // Return unique contacts return _.uniqBy( [...primaryContacts, ...secondaryContacts], - device => !!device + 'id' ); } diff --git a/ts/test/session/utils/Groups_test.ts b/ts/test/session/utils/Groups_test.ts index 2fda27ea2..0aa2840c8 100644 --- a/ts/test/session/utils/Groups_test.ts +++ b/ts/test/session/utils/Groups_test.ts @@ -14,13 +14,10 @@ chai.use(chaiAsPromised); const { expect } = chai; describe('Groups Utils', () => { - describe('getGroupMembers', () => { it('', async () => { // }); - - }); describe('isMediumGroup', () => { @@ -28,8 +25,4 @@ describe('Groups Utils', () => { // }); }); - - - - }); diff --git a/ts/test/session/utils/Messages_test.ts b/ts/test/session/utils/Messages_test.ts index 922d5c758..a3775abfb 100644 --- a/ts/test/session/utils/Messages_test.ts +++ b/ts/test/session/utils/Messages_test.ts @@ -10,7 +10,6 @@ chai.use(chaiAsPromised); const { expect } = chai; describe('Message Utils', () => { - describe('toRawMessage', () => { it('can convert to raw message', async () => { const device = TestUtils.generateFakePubKey(); @@ -37,8 +36,14 @@ describe('Message Utils', () => { const rawBufferJSON = JSON.stringify(rawBuffer); const messageBufferJSON = JSON.stringify(message.plainTextBuffer()); - expect(rawBuffer instanceof Uint8Array).to.equal(true, 'raw message did not contain a plainTextBuffer'); - expect(rawBufferJSON).to.equal(messageBufferJSON, 'plainTextBuffer was not converted correctly'); + expect(rawBuffer instanceof Uint8Array).to.equal( + true, + 'raw message did not contain a plainTextBuffer' + ); + expect(rawBufferJSON).to.equal( + messageBufferJSON, + 'plainTextBuffer was not converted correctly' + ); }); it('should maintain pubkey', async () => { @@ -49,8 +54,10 @@ describe('Message Utils', () => { const derivedPubKey = PubKey.from(rawMessage.device); expect(derivedPubKey).to.exist; - expect(derivedPubKey?.isEqual(device)).to.equal(true, 'pubkey of message was not converted correctly'); + expect(derivedPubKey?.isEqual(device)).to.equal( + true, + 'pubkey of message was not converted correctly' + ); }); - }); }); diff --git a/ts/test/session/utils/Promise_test.ts b/ts/test/session/utils/Promise_test.ts index a120e5fe8..229f64873 100644 --- a/ts/test/session/utils/Promise_test.ts +++ b/ts/test/session/utils/Promise_test.ts @@ -14,27 +14,21 @@ chai.use(chaiAsPromised); const { expect } = chai; describe('Promise Utils', () => { - describe('poll', () => { it('', async () => { // }); }); - + describe('waitForTask', () => { it('', async () => { // }); - - }); - - describe('waitUntil', () => { it('', async () => { // }); }); - }); diff --git a/ts/test/session/utils/String_test.ts b/ts/test/session/utils/String_test.ts index 73cac6bcf..d06987dce 100644 --- a/ts/test/session/utils/String_test.ts +++ b/ts/test/session/utils/String_test.ts @@ -14,13 +14,10 @@ chai.use(chaiAsPromised); const { expect } = chai; describe('String Utils', () => { - describe('encode', () => { it('', async () => { // }); - - }); describe('decode', () => { @@ -28,5 +25,4 @@ describe('String Utils', () => { // }); }); - }); diff --git a/ts/test/session/utils/SyncMessage_test.ts b/ts/test/session/utils/SyncMessage_test.ts index 6a98660c8..5a47f7bd4 100644 --- a/ts/test/session/utils/SyncMessage_test.ts +++ b/ts/test/session/utils/SyncMessage_test.ts @@ -6,8 +6,8 @@ import { SyncMessageUtils } from '../../../session/utils/'; import { SyncMessage } from '../../../session/messages/outgoing'; import { TestUtils } from '../../test-utils'; import { UserUtil } from '../../../util'; -import { generateFakePubKey } from '../../test-utils/testUtils'; import { MultiDeviceProtocol } from '../../../session/protocols'; +import { Integer } from '../../../types/Util'; // tslint:disable-next-line: no-require-imports no-var-requires const chaiAsPromised = require('chai-as-promised'); @@ -16,7 +16,6 @@ chai.use(chaiAsPromised); const { expect } = chai; describe('Sync Message Utils', () => { - describe('toSyncMessage', () => { it('can convert to sync message', async () => { const message = TestUtils.generateChatMessage(); @@ -28,8 +27,6 @@ describe('Sync Message Utils', () => { // Further tests required }); - - }); describe('canSync', () => { @@ -48,64 +45,119 @@ describe('Sync Message Utils', () => { const canSync = SyncMessageUtils.canSync(message); expect(canSync).to.equal(false, ''); }); - }); - // describe('getSyncContacts', () => { - // let getAllConversationsStub: sinon.SinonStub; - - // const primaryDevicePubkey = generateFakePubKey().key; - // let conversations = [ - // { - // isPrivate: () => true, - // isOurLocalDevice: () => false, - // isBlocked: () => false, - // getPrimaryDevicePubKey: () => primaryDevicePubkey, - - // attributes: { - // secondaryStatus: undefined, - // }, - // }, - // ]; - + describe('getSyncContacts', () => { + let getAllConversationsStub: sinon.SinonStub; + let getOrCreateAndWaitStub: sinon.SinonStub; + let getOrCreatAndWaitItem: any; + + // tslint:disable-next-line: insecure-random + const randomBoolean = () => !!Math.round(Math.random()); + const randomMockConv = (primary: boolean) => ( + // new (function(primary) { + + // return { + // id: generateFakePubKey().key, + // isPrivate: () => true, + // isOurLocalDevice: () => false, + // isBlocked: () => false, + // getPrimaryDevicePubKey: () => this.isPrivate ? + + // attributes: { + // secondaryStatus: !primary, + // }, + // }; + // })(); + {} + ); + + + + // Fill half with secondaries, half with primaries + const numConversations = 20; + const primaryConversations = new Array(numConversations / 2) + .fill({}) + .map(() => randomMockConv(true)); + const secondaryConversations = new Array(numConversations / 2) + .fill({}) + .map(() => randomMockConv(false)); + const conversations = [...primaryConversations, ...secondaryConversations]; + + const sandbox = sinon.createSandbox(); + const ourDevice = TestUtils.generateFakePubKey(); + const ourNumber = ourDevice.key; + + const ourPrimaryDevice = TestUtils.generateFakePubKey(); + + beforeEach(async () => { + // Util Stubs + TestUtils.stubWindow('Whisper', { + ConversationCollection: sandbox.stub(), + }); + + getAllConversationsStub = TestUtils.stubData( + 'getAllConversations' + ).resolves(conversations); + + // Scale result in sync with secondaryConversations on callCount + getOrCreateAndWaitStub = sandbox.stub().callsFake(() => { + const item = secondaryConversations[getOrCreateAndWaitStub.callCount - 1]; + + // Make the item a primary device to match the call in SyncMessage under secondaryContactsPromise + getOrCreatAndWaitItem = { + ...item, + getPrimaryDevicePubKey: () => item.id, + attributes: { + secondaryStatus: false, + }, + }; + + return getOrCreatAndWaitItem; + }); + + TestUtils.stubWindow('ConversationController', { + getOrCreateAndWait: getOrCreateAndWaitStub, + }); + + // Stubs + sandbox.stub(UserUtil, 'getCurrentDevicePubKey').resolves(ourNumber); + sandbox + .stub(MultiDeviceProtocol, 'getPrimaryDevice') + .resolves(ourPrimaryDevice); + }); + afterEach(() => { + sandbox.restore(); + TestUtils.restoreStubs(); + }); - // const sandbox = sinon.createSandbox(); - // const ourDevice = TestUtils.generateFakePubKey(); - // const ourNumber = ourDevice.key; + it('can get sync contacts with only primary contacts', async () => { + getAllConversationsStub.resolves(primaryConversations); - // const ourPrimaryDevice = TestUtils.generateFakePubKey(); - // const ourPrimaryNumber = ourPrimaryDevice.key; + const contacts = await SyncMessageUtils.getSyncContacts(); + expect(getAllConversationsStub.callCount).to.equal(1); - // beforeEach(async () => { + // Each contact should be a primary device + expect(contacts).to.have.length(numConversations / 2); + expect(contacts?.find(c => c.attributes.secondaryStatus)).to.not.exist; + }); - // getAllConversationsStub = TestUtils.stubData('getAllConversations').resolves(conversations); + it('can get sync contacts of assorted primaries and secondaries', async () => { + // Map secondary contacts to stub resolution + const contacts = await SyncMessageUtils.getSyncContacts(); + expect(getAllConversationsStub.callCount).to.equal(1); - // // Stubs - // sandbox.stub(UserUtil, 'getCurrentDevicePubKey').resolves(ourNumber); - // sandbox.stub(MultiDeviceProtocol, 'getPrimaryDevice').resolves(ourPrimaryDevice); + // We should have numConversations unique contacts + expect(contacts).to.have.length(numConversations); - // }); - - // afterEach(() => { - // sandbox.restore(); - // }); - - // it('can get sync contacts', async () => { - // // MAKE MORE SPECIFIC, CHECK PARAMETERS + // All contacts should be primary; half of which some from secondaries in secondaryContactsPromise + expect(contacts?.find(c => c.attributes.secondaryStatus)).to.not.exist; + expect(contacts) - // const contacts = await SyncMessageUtils.getSyncContacts(); - - // console.log('[vince] contacts:', contacts); - // console.log('[vince] contacts:', contacts); - // console.log('[vince] getAllConversationsStub.callCount:', getAllConversationsStub.callCount); - // console.log('[vince] getAllConversationsStub.callCount:', getAllConversationsStub.callCount); - // }); - - // }); - - + }); + }); - + // MAKE MORE SPECIFIC, CHECK PARAMETERS }); diff --git a/ts/test/test-utils/testUtils.ts b/ts/test/test-utils/testUtils.ts index eb9644838..69bbf7826 100644 --- a/ts/test/test-utils/testUtils.ts +++ b/ts/test/test-utils/testUtils.ts @@ -4,13 +4,14 @@ import * as window from '../../window'; import * as DataShape from '../../../js/modules/data'; import { v4 as uuid } from 'uuid'; -import { PubKey } from '../../../ts/session/types'; +import { OpenGroup, PubKey } from '../../../ts/session/types'; import { ChatMessage, ClosedGroupChatMessage, OpenGroupMessage, } from '../../session/messages/outgoing'; -import { OpenGroup } from '../../session/types/OpenGroup'; +import { Integer } from '../../types/Util'; +import { ConversationModel, ConversationAttributes } from '../../../js/models/conversation'; const globalAny: any = global; const sandbox = sinon.createSandbox(); @@ -78,11 +79,11 @@ export function generateFakePubKey(): PubKey { return new PubKey(pubkeyString); } -export function generateFakePubKeys(amount: number): Array { +export function generateFakePubKeys(amount: Integer): Array { const numPubKeys = amount > 0 ? Math.floor(amount) : 0; // tslint:disable-next-line: no-unnecessary-callback-wrapper - return new Array(numPubKeys).fill(0).map(() => generateFakePubKey()); + return new Array(amount).fill(0).map(() => generateFakePubKey()); } export function generateChatMessage(identifier?: string): ChatMessage { @@ -124,3 +125,39 @@ export function generateClosedGroupMessage( chatMessage: generateChatMessage(), }); } + + // Mock ConversationModel +export class MockPrivateConversation implements ConversationModel { + public id: string; + public isPrimary: boolean; + public attributes: ConversationAttributes; + + constructor(isPrimary: boolean) { + this.isPrimary = isPrimary; + + this.id = TestUtils.generateFakePubKey().key; + this.attributes = { + members + } + } + + public isPrivate() { + return true; + } + + public isOurLocalDevice() { + return false; + } + + public isBlocked() { + return false; + } + + public getPrimaryDevicePubKey() { + return this.isPrimary + ? this.id + : TestUtils.generateFakePubKey().key; + } + } + + const myconv = new MockPrivateConversation(false) ; \ No newline at end of file diff --git a/ts/types/Util.ts b/ts/types/Util.ts index bf9cd05de..e3455e0a3 100644 --- a/ts/types/Util.ts +++ b/ts/types/Util.ts @@ -1,3 +1,8 @@ + +// Integer Type - primarily for incremental values in ConversationModel etc +export type Integer = number & { __int__: void }; +export const roundToInt = (num: number): Integer => Math.round(num) as Integer; + export type RenderTextCallbackType = (options: { text: string; key: number;