feat: Multi-device support (#889)

* 🚑 Added ready selector for multi-device

* SendMessage fix

* File management system and some fixes

* cleanup

* cleanup again

* eslint

* critical fix for reloading the same session

* Checking for valid folder name (regex)

* ESLint hotfix (regex escapes)

* Typings cleanup

* cleanup listener

* Multi-device Branch merge (#888)

* Duplicate

* qr fix and allow non-beta users to connect

* urgent: selector fix

* urgent: qr timeout fix

* fix

* Updated type so no TS error when sending list/buttons

* Update index.d.ts

* fix QueryExist for Multidevice (#928)

* creates isRegisteredUserBeta

* fix QueryExist

* fix Error: GROUP_JID: invalid jid type: Not an instance of WID issue (#926)

* fix Error: GROUP_JID: invalid jid type: Not an instance of WID issue

* clean code

* Cleanup

* Fix for update chrome error

* ESLint fix

* :red_light: fix for RMDIR

* Update README.md

* Update README.md

* fix: getProfilePicUrl fix by victormga (#941)

* fix: MD presence available/unavailable (#942)

* delete session when appropriate & fix for SW

* ignore QR timeout errors

* Presence and ChatState updates working for MD+Non-MD

* shell uses new session storage

* lint fix

* support session.json-based auth for non-md

* md fix

* md fix

* fix shell clientId

* remove exclusive mocha test

* make linkPreview default to false

* remove ignored errors on getQuotedMessage

* fix: dont modify existing this.options.puppeteer object

* tests work with new dir auth

* remove exclusive test

* fixes and tests for group creation and participant functions

* remove unused function

* wip fix group settings functions

* isRegisteredUser && getNumberId hotFix (#955)

* isRegisteredUser && getNumberId hotFix

A fix for client.isRegisteredUser and client.getNumberId. Use for reference or if you are stuck with MD and NEEDS this function. Problably Whatsapp will break this in a couple weeks

* fix for non-md

Co-authored-by: Rajeh Taher <rajeh@reforward.dev>

* Fix WA 2.2146.9 MD +  victormga branch (#991)

* qrcode now uses observers instead of timeout

* automatic auth/qrcode detection

* Fix WA 2.2146.9 MD

Got from github:victormga/whatsapp-web.js#multidevice maybe it's behind pedro branch

Co-authored-by: victormga <victor_mga@hotmail.com>

* fix

* fix*

* getnumberid to multidevice (#1027)

* getNumberId to main

 isRegisteredUser && getNumberId hotFix #955 To main

* Update Client.js

Co-authored-by: tuyuribr <45042245+tuyuribr@users.noreply.github.com>

* Update Client.js

* Message.raw() (#1005)

* Message.raw()

* i just noticed

* Update index.d.ts

* Update index.d.ts

* Update Message.js

* Get rid of sharp now!!!!!!!! (#1045)

* commit 1

* finally, gotten rid of sharp

* pckg.json

* service worker fix & disableMessage option

* typings

* Update example.js

* clear session system

* Update Client.js

* Update Client.js

* Fix accepting group private invite (#1094)

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

* [MD] Add getCommonGroups with specific user. (#1097)

* Add getCommonGroups with specific user.

* Fix

* Fix

* Fix

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

* Fix getCommonGroups. (#1122)

* Fix of Unexpected identifier async destroy() (#1123)

* Fix of Unexpected identifier async destroy()

* Fix made in #1107

* Temporary fix for "Sticker" module

* some really quick changes

* Update Injected.js

* Update Injected.js

* Update index.d.ts

* fix: getNumberId Solved (#1142)

* getNumberId Solved

* isRegisteredUser Solved

* formmated

* Apply suggestions from code review

* Update src/util/Injected.js

Co-authored-by: Rajeh Taher <rajeh@reforward.dev>

* Fix: "Chrome user data dir was not found ..."

fixes the error caused by puppeteer.

* Update Client.js (#1154)

* fix: getNumberId and isRegisteredUser (#1159)

* fix: getNumberId and isRegisteredUser

* Apply suggestions from code review

Co-authored-by: Rajeh Taher <rajeh@reforward.dev>

* Update client.js

* Update Injected.js

* Update Client.js

* Update index.d.ts

* Update Client.js

* Update Client.js

* fix lint indentation

* fix auth_failure event for non-md, tests

* fix setting group subject

* fix finding Label module

* set remember-me after clearing localStorage

* fix: send messages to groups correctly on MD, use new ID format

* fix setting / getting contact status

* fix msg.getInfo, add message tests

* fix group settings functions

* fix set group description, handle errors in setSubject

* fix group invite functions

* fix leaving group

* bring back phone info for non-md users

* remove unused option, update typings

* add back jsdoc for qr event

* fix setting sticker metadata, clean up sticker functions

* rawData is a get only property

* fix and simplify getNumberId/isRegisteredUser

* fix getInviteInfo

* setDisplayName returns bool, not yet implemented for md

* fix: stream module (#1241)

* linkPreview has no effect on MD, return default to true

* fix: del linkPreview option on md

* cleanup, types and docs updates

* update readmes / test notes

* remove DS_Store

* DS_Store in gitignore

* test stability (timeouts/sleeps)

Co-authored-by: Rajeh Taher <rajeh@reforward.tk>
Co-authored-by: Gustavo B <52040719+Gugabit@users.noreply.github.com>
Co-authored-by: Maikel Ortega Hernández <maikeloh@gmail.com>
Co-authored-by: victormga <victor_mga@hotmail.com>
Co-authored-by: Pedro Lopez <pedroslopez@me.com>
Co-authored-by: tuyuribr <45042245+tuyuribr@users.noreply.github.com>
Co-authored-by: gon <68490103+nekiak@users.noreply.github.com>
Co-authored-by: Alon Schwartzblat <63599777+Schwartzblat@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Šebestíček <44745014+SebestikCZ@users.noreply.github.com>
Co-authored-by: Emmanuel Anaya Luna <38712443+KeruMx@users.noreply.github.com>
Co-authored-by: L337C0D3R <51872799+L337C0D3R@users.noreply.github.com>
Co-authored-by: Reni Delonzek <renidelonzek@gmail.com>
This commit is contained in:
Rajeh Taher
2022-02-27 14:51:08 -08:00
committed by GitHub
parent 8ef37b68ae
commit 0d55d40885
20 changed files with 1132 additions and 553 deletions

View File

@@ -3,8 +3,12 @@
These tests require an authenticated WhatsApp Web session, as well as an additional phone that you can send messages to.
This can be configured using the following environment variables:
- `WWEBJS_TEST_SESSION`: A JSON-formatted string with the session details. Must include `WABrowserId`, `WASecretBundle`, `WAToken1` and `WAToken2`.
- `WWEBJS_TEST_SESSION_PATH`: Path to a JSON file that contains the session details. Must include `WABrowserId`, `WASecretBundle`, `WAToken1` and `WAToken2`.
- `WWEBJS_TEST_SESSION`: A JSON-formatted string with legacy auth session details. Must include `WABrowserId`, `WASecretBundle`, `WAToken1` and `WAToken2`.
- `WWEBJS_TEST_SESSION_PATH`: Path to a JSON file that contains the legacy auth session details. Must include `WABrowserId`, `WASecretBundle`, `WAToken1` and `WAToken2`.
- `WWEBJS_TEST_CLIENT_ID`: `clientId` to use for local file based authentication.
- `WWEBJS_TEST_REMOTE_ID`: A valid WhatsApp ID that you can send messages to, e.g. `123456789@c.us`. It should be different from the ID used by the provided session.
You *must* set `WWEBJS_TEST_REMOTE_ID` **and** either `WWEBJS_TEST_SESSION` or `WWEBJS_TEST_SESSION_PATH` for the tests to run properly.
You *must* set `WWEBJS_TEST_REMOTE_ID` **and** either `WWEBJS_TEST_SESSION`, `WWEBJS_TEST_SESSION_PATH` or `WWEBJS_TEST_CLIENT_ID` for the tests to run properly.
### Multidevice
Some of the tested functionality depends on whether the account has multidevice enabled or not. If you are using multidevice, you should set `WWEBJS_TEST_MD=1`.

View File

@@ -1,4 +1,5 @@
const {expect} = require('chai');
const chai = require('chai');
const chaiAsPromised = require('chai-as-promised');
const sinon = require('sinon');
const helper = require('./helper');
@@ -9,7 +10,11 @@ const MessageMedia = require('../src/structures/MessageMedia');
const Location = require('../src/structures/Location');
const { MessageTypes, WAState } = require('../src/util/Constants');
const expect = chai.expect;
chai.use(chaiAsPromised);
const remoteId = helper.remoteId;
const isMD = helper.isMD();
describe('Client', function() {
describe('Authentication', function() {
@@ -47,76 +52,6 @@ describe('Client', function() {
expect(disconnectedCallback.calledOnceWith('Max qrcode retries reached')).to.eql(true);
});
it('should fail auth if session is invalid', async function() {
this.timeout(40000);
const authFailCallback = sinon.spy();
const qrCallback = sinon.spy();
const readyCallback = sinon.spy();
const client = helper.createClient({
options: {
session: {
WABrowserId: 'invalid',
WASecretBundle: 'invalid',
WAToken1: 'invalid',
WAToken2: 'invalid'
},
authTimeoutMs: 10000,
restartOnAuthFail: false
}
});
client.on('qr', qrCallback);
client.on('auth_failure', authFailCallback);
client.on('ready', readyCallback);
client.initialize();
await helper.sleep(25000);
expect(authFailCallback.called).to.equal(true);
expect(authFailCallback.args[0][0]).to.equal('Unable to log in. Are the session details valid?');
expect(readyCallback.called).to.equal(false);
expect(qrCallback.called).to.equal(false);
await client.destroy();
});
it('can restart without a session if session was invalid and restartOnAuthFail=true', async function() {
this.timeout(40000);
const authFailCallback = sinon.spy();
const qrCallback = sinon.spy();
const client = helper.createClient({
options:{
session: {
WABrowserId: 'invalid',
WASecretBundle: 'invalid',
WAToken1: 'invalid',
WAToken2: 'invalid'
},
authTimeoutMs: 10000,
restartOnAuthFail: true
}
});
client.on('auth_failure', authFailCallback);
client.on('qr', qrCallback);
client.initialize();
await helper.sleep(35000);
expect(authFailCallback.called).to.equal(true);
expect(qrCallback.called).to.equal(true);
expect(qrCallback.args[0][0]).to.have.lengthOf(152);
await client.destroy();
});
it('should authenticate with existing session', async function() {
this.timeout(40000);
@@ -124,7 +59,10 @@ describe('Client', function() {
const qrCallback = sinon.spy();
const readyCallback = sinon.spy();
const client = helper.createClient({withSession: true});
const client = helper.createClient({
authenticated: true,
});
client.on('qr', qrCallback);
client.on('authenticated', authenticatedCallback);
client.on('ready', readyCallback);
@@ -132,59 +70,137 @@ describe('Client', function() {
await client.initialize();
expect(authenticatedCallback.called).to.equal(true);
const newSession = authenticatedCallback.args[0][0];
expect(newSession).to.have.key([
'WABrowserId',
'WASecretBundle',
'WAToken1',
'WAToken2'
]);
expect(authenticatedCallback.called).to.equal(true);
if(helper.isUsingDeprecatedSession()) {
const newSession = authenticatedCallback.args[0][0];
expect(newSession).to.have.key([
'WABrowserId',
'WASecretBundle',
'WAToken1',
'WAToken2'
]);
}
expect(readyCallback.called).to.equal(true);
expect(qrCallback.called).to.equal(false);
await client.destroy();
});
it('can take over if client was logged in somewhere else with takeoverOnConflict=true', async function() {
this.timeout(40000);
const readyCallback1 = sinon.spy();
const readyCallback2 = sinon.spy();
const disconnectedCallback1 = sinon.spy();
const disconnectedCallback2 = sinon.spy();
const client1 = helper.createClient({
withSession: true,
options: { takeoverOnConflict: true, takeoverTimeoutMs: 5000 }
});
const client2 = helper.createClient({withSession: true});
client1.on('ready', readyCallback1);
client2.on('ready', readyCallback2);
client1.on('disconnected', disconnectedCallback1);
client2.on('disconnected', disconnectedCallback2);
await client1.initialize();
expect(readyCallback1.called).to.equal(true);
expect(readyCallback2.called).to.equal(false);
expect(disconnectedCallback1.called).to.equal(false);
expect(disconnectedCallback2.called).to.equal(false);
await client2.initialize();
expect(readyCallback2.called).to.equal(true);
expect(disconnectedCallback1.called).to.equal(false);
expect(disconnectedCallback2.called).to.equal(false);
// wait for takeoverTimeoutMs to kick in
await helper.sleep(5200);
expect(disconnectedCallback1.called).to.equal(false);
expect(disconnectedCallback2.called).to.equal(true);
expect(disconnectedCallback2.calledWith(WAState.CONFLICT)).to.equal(true);
await client1.destroy();
});
describe('Non-MD only', function () {
if(!isMD) {
it('can take over if client was logged in somewhere else with takeoverOnConflict=true', async function() {
this.timeout(40000);
const readyCallback1 = sinon.spy();
const readyCallback2 = sinon.spy();
const disconnectedCallback1 = sinon.spy();
const disconnectedCallback2 = sinon.spy();
const client1 = helper.createClient({
authenticated: true,
options: { takeoverOnConflict: true, takeoverTimeoutMs: 5000 }
});
const client2 = helper.createClient({authenticated: true});
client1.on('ready', readyCallback1);
client2.on('ready', readyCallback2);
client1.on('disconnected', disconnectedCallback1);
client2.on('disconnected', disconnectedCallback2);
await client1.initialize();
expect(readyCallback1.called).to.equal(true);
expect(readyCallback2.called).to.equal(false);
expect(disconnectedCallback1.called).to.equal(false);
expect(disconnectedCallback2.called).to.equal(false);
await client2.initialize();
expect(readyCallback2.called).to.equal(true);
expect(disconnectedCallback1.called).to.equal(false);
expect(disconnectedCallback2.called).to.equal(false);
// wait for takeoverTimeoutMs to kick in
await helper.sleep(5200);
expect(disconnectedCallback1.called).to.equal(false);
expect(disconnectedCallback2.called).to.equal(true);
expect(disconnectedCallback2.calledWith(WAState.CONFLICT)).to.equal(true);
await client1.destroy();
});
it('should fail auth if session is invalid', async function() {
this.timeout(40000);
const authFailCallback = sinon.spy();
const qrCallback = sinon.spy();
const readyCallback = sinon.spy();
const client = helper.createClient({
options: {
session: {
WABrowserId: 'invalid',
WASecretBundle: 'invalid',
WAToken1: 'invalid',
WAToken2: 'invalid'
},
authTimeoutMs: 10000,
restartOnAuthFail: false,
useDeprecatedSessionAuth: true
}
});
client.on('qr', qrCallback);
client.on('auth_failure', authFailCallback);
client.on('ready', readyCallback);
client.initialize();
await helper.sleep(25000);
expect(authFailCallback.called).to.equal(true);
expect(authFailCallback.args[0][0]).to.equal('Unable to log in. Are the session details valid?');
expect(readyCallback.called).to.equal(false);
expect(qrCallback.called).to.equal(false);
await client.destroy();
});
it('can restart without a session if session was invalid and restartOnAuthFail=true', async function() {
this.timeout(40000);
const authFailCallback = sinon.spy();
const qrCallback = sinon.spy();
const client = helper.createClient({
options:{
session: {
WABrowserId: 'invalid',
WASecretBundle: 'invalid',
WAToken1: 'invalid',
WAToken2: 'invalid'
},
authTimeoutMs: 10000,
restartOnAuthFail: true,
useDeprecatedSessionAuth: true
}
});
client.on('auth_failure', authFailCallback);
client.on('qr', qrCallback);
client.initialize();
await helper.sleep(35000);
expect(authFailCallback.called).to.equal(true);
expect(qrCallback.called).to.equal(true);
expect(qrCallback.args[0][0]).to.have.lengthOf(152);
await client.destroy();
});
}
});
});
describe('Authenticated', function() {
@@ -192,7 +208,7 @@ describe('Client', function() {
before(async function() {
this.timeout(35000);
client = helper.createClient({withSession: true});
client = helper.createClient({authenticated: true});
await client.initialize();
});
@@ -221,27 +237,38 @@ describe('Client', function() {
'BlockContact',
'Call',
'Chat',
'ChatState',
'Cmd',
'Conn',
'Contact',
'DownloadManager',
'Features',
'GroupMetadata',
'GroupParticipants',
'GroupUtils',
'Invite',
'InviteInfo',
'JoinInviteV4',
'Label',
'MediaObject',
'MediaPrep',
'MediaTypes',
'MediaUpload',
'MessageInfo',
'Msg',
'MsgKey',
'OpaqueData',
'QueryOrder',
'QueryProduct',
'PresenceUtils',
'QueryExist',
'QueryProduct',
'QueryOrder',
'SendClear',
'SendDelete',
'SendMessage',
'SendSeen',
'StatusUtils',
'Sticker',
'UploadUtils',
'UserConstructor',
@@ -249,7 +276,9 @@ describe('Client', function() {
'Validators',
'Wap',
'WidFactory',
'genId'
'findCommonGroups',
'genId',
'getProfilePicFull',
];
const loadedModules = await client.pupPage.evaluate((expectedModules) => {
@@ -535,8 +564,6 @@ END:VCARD`;
describe('Search messages', function () {
it('can search for messages', async function () {
this.timeout(5000);
const m1 = await client.sendMessage(remoteId, 'I\'m searching for Super Mario Brothers');
const m2 = await client.sendMessage(remoteId, 'This also contains Mario');
const m3 = await client.sendMessage(remoteId, 'Nothing of interest here, just Luigi');
@@ -581,4 +608,4 @@ END:VCARD`;
});
});
});
});
});

View File

@@ -1,13 +1,25 @@
const path = require('path');
const crypto = require('crypto');
const Client = require('../src/Client');
const Util = require('../src/util/Util');
require('dotenv').config();
const remoteId = process.env.WWEBJS_TEST_REMOTE_ID;
if(!remoteId) throw new Error('The WWEBJS_TEST_REMOTE_ID environment variable has not been set.');
function isUsingDeprecatedSession() {
return Boolean(process.env.WWEBJS_TEST_SESSION || process.env.WWEBJS_TEST_SESSION_PATH);
}
function isMD() {
return Boolean(process.env.WWEBJS_TEST_MD);
}
if(isUsingDeprecatedSession() && isMD()) throw 'Cannot use deprecated sessions with WWEBJS_TEST_MD=true';
function getSessionFromEnv() {
if (!isUsingDeprecatedSession()) return null;
const envSession = process.env.WWEBJS_TEST_SESSION;
if(envSession) return JSON.parse(envSession);
@@ -16,17 +28,27 @@ function getSessionFromEnv() {
const absPath = path.resolve(process.cwd(), envSessionPath);
return require(absPath);
}
throw new Error('No session found in environment.');
}
function createClient({withSession, options: additionalOpts}={}) {
function createClient({authenticated, options: additionalOpts}={}) {
const options = {};
if(withSession) {
options.session = getSessionFromEnv();
if(authenticated) {
const deprecatedSession = getSessionFromEnv();
if(deprecatedSession) {
options.session = deprecatedSession;
options.useDeprecatedSessionAuth = true;
} else {
const clientId = process.env.WWEBJS_TEST_CLIENT_ID;
if(!clientId) throw new Error('No session found in environment.');
options.clientId = clientId;
}
} else {
options.clientId = crypto.randomBytes(5).toString('hex');
}
return new Client(Util.mergeDefault(options, additionalOpts || {}));
const allOpts = {...options, ...(additionalOpts || {})};
return new Client(allOpts);
}
function sleep(ms) {
@@ -36,5 +58,7 @@ function sleep(ms) {
module.exports = {
sleep,
createClient,
remoteId
isUsingDeprecatedSession,
isMD,
remoteId,
};

View File

@@ -13,7 +13,7 @@ describe('Chat', function () {
before(async function() {
this.timeout(35000);
client = helper.createClient({ withSession: true });
client = helper.createClient({ authenticated: true });
await client.initialize();
chat = await client.getChatById(remoteId);
});
@@ -32,9 +32,9 @@ describe('Chat', function () {
});
it('can fetch messages sent in a chat', async function () {
this.timeout(5000);
await helper.sleep(1000);
const msg = await chat.sendMessage('another message');
await helper.sleep(500);
const messages = await chat.fetchMessages();
expect(messages.length).to.be.greaterThanOrEqual(2);
@@ -49,6 +49,7 @@ describe('Chat', function () {
it('can use a limit when fetching messages sent in a chat', async function () {
await helper.sleep(1000);
const msg = await chat.sendMessage('yet another message');
await helper.sleep(500);
const messages = await chat.fetchMessages({limit: 1});
expect(messages).to.have.lengthOf(1);
@@ -80,6 +81,8 @@ describe('Chat', function () {
const res = await chat.sendSeen();
expect(res).to.equal(true);
await helper.sleep(1000);
// refresh chat
chat = await client.getChatById(remoteId);
expect(chat.unreadCount).to.equal(0);
@@ -137,6 +140,8 @@ describe('Chat', function () {
it('can mute a chat forever', async function() {
await chat.mute();
await helper.sleep(1000);
// refresh chat
chat = await client.getChatById(remoteId);
expect(chat.isMuted).to.equal(true);
@@ -147,6 +152,8 @@ describe('Chat', function () {
const unmuteDate = new Date(new Date().getTime() + (1000*60*60));
await chat.mute(unmuteDate);
await helper.sleep(1000);
// refresh chat
chat = await client.getChatById(remoteId);
expect(chat.isMuted).to.equal(true);
@@ -168,9 +175,7 @@ describe('Chat', function () {
// eslint-disable-next-line mocha/no-skipped-tests
describe.skip('Destructive operations', function () {
it('can clear all messages from chat', async function () {
this.timeout(5000);
it('can clear all messages from chat', async function () {
const res = await chat.clearMessages();
expect(res).to.equal(true);

227
tests/structures/group.js Normal file
View File

@@ -0,0 +1,227 @@
const { expect } = require('chai');
const helper = require('../helper');
const remoteId = helper.remoteId;
describe('Group', function() {
let client;
let group;
before(async function() {
this.timeout(35000);
client = helper.createClient({
authenticated: true,
});
await client.initialize();
const createRes = await client.createGroup('My Awesome Group', [remoteId]);
expect(createRes.gid).to.exist;
await helper.sleep(500);
group = await client.getChatById(createRes.gid._serialized);
expect(group).to.exist;
});
beforeEach(async function () {
await helper.sleep(500);
});
describe('Settings', function () {
it('can change the group subject', async function () {
expect(group.name).to.equal('My Awesome Group');
const res = await group.setSubject('My Amazing Group');
expect(res).to.equal(true);
await helper.sleep(1000);
// reload
group = await client.getChatById(group.id._serialized);
expect(group.name).to.equal('My Amazing Group');
});
it('can change the group description', async function () {
expect(group.description).to.equal(undefined);
const res = await group.setDescription('some description');
expect(res).to.equal(true);
expect(group.description).to.equal('some description');
await helper.sleep(1000);
// reload
group = await client.getChatById(group.id._serialized);
expect(group.description).to.equal('some description');
});
it('can set only admins able to send messages', async function () {
expect(group.groupMetadata.announce).to.equal(false);
const res = await group.setMessagesAdminsOnly();
expect(res).to.equal(true);
expect(group.groupMetadata.announce).to.equal(true);
await helper.sleep(1000);
// reload
group = await client.getChatById(group.id._serialized);
expect(group.groupMetadata.announce).to.equal(true);
});
it('can set all participants able to send messages', async function () {
expect(group.groupMetadata.announce).to.equal(true);
const res = await group.setMessagesAdminsOnly(false);
expect(res).to.equal(true);
expect(group.groupMetadata.announce).to.equal(false);
await helper.sleep(1000);
// reload
group = await client.getChatById(group.id._serialized);
expect(group.groupMetadata.announce).to.equal(false);
});
it('can set only admins able to set group info', async function () {
expect(group.groupMetadata.restrict).to.equal(false);
const res = await group.setInfoAdminsOnly();
expect(res).to.equal(true);
expect(group.groupMetadata.restrict).to.equal(true);
await helper.sleep(1000);
// reload
group = await client.getChatById(group.id._serialized);
expect(group.groupMetadata.restrict).to.equal(true);
});
it('can set all participants able to set group info', async function () {
expect(group.groupMetadata.restrict).to.equal(true);
const res = await group.setInfoAdminsOnly(false);
expect(res).to.equal(true);
expect(group.groupMetadata.restrict).to.equal(false);
await helper.sleep(1000);
// reload
group = await client.getChatById(group.id._serialized);
expect(group.groupMetadata.restrict).to.equal(false);
});
});
describe('Invites', function () {
it('can get the invite code', async function () {
const code = await group.getInviteCode();
expect(typeof code).to.equal('string');
});
it('can get invite info', async function () {
const code = await group.getInviteCode();
const info = await client.getInviteInfo(code);
expect(info.id._serialized).to.equal(group.id._serialized);
expect(info.participants.length).to.equal(2);
});
it('can revoke the invite code', async function () {
const code = await group.getInviteCode();
const newCode = await group.revokeInvite();
expect(typeof newCode).to.equal('string');
expect(newCode).to.not.equal(code);
});
});
describe('Participants', function () {
it('can promote a user to admin', async function () {
let participant = group.participants.find(p => p.id._serialized === remoteId);
expect(participant).to.exist;
expect(participant.isAdmin).to.equal(false);
const res = await group.promoteParticipants([remoteId]);
expect(res.status).to.be.greaterThanOrEqual(200);
await helper.sleep(1000);
// reload and check
group = await client.getChatById(group.id._serialized);
participant = group.participants.find(p => p.id._serialized=== remoteId);
expect(participant).to.exist;
expect(participant.isAdmin).to.equal(true);
});
it('can demote a user', async function () {
let participant = group.participants.find(p => p.id._serialized=== remoteId);
expect(participant).to.exist;
expect(participant.isAdmin).to.equal(true);
const res = await group.demoteParticipants([remoteId]);
expect(res.status).to.be.greaterThanOrEqual(200);
await helper.sleep(1000);
// reload and check
group = await client.getChatById(group.id._serialized);
participant = group.participants.find(p => p.id._serialized=== remoteId);
expect(participant).to.exist;
expect(participant.isAdmin).to.equal(false);
});
it('can remove a user from the group', async function () {
let participant = group.participants.find(p => p.id._serialized=== remoteId);
expect(participant).to.exist;
const res = await group.removeParticipants([remoteId]);
expect(res.status).to.be.greaterThanOrEqual(200);
await helper.sleep(1000);
// reload and check
group = await client.getChatById(group.id._serialized);
participant = group.participants.find(p => p.id._serialized=== remoteId);
expect(participant).to.not.exist;
});
it('can add back a user to the group', async function () {
let participant = group.participants.find(p => p.id._serialized=== remoteId);
expect(participant).to.not.exist;
const res = await group.addParticipants([remoteId]);
expect(res.status).to.be.greaterThanOrEqual(200);
await helper.sleep(1000);
// reload and check
group = await client.getChatById(group.id._serialized);
participant = group.participants.find(p => p.id._serialized=== remoteId);
expect(participant).to.exist;
});
});
describe('Leave / re-join', function () {
let code;
before(async function () {
code = await group.getInviteCode();
});
it('can leave the group', async function () {
expect(group.isReadOnly).to.equal(false);
await group.leave();
await helper.sleep(1000);
// reload and check
group = await client.getChatById(group.id._serialized);
expect(group.isReadOnly).to.equal(true);
});
it('can join a group via invite code', async function () {
const chatId = await client.acceptInvite(code);
expect(chatId).to.equal(group.id._serialized);
await helper.sleep(1000);
// reload and check
group = await client.getChatById(group.id._serialized);
expect(group.isReadOnly).to.equal(false);
});
});
after(async function () {
await client.destroy();
});
});

112
tests/structures/message.js Normal file
View File

@@ -0,0 +1,112 @@
const { expect } = require('chai');
const sinon = require('sinon');
const helper = require('../helper');
const { Contact, Chat } = require('../../src/structures');
const remoteId = helper.remoteId;
describe('Message', function () {
let client;
let chat;
let message;
before(async function() {
this.timeout(35000);
client = helper.createClient({ authenticated: true });
await client.initialize();
chat = await client.getChatById(remoteId);
message = await chat.sendMessage('this is only a test');
// wait for message to be sent
await helper.sleep(1000);
});
after(async function () {
await client.destroy();
});
it('can get the related chat', async function () {
const chat = await message.getChat();
expect(chat).to.be.instanceOf(Chat);
expect(chat.id._serialized).to.equal(remoteId);
});
it('can get the related contact', async function () {
const contact = await message.getContact();
expect(contact).to.be.instanceOf(Contact);
expect(contact.id._serialized).to.equal(client.info.wid._serialized);
});
it('can get message info', async function () {
const info = await message.getInfo();
expect(typeof info).to.equal('object');
expect(Array.isArray(info.played)).to.equal(true);
expect(Array.isArray(info.read)).to.equal(true);
expect(Array.isArray(info.delivery)).to.equal(true);
});
describe('Replies', function () {
let replyMsg;
it('can reply to a message', async function () {
replyMsg = await message.reply('this is my reply');
expect(replyMsg.hasQuotedMsg).to.equal(true);
});
it('can get the quoted message', async function () {
const quotedMsg = await replyMsg.getQuotedMessage();
expect(quotedMsg.id._serialized).to.equal(message.id._serialized);
});
});
describe('Star', function () {
it('can star a message', async function () {
expect(message.isStarred).to.equal(false);
await message.star();
// reload and check
await message.reload();
expect(message.isStarred).to.equal(true);
});
it('can un-star a message', async function () {
expect(message.isStarred).to.equal(true);
await message.unstar();
// reload and check
await message.reload();
expect(message.isStarred).to.equal(false);
});
});
describe('Delete', function () {
it('can delete a message for me', async function () {
await message.delete();
await helper.sleep(1000);
expect(await message.reload()).to.equal(null);
});
it('can delete a message for everyone', async function () {
message = await chat.sendMessage('sneaky message');
await helper.sleep(1000);
const callback = sinon.spy();
client.once('message_revoke_everyone', callback);
await message.delete(true);
await helper.sleep(1000);
expect(await message.reload()).to.equal(null);
expect(callback.called).to.equal(true);
const [ revokeMsg, originalMsg ] = callback.args[0];
expect(revokeMsg.id._serialized).to.equal(originalMsg.id._serialized);
expect(originalMsg.body).to.equal('sneaky message');
expect(originalMsg.type).to.equal('chat');
expect(revokeMsg.body).to.equal('');
expect(revokeMsg.type).to.equal('revoked');
});
});
});