diff --git a/integration_test/common.js b/integration_test/common.js index e58238daa..8b8f9b1b5 100644 --- a/integration_test/common.js +++ b/integration_test/common.js @@ -21,8 +21,8 @@ chai.use(chaiAsPromised); chai.config.includeStack = true; // From https://github.com/chaijs/chai/issues/200 -chai.use(function (_chai, _) { - _chai.Assertion.addMethod('withMessage', function (msg) { +chai.use((_chai, _) => { + _chai.Assertion.addMethod('withMessage', msg => { _.flag(this, 'message', msg); }); }); @@ -250,7 +250,6 @@ module.exports = { }, async makeFriends(app1, client2) { - const [app2, pubkey2] = client2; /** add each other as friends */ @@ -259,11 +258,7 @@ module.exports = { await app1.client.element(ConversationPage.contactsButtonSection).click(); await app1.client.element(ConversationPage.addContactButton).click(); - await this.setValueWrapper( - app1, - ConversationPage.sessionIDInput, - pubkey2 - ); + await this.setValueWrapper(app1, ConversationPage.sessionIDInput, pubkey2); await app1.client.element(ConversationPage.nextButton).click(); await app1.client.waitForExist( ConversationPage.sendFriendRequestTextarea, @@ -310,10 +305,6 @@ module.exports = { ConversationPage.acceptedFriendRequestMessage, 5000 ); - - // click away to close the current pane and make further FR possible - await app1.client.element(ConversationPage.globeButtonSection).click(); - }, async startAppsAsFriends() { @@ -340,7 +331,6 @@ module.exports = { }, async addFriendToNewClosedGroup(members, useSenderKeys) { - const [app, ...others] = members; await this.setValueWrapper( @@ -348,28 +338,35 @@ module.exports = { ConversationPage.closedGroupNameTextarea, this.VALID_CLOSED_GROUP_NAME1 ); + await app.client .element(ConversationPage.closedGroupNameTextarea) .getValue() .should.eventually.equal(this.VALID_CLOSED_GROUP_NAME1); - await app.client - .element(ConversationPage.createClosedGroupMemberItem) - .isVisible().should.eventually.be.true; + // This 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(); + } - // select the first friend as a member of the groups being created - await app.client - .element(ConversationPage.createClosedGroupMemberItem) - .click(); await app.client .element(ConversationPage.createClosedGroupMemberItemSelected) .isVisible().should.eventually.be.true; if (useSenderKeys) { - // Select Sender Keys + // Select Sender Keys await app.client - .element(ConversationPage.createClosedGroupSealedSenderToggle) - .click(); + .element(ConversationPage.createClosedGroupSealedSenderToggle) + .click(); } // trigger the creation of the group @@ -384,8 +381,9 @@ module.exports = { await app.client.isExisting( ConversationPage.headerTitleGroupName(this.VALID_CLOSED_GROUP_NAME1) ).should.eventually.be.true; - await app.client.element(ConversationPage.headerTitleMembers(2)).isVisible() - .should.eventually.be.true; + await app.client + .element(ConversationPage.headerTitleMembers(members.length)) + .isVisible().should.eventually.be.true; // validate overlay is closed await app.client @@ -575,6 +573,10 @@ module.exports = { app1.webContents.executeJavaScript( 'window.LokiMessageAPI = window.StubMessageAPI;' ); + + app1.webContents.executeJavaScript( + 'window.LokiSnodeAPI = window.StubLokiSnodeAPI;' + ); }, logsContainsString: async (app1, str) => { @@ -589,35 +591,45 @@ module.exports = { const { query } = url.parse(request.url, true); const { pubkey, data, timestamp } = query; - if (pubkey) { - if (request.method === 'POST') { - if (ENABLE_LOG) { - console.warn('POST', [data, timestamp]); - } + if (!pubkey) { + console.warn('NO PUBKEY'); + response.writeHead(400, { 'Content-Type': 'text/html' }); + response.end(); + } - let ori = this.messages[pubkey]; - if (!this.messages[pubkey]) { - ori = []; - } + if (request.method === 'POST') { + if (ENABLE_LOG) { + console.warn( + 'POST', + pubkey.substr(2, 3), + data.substr(4, 10), + timestamp + ); + } - this.messages[pubkey] = [...ori, { data, timestamp }]; + let ori = this.messages[pubkey]; - response.writeHead(200, { 'Content-Type': 'text/html' }); - response.end(); - } else { - const retrievedMessages = { messages: this.messages[pubkey] }; - if (ENABLE_LOG) { - console.warn('GET', pubkey, retrievedMessages); - } - if (this.messages[pubkey]) { - response.writeHead(200, { 'Content-Type': 'application/json' }); - response.write(JSON.stringify(retrievedMessages)); - this.messages[pubkey] = []; - } - response.end(); + if (!this.messages[pubkey]) { + ori = []; + } + + this.messages[pubkey] = [...ori, { data, timestamp }]; + + response.writeHead(200, { 'Content-Type': 'text/html' }); + response.end(); + } else { + const retrievedMessages = { messages: this.messages[pubkey] || [] }; + + if (ENABLE_LOG) { + const messages = retrievedMessages.messages.map(m => + 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(); } - response.end(); }); this.stubSnode.listen(STUB_SNODE_SERVER_PORT); } else { diff --git a/integration_test/page-objects/conversation.page.js b/integration_test/page-objects/conversation.page.js index 64146d5c1..64515b91d 100644 --- a/integration_test/page-objects/conversation.page.js +++ b/integration_test/page-objects/conversation.page.js @@ -63,7 +63,8 @@ module.exports = { closedGroupNameTextarea: commonPage.textAreaWithPlaceholder( 'Enter a group name' ), - createClosedGroupMemberItem: commonPage.divWithClass('session-member-item'), + createClosedGroupMemberItem: idx => + commonPage.divWithClass(`session-member-item-${idx}`), createClosedGroupSealedSenderToggle: commonPage.divWithClass( 'session-toggle' ), diff --git a/integration_test/sender_keys_test.js b/integration_test/sender_keys_test.js index bd07a443a..446d19fce 100644 --- a/integration_test/sender_keys_test.js +++ b/integration_test/sender_keys_test.js @@ -6,132 +6,154 @@ const common = require('./common'); const ConversationPage = require('./page-objects/conversation.page'); async function generateAndSendMessage(app) { - - // send a message from app and validate it is received on app2 - const textMessage = common.generateSendMessageText(); - await app.client + // 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 + await app.client .element(ConversationPage.sendMessageTextarea) .getValue() .should.eventually.equal(textMessage); - // send the message - await app.client.keys('Enter'); + // 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; +} - // validate that the message has been added to the message list view - await app.client.waitForExist( - ConversationPage.existingSendMessageText(textMessage), - 2000 - ); +async function makeFriendsPlusMessage(app, [app2, pubkey]) { + await common.makeFriends(app, [app2, pubkey]); - return textMessage; + // Send something back so that `app` can see our name + 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.globeButtonSection).click(); } -describe('senderkeys', function() { - let app; - let app2; +async function testTwoMembers() { + const [app, app2] = await common.startAppsAsFriends(); - this.timeout(60000); - this.slow(30000); + await app.client.element(ConversationPage.globeButtonSection).click(); + await app.client.element(ConversationPage.createClosedGroupButton).click(); - beforeEach(async () => { - await common.killallElectron(); - await common.stopStubSnodeServer(); + const useSenderKeys = true; - }); + // create group and add new friend + await common.addFriendToNewClosedGroup([app, app2], useSenderKeys); - afterEach(async () => { - await common.stopApp(app); - await common.killallElectron(); - await common.stopStubSnodeServer(); - }); + const text1 = await generateAndSendMessage(app); - it('Two member group', async function() { + // validate that the message has been added to the message list view + await app2.client.waitForExist( + ConversationPage.existingReceivedMessageText(text1), + 5000 + ); - [app, app2] = await common.startAppsAsFriends(); + // Send a message back: + const text2 = await generateAndSendMessage(app2); - await app.client.element(ConversationPage.globeButtonSection).click(); - await app.client.element(ConversationPage.createClosedGroupButton).click(); + // TODO: fix this. We can send messages back manually, not sure + // why this test fails + await app.client.waitForExist( + ConversationPage.existingReceivedMessageText(text2), + 10000 + ); +} - const useSenderKeys = true; +async function testThreeMembers() { + // 1. Make three clients A, B, C - // create group and add new friend - await common.addFriendToNewClosedGroup([app, app2], useSenderKeys); + const app1Props = { + mnemonic: common.TEST_MNEMONIC1, + displayName: common.TEST_DISPLAY_NAME1, + stubSnode: true, + }; - const text1 = await generateAndSendMessage(app); + const app2Props = { + mnemonic: common.TEST_MNEMONIC2, + displayName: common.TEST_DISPLAY_NAME2, + stubSnode: true, + }; - // validate that the message has been added to the message list view - await app2.client.waitForExist( - ConversationPage.existingReceivedMessageText(text1), - 5000 - ); + const app3Props = { + mnemonic: common.TEST_MNEMONIC3, + displayName: common.TEST_DISPLAY_NAME3, + stubSnode: true, + }; - // Send a message back: - const text2 = await generateAndSendMessage(app2); + const [app1, app2, app3] = await Promise.all([ + common.startAndStub(app1Props), + common.startAndStubN(app2Props, 2), + common.startAndStubN(app3Props, 3), + ]); - // TODO: fix this. We can send messages back manually, not sure - // why this test fails - // await app.client.waitForExist( - // ConversationPage.existingReceivedMessageText(text2), - // 10000 - // ); + // 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]); - it('Three member group: test session requests', async function() { + const useSenderKeys = true; - // 1. Make three clients A, B, C + await app1.client.element(ConversationPage.globeButtonSection).click(); + await app1.client.element(ConversationPage.createClosedGroupButton).click(); - const app1Props = { - mnemonic: common.TEST_MNEMONIC1, - displayName: common.TEST_DISPLAY_NAME1, - stubSnode: true, - }; - - const app2Props = { - mnemonic: common.TEST_MNEMONIC2, - displayName: common.TEST_DISPLAY_NAME2, - stubSnode: true, - }; + // 3. Add all three to the group - const app3Props = { - mnemonic: common.TEST_MNEMONIC3, - 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) - ]); + await common.addFriendToNewClosedGroup([app1, app2, app3], useSenderKeys); - // 2. Make A friends with B and C (B and C are not friends) + // 4. Test that all members can see the message from app1 + const text1 = await generateAndSendMessage(app1); - await common.makeFriends(app1, [app2, common.TEST_PUBKEY2]); + await app2.client.waitForExist( + ConversationPage.existingReceivedMessageText(text1), + 5000 + ); - await common.makeFriends(app1, [app3, common.TEST_PUBKEY3]); + await app3.client.waitForExist( + ConversationPage.existingReceivedMessageText(text1), + 5000 + ); - // const text1 = await generateAndSendMessage(app1); + // TODO: test that B and C can send messages to the group - // // validate that the message has been added to the message list view - // await app2.client.waitForExist( - // ConversationPage.existingReceivedMessageText(text1), - // 5000 - // ); + // const text2 = await generateAndSendMessage(app3); - // // validate that the message has been added to the message list view - // await app3.client.waitForExist( - // ConversationPage.existingReceivedMessageText(text1), - // 5000 - // ); + // await app2.client.waitForExist( + // ConversationPage.existingReceivedMessageText(text2), + // 5000 + // ); +} - // TODO: test that B and C can send messages to the group +describe('senderkeys', function() { + let app; + this.timeout(600000); + this.slow(40000); + beforeEach(async () => { + await common.killallElectron(); + await common.stopStubSnodeServer(); }); + afterEach(async () => { + await common.stopApp(app); + await common.killallElectron(); + await common.stopStubSnodeServer(); + }); + + it('Two member group', testTwoMembers); + + it('Three member group: test session requests', testThreeMembers); }); diff --git a/integration_test/stubs/stub_loki_snode_api.js b/integration_test/stubs/stub_loki_snode_api.js new file mode 100644 index 000000000..92cd945e4 --- /dev/null +++ b/integration_test/stubs/stub_loki_snode_api.js @@ -0,0 +1,10 @@ +/* global log */ + +class StubLokiSnodeAPI { + // eslint-disable-next-line class-methods-use-this + async refreshSwarmNodesForPubKey(pubKey) { + log.info('refreshSwarmNodesForPubkey: ', pubKey); + } +} + +module.exports = StubLokiSnodeAPI; diff --git a/integration_test/stubs/stub_message_api.js b/integration_test/stubs/stub_message_api.js index c44694785..659577834 100644 --- a/integration_test/stubs/stub_message_api.js +++ b/integration_test/stubs/stub_message_api.js @@ -1,4 +1,4 @@ -/* global clearTimeout, dcodeIO, Buffer, TextDecoder, process */ +/* global clearTimeout, dcodeIO, Buffer, TextDecoder, process, log */ const nodeFetch = require('node-fetch'); class StubMessageAPI { @@ -26,6 +26,35 @@ class StubMessageAPI { ); } + async pollForGroupId(groupId, onMessages) { + const get = { + method: 'GET', + }; + const res = await nodeFetch( + `${this.baseUrl}/messages?pubkey=${groupId}`, + get + ); + + try { + const json = await res.json(); + + const modifiedMessages = json.messages.map(m => { + // eslint-disable-next-line no-param-reassign + m.conversationId = groupId; + return m; + }); + + onMessages(modifiedMessages || []); + } catch (e) { + log.error('invalid json for GROUP', e); + onMessages([]); + } + + setTimeout(() => { + this.pollForGroupId(groupId, onMessages); + }, 1000); + } + async startLongPolling(numConnections, stopPolling, callback) { const ourPubkey = this.ourKey; @@ -36,10 +65,15 @@ class StubMessageAPI { `${this.baseUrl}/messages?pubkey=${ourPubkey}`, get ); - const json = await res.json(); - // console.warn('STUBBED polling messages ', json.messages); - callback(json.messages || []); + try { + const json = await res.json(); + callback(json.messages || []); + } catch (e) { + log.error('invalid json: ', e); + callback([]); + } + // console.warn('STUBBED polling messages ', json.messages); } } diff --git a/package.json b/package.json index ea074269f..bb850ff22 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "test-electron": "yarn grunt test", "test-integration": "ELECTRON_DISABLE_SANDBOX=1 mocha --exit --timeout 10000 integration_test/integration_test.js", "test-integration-parts": "ELECTRON_DISABLE_SANDBOX=1 mocha --exit --timeout 10000 integration_test/integration_test.js --grep 'registration' && ELECTRON_DISABLE_SANDBOX=1 mocha --exit --timeout 10000 integration_test/integration_test.js --grep 'openGroup' && ELECTRON_DISABLE_SANDBOX=1 mocha --exit --timeout 10000 integration_test/integration_test.js --grep 'addFriends' && ELECTRON_DISABLE_SANDBOX=1 mocha --exit --timeout 10000 integration_test/integration_test.js --grep 'linkDevice' && ELECTRON_DISABLE_SANDBOX=1 mocha --exit --timeout 10000 integration_test/integration_test.js --grep 'closedGroup'", + "test-medium-groups": "ELECTRON_DISABLE_SANDBOX=1 mocha --exit --timeout 10000 integration_test/integration_test.js --grep 'senderkeys'", "test-node": "mocha --recursive --exit test/app test/modules ts/test libloki/test/node", "eslint": "eslint --cache .", "eslint-fix": "eslint --fix .", diff --git a/preload.js b/preload.js index 1f485dd20..e5c773d69 100644 --- a/preload.js +++ b/preload.js @@ -342,6 +342,7 @@ window.LokiMessageAPI = require('./js/modules/loki_message_api'); if (process.env.USE_STUBBED_NETWORK) { window.StubMessageAPI = require('./integration_test/stubs/stub_message_api'); window.StubAppDotNetApi = require('./integration_test/stubs/stub_app_dot_net_api'); + window.StubLokiSnodeAPI = require('./integration_test/stubs/stub_loki_snode_api'); } window.LokiPublicChatAPI = require('./js/modules/loki_public_chat_api'); @@ -456,7 +457,7 @@ if ( }; /* eslint-enable global-require, import/no-extraneous-dependencies */ window.lokiFeatureFlags = {}; - window.lokiSnodeAPI = {}; // no need stub out each function here + window.lokiSnodeAPI = new window.StubLokiSnodeAPI(); // no need stub out each function here } if (config.environment.includes('test-integration')) { window.lokiFeatureFlags = { diff --git a/ts/components/conversation/InviteFriendsDialog.tsx b/ts/components/conversation/InviteFriendsDialog.tsx index c601f62aa..a48672230 100644 --- a/ts/components/conversation/InviteFriendsDialog.tsx +++ b/ts/components/conversation/InviteFriendsDialog.tsx @@ -108,9 +108,10 @@ export class InviteFriendsDialog extends React.Component { private renderMemberList() { const members = this.state.friendList; - return members.map((member: ContactType) => ( + return members.map((member: ContactType, index: number) => ( { this.onMemberClicked(selectedMember); diff --git a/ts/components/conversation/UpdateGroupMembersDialog.tsx b/ts/components/conversation/UpdateGroupMembersDialog.tsx index 561e90fb1..76552a7bc 100644 --- a/ts/components/conversation/UpdateGroupMembersDialog.tsx +++ b/ts/components/conversation/UpdateGroupMembersDialog.tsx @@ -147,9 +147,10 @@ export class UpdateGroupMembersDialog extends React.Component { private renderMemberList() { const members = this.state.friendList; - return members.map((member: ContactType) => ( + return members.map((member: ContactType, index: number) => ( { count={notificationCount} size={NotificationCountSize.ON_HEADER} onClick={this.props.buttonClicked} + key="notificationCount" /> ); } diff --git a/ts/components/session/SessionClosableOverlay.tsx b/ts/components/session/SessionClosableOverlay.tsx index 966e0de29..44c9d5731 100644 --- a/ts/components/session/SessionClosableOverlay.tsx +++ b/ts/components/session/SessionClosableOverlay.tsx @@ -282,9 +282,10 @@ export class SessionClosableOverlay extends React.Component { } private renderMemberList(members: any) { - return members.map((member: ContactType) => ( + return members.map((member: ContactType, index: number) => ( { diff --git a/ts/components/session/SessionMemberListItem.tsx b/ts/components/session/SessionMemberListItem.tsx index 56a9f74fd..8e5cb965b 100644 --- a/ts/components/session/SessionMemberListItem.tsx +++ b/ts/components/session/SessionMemberListItem.tsx @@ -18,6 +18,7 @@ export interface ContactType { interface Props { member: ContactType; + index: number; // index in the list isSelected: boolean; onSelect?: any; onUnselect?: any; @@ -54,7 +55,11 @@ export class SessionMemberListItem extends React.Component { return (