remove inte tests + handle opengroupv2 messages in chunk

this is to avoid freezing UI and is only a temporary fix

We need to have webworkers in this app
This commit is contained in:
Audric Ackermann 2021-06-03 17:59:41 +10:00
parent f3768a674a
commit 118813661d
No known key found for this signature in database
GPG key ID: 999F434D76324AD4
22 changed files with 57 additions and 1475 deletions

View file

@ -160,6 +160,7 @@ const triggerSyncIfNeeded = async () => {
const removeAllV1OpenGroups = async () => {
const allV1Convos = (await getAllOpenGroupV1Conversations()).models || [];
// do this sequentially to avoid hurting the db
// tslint:disable-next-line: prefer-for-of
for (let index = 0; index < allV1Convos.length; index++) {
const v1Convo = allV1Convos[index];

View file

@ -86,6 +86,7 @@ export class SessionConversation extends React.Component<Props, State> {
private readonly messageContainerRef: React.RefObject<HTMLDivElement>;
private dragCounter: number;
private publicMembersRefreshTimeout?: NodeJS.Timeout;
private readonly updateMemberList: () => any;
constructor(props: any) {
super(props);
@ -108,6 +109,7 @@ export class SessionConversation extends React.Component<Props, State> {
this.compositionBoxRef = React.createRef();
this.messageContainerRef = React.createRef();
this.dragCounter = 0;
this.updateMemberList = _.debounce(this.updateMemberListBouncy, 1000);
autoBind(this);
}
@ -1125,8 +1127,9 @@ export class SessionConversation extends React.Component<Props, State> {
}
}
private async updateMemberList() {
private async updateMemberListBouncy() {
const allPubKeys = await getPubkeysInPublicConversation(this.props.selectedConversationKey);
window?.log?.info(`getPubkeysInPublicConversation returned '${allPubKeys?.length}' members`);
const allMembers = allPubKeys.map((pubKey: string) => {

View file

@ -10,6 +10,7 @@ import { MessageAttributes } from '../models/messageType';
import { HexKeyPair } from '../receiver/keypairs';
import { getSodium } from '../session/crypto';
import { PubKey } from '../session/types';
import { sleepFor } from '../session/utils/Promise';
import { fromArrayBufferToBase64, fromBase64ToArrayBuffer } from '../session/utils/String';
import { ConversationType } from '../state/ducks/conversations';
import { channels } from './channels';
@ -799,7 +800,7 @@ export async function removeAllMessagesInConversation(conversationId: string): P
// time so we don't use too much memory.
// eslint-disable-next-line no-await-in-loop
messages = await getMessagesByConversation(conversationId, {
limit: 100,
limit: 25,
});
if (!messages.length) {
@ -815,6 +816,7 @@ export async function removeAllMessagesInConversation(conversationId: string): P
// eslint-disable-next-line no-await-in-loop
await channels.removeMessage(ids);
await sleepFor(10);
} while (messages.length > 0);
}

View file

@ -1,7 +1,7 @@
import _ from 'lodash';
import { FileServerV2Request } from '../../fileserver/FileServerApiV2';
import { PubKey } from '../../session/types';
import { allowOnlyOneAtATime } from '../../session/utils/Promise';
import { allowOnlyOneAtATime, sleepFor } from '../../session/utils/Promise';
import { fromBase64ToArrayBuffer, fromHex } from '../../session/utils/String';
import { updateDefaultRooms, updateDefaultRoomsInProgress } from '../../state/ducks/defaultRooms';
import { getCompleteUrlFromRoom } from '../utils/OpenGroupUtils';
@ -46,37 +46,51 @@ export const parseMessages = async (
window?.log?.info('no new messages');
return [];
}
const messages = await Promise.all(
rawMessages.map(async r => {
try {
const opengroupv2Message = OpenGroupMessageV2.fromJson(r);
if (
!opengroupv2Message?.serverId ||
!opengroupv2Message.sentTimestamp ||
!opengroupv2Message.base64EncodedData ||
!opengroupv2Message.base64EncodedSignature
) {
window?.log?.warn('invalid open group message received');
const chunks = _.chunk(rawMessages, 10);
const handleChunk = async (chunk: Array<Record<string, any>>) => {
return Promise.all(
chunk.map(async r => {
try {
const opengroupv2Message = OpenGroupMessageV2.fromJson(r);
if (
!opengroupv2Message?.serverId ||
!opengroupv2Message.sentTimestamp ||
!opengroupv2Message.base64EncodedData ||
!opengroupv2Message.base64EncodedSignature
) {
window?.log?.warn('invalid open group message received');
return null;
}
// Validate the message signature
const senderPubKey = PubKey.cast(opengroupv2Message.sender).withoutPrefix();
const signature = fromBase64ToArrayBuffer(opengroupv2Message.base64EncodedSignature);
const messageData = fromBase64ToArrayBuffer(opengroupv2Message.base64EncodedData);
// throws if signature failed
await window.libsignal.Curve.async.verifySignature(
fromHex(senderPubKey),
messageData,
signature
);
return opengroupv2Message;
} catch (e) {
window?.log?.error('An error happened while fetching getMessages output:', e);
return null;
}
// Validate the message signature
const senderPubKey = PubKey.cast(opengroupv2Message.sender).withoutPrefix();
const signature = fromBase64ToArrayBuffer(opengroupv2Message.base64EncodedSignature);
const messageData = fromBase64ToArrayBuffer(opengroupv2Message.base64EncodedData);
// throws if signature failed
await window.libsignal.Curve.async.verifySignature(
fromHex(senderPubKey),
messageData,
signature
);
return opengroupv2Message;
} catch (e) {
window?.log?.error('An error happened while fetching getMessages output:', e);
return null;
}
})
);
return _.compact(messages).sort((a, b) => (a.serverId || 0) - (b.serverId || 0));
})
);
};
const allHandledMessages = [];
for (const currentChunk of chunks) {
window?.log?.info('Handling rawMessage chunk of size', currentChunk.length);
const messagesHandled = await handleChunk(currentChunk);
allHandledMessages.push(...messagesHandled);
await sleepFor(10);
}
return _.compact(allHandledMessages).sort((a, b) => (a.serverId || 0) - (b.serverId || 0));
};
// tslint:disable: no-http-string
const defaultServerUrl = 'http://116.203.70.33';

View file

@ -311,7 +311,8 @@ export async function handleDataMessage(
// Check if we need to update any profile names
if (!isMe && senderConversation && message.profile) {
await updateProfileOneAtATime(senderConversation, message.profile, message.profileKey);
// do not await this
void updateProfileOneAtATime(senderConversation, message.profile, message.profileKey);
}
if (isMessageEmpty(message)) {
window?.log?.warn(`Message ${getEnvelopeId(envelope)} ignored; it was empty`);

View file

@ -12,14 +12,14 @@ const PADDING_BYTE = 0x00;
*/
export function removeMessagePadding(paddedData: ArrayBuffer): ArrayBuffer {
const paddedPlaintext = new Uint8Array(paddedData);
window?.log?.info('Removing message padding...');
// window?.log?.info('Removing message padding...');
for (let i = paddedPlaintext.length - 1; i >= 0; i -= 1) {
if (paddedPlaintext[i] === 0x80) {
const plaintext = new Uint8Array(i);
plaintext.set(paddedPlaintext.subarray(0, i));
return plaintext.buffer;
} else if (paddedPlaintext[i] !== PADDING_BYTE) {
window?.log?.warn('got a message without padding... Letting it through for now');
// window?.log?.warn('got a message without padding... Letting it through for now');
return paddedPlaintext;
}
}

View file

@ -6,7 +6,7 @@ import crypto from 'crypto';
// libsodium-wrappers requires the `require` call to work
// tslint:disable-next-line: no-require-imports
import libsodiumwrappers = require('libsodium-wrappers');
import libsodiumwrappers from 'libsodium-wrappers';
import { toHex } from '../utils/String';
import { ECKeyPair } from '../../receiver/keypairs';

View file

@ -1,76 +0,0 @@
/* eslint-disable func-names */
/* eslint-disable import/no-extraneous-dependencies */
// tslint:disable: await-promise
// tslint:disable: no-implicit-dependencies
// tslint:disable: no-invalid-this
import { afterEach, beforeEach, describe, it } from 'mocha';
import { Common } from './common';
import { Application } from 'spectron';
import ConversationPage from './page-objects/conversation.page';
describe('Add contact', function() {
this.timeout(60000);
this.slow(20000);
let app: Application;
let app2: Application;
beforeEach(async () => {
await Common.killallElectron();
await Common.stopStubSnodeServer();
const app1Props = {
recoveryPhrase: Common.TEST_RECOVERY_PHRASE_1,
displayName: Common.TEST_DISPLAY_NAME1,
};
const app2Props = {
recoveryPhrase: Common.TEST_RECOVERY_PHRASE_2,
displayName: Common.TEST_DISPLAY_NAME2,
};
[app, app2] = await Promise.all([
Common.startAndStub(app1Props),
Common.startAndStubN(app2Props, 2),
]);
});
afterEach(async () => {
await Common.stopApp(app);
await Common.killallElectron();
await Common.stopStubSnodeServer();
});
it('addContacts: can add a contact by sessionID', async () => {
const textMessage = Common.generateSendMessageText();
await app.client.element(ConversationPage.contactsButtonSection).click();
await app.client.element(ConversationPage.addContactButton).click();
await app.client.isExisting(ConversationPage.leftPaneOverlay).should.eventually.be.true;
await Common.setValueWrapper(app, ConversationPage.sessionIDInput, Common.TEST_PUBKEY2);
await app.client
.element(ConversationPage.sessionIDInput)
.getValue()
.should.eventually.equal(Common.TEST_PUBKEY2);
await app.client.element(ConversationPage.nextButton).click();
await app.client.waitForExist(ConversationPage.sendMessageTextarea, 1000);
// send a text message to that user
await app.client.element(ConversationPage.sendMessageTextarea).setValue(textMessage);
await app.client.keys('Enter');
await app.client.waitForExist(ConversationPage.existingSendMessageText(textMessage), 1000);
// assure session request message has been sent
await Common.timeout(3000);
await app.client.isExisting(ConversationPage.retrySendButton).should.eventually.be.false;
await app2.client.waitForExist(ConversationPage.conversationItem, 5000);
await app2.client.element(ConversationPage.conversationItem).click();
await app2.client.waitForExist(ConversationPage.existingReceivedMessageText(textMessage), 1000);
});
});

View file

@ -1,52 +0,0 @@
/* eslint-disable func-names */
/* eslint-disable import/no-extraneous-dependencies */
// tslint:disable: await-promise
// tslint:disable: no-implicit-dependencies
// tslint:disable: no-invalid-this
import { afterEach, beforeEach, describe, it } from 'mocha';
import { Common } from './common';
import { Application } from 'spectron';
import ConversationPage from './page-objects/conversation.page';
describe('Closed groups', function() {
this.timeout(60000);
this.slow(20000);
let app: Application;
let app2: Application;
beforeEach(async () => {
await Common.killallElectron();
await Common.stopStubSnodeServer();
[app, app2] = await Common.startAppsAsFriends();
});
afterEach(async () => {
await Common.stopApp(app);
await Common.killallElectron();
await Common.stopStubSnodeServer();
});
it('closedGroup: can create a closed group with a friend and send/receive a message', async () => {
// create group and add new friend
await Common.addFriendToNewClosedGroup([app, app2]);
// send a message from app and validate it is received on app2
const textMessage = Common.generateSendMessageText();
await app.client.element(ConversationPage.sendMessageTextarea).setValue(textMessage);
await app.client
.element(ConversationPage.sendMessageTextarea)
.getValue()
.should.eventually.equal(textMessage);
// send the message
await app.client.keys('Enter');
// validate that the message has been added to the message list view
await app.client.waitForExist(ConversationPage.existingSendMessageText(textMessage), 2000);
// validate that the message has been added to the message list view
await app2.client.waitForExist(ConversationPage.existingReceivedMessageText(textMessage), 5000);
});
});

View file

@ -1,570 +0,0 @@
// tslint:disable: no-implicit-dependencies
import { Application } from 'spectron';
import path from 'path';
import url from 'url';
import http from 'http';
import fse from 'fs-extra';
import { exec } from 'child_process';
import chai from 'chai';
import chaiAsPromised from 'chai-as-promised';
import CommonPage from './page-objects/common.page';
import RegistrationPage from './page-objects/registration.page';
import ConversationPage from './page-objects/conversation.page';
import SettingsPage from './page-objects/settings.page';
chai.should();
chai.use(chaiAsPromised as any);
chai.config.includeStack = true;
// From https://github.com/chaijs/chai/issues/200
chai.use((_chai, _) => {
_chai.Assertion.addMethod('withMessage', (msg: string) => {
_.flag(Common, 'message', msg);
});
});
const STUB_SNODE_SERVER_PORT = 3000;
const ENABLE_LOG = false;
// tslint:disable-next-line: no-unnecessary-class
export class Common {
/* ************** USERS ****************** */
public static readonly TEST_RECOVERY_PHRASE_1 =
'faxed mechanic mocked agony unrest loincloth pencil eccentric boyfriend oasis speedy ribbon faxed';
public static readonly TEST_PUBKEY1 =
'0552b85a43fb992f6bdb122a5a379505a0b99a16f0628ab8840249e2a60e12a413';
public static readonly TEST_DISPLAY_NAME1 = 'tester_Alice';
public static readonly TEST_RECOVERY_PHRASE_2 =
'guide inbound jerseys bays nouns basin sulking awkward stockpile ostrich ascend pylons ascend';
public static readonly TEST_PUBKEY2 =
'054e1ca8681082dbd9aad1cf6fc89a32254e15cba50c75b5a73ac10a0b96bcbd2a';
public static readonly TEST_DISPLAY_NAME2 = 'tester_Bob';
public static readonly TEST_RECOVERY_PHRASE_3 =
'alpine lukewarm oncoming blender kiwi fuel lobster upkeep vogue simplest gasp fully simplest';
public static readonly TEST_PUBKEY3 =
'05f8662b6e83da5a31007cc3ded44c601f191e07999acb6db2314a896048d9036c';
public static readonly TEST_DISPLAY_NAME3 = 'tester_Charlie';
/* ************** OPEN GROUPS ****************** */
public static readonly VALID_GROUP_URL = 'https://chat.getsession.org';
public static readonly VALID_GROUP_URL2 = 'https://chat-dev.lokinet.org';
public static readonly VALID_GROUP_NAME = 'Session Public Chat';
public static readonly VALID_GROUP_NAME2 = 'Loki Dev Chat';
/* ************** CLOSED GROUPS ****************** */
public static readonly VALID_CLOSED_GROUP_NAME1 = 'Closed Group 1';
public static USER_DATA_ROOT_FOLDER = '';
private static stubSnode: any;
private static messages: any;
private static fileServer: any;
// tslint:disable: await-promise
// tslint:disable: no-console
public static async timeout(ms: number) {
// tslint:disable-next-line: no-string-based-set-timeout
return new Promise(resolve => setTimeout(resolve, ms));
}
public static async closeToast(app: Application) {
await app.client.element(CommonPage.toastCloseButton).click();
}
// a wrapper to work around electron/spectron bug
public static async setValueWrapper(app: Application, selector: any, value: string) {
// keys, setValue and addValue hang on certain platforms
if (process.platform === 'darwin') {
await app.client.execute(
(slctr, val) => {
// eslint-disable-next-line no-undef
const iter = document.evaluate(
slctr,
document,
null,
XPathResult.UNORDERED_NODE_ITERATOR_TYPE,
null
);
const elem = iter.iterateNext() as any;
if (elem) {
elem.value = val;
} else {
console.error('Cant find', slctr, elem, iter);
}
},
selector,
value
);
// let session js detect the text change
await app.client.element(selector).click();
} else {
// Linux & Windows don't require wrapper
await app.client.element(selector).setValue(value);
}
}
public static async startApp(environment = 'test-integration-session') {
const env = environment.startsWith('test-integration') ? 'test-integration' : environment;
const instance = environment.replace('test-integration-', '');
const app1 = new Application({
path: path.join(__dirname, '..', '..', '..', '..', 'node_modules', '.bin', 'electron'),
args: ['.'],
env: {
NODE_ENV: env,
NODE_APP_INSTANCE: instance,
USE_STUBBED_NETWORK: true,
ELECTRON_ENABLE_LOGGING: true,
ELECTRON_ENABLE_STACK_DUMPING: true,
ELECTRON_DISABLE_SANDBOX: 1,
},
requireName: 'electronRequire',
// chromeDriverLogPath: '../chromedriverlog.txt',
chromeDriverArgs: [
`remote-debugging-port=${Math.floor(
// tslint:disable-next-line: insecure-random
Math.random() * (9999 - 9000) + 9000
)}`,
],
});
await app1.start();
await app1.client.waitUntilWindowLoaded();
return app1;
}
public static async startApp2() {
const app2 = await Common.startApp('test-integration-session-2');
return app2;
}
public static async stopApp(app1: Application) {
if (app1 && app1.isRunning()) {
await app1.stop();
}
}
public static async killallElectron() {
// rtharp - my 2nd client on MacOs needs: pkill -f "node_modules/.bin/electron"
// node_modules/electron/dist/electron is node_modules/electron/dist/Electron.app on MacOS
const killStr =
process.platform === 'win32'
? 'taskkill /im electron.exe /t /f'
: 'pkill -f "node_modules/electron/dist/electron" | pkill -f "node_modules/.bin/electron"';
return new Promise(resolve => {
exec(killStr, (_err, stdout, stderr) => {
resolve({ stdout, stderr });
});
});
}
public static async rmFolder(folder: string) {
await fse.remove(folder);
}
public static async startAndAssureCleanedApp2() {
const app2 = await Common.startAndAssureCleanedApp('test-integration-session-2');
return app2;
}
public static async startAndAssureCleanedApp(env = 'test-integration-session') {
const userData = path.join(Common.USER_DATA_ROOT_FOLDER, `Session-${env}`);
await Common.rmFolder(userData);
const app1 = await Common.startApp(env);
await app1.client.waitForExist(RegistrationPage.registrationTabSignIn, 4000);
return app1;
}
public static async startAndStub({
recoveryPhrase,
displayName,
env = 'test-integration-session',
}: {
recoveryPhrase: string;
displayName: string;
env?: string;
}) {
const app = await Common.startAndAssureCleanedApp(env);
Common.startStubSnodeServer();
if (recoveryPhrase && displayName) {
await Common.restoreFromRecoveryPhrase(app, recoveryPhrase, displayName);
// not sure we need Common - rtharp.
await Common.timeout(2000);
}
return app;
}
public static async startAndStubN(props: any, n: number) {
// Make app with stub as number n
const appN = await Common.startAndStub({
env: `test-integration-session-${n}`,
...props,
});
return appN;
}
public static async restoreFromRecoveryPhrase(
app: Application,
recoveryPhrase: string,
displayName: string
) {
await app.client.element(RegistrationPage.registrationTabSignIn).click();
await app.client.element(RegistrationPage.restoreFromSeedMode).click();
await Common.setValueWrapper(app, RegistrationPage.recoveryPhraseInput, recoveryPhrase);
await Common.setValueWrapper(app, RegistrationPage.displayNameInput, displayName);
// await app.client.element(RegistrationPage.continueSessionButton).click();
await app.client.keys('Enter');
await app.client.waitForExist(RegistrationPage.conversationListContainer, 4000);
}
public static async makeFriends(app1: Application, client2: [Application, string]) {
const [_, pubkey2] = client2;
/** add each other as friends */
const textMessage = Common.generateSendMessageText();
await app1.client.element(ConversationPage.contactsButtonSection).click();
await app1.client.element(ConversationPage.addContactButton).click();
await Common.setValueWrapper(app1, ConversationPage.sessionIDInput, pubkey2);
await app1.client.element(ConversationPage.nextButton).click();
await app1.client.waitForExist(ConversationPage.sendMessageTextareaAndMessage, 1000);
// send a text message to that user (will be a friend request)
await Common.setValueWrapper(app1, ConversationPage.sendMessageTextareaAndMessage, textMessage);
await app1.client.keys('Enter');
await app1.client.waitForExist(ConversationPage.existingSendMessageText(textMessage), 1000);
}
public static async startAppsAsFriends() {
const app1Props = {
recoveryPhrase: Common.TEST_RECOVERY_PHRASE_1,
displayName: Common.TEST_DISPLAY_NAME1,
stubSnode: true,
};
const app2Props = {
recoveryPhrase: Common.TEST_RECOVERY_PHRASE_2,
displayName: Common.TEST_DISPLAY_NAME2,
stubSnode: true,
};
const [app1, app2] = await Promise.all([
Common.startAndStub(app1Props),
Common.startAndStubN(app2Props, 2),
]);
await Common.makeFriends(app1, [app2, Common.TEST_PUBKEY2]);
return [app1, app2];
}
public static async addFriendToNewClosedGroup(members: Array<Application>) {
const [app, ...others] = members;
await app.client.element(ConversationPage.conversationButtonSection).click();
await app.client.element(ConversationPage.createClosedGroupButton).click();
await Common.setValueWrapper(
app,
ConversationPage.closedGroupNameTextarea,
Common.VALID_CLOSED_GROUP_NAME1
);
await app.client
.element(ConversationPage.closedGroupNameTextarea)
.getValue()
.should.eventually.equal(Common.VALID_CLOSED_GROUP_NAME1);
// Common assumes that app does not have any other friends
for (let i = 0; i < others.length; i += 1) {
// eslint-disable-next-line no-await-in-loop
await app.client.element(ConversationPage.createClosedGroupMemberItem(i)).isVisible().should
.eventually.be.true;
// eslint-disable-next-line no-await-in-loop
await app.client.element(ConversationPage.createClosedGroupMemberItem(i)).click();
}
await app.client.element(ConversationPage.createClosedGroupMemberItemSelected).isVisible()
.should.eventually.be.true;
// trigger the creation of the group
await app.client.element(ConversationPage.validateCreationClosedGroupButton).click();
await app.client.waitForExist(ConversationPage.sessionToastGroupCreatedSuccess, 1000);
await app.client.isExisting(
ConversationPage.headerTitleGroupName(Common.VALID_CLOSED_GROUP_NAME1)
).should.eventually.be.true;
await app.client.element(ConversationPage.headerTitleMembers(members.length)).isVisible().should
.eventually.be.true;
// validate overlay is closed
await app.client.isExisting(ConversationPage.leftPaneOverlay).should.eventually.be.equal(false);
// move back to the conversation section
await app.client.element(ConversationPage.conversationButtonSection).click();
// validate open chat has been added
await app.client.isExisting(
ConversationPage.rowOpenGroupConversationName(Common.VALID_CLOSED_GROUP_NAME1)
).should.eventually.be.true;
await Promise.all(
others.map(async otherApp => {
// next check that other members have been invited and have the group in their conversations
await otherApp.client.waitForExist(
ConversationPage.rowOpenGroupConversationName(Common.VALID_CLOSED_GROUP_NAME1),
6000
);
// open the closed group conversation on otherApp
await otherApp.client.element(ConversationPage.conversationButtonSection).click();
await Common.timeout(500);
await otherApp.client
.element(ConversationPage.rowOpenGroupConversationName(Common.VALID_CLOSED_GROUP_NAME1))
.click();
})
);
}
public static async linkApp2ToApp(app1: Application, app2: Application, app1Pubkey: string) {
// app needs to be logged in as user1 and app2 needs to be logged out
// start the pairing dialog for the first app
await app1.client.element(SettingsPage.settingsButtonSection).click();
await app1.client.isVisible(ConversationPage.noPairedDeviceMessage);
// we should not find the linkDeviceButtonDisabled button (as DISABLED)
await app1.client.isExisting(ConversationPage.linkDeviceButtonDisabled).should.eventually.be
.false;
await app1.client.element(ConversationPage.linkDeviceButton).click();
// validate device pairing dialog is shown and has a qrcode
await app1.client.isVisible(ConversationPage.qrImageDiv);
// next trigger the link request from the app2 with the app1 pubkey
await app2.client.element(RegistrationPage.registrationTabSignIn).click();
await app2.client.element(RegistrationPage.linkDeviceMode).click();
await Common.setValueWrapper(app2, RegistrationPage.textareaLinkDevicePubkey, app1Pubkey);
await app2.client.element(RegistrationPage.linkDeviceTriggerButton).click();
await app1.client.waitForExist(SettingsPage.secretWordsTextInDialog, 7000);
const secretWordsapp1 = await app1.client
.element(SettingsPage.secretWordsTextInDialog)
.getText();
await app1.client.waitForExist(RegistrationPage.linkWithThisDevice, 10000);
await app2.client
.element(RegistrationPage.secretWordsText)
.getText()
.should.eventually.be.equal(secretWordsapp1);
await app1.client.element(ConversationPage.allowPairingButton).click();
await app1.client.element(ConversationPage.okButton).click();
// validate device paired in settings list with correct secrets
await app1.client.waitForExist(ConversationPage.devicePairedDescription(secretWordsapp1), 2000);
await app1.client.isExisting(ConversationPage.unpairDeviceButton).should.eventually.be.true;
await app1.client.isExisting(ConversationPage.linkDeviceButtonDisabled).should.eventually.be
.true;
// validate app2 (secondary device) is linked successfully
await app2.client.waitForExist(RegistrationPage.conversationListContainer, 4000);
// validate primary pubkey of app2 is the same that in app1
await app2.webContents
.executeJavaScript("window.storage.get('primaryDevicePubKey')")
.should.eventually.be.equal(app1Pubkey);
}
public static async triggerUnlinkApp2FromApp(app1: Application, app2: Application) {
// check app2 is loggedin
await app2.client.isExisting(RegistrationPage.conversationListContainer).should.eventually.be
.true;
await app1.client.element(SettingsPage.settingsButtonSection).click();
await app1.client.isExisting(ConversationPage.linkDeviceButtonDisabled).should.eventually.be
.true;
// click the unlink button
await app1.client.element(ConversationPage.unpairDeviceButton).click();
await app1.client.element(ConversationPage.validateUnpairDevice).click();
await app1.client.waitForExist(ConversationPage.noPairedDeviceMessage, 5000);
await app1.client.element(ConversationPage.linkDeviceButton).isEnabled().should.eventually.be
.true;
// let time to app2 to catch the event and restart dropping its data
await Common.timeout(5000);
// check that the app restarted
// (did not find a better way than checking the app no longer being accessible)
let isApp2Joinable = true;
try {
await app2.client.isExisting(RegistrationPage.registrationTabSignIn).should.eventually.be
.true;
} catch (err) {
// if we get an error here, it means Spectron is lost.
// Common is a good thing because it means app2 restarted
isApp2Joinable = false;
}
if (isApp2Joinable) {
throw new Error(
'app2 is still joinable so it did not restart, so it did not unlink correctly'
);
}
}
public static async sendMessage(app: Application, messageText: string, fileLocation?: string) {
await Common.setValueWrapper(app, ConversationPage.sendMessageTextarea, messageText);
await app.client
.element(ConversationPage.sendMessageTextarea)
.getValue()
.should.eventually.equal(messageText);
// attach a file
if (fileLocation) {
await Common.setValueWrapper(app, ConversationPage.attachmentInput, fileLocation);
}
// send message
await app.client.element(ConversationPage.sendMessageTextarea).click();
await app.client.keys('Enter');
}
public static generateSendMessageText(): string {
return `Test message from integration tests ${Date.now()}`;
}
public static startStubSnodeServer() {
if (!Common.stubSnode) {
Common.messages = {};
Common.stubSnode = http.createServer((request: any, response: any) => {
const { query } = url.parse(request.url, true);
const { pubkey, data, timestamp } = query;
if (!pubkey) {
console.warn('NO PUBKEY');
response.writeHead(400, { 'Content-Type': 'text/html' });
response.end();
return;
}
if (Array.isArray(pubkey)) {
console.error('pubkey cannot be an array');
response.writeHead(400, { 'Content-Type': 'text/html' });
response.end();
return;
}
if (Array.isArray(data)) {
console.error('data cannot be an array');
response.writeHead(400, { 'Content-Type': 'text/html' });
response.end();
return;
}
if (request.method === 'POST') {
if (ENABLE_LOG) {
console.warn('POST', pubkey.substr(2, 3), data.substr(4, 10), timestamp);
}
let ori = Common.messages[pubkey];
if (!Common.messages[pubkey]) {
ori = [];
}
Common.messages[pubkey] = [...ori, { data, timestamp }];
response.writeHead(200, { 'Content-Type': 'text/html' });
response.end();
} else {
const retrievedMessages = { messages: Common.messages[pubkey] || [] };
if (ENABLE_LOG) {
const messages = retrievedMessages.messages.map((m: any) => m.data.substr(4, 10));
console.warn('GET', pubkey.substr(2, 3), messages);
}
response.writeHead(200, { 'Content-Type': 'application/json' });
response.write(JSON.stringify(retrievedMessages));
response.end();
}
});
Common.startLocalFileServer();
Common.stubSnode.listen(STUB_SNODE_SERVER_PORT);
} else {
Common.messages = {};
}
}
public static startLocalFileServer() {
if (!Common.fileServer) {
// be sure to run `git submodule update --init && cd session-file-server && yarn install; cd -`
// eslint-disable-next-line global-require
// tslint:disable-next-line: no-require-imports
Common.fileServer = require('../../../../session-file-server/app');
}
}
public static async joinOpenGroup(app: Application, openGroupUrl: string, name: string) {
await app.client.element(ConversationPage.conversationButtonSection).click();
await app.client.element(ConversationPage.joinOpenGroupButton).click();
await Common.setValueWrapper(app, ConversationPage.openGroupInputUrl, openGroupUrl);
await app.client
.element(ConversationPage.openGroupInputUrl)
.getValue()
.should.eventually.equal(openGroupUrl);
await app.client.element(ConversationPage.joinOpenGroupButton).click();
await app.client.waitForExist(ConversationPage.sessionToastJoinOpenGroup, 2 * 1000);
// account for slow home internet connection delays...
await app.client.waitForExist(ConversationPage.sessionToastJoinOpenGroupSuccess, 20 * 1000);
// validate overlay is closed
await app.client.isExisting(ConversationPage.leftPaneOverlay).should.eventually.be.false;
// validate open chat has been added
await app.client.waitForExist(ConversationPage.rowOpenGroupConversationName(name), 20 * 1000);
}
public static async stopStubSnodeServer() {
if (Common.stubSnode) {
await Common.stubSnode.close();
Common.stubSnode = null;
}
}
/**
* Search for a string in logs
* @param app the render logs to search in
* @param str the string to search (not regex)
* Note: getRenderProcessLogs() clears the app logs each calls.
*/
public static logsContains(renderLogs: Array<{ message: string }>, str: string, count?: number) {
const foundLines = renderLogs.filter(log => log.message.includes(str));
// tslint:disable-next-line: no-unused-expression
chai.expect(foundLines.length > 0, `'${str}' not found in logs but was expected`).to.be.true;
if (count) {
chai
.expect(foundLines.length, `'${str}' found but not the correct number of times`)
.to.be.equal(count);
}
}
}

View file

@ -1,25 +0,0 @@
import { Common } from './common';
// tslint:disable: no-import-side-effect no-invalid-this await-promise
import './registration_itest';
import './open_group_itest';
import './add_contacts_itest';
import './link_device_itest';
import './closed_group_itest';
import './message_functions_itest';
import './settings_itest';
import './message_sync_itest';
import './sender_keys_itest';
before(async function() {
// start the app once before all tests to get the platform-dependent
// path of user data and store it to common.USER_DATA_ROOT_FOLDER
this.timeout(60000);
this.slow(20000);
const app1 = await Common.startApp();
const ret = await app1.electron.remote.app.getPath('appData');
Common.USER_DATA_ROOT_FOLDER = ret;
await Common.stopApp(app1);
});

View file

@ -1,71 +0,0 @@
/* eslint-disable func-names */
/* eslint-disable import/no-extraneous-dependencies */
// tslint:disable: await-promise
// tslint:disable: no-implicit-dependencies
// tslint:disable: no-invalid-this
import path from 'path';
import { afterEach, beforeEach, describe, it } from 'mocha';
import { Common } from './common';
import { Application } from 'spectron';
import ConversationPage from './page-objects/conversation.page';
describe('Message Functions', function() {
this.timeout(60000);
this.slow(20000);
let app: Application;
let app2: Application;
beforeEach(async () => {
await Common.killallElectron();
await Common.stopStubSnodeServer();
[app, app2] = await Common.startAppsAsFriends();
});
afterEach(async () => {
await Common.stopApp(app);
await Common.killallElectron();
await Common.stopStubSnodeServer();
});
it('messageFunction: can send attachment', async () => {
// create group and add new friend
await Common.addFriendToNewClosedGroup([app, app2]);
// send attachment from app1 to closed group
const fileLocation = path.join(__dirname, 'test_attachment');
const messageText = 'test_attachment';
await Common.closeToast(app);
await Common.sendMessage(app, messageText, fileLocation);
// validate attachment sent
await app.client.waitForExist(ConversationPage.existingSendMessageText(messageText), 3000);
// validate attachment recieved
await app2.client.waitForExist(ConversationPage.existingReceivedMessageText(messageText), 5000);
});
it('messageFunction: can delete message', async () => {
// create group and add new friend
await Common.addFriendToNewClosedGroup([app, app2]);
const messageText = 'delete_me';
await Common.sendMessage(app, messageText);
await app.client.waitForExist(ConversationPage.existingSendMessageText(messageText), 6000);
await app2.client.waitForExist(ConversationPage.existingReceivedMessageText(messageText), 7000);
// delete message in context menu
await app.client.element(ConversationPage.messageCtxMenu(messageText)).click();
await app.client.element(ConversationPage.deleteMessageCtxButton).click();
// delete message from modal
await app.client.waitForExist(ConversationPage.deleteMessageModalButton, 5000);
await app.client.element(ConversationPage.deleteMessageModalButton).click();
// verify the message is actually deleted
await app.client.isExisting(ConversationPage.existingSendMessageText(messageText)).should
.eventually.be.false;
});
});

View file

@ -1,97 +0,0 @@
/* eslint-disable func-names */
/* eslint-disable import/no-extraneous-dependencies */
// tslint:disable: await-promise
// tslint:disable: no-implicit-dependencies
// tslint:disable: no-invalid-this
import { afterEach, beforeEach, describe, it } from 'mocha';
import { Common } from './common';
import { Application } from 'spectron';
import ConversationPage from './page-objects/conversation.page';
describe('Open groups', function() {
this.timeout(60000);
this.slow(20000);
let app: Application;
beforeEach(async () => {
await Common.killallElectron();
const login = {
recoveryPhrase: Common.TEST_RECOVERY_PHRASE_1,
displayName: Common.TEST_DISPLAY_NAME1,
};
app = await Common.startAndStub(login);
});
afterEach(async () => {
await Common.killallElectron();
});
it('openGroup: works with valid open group url', async () => {
await Common.joinOpenGroup(app, Common.VALID_GROUP_URL, Common.VALID_GROUP_NAME);
});
it('openGroup: cannot join two times the same open group', async () => {
await Common.joinOpenGroup(app, Common.VALID_GROUP_URL2, Common.VALID_GROUP_NAME2);
// adding a second time the same open group
await app.client.element(ConversationPage.conversationButtonSection).click();
await app.client.element(ConversationPage.joinOpenGroupButton).click();
await Common.setValueWrapper(app, ConversationPage.openGroupInputUrl, Common.VALID_GROUP_URL2);
await app.client.element(ConversationPage.joinOpenGroupButton).click();
// validate session loader is not shown
await app.client.isExisting(ConversationPage.sessionLoader).should.eventually.be.false;
await app.client.waitForExist(ConversationPage.sessionToastJoinOpenGroupAlreadyExist, 1 * 1000);
// validate overlay is still opened as connection failed
await app.client.isExisting(ConversationPage.leftPaneOverlay).should.eventually.be.true;
});
it('openGroup: can send message to open group', async () => {
// join dev-chat group
await app.client.element(ConversationPage.conversationButtonSection).click();
await app.client.element(ConversationPage.joinOpenGroupButton).click();
await Common.setValueWrapper(app, ConversationPage.openGroupInputUrl, Common.VALID_GROUP_URL2);
await app.client.element(ConversationPage.joinOpenGroupButton).click();
// wait for toast to appear
await app.client.waitForExist(ConversationPage.sessionToastJoinOpenGroupSuccess, 30 * 1000);
await Common.timeout(5 * 1000); // wait for toast to clear
await app.client.waitForExist(
ConversationPage.rowOpenGroupConversationName(Common.VALID_GROUP_NAME2),
10 * 1000
);
// generate a message containing the current timestamp so we can find it in the list of messages
const textMessage = Common.generateSendMessageText();
await app.client.element(ConversationPage.conversationButtonSection).click();
await app.client.isExisting(
ConversationPage.rowOpenGroupConversationName(Common.VALID_GROUP_NAME2)
);
await app.client
.element(ConversationPage.rowOpenGroupConversationName(Common.VALID_GROUP_NAME2))
.click();
await Common.setValueWrapper(app, ConversationPage.sendMessageTextarea, textMessage);
await app.client
.element(ConversationPage.sendMessageTextarea)
.getValue()
.should.eventually.equal(textMessage);
// allow some time to fetch some messages
await Common.timeout(3000);
// send the message
await app.client.keys('Enter');
await Common.timeout(5000);
// validate that the message has been added to the message list view
await app.client.waitForExist(ConversationPage.existingSendMessageText(textMessage), 3 * 1000);
// we should validate that the message has been added effectively sent
// (checking the check icon on the metadata part of the message?)
});
});

View file

@ -1,28 +0,0 @@
export = {
// generics
objWithClassAndText: (obj: string, classname: string, text: string) =>
`//${obj}[contains(string(), "${text}")][contains(@class, "${classname}")]`,
divRoleButtonWithText: (text: string) =>
`//div[contains(string(), "${text}")][contains(@role, "button")]`,
divRoleButtonWithTextDisabled: (text: string) =>
`//div[contains(string(), "${text}")][contains(@role, "button")][contains(@class, "disabled")]`,
divRoleButtonDangerWithText: (text: string) =>
`${module.exports.divRoleButtonWithText(text)}[contains(@class, "danger")]`,
inputWithPlaceholder: (placeholder: string) =>
`//input[contains(@placeholder, "${placeholder}")]`,
inputWithId: (id: string) => `//input[contains(@id, '${id}')]`,
textAreaWithPlaceholder: (placeholder: string) =>
`//textarea[contains(@placeholder, "${placeholder}")]`,
textAreaWithClass: (classname: string) => `//textarea[contains(@class, "${classname}")]`,
byId: (id: string) => `//*[@id="${id}"]`,
divWithClass: (classname: string) => `//div[contains(@class, "${classname}")]`,
divWithClassAndText: (classname: string, text: string) =>
module.exports.objWithClassAndText('div', classname, text),
spanWithClassAndText: (classname: string, text: string) =>
module.exports.objWithClassAndText('span', classname, text),
toastWithText: (text: string) =>
module.exports.divWithClassAndText('session-toast-wrapper', text),
toastCloseButton:
'//div[contains(@class, "session-toast-wrapper")]//div[contains(@class, "toast-close")]/div',
};

View file

@ -1,88 +0,0 @@
import commonPage from './common.page';
export = {
// conversation view
sessionLoader: commonPage.divWithClass('session-loader'),
leftPaneOverlay: commonPage.divWithClass('module-left-pane-overlay'),
sendMessageTextarea: commonPage.textAreaWithClass('send-message'),
sendMessageTextareaAndMessage: commonPage.textAreaWithPlaceholder('Type your message'),
existingSendMessageText: (textMessage: string) =>
`//*[contains(@class, "module-message__text--outgoing") and .//span[contains(@class, "text-selectable")][contains(string(), '${textMessage}')]]`,
existingReceivedMessageText: (textMessage: string) =>
`//*[contains(@class, "module-message__text--incoming") and .//span[contains(@class, "text-selectable")][contains(string(), '${textMessage}')]]`,
// conversations
conversationButtonSection:
'//*[contains(@class,"session-icon-button") and .//*[contains(@class, "chatBubble")]]',
retrySendButton: commonPage.divWithClassAndText(
'module-friend-request__buttonContainer--outgoing',
'Retry Send'
),
headerTitleMembers: (num: number) =>
commonPage.spanWithClassAndText('module-conversation-header__title-text', `${num} members`),
conversationItem: "//*[contains(@class, 'module-conversation-list-item')]",
attachmentInput: '//*[contains(@class, "choose-file")]/input[@type="file"]',
attachmentButton: '//*[contains(@class, "choose-file")]/button',
messageCtxMenu: (message: string) =>
`//div[contains(@class, 'message-wrapper')]//span[contains(string(), '${message}')]/parent::div/parent::div/parent::div/parent::div//div[contains(@class, 'module-message__buttons__menu')]`,
deleteMessageCtxButton:
'//*[contains(@class, "react-contextmenu--visible")]/div[contains(string(), "Delete")]',
deleteMessageModalButton:
'//*[contains(@class, "session-modal")]//div[contains(string(), "Delete") and contains(@class, "session-button")]',
// channels
joinOpenGroupButton: commonPage.divRoleButtonWithText('Join Open Group'),
openGroupInputUrl: commonPage.textAreaWithPlaceholder('chat.getsession.org'),
sessionToastJoinOpenGroup: commonPage.toastWithText('Connecting to server...'),
sessionToastJoinOpenGroupSuccess: commonPage.toastWithText(
'Successfully connected to open group'
),
sessionToastJoinOpenGroupAlreadyExist: commonPage.toastWithText(
'You are already connected to this open group'
),
rowOpenGroupConversationName: (groupName: string) =>
commonPage.spanWithClassAndText('module-conversation__user__profile-number', groupName),
// closed group
createClosedGroupButton: commonPage.divRoleButtonWithText('Create Closed Group'),
closedGroupNameTextarea: commonPage.textAreaWithPlaceholder('Enter a group name'),
createClosedGroupMemberItem: (idx: number) =>
commonPage.divWithClass(`session-member-item-${idx}`),
createClosedGroupSealedSenderToggle: commonPage.divWithClass('session-toggle'),
createClosedGroupMemberItemSelected: commonPage.divWithClass('session-member-item selected'),
validateCreationClosedGroupButton: commonPage.divRoleButtonWithText('Create Closed Group'),
sessionToastGroupCreatedSuccess: commonPage.toastWithText('Group created successfully'),
headerTitleGroupName: (groupname: string) =>
commonPage.spanWithClassAndText('module-contact-name__profile-name', groupname),
// contacts
contactsButtonSection:
'//*[contains(@class,"session-icon-button") and .//*[contains(@class, "users")]]',
addContactButton: commonPage.divRoleButtonWithText('Add Contact'),
sessionIDInput: commonPage.textAreaWithPlaceholder('Enter Session ID'),
nextButton: commonPage.divRoleButtonWithText('Next'),
descriptionDeleteAccount: commonPage.spanWithClassAndText(
'session-confirm-main-message',
'Are you sure you want to delete your account?'
),
validateDeleteAccount: commonPage.divRoleButtonDangerWithText('OK'),
// device linking
noPairedDeviceMessage:
'//*[contains(@class, "session-settings-item__title")][contains(string(), "No linked devices")]',
linkDeviceButton: commonPage.divRoleButtonWithText('Link New Device'),
linkDeviceButtonDisabled: commonPage.divRoleButtonWithTextDisabled('Link New Device'),
qrImageDiv: commonPage.divWithClass('qr-image'),
allowPairingButton: commonPage.divRoleButtonWithText('Allow Linking'),
okButton: commonPage.divRoleButtonWithText('OK'),
devicePairedDescription: (secretWords: string) =>
commonPage.divWithClassAndText('session-settings-item__description', secretWords),
unpairDeviceButton: commonPage.divRoleButtonDangerWithText('Unlink Device'),
deleteAccountButton: commonPage.divRoleButtonDangerWithText('Delete Account'),
validateUnpairDevice: commonPage.divRoleButtonDangerWithText('Unlink'),
};

View file

@ -1,34 +0,0 @@
import commonPage from './common.page';
export = {
registrationTabSignIn:
'//div[contains(string(), "Sign In")][contains(@class, "session-registration__tab")][contains(@role, "tab")]',
// create new account
createSessionIDButton: commonPage.divRoleButtonWithText('Create Session ID'),
continueButton: commonPage.divRoleButtonWithText('Continue'),
textareaGeneratedPubkey: '//textarea[contains(@class, "session-id-editable-textarea")]',
getStartedButton: commonPage.divRoleButtonWithText('Get started'),
// restore from seed
restoreFromSeedMode: commonPage.divRoleButtonWithText('Restore From Recovery'),
recoveryPhraseInput: commonPage.inputWithPlaceholder('Enter Recovery Phrase'),
displayNameInput: commonPage.inputWithPlaceholder('Enter a display name'),
passwordInput: commonPage.inputWithPlaceholder('Enter password (optional)'),
continueSessionButton: commonPage.divRoleButtonWithText('Continue Your Session'),
conversationListContainer: commonPage.divWithClass('module-conversations-list-content'),
// device linking
linkDeviceMode: commonPage.divRoleButtonWithText('Link Device to Existing Session ID'),
textareaLinkDevicePubkey: commonPage.textAreaWithPlaceholder('Enter your Session ID'),
linkDeviceTriggerButton: commonPage.divRoleButtonWithText('Link Device'),
toastWrapper: '//*[contains(@class,"session-toast-wrapper")]',
secretWordsText:
'//div[contains(@class,"session-registration__content__secret-words")]/div[contains(@class,"subtle")]',
linkWithThisDevice: commonPage.objWithClassAndText(
'h4',
'device-pairing-dialog__desc',
'Allow linking with this device?'
),
};

View file

@ -1,24 +0,0 @@
export = {
// settings view
settingsButtonSection:
'//*[contains(@class,"session-icon-button") and .//*[contains(@class, "gear")]]',
settingsRowWithText: (text: string) =>
`//*[contains(@class, "left-pane-setting-category-list-item")][contains(string(), '${text}')]`,
leftPaneSettingsButton:
'//*[contains(@class,"session-icon-button") and .//*[contains(@class, "gear")]]',
settingToggleWithText: (text: string) =>
`//div[contains(@class, 'session-settings-item') and contains(string(), '${text}')]//*[contains(@class, 'session-toggle')]`,
settingButtonWithText: (text: string) =>
`//div[contains(@class, 'session-settings-item')]//*[contains(@class, 'session-button') and contains(string(), '${text}')]`,
settingCategoryWithText: (text: string) =>
`//div[contains(@class, 'left-pane-setting-category-list-item') and contains(string(), '${text}')]`,
// Confirm is a boolean. Selects confirmation input
passwordSetModalInput: (_confirm: boolean | undefined) =>
`//input[@id = 'password-modal-input${_confirm ? '-confirm' : ''}']`,
secretWordsTextInDialog:
'//div[@class="device-pairing-dialog__secret-words"]/div[@class="subtle"]',
};

View file

@ -1,132 +0,0 @@
/* eslint-disable prefer-arrow-callback */
/* eslint-disable func-names */
/* eslint-disable import/no-extraneous-dependencies */
// tslint:disable: no-implicit-dependencies
// tslint:disable: await-promise
// tslint:disable: no-invalid-this
import { afterEach, beforeEach, describe, it } from 'mocha';
import { Common } from './common';
import { Application } from 'spectron';
import SettingsPage from './page-objects/settings.page';
import RegistrationPage from './page-objects/registration.page';
import ConversationPage from './page-objects/conversation.page';
describe('Window Test and Login', function() {
this.timeout(60000);
this.slow(20000);
let app: Application;
beforeEach(async () => {
await Common.killallElectron();
});
afterEach(async () => {
await Common.stopApp(app);
await Common.killallElectron();
});
it('registration: opens one window', async () => {
app = await Common.startAndAssureCleanedApp();
app.client.getWindowCount().should.eventually.be.equal(1);
});
it('registration: window title is correct', async () => {
app = await Common.startAndAssureCleanedApp();
app.client.getTitle().should.eventually.be.equal('Session - test-integration-session');
});
it('registration: can restore from seed', async () => {
app = await Common.startAndAssureCleanedApp();
await app.client.element(RegistrationPage.registrationTabSignIn).click();
await app.client.element(RegistrationPage.restoreFromSeedMode).click();
await app.client
.element(RegistrationPage.recoveryPhraseInput)
.setValue(Common.TEST_RECOVERY_PHRASE_1);
await app.client.element(RegistrationPage.displayNameInput).setValue(Common.TEST_DISPLAY_NAME1);
// validate fields are filled
await app.client
.element(RegistrationPage.recoveryPhraseInput)
.getValue()
.should.eventually.equal(Common.TEST_RECOVERY_PHRASE_1);
await app.client
.element(RegistrationPage.displayNameInput)
.getValue()
.should.eventually.equal(Common.TEST_DISPLAY_NAME1);
// trigger login
await app.client.element(RegistrationPage.continueSessionButton).click();
await app.client.waitForExist(RegistrationPage.conversationListContainer, 4000);
await Common.timeout(2000);
await app.webContents
.executeJavaScript("window.storage.get('primaryDevicePubKey')")
.should.eventually.be.equal(Common.TEST_PUBKEY1);
});
it('registration: can create new account', async () => {
app = await Common.startAndAssureCleanedApp();
await app.client.element(RegistrationPage.createSessionIDButton).click();
// wait for the animation of generated pubkey to finish
await Common.timeout(2000);
const pubkeyGenerated = await app.client
.element(RegistrationPage.textareaGeneratedPubkey)
.getValue();
// validate generated pubkey
pubkeyGenerated.should.have.lengthOf(66);
pubkeyGenerated.substr(0, 2).should.be.equal('05');
await app.client.element(RegistrationPage.continueButton).click();
await app.client.isExisting(RegistrationPage.displayNameInput).should.eventually.be.true;
await app.client.element(RegistrationPage.displayNameInput).setValue(Common.TEST_DISPLAY_NAME1);
await app.client.element(RegistrationPage.getStartedButton).click();
await app.client.waitForExist(ConversationPage.conversationButtonSection, 5000);
await app.webContents
.executeJavaScript("window.storage.get('primaryDevicePubKey')")
.should.eventually.be.equal(pubkeyGenerated);
});
it('registration: can delete account when logged in', async () => {
// login as user1
const login = {
recoveryPhrase: Common.TEST_RECOVERY_PHRASE_1,
displayName: Common.TEST_DISPLAY_NAME1,
};
app = await Common.startAndStub(login);
await app.client.waitForExist(RegistrationPage.conversationListContainer, 4000);
await app.webContents
.executeJavaScript("window.storage.get('primaryDevicePubKey')")
.should.eventually.be.equal(Common.TEST_PUBKEY1);
// delete account
await app.client.element(SettingsPage.settingsButtonSection).click();
await app.client.element(ConversationPage.deleteAccountButton).click();
await app.client.isExisting(ConversationPage.descriptionDeleteAccount).should.eventually.be
.true;
// click on the modal OK button to delete the account
await app.client.element(ConversationPage.validateDeleteAccount).click();
// wait for the app restart
await Common.timeout(2000);
// Spectron will loose the connection with the app during the app restart.
// We have to restart the app without altering the logged in user or anything here, just to get a valid new ref to the app.
await Common.stopApp(app);
app = await Common.startApp();
// validate that on app start, the registration sign in is shown
await app.client.waitForExist(RegistrationPage.registrationTabSignIn, 3000);
// validate that no pubkey are set in storage
await app.webContents
.executeJavaScript("window.storage.get('primaryDevicePubKey')")
.should.eventually.be.equal(null);
// and that the conversation list is not shown
await app.client.isExisting(RegistrationPage.conversationListContainer).should.eventually.be
.false;
});
});

View file

@ -1,111 +0,0 @@
/* eslint-disable func-names */
/* eslint-disable import/no-extraneous-dependencies */
// tslint:disable: await-promise
// tslint:disable: no-implicit-dependencies
// tslint:disable: no-invalid-this
import { afterEach, beforeEach, describe, it } from 'mocha';
import { Common } from './common';
import { Application } from 'spectron';
import ConversationPage from './page-objects/conversation.page';
async function generateAndSendMessage(app: Application) {
// send a message from app and validate it is received on app2
const textMessage = Common.generateSendMessageText();
await app.client.element(ConversationPage.sendMessageTextarea).setValue(textMessage);
await app.client
.element(ConversationPage.sendMessageTextarea)
.getValue()
.should.eventually.equal(textMessage);
// send the message
await app.client.keys('Enter');
// validate that the message has been added to the message list view
await app.client.waitForExist(ConversationPage.existingSendMessageText(textMessage), 2000);
return textMessage;
}
async function makeFriendsPlusMessage(app: Application, [app2, pubkey]: [Application, string]) {
await Common.makeFriends(app, [app2, pubkey]);
// Send something back so that `app` can see our name
await app2.client.waitForExist(ConversationPage.conversationItem, 5000);
await app2.client.element(ConversationPage.conversationItem).click();
const text = await generateAndSendMessage(app2);
await app.client.waitForExist(ConversationPage.existingReceivedMessageText(text), 8000);
// Click away so we can call this function again
await app.client.element(ConversationPage.conversationButtonSection).click();
}
async function testTwoMembers() {
const [app, app2] = await Common.startAppsAsFriends();
// create group and add new friend
await Common.addFriendToNewClosedGroup([app, app2]);
const text1 = await generateAndSendMessage(app);
// validate that the message has been added to the message list view
await app2.client.waitForExist(ConversationPage.existingReceivedMessageText(text1), 5000);
// Send a message back:
const text2 = await generateAndSendMessage(app2);
// TODO: fix this. We can send messages back manually, not sure
// why this test fails
await app.client.waitForExist(ConversationPage.existingReceivedMessageText(text2), 10000);
}
async function testThreeMembers() {
// 1. Make three clients A, B, C
const app1Props = {
recoveryPhrase: Common.TEST_RECOVERY_PHRASE_1,
displayName: Common.TEST_DISPLAY_NAME1,
stubSnode: true,
};
const app2Props = {
recoveryPhrase: Common.TEST_RECOVERY_PHRASE_2,
displayName: Common.TEST_DISPLAY_NAME2,
stubSnode: true,
};
const app3Props = {
recoveryPhrase: Common.TEST_RECOVERY_PHRASE_3,
displayName: Common.TEST_DISPLAY_NAME3,
stubSnode: true,
};
const [app1, app2, app3] = await Promise.all([
Common.startAndStub(app1Props),
Common.startAndStubN(app2Props, 2),
Common.startAndStubN(app3Props, 3),
]);
// 2. Make A friends with B and C (B and C are not friends)
await makeFriendsPlusMessage(app1, [app2, Common.TEST_PUBKEY2]);
await makeFriendsPlusMessage(app1, [app3, Common.TEST_PUBKEY3]);
// 3. Add all three to the group
await Common.addFriendToNewClosedGroup([app1, app2, app3]);
// 4. Test that all members can see the message from app1
const text1 = await generateAndSendMessage(app1);
await app2.client.waitForExist(ConversationPage.existingReceivedMessageText(text1), 5000);
await app3.client.waitForExist(ConversationPage.existingReceivedMessageText(text1), 5000);
// TODO: test that B and C can send messages to the group
// const text2 = await generateAndSendMessage(app3);
// await app2.client.waitForExist(
// ConversationPage.existingReceivedMessageText(text2),
// 5000
// );
}

View file

@ -1,94 +0,0 @@
/* eslint-disable import/no-extraneous-dependencies */
// tslint:disable: await-promise
// tslint:disable: no-implicit-dependencies
// tslint:disable: no-invalid-this
import { after, before, describe, it } from 'mocha';
import { Common } from './common';
import { Application } from 'spectron';
import SettingsPage from './page-objects/settings.page';
import CommonPage from './page-objects/common.page';
// Generate random password
// tslint:disable-next-line: insecure-random
const password = Math.random()
.toString(36)
.substr(2, 8);
const passwordInputID = 'password-modal-input';
describe('Settings', function() {
this.timeout(60000);
this.slow(20000);
let app: Application;
before(async () => {
await Common.killallElectron();
await Common.stopStubSnodeServer();
const appProps = {
recoveryPhrase: Common.TEST_RECOVERY_PHRASE_1,
displayName: Common.TEST_DISPLAY_NAME1,
};
app = await Common.startAndStub(appProps);
});
after(async () => {
await Common.stopApp(app);
await Common.killallElectron();
await Common.stopStubSnodeServer();
});
it('settings: can toggle menubar', async () => {
const menuBarVisible = await app.browserWindow.isMenuBarVisible();
await app.client.element(SettingsPage.settingsButtonSection).click();
await app.client.element(SettingsPage.settingToggleWithText('Hide Menu Bar')).click();
// Confirm that toggling works
const menuBarToggled = await app.browserWindow.isMenuBarVisible();
menuBarToggled.should.equal(!menuBarVisible);
});
it('settings: can set password', async () => {
await app.client.element(SettingsPage.settingsRowWithText('Privacy')).click();
await app.client.element(SettingsPage.settingButtonWithText('Set Password')).click();
await Common.setValueWrapper(app, CommonPage.inputWithId(passwordInputID), password);
await Common.setValueWrapper(
app,
CommonPage.inputWithId(`${passwordInputID}-confirm`),
password
);
await app.client.keys('Enter');
// Verify password set
await app.client.waitForExist(CommonPage.toastWithText('Set Password'), 2000);
await Common.closeToast(app);
});
it('settings: can remove password', async () => {
// Enter password to unlock settings
await Common.setValueWrapper(app, CommonPage.inputWithId('password-lock-input'), password);
await app.client.keys('Enter');
// Remove password
await app.client.element(SettingsPage.settingButtonWithText('Remove Password')).click();
await Common.setValueWrapper(app, CommonPage.inputWithId(passwordInputID), password);
await app.client.keys('Enter');
// Verify password removed with toast
await app.client.waitForExist(CommonPage.toastWithText('Removed Password'), 2000);
// Verify password actully removed
await app.client.isExisting(CommonPage.divWithClass('session-settings__password-lock')).should
.eventually.be.false;
});
});

View file

@ -1,37 +0,0 @@
import { StringUtils } from '../../../../session/utils';
import { default as insecureNodeFetch } from 'node-fetch';
class StubMessageAPI {
public ourKey: string;
public baseUrl: string;
constructor(ourKey: string) {
this.ourKey = ourKey;
this.baseUrl = 'http://localhost:3000';
}
// eslint-disable-next-line no-unused-vars
public async sendMessage(
pubKey: string,
data: any,
messageTimeStamp: number,
ttl: number,
options = {}
) {
const post = {
method: 'POST',
};
const data64 = StringUtils.decode(data, 'base64');
// insecureNodeFetch but this is a stub
await insecureNodeFetch(
`${
this.baseUrl
}/messages?pubkey=${pubKey}&timestamp=${messageTimeStamp}&data=${encodeURIComponent(data64)}`,
post
);
}
}
module.exports = StubMessageAPI;