More integration tests for medium groups

This commit is contained in:
Maxim Shishmarev 2020-05-19 12:01:42 +10:00
parent 2a0130ff04
commit fcadcd780e
12 changed files with 254 additions and 164 deletions

View file

@ -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]);
}
let ori = this.messages[pubkey];
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) {
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 (!pubkey) {
console.warn('NO PUBKEY');
response.writeHead(400, { 'Content-Type': 'text/html' });
response.end();
}
if (request.method === 'POST') {
if (ENABLE_LOG) {
console.warn(
'POST',
pubkey.substr(2, 3),
data.substr(4, 10),
timestamp
);
}
let ori = this.messages[pubkey];
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 {

View file

@ -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'
),

View file

@ -6,41 +6,145 @@ 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
);
// validate that the message has been added to the message list view
await app.client.waitForExist(
ConversationPage.existingSendMessageText(textMessage),
2000
);
return textMessage;
return textMessage;
}
async function makeFriendsPlusMessage(app, [app2, pubkey]) {
await common.makeFriends(app, [app2, pubkey]);
// 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();
}
async function testTwoMembers() {
const [app, app2] = await common.startAppsAsFriends();
await app.client.element(ConversationPage.globeButtonSection).click();
await app.client.element(ConversationPage.createClosedGroupButton).click();
const useSenderKeys = true;
// create group and add new friend
await common.addFriendToNewClosedGroup([app, app2], useSenderKeys);
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 = {
mnemonic: common.TEST_MNEMONIC1,
displayName: common.TEST_DISPLAY_NAME1,
stubSnode: true,
};
const app2Props = {
mnemonic: common.TEST_MNEMONIC2,
displayName: common.TEST_DISPLAY_NAME2,
stubSnode: true,
};
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),
]);
// 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]);
const useSenderKeys = true;
await app1.client.element(ConversationPage.globeButtonSection).click();
await app1.client.element(ConversationPage.createClosedGroupButton).click();
// 3. Add all three to the group
await common.addFriendToNewClosedGroup([app1, app2, app3], useSenderKeys);
// 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
// );
}
describe('senderkeys', function() {
let app;
let app2;
this.timeout(60000);
this.slow(30000);
this.timeout(600000);
this.slow(40000);
beforeEach(async () => {
await common.killallElectron();
await common.stopStubSnodeServer();
});
afterEach(async () => {
@ -49,89 +153,7 @@ describe('senderkeys', function() {
await common.stopStubSnodeServer();
});
it('Two member group', async function() {
[app, app2] = await common.startAppsAsFriends();
await app.client.element(ConversationPage.globeButtonSection).click();
await app.client.element(ConversationPage.createClosedGroupButton).click();
const useSenderKeys = true;
// create group and add new friend
await common.addFriendToNewClosedGroup([app, app2], useSenderKeys);
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
// );
});
it('Three member group: test session requests', async function() {
// 1. Make three clients A, B, C
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,
};
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)
]);
// 2. Make A friends with B and C (B and C are not friends)
await common.makeFriends(app1, [app2, common.TEST_PUBKEY2]);
await common.makeFriends(app1, [app3, common.TEST_PUBKEY3]);
// const text1 = await generateAndSendMessage(app1);
// // validate that the message has been added to the message list view
// await app2.client.waitForExist(
// ConversationPage.existingReceivedMessageText(text1),
// 5000
// );
// // validate that the message has been added to the message list view
// await app3.client.waitForExist(
// ConversationPage.existingReceivedMessageText(text1),
// 5000
// );
// TODO: test that B and C can send messages to the group
});
it('Two member group', testTwoMembers);
it('Three member group: test session requests', testThreeMembers);
});

View file

@ -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;

View file

@ -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);
}
}

View file

@ -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 .",

View file

@ -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 = {

View file

@ -108,9 +108,10 @@ export class InviteFriendsDialog extends React.Component<Props, State> {
private renderMemberList() {
const members = this.state.friendList;
return members.map((member: ContactType) => (
return members.map((member: ContactType, index: number) => (
<SessionMemberListItem
member={member}
index={index}
isSelected={false}
onSelect={(selectedMember: ContactType) => {
this.onMemberClicked(selectedMember);

View file

@ -147,9 +147,10 @@ export class UpdateGroupMembersDialog extends React.Component<Props, State> {
private renderMemberList() {
const members = this.state.friendList;
return members.map((member: ContactType) => (
return members.map((member: ContactType, index: number) => (
<SessionMemberListItem
member={member}
index={index}
isSelected={!member.checkmarked}
onSelect={this.onMemberClicked}
onUnselect={this.onMemberClicked}

View file

@ -123,6 +123,7 @@ export class LeftPaneSectionHeader extends React.Component<Props, State> {
count={notificationCount}
size={NotificationCountSize.ON_HEADER}
onClick={this.props.buttonClicked}
key="notificationCount"
/>
);
}

View file

@ -282,9 +282,10 @@ export class SessionClosableOverlay extends React.Component<Props, State> {
}
private renderMemberList(members: any) {
return members.map((member: ContactType) => (
return members.map((member: ContactType, index: number) => (
<SessionMemberListItem
member={member}
index={index}
isSelected={false}
key={member.id}
onSelect={(selectedMember: ContactType) => {

View file

@ -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<Props, State> {
return (
<div
className={classNames('session-member-item', isSelected && 'selected')}
className={classNames(
`session-member-item-${this.props.index}`,
'session-member-item',
isSelected && 'selected'
)}
onClick={this.handleSelectionAction}
role="button"
>