snapshot of https://github.com/RocketChat/Rocket.Chat.git (develop, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 1s
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 1s
This commit is contained in:
313
apps/meteor/tests/end-to-end/api/34-engagement-dashboard.ts
Normal file
313
apps/meteor/tests/end-to-end/api/34-engagement-dashboard.ts
Normal file
@@ -0,0 +1,313 @@
|
||||
import type { IRoom } from '@rocket.chat/core-typings';
|
||||
import { expect } from 'chai';
|
||||
import { after, before, describe, it } from 'mocha';
|
||||
import type { Response } from 'supertest';
|
||||
|
||||
import { getCredentials, api, request, credentials } from '../../data/api-data';
|
||||
import { sendSimpleMessage } from '../../data/chat.helper';
|
||||
import { updatePermission } from '../../data/permissions.helper';
|
||||
import { createRoom, deleteRoom } from '../../data/rooms.helper';
|
||||
|
||||
describe('[Engagement Dashboard]', function () {
|
||||
this.retries(0);
|
||||
|
||||
const isEnterprise = Boolean(process.env.IS_EE);
|
||||
|
||||
before((done) => getCredentials(done));
|
||||
|
||||
before(() => updatePermission('view-engagement-dashboard', ['admin']));
|
||||
|
||||
after(() => updatePermission('view-engagement-dashboard', ['admin']));
|
||||
|
||||
(isEnterprise ? describe : describe.skip)('[/engagement-dashboard/channels/list]', () => {
|
||||
let testRoom: IRoom;
|
||||
let emptyRoom: IRoom;
|
||||
|
||||
before(async () => {
|
||||
testRoom = (await createRoom({ type: 'c', name: `channel.test.engagement.${Date.now()}-${Math.random()}` })).body.channel;
|
||||
emptyRoom = (await createRoom({ type: 'c', name: `channel.test.engagement.empty.${Date.now()}-${Math.random()}` })).body.channel;
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await deleteRoom({ type: 'c', roomId: testRoom._id });
|
||||
await deleteRoom({ type: 'c', roomId: emptyRoom._id });
|
||||
});
|
||||
|
||||
it('should fail if user does not have the view-engagement-dashboard permission', async () => {
|
||||
await updatePermission('view-engagement-dashboard', []);
|
||||
await request
|
||||
.get(api('engagement-dashboard/channels/list'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
end: new Date().toISOString(),
|
||||
start: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString(),
|
||||
offset: 0,
|
||||
count: 25,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(403)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body.error).to.be.equal('User does not have the permissions required for this action [error-unauthorized]');
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail if start param is not a valid date', async () => {
|
||||
await updatePermission('view-engagement-dashboard', ['admin']);
|
||||
await request
|
||||
.get(api('engagement-dashboard/channels/list'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
start: 'invalid-date',
|
||||
end: new Date().toISOString(),
|
||||
offset: 0,
|
||||
count: 25,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body.error).to.be.equal('Match error: Failed Match.Where validation in field start');
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail if end param is not a valid date', async () => {
|
||||
await request
|
||||
.get(api('engagement-dashboard/channels/list'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
start: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString(),
|
||||
end: 'invalid-date',
|
||||
offset: 0,
|
||||
count: 25,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body.error).to.be.equal('Match error: Failed Match.Where validation in field end');
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail if start param is not provided', async () => {
|
||||
await request
|
||||
.get(api('engagement-dashboard/channels/list'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
end: new Date(),
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body.error).to.be.equal("Match error: Missing key 'start'");
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail if end param is not provided', async () => {
|
||||
await request
|
||||
.get(api('engagement-dashboard/channels/list'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
start: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString(),
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body.error).to.be.equal("Match error: Missing key 'end'");
|
||||
});
|
||||
});
|
||||
|
||||
it('should succesfuly return results', async () => {
|
||||
await sendSimpleMessage({ roomId: testRoom._id });
|
||||
|
||||
await request
|
||||
.get(api('engagement-dashboard/channels/list'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
end: new Date().toISOString(),
|
||||
start: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString(),
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('offset', 0);
|
||||
expect(res.body).to.have.property('count');
|
||||
expect(res.body).to.have.property('total');
|
||||
expect(res.body).to.have.property('channels');
|
||||
expect(res.body.channels).to.be.an('array').that.is.not.empty;
|
||||
|
||||
expect(res.body.channels[0]).to.be.an('object').that.is.not.empty;
|
||||
expect(res.body.channels[0]).to.have.property('messages').that.is.a('number');
|
||||
expect(res.body.channels[0]).to.have.property('lastWeekMessages').that.is.a('number');
|
||||
expect(res.body.channels[0]).to.have.property('diffFromLastWeek').that.is.a('number');
|
||||
expect(res.body.channels[0].room).to.be.an('object').that.is.not.empty;
|
||||
|
||||
expect(res.body.channels[0].room).to.have.property('_id').that.is.a('string');
|
||||
expect(res.body.channels[0].room).to.have.property('name').that.is.a('string');
|
||||
expect(res.body.channels[0].room).to.have.property('ts').that.is.a('string');
|
||||
expect(res.body.channels[0].room).to.have.property('t').that.is.a('string');
|
||||
expect(res.body.channels[0].room).to.have.property('_updatedAt').that.is.a('string');
|
||||
});
|
||||
});
|
||||
|
||||
it('should not return empty rooms', async () => {
|
||||
await request
|
||||
.get(api('engagement-dashboard/channels/list'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
end: new Date().toISOString(),
|
||||
start: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString(),
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('offset', 0);
|
||||
expect(res.body).to.have.property('count');
|
||||
expect(res.body).to.have.property('total');
|
||||
expect(res.body).to.have.property('channels');
|
||||
const channelRecord = res.body.channels.find(({ room }: { room: { _id: string } }) => room._id === emptyRoom._id);
|
||||
expect(channelRecord).to.be.undefined;
|
||||
});
|
||||
});
|
||||
|
||||
it('should correctly count messages diff compared to last week when there are messages in a room', async () => {
|
||||
await request
|
||||
.get(api('engagement-dashboard/channels/list'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
end: new Date().toISOString(),
|
||||
start: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString(),
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('offset', 0);
|
||||
expect(res.body).to.have.property('count');
|
||||
expect(res.body).to.have.property('total');
|
||||
expect(res.body).to.have.property('channels');
|
||||
expect(res.body.channels).to.be.an('array').that.is.not.empty;
|
||||
|
||||
const channelRecord = res.body.channels.find(({ room }: { room: { _id: string } }) => room._id === testRoom._id);
|
||||
expect(channelRecord).not.to.be.undefined;
|
||||
|
||||
expect(channelRecord).to.be.an('object').that.is.not.empty;
|
||||
expect(channelRecord).to.have.property('messages', 1);
|
||||
expect(channelRecord).to.have.property('lastWeekMessages', 0);
|
||||
expect(channelRecord).to.have.property('diffFromLastWeek', 1);
|
||||
expect(channelRecord.room).to.be.an('object').that.is.not.empty;
|
||||
|
||||
expect(channelRecord.room).to.have.property('_id', testRoom._id);
|
||||
expect(channelRecord.room).to.have.property('name', testRoom.name);
|
||||
expect(channelRecord.room).to.have.property('ts', testRoom.ts);
|
||||
expect(channelRecord.room).to.have.property('t', testRoom.t);
|
||||
});
|
||||
});
|
||||
|
||||
it('should correctly count messages diff compared to last week when there are messages in a room', async () => {
|
||||
await request
|
||||
.get(api('engagement-dashboard/channels/list'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
end: new Date().toISOString(),
|
||||
start: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString(),
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('offset', 0);
|
||||
expect(res.body).to.have.property('count');
|
||||
expect(res.body).to.have.property('total');
|
||||
expect(res.body).to.have.property('channels');
|
||||
expect(res.body.channels).to.be.an('array').that.is.not.empty;
|
||||
|
||||
const channelRecord = res.body.channels.find(({ room }: { room: { _id: string } }) => room._id === testRoom._id);
|
||||
expect(channelRecord).not.to.be.undefined;
|
||||
|
||||
expect(channelRecord).to.be.an('object').that.is.not.empty;
|
||||
expect(channelRecord).to.have.property('messages', 1);
|
||||
expect(channelRecord).to.have.property('lastWeekMessages', 0);
|
||||
expect(channelRecord).to.have.property('diffFromLastWeek', 1);
|
||||
expect(channelRecord.room).to.be.an('object').that.is.not.empty;
|
||||
|
||||
expect(channelRecord.room).to.have.property('_id', testRoom._id);
|
||||
expect(channelRecord.room).to.have.property('name', testRoom.name);
|
||||
expect(channelRecord.room).to.have.property('ts', testRoom.ts);
|
||||
expect(channelRecord.room).to.have.property('t', testRoom.t);
|
||||
});
|
||||
});
|
||||
|
||||
it('should correctly count messages from last week and diff when moving to the next week', async () => {
|
||||
await request
|
||||
.get(api('engagement-dashboard/channels/list'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
end: new Date(Date.now() + 8 * 24 * 60 * 60 * 1000).toISOString(),
|
||||
start: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(),
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('offset', 0);
|
||||
expect(res.body).to.have.property('count');
|
||||
expect(res.body).to.have.property('total');
|
||||
expect(res.body).to.have.property('channels');
|
||||
expect(res.body.channels).to.be.an('array').that.is.not.empty;
|
||||
|
||||
const channelRecord = res.body.channels.find(({ room }: { room: { _id: string } }) => room._id === testRoom._id);
|
||||
expect(channelRecord).not.to.be.undefined;
|
||||
|
||||
expect(channelRecord).to.be.an('object').that.is.not.empty;
|
||||
expect(channelRecord).to.have.property('messages', 0);
|
||||
expect(channelRecord).to.have.property('lastWeekMessages', 1);
|
||||
expect(channelRecord).to.have.property('diffFromLastWeek', -1);
|
||||
expect(channelRecord.room).to.be.an('object').that.is.not.empty;
|
||||
|
||||
expect(channelRecord.room).to.have.property('_id', testRoom._id);
|
||||
expect(channelRecord.room).to.have.property('name', testRoom.name);
|
||||
expect(channelRecord.room).to.have.property('ts', testRoom.ts);
|
||||
expect(channelRecord.room).to.have.property('t', testRoom.t);
|
||||
});
|
||||
});
|
||||
|
||||
it('should correctly count messages from last week and diff when moving to the next week', async () => {
|
||||
await request
|
||||
.get(api('engagement-dashboard/channels/list'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
end: new Date(Date.now() + 8 * 24 * 60 * 60 * 1000).toISOString(),
|
||||
start: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(),
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('offset', 0);
|
||||
expect(res.body).to.have.property('count');
|
||||
expect(res.body).to.have.property('total');
|
||||
expect(res.body).to.have.property('channels');
|
||||
expect(res.body.channels).to.be.an('array').that.is.not.empty;
|
||||
|
||||
const channelRecord = res.body.channels.find(({ room }: { room: { _id: string } }) => room._id === testRoom._id);
|
||||
expect(channelRecord).not.to.be.undefined;
|
||||
|
||||
expect(channelRecord).to.be.an('object').that.is.not.empty;
|
||||
expect(channelRecord).to.have.property('messages', 0);
|
||||
expect(channelRecord).to.have.property('lastWeekMessages', 1);
|
||||
expect(channelRecord).to.have.property('diffFromLastWeek', -1);
|
||||
expect(channelRecord.room).to.be.an('object').that.is.not.empty;
|
||||
|
||||
expect(channelRecord.room).to.have.property('_id', testRoom._id);
|
||||
expect(channelRecord.room).to.have.property('name', testRoom.name);
|
||||
expect(channelRecord.room).to.have.property('ts', testRoom.ts);
|
||||
expect(channelRecord.room).to.have.property('t', testRoom.t);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
94
apps/meteor/tests/end-to-end/api/LDAP.ts
Normal file
94
apps/meteor/tests/end-to-end/api/LDAP.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import { expect } from 'chai';
|
||||
import { before, after, describe, it } from 'mocha';
|
||||
import type { Response } from 'supertest';
|
||||
|
||||
import { getCredentials, api, request, credentials } from '../../data/api-data';
|
||||
import { updatePermission } from '../../data/permissions.helper';
|
||||
|
||||
describe('LDAP', function () {
|
||||
this.retries(0);
|
||||
before((done) => getCredentials(done));
|
||||
|
||||
describe('[/ldap.syncNow]', () => {
|
||||
it('should throw an error containing totp-required error when not running EE', async function () {
|
||||
// TODO this is not the right way to do it. We're doing this way for now just because we have separate CI jobs for EE and CE,
|
||||
// ideally we should have a single CI job that adds a license and runs both CE and EE tests.
|
||||
if (process.env.IS_EE) {
|
||||
this.skip();
|
||||
}
|
||||
await request
|
||||
.post(api('ldap.syncNow'))
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('errorType', 'totp-required');
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw an error of LDAP disabled when running EE', async function () {
|
||||
if (!process.env.IS_EE) {
|
||||
this.skip();
|
||||
}
|
||||
await request
|
||||
.post(api('ldap.syncNow'))
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error', 'LDAP_disabled');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/ldap.testSearch]', () => {
|
||||
before(async () => {
|
||||
return updatePermission('test-admin-options', ['admin']);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
return updatePermission('test-admin-options', ['admin']);
|
||||
});
|
||||
|
||||
it('should not allow testing LDAP search if user does NOT have the test-admin-options permission', async () => {
|
||||
await updatePermission('test-admin-options', []);
|
||||
await request
|
||||
.post(api('ldap.testSearch'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
username: 'test-search',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(403)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error', 'User does not have the permissions required for this action [error-unauthorized]');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/ldap.testConnection]', () => {
|
||||
before(async () => {
|
||||
return updatePermission('test-admin-options', ['admin']);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
return updatePermission('test-admin-options', ['admin']);
|
||||
});
|
||||
|
||||
it('should not allow testing LDAP connection if user does NOT have the test-admin-options permission', async () => {
|
||||
await updatePermission('test-admin-options', []);
|
||||
await request
|
||||
.post(api('ldap.testConnection'))
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(403)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error', 'User does not have the permissions required for this action [error-unauthorized]');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
50
apps/meteor/tests/end-to-end/api/SAML.ts
Normal file
50
apps/meteor/tests/end-to-end/api/SAML.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { expect } from 'chai';
|
||||
import { after, before, describe, it } from 'mocha';
|
||||
import type { Response } from 'supertest';
|
||||
|
||||
import { getCredentials, api, request, credentials } from '../../data/api-data';
|
||||
import { updatePermission } from '../../data/permissions.helper';
|
||||
|
||||
describe('SAML', () => {
|
||||
before((done) => getCredentials(done));
|
||||
|
||||
describe('[/saml.parseMetadata]', () => {
|
||||
after(() => updatePermission('test-admin-options', ['admin']));
|
||||
|
||||
it('should fail without the test-admin-options permission', async () => {
|
||||
await updatePermission('test-admin-options', []);
|
||||
await request
|
||||
.post(api('saml.parseMetadata'))
|
||||
.set(credentials)
|
||||
.send({ url: 'https://example.com/metadata.xml' })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(403);
|
||||
await updatePermission('test-admin-options', ['admin']);
|
||||
});
|
||||
|
||||
it('should reject a body without url', async () => {
|
||||
await request
|
||||
.post(api('saml.parseMetadata'))
|
||||
.set(credentials)
|
||||
.send({})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
});
|
||||
});
|
||||
|
||||
it('should reject a URL blocked by SSRF protection', async () => {
|
||||
await request
|
||||
.post(api('saml.parseMetadata'))
|
||||
.set(credentials)
|
||||
.send({ url: 'http://169.254.169.254/latest/meta-data' })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body.error).to.equal('SAML_Metadata_url_blocked');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
4616
apps/meteor/tests/end-to-end/api/abac.ts
Normal file
4616
apps/meteor/tests/end-to-end/api/abac.ts
Normal file
File diff suppressed because it is too large
Load Diff
83
apps/meteor/tests/end-to-end/api/ai-search.ts
Normal file
83
apps/meteor/tests/end-to-end/api/ai-search.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import type { Credentials } from '@rocket.chat/api-client';
|
||||
import type { IUser } from '@rocket.chat/core-typings';
|
||||
import { expect } from 'chai';
|
||||
import { after, before, describe, it } from 'mocha';
|
||||
|
||||
import { api, credentials, getCredentials, request } from '../../data/api-data';
|
||||
import { adminUsername, password } from '../../data/user';
|
||||
import type { TestUser } from '../../data/users.helper';
|
||||
import { createUser, deleteUser, login } from '../../data/users.helper';
|
||||
|
||||
describe('AI Search', () => {
|
||||
before((done) => getCredentials(done));
|
||||
|
||||
describe('[/ai.search]', () => {
|
||||
it('should fail when query is missing', async () => {
|
||||
const response = await request.get(api('ai.search')).set(credentials);
|
||||
|
||||
expect(response.status).to.equal(400);
|
||||
expect(response.body).to.have.property('success', false);
|
||||
expect(response.body).to.have.property('error');
|
||||
});
|
||||
|
||||
it('should return the AI search response shape for authenticated users', async () => {
|
||||
const response = await request.get(api('ai.search')).query({ query: adminUsername, intelligentCount: 8 }).set(credentials);
|
||||
|
||||
expect(response.status).to.equal(200);
|
||||
expect(response.body).to.have.property('success', true);
|
||||
expect(response.body).to.have.property('intelligent').that.is.an('array');
|
||||
expect(response.body).to.have.property('meta');
|
||||
expect(response.body.meta).to.include.keys(['intelligentSearchEnabled', 'intelligentSearchConfigured', 'answerGenerationConfigured']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/ai.search.answer]', () => {
|
||||
it('should fail when not authenticated', async () => {
|
||||
const response = await request
|
||||
.post(api('ai.search.answer'))
|
||||
.send({ query: 'example question', messages: [{ _id: 'example-message-id' }] });
|
||||
|
||||
expect(response.status).to.equal(401);
|
||||
});
|
||||
|
||||
it('should reject answer generation without source messages', async () => {
|
||||
const response = await request.post(api('ai.search.answer')).set(credentials).send({ query: 'example question', messages: [] });
|
||||
|
||||
expect(response.status).to.equal(400);
|
||||
expect(response.body).to.have.property('success', false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/ai.llm.models]', () => {
|
||||
let user: TestUser<IUser>;
|
||||
let userCredentials: Credentials;
|
||||
|
||||
before(async () => {
|
||||
user = await createUser();
|
||||
userCredentials = await login(user.username, password);
|
||||
});
|
||||
|
||||
after(() => deleteUser(user));
|
||||
|
||||
it('should fail when not authenticated', async () => {
|
||||
const response = await request.get(api('ai.llm.models'));
|
||||
|
||||
expect(response.status).to.equal(401);
|
||||
});
|
||||
|
||||
it('should fail for users without settings permissions', async () => {
|
||||
const response = await request.get(api('ai.llm.models')).set(userCredentials);
|
||||
|
||||
expect(response.status).to.equal(403);
|
||||
expect(response.body).to.have.property('success', false);
|
||||
});
|
||||
|
||||
it('should return model options for authorized users', async () => {
|
||||
const response = await request.get(api('ai.llm.models')).set(credentials);
|
||||
|
||||
expect(response.status).to.equal(200);
|
||||
expect(response.body).to.have.property('success', true);
|
||||
expect(response.body).to.have.property('data').that.is.an('array');
|
||||
});
|
||||
});
|
||||
});
|
||||
15
apps/meteor/tests/end-to-end/api/assets.ts
Normal file
15
apps/meteor/tests/end-to-end/api/assets.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { describe, it } from 'mocha';
|
||||
|
||||
import { request } from '../../data/api-data';
|
||||
|
||||
describe('assets', () => {
|
||||
it('should always have CORS headers for assets', async () => {
|
||||
await request.get('/assets/favicon.svg').expect('Content-Type', 'image/svg+xml').expect('Access-Control-Allow-Origin', '*').expect(200);
|
||||
|
||||
await request
|
||||
.get('/fonts/rocketchat.woff2')
|
||||
.expect('Content-Type', 'font/woff2')
|
||||
.expect('Access-Control-Allow-Origin', '*')
|
||||
.expect(200);
|
||||
});
|
||||
});
|
||||
69
apps/meteor/tests/end-to-end/api/assets2.ts
Normal file
69
apps/meteor/tests/end-to-end/api/assets2.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import { expect } from 'chai';
|
||||
import { before, describe, it, after } from 'mocha';
|
||||
|
||||
import { getCredentials, api, request, credentials } from '../../data/api-data';
|
||||
import { imgURL } from '../../data/interactions';
|
||||
import { updatePermission } from '../../data/permissions.helper';
|
||||
|
||||
describe('[Assets]', () => {
|
||||
before((done) => getCredentials(done));
|
||||
|
||||
before(() => updatePermission('manage-assets', ['admin']));
|
||||
|
||||
after(() => updatePermission('manage-assets', ['admin']));
|
||||
|
||||
describe('[/assets.setAsset]', () => {
|
||||
it('should set the "logo" asset', async () => {
|
||||
const res = await request
|
||||
.post(api('assets.setAsset'))
|
||||
.set(credentials)
|
||||
.attach('asset', imgURL)
|
||||
.field({
|
||||
assetName: 'logo',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
expect(res.body).to.have.property('success', true);
|
||||
});
|
||||
|
||||
it('should throw an error when we try set an invalid asset', async () => {
|
||||
const res = await request
|
||||
.post(api('assets.setAsset'))
|
||||
.set(credentials)
|
||||
.attach('invalidAsset', imgURL)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
|
||||
expect(res.body).to.have.property('success', false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/assets.unsetAsset]', () => {
|
||||
it('should unset the "logo" asset', async () => {
|
||||
const res = await request
|
||||
.post(api('assets.unsetAsset'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
assetName: 'logo',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
expect(res.body).to.have.property('success', true);
|
||||
});
|
||||
|
||||
it('should throw an error when we try set an invalid asset', async () => {
|
||||
const res = await request
|
||||
.post(api('assets.unsetAsset'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
assetName: 'invalidAsset',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
|
||||
expect(res.body).to.have.property('success', false);
|
||||
});
|
||||
});
|
||||
});
|
||||
439
apps/meteor/tests/end-to-end/api/audit.ts
Normal file
439
apps/meteor/tests/end-to-end/api/audit.ts
Normal file
@@ -0,0 +1,439 @@
|
||||
import type { Credentials } from '@rocket.chat/api-client';
|
||||
import type { IRoom, IUser } from '@rocket.chat/core-typings';
|
||||
import { Random } from '@rocket.chat/random';
|
||||
import { expect } from 'chai';
|
||||
import EJSON from 'ejson';
|
||||
import { before, describe, it, after } from 'mocha';
|
||||
|
||||
import { getCredentials, api, request, credentials, methodCall } from '../../data/api-data';
|
||||
import { updatePermission } from '../../data/permissions.helper';
|
||||
import { createRoom, deleteRoom } from '../../data/rooms.helper';
|
||||
import { password } from '../../data/user';
|
||||
import { createUser, deleteUser, login } from '../../data/users.helper';
|
||||
import { IS_EE } from '../../e2e/config/constants';
|
||||
|
||||
(IS_EE ? describe : describe.skip)('Audit Panel', () => {
|
||||
let testChannel: IRoom;
|
||||
let testPrivateChannel: IRoom;
|
||||
let dummyUser: IUser;
|
||||
let auditor: IUser;
|
||||
let auditorCredentials: Credentials;
|
||||
before((done) => getCredentials(done));
|
||||
before(async () => {
|
||||
testChannel = (await createRoom({ type: 'c', name: `chat.api-test-${Date.now()}` })).body.channel;
|
||||
testPrivateChannel = (await createRoom({ type: 'p', name: `chat.api-test-${Date.now()}` })).body.group;
|
||||
dummyUser = await createUser();
|
||||
auditor = await createUser({ roles: ['user', 'auditor'] });
|
||||
|
||||
auditorCredentials = await login(auditor.username, password);
|
||||
|
||||
// the auditor role has can-audit but not can-audit-log by default; grant it suite-wide for the audit.auditions cases
|
||||
await updatePermission('can-audit-log', ['admin', 'auditor']);
|
||||
});
|
||||
after(async () => {
|
||||
await updatePermission('can-audit-log', ['admin']);
|
||||
await deleteRoom({ type: 'c', roomId: testChannel._id });
|
||||
await deleteUser({ _id: dummyUser._id });
|
||||
await deleteUser({ _id: auditor._id });
|
||||
await deleteRoom({ type: 'p', roomId: testPrivateChannel._id });
|
||||
});
|
||||
|
||||
describe('audit/rooms.members [no permissions]', () => {
|
||||
before(async () => {
|
||||
await updatePermission('view-members-list-all-rooms', []);
|
||||
});
|
||||
after(async () => {
|
||||
await updatePermission('view-members-list-all-rooms', ['admin', 'auditor']);
|
||||
});
|
||||
it('should fail if user does not have view-members-list-all-rooms permission', async () => {
|
||||
await request
|
||||
.get(api('audit/rooms.members'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
roomId: 'GENERAL',
|
||||
})
|
||||
.expect(403);
|
||||
await request
|
||||
.get(api('audit/rooms.members'))
|
||||
.set(auditorCredentials)
|
||||
.query({
|
||||
roomId: 'GENERAL',
|
||||
})
|
||||
.expect(403);
|
||||
});
|
||||
});
|
||||
|
||||
describe('audit/rooms.members', () => {
|
||||
it('should fail if user is not logged in', async () => {
|
||||
await request
|
||||
.get(api('audit/rooms.members'))
|
||||
.query({
|
||||
roomId: 'GENERAL',
|
||||
})
|
||||
.expect(401);
|
||||
});
|
||||
it('should fail if roomId is invalid', async () => {
|
||||
await request
|
||||
.get(api('audit/rooms.members'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
roomId: Random.id(),
|
||||
})
|
||||
.expect(404);
|
||||
});
|
||||
it('should fail if roomId is not present', async () => {
|
||||
await request.get(api('audit/rooms.members')).set(credentials).query({}).expect(400);
|
||||
});
|
||||
it('should fail if roomId is an empty string', async () => {
|
||||
await request
|
||||
.get(api('audit/rooms.members'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
roomId: '',
|
||||
})
|
||||
.expect(400);
|
||||
});
|
||||
it('should fetch the members of a room', async () => {
|
||||
await request
|
||||
.get(api('audit/rooms.members'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
roomId: testChannel._id,
|
||||
})
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body.members).to.be.an('array');
|
||||
expect(res.body.members).to.have.lengthOf(1);
|
||||
});
|
||||
});
|
||||
it('should persist a log entry', async () => {
|
||||
await request
|
||||
.get(api('audit/rooms.members'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
roomId: testChannel._id,
|
||||
})
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body.members).to.be.an('array');
|
||||
expect(res.body.members).to.have.lengthOf(1);
|
||||
});
|
||||
|
||||
await request
|
||||
.post(methodCall('auditGetAuditions'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
message: EJSON.stringify({
|
||||
method: 'auditGetAuditions',
|
||||
params: [{ startDate: new Date(Date.now() - 86400000), endDate: new Date() }],
|
||||
id: 'id',
|
||||
msg: 'method',
|
||||
}),
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
const message = JSON.parse(res.body.message);
|
||||
|
||||
expect(message.result).to.be.an('array').with.lengthOf.greaterThan(1);
|
||||
const entry = message.result.find((audition: any) => {
|
||||
return audition.fields?.rids?.includes(testChannel._id);
|
||||
});
|
||||
expect(entry).to.have.property('u').that.is.an('object').deep.equal({
|
||||
_id: 'rocketchat.internal.admin.test',
|
||||
username: 'rocketchat.internal.admin.test',
|
||||
name: 'RocketChat Internal Admin Test',
|
||||
});
|
||||
expect(entry).to.have.property('fields').that.is.an('object');
|
||||
const { fields } = entry;
|
||||
|
||||
expect(fields).to.have.property('msg', 'Room_members_list');
|
||||
expect(fields).to.have.property('rids').that.is.an('array').with.lengthOf(1);
|
||||
});
|
||||
});
|
||||
it('should fetch the members of a room with offset and count', async () => {
|
||||
await request
|
||||
.post(methodCall('addUsersToRoom'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
message: JSON.stringify({
|
||||
method: 'addUsersToRoom',
|
||||
params: [{ rid: testChannel._id, users: [dummyUser.username] }],
|
||||
id: 'id',
|
||||
msg: 'method',
|
||||
}),
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
});
|
||||
|
||||
await request
|
||||
.get(api('audit/rooms.members'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
roomId: testChannel._id,
|
||||
offset: 1,
|
||||
count: 1,
|
||||
})
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body.members).to.be.an('array');
|
||||
expect(res.body.members).to.have.lengthOf(1);
|
||||
expect(res.body.members[0].username).to.be.equal(dummyUser.username);
|
||||
expect(res.body.total).to.be.equal(2);
|
||||
expect(res.body.offset).to.be.equal(1);
|
||||
expect(res.body.count).to.be.equal(1);
|
||||
});
|
||||
});
|
||||
|
||||
it('should filter by username', async () => {
|
||||
await request
|
||||
.get(api('audit/rooms.members'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
roomId: testChannel._id,
|
||||
filter: dummyUser.username,
|
||||
})
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body.members).to.be.an('array');
|
||||
expect(res.body.members).to.have.lengthOf(1);
|
||||
expect(res.body.members[0].username).to.be.equal(dummyUser.username);
|
||||
});
|
||||
});
|
||||
|
||||
it('should filter by user name', async () => {
|
||||
await request
|
||||
.get(api('audit/rooms.members'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
roomId: testChannel._id,
|
||||
filter: dummyUser.name,
|
||||
})
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body.members).to.be.an('array');
|
||||
expect(res.body.members).to.have.lengthOf(1);
|
||||
expect(res.body.members[0].name).to.be.equal(dummyUser.name);
|
||||
});
|
||||
});
|
||||
|
||||
it('should sort by username', async () => {
|
||||
await request
|
||||
.get(api('audit/rooms.members'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
roomId: testChannel._id,
|
||||
sort: '{ "username": -1 }',
|
||||
})
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body.members).to.be.an('array');
|
||||
expect(res.body.members).to.have.lengthOf(2);
|
||||
expect(res.body.members[1].username).to.be.equal('rocketchat.internal.admin.test');
|
||||
expect(res.body.members[0].username).to.be.equal(dummyUser.username);
|
||||
});
|
||||
});
|
||||
|
||||
it('should not allow nosqlinjection on filter param', async () => {
|
||||
await request
|
||||
.get(api('audit/rooms.members'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
roomId: testChannel._id,
|
||||
filter: '{ "$ne": "rocketchat.internal.admin.test" }',
|
||||
})
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body.members).to.be.an('array');
|
||||
expect(res.body.members).to.have.lengthOf(0);
|
||||
});
|
||||
|
||||
await request
|
||||
.get(api('audit/rooms.members'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
roomId: testChannel._id,
|
||||
filter: { username: 'rocketchat.internal.admin.test' },
|
||||
})
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
it('should allow to fetch info even if user is not in the room', async () => {
|
||||
await request
|
||||
.get(api('audit/rooms.members'))
|
||||
.set(auditorCredentials)
|
||||
.query({
|
||||
roomId: testChannel._id,
|
||||
})
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body.members).to.be.an('array');
|
||||
expect(res.body.members[0].username).to.be.equal('rocketchat.internal.admin.test');
|
||||
expect(res.body.members[1].username).to.be.equal(dummyUser.username);
|
||||
expect(res.body.total).to.be.equal(2);
|
||||
});
|
||||
});
|
||||
|
||||
it('should allow to fetch info from private rooms', async () => {
|
||||
await request
|
||||
.get(api('audit/rooms.members'))
|
||||
.set(auditorCredentials)
|
||||
.query({
|
||||
roomId: testPrivateChannel._id,
|
||||
})
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body.members).to.be.an('array');
|
||||
expect(res.body.members[0].username).to.be.equal('rocketchat.internal.admin.test');
|
||||
expect(res.body.total).to.be.equal(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/audit.auditions]', () => {
|
||||
const startDate = new Date(0).toISOString();
|
||||
const endDate = new Date().toISOString();
|
||||
|
||||
it('should fail if user is not logged in', () => request.get(api('audit.auditions')).query({ startDate, endDate }).expect(401));
|
||||
|
||||
it('should fail with 400 when startDate/endDate are missing', () =>
|
||||
request.get(api('audit.auditions')).set(auditorCredentials).query({}).expect(400));
|
||||
|
||||
it('should fail with 400 when a date is invalid', () =>
|
||||
request.get(api('audit.auditions')).set(auditorCredentials).query({ startDate: 'not-a-date', endDate }).expect(400));
|
||||
|
||||
it('should fail with 403 when the user lacks the can-audit-log permission', async () => {
|
||||
await updatePermission('can-audit-log', []);
|
||||
await request.get(api('audit.auditions')).set(auditorCredentials).query({ startDate, endDate }).expect(403);
|
||||
await updatePermission('can-audit-log', ['admin', 'auditor']);
|
||||
});
|
||||
|
||||
it('should return the list of auditions', () =>
|
||||
request
|
||||
.get(api('audit.auditions'))
|
||||
.set(auditorCredentials)
|
||||
.query({ startDate, endDate })
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('auditions').that.is.an('array');
|
||||
}));
|
||||
});
|
||||
|
||||
describe('[/audit.messages]', () => {
|
||||
const startDate = new Date(0).toISOString();
|
||||
const endDate = new Date().toISOString();
|
||||
|
||||
it('should fail if user is not logged in', () =>
|
||||
request.post(api('audit.messages')).send({ startDate, endDate, users: [], msg: '', type: 'u' }).expect(401));
|
||||
|
||||
it('should fail with 400 when required params are missing', () =>
|
||||
request.post(api('audit.messages')).set(auditorCredentials).send({ startDate, endDate }).expect(400));
|
||||
|
||||
it('should fail with 400 when a date is invalid', () =>
|
||||
request
|
||||
.post(api('audit.messages'))
|
||||
.set(auditorCredentials)
|
||||
.send({ startDate: 'not-a-date', endDate, users: [], msg: '', type: 'u' })
|
||||
.expect(400));
|
||||
|
||||
it('should fail with 403 when the user lacks the can-audit permission', async () => {
|
||||
await updatePermission('can-audit', []);
|
||||
await request
|
||||
.post(api('audit.messages'))
|
||||
.set(auditorCredentials)
|
||||
.send({ startDate, endDate, users: [], msg: '', type: 'u' })
|
||||
.expect(403);
|
||||
await updatePermission('can-audit', ['admin', 'auditor']);
|
||||
});
|
||||
|
||||
it('should return matching messages', () =>
|
||||
request
|
||||
.post(api('audit.messages'))
|
||||
.set(auditorCredentials)
|
||||
.send({ startDate, endDate, users: [], msg: '', type: 'u' })
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('messages').that.is.an('array');
|
||||
}));
|
||||
|
||||
it('should persist an audit log entry', async () => {
|
||||
// query auditions with a future endDate so the entry created by the request below (ts = now) is in range
|
||||
const auditionsRange = { startDate, endDate: new Date(Date.now() + 60000).toISOString() };
|
||||
const before = await request.get(api('audit.auditions')).set(auditorCredentials).query(auditionsRange);
|
||||
await request
|
||||
.post(api('audit.messages'))
|
||||
.set(auditorCredentials)
|
||||
.send({ startDate, endDate, users: [], msg: '', type: 'u' })
|
||||
.expect(200);
|
||||
const after = await request.get(api('audit.auditions')).set(auditorCredentials).query(auditionsRange);
|
||||
expect(after.body.auditions.length).to.be.greaterThan(before.body.auditions.length);
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/audit.omnichannelMessages]', () => {
|
||||
const startDate = new Date(0).toISOString();
|
||||
const endDate = new Date().toISOString();
|
||||
|
||||
it('should fail if user is not logged in', () =>
|
||||
request.post(api('audit.omnichannelMessages')).send({ startDate, endDate, users: [], msg: '', type: 'l' }).expect(401));
|
||||
|
||||
it('should fail with 400 when required params are missing', () =>
|
||||
request.post(api('audit.omnichannelMessages')).set(auditorCredentials).send({ startDate, endDate }).expect(400));
|
||||
|
||||
it('should fail with 400 when a date is invalid', () =>
|
||||
request
|
||||
.post(api('audit.omnichannelMessages'))
|
||||
.set(auditorCredentials)
|
||||
.send({ startDate: 'not-a-date', endDate, users: [], msg: '', type: 'l' })
|
||||
.expect(400));
|
||||
|
||||
it('should fail with 403 when the user lacks the can-audit permission', async () => {
|
||||
await updatePermission('can-audit', []);
|
||||
await request
|
||||
.post(api('audit.omnichannelMessages'))
|
||||
.set(auditorCredentials)
|
||||
.send({ startDate, endDate, users: [], msg: '', type: 'l' })
|
||||
.expect(403);
|
||||
await updatePermission('can-audit', ['admin', 'auditor']);
|
||||
});
|
||||
|
||||
it('should return matching omnichannel messages', () =>
|
||||
request
|
||||
.post(api('audit.omnichannelMessages'))
|
||||
.set(auditorCredentials)
|
||||
.send({ startDate, endDate, users: [], msg: '', type: 'l' })
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('messages').that.is.an('array');
|
||||
}));
|
||||
|
||||
it('should persist an audit log entry', async () => {
|
||||
// query auditions with a future endDate so the entry created by the request below (ts = now) is in range
|
||||
const auditionsRange = { startDate, endDate: new Date(Date.now() + 60000).toISOString() };
|
||||
const before = await request.get(api('audit.auditions')).set(auditorCredentials).query(auditionsRange);
|
||||
await request
|
||||
.post(api('audit.omnichannelMessages'))
|
||||
.set(auditorCredentials)
|
||||
.send({ startDate, endDate, users: [], msg: '', type: 'l' })
|
||||
.expect(200);
|
||||
const after = await request.get(api('audit.auditions')).set(auditorCredentials).query(auditionsRange);
|
||||
expect(after.body.auditions.length).to.be.greaterThan(before.body.auditions.length);
|
||||
});
|
||||
});
|
||||
});
|
||||
726
apps/meteor/tests/end-to-end/api/autotranslate.ts
Normal file
726
apps/meteor/tests/end-to-end/api/autotranslate.ts
Normal file
@@ -0,0 +1,726 @@
|
||||
import type { Credentials } from '@rocket.chat/api-client';
|
||||
import type { IMessage, IRoom, IUser } from '@rocket.chat/core-typings';
|
||||
import { expect } from 'chai';
|
||||
import { before, describe, after, it } from 'mocha';
|
||||
|
||||
import { getCredentials, api, request, credentials, methodCall } from '../../data/api-data';
|
||||
import { sendSimpleMessage } from '../../data/chat.helper';
|
||||
import { updatePermission, updateSetting } from '../../data/permissions.helper';
|
||||
import { createRoom, deleteRoom } from '../../data/rooms.helper';
|
||||
import { password } from '../../data/user';
|
||||
import type { TestUser } from '../../data/users.helper';
|
||||
import { createUser, deleteUser, login } from '../../data/users.helper';
|
||||
|
||||
const resetAutoTranslateDefaults = async () => {
|
||||
await Promise.all([
|
||||
updateSetting('AutoTranslate_Enabled', false),
|
||||
updateSetting('AutoTranslate_AutoEnableOnJoinRoom', false),
|
||||
updateSetting('Language', ''),
|
||||
updatePermission('auto-translate', ['admin']),
|
||||
]);
|
||||
};
|
||||
|
||||
const resetE2EDefaults = async () => {
|
||||
await Promise.all([updateSetting('E2E_Enabled_Default_PrivateRooms', false), updateSetting('E2E_Enable', false)]);
|
||||
};
|
||||
|
||||
describe('AutoTranslate', () => {
|
||||
before((done) => getCredentials(done));
|
||||
|
||||
describe('[AutoTranslate]', () => {
|
||||
describe('[/autotranslate.getSupportedLanguages', () => {
|
||||
before(() => resetAutoTranslateDefaults());
|
||||
after(() => resetAutoTranslateDefaults());
|
||||
|
||||
it('should throw an error when the "AutoTranslate_Enabled" setting is disabled', async () => {
|
||||
const res = await request
|
||||
.get(api('autotranslate.getSupportedLanguages'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
targetLanguage: 'en',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
|
||||
expect(res.body).to.have.a.property('success', false);
|
||||
expect(res.body.error).to.be.equal('AutoTranslate is disabled.');
|
||||
});
|
||||
it('should throw an error when the user does not have the "auto-translate" permission', async () => {
|
||||
await updateSetting('AutoTranslate_Enabled', true);
|
||||
|
||||
await updatePermission('auto-translate', []);
|
||||
|
||||
const res = await request
|
||||
.get(api('autotranslate.getSupportedLanguages'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
targetLanguage: 'en',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
|
||||
expect(res.body).to.have.a.property('success', false);
|
||||
expect(res.body.errorType).to.be.equal('error-action-not-allowed');
|
||||
expect(res.body.error).to.be.equal('Auto-Translate is not allowed [error-action-not-allowed]');
|
||||
});
|
||||
|
||||
it('should return a list of languages', async () => {
|
||||
await updatePermission('auto-translate', ['admin']);
|
||||
await request
|
||||
.get(api('autotranslate.getSupportedLanguages'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
targetLanguage: 'en',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.a.property('success', true);
|
||||
expect(res.body.languages).to.be.an('array');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/autotranslate.saveSettings', () => {
|
||||
let testGroupId: IRoom['_id'];
|
||||
let testChannelId: IRoom['_id'];
|
||||
|
||||
before(async () => {
|
||||
await Promise.all([
|
||||
resetAutoTranslateDefaults(),
|
||||
updateSetting('E2E_Enable', true),
|
||||
updateSetting('E2E_Enabled_Default_PrivateRooms', true),
|
||||
]);
|
||||
|
||||
testGroupId = (await createRoom({ type: 'p', name: `e2etest-autotranslate-${Date.now()}` })).body.group._id;
|
||||
testChannelId = (await createRoom({ type: 'c', name: `test-autotranslate-${Date.now()}` })).body.channel._id;
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await Promise.all([
|
||||
resetAutoTranslateDefaults(),
|
||||
resetE2EDefaults(),
|
||||
deleteRoom({ type: 'p', roomId: testGroupId }),
|
||||
deleteRoom({ type: 'c', roomId: testChannelId }),
|
||||
]);
|
||||
});
|
||||
|
||||
it('should throw an error when the "AutoTranslate_Enabled" setting is disabled', async () => {
|
||||
const res = await request
|
||||
.post(api('autotranslate.saveSettings'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
roomId: testChannelId,
|
||||
field: 'autoTranslate',
|
||||
defaultLanguage: 'en',
|
||||
value: true,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
|
||||
expect(res.body).to.have.a.property('success', false);
|
||||
expect(res.body.error).to.be.equal('AutoTranslate is disabled.');
|
||||
});
|
||||
it('should throw an error when the user does not have the "auto-translate" permission', async () => {
|
||||
await updateSetting('AutoTranslate_Enabled', true);
|
||||
|
||||
await updatePermission('auto-translate', []);
|
||||
|
||||
const res = await request
|
||||
.post(api('autotranslate.saveSettings'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
roomId: testChannelId,
|
||||
defaultLanguage: 'en',
|
||||
field: 'autoTranslateLanguage',
|
||||
value: 'en',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
|
||||
expect(res.body).to.have.a.property('success', false);
|
||||
expect(res.body.errorType).to.be.equal('error-action-not-allowed');
|
||||
expect(res.body.error).to.be.equal('Auto-Translate is not allowed [error-action-not-allowed]');
|
||||
});
|
||||
it('should throw an error when the bodyParam "roomId" is not provided', async () => {
|
||||
await updatePermission('auto-translate', ['admin']);
|
||||
|
||||
const res = await request
|
||||
.post(api('autotranslate.saveSettings'))
|
||||
.set(credentials)
|
||||
.send({})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
|
||||
expect(res.body).to.have.a.property('success', false);
|
||||
});
|
||||
it('should throw an error when the bodyParam "field" is not provided', async () => {
|
||||
const res = await request
|
||||
.post(api('autotranslate.saveSettings'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
roomId: testChannelId,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
|
||||
expect(res.body).to.have.a.property('success', false);
|
||||
});
|
||||
it('should throw an error when the bodyParam "value" is not provided', async () => {
|
||||
const res = await request
|
||||
.post(api('autotranslate.saveSettings'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
roomId: testChannelId,
|
||||
field: 'autoTranslate',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
|
||||
expect(res.body).to.have.a.property('success', false);
|
||||
});
|
||||
it('should throw an error when the bodyParam "autoTranslate" is not a boolean', async () => {
|
||||
const res = await request
|
||||
.post(api('autotranslate.saveSettings'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
roomId: testChannelId,
|
||||
field: 'autoTranslate',
|
||||
value: 'test',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
|
||||
expect(res.body).to.have.a.property('success', false);
|
||||
});
|
||||
it('should throw an error when the bodyParam "autoTranslateLanguage" is not a string', async () => {
|
||||
const res = await request
|
||||
.post(api('autotranslate.saveSettings'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
roomId: testChannelId,
|
||||
field: 'autoTranslateLanguage',
|
||||
value: 12,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
|
||||
expect(res.body).to.have.a.property('success', false);
|
||||
});
|
||||
it('should throw an error when the bodyParam "field" is invalid', async () => {
|
||||
const res = await request
|
||||
.post(api('autotranslate.saveSettings'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
roomId: testChannelId,
|
||||
field: 'invalid',
|
||||
value: 12,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
|
||||
expect(res.body).to.have.a.property('success', false);
|
||||
});
|
||||
it('should throw an error when the bodyParam "roomId" is invalid or the user is not subscribed', async () => {
|
||||
const res = await request
|
||||
.post(api('autotranslate.saveSettings'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
roomId: 'invalid',
|
||||
field: 'autoTranslateLanguage',
|
||||
value: 'en',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
|
||||
expect(res.body).to.have.a.property('success', false);
|
||||
expect(res.body.errorType).to.be.equal('error-invalid-subscription');
|
||||
expect(res.body.error).to.be.equal('Invalid subscription [error-invalid-subscription]');
|
||||
});
|
||||
it('should throw an error when E2E encryption is enabled', async () => {
|
||||
await request
|
||||
.post(api('autotranslate.saveSettings'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
roomId: testGroupId,
|
||||
field: 'autoTranslate',
|
||||
defaultLanguage: 'en',
|
||||
value: true,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('errorType', 'error-e2e-enabled');
|
||||
});
|
||||
});
|
||||
it('should return success when the setting is saved correctly', async () => {
|
||||
const res = await request
|
||||
.post(api('autotranslate.saveSettings'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
roomId: testChannelId,
|
||||
field: 'autoTranslateLanguage',
|
||||
value: 'en',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
expect(res.body).to.have.a.property('success', true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/autotranslate.translateMessage', () => {
|
||||
let messageSent: IMessage;
|
||||
let testChannelId: IRoom['_id'];
|
||||
|
||||
before(async () => {
|
||||
await resetAutoTranslateDefaults();
|
||||
|
||||
testChannelId = (await createRoom({ type: 'c', name: `test-autotranslate-message-${Date.now()}` })).body.channel._id;
|
||||
const res = await sendSimpleMessage({
|
||||
roomId: testChannelId,
|
||||
text: 'Isso é um teste',
|
||||
});
|
||||
messageSent = res.body.message;
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await Promise.all([resetAutoTranslateDefaults(), deleteRoom({ type: 'c', roomId: testChannelId })]);
|
||||
});
|
||||
|
||||
it('should throw an error when the "AutoTranslate_Enabled" setting is disabled', async () => {
|
||||
const res = await request
|
||||
.post(api('autotranslate.translateMessage'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
messageId: 'test',
|
||||
targetLanguage: 'en',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
|
||||
expect(res.body).to.have.a.property('success', false);
|
||||
expect(res.body.error).to.be.equal('AutoTranslate is disabled.');
|
||||
});
|
||||
it('should throw an error when the bodyParam "messageId" is not provided', async () => {
|
||||
await updateSetting('AutoTranslate_Enabled', true);
|
||||
|
||||
await updatePermission('auto-translate', ['admin']);
|
||||
|
||||
const res = await request
|
||||
.post(api('autotranslate.translateMessage'))
|
||||
.set(credentials)
|
||||
.send({})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
|
||||
expect(res.body).to.have.a.property('success', false);
|
||||
});
|
||||
it('should throw an error when the bodyParam "messageId" is invalid', async () => {
|
||||
const res = await request
|
||||
.post(api('autotranslate.translateMessage'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
messageId: 'invalid',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
|
||||
expect(res.body).to.have.a.property('success', false);
|
||||
expect(res.body.error).to.be.equal('Message not found.');
|
||||
});
|
||||
it('should return success when the translate is successful', async () => {
|
||||
const res = await request
|
||||
.post(api('autotranslate.translateMessage'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
messageId: messageSent._id,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
expect(res.body).to.have.a.property('success', true);
|
||||
});
|
||||
|
||||
describe('room access check', () => {
|
||||
let userA: TestUser<IUser>;
|
||||
let userB: TestUser<IUser>;
|
||||
let credA: Credentials;
|
||||
let credB: Credentials;
|
||||
let privateRoom: IRoom;
|
||||
let privateMessage: IMessage;
|
||||
|
||||
before(async () => {
|
||||
await updateSetting('AutoTranslate_Enabled', true);
|
||||
|
||||
userA = await createUser();
|
||||
userB = await createUser();
|
||||
|
||||
credA = await login(userA.username, password);
|
||||
credB = await login(userB.username, password);
|
||||
|
||||
privateRoom = (
|
||||
await createRoom({
|
||||
type: 'p',
|
||||
name: `test-autotranslate-access-${Date.now()}`,
|
||||
credentials: credA,
|
||||
})
|
||||
).body.group;
|
||||
|
||||
const msgRes = await sendSimpleMessage({
|
||||
roomId: privateRoom._id,
|
||||
text: 'Isso é um teste',
|
||||
userCredentials: credA,
|
||||
});
|
||||
privateMessage = msgRes.body.message;
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await Promise.all([
|
||||
updateSetting('AutoTranslate_Enabled', false),
|
||||
deleteUser(userA),
|
||||
deleteUser(userB),
|
||||
deleteRoom({ type: 'p', roomId: privateRoom._id }),
|
||||
]);
|
||||
});
|
||||
|
||||
it('should return 403 forbidden when the user is not a member of the room', async () => {
|
||||
const res = await request
|
||||
.post(api('autotranslate.translateMessage'))
|
||||
.set(credB)
|
||||
.send({
|
||||
messageId: privateMessage._id,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(403);
|
||||
|
||||
expect(res.body).to.have.a.property('success', false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('[autoTranslate.translateMessage method]', () => {
|
||||
let userA: TestUser<IUser>;
|
||||
let userB: TestUser<IUser>;
|
||||
let credA: Credentials;
|
||||
let credB: Credentials;
|
||||
let privateRoom: IRoom;
|
||||
let privateMessage: IMessage;
|
||||
|
||||
before(async () => {
|
||||
await updateSetting('AutoTranslate_Enabled', true);
|
||||
|
||||
userA = await createUser();
|
||||
userB = await createUser();
|
||||
|
||||
credA = await login(userA.username, password);
|
||||
credB = await login(userB.username, password);
|
||||
|
||||
privateRoom = (
|
||||
await createRoom({
|
||||
type: 'p',
|
||||
name: `test-autotranslate-method-${Date.now()}`,
|
||||
credentials: credA,
|
||||
})
|
||||
).body.group;
|
||||
|
||||
const msgRes = await sendSimpleMessage({
|
||||
roomId: privateRoom._id,
|
||||
text: 'Isso é um teste',
|
||||
userCredentials: credA,
|
||||
});
|
||||
privateMessage = msgRes.body.message;
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await Promise.all([
|
||||
updateSetting('AutoTranslate_Enabled', false),
|
||||
deleteUser(userA),
|
||||
deleteUser(userB),
|
||||
deleteRoom({ type: 'p', roomId: privateRoom._id }),
|
||||
]);
|
||||
});
|
||||
|
||||
it('should fail when messageId is not a string', async () => {
|
||||
const res = await request
|
||||
.post(methodCall('autoTranslate.translateMessage'))
|
||||
.set(credA)
|
||||
.send({
|
||||
message: JSON.stringify({
|
||||
msg: 'method',
|
||||
id: 'id',
|
||||
method: 'autoTranslate.translateMessage',
|
||||
params: [{ _id: { $gt: '' } }, 'en'],
|
||||
}),
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
|
||||
expect(res.body).to.have.a.property('success', false);
|
||||
const parsedBody = JSON.parse(res.body.message);
|
||||
expect(parsedBody).to.have.a.property('error');
|
||||
});
|
||||
|
||||
it('should return error-not-allowed when the caller is not a member of the room', async () => {
|
||||
const res = await request
|
||||
.post(methodCall('autoTranslate.translateMessage'))
|
||||
.set(credB)
|
||||
.send({
|
||||
message: JSON.stringify({
|
||||
msg: 'method',
|
||||
id: 'id',
|
||||
method: 'autoTranslate.translateMessage',
|
||||
params: [{ _id: privateMessage._id }, 'en'],
|
||||
}),
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
|
||||
expect(res.body).to.have.a.property('success', false);
|
||||
const parsedBody = JSON.parse(res.body.message);
|
||||
expect(parsedBody).to.have.a.property('error');
|
||||
expect(parsedBody.error).to.have.a.property('error', 'error-not-allowed');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Autoenable setting', () => {
|
||||
let userA: TestUser<IUser>;
|
||||
let userB: TestUser<IUser>;
|
||||
let credA: Credentials;
|
||||
let credB: Credentials;
|
||||
let channel: IRoom;
|
||||
const channelsToRemove: IRoom[] = [];
|
||||
|
||||
const createChannel = async (members: string[] | undefined, cred: Credentials) =>
|
||||
(await createRoom({ type: 'c', members, name: `channel-test-${Date.now()}`, credentials: cred })).body.channel;
|
||||
|
||||
const setLanguagePref = async (language: string, cred: Credentials) => {
|
||||
await request
|
||||
.post(api('users.setPreferences'))
|
||||
.set(cred)
|
||||
.send({ data: { language } })
|
||||
.expect(200)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
});
|
||||
};
|
||||
|
||||
const getSub = async (roomId: IRoom['_id'], cred: Credentials) =>
|
||||
(
|
||||
await request
|
||||
.get(api('subscriptions.getOne'))
|
||||
.set(cred)
|
||||
.query({
|
||||
roomId,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('subscription').and.to.be.an('object');
|
||||
})
|
||||
).body.subscription;
|
||||
|
||||
before(async () => {
|
||||
await Promise.all([
|
||||
updateSetting('AutoTranslate_Enabled', true),
|
||||
updateSetting('AutoTranslate_AutoEnableOnJoinRoom', true),
|
||||
updateSetting('Language', 'pt-BR'),
|
||||
]);
|
||||
|
||||
userA = await createUser();
|
||||
userB = await createUser();
|
||||
|
||||
credA = await login(userA.username, password);
|
||||
credB = await login(userB.username, password);
|
||||
|
||||
channel = await createChannel(undefined, credA);
|
||||
|
||||
await setLanguagePref('en', credB);
|
||||
channelsToRemove.push(channel);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await Promise.all([
|
||||
updateSetting('AutoTranslate_AutoEnableOnJoinRoom', false),
|
||||
updateSetting('AutoTranslate_Enabled', false),
|
||||
updateSetting('Language', ''),
|
||||
deleteUser(userA),
|
||||
deleteUser(userB),
|
||||
channelsToRemove.map(() => deleteRoom({ type: 'c', roomId: channel._id })),
|
||||
]);
|
||||
});
|
||||
|
||||
it("should do nothing if the user hasn't changed his language preference", async () => {
|
||||
const sub = await getSub(channel._id, credA);
|
||||
expect(sub).to.not.have.property('autoTranslate');
|
||||
expect(sub).to.not.have.property('autoTranslateLanguage');
|
||||
});
|
||||
|
||||
it("should do nothing if the user changed his language preference to be the same as the server's", async () => {
|
||||
await setLanguagePref('pt-BR', credA);
|
||||
|
||||
const sub = await getSub(channel._id, credA);
|
||||
expect(sub).to.not.have.property('autoTranslate');
|
||||
expect(sub).to.not.have.property('autoTranslateLanguage');
|
||||
});
|
||||
|
||||
it('should enable autotranslate with the correct language when joining a room', async () => {
|
||||
await request
|
||||
.post(api('channels.join'))
|
||||
.set(credB)
|
||||
.send({
|
||||
roomId: channel._id,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
const sub = await getSub(channel._id, credB);
|
||||
expect(sub).to.have.property('autoTranslate');
|
||||
expect(sub).to.have.property('autoTranslateLanguage').and.to.be.equal('en');
|
||||
});
|
||||
|
||||
it('should enable autotranslate with the correct language when creating a new room', async () => {
|
||||
await setLanguagePref('en', credA);
|
||||
|
||||
const newChannel = await createChannel(undefined, credA);
|
||||
const sub = await getSub(newChannel._id, credA);
|
||||
expect(sub).to.have.property('autoTranslate');
|
||||
expect(sub).to.have.property('autoTranslateLanguage').and.to.be.equal('en');
|
||||
channelsToRemove.push(newChannel);
|
||||
});
|
||||
|
||||
it('should enable autotranslate for all the members added to the room upon creation', async () => {
|
||||
const newChannel = await createChannel([userA.username, userB.username], credA);
|
||||
const subA = await getSub(newChannel._id, credA);
|
||||
expect(subA).to.have.property('autoTranslate');
|
||||
expect(subA).to.have.property('autoTranslateLanguage').and.to.be.equal('en');
|
||||
|
||||
const subB = await getSub(newChannel._id, credB);
|
||||
expect(subB).to.have.property('autoTranslate');
|
||||
expect(subB).to.have.property('autoTranslateLanguage').and.to.be.equal('en');
|
||||
channelsToRemove.push(newChannel);
|
||||
});
|
||||
|
||||
it('should enable autotranslate with the correct language when added to a room', async () => {
|
||||
const newChannel = await createChannel(undefined, credA);
|
||||
await request
|
||||
.post(api('channels.invite'))
|
||||
.set(credA)
|
||||
.send({
|
||||
roomId: newChannel._id,
|
||||
userId: userB._id,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
const sub = await getSub(newChannel._id, credB);
|
||||
expect(sub).to.have.property('autoTranslate');
|
||||
expect(sub).to.have.property('autoTranslateLanguage').and.to.be.equal('en');
|
||||
channelsToRemove.push(newChannel);
|
||||
});
|
||||
|
||||
it('should change the auto translate language when the user changes his language preference', async () => {
|
||||
await setLanguagePref('es', credA);
|
||||
const newChannel = await createChannel(undefined, credA);
|
||||
const subscription = await getSub(newChannel._id, credA);
|
||||
|
||||
expect(subscription).to.have.property('autoTranslate', true);
|
||||
expect(subscription).to.have.property('autoTranslateLanguage', 'es');
|
||||
channelsToRemove.push(newChannel);
|
||||
});
|
||||
|
||||
it('should enable autotranslate on existing subscriptions when user changes from workspace language to a different one', async () => {
|
||||
await setLanguagePref('pt-BR', credA);
|
||||
const newChannel = await createChannel(undefined, credA);
|
||||
channelsToRemove.push(newChannel);
|
||||
|
||||
const subBefore = await getSub(newChannel._id, credA);
|
||||
expect(subBefore).to.not.have.property('autoTranslate');
|
||||
expect(subBefore).to.not.have.property('autoTranslateLanguage');
|
||||
|
||||
await setLanguagePref('en', credA);
|
||||
|
||||
const subAfter = await getSub(newChannel._id, credA);
|
||||
expect(subAfter).to.have.property('autoTranslate', true);
|
||||
expect(subAfter).to.have.property('autoTranslateLanguage', 'en');
|
||||
});
|
||||
|
||||
it("should enable autotranslate on existing subscriptions when user changes from 'default' language to a non-workspace language", async () => {
|
||||
await setLanguagePref('default', credA);
|
||||
const newChannel = await createChannel(undefined, credA);
|
||||
channelsToRemove.push(newChannel);
|
||||
|
||||
const subBefore = await getSub(newChannel._id, credA);
|
||||
expect(subBefore).to.not.have.property('autoTranslate');
|
||||
expect(subBefore).to.not.have.property('autoTranslateLanguage');
|
||||
|
||||
await setLanguagePref('en', credA);
|
||||
|
||||
const subAfter = await getSub(newChannel._id, credA);
|
||||
expect(subAfter).to.have.property('autoTranslate', true);
|
||||
expect(subAfter).to.have.property('autoTranslateLanguage', 'en');
|
||||
});
|
||||
|
||||
it('should disable autotranslate on existing subscriptions when user changes language to default', async () => {
|
||||
await setLanguagePref('en', credA);
|
||||
const newChannel = await createChannel(undefined, credA);
|
||||
channelsToRemove.push(newChannel);
|
||||
|
||||
const subBefore = await getSub(newChannel._id, credA);
|
||||
expect(subBefore).to.have.property('autoTranslate', true);
|
||||
|
||||
await setLanguagePref('default', credA);
|
||||
|
||||
const subAfter = await getSub(newChannel._id, credA);
|
||||
expect(subAfter).to.not.have.property('autoTranslate');
|
||||
expect(subAfter).to.not.have.property('autoTranslateLanguage');
|
||||
});
|
||||
|
||||
it('should disable autotranslate on existing subscriptions when user changes language to the workspace default', async () => {
|
||||
await setLanguagePref('en', credA);
|
||||
const newChannel = await createChannel(undefined, credA);
|
||||
channelsToRemove.push(newChannel);
|
||||
|
||||
const subBefore = await getSub(newChannel._id, credA);
|
||||
expect(subBefore).to.have.property('autoTranslate', true);
|
||||
|
||||
await setLanguagePref('pt-BR', credA);
|
||||
|
||||
const subAfter = await getSub(newChannel._id, credA);
|
||||
expect(subAfter).to.not.have.property('autoTranslate');
|
||||
expect(subAfter).to.not.have.property('autoTranslateLanguage');
|
||||
});
|
||||
|
||||
it('should not re-enable autotranslate for rooms where the user has explicitly opted out', async () => {
|
||||
await updatePermission('auto-translate', ['admin', 'user']);
|
||||
|
||||
await setLanguagePref('en', credA);
|
||||
const newChannel = await createChannel(undefined, credA);
|
||||
channelsToRemove.push(newChannel);
|
||||
|
||||
const subEnabled = await getSub(newChannel._id, credA);
|
||||
expect(subEnabled).to.have.property('autoTranslate', true);
|
||||
|
||||
await request
|
||||
.post(api('autotranslate.saveSettings'))
|
||||
.set(credA)
|
||||
.send({ roomId: newChannel._id, field: 'autoTranslate', value: false, defaultLanguage: 'en' })
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
});
|
||||
|
||||
const subOptedOut = await getSub(newChannel._id, credA);
|
||||
expect(subOptedOut).to.have.property('autoTranslate', false);
|
||||
|
||||
await setLanguagePref('es', credA);
|
||||
|
||||
const subAfterLangChange = await getSub(newChannel._id, credA);
|
||||
expect(subAfterLangChange).to.have.property('autoTranslate', false);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
288
apps/meteor/tests/end-to-end/api/banners.ts
Normal file
288
apps/meteor/tests/end-to-end/api/banners.ts
Normal file
@@ -0,0 +1,288 @@
|
||||
import type { IUser } from '@rocket.chat/core-typings';
|
||||
import { expect } from 'chai';
|
||||
import { after, before, describe, it } from 'mocha';
|
||||
import { MongoClient } from 'mongodb';
|
||||
|
||||
import { getCredentials, api, request, credentials } from '../../data/api-data';
|
||||
import { getMe } from '../../data/users.helper';
|
||||
import { URL_MONGODB } from '../../e2e/config/constants';
|
||||
|
||||
describe('banners', () => {
|
||||
before((done) => getCredentials(done));
|
||||
|
||||
describe('[/banners.dismiss]', () => {
|
||||
it('should fail if not logged in', async () => {
|
||||
const res = await request
|
||||
.post(api('banners.dismiss'))
|
||||
.send({
|
||||
bannerId: '123',
|
||||
})
|
||||
.expect(401);
|
||||
|
||||
expect(res.body).to.have.property('status', 'error');
|
||||
expect(res.body).to.have.property('message');
|
||||
});
|
||||
|
||||
it('should fail if missing bannerId key', async () => {
|
||||
const res = await request.post(api('banners.dismiss')).set(credentials).send({}).expect(400);
|
||||
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('errorType', 'invalid-params');
|
||||
});
|
||||
|
||||
it('should fail if bannerId is empty', async () => {
|
||||
const res = await request
|
||||
.post(api('banners.dismiss'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
bannerId: '',
|
||||
})
|
||||
.expect(400);
|
||||
|
||||
expect(res.body).to.have.property('success', false);
|
||||
});
|
||||
|
||||
it('should fail if bannerId is invalid', async () => {
|
||||
const res = await request
|
||||
.post(api('banners.dismiss'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
bannerId: '123',
|
||||
})
|
||||
.expect(400);
|
||||
|
||||
expect(res.body).to.have.property('success', false);
|
||||
});
|
||||
|
||||
describe('banners stored in the user record', () => {
|
||||
let connection: MongoClient;
|
||||
|
||||
const bannerId = 'alert-user-banner-test';
|
||||
|
||||
const getUserBanners = async () => (await getMe<IUser>(credentials)).banners;
|
||||
|
||||
before(async () => {
|
||||
connection = await MongoClient.connect(URL_MONGODB);
|
||||
|
||||
await connection
|
||||
.db()
|
||||
.collection<IUser>('users')
|
||||
.updateOne(
|
||||
{ _id: 'rocketchat.internal.admin.test' },
|
||||
{
|
||||
$set: {
|
||||
[`banners.${bannerId}`]: {
|
||||
id: bannerId,
|
||||
priority: 10,
|
||||
title: 'Banner_Title',
|
||||
text: 'Banner_Text',
|
||||
textArguments: [],
|
||||
modifiers: [],
|
||||
link: 'https://rocket.chat',
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await connection
|
||||
.db()
|
||||
.collection<IUser>('users')
|
||||
.updateOne(
|
||||
{ _id: 'rocketchat.internal.admin.test' },
|
||||
{
|
||||
$unset: { [`banners.${bannerId}`]: 1 },
|
||||
},
|
||||
);
|
||||
await connection.close();
|
||||
});
|
||||
|
||||
it('should mark the banner as read on the user record', async () => {
|
||||
const res = await request
|
||||
.post(api('banners.dismiss'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
bannerId,
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
expect(res.body).to.have.property('success', true);
|
||||
|
||||
const banners = await getUserBanners();
|
||||
|
||||
expect(banners).to.have.nested.property(`${bannerId}.read`, true);
|
||||
});
|
||||
|
||||
it('should succeed if the banner was already dismissed', async () => {
|
||||
const res = await request
|
||||
.post(api('banners.dismiss'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
bannerId,
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
expect(res.body).to.have.property('success', true);
|
||||
|
||||
const banners = await getUserBanners();
|
||||
|
||||
expect(banners).to.have.nested.property(`${bannerId}.read`, true);
|
||||
});
|
||||
|
||||
it('should not add an unknown banner to the user record', async () => {
|
||||
const res = await request
|
||||
.post(api('banners.dismiss'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
bannerId: 'an-unknown-banner-id',
|
||||
})
|
||||
.expect(400);
|
||||
|
||||
expect(res.body).to.have.property('success', false);
|
||||
|
||||
const banners = await getUserBanners();
|
||||
|
||||
expect(banners).to.not.have.property('an-unknown-banner-id');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/banners]', () => {
|
||||
it('should fail if not logged in', async () => {
|
||||
return request
|
||||
.get(api('banners'))
|
||||
.query({
|
||||
platform: 'web',
|
||||
})
|
||||
.expect(401)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('status', 'error');
|
||||
expect(res.body).to.have.property('message');
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail if missing platform', async () => {
|
||||
return request
|
||||
.get(api('banners'))
|
||||
.set(credentials)
|
||||
.query({})
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('errorType', 'error-invalid-params');
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail if platform is invalid', async () => {
|
||||
return request
|
||||
.get(api('banners'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
platform: 'invalid-platform',
|
||||
})
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('errorType', 'error-invalid-params');
|
||||
});
|
||||
});
|
||||
|
||||
it('should succesfully return web banners', async () => {
|
||||
return request
|
||||
.get(api('banners'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
platform: 'web',
|
||||
})
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('banners').that.is.an('array');
|
||||
});
|
||||
});
|
||||
|
||||
it('should succesfully return mobile banners', async () => {
|
||||
return request
|
||||
.get(api('banners'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
platform: 'mobile',
|
||||
})
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('banners').that.is.an('array');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/banners/:id]', () => {
|
||||
it('should fail if not logged in', async () => {
|
||||
return request
|
||||
.get(api('banners/some-id'))
|
||||
.query({
|
||||
platform: 'web',
|
||||
})
|
||||
.expect(401)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('status', 'error');
|
||||
expect(res.body).to.have.property('message');
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail if missing platform', async () => {
|
||||
return request
|
||||
.get(api('banners/some-id'))
|
||||
.set(credentials)
|
||||
.query({})
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('errorType', 'error-invalid-params');
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail if platform is invalid', async () => {
|
||||
return request
|
||||
.get(api('banners/some-id'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
platform: 'invalid-platform',
|
||||
})
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('errorType', 'error-invalid-params');
|
||||
});
|
||||
});
|
||||
|
||||
it('should succesfully return a web banner by id', async () => {
|
||||
return request
|
||||
.get(api('banners/some-id'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
platform: 'web',
|
||||
})
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('banners').that.is.an('array');
|
||||
});
|
||||
});
|
||||
|
||||
it('should succesfully return a mobile banner by id', async () => {
|
||||
return request
|
||||
.get(api('banners/some-id'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
platform: 'mobile',
|
||||
})
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('banners').that.is.an('array');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
720
apps/meteor/tests/end-to-end/api/calendar.ts
Normal file
720
apps/meteor/tests/end-to-end/api/calendar.ts
Normal file
@@ -0,0 +1,720 @@
|
||||
import type { Credentials } from '@rocket.chat/api-client';
|
||||
import type { IUser } from '@rocket.chat/core-typings';
|
||||
import { expect } from 'chai';
|
||||
import { after, before, describe, it } from 'mocha';
|
||||
import type { Response } from 'supertest';
|
||||
|
||||
import { sleep } from '../../../lib/utils/sleep';
|
||||
import { getCredentials, api, request, credentials } from '../../data/api-data';
|
||||
import { password } from '../../data/user';
|
||||
import { createUser, deleteUser, login } from '../../data/users.helper';
|
||||
import { withTimeout } from '../../data/utils';
|
||||
import { IS_EE } from '../../e2e/config/constants';
|
||||
|
||||
describe('[Calendar Events]', () => {
|
||||
let user2: IUser;
|
||||
let userCredentials: Credentials;
|
||||
|
||||
before((done) => getCredentials(done));
|
||||
|
||||
before(async () => {
|
||||
user2 = await createUser();
|
||||
userCredentials = await login(user2.username, password);
|
||||
});
|
||||
|
||||
after(() => deleteUser(user2));
|
||||
|
||||
describe('[/calendar-events.create]', () => {
|
||||
it('should successfully create an event in the calendar', async () => {
|
||||
let eventId: string | undefined;
|
||||
|
||||
await request
|
||||
.post(api('calendar-events.create'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
startTime: new Date().toISOString(),
|
||||
subject: 'Subject',
|
||||
description: 'Description',
|
||||
reminderMinutesBeforeStart: 10,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
eventId = res.body.id;
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await request.post(api('calendar-events.delete')).set(credentials).send({ eventId });
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail to create an event without a start time', async () => {
|
||||
await request
|
||||
.post(api('calendar-events.create'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
subject: 'Subject',
|
||||
description: 'Description',
|
||||
reminderMinutesBeforeStart: 10,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
it('should fail to create an event without a subject', async () => {
|
||||
await request
|
||||
.post(api('calendar-events.create'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
description: 'Description',
|
||||
startTime: new Date().toISOString(),
|
||||
reminderMinutesBeforeStart: 10,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
it('should fail to create an event without a description', async () => {
|
||||
await request
|
||||
.post(api('calendar-events.create'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
subject: 'Subject',
|
||||
startTime: new Date().toISOString(),
|
||||
reminderMinutesBeforeStart: 10,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
it('should successfully create an event without reminder information', async () => {
|
||||
let eventId: string | undefined;
|
||||
|
||||
await request
|
||||
.post(api('calendar-events.create'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
startTime: new Date().toISOString(),
|
||||
subject: 'Subject',
|
||||
description: 'Description',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
eventId = res.body.id;
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await request.post(api('calendar-events.delete')).set(credentials).send({ eventId });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/calendar-events.list]', () => {
|
||||
const testSubject = `calendar-events.list-${Date.now()}`;
|
||||
const testSubject2 = `calendar-events.list-${Date.now()}`;
|
||||
let eventId: string | undefined;
|
||||
let eventId2: string | undefined;
|
||||
let eventId3: string | undefined;
|
||||
|
||||
before('create sample events', async () => {
|
||||
await request
|
||||
.post(api('calendar-events.create'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
startTime: new Date().toISOString(),
|
||||
subject: testSubject,
|
||||
description: 'Description',
|
||||
reminderMinutesBeforeStart: 10,
|
||||
})
|
||||
.then((res) => {
|
||||
eventId = res.body.id;
|
||||
});
|
||||
|
||||
await request
|
||||
.post(api('calendar-events.create'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
startTime: new Date(Date.now() + 48 * 60 * 60 * 1000).toISOString(),
|
||||
subject: testSubject2,
|
||||
description: 'Future Event',
|
||||
reminderMinutesBeforeStart: 10,
|
||||
})
|
||||
.then((res) => {
|
||||
eventId2 = res.body.id;
|
||||
});
|
||||
|
||||
await request
|
||||
.post(api('calendar-events.create'))
|
||||
.set(userCredentials)
|
||||
.send({
|
||||
startTime: new Date().toISOString(),
|
||||
subject: testSubject,
|
||||
description: 'Description',
|
||||
reminderMinutesBeforeStart: 10,
|
||||
})
|
||||
.then((res) => {
|
||||
eventId3 = res.body.id;
|
||||
});
|
||||
});
|
||||
|
||||
after(() =>
|
||||
Promise.all([
|
||||
request.post(api('calendar-events.delete')).set(credentials).send({ eventId }),
|
||||
request.post(api('calendar-events.delete')).set(credentials).send({ eventId: eventId2 }),
|
||||
request.post(api('calendar-events.delete')).set(userCredentials).send({ eventId: eventId3 }),
|
||||
]),
|
||||
);
|
||||
|
||||
it('should list only the events with the same date', async () => {
|
||||
await request
|
||||
.get(api('calendar-events.list'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
date: new Date().toISOString(),
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('data').that.is.an('array');
|
||||
|
||||
const events = res.body.data.map((event: any) => event._id);
|
||||
expect(events).to.be.an('array').that.includes(eventId);
|
||||
expect(events).to.not.includes(eventId2);
|
||||
});
|
||||
});
|
||||
|
||||
it('should nost list events from other users', async () => {
|
||||
await request
|
||||
.get(api('calendar-events.list'))
|
||||
.set(userCredentials)
|
||||
.query({
|
||||
date: new Date().toISOString(),
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('data').that.is.an('array');
|
||||
|
||||
const events = res.body.data.map((event: any) => event._id);
|
||||
expect(events).to.be.an('array').that.includes(eventId3);
|
||||
expect(events).to.not.includes(eventId);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/calendar-events.info]', () => {
|
||||
const testSubject = `calendar-events.info-${Date.now()}`;
|
||||
let eventId: string | undefined;
|
||||
let eventId2: string | undefined;
|
||||
|
||||
before('create sample events', async () => {
|
||||
await request
|
||||
.post(api('calendar-events.create'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
startTime: new Date().toISOString(),
|
||||
subject: testSubject,
|
||||
description: 'Description',
|
||||
reminderMinutesBeforeStart: 10,
|
||||
})
|
||||
.then((res) => {
|
||||
eventId = res.body.id;
|
||||
});
|
||||
|
||||
await request
|
||||
.post(api('calendar-events.create'))
|
||||
.set(userCredentials)
|
||||
.send({
|
||||
startTime: new Date().toISOString(),
|
||||
subject: testSubject,
|
||||
description: 'Description',
|
||||
reminderMinutesBeforeStart: 10,
|
||||
})
|
||||
.then((res) => {
|
||||
eventId2 = res.body.id;
|
||||
});
|
||||
});
|
||||
|
||||
after(() =>
|
||||
Promise.all([
|
||||
request.post(api('calendar-events.delete')).set(credentials).send({ eventId }),
|
||||
request.post(api('calendar-events.delete')).set(userCredentials).send({ eventId: eventId2 }),
|
||||
]),
|
||||
);
|
||||
|
||||
it('should return the event information', async () => {
|
||||
await request
|
||||
.get(api('calendar-events.info'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
id: eventId,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('event').that.is.an('object').with.property('subject', testSubject);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return the event information - regular user', async () => {
|
||||
await request
|
||||
.get(api('calendar-events.info'))
|
||||
.set(userCredentials)
|
||||
.query({
|
||||
id: eventId2,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('event').that.is.an('object').with.property('subject', testSubject);
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail when querying an invalid event', async () => {
|
||||
await request
|
||||
.get(api('calendar-events.info'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
id: 'something-random',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
it('should fail when querying an event from another user', async () => {
|
||||
await request
|
||||
.get(api('calendar-events.info'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
id: eventId2,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/calendar-events.import]', () => {
|
||||
const eventsToRemove: Record<string, any>[] = [];
|
||||
|
||||
after(() =>
|
||||
Promise.all([
|
||||
...eventsToRemove.map(({ credentials, eventId }) => request.post(api('calendar-events.delete')).set(credentials).send({ eventId })),
|
||||
]),
|
||||
);
|
||||
|
||||
it('should successfully import an event to the calendar', async () => {
|
||||
let eventId: string | undefined;
|
||||
const externalId = `calendar-events.import-${Date.now()}`;
|
||||
|
||||
await request
|
||||
.post(api('calendar-events.import'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
startTime: new Date().toISOString(),
|
||||
subject: 'Subject',
|
||||
description: 'Description',
|
||||
reminderMinutesBeforeStart: 10,
|
||||
externalId,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
eventId = res.body.id;
|
||||
eventsToRemove.push({ credentials, eventId });
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail to import an event without an external id', async () => {
|
||||
await request
|
||||
.post(api('calendar-events.import'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
startTime: new Date().toISOString(),
|
||||
subject: 'Subject',
|
||||
description: 'Description',
|
||||
reminderMinutesBeforeStart: 10,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
it('should fail to import an event without a start time', async () => {
|
||||
await request
|
||||
.post(api('calendar-events.import'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
subject: 'Subject',
|
||||
description: 'Description',
|
||||
reminderMinutesBeforeStart: 10,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
it('should fail to import an event without a subject', async () => {
|
||||
await request
|
||||
.post(api('calendar-events.import'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
description: 'Description',
|
||||
startTime: new Date().toISOString(),
|
||||
reminderMinutesBeforeStart: 10,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
it('should fail to import an event without a description', async () => {
|
||||
await request
|
||||
.post(api('calendar-events.import'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
subject: 'Subject',
|
||||
startTime: new Date().toISOString(),
|
||||
reminderMinutesBeforeStart: 10,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
it('should successfully import an event without reminder information', async () => {
|
||||
let eventId: string | undefined;
|
||||
|
||||
await request
|
||||
.post(api('calendar-events.import'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
startTime: new Date().toISOString(),
|
||||
subject: 'Subject',
|
||||
description: 'Description',
|
||||
externalId: `calendar-events.import-external-id-${Date.now()}`,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
eventId = res.body.id;
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await request.post(api('calendar-events.delete')).set(credentials).send({ eventId });
|
||||
});
|
||||
});
|
||||
|
||||
it('should import a new event even if it was already imported by another user', async () => {
|
||||
let eventId: string | undefined;
|
||||
let eventId2: string | undefined;
|
||||
const externalId = `calendar-events.import-${Date.now()}`;
|
||||
|
||||
await request
|
||||
.post(api('calendar-events.import'))
|
||||
.set(userCredentials)
|
||||
.send({
|
||||
startTime: new Date().toISOString(),
|
||||
subject: 'First User',
|
||||
description: 'Description',
|
||||
reminderMinutesBeforeStart: 10,
|
||||
externalId,
|
||||
})
|
||||
.then((res) => {
|
||||
eventId = res.body.id;
|
||||
eventsToRemove.push({ credentials: userCredentials, eventId });
|
||||
});
|
||||
|
||||
await request
|
||||
.post(api('calendar-events.import'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
startTime: new Date().toISOString(),
|
||||
subject: 'Second User',
|
||||
description: 'Description',
|
||||
reminderMinutesBeforeStart: 10,
|
||||
externalId,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body.id).to.not.be.equal(eventId);
|
||||
eventId2 = res.body.id;
|
||||
eventsToRemove.push({ credentials, eventId: eventId2 });
|
||||
});
|
||||
|
||||
await request
|
||||
.get(api('calendar-events.info'))
|
||||
.set(userCredentials)
|
||||
.query({ id: eventId })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('event').that.is.an('object').with.property('subject', 'First User');
|
||||
});
|
||||
|
||||
await request
|
||||
.get(api('calendar-events.info'))
|
||||
.set(credentials)
|
||||
.query({ id: eventId2 })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('event').that.is.an('object').with.property('subject', 'Second User');
|
||||
});
|
||||
});
|
||||
|
||||
it('should update an event that has the same external id', async () => {
|
||||
let eventId: string | undefined;
|
||||
const externalId = `calendar-events.import-twice-${Date.now()}`;
|
||||
|
||||
await request
|
||||
.post(api('calendar-events.import'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
startTime: new Date().toISOString(),
|
||||
subject: 'Subject',
|
||||
description: 'Description',
|
||||
reminderMinutesBeforeStart: 10,
|
||||
externalId,
|
||||
})
|
||||
.then((res) => {
|
||||
eventId = res.body.id;
|
||||
eventsToRemove.push({ credentials, eventId });
|
||||
});
|
||||
|
||||
await request
|
||||
.post(api('calendar-events.import'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
startTime: new Date().toISOString(),
|
||||
subject: 'New Subject',
|
||||
description: 'New Description',
|
||||
reminderMinutesBeforeStart: 15,
|
||||
externalId,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect(async (res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('id', eventId);
|
||||
});
|
||||
|
||||
await request
|
||||
.get(api('calendar-events.info'))
|
||||
.set(credentials)
|
||||
.query({ id: eventId })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('event').that.is.an('object').with.property('subject', 'New Subject');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/calendar-events.update]', () => {
|
||||
const testSubject = `calendar-events.update-${Date.now()}`;
|
||||
let eventId: string | undefined;
|
||||
|
||||
before('create sample events', async () => {
|
||||
await request
|
||||
.post(api('calendar-events.create'))
|
||||
.set(userCredentials)
|
||||
.send({
|
||||
startTime: new Date().toISOString(),
|
||||
subject: 'Old Subject',
|
||||
description: 'Old Description',
|
||||
reminderMinutesBeforeStart: 10,
|
||||
})
|
||||
.then((res) => {
|
||||
eventId = res.body.id;
|
||||
});
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await request.post(api('calendar-events.delete')).set(userCredentials).send({ eventId });
|
||||
});
|
||||
|
||||
it('should update the event with the new data', async () => {
|
||||
await request
|
||||
.post(api('calendar-events.update'))
|
||||
.set(userCredentials)
|
||||
.send({
|
||||
eventId,
|
||||
startTime: new Date().toISOString(),
|
||||
subject: testSubject,
|
||||
description: 'New Description',
|
||||
reminderMinutesBeforeStart: 15,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect(async (res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
});
|
||||
|
||||
await request
|
||||
.get(api('calendar-events.info'))
|
||||
.set(userCredentials)
|
||||
.query({ id: eventId })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('event').that.is.an('object');
|
||||
expect(res.body.event).to.have.property('subject', testSubject);
|
||||
expect(res.body.event).to.have.property('description', 'New Description');
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail to update an event that doesnt exist', async () => {
|
||||
await request
|
||||
.post(api('calendar-events.update'))
|
||||
.set(userCredentials)
|
||||
.send({
|
||||
eventId: 'something-random',
|
||||
startTime: new Date().toISOString(),
|
||||
subject: testSubject,
|
||||
description: 'New Description',
|
||||
reminderMinutesBeforeStart: 15,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
it('should fail to update an event from another user', async () => {
|
||||
await request
|
||||
.post(api('calendar-events.update'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
eventId,
|
||||
startTime: new Date().toISOString(),
|
||||
subject: 'Another Subject',
|
||||
description: 'Third Description',
|
||||
reminderMinutesBeforeStart: 20,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/calendar-events.delete]', () => {
|
||||
let eventId: string | undefined;
|
||||
|
||||
before('create sample events', async () => {
|
||||
await request
|
||||
.post(api('calendar-events.create'))
|
||||
.set(userCredentials)
|
||||
.send({
|
||||
startTime: new Date().toISOString(),
|
||||
subject: 'Subject',
|
||||
description: 'Description',
|
||||
reminderMinutesBeforeStart: 10,
|
||||
})
|
||||
.then((res) => {
|
||||
eventId = res.body.id;
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail to delete an event from another user', async () => {
|
||||
await request
|
||||
.post(api('calendar-events.delete'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
eventId,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
it('should delete the specified event', async () => {
|
||||
await request
|
||||
.post(api('calendar-events.delete'))
|
||||
.set(userCredentials)
|
||||
.send({
|
||||
eventId,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect(async (res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
});
|
||||
|
||||
await request
|
||||
.get(api('calendar-events.info'))
|
||||
.set(userCredentials)
|
||||
.query({ id: eventId })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
it('should fail to delete an event that doesnt exist', async () => {
|
||||
await request
|
||||
.post(api('calendar-events.delete'))
|
||||
.set(userCredentials)
|
||||
.send({
|
||||
eventId: 'something-random',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
});
|
||||
});
|
||||
|
||||
(IS_EE ? describe : describe.skip)('[Calendar Events Status Sync]', () => {
|
||||
before(async () => {
|
||||
await request.post('/api/v1/users.setStatus').set(userCredentials).send({ status: 'online' }).expect(200);
|
||||
});
|
||||
|
||||
// claim apply/clear land asynchronously, so poll instead of asserting on a fixed sleep
|
||||
const waitForStatusSource = (
|
||||
predicate: (source: string | undefined) => boolean,
|
||||
{ timeout = 15000, interval = 500 } = {},
|
||||
): Promise<string | undefined> =>
|
||||
withTimeout(async (signal) => {
|
||||
for (;;) {
|
||||
const res = await request.get('/api/v1/users.getStatus').set(userCredentials).expect(200);
|
||||
const source = res.body.statusSource;
|
||||
if (predicate(source) || signal.aborted) {
|
||||
return source;
|
||||
}
|
||||
await sleep(interval);
|
||||
}
|
||||
}, timeout);
|
||||
|
||||
it('should apply a calendar (external) claim for an in-progress event and clear it when removed', async () => {
|
||||
const now = new Date();
|
||||
const startTime = new Date(now.getTime() - 1000);
|
||||
const endTime = new Date(now.getTime() + 60 * 60 * 1000);
|
||||
|
||||
const eventPayload = {
|
||||
startTime: startTime.toISOString(),
|
||||
endTime: endTime.toISOString(),
|
||||
subject: 'Subject',
|
||||
description: 'Description',
|
||||
reminderMinutesBeforeStart: 1,
|
||||
busy: true,
|
||||
};
|
||||
|
||||
const createResponse = await request.post('/api/v1/calendar-events.create').set(userCredentials).send(eventPayload).expect(200);
|
||||
const eventId = createResponse.body.id;
|
||||
|
||||
// REST-only user has no DDP session, so assert the claim via statusSource, not display status
|
||||
expect(await waitForStatusSource((source) => source === 'external')).to.equal('external');
|
||||
|
||||
// the claim must carry the event's endTime as its expiry, so the presence engine can auto-clear it
|
||||
const status = await request.get('/api/v1/users.getStatus').set(userCredentials).expect(200);
|
||||
expect(new Date(status.body.statusExpiresAt).getTime()).to.equal(endTime.getTime());
|
||||
|
||||
await request.post('/api/v1/calendar-events.delete').set(userCredentials).send({ eventId }).expect(200);
|
||||
|
||||
expect(await waitForStatusSource((source) => source === undefined)).to.be.undefined;
|
||||
});
|
||||
});
|
||||
});
|
||||
560
apps/meteor/tests/end-to-end/api/call-history.ts
Normal file
560
apps/meteor/tests/end-to-end/api/call-history.ts
Normal file
@@ -0,0 +1,560 @@
|
||||
import type { Credentials } from '@rocket.chat/api-client';
|
||||
import type { IUser } from '@rocket.chat/core-typings';
|
||||
import { expect } from 'chai';
|
||||
import { after, before, describe, it } from 'mocha';
|
||||
import type { Response } from 'supertest';
|
||||
|
||||
import { getCredentials, api, request, credentials } from '../../data/api-data';
|
||||
import { password } from '../../data/user';
|
||||
import { createUser, deleteUser, login } from '../../data/users.helper';
|
||||
|
||||
describe('[Call History]', () => {
|
||||
let user2: IUser;
|
||||
let userCredentials: Credentials;
|
||||
|
||||
before((done) => getCredentials(done));
|
||||
|
||||
before(async () => {
|
||||
user2 = await createUser();
|
||||
userCredentials = await login(user2.username, password);
|
||||
});
|
||||
|
||||
after(() => deleteUser(user2));
|
||||
|
||||
describe('[/call-history.list]', () => {
|
||||
it('should list all history entries for that uid', async () => {
|
||||
await request
|
||||
.get(api('call-history.list'))
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('items').that.is.an('array');
|
||||
|
||||
expect(res.body.items).to.have.lengthOf(6);
|
||||
expect(res.body).to.have.property('total', 6);
|
||||
expect(res.body).to.have.property('count', 6);
|
||||
|
||||
const historyIds = res.body.items.map((item: any) => item._id);
|
||||
expect(historyIds).to.include('rocketchat.internal.history.test.outbound');
|
||||
expect(historyIds).to.include('rocketchat.internal.history.test.inbound');
|
||||
expect(historyIds).to.include('rocketchat.external.history.test.outbound');
|
||||
expect(historyIds).to.include('rocketchat.external.history.test.inbound');
|
||||
expect(historyIds).to.include('rocketchat.internal.history.test.outbound.2');
|
||||
expect(historyIds).to.include('rocketchat.internal.history.test.inbound.2');
|
||||
|
||||
const internalItem1 = res.body.items.find((item: any) => item._id === 'rocketchat.internal.history.test.outbound');
|
||||
expect(internalItem1).to.have.property('callId', 'rocketchat.internal.call.test');
|
||||
expect(internalItem1).to.have.property('state', 'ended');
|
||||
expect(internalItem1).to.have.property('type', 'media-call');
|
||||
expect(internalItem1).to.have.property('duration', 10);
|
||||
expect(internalItem1).to.have.property('external', false);
|
||||
expect(internalItem1).to.have.property('direction', 'outbound');
|
||||
expect(internalItem1).to.have.property('contactId');
|
||||
expect(internalItem1).to.have.property('contactName', 'Pineapple');
|
||||
expect(internalItem1).to.have.property('contactUsername', 'fruit-001');
|
||||
|
||||
const internalItem2 = res.body.items.find((item: any) => item._id === 'rocketchat.internal.history.test.inbound');
|
||||
expect(internalItem2).to.have.property('callId', 'rocketchat.internal.call.test.2');
|
||||
expect(internalItem2).to.have.property('state', 'not-answered');
|
||||
expect(internalItem2).to.have.property('type', 'media-call');
|
||||
expect(internalItem2).to.have.property('duration', 10);
|
||||
expect(internalItem2).to.have.property('external', false);
|
||||
expect(internalItem2).to.have.property('direction', 'inbound');
|
||||
expect(internalItem2).to.have.property('contactId');
|
||||
expect(internalItem2).to.have.property('contactName', 'Apple');
|
||||
expect(internalItem2).to.have.property('contactUsername', 'fruit-002');
|
||||
|
||||
const internalItem3 = res.body.items.find((item: any) => item._id === 'rocketchat.internal.history.test.outbound.2');
|
||||
expect(internalItem3).to.have.property('callId', 'rocketchat.extra.call.test.1');
|
||||
expect(internalItem3).to.have.property('state', 'transferred');
|
||||
expect(internalItem3).to.have.property('direction', 'outbound');
|
||||
expect(internalItem3).to.have.property('contactName', 'Grapefruit 002');
|
||||
|
||||
const internalItem4 = res.body.items.find((item: any) => item._id === 'rocketchat.internal.history.test.inbound.2');
|
||||
expect(internalItem4).to.have.property('callId', 'rocketchat.extra.call.test.2');
|
||||
expect(internalItem4).to.have.property('state', 'transferred');
|
||||
expect(internalItem4).to.have.property('direction', 'inbound');
|
||||
expect(internalItem4).to.have.property('contactName', 'Pasta 1');
|
||||
|
||||
const externalItem1 = res.body.items.find((item: any) => item._id === 'rocketchat.external.history.test.outbound');
|
||||
expect(externalItem1).to.have.property('callId', 'rocketchat.external.call.test.outbound');
|
||||
expect(externalItem1).to.have.property('state', 'failed');
|
||||
expect(externalItem1).to.have.property('type', 'media-call');
|
||||
expect(externalItem1).to.have.property('duration', 10);
|
||||
expect(externalItem1).to.have.property('external', true);
|
||||
expect(externalItem1).to.have.property('direction', 'outbound');
|
||||
expect(externalItem1).to.have.property('contactExtension', '1001');
|
||||
|
||||
const externalItem2 = res.body.items.find((item: any) => item._id === 'rocketchat.external.history.test.inbound');
|
||||
expect(externalItem2).to.have.property('callId', 'rocketchat.external.call.test.inbound');
|
||||
expect(externalItem2).to.have.property('state', 'ended');
|
||||
expect(externalItem2).to.have.property('type', 'media-call');
|
||||
expect(externalItem2).to.have.property('duration', 10);
|
||||
expect(externalItem2).to.have.property('external', true);
|
||||
expect(externalItem2).to.have.property('direction', 'inbound');
|
||||
expect(externalItem2).to.have.property('contactExtension', '1002');
|
||||
});
|
||||
});
|
||||
|
||||
it('should not list history entries from other users', async () => {
|
||||
await request
|
||||
.get(api('call-history.list'))
|
||||
.set(userCredentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('items').that.is.an('array');
|
||||
|
||||
expect(res.body.items).to.have.lengthOf(0);
|
||||
expect(res.body).to.have.property('total', 0);
|
||||
expect(res.body).to.have.property('count', 0);
|
||||
});
|
||||
});
|
||||
|
||||
it('should apply filter by state', async () => {
|
||||
await request
|
||||
.get(api('call-history.list'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
state: ['ended'],
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('items').that.is.an('array');
|
||||
|
||||
expect(res.body.items).to.have.lengthOf(2);
|
||||
expect(res.body).to.have.property('total', 2);
|
||||
expect(res.body).to.have.property('count', 2);
|
||||
|
||||
const historyIds = res.body.items.map((item: any) => item._id);
|
||||
expect(historyIds).to.include('rocketchat.internal.history.test.outbound');
|
||||
expect(historyIds).to.include('rocketchat.external.history.test.inbound');
|
||||
});
|
||||
});
|
||||
|
||||
it('should apply filter by multiple states', async () => {
|
||||
await request
|
||||
.get(api('call-history.list'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
state: ['failed', 'ended'],
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('items').that.is.an('array');
|
||||
|
||||
expect(res.body.items).to.have.lengthOf(3);
|
||||
expect(res.body).to.have.property('total', 3);
|
||||
expect(res.body).to.have.property('count', 3);
|
||||
|
||||
const historyIds = res.body.items.map((item: any) => item._id);
|
||||
expect(historyIds).to.include('rocketchat.internal.history.test.outbound');
|
||||
expect(historyIds).to.include('rocketchat.external.history.test.outbound');
|
||||
expect(historyIds).to.include('rocketchat.external.history.test.inbound');
|
||||
});
|
||||
});
|
||||
|
||||
it('should apply filter by direction', async () => {
|
||||
await request
|
||||
.get(api('call-history.list'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
direction: 'inbound',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('items').that.is.an('array');
|
||||
|
||||
expect(res.body.items).to.have.lengthOf(3);
|
||||
expect(res.body).to.have.property('total', 3);
|
||||
expect(res.body).to.have.property('count', 3);
|
||||
|
||||
const historyIds = res.body.items.map((item: any) => item._id);
|
||||
expect(historyIds).to.include('rocketchat.internal.history.test.inbound');
|
||||
expect(historyIds).to.include('rocketchat.external.history.test.inbound');
|
||||
expect(historyIds).to.include('rocketchat.internal.history.test.inbound.2');
|
||||
});
|
||||
});
|
||||
|
||||
it('should apply filter by state and direction', async () => {
|
||||
await request
|
||||
.get(api('call-history.list'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
state: ['failed', 'ended'],
|
||||
direction: 'inbound',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('items').that.is.an('array');
|
||||
|
||||
expect(res.body.items).to.have.lengthOf(1);
|
||||
expect(res.body).to.have.property('total', 1);
|
||||
expect(res.body).to.have.property('count', 1);
|
||||
|
||||
const historyIds = res.body.items.map((item: any) => item._id);
|
||||
expect(historyIds).to.include('rocketchat.external.history.test.inbound');
|
||||
});
|
||||
});
|
||||
|
||||
it('should return item that match full contact name', async () => {
|
||||
await request
|
||||
.get(api('call-history.list'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
filter: 'Pineapple',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('items').that.is.an('array');
|
||||
|
||||
expect(res.body.items).to.have.lengthOf(1);
|
||||
expect(res.body).to.have.property('total', 1);
|
||||
expect(res.body).to.have.property('count', 1);
|
||||
|
||||
const historyIds = res.body.items.map((item: any) => item._id);
|
||||
expect(historyIds).to.include('rocketchat.internal.history.test.outbound');
|
||||
});
|
||||
});
|
||||
|
||||
it('should return items that match partial contact name', async () => {
|
||||
await request
|
||||
.get(api('call-history.list'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
filter: 'Apple',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('items').that.is.an('array');
|
||||
|
||||
expect(res.body.items).to.have.lengthOf(2);
|
||||
expect(res.body).to.have.property('total', 2);
|
||||
expect(res.body).to.have.property('count', 2);
|
||||
|
||||
const historyIds = res.body.items.map((item: any) => item._id);
|
||||
expect(historyIds).to.include('rocketchat.internal.history.test.outbound');
|
||||
expect(historyIds).to.include('rocketchat.internal.history.test.inbound');
|
||||
});
|
||||
});
|
||||
|
||||
it('should return item that match full contact username', async () => {
|
||||
await request
|
||||
.get(api('call-history.list'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
filter: 'fruit-001',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('items').that.is.an('array');
|
||||
|
||||
expect(res.body.items).to.have.lengthOf(1);
|
||||
expect(res.body).to.have.property('total', 1);
|
||||
expect(res.body).to.have.property('count', 1);
|
||||
|
||||
const historyIds = res.body.items.map((item: any) => item._id);
|
||||
expect(historyIds).to.include('rocketchat.internal.history.test.outbound');
|
||||
});
|
||||
});
|
||||
|
||||
it('should return items that match partial contact username', async () => {
|
||||
await request
|
||||
.get(api('call-history.list'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
filter: 'fruit-',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('items').that.is.an('array');
|
||||
|
||||
expect(res.body.items).to.have.lengthOf(2);
|
||||
expect(res.body).to.have.property('total', 2);
|
||||
expect(res.body).to.have.property('count', 2);
|
||||
|
||||
const historyIds = res.body.items.map((item: any) => item._id);
|
||||
expect(historyIds).to.include('rocketchat.internal.history.test.outbound');
|
||||
expect(historyIds).to.include('rocketchat.internal.history.test.inbound');
|
||||
});
|
||||
});
|
||||
|
||||
it('should return items that match partial contact name or username', async () => {
|
||||
await request
|
||||
.get(api('call-history.list'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
filter: 'fruit',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('items').that.is.an('array');
|
||||
|
||||
expect(res.body.items).to.have.lengthOf(3);
|
||||
expect(res.body).to.have.property('total', 3);
|
||||
expect(res.body).to.have.property('count', 3);
|
||||
|
||||
const historyIds = res.body.items.map((item: any) => item._id);
|
||||
expect(historyIds).to.include('rocketchat.internal.history.test.outbound');
|
||||
expect(historyIds).to.include('rocketchat.internal.history.test.inbound');
|
||||
expect(historyIds).to.include('rocketchat.internal.history.test.outbound.2');
|
||||
});
|
||||
});
|
||||
|
||||
it('should return item that match full contact extension', async () => {
|
||||
await request
|
||||
.get(api('call-history.list'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
filter: '1001',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('items').that.is.an('array');
|
||||
|
||||
expect(res.body.items).to.have.lengthOf(1);
|
||||
expect(res.body).to.have.property('total', 1);
|
||||
expect(res.body).to.have.property('count', 1);
|
||||
|
||||
const historyIds = res.body.items.map((item: any) => item._id);
|
||||
expect(historyIds).to.include('rocketchat.external.history.test.outbound');
|
||||
});
|
||||
});
|
||||
|
||||
it('should return items that match partial contact extension', async () => {
|
||||
await request
|
||||
.get(api('call-history.list'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
filter: '100',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('items').that.is.an('array');
|
||||
|
||||
expect(res.body.items).to.have.lengthOf(2);
|
||||
expect(res.body).to.have.property('total', 2);
|
||||
expect(res.body).to.have.property('count', 2);
|
||||
|
||||
const historyIds = res.body.items.map((item: any) => item._id);
|
||||
expect(historyIds).to.include('rocketchat.external.history.test.outbound');
|
||||
expect(historyIds).to.include('rocketchat.external.history.test.inbound');
|
||||
});
|
||||
});
|
||||
|
||||
it('should return items that match partial contact name, username or extension', async () => {
|
||||
await request
|
||||
.get(api('call-history.list'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
filter: '002',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('items').that.is.an('array');
|
||||
|
||||
expect(res.body.items).to.have.lengthOf(3);
|
||||
expect(res.body).to.have.property('total', 3);
|
||||
expect(res.body).to.have.property('count', 3);
|
||||
|
||||
const historyIds = res.body.items.map((item: any) => item._id);
|
||||
expect(historyIds).to.include('rocketchat.internal.history.test.inbound');
|
||||
expect(historyIds).to.include('rocketchat.internal.history.test.outbound.2');
|
||||
expect(historyIds).to.include('rocketchat.external.history.test.inbound');
|
||||
});
|
||||
});
|
||||
|
||||
it('should apply filter with falsy value', async () => {
|
||||
await request
|
||||
.get(api('call-history.list'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
filter: '0',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('items').that.is.an('array');
|
||||
|
||||
expect(res.body.items).to.have.lengthOf(5);
|
||||
expect(res.body).to.have.property('total', 5);
|
||||
expect(res.body).to.have.property('count', 5);
|
||||
|
||||
const historyIds = res.body.items.map((item: any) => item._id);
|
||||
expect(historyIds).to.include('rocketchat.internal.history.test.outbound');
|
||||
expect(historyIds).to.include('rocketchat.internal.history.test.inbound');
|
||||
expect(historyIds).to.include('rocketchat.internal.history.test.outbound.2');
|
||||
expect(historyIds).to.include('rocketchat.external.history.test.outbound');
|
||||
expect(historyIds).to.include('rocketchat.external.history.test.inbound');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/call-history.info]', () => {
|
||||
it('should return the history entry information', async () => {
|
||||
await request
|
||||
.get(api('call-history.info'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
historyId: 'rocketchat.internal.history.test.outbound',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('item').that.is.an('object');
|
||||
expect(res.body).to.have.property('call').that.is.an('object');
|
||||
|
||||
const { item, call } = res.body;
|
||||
expect(item).to.have.property('_id', 'rocketchat.internal.history.test.outbound');
|
||||
expect(item).to.have.property('callId', 'rocketchat.internal.call.test');
|
||||
expect(item).to.have.property('state', 'ended');
|
||||
expect(item).to.have.property('type', 'media-call');
|
||||
expect(item).to.have.property('duration', 10);
|
||||
expect(item).to.have.property('external', false);
|
||||
expect(item).to.have.property('direction', 'outbound');
|
||||
expect(item).to.have.property('contactId');
|
||||
expect(item).to.have.property('contactName', 'Pineapple');
|
||||
expect(item).to.have.property('contactUsername', 'fruit-001');
|
||||
expect(item).to.have.property('ts');
|
||||
expect(item).to.have.property('endedAt');
|
||||
|
||||
expect(call).to.have.property('_id', 'rocketchat.internal.call.test');
|
||||
expect(call).to.have.property('service', 'webrtc');
|
||||
expect(call).to.have.property('kind', 'direct');
|
||||
expect(call).to.have.property('state', 'hangup');
|
||||
expect(call).to.have.property('ended', true);
|
||||
expect(call).to.have.property('hangupReason', 'normal');
|
||||
expect(call).to.have.property('createdBy').that.is.an('object');
|
||||
expect(call.createdBy).to.have.property('type', 'user');
|
||||
expect(call).to.have.property('caller').that.is.an('object');
|
||||
expect(call.caller).to.have.property('type', 'user');
|
||||
expect(call).to.have.property('callee').that.is.an('object');
|
||||
expect(call.callee).to.have.property('type', 'user');
|
||||
expect(call).to.have.property('endedBy').that.is.an('object');
|
||||
expect(call.endedBy).to.have.property('type', 'user');
|
||||
expect(call).to.have.property('uids').that.is.an('array');
|
||||
expect(call.uids).to.have.lengthOf(2);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return the history entry information when searching by call id', async () => {
|
||||
await request
|
||||
.get(api('call-history.info'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
callId: 'rocketchat.internal.call.test.2',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('item').that.is.an('object');
|
||||
expect(res.body).to.have.property('call').that.is.an('object');
|
||||
|
||||
const { item, call } = res.body;
|
||||
expect(item).to.have.property('_id', 'rocketchat.internal.history.test.inbound');
|
||||
expect(item).to.have.property('callId', 'rocketchat.internal.call.test.2');
|
||||
expect(item).to.have.property('state', 'not-answered');
|
||||
expect(item).to.have.property('type', 'media-call');
|
||||
expect(item).to.have.property('duration', 10);
|
||||
expect(item).to.have.property('external', false);
|
||||
expect(item).to.have.property('direction', 'inbound');
|
||||
expect(item).to.have.property('contactId');
|
||||
expect(item).to.have.property('contactName', 'Apple');
|
||||
expect(item).to.have.property('contactUsername', 'fruit-002');
|
||||
expect(item).to.have.property('ts');
|
||||
expect(item).to.have.property('endedAt');
|
||||
|
||||
expect(call).to.have.property('_id', 'rocketchat.internal.call.test.2');
|
||||
expect(call).to.have.property('service', 'webrtc');
|
||||
expect(call).to.have.property('kind', 'direct');
|
||||
expect(call).to.have.property('state', 'hangup');
|
||||
expect(call).to.have.property('ended', true);
|
||||
expect(call).to.have.property('hangupReason', 'normal');
|
||||
expect(call).to.have.property('createdBy').that.is.an('object');
|
||||
expect(call.createdBy).to.have.property('type', 'user');
|
||||
expect(call).to.have.property('caller').that.is.an('object');
|
||||
expect(call.caller).to.have.property('type', 'user');
|
||||
expect(call).to.have.property('callee').that.is.an('object');
|
||||
expect(call.callee).to.have.property('type', 'user');
|
||||
expect(call).to.have.property('endedBy').that.is.an('object');
|
||||
expect(call.endedBy).to.have.property('type', 'user');
|
||||
expect(call).to.have.property('uids').that.is.an('array');
|
||||
expect(call.uids).to.have.lengthOf(2);
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail when querying an invalid entry', async () => {
|
||||
await request
|
||||
.get(api('call-history.info'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
historyId: 'something-random',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(404);
|
||||
});
|
||||
|
||||
it('should fail when querying an invalid entry by call id', async () => {
|
||||
await request
|
||||
.get(api('call-history.info'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
callId: 'something-random',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(404);
|
||||
});
|
||||
|
||||
it('should fail when querying an entry from another user', async () => {
|
||||
await request
|
||||
.get(api('call-history.info'))
|
||||
.set(userCredentials)
|
||||
.query({
|
||||
historyId: 'rocketchat.internal.history.test.outbound',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(404);
|
||||
});
|
||||
|
||||
it('should fail when querying an entry from another user by call id', async () => {
|
||||
await request
|
||||
.get(api('call-history.info'))
|
||||
.set(userCredentials)
|
||||
.query({
|
||||
callId: 'rocketchat.internal.call.test.2',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(404);
|
||||
});
|
||||
});
|
||||
});
|
||||
4776
apps/meteor/tests/end-to-end/api/channels.ts
Normal file
4776
apps/meteor/tests/end-to-end/api/channels.ts
Normal file
File diff suppressed because it is too large
Load Diff
5400
apps/meteor/tests/end-to-end/api/chat.ts
Normal file
5400
apps/meteor/tests/end-to-end/api/chat.ts
Normal file
File diff suppressed because it is too large
Load Diff
321
apps/meteor/tests/end-to-end/api/cloud.ts
Normal file
321
apps/meteor/tests/end-to-end/api/cloud.ts
Normal file
@@ -0,0 +1,321 @@
|
||||
import { expect } from 'chai';
|
||||
import { after, before, describe, it } from 'mocha';
|
||||
import type { Response } from 'supertest';
|
||||
|
||||
import { getCredentials, api, request, credentials } from '../../data/api-data';
|
||||
import { updatePermission } from '../../data/permissions.helper';
|
||||
|
||||
describe('[Cloud]', function () {
|
||||
this.retries(0);
|
||||
|
||||
before((done) => getCredentials(done));
|
||||
|
||||
describe('[/cloud.manualRegister]', () => {
|
||||
before(async () => {
|
||||
return updatePermission('register-on-cloud', ['admin']);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
return updatePermission('register-on-cloud', ['admin']);
|
||||
});
|
||||
|
||||
it('should fail if user is not authenticated', async () => {
|
||||
return request
|
||||
.post(api('cloud.manualRegister'))
|
||||
.expect('Content-Type', 'application/json')
|
||||
.send({
|
||||
cloudBlob: 'test-blob',
|
||||
})
|
||||
.expect(401)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('status', 'error');
|
||||
expect(res.body).to.have.property('message', 'You must be logged in to do this.');
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail when cloudBlob property is not provided', async () => {
|
||||
return request
|
||||
.post(api('cloud.manualRegister'))
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('errorType', 'invalid-params');
|
||||
expect(res.body).to.have.property('error', "must have required property 'cloudBlob'");
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail when user does not have the register-on-cloud permission', async () => {
|
||||
await updatePermission('register-on-cloud', []);
|
||||
return request
|
||||
.post(api('cloud.manualRegister'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
cloudBlob: 'test-blob',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(403)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error', 'User does not have the permissions required for this action [error-unauthorized]');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/cloud.createRegistrationIntent]', () => {
|
||||
before(async () => {
|
||||
return updatePermission('manage-cloud', ['admin']);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
return updatePermission('manage-cloud', ['admin']);
|
||||
});
|
||||
|
||||
it('should fail if user is not authenticated', async () => {
|
||||
return request
|
||||
.post(api('cloud.createRegistrationIntent'))
|
||||
.expect('Content-Type', 'application/json')
|
||||
.send({
|
||||
email: 'test-mail@example.com',
|
||||
resend: true,
|
||||
})
|
||||
.expect(401)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('status', 'error');
|
||||
expect(res.body).to.have.property('message', 'You must be logged in to do this.');
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail when resend property is not provided', async () => {
|
||||
return request
|
||||
.post(api('cloud.createRegistrationIntent'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
email: 'test-mail@example.com',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('errorType', 'invalid-params');
|
||||
expect(res.body).to.have.property('error', "must have required property 'resend'");
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail when email property is not provided', async () => {
|
||||
return request
|
||||
.post(api('cloud.createRegistrationIntent'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
resend: true,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('errorType', 'invalid-params');
|
||||
expect(res.body).to.have.property('error', "must have required property 'email'");
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail when user does not have the manage-cloud permission', async () => {
|
||||
await updatePermission('manage-cloud', []);
|
||||
return request
|
||||
.post(api('cloud.createRegistrationIntent'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
email: 'test-mail@example.com',
|
||||
resend: true,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(403)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error', 'User does not have the permissions required for this action [error-unauthorized]');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/cloud.confirmationPoll]', () => {
|
||||
before(async () => {
|
||||
return updatePermission('manage-cloud', ['admin']);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
return updatePermission('manage-cloud', ['admin']);
|
||||
});
|
||||
|
||||
it('should fail if user is not authenticated', async () => {
|
||||
return request
|
||||
.get(api('cloud.confirmationPoll'))
|
||||
.query({
|
||||
deviceCode: 'test-code',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(401)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('status', 'error');
|
||||
expect(res.body).to.have.property('message', 'You must be logged in to do this.');
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail when deviceCode property is not provided', async () => {
|
||||
return request
|
||||
.get(api('cloud.confirmationPoll'))
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('errorType', 'error-invalid-params');
|
||||
expect(res.body).to.have.property('error', "must have required property 'deviceCode'");
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail when user does not have the manage-cloud permission', async () => {
|
||||
await updatePermission('manage-cloud', []);
|
||||
return request
|
||||
.get(api('cloud.confirmationPoll'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
deviceCode: 'test-code',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(403)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error', 'User does not have the permissions required for this action [error-unauthorized]');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/cloud.registrationStatus]', () => {
|
||||
before(async () => {
|
||||
return updatePermission('manage-cloud', ['admin']);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
return updatePermission('manage-cloud', ['admin']);
|
||||
});
|
||||
|
||||
it('should fail if user is not authenticated', async () => {
|
||||
return request
|
||||
.get(api('cloud.registrationStatus'))
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(401)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('status', 'error');
|
||||
expect(res.body).to.have.property('message', 'You must be logged in to do this.');
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail when user does not have the manage-cloud permission', async () => {
|
||||
await updatePermission('manage-cloud', []);
|
||||
return request
|
||||
.get(api('cloud.registrationStatus'))
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(403)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return registration status when user has the manage-cloud permission', async () => {
|
||||
await updatePermission('manage-cloud', ['admin']);
|
||||
return request
|
||||
.get(api('cloud.registrationStatus'))
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('registrationStatus');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/cloud.workspaceRegisterData]', () => {
|
||||
before(async () => {
|
||||
return updatePermission('manage-cloud', ['admin']);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
return updatePermission('manage-cloud', ['admin']);
|
||||
});
|
||||
|
||||
it('should fail if user is not authenticated', async () => {
|
||||
return request
|
||||
.get(api('cloud.workspaceRegisterData'))
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(401)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('status', 'error');
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail when user does not have the manage-cloud permission', async () => {
|
||||
await updatePermission('manage-cloud', []);
|
||||
return request
|
||||
.get(api('cloud.workspaceRegisterData'))
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(403)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return a base64 encoded registration payload when user has the manage-cloud permission', async () => {
|
||||
await updatePermission('manage-cloud', ['admin']);
|
||||
return request
|
||||
.get(api('cloud.workspaceRegisterData'))
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('registerData').that.is.a('string');
|
||||
|
||||
const decoded = JSON.parse(Buffer.from(res.body.registerData, 'base64').toString());
|
||||
expect(decoded).to.have.property('uniqueId');
|
||||
expect(decoded).to.have.property('workspaceId');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/cloud.connectWorkspace]', () => {
|
||||
before(async () => {
|
||||
return updatePermission('manage-cloud', ['admin']);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
return updatePermission('manage-cloud', ['admin']);
|
||||
});
|
||||
|
||||
it('should fail if user is not authenticated', async () => {
|
||||
return request
|
||||
.post(api('cloud.connectWorkspace'))
|
||||
.send({ token: 'invalid-token' })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(401)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('status', 'error');
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail when user does not have the manage-cloud permission', async () => {
|
||||
await updatePermission('manage-cloud', []);
|
||||
return request
|
||||
.post(api('cloud.connectWorkspace'))
|
||||
.set(credentials)
|
||||
.send({ token: 'invalid-token' })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(403)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
581
apps/meteor/tests/end-to-end/api/commands.ts
Normal file
581
apps/meteor/tests/end-to-end/api/commands.ts
Normal file
@@ -0,0 +1,581 @@
|
||||
import type { Credentials } from '@rocket.chat/api-client';
|
||||
import type { IRoom, IThreadMessage, IUser } from '@rocket.chat/core-typings';
|
||||
import { Random } from '@rocket.chat/random';
|
||||
import { expect } from 'chai';
|
||||
import { before, describe, it, after } from 'mocha';
|
||||
|
||||
import { getCredentials, api, request, credentials } from '../../data/api-data';
|
||||
import { sendSimpleMessage } from '../../data/chat.helper';
|
||||
import { createRoom, deleteRoom } from '../../data/rooms.helper';
|
||||
import { password } from '../../data/user';
|
||||
import type { TestUser } from '../../data/users.helper';
|
||||
import { createUser, deleteUser, login } from '../../data/users.helper';
|
||||
|
||||
describe('[Commands]', () => {
|
||||
before((done) => getCredentials(done));
|
||||
|
||||
describe('[/commands.get]', () => {
|
||||
it('should return an error when call the endpoint without "command" required parameter', async () => {
|
||||
const res = await request.get(api('commands.get')).set(credentials).expect(400);
|
||||
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body.error).to.be.equal(`must have required property 'command'`);
|
||||
});
|
||||
it('should return an error when call the endpoint with an invalid command', async () => {
|
||||
const res = await request
|
||||
.get(api('commands.get'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
command: 'invalid-command',
|
||||
})
|
||||
.expect(400);
|
||||
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body.error).to.be.equal('There is no command in the system by the name of: invalid-command');
|
||||
});
|
||||
it('should return success when parameters are correct', async () => {
|
||||
const res = await request
|
||||
.get(api('commands.get'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
command: 'help',
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('command');
|
||||
expect(res.body.command).to.have.property('command');
|
||||
expect(res.body.command).to.have.property('description');
|
||||
expect(res.body.command).to.have.property('clientOnly');
|
||||
expect(res.body.command).to.have.property('providesPreview');
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/commands.list]', () => {
|
||||
it('should return a list of commands', async () => {
|
||||
const res = await request.get(api('commands.list')).set(credentials).expect(200);
|
||||
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('offset');
|
||||
expect(res.body).to.have.property('count');
|
||||
expect(res.body).to.have.property('total');
|
||||
expect(res.body).to.have.property('commands').and.to.be.an('array');
|
||||
});
|
||||
it('should return a list of commands even requested with count and offset params', async () => {
|
||||
const res = await request
|
||||
.get(api('commands.list'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
count: 5,
|
||||
offset: 0,
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('offset');
|
||||
expect(res.body).to.have.property('count');
|
||||
expect(res.body).to.have.property('total');
|
||||
expect(res.body).to.have.property('commands').and.to.be.an('array');
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/commands.run]', () => {
|
||||
let testChannel: IRoom;
|
||||
let threadMessage: IThreadMessage;
|
||||
|
||||
before(async () => {
|
||||
testChannel = (await createRoom({ type: 'c', name: `channel.test.commands.${Date.now()}` })).body.channel;
|
||||
const { body: { message } = {} } = await sendSimpleMessage({
|
||||
roomId: testChannel._id,
|
||||
text: 'Message to create thread',
|
||||
});
|
||||
|
||||
threadMessage = (
|
||||
await sendSimpleMessage({
|
||||
roomId: testChannel._id,
|
||||
text: 'Thread Message',
|
||||
tmid: message._id,
|
||||
})
|
||||
).body.message;
|
||||
});
|
||||
|
||||
after(() => deleteRoom({ type: 'c', roomId: testChannel._id }));
|
||||
|
||||
it('should return an error when call the endpoint without "command" required parameter', async () => {
|
||||
const res = await request.post(api('commands.run')).set(credentials).expect(400);
|
||||
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body.error).to.be.equal("must have required property 'command'");
|
||||
});
|
||||
|
||||
it('should coerce non-string "params" to string via ajv coercion', async () => {
|
||||
const res = await request
|
||||
.post(api('commands.run'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
command: 'help',
|
||||
roomId: 'GENERAL',
|
||||
params: true,
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
expect(res.body).to.have.property('success', true);
|
||||
});
|
||||
it('should return an error when call the endpoint without "roomId" required parameter', async () => {
|
||||
const res = await request
|
||||
.post(api('commands.run'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
command: 'help',
|
||||
params: 'params',
|
||||
})
|
||||
.expect(400);
|
||||
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body.error).to.be.equal("must have required property 'roomId'");
|
||||
});
|
||||
it('should coerce non-string "tmid" to string via ajv coercion and fail with invalid thread', async () => {
|
||||
const res = await request
|
||||
.post(api('commands.run'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
command: 'help',
|
||||
params: 'params',
|
||||
roomId: 'GENERAL',
|
||||
tmid: true,
|
||||
})
|
||||
.expect(400);
|
||||
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body.error).to.be.equal('Invalid thread.');
|
||||
});
|
||||
it('should return an error when call the endpoint with the invalid "command" param', async () => {
|
||||
const res = await request
|
||||
.post(api('commands.run'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
command: 'invalid-command',
|
||||
params: 'params',
|
||||
roomId: 'GENERAL',
|
||||
})
|
||||
.expect(400);
|
||||
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body.error).to.be.equal('The command provided does not exist (or is disabled).');
|
||||
});
|
||||
it('should return an error when call the endpoint with an invalid thread id', async () => {
|
||||
const res = await request
|
||||
.post(api('commands.run'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
command: 'tableflip',
|
||||
params: 'params',
|
||||
roomId: 'GENERAL',
|
||||
tmid: 'invalid-thread',
|
||||
})
|
||||
.expect(400);
|
||||
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body.error).to.be.equal('Invalid thread.');
|
||||
});
|
||||
it('should return an error when call the endpoint with a valid thread id of wrong channel', async () => {
|
||||
const res = await request
|
||||
.post(api('commands.run'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
command: 'tableflip',
|
||||
params: 'params',
|
||||
roomId: 'GENERAL',
|
||||
tmid: threadMessage.tmid,
|
||||
})
|
||||
.expect(400);
|
||||
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body.error).to.be.equal('Invalid thread.');
|
||||
});
|
||||
it('should return success when parameters are correct', async () => {
|
||||
const res = await request
|
||||
.post(api('commands.run'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
command: 'tableflip',
|
||||
params: 'params',
|
||||
roomId: threadMessage.rid,
|
||||
tmid: threadMessage.tmid,
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
expect(res.body).to.have.property('success', true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Command archive', function () {
|
||||
describe('unauthorized cases', () => {
|
||||
let user: TestUser<IUser>;
|
||||
let credentials: Credentials;
|
||||
|
||||
this.beforeAll(async () => {
|
||||
user = await createUser({
|
||||
joinDefaultChannels: true,
|
||||
});
|
||||
credentials = await login(user.username, password);
|
||||
});
|
||||
|
||||
this.afterAll(async () => {
|
||||
await deleteUser(user);
|
||||
});
|
||||
|
||||
it('should return an error when the user is not logged in', async () => {
|
||||
await request
|
||||
.post(api('commands.run'))
|
||||
.send({ command: 'archive', roomId: 'GENERAL' })
|
||||
.expect(401)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('status', 'error');
|
||||
});
|
||||
});
|
||||
|
||||
it('should return an error when the user has not enough permissions', async () => {
|
||||
await request
|
||||
.post(api('commands.run'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
command: 'archive',
|
||||
roomId: 'GENERAL',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('errorType', 'error-not-authorized');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('authorized cases', function () {
|
||||
this.afterAll(async () => {
|
||||
await request
|
||||
.post(api('commands.run'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
command: 'unarchive',
|
||||
roomId: 'GENERAL',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return a success when the user has enough permissions', async () => {
|
||||
await request
|
||||
.post(api('commands.run'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
command: 'archive',
|
||||
roomId: 'GENERAL',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Command unarchive', function () {
|
||||
describe('unauthorized cases', () => {
|
||||
let user: TestUser<IUser>;
|
||||
let credentials: Credentials;
|
||||
this.beforeAll(async () => {
|
||||
user = await createUser({
|
||||
joinDefaultChannels: true,
|
||||
});
|
||||
credentials = await login(user.username, password);
|
||||
});
|
||||
|
||||
this.afterAll(async () => {
|
||||
await deleteUser(user);
|
||||
});
|
||||
|
||||
it('should return an error when the user is not logged in', async () => {
|
||||
await request
|
||||
.post(api('commands.run'))
|
||||
.send({ command: 'unarchive', roomId: 'GENERAL' })
|
||||
.expect(401)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('status', 'error');
|
||||
});
|
||||
});
|
||||
|
||||
it('should return an error when the user has not enough permissions', async () => {
|
||||
await request
|
||||
.post(api('commands.run'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
command: 'unarchive',
|
||||
roomId: 'GENERAL',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('errorType', 'error-not-authorized');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('authorized cases', () => {
|
||||
before(async () => {
|
||||
await request
|
||||
.post(api('commands.run'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
command: 'archive',
|
||||
roomId: 'GENERAL',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
});
|
||||
});
|
||||
it('should return a success when the user has enough permissions', async () => {
|
||||
await request
|
||||
.post(api('commands.run'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
command: 'unarchive',
|
||||
roomId: 'GENERAL',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Command "invite-all-from"', function () {
|
||||
let group: IRoom;
|
||||
let group1: IRoom;
|
||||
let channel: IRoom;
|
||||
let user1: TestUser<IUser>;
|
||||
let user2: TestUser<IUser>;
|
||||
let user1Credentials: Credentials;
|
||||
let user2Credentials: Credentials;
|
||||
|
||||
this.beforeAll(async () => {
|
||||
user1 = await createUser();
|
||||
user2 = await createUser();
|
||||
|
||||
[user1Credentials, user2Credentials] = await Promise.all([login(user1.username, password), login(user2.username, password)]);
|
||||
});
|
||||
|
||||
this.beforeAll(async () => {
|
||||
const [response1, response2, response3] = await Promise.all([
|
||||
createRoom({ type: 'p', name: `room1-${Date.now()}.${Random.id()}`, credentials: user1Credentials }),
|
||||
createRoom({ type: 'c', name: `room2-${Date.now()}.${Random.id()}`, credentials: user2Credentials }),
|
||||
createRoom({ type: 'p', name: `room3-${Date.now()}.${Random.id()}` }),
|
||||
]);
|
||||
group = response1.body.group;
|
||||
channel = response2.body.channel;
|
||||
group1 = response3.body.group;
|
||||
});
|
||||
|
||||
this.afterAll(async () => {
|
||||
await Promise.all([
|
||||
deleteRoom({ type: 'p', roomId: group._id }),
|
||||
deleteRoom({ type: 'c', roomId: channel._id }),
|
||||
deleteRoom({ type: 'p', roomId: group1._id }),
|
||||
]);
|
||||
await Promise.all([deleteUser(user1), deleteUser(user2)]);
|
||||
});
|
||||
|
||||
it('should not add users from group which is not accessible by current user', async () => {
|
||||
await request
|
||||
.post(api('commands.run'))
|
||||
.set(user2Credentials)
|
||||
.send({
|
||||
roomId: channel._id,
|
||||
command: 'invite-all-from',
|
||||
params: `#${group.name}`,
|
||||
triggerId: Random.id(),
|
||||
})
|
||||
.expect(200)
|
||||
.expect(async (res) => {
|
||||
expect(res.body).to.have.a.property('success', true);
|
||||
});
|
||||
|
||||
await request
|
||||
.get(api('channels.members'))
|
||||
.query({ roomId: channel._id })
|
||||
.set(user2Credentials)
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
const isUser1Added = res.body.members.some((member: IUser) => member.username === user1.username);
|
||||
expect(isUser1Added).to.be.false;
|
||||
});
|
||||
});
|
||||
|
||||
it('should not add users to a room that is not accessible by the current user', async () => {
|
||||
await request
|
||||
.post(api('commands.run'))
|
||||
.set(user1Credentials)
|
||||
.send({
|
||||
roomId: group1._id,
|
||||
command: 'invite-all-from',
|
||||
params: `#${group.name}`,
|
||||
triggerId: Random.id(),
|
||||
})
|
||||
.expect(403)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.a.property('error', 'unauthorized');
|
||||
});
|
||||
});
|
||||
|
||||
it('should add users from group which is accessible by current user', async () => {
|
||||
await request
|
||||
.post(api('groups.invite'))
|
||||
.set(user1Credentials)
|
||||
.send({
|
||||
roomId: group._id,
|
||||
userId: user2._id,
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
await request
|
||||
.post(api('commands.run'))
|
||||
.set(user2Credentials)
|
||||
.send({
|
||||
roomId: channel._id,
|
||||
command: 'invite-all-from',
|
||||
params: `#${group.name}`,
|
||||
triggerId: Random.id(),
|
||||
})
|
||||
.expect(200)
|
||||
.expect(async (res) => {
|
||||
expect(res.body).to.have.a.property('success', true);
|
||||
});
|
||||
|
||||
await request
|
||||
.get(api('channels.members'))
|
||||
.set(user2Credentials)
|
||||
.query({ roomId: channel._id })
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
const isUser1Added = res.body.members.some((member: IUser) => member.username === user1.username);
|
||||
expect(isUser1Added).to.be.true;
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('Command "kick"', function () {
|
||||
let directMessageRoom: IRoom;
|
||||
let user1: TestUser<IUser>;
|
||||
let user1Credentials: Credentials;
|
||||
this.beforeAll(async () => {
|
||||
user1 = await createUser();
|
||||
|
||||
[user1Credentials] = await Promise.all([login(user1.username, password)]);
|
||||
});
|
||||
|
||||
this.beforeAll(async () => {
|
||||
const [response1] = await Promise.all([
|
||||
createRoom({ type: 'd', name: `room1-${Date.now()}.${Random.id()}`, username: user1.username }),
|
||||
]);
|
||||
directMessageRoom = response1.body.room;
|
||||
});
|
||||
|
||||
this.afterAll(async () => {
|
||||
await Promise.all([deleteRoom({ type: 'd', roomId: directMessageRoom._id })]);
|
||||
await Promise.all([deleteUser(user1)]);
|
||||
});
|
||||
|
||||
it('should fail when trying to kick a user from a direct message room', async () => {
|
||||
const res = await request
|
||||
.post(api('commands.run'))
|
||||
.set(user1Credentials)
|
||||
.send({ command: 'kick', roomId: directMessageRoom._id, params: user1.username })
|
||||
.expect(400);
|
||||
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error').that.is.a('string');
|
||||
});
|
||||
});
|
||||
describe('Command "leave"', function () {
|
||||
let directMessageRoom: IRoom;
|
||||
let user1: TestUser<IUser>;
|
||||
let user2: TestUser<IUser>;
|
||||
let user1Credentials: Credentials;
|
||||
this.beforeAll(async () => {
|
||||
user1 = await createUser();
|
||||
user2 = await createUser();
|
||||
|
||||
[user1Credentials] = await Promise.all([login(user1.username, password)]);
|
||||
});
|
||||
|
||||
this.beforeAll(async () => {
|
||||
const [response1] = await Promise.all([
|
||||
createRoom({ type: 'd', name: `room1-${Date.now()}.${Random.id()}`, username: user1.username }),
|
||||
]);
|
||||
directMessageRoom = response1.body.room;
|
||||
});
|
||||
|
||||
this.afterAll(async () => {
|
||||
await Promise.all([deleteRoom({ type: 'd', roomId: directMessageRoom._id })]);
|
||||
await Promise.all([deleteUser(user1), deleteUser(user2)]);
|
||||
});
|
||||
|
||||
it('should fail when trying to leave a direct message room', async () => {
|
||||
const res = await request
|
||||
.post(api('commands.run'))
|
||||
.set(user1Credentials)
|
||||
.send({ command: 'leave', roomId: directMessageRoom._id })
|
||||
.expect(400);
|
||||
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error').that.is.a('string');
|
||||
});
|
||||
});
|
||||
|
||||
// TODO: invite never fails
|
||||
describe.skip('Command "invite"', function () {
|
||||
let directMessageRoom: IRoom;
|
||||
let user1: TestUser<IUser>;
|
||||
let user2: TestUser<IUser>;
|
||||
let user1Credentials: Credentials;
|
||||
this.beforeAll(async () => {
|
||||
user1 = await createUser();
|
||||
user2 = await createUser();
|
||||
|
||||
[user1Credentials] = await Promise.all([login(user1.username, password)]);
|
||||
});
|
||||
|
||||
this.beforeAll(async () => {
|
||||
const [response1] = await Promise.all([
|
||||
createRoom({ type: 'd', name: `room1-${Date.now()}.${Random.id()}`, username: user1.username }),
|
||||
]);
|
||||
directMessageRoom = response1.body.room;
|
||||
});
|
||||
|
||||
this.afterAll(async () => {
|
||||
await Promise.all([deleteRoom({ type: 'd', roomId: directMessageRoom._id })]);
|
||||
await Promise.all([deleteUser(user1), deleteUser(user2)]);
|
||||
});
|
||||
|
||||
it('should fail when trying to invite a user to a direct message room', async () => {
|
||||
const res = await request
|
||||
.post(api('commands.run'))
|
||||
.set(user1Credentials)
|
||||
.send({ command: 'invite', roomId: directMessageRoom._id, params: 'g1' })
|
||||
.expect(403);
|
||||
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error').that.is.a('string');
|
||||
expect(res.body.error).to.equal('unauthorized');
|
||||
});
|
||||
});
|
||||
});
|
||||
72
apps/meteor/tests/end-to-end/api/cors.ts
Normal file
72
apps/meteor/tests/end-to-end/api/cors.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import { expect } from 'chai';
|
||||
import { after, before, describe, it } from 'mocha';
|
||||
|
||||
import { getCredentials, request } from '../../data/api-data';
|
||||
import { updateSetting } from '../../data/permissions.helper';
|
||||
|
||||
const getHash = () =>
|
||||
request
|
||||
.get('/')
|
||||
.expect(200)
|
||||
.expect('Content-Type', 'text/html; charset=utf-8')
|
||||
.then((res) => {
|
||||
const hashMatch = res.text.match(/meteor_runtime_config\.js\?hash=([^"']+)/);
|
||||
expect(hashMatch).to.not.be.null;
|
||||
const hash = hashMatch![1];
|
||||
return hash;
|
||||
});
|
||||
|
||||
describe('[CORS]', () => {
|
||||
before((done) => getCredentials(done));
|
||||
after(async () => {
|
||||
await updateSetting('Site_Url', 'http://localhost:3000');
|
||||
});
|
||||
|
||||
describe('[/meteor_runtime_config.js]', () => {
|
||||
it('should return 404 when hash is missing', async () => {
|
||||
await request.get('/meteor_runtime_config.js').expect(404);
|
||||
});
|
||||
|
||||
it('should return 404 when hash is invalid', async () => {
|
||||
await request.get('/meteor_runtime_config.js').query({ hash: 'invalid_hash' }).expect(404);
|
||||
});
|
||||
|
||||
it('should return runtime config when hash matches', async () => {
|
||||
// First get the root page to extract the hash
|
||||
const hash = await getHash();
|
||||
|
||||
// Now request with the extracted hash
|
||||
await request
|
||||
.get('/meteor_runtime_config.js')
|
||||
.query({ hash })
|
||||
.expect(200)
|
||||
.expect('Content-Type', 'application/javascript; charset=UTF-8')
|
||||
.expect((res) => {
|
||||
expect(res.text).to.include('__meteor_runtime_config__');
|
||||
expect(res.text).to.include('http://localhost:3000');
|
||||
});
|
||||
});
|
||||
|
||||
it('should return 404 when hash does not match', async () => {
|
||||
// First get the root page to extract the hash
|
||||
await request.get('/meteor_runtime_config.js').query({ hash: 'invalidHash' }).expect(404);
|
||||
});
|
||||
|
||||
it('should update hash when ROOT_URL changes', async () => {
|
||||
const originalHash = getHash();
|
||||
await updateSetting('Site_Url', 'http://new-url:3000');
|
||||
const newHash = await getHash();
|
||||
expect(newHash).to.not.equal(originalHash);
|
||||
// it should return if the new hash is valid
|
||||
await request
|
||||
.get('/meteor_runtime_config.js')
|
||||
.query({ hash: newHash })
|
||||
.expect(200)
|
||||
.expect('Content-Type', 'application/javascript; charset=UTF-8')
|
||||
.expect((res) => {
|
||||
expect(res.text).to.include('__meteor_runtime_config__');
|
||||
expect(res.text).to.include('http://new-url:3000');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
747
apps/meteor/tests/end-to-end/api/custom-sounds.ts
Normal file
747
apps/meteor/tests/end-to-end/api/custom-sounds.ts
Normal file
@@ -0,0 +1,747 @@
|
||||
import { randomUUID } from 'crypto';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
import type { Credentials } from '@rocket.chat/api-client';
|
||||
import type { IUser } from '@rocket.chat/core-typings';
|
||||
import { expect } from 'chai';
|
||||
import { before, describe, it, after } from 'mocha';
|
||||
|
||||
import { getCredentials, api, request, credentials } from '../../data/api-data';
|
||||
import { updateSetting } from '../../data/permissions.helper';
|
||||
import { password } from '../../data/user';
|
||||
import { createUser, deleteUser, login } from '../../data/users.helper';
|
||||
|
||||
async function createCustomSound(fileName: string, filePath: string): Promise<string> {
|
||||
let fileId = '';
|
||||
|
||||
await request
|
||||
.post(api('custom-sounds.create'))
|
||||
.set(credentials)
|
||||
.attach('sound', filePath)
|
||||
.field('name', fileName)
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('sound');
|
||||
fileId = res.body.sound._id;
|
||||
});
|
||||
|
||||
return fileId;
|
||||
}
|
||||
|
||||
async function deleteCustomSound(_id: string) {
|
||||
await request
|
||||
.post(api('custom-sounds.delete'))
|
||||
.set(credentials)
|
||||
.send({ _id })
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
});
|
||||
}
|
||||
|
||||
describe('[CustomSounds]', () => {
|
||||
const fileName = `test-file-${randomUUID()}`;
|
||||
const mockWavAudioPath = path.resolve(__dirname, '../../mocks/files/audio_mock.wav');
|
||||
const mockMp3AudioPath = path.resolve(__dirname, '../../mocks/files/audio_mock.mp3');
|
||||
|
||||
let fileId: string;
|
||||
let fileId2: string;
|
||||
let uploadDate: string | undefined;
|
||||
|
||||
before((done) => getCredentials(done));
|
||||
|
||||
before(async () => {
|
||||
fileId = await createCustomSound(fileName, mockWavAudioPath);
|
||||
fileId2 = await createCustomSound(`${fileName}-2`, mockWavAudioPath);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
if (fileId) {
|
||||
await deleteCustomSound(fileId);
|
||||
}
|
||||
if (fileId2) {
|
||||
await deleteCustomSound(fileId2);
|
||||
}
|
||||
});
|
||||
|
||||
describe('[/custom-sounds.create]', () => {
|
||||
let fileId3: string;
|
||||
|
||||
after(async () => {
|
||||
if (fileId3) {
|
||||
await deleteCustomSound(fileId3);
|
||||
}
|
||||
});
|
||||
|
||||
it('should successfully create a new custom sound and return its _id', async () => {
|
||||
const response = await request
|
||||
.post(api('custom-sounds.create'))
|
||||
.set(credentials)
|
||||
.attach('sound', mockWavAudioPath)
|
||||
.field('name', `happy-path-sound-${randomUUID()}`)
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('sound').and.to.be.an('object');
|
||||
expect(res.body.sound).to.have.property('_id').and.to.be.a('string');
|
||||
});
|
||||
fileId3 = response.body.sound._id;
|
||||
});
|
||||
|
||||
it('should not be able to create two sounds with the same name', async () => {
|
||||
await request
|
||||
.post(api('custom-sounds.create'))
|
||||
.set(credentials)
|
||||
.attach('sound', mockWavAudioPath)
|
||||
.field('name', fileName)
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error', 'The custom sound name is already in use [Custom_Sound_Error_Name_Already_In_Use]');
|
||||
});
|
||||
});
|
||||
|
||||
it('should return unauthorized if not authenticated', async () => {
|
||||
await request.post(api('custom-sounds.create')).expect(401);
|
||||
});
|
||||
|
||||
it('should fail if the file exceeds the 5MB size limit', async () => {
|
||||
const largeBuffer = Buffer.alloc(6 * 1024 * 1024, 'a');
|
||||
|
||||
await request
|
||||
.post(api('custom-sounds.create'))
|
||||
.set(credentials)
|
||||
.attach('sound', largeBuffer, { filename: 'large.wav', contentType: 'audio/wav' })
|
||||
.field('name', 'large-sound')
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('error').and.to.be.equal('[error-file-too-large]');
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail if the file is an invalid mime type', async () => {
|
||||
await request
|
||||
.post(api('custom-sounds.create'))
|
||||
.set(credentials)
|
||||
.attach('sound', Buffer.from('this is not audio'), { filename: 'test.txt', contentType: 'text/plain' })
|
||||
.field('name', 'invalid-sound')
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body.error).to.equal('MIME type not allowed');
|
||||
});
|
||||
});
|
||||
|
||||
it('should reject injection of invalid characters and symbols in name', async () => {
|
||||
await request
|
||||
.post(api('custom-sounds.create'))
|
||||
.set(credentials)
|
||||
.attach('sound', mockWavAudioPath)
|
||||
.field('name', '<script>alert("xss")</script>')
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body.error).to.include('is not a valid name');
|
||||
});
|
||||
});
|
||||
|
||||
it('should reject NoSQL regex injection in name', async () => {
|
||||
await request
|
||||
.post(api('custom-sounds.create'))
|
||||
.set(credentials)
|
||||
.attach('sound', mockWavAudioPath)
|
||||
.field('name', '{"$regex":".*"}')
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
describe('without manage-sounds permission', () => {
|
||||
let unauthorizedUser: IUser;
|
||||
let unauthorizedUserCredentials: Credentials;
|
||||
|
||||
before(async () => {
|
||||
unauthorizedUser = await createUser();
|
||||
unauthorizedUserCredentials = await login(unauthorizedUser.username, password);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await deleteUser(unauthorizedUser);
|
||||
});
|
||||
|
||||
it('should return forbidden if user does not have the manage-sounds permission', async () => {
|
||||
await request
|
||||
.post(api('custom-sounds.create'))
|
||||
.set(unauthorizedUserCredentials)
|
||||
.attach('sound', mockWavAudioPath)
|
||||
.field('name', `forbidden-sound-${randomUUID()}`)
|
||||
.expect(403);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/custom-sounds.update]', () => {
|
||||
let previousFileName: string;
|
||||
|
||||
before(async () => {
|
||||
await request
|
||||
.get(api('custom-sounds.getOne'))
|
||||
.set(credentials)
|
||||
.query({ _id: fileId })
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('sound').and.to.be.an('object');
|
||||
previousFileName = res.body.sound.name;
|
||||
});
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await request
|
||||
.post(api('custom-sounds.update'))
|
||||
.set(credentials)
|
||||
.field('_id', fileId)
|
||||
.field('name', previousFileName)
|
||||
.attach('sound', mockWavAudioPath)
|
||||
.expect(200);
|
||||
});
|
||||
|
||||
it('should successfully update only the name of the sound without sending the file again', async () => {
|
||||
const newSoundName = `${fileName}-updated`;
|
||||
await request
|
||||
.post(api('custom-sounds.update'))
|
||||
.set(credentials)
|
||||
.field('_id', fileId)
|
||||
.field('name', newSoundName)
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
});
|
||||
|
||||
await request
|
||||
.get(api('custom-sounds.getOne'))
|
||||
.set(credentials)
|
||||
.query({ _id: fileId })
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('sound').and.to.be.an('object');
|
||||
expect(res.body.sound).to.have.property('name').and.to.be.equal(newSoundName);
|
||||
});
|
||||
});
|
||||
|
||||
it('should successfully update the sound file and name', async () => {
|
||||
const newSoundName = `${fileName}-2-updated`;
|
||||
await request
|
||||
.post(api('custom-sounds.update'))
|
||||
.set(credentials)
|
||||
.attach('sound', mockMp3AudioPath)
|
||||
.field('_id', fileId)
|
||||
.field('name', newSoundName)
|
||||
.expect(200);
|
||||
|
||||
await request
|
||||
.get(api('custom-sounds.getOne'))
|
||||
.set(credentials)
|
||||
.query({ _id: fileId })
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('sound').and.to.be.an('object');
|
||||
expect(res.body.sound).to.have.property('name').and.to.be.equal(newSoundName);
|
||||
expect(res.body.sound).to.have.property('extension').and.to.be.equal('mp3');
|
||||
});
|
||||
});
|
||||
|
||||
it('should not be able to update sounds name if the name was already taken', async () => {
|
||||
await request
|
||||
.post(api('custom-sounds.update'))
|
||||
.set(credentials)
|
||||
.attach('sound', mockWavAudioPath)
|
||||
.field('_id', fileId)
|
||||
.field('name', `${fileName}-2`)
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error', 'The custom sound name is already in use [Custom_Sound_Error_Name_Already_In_Use]');
|
||||
});
|
||||
});
|
||||
|
||||
it('should return unauthorized if not authenticated', async () => {
|
||||
await request.post(api('custom-sounds.update')).expect(401);
|
||||
});
|
||||
|
||||
it('should reject injection of invalid characters and symbols in name', async () => {
|
||||
await request
|
||||
.post(api('custom-sounds.update'))
|
||||
.set(credentials)
|
||||
.attach('sound', mockWavAudioPath)
|
||||
.field('_id', fileId)
|
||||
.field('name', '<script>alert("xss")</script>')
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body.error).to.include('is not a valid name');
|
||||
});
|
||||
});
|
||||
|
||||
it('should reject NoSQL regex injection in name', async () => {
|
||||
await request
|
||||
.post(api('custom-sounds.update'))
|
||||
.set(credentials)
|
||||
.attach('sound', mockWavAudioPath)
|
||||
.field('_id', fileId)
|
||||
.field('name', '{"$regex":".*"}')
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
it('should fail if the file exceeds the 5MB size limit', async () => {
|
||||
const largeBuffer = Buffer.alloc(6 * 1024 * 1024, 'a');
|
||||
|
||||
await request
|
||||
.post(api('custom-sounds.update'))
|
||||
.set(credentials)
|
||||
.attach('sound', largeBuffer, { filename: 'large.wav', contentType: 'audio/wav' })
|
||||
.field('_id', fileId)
|
||||
.field('name', 'large-sound')
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('error').and.to.be.equal('[error-file-too-large]');
|
||||
});
|
||||
});
|
||||
|
||||
it('should return an error when trying to update a non-existent sound', async () => {
|
||||
await request
|
||||
.post(api('custom-sounds.update'))
|
||||
.set(credentials)
|
||||
.field('_id', 'invalid-id-123')
|
||||
.field('name', 'new-name')
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body.error).to.equal('Custom Sound not found.');
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail if attempting to update with an invalid mime type file', async () => {
|
||||
await request
|
||||
.post(api('custom-sounds.update'))
|
||||
.set(credentials)
|
||||
.attach('sound', Buffer.from('fake-audio'), { filename: 'test.mp4', contentType: 'video/mp4' })
|
||||
.field('_id', fileId)
|
||||
.field('name', fileName)
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body.error).to.equal('MIME type not allowed');
|
||||
});
|
||||
});
|
||||
|
||||
describe('without manage-sounds permission', async () => {
|
||||
let unauthorizedUser: IUser;
|
||||
let unauthorizedUserCredentials: Credentials;
|
||||
|
||||
before(async () => {
|
||||
unauthorizedUser = await createUser();
|
||||
unauthorizedUserCredentials = await login(unauthorizedUser.username, password);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await deleteUser(unauthorizedUser);
|
||||
});
|
||||
|
||||
it('should return forbidden if user does not have the manage-sounds permission', async () => {
|
||||
await request
|
||||
.post(api('custom-sounds.update'))
|
||||
.set(unauthorizedUserCredentials)
|
||||
.attach('sound', mockWavAudioPath)
|
||||
.field('_id', fileId)
|
||||
.field('name', `forbidden-case`)
|
||||
.expect(403);
|
||||
});
|
||||
});
|
||||
|
||||
describe('mutating file on failed validation', () => {
|
||||
let soundAId: string;
|
||||
let soundBId: string;
|
||||
const soundAName = `sound-a-${randomUUID()}`;
|
||||
const soundBName = `sound-b-${randomUUID()}`;
|
||||
|
||||
before(async () => {
|
||||
soundAId = await createCustomSound(soundAName, mockWavAudioPath);
|
||||
soundBId = await createCustomSound(soundBName, mockMp3AudioPath);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await deleteCustomSound(soundAId);
|
||||
await deleteCustomSound(soundBId);
|
||||
});
|
||||
|
||||
it('should not mutate the underlying file or metadata if the update fails validation (e.g., name collision)', async () => {
|
||||
await request
|
||||
.post(api('custom-sounds.update'))
|
||||
.set(credentials)
|
||||
.attach('sound', mockMp3AudioPath)
|
||||
.field('_id', soundAId)
|
||||
.field('name', soundBName)
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error', 'The custom sound name is already in use [Custom_Sound_Error_Name_Already_In_Use]');
|
||||
});
|
||||
|
||||
await request
|
||||
.get(api('custom-sounds.getOne'))
|
||||
.set(credentials)
|
||||
.query({ _id: soundAId })
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body.sound).to.have.property('name', soundAName);
|
||||
expect(res.body.sound).to.have.property('extension', 'wav');
|
||||
});
|
||||
|
||||
const originalWavBuffer = fs.readFileSync(mockWavAudioPath);
|
||||
|
||||
await request
|
||||
.get(`/custom-sounds/${soundAId}.wav`)
|
||||
.set(credentials)
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.headers).to.have.property('content-type', 'audio/wav');
|
||||
expect(Buffer.isBuffer(res.body)).to.be.true;
|
||||
expect(originalWavBuffer.equals(res.body)).to.be.true;
|
||||
});
|
||||
|
||||
await request.get(`/custom-sounds/${soundAId}.mp3`).set(credentials).expect(404);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/custom-sounds.list]', () => {
|
||||
it('should return custom sounds', async () => {
|
||||
const res = await request.get(api('custom-sounds.list')).set(credentials).expect(200);
|
||||
|
||||
expect(res.body).to.have.property('sounds').and.to.be.an('array');
|
||||
expect(res.body).to.have.property('total');
|
||||
expect(res.body).to.have.property('offset');
|
||||
expect(res.body).to.have.property('count');
|
||||
});
|
||||
it('should return custom sounds even requested with count and offset params', async () => {
|
||||
const res = await request.get(api('custom-sounds.list')).set(credentials).expect(200).query({
|
||||
count: 5,
|
||||
offset: 0,
|
||||
});
|
||||
|
||||
expect(res.body).to.have.property('sounds').and.to.be.an('array');
|
||||
expect(res.body).to.have.property('total');
|
||||
expect(res.body).to.have.property('offset');
|
||||
expect(res.body).to.have.property('count');
|
||||
});
|
||||
it('should return custom sounds filtering it using the `name` parameter', async () => {
|
||||
const res = await request
|
||||
.get(api('custom-sounds.list'))
|
||||
.set(credentials)
|
||||
.expect(200)
|
||||
.query({
|
||||
name: `${fileName}-2`,
|
||||
count: 5,
|
||||
offset: 0,
|
||||
});
|
||||
|
||||
expect(res.body).to.have.property('sounds').and.to.be.an('array');
|
||||
expect(res.body).to.have.property('total').to.equal(1);
|
||||
expect(res.body).to.have.property('offset').to.equal(0);
|
||||
expect(res.body).to.have.property('count').to.equal(1);
|
||||
expect(res.body.sounds[0]._id).to.be.equal(fileId2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/custom-sounds.delete]', () => {
|
||||
let soundToDeleteId: string;
|
||||
let soundDeleted: boolean = false;
|
||||
|
||||
before(async () => {
|
||||
soundToDeleteId = await createCustomSound(`sound-to-delete-${randomUUID()}`, mockWavAudioPath);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
if (soundToDeleteId && !soundDeleted) {
|
||||
await deleteCustomSound(soundToDeleteId);
|
||||
}
|
||||
});
|
||||
|
||||
it('should return unauthorized if the user is not authenticated', async () => {
|
||||
await request.post(api('custom-sounds.delete')).send({ _id: soundToDeleteId }).expect(401);
|
||||
});
|
||||
|
||||
it('should return a 400 if attempting to delete a sound that does not exist', async () => {
|
||||
await request
|
||||
.post(api('custom-sounds.delete'))
|
||||
.set(credentials)
|
||||
.send({ _id: 'invalid-non-existent-id' })
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body.error).to.equal('Custom_Sound_Error_Invalid_Sound');
|
||||
});
|
||||
});
|
||||
|
||||
it('should reject requests with invalid parameter types', async () => {
|
||||
await request
|
||||
.post(api('custom-sounds.delete'))
|
||||
.set(credentials)
|
||||
.send({ _id: { $ne: null } })
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('without manage-sounds permission', async () => {
|
||||
let unauthorizedUser: IUser;
|
||||
let unauthorizedUserCredentials: Credentials;
|
||||
|
||||
before(async () => {
|
||||
unauthorizedUser = await createUser();
|
||||
unauthorizedUserCredentials = await login(unauthorizedUser.username, password);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await deleteUser(unauthorizedUser);
|
||||
});
|
||||
|
||||
it('should return forbidden if user does not have the manage-sounds permission', async () => {
|
||||
await request.post(api('custom-sounds.delete')).set(unauthorizedUserCredentials).send({ _id: soundToDeleteId }).expect(403);
|
||||
});
|
||||
});
|
||||
|
||||
it('should successfully delete a custom sound when providing a valid _id', async () => {
|
||||
await request
|
||||
.post(api('custom-sounds.delete'))
|
||||
.set(credentials)
|
||||
.send({ _id: soundToDeleteId })
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
});
|
||||
soundDeleted = true;
|
||||
});
|
||||
});
|
||||
|
||||
describe('Accessing custom sounds', () => {
|
||||
it('should return forbidden if the there is no fileId on the url', async () => {
|
||||
const res = await request.get('/custom-sounds/').set(credentials).expect(403);
|
||||
|
||||
expect(res.text).to.be.equal('Forbidden');
|
||||
});
|
||||
|
||||
it('should return not found if the the requested file does not exists', async () => {
|
||||
const res = await request.get('/custom-sounds/invalid.mp3').set(credentials).expect(404);
|
||||
|
||||
expect(res.text).to.be.equal('Not found');
|
||||
});
|
||||
|
||||
it('should return success if the the requested exists', async () => {
|
||||
const res = await request.get(`/custom-sounds/${fileId}.wav`).set(credentials).expect(200);
|
||||
|
||||
expect(res.headers).to.have.property('last-modified');
|
||||
expect(res.headers).to.have.property('content-type', 'audio/wav');
|
||||
expect(res.headers).to.have.property('cache-control', 'public, max-age=0');
|
||||
expect(res.headers).to.have.property('expires', '-1');
|
||||
uploadDate = res.headers['last-modified'];
|
||||
});
|
||||
|
||||
it('should return not modified if the the requested file contains a valid-since equal to the upload date', async () => {
|
||||
const res = await request
|
||||
.get(`/custom-sounds/${fileId}.wav`)
|
||||
.set(credentials)
|
||||
.set({
|
||||
'if-modified-since': uploadDate,
|
||||
})
|
||||
.expect(304);
|
||||
|
||||
expect(res.headers).to.have.property('last-modified', uploadDate);
|
||||
expect(res.headers).not.to.have.property('content-type');
|
||||
expect(res.headers).not.to.have.property('cache-control');
|
||||
expect(res.headers).not.to.have.property('expires');
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/custom-sounds.delete]', () => {
|
||||
let toDeleteFileId: string;
|
||||
|
||||
before(async () => {
|
||||
toDeleteFileId = await createCustomSound(`${fileName}-delete-${randomUUID()}`, mockWavAudioPath);
|
||||
});
|
||||
|
||||
it('should return unauthorized if not authenticated', async () => {
|
||||
await request.post(api('custom-sounds.delete')).send({ _id: toDeleteFileId }).expect(401);
|
||||
});
|
||||
|
||||
it('should return bad request when _id is missing', async () => {
|
||||
await request.post(api('custom-sounds.delete')).set(credentials).send({}).expect(400);
|
||||
});
|
||||
|
||||
it('should return bad request when _id is an empty string', async () => {
|
||||
await request.post(api('custom-sounds.delete')).set(credentials).send({ _id: '' }).expect(400);
|
||||
});
|
||||
|
||||
it('should fail when the user does not have manage-sounds permission', async () => {
|
||||
const testUser = await createUser();
|
||||
const testUserCredentials = await login(testUser.username, password);
|
||||
try {
|
||||
await request.post(api('custom-sounds.delete')).set(testUserCredentials).send({ _id: toDeleteFileId }).expect(403);
|
||||
} finally {
|
||||
await deleteUser(testUser);
|
||||
}
|
||||
});
|
||||
|
||||
it('should delete the sound successfully and remove it from the list', async () => {
|
||||
await request
|
||||
.post(api('custom-sounds.delete'))
|
||||
.set(credentials)
|
||||
.send({ _id: toDeleteFileId })
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
});
|
||||
|
||||
await request.get(api('custom-sounds.getOne')).set(credentials).query({ _id: toDeleteFileId }).expect(404);
|
||||
});
|
||||
|
||||
it('should fail when the sound id does not exist', async () => {
|
||||
await request
|
||||
.post(api('custom-sounds.delete'))
|
||||
.set(credentials)
|
||||
.send({ _id: 'non-existent-sound-id' })
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/custom-sounds.getOne]', () => {
|
||||
it('should return unauthorized if not authenticated', async () => {
|
||||
await request.get(api('custom-sounds.getOne')).query({ _id: fileId }).expect(401);
|
||||
});
|
||||
|
||||
it('should return not found if custom sound does not exist', async () => {
|
||||
await request.get(api('custom-sounds.getOne')).set(credentials).query({ _id: 'invalid-id' }).expect(404);
|
||||
});
|
||||
|
||||
it('should return bad request if the _id length is not more than one', async () => {
|
||||
await request.get(api('custom-sounds.getOne')).set(credentials).query({ _id: '' }).expect(400);
|
||||
});
|
||||
|
||||
it('should return the custom sound successfully', async () => {
|
||||
await request
|
||||
.get(api('custom-sounds.getOne'))
|
||||
.set(credentials)
|
||||
.query({ _id: fileId })
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('sound').and.to.be.an('object');
|
||||
expect(res.body.sound).to.have.property('_id', fileId);
|
||||
expect(res.body.sound).to.have.property('name').and.to.be.a('string');
|
||||
expect(res.body.sound).to.have.property('extension').and.to.be.a('string');
|
||||
});
|
||||
});
|
||||
|
||||
it('should reject regex injection via query object', async () => {
|
||||
await request
|
||||
.get(api('custom-sounds.getOne'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
_id: { $regex: '.*' },
|
||||
})
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
it('should reject regex injection via bracket syntax', async () => {
|
||||
await request.get(api('custom-sounds.getOne')).set(credentials).query('_id[$regex]=.*').expect(400);
|
||||
});
|
||||
|
||||
it('should reject encoded regex injection attempt', async () => {
|
||||
await request
|
||||
.get(api('custom-sounds.getOne'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
_id: '{"$regex":".*"}',
|
||||
})
|
||||
.expect(404); // valid string, but it doesn't exist
|
||||
});
|
||||
});
|
||||
|
||||
describe('Sounds storage settings reactivity', () => {
|
||||
let fsFileId: string;
|
||||
let gridFsFileId: string;
|
||||
|
||||
before(async () => {
|
||||
await updateSetting('CustomSounds_FileSystemPath', '', false);
|
||||
|
||||
await updateSetting('CustomSounds_Storage_Type', 'FileSystem');
|
||||
fsFileId = await createCustomSound(`${fileName}-3`, mockWavAudioPath);
|
||||
|
||||
await updateSetting('CustomSounds_Storage_Type', 'GridFS');
|
||||
gridFsFileId = await createCustomSound(`${fileName}-4`, mockWavAudioPath);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await updateSetting('CustomSounds_Storage_Type', 'FileSystem', false);
|
||||
await updateSetting('CustomSounds_FileSystemPath', '');
|
||||
await deleteCustomSound(fsFileId);
|
||||
await updateSetting('CustomSounds_Storage_Type', 'GridFS');
|
||||
await deleteCustomSound(gridFsFileId);
|
||||
});
|
||||
|
||||
describe('CustomSounds_Storage_Type', () => {
|
||||
describe('when storage is GridFS', () => {
|
||||
before(async () => {
|
||||
await updateSetting('CustomSounds_Storage_Type', 'GridFS');
|
||||
});
|
||||
|
||||
it('should resolve GridFS files only', async () => {
|
||||
await request.get(`/custom-sounds/${gridFsFileId}.wav`).set(credentials).expect(200);
|
||||
await request.get(`/custom-sounds/${fsFileId}.wav`).set(credentials).expect(404);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when storage is FileSystem', () => {
|
||||
before(async () => {
|
||||
await updateSetting('CustomSounds_Storage_Type', 'FileSystem');
|
||||
});
|
||||
|
||||
it('should resolve FileSystem files only', async () => {
|
||||
await request.get(`/custom-sounds/${gridFsFileId}.wav`).set(credentials).expect(404);
|
||||
await request.get(`/custom-sounds/${fsFileId}.wav`).set(credentials).expect(200);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('CustomSounds_FileSystemPath', () => {
|
||||
before(async () => {
|
||||
await updateSetting('CustomSounds_Storage_Type', 'FileSystem');
|
||||
});
|
||||
|
||||
describe('when file system path is the default one', () => {
|
||||
it('should resolve files', async () => {
|
||||
await request.get(`/custom-sounds/${fsFileId}.wav`).set(credentials).expect(200);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when file system path is NOT the default one', () => {
|
||||
before(async () => {
|
||||
await updateSetting('CustomSounds_FileSystemPath', '~/sounds');
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await updateSetting('CustomSounds_FileSystemPath', '');
|
||||
});
|
||||
|
||||
it('should NOT resolve files', async () => {
|
||||
await request.get(`/custom-sounds/${fsFileId}.wav`).set(credentials).expect(404);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
382
apps/meteor/tests/end-to-end/api/custom-user-status.ts
Normal file
382
apps/meteor/tests/end-to-end/api/custom-user-status.ts
Normal file
@@ -0,0 +1,382 @@
|
||||
import { expect } from 'chai';
|
||||
import { after, afterEach, before, describe, it } from 'mocha';
|
||||
import type { Response } from 'supertest';
|
||||
|
||||
import { getCredentials, api, request, credentials } from '../../data/api-data';
|
||||
import { updatePermission } from '../../data/permissions.helper';
|
||||
import { password } from '../../data/user';
|
||||
import { createUser, deleteUser, login } from '../../data/users.helper';
|
||||
|
||||
async function createCustomUserStatus(name: string, statusType?: string): Promise<string> {
|
||||
const res = await request.post(api('custom-user-status.create')).set(credentials).send({ name, statusType }).expect(200);
|
||||
return res.body.customUserStatus._id;
|
||||
}
|
||||
|
||||
async function deleteCustomUserStatus(id: string): Promise<void> {
|
||||
await request.post(api('custom-user-status.delete')).set(credentials).send({ customUserStatusId: id }).expect(200);
|
||||
}
|
||||
|
||||
describe('[CustomUserStatus]', () => {
|
||||
let unauthorizedUser: any;
|
||||
let unauthorizedUserCredentials: any;
|
||||
|
||||
before((done) => {
|
||||
getCredentials(done);
|
||||
});
|
||||
|
||||
before(async () => {
|
||||
unauthorizedUser = await createUser();
|
||||
unauthorizedUserCredentials = await login(unauthorizedUser.username, password);
|
||||
});
|
||||
|
||||
after(() => Promise.all([updatePermission('manage-user-status', ['admin']), deleteUser(unauthorizedUser)]));
|
||||
|
||||
describe('[/custom-user-status.list]', () => {
|
||||
let customUserStatusId: string;
|
||||
let customUserStatusName: string;
|
||||
|
||||
before(async () => {
|
||||
customUserStatusName = `test-${Date.now()}`;
|
||||
customUserStatusId = await createCustomUserStatus(customUserStatusName);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await deleteCustomUserStatus(customUserStatusId);
|
||||
});
|
||||
|
||||
it('should return custom user status', async () => {
|
||||
const res = await request.get(api('custom-user-status.list')).set(credentials).expect(200);
|
||||
|
||||
expect(res.body).to.have.property('statuses').and.to.be.an('array');
|
||||
expect(res.body).to.have.property('total');
|
||||
expect(res.body).to.have.property('offset');
|
||||
expect(res.body).to.have.property('count');
|
||||
});
|
||||
|
||||
it('should return custom user status even requested with count and offset params', async () => {
|
||||
const res = await request.get(api('custom-user-status.list')).set(credentials).expect(200).query({
|
||||
count: 5,
|
||||
offset: 0,
|
||||
});
|
||||
|
||||
expect(res.body).to.have.property('statuses').and.to.be.an('array');
|
||||
expect(res.body).to.have.property('total');
|
||||
expect(res.body).to.have.property('offset');
|
||||
expect(res.body).to.have.property('count');
|
||||
});
|
||||
|
||||
it('should return one custom user status when requested with id param', async () => {
|
||||
const res = await request.get(api('custom-user-status.list')).set(credentials).expect(200).query({
|
||||
_id: customUserStatusId,
|
||||
});
|
||||
|
||||
expect(res.body).to.have.property('statuses').and.to.be.an('array').and.to.have.lengthOf(1);
|
||||
expect(res.body).to.have.property('total').and.to.equal(1);
|
||||
expect(res.body).to.have.property('offset').and.to.equal(0);
|
||||
expect(res.body).to.have.property('count').and.to.equal(1);
|
||||
});
|
||||
|
||||
it('should return empty array when requested with an existing name param', async () => {
|
||||
const res = await request.get(api('custom-user-status.list')).set(credentials).expect(200).query({
|
||||
name: customUserStatusName,
|
||||
});
|
||||
|
||||
expect(res.body).to.have.property('statuses').and.to.be.an('array').and.to.have.lengthOf(1);
|
||||
expect(res.body).to.have.property('total').and.to.equal(1);
|
||||
expect(res.body).to.have.property('offset').and.to.equal(0);
|
||||
expect(res.body).to.have.property('count').and.to.equal(1);
|
||||
});
|
||||
|
||||
it('should return empty array when requested with unknown name param', async () => {
|
||||
const res = await request.get(api('custom-user-status.list')).set(credentials).expect(200).query({
|
||||
name: 'testxxx',
|
||||
});
|
||||
|
||||
expect(res.body).to.have.property('statuses').and.to.be.an('array').and.to.have.lengthOf(0);
|
||||
expect(res.body).to.have.property('total').and.to.equal(0);
|
||||
expect(res.body).to.have.property('offset').and.to.equal(0);
|
||||
expect(res.body).to.have.property('count').and.to.equal(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/custom-user-status.create]', () => {
|
||||
let customUserStatusId: string;
|
||||
|
||||
afterEach(async () => {
|
||||
await updatePermission('manage-user-status', ['admin']);
|
||||
|
||||
if (customUserStatusId) {
|
||||
await deleteCustomUserStatus(customUserStatusId);
|
||||
customUserStatusId = '';
|
||||
}
|
||||
});
|
||||
|
||||
it('should throw an error if not authenticated', async () => {
|
||||
await request
|
||||
.post(api('custom-user-status.create'))
|
||||
.send({ name: 'test-status' })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(401)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('status', 'error');
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw an error if user does not have manage-user-status permission', async () => {
|
||||
await updatePermission('manage-user-status', []);
|
||||
|
||||
await request
|
||||
.post(api('custom-user-status.create'))
|
||||
.set(unauthorizedUserCredentials)
|
||||
.send({ name: 'test-status' })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('errorType', 'not_authorized');
|
||||
});
|
||||
});
|
||||
|
||||
it('should create a custom user status successfully', async () => {
|
||||
const statusName = `test-create-${Date.now()}`;
|
||||
|
||||
await request
|
||||
.post(api('custom-user-status.create'))
|
||||
.set(credentials)
|
||||
.send({ name: statusName, statusType: 'busy' })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('customUserStatus');
|
||||
expect(res.body.customUserStatus).to.have.property('_id');
|
||||
expect(res.body.customUserStatus).to.have.property('name', statusName);
|
||||
expect(res.body.customUserStatus).to.have.property('statusType', 'busy');
|
||||
customUserStatusId = res.body.customUserStatus._id;
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw an error if name already exists', async () => {
|
||||
const statusName = `test-duplicate-${Date.now()}`;
|
||||
customUserStatusId = await createCustomUserStatus(statusName);
|
||||
|
||||
await request
|
||||
.post(api('custom-user-status.create'))
|
||||
.set(credentials)
|
||||
.send({ name: statusName })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('errorType', 'Custom_User_Status_Error_Name_Already_In_Use');
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw an error if statusType is invalid', async () => {
|
||||
await request
|
||||
.post(api('custom-user-status.create'))
|
||||
.set(credentials)
|
||||
.send({ name: `test-invalid-status-type-${Date.now()}`, statusType: 'invalid' })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('errorType', 'error-input-is-not-a-valid-field');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/custom-user-status.update]', () => {
|
||||
let customUserStatusId: string;
|
||||
let customUserStatusName: string;
|
||||
|
||||
before(async () => {
|
||||
customUserStatusName = `test-update-${Date.now()}`;
|
||||
customUserStatusId = await createCustomUserStatus(customUserStatusName);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await updatePermission('manage-user-status', ['admin']);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
if (customUserStatusId) {
|
||||
await deleteCustomUserStatus(customUserStatusId);
|
||||
}
|
||||
await updatePermission('manage-user-status', ['admin']);
|
||||
});
|
||||
|
||||
it('should throw an error if not authenticated', async () => {
|
||||
await request
|
||||
.post(api('custom-user-status.update'))
|
||||
.send({ _id: customUserStatusId, name: 'updated-name' })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(401)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('status', 'error');
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw an error if user does not have manage-user-status permission', async () => {
|
||||
await updatePermission('manage-user-status', []);
|
||||
|
||||
await request
|
||||
.post(api('custom-user-status.update'))
|
||||
.set(unauthorizedUserCredentials)
|
||||
.send({ _id: customUserStatusId, name: 'updated-name' })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('errorType', 'not_authorized');
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw an error if custom user status does not exist', async () => {
|
||||
await request
|
||||
.post(api('custom-user-status.update'))
|
||||
.set(credentials)
|
||||
.send({ _id: 'invalid-id', name: 'updated-name' })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
});
|
||||
});
|
||||
|
||||
it('should update custom user status successfully', async () => {
|
||||
const newName = `test-updated-${Date.now()}`;
|
||||
|
||||
await request
|
||||
.post(api('custom-user-status.update'))
|
||||
.set(credentials)
|
||||
.send({ _id: customUserStatusId, name: newName, statusType: 'away' })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('customUserStatus');
|
||||
expect(res.body.customUserStatus).to.have.property('_id', customUserStatusId);
|
||||
expect(res.body.customUserStatus).to.have.property('name', newName);
|
||||
expect(res.body.customUserStatus).to.have.property('statusType', 'away');
|
||||
});
|
||||
|
||||
customUserStatusName = newName;
|
||||
});
|
||||
|
||||
it('should throw an error if status name already exists', async () => {
|
||||
const existingStatusName = `test-update-duplicate-${Date.now()}`;
|
||||
const existingStatusId = await createCustomUserStatus(existingStatusName);
|
||||
|
||||
await request
|
||||
.post(api('custom-user-status.update'))
|
||||
.set(credentials)
|
||||
.send({ _id: customUserStatusId, name: existingStatusName })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('errorType', 'Custom_User_Status_Error_Name_Already_In_Use');
|
||||
});
|
||||
|
||||
await deleteCustomUserStatus(existingStatusId);
|
||||
});
|
||||
|
||||
it('should throw an error if statusType is invalid', async () => {
|
||||
await request
|
||||
.post(api('custom-user-status.update'))
|
||||
.set(credentials)
|
||||
.send({ _id: customUserStatusId, name: customUserStatusName, statusType: 'invalid' })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('errorType', 'error-input-is-not-a-valid-field');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/custom-user-status.delete]', () => {
|
||||
let customUserStatusId: string;
|
||||
|
||||
beforeEach(async () => {
|
||||
const statusName = `test-delete-${Date.now()}`;
|
||||
customUserStatusId = await createCustomUserStatus(statusName);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await updatePermission('manage-user-status', ['admin']);
|
||||
|
||||
if (customUserStatusId) {
|
||||
await deleteCustomUserStatus(customUserStatusId);
|
||||
customUserStatusId = '';
|
||||
}
|
||||
});
|
||||
|
||||
it('should throw an error if not authenticated', async () => {
|
||||
await request
|
||||
.post(api('custom-user-status.delete'))
|
||||
.send({ customUserStatusId })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(401)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('status', 'error');
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw an error if user does not have manage-user-status permission', async () => {
|
||||
await updatePermission('manage-user-status', []);
|
||||
|
||||
await request
|
||||
.post(api('custom-user-status.delete'))
|
||||
.set(unauthorizedUserCredentials)
|
||||
.send({ customUserStatusId })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('errorType', 'not_authorized');
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw an error if customUserStatusId is not provided', async () => {
|
||||
await request
|
||||
.post(api('custom-user-status.delete'))
|
||||
.set(credentials)
|
||||
.send({})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error', "must have required property 'customUserStatusId'");
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw an error if custom user status does not exist', async () => {
|
||||
await request
|
||||
.post(api('custom-user-status.delete'))
|
||||
.set(credentials)
|
||||
.send({ customUserStatusId: 'invalid-id' })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('errorType', 'Custom_User_Status_Error_Invalid_User_Status');
|
||||
});
|
||||
});
|
||||
|
||||
it('should delete custom user status successfully', async () => {
|
||||
await request
|
||||
.post(api('custom-user-status.delete'))
|
||||
.set(credentials)
|
||||
.send({ customUserStatusId })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
});
|
||||
|
||||
customUserStatusId = '';
|
||||
});
|
||||
});
|
||||
});
|
||||
1399
apps/meteor/tests/end-to-end/api/direct-message.ts
Normal file
1399
apps/meteor/tests/end-to-end/api/direct-message.ts
Normal file
File diff suppressed because it is too large
Load Diff
539
apps/meteor/tests/end-to-end/api/emoji-custom.ts
Normal file
539
apps/meteor/tests/end-to-end/api/emoji-custom.ts
Normal file
@@ -0,0 +1,539 @@
|
||||
import type { IEmojiCustom } from '@rocket.chat/core-typings';
|
||||
import { assert, expect } from 'chai';
|
||||
import { before, describe, it, after } from 'mocha';
|
||||
|
||||
import { getCredentials, api, request, credentials } from '../../data/api-data';
|
||||
import { imgURL } from '../../data/interactions';
|
||||
import { updateSetting } from '../../data/permissions.helper';
|
||||
|
||||
describe('[EmojiCustom]', () => {
|
||||
const customEmojiName = `my-custom-emoji-${Date.now()}`;
|
||||
|
||||
let withoutAliases: IEmojiCustom;
|
||||
|
||||
before((done) => getCredentials(done));
|
||||
|
||||
after(() =>
|
||||
request.post(api('emoji-custom.delete')).set(credentials).send({
|
||||
emojiId: withoutAliases._id,
|
||||
}),
|
||||
);
|
||||
|
||||
describe('[/emoji-custom.create]', () => {
|
||||
it('should create new custom emoji', async () => {
|
||||
const res = await request
|
||||
.post(api('emoji-custom.create'))
|
||||
.set(credentials)
|
||||
.attach('emoji', imgURL)
|
||||
.field({
|
||||
name: customEmojiName,
|
||||
aliases: `${customEmojiName}-alias`,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
expect(res.body).to.have.property('success', true);
|
||||
});
|
||||
it('should create new custom emoji without optional parameter "aliases"', async () => {
|
||||
const res = await request
|
||||
.post(api('emoji-custom.create'))
|
||||
.set(credentials)
|
||||
.attach('emoji', imgURL)
|
||||
.field({
|
||||
name: `${customEmojiName}-without-aliases`,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
expect(res.body).to.have.property('success', true);
|
||||
});
|
||||
it('should throw an error when the filename is wrong', async () => {
|
||||
const res = await request
|
||||
.post(api('emoji-custom.create'))
|
||||
.set(credentials)
|
||||
.attach('emojiwrong', imgURL)
|
||||
.field({
|
||||
_id: 'invalid-id',
|
||||
name: 'my-custom-emoji-without-aliases',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body.errorType).to.be.equal('invalid-field');
|
||||
});
|
||||
});
|
||||
|
||||
let createdCustomEmoji: IEmojiCustom;
|
||||
|
||||
describe('[/emoji-custom.update]', () => {
|
||||
before((done) => {
|
||||
void request
|
||||
.get(api('emoji-custom.list'))
|
||||
.set(credentials)
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('emojis').and.to.be.a('object');
|
||||
expect(res.body.emojis).to.have.property('update').and.to.be.a('array').and.to.not.have.lengthOf(0);
|
||||
expect(res.body.emojis).to.have.property('remove').and.to.be.a('array').and.to.have.lengthOf(0);
|
||||
|
||||
const _createdCustomEmoji = (res.body.emojis.update as IEmojiCustom[]).find((emoji) => emoji.name === customEmojiName);
|
||||
const _withoutAliases = (res.body.emojis.update as IEmojiCustom[]).find(
|
||||
(emoji) => emoji.name === `${customEmojiName}-without-aliases`,
|
||||
);
|
||||
|
||||
assert.isDefined(_createdCustomEmoji);
|
||||
assert.isDefined(_withoutAliases);
|
||||
|
||||
createdCustomEmoji = _createdCustomEmoji;
|
||||
withoutAliases = _withoutAliases;
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
describe('successfully:', () => {
|
||||
it('should update the custom emoji without a file', async () => {
|
||||
const res = await request
|
||||
.post(api('emoji-custom.update'))
|
||||
.set(credentials)
|
||||
.field({
|
||||
_id: createdCustomEmoji._id,
|
||||
name: customEmojiName,
|
||||
aliases: 'alias-my-custom-emoji',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
expect(res.body).to.have.property('success', true);
|
||||
});
|
||||
it('should update the custom emoji without optional parameter "aliases"', async () => {
|
||||
const res = await request
|
||||
.post(api('emoji-custom.update'))
|
||||
.set(credentials)
|
||||
.field({
|
||||
_id: createdCustomEmoji._id,
|
||||
name: customEmojiName,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
expect(res.body).to.have.property('success', true);
|
||||
});
|
||||
it('should update the custom emoji with all parameters and with a file', async () => {
|
||||
const res = await request
|
||||
.post(api('emoji-custom.update'))
|
||||
.set(credentials)
|
||||
.attach('emoji', imgURL)
|
||||
.field({
|
||||
_id: createdCustomEmoji._id,
|
||||
name: customEmojiName,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
expect(res.body).to.have.property('success', true);
|
||||
});
|
||||
|
||||
it('should change the etag when the custom emoji image is updated', async () => {
|
||||
const prevEtag = createdCustomEmoji.etag;
|
||||
|
||||
await request
|
||||
.post(api('emoji-custom.update'))
|
||||
.set(credentials)
|
||||
.attach('emoji', imgURL)
|
||||
.field({
|
||||
_id: createdCustomEmoji._id,
|
||||
name: customEmojiName,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
});
|
||||
|
||||
const emojis = await request.get(api(`emoji-custom.all`)).set(credentials).expect(200);
|
||||
const updatedCustomEmoji = emojis.body.emojis.find((emoji: IEmojiCustom) => emoji._id === createdCustomEmoji._id);
|
||||
expect(updatedCustomEmoji.etag).not.to.be.equal(prevEtag);
|
||||
});
|
||||
});
|
||||
|
||||
describe('should throw error when:', () => {
|
||||
it('the fields does not include "_id"', async () => {
|
||||
const res = await request
|
||||
.post(api('emoji-custom.update'))
|
||||
.set(credentials)
|
||||
.attach('emoji', imgURL)
|
||||
.field({
|
||||
name: 'my-custom-emoji-without-aliases',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body.errorType).to.be.equal('The required "_id" query param is missing.');
|
||||
});
|
||||
it('the custom emoji does not exists', async () => {
|
||||
const res = await request
|
||||
.post(api('emoji-custom.update'))
|
||||
.set(credentials)
|
||||
.attach('emoji', imgURL)
|
||||
.field({
|
||||
_id: 'invalid-id',
|
||||
name: 'my-custom-emoji-without-aliases',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body.errorType).to.be.equal('Emoji not found.');
|
||||
});
|
||||
it('the emoji file field is wrong', async () => {
|
||||
const res = await request
|
||||
.post(api('emoji-custom.update'))
|
||||
.set(credentials)
|
||||
.attach('emojiwrong', imgURL)
|
||||
.field({
|
||||
_id: createdCustomEmoji._id,
|
||||
name: 'my-custom-emoji-without-aliases',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body.errorType).to.be.equal('invalid-field');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/emoji-custom.list]', () => {
|
||||
it('should return emojis', async () => {
|
||||
const res = await request.get(api('emoji-custom.list')).set(credentials).expect(200);
|
||||
|
||||
expect(res.body).to.have.property('emojis').and.to.be.a('object');
|
||||
expect(res.body.emojis).to.have.property('update').and.to.be.a('array').and.to.not.have.lengthOf(0);
|
||||
expect(res.body.emojis).to.have.property('remove').and.to.be.a('array').and.to.have.lengthOf(0);
|
||||
});
|
||||
it('should return emojis when use "query" query parameter', async () => {
|
||||
const res = await request.get(api('emoji-custom.list')).query({ _updatedAt: new Date().toISOString() }).set(credentials).expect(200);
|
||||
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('emojis').and.to.be.a('object');
|
||||
expect(res.body.emojis).to.have.property('update').and.to.be.a('array').and.to.have.lengthOf(0);
|
||||
expect(res.body.emojis).to.have.property('remove').and.to.be.a('array').and.to.have.lengthOf(0);
|
||||
});
|
||||
it('should return emojis when use "updateSince" query parameter', async () => {
|
||||
const res = await request
|
||||
.get(api('emoji-custom.list'))
|
||||
.query({ updatedSince: new Date().toISOString() })
|
||||
.set(credentials)
|
||||
.expect(200);
|
||||
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('emojis').and.to.be.a('object');
|
||||
expect(res.body.emojis).to.have.property('update').and.to.be.a('array').and.to.have.lengthOf(0);
|
||||
expect(res.body.emojis).to.have.property('remove').and.to.be.a('array').and.to.have.lengthOf(0);
|
||||
});
|
||||
it('should return emojis when use both, "updateSince" and "query" query parameter', async () => {
|
||||
const res = await request
|
||||
.get(api('emoji-custom.list'))
|
||||
.query({ _updatedAt: new Date().toISOString(), updatedSince: new Date().toISOString() })
|
||||
.set(credentials)
|
||||
.expect(200);
|
||||
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('emojis').and.to.be.a('object');
|
||||
expect(res.body.emojis).to.have.property('update').and.to.be.a('array').and.to.have.lengthOf(0);
|
||||
expect(res.body.emojis).to.have.property('remove').and.to.be.a('array').and.to.have.lengthOf(0);
|
||||
});
|
||||
it('should return an error when the "updateSince" query parameter is a invalid date', async () => {
|
||||
const res = await request.get(api('emoji-custom.list')).query({ updatedSince: 'invalid-date' }).set(credentials).expect(400);
|
||||
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body.errorType).to.be.equal('error-roomId-param-invalid');
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/emoji-custom.all]', () => {
|
||||
it('should return emojis', async () => {
|
||||
const res = await request.get(api('emoji-custom.all')).set(credentials).expect(200);
|
||||
|
||||
expect(res.body).to.have.property('emojis').and.to.be.an('array');
|
||||
expect(res.body).to.have.property('total');
|
||||
expect(res.body).to.have.property('offset');
|
||||
expect(res.body).to.have.property('count');
|
||||
});
|
||||
it('should return emojis even requested with count and offset params', async () => {
|
||||
const res = await request
|
||||
.get(api('emoji-custom.all'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
count: 5,
|
||||
offset: 0,
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
expect(res.body).to.have.property('emojis').and.to.be.an('array');
|
||||
expect(res.body).to.have.property('total');
|
||||
expect(res.body).to.have.property('offset');
|
||||
expect(res.body).to.have.property('count');
|
||||
});
|
||||
it('should return only filtered by name emojis', async () => {
|
||||
const res = await request
|
||||
.get(api('emoji-custom.all'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
name: `${customEmojiName}-without-aliases`,
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
expect(res.body).to.have.property('emojis').and.to.be.an('array').and.to.have.lengthOf(1);
|
||||
expect(res.body).to.have.property('total');
|
||||
expect(res.body).to.have.property('offset');
|
||||
expect(res.body).to.have.property('count');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Accessing custom emojis', () => {
|
||||
let uploadDate: string | undefined;
|
||||
|
||||
it('should return forbidden if the there is no fileId on the url', async () => {
|
||||
const res = await request.get('/emoji-custom/').set(credentials).expect(403);
|
||||
|
||||
expect(res.text).to.be.equal('Forbidden');
|
||||
});
|
||||
|
||||
it('should return success if the file does not exists with some specific headers', async () => {
|
||||
const res = await request.get('/emoji-custom/invalid').set(credentials).expect(200);
|
||||
|
||||
expect(res.headers).to.have.property('last-modified', 'Thu, 01 Jan 2015 00:00:00 GMT');
|
||||
expect(res.headers).to.have.property('content-type', 'image/svg+xml');
|
||||
expect(res.headers).to.have.property('cache-control', 'public, max-age=0');
|
||||
expect(res.headers).to.have.property('expires', '-1');
|
||||
expect(res.headers).to.have.property('content-disposition', 'inline');
|
||||
});
|
||||
|
||||
it('should return not modified if the file does not exists and if-modified-since is equal to the Thu, 01 Jan 2015 00:00:00 GMT', async () => {
|
||||
const res = await request
|
||||
.get('/emoji-custom/invalid')
|
||||
.set(credentials)
|
||||
.set({
|
||||
'if-modified-since': 'Thu, 01 Jan 2015 00:00:00 GMT',
|
||||
})
|
||||
.expect(304);
|
||||
|
||||
expect(res.headers).to.have.property('last-modified', 'Thu, 01 Jan 2015 00:00:00 GMT');
|
||||
});
|
||||
|
||||
it('should return success if the the requested exists', async () => {
|
||||
const res = await request.get(`/emoji-custom/${customEmojiName}.png`).set(credentials).expect(200);
|
||||
|
||||
expect(res.headers).to.have.property('last-modified');
|
||||
expect(res.headers).to.have.property('content-type', 'image/png');
|
||||
expect(res.headers).to.have.property('cache-control', 'public, max-age=31536000');
|
||||
expect(res.headers).to.have.property('content-disposition', 'inline');
|
||||
uploadDate = res.headers['last-modified'];
|
||||
});
|
||||
|
||||
it('should return not modified if the the requested file contains a valid-since equal to the upload date', async () => {
|
||||
const res = await request
|
||||
.get(`/emoji-custom/${customEmojiName}.png`)
|
||||
.set(credentials)
|
||||
.set({
|
||||
'if-modified-since': uploadDate,
|
||||
})
|
||||
.expect(304);
|
||||
|
||||
expect(res.headers).to.have.property('last-modified', uploadDate);
|
||||
expect(res.headers).not.to.have.property('content-type');
|
||||
expect(res.headers).not.to.have.property('content-length');
|
||||
expect(res.headers).not.to.have.property('cache-control');
|
||||
});
|
||||
|
||||
it('should return the emoji even when no etag is passed (for old emojis)', async () => {
|
||||
const res = await request.get(`/emoji-custom/${createdCustomEmoji.name}.png`).set(credentials).expect(200);
|
||||
|
||||
expect(res.headers).to.have.property('content-type', 'image/png');
|
||||
expect(res.headers).to.have.property('cache-control', 'public, max-age=31536000');
|
||||
expect(res.headers).to.have.property('content-disposition', 'inline');
|
||||
});
|
||||
|
||||
it('should return success if the etag is invalid', async () => {
|
||||
const res = await request
|
||||
.get(`/emoji-custom/${createdCustomEmoji.name}.png?etag=1234`)
|
||||
.set(credentials)
|
||||
.set({
|
||||
'if-none-match': 'invalid-etag',
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
expect(res.headers).to.have.property('content-type', 'image/png');
|
||||
expect(res.headers).to.have.property('cache-control', 'public, max-age=31536000');
|
||||
expect(res.headers).to.have.property('content-disposition', 'inline');
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/emoji-custom.delete]', () => {
|
||||
it('should throw an error when trying delete custom emoji without the required param "emojid"', async () => {
|
||||
const res = await request
|
||||
.post(api('emoji-custom.delete'))
|
||||
.set(credentials)
|
||||
.send({})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body.error).to.be.equal("must have required property 'emojiId'");
|
||||
});
|
||||
it('should throw an error when trying delete custom emoji that does not exists', async () => {
|
||||
const res = await request
|
||||
.post(api('emoji-custom.delete'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
emojiId: 'invalid-id',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body.errorType).to.be.equal('Custom_Emoji_Error_Invalid_Emoji');
|
||||
});
|
||||
it('should delete the custom emoji created before successfully', async () => {
|
||||
const res = await request
|
||||
.post(api('emoji-custom.delete'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
emojiId: createdCustomEmoji._id,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
expect(res.body).to.have.property('success', true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Emoji storage settings reactivity', () => {
|
||||
const normalizeSvg = (svg: string) => svg.replace(/\r\n/g, '\n').trim();
|
||||
|
||||
const now = Date.now();
|
||||
const fsEmojiName = `emoji-fs-${now}`;
|
||||
const gridFsEmojiName = `emoji-gridfs-${now}`;
|
||||
const svgFallback = normalizeSvg(`<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" pointer-events="none" width="50" height="50" style="width: 50px; height: 50px; background-color: #000;">
|
||||
<text text-anchor="middle" y="50%" x="50%" dy="0.36em" pointer-events="auto" fill="#ffffff" font-family="Helvetica, Arial, Lucida Grande, sans-serif" style="font-weight: 400; font-size: 28px;">
|
||||
?
|
||||
</text>
|
||||
</svg>`);
|
||||
|
||||
before(async () => {
|
||||
await updateSetting('EmojiUpload_FileSystemPath', '', false);
|
||||
await updateSetting('EmojiUpload_Storage_Type', 'FileSystem');
|
||||
await request.post(api('emoji-custom.create')).set(credentials).attach('emoji', imgURL).field({ name: fsEmojiName }).expect(200);
|
||||
|
||||
await updateSetting('EmojiUpload_Storage_Type', 'GridFS');
|
||||
await request.post(api('emoji-custom.create')).set(credentials).attach('emoji', imgURL).field({ name: gridFsEmojiName }).expect(200);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
const list = await request.get(api('emoji-custom.all')).set(credentials);
|
||||
const fsEmoji = list.body.emojis.find((e: IEmojiCustom) => e.name === fsEmojiName);
|
||||
const gridEmoji = list.body.emojis.find((e: IEmojiCustom) => e.name === gridFsEmojiName);
|
||||
|
||||
await updateSetting('EmojiUpload_Storage_Type', 'FileSystem', false);
|
||||
await updateSetting('EmojiUpload_FileSystemPath', '');
|
||||
if (fsEmoji) {
|
||||
await request.post(api('emoji-custom.delete')).set(credentials).send({ emojiId: fsEmoji._id });
|
||||
}
|
||||
|
||||
await updateSetting('EmojiUpload_Storage_Type', 'GridFS');
|
||||
if (gridEmoji) {
|
||||
await request.post(api('emoji-custom.delete')).set(credentials).send({ emojiId: gridEmoji._id });
|
||||
}
|
||||
});
|
||||
|
||||
describe('EmojiUpload_Storage_Type', () => {
|
||||
describe('when storage is GridFs', () => {
|
||||
before(async () => {
|
||||
await updateSetting('EmojiUpload_Storage_Type', 'GridFS');
|
||||
});
|
||||
|
||||
it('should resolve GridFS files only', async () => {
|
||||
await request
|
||||
.get(`/emoji-custom/${fsEmojiName}.png`)
|
||||
.set(credentials)
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
const received = normalizeSvg(res.body.toString());
|
||||
expect(received).to.equal(svgFallback);
|
||||
});
|
||||
await request
|
||||
.get(`/emoji-custom/${gridFsEmojiName}.png`)
|
||||
.set(credentials)
|
||||
.expect(200)
|
||||
.expect((res) => expect(res.headers).to.have.property('content-type', 'image/png'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('when storage is FileSystem', () => {
|
||||
before(async () => {
|
||||
await updateSetting('EmojiUpload_Storage_Type', 'FileSystem');
|
||||
});
|
||||
|
||||
it('should resolve FileSystem files only', async () => {
|
||||
await request
|
||||
.get(`/emoji-custom/${fsEmojiName}.png`)
|
||||
.set(credentials)
|
||||
.expect(200)
|
||||
.expect((res) => expect(res.headers).to.have.property('content-type', 'image/png'));
|
||||
await request
|
||||
.get(`/emoji-custom/${gridFsEmojiName}.png`)
|
||||
.set(credentials)
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
const received = normalizeSvg(res.body.toString());
|
||||
expect(received).to.equal(svgFallback);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('EmojiUpload_FileSystemPath', () => {
|
||||
before(async () => {
|
||||
await updateSetting('EmojiUpload_Storage_Type', 'FileSystem');
|
||||
});
|
||||
|
||||
describe('when file system path is the default one', () => {
|
||||
before(async () => {
|
||||
await updateSetting('EmojiUpload_FileSystemPath', '');
|
||||
});
|
||||
|
||||
it('should resolve files', async () => {
|
||||
await request
|
||||
.get(`/emoji-custom/${fsEmojiName}.png`)
|
||||
.set(credentials)
|
||||
.expect(200)
|
||||
.expect((res) => expect(res.headers).to.have.property('content-type', 'image/png'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('when file system path is NOT the default one', () => {
|
||||
before(async () => {
|
||||
await updateSetting('EmojiUpload_FileSystemPath', '~/emoji-test');
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await updateSetting('CustomSounds_FileSystemPath', '');
|
||||
});
|
||||
|
||||
it('should NOT resolve files', async () => {
|
||||
await request
|
||||
.get(`/emoji-custom/${fsEmojiName}.png`)
|
||||
.set(credentials)
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
const received = normalizeSvg(res.body.toString());
|
||||
expect(received).to.equal(svgFallback);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
217
apps/meteor/tests/end-to-end/api/failed-login-attempts.ts
Normal file
217
apps/meteor/tests/end-to-end/api/failed-login-attempts.ts
Normal file
@@ -0,0 +1,217 @@
|
||||
import type { IUser } from '@rocket.chat/core-typings';
|
||||
import { expect } from 'chai';
|
||||
import { after, before, beforeEach, afterEach, describe, it } from 'mocha';
|
||||
|
||||
import { sleep } from '../../../lib/utils/sleep';
|
||||
import { getCredentials, api, request, credentials } from '../../data/api-data';
|
||||
import { updateSetting, updatePermission } from '../../data/permissions.helper';
|
||||
import { password } from '../../data/user';
|
||||
import type { TestUser } from '../../data/users.helper';
|
||||
import { createUser, deleteUser } from '../../data/users.helper';
|
||||
|
||||
describe('[Failed Login Attempts]', () => {
|
||||
const maxAttemptsByUser = 2;
|
||||
const maxAttemptsByIp = 4;
|
||||
const userBlockSeconds = 3;
|
||||
const ipBlockSeconds = 8;
|
||||
|
||||
before((done) => getCredentials(done));
|
||||
|
||||
before(() =>
|
||||
Promise.all([
|
||||
updateSetting('Block_Multiple_Failed_Logins_Enabled', true),
|
||||
updateSetting('Block_Multiple_Failed_Logins_By_Ip', true),
|
||||
updateSetting('Block_Multiple_Failed_Logins_By_User', true),
|
||||
updateSetting('Block_Multiple_Failed_Logins_Attempts_Until_Block_by_User', maxAttemptsByUser),
|
||||
updateSetting('Block_Multiple_Failed_Logins_Time_To_Unblock_By_User_In_Minutes', userBlockSeconds / 60),
|
||||
updateSetting('Block_Multiple_Failed_Logins_Attempts_Until_Block_By_Ip', maxAttemptsByIp),
|
||||
updateSetting('Block_Multiple_Failed_Logins_Time_To_Unblock_By_Ip_In_Minutes', ipBlockSeconds / 60),
|
||||
updatePermission('logout-other-user', ['admin']),
|
||||
]),
|
||||
);
|
||||
|
||||
after(() =>
|
||||
Promise.all([
|
||||
updateSetting('Block_Multiple_Failed_Logins_Attempts_Until_Block_by_User', 10),
|
||||
updateSetting('Block_Multiple_Failed_Logins_Time_To_Unblock_By_User_In_Minutes', 5),
|
||||
updateSetting('Block_Multiple_Failed_Logins_Attempts_Until_Block_By_Ip', 50),
|
||||
updateSetting('Block_Multiple_Failed_Logins_Time_To_Unblock_By_Ip_In_Minutes', 5),
|
||||
updateSetting('Block_Multiple_Failed_Logins_Enabled', true),
|
||||
updateSetting('Block_Multiple_Failed_Logins_By_Ip', true),
|
||||
updateSetting('Block_Multiple_Failed_Logins_By_User', true),
|
||||
updatePermission('logout-other-user', ['admin']),
|
||||
]),
|
||||
);
|
||||
|
||||
async function shouldFailLoginWithUser(username: string, password: string) {
|
||||
await request
|
||||
.post(api('login'))
|
||||
.send({
|
||||
user: username,
|
||||
password,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(401)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('status', 'error');
|
||||
expect(res.body).to.have.property('message', 'Unauthorized');
|
||||
});
|
||||
}
|
||||
|
||||
async function shouldSuccesfullyLoginWithUser(username: string, password: string) {
|
||||
await request
|
||||
.post(api('login'))
|
||||
.send({
|
||||
user: username,
|
||||
password,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('status', 'success');
|
||||
expect(res.body).to.have.property('data').and.to.be.an('object');
|
||||
expect(res.body.data).to.have.property('userId');
|
||||
expect(res.body.data).to.have.property('authToken');
|
||||
});
|
||||
}
|
||||
|
||||
async function shouldLogoutUser(uid: string) {
|
||||
await request.post(api('users.logout')).set(credentials).send({ userId: uid }).expect('Content-Type', 'application/json').expect(200);
|
||||
}
|
||||
|
||||
async function shouldBlockLogin(username: string, password: string, reason: 'user' | 'ip') {
|
||||
await request
|
||||
.post(api('login'))
|
||||
.send({
|
||||
user: username,
|
||||
password,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(401)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('status', 'error');
|
||||
expect(res.body).to.have.property('error', `error-login-blocked-for-${reason}`);
|
||||
});
|
||||
}
|
||||
|
||||
async function failMaxAttempts(username: string, password: string) {
|
||||
const promises = [];
|
||||
for (let i = 0; i < maxAttemptsByUser; i++) {
|
||||
promises.push(shouldFailLoginWithUser(username, password));
|
||||
}
|
||||
await Promise.all(promises);
|
||||
}
|
||||
|
||||
describe('[Block by User]', () => {
|
||||
let user: TestUser<IUser>;
|
||||
|
||||
before(async () => {
|
||||
await updateSetting('Block_Multiple_Failed_Logins_By_Ip', false);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await updateSetting('Block_Multiple_Failed_Logins_By_Ip', true);
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await createUser();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await deleteUser(user);
|
||||
});
|
||||
|
||||
it('should block by User when the limit amount of failed attempts is reached', async () => {
|
||||
await failMaxAttempts(user.username, `${password}-incorrect`);
|
||||
|
||||
await shouldBlockLogin(user.username, password, 'user');
|
||||
});
|
||||
|
||||
it('should unblock user after block time', async () => {
|
||||
await failMaxAttempts(user.username, `${password}-incorrect`);
|
||||
|
||||
await shouldBlockLogin(user.username, password, 'user');
|
||||
await sleep(userBlockSeconds * 1000);
|
||||
await shouldSuccesfullyLoginWithUser(user.username, password);
|
||||
});
|
||||
|
||||
it('should reset counter of failed attempts after a successful login', async () => {
|
||||
await failMaxAttempts(user.username, `${password}-incorrect`);
|
||||
|
||||
await shouldBlockLogin(user.username, password, 'user');
|
||||
await sleep(userBlockSeconds * 1000);
|
||||
|
||||
await shouldFailLoginWithUser(user.username, `${password}-incorrect`);
|
||||
await shouldSuccesfullyLoginWithUser(user.username, password);
|
||||
await shouldLogoutUser(user._id);
|
||||
await failMaxAttempts(user.username, `${password}-incorrect`);
|
||||
});
|
||||
|
||||
it('should count failed attempts by user', async () => {
|
||||
const newUser = await createUser();
|
||||
|
||||
await failMaxAttempts(user.username, `${password}-incorrect`);
|
||||
await shouldFailLoginWithUser(newUser.username, `${password}-incorrect`);
|
||||
await shouldSuccesfullyLoginWithUser(newUser.username, password);
|
||||
|
||||
await deleteUser(newUser);
|
||||
});
|
||||
});
|
||||
|
||||
describe('[Block by IP]', () => {
|
||||
let user: TestUser<IUser>;
|
||||
let user2: TestUser<IUser>;
|
||||
let userLogin: TestUser<IUser>;
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await createUser();
|
||||
user2 = await createUser();
|
||||
userLogin = await createUser();
|
||||
});
|
||||
|
||||
afterEach(() => Promise.all([deleteUser(user), deleteUser(user2), deleteUser(userLogin)]));
|
||||
|
||||
afterEach(async () => {
|
||||
// reset counter
|
||||
await sleep(ipBlockSeconds * 1000);
|
||||
});
|
||||
|
||||
it('should block by IP when trying to login with one user and the limit amount of failed attempts is reached', async () => {
|
||||
await failMaxAttempts(user.username, `${password}-incorrect`);
|
||||
await sleep(userBlockSeconds * 1000);
|
||||
await failMaxAttempts(user.username, `${password}-incorrect`);
|
||||
|
||||
await shouldBlockLogin(user.username, password, 'ip');
|
||||
});
|
||||
|
||||
it('should block by IP when trying to login with multiple users and the limit amount of failed attempts is reached', async () => {
|
||||
await failMaxAttempts(user.username, `${password}-incorrect`);
|
||||
await failMaxAttempts(user2.username, `${password}-incorrect`);
|
||||
|
||||
await shouldBlockLogin(userLogin.username, password, 'ip');
|
||||
});
|
||||
|
||||
it('should unblock IP after block time', async () => {
|
||||
await failMaxAttempts(user.username, `${password}-incorrect`);
|
||||
await failMaxAttempts(user2.username, `${password}-incorrect`);
|
||||
|
||||
await shouldBlockLogin(userLogin.username, password, 'ip');
|
||||
await sleep(ipBlockSeconds * 1000);
|
||||
await shouldSuccesfullyLoginWithUser(userLogin.username, password);
|
||||
}).timeout(20000);
|
||||
|
||||
it('should reset counter of failed attempts after a successful login', async () => {
|
||||
await failMaxAttempts(user.username, `${password}-incorrect`);
|
||||
await failMaxAttempts(user2.username, `${password}-incorrect`);
|
||||
|
||||
await sleep(ipBlockSeconds * 1000);
|
||||
await shouldFailLoginWithUser(userLogin.username, `${password}-incorrect`);
|
||||
await shouldSuccesfullyLoginWithUser(userLogin.username, password);
|
||||
await shouldLogoutUser(userLogin._id);
|
||||
|
||||
await failMaxAttempts(user.username, `${password}-incorrect`);
|
||||
await failMaxAttempts(user2.username, `${password}-incorrect`);
|
||||
await shouldBlockLogin(userLogin.username, password, 'ip');
|
||||
}).timeout(20000);
|
||||
});
|
||||
});
|
||||
49
apps/meteor/tests/end-to-end/api/federation.ts
Normal file
49
apps/meteor/tests/end-to-end/api/federation.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { expect } from 'chai';
|
||||
import { after, before, describe, it } from 'mocha';
|
||||
|
||||
import { getCredentials, request } from '../../data/api-data';
|
||||
import { updateSetting } from '../../data/permissions.helper';
|
||||
import { IS_EE } from '../../e2e/config/constants';
|
||||
|
||||
(IS_EE ? describe : describe.skip)('federation', () => {
|
||||
before((done) => getCredentials(done));
|
||||
|
||||
describe('well-known', () => {
|
||||
describe('when matrix disabled', () => {
|
||||
before(async () => {
|
||||
await updateSetting('Federation_Service_Enabled', false, false);
|
||||
await updateSetting('Federation_Service_Domain', 'localhost', false);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await updateSetting('Federation_Service_Enabled', false, false);
|
||||
});
|
||||
|
||||
it('should return 404 not found', async () => {
|
||||
await request.get('/.well-known/matrix/server').expect(403);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when enabled', () => {
|
||||
before(async () => {
|
||||
await updateSetting('Federation_Service_Enabled', true);
|
||||
await updateSetting('Federation_Service_Domain', 'rc.host');
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await updateSetting('Federation_Service_Enabled', false);
|
||||
await updateSetting('Federation_Service_Domain', '');
|
||||
});
|
||||
|
||||
it('should return matrix information', async () => {
|
||||
await request
|
||||
.get('/.well-known/matrix/server')
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('m.server', 'rc.host:443');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
145
apps/meteor/tests/end-to-end/api/file-upload-image-rotation.ts
Normal file
145
apps/meteor/tests/end-to-end/api/file-upload-image-rotation.ts
Normal file
@@ -0,0 +1,145 @@
|
||||
import path from 'path';
|
||||
|
||||
import type { Credentials } from '@rocket.chat/api-client';
|
||||
import type { ImageAttachmentProps, IRoom, IUser, SettingValue } from '@rocket.chat/core-typings';
|
||||
import { expect } from 'chai';
|
||||
import { after, before, describe, it } from 'mocha';
|
||||
import sharp from 'sharp';
|
||||
|
||||
import { getCredentials, request } from '../../data/api-data';
|
||||
import { uploadFileToRC } from '../../data/file.helper';
|
||||
import { getSettingValueById, updateSetting } from '../../data/permissions.helper';
|
||||
import { createRoom, deleteRoom } from '../../data/rooms.helper';
|
||||
import type { IRequestConfig, TestUser } from '../../data/users.helper';
|
||||
import { createUser, deleteUser, login } from '../../data/users.helper';
|
||||
|
||||
const testImagePath = path.join(__dirname, '../../mocks/files/exif-orientation-6.jpg');
|
||||
const testImageName = 'exif-orientation-6.jpg';
|
||||
|
||||
const downloadBuffer = async (url: string, auth: Credentials): Promise<Buffer> => {
|
||||
const response = await request.get(url).set(auth).buffer(true).expect(200);
|
||||
return response.body as Buffer;
|
||||
};
|
||||
|
||||
describe('[File Upload - Image Rotation]', () => {
|
||||
before((done) => getCredentials(done));
|
||||
|
||||
let user: TestUser<IUser>;
|
||||
const userPassword = `pass${Date.now()}`;
|
||||
let userCredentials: Credentials;
|
||||
let testRoom: IRoom;
|
||||
let rotateImagesSetting: SettingValue;
|
||||
let stripExifSetting: SettingValue;
|
||||
let thumbnailsEnabledSetting: SettingValue;
|
||||
let thumbnailsWidthSetting: SettingValue;
|
||||
let thumbnailsHeightSetting: SettingValue;
|
||||
|
||||
before(async () => {
|
||||
user = await createUser({ joinDefaultChannels: false, password: userPassword });
|
||||
userCredentials = await login(user.username, userPassword);
|
||||
testRoom = (await createRoom({ type: 'p', name: `rotate-upload-${Date.now()}`, members: [user.username] })).body.group;
|
||||
|
||||
rotateImagesSetting = await getSettingValueById('FileUpload_RotateImages');
|
||||
stripExifSetting = await getSettingValueById('Message_Attachments_Strip_Exif');
|
||||
thumbnailsEnabledSetting = await getSettingValueById('Message_Attachments_Thumbnails_Enabled');
|
||||
thumbnailsWidthSetting = await getSettingValueById('Message_Attachments_Thumbnails_Width');
|
||||
thumbnailsHeightSetting = await getSettingValueById('Message_Attachments_Thumbnails_Height');
|
||||
|
||||
await updateSetting('FileUpload_RotateImages', true, false);
|
||||
await updateSetting('Message_Attachments_Strip_Exif', true, false);
|
||||
await updateSetting('Message_Attachments_Thumbnails_Enabled', true);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await Promise.all([
|
||||
updateSetting('FileUpload_RotateImages', rotateImagesSetting),
|
||||
updateSetting('Message_Attachments_Strip_Exif', stripExifSetting),
|
||||
updateSetting('Message_Attachments_Thumbnails_Enabled', thumbnailsEnabledSetting),
|
||||
deleteRoom({ type: 'p', roomId: testRoom._id }),
|
||||
deleteUser(user),
|
||||
]);
|
||||
});
|
||||
|
||||
it('should rotate pixels and strip EXIF orientation', async () => {
|
||||
const fixtureMetadata = await sharp(testImagePath).metadata();
|
||||
expect(fixtureMetadata.width).to.equal(719);
|
||||
expect(fixtureMetadata.height).to.equal(479);
|
||||
expect(fixtureMetadata.orientation).to.equal(6);
|
||||
|
||||
const requestConfig: IRequestConfig = { request, credentials: userCredentials };
|
||||
const { message } = await uploadFileToRC(testRoom._id, testImagePath, 'rotation-exif-test', requestConfig);
|
||||
|
||||
expect(message).to.have.property('attachments');
|
||||
const attachment = message.attachments?.find((item) => item.title === testImageName);
|
||||
expect(attachment).to.be.an('object');
|
||||
|
||||
const fileUrl = (attachment as { title_link?: string }).title_link;
|
||||
const thumbUrl = (attachment as ImageAttachmentProps).image_url;
|
||||
|
||||
expect(fileUrl).to.be.a('string');
|
||||
expect(thumbUrl).to.be.a('string');
|
||||
|
||||
const originalBuffer = await downloadBuffer(fileUrl as string, userCredentials);
|
||||
const originalMetadata = await sharp(originalBuffer).metadata();
|
||||
|
||||
expect(originalMetadata.width).to.equal(479);
|
||||
expect(originalMetadata.height).to.equal(719);
|
||||
expect(originalMetadata.exif).to.be.undefined;
|
||||
});
|
||||
|
||||
it('should generate thumbnail from rotated image', async () => {
|
||||
const requestConfig: IRequestConfig = { request, credentials: userCredentials };
|
||||
const { message } = await uploadFileToRC(testRoom._id, testImagePath, 'rotation-thumb-test', requestConfig);
|
||||
|
||||
expect(message).to.have.property('attachments');
|
||||
const attachment = message.attachments?.find((item) => item.title === testImageName);
|
||||
expect(attachment).to.be.an('object');
|
||||
|
||||
const fileUrl = (attachment as { title_link?: string }).title_link;
|
||||
const thumbUrl = (attachment as ImageAttachmentProps).image_url;
|
||||
|
||||
expect(fileUrl).to.be.a('string');
|
||||
expect(thumbUrl).to.be.a('string');
|
||||
|
||||
const originalBuffer = await downloadBuffer(fileUrl as string, userCredentials);
|
||||
const originalMetadata = await sharp(originalBuffer).metadata();
|
||||
const thumbBuffer = await downloadBuffer(thumbUrl as string, userCredentials);
|
||||
const thumbMetadata = await sharp(thumbBuffer).metadata();
|
||||
const thumbWidthSetting = Number(thumbnailsWidthSetting);
|
||||
const thumbHeightSetting = Number(thumbnailsHeightSetting);
|
||||
const originalWidth = originalMetadata.width as number;
|
||||
const originalHeight = originalMetadata.height as number;
|
||||
const resizeFactor = Math.min(thumbWidthSetting / originalWidth, thumbHeightSetting / originalHeight);
|
||||
|
||||
expect(thumbMetadata.width).to.be.closeTo(Math.round(originalWidth * resizeFactor), 1);
|
||||
expect(thumbMetadata.height).to.be.closeTo(Math.round(originalHeight * resizeFactor), 1);
|
||||
});
|
||||
|
||||
describe('when FileUpload_RotateImages is disabled', () => {
|
||||
before(async () => {
|
||||
await updateSetting('FileUpload_RotateImages', false);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await updateSetting('FileUpload_RotateImages', rotateImagesSetting);
|
||||
});
|
||||
|
||||
it('should NOT rotate pixels', async () => {
|
||||
const requestConfig: IRequestConfig = { request, credentials: userCredentials };
|
||||
const { message } = await uploadFileToRC(testRoom._id, testImagePath, 'no-rotation-test', requestConfig);
|
||||
|
||||
expect(message).to.have.property('attachments');
|
||||
const attachment = message.attachments?.find((item) => item.title === testImageName);
|
||||
expect(attachment).to.be.an('object');
|
||||
|
||||
const fileUrl = (attachment as { title_link?: string }).title_link;
|
||||
expect(fileUrl).to.be.a('string');
|
||||
|
||||
const originalBuffer = await downloadBuffer(fileUrl as string, userCredentials);
|
||||
const originalMetadata = await sharp(originalBuffer).metadata();
|
||||
|
||||
expect(originalMetadata.width).to.equal(719);
|
||||
expect(originalMetadata.height).to.equal(479);
|
||||
});
|
||||
});
|
||||
});
|
||||
2922
apps/meteor/tests/end-to-end/api/groups.ts
Normal file
2922
apps/meteor/tests/end-to-end/api/groups.ts
Normal file
File diff suppressed because it is too large
Load Diff
118
apps/meteor/tests/end-to-end/api/guest-permissions.ts
Normal file
118
apps/meteor/tests/end-to-end/api/guest-permissions.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
import { expect } from 'chai';
|
||||
import { before, describe, it, after } from 'mocha';
|
||||
|
||||
import { getCredentials, api, request, credentials, methodCall } from '../../data/api-data';
|
||||
import { restorePermissionToRoles } from '../../data/permissions.helper';
|
||||
import { IS_EE } from '../../e2e/config/constants';
|
||||
|
||||
(IS_EE ? describe : describe.skip)('[Guest Permissions]', () => {
|
||||
const guestPermissions = ['view-d-room', 'view-joined-room', 'view-p-room', 'start-discussion', 'mobile-upload-file'];
|
||||
|
||||
before((done) => getCredentials(done));
|
||||
|
||||
after(() => Promise.all(guestPermissions.map((permissionName) => restorePermissionToRoles(permissionName))));
|
||||
|
||||
function succeedRemoveGuestPermission(permissionName: string) {
|
||||
it(`should allow removing the whitelisted permission ${permissionName} from the guest role`, async () => {
|
||||
const res = await request
|
||||
.post(methodCall('authorization:removeRoleFromPermission'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
message: JSON.stringify({
|
||||
method: 'authorization:removeRoleFromPermission',
|
||||
params: [permissionName, 'guest'],
|
||||
id: 'id',
|
||||
msg: 'method',
|
||||
}),
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('message');
|
||||
const message = JSON.parse(res.body.message);
|
||||
expect(message).to.not.have.property('error');
|
||||
|
||||
const permissionsListRes = await request
|
||||
.get(api('permissions.listAll'))
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
expect(permissionsListRes.body).to.have.property('success', true);
|
||||
expect(permissionsListRes.body).to.have.property('update').and.to.be.an('array');
|
||||
expect(permissionsListRes.body).to.have.property('remove').and.to.be.an('array');
|
||||
|
||||
const updatedPermission = permissionsListRes.body.update.find((permission: any) => permission._id === permissionName);
|
||||
expect(updatedPermission).to.have.property('_id', permissionName);
|
||||
expect(updatedPermission).to.have.property('roles').and.to.be.an('array').that.does.not.include('guest');
|
||||
});
|
||||
}
|
||||
|
||||
function succeedAddGuestPermission(permissionName: string) {
|
||||
it(`should allow granting the whitelisted permission ${permissionName} to the guest role`, async () => {
|
||||
const res = await request
|
||||
.post(methodCall('authorization:addPermissionToRole'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
message: JSON.stringify({
|
||||
method: 'authorization:addPermissionToRole',
|
||||
params: [permissionName, 'guest'],
|
||||
id: 'id',
|
||||
msg: 'method',
|
||||
}),
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('message');
|
||||
const message = JSON.parse(res.body.message);
|
||||
expect(message).to.not.have.property('error');
|
||||
|
||||
const permissionsListRes = await request
|
||||
.get(api('permissions.listAll'))
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
expect(permissionsListRes.body).to.have.property('success', true);
|
||||
expect(permissionsListRes.body).to.have.property('update').and.to.be.an('array');
|
||||
expect(permissionsListRes.body).to.have.property('remove').and.to.be.an('array');
|
||||
|
||||
const updatedPermission = permissionsListRes.body.update.find((permission: any) => permission._id === permissionName);
|
||||
expect(updatedPermission).to.have.property('_id', permissionName);
|
||||
expect(updatedPermission).to.have.property('roles').and.to.be.an('array').that.includes('guest');
|
||||
});
|
||||
}
|
||||
|
||||
describe('Default guest roles', () => {
|
||||
guestPermissions.forEach((permissionName) => {
|
||||
succeedRemoveGuestPermission(permissionName);
|
||||
});
|
||||
|
||||
guestPermissions.forEach((permissionName) => {
|
||||
succeedAddGuestPermission(permissionName);
|
||||
});
|
||||
|
||||
it('should not allow adding a non whitelisted permission to the guest role', async () => {
|
||||
const res = await request
|
||||
.post(methodCall('authorization:addPermissionToRole'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
message: JSON.stringify({
|
||||
method: 'authorization:addPermissionToRole',
|
||||
params: ['create-c', 'guest'],
|
||||
id: 'id',
|
||||
msg: 'method',
|
||||
}),
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('message');
|
||||
const message = JSON.parse(res.body.message);
|
||||
expect(message).to.have.property('error');
|
||||
expect(message.error).to.have.property('reason', 'Permission is restricted');
|
||||
});
|
||||
});
|
||||
});
|
||||
31
apps/meteor/tests/end-to-end/api/helpers/retry.ts
Normal file
31
apps/meteor/tests/end-to-end/api/helpers/retry.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
||||
/**
|
||||
* Retry a given function N times until it succeeds.
|
||||
* It should not be as workaround for eventual consistency, but in cases api calls have to be retried because of async tasks, it must be intentionally used.
|
||||
*
|
||||
* @param {Function} fn - Function that performs the assert/check.
|
||||
* @param {number} retries - Number of retries.
|
||||
* @param {number} delayMs - Delay between retries (ms).
|
||||
*/
|
||||
export async function retry(_description: string, fn: () => Promise<void> | void, options: { retries?: number; delayMs?: number } = {}) {
|
||||
const { retries = 3, delayMs = 10 } = options;
|
||||
let lastError;
|
||||
|
||||
await delay(delayMs);
|
||||
for (let i = 0; i <= retries; i++) {
|
||||
try {
|
||||
const result = fn();
|
||||
if (result instanceof Promise) {
|
||||
await result;
|
||||
}
|
||||
return;
|
||||
} catch (err) {
|
||||
lastError = err;
|
||||
if (i < retries && delayMs > 0) {
|
||||
await delay(delayMs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw lastError;
|
||||
}
|
||||
86
apps/meteor/tests/end-to-end/api/http-response-truncation.ts
Normal file
86
apps/meteor/tests/end-to-end/api/http-response-truncation.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import { expect } from 'chai';
|
||||
import { describe, it } from 'mocha';
|
||||
|
||||
import { apiUrl } from '../../data/api-data';
|
||||
|
||||
// Regression test for the dynamic-import response truncation flake (PR #41399).
|
||||
//
|
||||
// Through traefik with `serverstransport.maxidleconnsperhost=-1` (upstream
|
||||
// keep-alive disabled), Go's transport rarely closes its upstream connection
|
||||
// while the ReverseProxy is still copying a chunked gzip response body
|
||||
// ("use of closed network connection"), truncating the response mid-stream:
|
||||
// the browser gets 200 headers and then ERR_INCOMPLETE_CHUNKED_ENCODING, and
|
||||
// the meteor module loader hangs the page. Rate is ~1.6e-4 per request under
|
||||
// load, so this test replays the exact request shape (real dynamic-import
|
||||
// bodies captured from a failing CI run — the fatal one is the same
|
||||
// AppLayoutThemeWrapper.tsx request seen in every reproduction) at high
|
||||
// volume and concurrency: red on the broken proxy config, green once
|
||||
// upstream keep-alive is restored.
|
||||
//
|
||||
// The bodies are build-dependent fixtures: if the app tree renames these
|
||||
// modules the responses shrink below the gzip threshold and lose the
|
||||
// chunked+gzip shape the race needs, so the test asserts that shape first.
|
||||
|
||||
const FETCH_URL = `${apiUrl}/__meteor__/dynamic-import/fetch`;
|
||||
|
||||
const BODIES = [
|
||||
'{"client":{"components":{"AppLayoutThemeWrapper.tsx":1}}}',
|
||||
'{"client":{"meteor":{"login":{"index.ts":1,"cas.ts":1,"crowd.ts":1,"facebook.ts":1,"oauth.ts":1,"LoginCancelledError.ts":1,"google.ts":1,"ldap.ts":1,"meteorDeveloperAccount.ts":1,"password.ts":1,"saml.ts":1,"twitter.ts":1}},"lib":{"2fa":{"overrideLoginMethod.ts":1},"wrapRequestCredentialFn.ts":1,"loginServices.ts":1}},"node_modules":{"@rocket.chat":{"string-helpers":{"package.json":1,"dist":{"esm":{"index.js":1}}}}}}',
|
||||
];
|
||||
|
||||
const TOTAL_REQUESTS = 30000;
|
||||
const CONCURRENCY = 64;
|
||||
|
||||
const fetchModuleTree = async (body: string): Promise<{ error?: string }> => {
|
||||
try {
|
||||
const response = await fetch(FETCH_URL, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'text/plain;charset=UTF-8' },
|
||||
body,
|
||||
});
|
||||
if (response.status !== 200) {
|
||||
return { error: `unexpected status ${response.status}` };
|
||||
}
|
||||
await response.text();
|
||||
return {};
|
||||
} catch (error) {
|
||||
return { error: `${error} (cause: ${(error as { cause?: unknown }).cause})` };
|
||||
}
|
||||
};
|
||||
|
||||
describe('dynamic-import response delivery through the proxy', () => {
|
||||
it('should keep the chunked gzip response shape the race depends on', async () => {
|
||||
const response = await fetch(FETCH_URL, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'text/plain;charset=UTF-8' },
|
||||
body: BODIES[1],
|
||||
});
|
||||
const text = await response.text();
|
||||
expect(response.status).to.equal(200);
|
||||
expect(
|
||||
text.length,
|
||||
'fixture went stale: response too small to be gzip-chunked, update BODIES from a fresh browser trace',
|
||||
).to.be.greaterThan(10000);
|
||||
});
|
||||
|
||||
it(`should deliver ${TOTAL_REQUESTS} concurrent dynamic-import responses without a single truncation`, async function () {
|
||||
this.timeout(10 * 60 * 1000);
|
||||
|
||||
const failures: string[] = [];
|
||||
let issued = 0;
|
||||
|
||||
const worker = async (): Promise<void> => {
|
||||
while (issued < TOTAL_REQUESTS && failures.length === 0) {
|
||||
const current = issued++;
|
||||
const { error } = await fetchModuleTree(BODIES[current % BODIES.length]);
|
||||
if (error) {
|
||||
failures.push(`request #${current}: ${error}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
await Promise.all(Array.from({ length: CONCURRENCY }, worker));
|
||||
|
||||
expect(failures).to.deep.equal([]);
|
||||
});
|
||||
});
|
||||
216
apps/meteor/tests/end-to-end/api/import.spec.ts
Normal file
216
apps/meteor/tests/end-to-end/api/import.spec.ts
Normal file
@@ -0,0 +1,216 @@
|
||||
import { expect } from 'chai';
|
||||
import { after, before, describe, it } from 'mocha';
|
||||
import type { Response } from 'supertest';
|
||||
|
||||
import { sleep } from '../../../lib/utils/sleep';
|
||||
import { getCredentials, api, request, credentials } from '../../data/api-data';
|
||||
import { mockServerHealthy, mockServerReset, mockServerSet } from '../../data/mock-server.helper';
|
||||
import { getSettingValueById, updateSetting } from '../../data/permissions.helper';
|
||||
import { password } from '../../data/user';
|
||||
import { createUser, login, deleteUser } from '../../data/users.helper';
|
||||
import { withTimeout } from '../../data/utils';
|
||||
|
||||
const IMPORT_MOCK_SERVER_URL = process.env.IMPORT_MOCK_SERVER_URL ?? 'http://mock-server.dev:8080';
|
||||
|
||||
describe('Imports', () => {
|
||||
before((done) => getCredentials(done));
|
||||
|
||||
describe('[/getCurrentImportOperation]', () => {
|
||||
it('should return the current import operation', async () => {
|
||||
await request
|
||||
.get(api('getCurrentImportOperation'))
|
||||
.set(credentials)
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body.success).to.be.true;
|
||||
expect(res.body.operation).not.be.null;
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('[/downloadPendingFiles]', () => {
|
||||
it('should return the number of pending files', async () => {
|
||||
await request
|
||||
.post(api('downloadPendingFiles'))
|
||||
.set(credentials)
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body.success).to.be.true;
|
||||
expect(res.body.count).to.be.greaterThanOrEqual(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('[/downloadPendingAvatars]', () => {
|
||||
it('should return the number of pending avatars', async () => {
|
||||
await request
|
||||
.post(api('downloadPendingAvatars'))
|
||||
.set(credentials)
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body.success).to.be.true;
|
||||
expect(res.body.count).to.be.greaterThanOrEqual(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('[/getLatestImportOperations]', () => {
|
||||
let testUser: any = {};
|
||||
before(async () => {
|
||||
testUser = await createUser();
|
||||
});
|
||||
let testCredentials: any = {};
|
||||
before(async () => {
|
||||
testCredentials = await login(testUser.username, password);
|
||||
});
|
||||
after(async () => {
|
||||
await deleteUser(testUser);
|
||||
testUser = undefined;
|
||||
});
|
||||
|
||||
it('should fail if the user is not authorized', async () => {
|
||||
await request
|
||||
.get(api('getLatestImportOperations'))
|
||||
.set(testCredentials)
|
||||
.expect(403)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body.success).to.be.false;
|
||||
expect(res.body.error).to.equal('User does not have the permissions required for this action [error-unauthorized]');
|
||||
});
|
||||
});
|
||||
|
||||
it('should return the latest import operation', async () => {
|
||||
await request
|
||||
.get(api('getLatestImportOperations'))
|
||||
.set(credentials)
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.be.an('array');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/getImportProgress]', () => {
|
||||
it('should return the import progress', async () => {
|
||||
await request
|
||||
.get(api('getImportProgress'))
|
||||
.set(credentials)
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body.success).to.be.true;
|
||||
expect(res.body.key).to.be.an('string');
|
||||
expect(res.body.name).to.be.an('string');
|
||||
expect(res.body.step).to.be.an('string');
|
||||
expect(res.body.count).to.be.an('object');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/getImportFileData]', () => {
|
||||
it('should return the import file data', async () => {
|
||||
await request
|
||||
.get(api('getImportFileData'))
|
||||
.set(credentials)
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body.success).to.be.true;
|
||||
expect(res.body.users).to.be.an('array');
|
||||
expect(res.body.channels).to.be.an('array');
|
||||
expect(res.body.message_count).to.greaterThanOrEqual(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/downloadPublicImportFile]', () => {
|
||||
let previousSsrfAllowlist: Awaited<ReturnType<typeof getSettingValueById>>;
|
||||
|
||||
const waitForImportStep = (expectedStep: string): Promise<string> =>
|
||||
withTimeout(async (signal) => {
|
||||
let step = '';
|
||||
while (!signal.aborted) {
|
||||
const res = await request.get(api('getImportProgress')).set(credentials).expect(200);
|
||||
step = res.body.step;
|
||||
if (step === expectedStep || step === 'importer_import_failed') {
|
||||
return step;
|
||||
}
|
||||
await sleep(100);
|
||||
}
|
||||
return step;
|
||||
}, 10_000);
|
||||
|
||||
before(async () => {
|
||||
expect(await mockServerHealthy(), 'mock-server is not reachable — ensure it is running').to.be.true;
|
||||
previousSsrfAllowlist = await getSettingValueById('SSRF_Allowlist');
|
||||
await Promise.all([mockServerReset(), updateSetting('SSRF_Allowlist', '')]);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await Promise.all([
|
||||
mockServerReset(),
|
||||
previousSsrfAllowlist !== undefined ? updateSetting('SSRF_Allowlist', previousSsrfAllowlist) : Promise.resolve(),
|
||||
]);
|
||||
});
|
||||
|
||||
it('should reject a private target that is not on the SSRF allowlist and mark the operation as failed', async () => {
|
||||
await request
|
||||
.post(api('downloadPublicImportFile'))
|
||||
.set(credentials)
|
||||
.send({ fileUrl: 'http://127.0.0.1:3000/api/v1/info', importerKey: 'slack-users' })
|
||||
.expect(400)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body.success).to.be.false;
|
||||
expect(res.body.error).to.equal('error-ssrf-validation-failed');
|
||||
});
|
||||
|
||||
const progress = await request.get(api('getImportProgress')).set(credentials).expect(200);
|
||||
expect(progress.body.step).to.equal('importer_import_failed');
|
||||
});
|
||||
|
||||
it('should download a file from an allowlisted private target', async () => {
|
||||
await Promise.all([
|
||||
mockServerSet('GET', '/import-test.zip', { marker: 'downloaded-by-server-fetch' }),
|
||||
updateSetting('SSRF_Allowlist', new URL(IMPORT_MOCK_SERVER_URL).host),
|
||||
]);
|
||||
|
||||
await request
|
||||
.post(api('downloadPublicImportFile'))
|
||||
.set(credentials)
|
||||
.send({ fileUrl: `${IMPORT_MOCK_SERVER_URL}/import-test.zip`, importerKey: 'csv' })
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body.success).to.be.true;
|
||||
});
|
||||
|
||||
expect(await waitForImportStep('importer_file_loaded')).to.equal('importer_file_loaded');
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/uploadImportFile]', () => {
|
||||
let testUser: any = {};
|
||||
before(async () => {
|
||||
testUser = await createUser();
|
||||
});
|
||||
let testCredentials: any = {};
|
||||
before(async () => {
|
||||
testCredentials = await login(testUser.username, password);
|
||||
});
|
||||
after(async () => {
|
||||
await deleteUser(testUser);
|
||||
testUser = undefined;
|
||||
});
|
||||
|
||||
it('should fail if the user is not authorized', async () => {
|
||||
await request
|
||||
.post(api('uploadImportFile'))
|
||||
.set(testCredentials)
|
||||
.send({
|
||||
binaryContent: 'ZXJzLmNzdlBLBQYAAAAAAQABADcAAAAmAQAAAAA=',
|
||||
contentType: 'application/zip',
|
||||
fileName: 'users11.zip',
|
||||
importerKey: 'csv',
|
||||
})
|
||||
.expect(403)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body.success).to.be.false;
|
||||
expect(res.body.error).to.equal('User does not have the permissions required for this action [error-unauthorized]');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
1731
apps/meteor/tests/end-to-end/api/incoming-integrations.ts
Normal file
1731
apps/meteor/tests/end-to-end/api/incoming-integrations.ts
Normal file
File diff suppressed because it is too large
Load Diff
260
apps/meteor/tests/end-to-end/api/invites.ts
Normal file
260
apps/meteor/tests/end-to-end/api/invites.ts
Normal file
@@ -0,0 +1,260 @@
|
||||
import type { Credentials } from '@rocket.chat/api-client';
|
||||
import type { IInvite, IRoom, IUser } from '@rocket.chat/core-typings';
|
||||
import { expect } from 'chai';
|
||||
import { after, before, describe, it } from 'mocha';
|
||||
|
||||
import { getCredentials, api, request, credentials } from '../../data/api-data';
|
||||
import { createRoom, deleteRoom } from '../../data/rooms.helper';
|
||||
import { password } from '../../data/user';
|
||||
import type { TestUser } from '../../data/users.helper';
|
||||
import { createUser, deleteUser, login } from '../../data/users.helper';
|
||||
|
||||
describe('Invites', () => {
|
||||
let testInviteID: IInvite['_id'];
|
||||
|
||||
before((done) => getCredentials(done));
|
||||
describe('POST [/findOrCreateInvite]', () => {
|
||||
it('should fail if not logged in', async () => {
|
||||
const res = await request
|
||||
.post(api('findOrCreateInvite'))
|
||||
.send({
|
||||
rid: 'GENERAL',
|
||||
days: 1,
|
||||
maxUses: 10,
|
||||
})
|
||||
.expect(401);
|
||||
|
||||
expect(res.body).to.have.property('status', 'error');
|
||||
expect(res.body).to.have.property('message');
|
||||
});
|
||||
|
||||
it('should fail if invalid roomid', async () => {
|
||||
const res = await request
|
||||
.post(api('findOrCreateInvite'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
rid: 'invalid',
|
||||
days: 1,
|
||||
maxUses: 10,
|
||||
})
|
||||
.expect(400);
|
||||
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('errorType', 'error-invalid-room');
|
||||
});
|
||||
|
||||
it('should create an invite for GENERAL', async () => {
|
||||
const res = await request
|
||||
.post(api('findOrCreateInvite'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
rid: 'GENERAL',
|
||||
days: 1,
|
||||
maxUses: 10,
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('days', 1);
|
||||
expect(res.body).to.have.property('maxUses', 10);
|
||||
expect(res.body).to.have.property('uses');
|
||||
expect(res.body).to.have.property('_id');
|
||||
testInviteID = res.body._id;
|
||||
});
|
||||
|
||||
it('should return an existing invite for GENERAL', async () => {
|
||||
const res = await request
|
||||
.post(api('findOrCreateInvite'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
rid: 'GENERAL',
|
||||
days: 1,
|
||||
maxUses: 10,
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('days', 1);
|
||||
expect(res.body).to.have.property('maxUses', 10);
|
||||
expect(res.body).to.have.property('uses');
|
||||
expect(res.body).to.have.property('_id', testInviteID);
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET [/listInvites]', () => {
|
||||
it('should fail if not logged in', async () => {
|
||||
const res = await request.get(api('listInvites')).expect(401);
|
||||
|
||||
expect(res.body).to.have.property('status', 'error');
|
||||
expect(res.body).to.have.property('message');
|
||||
});
|
||||
|
||||
it('should return the existing invite for GENERAL', async () => {
|
||||
const res = await request.get(api('listInvites')).set(credentials).expect(200);
|
||||
|
||||
expect(res.body[0]).to.have.property('_id', testInviteID);
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST [/useInviteToken]', () => {
|
||||
it('should fail if not logged in', async () => {
|
||||
const res = await request.post(api('useInviteToken')).expect(401);
|
||||
|
||||
expect(res.body).to.have.property('status', 'error');
|
||||
expect(res.body).to.have.property('message');
|
||||
});
|
||||
|
||||
it('should fail if invalid token', async () => {
|
||||
const res = await request
|
||||
.post(api('useInviteToken'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
token: 'invalid',
|
||||
})
|
||||
.expect(400);
|
||||
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('errorType', 'error-invalid-token');
|
||||
});
|
||||
|
||||
it('should fail if missing token', async () => {
|
||||
const res = await request.post(api('useInviteToken')).set(credentials).send({}).expect(400);
|
||||
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('errorType', 'invalid-params');
|
||||
});
|
||||
|
||||
it('should use the existing invite for GENERAL', async () => {
|
||||
const res = await request
|
||||
.post(api('useInviteToken'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
token: testInviteID,
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
expect(res.body).to.have.property('success', true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST [/validateInviteToken]', () => {
|
||||
it('should warn if invalid token', async () => {
|
||||
const res = await request
|
||||
.post(api('validateInviteToken'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
token: 'invalid',
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('valid', false);
|
||||
});
|
||||
|
||||
it('should succeed when valid token', async () => {
|
||||
const res = await request
|
||||
.post(api('validateInviteToken'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
token: testInviteID,
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('valid', true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST [/useInviteToken] - banned user', () => {
|
||||
let room: IRoom;
|
||||
let bannedUser: TestUser<IUser>;
|
||||
let bannedUserCredentials: Credentials;
|
||||
let inviteId: IInvite['_id'];
|
||||
|
||||
before(async () => {
|
||||
bannedUser = await createUser();
|
||||
bannedUserCredentials = await login(bannedUser.username, password);
|
||||
|
||||
const result = await createRoom({ type: 'p', name: `invite-ban-test-${Date.now()}` });
|
||||
room = result.body.group;
|
||||
|
||||
// Add user then ban them
|
||||
await request.post(api('groups.invite')).set(credentials).send({ roomId: room._id, userId: bannedUser._id }).expect(200);
|
||||
await request.post(api('rooms.banUser')).set(credentials).send({ roomId: room._id, userId: bannedUser._id }).expect(200);
|
||||
|
||||
// Create invite link for the room
|
||||
const invite = await request
|
||||
.post(api('findOrCreateInvite'))
|
||||
.set(credentials)
|
||||
.send({ rid: room._id, days: 1, maxUses: 10 })
|
||||
.expect(200);
|
||||
inviteId = invite.body._id;
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await deleteRoom({ type: 'p', roomId: room._id });
|
||||
await deleteUser(bannedUser);
|
||||
});
|
||||
|
||||
it('should fail if user is banned from the room', async () => {
|
||||
await request
|
||||
.post(api('useInviteToken'))
|
||||
.set(bannedUserCredentials)
|
||||
.send({ token: inviteId })
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('errorType', 'error-user-is-banned');
|
||||
});
|
||||
});
|
||||
|
||||
it('should succeed after the user is unbanned', async () => {
|
||||
await request.post(api('rooms.unbanUser')).set(credentials).send({ roomId: room._id, userId: bannedUser._id }).expect(200);
|
||||
|
||||
await request
|
||||
.post(api('useInviteToken'))
|
||||
.set(bannedUserCredentials)
|
||||
.send({ token: inviteId })
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('room').and.to.have.property('rid', room._id);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('DELETE [/removeInvite]', () => {
|
||||
it('should fail if not logged in', async () => {
|
||||
const res = await request.delete(api(`removeInvite/${testInviteID}`)).expect(401);
|
||||
|
||||
expect(res.body).to.have.property('status', 'error');
|
||||
expect(res.body).to.have.property('message');
|
||||
});
|
||||
|
||||
it('should fail if invalid token', async () => {
|
||||
const res = await request.delete(api('removeInvite/invalid')).set(credentials).expect(400);
|
||||
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('errorType', 'invalid-invitation-id');
|
||||
});
|
||||
|
||||
it('should succeed when valid token', async () => {
|
||||
const res = await request
|
||||
.delete(api(`removeInvite/${testInviteID}`))
|
||||
.set(credentials)
|
||||
.expect(200);
|
||||
|
||||
expect(res.body).to.equal(true);
|
||||
});
|
||||
|
||||
it('should fail when deleting the same invite again', async () => {
|
||||
const res = await request
|
||||
.delete(api(`removeInvite/${testInviteID}`))
|
||||
.set(credentials)
|
||||
.expect(400);
|
||||
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('errorType', 'invalid-invitation-id');
|
||||
});
|
||||
});
|
||||
});
|
||||
99
apps/meteor/tests/end-to-end/api/licenses.ts
Normal file
99
apps/meteor/tests/end-to-end/api/licenses.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
import type { Credentials } from '@rocket.chat/api-client';
|
||||
import type { IUser } from '@rocket.chat/core-typings';
|
||||
import { expect } from 'chai';
|
||||
import { before, describe, it, after } from 'mocha';
|
||||
|
||||
import { getCredentials, api, request, credentials } from '../../data/api-data';
|
||||
import { password } from '../../data/user';
|
||||
import type { TestUser } from '../../data/users.helper';
|
||||
import { createUser, deleteUser, login } from '../../data/users.helper';
|
||||
|
||||
describe('licenses', () => {
|
||||
let createdUser: TestUser<IUser>;
|
||||
|
||||
before((done) => getCredentials(done));
|
||||
let unauthorizedUserCredentials: Credentials;
|
||||
|
||||
before(async () => {
|
||||
createdUser = await createUser();
|
||||
unauthorizedUserCredentials = await login(createdUser.username, password);
|
||||
});
|
||||
|
||||
after(() => deleteUser(createdUser));
|
||||
|
||||
describe('[/licenses.add]', () => {
|
||||
it('should fail if not logged in', async () => {
|
||||
const res = await request
|
||||
.post(api('licenses.add'))
|
||||
.send({
|
||||
license: '',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(401);
|
||||
|
||||
expect(res.body).to.have.property('status', 'error');
|
||||
expect(res.body).to.have.property('message');
|
||||
});
|
||||
|
||||
it('should fail if user is unauthorized', async () => {
|
||||
const res = await request
|
||||
.post(api('licenses.add'))
|
||||
.set(unauthorizedUserCredentials)
|
||||
.send({
|
||||
license: '',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(403);
|
||||
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error', 'User does not have the permissions required for this action [error-unauthorized]');
|
||||
});
|
||||
|
||||
it('should fail if license is invalid', async () => {
|
||||
const res = await request
|
||||
.post(api('licenses.add'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
license: '',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error');
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/licenses.info]', () => {
|
||||
it('should fail if not logged in', async () => {
|
||||
const res = await request.get(api('licenses.info')).expect('Content-Type', 'application/json').expect(401);
|
||||
|
||||
expect(res.body).to.have.property('status', 'error');
|
||||
expect(res.body).to.have.property('message');
|
||||
});
|
||||
|
||||
it('should return limited information if user is unauthorized', async () => {
|
||||
const res = await request
|
||||
.get(api('licenses.info'))
|
||||
.set(unauthorizedUserCredentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('license').and.to.be.an('object');
|
||||
expect(res.body.license).to.not.have.property('license');
|
||||
expect(res.body.license).to.have.property('tags').and.to.be.an('array');
|
||||
});
|
||||
|
||||
it('should return unrestricted info if user is logged in and is authorized', async () => {
|
||||
const res = await request.get(api('licenses.info')).set(credentials).expect(200);
|
||||
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('license').and.to.be.an('object');
|
||||
if (process.env.IS_EE) {
|
||||
expect(res.body.license).to.have.property('license').and.to.be.an('object');
|
||||
}
|
||||
expect(res.body.license).to.have.property('tags').and.to.be.an('array');
|
||||
});
|
||||
});
|
||||
});
|
||||
3796
apps/meteor/tests/end-to-end/api/livechat/00-rooms.ts
Normal file
3796
apps/meteor/tests/end-to-end/api/livechat/00-rooms.ts
Normal file
File diff suppressed because it is too large
Load Diff
788
apps/meteor/tests/end-to-end/api/livechat/01-agents.ts
Normal file
788
apps/meteor/tests/end-to-end/api/livechat/01-agents.ts
Normal file
@@ -0,0 +1,788 @@
|
||||
import type { Credentials } from '@rocket.chat/api-client';
|
||||
import { UserStatus } from '@rocket.chat/core-typings';
|
||||
import type { ILivechatVisitor, IOmnichannelRoom, ILivechatAgent, ILivechatDepartment, IRoom, IUser } from '@rocket.chat/core-typings';
|
||||
import { Random } from '@rocket.chat/random';
|
||||
import { expect } from 'chai';
|
||||
import { after, before, describe, it } from 'mocha';
|
||||
import type { Response } from 'supertest';
|
||||
|
||||
import { getCredentials, api, request, credentials } from '../../../data/api-data';
|
||||
import { disableDefaultBusinessHour, makeDefaultBusinessHourActiveAndClosed } from '../../../data/livechat/businessHours';
|
||||
import { createDepartment, deleteDepartment } from '../../../data/livechat/department';
|
||||
import {
|
||||
createAgent,
|
||||
createManager,
|
||||
createVisitor,
|
||||
createLivechatRoom,
|
||||
takeInquiry,
|
||||
fetchInquiry,
|
||||
makeAgentAvailable,
|
||||
startANewLivechatRoomAndTakeIt,
|
||||
moveBackToQueue,
|
||||
closeOmnichannelRoom,
|
||||
deleteVisitor,
|
||||
} from '../../../data/livechat/rooms';
|
||||
import {
|
||||
removePermissionFromAllRoles,
|
||||
restorePermissionToRoles,
|
||||
updateEESetting,
|
||||
updatePermission,
|
||||
updateSetting,
|
||||
} from '../../../data/permissions.helper';
|
||||
import { password } from '../../../data/user';
|
||||
import { createUser, deleteUser, getMe, login, setUserStatus } from '../../../data/users.helper';
|
||||
import { IS_EE } from '../../../e2e/config/constants';
|
||||
|
||||
describe('LIVECHAT - Agents', () => {
|
||||
let agent: ILivechatAgent;
|
||||
let manager: ILivechatAgent;
|
||||
|
||||
let agent2: { user: IUser; credentials: Credentials };
|
||||
|
||||
before((done) => getCredentials(done));
|
||||
|
||||
before(async () => {
|
||||
await updateSetting('Livechat_enabled', true);
|
||||
await updateSetting('Livechat_Routing_Method', 'Manual_Selection');
|
||||
await updateEESetting('Livechat_Require_Contact_Verification', 'never');
|
||||
await updateSetting('Omnichannel_enable_department_removal', true);
|
||||
agent = await createAgent();
|
||||
manager = await createManager();
|
||||
});
|
||||
|
||||
before(async () => {
|
||||
const user = await createUser();
|
||||
const userCredentials = await login(user.username, password);
|
||||
await createAgent(user.username);
|
||||
|
||||
await makeAgentAvailable(userCredentials);
|
||||
|
||||
agent2 = {
|
||||
user,
|
||||
credentials: userCredentials,
|
||||
};
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await updateSetting('Omnichannel_enable_department_removal', false);
|
||||
await deleteUser(agent2.user);
|
||||
});
|
||||
|
||||
// TODO: missing test cases for POST method
|
||||
describe('GET livechat/users/:type', () => {
|
||||
describe('with no permissions', () => {
|
||||
before(async () => {
|
||||
await updatePermission('edit-omnichannel-contact', []);
|
||||
await updatePermission('transfer-livechat-guest', []);
|
||||
await updatePermission('manage-livechat-agents', []);
|
||||
});
|
||||
after(async () => {
|
||||
await updatePermission('edit-omnichannel-contact', ['admin']);
|
||||
await updatePermission('transfer-livechat-guest', ['admin']);
|
||||
await updatePermission('manage-livechat-agents', ['admin']);
|
||||
});
|
||||
it('should return an "unauthorized error" when the user does not have the necessary permission', async () => {
|
||||
await request.get(api('livechat/users/agent')).set(credentials).expect('Content-Type', 'application/json').expect(403);
|
||||
});
|
||||
});
|
||||
it('should throw an error when the type is invalid', async () => {
|
||||
await request
|
||||
.get(api('livechat/users/invalid-type'))
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body.error).to.be.equal('Invalid type');
|
||||
});
|
||||
});
|
||||
it('should return an array of agents', async () => {
|
||||
await request
|
||||
.get(api('livechat/users/agent'))
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body.users).to.be.an('array');
|
||||
expect(res.body).to.have.property('offset');
|
||||
expect(res.body).to.have.property('total');
|
||||
expect(res.body).to.have.property('count');
|
||||
const agentRecentlyCreated = (res.body.users as ILivechatAgent[]).find((user) => agent._id === user._id);
|
||||
expect(agentRecentlyCreated?._id).to.be.equal(agent._id);
|
||||
});
|
||||
});
|
||||
it('should return an array of available agents', async () => {
|
||||
await request
|
||||
.get(api('livechat/users/agent'))
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.query({ onlyAvailable: true })
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body.users).to.be.an('array');
|
||||
expect(res.body).to.have.property('offset');
|
||||
expect(res.body).to.have.property('total');
|
||||
expect(res.body).to.have.property('count');
|
||||
expect(res.body.users.every((u: { statusLivechat: string }) => u.statusLivechat === 'available')).to.be.true;
|
||||
});
|
||||
});
|
||||
it('should return an array of available/unavailable agents when onlyAvailable is false', async () => {
|
||||
await request
|
||||
.get(api('livechat/users/agent'))
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.query({ onlyAvailable: false })
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body.users).to.be.an('array');
|
||||
expect(res.body).to.have.property('offset');
|
||||
expect(res.body).to.have.property('total');
|
||||
expect(res.body).to.have.property('count');
|
||||
expect(
|
||||
res.body.users.every(
|
||||
(u: { statusLivechat: string }) => !u.statusLivechat || ['available', 'not-available'].includes(u.statusLivechat),
|
||||
),
|
||||
).to.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
it('should return offline agents when showIdleAgents is true', async () => {
|
||||
await setUserStatus(agent2.credentials, UserStatus.OFFLINE);
|
||||
await request
|
||||
.get(api('livechat/users/agent'))
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.query({ showIdleAgents: true })
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body.users).to.be.an('array');
|
||||
expect(res.body).to.have.property('offset');
|
||||
expect(res.body).to.have.property('total');
|
||||
expect(res.body).to.have.property('count');
|
||||
expect(
|
||||
res.body.users.every(
|
||||
(u: { status: UserStatus }) =>
|
||||
!u.status || [UserStatus.ONLINE, UserStatus.OFFLINE, UserStatus.AWAY, UserStatus.BUSY].includes(u.status),
|
||||
),
|
||||
).to.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
it('should return only online agents when showIdleAgents is false', async () => {
|
||||
await setUserStatus(agent2.credentials, UserStatus.ONLINE);
|
||||
await request
|
||||
.get(api('livechat/users/agent'))
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.query({ showIdleAgents: false })
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body.users).to.be.an('array');
|
||||
expect(res.body).to.have.property('offset');
|
||||
expect(res.body).to.have.property('total');
|
||||
expect(res.body).to.have.property('count');
|
||||
expect(res.body.users.every((u: { status: UserStatus }) => u.status !== UserStatus.OFFLINE)).to.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
it('should return an array of managers', async () => {
|
||||
await request
|
||||
.get(api('livechat/users/manager'))
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body.users).to.be.an('array');
|
||||
expect(res.body).to.have.property('offset');
|
||||
expect(res.body).to.have.property('total');
|
||||
expect(res.body).to.have.property('count');
|
||||
const managerRecentlyCreated = (res.body.users as ILivechatAgent[]).find((user) => manager._id === user._id);
|
||||
expect(managerRecentlyCreated?._id).to.be.equal(manager._id);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST livechat/users/:type', () => {
|
||||
describe('with no permission', () => {
|
||||
before(async () => {
|
||||
await updatePermission('view-livechat-manager', []);
|
||||
});
|
||||
after(async () => {
|
||||
await updatePermission('view-livechat-manager', ['admin']);
|
||||
});
|
||||
it('should return an "unauthorized error" when the user does not have the necessary permission', async () => {
|
||||
await request
|
||||
.post(api('livechat/users/agent'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
username: 'test-agent',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(403);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return an error when type is invalid', async () => {
|
||||
await request
|
||||
.post(api('livechat/users/invalid-type'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
username: 'test-agent',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
it('should return an error when username is invalid', async () => {
|
||||
await request
|
||||
.post(api('livechat/users/agent'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
username: 'mr-not-valid',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
it('should return a valid user when all goes fine', async () => {
|
||||
const user = await createUser();
|
||||
await request
|
||||
.post(api('livechat/users/agent'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
username: user.username,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('user');
|
||||
expect(res.body.user).to.have.property('_id');
|
||||
expect(res.body.user).to.have.property('username');
|
||||
});
|
||||
|
||||
// cleanup
|
||||
await deleteUser(user);
|
||||
});
|
||||
|
||||
it('should properly create a manager', async () => {
|
||||
const user = await createUser();
|
||||
await request
|
||||
.post(api('livechat/users/manager'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
username: user.username,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('user');
|
||||
expect(res.body.user).to.have.property('_id');
|
||||
expect(res.body.user).to.have.property('username');
|
||||
});
|
||||
|
||||
// cleanup
|
||||
await deleteUser(user);
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET livechat/users/:type/:_id', () => {
|
||||
describe('with no permission', () => {
|
||||
before(async () => {
|
||||
await updatePermission('view-livechat-manager', []);
|
||||
});
|
||||
after(async () => {
|
||||
await updatePermission('view-livechat-manager', ['admin']);
|
||||
});
|
||||
it('should return an "unauthorized error" when the user does not have the necessary permission', async () => {
|
||||
await request
|
||||
.get(api(`livechat/users/agent/id${agent._id}`))
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(403);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with new user', () => {
|
||||
let user: IUser;
|
||||
before(async () => {
|
||||
user = await createUser();
|
||||
});
|
||||
after(async () => {
|
||||
await deleteUser(user);
|
||||
});
|
||||
it('should return an error when type is invalid', async () => {
|
||||
await request
|
||||
.get(api(`livechat/users/invalid-type/${agent._id}`))
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
}).timeout(5000);
|
||||
|
||||
it('should return a valid user when all goes fine', async () => {
|
||||
await request
|
||||
.get(api(`livechat/users/agent/${agent._id}`))
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('user');
|
||||
expect(res.body.user).to.have.property('_id');
|
||||
expect(res.body.user).to.have.property('username');
|
||||
expect(res.body.user).to.not.have.property('roles');
|
||||
});
|
||||
});
|
||||
|
||||
it('should return { user: null } when user is not an agent', async () => {
|
||||
await request
|
||||
.get(api(`livechat/users/agent/${user._id}`))
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('user');
|
||||
expect(res.body.user).to.be.null;
|
||||
});
|
||||
});
|
||||
|
||||
it('should return { user: null } when user is not a manager', async () => {
|
||||
await request
|
||||
.get(api(`livechat/users/manager/${user._id}`))
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('user');
|
||||
expect(res.body.user).to.be.null;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('DELETE livechat/users/:type/:_id', () => {
|
||||
describe('with no permission', () => {
|
||||
before(async () => {
|
||||
await updatePermission('view-livechat-manager', []);
|
||||
});
|
||||
after(async () => {
|
||||
await updatePermission('view-livechat-manager', ['admin']);
|
||||
});
|
||||
it('should return an "unauthorized error" when the user does not have the necessary permission', async () => {
|
||||
await request.delete(api(`livechat/users/agent/id`)).set(credentials).expect('Content-Type', 'application/json').expect(403);
|
||||
}).timeout(5000);
|
||||
});
|
||||
|
||||
it('should return an error when type is invalid', async () => {
|
||||
await request.delete(api(`livechat/users/invalid-type/id`)).set(credentials).expect('Content-Type', 'application/json').expect(400);
|
||||
}).timeout(5000);
|
||||
|
||||
it('should return an error when _id is invalid', async () => {
|
||||
await request.delete(api('livechat/users/agent/invalid-id')).set(credentials).expect('Content-Type', 'application/json').expect(400);
|
||||
}).timeout(5000);
|
||||
|
||||
it('should return a valid user when all goes fine', async () => {
|
||||
const agent = await createAgent();
|
||||
await request
|
||||
.delete(api(`livechat/users/agent/${agent._id}`))
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
});
|
||||
});
|
||||
|
||||
(IS_EE ? describe : describe.skip)('livechat/agents/:agentId/departments', () => {
|
||||
let dep1: ILivechatDepartment;
|
||||
let dep2: ILivechatDepartment;
|
||||
before(async () => {
|
||||
dep1 = await createDepartment(
|
||||
{
|
||||
enabled: true,
|
||||
name: Random.id(),
|
||||
showOnRegistration: true,
|
||||
email: `${Random.id()}@example.com`,
|
||||
showOnOfflineForm: true,
|
||||
},
|
||||
[{ agentId: credentials['X-User-Id'] }],
|
||||
);
|
||||
dep2 = await createDepartment(
|
||||
{
|
||||
enabled: false,
|
||||
name: Random.id(),
|
||||
email: `${Random.id()}@example.com`,
|
||||
showOnRegistration: true,
|
||||
showOnOfflineForm: true,
|
||||
},
|
||||
[{ agentId: credentials['X-User-Id'] }],
|
||||
);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await deleteDepartment(dep1._id);
|
||||
await deleteDepartment(dep2._id);
|
||||
});
|
||||
describe('with no permission', () => {
|
||||
before(async () => {
|
||||
await updatePermission('view-l-room', []);
|
||||
});
|
||||
after(async () => {
|
||||
await updatePermission('view-l-room', ['livechat-manager', 'livechat-agent', 'admin']);
|
||||
});
|
||||
it('should return an "unauthorized error" when the user does not have the necessary permission', async () => {
|
||||
await request
|
||||
.get(api(`livechat/agents/${agent._id}/departments`))
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(403);
|
||||
});
|
||||
});
|
||||
it('should return an empty array of departments when the agentId is invalid', async () => {
|
||||
await request
|
||||
.get(api('livechat/agents/invalid-id/departments'))
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('departments').and.to.be.an('array');
|
||||
});
|
||||
});
|
||||
it('should return an array of departments when the agentId is valid', async () => {
|
||||
await request
|
||||
.get(api(`livechat/agents/${agent._id}/departments`))
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('departments').and.to.be.an('array');
|
||||
expect(res.body.departments.length).to.be.equal(2);
|
||||
(res.body.departments as ILivechatDepartment[]).forEach((department) => {
|
||||
expect(department.agentId).to.be.equal(agent._id);
|
||||
expect(department).to.have.property('departmentName').that.is.a('string');
|
||||
});
|
||||
});
|
||||
});
|
||||
it('should return only enabled departments when param `enabledDepartmentsOnly` is true ', async () => {
|
||||
await request
|
||||
.get(api(`livechat/agents/${agent._id}/departments`))
|
||||
.set(credentials)
|
||||
.query({ enabledDepartmentsOnly: true })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('departments').and.to.be.an('array');
|
||||
expect(res.body.departments.length).to.be.equal(1);
|
||||
expect(res.body.departments[0].departmentEnabled).to.be.true;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('livechat/agent.info/:rid/:token', () => {
|
||||
let room: IOmnichannelRoom;
|
||||
let visitor: ILivechatVisitor;
|
||||
before(async () => {
|
||||
visitor = await createVisitor();
|
||||
room = await createLivechatRoom(visitor.token);
|
||||
});
|
||||
after(async () => {
|
||||
await closeOmnichannelRoom(room._id);
|
||||
await deleteVisitor(visitor.token);
|
||||
});
|
||||
it('should fail when token in url params is not valid', async () => {
|
||||
await request.get(api(`livechat/agent.info/soemthing/invalid-token`)).expect(400);
|
||||
});
|
||||
it('should fail when token is valid but rid isnt', async () => {
|
||||
const visitor = await createVisitor();
|
||||
await request.get(api(`livechat/agent.info/invalid-rid/${visitor.token}`)).expect(400);
|
||||
await deleteVisitor(visitor.token);
|
||||
});
|
||||
/* it('should fail when room is not being served by any agent', async () => {
|
||||
await updateSetting('Livechat_Routing_Method', 'Manual_Selection');
|
||||
const visitor = await createVisitor();
|
||||
const room = await createLivechatRoom(visitor.token);
|
||||
await request.get(api(`livechat/agent.info/${room._id}/${visitor.token}`)).expect(400);
|
||||
}); */
|
||||
it('should return a valid agent when the room is being served and the room belongs to visitor', async () => {
|
||||
const inq = await fetchInquiry(room._id);
|
||||
await takeInquiry(inq._id, agent2.credentials);
|
||||
|
||||
const { body } = await request.get(api(`livechat/agent.info/${room._id}/${visitor.token}`));
|
||||
expect(body).to.have.property('success', true);
|
||||
expect(body).to.have.property('agent');
|
||||
expect(body.agent).to.have.property('_id', agent2.user._id);
|
||||
});
|
||||
});
|
||||
|
||||
describe('livechat/agent.next/:token', () => {
|
||||
let visitor: ILivechatVisitor;
|
||||
let room: IOmnichannelRoom;
|
||||
before(async () => {
|
||||
visitor = await createVisitor();
|
||||
room = await createLivechatRoom(visitor.token);
|
||||
});
|
||||
after(async () => {
|
||||
await closeOmnichannelRoom(room._id);
|
||||
await deleteVisitor(visitor.token);
|
||||
});
|
||||
it('should fail when token in url params is not valid', async () => {
|
||||
await request.get(api(`livechat/agent.next/invalid-token`)).expect(400);
|
||||
});
|
||||
it('should return success when visitor with token has an open room', async () => {
|
||||
await request.get(api(`livechat/agent.next/${visitor.token}`)).expect(200);
|
||||
});
|
||||
|
||||
// TODO: test cases when algo is Auto_Selection
|
||||
});
|
||||
|
||||
describe('livechat/agent.next/:token - with manual selection', () => {
|
||||
before(async () => {
|
||||
await updateSetting('Livechat_Routing_Method', 'Manual_Selection');
|
||||
});
|
||||
after(async () => {
|
||||
await updateSetting('Livechat_Routing_Method', 'Auto_Selection');
|
||||
});
|
||||
it('should fail if theres no open room for visitor and algo is manual selection', async () => {
|
||||
const visitor = await createVisitor();
|
||||
|
||||
await request.get(api(`livechat/agent.next/${visitor.token}`)).expect(400);
|
||||
|
||||
await deleteVisitor(visitor.token);
|
||||
});
|
||||
});
|
||||
|
||||
describe('livechat/agent.status', () => {
|
||||
describe('with no view-l permission', () => {
|
||||
before(async () => {
|
||||
await removePermissionFromAllRoles('view-l-room');
|
||||
});
|
||||
after(async () => {
|
||||
await restorePermissionToRoles('view-l-room');
|
||||
});
|
||||
it('should return an "unauthorized error" when the user does not have the necessary permission to change its own status', async () => {
|
||||
await request.post(api('livechat/agent.status')).set(credentials).send({ status: 'not-available' }).expect(403);
|
||||
});
|
||||
});
|
||||
describe('with no manage-agent permission', () => {
|
||||
before(async () => {
|
||||
await request
|
||||
.post(api('livechat/agent.status'))
|
||||
.set(credentials)
|
||||
.send({ status: 'not-available', agentId: agent2.user._id })
|
||||
.expect(200);
|
||||
await removePermissionFromAllRoles('manage-livechat-agents');
|
||||
});
|
||||
after(async () => {
|
||||
await restorePermissionToRoles('manage-livechat-agents');
|
||||
});
|
||||
|
||||
it('should return an "unauthorized error" when the user does not have the necessary permission to change other status', async () => {
|
||||
await request
|
||||
.post(api('livechat/agent.status'))
|
||||
.set(credentials)
|
||||
.send({ status: 'available', agentId: agent2.user._id })
|
||||
.expect(403);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return an error if user is not an agent', async () => {
|
||||
const user = await createUser({ roles: ['livechat-manager'] });
|
||||
const userCredentials = await login(user.username, password);
|
||||
await request
|
||||
.post(api('livechat/agent.status'))
|
||||
.set(userCredentials)
|
||||
.send({ status: 'available', agentId: user._id })
|
||||
.expect(404)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error', 'Agent not found');
|
||||
});
|
||||
|
||||
// cleanup
|
||||
await deleteUser(user);
|
||||
});
|
||||
it('should return an error if status is not valid', async () => {
|
||||
await request
|
||||
.post(api('livechat/agent.status'))
|
||||
.set(agent2.credentials)
|
||||
.send({ status: 'invalid-status', agentId: agent2.user._id })
|
||||
.expect(400)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error');
|
||||
});
|
||||
});
|
||||
it('should return an error if agentId param is not valid', async () => {
|
||||
await request
|
||||
.post(api('livechat/agent.status'))
|
||||
.set(agent2.credentials)
|
||||
.send({ status: 'available', agentId: 'invalid-agent-id' })
|
||||
.expect(404)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error', 'Agent not found');
|
||||
});
|
||||
});
|
||||
|
||||
describe('cases for valid agents', () => {
|
||||
let currentUser: ILivechatAgent;
|
||||
before(async () => {
|
||||
currentUser = await getMe(agent2.credentials);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await request
|
||||
.post(api('livechat/agent.status'))
|
||||
.set(agent2.credentials)
|
||||
.send({ status: 'not-available', agentId: currentUser._id })
|
||||
.expect(200);
|
||||
});
|
||||
|
||||
it('should be able to set a logged in users status to available', async () => {
|
||||
await request
|
||||
.post(api('livechat/agent.status'))
|
||||
.set(agent2.credentials)
|
||||
.send({ status: 'available', agentId: currentUser._id })
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('status', 'available');
|
||||
});
|
||||
});
|
||||
it('should allow managers to set other agents status to available', async () => {
|
||||
await request
|
||||
.post(api('livechat/agent.status'))
|
||||
.set(credentials)
|
||||
.send({ status: 'available', agentId: currentUser._id })
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('status', 'available');
|
||||
});
|
||||
});
|
||||
describe('outside of business hours', () => {
|
||||
before(async () => {
|
||||
await makeDefaultBusinessHourActiveAndClosed();
|
||||
|
||||
await request
|
||||
.post(api('livechat/agent.status'))
|
||||
.set(agent2.credentials)
|
||||
.send({ status: 'not-available', agentId: currentUser._id })
|
||||
.expect(200);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await disableDefaultBusinessHour();
|
||||
});
|
||||
it('should throw an error if agent tries to make themselves available outside of Business hour', async () => {
|
||||
await request
|
||||
.post(api('livechat/agent.status'))
|
||||
.set(agent2.credentials)
|
||||
.send({ status: 'available', agentId: currentUser._id })
|
||||
.expect(400)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error', 'error-business-hours-are-closed');
|
||||
});
|
||||
});
|
||||
it('should return success but not change the agent when admin make try other agents available outside business hour', async () => {
|
||||
await request
|
||||
.post(api('livechat/agent.status'))
|
||||
.set(credentials)
|
||||
.send({ status: 'available', agentId: currentUser._id })
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('status', 'not-available');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Agent sidebar', () => {
|
||||
let testUser: { user: IUser; credentials: Credentials };
|
||||
before(async () => {
|
||||
const user = await createUser();
|
||||
await createAgent(user.username);
|
||||
const credentials2 = await login(user.username, password);
|
||||
await makeAgentAvailable(credentials2);
|
||||
|
||||
testUser = {
|
||||
user,
|
||||
credentials: credentials2,
|
||||
};
|
||||
});
|
||||
after(async () => {
|
||||
await deleteUser(testUser.user);
|
||||
});
|
||||
|
||||
it('should return an empty list of rooms for a newly created agent', async () => {
|
||||
const { body } = await request.get(api('rooms.get')).set(testUser.credentials).send({}).expect(200);
|
||||
|
||||
expect(body).to.have.property('success', true);
|
||||
expect(body.update.filter((r: IRoom) => r.t === 'l')).to.have.lengthOf(0);
|
||||
});
|
||||
|
||||
describe('with a room', () => {
|
||||
let room: IOmnichannelRoom;
|
||||
let visitor: ILivechatVisitor;
|
||||
|
||||
before(async () => {
|
||||
const { room: r, visitor: v } = await startANewLivechatRoomAndTakeIt({ agent: testUser.credentials });
|
||||
room = r;
|
||||
visitor = v;
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await closeOmnichannelRoom(room._id);
|
||||
await deleteVisitor(visitor.token);
|
||||
});
|
||||
|
||||
it('should have a new room in his sidebar after taking a conversation from the queue', async () => {
|
||||
const { body } = await request.get(api('rooms.get')).set(testUser.credentials).send({}).expect(200);
|
||||
|
||||
expect(body).to.have.property('success', true);
|
||||
const livechatRooms = body.update.filter((r: IRoom) => r.t === 'l');
|
||||
expect(livechatRooms).to.have.lengthOf(1);
|
||||
expect(body.update.find((r: { _id: string }) => r._id === room._id)).to.be.an('object');
|
||||
expect(body.update.find((r: { _id: string }) => r._id === 'GENERAL')).to.be.an('object');
|
||||
});
|
||||
|
||||
it('should not have the room if user moves room back to queue', async () => {
|
||||
await moveBackToQueue(room._id, testUser.credentials);
|
||||
|
||||
const { body } = await request
|
||||
.get(api('rooms.get'))
|
||||
.set(testUser.credentials)
|
||||
.query({ updatedSince: new Date(new Date().getTime() - 2000) })
|
||||
.expect(200);
|
||||
|
||||
expect(body).to.have.property('success', true);
|
||||
expect(body.update.find((r: { _id: string }) => r._id === room._id)).to.be.undefined;
|
||||
});
|
||||
});
|
||||
|
||||
it('should not have the room if the user closes the room', async () => {
|
||||
const { room, visitor } = await startANewLivechatRoomAndTakeIt({ agent: testUser.credentials });
|
||||
|
||||
await closeOmnichannelRoom(room._id);
|
||||
|
||||
const { body } = await request.get(api('rooms.get')).set(testUser.credentials).expect(200);
|
||||
|
||||
expect(body.update.find((r: { _id: string }) => r._id === room._id)).to.be.undefined;
|
||||
|
||||
await deleteVisitor(visitor.token);
|
||||
});
|
||||
});
|
||||
});
|
||||
265
apps/meteor/tests/end-to-end/api/livechat/02-appearance.ts
Normal file
265
apps/meteor/tests/end-to-end/api/livechat/02-appearance.ts
Normal file
@@ -0,0 +1,265 @@
|
||||
import { expect } from 'chai';
|
||||
import { before, describe, it } from 'mocha';
|
||||
import type { Response } from 'supertest';
|
||||
|
||||
import { getCredentials, api, request, credentials } from '../../../data/api-data';
|
||||
import { sleep } from '../../../data/livechat/utils';
|
||||
import { removePermissionFromAllRoles, restorePermissionToRoles, updatePermission, updateSetting } from '../../../data/permissions.helper';
|
||||
import { IS_EE } from '../../../e2e/config/constants';
|
||||
|
||||
describe('LIVECHAT - appearance', () => {
|
||||
before((done) => getCredentials(done));
|
||||
|
||||
before(async () => {
|
||||
await updateSetting('Livechat_enabled', true);
|
||||
});
|
||||
|
||||
describe('livechat/appearance', () => {
|
||||
it('should return an "unauthorized error" when the user does not have the necessary permission', async () => {
|
||||
await updatePermission('view-livechat-manager', []);
|
||||
await request.get(api('livechat/appearance')).set(credentials).expect('Content-Type', 'application/json').expect(403);
|
||||
});
|
||||
it('should return an array of settings', async () => {
|
||||
await updatePermission('view-livechat-manager', ['admin']);
|
||||
await request
|
||||
.get(api('livechat/appearance'))
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body.appearance).to.be.an('array');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST livechat/appearance', () => {
|
||||
it('should fail if user is not logged in', async () => {
|
||||
await request.post(api('livechat/appearance')).send({}).expect(401);
|
||||
});
|
||||
it('should fail if body is not an array', async () => {
|
||||
await request.post(api('livechat/appearance')).set(credentials).send({}).expect(400);
|
||||
});
|
||||
it('should fail if body is an empty array', async () => {
|
||||
await request.post(api('livechat/appearance')).set(credentials).send([]).expect(400);
|
||||
});
|
||||
it('should fail if body does not contain value', async () => {
|
||||
await request
|
||||
.post(api('livechat/appearance'))
|
||||
.set(credentials)
|
||||
.send([{ name: 'Livechat_title' }])
|
||||
.expect(400);
|
||||
});
|
||||
it('should fail if body does not contain name', async () => {
|
||||
await request
|
||||
.post(api('livechat/appearance'))
|
||||
.set(credentials)
|
||||
.send([{ value: 'test' }])
|
||||
.expect(400);
|
||||
});
|
||||
it('should fail if user does not have the necessary permission', async () => {
|
||||
await removePermissionFromAllRoles('view-livechat-manager');
|
||||
await request
|
||||
.post(api('livechat/appearance'))
|
||||
.set(credentials)
|
||||
.send([{ _id: 'invalid', value: 'test' }])
|
||||
.expect(403);
|
||||
});
|
||||
it('should fail if body contains invalid _id', async () => {
|
||||
await restorePermissionToRoles('view-livechat-manager');
|
||||
await request
|
||||
.post(api('livechat/appearance'))
|
||||
.set(credentials)
|
||||
.send([{ _id: 'invalid', value: 'test' }])
|
||||
.expect(400);
|
||||
});
|
||||
it('should update the settings', async () => {
|
||||
await request
|
||||
.post(api('livechat/appearance'))
|
||||
.set(credentials)
|
||||
.send([{ _id: 'Livechat_title', value: 'test' }])
|
||||
.expect(200);
|
||||
});
|
||||
// Test for: https://github.com/ajv-validator/ajv/issues/1140
|
||||
it('should update a boolean setting and keep it as boolean', async () => {
|
||||
await request
|
||||
.post(api('livechat/appearance'))
|
||||
.set(credentials)
|
||||
.send([{ _id: 'Livechat_registration_form', value: true }])
|
||||
.expect(200);
|
||||
|
||||
// Just enough to get the stream to update cached settings
|
||||
await sleep(500);
|
||||
|
||||
// Get data from livechat/config
|
||||
const { body } = await request.get(api('livechat/config')).set(credentials).expect(200);
|
||||
expect(body.config.settings.registrationForm).to.be.true;
|
||||
});
|
||||
it('should update a boolean setting and keep it as boolean', async () => {
|
||||
await request
|
||||
.post(api('livechat/appearance'))
|
||||
.set(credentials)
|
||||
.send([{ _id: 'Livechat_registration_form', value: false }])
|
||||
.expect(200);
|
||||
|
||||
// Just enough to get the stream to update cached settings
|
||||
await sleep(500);
|
||||
// Get data from livechat/config
|
||||
const { body } = await request.get(api('livechat/config')).set(credentials).expect(200);
|
||||
expect(body.config.settings.registrationForm).to.be.false;
|
||||
});
|
||||
it('should update a number setting and keep it as number', async () => {
|
||||
await updateSetting('Livechat_enable_message_character_limit', true);
|
||||
await request
|
||||
.post(api('livechat/appearance'))
|
||||
.set(credentials)
|
||||
.send([{ _id: 'Livechat_message_character_limit', value: 100 }])
|
||||
.expect(200);
|
||||
|
||||
// Just enough to get the stream to update cached settings
|
||||
await sleep(500);
|
||||
|
||||
// Get data from livechat/config
|
||||
const { body } = await request.get(api('livechat/config')).set(credentials).expect(200);
|
||||
expect(body.config.settings.limitTextLength).to.be.equal(100);
|
||||
await updateSetting('Livechat_enable_message_character_limit', false);
|
||||
});
|
||||
it('should coerce the value of a setting based on its stored datatype (int)', async () => {
|
||||
await updateSetting('Livechat_enable_message_character_limit', true);
|
||||
await request
|
||||
.post(api('livechat/appearance'))
|
||||
.set(credentials)
|
||||
.send([{ _id: 'Livechat_message_character_limit', value: '100' }])
|
||||
.expect(200);
|
||||
// Just enough to get the stream to update cached settings
|
||||
await sleep(500);
|
||||
// Get data from livechat/config
|
||||
const { body } = await request.get(api('livechat/config')).set(credentials).expect(200);
|
||||
expect(body.config.settings.limitTextLength).to.be.equal(100);
|
||||
await updateSetting('Livechat_enable_message_character_limit', false);
|
||||
});
|
||||
it('should coerce the value of a setting based on its stored datatype (boolean)', async () => {
|
||||
await request
|
||||
.post(api('livechat/appearance'))
|
||||
.set(credentials)
|
||||
.send([{ _id: 'Livechat_registration_form', value: 'true' }])
|
||||
.expect(200);
|
||||
|
||||
// Just enough to get the stream to update cached settings
|
||||
await sleep(500);
|
||||
// Get data from livechat/config
|
||||
const { body } = await request.get(api('livechat/config')).set(credentials).expect(200);
|
||||
expect(body.config.settings.registrationForm).to.be.true;
|
||||
});
|
||||
it('should coerce an invalid number value to zero', async () => {
|
||||
await request
|
||||
.post(api('livechat/appearance'))
|
||||
.set(credentials)
|
||||
.send([
|
||||
{ _id: 'Livechat_message_character_limit', value: 'xxxx' },
|
||||
{ _id: 'Livechat_enable_message_character_limit', value: true },
|
||||
])
|
||||
.expect(200);
|
||||
|
||||
// Just enough to get the stream to update cached settings
|
||||
await sleep(500);
|
||||
// Get data from livechat/config
|
||||
const { body } = await request.get(api('livechat/config')).set(credentials).expect(200);
|
||||
// When setting is 0, we default to Message_MaxAllowedSize value
|
||||
expect(body.config.settings.limitTextLength).to.be.equal(5000);
|
||||
});
|
||||
it('should coerce a boolean value on an int setting to 0', async () => {
|
||||
await request
|
||||
.post(api('livechat/appearance'))
|
||||
.set(credentials)
|
||||
.send([
|
||||
{ _id: 'Livechat_message_character_limit', value: true },
|
||||
{ _id: 'Livechat_enable_message_character_limit', value: true },
|
||||
])
|
||||
.expect(200);
|
||||
|
||||
// Just enough to get the stream to update cached settings
|
||||
await sleep(500);
|
||||
// Get data from livechat/config
|
||||
const { body } = await request.get(api('livechat/config')).set(credentials).expect(200);
|
||||
expect(body.config.settings.limitTextLength).to.be.equal(5000);
|
||||
});
|
||||
it('should coerce a non boolean value on a boolean setting to false', async () => {
|
||||
await request
|
||||
.post(api('livechat/appearance'))
|
||||
.set(credentials)
|
||||
.send([{ _id: 'Livechat_enable_message_character_limit', value: 'xxxx' }])
|
||||
.expect(200);
|
||||
|
||||
// Just enough to get the stream to update cached settings
|
||||
await sleep(500);
|
||||
// Get data from livechat/config
|
||||
const { body } = await request.get(api('livechat/config')).set(credentials).expect(200);
|
||||
expect(body.config.settings.limitTextLength).to.be.false;
|
||||
});
|
||||
|
||||
(IS_EE ? it : it.skip)('should accept an array setting', async () => {
|
||||
await request
|
||||
.post(api('livechat/appearance'))
|
||||
.set(credentials)
|
||||
.send([{ _id: 'Livechat_hide_system_messages', value: ['livechat-started'] }])
|
||||
.expect(200);
|
||||
await sleep(500);
|
||||
|
||||
// Get data from livechat/config
|
||||
const { body } = await request.get(api('livechat/config')).set(credentials).expect(200);
|
||||
expect(body.config.settings.hiddenSystemMessages).to.be.an('array');
|
||||
expect(body.config.settings.hiddenSystemMessages).to.include('livechat-started');
|
||||
});
|
||||
|
||||
(IS_EE ? it : it.skip)('should accept an array setting with multiple values', async () => {
|
||||
await request
|
||||
.post(api('livechat/appearance'))
|
||||
.set(credentials)
|
||||
.send([{ _id: 'Livechat_hide_system_messages', value: ['uj', 'livechat_transfer_history'] }])
|
||||
.expect(200);
|
||||
await sleep(500);
|
||||
|
||||
// Get data from livechat/config
|
||||
const { body } = await request.get(api('livechat/config')).set(credentials).expect(200);
|
||||
expect(body.config.settings.hiddenSystemMessages).to.be.an('array');
|
||||
expect(body.config.settings.hiddenSystemMessages).to.include('uj');
|
||||
expect(body.config.settings.hiddenSystemMessages).to.include('livechat_transfer_history');
|
||||
expect(body.config.settings.hiddenSystemMessages).not.to.include('livechat-started');
|
||||
});
|
||||
|
||||
(IS_EE ? it : it.skip)('should not update an array setting with a value other than array', async () => {
|
||||
await request
|
||||
.post(api('livechat/appearance'))
|
||||
.set(credentials)
|
||||
.send([{ _id: 'Livechat_hide_system_messages', value: 'uj' }])
|
||||
.expect(200);
|
||||
|
||||
await sleep(500);
|
||||
|
||||
// Get data from livechat/config
|
||||
const { body } = await request.get(api('livechat/config')).set(credentials).expect(200);
|
||||
expect(body.config.settings.hiddenSystemMessages).to.be.an('array');
|
||||
expect(body.config.settings.hiddenSystemMessages).to.include('uj');
|
||||
expect(body.config.settings.hiddenSystemMessages).to.include('livechat_transfer_history');
|
||||
});
|
||||
|
||||
(IS_EE ? it : it.skip)('should not update an array setting with values that are not valid setting values', async () => {
|
||||
await request
|
||||
.post(api('livechat/appearance'))
|
||||
.set(credentials)
|
||||
.send([{ _id: 'Livechat_hide_system_messages', value: ['livechat-started', 'invalid'] }])
|
||||
.expect(200);
|
||||
|
||||
await sleep(500);
|
||||
|
||||
// Get data from livechat/config
|
||||
const { body } = await request.get(api('livechat/config')).set(credentials).expect(200);
|
||||
expect(body.config.settings.hiddenSystemMessages).to.be.an('array');
|
||||
expect(body.config.settings.hiddenSystemMessages).to.include('uj');
|
||||
expect(body.config.settings.hiddenSystemMessages).to.include('livechat_transfer_history');
|
||||
expect(body.config.settings.hiddenSystemMessages).to.not.include('livechat-started');
|
||||
expect(body.config.settings.hiddenSystemMessages).to.not.include('invalid');
|
||||
});
|
||||
});
|
||||
});
|
||||
746
apps/meteor/tests/end-to-end/api/livechat/03-custom-fields.ts
Normal file
746
apps/meteor/tests/end-to-end/api/livechat/03-custom-fields.ts
Normal file
@@ -0,0 +1,746 @@
|
||||
import type { ILivechatVisitor, IOmnichannelRoom } from '@rocket.chat/core-typings';
|
||||
import { expect } from 'chai';
|
||||
import { after, before, describe, it } from 'mocha';
|
||||
import type { Response } from 'supertest';
|
||||
|
||||
import { getCredentials, api, request, credentials } from '../../../data/api-data';
|
||||
import { createCustomField, deleteCustomField } from '../../../data/livechat/custom-fields';
|
||||
import { closeOmnichannelRoom, createLivechatRoom, createVisitor, deleteVisitor, getLivechatRoomInfo } from '../../../data/livechat/rooms';
|
||||
import { updatePermission, updateSetting } from '../../../data/permissions.helper';
|
||||
|
||||
describe('LIVECHAT - custom fields', () => {
|
||||
before((done) => getCredentials(done));
|
||||
|
||||
before(async () => {
|
||||
await updateSetting('Livechat_enabled', true);
|
||||
});
|
||||
|
||||
describe('livechat/custom-fields', () => {
|
||||
it('should return an "unauthorized error" when the user does not have the necessary permission', async () => {
|
||||
await updatePermission('view-l-room', []);
|
||||
await request.get(api('livechat/custom-fields')).set(credentials).expect('Content-Type', 'application/json').expect(403);
|
||||
});
|
||||
it('should return an array of custom fields', async () => {
|
||||
await updatePermission('view-l-room', ['admin']);
|
||||
await request
|
||||
.get(api('livechat/custom-fields'))
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body.customFields).to.be.an('array');
|
||||
expect(res.body).to.have.property('offset');
|
||||
expect(res.body).to.have.property('total');
|
||||
expect(res.body).to.have.property('count');
|
||||
});
|
||||
});
|
||||
it('should return an array of custom fields even requested with count and offset params', async () => {
|
||||
await updatePermission('view-l-room', ['admin']);
|
||||
await request
|
||||
.get(api('livechat/custom-fields'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
count: 5,
|
||||
offset: 0,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body.customFields).to.be.an('array');
|
||||
expect(res.body).to.have.property('offset');
|
||||
expect(res.body).to.have.property('total');
|
||||
expect(res.body).to.have.property('count');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('livechat/custom-fields/id', () => {
|
||||
it('should return an "unauthorized error" when the user does not have the necessary permission', async () => {
|
||||
await updatePermission('view-l-room', []);
|
||||
await request.get(api('livechat/custom-fields/invalid-id')).set(credentials).expect('Content-Type', 'application/json').expect(403);
|
||||
await updatePermission('view-l-room', ['admin']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('livechat/custom.field', () => {
|
||||
it('should fail when token is not on body params', async () => {
|
||||
await request.post(api('livechat/custom.field')).expect(400);
|
||||
});
|
||||
it('should fail when key is not on body params', async () => {
|
||||
await request.post(api('livechat/custom.field')).send({ token: 'invalid-token' }).expect(400);
|
||||
});
|
||||
it('should fail when value is not on body params', async () => {
|
||||
await request.post(api('livechat/custom.field')).send({ token: 'invalid-token', key: 'invalid-key' }).expect(400);
|
||||
});
|
||||
it('should fail when overwrite is not on body params', async () => {
|
||||
await request
|
||||
.post(api('livechat/custom.field'))
|
||||
.send({ token: 'invalid-token', key: 'invalid-key', value: 'invalid-value' })
|
||||
.expect(400);
|
||||
});
|
||||
it('should fail when token is invalid', async () => {
|
||||
await request
|
||||
.post(api('livechat/custom.field'))
|
||||
.send({ token: 'invalid-token', key: 'invalid-key', value: 'invalid-value', overwrite: true })
|
||||
.expect(400);
|
||||
});
|
||||
it('should fail when key is invalid', async () => {
|
||||
const visitor = await createVisitor();
|
||||
await request
|
||||
.post(api('livechat/custom.field'))
|
||||
.send({ token: visitor.token, key: 'invalid-key', value: 'invalid-value', overwrite: true })
|
||||
.expect(400);
|
||||
|
||||
await deleteVisitor(visitor.token);
|
||||
});
|
||||
it('should save a custom field on visitor', async () => {
|
||||
const visitor = await createVisitor();
|
||||
const customFieldName = `new_custom_field_${Date.now()}`;
|
||||
await createCustomField({
|
||||
searchable: true,
|
||||
field: customFieldName,
|
||||
label: customFieldName,
|
||||
defaultValue: 'test_default_address',
|
||||
scope: 'visitor',
|
||||
visibility: 'public',
|
||||
regexp: '',
|
||||
});
|
||||
|
||||
const { body } = await request
|
||||
.post(api('livechat/custom.field'))
|
||||
.send({ token: visitor.token, key: customFieldName, value: 'test_address', overwrite: true })
|
||||
.expect(200);
|
||||
|
||||
expect(body).to.have.property('success', true);
|
||||
expect(body).to.have.property('field');
|
||||
expect(body.field).to.have.property('value', 'test_address');
|
||||
|
||||
await deleteVisitor(visitor.token);
|
||||
});
|
||||
});
|
||||
|
||||
describe('livechat/custom.fields', () => {
|
||||
const customFieldName = `new_custom_field_${Date.now()}_1`;
|
||||
const customFieldName2 = `new_custom_field_${Date.now()}_2`;
|
||||
const customFieldName3 = `new_custom_field_${Date.now()}_3`;
|
||||
const roomCustomField = `new_custom_field_${Date.now()}_4`;
|
||||
let visitor: ILivechatVisitor;
|
||||
let visitorRoom: IOmnichannelRoom;
|
||||
|
||||
before(async () => {
|
||||
await updateSetting('Livechat_accept_chats_with_no_agents', true);
|
||||
await createCustomField({
|
||||
searchable: true,
|
||||
field: customFieldName,
|
||||
label: customFieldName,
|
||||
defaultValue: 'test_default_address',
|
||||
scope: 'visitor',
|
||||
visibility: 'public',
|
||||
regexp: '',
|
||||
});
|
||||
await createCustomField({
|
||||
searchable: true,
|
||||
field: customFieldName2,
|
||||
label: customFieldName2,
|
||||
defaultValue: 'test_default_address',
|
||||
scope: 'visitor',
|
||||
visibility: 'public',
|
||||
regexp: '',
|
||||
});
|
||||
await createCustomField({
|
||||
searchable: true,
|
||||
field: customFieldName3,
|
||||
label: customFieldName3,
|
||||
defaultValue: 'test_default_address',
|
||||
scope: 'visitor',
|
||||
visibility: 'public',
|
||||
regexp: '',
|
||||
});
|
||||
await createCustomField({
|
||||
searchable: true,
|
||||
field: roomCustomField,
|
||||
label: roomCustomField,
|
||||
defaultValue: 'test_default_address',
|
||||
scope: 'room',
|
||||
visibility: 'public',
|
||||
regexp: '',
|
||||
});
|
||||
visitor = await createVisitor();
|
||||
// start a room for visitor2
|
||||
visitorRoom = await createLivechatRoom(visitor.token);
|
||||
});
|
||||
after(async () => {
|
||||
await Promise.all([
|
||||
deleteCustomField(customFieldName),
|
||||
deleteCustomField(customFieldName2),
|
||||
deleteCustomField(customFieldName3),
|
||||
deleteCustomField(roomCustomField),
|
||||
closeOmnichannelRoom(visitorRoom._id),
|
||||
deleteVisitor(visitor.token),
|
||||
updateSetting('Livechat_accept_chats_with_no_agents', false),
|
||||
]);
|
||||
});
|
||||
it('should fail when token is not on body params', async () => {
|
||||
await request.post(api('livechat/custom.fields')).expect(400);
|
||||
});
|
||||
it('should fail if customFields is not on body params', async () => {
|
||||
await request.post(api('livechat/custom.fields')).send({ token: 'invalid-token' }).expect(400);
|
||||
});
|
||||
it('should fail if customFields is not an array', async () => {
|
||||
await request
|
||||
.post(api('livechat/custom.fields'))
|
||||
.send({
|
||||
token: 'invalid-token',
|
||||
customFields: 'invalid-custom-fields',
|
||||
})
|
||||
.expect(400);
|
||||
});
|
||||
it('should fail if customFields is an empty array', async () => {
|
||||
await request.post(api('livechat/custom.fields')).send({ token: 'invalid-token', customFields: [] }).expect(400);
|
||||
});
|
||||
it('should fail if customFields is an array with invalid objects', async () => {
|
||||
await request
|
||||
.post(api('livechat/custom.fields'))
|
||||
.send({ token: 'invalid-token', customFields: [{}] })
|
||||
.expect(400);
|
||||
});
|
||||
it('should fail if token is not a valid token', async () => {
|
||||
await request
|
||||
.post(api('livechat/custom.fields'))
|
||||
.send({
|
||||
token: 'invalid-token',
|
||||
customFields: [{ key: 'invalid-key', value: 'invalid-value', overwrite: true }],
|
||||
})
|
||||
.expect(400);
|
||||
});
|
||||
it('should fail when customFields.key is invalid', async () => {
|
||||
await request
|
||||
.post(api('livechat/custom.fields'))
|
||||
.send({
|
||||
token: visitor.token,
|
||||
customFields: [{ key: 'invalid-key', value: 'invalid-value', overwrite: true }],
|
||||
})
|
||||
.expect(400);
|
||||
});
|
||||
it('should save a custom field on visitor', async () => {
|
||||
const visitor = await createVisitor();
|
||||
|
||||
const { body } = await request
|
||||
.post(api('livechat/custom.fields'))
|
||||
.send({
|
||||
token: visitor.token,
|
||||
customFields: [{ key: customFieldName, value: 'test_address', overwrite: true }],
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
expect(body).to.have.property('success', true);
|
||||
expect(body).to.have.property('fields');
|
||||
expect(body.fields).to.be.an('array');
|
||||
expect(body.fields).to.have.lengthOf(1);
|
||||
expect(body.fields[0]).to.have.property('value', 'test_address');
|
||||
|
||||
await deleteVisitor(visitor.token);
|
||||
});
|
||||
it('should save multiple custom fields on a visitor', async () => {
|
||||
const visitor = await createVisitor();
|
||||
|
||||
const { body } = await request
|
||||
.post(api('livechat/custom.fields'))
|
||||
.send({
|
||||
token: visitor.token,
|
||||
customFields: [
|
||||
{ key: customFieldName, value: 'test_address', overwrite: true },
|
||||
{ key: customFieldName2, value: 'test_address2', overwrite: true },
|
||||
{ key: customFieldName3, value: 'test_address3', overwrite: true },
|
||||
],
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
expect(body).to.have.property('success', true);
|
||||
expect(body).to.have.property('fields');
|
||||
expect(body.fields).to.be.an('array');
|
||||
expect(body.fields).to.have.lengthOf(3);
|
||||
expect(body.fields[0]).to.have.property('value', 'test_address');
|
||||
expect(body.fields[1]).to.have.property('value', 'test_address2');
|
||||
expect(body.fields[2]).to.have.property('value', 'test_address3');
|
||||
|
||||
await deleteVisitor(visitor.token);
|
||||
});
|
||||
it('should save multiple custom fields on contact when visitor already has custom fields and an update with multiple fields is issued', async () => {
|
||||
const { body } = await request
|
||||
.post(api('livechat/custom.fields'))
|
||||
.send({
|
||||
token: visitor.token,
|
||||
customFields: [{ key: customFieldName, value: 'test_address', overwrite: true }],
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
expect(body).to.have.property('success', true);
|
||||
expect(body).to.have.property('fields');
|
||||
expect(body.fields).to.be.an('array');
|
||||
expect(body.fields).to.have.lengthOf(1);
|
||||
expect(body.fields[0]).to.have.property('value', 'test_address');
|
||||
|
||||
await request
|
||||
.post(api('livechat/custom.fields'))
|
||||
.send({
|
||||
token: visitor.token,
|
||||
customFields: [
|
||||
{ key: customFieldName2, value: 'test_address2', overwrite: true },
|
||||
{ key: customFieldName3, value: 'test_address3', overwrite: true },
|
||||
],
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
await request
|
||||
.get(api(`omnichannel/contacts.get`))
|
||||
.set(credentials)
|
||||
.query({ contactId: visitorRoom.contactId })
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('contact');
|
||||
expect(res.body.contact).to.have.property('customFields');
|
||||
expect(res.body.contact.customFields).to.have.property(customFieldName, 'test_address');
|
||||
expect(res.body.contact.customFields).to.have.property(customFieldName2, 'test_address2');
|
||||
expect(res.body.contact.customFields).to.have.property(customFieldName3, 'test_address3');
|
||||
});
|
||||
});
|
||||
it('should mark a conflict on a contact custom fields when overwrite is true and visitor already has the custom field set', async () => {
|
||||
await request
|
||||
.post(api('livechat/custom.fields'))
|
||||
.send({
|
||||
token: visitor.token,
|
||||
customFields: [{ key: customFieldName, value: 'test_address_conflict', overwrite: false }],
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
await request
|
||||
.get(api(`omnichannel/contacts.get`))
|
||||
.set(credentials)
|
||||
.query({ contactId: visitorRoom.contactId })
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('contact');
|
||||
expect(res.body.contact).to.have.property('customFields');
|
||||
expect(res.body.contact.customFields).to.have.property(customFieldName, 'test_address');
|
||||
expect(res.body.contact.customFields).to.have.property(customFieldName2, 'test_address2');
|
||||
expect(res.body.contact.customFields).to.have.property(customFieldName3, 'test_address3');
|
||||
expect(res.body.contact).to.have.property('conflictingFields').that.is.an('array');
|
||||
expect(res.body.contact.conflictingFields[0]).to.deep.equal({
|
||||
field: `customFields.${customFieldName}`,
|
||||
value: 'test_address_conflict',
|
||||
});
|
||||
});
|
||||
});
|
||||
it('should overwrite the contact custom field when overwrite is true', async () => {
|
||||
await request
|
||||
.post(api('livechat/custom.fields'))
|
||||
.send({
|
||||
token: visitor.token,
|
||||
customFields: [{ key: customFieldName2, value: 'test_new_add', overwrite: true }],
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
await request
|
||||
.get(api(`omnichannel/contacts.get`))
|
||||
.set(credentials)
|
||||
.query({ contactId: visitorRoom.contactId })
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('contact');
|
||||
expect(res.body.contact).to.have.property('customFields');
|
||||
expect(res.body.contact.customFields).to.have.property(customFieldName, 'test_address');
|
||||
expect(res.body.contact.customFields).to.have.property(customFieldName2, 'test_new_add');
|
||||
expect(res.body.contact.customFields).to.have.property(customFieldName3, 'test_address3');
|
||||
expect(res.body.contact).to.have.property('conflictingFields').that.is.an('array');
|
||||
expect(res.body.contact.conflictingFields[0]).to.deep.equal({
|
||||
field: `customFields.${customFieldName}`,
|
||||
value: 'test_address_conflict',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('with a room', () => {
|
||||
let visitor: ILivechatVisitor;
|
||||
let room: IOmnichannelRoom;
|
||||
before(async () => {
|
||||
visitor = await createVisitor();
|
||||
room = await createLivechatRoom(visitor.token);
|
||||
});
|
||||
after(async () => {
|
||||
await closeOmnichannelRoom(room._id);
|
||||
await deleteVisitor(visitor.token);
|
||||
});
|
||||
|
||||
it('should save both room & vistor custom fields on one call', async () => {
|
||||
await request
|
||||
.post(api('livechat/custom.fields'))
|
||||
.send({
|
||||
token: visitor.token,
|
||||
customFields: [
|
||||
{ key: customFieldName, value: 'test_address', overwrite: true },
|
||||
{ key: roomCustomField, value: 'test_address2', overwrite: true },
|
||||
{ key: customFieldName3, value: 'test_address3', overwrite: true },
|
||||
],
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
await request
|
||||
.get(api(`omnichannel/contacts.get`))
|
||||
.set(credentials)
|
||||
.query({ contactId: visitorRoom.contactId })
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('contact');
|
||||
expect(res.body.contact).to.have.property('customFields');
|
||||
expect(res.body.contact.customFields).to.have.property(customFieldName, 'test_address');
|
||||
expect(res.body.contact.customFields).to.have.property(customFieldName3, 'test_address3');
|
||||
});
|
||||
|
||||
const roomInfo = await getLivechatRoomInfo(room._id);
|
||||
expect(roomInfo).to.have.property('livechatData').that.is.an('object');
|
||||
expect(roomInfo.livechatData).to.have.property(roomCustomField, 'test_address2');
|
||||
});
|
||||
it('should ignore a room custom field when room already has a value for it and overwrite is false', async () => {
|
||||
await request
|
||||
.post(api('livechat/custom.fields'))
|
||||
.send({
|
||||
token: visitor.token,
|
||||
customFields: [{ key: roomCustomField, value: 'test_value_overriden', overwrite: false }],
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
const roomInfo = await getLivechatRoomInfo(room._id);
|
||||
expect(roomInfo).to.have.property('livechatData').that.is.an('object');
|
||||
expect(roomInfo.livechatData).to.have.property(roomCustomField, 'test_address2');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('livechat/custom.field [with Contacts]', () => {
|
||||
let visitor: ILivechatVisitor;
|
||||
let contactId: string;
|
||||
let room: IOmnichannelRoom;
|
||||
|
||||
const customFieldName = `custom_field_${Date.now()}`;
|
||||
const customFieldValue = 'custom-field-value';
|
||||
|
||||
before(async () => {
|
||||
await updatePermission('create-livechat-contact', ['admin']);
|
||||
await updatePermission('view-livechat-contact', ['admin']);
|
||||
await updateSetting('Livechat_accept_chats_with_no_agents', true);
|
||||
|
||||
// Create a Visitor
|
||||
visitor = await createVisitor();
|
||||
|
||||
// Create a Contact and store id on var
|
||||
await request
|
||||
.post(api('omnichannel/contacts'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
name: visitor.name,
|
||||
emails: [visitor.visitorEmails?.[0].address],
|
||||
phones: [visitor.phone?.[0].phoneNumber],
|
||||
})
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('contactId');
|
||||
|
||||
contactId = res.body.contactId;
|
||||
});
|
||||
|
||||
room = await createLivechatRoom(visitor.token);
|
||||
|
||||
// Create Custom Field
|
||||
await createCustomField({
|
||||
searchable: true,
|
||||
field: customFieldName,
|
||||
label: customFieldName,
|
||||
defaultValue: 'test_default_address',
|
||||
scope: 'visitor',
|
||||
visibility: 'public',
|
||||
regexp: '',
|
||||
});
|
||||
|
||||
await createCustomField({
|
||||
searchable: true,
|
||||
field: `${customFieldName}_2`,
|
||||
label: `${customFieldName}_2`,
|
||||
defaultValue: 'test_default_address',
|
||||
scope: 'visitor',
|
||||
visibility: 'public',
|
||||
regexp: '',
|
||||
});
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
// TODO: add clean up for contacts, visitors, etc
|
||||
await Promise.all([
|
||||
deleteCustomField(customFieldName),
|
||||
deleteCustomField(`${customFieldName}_2`),
|
||||
deleteCustomField(`${customFieldName}_3`),
|
||||
deleteVisitor(visitor.token),
|
||||
closeOmnichannelRoom(room._id),
|
||||
updateSetting('Livechat_accept_chats_with_no_agents', false),
|
||||
]);
|
||||
});
|
||||
|
||||
it('should save the custom field on Contact when available', async () => {
|
||||
// Save the custom field on Visitor/Contact
|
||||
await request
|
||||
.post(api('livechat/custom.field'))
|
||||
.send({ token: visitor.token, key: customFieldName, value: customFieldValue, overwrite: true })
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
});
|
||||
|
||||
// Fetch the visitor to validate custom fields are properly set.
|
||||
await request
|
||||
.get(api(`livechat/visitor/${visitor.token}`))
|
||||
.set(credentials)
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body.visitor).to.have.property('livechatData');
|
||||
expect(res.body.visitor.livechatData).to.have.property(customFieldName, customFieldValue);
|
||||
});
|
||||
|
||||
// Fetch the visitor's contact to validate custom fields are properly set.
|
||||
await request
|
||||
.get(api(`omnichannel/contacts.get`))
|
||||
.set(credentials)
|
||||
.query({ contactId })
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('contact');
|
||||
expect(res.body.contact).to.have.property('customFields');
|
||||
expect(res.body.contact.customFields).to.have.property(customFieldName, customFieldValue);
|
||||
});
|
||||
});
|
||||
|
||||
it('Should save multiple custom fields on Contact when available', async () => {
|
||||
// Save the custom field on Visitor/Contact
|
||||
await request
|
||||
.post(api('livechat/custom.field'))
|
||||
.send({ token: visitor.token, key: customFieldName, value: customFieldValue, overwrite: true })
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
});
|
||||
|
||||
await request
|
||||
.post(api('livechat/custom.field'))
|
||||
.send({ token: visitor.token, key: `${customFieldName}_2`, value: customFieldValue, overwrite: true })
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
});
|
||||
|
||||
// Fetch the visitor to validate custom fields are properly set.
|
||||
await request
|
||||
.get(api(`livechat/visitor/${visitor.token}`))
|
||||
.set(credentials)
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body.visitor).to.have.property('livechatData');
|
||||
expect(res.body.visitor.livechatData).to.have.property(customFieldName, customFieldValue);
|
||||
expect(res.body.visitor.livechatData).to.have.property(`${customFieldName}_2`, customFieldValue);
|
||||
});
|
||||
|
||||
// Fetch the visitor's contact to validate custom fields are properly set.
|
||||
await request
|
||||
.get(api(`omnichannel/contacts.get`))
|
||||
.set(credentials)
|
||||
.query({ contactId })
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('contact');
|
||||
expect(res.body.contact).to.have.property('customFields');
|
||||
expect(res.body.contact.customFields).to.have.property(customFieldName, customFieldValue);
|
||||
expect(res.body.contact.customFields).to.have.property(`${customFieldName}_2`, customFieldValue);
|
||||
});
|
||||
});
|
||||
|
||||
it('should add the custom field as conflict on Contact when overwrite is false', async () => {
|
||||
const conflictingFieldValue = 'conflicting-custom-field-value';
|
||||
|
||||
// Save the custom field on Contact
|
||||
await request
|
||||
.post(api('livechat/custom.field'))
|
||||
.send({ token: visitor.token, key: customFieldName, value: conflictingFieldValue, overwrite: false })
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
});
|
||||
|
||||
// Fetch the visitor to validate custom fields are properly set.
|
||||
await request
|
||||
.get(api(`livechat/visitor/${visitor.token}`))
|
||||
.set(credentials)
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body.visitor).to.have.property('livechatData');
|
||||
expect(res.body.visitor.livechatData).to.have.property(customFieldName, customFieldValue);
|
||||
});
|
||||
|
||||
// Fetch the visitor's contact to validate custom fields are properly set.
|
||||
await request
|
||||
.get(api(`omnichannel/contacts.get`))
|
||||
.set(credentials)
|
||||
.query({ contactId })
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('contact');
|
||||
expect(res.body.contact).to.have.property('customFields');
|
||||
expect(res.body.contact.customFields).to.have.property(customFieldName, customFieldValue);
|
||||
|
||||
// Validate custom fields contain both entries, indicating conflict criteria
|
||||
expect(res.body.contact.conflictingFields).to.have.lengthOf(1);
|
||||
expect(res.body.contact.conflictingFields[0]).to.have.property('field', `customFields.${customFieldName}`);
|
||||
expect(res.body.contact.conflictingFields[0]).to.have.property('value', conflictingFieldValue);
|
||||
});
|
||||
});
|
||||
|
||||
it('should not save the custom field as a conflict on Contact when overwrite is false but custom field is not registered yet', async () => {
|
||||
await createCustomField({
|
||||
searchable: true,
|
||||
field: `${customFieldName}_3`,
|
||||
label: `${customFieldName}_3`,
|
||||
defaultValue: 'test_default_address',
|
||||
scope: 'visitor',
|
||||
visibility: 'public',
|
||||
regexp: '',
|
||||
});
|
||||
|
||||
// Save the custom field on Contact
|
||||
await request
|
||||
.post(api('livechat/custom.field'))
|
||||
.send({ token: visitor.token, key: `${customFieldName}_3`, value: customFieldValue, overwrite: false })
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
});
|
||||
|
||||
// Fetch the visitor to validate custom fields are properly set.
|
||||
await request
|
||||
.get(api(`livechat/visitor/${visitor.token}`))
|
||||
.set(credentials)
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body.visitor).to.have.property('livechatData');
|
||||
expect(res.body.visitor.livechatData).to.have.property(`${customFieldName}_3`, customFieldValue);
|
||||
});
|
||||
|
||||
// Fetch the visitor's contact to validate custom fields are properly set.
|
||||
await request
|
||||
.get(api(`omnichannel/contacts.get`))
|
||||
.set(credentials)
|
||||
.query({ contactId })
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('contact');
|
||||
expect(res.body.contact).to.have.property('customFields');
|
||||
expect(res.body.contact.customFields).to.have.property(`${customFieldName}_3`, customFieldValue);
|
||||
|
||||
// Validate custom fields contain both entries, indicating conflict criteria
|
||||
expect(res.body.contact.conflictingFields).to.have.lengthOf(1);
|
||||
expect(res.body.contact.conflictingFields[0]).to.not.have.property('field', `customFields.${customFieldName}_3`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('conflictingField not nullish', () => {
|
||||
let room: IOmnichannelRoom;
|
||||
let visitor: ILivechatVisitor;
|
||||
let contactId2: string | undefined;
|
||||
|
||||
before(async () => {
|
||||
visitor = await createVisitor();
|
||||
await request
|
||||
.post(api('omnichannel/contacts'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
name: visitor.name,
|
||||
emails: [visitor.visitorEmails?.[0].address],
|
||||
phones: [visitor.phone?.[0].phoneNumber],
|
||||
})
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('contactId');
|
||||
|
||||
contactId2 = res.body.contactId;
|
||||
});
|
||||
room = await createLivechatRoom(visitor.token);
|
||||
});
|
||||
after(async () => {
|
||||
await closeOmnichannelRoom(room._id);
|
||||
await deleteVisitor(visitor.token);
|
||||
});
|
||||
|
||||
it('should not save contact conflictingFields as nullish if not modified', async () => {
|
||||
await request
|
||||
.post(api('livechat/custom.field'))
|
||||
.send({ token: visitor.token, key: `${customFieldName}`, value: customFieldValue, overwrite: true })
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
});
|
||||
|
||||
// Fetch the visitor's contact to validate custom fields are properly set.
|
||||
await request
|
||||
.get(api(`omnichannel/contacts.get`))
|
||||
.set(credentials)
|
||||
.query({ contactId: contactId2 })
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('contact');
|
||||
expect(res.body.contact).to.have.property('customFields');
|
||||
expect(res.body.contact.customFields).to.have.property(`${customFieldName}`, customFieldValue);
|
||||
|
||||
// Validate conflictingFields was not saved as null
|
||||
expect(res.body.contact).to.not.have.property('conflictingFields');
|
||||
});
|
||||
});
|
||||
|
||||
it('should not save contact conflictingFields as nullish if not modified (through visitor update)', async () => {
|
||||
await request
|
||||
.post(api('livechat/visitor'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
visitor: {
|
||||
token: visitor.token,
|
||||
customFields: [{ key: `${customFieldName}`, value: customFieldValue, overwrite: true }],
|
||||
},
|
||||
});
|
||||
|
||||
// Fetch the visitor's contact to validate custom fields are properly set.
|
||||
await request
|
||||
.get(api(`omnichannel/contacts.get`))
|
||||
.set(credentials)
|
||||
.query({ contactId: contactId2 })
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('contact');
|
||||
expect(res.body.contact).to.have.property('customFields');
|
||||
expect(res.body.contact.customFields).to.have.property(`${customFieldName}`, customFieldValue);
|
||||
|
||||
// Validate conflictingFields was not saved as null
|
||||
expect(res.body.contact).to.not.have.property('conflictingFields');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
1473
apps/meteor/tests/end-to-end/api/livechat/04-dashboards.ts
Normal file
1473
apps/meteor/tests/end-to-end/api/livechat/04-dashboards.ts
Normal file
File diff suppressed because it is too large
Load Diff
611
apps/meteor/tests/end-to-end/api/livechat/05-inquiries.ts
Normal file
611
apps/meteor/tests/end-to-end/api/livechat/05-inquiries.ts
Normal file
@@ -0,0 +1,611 @@
|
||||
import type { Credentials } from '@rocket.chat/api-client';
|
||||
import type { ILivechatDepartment, ILivechatInquiryRecord, ILivechatVisitor, IOmnichannelRoom, IUser } from '@rocket.chat/core-typings';
|
||||
import { expect } from 'chai';
|
||||
import { before, describe, it, after } from 'mocha';
|
||||
import type { Response } from 'supertest';
|
||||
|
||||
import { getCredentials, api, request, credentials } from '../../../data/api-data';
|
||||
import { deleteDepartment } from '../../../data/livechat/department';
|
||||
import {
|
||||
closeOmnichannelRoom,
|
||||
createAgent,
|
||||
createDepartment,
|
||||
createLivechatRoom,
|
||||
createVisitor,
|
||||
deleteVisitor,
|
||||
fetchInquiry,
|
||||
getLivechatRoomInfo,
|
||||
makeAgentAvailable,
|
||||
startANewLivechatRoomAndTakeIt,
|
||||
takeInquiry,
|
||||
} from '../../../data/livechat/rooms';
|
||||
import {
|
||||
removePermissionFromAllRoles,
|
||||
restorePermissionToRoles,
|
||||
updateEESetting,
|
||||
updatePermission,
|
||||
updateSetting,
|
||||
} from '../../../data/permissions.helper';
|
||||
import { password } from '../../../data/user';
|
||||
import { createUser, login, deleteUser } from '../../../data/users.helper';
|
||||
import { IS_EE } from '../../../e2e/config/constants';
|
||||
import { retry } from '../helpers/retry';
|
||||
|
||||
describe('LIVECHAT - inquiries', () => {
|
||||
before((done) => getCredentials(done));
|
||||
|
||||
before(async () => {
|
||||
await updateSetting('Livechat_enabled', true);
|
||||
await updateSetting('Livechat_Routing_Method', 'Manual_Selection');
|
||||
await updateEESetting('Livechat_Require_Contact_Verification', 'never');
|
||||
});
|
||||
|
||||
describe('livechat/inquiries.list', () => {
|
||||
it('should return an "unauthorized error" when the user does not have the necessary permission', async () => {
|
||||
await updatePermission('view-livechat-manager', []);
|
||||
await request.get(api('livechat/inquiries.list')).set(credentials).expect('Content-Type', 'application/json').expect(403);
|
||||
});
|
||||
it('should return an array of inquiries', async () => {
|
||||
await updatePermission('view-livechat-manager', ['admin']);
|
||||
await request
|
||||
.get(api('livechat/inquiries.list'))
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body.inquiries).to.be.an('array');
|
||||
expect(res.body).to.have.property('offset');
|
||||
expect(res.body).to.have.property('total');
|
||||
expect(res.body).to.have.property('count');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('livechat/inquiries.getOne', () => {
|
||||
let room: IOmnichannelRoom;
|
||||
let visitor: ILivechatVisitor;
|
||||
before(async () => {
|
||||
await createAgent();
|
||||
await makeAgentAvailable();
|
||||
|
||||
visitor = await createVisitor();
|
||||
|
||||
room = await createLivechatRoom(visitor.token);
|
||||
});
|
||||
after(async () => {
|
||||
await closeOmnichannelRoom(room._id);
|
||||
await deleteVisitor(visitor.token);
|
||||
});
|
||||
|
||||
it('should return an "unauthorized error" when the user does not have the necessary permission', async () => {
|
||||
await updatePermission('view-l-room', []);
|
||||
await request
|
||||
.get(api('livechat/inquiries.getOne'))
|
||||
.query({ roomId: 'room-id' })
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(403);
|
||||
});
|
||||
it('should return a inquiry', async () => {
|
||||
await updatePermission('view-l-room', ['admin']);
|
||||
await request
|
||||
.get(api('livechat/inquiries.getOne'))
|
||||
.query({ roomId: 'room-id' })
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('inquiry');
|
||||
});
|
||||
});
|
||||
|
||||
it('should get an inquiry by room id', async () => {
|
||||
const inquiry = await fetchInquiry(room._id);
|
||||
await request
|
||||
.get(api(`livechat/inquiries.getOne`))
|
||||
.query({ roomId: room._id })
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('inquiry');
|
||||
expect(res.body.inquiry).to.have.property('_id', inquiry._id);
|
||||
expect(res.body.inquiry).to.have.property('rid', room._id);
|
||||
expect(res.body.inquiry).to.have.property('ts');
|
||||
expect(res.body.inquiry.ts).to.be.a('string');
|
||||
expect(res.body.inquiry).to.have.property('status', 'queued');
|
||||
expect(res.body.inquiry).to.have.property('name', visitor.name);
|
||||
expect(res.body.inquiry).to.have.property('t', 'l');
|
||||
expect(res.body.inquiry).to.have.property('priorityWeight');
|
||||
expect(res.body.inquiry).to.have.property('estimatedWaitingTimeQueue');
|
||||
expect(res.body.inquiry.source).to.have.property('type', 'api');
|
||||
expect(res.body.inquiry).to.have.property('_updatedAt');
|
||||
expect(res.body.inquiry).to.have.property('v').and.be.an('object');
|
||||
expect(res.body.inquiry.v).to.have.property('_id', visitor._id);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST livechat/inquiries.take', () => {
|
||||
let takenRoom: IOmnichannelRoom;
|
||||
let servedByRoom: IOmnichannelRoom;
|
||||
let visitor: ILivechatVisitor;
|
||||
let visitor2: ILivechatVisitor;
|
||||
before(async () => {
|
||||
visitor = await createVisitor();
|
||||
visitor2 = await createVisitor();
|
||||
|
||||
takenRoom = await createLivechatRoom(visitor.token);
|
||||
servedByRoom = await createLivechatRoom(visitor2.token);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await closeOmnichannelRoom(takenRoom._id);
|
||||
await closeOmnichannelRoom(servedByRoom._id);
|
||||
await deleteVisitor(visitor.token);
|
||||
await deleteVisitor(visitor2.token);
|
||||
});
|
||||
|
||||
it('should return an "unauthorized error" when the user does not have the necessary permission', async () => {
|
||||
await updatePermission('view-l-room', []);
|
||||
await request
|
||||
.post(api('livechat/inquiries.take'))
|
||||
.set(credentials)
|
||||
.send({ inquiryId: 'room-id' })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(403);
|
||||
});
|
||||
it('should throw an error when userId is provided but is invalid', async () => {
|
||||
await updatePermission('view-l-room', ['admin', 'livechat-agent']);
|
||||
await request
|
||||
.post(api('livechat/inquiries.take'))
|
||||
.set(credentials)
|
||||
.send({ inquiryId: 'room-id', userId: 'invalid-user-id' })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
});
|
||||
});
|
||||
it('should throw an error if inquiryId is not an string', async () => {
|
||||
await updatePermission('view-l-room', ['admin', 'livechat-agent']);
|
||||
await request
|
||||
.post(api('livechat/inquiries.take'))
|
||||
.set(credentials)
|
||||
.send({ inquiryId: { regexxxx: 'bla' }, userId: 'user-id' })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
});
|
||||
});
|
||||
it('should take an inquiry if all params are good', async () => {
|
||||
await updatePermission('view-l-room', ['admin', 'livechat-agent']);
|
||||
const agent = await createAgent();
|
||||
await makeAgentAvailable();
|
||||
const inquiry = await fetchInquiry(takenRoom._id);
|
||||
|
||||
await request
|
||||
.post(api('livechat/inquiries.take'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
inquiryId: inquiry._id,
|
||||
userId: agent._id,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
});
|
||||
const inquiry2 = (await fetchInquiry(takenRoom._id)) as ILivechatInquiryRecord;
|
||||
expect(inquiry2.source?.type).to.equal('api');
|
||||
expect(inquiry2.status).to.equal('taken');
|
||||
});
|
||||
it('should mark a taken room as servedBy me', async () => {
|
||||
const agent = await createAgent();
|
||||
await makeAgentAvailable();
|
||||
const inquiry = await fetchInquiry(servedByRoom._id);
|
||||
|
||||
await request
|
||||
.post(api('livechat/inquiries.take'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
inquiryId: inquiry._id,
|
||||
userId: agent._id,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
});
|
||||
|
||||
const roomInfo = await getLivechatRoomInfo(servedByRoom._id);
|
||||
|
||||
expect(roomInfo).to.have.property('servedBy').that.is.an('object');
|
||||
expect(roomInfo.servedBy).to.have.property('_id', 'rocketchat.internal.admin.test');
|
||||
});
|
||||
});
|
||||
|
||||
describe('livechat/inquiries.queuedForUser', () => {
|
||||
let testUser: { user: IUser; credentials: { [key: string]: string } };
|
||||
const departmentsToDelete: ILivechatDepartment[] = [];
|
||||
const visitorsToDelete: ILivechatVisitor[] = [];
|
||||
const roomsToClose: IOmnichannelRoom[] = [];
|
||||
before(async () => {
|
||||
await updateSetting('Livechat_accept_chats_with_no_agents', true);
|
||||
await updateSetting('Omnichannel_enable_department_removal', true);
|
||||
const user = await createUser();
|
||||
await createAgent(user.username);
|
||||
const credentials2 = await login(user.username, password);
|
||||
await makeAgentAvailable(credentials2);
|
||||
|
||||
testUser = {
|
||||
user,
|
||||
credentials: credentials2,
|
||||
};
|
||||
});
|
||||
after(async () => {
|
||||
await Promise.allSettled(roomsToClose.map((room) => closeOmnichannelRoom(room._id)));
|
||||
await Promise.allSettled(visitorsToDelete.map((visitor) => deleteVisitor(visitor.token)));
|
||||
await Promise.allSettled(departmentsToDelete.map((dep) => deleteDepartment(dep._id)));
|
||||
await updateSetting('Livechat_accept_chats_with_no_agents', false);
|
||||
await updateSetting('Omnichannel_enable_department_removal', false);
|
||||
await deleteUser(testUser.user);
|
||||
});
|
||||
it('should return an "unauthorized error" when the user does not have the necessary permission', async () => {
|
||||
await updatePermission('view-l-room', []);
|
||||
await request.get(api('livechat/inquiries.queuedForUser')).set(credentials).expect('Content-Type', 'application/json').expect(403);
|
||||
});
|
||||
it('should return an array of inquiries', async () => {
|
||||
await restorePermissionToRoles('view-l-room');
|
||||
await request
|
||||
.get(api('livechat/inquiries.queuedForUser'))
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body.inquiries).to.be.an('array');
|
||||
expect(res.body).to.have.property('offset');
|
||||
expect(res.body).to.have.property('total');
|
||||
expect(res.body).to.have.property('count');
|
||||
});
|
||||
});
|
||||
it('should validate all returned inquiries are queued', async () => {
|
||||
await request
|
||||
.get(api('livechat/inquiries.queuedForUser'))
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect(async (res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body.inquiries).to.be.an('array');
|
||||
for (const inquiry of res.body.inquiries) {
|
||||
expect(inquiry).to.have.property('status', 'queued');
|
||||
}
|
||||
});
|
||||
});
|
||||
it('should return only public inquiries for a user with no departments', async () => {
|
||||
const { body } = await request
|
||||
.get(api('livechat/inquiries.queuedForUser'))
|
||||
.set(testUser.credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
expect(body).to.have.property('success', true);
|
||||
expect(body.inquiries).to.be.an('array');
|
||||
for (const inq of body.inquiries) {
|
||||
expect(inq).to.not.have.property('department');
|
||||
expect(inq).to.have.property('status', 'queued');
|
||||
}
|
||||
});
|
||||
(IS_EE ? it : it.skip)('should return inquiries of the same department as the user', async () => {
|
||||
const dep = await createDepartment([{ agentId: testUser.user._id }]);
|
||||
departmentsToDelete.push(dep);
|
||||
const visitor = await createVisitor(dep._id);
|
||||
visitorsToDelete.push(visitor);
|
||||
const room = await createLivechatRoom(visitor.token);
|
||||
roomsToClose.push(room);
|
||||
|
||||
const { body } = await request
|
||||
.get(api('livechat/inquiries.queuedForUser'))
|
||||
.set(testUser.credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
expect(body).to.have.property('success', true);
|
||||
expect(body.inquiries).to.be.an('array');
|
||||
const depInq = body.inquiries.filter((inq: { department: string }) => inq.department === dep._id);
|
||||
|
||||
expect(depInq.length).to.be.equal(1);
|
||||
});
|
||||
(IS_EE ? it : it.skip)('should not return an inquiry of a department the user is not part of', async () => {
|
||||
const dep = await createDepartment();
|
||||
departmentsToDelete.push(dep);
|
||||
const visitor = await createVisitor(dep._id);
|
||||
visitorsToDelete.push(visitor);
|
||||
const room = await createLivechatRoom(visitor.token);
|
||||
roomsToClose.push(room);
|
||||
|
||||
const { body } = await request
|
||||
.get(api('livechat/inquiries.queuedForUser'))
|
||||
.set(testUser.credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
expect(body).to.have.property('success', true);
|
||||
expect(body.inquiries).to.be.an('array');
|
||||
const depInq = body.inquiries.filter((inq: { department: string }) => inq.department === dep._id);
|
||||
|
||||
expect(depInq.length).to.be.equal(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('livechat/inquiries.returnAsInquiry', () => {
|
||||
let testUser: { user: IUser; credentials: Credentials };
|
||||
before(async () => {
|
||||
const user = await createUser();
|
||||
await createAgent(user.username);
|
||||
const credentials2 = await login(user.username, password);
|
||||
await makeAgentAvailable(credentials2);
|
||||
|
||||
testUser = {
|
||||
user,
|
||||
credentials: credentials2,
|
||||
};
|
||||
});
|
||||
after(async () => {
|
||||
await deleteUser(testUser.user);
|
||||
});
|
||||
|
||||
it('should throw an error if user doesnt have view-l-room permission', async () => {
|
||||
await removePermissionFromAllRoles('view-l-room');
|
||||
const { body } = await request
|
||||
.post(api('livechat/inquiries.returnAsInquiry'))
|
||||
.set(credentials)
|
||||
.send({ roomId: 'test' })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(403);
|
||||
|
||||
expect(body).to.have.property('success', false);
|
||||
expect(body.error).to.have.equal('User does not have the permissions required for this action [error-unauthorized]');
|
||||
});
|
||||
it('should fail if provided room doesnt exists', async () => {
|
||||
await restorePermissionToRoles('view-l-room');
|
||||
const { body } = await request
|
||||
.post(api('livechat/inquiries.returnAsInquiry'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
roomId: 'test',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
|
||||
expect(body).to.have.property('success', false);
|
||||
expect(body).to.have.property('error', 'error-room-not-found');
|
||||
});
|
||||
it('should fail if room is not a livechat room', async () => {
|
||||
const { body } = await request
|
||||
.post(api('livechat/inquiries.returnAsInquiry'))
|
||||
.set(credentials)
|
||||
.send({ roomId: 'GENERAL' })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
|
||||
expect(body).to.have.property('success', false);
|
||||
expect(body).to.have.property('error', 'error-room-not-found');
|
||||
});
|
||||
it('should fail if room is closed', async () => {
|
||||
const visitor = await createVisitor();
|
||||
const room = await createLivechatRoom(visitor.token);
|
||||
await closeOmnichannelRoom(room._id);
|
||||
|
||||
const { body } = await request
|
||||
.post(api('livechat/inquiries.returnAsInquiry'))
|
||||
.set(credentials)
|
||||
.send({ roomId: room._id })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
|
||||
expect(body).to.have.property('success', false);
|
||||
expect(body).to.have.property('error', 'room-closed');
|
||||
|
||||
await deleteVisitor(visitor.token);
|
||||
});
|
||||
describe('no serving', () => {
|
||||
let room: IOmnichannelRoom;
|
||||
let visitor: ILivechatVisitor;
|
||||
before(async () => {
|
||||
visitor = await createVisitor();
|
||||
room = await createLivechatRoom(visitor.token);
|
||||
});
|
||||
after(async () => {
|
||||
await closeOmnichannelRoom(room._id);
|
||||
await deleteVisitor(visitor.token);
|
||||
});
|
||||
it('should fail if no one is serving the room', async () => {
|
||||
const { body } = await request
|
||||
.post(api('livechat/inquiries.returnAsInquiry'))
|
||||
.set(credentials)
|
||||
.send({ roomId: room._id })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
expect(body).to.have.property('success', true);
|
||||
expect(body).to.have.property('result', false);
|
||||
});
|
||||
});
|
||||
|
||||
let inquiry: ILivechatInquiryRecord;
|
||||
let room: IOmnichannelRoom;
|
||||
let returnAsInquiryDep: ILivechatDepartment;
|
||||
let returnAsInquiryVisitor: ILivechatVisitor;
|
||||
(IS_EE ? it : it.skip)('should move a room back to queue', async () => {
|
||||
await updateSetting('Omnichannel_enable_department_removal', true);
|
||||
returnAsInquiryDep = await createDepartment([{ agentId: testUser.user._id }]);
|
||||
returnAsInquiryVisitor = await createVisitor(returnAsInquiryDep._id);
|
||||
room = await createLivechatRoom(returnAsInquiryVisitor.token);
|
||||
const inq = await fetchInquiry(room._id);
|
||||
inquiry = inq;
|
||||
await takeInquiry(inq._id, testUser.credentials);
|
||||
|
||||
const { body } = await request
|
||||
.post(api('livechat/inquiries.returnAsInquiry'))
|
||||
.set(testUser.credentials)
|
||||
.send({ roomId: room._id })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
expect(body).to.have.property('success', true);
|
||||
expect(body).to.have.property('result', true);
|
||||
});
|
||||
(IS_EE ? it : it.skip)('should appear on users queued elements', async () => {
|
||||
const { body } = await request
|
||||
.get(api('livechat/inquiries.queuedForUser'))
|
||||
.set(testUser.credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
expect(body.inquiries).to.be.an('array');
|
||||
const depInq = body.inquiries.filter((inq: { _id: string }) => inq._id === inquiry._id);
|
||||
|
||||
expect(depInq.length).to.be.equal(1);
|
||||
|
||||
await closeOmnichannelRoom(room._id);
|
||||
await deleteVisitor(returnAsInquiryVisitor.token);
|
||||
await deleteDepartment(returnAsInquiryDep._id);
|
||||
await updateSetting('Omnichannel_enable_department_removal', false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('keep inquiry last message updated', () => {
|
||||
let room: any;
|
||||
let visitor: any;
|
||||
let agent: any;
|
||||
|
||||
before(async () => {
|
||||
agent = await createAgent();
|
||||
visitor = await createVisitor();
|
||||
|
||||
await makeAgentAvailable();
|
||||
room = await createLivechatRoom(visitor.token);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await closeOmnichannelRoom(room._id);
|
||||
await deleteVisitor(visitor.token);
|
||||
});
|
||||
|
||||
it('should update inquiry last message', async () => {
|
||||
const msgText = `update inquiry ${Date.now()}`;
|
||||
|
||||
await request.post(api('livechat/message')).send({ token: visitor.token, rid: room._id, msg: msgText }).expect(200);
|
||||
|
||||
const inquiry = await fetchInquiry(room._id);
|
||||
|
||||
expect(inquiry).to.have.property('_id', inquiry._id);
|
||||
expect(inquiry).to.have.property('rid', room._id);
|
||||
expect(inquiry).to.have.property('lastMessage');
|
||||
expect(inquiry.lastMessage).to.have.property('msg', msgText);
|
||||
});
|
||||
|
||||
it('should update room last message after inquiry is taken', async () => {
|
||||
const msgText = `update room ${Date.now()}`;
|
||||
|
||||
const inquiry = await fetchInquiry(room._id);
|
||||
|
||||
await request
|
||||
.post(api('livechat/inquiries.take'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
inquiryId: inquiry._id,
|
||||
userId: agent._id,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
});
|
||||
|
||||
await request.post(api('livechat/message')).send({ token: visitor.token, rid: room._id, msg: msgText }).expect(200);
|
||||
|
||||
// check room
|
||||
const roomInfo = await getLivechatRoomInfo(room._id);
|
||||
expect(roomInfo).to.have.property('lastMessage');
|
||||
expect(roomInfo.lastMessage).to.have.property('msg', msgText);
|
||||
});
|
||||
|
||||
it('should have the correct last message when room is returned to queue', async () => {
|
||||
const msgText = `return to queue ${Date.now()}`;
|
||||
|
||||
await request.post(api('livechat/message')).send({ token: visitor.token, rid: room._id, msg: msgText }).expect(200);
|
||||
|
||||
await request
|
||||
.post(api('livechat/inquiries.returnAsInquiry'))
|
||||
.set(credentials)
|
||||
.send({ roomId: room._id })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
const inquiry = await fetchInquiry(room._id);
|
||||
|
||||
expect(inquiry).to.have.property('_id', inquiry._id);
|
||||
expect(inquiry).to.have.property('rid', room._id);
|
||||
expect(inquiry).to.have.property('lastMessage');
|
||||
expect(inquiry.lastMessage).to.have.property('msg', msgText);
|
||||
});
|
||||
});
|
||||
|
||||
(IS_EE ? describe : describe.skip)('Auto Transfer Scheduler - Manual_Selection', () => {
|
||||
let testRoom: IOmnichannelRoom;
|
||||
let testDepartment: ILivechatDepartment;
|
||||
let testVisitor: ILivechatVisitor;
|
||||
before(async () => {
|
||||
// seconds
|
||||
await Promise.all([
|
||||
updateSetting('Livechat_auto_transfer_chat_timeout', 3),
|
||||
createAgent(),
|
||||
updateSetting('Omnichannel_enable_department_removal', true),
|
||||
]);
|
||||
testDepartment = await createDepartment([{ agentId: 'rocketchat.internal.admin.test' }]);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await deleteVisitor(testVisitor.token);
|
||||
await deleteDepartment(testDepartment._id);
|
||||
await Promise.all([
|
||||
updateSetting('Livechat_auto_transfer_chat_timeout', 0),
|
||||
updateSetting('Omnichannel_enable_department_removal', false),
|
||||
]);
|
||||
});
|
||||
|
||||
it('should create a room and schedule it for transfer', async () => {
|
||||
const { room, visitor } = await startANewLivechatRoomAndTakeIt({ departmentId: testDepartment._id });
|
||||
testVisitor = visitor;
|
||||
// The room returned is not updated :(
|
||||
const updatedRoom = await getLivechatRoomInfo(room._id);
|
||||
|
||||
expect(updatedRoom).to.have.property('servedBy').that.is.an('object');
|
||||
testRoom = updatedRoom;
|
||||
});
|
||||
it('should return a chat to the queue when not answered after 3 seconds', async () => {
|
||||
await retry(
|
||||
`The agenda has a 'heartbeat' of 3 seconds (for testing environment), so its likely that the first fetch will not have the room updated`,
|
||||
async () => {
|
||||
const inquiry = await fetchInquiry(testRoom._id);
|
||||
const room = await getLivechatRoomInfo(testRoom._id);
|
||||
|
||||
expect(room).to.not.have.property('servedBy');
|
||||
expect(inquiry).to.have.property('status', 'queued');
|
||||
|
||||
await closeOmnichannelRoom(testRoom._id);
|
||||
},
|
||||
{
|
||||
delayMs: 3000,
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
300
apps/meteor/tests/end-to-end/api/livechat/06-integrations.ts
Normal file
300
apps/meteor/tests/end-to-end/api/livechat/06-integrations.ts
Normal file
@@ -0,0 +1,300 @@
|
||||
import type { ILivechatVisitor, IOmnichannelRoom, ISetting } from '@rocket.chat/core-typings';
|
||||
import { expect } from 'chai';
|
||||
import { after, before, describe, it } from 'mocha';
|
||||
|
||||
import { getCredentials, api, request, credentials } from '../../../data/api-data';
|
||||
import {
|
||||
closeOmnichannelRoom,
|
||||
createAgent,
|
||||
createLivechatRoom,
|
||||
createVisitor,
|
||||
deleteVisitor,
|
||||
getLivechatRoomInfo,
|
||||
startANewLivechatRoomAndTakeIt,
|
||||
} from '../../../data/livechat/rooms';
|
||||
import { sleep } from '../../../data/livechat/utils';
|
||||
import { getLivechatVisitorByToken } from '../../../data/livechat/visitor';
|
||||
import { updatePermission, updateSetting } from '../../../data/permissions.helper';
|
||||
|
||||
describe('LIVECHAT - Integrations', () => {
|
||||
before((done) => getCredentials(done));
|
||||
|
||||
before(async () => {
|
||||
await updateSetting('Livechat_enabled', true);
|
||||
});
|
||||
|
||||
describe('livechat/integrations.settings', () => {
|
||||
it('should return an "unauthorized error" when the user does not have the necessary permission', async () => {
|
||||
await updatePermission('view-livechat-manager', []);
|
||||
await request.get(api('livechat/integrations.settings')).set(credentials).expect('Content-Type', 'application/json').expect(403);
|
||||
});
|
||||
it('should return an array of settings', (done) => {
|
||||
updatePermission('view-livechat-manager', ['admin'])
|
||||
.then(async () => {
|
||||
const res = await request
|
||||
.get(api('livechat/integrations.settings'))
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body.settings).to.be.an('array');
|
||||
const settingIds = res.body.settings.map((setting: ISetting) => setting._id);
|
||||
expect(settingIds).to.include.members([
|
||||
'Livechat_webhookUrl',
|
||||
'Livechat_secret_token',
|
||||
'Livechat_http_timeout',
|
||||
'Livechat_webhook_on_start',
|
||||
'Livechat_webhook_on_close',
|
||||
'Livechat_webhook_on_chat_taken',
|
||||
'Livechat_webhook_on_chat_queued',
|
||||
'Livechat_webhook_on_forward',
|
||||
'Livechat_webhook_on_offline_msg',
|
||||
'Livechat_webhook_on_visitor_message',
|
||||
'Livechat_webhook_on_agent_message',
|
||||
]);
|
||||
})
|
||||
.then(done)
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Incoming SMS', () => {
|
||||
let smsVisitor: ILivechatVisitor;
|
||||
let smsRoom: string;
|
||||
|
||||
before(async () => {
|
||||
await updateSetting('SMS_Enabled', true);
|
||||
await updateSetting('SMS_Service', '');
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await closeOmnichannelRoom(smsRoom);
|
||||
|
||||
await deleteVisitor(smsVisitor.token);
|
||||
|
||||
await updateSetting('SMS_Default_Omnichannel_Department', '');
|
||||
await updateSetting('SMS_Service', 'twilio');
|
||||
});
|
||||
|
||||
// Twilio sends the body as a x-www-form-urlencoded, the tests should do the same
|
||||
describe('POST livechat/sms-incoming/:service', () => {
|
||||
it('should throw an error if SMS is disabled', async () => {
|
||||
await updateSetting('SMS_Enabled', false);
|
||||
await request
|
||||
.post(api('livechat/sms-incoming/twilio'))
|
||||
.set(credentials)
|
||||
.send('from=%2B123456789&body=Hello')
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
it('should return an error when SMS service is not configured', async () => {
|
||||
await updateSetting('SMS_Enabled', true);
|
||||
await request
|
||||
.post(api('livechat/sms-incoming/twilio'))
|
||||
.set(credentials)
|
||||
.send('From=%2B123456789&To=%2B123456789&Body=Hello')
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
it('should throw an error if SMS_Default_Omnichannel_Department does not exists', async () => {
|
||||
await updateSetting('SMS_Default_Omnichannel_Department', '123');
|
||||
|
||||
await request
|
||||
.post(api('livechat/sms-incoming/twilio'))
|
||||
.set(credentials)
|
||||
.send('From=%2B123456789&To=%2B123456789&Body=Hello')
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
it('should return headers and <Response> as body on success', async () => {
|
||||
await updateSetting('SMS_Default_Omnichannel_Department', '');
|
||||
await updateSetting('SMS_Service', 'twilio');
|
||||
|
||||
// Create visitor with the phone number that will be used in SMS
|
||||
smsVisitor = await createVisitor(undefined, 'sms-visitor', undefined, '+12345678910');
|
||||
|
||||
const response = await request
|
||||
.post(api('livechat/sms-incoming/twilio'))
|
||||
.set(credentials)
|
||||
.send('From=%2B12345678910&To=%2B123456789&Body=Hello')
|
||||
.expect('Content-Type', 'text/xml')
|
||||
.expect(200);
|
||||
|
||||
expect(response).to.have.property('text', '<Response></Response>');
|
||||
|
||||
const lastChat = (await getLivechatVisitorByToken(smsVisitor.token)).lastChat?._id;
|
||||
expect(lastChat).to.exist;
|
||||
|
||||
smsRoom = lastChat as string;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Livechat - Webhooks', () => {
|
||||
const webhookUrl = process.env.WEBHOOK_TEST_URL || 'https://httpbin.org';
|
||||
|
||||
describe('livechat/webhook.test', () => {
|
||||
it('should fail when user doesnt have view-livechat-webhooks permission', async () => {
|
||||
await updatePermission('view-livechat-webhooks', []);
|
||||
const response = await request.post(api('livechat/webhook.test')).set(credentials).expect(403);
|
||||
expect(response.body).to.have.property('success', false);
|
||||
});
|
||||
it('should fail if setting Livechat_webhookUrl is not set', async () => {
|
||||
await updateSetting('Livechat_webhookUrl', '');
|
||||
await updatePermission('view-livechat-webhooks', ['admin', 'livechat-manager']);
|
||||
const response = await request.post(api('livechat/webhook.test')).set(credentials).expect(400);
|
||||
expect(response.body).to.have.property('success', false);
|
||||
});
|
||||
it('should return true if webhook test went good', async () => {
|
||||
await updateSetting('Livechat_webhookUrl', `${webhookUrl}/status/200`);
|
||||
const response = await request.post(api('livechat/webhook.test')).set(credentials).expect(200);
|
||||
expect(response.body.success).to.be.true;
|
||||
});
|
||||
it('should fail if webhook test went bad', async () => {
|
||||
await updateSetting('Livechat_webhookUrl', `${webhookUrl}/status/400`);
|
||||
await request.post(api('livechat/webhook.test')).set(credentials).expect(400);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Webhook notifications', () => {
|
||||
const visitorsToDelete: ILivechatVisitor[] = [];
|
||||
const roomsToClose: IOmnichannelRoom[] = [];
|
||||
|
||||
before(async () => {
|
||||
await updateSetting('Livechat_webhookUrl', `${webhookUrl}/anything`);
|
||||
await updateSetting('Livechat_Routing_Method', 'Manual_Selection');
|
||||
await createAgent();
|
||||
});
|
||||
after(async () => {
|
||||
await Promise.allSettled(roomsToClose.map((room) => closeOmnichannelRoom(room._id)));
|
||||
await Promise.allSettled(visitorsToDelete.map((visitor) => deleteVisitor(visitor.token)));
|
||||
await updateSetting('Livechat_webhookUrl', '');
|
||||
await updateSetting('Livechat_Routing_Method', 'Auto_Selection');
|
||||
await updateSetting('Livechat_webhook_on_start', false);
|
||||
await updateSetting('Livechat_webhook_on_close', false);
|
||||
await updateSetting('Livechat_webhook_on_chat_taken', false);
|
||||
await updateSetting('Livechat_webhook_on_chat_queued', false);
|
||||
});
|
||||
|
||||
it('should send a notification on chat start', async () => {
|
||||
await updateSetting('Livechat_webhook_on_start', true);
|
||||
|
||||
const { room, visitor } = await startANewLivechatRoomAndTakeIt();
|
||||
roomsToClose.push(room);
|
||||
visitorsToDelete.push(visitor);
|
||||
await sleep(1000);
|
||||
|
||||
const roomInfo = await getLivechatRoomInfo(room._id);
|
||||
|
||||
expect(roomInfo.crmData).to.be.an('string');
|
||||
expect(JSON.parse(roomInfo.crmData as string))
|
||||
.to.have.property('json')
|
||||
.that.has.property('type', 'LivechatSessionStart');
|
||||
await updateSetting('Livechat_webhook_on_start', false);
|
||||
});
|
||||
it('should send a notification on chat taken', async () => {
|
||||
await updateSetting('Livechat_webhook_on_chat_taken', true);
|
||||
|
||||
const { room, visitor } = await startANewLivechatRoomAndTakeIt();
|
||||
roomsToClose.push(room);
|
||||
visitorsToDelete.push(visitor);
|
||||
await sleep(1000);
|
||||
|
||||
const roomInfo = await getLivechatRoomInfo(room._id);
|
||||
|
||||
expect(roomInfo.crmData).to.be.an('string');
|
||||
expect(JSON.parse(roomInfo.crmData as string))
|
||||
.to.have.property('json')
|
||||
.that.has.property('type', 'LivechatSessionTaken');
|
||||
await updateSetting('Livechat_webhook_on_chat_taken', false);
|
||||
});
|
||||
let queuedRoom: IOmnichannelRoom;
|
||||
let queuedVisitor: ILivechatVisitor;
|
||||
it('should send a notification on chat queued', async () => {
|
||||
await updateSetting('Livechat_webhook_on_chat_queued', true);
|
||||
|
||||
queuedVisitor = await createVisitor();
|
||||
queuedRoom = await createLivechatRoom(queuedVisitor.token);
|
||||
roomsToClose.push(queuedRoom);
|
||||
visitorsToDelete.push(queuedVisitor);
|
||||
await sleep(1000);
|
||||
|
||||
const roomInfo = await getLivechatRoomInfo(queuedRoom._id);
|
||||
|
||||
expect(roomInfo.crmData).to.be.an('string');
|
||||
expect(JSON.parse(roomInfo.crmData as string))
|
||||
.to.have.property('json')
|
||||
.that.has.property('type', 'LivechatSessionQueued');
|
||||
await updateSetting('Livechat_webhook_on_chat_queued', false);
|
||||
});
|
||||
it('should send a notification on chat close', async () => {
|
||||
await updateSetting('Livechat_webhook_on_close', true);
|
||||
|
||||
await closeOmnichannelRoom(queuedRoom._id);
|
||||
const idx = roomsToClose.indexOf(queuedRoom);
|
||||
if (idx > -1) {
|
||||
roomsToClose.splice(idx, 1);
|
||||
}
|
||||
|
||||
await sleep(1000);
|
||||
|
||||
const roomInfo = await getLivechatRoomInfo(queuedRoom._id);
|
||||
|
||||
expect(roomInfo.crmData).to.be.an('string');
|
||||
expect(JSON.parse(roomInfo.crmData as string))
|
||||
.to.have.property('json')
|
||||
.that.has.property('type', 'LivechatSession');
|
||||
await updateSetting('Livechat_webhook_on_close', false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('omnichannel/integrations', () => {
|
||||
describe('POST', () => {
|
||||
it('should update the integration settings if the required parameters are provided', async () => {
|
||||
const response = await request
|
||||
.post(api('omnichannel/integrations'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
LivechatWebhookUrl: 'http://localhost:8080',
|
||||
LivechatSecretToken: 'asdfasdf',
|
||||
LivechatHttpTimeout: 3000,
|
||||
LivechatWebhookOnStart: false,
|
||||
LivechatWebhookOnClose: false,
|
||||
LivechatWebhookOnChatTaken: false,
|
||||
LivechatWebhookOnChatQueued: false,
|
||||
LivechatWebhookOnForward: false,
|
||||
LivechatWebhookOnOfflineMsg: false,
|
||||
LivechatWebhookOnVisitorMessage: false,
|
||||
LivechatWebhookOnAgentMessage: false,
|
||||
})
|
||||
.expect(200);
|
||||
expect(response.body).to.have.property('success', true);
|
||||
});
|
||||
it('should fail if a wrong type is provided', async () => {
|
||||
const response = await request
|
||||
.post(api('omnichannel/integrations'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
LivechatWebhookUrl: 8000,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
expect(response.body).to.have.property('success', true);
|
||||
});
|
||||
it('should fail if a wrong setting is provided', async () => {
|
||||
const response = await request
|
||||
.post(api('omnichannel/integrations'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
LivechatWebhook_url: 'http://localhost:8000',
|
||||
})
|
||||
.expect(400);
|
||||
expect(response.body).to.have.property('success', false);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
1479
apps/meteor/tests/end-to-end/api/livechat/07-queue.ts
Normal file
1479
apps/meteor/tests/end-to-end/api/livechat/07-queue.ts
Normal file
File diff suppressed because it is too large
Load Diff
426
apps/meteor/tests/end-to-end/api/livechat/08-triggers.ts
Normal file
426
apps/meteor/tests/end-to-end/api/livechat/08-triggers.ts
Normal file
@@ -0,0 +1,426 @@
|
||||
import { expect } from 'chai';
|
||||
import { before, describe, it, after } from 'mocha';
|
||||
import type { Response } from 'supertest';
|
||||
|
||||
import { getCredentials, api, request, credentials } from '../../../data/api-data';
|
||||
import { createTrigger, fetchTriggers } from '../../../data/livechat/triggers';
|
||||
import { removePermissionFromAllRoles, restorePermissionToRoles, updatePermission, updateSetting } from '../../../data/permissions.helper';
|
||||
import { IS_EE } from '../../../e2e/config/constants';
|
||||
|
||||
describe('LIVECHAT - triggers', () => {
|
||||
before((done) => getCredentials(done));
|
||||
|
||||
before(async () => {
|
||||
await updateSetting('Livechat_enabled', true);
|
||||
});
|
||||
|
||||
describe('livechat/triggers', () => {
|
||||
it('should return an "unauthorized error" when the user does not have the necessary permission', async () => {
|
||||
await removePermissionFromAllRoles('view-livechat-manager');
|
||||
await request.get(api('livechat/triggers')).set(credentials).expect('Content-Type', 'application/json').expect(403);
|
||||
});
|
||||
|
||||
it('should return an array of triggers', async () => {
|
||||
await restorePermissionToRoles('view-livechat-manager');
|
||||
await createTrigger(`test${Date.now()}`);
|
||||
await request
|
||||
.get(api('livechat/triggers'))
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body.triggers).to.be.an('array');
|
||||
expect(res.body).to.have.property('count').to.be.greaterThan(0);
|
||||
expect(res.body.triggers[0]).to.have.property('_id');
|
||||
expect(res.body.triggers[0]).to.have.property('name');
|
||||
expect(res.body.triggers[0]).to.have.property('description');
|
||||
expect(res.body.triggers[0]).to.have.property('enabled', true);
|
||||
expect(res.body.triggers[0]).to.have.property('runOnce').that.is.a('boolean');
|
||||
expect(res.body.triggers[0]).to.have.property('conditions').that.is.an('array').with.lengthOf.greaterThan(0);
|
||||
expect(res.body.triggers[0]).to.have.property('actions').that.is.an('array').with.lengthOf.greaterThan(0);
|
||||
expect(res.body).to.have.property('offset');
|
||||
expect(res.body).to.have.property('total');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST livechat/triggers', () => {
|
||||
it('should fail if user is not logged in', async () => {
|
||||
await request.post(api('livechat/triggers')).send({}).expect(401);
|
||||
});
|
||||
it('should fail if no data is sent', async () => {
|
||||
await request.post(api('livechat/triggers')).set(credentials).send({}).expect(400);
|
||||
});
|
||||
it('should fail if invalid data is sent', async () => {
|
||||
await request.post(api('livechat/triggers')).set(credentials).send({ name: 'test' }).expect(400);
|
||||
});
|
||||
it('should fail if name is not an string', async () => {
|
||||
await request
|
||||
.post(api('livechat/triggers'))
|
||||
.set(credentials)
|
||||
.send({ name: 1, description: 'test', enabled: true, runOnce: true, conditions: [], actions: [] })
|
||||
.expect(400);
|
||||
});
|
||||
it('should fail if description is not an string', async () => {
|
||||
await request
|
||||
.post(api('livechat/triggers'))
|
||||
.set(credentials)
|
||||
.send({ name: 'test', description: 1, enabled: true, runOnce: true, conditions: [], actions: [] })
|
||||
.expect(400);
|
||||
});
|
||||
it('should fail if enabled is not an boolean', async () => {
|
||||
await request
|
||||
.post(api('livechat/triggers'))
|
||||
.set(credentials)
|
||||
.send({ name: 'test', description: 'test', enabled: 1, runOnce: true, conditions: [], actions: [] })
|
||||
.expect(400);
|
||||
});
|
||||
it('should fail if runOnce is not an boolean', async () => {
|
||||
await request
|
||||
.post(api('livechat/triggers'))
|
||||
.set(credentials)
|
||||
.send({ name: 'test', description: 'test', enabled: true, runOnce: 1, conditions: [], actions: [] })
|
||||
.expect(400);
|
||||
});
|
||||
it('should fail if conditions is not an array', async () => {
|
||||
await request
|
||||
.post(api('livechat/triggers'))
|
||||
.set(credentials)
|
||||
.send({ name: 'test', description: 'test', enabled: true, runOnce: true, conditions: 1, actions: [] })
|
||||
.expect(400);
|
||||
});
|
||||
it('should fail if actions is not an array', async () => {
|
||||
await request
|
||||
.post(api('livechat/triggers'))
|
||||
.set(credentials)
|
||||
.send({ name: 'test', description: 'test', enabled: true, runOnce: true, conditions: [], actions: 1 })
|
||||
.expect(400);
|
||||
});
|
||||
it('should fail if conditions is an array with invalid data', async () => {
|
||||
await request
|
||||
.post(api('livechat/triggers'))
|
||||
.set(credentials)
|
||||
.send({ name: 'test', description: 'test', enabled: true, runOnce: true, conditions: [1], actions: [] })
|
||||
.expect(400);
|
||||
});
|
||||
it('should fail if conditions is an array of objects, but name is not a valid value', async () => {
|
||||
await request
|
||||
.post(api('livechat/triggers'))
|
||||
.set(credentials)
|
||||
.send({ name: 'test', description: 'test', enabled: true, runOnce: true, conditions: [{ name: 'invalid' }], actions: [] })
|
||||
.expect(400);
|
||||
});
|
||||
it('should fail if actions is an array of invalid values', async () => {
|
||||
await request
|
||||
.post(api('livechat/triggers'))
|
||||
.set(credentials)
|
||||
.send({ name: 'test', description: 'test', enabled: true, runOnce: true, conditions: [{ name: 'page-url' }], actions: [1] })
|
||||
.expect(400);
|
||||
});
|
||||
it('should fail if actions is an array of objects, but name is not a valid value', async () => {
|
||||
await request
|
||||
.post(api('livechat/triggers'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
name: 'test',
|
||||
description: 'test',
|
||||
enabled: true,
|
||||
runOnce: true,
|
||||
conditions: [{ name: 'page-url', value: 'http://localhost:3000' }],
|
||||
actions: [{ name: 'invalid' }],
|
||||
})
|
||||
.expect(400);
|
||||
});
|
||||
it('should fail if actions is an array of objects, but sender is not a valid value', async () => {
|
||||
await request
|
||||
.post(api('livechat/triggers'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
name: 'test',
|
||||
description: 'test',
|
||||
enabled: true,
|
||||
runOnce: true,
|
||||
conditions: [{ name: 'page-url' }],
|
||||
actions: [{ name: 'send-message', params: { sender: 'invalid' } }],
|
||||
})
|
||||
.expect(400);
|
||||
});
|
||||
it('should fail if actions is an array of objects, but msg is not a valid value', async () => {
|
||||
await request
|
||||
.post(api('livechat/triggers'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
name: 'test',
|
||||
description: 'test',
|
||||
enabled: true,
|
||||
runOnce: true,
|
||||
conditions: [{ name: 'page-url', value: 'http://localhost:3000' }],
|
||||
actions: [{ name: 'send-message', params: { sender: 'custom' } }],
|
||||
})
|
||||
.expect(400);
|
||||
});
|
||||
it('should fail if actions is an array of objects, but name is not a valid value', async () => {
|
||||
await request
|
||||
.post(api('livechat/triggers'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
name: 'test',
|
||||
description: 'test',
|
||||
enabled: true,
|
||||
runOnce: true,
|
||||
conditions: [{ name: 'page-url', value: 'http://localhost:3000' }],
|
||||
actions: [{ name: 'send-message', params: { sender: 'custom', msg: 'test', name: {} } }],
|
||||
})
|
||||
.expect(400);
|
||||
});
|
||||
it('should fail if user doesnt have view-livechat-manager permission', async () => {
|
||||
await removePermissionFromAllRoles('view-livechat-manager');
|
||||
await request
|
||||
.post(api('livechat/triggers'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
name: 'test',
|
||||
description: 'test',
|
||||
enabled: true,
|
||||
runOnce: true,
|
||||
conditions: [{ name: 'page-url', value: 'http://localhost:3000' }],
|
||||
actions: [{ name: 'send-message', params: { sender: 'custom', msg: 'test', name: 'test' } }],
|
||||
})
|
||||
.expect(403);
|
||||
});
|
||||
it('should save a new trigger of type send-message', async () => {
|
||||
await restorePermissionToRoles('view-livechat-manager');
|
||||
await request
|
||||
.post(api('livechat/triggers'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
name: 'test',
|
||||
description: 'test',
|
||||
enabled: true,
|
||||
runOnce: true,
|
||||
conditions: [{ name: 'page-url', value: 'http://localhost:3000' }],
|
||||
actions: [{ name: 'send-message', params: { sender: 'custom', msg: 'test', name: 'test' } }],
|
||||
})
|
||||
.expect(200);
|
||||
});
|
||||
it('should fail if type is use-external-service but serviceUrl is not a present', async () => {
|
||||
await request
|
||||
.post(api('livechat/triggers'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
name: 'test2',
|
||||
description: 'test2',
|
||||
enabled: true,
|
||||
runOnce: true,
|
||||
conditions: [{ name: 'page-url', value: 'http://localhost:3000' }],
|
||||
actions: [
|
||||
{
|
||||
name: 'use-external-service',
|
||||
params: {
|
||||
serviceTimeout: 5000,
|
||||
serviceFallbackMessage: 'Were sorry, we cannot complete your request',
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
.expect(400);
|
||||
});
|
||||
it('should fail if type is use-external-service but serviceTimeout is not a present', async () => {
|
||||
await request
|
||||
.post(api('livechat/triggers'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
name: 'test2',
|
||||
description: 'test2',
|
||||
enabled: true,
|
||||
runOnce: true,
|
||||
conditions: [{ name: 'page-url', value: 'http://localhost:3000' }],
|
||||
actions: [
|
||||
{
|
||||
name: 'use-external-service',
|
||||
params: {
|
||||
serviceUrl: 'http://localhost:3000/api/vX',
|
||||
serviceFallbackMessage: 'Were sorry, we cannot complete your request',
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
.expect(400);
|
||||
});
|
||||
it('should fail if type is use-external-service but serviceFallbackMessage is not a present', async () => {
|
||||
await request
|
||||
.post(api('livechat/triggers'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
name: 'test2',
|
||||
description: 'test2',
|
||||
enabled: true,
|
||||
runOnce: true,
|
||||
conditions: [{ name: 'page-url', value: 'http://localhost:3000' }],
|
||||
actions: [
|
||||
{
|
||||
name: 'use-external-service',
|
||||
params: {
|
||||
serviceUrl: 'http://localhost:3000/api/vX',
|
||||
serviceTimeout: 5000,
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
.expect(400);
|
||||
});
|
||||
it('should save a new trigger of type use-external-service', async () => {
|
||||
await request
|
||||
.post(api('livechat/triggers'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
name: 'test3',
|
||||
description: 'test3',
|
||||
enabled: true,
|
||||
runOnce: true,
|
||||
conditions: [{ name: 'page-url', value: 'http://localhost:3000' }],
|
||||
actions: [
|
||||
{
|
||||
name: 'use-external-service',
|
||||
params: {
|
||||
serviceUrl: 'http://localhost:3000/api/vX',
|
||||
serviceTimeout: 5000,
|
||||
serviceFallbackMessage: 'Were sorry, we cannot complete your request',
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
.expect(200);
|
||||
});
|
||||
});
|
||||
|
||||
(IS_EE ? describe : describe.skip)('POST livechat/triggers/external-service/test', () => {
|
||||
const webhookUrl = process.env.WEBHOOK_TEST_URL || 'https://httpbin.org';
|
||||
|
||||
after(() => Promise.all([updateSetting('Livechat_secret_token', ''), restorePermissionToRoles('view-livechat-manager')]));
|
||||
|
||||
it('should fail if user is not logged in', async () => {
|
||||
await request.post(api('livechat/triggers/external-service/test')).send({}).expect(401);
|
||||
});
|
||||
it('should fail if no data is sent', async () => {
|
||||
await request.post(api('livechat/triggers/external-service/test')).set(credentials).send({}).expect(400);
|
||||
});
|
||||
it('should fail if invalid data is sent', async () => {
|
||||
await request.post(api('livechat/triggers/external-service/test')).set(credentials).send({ webhookUrl: 'test' }).expect(400);
|
||||
});
|
||||
it('should fail if webhookUrl is not an string', async () => {
|
||||
await request
|
||||
.post(api('livechat/triggers/external-service/test'))
|
||||
.set(credentials)
|
||||
.send({ webhookUrl: 1, timeout: 1000, fallbackMessage: 'test', extraData: [] })
|
||||
.expect(400);
|
||||
});
|
||||
it('should fail if timeout is not an number', async () => {
|
||||
await request
|
||||
.post(api('livechat/triggers/external-service/test'))
|
||||
.set(credentials)
|
||||
.send({ webhookUrl: 'test', timeout: '1000', fallbackMessage: 'test', extraData: [] })
|
||||
.expect(400);
|
||||
});
|
||||
it('should fail if fallbackMessage is not an string', async () => {
|
||||
await request
|
||||
.post(api('livechat/triggers/external-service/test'))
|
||||
.set(credentials)
|
||||
.send({ webhookUrl: 'test', timeout: 1000, fallbackMessage: 1, extraData: [] })
|
||||
.expect(400);
|
||||
});
|
||||
it('should fail if params is not an array', async () => {
|
||||
await request
|
||||
.post(api('livechat/triggers/external-service/test'))
|
||||
.set(credentials)
|
||||
.send({ webhookUrl: 'test', timeout: 1000, fallbackMessage: 'test', extraData: 1 })
|
||||
.expect(400);
|
||||
});
|
||||
it('should fail if user doesnt have view-livechat-webhooks permission', async () => {
|
||||
await removePermissionFromAllRoles('view-livechat-manager');
|
||||
await request
|
||||
.post(api('livechat/triggers/external-service/test'))
|
||||
.set(credentials)
|
||||
.send({ webhookUrl: 'test', timeout: 1000, fallbackMessage: 'test', extraData: [] })
|
||||
.expect(403);
|
||||
});
|
||||
it('should fail if Livechat_secret_token setting is empty', async () => {
|
||||
await restorePermissionToRoles('view-livechat-manager');
|
||||
await updateSetting('Livechat_secret_token', '');
|
||||
await request
|
||||
.post(api('livechat/triggers/external-service/test'))
|
||||
.set(credentials)
|
||||
.send({ webhookUrl: 'test', timeout: 1000, fallbackMessage: 'test', extraData: [] })
|
||||
.expect(400);
|
||||
});
|
||||
it('should return error when webhook returns error', async () => {
|
||||
await updateSetting('Livechat_secret_token', 'test');
|
||||
|
||||
await request
|
||||
.post(api('livechat/triggers/external-service/test'))
|
||||
.set(credentials)
|
||||
.send({ webhookUrl: `${webhookUrl}/status/500`, timeout: 5000, fallbackMessage: 'test', extraData: [] })
|
||||
.expect(400)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error', 'error-invalid-external-service-response');
|
||||
expect(res.body).to.have.property('response').to.be.a('string');
|
||||
});
|
||||
});
|
||||
it('should return error when webhook times out', async () => {
|
||||
await request
|
||||
.post(api('livechat/triggers/external-service/test'))
|
||||
.set(credentials)
|
||||
.send({ webhookUrl: `${webhookUrl}/delay/2`, timeout: 1000, fallbackMessage: 'test', extraData: [] })
|
||||
.expect(400)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error', 'error-timeout');
|
||||
expect(res.body).to.have.property('response').to.be.a('string');
|
||||
});
|
||||
});
|
||||
it('should fail when webhook returns an answer that doesnt match the format', async () => {
|
||||
await request
|
||||
.post(api('livechat/triggers/external-service/test'))
|
||||
.set(credentials)
|
||||
.send({ webhookUrl: `${webhookUrl}/anything`, timeout: 5000, fallbackMessage: 'test', extraData: [] })
|
||||
.expect(400)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error', 'error-invalid-external-service-response');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('livechat/triggers/:id', () => {
|
||||
it('should return an "unauthorized error" when the user does not have the necessary permission', async () => {
|
||||
await updatePermission('view-livechat-manager', []);
|
||||
await request.get(api('livechat/triggers/invalid-id')).set(credentials).expect('Content-Type', 'application/json').expect(403);
|
||||
await updatePermission('view-livechat-manager', ['admin']);
|
||||
});
|
||||
it('should return null when trigger does not exist', async () => {
|
||||
await updatePermission('view-livechat-manager', ['admin']);
|
||||
const response = await request
|
||||
.get(api('livechat/triggers/invalid-id'))
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
expect(response.body).to.have.property('success', true);
|
||||
expect(response.body.trigger).to.be.null;
|
||||
});
|
||||
it('should return the trigger', async () => {
|
||||
const triggerName = `test${Date.now()}`;
|
||||
await createTrigger(triggerName);
|
||||
const trigger = (await fetchTriggers()).find((t) => t.name === triggerName);
|
||||
const response = await request
|
||||
.get(api(`livechat/triggers/${trigger?._id}`))
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
expect(response.body).to.have.property('success', true);
|
||||
expect(response.body.trigger).to.be.an('object');
|
||||
expect(response.body.trigger).to.have.property('_id', trigger?._id);
|
||||
});
|
||||
});
|
||||
});
|
||||
1680
apps/meteor/tests/end-to-end/api/livechat/09-visitors.ts
Normal file
1680
apps/meteor/tests/end-to-end/api/livechat/09-visitors.ts
Normal file
File diff suppressed because it is too large
Load Diff
1022
apps/meteor/tests/end-to-end/api/livechat/10-departments.ts
Normal file
1022
apps/meteor/tests/end-to-end/api/livechat/10-departments.ts
Normal file
File diff suppressed because it is too large
Load Diff
215
apps/meteor/tests/end-to-end/api/livechat/11-email-inbox.ts
Normal file
215
apps/meteor/tests/end-to-end/api/livechat/11-email-inbox.ts
Normal file
@@ -0,0 +1,215 @@
|
||||
import type { IEmailInbox } from '@rocket.chat/core-typings';
|
||||
import { expect } from 'chai';
|
||||
import { after, before, describe, it } from 'mocha';
|
||||
import type { Response } from 'supertest';
|
||||
|
||||
import { getCredentials, api, request, credentials } from '../../../data/api-data';
|
||||
import { createEmailInbox } from '../../../data/livechat/inboxes';
|
||||
import { updatePermission } from '../../../data/permissions.helper';
|
||||
|
||||
describe('Email inbox', () => {
|
||||
before((done) => getCredentials(done));
|
||||
let testInbox = '';
|
||||
before(async () => {
|
||||
await request
|
||||
.post(api('email-inbox'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
active: true,
|
||||
name: 'test-email-inbox##',
|
||||
email: 'test-email@example.com',
|
||||
description: 'test email inbox',
|
||||
senderInfo: 'test email inbox',
|
||||
smtp: {
|
||||
server: 'smtp.example.com',
|
||||
port: 587,
|
||||
username: 'example@example.com',
|
||||
password: 'not-a-real-password',
|
||||
secure: true,
|
||||
},
|
||||
imap: {
|
||||
server: 'imap.example.com',
|
||||
port: 993,
|
||||
username: 'example@example.com',
|
||||
password: 'not-a-real-password',
|
||||
secure: true,
|
||||
maxRetries: 10,
|
||||
},
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success');
|
||||
if (res.body.success === true) {
|
||||
testInbox = res.body._id;
|
||||
} else {
|
||||
expect(res.body).to.have.property('error');
|
||||
expect(res.body.error.includes('E11000')).to.be.eq(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
after(async () => {
|
||||
if (testInbox) {
|
||||
await request
|
||||
.delete(api(`email-inbox/${testInbox}`))
|
||||
.set(credentials)
|
||||
.send()
|
||||
.expect(200);
|
||||
}
|
||||
});
|
||||
describe('GET email-inbox.list', () => {
|
||||
it('should fail if user doesnt have manage-email-inbox permission', async () => {
|
||||
await updatePermission('manage-email-inbox', []);
|
||||
await request.get(api('email-inbox.list')).set(credentials).expect(403);
|
||||
});
|
||||
it('should return a list of email inboxes', async () => {
|
||||
await updatePermission('manage-email-inbox', ['admin']);
|
||||
const res = await request.get(api('email-inbox.list')).set(credentials).send().expect('Content-Type', 'application/json').expect(200);
|
||||
|
||||
expect(res.body).to.have.property('emailInboxes');
|
||||
expect(res.body.emailInboxes).to.be.an('array');
|
||||
expect(res.body.emailInboxes).to.have.length.of.at.least(1);
|
||||
expect(res.body.emailInboxes.filter((ibx: IEmailInbox) => ibx.email === 'test-email@example.com')).to.have.length.gte(1);
|
||||
// make sure we delete the test inbox, even if creation failed on this test run
|
||||
testInbox = res.body.emailInboxes.filter((ibx: IEmailInbox) => ibx.email === 'test-email@example.com')[0]._id;
|
||||
expect(res.body).to.have.property('total');
|
||||
expect(res.body.total).to.be.a('number');
|
||||
expect(res.body).to.have.property('count');
|
||||
expect(res.body.count).to.be.a('number');
|
||||
expect(res.body).to.have.property('offset');
|
||||
expect(res.body.offset).to.be.a('number');
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST email-inbox', () => {
|
||||
let inboxId: string;
|
||||
const mockedPayload = {
|
||||
name: 'test',
|
||||
active: false,
|
||||
email: `test${new Date().getTime()}@test.com`,
|
||||
description: 'Updated test description',
|
||||
senderInfo: 'test',
|
||||
smtp: {
|
||||
server: 'smtp.example.com',
|
||||
port: 587,
|
||||
username: 'xxxx',
|
||||
password: 'xxxx',
|
||||
secure: true,
|
||||
},
|
||||
imap: {
|
||||
server: 'imap.example.com',
|
||||
port: 993,
|
||||
username: 'xxxx',
|
||||
password: 'xxxx',
|
||||
secure: true,
|
||||
maxRetries: 10,
|
||||
},
|
||||
};
|
||||
it('should fail if user doesnt have manage-email-inbox permission', async () => {
|
||||
await updatePermission('manage-email-inbox', []);
|
||||
await request.post(api('email-inbox')).set(credentials).send(mockedPayload).expect(403);
|
||||
});
|
||||
it('should fail if smtp config is not on body params', async () => {
|
||||
await updatePermission('manage-email-inbox', ['admin']);
|
||||
await request.post(api('email-inbox')).set(credentials).send({}).expect(400);
|
||||
});
|
||||
it('should fail if imap config is not on body params', async () => {
|
||||
const { imap, ...payload } = mockedPayload;
|
||||
await request.post(api('email-inbox')).set(credentials).send(payload).expect(400);
|
||||
});
|
||||
it('should fail if name is not on body params', async () => {
|
||||
await updatePermission('manage-email-inbox', ['admin']);
|
||||
const { name, ...payload } = mockedPayload;
|
||||
await request.post(api('email-inbox')).set(credentials).send(payload).expect(400);
|
||||
});
|
||||
it('should fail if active is not on body params', async () => {
|
||||
await updatePermission('manage-email-inbox', ['admin']);
|
||||
const { active, ...payload } = mockedPayload;
|
||||
await request.post(api('email-inbox')).set(credentials).send(payload).expect(400);
|
||||
});
|
||||
it('should fail if email is not on body params', async () => {
|
||||
await updatePermission('manage-email-inbox', ['admin']);
|
||||
const { email, ...payload } = mockedPayload;
|
||||
await request.post(api('email-inbox')).set(credentials).send(payload).expect(400);
|
||||
});
|
||||
it('should save an email inbox', async () => {
|
||||
await updatePermission('manage-email-inbox', ['admin']);
|
||||
const { email, ...payload } = mockedPayload;
|
||||
const { body } = await request
|
||||
.post(api('email-inbox'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
...payload,
|
||||
email: `test${new Date().getTime()}@test.com`,
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
expect(body).to.have.property('_id');
|
||||
inboxId = body._id;
|
||||
});
|
||||
it('should update an email inbox when _id is passed in the object', async () => {
|
||||
await updatePermission('manage-email-inbox', ['admin']);
|
||||
const { body } = await request
|
||||
.post(api('email-inbox'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
...mockedPayload,
|
||||
_id: inboxId,
|
||||
description: 'Updated test description',
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
expect(body).to.have.property('_id');
|
||||
});
|
||||
});
|
||||
describe('GET email-inbox/:_id', () => {
|
||||
it('should fail if user doesnt have manage-email-inbox permission', async () => {
|
||||
await updatePermission('manage-email-inbox', []);
|
||||
await request.get(api('email-inbox/123')).set(credentials).expect(403);
|
||||
});
|
||||
it('should fail when email inbox does not exist', async () => {
|
||||
await updatePermission('manage-email-inbox', ['admin']);
|
||||
await request.get(api('email-inbox/123')).set(credentials).expect(404);
|
||||
});
|
||||
it('should return an email inbox', async () => {
|
||||
const inbox = await createEmailInbox();
|
||||
const { body } = await request
|
||||
.get(api(`email-inbox/${inbox._id}`))
|
||||
.set(credentials)
|
||||
.expect(200);
|
||||
|
||||
expect(body).to.have.property('_id');
|
||||
expect(body).to.have.property('name', 'test');
|
||||
});
|
||||
});
|
||||
describe('DELETE email-inbox/:_id', () => {
|
||||
it('should fail if user doesnt have manage-email-inbox permission', async () => {
|
||||
await updatePermission('manage-email-inbox', []);
|
||||
await request.delete(api('email-inbox/123')).set(credentials).expect(403);
|
||||
});
|
||||
it('should return nothing when email inbox does not exist', async () => {
|
||||
await updatePermission('manage-email-inbox', ['admin']);
|
||||
await request.delete(api('email-inbox/123')).set(credentials).expect(404);
|
||||
});
|
||||
it('should delete an email inbox', async () => {
|
||||
const inbox = await createEmailInbox();
|
||||
const { body } = await request
|
||||
.delete(api(`email-inbox/${inbox._id}`))
|
||||
.set(credentials)
|
||||
.expect(200);
|
||||
|
||||
expect(body).to.have.property('success', true);
|
||||
});
|
||||
});
|
||||
describe('GET email-inbox.search', () => {
|
||||
it('should fail if user doesnt have manage-email-inbox permission', async () => {
|
||||
await updatePermission('manage-email-inbox', []);
|
||||
await request.get(api('email-inbox.search')).set(credentials).expect(403);
|
||||
});
|
||||
it('should return an email inbox matching email', async () => {
|
||||
await createEmailInbox();
|
||||
await updatePermission('manage-email-inbox', ['admin']);
|
||||
await request.get(api('email-inbox.search')).query({ email: 'test' }).set(credentials).expect(200);
|
||||
});
|
||||
});
|
||||
});
|
||||
844
apps/meteor/tests/end-to-end/api/livechat/11-livechat.ts
Normal file
844
apps/meteor/tests/end-to-end/api/livechat/11-livechat.ts
Normal file
@@ -0,0 +1,844 @@
|
||||
import type { Credentials } from '@rocket.chat/api-client';
|
||||
import type { ILivechatDepartment, IUser } from '@rocket.chat/core-typings';
|
||||
import { expect } from 'chai';
|
||||
import { after, before, describe, it } from 'mocha';
|
||||
|
||||
import { sleep } from '../../../../lib/utils/sleep';
|
||||
import { getCredentials, api, request, credentials } from '../../../data/api-data';
|
||||
import { createCustomField, deleteCustomField } from '../../../data/livechat/custom-fields';
|
||||
import {
|
||||
addOrRemoveAgentFromDepartment,
|
||||
createDepartmentWithAnOfflineAgent,
|
||||
createDepartmentWithAnOnlineAgent,
|
||||
deleteDepartment,
|
||||
} from '../../../data/livechat/department';
|
||||
import {
|
||||
createVisitor,
|
||||
createLivechatRoom,
|
||||
makeAgentUnavailable,
|
||||
closeOmnichannelRoom,
|
||||
sendMessage,
|
||||
deleteVisitor,
|
||||
createDepartment,
|
||||
} from '../../../data/livechat/rooms';
|
||||
import { createBotAgent, getRandomVisitorToken } from '../../../data/livechat/users';
|
||||
import type { WithRequiredProperty } from '../../../data/livechat/utils';
|
||||
import { removePermissionFromAllRoles, restorePermissionToRoles, updatePermission, updateSetting } from '../../../data/permissions.helper';
|
||||
import { deleteUser } from '../../../data/users.helper';
|
||||
import { IS_EE } from '../../../e2e/config/constants';
|
||||
|
||||
describe('LIVECHAT - Utils', () => {
|
||||
before((done) => getCredentials(done));
|
||||
before(async () => {
|
||||
await updateSetting('Omnichannel_enable_department_removal', true);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await updateSetting('Livechat_enabled', true);
|
||||
await updateSetting('Livechat_offline_email', '');
|
||||
await updateSetting('Omnichannel_enable_department_removal', false);
|
||||
});
|
||||
|
||||
describe('livechat/offline.message', () => {
|
||||
it('should fail if name is not sent as body parameter', async () => {
|
||||
const { body } = await request.post(api('livechat/offline.message')).set(credentials).send({});
|
||||
expect(body).to.have.property('success', false);
|
||||
});
|
||||
it('should fail if email is not sent as body parameter', async () => {
|
||||
const { body } = await request.post(api('livechat/offline.message')).set(credentials).send({ name: 'test' });
|
||||
expect(body).to.have.property('success', false);
|
||||
});
|
||||
it('should fail if message is not sent as body parameter', async () => {
|
||||
await request.post(api('livechat/offline.message')).set(credentials).send({ name: 'test', email: '' }).expect(400);
|
||||
});
|
||||
it('should fail if setting Livechat_display_offline_form is disabled', async () => {
|
||||
await updateSetting('Livechat_display_offline_form', false);
|
||||
await request.post(api('livechat/offline.message')).set(credentials).send({ name: 'test', email: '', message: 'test' }).expect(400);
|
||||
});
|
||||
it('should fail if Livechat_validate_offline_email is enabled and email passed is not resolvable', async () => {
|
||||
await updateSetting('Livechat_display_offline_form', true);
|
||||
await updateSetting('Livechat_validate_offline_email', true);
|
||||
await request
|
||||
.post(api('livechat/offline.message'))
|
||||
.set(credentials)
|
||||
.send({ name: 'test', email: 'test@fadsjfldasjfd.com', message: 'test' })
|
||||
.expect(400);
|
||||
});
|
||||
it('should fail if setting Livechat_offline_email is not setup or is invalid', async () => {
|
||||
await updateSetting('Livechat_validate_offline_email', false);
|
||||
await updateSetting('Livechat_validate_offline_email', 'afsdxcvxc');
|
||||
await request
|
||||
.post(api('livechat/offline.message'))
|
||||
.set(credentials)
|
||||
.send({ name: 'test', email: 'test@email.com', message: 'this is a test :)' })
|
||||
.expect(400);
|
||||
});
|
||||
it('should send an offline email', async () => {
|
||||
await updateSetting('Livechat_validate_offline_email', false);
|
||||
await updateSetting('Livechat_offline_email', 'test-email@rocket.chat');
|
||||
await request
|
||||
.post(api('livechat/offline.message'))
|
||||
.set(credentials)
|
||||
.send({ name: 'test', email: 'test@email.com', message: 'this is a test :)' })
|
||||
.expect(200);
|
||||
});
|
||||
});
|
||||
|
||||
describe('livechat/config', () => {
|
||||
let emptyDepartment: ILivechatDepartment;
|
||||
let forwardDepartment: ILivechatDepartment;
|
||||
let testDepartment: ILivechatDepartment;
|
||||
let agent: { user: WithRequiredProperty<IUser, 'username'>; credentials: Credentials };
|
||||
let agent2: { user: WithRequiredProperty<IUser, 'username'>; credentials: Credentials };
|
||||
|
||||
before(async () => {
|
||||
if (!IS_EE) {
|
||||
return;
|
||||
}
|
||||
|
||||
emptyDepartment = await createDepartment();
|
||||
({ department: forwardDepartment, agent } = await createDepartmentWithAnOnlineAgent());
|
||||
({ department: testDepartment, agent: agent2 } = await createDepartmentWithAnOfflineAgent({
|
||||
departmentsAllowedToForward: [forwardDepartment._id],
|
||||
}));
|
||||
});
|
||||
|
||||
after(() => {
|
||||
if (!IS_EE) {
|
||||
return;
|
||||
}
|
||||
|
||||
return Promise.all([
|
||||
deleteDepartment(emptyDepartment._id),
|
||||
deleteDepartment(forwardDepartment._id),
|
||||
deleteDepartment(testDepartment._id),
|
||||
deleteUser(agent.user),
|
||||
deleteUser(agent2.user),
|
||||
]);
|
||||
});
|
||||
|
||||
it('should return enabled: false if livechat is disabled', async () => {
|
||||
await updateSetting('Livechat_enabled', false);
|
||||
const { body } = await request.get(api('livechat/config')).set(credentials);
|
||||
expect(body).to.have.property('config');
|
||||
expect(body.config).to.have.property('enabled', false);
|
||||
});
|
||||
it('should return basic livechat config when enabled', async () => {
|
||||
await updateSetting('Livechat_enabled', true);
|
||||
const { body } = await request.get(api('livechat/config')).set(credentials);
|
||||
expect(body).to.have.property('config');
|
||||
expect(body.config).to.have.property('enabled', true);
|
||||
expect(body.config).to.have.property('settings');
|
||||
expect(body.config).to.have.property('departments').that.is.an('array');
|
||||
});
|
||||
it('should have custom fields data', async () => {
|
||||
const customFieldName = `new_custom_field_${Date.now()}`;
|
||||
await createCustomField({
|
||||
searchable: true,
|
||||
field: customFieldName,
|
||||
label: customFieldName,
|
||||
defaultValue: 'test_default_address',
|
||||
scope: 'visitor',
|
||||
visibility: 'visible',
|
||||
regexp: '',
|
||||
public: true,
|
||||
required: false,
|
||||
options: '',
|
||||
});
|
||||
|
||||
const { body } = await request.get(api('livechat/config')).set(credentials);
|
||||
|
||||
expect(body).to.have.property('config');
|
||||
expect(body.config).to.have.property('customFields').that.is.an('array');
|
||||
expect(body.config.customFields).to.have.length.greaterThan(0);
|
||||
const customField = body.config.customFields.find((field: any) => field._id === customFieldName);
|
||||
expect(customField).to.be.an('object');
|
||||
expect(customField).to.have.property('label', customFieldName);
|
||||
|
||||
await deleteCustomField(customFieldName);
|
||||
});
|
||||
(IS_EE ? it : it.skip)('should return online as true if there is at least one agent online', async () => {
|
||||
const { department } = await createDepartmentWithAnOnlineAgent();
|
||||
|
||||
const { body } = await request.get(api('livechat/config')).query({ department: department._id }).set(credentials);
|
||||
expect(body).to.have.property('config');
|
||||
expect(body.config).to.have.property('online', true);
|
||||
});
|
||||
(IS_EE ? it : it.skip)('should return online as false if there is no agent online', async () => {
|
||||
const { department, agent } = await createDepartmentWithAnOnlineAgent();
|
||||
await makeAgentUnavailable(agent.credentials);
|
||||
|
||||
const { body } = await request.get(api('livechat/config')).query({ department: department._id }).set(credentials);
|
||||
expect(body).to.have.property('config');
|
||||
expect(body.config).to.have.property('online', false);
|
||||
});
|
||||
(IS_EE ? it : it.skip)('should return online as true if bot is online and there is no agent online', async () => {
|
||||
await updateSetting('Livechat_assign_new_conversation_to_bot', true);
|
||||
|
||||
const { department, agent } = await createDepartmentWithAnOnlineAgent();
|
||||
await makeAgentUnavailable(agent.credentials);
|
||||
|
||||
const botUser = await createBotAgent();
|
||||
await addOrRemoveAgentFromDepartment(department._id, { agentId: botUser.user._id, username: botUser.user.username as string }, true);
|
||||
|
||||
const { body } = await request.get(api('livechat/config')).query({ department: department._id }).set(credentials);
|
||||
expect(body).to.have.property('config');
|
||||
|
||||
await updateSetting('Livechat_assign_new_conversation_to_bot', false);
|
||||
await makeAgentUnavailable(botUser.credentials);
|
||||
});
|
||||
it('should return a guest if there exists a guest with the same token', async () => {
|
||||
const guest = await createVisitor();
|
||||
const { body } = await request.get(api('livechat/config')).query({ token: guest.token }).set(credentials);
|
||||
expect(body).to.have.property('config');
|
||||
expect(body.config).to.have.property('guest');
|
||||
expect(body.config.guest).to.have.property('name', guest.name);
|
||||
});
|
||||
it('should not return a guest if there exists a guest with the same token but the guest is not online', async () => {
|
||||
const token = getRandomVisitorToken();
|
||||
|
||||
const { body } = await request.get(api('livechat/config')).query({ token }).set(credentials);
|
||||
expect(body).to.have.property('config');
|
||||
expect(body.config).to.not.have.property('guest');
|
||||
});
|
||||
it('should return no online room if visitor is not chatting with an agent', async () => {
|
||||
const visitor = await createVisitor();
|
||||
const { body } = await request.get(api('livechat/config')).query({ token: visitor.token }).set(credentials);
|
||||
expect(body).to.have.property('config');
|
||||
expect(body.config).to.not.have.property('room');
|
||||
});
|
||||
it('should return online room if visitor is already chatting with an agent', async () => {
|
||||
const newVisitor = await createVisitor();
|
||||
const newRoom = await createLivechatRoom(newVisitor.token);
|
||||
|
||||
const { body } = await request.get(api('livechat/config')).query({ token: newVisitor.token }).set(credentials);
|
||||
|
||||
expect(body).to.have.property('config');
|
||||
expect(body.config).to.have.property('room');
|
||||
expect(body.config.room).to.have.property('_id', newRoom._id);
|
||||
});
|
||||
(IS_EE ? it : it.skip)('should return list of departments with at least one agent', async () => {
|
||||
const { body } = await request.get(api('livechat/config')).set(credentials);
|
||||
|
||||
expect(body).to.have.property('success', true);
|
||||
expect(body).to.have.property('config');
|
||||
expect(body.config).to.have.property('departments');
|
||||
expect(body.config.departments).to.be.an('array').with.lengthOf.at.least(2);
|
||||
|
||||
expect(body.config.departments).to.not.deep.include({
|
||||
_id: emptyDepartment._id,
|
||||
name: emptyDepartment.name,
|
||||
showOnRegistration: emptyDepartment.showOnRegistration,
|
||||
showOnOfflineForm: emptyDepartment.showOnOfflineForm,
|
||||
});
|
||||
expect(body.config.departments).to.deep.include({
|
||||
_id: forwardDepartment._id,
|
||||
name: forwardDepartment.name,
|
||||
showOnRegistration: forwardDepartment.showOnRegistration,
|
||||
showOnOfflineForm: forwardDepartment.showOnOfflineForm,
|
||||
});
|
||||
expect(body.config.departments).to.deep.include({
|
||||
_id: testDepartment._id,
|
||||
name: testDepartment.name,
|
||||
showOnRegistration: testDepartment.showOnRegistration,
|
||||
showOnOfflineForm: testDepartment.showOnOfflineForm,
|
||||
departmentsAllowedToForward: [forwardDepartment._id],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('livechat/page.visited', () => {
|
||||
it('should fail if token is not sent as body param', async () => {
|
||||
const { body } = await request.post(api('livechat/page.visited')).set(credentials).send({});
|
||||
expect(body).to.have.property('success', false);
|
||||
});
|
||||
it('should fail if rid is not an string', async () => {
|
||||
const { body } = await request.post(api('livechat/page.visited')).set(credentials).send({ token: 'test', rid: {} });
|
||||
expect(body).to.have.property('success', false);
|
||||
});
|
||||
it('should fail if pageInfo is not sent as body param', async () => {
|
||||
const { body } = await request.post(api('livechat/page.visited')).set(credentials).send({ token: 'test', rid: 'test' });
|
||||
expect(body).to.have.property('success', false);
|
||||
});
|
||||
it('should fail if pageInfo is not of the right format', async () => {
|
||||
const { body } = await request.post(api('livechat/page.visited')).set(credentials).send({ token: 'test', rid: 'test', pageInfo: {} });
|
||||
expect(body).to.have.property('success', false);
|
||||
});
|
||||
it('should return empty if pageInfo.change is not equal to Livechat_history_monitor_type', async () => {
|
||||
const { body } = await request
|
||||
.post(api('livechat/page.visited'))
|
||||
.set(credentials)
|
||||
.send({ token: 'test', rid: 'test', pageInfo: { change: 'test', title: 'test', location: { href: 'test' } } });
|
||||
expect(body).to.have.property('success', true);
|
||||
});
|
||||
it('should store values for 1 month if visitor has no room yet', async () => {
|
||||
const { body } = await request
|
||||
.post(api('livechat/page.visited'))
|
||||
.set(credentials)
|
||||
.send({ token: 'test', rid: 'test', pageInfo: { change: 'url', title: 'Rocket.Chat', location: { href: 'https://rocket.chat' } } });
|
||||
expect(body).to.have.property('success', true);
|
||||
expect(body).to.have.property('page');
|
||||
expect(body.page).to.have.property('navigation');
|
||||
expect(body.page).to.have.property('msg');
|
||||
});
|
||||
it('should store values correctly on visitor room when room is valid', async () => {
|
||||
const visitor = await createVisitor();
|
||||
const room = await createLivechatRoom(visitor.token);
|
||||
|
||||
const { body } = await request
|
||||
.post(api('livechat/page.visited'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
token: visitor.token,
|
||||
rid: room._id,
|
||||
pageInfo: { change: 'url', title: 'Rocket.Chat', location: { href: 'https://rocket.chat' } },
|
||||
});
|
||||
|
||||
expect(body).to.have.property('success', true);
|
||||
expect(body).to.have.property('page');
|
||||
expect(body.page).to.have.property('navigation');
|
||||
expect(body.page).to.have.property('msg');
|
||||
});
|
||||
});
|
||||
|
||||
describe('livechat/transcript', () => {
|
||||
it('should fail if token is not in body params', async () => {
|
||||
const { body } = await request.post(api('livechat/transcript')).set(credentials).send({});
|
||||
expect(body).to.have.property('success', false);
|
||||
});
|
||||
it('should fail if rid is not in body params', async () => {
|
||||
const { body } = await request.post(api('livechat/transcript')).set(credentials).send({ token: 'test' });
|
||||
expect(body).to.have.property('success', false);
|
||||
});
|
||||
it('should fail if email is not in body params', async () => {
|
||||
const { body } = await request.post(api('livechat/transcript')).set(credentials).send({ token: 'test', rid: 'test' });
|
||||
expect(body).to.have.property('success', false);
|
||||
});
|
||||
it('should fail if token is not a valid guest token', async () => {
|
||||
const { body } = await request.post(api('livechat/transcript')).set(credentials).send({ token: 'test', rid: 'test', email: '' });
|
||||
expect(body).to.have.property('success', false);
|
||||
});
|
||||
it('should fail if rid is not a valid room id', async () => {
|
||||
const visitor = await createVisitor();
|
||||
const { body } = await request
|
||||
.post(api('livechat/transcript'))
|
||||
.set(credentials)
|
||||
.send({ token: visitor.token, rid: 'test', email: '' });
|
||||
expect(body).to.have.property('success', false);
|
||||
});
|
||||
it('should fail if requesting a transcript of another visitors room', async () => {
|
||||
const visitor = await createVisitor();
|
||||
const room = await createLivechatRoom(visitor.token);
|
||||
const visitor2 = await createVisitor();
|
||||
|
||||
const { body } = await request
|
||||
.post(api('livechat/transcript'))
|
||||
.set(credentials)
|
||||
.send({ token: visitor2.token, rid: room._id, email: '' });
|
||||
expect(body).to.have.property('success', false);
|
||||
});
|
||||
it('should fail if email is not a valid email', async () => {
|
||||
const visitor = await createVisitor();
|
||||
const room = await createLivechatRoom(visitor.token);
|
||||
|
||||
const { body } = await request
|
||||
.post(api('livechat/transcript'))
|
||||
.set(credentials)
|
||||
.send({ token: visitor.token, rid: room._id, email: 'test' });
|
||||
expect(body).to.have.property('success', false);
|
||||
});
|
||||
it('should send a transcript if all is good', async () => {
|
||||
const visitor = await createVisitor();
|
||||
const room = await createLivechatRoom(visitor.token);
|
||||
|
||||
const { body } = await request
|
||||
.post(api('livechat/transcript'))
|
||||
.set(credentials)
|
||||
.send({ token: visitor.token, rid: room._id, email: 'visitor@notadomain.com' });
|
||||
expect(body).to.have.property('success', true);
|
||||
});
|
||||
it('should allow a visitor to get a transcript even if token changed by using an old token that matches room.v', async () => {
|
||||
const visitor = await createVisitor();
|
||||
const room = await createLivechatRoom(visitor.token);
|
||||
await closeOmnichannelRoom(room._id);
|
||||
const visitor2 = await createVisitor(undefined, undefined, visitor.visitorEmails?.[0].address);
|
||||
const room2 = await createLivechatRoom(visitor2.token);
|
||||
await closeOmnichannelRoom(room2._id);
|
||||
|
||||
expect(visitor.token !== visitor2.token).to.be.true;
|
||||
const { body } = await request
|
||||
.post(api('livechat/transcript'))
|
||||
.set(credentials)
|
||||
.send({ token: visitor.token, rid: room._id, email: 'visitor@notadomain.com' });
|
||||
expect(body).to.have.property('success', true);
|
||||
|
||||
const { body: body2 } = await request
|
||||
.post(api('livechat/transcript'))
|
||||
.set(credentials)
|
||||
.send({ token: visitor2.token, rid: room2._id, email: 'visitor@notadomain.com' });
|
||||
expect(body2).to.have.property('success', true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('livechat/transcript/:rid', () => {
|
||||
it('should fail if user is not authenticated', async () => {
|
||||
await request.delete(api('livechat/transcript/rid')).send({}).expect(401);
|
||||
});
|
||||
it('should fail if user doesnt have "send-omnichannel-chat-transcript" permission', async () => {
|
||||
const user = await createVisitor();
|
||||
const room = await createLivechatRoom(user.token);
|
||||
await removePermissionFromAllRoles('send-omnichannel-chat-transcript');
|
||||
|
||||
await request
|
||||
.delete(api(`livechat/transcript/${room._id}`))
|
||||
.set(credentials)
|
||||
.send({})
|
||||
.expect(403);
|
||||
});
|
||||
it('should fail if rid is not a valid room id', async () => {
|
||||
await restorePermissionToRoles('send-omnichannel-chat-transcript');
|
||||
await request.delete(api('livechat/transcript/rid')).set(credentials).send({}).expect(400);
|
||||
});
|
||||
it('should fail if room is not open', async () => {
|
||||
const user = await createVisitor();
|
||||
const room = await createLivechatRoom(user.token);
|
||||
await closeOmnichannelRoom(room._id);
|
||||
await request
|
||||
.delete(api(`livechat/transcript/${room._id}`))
|
||||
.set(credentials)
|
||||
.send({})
|
||||
.expect(400);
|
||||
});
|
||||
it('should fail if room doesnt have transcript requested', async () => {
|
||||
const user = await createVisitor();
|
||||
const room = await createLivechatRoom(user.token);
|
||||
|
||||
await request
|
||||
.delete(api(`livechat/transcript/${room._id}`))
|
||||
.set(credentials)
|
||||
.send({})
|
||||
.expect(400);
|
||||
});
|
||||
it('should remove transcript if all good', async () => {
|
||||
const user = await createVisitor();
|
||||
const room = await createLivechatRoom(user.token);
|
||||
|
||||
await request
|
||||
.post(api(`livechat/transcript/${room._id}`))
|
||||
.set(credentials)
|
||||
.send({ email: 'abc@abc.com', subject: 'test' })
|
||||
.expect(200);
|
||||
|
||||
await request
|
||||
.delete(api(`livechat/transcript/${room._id}`))
|
||||
.set(credentials)
|
||||
.send({})
|
||||
.expect(200);
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST livechat/transcript/:rid', () => {
|
||||
it('should fail if user is not authenticated', async () => {
|
||||
await request.post(api('livechat/transcript/rid')).send({}).expect(401);
|
||||
});
|
||||
it('should fail if "email" param is not sent', async () => {
|
||||
const user = await createVisitor();
|
||||
const room = await createLivechatRoom(user.token);
|
||||
|
||||
await request
|
||||
.post(api(`livechat/transcript/${room._id}`))
|
||||
.set(credentials)
|
||||
.send({})
|
||||
.expect(400);
|
||||
});
|
||||
it('should fail if "subject" param is not sent', async () => {
|
||||
const user = await createVisitor();
|
||||
const room = await createLivechatRoom(user.token);
|
||||
|
||||
await request
|
||||
.post(api(`livechat/transcript/${room._id}`))
|
||||
.set(credentials)
|
||||
.send({ email: 'abc@abc.xmz' })
|
||||
.expect(400);
|
||||
});
|
||||
it('should fail if user doesnt have "send-omnichannel-chat-transcript" permission', async () => {
|
||||
const user = await createVisitor();
|
||||
const room = await createLivechatRoom(user.token);
|
||||
await updatePermission('send-omnichannel-chat-transcript', []);
|
||||
|
||||
await request
|
||||
.post(api(`livechat/transcript/${room._id}`))
|
||||
.set(credentials)
|
||||
.send({ email: 'abc@abc.com', subject: 'test' })
|
||||
.expect(403);
|
||||
});
|
||||
it('should fail if rid is not a valid room id', async () => {
|
||||
await updatePermission('send-omnichannel-chat-transcript', ['livechat-manager', 'admin']);
|
||||
await request.post(api('livechat/transcript/rid')).set(credentials).send({ email: 'abc@abc.com', subject: 'test' }).expect(400);
|
||||
});
|
||||
it('should fail if room is not open', async () => {
|
||||
const user = await createVisitor();
|
||||
const room = await createLivechatRoom(user.token);
|
||||
await closeOmnichannelRoom(room._id);
|
||||
await request
|
||||
.post(api(`livechat/transcript/${room._id}`))
|
||||
.set(credentials)
|
||||
.send({ email: 'abc@abc.com', subject: 'test' })
|
||||
.expect(400);
|
||||
});
|
||||
it('should fail if room already has transcript requested', async () => {
|
||||
const user = await createVisitor();
|
||||
const room = await createLivechatRoom(user.token);
|
||||
|
||||
await request
|
||||
.post(api(`livechat/transcript/${room._id}`))
|
||||
.set(credentials)
|
||||
.send({ email: 'abc@abc.com', subject: 'test' })
|
||||
.expect(200);
|
||||
|
||||
await request
|
||||
.post(api(`livechat/transcript/${room._id}`))
|
||||
.set(credentials)
|
||||
.send({ email: 'abc@abc.com', subject: 'test' })
|
||||
.expect(400);
|
||||
});
|
||||
it('should request transcript if all good', async () => {
|
||||
const user = await createVisitor();
|
||||
const room = await createLivechatRoom(user.token);
|
||||
|
||||
await request
|
||||
.post(api(`livechat/transcript/${room._id}`))
|
||||
.set(credentials)
|
||||
.send({ email: 'abc@abc.com', subject: 'test' })
|
||||
.expect(200);
|
||||
});
|
||||
});
|
||||
|
||||
describe('livechat/visitors.search', () => {
|
||||
it('should bring sorted data by last chat time', async () => {
|
||||
const visitor1 = await createVisitor(undefined, 'VisitorInPast');
|
||||
const room1 = await createLivechatRoom(visitor1.token);
|
||||
|
||||
const visitor2 = await createVisitor(undefined, 'VisitorInPresent');
|
||||
const room2 = await createLivechatRoom(visitor2.token);
|
||||
|
||||
const { body: result1 } = await request
|
||||
.get(api('livechat/visitors.search'))
|
||||
.query({ term: 'VisitorIn', sort: '{"lastChat.ts":1}' })
|
||||
.set(credentials)
|
||||
.send();
|
||||
|
||||
expect(result1).to.have.property('visitors').that.is.an('array');
|
||||
expect(result1.visitors[0]).to.have.property('name');
|
||||
expect(result1.visitors[0].name).to.be.eq('VisitorInPast');
|
||||
|
||||
const { body: result2 } = await request
|
||||
.get(api('livechat/visitors.search'))
|
||||
.query({ term: 'VisitorIn', sort: '{"lastChat.ts":-1}' })
|
||||
.set(credentials)
|
||||
.send();
|
||||
|
||||
expect(result2).to.have.property('visitors').that.is.an('array');
|
||||
expect(result2.visitors[0]).to.have.property('name');
|
||||
expect(result2.visitors[0].name).to.be.eq('VisitorInPresent');
|
||||
|
||||
await closeOmnichannelRoom(room1._id);
|
||||
await closeOmnichannelRoom(room2._id);
|
||||
await deleteVisitor(visitor1.token);
|
||||
await deleteVisitor(visitor2.token);
|
||||
});
|
||||
});
|
||||
|
||||
describe('livechat/message', () => {
|
||||
const visitorTokens: string[] = [];
|
||||
|
||||
after(() => Promise.all(visitorTokens.map((token) => deleteVisitor(token))));
|
||||
|
||||
it('should fail if no token', async () => {
|
||||
await request.post(api('livechat/message')).set(credentials).send({}).expect(400);
|
||||
});
|
||||
it('should fail if no rid', async () => {
|
||||
await request.post(api('livechat/message')).set(credentials).send({ token: 'test' }).expect(400);
|
||||
});
|
||||
it('should fail if no msg', async () => {
|
||||
await request.post(api('livechat/message')).set(credentials).send({ token: 'test', rid: 'test' }).expect(400);
|
||||
});
|
||||
it('should fail if token is invalid', async () => {
|
||||
await request.post(api('livechat/message')).set(credentials).send({ token: 'test', rid: 'test', msg: 'test' }).expect(400);
|
||||
});
|
||||
it('should fail if rid is invalid', async () => {
|
||||
const visitor = await createVisitor();
|
||||
visitorTokens.push(visitor.token);
|
||||
await request.post(api('livechat/message')).set(credentials).send({ token: visitor.token, rid: 'test', msg: 'test' }).expect(400);
|
||||
});
|
||||
it('should fail if rid belongs to another visitor', async () => {
|
||||
const visitor = await createVisitor();
|
||||
const visitor2 = await createVisitor();
|
||||
visitorTokens.push(visitor.token, visitor2.token);
|
||||
|
||||
const room = await createLivechatRoom(visitor2.token);
|
||||
await request.post(api('livechat/message')).set(credentials).send({ token: visitor.token, rid: room._id, msg: 'test' }).expect(400);
|
||||
});
|
||||
it('should fail if room is closed', async () => {
|
||||
const visitor = await createVisitor();
|
||||
visitorTokens.push(visitor.token);
|
||||
|
||||
const room = await createLivechatRoom(visitor.token);
|
||||
await closeOmnichannelRoom(room._id);
|
||||
await request.post(api('livechat/message')).set(credentials).send({ token: visitor.token, rid: room._id, msg: 'test' }).expect(400);
|
||||
});
|
||||
it('should fail if message is greater than Livechat_enable_message_character_limit setting', async () => {
|
||||
const visitor = await createVisitor();
|
||||
visitorTokens.push(visitor.token);
|
||||
|
||||
const room = await createLivechatRoom(visitor.token);
|
||||
await updateSetting('Livechat_enable_message_character_limit', true);
|
||||
await updateSetting('Livechat_message_character_limit', 1);
|
||||
await request.post(api('livechat/message')).set(credentials).send({ token: visitor.token, rid: room._id, msg: 'test' }).expect(400);
|
||||
|
||||
await updateSetting('Livechat_enable_message_character_limit', false);
|
||||
});
|
||||
it('should send a message', async () => {
|
||||
const visitor = await createVisitor();
|
||||
visitorTokens.push(visitor.token);
|
||||
|
||||
const room = await createLivechatRoom(visitor.token);
|
||||
await request.post(api('livechat/message')).set(credentials).send({ token: visitor.token, rid: room._id, msg: 'test' }).expect(200);
|
||||
});
|
||||
});
|
||||
|
||||
describe('[GET] livechat/message/:_id', () => {
|
||||
it('should fail if no token is provided', async () => {
|
||||
await request.get(api('livechat/message/rid')).set(credentials).expect(400);
|
||||
});
|
||||
it('should fail if no rid is provided', async () => {
|
||||
await request.get(api('livechat/message/mid')).query({ token: 'test' }).expect(400);
|
||||
});
|
||||
it('should fail if token points to an invalid visitor', async () => {
|
||||
await request.get(api('livechat/message/mid')).query({ token: 'test', rid: 'test' }).expect(400);
|
||||
});
|
||||
it('should fail if room points to an invalid room', async () => {
|
||||
const visitor = await createVisitor();
|
||||
await request.get(api('livechat/message/mid')).query({ token: visitor.token, rid: 'test' }).expect(400);
|
||||
});
|
||||
it('should fail if _id points to an invalid message', async () => {
|
||||
const visitor = await createVisitor();
|
||||
const room = await createLivechatRoom(visitor.token);
|
||||
await request.get(api('livechat/message/mid')).query({ token: visitor.token, rid: room._id }).expect(400);
|
||||
});
|
||||
it('should return a message', async () => {
|
||||
const visitor = await createVisitor();
|
||||
const room = await createLivechatRoom(visitor.token);
|
||||
const message = await sendMessage(room._id, 'test', visitor.token);
|
||||
await request
|
||||
.get(api(`livechat/message/${message._id}`))
|
||||
.query({ token: visitor.token, rid: room._id })
|
||||
.expect(200);
|
||||
});
|
||||
});
|
||||
|
||||
describe('[PUT] livechat/message/:_id', () => {
|
||||
it('should fail if no token is provided', async () => {
|
||||
await request.put(api('livechat/message/rid')).set(credentials).expect(400);
|
||||
});
|
||||
it('should fail if no rid is provided', async () => {
|
||||
await request.put(api('livechat/message/mid')).query({ token: 'test' }).expect(400);
|
||||
});
|
||||
it('should fail if token points to an invalid visitor', async () => {
|
||||
await request.put(api('livechat/message/mid')).query({ token: 'test', rid: 'test' }).expect(400);
|
||||
});
|
||||
it('should fail if room points to an invalid room', async () => {
|
||||
const visitor = await createVisitor();
|
||||
await request.put(api('livechat/message/mid')).query({ token: visitor.token, rid: 'test' }).expect(400);
|
||||
});
|
||||
it('should fail if _id points to an invalid message', async () => {
|
||||
const visitor = await createVisitor();
|
||||
const room = await createLivechatRoom(visitor.token);
|
||||
await request.put(api('livechat/message/mid')).query({ token: visitor.token, rid: room._id }).expect(400);
|
||||
});
|
||||
it('should update a message', async () => {
|
||||
const visitor = await createVisitor();
|
||||
const room = await createLivechatRoom(visitor.token);
|
||||
const message = await sendMessage(room._id, 'test', visitor.token);
|
||||
const { body } = await request
|
||||
.put(api(`livechat/message/${message._id}`))
|
||||
.set(credentials)
|
||||
.send({ msg: 'test2', token: visitor.token, rid: room._id })
|
||||
.expect(200);
|
||||
expect(body).to.have.property('message');
|
||||
expect(body.message).to.have.property('_id', message._id);
|
||||
expect(body.message).to.have.property('msg', 'test2');
|
||||
});
|
||||
});
|
||||
|
||||
describe('[DELETE] livechat/message/:_id', () => {
|
||||
it('should fail if no token is provided', async () => {
|
||||
await request.delete(api('livechat/message/rid')).set(credentials).expect(400);
|
||||
});
|
||||
it('should fail if no rid is provided', async () => {
|
||||
await request.delete(api('livechat/message/mid')).query({ token: 'test' }).expect(400);
|
||||
});
|
||||
it('should fail if token points to an invalid visitor', async () => {
|
||||
await request.delete(api('livechat/message/mid')).query({ token: 'test', rid: 'test' }).expect(400);
|
||||
});
|
||||
it('should fail if room points to an invalid room', async () => {
|
||||
const visitor = await createVisitor();
|
||||
await request.delete(api('livechat/message/mid')).query({ token: visitor.token, rid: 'test' }).expect(400);
|
||||
});
|
||||
it('should fail if _id points to an invalid message', async () => {
|
||||
const visitor = await createVisitor();
|
||||
const room = await createLivechatRoom(visitor.token);
|
||||
await request.delete(api('livechat/message/mid')).query({ token: visitor.token, rid: room._id }).expect(400);
|
||||
});
|
||||
it('should delete a message', async () => {
|
||||
const visitor = await createVisitor();
|
||||
const room = await createLivechatRoom(visitor.token);
|
||||
const message = await sendMessage(room._id, 'test', visitor.token);
|
||||
|
||||
const { body } = await request
|
||||
.delete(api(`livechat/message/${message._id}`))
|
||||
.send({ token: visitor.token, rid: room._id })
|
||||
.expect(200);
|
||||
|
||||
expect(body).to.have.property('message');
|
||||
expect(body.message).to.have.property('_id', message._id);
|
||||
});
|
||||
});
|
||||
|
||||
describe('livechat/messages.history/:rid', () => {
|
||||
it('should fail if no token is provided', async () => {
|
||||
await request.get(api('livechat/messages.history/rid')).set(credentials).expect(400);
|
||||
});
|
||||
it('should fail if token points to an invalid visitor', async () => {
|
||||
await request.get(api('livechat/messages.history/rid')).query({ token: 'test' }).expect(400);
|
||||
});
|
||||
it('should fail if room points to an invalid room', async () => {
|
||||
const visitor = await createVisitor();
|
||||
await request.get(api('livechat/messages.history/rid')).query({ token: visitor.token }).expect(400);
|
||||
});
|
||||
it('should fail if room points to a room of another visitor', async () => {
|
||||
const visitor = await createVisitor();
|
||||
const visitor2 = await createVisitor();
|
||||
const room = await createLivechatRoom(visitor2.token);
|
||||
await request
|
||||
.get(api(`livechat/messages.history/${room._id}`))
|
||||
.query({ token: visitor.token })
|
||||
.expect(400);
|
||||
});
|
||||
it('should return a list of messages', async () => {
|
||||
const visitor = await createVisitor();
|
||||
const room = await createLivechatRoom(visitor.token);
|
||||
await sendMessage(room._id, 'test', visitor.token);
|
||||
const { body } = await request
|
||||
.get(api(`livechat/messages.history/${room._id}`))
|
||||
.query({ token: visitor.token })
|
||||
.expect(200);
|
||||
expect(body).to.have.property('messages').that.is.an('array');
|
||||
expect(body.messages).to.have.lengthOf(2);
|
||||
expect(body.messages[0]).to.have.property('msg', 'test');
|
||||
});
|
||||
it('should return a list of messages with offset and count', async () => {
|
||||
const visitor = await createVisitor();
|
||||
const room = await createLivechatRoom(visitor.token);
|
||||
await sendMessage(room._id, 'test', visitor.token);
|
||||
await sendMessage(room._id, 'test2', visitor.token);
|
||||
const { body } = await request
|
||||
.get(api(`livechat/messages.history/${room._id}`))
|
||||
.query({ token: visitor.token, offset: 1, limit: 1 })
|
||||
.expect(200);
|
||||
expect(body).to.have.property('messages').that.is.an('array');
|
||||
expect(body.messages).to.have.lengthOf(1);
|
||||
expect(body.messages[0]).to.have.property('msg', 'test');
|
||||
});
|
||||
it('should return a list of unseen messages', async () => {
|
||||
const visitor = await createVisitor();
|
||||
const room = await createLivechatRoom(visitor.token);
|
||||
await sendMessage(room._id, 'test', visitor.token);
|
||||
await sendMessage(room._id, 'test2', visitor.token);
|
||||
const { body } = await request
|
||||
.get(api(`livechat/messages.history/${room._id}`))
|
||||
.query({ token: visitor.token, ls: new Date() })
|
||||
.expect(200);
|
||||
expect(body).to.have.property('messages').that.is.an('array');
|
||||
expect(body.messages).to.have.lengthOf(3);
|
||||
expect(body.messages[0]).to.have.property('msg', 'test2');
|
||||
});
|
||||
it('should return a list of messages up to a specific date', async () => {
|
||||
const visitor = await createVisitor();
|
||||
const room = await createLivechatRoom(visitor.token);
|
||||
const msg = await sendMessage(room._id, 'test', visitor.token);
|
||||
const tsPlusSomeMillis = new Date(new Date(msg.ts).getTime() + 500);
|
||||
await sleep(1000);
|
||||
await sendMessage(room._id, 'test2', visitor.token);
|
||||
const { body } = await request
|
||||
.get(api(`livechat/messages.history/${room._id}`))
|
||||
.query({ token: visitor.token, end: tsPlusSomeMillis })
|
||||
.expect(200);
|
||||
expect(body).to.have.property('messages').that.is.an('array');
|
||||
expect(body.messages).to.have.lengthOf(2);
|
||||
expect(body.messages[0]).to.have.property('msg', 'test');
|
||||
});
|
||||
it('should return message history for a valid room with pagination', async () => {
|
||||
const visitor = await createVisitor();
|
||||
const room = await createLivechatRoom(visitor.token);
|
||||
await sendMessage(room._id, 'Hello', visitor.token);
|
||||
|
||||
const { body } = await request
|
||||
.get(api(`livechat/messages.history/${room._id}`))
|
||||
.set(credentials)
|
||||
.query({ token: visitor.token, limit: 1 })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
expect(body).to.have.property('success', true);
|
||||
expect(body).to.have.property('messages').of.length(1);
|
||||
expect(body.messages[0]).to.have.property('msg', 'Hello');
|
||||
});
|
||||
it('should return message history for a valid room with pagination and offset', async () => {
|
||||
const visitor = await createVisitor();
|
||||
const room = await createLivechatRoom(visitor.token);
|
||||
await sendMessage(room._id, 'Hello', visitor.token);
|
||||
|
||||
const { body } = await request
|
||||
.get(api(`livechat/messages.history/${room._id}`))
|
||||
.set(credentials)
|
||||
.query({ token: visitor.token, limit: 1, offset: 1 })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
expect(body).to.have.property('success', true);
|
||||
expect(body).to.have.property('messages').of.length(1);
|
||||
expect(body.messages[0]).to.have.property('t');
|
||||
});
|
||||
});
|
||||
|
||||
(IS_EE ? describe : describe.skip)('[EE] livechat widget', () => {
|
||||
it('should include additional css when provided via Livechat_WidgetLayoutClasses setting', async () => {
|
||||
await updateSetting('Livechat_WidgetLayoutClasses', 'http://my.css.com/my.css');
|
||||
const x = await request.get('/livechat').expect(200);
|
||||
|
||||
expect(x.text.includes('http://my.css.com/my.css')).to.be.true;
|
||||
});
|
||||
|
||||
it('should remove additional css when setting Livechat_WidgetLayoutClasses is empty', async () => {
|
||||
await updateSetting('Livechat_WidgetLayoutClasses', '');
|
||||
const x = await request.get('/livechat').expect(200);
|
||||
|
||||
expect(x.text.includes('http://my.css.com/my.css')).to.be.false;
|
||||
});
|
||||
|
||||
it('should include additional js when provided via Livechat_AdditionalWidgetScripts setting', async () => {
|
||||
await updateSetting('Livechat_AdditionalWidgetScripts', 'http://my.js.com/my.js');
|
||||
const x = await request.get('/livechat').expect(200);
|
||||
|
||||
expect(x.text.includes('http://my.js.com/my.js')).to.be.true;
|
||||
});
|
||||
|
||||
it('should remove additional js when setting Livechat_AdditionalWidgetScripts is empty', async () => {
|
||||
await updateSetting('Livechat_AdditionalWidgetScripts', '');
|
||||
const x = await request.get('/livechat').expect(200);
|
||||
|
||||
expect(x.text.includes('http://my.js.com/my.js')).to.be.false;
|
||||
});
|
||||
});
|
||||
});
|
||||
135
apps/meteor/tests/end-to-end/api/livechat/12-mailer.ts
Normal file
135
apps/meteor/tests/end-to-end/api/livechat/12-mailer.ts
Normal file
@@ -0,0 +1,135 @@
|
||||
import { expect } from 'chai';
|
||||
import { before, after, describe, it } from 'mocha';
|
||||
import type { Response } from 'supertest';
|
||||
|
||||
import { api, request, credentials, getCredentials } from '../../../data/api-data';
|
||||
import { updatePermission } from '../../../data/permissions.helper';
|
||||
|
||||
describe('Mailer', () => {
|
||||
before((done) => getCredentials(done));
|
||||
|
||||
describe('POST mailer', async () => {
|
||||
before(async () => {
|
||||
return updatePermission('send-mail', ['admin']);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
return updatePermission('send-mail', ['admin']);
|
||||
});
|
||||
|
||||
it('should send an email if the payload is correct', async () => {
|
||||
await request
|
||||
.post(api('mailer'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
from: 'rocketchat.internal.admin.test@rocket.chat',
|
||||
subject: 'Test email subject',
|
||||
body: 'Test email body [unsubscribe]',
|
||||
dryrun: true,
|
||||
query: '',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
});
|
||||
});
|
||||
it('should throw an error if the request is incorrect', async () => {
|
||||
await request
|
||||
.post(api('mailer'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
from: 12345,
|
||||
subject: {},
|
||||
body: 'Test email body',
|
||||
dryrun: true,
|
||||
query: '',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
});
|
||||
});
|
||||
it('should throw an error if the "from" param is missing', async () => {
|
||||
await request
|
||||
.post(api('mailer'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
subject: 'Test email subject',
|
||||
body: 'Test email body',
|
||||
dryrun: true,
|
||||
query: '',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
});
|
||||
});
|
||||
it('should throw an error if user does NOT have the send-mail permission', async () => {
|
||||
await updatePermission('send-mail', []);
|
||||
await request
|
||||
.post(api('mailer'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
from: 'test-mail@test.com',
|
||||
subject: 'Test email subject',
|
||||
body: 'Test email body',
|
||||
dryrun: true,
|
||||
query: '',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(403)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error', 'User does not have the permissions required for this action [error-unauthorized]');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Mailer Unsubscribe', () => {
|
||||
describe('POST mailer unsubscribe', () => {
|
||||
it('should unsubscribe to mailer if the request is correct', async () => {
|
||||
await request
|
||||
.post(api('mailer.unsubscribe'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
_id: credentials['X-User-Id'],
|
||||
createdAt: new Date().getTime().toString(),
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
});
|
||||
});
|
||||
it('should throw an error if the "_id" param is missing', async () => {
|
||||
await request
|
||||
.post(api('mailer.unsubscribe'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
createdAt: new Date().getTime().toString(),
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
});
|
||||
});
|
||||
it('should throw an error if the "createdAt" param is missing', async () => {
|
||||
await request
|
||||
.post(api('mailer.unsubscribe'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
_id: credentials['X-User-Id'],
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
936
apps/meteor/tests/end-to-end/api/livechat/12-priorites.ts
Normal file
936
apps/meteor/tests/end-to-end/api/livechat/12-priorites.ts
Normal file
@@ -0,0 +1,936 @@
|
||||
import { faker } from '@faker-js/faker';
|
||||
import type {
|
||||
ILivechatInquiryRecord,
|
||||
ILivechatPriority,
|
||||
IOmnichannelRoom,
|
||||
IOmnichannelServiceLevelAgreements,
|
||||
} from '@rocket.chat/core-typings';
|
||||
import { OmnichannelSortingMechanismSettingType } from '@rocket.chat/core-typings';
|
||||
import { expect } from 'chai';
|
||||
import { after, before, describe, it } from 'mocha';
|
||||
|
||||
import { getCredentials, api, request, credentials } from '../../../data/api-data';
|
||||
import { createDepartmentWithAnOnlineAgent } from '../../../data/livechat/department';
|
||||
import { fetchAllInquiries } from '../../../data/livechat/inquiries';
|
||||
import { createSLA, deleteSLA, bulkCreateSLA, deleteAllSLA } from '../../../data/livechat/priorities';
|
||||
import {
|
||||
createAgent,
|
||||
createVisitor,
|
||||
createLivechatRoom,
|
||||
bulkCreateLivechatRooms,
|
||||
startANewLivechatRoomAndTakeIt,
|
||||
makeAgentAvailable,
|
||||
deleteAgent,
|
||||
} from '../../../data/livechat/rooms';
|
||||
import {
|
||||
addPermissions,
|
||||
removePermissionFromAllRoles,
|
||||
removePermissions,
|
||||
restorePermissionToRoles,
|
||||
updateEESetting,
|
||||
updatePermission,
|
||||
updateSetting,
|
||||
} from '../../../data/permissions.helper';
|
||||
import { IS_EE } from '../../../e2e/config/constants';
|
||||
import { generateRandomSLAData } from '../../../e2e/utils/omnichannel/sla';
|
||||
|
||||
(IS_EE ? describe : describe.skip)('[EE] LIVECHAT - Priorities & SLAs', () => {
|
||||
before((done) => getCredentials(done));
|
||||
|
||||
before(async () => {
|
||||
await updateSetting('Livechat_enabled', true);
|
||||
await updateSetting('Livechat_Routing_Method', 'Manual_Selection');
|
||||
await updateEESetting('Livechat_Require_Contact_Verification', 'never');
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await addPermissions({
|
||||
'manage-livechat-priorities': ['admin', 'livechat-manager'],
|
||||
'manage-livechat-sla': ['admin', 'livechat-manager'],
|
||||
'view-l-room': ['admin', 'livechat-manager', 'livechat-agent'],
|
||||
});
|
||||
});
|
||||
|
||||
describe('livechat/sla', () => {
|
||||
// GET
|
||||
it('should return an "unauthorized error" when the user does not have the necessary permission for [GET] livechat/sla endpoint', async () => {
|
||||
await removePermissions(['manage-livechat-sla', 'view-l-room']);
|
||||
const response = await request.get(api('livechat/sla')).set(credentials).expect('Content-Type', 'application/json').expect(403);
|
||||
expect(response.body).to.have.property('success', false);
|
||||
});
|
||||
it('should return an array of slas', async () => {
|
||||
await addPermissions({
|
||||
'manage-livechat-sla': ['admin', 'livechat-manager'],
|
||||
'view-l-room': ['livechat-agent', 'admin', 'livechat-manager'],
|
||||
});
|
||||
const sla = await createSLA();
|
||||
expect(sla).to.not.be.undefined;
|
||||
expect(sla).to.have.property('_id');
|
||||
const response = await request.get(api('livechat/sla')).set(credentials).expect('Content-Type', 'application/json').expect(200);
|
||||
expect(response.body).to.have.property('success', true);
|
||||
expect(response.body.sla).to.be.an('array').with.lengthOf.greaterThan(0);
|
||||
const current = response.body.sla.find((p: IOmnichannelServiceLevelAgreements) => p && p?._id === sla._id);
|
||||
expect(current).to.be.an('object');
|
||||
expect(current).to.have.property('name', sla.name);
|
||||
expect(current).to.have.property('description', sla.description);
|
||||
expect(current).to.have.property('dueTimeInMinutes', sla.dueTimeInMinutes);
|
||||
await deleteSLA(sla._id);
|
||||
});
|
||||
// POST
|
||||
it('should return an "unauthorized error" when the user does not have the necessary permission for [POST] livechat/sla endpoint', async () => {
|
||||
await updatePermission('manage-livechat-sla', []);
|
||||
const response = await request
|
||||
.post(api('livechat/sla'))
|
||||
.set(credentials)
|
||||
.send(generateRandomSLAData())
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(403);
|
||||
expect(response.body).to.have.property('success', false);
|
||||
});
|
||||
it('should create a new sla', async () => {
|
||||
await restorePermissionToRoles('manage-livechat-sla');
|
||||
|
||||
const sla = generateRandomSLAData();
|
||||
|
||||
const response = await request
|
||||
.post(api('livechat/sla'))
|
||||
.set(credentials)
|
||||
.send(sla)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
expect(response.body).to.have.property('success', true);
|
||||
expect(response.body.sla).to.be.an('object');
|
||||
expect(response.body.sla).to.have.property('_id');
|
||||
expect(response.body.sla).to.have.property('name', sla.name);
|
||||
expect(response.body.sla).to.have.property('description', sla.description);
|
||||
expect(response.body.sla).to.have.property('dueTimeInMinutes', sla.dueTimeInMinutes);
|
||||
|
||||
await deleteSLA(response.body.sla._id);
|
||||
});
|
||||
it('should throw an error when trying to create a duplicate sla with same dueTimeInMinutes', async () => {
|
||||
const firstSla = generateRandomSLAData();
|
||||
|
||||
const response = await request
|
||||
.post(api('livechat/sla'))
|
||||
.set(credentials)
|
||||
.send(firstSla)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
expect(response.body).to.have.property('success', true);
|
||||
|
||||
const secondSla = generateRandomSLAData();
|
||||
|
||||
secondSla.dueTimeInMinutes = firstSla.dueTimeInMinutes;
|
||||
|
||||
const secondResponse = await request
|
||||
.post(api('livechat/sla'))
|
||||
.set(credentials)
|
||||
.send(secondSla)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
|
||||
expect(secondResponse.body).to.have.property('success', false);
|
||||
expect(secondResponse.body).to.have.property('error');
|
||||
expect(secondResponse.body?.error).to.contain('error-duplicated-sla');
|
||||
|
||||
await deleteSLA(response.body.sla._id);
|
||||
});
|
||||
it('should throw an error when trying to create a duplicate sla with same name', async () => {
|
||||
const firstSla = generateRandomSLAData();
|
||||
|
||||
const response = await request
|
||||
.post(api('livechat/sla'))
|
||||
.set(credentials)
|
||||
.send(firstSla)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
expect(response.body).to.have.property('success', true);
|
||||
|
||||
const secondSla = generateRandomSLAData();
|
||||
|
||||
secondSla.name = firstSla.name;
|
||||
|
||||
const secondResponse = await request
|
||||
.post(api('livechat/sla'))
|
||||
.set(credentials)
|
||||
.send(secondSla)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
|
||||
expect(secondResponse.body).to.have.property('success', false);
|
||||
expect(secondResponse.body).to.have.property('error');
|
||||
expect(secondResponse.body?.error).to.contain('error-duplicated-sla');
|
||||
|
||||
await deleteSLA(response.body.sla._id);
|
||||
});
|
||||
});
|
||||
|
||||
describe('livechat/sla/:slaId', () => {
|
||||
// GET
|
||||
it('should return an "unauthorized error" when the user does not have the necessary permission for [GET] livechat/sla/:slaId endpoint', async () => {
|
||||
await Promise.all([removePermissionFromAllRoles('manage-livechat-sla'), removePermissionFromAllRoles('view-l-room')]);
|
||||
|
||||
const response = await request.get(api('livechat/sla/123')).set(credentials).expect('Content-Type', 'application/json').expect(403);
|
||||
expect(response.body).to.have.property('success', false);
|
||||
});
|
||||
it('should create, find and delete an sla', async () => {
|
||||
await Promise.all([restorePermissionToRoles('manage-livechat-sla'), restorePermissionToRoles('view-l-room')]);
|
||||
|
||||
const sla = await createSLA();
|
||||
const response = await request
|
||||
.get(api(`livechat/sla/${sla._id}`))
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
expect(response.body).to.have.property('success', true);
|
||||
expect(response.body).to.be.an('object');
|
||||
expect(response.body._id).to.be.equal(sla._id);
|
||||
await deleteSLA(sla._id);
|
||||
});
|
||||
// PUT
|
||||
it('should return an "unauthorized error" when the user does not have the necessary permission for [PUT] livechat/sla/:slaId endpoint', async () => {
|
||||
await removePermissionFromAllRoles('manage-livechat-sla');
|
||||
|
||||
const response = await request
|
||||
.put(api('livechat/sla/123'))
|
||||
.set(credentials)
|
||||
.send(generateRandomSLAData())
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(403);
|
||||
|
||||
expect(response.body).to.have.property('success', false);
|
||||
});
|
||||
it('should update an sla', async () => {
|
||||
await restorePermissionToRoles('manage-livechat-sla');
|
||||
|
||||
const sla = await createSLA();
|
||||
const newSlaData = generateRandomSLAData();
|
||||
|
||||
const response = await request
|
||||
.put(api(`livechat/sla/${sla._id}`))
|
||||
.set(credentials)
|
||||
.send(newSlaData)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
expect(response.body).to.have.property('success', true);
|
||||
expect(response.body.sla).to.be.an('object');
|
||||
expect(response.body.sla).to.have.property('_id');
|
||||
expect(response.body.sla).to.have.property('name', newSlaData.name);
|
||||
expect(response.body.sla).to.have.property('description', newSlaData.description);
|
||||
expect(response.body.sla).to.have.property('dueTimeInMinutes', newSlaData.dueTimeInMinutes);
|
||||
|
||||
await deleteSLA(response.body.sla._id);
|
||||
});
|
||||
it('should throw an error when trying to update a sla with a duplicate name', async () => {
|
||||
const firstSla = await createSLA();
|
||||
const secondSla = await createSLA();
|
||||
|
||||
secondSla.name = firstSla.name;
|
||||
|
||||
const response = await request
|
||||
.put(api(`livechat/sla/${secondSla._id}`))
|
||||
.set(credentials)
|
||||
.send({
|
||||
name: secondSla.name,
|
||||
description: secondSla.description,
|
||||
dueTimeInMinutes: secondSla.dueTimeInMinutes,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
|
||||
expect(response.body).to.have.property('success', false);
|
||||
expect(response.body).to.have.property('error');
|
||||
expect(response.body?.error).to.contain('error-duplicated-sla');
|
||||
|
||||
await deleteSLA(firstSla._id);
|
||||
await deleteSLA(secondSla._id);
|
||||
});
|
||||
it('should throw an error when trying to update a sla with a duplicate dueTimeInMinutes', async () => {
|
||||
const firstSla = await createSLA();
|
||||
const secondSla = await createSLA();
|
||||
|
||||
secondSla.dueTimeInMinutes = firstSla.dueTimeInMinutes;
|
||||
|
||||
const response = await request
|
||||
.put(api(`livechat/sla/${secondSla._id}`))
|
||||
.set(credentials)
|
||||
.send({
|
||||
name: secondSla.name,
|
||||
description: secondSla.description,
|
||||
dueTimeInMinutes: secondSla.dueTimeInMinutes,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
|
||||
expect(response.body).to.have.property('success', false);
|
||||
expect(response.body).to.have.property('error');
|
||||
expect(response.body?.error).to.contain('error-duplicated-sla');
|
||||
|
||||
await deleteSLA(firstSla._id);
|
||||
await deleteSLA(secondSla._id);
|
||||
});
|
||||
|
||||
// DELETE
|
||||
it('should return an "unauthorized error" when the user does not have the necessary permission for [DELETE] livechat/sla/:slaId endpoint', async () => {
|
||||
await updatePermission('manage-livechat-sla', []);
|
||||
const response = await request
|
||||
.delete(api('livechat/sla/123'))
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(403);
|
||||
expect(response.body).to.have.property('success', false);
|
||||
});
|
||||
it('should delete an sla', async () => {
|
||||
await updatePermission('manage-livechat-sla', ['admin', 'livechat-manager']);
|
||||
const sla = await createSLA();
|
||||
const response = await request
|
||||
.delete(api(`livechat/sla/${sla._id}`))
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
expect(response.body).to.have.property('success', true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('livechat/inquiry.setSLA', () => {
|
||||
before(async () => {
|
||||
await createAgent();
|
||||
await makeAgentAvailable();
|
||||
});
|
||||
after(async () => {
|
||||
await deleteAgent();
|
||||
});
|
||||
it('should return an "unauthorized error" when the user does not have the necessary permission', async () => {
|
||||
await removePermissions(['manage-livechat-sla', 'view-l-room', 'manage-livechat-priorities']);
|
||||
const response = await request
|
||||
.put(api('livechat/inquiry.setSLA'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
roomId: '123',
|
||||
sla: '123',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(403);
|
||||
expect(response.body).to.have.property('success', false);
|
||||
});
|
||||
it('should fail if roomId is not in request body', async () => {
|
||||
await addPermissions({
|
||||
'manage-livechat-sla': ['admin', 'livechat-manager'],
|
||||
'manage-livechat-priorities': ['admin', 'livechat-manager'],
|
||||
'view-l-room': ['livechat-agent', 'admin', 'livechat-manager'],
|
||||
});
|
||||
const response = await request
|
||||
.put(api('livechat/inquiry.setSLA'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
sla: '123',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
expect(response.body).to.have.property('success', false);
|
||||
});
|
||||
it('should fail if roomId is invalid', async () => {
|
||||
const response = await request
|
||||
.put(api('livechat/inquiry.setSLA'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
roomId: '123',
|
||||
sla: '123',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
expect(response.body).to.have.property('success', false);
|
||||
});
|
||||
it('should fail if sla is not in request body', async () => {
|
||||
const response = await request
|
||||
.put(api('livechat/inquiry.setSLA'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
roomId: '123',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
expect(response.body).to.have.property('success', false);
|
||||
});
|
||||
it('should fail if sla is not valid', async () => {
|
||||
const visitor = await createVisitor();
|
||||
const room = await createLivechatRoom(visitor.token);
|
||||
|
||||
const response = await request
|
||||
.put(api('livechat/inquiry.setSLA'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
roomId: room._id,
|
||||
sla: '123',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
expect(response.body).to.have.property('success', false);
|
||||
});
|
||||
it('should fail if inquiry is not queued', async () => {
|
||||
const {
|
||||
room: { _id: roomId },
|
||||
} = await startANewLivechatRoomAndTakeIt();
|
||||
|
||||
const response = await request
|
||||
.put(api('livechat/inquiry.setSLA'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
roomId,
|
||||
sla: '123',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
expect(response.body).to.have.property('success', false);
|
||||
});
|
||||
it('should prioritize an inquiry', async () => {
|
||||
const visitor = await createVisitor();
|
||||
const room = await createLivechatRoom(visitor.token);
|
||||
const sla = await createSLA();
|
||||
const response = await request
|
||||
.put(api('livechat/inquiry.setSLA'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
roomId: room._id,
|
||||
sla: sla._id,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
expect(response.body).to.have.property('success', true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('livechat/priorities', () => {
|
||||
let priority: ILivechatPriority;
|
||||
it('should return an "unauthorized error" when the user does not have the necessary permission', async () => {
|
||||
await removePermissions(['manage-livechat-priorities', 'view-l-room']);
|
||||
const response = await request
|
||||
.get(api('livechat/priorities'))
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(403);
|
||||
expect(response.body).to.have.property('success', false);
|
||||
});
|
||||
it('should return an array of priorities', async () => {
|
||||
await addPermissions({
|
||||
'manage-livechat-priorities': ['admin', 'livechat-manager'],
|
||||
'view-l-room': ['livechat-agent', 'admin', 'livechat-manager'],
|
||||
});
|
||||
const response = await request
|
||||
.get(api('livechat/priorities'))
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
expect(response.body).to.have.property('success', true);
|
||||
expect(response.body.priorities).to.be.an('array');
|
||||
expect(response.body.priorities).to.have.lengthOf(5);
|
||||
expect(response.body.priorities[0]).to.have.property('_id');
|
||||
expect(response.body.priorities[0]).to.have.property('i18n');
|
||||
expect(response.body.priorities[0]).to.have.property('dirty');
|
||||
priority = response.body.priorities[0];
|
||||
});
|
||||
it('should return an array of priorities matching text param', async () => {
|
||||
const response = await request
|
||||
.get(api('livechat/priorities'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
text: priority.name,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
expect(response.body).to.have.property('success', true);
|
||||
expect(response.body.priorities).to.be.an('array');
|
||||
expect(response.body.priorities).to.have.length.greaterThan(0);
|
||||
expect(response.body.priorities[0]).to.have.property('_id');
|
||||
});
|
||||
});
|
||||
|
||||
describe('livechat/priorities/:priorityId', () => {
|
||||
let priority: ILivechatPriority;
|
||||
const name = `${faker.lorem.word()}${faker.git.commitSha({ length: 6 })}`;
|
||||
it('should return an "unauthorized error" when the user does not have the necessary permission', async () => {
|
||||
await removePermissions(['manage-livechat-priorities', 'view-l-room']);
|
||||
const response = await request.get(api('livechat/priorities/123')).set(credentials).expect(403);
|
||||
expect(response.body).to.have.property('success', false);
|
||||
expect(response.body).to.have.property('error');
|
||||
expect(response.body?.error).to.contain('error-unauthorized');
|
||||
});
|
||||
it('should return a priority by id', async () => {
|
||||
await addPermissions({
|
||||
'manage-livechat-priorities': ['admin', 'livechat-manager'],
|
||||
'view-l-room': ['livechat-agent', 'admin', 'livechat-manager'],
|
||||
});
|
||||
const {
|
||||
body: { priorities },
|
||||
} = await request.get(api('livechat/priorities')).set(credentials).expect('Content-Type', 'application/json').expect(200);
|
||||
|
||||
priority = priorities[0];
|
||||
|
||||
const response = await request
|
||||
.get(api(`livechat/priorities/${priority._id}`))
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
expect(response.body).to.have.property('success', true);
|
||||
expect(response.body).to.be.an('object');
|
||||
expect(response.body._id).to.be.equal(priority._id);
|
||||
});
|
||||
it('should throw 404 when priority does not exist', async () => {
|
||||
const response = await request.get(api('livechat/priorities/123')).set(credentials).expect(404);
|
||||
expect(response.body).to.have.property('success', false);
|
||||
});
|
||||
it('should update a priority when using PUT', async () => {
|
||||
const response = await request
|
||||
.put(api(`livechat/priorities/${priority._id}`))
|
||||
.set(credentials)
|
||||
.send({
|
||||
name,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
expect(response.body).to.have.property('success', true);
|
||||
});
|
||||
it('should return dirty: true after a priority has been updated', async () => {
|
||||
const response = await request
|
||||
.get(api(`livechat/priorities/${priority._id}`))
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
expect(response.body).to.have.property('success', true);
|
||||
expect(response.body).to.have.property('dirty', true);
|
||||
});
|
||||
it('should return an array of priorities matching text param', async () => {
|
||||
const response = await request
|
||||
.get(api('livechat/priorities'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
text: name,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
expect(response.body).to.have.property('success', true);
|
||||
expect(response.body.priorities).to.be.an('array');
|
||||
expect(response.body.priorities).to.have.length.greaterThan(0);
|
||||
const pos = response.body.priorities.findIndex((p: ILivechatPriority) => p._id === priority._id);
|
||||
expect(pos).to.be.greaterThan(-1);
|
||||
expect(response.body.priorities[pos]).to.have.property('_id');
|
||||
expect(response.body.priorities[pos]).to.have.property('i18n', priority.i18n);
|
||||
});
|
||||
it('should edit a priority with a PUT', async () => {
|
||||
const newName = `${faker.lorem.word()}${faker.git.commitSha({ length: 6 })}`;
|
||||
const response = await request
|
||||
.put(api(`livechat/priorities/${priority._id}`))
|
||||
.set(credentials)
|
||||
.send({
|
||||
name: newName,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
expect(response.body).to.have.property('success', true);
|
||||
const newPriorityResponse = await request
|
||||
.get(api(`livechat/priorities/${priority._id}`))
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
expect(newPriorityResponse.body).to.have.property('success', true);
|
||||
expect(newPriorityResponse.body).to.have.property('dirty', true);
|
||||
expect(newPriorityResponse.body).to.have.property('name', newName);
|
||||
});
|
||||
it('should fail to edit a priority with a PUT if using too many parameters', async () => {
|
||||
const newName = faker.lorem.word();
|
||||
const response = await request
|
||||
.put(api(`livechat/priorities/${priority._id}`))
|
||||
.set(credentials)
|
||||
.send({
|
||||
name: newName,
|
||||
reset: true,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
expect(response.body).to.have.property('success', false);
|
||||
expect(response.body).to.have.property('error');
|
||||
expect(response.body?.error).to.contain('invalid-params');
|
||||
});
|
||||
it('should fail to edit a priority with a PUT if using an object as name', async () => {
|
||||
const newName = faker.lorem.word();
|
||||
const response = await request
|
||||
.put(api(`livechat/priorities/${priority._id}`))
|
||||
.set(credentials)
|
||||
.send({
|
||||
name: { name: newName },
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
expect(response.body).to.have.property('success', false);
|
||||
expect(response.body).to.have.property('error');
|
||||
expect(response.body?.error).to.contain('invalid-params');
|
||||
});
|
||||
it('should not fail to edit a priority with a PUT if using a boolean as name (it becomes a string)', async () => {
|
||||
const response = await request
|
||||
.put(api(`livechat/priorities/${priority._id}`))
|
||||
.set(credentials)
|
||||
.send({
|
||||
name: false,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
expect(response.body).to.have.property('success', true);
|
||||
});
|
||||
it('should fail to update a non-existing priority', async () => {
|
||||
const response = await request
|
||||
.put(api('livechat/priorities/123'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
name: faker.lorem.word(),
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
expect(response.body).to.have.property('success', false);
|
||||
});
|
||||
it('should reset a single priority with a reset:true PUT parameter', async () => {
|
||||
const response = await request
|
||||
.put(api(`livechat/priorities/${priority._id}`))
|
||||
.set(credentials)
|
||||
.send({
|
||||
reset: true,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
expect(response.body).to.have.property('success', true);
|
||||
const newPriorityResponse = await request
|
||||
.get(api(`livechat/priorities/${priority._id}`))
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
expect(newPriorityResponse.body).to.have.property('success', true);
|
||||
expect(newPriorityResponse.body).to.have.property('dirty', false);
|
||||
expect(newPriorityResponse.body).to.not.have.property('name');
|
||||
});
|
||||
it('should throw a duplicate error incase there is already a priority with same name', async () => {
|
||||
const {
|
||||
body: { priorities },
|
||||
} = await request.get(api('livechat/priorities')).set(credentials).expect('Content-Type', 'application/json').expect(200);
|
||||
|
||||
// change name of first priority to a random name
|
||||
const newName = `${faker.lorem.word()}${faker.git.commitSha({ length: 6 })}`;
|
||||
await request
|
||||
.put(api(`livechat/priorities/${priorities[0]._id}`))
|
||||
.set(credentials)
|
||||
.send({ name: newName })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
// change name of second priority to the name of first priority and expect error
|
||||
const response = await request
|
||||
.put(api(`livechat/priorities/${priorities[1]._id}`))
|
||||
.set(credentials)
|
||||
.send({ name: newName })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
expect(response.body).to.have.property('success', false);
|
||||
expect(response.body).to.have.property('error', 'error-duplicate-priority-name');
|
||||
});
|
||||
it('should throw a duplicate error incase there is already a priority with same name (case insensitive)', async () => {
|
||||
const {
|
||||
body: { priorities },
|
||||
} = await request.get(api('livechat/priorities')).set(credentials).expect('Content-Type', 'application/json').expect(200);
|
||||
|
||||
// change name of first priority to a random name
|
||||
const newNameLowercase = `${faker.lorem.word()}${faker.git.commitSha({ length: 6 })}`.toLowerCase();
|
||||
await request
|
||||
.put(api(`livechat/priorities/${priorities[0]._id}`))
|
||||
.set(credentials)
|
||||
.send({ name: newNameLowercase })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
// change name of second priority to the name of first priority in different case and expect error
|
||||
const response = await request
|
||||
.put(api(`livechat/priorities/${priorities[1]._id}`))
|
||||
.set(credentials)
|
||||
.send({ name: newNameLowercase.toUpperCase() })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
expect(response.body).to.have.property('success', false);
|
||||
expect(response.body).to.have.property('error', 'error-duplicate-priority-name');
|
||||
});
|
||||
});
|
||||
|
||||
describe('livechat/priorities.reset', () => {
|
||||
let priority: ILivechatPriority;
|
||||
it('should return an "unauthorized error" when the user does not have the necessary permission', async () => {
|
||||
await updatePermission('manage-livechat-priorities', []);
|
||||
const response = await request.post(api('livechat/priorities.reset')).set(credentials).expect(403);
|
||||
expect(response.body).to.have.property('success', false);
|
||||
expect(response.body).to.have.property('error');
|
||||
expect(response.body?.error).to.contain('error-unauthorized');
|
||||
});
|
||||
it('should respond reset:true when a priority has been changed', async () => {
|
||||
await updatePermission('manage-livechat-priorities', ['admin', 'livechat-manager']);
|
||||
const {
|
||||
body: { priorities },
|
||||
} = await request.get(api('livechat/priorities')).set(credentials).expect('Content-Type', 'application/json').expect(200);
|
||||
|
||||
priority = priorities[0];
|
||||
|
||||
priority.name = `${faker.lorem.word()}${faker.git.commitSha({ length: 6 })}`;
|
||||
const responseChange = await request
|
||||
.put(api(`livechat/priorities/${priority._id}`))
|
||||
.set(credentials)
|
||||
.send({
|
||||
name: priority.name,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
expect(responseChange.body).to.have.property('success', true);
|
||||
|
||||
const response = await request
|
||||
.get(api('livechat/priorities.reset'))
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
expect(response.body).to.have.property('success', true);
|
||||
expect(response.body).to.have.property('reset', true);
|
||||
});
|
||||
it('should reset all priorities', async () => {
|
||||
const resetRespose = await request
|
||||
.post(api('livechat/priorities.reset'))
|
||||
.set(credentials)
|
||||
.send()
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
expect(resetRespose.body).to.have.property('success', true);
|
||||
});
|
||||
it('should return reset: false after all priorities have been reset', async () => {
|
||||
const response = await request
|
||||
.get(api('livechat/priorities.reset'))
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
expect(response.body).to.have.property('success', true);
|
||||
expect(response.body).to.have.property('reset', false);
|
||||
});
|
||||
it('should change the priority name and dirty status when reset', async () => {
|
||||
const response = await request
|
||||
.get(api(`livechat/priorities/${priority._id}`))
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
expect(response.body).to.have.property('success', true);
|
||||
expect(response.body).to.have.property('dirty', false);
|
||||
expect(response.body).to.not.have.property('name');
|
||||
});
|
||||
it('should change all priorities to their default', async () => {
|
||||
const response = await request
|
||||
.get(api('livechat/priorities'))
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
expect(response.body).to.have.property('success', true);
|
||||
expect(response.body.priorities).to.be.an('array');
|
||||
response.body.priorities.forEach((priority: ILivechatPriority) => {
|
||||
expect(priority).to.not.have.property('name');
|
||||
expect(priority).to.have.property('dirty', false);
|
||||
});
|
||||
});
|
||||
it('should fail to reset when lacking permissions', async () => {
|
||||
await updatePermission('manage-livechat-priorities', []);
|
||||
const response = await request.post(api('livechat/priorities.reset')).set(credentials).expect(403);
|
||||
expect(response.body).to.have.property('success', false);
|
||||
expect(response.body).to.have.property('error');
|
||||
expect(response.body?.error).to.contain('error-unauthorized');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Inquiry queue sorting mechanism', () => {
|
||||
let omniRooms: IOmnichannelRoom[];
|
||||
let departmentWithAgent: Awaited<ReturnType<typeof createDepartmentWithAnOnlineAgent>>;
|
||||
let priorities: ILivechatPriority[];
|
||||
let slas: Awaited<ReturnType<typeof bulkCreateSLA>>;
|
||||
|
||||
const hasSlaProps = (inquiry: ILivechatInquiryRecord): inquiry is ILivechatInquiryRecord & { slaId: string } =>
|
||||
!!inquiry.estimatedWaitingTimeQueue && !!inquiry.slaId;
|
||||
const hasPriorityProps = (inquiry: ILivechatInquiryRecord): inquiry is ILivechatInquiryRecord & { priorityId: string } =>
|
||||
inquiry.priorityId !== undefined;
|
||||
|
||||
const getPriorityOrderById = (id: string) => {
|
||||
const priority = priorities.find((priority) => priority._id === id);
|
||||
return priority?.sortItem || 0;
|
||||
};
|
||||
|
||||
const sortBySLAProps = (inquiry1: ILivechatInquiryRecord, inquiry2: ILivechatInquiryRecord): number => {
|
||||
if (hasSlaProps(inquiry1) || hasSlaProps(inquiry2)) {
|
||||
if (hasSlaProps(inquiry1) && hasSlaProps(inquiry2)) {
|
||||
// if both inquiries have sla props, then sort by estimatedWaitingTimeQueue: 1
|
||||
const estimatedWaitingTimeQueue1 = inquiry1.estimatedWaitingTimeQueue;
|
||||
const estimatedWaitingTimeQueue2 = inquiry2.estimatedWaitingTimeQueue;
|
||||
|
||||
if (estimatedWaitingTimeQueue1 !== estimatedWaitingTimeQueue2) {
|
||||
return estimatedWaitingTimeQueue1 - estimatedWaitingTimeQueue2;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasSlaProps(inquiry1)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (hasSlaProps(inquiry2)) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
};
|
||||
|
||||
const sortByPriorityProps = (inquiry1: ILivechatInquiryRecord, inquiry2: ILivechatInquiryRecord): number => {
|
||||
if (hasPriorityProps(inquiry1) || hasPriorityProps(inquiry2)) {
|
||||
if (hasPriorityProps(inquiry1) && hasPriorityProps(inquiry2)) {
|
||||
// if both inquiries have priority props, then sort by priorityId: 1
|
||||
return getPriorityOrderById(inquiry1.priorityId) - getPriorityOrderById(inquiry2.priorityId);
|
||||
}
|
||||
|
||||
if (hasPriorityProps(inquiry1)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (hasPriorityProps(inquiry2)) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
};
|
||||
|
||||
// this should sort using logic - { ts: 1 }
|
||||
const sortByTimestamp = (inquiry1: ILivechatInquiryRecord, inquiry2: ILivechatInquiryRecord) => {
|
||||
return new Date(inquiry1.ts).getTime() - new Date(inquiry2.ts).getTime();
|
||||
};
|
||||
|
||||
// this should sort using logic - { priorityWeight: 1, ts: 1 }
|
||||
const sortByPriority = (inquiry1: ILivechatInquiryRecord, inquiry2: ILivechatInquiryRecord) => {
|
||||
const priorityPropsSort = sortByPriorityProps(inquiry1, inquiry2);
|
||||
if (priorityPropsSort !== 0) {
|
||||
return priorityPropsSort;
|
||||
}
|
||||
|
||||
return sortByTimestamp(inquiry1, inquiry2);
|
||||
};
|
||||
|
||||
// this should sort using logic - { estimatedWaitingTimeQueue: 1, ts: 1 }
|
||||
const sortBySLA = (inquiry1: ILivechatInquiryRecord, inquiry2: ILivechatInquiryRecord) => {
|
||||
const slaPropsSort = sortBySLAProps(inquiry1, inquiry2);
|
||||
if (slaPropsSort !== 0) {
|
||||
return slaPropsSort;
|
||||
}
|
||||
|
||||
return sortByTimestamp(inquiry1, inquiry2);
|
||||
};
|
||||
|
||||
const filterUnnecessaryProps = (inquiries: ILivechatInquiryRecord[]) =>
|
||||
inquiries.map((inquiry) => {
|
||||
return {
|
||||
_id: inquiry._id,
|
||||
rid: inquiry.rid,
|
||||
...(hasPriorityProps(inquiry) && {
|
||||
priority: {
|
||||
priorityWeight: getPriorityOrderById(inquiry.priorityId),
|
||||
id: inquiry.priorityWeight,
|
||||
},
|
||||
}),
|
||||
...(hasSlaProps(inquiry) && {
|
||||
sla: {
|
||||
estimatedWaitingTimeQueue: inquiry.estimatedWaitingTimeQueue,
|
||||
},
|
||||
}),
|
||||
ts: inquiry.ts,
|
||||
};
|
||||
});
|
||||
|
||||
const compareInquiries = (inquiries1: ILivechatInquiryRecord[], inquiries2: ILivechatInquiryRecord[]) => {
|
||||
const filteredInquiries1 = filterUnnecessaryProps(inquiries1);
|
||||
const filteredInquiries2 = filterUnnecessaryProps(inquiries2);
|
||||
|
||||
expect(filteredInquiries1).to.deep.equal(filteredInquiries2);
|
||||
};
|
||||
|
||||
it('it should create all the data required for further testing', async () => {
|
||||
departmentWithAgent = await createDepartmentWithAnOnlineAgent();
|
||||
|
||||
const {
|
||||
body: { priorities: prioritiesResponse },
|
||||
} = (await request.get(api('livechat/priorities')).set(credentials).expect('Content-Type', 'application/json').expect(200)) as {
|
||||
body: { priorities: ILivechatPriority[] };
|
||||
};
|
||||
priorities = prioritiesResponse;
|
||||
|
||||
await deleteAllSLA();
|
||||
slas = await bulkCreateSLA(5);
|
||||
|
||||
type RoomParamsReturnType = { priority: string } | { sla: string } | { priority: string; sla: string } | undefined;
|
||||
|
||||
// create 20 rooms, 5 with only priority, 5 with only SLA, 5 with both, 5 without priority or SLA
|
||||
omniRooms = await bulkCreateLivechatRooms(20, departmentWithAgent.department._id, (index): RoomParamsReturnType => {
|
||||
if (index < 5) {
|
||||
return {
|
||||
priority: priorities[index]._id,
|
||||
};
|
||||
}
|
||||
|
||||
if (index < 10) {
|
||||
return {
|
||||
sla: slas[index - 5]._id,
|
||||
};
|
||||
}
|
||||
|
||||
if (index < 15) {
|
||||
// random number between 0 and 4
|
||||
const randomPriorityIndex = Math.floor(Math.random() * 5);
|
||||
const randomSlaIndex = Math.floor(Math.random() * 5);
|
||||
|
||||
return {
|
||||
priority: priorities[randomPriorityIndex]._id,
|
||||
sla: slas[randomSlaIndex]._id,
|
||||
};
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('it should sort the queue based on priority', async () => {
|
||||
await updateEESetting('Omnichannel_sorting_mechanism', OmnichannelSortingMechanismSettingType.Priority);
|
||||
|
||||
const origInquiries = await fetchAllInquiries(departmentWithAgent.agent.credentials, departmentWithAgent.department._id);
|
||||
expect(origInquiries.length).to.be.greaterThanOrEqual(omniRooms.length);
|
||||
|
||||
const expectedSortedInquiries: ILivechatInquiryRecord[] = JSON.parse(JSON.stringify(origInquiries));
|
||||
|
||||
expectedSortedInquiries.sort(sortByPriority);
|
||||
|
||||
compareInquiries(origInquiries, expectedSortedInquiries);
|
||||
});
|
||||
|
||||
it('it should sort the queue based on slas', async () => {
|
||||
await updateEESetting('Omnichannel_sorting_mechanism', OmnichannelSortingMechanismSettingType.SLAs);
|
||||
|
||||
const origInquiries = await fetchAllInquiries(departmentWithAgent.agent.credentials, departmentWithAgent.department._id);
|
||||
expect(origInquiries.length).to.be.greaterThanOrEqual(omniRooms.length);
|
||||
|
||||
const expectedSortedInquiries = origInquiries.sort(sortBySLA);
|
||||
|
||||
compareInquiries(origInquiries, expectedSortedInquiries);
|
||||
});
|
||||
|
||||
it('it should sort the queue based on timestamp', async () => {
|
||||
await updateEESetting('Omnichannel_sorting_mechanism', OmnichannelSortingMechanismSettingType.Timestamp);
|
||||
|
||||
const origInquiries = await fetchAllInquiries(departmentWithAgent.agent.credentials, departmentWithAgent.department._id);
|
||||
expect(origInquiries.length).to.be.greaterThanOrEqual(omniRooms.length);
|
||||
|
||||
const expectedSortedInquiries = origInquiries.sort(sortByTimestamp);
|
||||
|
||||
compareInquiries(origInquiries, expectedSortedInquiries);
|
||||
});
|
||||
});
|
||||
});
|
||||
242
apps/meteor/tests/end-to-end/api/livechat/13-tags.ts
Normal file
242
apps/meteor/tests/end-to-end/api/livechat/13-tags.ts
Normal file
@@ -0,0 +1,242 @@
|
||||
import type { ILivechatDepartment, ILivechatTag } from '@rocket.chat/core-typings';
|
||||
import { expect } from 'chai';
|
||||
import { after, before, describe, it } from 'mocha';
|
||||
|
||||
import { getCredentials, api, request, credentials } from '../../../data/api-data';
|
||||
import { createDepartmentWithAnOnlineAgent } from '../../../data/livechat/department';
|
||||
import { saveTags, removeAllTags } from '../../../data/livechat/tags';
|
||||
import { createMonitor, createUnit } from '../../../data/livechat/units';
|
||||
import { removePermissionFromAllRoles, restorePermissionToRoles, updatePermission, updateSetting } from '../../../data/permissions.helper';
|
||||
import type { IUserWithCredentials } from '../../../data/user';
|
||||
import { password } from '../../../data/user';
|
||||
import { createUser, deleteUser, login } from '../../../data/users.helper';
|
||||
import { IS_EE } from '../../../e2e/config/constants';
|
||||
|
||||
(IS_EE ? describe : describe.skip)('[EE] Livechat - Tags', () => {
|
||||
before((done) => getCredentials(done));
|
||||
|
||||
before(async () => {
|
||||
await updateSetting('Livechat_enabled', true);
|
||||
});
|
||||
|
||||
describe('livechat/tags', () => {
|
||||
let tagsData: {
|
||||
caseA: { department: ILivechatDepartment; tag: ILivechatTag; agent: IUserWithCredentials };
|
||||
caseB: { department: ILivechatDepartment; tag: ILivechatTag; agent: IUserWithCredentials };
|
||||
casePublic: { tag: ILivechatTag };
|
||||
};
|
||||
let monitor: IUserWithCredentials;
|
||||
|
||||
before(async () => {
|
||||
// create monitor
|
||||
const monitorUser = await createUser();
|
||||
const monitorCredentials = await login(monitorUser.username, password);
|
||||
await createMonitor(monitorUser.username);
|
||||
monitor = {
|
||||
user: monitorUser,
|
||||
credentials: monitorCredentials,
|
||||
};
|
||||
|
||||
// remove all existing tags
|
||||
await removeAllTags();
|
||||
|
||||
// should add 3 tags
|
||||
const { department: dA, agent: agentA } = await createDepartmentWithAnOnlineAgent();
|
||||
const tagA = await saveTags([dA._id]);
|
||||
const { department: dB, agent: agentB } = await createDepartmentWithAnOnlineAgent();
|
||||
const tagB = await saveTags([dB._id]);
|
||||
const publicTag = await saveTags();
|
||||
tagsData = {
|
||||
caseA: { department: dA, tag: tagA, agent: agentA },
|
||||
caseB: { department: dB, tag: tagB, agent: agentB },
|
||||
casePublic: { tag: publicTag },
|
||||
};
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await deleteUser(monitor.user);
|
||||
await removeAllTags();
|
||||
});
|
||||
|
||||
it('should throw unauthorized error when the user does not have the necessary permission', async () => {
|
||||
await removePermissionFromAllRoles('manage-livechat-tags');
|
||||
await removePermissionFromAllRoles('view-l-room');
|
||||
const response = await request.get(api('livechat/tags')).set(credentials).expect('Content-Type', 'application/json').expect(403);
|
||||
expect(response.body).to.have.property('success', false);
|
||||
|
||||
await restorePermissionToRoles('manage-livechat-tags');
|
||||
await restorePermissionToRoles('view-l-room');
|
||||
});
|
||||
it('should return an array of tags', async () => {
|
||||
const { tag } = tagsData.caseA;
|
||||
const response = await request
|
||||
.get(api('livechat/tags'))
|
||||
.set(credentials)
|
||||
.query({ text: tag.name, viewAll: 'true' })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
expect(response.body).to.have.property('success', true);
|
||||
expect(response.body.tags).to.be.an('array').with.lengthOf(1);
|
||||
expect(response.body.tags[0]).to.have.property('_id', tag._id);
|
||||
});
|
||||
it('[Manager role] show return all tags when "viewAll" param is true and user has access to all tags', async () => {
|
||||
const response = await request
|
||||
.get(api('livechat/tags'))
|
||||
.set(credentials)
|
||||
.query({ viewAll: 'true' })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
expect(response.body).to.have.property('success', true);
|
||||
expect(response.body.tags).to.be.an('array').with.lengthOf(3);
|
||||
|
||||
const expectedTags = [tagsData.caseA.tag, tagsData.caseB.tag, tagsData.casePublic.tag].map((tag) => tag._id).sort();
|
||||
const actualTags = response.body.tags.map((tag: ILivechatTag) => tag._id).sort();
|
||||
expect(actualTags).to.deep.equal(expectedTags);
|
||||
});
|
||||
it('[Monitor role] show return only public tags when "viewAll" param is true and user does not have access to all tags', async () => {
|
||||
const response = await request
|
||||
.get(api('livechat/tags'))
|
||||
.set(monitor.credentials)
|
||||
.query({ viewAll: 'true' })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
expect(response.body).to.have.property('success', true);
|
||||
expect(response.body.tags).to.be.an('array').with.lengthOf(1);
|
||||
|
||||
const expectedTags = [tagsData.casePublic.tag].map((tag) => tag._id).sort();
|
||||
const actualTags = response.body.tags.map((tag: ILivechatTag) => tag._id).sort();
|
||||
expect(actualTags).to.deep.equal(expectedTags);
|
||||
});
|
||||
it('[Manager Role] should return department tags and public tags when "departmentId" param is provided', async () => {
|
||||
const { department } = tagsData.caseA;
|
||||
|
||||
const response = await request
|
||||
.get(api('livechat/tags'))
|
||||
.set(credentials)
|
||||
.query({ department: department._id })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
expect(response.body).to.have.property('success', true);
|
||||
expect(response.body.tags).to.be.an('array').with.lengthOf(2);
|
||||
|
||||
const expectedTags = [tagsData.caseA.tag, tagsData.casePublic.tag].map((tag) => tag._id).sort();
|
||||
const actualTags = response.body.tags.map((tag: ILivechatTag) => tag._id).sort();
|
||||
expect(actualTags).to.deep.equal(expectedTags);
|
||||
});
|
||||
it('[Manager role] should return public tags when "departmentId" param is not provided', async () => {
|
||||
const response = await request.get(api('livechat/tags')).set(credentials).expect('Content-Type', 'application/json').expect(200);
|
||||
|
||||
expect(response.body).to.have.property('success', true);
|
||||
expect(response.body.tags).to.be.an('array').with.lengthOf(1);
|
||||
expect(response.body.tags[0]).to.have.property('_id', tagsData.casePublic.tag._id);
|
||||
});
|
||||
it('[Monitor role] should return only public tags when user does not have access to department', async () => {
|
||||
const { department } = tagsData.caseA;
|
||||
|
||||
const response = await request
|
||||
.get(api('livechat/tags'))
|
||||
.set(monitor.credentials)
|
||||
.query({ department: department._id })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
expect(response.body).to.have.property('success', true);
|
||||
expect(response.body.tags).to.be.an('array').with.lengthOf(1);
|
||||
|
||||
const expectedTags = [tagsData.casePublic.tag].map((tag) => tag._id).sort();
|
||||
const actualTags = response.body.tags.map((tag: ILivechatTag) => tag._id).sort();
|
||||
expect(actualTags).to.deep.equal(expectedTags);
|
||||
});
|
||||
it('[Monitor Role] should return department tags and public tags when "departmentId" param is provided', async () => {
|
||||
const { department } = tagsData.caseA;
|
||||
|
||||
await createUnit(monitor.user._id, monitor.user.username || '', [department._id]);
|
||||
|
||||
const response = await request
|
||||
.get(api('livechat/tags'))
|
||||
.set(monitor.credentials)
|
||||
.query({ department: department._id })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
expect(response.body).to.have.property('success', true);
|
||||
expect(response.body.tags).to.be.an('array').with.lengthOf(2);
|
||||
|
||||
const expectedTags = [tagsData.caseA.tag, tagsData.casePublic.tag].map((tag) => tag._id).sort();
|
||||
const actualTags = response.body.tags.map((tag: ILivechatTag) => tag._id).sort();
|
||||
expect(actualTags).to.deep.equal(expectedTags);
|
||||
});
|
||||
it('[Agent Role] should return department tags and public tags when "departmentId" param is provided', async () => {
|
||||
const { department, agent } = tagsData.caseA;
|
||||
|
||||
const response = await request
|
||||
.get(api('livechat/tags'))
|
||||
.set(agent.credentials)
|
||||
.query({ department: department._id })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
expect(response.body).to.have.property('success', true);
|
||||
expect(response.body.tags).to.be.an('array').with.lengthOf(2);
|
||||
|
||||
const expectedTags = [tagsData.caseA.tag, tagsData.casePublic.tag].map((tag) => tag._id).sort();
|
||||
const actualTags = response.body.tags.map((tag: ILivechatTag) => tag._id).sort();
|
||||
expect(actualTags).to.deep.equal(expectedTags);
|
||||
});
|
||||
it('[Monitor role] should return public tags and department tags which they are part of, when "departmentId" param is not provided', async () => {
|
||||
const response = await request
|
||||
.get(api('livechat/tags'))
|
||||
.set(monitor.credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
expect(response.body).to.have.property('success', true);
|
||||
expect(response.body.tags).to.be.an('array').with.lengthOf(2);
|
||||
const expectedTags = [tagsData.caseA.tag, tagsData.casePublic.tag].map((tag) => tag._id).sort();
|
||||
const actualTags = response.body.tags.map((tag: ILivechatTag) => tag._id).sort();
|
||||
expect(actualTags).to.deep.equal(expectedTags);
|
||||
});
|
||||
it('[Agent role] should return public tags when "departmentId" param is not provided', async () => {
|
||||
const response = await request
|
||||
.get(api('livechat/tags'))
|
||||
.set(monitor.credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
expect(response.body).to.have.property('success', true);
|
||||
expect(response.body.tags).to.be.an('array').with.lengthOf(2);
|
||||
const expectedTags = [tagsData.caseA.tag, tagsData.casePublic.tag].map((tag) => tag._id).sort();
|
||||
const actualTags = response.body.tags.map((tag: ILivechatTag) => tag._id).sort();
|
||||
expect(actualTags).to.deep.equal(expectedTags);
|
||||
});
|
||||
});
|
||||
|
||||
describe('livechat/tags/:tagId', () => {
|
||||
it('should throw unauthorized error when the user does not have the necessary permission', async () => {
|
||||
await updatePermission('manage-livechat-tags', []);
|
||||
await updatePermission('view-l-room', []);
|
||||
const response = await request.get(api('livechat/tags/123')).set(credentials).expect('Content-Type', 'application/json').expect(403);
|
||||
expect(response.body).to.have.property('success', false);
|
||||
});
|
||||
it('should return null when the tag does not exist', async () => {
|
||||
await updatePermission('manage-livechat-tags', ['admin']);
|
||||
await updatePermission('view-l-room', ['livechat-agent']);
|
||||
await request.get(api('livechat/tags/123')).set(credentials).expect('Content-Type', 'application/json').expect(404);
|
||||
});
|
||||
it('should return a tag', async () => {
|
||||
const tag = await saveTags();
|
||||
const response = await request
|
||||
.get(api(`livechat/tags/${tag._id}`))
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
expect(response.body).to.have.property('success', true);
|
||||
expect(response.body).to.have.property('_id', tag._id);
|
||||
expect(response.body).to.have.property('name', tag.name);
|
||||
expect(response.body).to.have.property('numDepartments', 0);
|
||||
});
|
||||
});
|
||||
});
|
||||
739
apps/meteor/tests/end-to-end/api/livechat/14-units.ts
Normal file
739
apps/meteor/tests/end-to-end/api/livechat/14-units.ts
Normal file
@@ -0,0 +1,739 @@
|
||||
import type { ILivechatDepartment, IOmnichannelBusinessUnit } from '@rocket.chat/core-typings';
|
||||
import { expect } from 'chai';
|
||||
import { before, after, describe, it } from 'mocha';
|
||||
|
||||
import { getCredentials, api, request, credentials } from '../../../data/api-data';
|
||||
import { deleteDepartment, getDepartmentById } from '../../../data/livechat/department';
|
||||
import { createDepartment, updateDepartment } from '../../../data/livechat/rooms';
|
||||
import { createMonitor, createUnit, deleteUnit, getUnit } from '../../../data/livechat/units';
|
||||
import { updatePermission, updateSetting } from '../../../data/permissions.helper';
|
||||
import { password } from '../../../data/user';
|
||||
import { createUser, deleteUser, login } from '../../../data/users.helper';
|
||||
import { IS_EE } from '../../../e2e/config/constants';
|
||||
|
||||
(IS_EE ? describe : describe.skip)('[EE] LIVECHAT - Units', () => {
|
||||
before((done) => getCredentials(done));
|
||||
|
||||
before(async () => {
|
||||
await updateSetting('Livechat_enabled', true);
|
||||
await updatePermission('manage-livechat-departments', ['livechat-manager', 'livechat-monitor', 'admin']);
|
||||
await updateSetting('Omnichannel_enable_department_removal', true);
|
||||
});
|
||||
after(async () => {
|
||||
await updateSetting('Omnichannel_enable_department_removal', false);
|
||||
});
|
||||
|
||||
describe('[GET] livechat/units', () => {
|
||||
it('should return empty if manage-livechat-units permission is missing', async () => {
|
||||
await updatePermission('manage-livechat-units', []);
|
||||
return request
|
||||
.get(api('livechat/units'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
unitData: {
|
||||
name: 'test',
|
||||
enabled: true,
|
||||
},
|
||||
unitMonitors: [],
|
||||
unitDepartments: [],
|
||||
})
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('units').that.is.an('array').with.lengthOf(0);
|
||||
});
|
||||
});
|
||||
it('should return a list of units', async () => {
|
||||
await updatePermission('manage-livechat-units', ['admin']);
|
||||
const user = await createUser();
|
||||
await createMonitor(user.username);
|
||||
const department = await createDepartment();
|
||||
const unit = await createUnit(user._id, user.username, [department._id]);
|
||||
|
||||
const { body } = await request.get(api('livechat/units')).set(credentials).expect(200);
|
||||
expect(body.units).to.be.an('array').with.lengthOf.greaterThan(0);
|
||||
const unitFound = body.units.find((u: IOmnichannelBusinessUnit) => u._id === unit._id);
|
||||
expect(unitFound).to.have.property('_id', unit._id);
|
||||
expect(unitFound).to.have.property('name', unit.name);
|
||||
expect(unitFound).to.have.property('numMonitors', 1);
|
||||
expect(unitFound).to.have.property('numDepartments', 1);
|
||||
expect(unitFound).to.have.property('type', 'u');
|
||||
|
||||
// cleanup
|
||||
await deleteUser(user);
|
||||
});
|
||||
|
||||
it('should return a list of units matching the provided filter', async () => {
|
||||
const user = await createUser();
|
||||
await createMonitor(user.username);
|
||||
const department = await createDepartment();
|
||||
const unit = await createUnit(user._id, user.username, [department._id]);
|
||||
|
||||
const { body } = await request.get(api('livechat/units')).query({ text: unit.name }).set(credentials).expect(200);
|
||||
expect(body.units).to.be.an('array').with.lengthOf(1);
|
||||
const unitFound = body.units.find((u: IOmnichannelBusinessUnit) => u._id === unit._id);
|
||||
expect(unitFound).to.have.property('_id', unit._id);
|
||||
expect(unitFound).to.have.property('name', unit.name);
|
||||
expect(unitFound).to.have.property('numMonitors', 1);
|
||||
expect(unitFound).to.have.property('numDepartments', 1);
|
||||
expect(unitFound).to.have.property('type', 'u');
|
||||
|
||||
// cleanup
|
||||
await deleteUser(user);
|
||||
});
|
||||
|
||||
it('should properly paginate the result set', async () => {
|
||||
const { body } = await request.get(api('livechat/units')).query({ count: 1 }).set(credentials).expect(200);
|
||||
expect(body).to.have.property('units').and.to.be.an('array').with.lengthOf(1);
|
||||
expect(body).to.have.property('count').and.to.be.equal(1);
|
||||
const unit = body.units[0];
|
||||
|
||||
const { body: body2 } = await request.get(api('livechat/units')).query({ count: 1, offset: 1 }).set(credentials).expect(200);
|
||||
expect(body2).to.have.property('units').and.to.be.an('array').with.lengthOf(1);
|
||||
const unit2 = body2.units[0];
|
||||
|
||||
expect(unit._id).to.not.be.equal(unit2._id);
|
||||
});
|
||||
|
||||
it('should sort the result set based on provided fields', async () => {
|
||||
const user = await createUser();
|
||||
await createMonitor(user.username);
|
||||
const department = await createDepartment();
|
||||
const unit = await createUnit(user._id, user.username, [department._id], 'A test 1234');
|
||||
const unit2 = await createUnit(user._id, user.username, [department._id], 'a test 1234');
|
||||
|
||||
const { body } = await request
|
||||
.get(api('livechat/units'))
|
||||
.query({ sort: JSON.stringify({ name: 1 }), text: 'test', count: 2 })
|
||||
.set(credentials)
|
||||
.expect(200);
|
||||
expect(body).to.have.property('units').and.to.be.an('array').with.lengthOf(2);
|
||||
expect(body.units[0]._id).to.be.equal(unit._id);
|
||||
expect(body.units[1]._id).to.be.equal(unit2._id);
|
||||
|
||||
await deleteUser(user);
|
||||
});
|
||||
});
|
||||
|
||||
describe('[POST] livechat/units', () => {
|
||||
it('should fail if manage-livechat-units permission is missing', async () => {
|
||||
await updatePermission('manage-livechat-units', []);
|
||||
return request
|
||||
.post(api('livechat/units'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
unitData: {
|
||||
name: 'test',
|
||||
enabled: true,
|
||||
},
|
||||
unitMonitors: [],
|
||||
unitDepartments: [],
|
||||
})
|
||||
.expect(403);
|
||||
});
|
||||
it('should return a created unit', async () => {
|
||||
await updatePermission('manage-livechat-units', ['admin']);
|
||||
const user = await createUser();
|
||||
await createMonitor(user.username);
|
||||
const department = await createDepartment();
|
||||
|
||||
const { body } = await request
|
||||
.post(api('livechat/units'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
unitData: { name: 'test', visibility: 'public', enabled: true, description: 'test' },
|
||||
unitMonitors: [{ monitorId: user._id, username: user.username }],
|
||||
unitDepartments: [{ departmentId: department._id }],
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
expect(body).to.have.property('_id');
|
||||
expect(body).to.have.property('name', 'test');
|
||||
expect(body).to.have.property('visibility', 'public');
|
||||
expect(body).to.have.property('type', 'u');
|
||||
expect(body).to.have.property('numMonitors', 1);
|
||||
expect(body).to.have.property('numDepartments', 1);
|
||||
|
||||
// cleanup
|
||||
await deleteUser(user);
|
||||
});
|
||||
|
||||
it('should return a unit with no monitors if a user who is not a monitor is passed', async () => {
|
||||
await updatePermission('manage-livechat-units', ['admin']);
|
||||
const user = await createUser();
|
||||
const department = await createDepartment();
|
||||
|
||||
const { body } = await request
|
||||
.post(api('livechat/units'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
unitData: { name: 'test', visibility: 'public', enabled: true, description: 'test' },
|
||||
unitMonitors: [{ monitorId: user._id, username: user.username }],
|
||||
unitDepartments: [{ departmentId: department._id }],
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
expect(body).to.have.property('numMonitors', 0);
|
||||
expect(body).to.have.property('name', 'test');
|
||||
|
||||
// cleanup
|
||||
await deleteUser(user);
|
||||
});
|
||||
});
|
||||
|
||||
describe('[GET] livechat/units/:id', () => {
|
||||
it('should fail if manage-livechat-units permission is missing', async () => {
|
||||
await updatePermission('manage-livechat-units', []);
|
||||
return request.get(api('livechat/units/123')).set(credentials).send().expect(403);
|
||||
});
|
||||
it('should return a unit', async () => {
|
||||
await updatePermission('manage-livechat-units', ['admin']);
|
||||
const user = await createUser();
|
||||
await createMonitor(user.username);
|
||||
const department = await createDepartment();
|
||||
const unit = await createUnit(user._id, user.username, [department._id]);
|
||||
|
||||
const { body } = await request
|
||||
.get(api(`livechat/units/${unit._id}`))
|
||||
.set(credentials)
|
||||
.expect(200);
|
||||
expect(body).to.have.property('_id', unit._id);
|
||||
expect(body).to.have.property('name', unit.name);
|
||||
expect(body).to.have.property('numMonitors', 1);
|
||||
expect(body).to.have.property('numDepartments', 1);
|
||||
expect(body).to.have.property('type', 'u');
|
||||
|
||||
// cleanup
|
||||
await deleteUser(user);
|
||||
});
|
||||
});
|
||||
|
||||
describe('[POST] livechat/units/:id', () => {
|
||||
it('should fail if manage-livechat-units permission is missing', async () => {
|
||||
await updatePermission('manage-livechat-units', []);
|
||||
return request.post(api('livechat/units/123')).set(credentials).expect(403);
|
||||
});
|
||||
it('should fail if unit does not exist', async () => {
|
||||
await updatePermission('manage-livechat-units', ['admin']);
|
||||
const user = await createUser();
|
||||
await createMonitor(user.username);
|
||||
const department = await createDepartment();
|
||||
|
||||
const { body } = await request
|
||||
.post(api('livechat/units/123'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
unitData: { name: 'test', visibility: 'public', enabled: true, description: 'test' },
|
||||
unitMonitors: [{ monitorId: user._id, username: user.username }],
|
||||
unitDepartments: [{ departmentId: department._id }],
|
||||
})
|
||||
.expect(400);
|
||||
|
||||
expect(body).to.have.property('success', false);
|
||||
// cleanup
|
||||
await deleteUser(user);
|
||||
});
|
||||
it('should return a updated unit', async () => {
|
||||
const user = await createUser();
|
||||
await createMonitor(user.username);
|
||||
const department = await createDepartment();
|
||||
const unit = await createUnit(user._id, user.username, [department._id]);
|
||||
|
||||
const { body } = await request
|
||||
.post(api(`livechat/units/${unit._id}`))
|
||||
.set(credentials)
|
||||
.send({
|
||||
unitData: { name: 'test', visibility: 'private', enabled: true, description: 'test' },
|
||||
unitMonitors: [{ monitorId: user._id, username: user.username }],
|
||||
unitDepartments: [{ departmentId: department._id }],
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
expect(body).to.have.property('_id');
|
||||
expect(body).to.have.property('name', 'test');
|
||||
expect(body).to.have.property('visibility', 'private');
|
||||
expect(body).to.have.property('type', 'u');
|
||||
expect(body).to.have.property('numMonitors', 1);
|
||||
expect(body).to.have.property('numDepartments', 1);
|
||||
|
||||
// cleanup
|
||||
await deleteUser(user);
|
||||
});
|
||||
it('should move the department to the latest unit that attempted to assign it', async () => {
|
||||
const user = await createUser();
|
||||
await createMonitor(user.username);
|
||||
const department = await createDepartment();
|
||||
const unit1 = await createUnit(user._id, user.username, [department._id]);
|
||||
const unit2 = await createUnit(user._id, user.username, [department._id]);
|
||||
|
||||
const { body } = await request
|
||||
.get(api(`livechat/units/${unit1._id}/departments`))
|
||||
.set(credentials)
|
||||
.expect(200);
|
||||
|
||||
expect(body).to.have.property('departments');
|
||||
expect(body.departments).to.have.lengthOf(0);
|
||||
expect(unit2.numDepartments).to.be.equal(1);
|
||||
});
|
||||
it('should remove the department from the unit if it is not passed in the request', async () => {
|
||||
const user = await createUser();
|
||||
await createMonitor(user.username);
|
||||
const department = await createDepartment();
|
||||
const unit1 = await createUnit(user._id, user.username, [department._id]);
|
||||
|
||||
const { body } = await request
|
||||
.post(api(`livechat/units/${unit1._id}`))
|
||||
.set(credentials)
|
||||
.send({
|
||||
unitData: { name: unit1.name, visibility: unit1.visibility },
|
||||
unitMonitors: [{ monitorId: user._id, username: user.username }],
|
||||
unitDepartments: [],
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
expect(body).to.have.property('_id', unit1._id);
|
||||
expect(body).to.have.property('numDepartments', 0);
|
||||
});
|
||||
it('should remove the monitor from the unit if it is not passed in the request', async () => {
|
||||
const user = await createUser();
|
||||
await createMonitor(user.username);
|
||||
const department = await createDepartment();
|
||||
const unit1 = await createUnit(user._id, user.username, [department._id]);
|
||||
|
||||
const { body } = await request
|
||||
.post(api(`livechat/units/${unit1._id}`))
|
||||
.set(credentials)
|
||||
.send({
|
||||
unitData: { name: unit1.name, visibility: unit1.visibility },
|
||||
unitMonitors: [],
|
||||
unitDepartments: [{ departmentId: department._id }],
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
expect(body).to.have.property('_id', unit1._id);
|
||||
expect(body).to.have.property('numMonitors', 0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('[DELETE] livechat/units/:id', () => {
|
||||
it('should fail if manage-livechat-units permission is missing', async () => {
|
||||
await updatePermission('manage-livechat-units', []);
|
||||
return request.delete(api('livechat/units/123')).set(credentials).expect(403);
|
||||
});
|
||||
it('should return a deleted unit', async () => {
|
||||
await updatePermission('manage-livechat-units', ['admin']);
|
||||
const user = await createUser();
|
||||
await createMonitor(user.username);
|
||||
const department = await createDepartment();
|
||||
const unit = await createUnit(user._id, user.username, [department._id]);
|
||||
|
||||
const { body } = await request
|
||||
.delete(api(`livechat/units/${unit._id}`))
|
||||
.set(credentials)
|
||||
.expect(200);
|
||||
|
||||
expect(body).to.have.be.a('number').equal(1);
|
||||
|
||||
// cleanup
|
||||
await deleteUser(user);
|
||||
});
|
||||
});
|
||||
|
||||
describe('livechat/units/:unitId/departments', () => {
|
||||
it('should fail if manage-livechat-units permission is missing', async () => {
|
||||
await updatePermission('manage-livechat-units', []);
|
||||
return request.get(api('livechat/units/123/departments')).set(credentials).expect(403);
|
||||
});
|
||||
it('should return departments associated with a unit', async () => {
|
||||
await updatePermission('manage-livechat-units', ['admin']);
|
||||
const user = await createUser();
|
||||
await createMonitor(user.username);
|
||||
const department = await createDepartment();
|
||||
const unit = await createUnit(user._id, user.username, [department._id]);
|
||||
|
||||
const { body } = await request
|
||||
.get(api(`livechat/units/${unit._id}/departments`))
|
||||
.set(credentials)
|
||||
.expect(200);
|
||||
|
||||
expect(body).to.have.property('departments');
|
||||
expect(body.departments).to.have.lengthOf(1);
|
||||
expect(body.departments[0]).to.have.property('_id', department._id);
|
||||
expect(body.departments[0]).to.have.property('name', department.name);
|
||||
|
||||
// cleanup
|
||||
await deleteUser(user);
|
||||
});
|
||||
});
|
||||
|
||||
describe('livechat/units/:unitId/departments/available', () => {
|
||||
it('should fail if manage-livechat-units permission is missing', async () => {
|
||||
await updatePermission('manage-livechat-units', []);
|
||||
return request.get(api('livechat/units/123/departments/available')).set(credentials).expect(403);
|
||||
});
|
||||
it('should return departments not associated with a unit', async () => {
|
||||
await updatePermission('manage-livechat-units', ['admin']);
|
||||
const user = await createUser();
|
||||
await createMonitor(user.username);
|
||||
const department = await createDepartment();
|
||||
const unit = await createUnit(user._id, user.username, [department._id]);
|
||||
|
||||
const { body } = await request
|
||||
.get(api(`livechat/units/${unit._id}/departments/available`))
|
||||
.set(credentials)
|
||||
.expect(200);
|
||||
|
||||
expect(body).to.have.property('departments');
|
||||
expect(body.departments).to.have.lengthOf.greaterThan(0);
|
||||
|
||||
const myUnit = body.departments.find((d: ILivechatDepartment) => d.parentId === unit._id);
|
||||
expect(myUnit).to.not.be.undefined.and.not.be.null;
|
||||
|
||||
// cleanup
|
||||
await deleteUser(user);
|
||||
});
|
||||
});
|
||||
|
||||
describe('livechat/units/:unitId/monitors', () => {
|
||||
it('should fail if manage-livechat-units permission is missing', async () => {
|
||||
await updatePermission('manage-livechat-monitors', []);
|
||||
return request.get(api('livechat/units/123/monitors')).set(credentials).expect(403);
|
||||
});
|
||||
it('should return monitors associated with a unit', async () => {
|
||||
await updatePermission('manage-livechat-monitors', ['admin']);
|
||||
const user = await createUser();
|
||||
await createMonitor(user.username);
|
||||
const department = await createDepartment();
|
||||
const unit = await createUnit(user._id, user.username, [department._id]);
|
||||
|
||||
const { body } = await request
|
||||
.get(api(`livechat/units/${unit._id}/monitors`))
|
||||
.set(credentials)
|
||||
.expect(200);
|
||||
|
||||
expect(body).to.have.property('monitors');
|
||||
expect(body.monitors).to.have.lengthOf(1);
|
||||
expect(body.monitors[0]).to.have.property('monitorId', user._id);
|
||||
expect(body.monitors[0]).to.have.property('username', user.username);
|
||||
|
||||
// cleanup
|
||||
await deleteUser(user);
|
||||
});
|
||||
});
|
||||
|
||||
describe('[POST] livechat/department', () => {
|
||||
let monitor1: Awaited<ReturnType<typeof createUser>>;
|
||||
let monitor1Credentials: Awaited<ReturnType<typeof login>>;
|
||||
let monitor2: Awaited<ReturnType<typeof createUser>>;
|
||||
let monitor2Credentials: Awaited<ReturnType<typeof login>>;
|
||||
let unit: IOmnichannelBusinessUnit;
|
||||
|
||||
before(async () => {
|
||||
monitor1 = await createUser();
|
||||
monitor2 = await createUser();
|
||||
await createMonitor(monitor1.username);
|
||||
monitor1Credentials = await login(monitor1.username, password);
|
||||
await createMonitor(monitor2.username);
|
||||
monitor2Credentials = await login(monitor2.username, password);
|
||||
unit = await createUnit(monitor1._id, monitor1.username, []);
|
||||
});
|
||||
|
||||
after(async () => Promise.all([deleteUser(monitor1), deleteUser(monitor2), deleteUnit(unit)]));
|
||||
|
||||
it('should fail creating department when providing an invalid property in the department unit object', () => {
|
||||
return request
|
||||
.post(api('livechat/department'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
department: { name: 'Fail-Test', enabled: true, showOnOfflineForm: true, showOnRegistration: true, email: 'bla@bla' },
|
||||
departmentUnit: { invalidProperty: true },
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('errorType', 'invalid-params');
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail creating a department into an existing unit that a monitor does not supervise', async () => {
|
||||
const department = await createDepartment(undefined, undefined, undefined, undefined, { _id: unit._id }, monitor2Credentials);
|
||||
|
||||
const updatedUnit = await getUnit(unit._id);
|
||||
expect(updatedUnit).to.have.property('name', unit.name);
|
||||
expect(updatedUnit).to.have.property('numMonitors', 1);
|
||||
expect(updatedUnit).to.have.property('numDepartments', 0);
|
||||
|
||||
const fullDepartment = await getDepartmentById(department._id);
|
||||
expect(fullDepartment).to.not.have.property('parentId');
|
||||
expect(fullDepartment).to.not.have.property('ancestors');
|
||||
|
||||
await deleteDepartment(department._id);
|
||||
});
|
||||
|
||||
it('should succesfully create a department into an existing unit as an admin', async () => {
|
||||
const department = await createDepartment(undefined, undefined, undefined, undefined, { _id: unit._id });
|
||||
|
||||
const updatedUnit = await getUnit(unit._id);
|
||||
expect(updatedUnit).to.have.property('name', unit.name);
|
||||
expect(updatedUnit).to.have.property('numMonitors', 1);
|
||||
expect(updatedUnit).to.have.property('numDepartments', 1);
|
||||
|
||||
const fullDepartment = await getDepartmentById(department._id);
|
||||
expect(fullDepartment).to.have.property('parentId', unit._id);
|
||||
expect(fullDepartment).to.have.property('ancestors').that.is.an('array').with.lengthOf(1);
|
||||
expect(fullDepartment.ancestors?.[0]).to.equal(unit._id);
|
||||
|
||||
await deleteDepartment(department._id);
|
||||
});
|
||||
|
||||
it('should succesfully create a department into an existing unit that a monitor supervises', async () => {
|
||||
const department = await createDepartment(undefined, undefined, undefined, undefined, { _id: unit._id }, monitor1Credentials);
|
||||
|
||||
const updatedUnit = await getUnit(unit._id);
|
||||
expect(updatedUnit).to.have.property('name', unit.name);
|
||||
expect(updatedUnit).to.have.property('numMonitors', 1);
|
||||
expect(updatedUnit).to.have.property('numDepartments', 1);
|
||||
|
||||
const fullDepartment = await getDepartmentById(department._id);
|
||||
expect(fullDepartment).to.have.property('parentId', unit._id);
|
||||
expect(fullDepartment).to.have.property('ancestors').that.is.an('array').with.lengthOf(1);
|
||||
expect(fullDepartment.ancestors?.[0]).to.equal(unit._id);
|
||||
|
||||
await deleteDepartment(department._id);
|
||||
});
|
||||
|
||||
it('unit should end up with 0 departments after removing all of them', async () => {
|
||||
const updatedUnit = await getUnit(unit._id);
|
||||
expect(updatedUnit).to.have.property('name', unit.name);
|
||||
expect(updatedUnit).to.have.property('numMonitors', 1);
|
||||
expect(updatedUnit).to.have.property('numDepartments', 0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('[PUT] livechat/department', () => {
|
||||
let monitor1: Awaited<ReturnType<typeof createUser>>;
|
||||
let monitor1Credentials: Awaited<ReturnType<typeof login>>;
|
||||
let monitor2: Awaited<ReturnType<typeof createUser>>;
|
||||
let monitor2Credentials: Awaited<ReturnType<typeof login>>;
|
||||
let unit: IOmnichannelBusinessUnit;
|
||||
let department: ILivechatDepartment;
|
||||
let baseDepartment: ILivechatDepartment;
|
||||
|
||||
before(async () => {
|
||||
monitor1 = await createUser();
|
||||
monitor2 = await createUser();
|
||||
await createMonitor(monitor1.username);
|
||||
monitor1Credentials = await login(monitor1.username, password);
|
||||
await createMonitor(monitor2.username);
|
||||
monitor2Credentials = await login(monitor2.username, password);
|
||||
department = await createDepartment();
|
||||
baseDepartment = await createDepartment();
|
||||
unit = await createUnit(monitor1._id, monitor1.username, [baseDepartment._id]);
|
||||
});
|
||||
|
||||
after(async () =>
|
||||
Promise.all([
|
||||
deleteUser(monitor1),
|
||||
deleteUser(monitor2),
|
||||
deleteUnit(unit),
|
||||
deleteDepartment(department._id),
|
||||
deleteDepartment(baseDepartment._id),
|
||||
]),
|
||||
);
|
||||
|
||||
it("should fail updating a department's unit when providing an invalid property in the department unit object", () => {
|
||||
const updatedName = 'updated-department-name';
|
||||
return request
|
||||
.put(api(`livechat/department/${department._id}`))
|
||||
.set(credentials)
|
||||
.send({
|
||||
department: { name: updatedName, enabled: true, showOnOfflineForm: true, showOnRegistration: true, email: 'bla@bla' },
|
||||
departmentUnit: { invalidProperty: true },
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error', 'Match error: Unknown key in field departmentUnit.invalidProperty');
|
||||
});
|
||||
});
|
||||
|
||||
it("should fail updating a department's unit when providing an invalid _id type in the department unit object", () => {
|
||||
const updatedName = 'updated-department-name';
|
||||
return request
|
||||
.put(api(`livechat/department/${department._id}`))
|
||||
.set(credentials)
|
||||
.send({
|
||||
department: { name: updatedName, enabled: true, showOnOfflineForm: true, showOnRegistration: true, email: 'bla@bla' },
|
||||
departmentUnit: { _id: true },
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error', 'Match error: Expected string, got boolean in field departmentUnit._id');
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail removing the last department from a unit', () => {
|
||||
const updatedName = 'updated-department-name';
|
||||
return request
|
||||
.put(api(`livechat/department/${baseDepartment._id}`))
|
||||
.set(credentials)
|
||||
.send({
|
||||
department: { name: updatedName, enabled: true, showOnOfflineForm: true, showOnRegistration: true, email: 'bla@bla' },
|
||||
departmentUnit: {},
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('errorType', 'error-unit-cant-be-empty');
|
||||
});
|
||||
});
|
||||
|
||||
it('should succesfully add an existing department to a unit as an admin', async () => {
|
||||
const updatedName = 'updated-department-name';
|
||||
|
||||
const updatedDepartment = await updateDepartment({
|
||||
userCredentials: credentials,
|
||||
departmentId: department._id,
|
||||
name: updatedName,
|
||||
departmentUnit: { _id: unit._id },
|
||||
});
|
||||
expect(updatedDepartment).to.have.property('name', updatedName);
|
||||
expect(updatedDepartment).to.have.property('type', 'd');
|
||||
expect(updatedDepartment).to.have.property('_id', department._id);
|
||||
|
||||
const updatedUnit = await getUnit(unit._id);
|
||||
expect(updatedUnit).to.have.property('name', unit.name);
|
||||
expect(updatedUnit).to.have.property('numMonitors', 1);
|
||||
expect(updatedUnit).to.have.property('numDepartments', 2);
|
||||
|
||||
const fullDepartment = await getDepartmentById(department._id);
|
||||
expect(fullDepartment).to.have.property('parentId', unit._id);
|
||||
expect(fullDepartment).to.have.property('ancestors').that.is.an('array').with.lengthOf(1);
|
||||
expect(fullDepartment.ancestors?.[0]).to.equal(unit._id);
|
||||
});
|
||||
|
||||
it('should succesfully remove an existing department from a unit as an admin', async () => {
|
||||
const updatedName = 'updated-department-name';
|
||||
|
||||
const updatedDepartment = await updateDepartment({
|
||||
userCredentials: credentials,
|
||||
departmentId: department._id,
|
||||
name: updatedName,
|
||||
departmentUnit: {},
|
||||
});
|
||||
expect(updatedDepartment).to.have.property('name', updatedName);
|
||||
expect(updatedDepartment).to.have.property('type', 'd');
|
||||
expect(updatedDepartment).to.have.property('_id', department._id);
|
||||
|
||||
const updatedUnit = await getUnit(unit._id);
|
||||
expect(updatedUnit).to.have.property('name', unit.name);
|
||||
expect(updatedUnit).to.have.property('numMonitors', 1);
|
||||
expect(updatedUnit).to.have.property('numDepartments', 1);
|
||||
|
||||
const fullDepartment = await getDepartmentById(department._id);
|
||||
expect(fullDepartment).to.not.have.property('parentId');
|
||||
expect(fullDepartment).to.not.have.property('ancestors');
|
||||
});
|
||||
|
||||
it('should fail adding a department into an existing unit that a monitor does not supervise', async () => {
|
||||
const updatedName = 'updated-department-name2';
|
||||
|
||||
const updatedDepartment = await updateDepartment({
|
||||
userCredentials: monitor2Credentials,
|
||||
departmentId: department._id,
|
||||
name: updatedName,
|
||||
departmentUnit: { _id: unit._id },
|
||||
});
|
||||
expect(updatedDepartment).to.have.property('name', updatedName);
|
||||
expect(updatedDepartment).to.have.property('type', 'd');
|
||||
expect(updatedDepartment).to.have.property('_id', department._id);
|
||||
|
||||
const updatedUnit = await getUnit(unit._id);
|
||||
expect(updatedUnit).to.have.property('name', unit.name);
|
||||
expect(updatedUnit).to.have.property('numMonitors', 1);
|
||||
expect(updatedUnit).to.have.property('numDepartments', 1);
|
||||
|
||||
const fullDepartment = await getDepartmentById(department._id);
|
||||
expect(fullDepartment).to.not.have.property('parentId');
|
||||
expect(fullDepartment).to.not.have.property('ancestors');
|
||||
});
|
||||
|
||||
it('should succesfully add a department into an existing unit that a monitor supervises', async () => {
|
||||
const updatedName = 'updated-department-name3';
|
||||
|
||||
const updatedDepartment = await updateDepartment({
|
||||
userCredentials: monitor1Credentials,
|
||||
departmentId: department._id,
|
||||
name: updatedName,
|
||||
departmentUnit: { _id: unit._id },
|
||||
});
|
||||
expect(updatedDepartment).to.have.property('name', updatedName);
|
||||
expect(updatedDepartment).to.have.property('type', 'd');
|
||||
expect(updatedDepartment).to.have.property('_id', department._id);
|
||||
|
||||
const updatedUnit = await getUnit(unit._id);
|
||||
expect(updatedUnit).to.have.property('name', unit.name);
|
||||
expect(updatedUnit).to.have.property('numMonitors', 1);
|
||||
expect(updatedUnit).to.have.property('numDepartments', 2);
|
||||
|
||||
const fullDepartment = await getDepartmentById(department._id);
|
||||
expect(fullDepartment).to.have.property('name', updatedName);
|
||||
expect(fullDepartment).to.have.property('parentId', unit._id);
|
||||
expect(fullDepartment).to.have.property('ancestors').that.is.an('array').with.lengthOf(1);
|
||||
expect(fullDepartment.ancestors?.[0]).to.equal(unit._id);
|
||||
});
|
||||
|
||||
it('should fail removing a department from a unit that a monitor does not supervise', async () => {
|
||||
const updatedName = 'updated-department-name4';
|
||||
|
||||
const updatedDepartment = await updateDepartment({
|
||||
userCredentials: monitor2Credentials,
|
||||
departmentId: department._id,
|
||||
name: updatedName,
|
||||
departmentUnit: {},
|
||||
});
|
||||
expect(updatedDepartment).to.have.property('name', updatedName);
|
||||
expect(updatedDepartment).to.have.property('type', 'd');
|
||||
expect(updatedDepartment).to.have.property('_id', department._id);
|
||||
|
||||
const updatedUnit = await getUnit(unit._id);
|
||||
expect(updatedUnit).to.have.property('name', unit.name);
|
||||
expect(updatedUnit).to.have.property('numMonitors', 1);
|
||||
expect(updatedUnit).to.have.property('numDepartments', 2);
|
||||
|
||||
const fullDepartment = await getDepartmentById(department._id);
|
||||
expect(fullDepartment).to.have.property('name', updatedName);
|
||||
expect(fullDepartment).to.have.property('parentId', unit._id);
|
||||
expect(fullDepartment).to.have.property('ancestors').that.is.an('array').with.lengthOf(1);
|
||||
expect(fullDepartment.ancestors?.[0]).to.equal(unit._id);
|
||||
});
|
||||
|
||||
it('should succesfully remove a department from a unit that a monitor supervises', async () => {
|
||||
const updatedName = 'updated-department-name5';
|
||||
|
||||
const updatedDepartment = await updateDepartment({
|
||||
userCredentials: monitor1Credentials,
|
||||
departmentId: department._id,
|
||||
name: updatedName,
|
||||
departmentUnit: {},
|
||||
});
|
||||
expect(updatedDepartment).to.have.property('name', updatedName);
|
||||
expect(updatedDepartment).to.have.property('type', 'd');
|
||||
expect(updatedDepartment).to.have.property('_id', department._id);
|
||||
|
||||
const updatedUnit = await getUnit(unit._id);
|
||||
expect(updatedUnit).to.have.property('name', unit.name);
|
||||
expect(updatedUnit).to.have.property('numMonitors', 1);
|
||||
expect(updatedUnit).to.have.property('numDepartments', 1);
|
||||
|
||||
const fullDepartment = await getDepartmentById(department._id);
|
||||
expect(fullDepartment).to.have.property('name', updatedName);
|
||||
expect(fullDepartment).to.not.have.property('parentId');
|
||||
expect(fullDepartment).to.not.have.property('ancestors');
|
||||
});
|
||||
});
|
||||
});
|
||||
296
apps/meteor/tests/end-to-end/api/livechat/15-canned-responses.ts
Normal file
296
apps/meteor/tests/end-to-end/api/livechat/15-canned-responses.ts
Normal file
@@ -0,0 +1,296 @@
|
||||
import { faker } from '@faker-js/faker';
|
||||
import type { IOmnichannelCannedResponse } from '@rocket.chat/core-typings';
|
||||
import { expect } from 'chai';
|
||||
import { before, describe, it } from 'mocha';
|
||||
|
||||
import { getCredentials, api, request, credentials } from '../../../data/api-data';
|
||||
import { createCannedResponse } from '../../../data/livechat/canned-responses';
|
||||
import { createAgent, createDepartment } from '../../../data/livechat/rooms';
|
||||
import { removeTag, saveTags } from '../../../data/livechat/tags';
|
||||
import { createMonitor, createUnit } from '../../../data/livechat/units';
|
||||
import { updatePermission, updateSetting } from '../../../data/permissions.helper';
|
||||
import { password, adminUsername } from '../../../data/user';
|
||||
import { createUser, login } from '../../../data/users.helper';
|
||||
import { IS_EE } from '../../../e2e/config/constants';
|
||||
|
||||
(IS_EE ? describe : describe.skip)('[EE] LIVECHAT - Canned responses', () => {
|
||||
before((done) => getCredentials(done));
|
||||
|
||||
before(async () => {
|
||||
await updateSetting('Livechat_enabled', true);
|
||||
});
|
||||
|
||||
describe('canned-responses.get', () => {
|
||||
it('should throw unauthorized when view-canned-responses permission is not set', async () => {
|
||||
await updatePermission('view-canned-responses', []);
|
||||
return request.get(api('canned-responses.get')).set(credentials).expect(403);
|
||||
});
|
||||
it('should return an array of canned responses when available', async () => {
|
||||
await updatePermission('view-canned-responses', ['livechat-agent', 'livechat-monitor', 'livechat-manager', 'admin']);
|
||||
await createCannedResponse();
|
||||
const { body } = await request.get(api('canned-responses.get')).set(credentials).expect(200);
|
||||
expect(body).to.have.property('success', true);
|
||||
expect(body.responses).to.be.an('array').with.lengthOf.greaterThan(0);
|
||||
expect(body.responses[0]).to.have.property('_id');
|
||||
expect(body.responses[0]).to.have.property('shortcut');
|
||||
expect(body.responses[0]).to.have.property('scope');
|
||||
expect(body.responses[0]).to.have.property('tags');
|
||||
expect(body.responses[0]).to.have.property('text');
|
||||
expect(body.responses[0]).to.have.property('userId');
|
||||
});
|
||||
it('should return canned responses for monitors even if department is disabled', async () => {
|
||||
const user = await createUser();
|
||||
const monitor = await createMonitor(user.username);
|
||||
const creds = await login(user.username, password);
|
||||
const department = await createDepartment(undefined, undefined, false);
|
||||
await createUnit(monitor._id, user.username, [department._id]);
|
||||
|
||||
// create canned response
|
||||
const shortcut = faker.string.uuid();
|
||||
await request
|
||||
.post(api('canned-responses'))
|
||||
.set(credentials)
|
||||
.send({ shortcut, scope: 'department', tags: ['tag'], text: 'text', departmentId: department._id })
|
||||
.expect(200);
|
||||
|
||||
const { body } = await request.get(api('canned-responses.get')).set(creds).expect(200);
|
||||
|
||||
expect(body).to.have.property('success', true);
|
||||
expect(body.responses).to.be.an('array').with.lengthOf.greaterThan(0);
|
||||
expect(body.responses.find((response: IOmnichannelCannedResponse) => response.shortcut === shortcut)).to.be.an('object');
|
||||
});
|
||||
it('should return canned respones for scope user when agent is not on any department', async () => {
|
||||
const user = await createUser();
|
||||
await createAgent(user.username);
|
||||
const creds = await login(user.username, password);
|
||||
|
||||
// create canned response
|
||||
const shortcut = faker.string.uuid();
|
||||
await request
|
||||
.post(api('canned-responses'))
|
||||
.set(creds)
|
||||
.send({ shortcut, scope: 'user', tags: ['tag'], text: 'text' })
|
||||
.expect(200);
|
||||
|
||||
const { body } = await request.get(api('canned-responses.get')).set(creds).expect(200);
|
||||
|
||||
expect(body).to.have.property('success', true);
|
||||
expect(body.responses).to.be.an('array').with.lengthOf.greaterThan(0);
|
||||
expect(body.responses.find((response: IOmnichannelCannedResponse) => response.shortcut === shortcut)).to.be.an('object');
|
||||
});
|
||||
it('should return canned responses on the global scope', async () => {
|
||||
const user = await createUser();
|
||||
const creds = await login(user.username, password);
|
||||
await createAgent(user.username);
|
||||
|
||||
// create canned response
|
||||
const shortcut = faker.string.uuid();
|
||||
await request.post(api('canned-responses')).set(credentials).send({ shortcut, scope: 'global', text: 'text' }).expect(200);
|
||||
|
||||
const { body } = await request.get(api('canned-responses.get')).set(creds).expect(200);
|
||||
|
||||
expect(body).to.have.property('success', true);
|
||||
expect(body.responses).to.be.an('array').with.lengthOf.greaterThan(0);
|
||||
expect(body.responses.find((response: IOmnichannelCannedResponse) => response.shortcut === shortcut)).to.be.an('object');
|
||||
});
|
||||
it('should return canned responses from the departments user is in', async () => {
|
||||
const user = await createUser();
|
||||
const creds = await login(user.username, password);
|
||||
await createAgent(user.username);
|
||||
const department = await createDepartment([{ agentId: user._id }], undefined, true);
|
||||
|
||||
// create canned response
|
||||
const shortcut = faker.string.uuid();
|
||||
await request
|
||||
.post(api('canned-responses'))
|
||||
.set(credentials)
|
||||
.send({ shortcut, scope: 'department', text: 'text', departmentId: department._id })
|
||||
.expect(200);
|
||||
|
||||
const { body } = await request.get(api('canned-responses.get')).set(creds).expect(200);
|
||||
|
||||
expect(body).to.have.property('success', true);
|
||||
expect(body.responses).to.be.an('array').with.lengthOf.greaterThan(0);
|
||||
expect(body.responses.find((response: IOmnichannelCannedResponse) => response.shortcut === shortcut)).to.be.an('object');
|
||||
});
|
||||
});
|
||||
|
||||
describe('[GET] canned-responses', () => {
|
||||
it('should fail if user dont have view-canned-responses permission', async () => {
|
||||
await updatePermission('view-canned-responses', []);
|
||||
return request.get(api('canned-responses')).set(credentials).expect(403);
|
||||
});
|
||||
it('should return an array of canned responses when available', async () => {
|
||||
await updatePermission('view-canned-responses', ['livechat-agent', 'livechat-monitor', 'livechat-manager', 'admin']);
|
||||
await createCannedResponse();
|
||||
const { body } = await request.get(api('canned-responses')).query({ sort: '{ "_createdAt": -1 }' }).set(credentials).expect(200);
|
||||
expect(body).to.have.property('success', true);
|
||||
expect(body.cannedResponses).to.be.an('array').with.lengthOf.greaterThan(0);
|
||||
expect(body.cannedResponses[0]).to.have.property('_id');
|
||||
expect(body.cannedResponses[0]).to.have.property('shortcut');
|
||||
expect(body.cannedResponses[0]).to.have.property('scope');
|
||||
expect(body.cannedResponses[0]).to.have.property('tags');
|
||||
expect(body.cannedResponses[0]).to.have.property('text');
|
||||
expect(body.cannedResponses[0]).to.have.property('userId');
|
||||
});
|
||||
it('should return a canned response matching the params provided (shortcut)', async () => {
|
||||
const response = await createCannedResponse();
|
||||
const { body } = await request.get(api('canned-responses')).set(credentials).query({ shortcut: response.shortcut }).expect(200);
|
||||
expect(body).to.have.property('success', true);
|
||||
expect(body.cannedResponses).to.be.an('array').with.lengthOf(1);
|
||||
expect(body.cannedResponses[0]).to.have.property('_id');
|
||||
expect(body.cannedResponses[0]).to.have.property('shortcut', response.shortcut);
|
||||
});
|
||||
it('should return a canned response matching the params provided (scope)', async () => {
|
||||
const response = await createCannedResponse();
|
||||
const { body } = await request.get(api('canned-responses')).set(credentials).query({ scope: response.scope }).expect(200);
|
||||
expect(body).to.have.property('success', true);
|
||||
expect(body.cannedResponses).to.be.an('array').with.lengthOf.greaterThan(0);
|
||||
expect(body.cannedResponses[0]).to.have.property('_id');
|
||||
expect(body.cannedResponses[0]).to.have.property('scope', response.scope);
|
||||
});
|
||||
it('should return a canned response matching the params provided (tags)', async () => {
|
||||
const response = await createCannedResponse();
|
||||
const { body } = await request.get(api('canned-responses')).set(credentials).query({ 'tags[]': response.tags?.[0] }).expect(200);
|
||||
expect(body).to.have.property('success', true);
|
||||
expect(body.cannedResponses).to.be.an('array').with.lengthOf(1);
|
||||
expect(body.cannedResponses[0]).to.have.property('_id');
|
||||
expect(body.cannedResponses[0]).to.have.property('tags').that.is.an('array').which.includes(response.tags?.[0]);
|
||||
});
|
||||
it('should return a canned response matching the params provided (text)', async () => {
|
||||
const response = await createCannedResponse();
|
||||
const { body } = await request.get(api('canned-responses')).set(credentials).query({ text: response.text }).expect(200);
|
||||
expect(body).to.have.property('success', true);
|
||||
expect(body.cannedResponses).to.be.an('array').with.lengthOf(1);
|
||||
expect(body.cannedResponses[0]).to.have.property('_id');
|
||||
expect(body.cannedResponses[0]).to.have.property('text', response.text);
|
||||
});
|
||||
});
|
||||
|
||||
describe('[POST] canned-responses', () => {
|
||||
const dupshortcut = `shortcut-${faker.string.nanoid(6)}`;
|
||||
it('should fail if user dont have save-canned-responses permission', async () => {
|
||||
await updatePermission('save-canned-responses', []);
|
||||
return request
|
||||
.post(api('canned-responses'))
|
||||
.set(credentials)
|
||||
.send({ shortcut: 'shortcut', scope: 'user', tags: ['tag'], text: 'text' })
|
||||
.expect(403);
|
||||
});
|
||||
it('should fail if shortcut is not on the request', async () => {
|
||||
await updatePermission('save-canned-responses', ['livechat-agent', 'livechat-monitor', 'livechat-manager', 'admin']);
|
||||
return request.post(api('canned-responses')).set(credentials).expect(400);
|
||||
});
|
||||
it('should fail if text is not on the request', async () => {
|
||||
return request.post(api('canned-responses')).set(credentials).send({ shortcut: 'shortcut' }).expect(400);
|
||||
});
|
||||
it('should fail if scope is not on the request', async () => {
|
||||
return request.post(api('canned-responses')).set(credentials).send({ shortcut: 'shortcut', text: 'text' }).expect(400);
|
||||
});
|
||||
it('should fail if tags is not an array of strings', async () => {
|
||||
return request
|
||||
.post(api('canned-responses'))
|
||||
.set(credentials)
|
||||
.send({ shortcut: 'shortcut', text: 'text', scope: 'department', tags: 'tag' })
|
||||
.expect(400);
|
||||
});
|
||||
it('should create a new canned response', async () => {
|
||||
const { body } = await request
|
||||
.post(api('canned-responses'))
|
||||
.set(credentials)
|
||||
.send({ shortcut: dupshortcut, scope: 'user', tags: ['tag'], text: 'text' })
|
||||
.expect(200);
|
||||
expect(body).to.have.property('success', true);
|
||||
});
|
||||
it('should fail if shortcut is already in use', async () => {
|
||||
return request
|
||||
.post(api('canned-responses'))
|
||||
.set(credentials)
|
||||
.send({ shortcut: dupshortcut, scope: 'user', tags: ['tag'], text: 'text' })
|
||||
.expect(400);
|
||||
});
|
||||
it('should save a canned response related to an EE tag', async () => {
|
||||
const tag = await saveTags();
|
||||
|
||||
const { body } = await request
|
||||
.post(api('canned-responses'))
|
||||
.set(credentials)
|
||||
.send({ shortcut: `eetag-${faker.string.nanoid(6)}`, scope: 'user', tags: [tag.name], text: 'text' })
|
||||
.expect(200);
|
||||
|
||||
expect(body).to.have.property('success', true);
|
||||
|
||||
const { body: getResult } = await request.get(api('canned-responses')).set(credentials).query({ 'tags[]': tag.name }).expect(200);
|
||||
|
||||
expect(getResult).to.have.property('success', true);
|
||||
expect(getResult.cannedResponses).to.be.an('array').with.lengthOf(1);
|
||||
expect(getResult.cannedResponses[0]).to.have.property('_id');
|
||||
expect(getResult.cannedResponses[0]).to.have.property('tags').that.is.an('array').which.includes(tag.name);
|
||||
});
|
||||
it('should not include removed tags in the response', async () => {
|
||||
const tag = await saveTags();
|
||||
|
||||
const { body } = await request
|
||||
.post(api('canned-responses'))
|
||||
.set(credentials)
|
||||
.send({ shortcut: `remove-${faker.string.nanoid(6)}`, scope: 'user', tags: [tag.name], text: 'text' })
|
||||
.expect(200);
|
||||
|
||||
expect(body).to.have.property('success', true);
|
||||
|
||||
await removeTag(tag._id);
|
||||
|
||||
const { body: getResult } = await request.get(api('canned-responses')).set(credentials).query({ 'tags[]': tag.name }).expect(200);
|
||||
|
||||
expect(getResult).to.have.property('success', true);
|
||||
expect(getResult.cannedResponses).to.be.an('array').with.lengthOf(0);
|
||||
});
|
||||
|
||||
it('should persist userId when updating a canned response', async () => {
|
||||
const response = await createCannedResponse();
|
||||
|
||||
const {
|
||||
body: { cannedResponses },
|
||||
} = await request.get(api('canned-responses')).set(credentials).query({}).expect(200);
|
||||
|
||||
const targetCannedResponse = cannedResponses.find(
|
||||
(cannedResponse: IOmnichannelCannedResponse) => cannedResponse.shortcut === response.shortcut,
|
||||
);
|
||||
|
||||
const { body } = await request
|
||||
.post(api('canned-responses'))
|
||||
.set(credentials)
|
||||
.send({ _id: targetCannedResponse._id, shortcut: `${targetCannedResponse.shortcut}-edited`, text: 'edited text', scope: 'user' })
|
||||
.expect(200);
|
||||
expect(body).to.have.property('success', true);
|
||||
|
||||
const { body: getResult } = await request
|
||||
.get(api('canned-responses'))
|
||||
.set(credentials)
|
||||
.query({ shortcut: `${targetCannedResponse.shortcut}-edited` })
|
||||
.expect(200);
|
||||
expect(getResult).to.have.property('success', true);
|
||||
expect(getResult.cannedResponses[0]).to.have.property('userId', adminUsername);
|
||||
});
|
||||
});
|
||||
|
||||
describe('[DELETE] canned-responses', () => {
|
||||
it('should fail if user dont have remove-canned-responses permission', async () => {
|
||||
await updatePermission('remove-canned-responses', []);
|
||||
return request.delete(api('canned-responses/sfdads')).set(credentials).expect(403);
|
||||
});
|
||||
it('should fail if _id is not on the request', async () => {
|
||||
await updatePermission('remove-canned-responses', ['livechat-agent', 'livechat-monitor', 'livechat-manager', 'admin']);
|
||||
return request.delete(api('canned-responses')).set(credentials).expect(400);
|
||||
});
|
||||
it('should delete a canned response', async () => {
|
||||
const response = await createCannedResponse();
|
||||
const { body: cr } = await request.get(api('canned-responses')).set(credentials).query({ shortcut: response.shortcut }).expect(200);
|
||||
const { body } = await request
|
||||
.delete(api(`canned-responses/${cr.cannedResponses[0]._id}`))
|
||||
.set(credentials)
|
||||
.expect(200);
|
||||
expect(body).to.have.property('success', true);
|
||||
});
|
||||
});
|
||||
});
|
||||
821
apps/meteor/tests/end-to-end/api/livechat/17-dashboards-ee.ts
Normal file
821
apps/meteor/tests/end-to-end/api/livechat/17-dashboards-ee.ts
Normal file
@@ -0,0 +1,821 @@
|
||||
import type { ILivechatDepartment, ILivechatVisitor } from '@rocket.chat/core-typings';
|
||||
import { expect } from 'chai';
|
||||
import { before, describe, it, after } from 'mocha';
|
||||
|
||||
import { getCredentials, api, request, credentials } from '../../../data/api-data';
|
||||
import { createDepartment, deleteDepartment } from '../../../data/livechat/department';
|
||||
import {
|
||||
createVisitor,
|
||||
createLivechatRoom,
|
||||
takeInquiry,
|
||||
closeOmnichannelRoom,
|
||||
makeAgentUnavailable,
|
||||
makeAgentAvailable,
|
||||
createAgent,
|
||||
sendAgentMessage,
|
||||
sendMessage,
|
||||
fetchInquiry,
|
||||
deleteVisitor,
|
||||
} from '../../../data/livechat/rooms';
|
||||
import { updateEESetting, updatePermission, updateSetting } from '../../../data/permissions.helper';
|
||||
import { pagination } from '../../../data/utils';
|
||||
import { IS_EE } from '../../../e2e/config/constants';
|
||||
|
||||
(IS_EE ? describe : describe.skip)('[EE] LIVECHAT - dashboards', () => {
|
||||
before((done) => getCredentials(done));
|
||||
|
||||
before(async () => {
|
||||
await updateSetting('Livechat_enabled', true);
|
||||
await updateEESetting('Livechat_Require_Contact_Verification', 'never');
|
||||
await updateSetting('Omnichannel_enable_department_removal', true);
|
||||
await createAgent();
|
||||
await makeAgentAvailable();
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await updateSetting('Omnichannel_enable_department_removal', false);
|
||||
});
|
||||
|
||||
describe('livechat/analytics/agents/average-service-time', () => {
|
||||
it('should return an "unauthorized error" when the user does not have the necessary permission', async () => {
|
||||
await updatePermission('view-livechat-manager', []);
|
||||
await request
|
||||
.get(api('livechat/analytics/agents/average-service-time'))
|
||||
.set(credentials)
|
||||
.query({ start: '2020-01-01', end: '2020-01-02' })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(403);
|
||||
});
|
||||
it('should fail if start is not present as query param', async () => {
|
||||
await updatePermission('view-livechat-manager', ['admin']);
|
||||
const response = await request
|
||||
.get(api('livechat/analytics/agents/average-service-time'))
|
||||
.set(credentials)
|
||||
.query({ end: '2020-01-02' })
|
||||
.expect(400);
|
||||
expect(response.body).to.have.property('success', false);
|
||||
});
|
||||
it('should fail if end is not present as query param', async () => {
|
||||
const response = await request
|
||||
.get(api('livechat/analytics/agents/average-service-time'))
|
||||
.set(credentials)
|
||||
.query({ start: '2020-01-01' })
|
||||
.expect(400);
|
||||
expect(response.body).to.have.property('success', false);
|
||||
});
|
||||
it('should fail if start is not a valid date', async () => {
|
||||
const date = new Date(new Date().setDate(new Date().getDate() - 2)).toISOString();
|
||||
const response = await request
|
||||
.get(api('livechat/analytics/agents/average-service-time'))
|
||||
.set(credentials)
|
||||
.query({ start: '2020-01-01x', end: date })
|
||||
.expect(400);
|
||||
expect(response.body).to.have.property('success', false);
|
||||
});
|
||||
it('should fail if end is not a valid date', async () => {
|
||||
const date = new Date(new Date().setDate(new Date().getDate() - 2)).toISOString();
|
||||
const response = await request
|
||||
.get(api('livechat/analytics/agents/average-service-time'))
|
||||
.set(credentials)
|
||||
.query({ start: date, end: '2020-x01-02' })
|
||||
.expect(400);
|
||||
expect(response.body).to.have.property('success', false);
|
||||
});
|
||||
it('should return an array of agents', async () => {
|
||||
const date = new Date(new Date().setDate(new Date().getDate() - 2)).toISOString();
|
||||
const visitor = await createVisitor();
|
||||
const room = await createLivechatRoom(visitor.token);
|
||||
const inq = await fetchInquiry(room._id);
|
||||
await takeInquiry(inq._id);
|
||||
await closeOmnichannelRoom(room._id);
|
||||
|
||||
const { body } = await request
|
||||
.get(api('livechat/analytics/agents/average-service-time'))
|
||||
.set(credentials)
|
||||
.query({ start: date, end: new Date().toISOString() })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
expect(body).to.have.property('success', true);
|
||||
expect(body.agents).to.be.an('array').with.lengthOf.greaterThan(0);
|
||||
expect(body.agents[0]).to.have.a.property('_id');
|
||||
expect(body.agents[0]).to.have.a.property('username');
|
||||
expect(body.agents[0]).to.have.a.property('averageServiceTimeInSeconds').that.is.a('number');
|
||||
});
|
||||
it('should accept pagination', async () => {
|
||||
await pagination('livechat/analytics/agents/average-service-time', credentials, {
|
||||
start: new Date().toISOString(),
|
||||
end: new Date().toISOString(),
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('livechat/analytics/agents/total-service-time', () => {
|
||||
it('should return an "unauthorized error" when the user does not have the necessary permission', async () => {
|
||||
await updatePermission('view-livechat-manager', []);
|
||||
await request
|
||||
.get(api('livechat/analytics/agents/total-service-time'))
|
||||
.set(credentials)
|
||||
.query({ start: '2020-01-01', end: '2020-01-02' })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(403);
|
||||
});
|
||||
it('should fail if start is not present as query param', async () => {
|
||||
await updatePermission('view-livechat-manager', ['admin']);
|
||||
const response = await request
|
||||
.get(api('livechat/analytics/agents/total-service-time'))
|
||||
.set(credentials)
|
||||
.query({ end: '2020-01-02' })
|
||||
.expect(400);
|
||||
expect(response.body).to.have.property('success', false);
|
||||
});
|
||||
it('should fail if end is not present as query param', async () => {
|
||||
const response = await request
|
||||
.get(api('livechat/analytics/agents/total-service-time'))
|
||||
.set(credentials)
|
||||
.query({ start: '2020-01-01' })
|
||||
.expect(400);
|
||||
expect(response.body).to.have.property('success', false);
|
||||
});
|
||||
it('should fail if start is not a valid date', async () => {
|
||||
const date = new Date(new Date().setDate(new Date().getDate() - 2)).toISOString();
|
||||
const response = await request
|
||||
.get(api('livechat/analytics/agents/total-service-time'))
|
||||
.set(credentials)
|
||||
.query({ start: '2020-01-01x', end: date })
|
||||
.expect(400);
|
||||
expect(response.body).to.have.property('success', false);
|
||||
});
|
||||
it('should fail if end is not a valid date', async () => {
|
||||
const date = new Date(new Date().setDate(new Date().getDate() - 2)).toISOString();
|
||||
const response = await request
|
||||
.get(api('livechat/analytics/agents/total-service-time'))
|
||||
.set(credentials)
|
||||
.query({ start: date, end: '2020-x01-02' })
|
||||
.expect(400);
|
||||
expect(response.body).to.have.property('success', false);
|
||||
});
|
||||
it('should return an array of agents', async () => {
|
||||
const date = new Date(new Date().setDate(new Date().getDate() - 2)).toISOString();
|
||||
const { body } = await request
|
||||
.get(api('livechat/analytics/agents/total-service-time'))
|
||||
.set(credentials)
|
||||
.query({ start: date, end: new Date().toISOString() })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
expect(body).to.have.property('success', true);
|
||||
expect(body.agents).to.be.an('array').with.lengthOf.greaterThan(0);
|
||||
expect(body.agents[0]).to.have.a.property('_id');
|
||||
expect(body.agents[0]).to.have.a.property('username');
|
||||
expect(body.agents[0]).to.have.a.property('serviceTimeDuration').that.is.a('number');
|
||||
});
|
||||
it('should accept pagination', async () => {
|
||||
await pagination('livechat/analytics/agents/total-service-time', credentials, {
|
||||
start: new Date().toISOString(),
|
||||
end: new Date().toISOString(),
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('livechat/analytics/agents/available-for-service-history', () => {
|
||||
it('should return an "unauthorized error" when the user does not have the necessary permission', async () => {
|
||||
await updatePermission('view-livechat-manager', []);
|
||||
await request
|
||||
.get(api('livechat/analytics/agents/available-for-service-history'))
|
||||
.set(credentials)
|
||||
.query({ start: '2020-01-01', end: '2020-01-02' })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(403);
|
||||
});
|
||||
it('should fail if start is not present as query param', async () => {
|
||||
await updatePermission('view-livechat-manager', ['admin']);
|
||||
const response = await request
|
||||
.get(api('livechat/analytics/agents/available-for-service-history'))
|
||||
.set(credentials)
|
||||
.query({ end: '2020-01-02' })
|
||||
.expect(400);
|
||||
expect(response.body).to.have.property('success', false);
|
||||
});
|
||||
it('should fail if end is not present as query param', async () => {
|
||||
const response = await request
|
||||
.get(api('livechat/analytics/agents/available-for-service-history'))
|
||||
.set(credentials)
|
||||
.query({ start: '2020-01-01' })
|
||||
.expect(400);
|
||||
expect(response.body).to.have.property('success', false);
|
||||
});
|
||||
it('should fail if start is not a valid date', async () => {
|
||||
const date = new Date(new Date().setDate(new Date().getDate() - 2)).toISOString();
|
||||
const response = await request
|
||||
.get(api('livechat/analytics/agents/available-for-service-history'))
|
||||
.set(credentials)
|
||||
.query({ start: '2020-01-01x', end: date })
|
||||
.expect(400);
|
||||
expect(response.body).to.have.property('success', false);
|
||||
});
|
||||
it('should fail if end is not a valid date', async () => {
|
||||
const date = new Date(new Date().setDate(new Date().getDate() - 2)).toISOString();
|
||||
const response = await request
|
||||
.get(api('livechat/analytics/agents/available-for-service-history'))
|
||||
.set(credentials)
|
||||
.query({ start: date, end: '2020-x01-02' })
|
||||
.expect(400);
|
||||
expect(response.body).to.have.property('success', false);
|
||||
});
|
||||
it('should return an array of agents', async () => {
|
||||
// Toggling to populate agent activity collection
|
||||
await createAgent();
|
||||
await makeAgentUnavailable();
|
||||
await makeAgentAvailable();
|
||||
|
||||
const date = new Date(new Date().setDate(new Date().getDate() - 2)).toISOString();
|
||||
const { body } = await request
|
||||
.get(api('livechat/analytics/agents/available-for-service-history'))
|
||||
.set(credentials)
|
||||
.query({ start: date, end: new Date().toISOString() })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
expect(body).to.have.property('success', true);
|
||||
expect(body.agents).to.be.an('array').with.lengthOf.greaterThan(0);
|
||||
expect(body.agents[0]).to.have.a.property('username');
|
||||
expect(body.agents[0]).to.have.a.property('availableTimeInSeconds').that.is.a('number');
|
||||
});
|
||||
it('should accept pagination', async () => {
|
||||
await pagination('livechat/analytics/agents/available-for-service-history', credentials, {
|
||||
start: new Date().toISOString(),
|
||||
end: new Date().toISOString(),
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('livechat/analytics/departments/amount-of-chats', () => {
|
||||
it('should return an "unauthorized error" when the user does not have the necessary permission', async () => {
|
||||
await updatePermission('view-livechat-manager', []);
|
||||
await request
|
||||
.get(api('livechat/analytics/departments/amount-of-chats'))
|
||||
.set(credentials)
|
||||
.query({ start: '2020-01-01', end: '2020-01-02' })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(403);
|
||||
});
|
||||
it('should fail if start is not present as query param', async () => {
|
||||
await updatePermission('view-livechat-manager', ['admin']);
|
||||
const response = await request
|
||||
.get(api('livechat/analytics/departments/amount-of-chats'))
|
||||
.set(credentials)
|
||||
.query({ end: '2020-01-02' })
|
||||
.expect(400);
|
||||
expect(response.body).to.have.property('success', false);
|
||||
});
|
||||
it('should fail if end is not present as query param', async () => {
|
||||
const response = await request
|
||||
.get(api('livechat/analytics/departments/amount-of-chats'))
|
||||
.set(credentials)
|
||||
.query({ start: '2020-01-01' })
|
||||
.expect(400);
|
||||
expect(response.body).to.have.property('success', false);
|
||||
});
|
||||
it('should fail if start is not a valid date', async () => {
|
||||
const date = new Date(new Date().setDate(new Date().getDate() - 2)).toISOString();
|
||||
const response = await request
|
||||
.get(api('livechat/analytics/departments/amount-of-chats'))
|
||||
.set(credentials)
|
||||
.query({ start: '2020-01-01x', end: date })
|
||||
.expect(400);
|
||||
expect(response.body).to.have.property('success', false);
|
||||
});
|
||||
it('should fail if end is not a valid date', async () => {
|
||||
const date = new Date(new Date().setDate(new Date().getDate() - 2)).toISOString();
|
||||
const response = await request
|
||||
.get(api('livechat/analytics/departments/amount-of-chats'))
|
||||
.set(credentials)
|
||||
.query({ start: date, end: '2020-x01-02' })
|
||||
.expect(400);
|
||||
expect(response.body).to.have.property('success', false);
|
||||
});
|
||||
it('should return an array of departments', async () => {
|
||||
const date = new Date(new Date().setDate(new Date().getDate() - 2)).toISOString();
|
||||
const { body } = await request
|
||||
.get(api('livechat/analytics/departments/amount-of-chats'))
|
||||
.set(credentials)
|
||||
.query({ start: date, end: new Date().toISOString() })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
expect(body).to.have.property('success', true);
|
||||
expect(body.departments).to.be.an('array').with.lengthOf.greaterThan(0);
|
||||
expect(body.departments[0]).to.have.a.property('rooms').that.is.a('number');
|
||||
expect(body.departments[0]).to.have.a.property('_id');
|
||||
});
|
||||
it('should accept pagination', async () => {
|
||||
await pagination('livechat/analytics/departments/amount-of-chats', credentials, {
|
||||
start: new Date().toISOString(),
|
||||
end: new Date().toISOString(),
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('livechat/analytics/departments/average-service-time', () => {
|
||||
it('should return an "unauthorized error" when the user does not have the necessary permission', async () => {
|
||||
await updatePermission('view-livechat-manager', []);
|
||||
await request
|
||||
.get(api('livechat/analytics/departments/average-service-time'))
|
||||
.set(credentials)
|
||||
.query({ start: '2020-01-01', end: '2020-01-02' })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(403);
|
||||
});
|
||||
it('should fail if start is not present as query param', async () => {
|
||||
await updatePermission('view-livechat-manager', ['admin']);
|
||||
const response = await request
|
||||
.get(api('livechat/analytics/departments/average-service-time'))
|
||||
.set(credentials)
|
||||
.query({ end: '2020-01-02' })
|
||||
.expect(400);
|
||||
expect(response.body).to.have.property('success', false);
|
||||
});
|
||||
it('should fail if end is not present as query param', async () => {
|
||||
const response = await request
|
||||
.get(api('livechat/analytics/departments/average-service-time'))
|
||||
.set(credentials)
|
||||
.query({ start: '2020-01-01' })
|
||||
.expect(400);
|
||||
expect(response.body).to.have.property('success', false);
|
||||
});
|
||||
it('should fail if start is not a valid date', async () => {
|
||||
const date = new Date(new Date().setDate(new Date().getDate() - 2)).toISOString();
|
||||
const response = await request
|
||||
.get(api('livechat/analytics/departments/average-service-time'))
|
||||
.set(credentials)
|
||||
.query({ start: '2020-01-01x', end: date })
|
||||
.expect(400);
|
||||
expect(response.body).to.have.property('success', false);
|
||||
});
|
||||
it('should fail if end is not a valid date', async () => {
|
||||
const date = new Date(new Date().setDate(new Date().getDate() - 2)).toISOString();
|
||||
const response = await request
|
||||
.get(api('livechat/analytics/departments/average-service-time'))
|
||||
.set(credentials)
|
||||
.query({ start: date, end: '2020-x01-02' })
|
||||
.expect(400);
|
||||
expect(response.body).to.have.property('success', false);
|
||||
});
|
||||
describe('with data', () => {
|
||||
let department: ILivechatDepartment;
|
||||
let visitor: ILivechatVisitor;
|
||||
|
||||
before(async () => {
|
||||
department = await createDepartment({}, [{ agentId: credentials['X-User-Id'] }]);
|
||||
|
||||
visitor = await createVisitor(department._id);
|
||||
const room = await createLivechatRoom(visitor.token);
|
||||
const inq = await fetchInquiry(room._id);
|
||||
await takeInquiry(inq._id);
|
||||
await sendAgentMessage(room._id);
|
||||
await closeOmnichannelRoom(room._id);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await deleteVisitor(visitor.token);
|
||||
await deleteDepartment(department._id);
|
||||
});
|
||||
|
||||
it('should return an array of departments', async () => {
|
||||
const date = new Date(new Date().setDate(new Date().getDate() - 2)).toISOString();
|
||||
|
||||
const { body } = await request
|
||||
.get(api('livechat/analytics/departments/average-service-time'))
|
||||
.set(credentials)
|
||||
.query({ start: date, end: new Date().toISOString() })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
expect(body).to.have.property('success', true);
|
||||
expect(body.departments).to.be.an('array').with.lengthOf.greaterThan(0);
|
||||
expect(body.departments[0]).to.have.a.property('averageServiceTimeInSeconds').that.is.a('number');
|
||||
expect(body.departments[0]).to.have.a.property('_id');
|
||||
});
|
||||
});
|
||||
|
||||
it('should accept pagination', async () => {
|
||||
await pagination('livechat/analytics/departments/average-service-time', credentials, {
|
||||
start: new Date().toISOString(),
|
||||
end: new Date().toISOString(),
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('livechat/analytics/departments/average-chat-duration-time', () => {
|
||||
it('should return an "unauthorized error" when the user does not have the necessary permission', async () => {
|
||||
await updatePermission('view-livechat-manager', []);
|
||||
await request
|
||||
.get(api('livechat/analytics/departments/average-chat-duration-time'))
|
||||
.set(credentials)
|
||||
.query({ start: '2020-01-01', end: '2020-01-02' })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(403);
|
||||
});
|
||||
it('should fail if start is not present as query param', async () => {
|
||||
await updatePermission('view-livechat-manager', ['admin']);
|
||||
const response = await request
|
||||
.get(api('livechat/analytics/departments/average-chat-duration-time'))
|
||||
.set(credentials)
|
||||
.query({ end: '2020-01-02' })
|
||||
.expect(400);
|
||||
expect(response.body).to.have.property('success', false);
|
||||
});
|
||||
it('should fail if end is not present as query param', async () => {
|
||||
const response = await request
|
||||
.get(api('livechat/analytics/departments/average-chat-duration-time'))
|
||||
.set(credentials)
|
||||
.query({ start: '2020-01-01' })
|
||||
.expect(400);
|
||||
expect(response.body).to.have.property('success', false);
|
||||
});
|
||||
it('should fail if start is not a valid date', async () => {
|
||||
const date = new Date(new Date().setDate(new Date().getDate() - 2)).toISOString();
|
||||
const response = await request
|
||||
.get(api('livechat/analytics/departments/average-chat-duration-time'))
|
||||
.set(credentials)
|
||||
.query({ start: '2020-01-01x', end: date })
|
||||
.expect(400);
|
||||
expect(response.body).to.have.property('success', false);
|
||||
});
|
||||
it('should fail if end is not a valid date', async () => {
|
||||
const date = new Date(new Date().setDate(new Date().getDate() - 2)).toISOString();
|
||||
const response = await request
|
||||
.get(api('livechat/analytics/departments/average-chat-duration-time'))
|
||||
.set(credentials)
|
||||
.query({ start: date, end: '2020-x01-02' })
|
||||
.expect(400);
|
||||
expect(response.body).to.have.property('success', false);
|
||||
});
|
||||
it('should return an array of departments', async () => {
|
||||
const date = new Date(new Date().setDate(new Date().getDate() - 2)).toISOString();
|
||||
const { body } = await request
|
||||
.get(api('livechat/analytics/departments/average-chat-duration-time'))
|
||||
.set(credentials)
|
||||
.query({ start: date, end: new Date().toISOString() })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
expect(body).to.have.property('success', true);
|
||||
expect(body.departments).to.be.an('array').with.lengthOf.greaterThan(0);
|
||||
expect(body.departments[0]).to.have.a.property('averageChatDurationTimeInSeconds').that.is.a('number');
|
||||
expect(body.departments[0]).to.have.a.property('_id');
|
||||
});
|
||||
it('should accept pagination', async () => {
|
||||
await pagination('livechat/analytics/departments/average-chat-duration-time', credentials, {
|
||||
start: new Date().toISOString(),
|
||||
end: new Date().toISOString(),
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('livechat/analytics/departments/total-service-time', () => {
|
||||
it('should return an "unauthorized error" when the user does not have the necessary permission', async () => {
|
||||
await updatePermission('view-livechat-manager', []);
|
||||
await request
|
||||
.get(api('livechat/analytics/departments/total-service-time'))
|
||||
.set(credentials)
|
||||
.query({ start: '2020-01-01', end: '2020-01-02' })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(403);
|
||||
});
|
||||
it('should fail if start is not present as query param', async () => {
|
||||
await updatePermission('view-livechat-manager', ['admin']);
|
||||
const response = await request
|
||||
.get(api('livechat/analytics/departments/total-service-time'))
|
||||
.set(credentials)
|
||||
.query({ end: '2020-01-02' })
|
||||
.expect(400);
|
||||
expect(response.body).to.have.property('success', false);
|
||||
});
|
||||
it('should fail if end is not present as query param', async () => {
|
||||
const response = await request
|
||||
.get(api('livechat/analytics/departments/total-service-time'))
|
||||
.set(credentials)
|
||||
.query({ start: '2020-01-01' })
|
||||
.expect(400);
|
||||
expect(response.body).to.have.property('success', false);
|
||||
});
|
||||
it('should fail if start is not a valid date', async () => {
|
||||
const date = new Date(new Date().setDate(new Date().getDate() - 2)).toISOString();
|
||||
const response = await request
|
||||
.get(api('livechat/analytics/departments/total-service-time'))
|
||||
.set(credentials)
|
||||
.query({ start: '2020-01-01x', end: date })
|
||||
.expect(400);
|
||||
expect(response.body).to.have.property('success', false);
|
||||
});
|
||||
it('should fail if end is not a valid date', async () => {
|
||||
const date = new Date(new Date().setDate(new Date().getDate() - 2)).toISOString();
|
||||
const response = await request
|
||||
.get(api('livechat/analytics/departments/total-service-time'))
|
||||
.set(credentials)
|
||||
.query({ start: date, end: '2020-x01-02' })
|
||||
.expect(400);
|
||||
expect(response.body).to.have.property('success', false);
|
||||
});
|
||||
it('should return an array of departments', async () => {
|
||||
const date = new Date(new Date().setDate(new Date().getDate() - 2)).toISOString();
|
||||
const { body } = await request
|
||||
.get(api('livechat/analytics/departments/total-service-time'))
|
||||
.set(credentials)
|
||||
.query({ start: date, end: new Date().toISOString() })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
expect(body).to.have.property('success', true);
|
||||
expect(body.departments).to.be.an('array').with.lengthOf.greaterThan(0);
|
||||
expect(body.departments[0]).to.have.a.property('chats').that.is.a('number');
|
||||
expect(body.departments[0]).to.have.a.property('serviceTimeDuration').that.is.a('number');
|
||||
expect(body.departments[0]).to.have.a.property('_id');
|
||||
});
|
||||
it('should accept pagination', async () => {
|
||||
await pagination('livechat/analytics/departments/total-service-time', credentials, {
|
||||
start: new Date().toISOString(),
|
||||
end: new Date().toISOString(),
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('livechat/analytics/departments/average-waiting-time', () => {
|
||||
it('should return an "unauthorized error" when the user does not have the necessary permission', async () => {
|
||||
await updatePermission('view-livechat-manager', []);
|
||||
await request
|
||||
.get(api('livechat/analytics/departments/average-waiting-time'))
|
||||
.set(credentials)
|
||||
.query({ start: '2020-01-01', end: '2020-01-02' })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(403);
|
||||
});
|
||||
it('should fail if start is not present as query param', async () => {
|
||||
await updatePermission('view-livechat-manager', ['admin']);
|
||||
const response = await request
|
||||
.get(api('livechat/analytics/departments/average-waiting-time'))
|
||||
.set(credentials)
|
||||
.query({ end: '2020-01-02' })
|
||||
.expect(400);
|
||||
expect(response.body).to.have.property('success', false);
|
||||
});
|
||||
it('should fail if end is not present as query param', async () => {
|
||||
const response = await request
|
||||
.get(api('livechat/analytics/departments/average-waiting-time'))
|
||||
.set(credentials)
|
||||
.query({ start: '2020-01-01' })
|
||||
.expect(400);
|
||||
expect(response.body).to.have.property('success', false);
|
||||
});
|
||||
it('should fail if start is not a valid date', async () => {
|
||||
const date = new Date(new Date().setDate(new Date().getDate() - 2)).toISOString();
|
||||
const response = await request
|
||||
.get(api('livechat/analytics/departments/average-waiting-time'))
|
||||
.set(credentials)
|
||||
.query({ start: '2020-01-01x', end: date })
|
||||
.expect(400);
|
||||
expect(response.body).to.have.property('success', false);
|
||||
});
|
||||
it('should fail if end is not a valid date', async () => {
|
||||
const date = new Date(new Date().setDate(new Date().getDate() - 2)).toISOString();
|
||||
const response = await request
|
||||
.get(api('livechat/analytics/departments/average-waiting-time'))
|
||||
.set(credentials)
|
||||
.query({ start: date, end: '2020-x01-02' })
|
||||
.expect(400);
|
||||
expect(response.body).to.have.property('success', false);
|
||||
});
|
||||
describe('with data', () => {
|
||||
let department: ILivechatDepartment;
|
||||
let visitor: ILivechatVisitor;
|
||||
|
||||
before(async () => {
|
||||
department = await createDepartment({}, [{ agentId: credentials['X-User-Id'] }]);
|
||||
visitor = await createVisitor(department._id);
|
||||
const room = await createLivechatRoom(visitor.token);
|
||||
const inq = await fetchInquiry(room._id);
|
||||
await takeInquiry(inq._id);
|
||||
await sendAgentMessage(room._id);
|
||||
await closeOmnichannelRoom(room._id);
|
||||
});
|
||||
after(async () => {
|
||||
await deleteVisitor(visitor.token);
|
||||
await deleteDepartment(department._id);
|
||||
});
|
||||
|
||||
it('should return an array of departments', async () => {
|
||||
const date = new Date(new Date().setDate(new Date().getDate() - 2)).toISOString();
|
||||
const { body } = await request
|
||||
.get(api('livechat/analytics/departments/average-waiting-time'))
|
||||
.set(credentials)
|
||||
.query({ start: date, end: new Date().toISOString() })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
expect(body).to.have.property('success', true);
|
||||
expect(body.departments).to.be.an('array').with.lengthOf.greaterThan(0);
|
||||
expect(body.departments[0]).to.have.a.property('averageWaitingTimeInSeconds').that.is.a('number');
|
||||
expect(body.departments[0]).to.have.a.property('_id');
|
||||
});
|
||||
});
|
||||
it('should accept pagination', async () => {
|
||||
await pagination('livechat/analytics/departments/average-waiting-time', credentials, {
|
||||
start: new Date().toISOString(),
|
||||
end: new Date().toISOString(),
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('livechat/analytics/departments/total-transferred-chats', () => {
|
||||
it('should return an "unauthorized error" when the user does not have the necessary permission', async () => {
|
||||
await updatePermission('view-livechat-manager', []);
|
||||
await request
|
||||
.get(api('livechat/analytics/departments/total-transferred-chats'))
|
||||
.set(credentials)
|
||||
.query({ start: '2020-01-01', end: '2020-01-02' })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(403);
|
||||
});
|
||||
it('should fail if start is not present as query param', async () => {
|
||||
await updatePermission('view-livechat-manager', ['admin']);
|
||||
const response = await request
|
||||
.get(api('livechat/analytics/departments/total-transferred-chats'))
|
||||
.set(credentials)
|
||||
.query({ end: '2020-01-02' })
|
||||
.expect(400);
|
||||
expect(response.body).to.have.property('success', false);
|
||||
});
|
||||
it('should fail if end is not present as query param', async () => {
|
||||
const response = await request
|
||||
.get(api('livechat/analytics/departments/total-transferred-chats'))
|
||||
.set(credentials)
|
||||
.query({ start: '2020-01-01' })
|
||||
.expect(400);
|
||||
expect(response.body).to.have.property('success', false);
|
||||
});
|
||||
it('should fail if start is not a valid date', async () => {
|
||||
const date = new Date(new Date().setDate(new Date().getDate() - 2)).toISOString();
|
||||
const response = await request
|
||||
.get(api('livechat/analytics/departments/total-transferred-chats'))
|
||||
.set(credentials)
|
||||
.query({ start: '2020-01-01x', end: date })
|
||||
.expect(400);
|
||||
expect(response.body).to.have.property('success', false);
|
||||
});
|
||||
it('should fail if end is not a valid date', async () => {
|
||||
const date = new Date(new Date().setDate(new Date().getDate() - 2)).toISOString();
|
||||
const response = await request
|
||||
.get(api('livechat/analytics/departments/total-transferred-chats'))
|
||||
.set(credentials)
|
||||
.query({ start: date, end: '2020-x01-02' })
|
||||
.expect(400);
|
||||
expect(response.body).to.have.property('success', false);
|
||||
});
|
||||
it('should return an array of departments', async () => {
|
||||
const date = new Date(new Date().setDate(new Date().getDate() - 2)).toISOString();
|
||||
const { body } = await request
|
||||
.get(api('livechat/analytics/departments/total-transferred-chats'))
|
||||
.set(credentials)
|
||||
.query({ start: date, end: new Date().toISOString() })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
expect(body).to.have.property('success', true);
|
||||
expect(body.departments).to.be.an('array').with.lengthOf.greaterThan(0);
|
||||
expect(body.departments[0]).to.have.a.property('numberOfTransferredRooms').that.is.a('number');
|
||||
expect(body.departments[0]).to.have.a.property('_id');
|
||||
});
|
||||
it('should accept pagination', async () => {
|
||||
await pagination('livechat/analytics/departments/total-transferred-chats', credentials, {
|
||||
start: new Date().toISOString(),
|
||||
end: new Date().toISOString(),
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('livechat/analytics/departments/total-abandoned-chats', () => {
|
||||
it('should return an "unauthorized error" when the user does not have the necessary permission', async () => {
|
||||
await updatePermission('view-livechat-manager', []);
|
||||
await request
|
||||
.get(api('livechat/analytics/departments/total-abandoned-chats'))
|
||||
.set(credentials)
|
||||
.query({ start: '2020-01-01', end: '2020-01-02' })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(403);
|
||||
});
|
||||
it('should fail if start is not present as query param', async () => {
|
||||
await updatePermission('view-livechat-manager', ['admin']);
|
||||
const response = await request
|
||||
.get(api('livechat/analytics/departments/total-abandoned-chats'))
|
||||
.set(credentials)
|
||||
.query({ end: '2020-01-02' })
|
||||
.expect(400);
|
||||
expect(response.body).to.have.property('success', false);
|
||||
});
|
||||
it('should fail if end is not present as query param', async () => {
|
||||
const response = await request
|
||||
.get(api('livechat/analytics/departments/total-abandoned-chats'))
|
||||
.set(credentials)
|
||||
.query({ start: '2020-01-01' })
|
||||
.expect(400);
|
||||
expect(response.body).to.have.property('success', false);
|
||||
});
|
||||
it('should fail if start is not a valid date', async () => {
|
||||
const date = new Date(new Date().setDate(new Date().getDate() - 2)).toISOString();
|
||||
const response = await request
|
||||
.get(api('livechat/analytics/departments/total-abandoned-chats'))
|
||||
.set(credentials)
|
||||
.query({ start: '2020-01-01x', end: date })
|
||||
.expect(400);
|
||||
expect(response.body).to.have.property('success', false);
|
||||
});
|
||||
it('should fail if end is not a valid date', async () => {
|
||||
const date = new Date(new Date().setDate(new Date().getDate() - 2)).toISOString();
|
||||
const response = await request
|
||||
.get(api('livechat/analytics/departments/total-abandoned-chats'))
|
||||
.set(credentials)
|
||||
.query({ start: date, end: '2020-x01-02' })
|
||||
.expect(400);
|
||||
expect(response.body).to.have.property('success', false);
|
||||
});
|
||||
it('should return an array of departments', async () => {
|
||||
await updateSetting('Livechat_visitor_inactivity_timeout', 0);
|
||||
const date = new Date(new Date().setDate(new Date().getDate() - 2)).toISOString();
|
||||
const visitor = await createVisitor();
|
||||
const room = await createLivechatRoom(visitor.token);
|
||||
const inq = await fetchInquiry(room._id);
|
||||
await takeInquiry(inq._id);
|
||||
await sendMessage(room._id, 'first message', visitor.token);
|
||||
await sendAgentMessage(room._id);
|
||||
await closeOmnichannelRoom(room._id);
|
||||
|
||||
const { body } = await request
|
||||
.get(api('livechat/analytics/departments/total-abandoned-chats'))
|
||||
.set(credentials)
|
||||
.query({ start: date, end: new Date().toISOString() })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
expect(body).to.have.property('success', true);
|
||||
expect(body.departments).to.be.an('array').with.lengthOf.greaterThan(0);
|
||||
expect(body.departments[0]).to.have.a.property('abandonedRooms').that.is.a('number');
|
||||
expect(body.departments[0]).to.have.a.property('_id');
|
||||
});
|
||||
it('should accept pagination', async () => {
|
||||
await pagination('livechat/analytics/departments/total-abandoned-chats', credentials, {
|
||||
start: new Date().toISOString(),
|
||||
end: new Date().toISOString(),
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('livechat/analytics/departments/percentage-abandoned-chats', () => {
|
||||
it('should return an "unauthorized error" when the user does not have the necessary permission', async () => {
|
||||
await updatePermission('view-livechat-manager', []);
|
||||
await request
|
||||
.get(api('livechat/analytics/departments/percentage-abandoned-chats'))
|
||||
.set(credentials)
|
||||
.query({ start: '2020-01-01', end: '2020-01-02' })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(403);
|
||||
});
|
||||
it('should fail if start is not present as query param', async () => {
|
||||
await updatePermission('view-livechat-manager', ['admin']);
|
||||
const response = await request
|
||||
.get(api('livechat/analytics/departments/percentage-abandoned-chats'))
|
||||
.set(credentials)
|
||||
.query({ end: '2020-01-02' })
|
||||
.expect(400);
|
||||
expect(response.body).to.have.property('success', false);
|
||||
});
|
||||
it('should fail if end is not present as query param', async () => {
|
||||
const response = await request
|
||||
.get(api('livechat/analytics/departments/percentage-abandoned-chats'))
|
||||
.set(credentials)
|
||||
.query({ start: '2020-01-01' })
|
||||
.expect(400);
|
||||
expect(response.body).to.have.property('success', false);
|
||||
});
|
||||
it('should fail if start is not a valid date', async () => {
|
||||
const date = new Date(new Date().setDate(new Date().getDate() - 2)).toISOString();
|
||||
const response = await request
|
||||
.get(api('livechat/analytics/departments/percentage-abandoned-chats'))
|
||||
.set(credentials)
|
||||
.query({ start: '2020-01-01x', end: date })
|
||||
.expect(400);
|
||||
expect(response.body).to.have.property('success', false);
|
||||
});
|
||||
it('should fail if end is not a valid date', async () => {
|
||||
const date = new Date(new Date().setDate(new Date().getDate() - 2)).toISOString();
|
||||
const response = await request
|
||||
.get(api('livechat/analytics/departments/percentage-abandoned-chats'))
|
||||
.set(credentials)
|
||||
.query({ start: date, end: '2020-x01-02' })
|
||||
.expect(400);
|
||||
expect(response.body).to.have.property('success', false);
|
||||
});
|
||||
it('should return an array of departments', async () => {
|
||||
await updateSetting('Livechat_visitor_inactivity_timeout', 0);
|
||||
const date = new Date(new Date().setDate(new Date().getDate() - 2)).toISOString();
|
||||
|
||||
const { body } = await request
|
||||
.get(api('livechat/analytics/departments/percentage-abandoned-chats'))
|
||||
.set(credentials)
|
||||
.query({ start: date, end: new Date().toISOString() })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
expect(body).to.have.property('success', true);
|
||||
expect(body.departments).to.be.an('array').with.lengthOf.greaterThan(0);
|
||||
expect(body.departments[0]).to.have.a.property('percentageOfAbandonedChats').that.is.a('number');
|
||||
expect(body.departments[0]).to.have.a.property('_id');
|
||||
});
|
||||
it('should accept pagination', async () => {
|
||||
await pagination('livechat/analytics/departments/percentage-abandoned-chats', credentials, {
|
||||
start: new Date().toISOString(),
|
||||
end: new Date().toISOString(),
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
374
apps/meteor/tests/end-to-end/api/livechat/18-rooms-ee.ts
Normal file
374
apps/meteor/tests/end-to-end/api/livechat/18-rooms-ee.ts
Normal file
@@ -0,0 +1,374 @@
|
||||
import type { Credentials } from '@rocket.chat/api-client';
|
||||
import type { IOmnichannelRoom, IUser } from '@rocket.chat/core-typings';
|
||||
import { expect } from 'chai';
|
||||
import { after, before, describe, it } from 'mocha';
|
||||
|
||||
import { getCredentials, api, request, credentials } from '../../../data/api-data';
|
||||
import {
|
||||
createVisitor,
|
||||
createLivechatRoom,
|
||||
sendMessage,
|
||||
sendAgentMessage,
|
||||
placeRoomOnHold,
|
||||
getLivechatRoomInfo,
|
||||
startANewLivechatRoomAndTakeIt,
|
||||
makeAgentAvailable,
|
||||
createAgent,
|
||||
closeOmnichannelRoom,
|
||||
fetchMessages,
|
||||
} from '../../../data/livechat/rooms';
|
||||
import { sleep } from '../../../data/livechat/utils';
|
||||
import { updateEESetting, updatePermission, updateSetting } from '../../../data/permissions.helper';
|
||||
import { password } from '../../../data/user';
|
||||
import { createUser, deleteUser, login } from '../../../data/users.helper';
|
||||
import { IS_EE } from '../../../e2e/config/constants';
|
||||
|
||||
(IS_EE ? describe : describe.skip)('[EE] LIVECHAT - rooms', () => {
|
||||
before((done) => getCredentials(done));
|
||||
|
||||
let agent2: { user: IUser; credentials: Credentials };
|
||||
|
||||
before(async () => {
|
||||
await updateSetting('Livechat_enabled', true);
|
||||
await updateSetting('Livechat_Routing_Method', 'Manual_Selection');
|
||||
await updateEESetting('Livechat_Require_Contact_Verification', 'never');
|
||||
await createAgent();
|
||||
await makeAgentAvailable();
|
||||
});
|
||||
|
||||
before(async () => {
|
||||
const user = await createUser();
|
||||
const userCredentials = await login(user.username, password);
|
||||
await createAgent(user.username);
|
||||
await updateSetting('Livechat_allow_manual_on_hold', true);
|
||||
|
||||
agent2 = {
|
||||
user,
|
||||
credentials: userCredentials,
|
||||
};
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await deleteUser(agent2.user);
|
||||
await updateSetting('Livechat_allow_manual_on_hold', false);
|
||||
await updateSetting('Livechat_allow_manual_on_hold_upon_agent_engagement_only', true);
|
||||
});
|
||||
describe('livechat/room.onHold', () => {
|
||||
it('should fail if user doesnt have on-hold-livechat-room permission', async () => {
|
||||
await updatePermission('on-hold-livechat-room', []);
|
||||
const response = await request
|
||||
.post(api('livechat/room.onHold'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
roomId: 'invalid-room-id',
|
||||
})
|
||||
.expect(403);
|
||||
|
||||
expect(response.body.success).to.be.false;
|
||||
|
||||
await updatePermission('on-hold-livechat-room', ['livechat-manager', 'livechat-monitor', 'livechat-agent', 'admin']);
|
||||
});
|
||||
it('should fail if roomId is invalid', async () => {
|
||||
const response = await request
|
||||
.post(api('livechat/room.onHold'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
roomId: 'invalid-room-id',
|
||||
})
|
||||
.expect(400);
|
||||
|
||||
expect(response.body.success).to.be.false;
|
||||
expect(response.body.error).to.be.equal('error-invalid-room');
|
||||
});
|
||||
it('should fail if room is an empty string', async () => {
|
||||
const response = await request
|
||||
.post(api('livechat/room.onHold'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
roomId: ' ',
|
||||
})
|
||||
.expect(400);
|
||||
|
||||
expect(response.body.success).to.be.false;
|
||||
expect(response.body.error).to.be.equal('error-invalid-room');
|
||||
});
|
||||
it('should fail if room is not a livechat room', async () => {
|
||||
const response = await request
|
||||
.post(api('livechat/room.onHold'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
roomId: 'GENERAL',
|
||||
})
|
||||
.expect(400);
|
||||
|
||||
expect(response.body.success).to.be.false;
|
||||
expect(response.body.error).to.be.equal('error-invalid-room');
|
||||
});
|
||||
it('should fail if visitor is awaiting response (visitor sent last message)', async () => {
|
||||
const visitor = await createVisitor();
|
||||
const room = await createLivechatRoom(visitor.token);
|
||||
await sendMessage(room._id, 'test', visitor.token);
|
||||
|
||||
const response = await request
|
||||
.post(api('livechat/room.onHold'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
roomId: room._id,
|
||||
})
|
||||
.expect(400);
|
||||
|
||||
expect(response.body.success).to.be.false;
|
||||
expect(response.body.error).to.be.equal('error-cannot-place-chat-on-hold');
|
||||
});
|
||||
it('should fail if room is closed', async () => {
|
||||
const visitor = await createVisitor();
|
||||
const room = await createLivechatRoom(visitor.token);
|
||||
await closeOmnichannelRoom(room._id);
|
||||
|
||||
const response = await request
|
||||
.post(api('livechat/room.onHold'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
roomId: room._id,
|
||||
})
|
||||
.expect(400);
|
||||
|
||||
expect(response.body.success).to.be.false;
|
||||
expect(response.body.error).to.be.equal('error-room-already-closed');
|
||||
});
|
||||
it('should fail if user is not serving the chat and doesnt have on-hold-others-livechat-room permission', async () => {
|
||||
const { room } = await startANewLivechatRoomAndTakeIt();
|
||||
await sendAgentMessage(room._id);
|
||||
|
||||
const response = await request
|
||||
.post(api('livechat/room.onHold'))
|
||||
.set(agent2.credentials)
|
||||
.send({
|
||||
roomId: room._id,
|
||||
})
|
||||
.expect(400);
|
||||
|
||||
expect(response.body.success).to.be.false;
|
||||
expect(response.body.error).to.be.equal('Not_authorized');
|
||||
});
|
||||
it('should put room on hold', async () => {
|
||||
const { room } = await startANewLivechatRoomAndTakeIt();
|
||||
await sendAgentMessage(room._id);
|
||||
const response = await request
|
||||
.post(api('livechat/room.onHold'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
roomId: room._id,
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
expect(response.body.success).to.be.true;
|
||||
|
||||
const updatedRoom = await getLivechatRoomInfo(room._id);
|
||||
expect(updatedRoom.onHold).to.be.true;
|
||||
});
|
||||
it('Should put room on hold, even in the visitor sent the last message', async () => {
|
||||
const { room, visitor } = await startANewLivechatRoomAndTakeIt();
|
||||
await updateSetting('Livechat_allow_manual_on_hold_upon_agent_engagement_only', false);
|
||||
await sendMessage(room._id, '-', visitor.token);
|
||||
const response = await request
|
||||
.post(api('livechat/room.onHold'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
roomId: room._id,
|
||||
})
|
||||
.expect(200);
|
||||
expect(response.body.success).to.be.true;
|
||||
const updatedRoom = await getLivechatRoomInfo(room._id);
|
||||
expect(updatedRoom.onHold).to.be.true;
|
||||
});
|
||||
it('should not put room on hold when visitor sent the last message', async () => {
|
||||
const { room, visitor } = await startANewLivechatRoomAndTakeIt();
|
||||
await updateSetting('Livechat_allow_manual_on_hold_upon_agent_engagement_only', true);
|
||||
await sendMessage(room._id, '-', visitor.token);
|
||||
const response = await request
|
||||
.post(api('livechat/room.onHold'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
roomId: room._id,
|
||||
})
|
||||
.expect(400);
|
||||
expect(response.body.success).to.be.false;
|
||||
expect(response.body.error).to.be.equal('error-cannot-place-chat-on-hold');
|
||||
});
|
||||
});
|
||||
|
||||
describe('livechat/room.resumeOnHold', () => {
|
||||
it('should fail if user doesnt have view-l-room permission', async () => {
|
||||
await updatePermission('view-l-room', []);
|
||||
const response = await request
|
||||
.post(api('livechat/room.resumeOnHold'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
roomId: 'invalid-room-id',
|
||||
})
|
||||
.expect(403);
|
||||
|
||||
expect(response.body.success).to.be.false;
|
||||
});
|
||||
it('should fail if roomId is invalid', async () => {
|
||||
await updatePermission('view-l-room', ['admin', 'livechat-manager', 'livechat-agent']);
|
||||
|
||||
const response = await request
|
||||
.post(api('livechat/room.resumeOnHold'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
roomId: 'invalid-room-id',
|
||||
})
|
||||
.expect(400);
|
||||
|
||||
expect(response.body.success).to.be.false;
|
||||
expect(response.body.error).to.be.equal('error-invalid-room');
|
||||
});
|
||||
it('should fail if room is not a livechat room', async () => {
|
||||
const response = await request
|
||||
.post(api('livechat/room.resumeOnHold'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
roomId: 'GENERAL',
|
||||
})
|
||||
.expect(400);
|
||||
|
||||
expect(response.body.success).to.be.false;
|
||||
expect(response.body.error).to.be.equal('error-invalid-room');
|
||||
});
|
||||
|
||||
it('should fail if room is not on hold', async () => {
|
||||
const { room } = await startANewLivechatRoomAndTakeIt();
|
||||
|
||||
const response = await request
|
||||
.post(api('livechat/room.resumeOnHold'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
roomId: room._id,
|
||||
})
|
||||
.expect(400);
|
||||
|
||||
expect(response.body.success).to.be.false;
|
||||
expect(response.body.error).to.be.equal('error-room-not-on-hold');
|
||||
});
|
||||
it('should resume room on hold', async () => {
|
||||
const { room } = await startANewLivechatRoomAndTakeIt();
|
||||
|
||||
await sendAgentMessage(room._id);
|
||||
await placeRoomOnHold(room._id);
|
||||
|
||||
const response = await request
|
||||
.post(api('livechat/room.resumeOnHold'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
roomId: room._id,
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
expect(response.body.success).to.be.true;
|
||||
|
||||
const updatedRoom = await getLivechatRoomInfo(room._id);
|
||||
expect(updatedRoom).to.not.have.property('onHold');
|
||||
});
|
||||
it('should resume room on hold and send proper system message', async () => {
|
||||
const { room, visitor } = await startANewLivechatRoomAndTakeIt();
|
||||
|
||||
await sendAgentMessage(room._id);
|
||||
await placeRoomOnHold(room._id);
|
||||
|
||||
const response = await request
|
||||
.post(api('livechat/room.resumeOnHold'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
roomId: room._id,
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
expect(response.body.success).to.be.true;
|
||||
|
||||
const messages = await fetchMessages(room._id, visitor.token);
|
||||
expect(messages).to.be.an('array');
|
||||
expect(messages[0]).to.not.be.undefined;
|
||||
expect(messages[0]).to.have.property('t', 'omnichannel_on_hold_chat_resumed');
|
||||
expect(messages[0]).to.have.property('comment', 'The chat was manually resumed from On Hold by RocketChat Internal Admin Test');
|
||||
});
|
||||
it('should resume chat automatically if visitor sent a message', async () => {
|
||||
const { room, visitor } = await startANewLivechatRoomAndTakeIt();
|
||||
|
||||
await sendAgentMessage(room._id);
|
||||
await placeRoomOnHold(room._id);
|
||||
await sendMessage(room._id, 'test', visitor.token);
|
||||
|
||||
// wait for the room to be resumed since that logic is within callbacks
|
||||
await sleep(500);
|
||||
|
||||
const updatedRoom = await getLivechatRoomInfo(room._id);
|
||||
expect(updatedRoom).to.not.have.property('onHold');
|
||||
});
|
||||
});
|
||||
|
||||
describe('visitor abandonment feature', () => {
|
||||
let room: IOmnichannelRoom;
|
||||
|
||||
before(async () => {
|
||||
await updateSetting('Livechat_abandoned_rooms_action', 'Livechat_close_chat');
|
||||
await updateSetting('Livechat_visitor_inactivity_timeout', 60);
|
||||
});
|
||||
|
||||
it('should set predictedVisitorAbandonmentAt when agent sends a message', async () => {
|
||||
const { room: newRoom } = await startANewLivechatRoomAndTakeIt();
|
||||
|
||||
room = newRoom;
|
||||
|
||||
await sendAgentMessage(room._id);
|
||||
|
||||
const updatedRoom = await getLivechatRoomInfo(room._id);
|
||||
|
||||
const lastMessageTs = updatedRoom.responseBy?.lastMessageTs;
|
||||
const firstResponseTs = updatedRoom.responseBy?.firstResponseTs;
|
||||
const predictedVisitorAbandonmentAt = updatedRoom.omnichannel?.predictedVisitorAbandonmentAt;
|
||||
|
||||
expect(predictedVisitorAbandonmentAt).to.not.be.undefined;
|
||||
expect(lastMessageTs).to.not.be.undefined;
|
||||
expect(firstResponseTs).to.not.be.undefined;
|
||||
|
||||
// expect predictedVisitorAbandonmentAt to be 60 seconds after lastMessageTs
|
||||
const lastMessageTsDate = new Date(lastMessageTs as Date);
|
||||
const predictedVisitorAbandonmentAtDate = new Date(predictedVisitorAbandonmentAt as Date);
|
||||
const firstResponseTsDate = new Date(firstResponseTs as Date);
|
||||
|
||||
expect(predictedVisitorAbandonmentAtDate.getTime()).to.be.equal(lastMessageTsDate.getTime() + 60000);
|
||||
expect(firstResponseTsDate.getTime()).to.be.equal(lastMessageTsDate.getTime());
|
||||
});
|
||||
|
||||
it('should not update predictedVisitorAbandonmentAt when agent sends yet another message', async () => {
|
||||
await sendAgentMessage(room._id);
|
||||
|
||||
const updatedRoom = await getLivechatRoomInfo(room._id);
|
||||
|
||||
const lastMessageTs = updatedRoom.responseBy?.lastMessageTs;
|
||||
const firstResponseTs = updatedRoom.responseBy?.firstResponseTs;
|
||||
const predictedVisitorAbandonmentAt = updatedRoom.omnichannel?.predictedVisitorAbandonmentAt;
|
||||
|
||||
expect(predictedVisitorAbandonmentAt).to.not.be.undefined;
|
||||
expect(lastMessageTs).to.not.be.undefined;
|
||||
|
||||
// expect predictedVisitorAbandonmentAt to be 60 seconds after first message
|
||||
const lastMessageTsDate = new Date(lastMessageTs as Date);
|
||||
const predictedVisitorAbandonmentAtDate = new Date(predictedVisitorAbandonmentAt as Date);
|
||||
const firstResponseTsDate = new Date(firstResponseTs as Date);
|
||||
|
||||
expect(predictedVisitorAbandonmentAtDate.getTime()).to.be.equal(firstResponseTsDate.getTime() + 60000);
|
||||
|
||||
// lastMessageTs should be updated
|
||||
expect(lastMessageTsDate.getTime()).to.not.be.equal(firstResponseTsDate.getTime());
|
||||
expect(lastMessageTsDate.getTime()).to.be.greaterThan(firstResponseTsDate.getTime());
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await updateSetting('Livechat_abandoned_rooms_action', 'none');
|
||||
await updateSetting('Livechat_visitor_inactivity_timeout', 3600);
|
||||
});
|
||||
});
|
||||
});
|
||||
1128
apps/meteor/tests/end-to-end/api/livechat/19-business-hours.ts
Normal file
1128
apps/meteor/tests/end-to-end/api/livechat/19-business-hours.ts
Normal file
File diff suppressed because it is too large
Load Diff
124
apps/meteor/tests/end-to-end/api/livechat/20-messages.ts
Normal file
124
apps/meteor/tests/end-to-end/api/livechat/20-messages.ts
Normal file
@@ -0,0 +1,124 @@
|
||||
import { faker } from '@faker-js/faker';
|
||||
import type { ILivechatAgent, ILivechatVisitor, IOmnichannelRoom, IRoom } from '@rocket.chat/core-typings';
|
||||
import { expect } from 'chai';
|
||||
import { before, describe, it, after } from 'mocha';
|
||||
|
||||
import { api, getCredentials, request } from '../../../data/api-data';
|
||||
import { sendSimpleMessage } from '../../../data/chat.helper';
|
||||
import {
|
||||
sendMessage,
|
||||
startANewLivechatRoomAndTakeIt,
|
||||
sendAgentMessage,
|
||||
createAgent,
|
||||
makeAgentAvailable,
|
||||
uploadFile,
|
||||
closeOmnichannelRoom,
|
||||
} from '../../../data/livechat/rooms';
|
||||
import { removeAgent } from '../../../data/livechat/users';
|
||||
import { updateEESetting, updateSetting } from '../../../data/permissions.helper';
|
||||
import { createRoom, deleteRoom } from '../../../data/rooms.helper';
|
||||
|
||||
describe('LIVECHAT - messages', () => {
|
||||
let agent: ILivechatAgent;
|
||||
before((done) => getCredentials(done));
|
||||
|
||||
before(async () => {
|
||||
agent = await createAgent();
|
||||
await makeAgentAvailable();
|
||||
await updateSetting('Livechat_Routing_Method', 'Manual_Selection');
|
||||
await updateEESetting('Livechat_Require_Contact_Verification', 'never');
|
||||
});
|
||||
|
||||
after(() => Promise.all([updateSetting('Livechat_Routing_Method', 'Auto_Selection'), removeAgent(agent._id)]));
|
||||
|
||||
describe('Quote message feature for visitors', () => {
|
||||
it('it should verify if visitor can quote message', async () => {
|
||||
const {
|
||||
room: { _id: roomId },
|
||||
visitor: { token },
|
||||
} = await startANewLivechatRoomAndTakeIt();
|
||||
|
||||
await sendMessage(roomId, 'Hello from visitor', token);
|
||||
const agentMsgSentence = faker.lorem.sentence();
|
||||
const agentMsg = await sendAgentMessage(roomId, agentMsgSentence);
|
||||
|
||||
const siteUrl = process.env.SITE_URL || process.env.TEST_API_URL || 'http://localhost:3000';
|
||||
|
||||
const msgLink = `${siteUrl}/live/${roomId}?msg=${agentMsg._id}`;
|
||||
const quotedMsgSentence = faker.lorem.sentence();
|
||||
const wholeQuotedMsg = `[${quotedMsgSentence}](${msgLink})`;
|
||||
|
||||
const quotedMessage = await sendMessage(roomId, wholeQuotedMsg, token);
|
||||
|
||||
expect(quotedMessage).to.have.property('_id');
|
||||
expect(quotedMessage).to.have.property('msg').that.is.equal(wholeQuotedMsg);
|
||||
|
||||
expect(quotedMessage).to.have.property('attachments');
|
||||
expect(quotedMessage.attachments).to.be.an('array').that.has.lengthOf(1);
|
||||
const quotedMessageAttachments = quotedMessage.attachments?.[0];
|
||||
if (quotedMessageAttachments) {
|
||||
expect(quotedMessageAttachments).to.have.property('message_link').that.is.equal(msgLink);
|
||||
expect(quotedMessageAttachments).to.have.property('text').that.is.equal(agentMsgSentence);
|
||||
}
|
||||
});
|
||||
|
||||
it('should verify if visitor is receiving a message with a image attachment', async () => {
|
||||
const {
|
||||
room: { _id: roomId },
|
||||
visitor: { token },
|
||||
} = await startANewLivechatRoomAndTakeIt();
|
||||
|
||||
const imgMessage = await uploadFile(roomId, token);
|
||||
|
||||
expect(imgMessage).to.have.property('files').that.is.an('array');
|
||||
expect(imgMessage.files?.[0]).to.have.keys('_id', 'name', 'type');
|
||||
expect(imgMessage).to.have.property('file').that.deep.equal(imgMessage?.files?.[0]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Livechat Messages', async () => {
|
||||
let room: IOmnichannelRoom;
|
||||
let privateRoom: IRoom;
|
||||
let visitor: ILivechatVisitor;
|
||||
|
||||
before(async () => {
|
||||
await updateSetting('Livechat_Routing_Method', 'Auto_Selection');
|
||||
|
||||
const data = await startANewLivechatRoomAndTakeIt();
|
||||
visitor = data.visitor;
|
||||
room = data.room;
|
||||
|
||||
const response = await createRoom({ type: 'p', name: `private-room-${Math.random()}` } as any);
|
||||
privateRoom = response.body.group;
|
||||
});
|
||||
|
||||
after(() => Promise.all([closeOmnichannelRoom(room._id), deleteRoom({ roomId: privateRoom._id, type: 'p' })]));
|
||||
|
||||
it('should not allow fetching arbitrary messages from another channel', async () => {
|
||||
const response = await sendSimpleMessage({ roomId: privateRoom._id } as any);
|
||||
const { message } = response.body;
|
||||
|
||||
await request
|
||||
.get(api(`livechat/message/${message._id}`))
|
||||
.query({ token: visitor.token, rid: room._id })
|
||||
.send()
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body.error).to.be.equal('invalid-message');
|
||||
});
|
||||
});
|
||||
|
||||
it('should allow fetching messages using their _id and roomId', async () => {
|
||||
const message = await sendMessage(room._id, 'Hello from visitor', visitor.token);
|
||||
|
||||
await request
|
||||
.get(api(`livechat/message/${message._id}`))
|
||||
.query({ token: visitor.token, rid: room._id })
|
||||
.send()
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body.message._id).to.be.equal(message._id);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
573
apps/meteor/tests/end-to-end/api/livechat/21-reports.ts
Normal file
573
apps/meteor/tests/end-to-end/api/livechat/21-reports.ts
Normal file
@@ -0,0 +1,573 @@
|
||||
import type { Credentials } from '@rocket.chat/api-client';
|
||||
import type { IUser, IOmnichannelRoom } from '@rocket.chat/core-typings';
|
||||
import { expect } from 'chai';
|
||||
import { after, before, describe, it } from 'mocha';
|
||||
|
||||
import { api, request, credentials, getCredentials } from '../../../data/api-data';
|
||||
import { createDepartment, addOrRemoveAgentFromDepartment } from '../../../data/livechat/department';
|
||||
import { startANewLivechatRoomAndTakeIt, createAgent } from '../../../data/livechat/rooms';
|
||||
import { createMonitor, createUnit } from '../../../data/livechat/units';
|
||||
import { restorePermissionToRoles, updateEESetting, updatePermission } from '../../../data/permissions.helper';
|
||||
import { password } from '../../../data/user';
|
||||
import { createUser, deleteUser, login } from '../../../data/users.helper';
|
||||
import { IS_EE } from '../../../e2e/config/constants';
|
||||
|
||||
(IS_EE ? describe : describe.skip)('LIVECHAT - reports', () => {
|
||||
before((done) => getCredentials(done));
|
||||
|
||||
let agent2: { user: IUser; credentials: Credentials };
|
||||
let agent3: { user: IUser; credentials: Credentials };
|
||||
|
||||
before(async () => {
|
||||
await updateEESetting('Livechat_Require_Contact_Verification', 'never');
|
||||
const user = await createUser();
|
||||
const userCredentials = await login(user.username, password);
|
||||
if (!user.username) {
|
||||
throw new Error('user not created');
|
||||
}
|
||||
await createMonitor(user.username);
|
||||
|
||||
agent2 = {
|
||||
user,
|
||||
credentials: userCredentials,
|
||||
};
|
||||
});
|
||||
|
||||
before(async () => {
|
||||
const user = await createUser();
|
||||
const userCredentials = await login(user.username, password);
|
||||
await createAgent();
|
||||
if (!user.username) {
|
||||
throw new Error('user not created');
|
||||
}
|
||||
await createMonitor(user.username);
|
||||
const dep1 = await createDepartment();
|
||||
await addOrRemoveAgentFromDepartment(
|
||||
dep1._id,
|
||||
{ agentId: 'rocketchat.internal.admin.test', username: 'rocketchat.internal.admin.test', count: 0, order: 0 },
|
||||
true,
|
||||
);
|
||||
const { room, visitor } = await startANewLivechatRoomAndTakeIt({ departmentId: dep1._id });
|
||||
|
||||
await request
|
||||
.post(api('livechat/room.saveInfo'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
roomData: {
|
||||
_id: room._id,
|
||||
topic: 'new topic',
|
||||
tags: ['tag1', 'tag2'],
|
||||
},
|
||||
guestData: {
|
||||
_id: visitor._id,
|
||||
},
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
});
|
||||
|
||||
await createUnit(user._id, user.username, [dep1._id]);
|
||||
|
||||
agent3 = {
|
||||
user,
|
||||
credentials: userCredentials,
|
||||
};
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await deleteUser(agent2.user);
|
||||
await deleteUser(agent3.user);
|
||||
});
|
||||
|
||||
describe('livechat/analytics/dashboards/conversations-by-source', () => {
|
||||
it('should return an error when the user does not have the necessary permission', async () => {
|
||||
await updatePermission('view-livechat-reports', []);
|
||||
await request
|
||||
.get(api('livechat/analytics/dashboards/conversations-by-source'))
|
||||
.set(credentials)
|
||||
.query({ start: 'test', end: 'test' })
|
||||
.expect(403);
|
||||
});
|
||||
it('should return an error when the start and end parameters are not provided', async () => {
|
||||
await restorePermissionToRoles('view-livechat-reports');
|
||||
await request.get(api('livechat/analytics/dashboards/conversations-by-source')).set(credentials).expect(400);
|
||||
});
|
||||
it('should return an error when the start parameter is not provided', async () => {
|
||||
await request.get(api('livechat/analytics/dashboards/conversations-by-source')).set(credentials).query({ end: 'test' }).expect(400);
|
||||
});
|
||||
it('should return an error when the end parameter is not provided', async () => {
|
||||
await request.get(api('livechat/analytics/dashboards/conversations-by-source')).set(credentials).query({ start: 'test' }).expect(400);
|
||||
});
|
||||
it('should return an error when the start parameter is not a valid date', async () => {
|
||||
await request
|
||||
.get(api('livechat/analytics/dashboards/conversations-by-source'))
|
||||
.set(credentials)
|
||||
.query({ start: 'test', end: 'test' })
|
||||
.expect(400);
|
||||
});
|
||||
it('should fail if dates are more than 1 year apart', async () => {
|
||||
const oneYearAgo = new Date(Date.now() - 380 * 24 * 60 * 60 * 1000).toISOString();
|
||||
const now = new Date().toISOString();
|
||||
|
||||
await request
|
||||
.get(api('livechat/analytics/dashboards/conversations-by-source'))
|
||||
.set(credentials)
|
||||
.query({ start: oneYearAgo, end: now })
|
||||
.expect(400);
|
||||
});
|
||||
it('should return an error when start is after end', async () => {
|
||||
const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000).toISOString();
|
||||
const now = new Date().toISOString();
|
||||
|
||||
await request
|
||||
.get(api('livechat/analytics/dashboards/conversations-by-source'))
|
||||
.set(credentials)
|
||||
.query({ start: now, end: oneHourAgo })
|
||||
.expect(400);
|
||||
});
|
||||
it('should return an error when the end parameter is not a valid date', async () => {
|
||||
await request
|
||||
.get(api('livechat/analytics/dashboards/conversations-by-source'))
|
||||
.set(credentials)
|
||||
.query({ start: '2020-01-01', end: 'test' })
|
||||
.expect(400);
|
||||
});
|
||||
it('should return the proper data when the parameters are valid', async () => {
|
||||
// Note: this way all data will come as 0
|
||||
const now = new Date().toISOString();
|
||||
const { body } = await request
|
||||
.get(api('livechat/analytics/dashboards/conversations-by-source'))
|
||||
.set(credentials)
|
||||
.query({ start: now, end: now })
|
||||
.expect(200);
|
||||
|
||||
expect(body).to.have.property('data').and.to.be.an('array');
|
||||
expect(body.data).to.have.lengthOf(0);
|
||||
expect(body.total).to.be.equal(0);
|
||||
expect(body.success).to.be.true;
|
||||
});
|
||||
it('should return only public rooms for a monitor with no units', async () => {
|
||||
const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000).toISOString();
|
||||
const now = new Date().toISOString();
|
||||
const { body } = await request
|
||||
.get(api('livechat/analytics/dashboards/conversations-by-source'))
|
||||
.set(agent2.credentials)
|
||||
.query({ start: oneHourAgo, end: now })
|
||||
.expect(200);
|
||||
|
||||
expect(body).to.have.property('data').and.to.be.an('array');
|
||||
expect(body.data.every((room: IOmnichannelRoom) => !room.departmentAncestors && !room.departmentId)).to.be.true;
|
||||
});
|
||||
it('should return only the data from the unit the monitor belongs to', async () => {
|
||||
const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000).toISOString();
|
||||
const now = new Date().toISOString();
|
||||
const { body } = await request
|
||||
.get(api('livechat/analytics/dashboards/conversations-by-source'))
|
||||
.set(agent3.credentials)
|
||||
.query({ start: oneHourAgo, end: now })
|
||||
.expect(200);
|
||||
|
||||
expect(body).to.have.property('data').and.to.be.an('array');
|
||||
expect(body.data).to.have.lengthOf.greaterThan(0);
|
||||
expect(body.data.every((item: { value: number }) => item.value >= 0)).to.be.true;
|
||||
expect(body.total).to.be.greaterThan(0);
|
||||
expect(body.success).to.be.true;
|
||||
});
|
||||
it('should return valid data when login as a manager', async () => {
|
||||
const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000).toISOString();
|
||||
const now = new Date().toISOString();
|
||||
|
||||
const { body } = await request
|
||||
.get(api('livechat/analytics/dashboards/conversations-by-source'))
|
||||
.set(credentials)
|
||||
.query({ start: oneHourAgo, end: now })
|
||||
.expect(200);
|
||||
|
||||
expect(body).to.have.property('data').and.to.be.an('array');
|
||||
expect(body.data).to.have.lengthOf.greaterThan(0);
|
||||
expect(body.data.every((item: { value: number }) => item.value >= 0)).to.be.true;
|
||||
expect(body.total).to.be.greaterThan(0);
|
||||
});
|
||||
});
|
||||
describe('livechat/analytics/dashboards/conversations-by-status', () => {
|
||||
it('should return an error when the user does not have the necessary permission', async () => {
|
||||
await updatePermission('view-livechat-reports', []);
|
||||
await request
|
||||
.get(api('livechat/analytics/dashboards/conversations-by-status'))
|
||||
.set(credentials)
|
||||
.query({ start: 'test', end: 'test' })
|
||||
.expect(403);
|
||||
});
|
||||
it('should return an error when the start and end parameters are not provided', async () => {
|
||||
await restorePermissionToRoles('view-livechat-reports');
|
||||
await request.get(api('livechat/analytics/dashboards/conversations-by-status')).set(credentials).expect(400);
|
||||
});
|
||||
it('should return an error when the start parameter is not provided', async () => {
|
||||
await request.get(api('livechat/analytics/dashboards/conversations-by-status')).set(credentials).query({ end: 'test' }).expect(400);
|
||||
});
|
||||
it('should return an error when the end parameter is not provided', async () => {
|
||||
await request.get(api('livechat/analytics/dashboards/conversations-by-status')).set(credentials).query({ start: 'test' }).expect(400);
|
||||
});
|
||||
it('should return an error when the start parameter is not a valid date', async () => {
|
||||
await request
|
||||
.get(api('livechat/analytics/dashboards/conversations-by-status'))
|
||||
.set(credentials)
|
||||
.query({ start: 'test', end: 'test' })
|
||||
.expect(400);
|
||||
});
|
||||
it('should return an error when the end parameter is not a valid date', async () => {
|
||||
await request
|
||||
.get(api('livechat/analytics/dashboards/conversations-by-status'))
|
||||
.set(credentials)
|
||||
.query({ start: '2020-01-01', end: 'test' })
|
||||
.expect(400);
|
||||
});
|
||||
it('should return an error if dates are more than 1 year apart', async () => {
|
||||
const oneYearAgo = new Date(Date.now() - 380 * 24 * 60 * 60 * 1000).toISOString();
|
||||
const now = new Date().toISOString();
|
||||
|
||||
await request
|
||||
.get(api('livechat/analytics/dashboards/conversations-by-status'))
|
||||
.set(credentials)
|
||||
.query({ start: oneYearAgo, end: now })
|
||||
.expect(400);
|
||||
});
|
||||
it('should return an error when start is after end', async () => {
|
||||
const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000).toISOString();
|
||||
const now = new Date().toISOString();
|
||||
|
||||
await request
|
||||
.get(api('livechat/analytics/dashboards/conversations-by-status'))
|
||||
.set(credentials)
|
||||
.query({ start: now, end: oneHourAgo })
|
||||
.expect(400);
|
||||
});
|
||||
it('should return the proper data when the parameters are valid', async () => {
|
||||
// Note: this way all data will come as 0
|
||||
const now = new Date().toISOString();
|
||||
const { body } = await request
|
||||
.get(api('livechat/analytics/dashboards/conversations-by-status'))
|
||||
.set(credentials)
|
||||
.query({ start: now, end: now })
|
||||
.expect(200);
|
||||
|
||||
expect(body).to.have.property('data').and.to.be.an('array');
|
||||
expect(body.data).to.have.lengthOf(0);
|
||||
expect(body.success).to.be.true;
|
||||
});
|
||||
it('should return public rooms for a monitor with no units', async () => {
|
||||
const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000).toISOString();
|
||||
const now = new Date().toISOString();
|
||||
const { body } = await request
|
||||
.get(api('livechat/analytics/dashboards/conversations-by-status'))
|
||||
.set(agent2.credentials)
|
||||
.query({ start: oneHourAgo, end: now })
|
||||
.expect(200);
|
||||
|
||||
expect(body).to.have.property('data').and.to.be.an('array');
|
||||
expect(body.data.every((room: IOmnichannelRoom) => !room.departmentAncestors && !room.departmentId)).to.be.true;
|
||||
});
|
||||
it('should return the proper data when login as a manager', async () => {
|
||||
const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000).toISOString();
|
||||
const now = new Date().toISOString();
|
||||
|
||||
const { body } = await request
|
||||
.get(api('livechat/analytics/dashboards/conversations-by-status'))
|
||||
.set(credentials)
|
||||
.query({ start: oneHourAgo, end: now })
|
||||
.expect(200);
|
||||
|
||||
expect(body).to.have.property('data').and.to.be.an('array');
|
||||
expect(body.data).to.have.lengthOf.greaterThan(0);
|
||||
expect(body.data.every((item: { value: number }) => item.value >= 0)).to.be.true;
|
||||
});
|
||||
});
|
||||
describe('livechat/analytics/dashboards/conversations-by-department', () => {
|
||||
it('should return an error when the user does not have the necessary permission', async () => {
|
||||
await updatePermission('view-livechat-reports', []);
|
||||
await request
|
||||
.get(api('livechat/analytics/dashboards/conversations-by-department'))
|
||||
.set(credentials)
|
||||
.query({ start: 'test', end: 'test' })
|
||||
.expect(403);
|
||||
});
|
||||
it('should return an error when the start and end parameters are not provided', async () => {
|
||||
await restorePermissionToRoles('view-livechat-reports');
|
||||
await request.get(api('livechat/analytics/dashboards/conversations-by-department')).set(credentials).expect(400);
|
||||
});
|
||||
it('should return an error when the start parameter is not provided', async () => {
|
||||
await request
|
||||
.get(api('livechat/analytics/dashboards/conversations-by-department'))
|
||||
.set(credentials)
|
||||
.query({ end: 'test' })
|
||||
.expect(400);
|
||||
});
|
||||
it('should return an error when the end parameter is not provided', async () => {
|
||||
await request
|
||||
.get(api('livechat/analytics/dashboards/conversations-by-department'))
|
||||
.set(credentials)
|
||||
.query({ start: 'test' })
|
||||
.expect(400);
|
||||
});
|
||||
it('should return an error when the start parameter is not a valid date', async () => {
|
||||
await request
|
||||
.get(api('livechat/analytics/dashboards/conversations-by-department'))
|
||||
.set(credentials)
|
||||
.query({ start: 'test', end: 'test' })
|
||||
.expect(400);
|
||||
});
|
||||
it('should return an error when the end parameter is not a valid date', async () => {
|
||||
await request
|
||||
.get(api('livechat/analytics/dashboards/conversations-by-department'))
|
||||
.set(credentials)
|
||||
.query({ start: '2020-01-01', end: 'test' })
|
||||
.expect(400);
|
||||
});
|
||||
it('should return an error if dates are more than 1 year apart', async () => {
|
||||
const oneYearAgo = new Date(Date.now() - 380 * 24 * 60 * 60 * 1000).toISOString();
|
||||
const now = new Date().toISOString();
|
||||
|
||||
await request
|
||||
.get(api('livechat/analytics/dashboards/conversations-by-department'))
|
||||
.set(credentials)
|
||||
.query({ start: oneYearAgo, end: now })
|
||||
.expect(400);
|
||||
});
|
||||
it('should return an error when start is after end', async () => {
|
||||
const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000).toISOString();
|
||||
const now = new Date().toISOString();
|
||||
|
||||
await request
|
||||
.get(api('livechat/analytics/dashboards/conversations-by-department'))
|
||||
.set(credentials)
|
||||
.query({ start: now, end: oneHourAgo })
|
||||
.expect(400);
|
||||
});
|
||||
it('should return the proper data when the parameters are valid', async () => {
|
||||
// Note: this way all data will come as 0
|
||||
const now = new Date().toISOString();
|
||||
const { body } = await request
|
||||
.get(api('livechat/analytics/dashboards/conversations-by-department'))
|
||||
.set(credentials)
|
||||
.query({ start: now, end: now })
|
||||
.expect(200);
|
||||
|
||||
expect(body).to.have.property('data').and.to.be.an('array');
|
||||
expect(body.data).to.have.lengthOf(0);
|
||||
expect(body.success).to.be.true;
|
||||
});
|
||||
it('should return public rooms for a monitor with no units', async () => {
|
||||
const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000).toISOString();
|
||||
const now = new Date().toISOString();
|
||||
const { body } = await request
|
||||
.get(api('livechat/analytics/dashboards/conversations-by-department'))
|
||||
.set(agent2.credentials)
|
||||
.query({ start: oneHourAgo, end: now })
|
||||
.expect(200);
|
||||
|
||||
expect(body).to.have.property('data').and.to.be.an('array');
|
||||
expect(body.data.every((room: IOmnichannelRoom) => !room.departmentAncestors && !room.departmentId)).to.be.true;
|
||||
});
|
||||
it('should return the proper data when login as a manager', async () => {
|
||||
const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000).toISOString();
|
||||
const now = new Date().toISOString();
|
||||
|
||||
const { body } = await request
|
||||
.get(api('livechat/analytics/dashboards/conversations-by-department'))
|
||||
.set(credentials)
|
||||
.query({ start: oneHourAgo, end: now })
|
||||
.expect(200);
|
||||
|
||||
expect(body).to.have.property('data').and.to.be.an('array');
|
||||
expect(body.data).to.have.lengthOf.greaterThan(0);
|
||||
expect(body.data.every((item: { value: number }) => item.value >= 0)).to.be.true;
|
||||
});
|
||||
});
|
||||
describe('livechat/analytics/dashboards/conversations-by-tags', () => {
|
||||
it('should return an error when the user does not have the necessary permission', async () => {
|
||||
await updatePermission('view-livechat-reports', []);
|
||||
await request
|
||||
.get(api('livechat/analytics/dashboards/conversations-by-tags'))
|
||||
.set(credentials)
|
||||
.query({ start: 'test', end: 'test' })
|
||||
.expect(403);
|
||||
});
|
||||
it('should return an error when the start and end parameters are not provided', async () => {
|
||||
await restorePermissionToRoles('view-livechat-reports');
|
||||
await request.get(api('livechat/analytics/dashboards/conversations-by-tags')).set(credentials).expect(400);
|
||||
});
|
||||
it('should return an error when the start parameter is not provided', async () => {
|
||||
await request.get(api('livechat/analytics/dashboards/conversations-by-tags')).set(credentials).query({ end: 'test' }).expect(400);
|
||||
});
|
||||
it('should return an error when the end parameter is not provided', async () => {
|
||||
await request.get(api('livechat/analytics/dashboards/conversations-by-tags')).set(credentials).query({ start: 'test' }).expect(400);
|
||||
});
|
||||
it('should return an error when the start parameter is not a valid date', async () => {
|
||||
await request
|
||||
.get(api('livechat/analytics/dashboards/conversations-by-tags'))
|
||||
.set(credentials)
|
||||
.query({ start: 'test', end: 'test' })
|
||||
.expect(400);
|
||||
});
|
||||
it('should return an error when the end parameter is not a valid date', async () => {
|
||||
await request
|
||||
.get(api('livechat/analytics/dashboards/conversations-by-tags'))
|
||||
.set(credentials)
|
||||
.query({ start: '2020-01-01', end: 'test' })
|
||||
.expect(400);
|
||||
});
|
||||
it('should return an error if dates are more than 1 year apart', async () => {
|
||||
const oneYearAgo = new Date(Date.now() - 380 * 24 * 60 * 60 * 1000).toISOString();
|
||||
const now = new Date().toISOString();
|
||||
|
||||
await request
|
||||
.get(api('livechat/analytics/dashboards/conversations-by-tags'))
|
||||
.set(credentials)
|
||||
.query({ start: oneYearAgo, end: now })
|
||||
.expect(400);
|
||||
});
|
||||
it('should return an error when start is after end', async () => {
|
||||
const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000).toISOString();
|
||||
const now = new Date().toISOString();
|
||||
|
||||
await request
|
||||
.get(api('livechat/analytics/dashboards/conversations-by-tags'))
|
||||
.set(credentials)
|
||||
.query({ start: now, end: oneHourAgo })
|
||||
.expect(400);
|
||||
});
|
||||
it('should return the proper data when the parameters are valid', async () => {
|
||||
// Note: this way all data will come as 0
|
||||
const now = new Date().toISOString();
|
||||
const { body } = await request
|
||||
.get(api('livechat/analytics/dashboards/conversations-by-tags'))
|
||||
.set(credentials)
|
||||
.query({ start: now, end: now })
|
||||
.expect(200);
|
||||
|
||||
expect(body).to.have.property('data').and.to.be.an('array');
|
||||
expect(body.data).to.have.lengthOf(0);
|
||||
expect(body.success).to.be.true;
|
||||
});
|
||||
it('should return public rooms for a monitor with no units', async () => {
|
||||
const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000).toISOString();
|
||||
const now = new Date().toISOString();
|
||||
const { body } = await request
|
||||
.get(api('livechat/analytics/dashboards/conversations-by-tags'))
|
||||
.set(agent2.credentials)
|
||||
.query({ start: oneHourAgo, end: now })
|
||||
.expect(200);
|
||||
|
||||
expect(body).to.have.property('data').and.to.be.an('array');
|
||||
expect(body.data.every((room: IOmnichannelRoom) => !room.departmentAncestors && !room.departmentId)).to.be.true;
|
||||
});
|
||||
it('should return the proper data when login as a manager', async () => {
|
||||
const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000).toISOString();
|
||||
const now = new Date().toISOString();
|
||||
|
||||
const { body } = await request
|
||||
.get(api('livechat/analytics/dashboards/conversations-by-tags'))
|
||||
.set(credentials)
|
||||
.query({ start: oneHourAgo, end: now })
|
||||
.expect(200);
|
||||
|
||||
expect(body).to.have.property('data').and.to.be.an('array');
|
||||
expect(body.data).to.have.lengthOf.greaterThan(0);
|
||||
expect(body.data.every((item: { value: number }) => item.value >= 0)).to.be.true;
|
||||
});
|
||||
});
|
||||
describe('livechat/analytics/dashboards/conversations-by-agent', () => {
|
||||
it('should return an error when the user does not have the necessary permission', async () => {
|
||||
await updatePermission('view-livechat-reports', []);
|
||||
await request
|
||||
.get(api('livechat/analytics/dashboards/conversations-by-agent'))
|
||||
.set(credentials)
|
||||
.query({ start: 'test', end: 'test' })
|
||||
.expect(403);
|
||||
});
|
||||
it('should return an error when the start and end parameters are not provided', async () => {
|
||||
await restorePermissionToRoles('view-livechat-reports');
|
||||
await request.get(api('livechat/analytics/dashboards/conversations-by-agent')).set(credentials).expect(400);
|
||||
});
|
||||
it('should return an error when the start parameter is not provided', async () => {
|
||||
await request.get(api('livechat/analytics/dashboards/conversations-by-agent')).set(credentials).query({ end: 'test' }).expect(400);
|
||||
});
|
||||
it('should return an error when the end parameter is not provided', async () => {
|
||||
await request.get(api('livechat/analytics/dashboards/conversations-by-agent')).set(credentials).query({ start: 'test' }).expect(400);
|
||||
});
|
||||
it('should return an error when the start parameter is not a valid date', async () => {
|
||||
await request
|
||||
.get(api('livechat/analytics/dashboards/conversations-by-agent'))
|
||||
.set(credentials)
|
||||
.query({ start: 'test', end: 'test' })
|
||||
.expect(400);
|
||||
});
|
||||
it('should return an error when the end parameter is not a valid date', async () => {
|
||||
await request
|
||||
.get(api('livechat/analytics/dashboards/conversations-by-agent'))
|
||||
.set(credentials)
|
||||
.query({ start: '2020-01-01', end: 'test' })
|
||||
.expect(400);
|
||||
});
|
||||
it('should return an error if dates are more than 1 year apart', async () => {
|
||||
const oneYearAgo = new Date(Date.now() - 380 * 24 * 60 * 60 * 1000).toISOString();
|
||||
const now = new Date().toISOString();
|
||||
|
||||
await request
|
||||
.get(api('livechat/analytics/dashboards/conversations-by-agent'))
|
||||
.set(credentials)
|
||||
.query({ start: oneYearAgo, end: now })
|
||||
.expect(400);
|
||||
});
|
||||
it('should return an error when start is after end', async () => {
|
||||
const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000).toISOString();
|
||||
const now = new Date().toISOString();
|
||||
|
||||
await request
|
||||
.get(api('livechat/analytics/dashboards/conversations-by-agent'))
|
||||
.set(credentials)
|
||||
.query({ start: now, end: oneHourAgo })
|
||||
.expect(400);
|
||||
});
|
||||
it('should return the proper data when the parameters are valid', async () => {
|
||||
// Note: this way all data will come as 0
|
||||
const now = new Date().toISOString();
|
||||
const { body } = await request
|
||||
.get(api('livechat/analytics/dashboards/conversations-by-agent'))
|
||||
.set(credentials)
|
||||
.query({ start: now, end: now })
|
||||
.expect(200);
|
||||
|
||||
expect(body).to.have.property('data').and.to.be.an('array');
|
||||
expect(body.data).to.have.lengthOf(0);
|
||||
});
|
||||
it('should return public rooms for a monitor with no units', async () => {
|
||||
const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000).toISOString();
|
||||
const now = new Date().toISOString();
|
||||
const { body } = await request
|
||||
.get(api('livechat/analytics/dashboards/conversations-by-agent'))
|
||||
.set(agent2.credentials)
|
||||
.query({ start: oneHourAgo, end: now })
|
||||
.expect(200);
|
||||
|
||||
expect(body).to.have.property('data').and.to.be.an('array');
|
||||
expect(body.data.every((room: IOmnichannelRoom) => !room.departmentAncestors && !room.departmentId)).to.be.true;
|
||||
});
|
||||
it('should return the proper data when login as a manager', async () => {
|
||||
const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000).toISOString();
|
||||
const now = new Date().toISOString();
|
||||
|
||||
const { body } = await request
|
||||
.get(api('livechat/analytics/dashboards/conversations-by-agent'))
|
||||
.set(credentials)
|
||||
.query({ start: oneHourAgo, end: now })
|
||||
.expect(200);
|
||||
|
||||
expect(body).to.have.property('data').and.to.be.an('array');
|
||||
expect(body.data).to.have.lengthOf.greaterThan(0);
|
||||
expect(body.data.every((item: { value: number }) => item.value >= 0)).to.be.true;
|
||||
});
|
||||
});
|
||||
});
|
||||
301
apps/meteor/tests/end-to-end/api/livechat/22-monitors.ts
Normal file
301
apps/meteor/tests/end-to-end/api/livechat/22-monitors.ts
Normal file
@@ -0,0 +1,301 @@
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
import type { Credentials } from '@rocket.chat/api-client';
|
||||
import type { ILivechatDepartment, IUser } from '@rocket.chat/core-typings';
|
||||
import { expect } from 'chai';
|
||||
import { before, it, describe, after } from 'mocha';
|
||||
|
||||
import { getCredentials, api, request, credentials } from '../../../data/api-data';
|
||||
import { addOrRemoveAgentFromDepartment, createDepartment } from '../../../data/livechat/department';
|
||||
import {
|
||||
createAgent,
|
||||
createLivechatRoom,
|
||||
createManager,
|
||||
createVisitor,
|
||||
getLivechatRoomInfo,
|
||||
makeAgentAvailable,
|
||||
} from '../../../data/livechat/rooms';
|
||||
import { createMonitor, createUnit } from '../../../data/livechat/units';
|
||||
import { updateSetting, updatePermission, restorePermissionToRoles, removePermissionFromAllRoles } from '../../../data/permissions.helper';
|
||||
import { password } from '../../../data/user';
|
||||
import { createUser, deleteUser, login, setUserActiveStatus } from '../../../data/users.helper';
|
||||
import { IS_EE } from '../../../e2e/config/constants';
|
||||
|
||||
type TestUser = { user: IUser; credentials: Credentials };
|
||||
|
||||
(IS_EE ? describe : describe.skip)('Omnichannel - Monitors', () => {
|
||||
let manager: TestUser;
|
||||
let monitor: TestUser;
|
||||
let noUnitDepartment: ILivechatDepartment;
|
||||
let unitDepartment: ILivechatDepartment;
|
||||
|
||||
before((done) => getCredentials(done));
|
||||
before(async () => {
|
||||
await updateSetting('Livechat_accept_chats_with_no_agents', true);
|
||||
await setUserActiveStatus('rocketchat.internal.admin.test', true);
|
||||
await createAgent();
|
||||
await makeAgentAvailable();
|
||||
});
|
||||
before(async () => {
|
||||
const user = await createUser();
|
||||
const userCredentials = await login(user.username, password);
|
||||
if (!user.username) {
|
||||
throw new Error('user not created');
|
||||
}
|
||||
await createManager(user.username);
|
||||
|
||||
manager = {
|
||||
user,
|
||||
credentials: userCredentials,
|
||||
};
|
||||
});
|
||||
before(async () => {
|
||||
const user = await createUser();
|
||||
const userCredentials = await login(user.username, password);
|
||||
if (!user.username) {
|
||||
throw new Error('user not created');
|
||||
}
|
||||
await createMonitor(user.username);
|
||||
|
||||
monitor = {
|
||||
user,
|
||||
credentials: userCredentials,
|
||||
};
|
||||
});
|
||||
before(async () => {
|
||||
noUnitDepartment = await createDepartment();
|
||||
unitDepartment = await createDepartment();
|
||||
|
||||
await createUnit(monitor.user._id, monitor.user.username!, [unitDepartment._id]);
|
||||
});
|
||||
before(async () => {
|
||||
await updatePermission('transfer-livechat-guest', ['admin', 'livechat-manager', 'livechat-agent', 'livechat-monitor']);
|
||||
});
|
||||
|
||||
describe('Monitors', () => {
|
||||
let user: IUser;
|
||||
before(async () => {
|
||||
user = await createUser();
|
||||
});
|
||||
after(async () => {
|
||||
await deleteUser(user);
|
||||
});
|
||||
|
||||
it('should properly create a new monitor', async () => {
|
||||
const { body } = await request.post(api('livechat/monitors.create')).send({ username: user.username }).set(credentials).expect(200);
|
||||
|
||||
expect(body.success).to.be.true;
|
||||
});
|
||||
|
||||
it('should not fail when trying to create a monitor that already exists', async () => {
|
||||
const { body } = await request.post(api('livechat/monitors.create')).send({ username: user.username }).set(credentials).expect(200);
|
||||
|
||||
expect(body.success).to.be.true;
|
||||
});
|
||||
|
||||
it('should fail when trying to create a monitor with an invalid username', async () => {
|
||||
const { body } = await request
|
||||
.post(api('livechat/monitors.create'))
|
||||
.set(credentials)
|
||||
.send({ username: 'invalid-username' })
|
||||
.expect(400);
|
||||
|
||||
expect(body.success).to.be.false;
|
||||
expect(body).to.have.property('error').to.be.equal('Invalid user [error-invalid-user]');
|
||||
});
|
||||
|
||||
it('should fail when trying to create a monitor with an empty username', async () => {
|
||||
const { body } = await request.post(api('livechat/monitors.create')).set(credentials).send({ username: '' }).expect(400);
|
||||
|
||||
expect(body.success).to.be.false;
|
||||
expect(body).to.have.property('error').to.be.equal('Invalid user [error-invalid-user]');
|
||||
});
|
||||
|
||||
it('should remove a monitor', async () => {
|
||||
const { body } = await request.post(api('livechat/monitors.delete')).set(credentials).send({ username: user.username }).expect(200);
|
||||
|
||||
expect(body.success).to.be.true;
|
||||
});
|
||||
|
||||
it('should fail when trying to remove a monitor that does not exist', async () => {
|
||||
const { body } = await request
|
||||
.post(api('livechat/monitors.delete'))
|
||||
.set(credentials)
|
||||
.send({ username: 'some-random-username' })
|
||||
.expect(400);
|
||||
|
||||
expect(body.success).to.be.false;
|
||||
expect(body).to.have.property('error').to.be.equal('Invalid user');
|
||||
});
|
||||
});
|
||||
|
||||
describe('[GET] livechat/monitors', () => {
|
||||
it('should fail if manage-livechat-monitors permission is missing', async () => {
|
||||
await removePermissionFromAllRoles('manage-livechat-monitors');
|
||||
return request.get(api('livechat/monitors')).set(credentials).expect(403);
|
||||
});
|
||||
it('should return all monitors', async () => {
|
||||
await restorePermissionToRoles('manage-livechat-monitors');
|
||||
const user = await createUser();
|
||||
await createMonitor(user.username);
|
||||
|
||||
const { body } = await request.get(api('livechat/monitors')).set(credentials).query({ text: user.username }).expect(200);
|
||||
expect(body).to.have.property('monitors');
|
||||
expect(body.monitors).to.have.lengthOf(1);
|
||||
expect(body.monitors[0]).to.have.property('username', user.username);
|
||||
|
||||
// cleanup
|
||||
await deleteUser(user);
|
||||
});
|
||||
});
|
||||
|
||||
describe('livechat/monitors/:username', () => {
|
||||
it('should fail if manage-livechat-monitors permission is missing', async () => {
|
||||
await removePermissionFromAllRoles('manage-livechat-monitors');
|
||||
return request.get(api('livechat/monitors/123')).set(credentials).expect(403);
|
||||
});
|
||||
it('should return a monitor', async () => {
|
||||
await restorePermissionToRoles('manage-livechat-monitors');
|
||||
const user = await createUser();
|
||||
await createMonitor(user.username);
|
||||
|
||||
const { body } = await request
|
||||
.get(api(`livechat/monitors/${user.username}`))
|
||||
.set(credentials)
|
||||
.expect(200);
|
||||
expect(body).to.have.property('username', user.username);
|
||||
|
||||
// cleanup
|
||||
await deleteUser(user);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Monitors & Rooms', () => {
|
||||
it('should not return a room of a department that the monitor is not assigned to', async () => {
|
||||
const visitor = await createVisitor(noUnitDepartment._id);
|
||||
const room = await createLivechatRoom(visitor.token);
|
||||
|
||||
const { body } = await request
|
||||
.get(api('livechat/rooms'))
|
||||
.set(monitor.credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
expect(body).to.have.property('rooms').that.is.an('array');
|
||||
expect(body.rooms.find((r: any) => r._id === room._id)).to.not.exist;
|
||||
});
|
||||
it('should return a room of a department the monitor is assigned to', async () => {
|
||||
const visitor = await createVisitor(unitDepartment._id);
|
||||
const room = await createLivechatRoom(visitor.token);
|
||||
|
||||
const { body } = await request
|
||||
.get(api('livechat/rooms'))
|
||||
.set(monitor.credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
expect(body).to.have.property('rooms').that.is.an('array');
|
||||
expect(body.rooms.find((r: any) => r._id === room._id)).to.exist;
|
||||
});
|
||||
});
|
||||
|
||||
describe('Monitors & Departments', () => {
|
||||
it('should not return a department that the monitor is not assigned to', async () => {
|
||||
const { body } = await request
|
||||
.get(api('livechat/department'))
|
||||
.query({ onlyMyDepartments: true })
|
||||
.set(monitor.credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
expect(body).to.have.property('departments').that.is.an('array');
|
||||
expect(body.departments.find((d: any) => d._id === noUnitDepartment._id)).to.not.exist;
|
||||
});
|
||||
it('should return a department that the monitor is assigned to', async () => {
|
||||
const { body } = await request
|
||||
.get(api('livechat/department'))
|
||||
.query({ onlyMyDepartments: true })
|
||||
.set(monitor.credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
expect(body).to.have.property('departments').that.is.an('array');
|
||||
expect(body.departments.length).to.be.equal(1);
|
||||
expect(body.departments.find((d: any) => d._id === unitDepartment._id)).to.exist;
|
||||
});
|
||||
it('should return both created departments to a manager', async () => {
|
||||
const { body } = await request
|
||||
.get(api('livechat/department'))
|
||||
.query({ onlyMyDepartments: true, sort: '{ "_updatedAt": 1 }' })
|
||||
.set(manager.credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
expect(body).to.have.property('departments').that.is.an('array');
|
||||
expect(body.departments.find((d: any) => d._id === noUnitDepartment._id)).to.exist;
|
||||
expect(body.departments.find((d: any) => d._id === unitDepartment._id)).to.exist;
|
||||
});
|
||||
// OnlyMyDepartments to return data from units + the ones you serve in
|
||||
it.skip('should not return a department when monitor is only assigned as agent there', async () => {
|
||||
await createAgent(monitor.user.username!);
|
||||
await addOrRemoveAgentFromDepartment(
|
||||
noUnitDepartment._id,
|
||||
{ agentId: monitor.user._id, username: monitor.user.username!, count: 0, order: 0 },
|
||||
true,
|
||||
);
|
||||
|
||||
const { body } = await request
|
||||
.get(api('livechat/department'))
|
||||
.query({ onlyMyDepartments: true })
|
||||
.set(monitor.credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
expect(body).to.have.property('departments').that.is.an('array');
|
||||
expect(body.departments.length).to.be.equal(1);
|
||||
expect(body.departments.find((d: any) => d._id === noUnitDepartment._id)).to.not.exist;
|
||||
});
|
||||
});
|
||||
|
||||
describe('Monitors & Forward', () => {
|
||||
it('should successfully forward a room to another agent', async () => {
|
||||
const visitor = await createVisitor(unitDepartment._id);
|
||||
const room = await createLivechatRoom(visitor.token);
|
||||
|
||||
const { body } = await request
|
||||
.post(api('livechat/room.forward'))
|
||||
.set(monitor.credentials)
|
||||
.send({
|
||||
roomId: room._id,
|
||||
userId: 'rocketchat.internal.admin.test',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
expect(body).to.have.property('success', true);
|
||||
|
||||
const room2 = await getLivechatRoomInfo(room._id);
|
||||
|
||||
expect(room2).to.have.property('servedBy').that.is.an('object');
|
||||
expect(room2.servedBy).to.have.property('_id', 'rocketchat.internal.admin.test');
|
||||
});
|
||||
it('should successfully forward a room to a department', async () => {
|
||||
const visitor = await createVisitor(noUnitDepartment._id);
|
||||
const room = await createLivechatRoom(visitor.token);
|
||||
|
||||
const { body } = await request
|
||||
.post(api('livechat/room.forward'))
|
||||
.set(monitor.credentials)
|
||||
.send({
|
||||
roomId: room._id,
|
||||
departmentId: unitDepartment._id,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
expect(body).to.have.property('success', true);
|
||||
|
||||
const room2 = await getLivechatRoomInfo(room._id);
|
||||
expect(room2.departmentId).to.be.equal(unitDepartment._id);
|
||||
});
|
||||
});
|
||||
});
|
||||
134
apps/meteor/tests/end-to-end/api/livechat/23-mac.ts
Normal file
134
apps/meteor/tests/end-to-end/api/livechat/23-mac.ts
Normal file
@@ -0,0 +1,134 @@
|
||||
import type { ILivechatVisitor } from '@rocket.chat/core-typings';
|
||||
import { expect } from 'chai';
|
||||
import { before, afterEach, after, describe, it } from 'mocha';
|
||||
import moment from 'moment';
|
||||
|
||||
import { api, getCredentials, request, credentials } from '../../../data/api-data';
|
||||
import {
|
||||
createVisitor,
|
||||
createLivechatRoom,
|
||||
createAgent,
|
||||
makeAgentAvailable,
|
||||
sendAgentMessage,
|
||||
getLivechatRoomInfo,
|
||||
fetchInquiry,
|
||||
closeOmnichannelRoom,
|
||||
deleteVisitor,
|
||||
} from '../../../data/livechat/rooms';
|
||||
|
||||
describe('MAC', () => {
|
||||
before((done) => getCredentials(done));
|
||||
|
||||
before(async () => {
|
||||
await createAgent();
|
||||
await makeAgentAvailable();
|
||||
});
|
||||
|
||||
describe('MAC rooms', () => {
|
||||
let visitor: ILivechatVisitor;
|
||||
let multipleContactsVisitor: ILivechatVisitor;
|
||||
|
||||
afterEach(() => deleteVisitor(visitor.token));
|
||||
|
||||
after(() => deleteVisitor(multipleContactsVisitor.token));
|
||||
|
||||
it('Should create an innactive room and contact by default', async () => {
|
||||
visitor = await createVisitor();
|
||||
const room = await createLivechatRoom(visitor.token);
|
||||
|
||||
expect(room).to.be.an('object');
|
||||
expect(room.v.activity).to.be.undefined;
|
||||
});
|
||||
|
||||
it('Should create an innactive contact by default', async () => {
|
||||
visitor = await createVisitor();
|
||||
const room = await createLivechatRoom(visitor.token);
|
||||
|
||||
const res = await request.get(api(`omnichannel/contacts.get`)).set(credentials).query({ contactId: room.contactId });
|
||||
expect(res.body.contact.channels[0].visitor.visitorId).to.be.equal(visitor._id);
|
||||
expect(res.body.contact).not.to.have.property('activity');
|
||||
});
|
||||
|
||||
it('should mark room as active when agent sends a message', async () => {
|
||||
visitor = await createVisitor();
|
||||
const room = await createLivechatRoom(visitor.token);
|
||||
|
||||
await sendAgentMessage(room._id);
|
||||
|
||||
const updatedRoom = await getLivechatRoomInfo(room._id);
|
||||
|
||||
expect(updatedRoom).to.have.nested.property('v.activity').and.to.be.an('array');
|
||||
});
|
||||
|
||||
it('should mark contact as active when agent sends a message', async () => {
|
||||
multipleContactsVisitor = await createVisitor();
|
||||
const room = await createLivechatRoom(multipleContactsVisitor.token);
|
||||
|
||||
await sendAgentMessage(room._id);
|
||||
|
||||
const res = await request.get(api(`omnichannel/contacts.get`)).set(credentials).query({ contactId: room.contactId });
|
||||
expect(res.body.contact.channels[0].visitor.visitorId).to.be.equal(multipleContactsVisitor._id);
|
||||
expect(res.body.contact).to.have.property('activity').that.is.an('array').with.lengthOf(1);
|
||||
expect(res.body.contact.activity[0]).to.equal(moment.utc().format('YYYY-MM'));
|
||||
});
|
||||
|
||||
it('should mark multiple rooms as active when they come from same contact after an agent sends a message', async () => {
|
||||
const room = await createLivechatRoom(multipleContactsVisitor.token);
|
||||
|
||||
await sendAgentMessage(room._id);
|
||||
|
||||
const updatedRoom = await getLivechatRoomInfo(room._id);
|
||||
|
||||
expect(updatedRoom).to.have.nested.property('v.activity').and.to.be.an('array');
|
||||
await closeOmnichannelRoom(room._id);
|
||||
});
|
||||
|
||||
it('should keep contact active when reusing it and an agent response is received', async () => {
|
||||
const room = await createLivechatRoom(multipleContactsVisitor.token);
|
||||
|
||||
await sendAgentMessage(room._id);
|
||||
|
||||
const res = await request.get(api(`omnichannel/contacts.get`)).set(credentials).query({ contactId: room.contactId });
|
||||
expect(res.body.contact.channels[0].visitor.visitorId).to.be.equal(multipleContactsVisitor._id);
|
||||
expect(res.body.contact).to.have.property('activity').that.is.an('array').with.lengthOf(1);
|
||||
expect(res.body.contact.activity[0]).to.equal(moment.utc().format('YYYY-MM'));
|
||||
await closeOmnichannelRoom(room._id);
|
||||
});
|
||||
|
||||
it('should mark room as active when it comes from same contact on same period, even without agent interaction', async () => {
|
||||
const room = await createLivechatRoom(multipleContactsVisitor.token);
|
||||
|
||||
expect(room).to.have.nested.property('v.activity').and.to.be.an('array');
|
||||
expect(room.v.activity?.includes(moment.utc().format('YYYY-MM'))).to.be.true;
|
||||
await closeOmnichannelRoom(room._id);
|
||||
});
|
||||
|
||||
it('should mark an inquiry as active when it comes from same contact on same period, even without agent interaction', async () => {
|
||||
const room = await createLivechatRoom(multipleContactsVisitor.token);
|
||||
const inquiry = await fetchInquiry(room._id);
|
||||
|
||||
expect(inquiry).to.have.nested.property('v.activity').and.to.be.an('array');
|
||||
expect(inquiry.v.activity?.includes(moment.utc().format('YYYY-MM'))).to.be.true;
|
||||
expect(room.v.activity?.includes(moment.utc().format('YYYY-MM'))).to.be.true;
|
||||
await closeOmnichannelRoom(room._id);
|
||||
});
|
||||
|
||||
it('contact should be marked as active for period', async () => {
|
||||
visitor = await createVisitor();
|
||||
const room = await createLivechatRoom(visitor.token);
|
||||
await sendAgentMessage(room._id);
|
||||
const res = await request.get(api(`omnichannel/contacts.get`)).set(credentials).query({ contactId: room.contactId });
|
||||
|
||||
expect(res.status).to.be.equal(200);
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.nested.property('contact').and.to.be.an('object');
|
||||
expect(res.body.contact.channels).to.be.an('array').with.lengthOf(1);
|
||||
expect(res.body.contact.channels[0].name).to.be.equal('api');
|
||||
expect(res.body.contact.channels[0].visitor.visitorId).to.be.equal(visitor._id);
|
||||
|
||||
expect(res.body.contact).to.have.nested.property('activity').and.to.be.an('array').with.lengthOf(1);
|
||||
expect(res.body.contact.activity[0]).to.equal(moment.utc().format('YYYY-MM'));
|
||||
await closeOmnichannelRoom(room._id);
|
||||
});
|
||||
});
|
||||
});
|
||||
702
apps/meteor/tests/end-to-end/api/livechat/24-routing.ts
Normal file
702
apps/meteor/tests/end-to-end/api/livechat/24-routing.ts
Normal file
@@ -0,0 +1,702 @@
|
||||
import { faker } from '@faker-js/faker';
|
||||
import type { Credentials } from '@rocket.chat/api-client';
|
||||
import { UserStatus } from '@rocket.chat/core-typings';
|
||||
import type { ILivechatDepartment, IUser, ILivechatAgent } from '@rocket.chat/core-typings';
|
||||
import { expect } from 'chai';
|
||||
import { after, before, describe, it } from 'mocha';
|
||||
|
||||
import { getCredentials, request, api, credentials } from '../../../data/api-data';
|
||||
import {
|
||||
createAgent,
|
||||
makeAgentAvailable,
|
||||
createDepartment,
|
||||
createVisitor,
|
||||
createLivechatRoom,
|
||||
getLivechatRoomInfo,
|
||||
makeAgentUnavailable,
|
||||
switchLivechatStatus,
|
||||
} from '../../../data/livechat/rooms';
|
||||
import { getAgent } from '../../../data/livechat/users';
|
||||
import { getSettingValueById, updateSetting } from '../../../data/permissions.helper';
|
||||
import { password } from '../../../data/user';
|
||||
import { createUser, deleteUser, login, setUserActiveStatus, setUserStatus } from '../../../data/users.helper';
|
||||
import { IS_EE } from '../../../e2e/config/constants';
|
||||
|
||||
(IS_EE ? describe : describe.skip)('Omnichannel - Routing', () => {
|
||||
before((done) => getCredentials(done));
|
||||
|
||||
after(async () => {
|
||||
await updateSetting('Livechat_Routing_Method', 'Manual_Selection');
|
||||
});
|
||||
|
||||
// Basically: if there's a bot in the department, it should be assigned to the conversation
|
||||
// No matter what settings workspace has in, as long as the setting to assign new conversations to bots is enabled
|
||||
describe('Bots - Manual selection', () => {
|
||||
let botUser: { user: IUser; credentials: Credentials };
|
||||
let testDepartment: ILivechatDepartment;
|
||||
let testDepartment2: ILivechatDepartment;
|
||||
before(async () => {
|
||||
const bot = await createUser({ roles: ['bot', 'livechat-agent'] });
|
||||
const credentials = await login(bot.username, password);
|
||||
|
||||
botUser = { user: bot, credentials };
|
||||
});
|
||||
before(async () => {
|
||||
testDepartment = await createDepartment([{ agentId: botUser.user._id }]);
|
||||
testDepartment2 = await createDepartment();
|
||||
await updateSetting('Livechat_Routing_Method', 'Manual_Selection');
|
||||
await updateSetting('Livechat_assign_new_conversation_to_bot', true);
|
||||
await updateSetting('Livechat_accept_chats_with_no_agents', true);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await deleteUser(botUser.user);
|
||||
await updateSetting('Livechat_Routing_Method', 'Auto_Selection');
|
||||
await updateSetting('Livechat_assign_new_conversation_to_bot', false);
|
||||
await updateSetting('Livechat_accept_chats_with_no_agents', false);
|
||||
});
|
||||
|
||||
it('should assign conversation to bot', async () => {
|
||||
const visitor = await createVisitor(testDepartment._id);
|
||||
const room = await createLivechatRoom(visitor.token);
|
||||
|
||||
const roomInfo = await getLivechatRoomInfo(room._id);
|
||||
|
||||
expect(roomInfo.servedBy?._id).to.be.equal(botUser.user._id);
|
||||
});
|
||||
it('should not assign conversation to bot if department has no bots', async () => {
|
||||
const visitor = await createVisitor(testDepartment2._id);
|
||||
const room = await createLivechatRoom(visitor.token);
|
||||
|
||||
expect(room.servedBy).to.be.undefined;
|
||||
});
|
||||
|
||||
describe('with setting disabled', () => {
|
||||
before(async () => {
|
||||
await updateSetting('Livechat_assign_new_conversation_to_bot', false);
|
||||
});
|
||||
after(async () => {
|
||||
await updateSetting('Livechat_assign_new_conversation_to_bot', true);
|
||||
});
|
||||
|
||||
it('should not assign conversation to bot', async () => {
|
||||
const visitor = await createVisitor(testDepartment._id);
|
||||
const room = await createLivechatRoom(visitor.token);
|
||||
|
||||
expect(room.servedBy).to.be.undefined;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Bots - Auto selection', () => {
|
||||
let botUser: { user: IUser; credentials: Credentials };
|
||||
let otherUser: { user: IUser; credentials: Credentials };
|
||||
let testDepartment: ILivechatDepartment;
|
||||
let testDepartment2: ILivechatDepartment;
|
||||
before(async () => {
|
||||
const bot = await createUser({ roles: ['bot', 'livechat-agent'] });
|
||||
const credentials = await login(bot.username, password);
|
||||
|
||||
const other = await createUser({ roles: ['livechat-agent'] });
|
||||
const otherCredentials = await login(other.username, password);
|
||||
|
||||
await makeAgentAvailable(otherCredentials);
|
||||
|
||||
botUser = { user: bot, credentials };
|
||||
otherUser = { user: other, credentials: otherCredentials };
|
||||
});
|
||||
before(async () => {
|
||||
testDepartment = await createDepartment([{ agentId: botUser.user._id }, { agentId: otherUser.user._id }]);
|
||||
testDepartment2 = await createDepartment();
|
||||
await updateSetting('Livechat_Routing_Method', 'Auto_Selection');
|
||||
await updateSetting('Livechat_assign_new_conversation_to_bot', true);
|
||||
await updateSetting('Livechat_accept_chats_with_no_agents', true);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await deleteUser(botUser.user);
|
||||
await updateSetting('Livechat_assign_new_conversation_to_bot', false);
|
||||
await updateSetting('Livechat_accept_chats_with_no_agents', false);
|
||||
});
|
||||
|
||||
it('should assign conversation to bot', async () => {
|
||||
const visitor = await createVisitor(testDepartment._id);
|
||||
const room = await createLivechatRoom(visitor.token);
|
||||
|
||||
const roomInfo = await getLivechatRoomInfo(room._id);
|
||||
|
||||
expect(roomInfo.servedBy?._id).to.be.equal(botUser.user._id);
|
||||
});
|
||||
it('should not assign conversation to bot if department has no bots', async () => {
|
||||
const visitor = await createVisitor(testDepartment2._id);
|
||||
const room = await createLivechatRoom(visitor.token);
|
||||
|
||||
expect(room.servedBy).to.be.undefined;
|
||||
});
|
||||
|
||||
describe('with setting disabled', () => {
|
||||
before(async () => {
|
||||
await updateSetting('Livechat_assign_new_conversation_to_bot', false);
|
||||
});
|
||||
after(async () => {
|
||||
await updateSetting('Livechat_assign_new_conversation_to_bot', true);
|
||||
});
|
||||
|
||||
it('should not assign conversation to bot', async () => {
|
||||
const visitor = await createVisitor(testDepartment._id);
|
||||
const room = await createLivechatRoom(visitor.token);
|
||||
|
||||
expect(room.servedBy?._id).to.be.equal(otherUser.user._id);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Bots - Auto selection & Waiting queue', () => {
|
||||
let botUser: { user: IUser; credentials: Credentials };
|
||||
let testDepartment: ILivechatDepartment;
|
||||
let testDepartment2: ILivechatDepartment;
|
||||
before(async () => {
|
||||
const bot = await createUser({ roles: ['bot', 'livechat-agent'] });
|
||||
const credentials = await login(bot.username, password);
|
||||
|
||||
botUser = { user: bot, credentials };
|
||||
});
|
||||
before(async () => {
|
||||
testDepartment = await createDepartment([{ agentId: botUser.user._id }]);
|
||||
testDepartment2 = await createDepartment();
|
||||
await updateSetting('Livechat_Routing_Method', 'Auto_Selection');
|
||||
await updateSetting('Livechat_waiting_queue', true);
|
||||
await updateSetting('Livechat_assign_new_conversation_to_bot', true);
|
||||
await updateSetting('Livechat_accept_chats_with_no_agents', true);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await deleteUser(botUser.user);
|
||||
await updateSetting('Livechat_waiting_queue', false);
|
||||
await updateSetting('Livechat_assign_new_conversation_to_bot', false);
|
||||
await updateSetting('Livechat_accept_chats_with_no_agents', false);
|
||||
});
|
||||
|
||||
it('should assign conversation to bot', async () => {
|
||||
const visitor = await createVisitor(testDepartment._id);
|
||||
const room = await createLivechatRoom(visitor.token);
|
||||
|
||||
const roomInfo = await getLivechatRoomInfo(room._id);
|
||||
|
||||
expect(roomInfo.servedBy?._id).to.be.equal(botUser.user._id);
|
||||
});
|
||||
it('should not assign conversation to bot if department has no bots', async () => {
|
||||
const visitor = await createVisitor(testDepartment2._id);
|
||||
const room = await createLivechatRoom(visitor.token);
|
||||
|
||||
expect(room.servedBy).to.be.undefined;
|
||||
});
|
||||
|
||||
describe('with setting disabled', () => {
|
||||
before(async () => {
|
||||
await updateSetting('Livechat_assign_new_conversation_to_bot', false);
|
||||
});
|
||||
after(async () => {
|
||||
await updateSetting('Livechat_assign_new_conversation_to_bot', true);
|
||||
});
|
||||
|
||||
it('should not assign conversation to bot', async () => {
|
||||
const visitor = await createVisitor(testDepartment._id);
|
||||
const room = await createLivechatRoom(visitor.token);
|
||||
|
||||
expect(room.servedBy).to.be.undefined;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Auto-Selection', () => {
|
||||
before(async () => {
|
||||
await updateSetting('Livechat_Routing_Method', 'Auto_Selection');
|
||||
});
|
||||
|
||||
let testUser: { user: IUser; credentials: Credentials };
|
||||
let testUser2: { user: IUser; credentials: Credentials };
|
||||
let testUser3: { user: IUser; credentials: Credentials };
|
||||
let testDepartment: ILivechatDepartment;
|
||||
let visitorEmail: string;
|
||||
|
||||
before(async () => {
|
||||
const user = await createUser();
|
||||
await createAgent(user.username);
|
||||
const credentials2 = await login(user.username, password);
|
||||
await makeAgentAvailable(credentials2);
|
||||
|
||||
testUser = {
|
||||
user,
|
||||
credentials: credentials2,
|
||||
};
|
||||
});
|
||||
|
||||
before(async () => {
|
||||
const user = await createUser();
|
||||
await createAgent(user.username);
|
||||
const credentials2 = await login(user.username, password);
|
||||
await makeAgentUnavailable(credentials2);
|
||||
|
||||
testUser2 = {
|
||||
user,
|
||||
credentials: credentials2,
|
||||
};
|
||||
});
|
||||
|
||||
before(async () => {
|
||||
const user = await createUser();
|
||||
await createAgent(user.username);
|
||||
const credentials3 = await login(user.username, password);
|
||||
await makeAgentAvailable(credentials3);
|
||||
|
||||
testUser3 = {
|
||||
user,
|
||||
credentials: credentials3,
|
||||
};
|
||||
});
|
||||
|
||||
before(async () => {
|
||||
testDepartment = await createDepartment([{ agentId: testUser.user._id }, { agentId: testUser3.user._id }]);
|
||||
|
||||
const visitorName = faker.person.fullName();
|
||||
visitorEmail = faker.internet.email().toLowerCase();
|
||||
await request
|
||||
.post(api('omnichannel/contacts'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
name: visitorName,
|
||||
emails: [visitorEmail],
|
||||
phones: [],
|
||||
contactManager: testUser3.user._id,
|
||||
});
|
||||
});
|
||||
|
||||
after(async () =>
|
||||
Promise.all([
|
||||
deleteUser(testUser.user),
|
||||
deleteUser(testUser2.user),
|
||||
deleteUser(testUser3.user),
|
||||
updateSetting('Livechat_enabled_when_agent_idle', true),
|
||||
]),
|
||||
);
|
||||
|
||||
it('should route a room to an available agent', async () => {
|
||||
const visitor = await createVisitor(testDepartment._id);
|
||||
const room = await createLivechatRoom(visitor.token);
|
||||
|
||||
const roomInfo = await getLivechatRoomInfo(room._id);
|
||||
|
||||
expect(roomInfo.servedBy).to.be.an('object');
|
||||
expect(roomInfo.servedBy?._id).to.be.equal(testUser.user._id);
|
||||
expect(roomInfo.servedBy?._id).to.not.be.equal(testUser2.user._id);
|
||||
});
|
||||
it('should not route to a not-available agent', async () => {
|
||||
const visitor = await createVisitor(testDepartment._id);
|
||||
const room = await createLivechatRoom(visitor.token);
|
||||
|
||||
const roomInfo = await getLivechatRoomInfo(room._id);
|
||||
|
||||
expect(roomInfo.servedBy).to.be.an('object');
|
||||
expect(roomInfo.servedBy?._id).to.not.be.equal(testUser2.user._id);
|
||||
});
|
||||
it('should route to contact manager if it is online and Omnichannel_contact_manager_routing is enabled', async () => {
|
||||
const visitor = await createVisitor(testDepartment._id, faker.person.fullName(), visitorEmail);
|
||||
const room = await createLivechatRoom(visitor.token);
|
||||
|
||||
const roomInfo = await getLivechatRoomInfo(room._id);
|
||||
|
||||
expect(roomInfo.servedBy).to.be.an('object');
|
||||
expect(roomInfo.servedBy?._id).to.be.equal(testUser3.user._id);
|
||||
});
|
||||
it('should fail to start a conversation if there is noone available and Livechat_accept_chats_with_no_agents is false', async () => {
|
||||
await updateSetting('Livechat_accept_chats_with_no_agents', false);
|
||||
await makeAgentUnavailable(testUser.credentials);
|
||||
await makeAgentUnavailable(testUser3.credentials);
|
||||
|
||||
const visitor = await createVisitor(testDepartment._id);
|
||||
const { body } = await request.get(api('livechat/room')).query({ token: visitor.token }).expect(400);
|
||||
expect(body.error).to.be.equal('Sorry, no online agents [no-agent-online]');
|
||||
});
|
||||
it('should accept a conversation but not route to anyone when Livechat_accept_chats_with_no_agents is true', async () => {
|
||||
await updateSetting('Livechat_accept_chats_with_no_agents', true);
|
||||
|
||||
const visitor = await createVisitor(testDepartment._id);
|
||||
const room = await createLivechatRoom(visitor.token);
|
||||
|
||||
const roomInfo = await getLivechatRoomInfo(room._id);
|
||||
|
||||
expect(roomInfo.servedBy).to.be.undefined;
|
||||
});
|
||||
it('should not allow users to take more than Livechat_maximum_chats_per_agent chats', async () => {
|
||||
await updateSetting('Livechat_maximum_chats_per_agent', 2);
|
||||
|
||||
const visitor = await createVisitor(testDepartment._id);
|
||||
const room = await createLivechatRoom(visitor.token);
|
||||
|
||||
const roomInfo = await getLivechatRoomInfo(room._id);
|
||||
|
||||
expect(roomInfo.servedBy).to.be.undefined;
|
||||
});
|
||||
it('should ignore disabled users', async () => {
|
||||
await updateSetting('Livechat_maximum_chats_per_agent', 0);
|
||||
await setUserActiveStatus(testUser2.user._id, false);
|
||||
|
||||
const visitor = await createVisitor(testDepartment._id);
|
||||
const room = await createLivechatRoom(visitor.token);
|
||||
|
||||
const roomInfo = await getLivechatRoomInfo(room._id);
|
||||
expect(roomInfo.servedBy).to.be.undefined;
|
||||
});
|
||||
it('should route to another available agent if contact manager is unavailable and Omnichannel_contact_manager_routing is enabled', async () => {
|
||||
await makeAgentAvailable(testUser.credentials);
|
||||
const visitor = await createVisitor(testDepartment._id, faker.person.fullName(), visitorEmail);
|
||||
const room = await createLivechatRoom(visitor.token);
|
||||
|
||||
const roomInfo = await getLivechatRoomInfo(room._id);
|
||||
|
||||
expect(roomInfo.servedBy).to.be.an('object');
|
||||
expect(roomInfo.servedBy?._id).to.be.equal(testUser.user._id);
|
||||
});
|
||||
describe('with setting Omnichannel_contact_manager_routing disabled', () => {
|
||||
let testDepartment2: ILivechatDepartment;
|
||||
before(async () => {
|
||||
await updateSetting('Omnichannel_contact_manager_routing', false);
|
||||
// We should update the count of the user
|
||||
});
|
||||
after(async () => {
|
||||
await updateSetting('Omnichannel_contact_manager_routing', true);
|
||||
});
|
||||
|
||||
before(async () => {
|
||||
// This will deprioritize the CM from normal routing. So the test is predictable
|
||||
// IF the setting is off, the CM will be treated as a normal agent
|
||||
// IF the setting is on, the CM will be treated as a CM and no matter the count it will get assigned
|
||||
testDepartment2 = await createDepartment([{ agentId: testUser.user._id }, { agentId: testUser3.user._id, count: 10000 }]);
|
||||
|
||||
const visitorName = faker.person.fullName();
|
||||
visitorEmail = faker.internet.email().toLowerCase();
|
||||
await request
|
||||
.post(api('omnichannel/contacts'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
name: visitorName,
|
||||
emails: [visitorEmail],
|
||||
phones: [],
|
||||
contactManager: testUser3.user._id,
|
||||
});
|
||||
});
|
||||
|
||||
it('should not route to contact manager if it is online but setting is off', async () => {
|
||||
const visitor = await createVisitor(testDepartment2._id, faker.person.fullName(), visitorEmail);
|
||||
const room = await createLivechatRoom(visitor.token);
|
||||
|
||||
const roomInfo = await getLivechatRoomInfo(room._id);
|
||||
|
||||
expect(roomInfo.servedBy).to.be.an('object').that.has.property('_id').that.is.not.equal(testUser3.user._id);
|
||||
});
|
||||
});
|
||||
describe('Auto_Selection - Livechat_accept_chats_with_no_agents', async () => {
|
||||
let initialSettingValue: any;
|
||||
let testUserInitialState: ILivechatAgent;
|
||||
let testUser3InitialState: ILivechatAgent;
|
||||
|
||||
before(async () => {
|
||||
testUserInitialState = await getAgent(testUser.user._id);
|
||||
testUser3InitialState = await getAgent(testUser3.user._id);
|
||||
initialSettingValue = await getSettingValueById('Livechat_accept_chats_with_no_agents');
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await updateSetting('Livechat_accept_chats_with_no_agents', initialSettingValue);
|
||||
await switchLivechatStatus(testUserInitialState.statusLivechat, testUser.credentials);
|
||||
await switchLivechatStatus(testUser3InitialState.statusLivechat, testUser3.credentials);
|
||||
await setUserStatus(testUser3.credentials, testUser3InitialState.status);
|
||||
});
|
||||
describe('Livechat_accept_chats_with_no_agents is false', () => {
|
||||
before(async () => {
|
||||
await updateSetting('Livechat_accept_chats_with_no_agents', false);
|
||||
await switchLivechatStatus('not-available', testUser.credentials);
|
||||
await switchLivechatStatus('available', testUser3.credentials);
|
||||
await setUserStatus(testUser3.credentials, UserStatus.OFFLINE);
|
||||
});
|
||||
|
||||
it('should fail to start a conversation if there is an available but offline agent and Livechat_accept_chats_with_no_agents is false', async () => {
|
||||
const visitor = await createVisitor(testDepartment._id);
|
||||
const { body } = await request.get(api('livechat/room')).query({ token: visitor.token }).expect(400);
|
||||
expect(body.error).to.be.equal('Sorry, no online agents [no-agent-online]');
|
||||
});
|
||||
});
|
||||
describe('Livechat_accept_chats_with_no_agents is true', () => {
|
||||
before(async () => {
|
||||
await updateSetting('Livechat_accept_chats_with_no_agents', true);
|
||||
await switchLivechatStatus('available', testUser3.credentials);
|
||||
await setUserStatus(testUser3.credentials, UserStatus.OFFLINE);
|
||||
});
|
||||
it('should accept a conversation and route to an offline agent', async () => {
|
||||
const visitor = await createVisitor(testDepartment._id);
|
||||
const room = await createLivechatRoom(visitor.token);
|
||||
const roomInfo = await getLivechatRoomInfo(room._id);
|
||||
expect(roomInfo.servedBy).to.be.an('object');
|
||||
expect(roomInfo.servedBy?._id).to.be.equal(testUser3.user._id);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('Load Balancing', () => {
|
||||
before(async () => {
|
||||
await updateSetting('Livechat_Routing_Method', 'Load_Balancing');
|
||||
});
|
||||
|
||||
let testUser: { user: IUser; credentials: Credentials };
|
||||
let testUser2: { user: IUser; credentials: Credentials };
|
||||
let testDepartment: ILivechatDepartment;
|
||||
|
||||
before(async () => {
|
||||
const user = await createUser();
|
||||
await createAgent(user.username);
|
||||
const credentials2 = await login(user.username, password);
|
||||
await makeAgentAvailable(credentials2);
|
||||
|
||||
testUser = {
|
||||
user,
|
||||
credentials: credentials2,
|
||||
};
|
||||
});
|
||||
|
||||
before(async () => {
|
||||
const user = await createUser();
|
||||
await createAgent(user.username);
|
||||
const credentials2 = await login(user.username, password);
|
||||
await makeAgentUnavailable(credentials2);
|
||||
|
||||
testUser2 = {
|
||||
user,
|
||||
credentials: credentials2,
|
||||
};
|
||||
});
|
||||
|
||||
before(async () => {
|
||||
testDepartment = await createDepartment([{ agentId: testUser.user._id }, { agentId: testUser2.user._id }]);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await deleteUser(testUser.user);
|
||||
await deleteUser(testUser2.user);
|
||||
});
|
||||
|
||||
it('should route a room to an available agent', async () => {
|
||||
const visitor = await createVisitor(testDepartment._id);
|
||||
const room = await createLivechatRoom(visitor.token);
|
||||
|
||||
const roomInfo = await getLivechatRoomInfo(room._id);
|
||||
|
||||
expect(roomInfo.servedBy).to.be.an('object');
|
||||
expect(roomInfo.servedBy?._id).to.be.equal(testUser.user._id);
|
||||
expect(roomInfo.servedBy?._id).to.not.be.equal(testUser2.user._id);
|
||||
});
|
||||
it('should not route to a not-available agent', async () => {
|
||||
const visitor = await createVisitor(testDepartment._id);
|
||||
const room = await createLivechatRoom(visitor.token);
|
||||
|
||||
const roomInfo = await getLivechatRoomInfo(room._id);
|
||||
|
||||
expect(roomInfo.servedBy).to.be.an('object');
|
||||
expect(roomInfo.servedBy?._id).to.not.be.equal(testUser2.user._id);
|
||||
});
|
||||
it('should route the chat to the less busy agent when both are available', async () => {
|
||||
await makeAgentAvailable(testUser2.credentials);
|
||||
const visitor = await createVisitor(testDepartment._id);
|
||||
const room = await createLivechatRoom(visitor.token);
|
||||
|
||||
const roomInfo = await getLivechatRoomInfo(room._id);
|
||||
|
||||
expect(roomInfo.servedBy).to.be.an('object');
|
||||
expect(roomInfo.servedBy?._id).to.be.equal(testUser2.user._id);
|
||||
});
|
||||
it('should route again to the less busy agent', async () => {
|
||||
const visitor = await createVisitor(testDepartment._id);
|
||||
const room = await createLivechatRoom(visitor.token);
|
||||
|
||||
const roomInfo = await getLivechatRoomInfo(room._id);
|
||||
|
||||
expect(roomInfo.servedBy).to.be.an('object');
|
||||
expect(roomInfo.servedBy?._id).to.be.equal(testUser2.user._id);
|
||||
});
|
||||
describe('Load_Balancing - Livechat_accept_chats_with_no_agents', async () => {
|
||||
let initialSettingValue: any;
|
||||
let testUserInitialState: ILivechatAgent;
|
||||
let testUser2InitialState: ILivechatAgent;
|
||||
|
||||
before(async () => {
|
||||
testUserInitialState = await getAgent(testUser.user._id);
|
||||
testUser2InitialState = await getAgent(testUser2.user._id);
|
||||
initialSettingValue = await getSettingValueById('Livechat_accept_chats_with_no_agents');
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await updateSetting('Livechat_accept_chats_with_no_agents', initialSettingValue);
|
||||
await switchLivechatStatus(testUserInitialState.statusLivechat, testUser.credentials);
|
||||
await switchLivechatStatus(testUser2InitialState.statusLivechat, testUser2.credentials);
|
||||
await setUserStatus(testUser2.credentials, testUser2InitialState.status);
|
||||
});
|
||||
describe('Livechat_accept_chats_with_no_agents is false', () => {
|
||||
before(async () => {
|
||||
await updateSetting('Livechat_accept_chats_with_no_agents', false);
|
||||
await switchLivechatStatus('not-available', testUser.credentials);
|
||||
await switchLivechatStatus('available', testUser2.credentials);
|
||||
await setUserStatus(testUser2.credentials, UserStatus.OFFLINE);
|
||||
});
|
||||
|
||||
it('should fail to start a conversation if there is an available but offline agent and Livechat_accept_chats_with_no_agents is false', async () => {
|
||||
const visitor = await createVisitor(testDepartment._id);
|
||||
const { body } = await request.get(api('livechat/room')).query({ token: visitor.token }).expect(400);
|
||||
expect(body.error).to.be.equal('Sorry, no online agents [no-agent-online]');
|
||||
});
|
||||
});
|
||||
describe('Livechat_accept_chats_with_no_agents is true', () => {
|
||||
before(async () => {
|
||||
await updateSetting('Livechat_accept_chats_with_no_agents', true);
|
||||
await switchLivechatStatus('available', testUser2.credentials);
|
||||
await setUserStatus(testUser2.credentials, UserStatus.OFFLINE);
|
||||
});
|
||||
it('should accept a conversation and route to an offline agent', async () => {
|
||||
const visitor = await createVisitor(testDepartment._id);
|
||||
const room = await createLivechatRoom(visitor.token);
|
||||
const roomInfo = await getLivechatRoomInfo(room._id);
|
||||
|
||||
expect(roomInfo.servedBy).to.be.an('object');
|
||||
expect(roomInfo.servedBy?._id).to.be.equal(testUser2.user._id);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('Load Rotation', () => {
|
||||
before(async () => {
|
||||
await updateSetting('Livechat_Routing_Method', 'Load_Rotation');
|
||||
});
|
||||
|
||||
let testUser: { user: IUser; credentials: Credentials };
|
||||
let testUser2: { user: IUser; credentials: Credentials };
|
||||
let testDepartment: ILivechatDepartment;
|
||||
|
||||
before(async () => {
|
||||
const user = await createUser();
|
||||
await createAgent(user.username);
|
||||
const credentials2 = await login(user.username, password);
|
||||
await makeAgentAvailable(credentials2);
|
||||
|
||||
testUser = {
|
||||
user,
|
||||
credentials: credentials2,
|
||||
};
|
||||
});
|
||||
|
||||
before(async () => {
|
||||
const user = await createUser();
|
||||
await createAgent(user.username);
|
||||
const credentials2 = await login(user.username, password);
|
||||
await makeAgentUnavailable(credentials2);
|
||||
|
||||
testUser2 = {
|
||||
user,
|
||||
credentials: credentials2,
|
||||
};
|
||||
});
|
||||
|
||||
before(async () => {
|
||||
testDepartment = await createDepartment([{ agentId: testUser.user._id }, { agentId: testUser2.user._id }]);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await deleteUser(testUser.user);
|
||||
await deleteUser(testUser2.user);
|
||||
});
|
||||
|
||||
it('should route a room to an available agent', async () => {
|
||||
const visitor = await createVisitor(testDepartment._id);
|
||||
const room = await createLivechatRoom(visitor.token);
|
||||
|
||||
const roomInfo = await getLivechatRoomInfo(room._id);
|
||||
|
||||
expect(roomInfo.servedBy).to.be.an('object');
|
||||
expect(roomInfo.servedBy?._id).to.be.equal(testUser.user._id);
|
||||
expect(roomInfo.servedBy?._id).to.not.be.equal(testUser2.user._id);
|
||||
});
|
||||
it('should not route chat to an unavailable agent', async () => {
|
||||
const visitor = await createVisitor(testDepartment._id);
|
||||
const room = await createLivechatRoom(visitor.token);
|
||||
|
||||
const roomInfo = await getLivechatRoomInfo(room._id);
|
||||
|
||||
expect(roomInfo.servedBy).to.be.an('object');
|
||||
expect(roomInfo.servedBy?._id).to.not.be.equal(testUser2.user._id);
|
||||
});
|
||||
it('should route the chat to the agent with the oldest routing time when both are available', async () => {
|
||||
await makeAgentAvailable(testUser2.credentials);
|
||||
const visitor = await createVisitor(testDepartment._id);
|
||||
const room = await createLivechatRoom(visitor.token);
|
||||
|
||||
const roomInfo = await getLivechatRoomInfo(room._id);
|
||||
|
||||
expect(roomInfo.servedBy).to.be.an('object');
|
||||
expect(roomInfo.servedBy?._id).to.be.equal(testUser2.user._id);
|
||||
});
|
||||
it('should route again to the agent with the oldest routing time', async () => {
|
||||
const visitor = await createVisitor(testDepartment._id);
|
||||
const room = await createLivechatRoom(visitor.token);
|
||||
|
||||
const roomInfo = await getLivechatRoomInfo(room._id);
|
||||
|
||||
expect(roomInfo.servedBy).to.be.an('object');
|
||||
expect(roomInfo.servedBy?._id).to.be.equal(testUser.user._id);
|
||||
});
|
||||
describe('Load_Rotation - Livechat_accept_chats_with_no_agents', async () => {
|
||||
let initialSettingValue: any;
|
||||
let testUserInitialState: ILivechatAgent;
|
||||
let testUser2InitialState: ILivechatAgent;
|
||||
|
||||
before(async () => {
|
||||
testUserInitialState = await getAgent(testUser.user._id);
|
||||
testUser2InitialState = await getAgent(testUser2.user._id);
|
||||
initialSettingValue = await getSettingValueById('Livechat_accept_chats_with_no_agents');
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await updateSetting('Livechat_accept_chats_with_no_agents', initialSettingValue);
|
||||
await switchLivechatStatus(testUserInitialState.statusLivechat, testUser.credentials);
|
||||
await switchLivechatStatus(testUser2InitialState.statusLivechat, testUser2.credentials);
|
||||
await setUserStatus(testUser2.credentials, testUser2InitialState.status);
|
||||
});
|
||||
describe('Livechat_accept_chats_with_no_agents is false', () => {
|
||||
before(async () => {
|
||||
await updateSetting('Livechat_accept_chats_with_no_agents', false);
|
||||
await switchLivechatStatus('not-available', testUser.credentials);
|
||||
await switchLivechatStatus('available', testUser2.credentials);
|
||||
await setUserStatus(testUser2.credentials, UserStatus.OFFLINE);
|
||||
});
|
||||
|
||||
it('should fail to start a conversation if there is an available but offline agent and Livechat_accept_chats_with_no_agents is false', async () => {
|
||||
const visitor = await createVisitor(testDepartment._id);
|
||||
const { body } = await request.get(api('livechat/room')).query({ token: visitor.token }).expect(400);
|
||||
expect(body.error).to.be.equal('Sorry, no online agents [no-agent-online]');
|
||||
});
|
||||
});
|
||||
describe('Livechat_accept_chats_with_no_agents is true', () => {
|
||||
before(async () => {
|
||||
await updateSetting('Livechat_accept_chats_with_no_agents', true);
|
||||
await switchLivechatStatus('available', testUser2.credentials);
|
||||
await setUserStatus(testUser2.credentials, UserStatus.OFFLINE);
|
||||
});
|
||||
it('should accept a conversation and route to an offline agent', async () => {
|
||||
const visitor = await createVisitor(testDepartment._id);
|
||||
const room = await createLivechatRoom(visitor.token);
|
||||
const roomInfo = await getLivechatRoomInfo(room._id);
|
||||
|
||||
expect(roomInfo.servedBy).to.be.an('object');
|
||||
expect(roomInfo.servedBy?._id).to.be.equal(testUser2.user._id);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
1881
apps/meteor/tests/end-to-end/api/livechat/contacts.ts
Normal file
1881
apps/meteor/tests/end-to-end/api/livechat/contacts.ts
Normal file
File diff suppressed because it is too large
Load Diff
297
apps/meteor/tests/end-to-end/api/livechat/custom-fields-save.ts
Normal file
297
apps/meteor/tests/end-to-end/api/livechat/custom-fields-save.ts
Normal file
@@ -0,0 +1,297 @@
|
||||
import type { ILivechatCustomField } from '@rocket.chat/core-typings';
|
||||
import { expect } from 'chai';
|
||||
import { after, before, describe, it } from 'mocha';
|
||||
import type { Response } from 'supertest';
|
||||
|
||||
import { getCredentials, api, request, credentials } from '../../../data/api-data';
|
||||
import { createCustomField, deleteCustomField } from '../../../data/livechat/custom-fields';
|
||||
import {
|
||||
getSettingValueById,
|
||||
removePermissionFromAllRoles,
|
||||
restorePermissionToRoles,
|
||||
updateSetting,
|
||||
} from '../../../data/permissions.helper';
|
||||
|
||||
describe('LIVECHAT - custom fields', () => {
|
||||
let settingLivechatEnabled: boolean;
|
||||
|
||||
before((done) => getCredentials(done));
|
||||
|
||||
before(async () => {
|
||||
settingLivechatEnabled = (await getSettingValueById('Livechat_enabled')) as boolean;
|
||||
await updateSetting('Livechat_enabled', true);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await updateSetting('Livechat_enabled', settingLivechatEnabled);
|
||||
});
|
||||
|
||||
describe('livechat/custom-fields.save', () => {
|
||||
let customFieldId: string;
|
||||
|
||||
after(async () => {
|
||||
if (customFieldId) {
|
||||
await deleteCustomField(customFieldId);
|
||||
}
|
||||
});
|
||||
|
||||
describe('Authentication/Authorization', () => {
|
||||
it('should return an "unauthenticated error" when user is not logged in', async () => {
|
||||
await request
|
||||
.post(api('livechat/custom-fields.save'))
|
||||
.send({
|
||||
customFieldId: null,
|
||||
customFieldData: {
|
||||
field: 'test_field',
|
||||
label: 'Test Field',
|
||||
scope: 'visitor',
|
||||
visibility: 'public',
|
||||
},
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(401);
|
||||
});
|
||||
|
||||
it('should return an "unauthorized error" when the user does not have the necessary permission', async () => {
|
||||
await removePermissionFromAllRoles('view-livechat-manager');
|
||||
|
||||
await request
|
||||
.post(api('livechat/custom-fields.save'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
customFieldId: null,
|
||||
customFieldData: {
|
||||
field: 'test_field',
|
||||
label: 'Test Field',
|
||||
scope: 'visitor',
|
||||
visibility: 'public',
|
||||
},
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(403);
|
||||
|
||||
await restorePermissionToRoles('view-livechat-manager');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Create custom field', () => {
|
||||
afterEach(async () => {
|
||||
if (customFieldId) {
|
||||
await deleteCustomField(customFieldId);
|
||||
customFieldId = '';
|
||||
}
|
||||
});
|
||||
|
||||
it('should create a new custom field with minimal required fields', async () => {
|
||||
const fieldName = `field_${Date.now()}`;
|
||||
|
||||
await request
|
||||
.post(api('livechat/custom-fields.save'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
customFieldId: null,
|
||||
customFieldData: {
|
||||
field: fieldName,
|
||||
label: 'Test Field',
|
||||
scope: 'visitor',
|
||||
visibility: 'public',
|
||||
},
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('customField');
|
||||
expect(res.body.customField).to.have.property('_id');
|
||||
expect(res.body.customField).to.have.property('label', 'Test Field');
|
||||
expect(res.body.customField).to.have.property('scope', 'visitor');
|
||||
expect(res.body.customField).to.have.property('visibility', 'public');
|
||||
customFieldId = res.body.customField._id;
|
||||
});
|
||||
});
|
||||
|
||||
it('should create a new custom field with scope "room"', async () => {
|
||||
const fieldName = `room_field_${Date.now()}`;
|
||||
|
||||
await request
|
||||
.post(api('livechat/custom-fields.save'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
customFieldId: null,
|
||||
customFieldData: {
|
||||
field: fieldName,
|
||||
label: 'Room Test Field',
|
||||
scope: 'room',
|
||||
visibility: 'public',
|
||||
},
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('customField');
|
||||
expect(res.body.customField).to.have.property('scope', 'room');
|
||||
customFieldId = res.body.customField._id;
|
||||
});
|
||||
});
|
||||
|
||||
it('should create a new custom field with all optional fields', async () => {
|
||||
const fieldName = `full_field_${Date.now()}`;
|
||||
|
||||
await request
|
||||
.post(api('livechat/custom-fields.save'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
customFieldId: null,
|
||||
customFieldData: {
|
||||
field: fieldName,
|
||||
label: 'Full Test Field',
|
||||
scope: 'visitor',
|
||||
visibility: 'public',
|
||||
type: 'input',
|
||||
regexp: '^[A-Za-z]+$',
|
||||
required: true,
|
||||
defaultValue: 'default',
|
||||
options: 'option1,option2,option3',
|
||||
public: true,
|
||||
searchable: true,
|
||||
},
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('customField');
|
||||
expect(res.body.customField).to.have.property('type', 'input');
|
||||
expect(res.body.customField).to.have.property('regexp', '^[A-Za-z]+$');
|
||||
expect(res.body.customField).to.have.property('required', true);
|
||||
expect(res.body.customField).to.have.property('defaultValue', 'default');
|
||||
expect(res.body.customField).to.have.property('options', 'option1,option2,option3');
|
||||
expect(res.body.customField).to.have.property('public', true);
|
||||
expect(res.body.customField).to.have.property('searchable', true);
|
||||
customFieldId = res.body.customField._id;
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail when trying to create a custom field with a field name that already exists', async () => {
|
||||
const fieldName = `duplicate_field_${Date.now()}`;
|
||||
|
||||
// Create the first custom field
|
||||
const { body } = await request
|
||||
.post(api('livechat/custom-fields.save'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
customFieldId: null,
|
||||
customFieldData: {
|
||||
field: fieldName,
|
||||
label: 'First Field',
|
||||
scope: 'visitor',
|
||||
visibility: 'public',
|
||||
},
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
customFieldId = body.customField._id;
|
||||
|
||||
// Try to create another with the same field name
|
||||
await request
|
||||
.post(api('livechat/custom-fields.save'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
customFieldId: null,
|
||||
customFieldData: {
|
||||
field: fieldName,
|
||||
label: 'Second Field',
|
||||
scope: 'visitor',
|
||||
visibility: 'public',
|
||||
},
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Update custom field', () => {
|
||||
let existingField: ILivechatCustomField;
|
||||
|
||||
before(async () => {
|
||||
const fieldName = `update_test_field_${Date.now()}`;
|
||||
existingField = await createCustomField({
|
||||
searchable: true,
|
||||
field: fieldName,
|
||||
label: 'Original Label',
|
||||
defaultValue: 'original_default',
|
||||
scope: 'visitor',
|
||||
visibility: 'public',
|
||||
regexp: '',
|
||||
});
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
if (existingField?._id) {
|
||||
await deleteCustomField(existingField._id);
|
||||
}
|
||||
});
|
||||
|
||||
it('should fail when trying to update a non-existent custom field', async () => {
|
||||
await request
|
||||
.post(api('livechat/custom-fields.save'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
customFieldId: 'non-existent-id',
|
||||
customFieldData: {
|
||||
field: 'test_field',
|
||||
label: 'Updated Label',
|
||||
scope: 'visitor',
|
||||
visibility: 'public',
|
||||
},
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error');
|
||||
});
|
||||
});
|
||||
|
||||
it('should update an existing custom field with all optional fields', async () => {
|
||||
await request
|
||||
.post(api('livechat/custom-fields.save'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
customFieldId: existingField._id,
|
||||
customFieldData: {
|
||||
field: existingField._id,
|
||||
label: 'Fully Updated Field',
|
||||
scope: existingField.scope,
|
||||
visibility: 'public',
|
||||
type: 'select',
|
||||
regexp: '^[0-9]+$',
|
||||
required: true,
|
||||
defaultValue: 'new_default',
|
||||
options: 'opt1,opt2,opt3',
|
||||
public: true,
|
||||
searchable: false,
|
||||
},
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body.customField).to.have.property('label', 'Fully Updated Field');
|
||||
expect(res.body.customField).to.have.property('type', 'select');
|
||||
expect(res.body.customField).to.have.property('regexp', '^[0-9]+$');
|
||||
expect(res.body.customField).to.have.property('required', true);
|
||||
expect(res.body.customField).to.have.property('defaultValue', 'new_default');
|
||||
expect(res.body.customField).to.have.property('options', 'opt1,opt2,opt3');
|
||||
expect(res.body.customField).to.have.property('public', true);
|
||||
expect(res.body.customField).to.have.property('searchable', false);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
140
apps/meteor/tests/end-to-end/api/login-code.ts
Normal file
140
apps/meteor/tests/end-to-end/api/login-code.ts
Normal file
@@ -0,0 +1,140 @@
|
||||
import { randomBytes } from 'crypto';
|
||||
|
||||
import type { ILoginCode, IUser } from '@rocket.chat/core-typings';
|
||||
import { expect } from 'chai';
|
||||
import { after, before, describe, it } from 'mocha';
|
||||
import { MongoClient } from 'mongodb';
|
||||
|
||||
import { api, request, getCredentials } from '../../data/api-data';
|
||||
import type { TestUser } from '../../data/users.helper';
|
||||
import { createUser, deleteUser } from '../../data/users.helper';
|
||||
import { URL_MONGODB } from '../../e2e/config/constants';
|
||||
|
||||
const CODE_TTL_MS = 60 * 1000;
|
||||
|
||||
async function insertLoginCode(connection: MongoClient, userId: string, insertExpiredToken: boolean = false): Promise<string> {
|
||||
const now = new Date();
|
||||
const code = randomBytes(32).toString('hex');
|
||||
|
||||
let expireAt = new Date(now.getTime() + CODE_TTL_MS);
|
||||
|
||||
if (insertExpiredToken) {
|
||||
expireAt = new Date(now.getTime() - 1);
|
||||
}
|
||||
|
||||
await connection.db().collection<ILoginCode>('rocketchat_login_codes').insertOne({
|
||||
_id: code,
|
||||
userId,
|
||||
createdAt: now,
|
||||
expireAt,
|
||||
});
|
||||
|
||||
return code;
|
||||
}
|
||||
|
||||
describe('[Login Codes]', () => {
|
||||
let connection: MongoClient;
|
||||
|
||||
before((done) => getCredentials(done));
|
||||
|
||||
before(async () => {
|
||||
connection = await MongoClient.connect(URL_MONGODB);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await connection.close();
|
||||
});
|
||||
|
||||
describe('POST [/loginCode.redeem]', () => {
|
||||
let testUser: TestUser<IUser>;
|
||||
|
||||
before(async () => {
|
||||
testUser = await createUser();
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await deleteUser(testUser);
|
||||
});
|
||||
|
||||
it('should fail when code does not exist in the database', async () => {
|
||||
await request
|
||||
.post(api('loginCode.redeem'))
|
||||
.send({ code: 'a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2' })
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail when code is an empty string', async () => {
|
||||
await request
|
||||
.post(api('loginCode.redeem'))
|
||||
.send({ code: '' })
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('errorType', 'invalid-params');
|
||||
});
|
||||
});
|
||||
|
||||
it('should return a loginToken and userId for a valid code', async () => {
|
||||
const code = await insertLoginCode(connection, testUser._id);
|
||||
|
||||
await request
|
||||
.post(api('loginCode.redeem'))
|
||||
.send({ code })
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('loginToken').that.is.a('string').and.is.not.empty;
|
||||
expect(res.body).to.have.property('userId', testUser._id);
|
||||
});
|
||||
});
|
||||
|
||||
it('should invalidate a code after it has been redeemed', async () => {
|
||||
const code = await insertLoginCode(connection, testUser._id);
|
||||
|
||||
await request.post(api('loginCode.redeem')).send({ code }).expect(200);
|
||||
|
||||
await request
|
||||
.post(api('loginCode.redeem'))
|
||||
.send({ code })
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error', 'error-invalid-code');
|
||||
});
|
||||
});
|
||||
|
||||
it('should return a loginToken that can be used for subsequent authenticated requests', async () => {
|
||||
const code = await insertLoginCode(connection, testUser._id);
|
||||
|
||||
const redeemRes = await request.post(api('loginCode.redeem')).send({ code }).expect(200);
|
||||
|
||||
const { loginToken, userId } = redeemRes.body;
|
||||
|
||||
await request
|
||||
.get(api('me'))
|
||||
.set('X-Auth-Token', loginToken)
|
||||
.set('X-User-Id', userId)
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('_id', testUser._id);
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail when code is expired', async () => {
|
||||
const code = await insertLoginCode(connection, testUser._id, true);
|
||||
|
||||
await request
|
||||
.post(api('loginCode.redeem'))
|
||||
.send({ code })
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error', 'error-invalid-code');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
114
apps/meteor/tests/end-to-end/api/media-calls.ts
Normal file
114
apps/meteor/tests/end-to-end/api/media-calls.ts
Normal file
@@ -0,0 +1,114 @@
|
||||
import type { Credentials } from '@rocket.chat/api-client';
|
||||
import type { IUser } from '@rocket.chat/core-typings';
|
||||
import { expect } from 'chai';
|
||||
import { after, before, describe, it } from 'mocha';
|
||||
import type { Response } from 'supertest';
|
||||
|
||||
import { getCredentials, api, request, credentials } from '../../data/api-data';
|
||||
import { password } from '../../data/user';
|
||||
import { createUser, deleteUser, login } from '../../data/users.helper';
|
||||
|
||||
describe('[Media Calls]', () => {
|
||||
let user2: IUser;
|
||||
let userCredentials: Credentials;
|
||||
|
||||
before((done) => getCredentials(done));
|
||||
|
||||
before(async () => {
|
||||
user2 = await createUser();
|
||||
userCredentials = await login(user2.username, password);
|
||||
});
|
||||
|
||||
after(() => deleteUser(user2));
|
||||
|
||||
describe('[/media-calls.info]', () => {
|
||||
it('should return valid internal call information', async () => {
|
||||
await request
|
||||
.get(api('media-calls.info'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
callId: 'rocketchat.internal.call.test',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('call').that.is.an('object');
|
||||
|
||||
const { call } = res.body;
|
||||
expect(call).to.have.property('_id', 'rocketchat.internal.call.test');
|
||||
expect(call).to.have.property('service', 'webrtc');
|
||||
expect(call).to.have.property('kind', 'direct');
|
||||
expect(call).to.have.property('state', 'hangup');
|
||||
expect(call).to.have.property('ended', true);
|
||||
expect(call).to.have.property('hangupReason', 'normal');
|
||||
expect(call).to.have.property('createdBy').that.is.an('object');
|
||||
expect(call.createdBy).to.have.property('type', 'user');
|
||||
expect(call).to.have.property('caller').that.is.an('object');
|
||||
expect(call.caller).to.have.property('type', 'user');
|
||||
expect(call).to.have.property('callee').that.is.an('object');
|
||||
expect(call.callee).to.have.property('type', 'user');
|
||||
expect(call).to.have.property('endedBy').that.is.an('object');
|
||||
expect(call.endedBy).to.have.property('type', 'user');
|
||||
expect(call).to.have.property('uids').that.is.an('array');
|
||||
expect(call.uids).to.have.lengthOf(2);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return valid external call information', async () => {
|
||||
await request
|
||||
.get(api('media-calls.info'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
callId: 'rocketchat.external.call.test.outbound',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('call').that.is.an('object');
|
||||
|
||||
const { call } = res.body;
|
||||
expect(call).to.have.property('_id', 'rocketchat.external.call.test.outbound');
|
||||
expect(call).to.have.property('service', 'webrtc');
|
||||
expect(call).to.have.property('kind', 'direct');
|
||||
expect(call).to.have.property('state', 'hangup');
|
||||
expect(call).to.have.property('ended', true);
|
||||
expect(call).to.have.property('hangupReason', 'normal');
|
||||
expect(call).to.have.property('createdBy').that.is.an('object');
|
||||
expect(call).to.have.property('sipCallId', 'sipCallId3');
|
||||
expect(call.createdBy).to.have.property('type', 'user');
|
||||
expect(call).to.have.property('caller').that.is.an('object');
|
||||
expect(call.caller).to.have.property('type', 'user');
|
||||
expect(call).to.have.property('callee').that.is.an('object');
|
||||
expect(call.callee).to.have.property('type', 'sip');
|
||||
expect(call).to.have.property('endedBy').that.is.an('object');
|
||||
expect(call.endedBy).to.have.property('type', 'user');
|
||||
expect(call).to.have.property('uids').that.is.an('array');
|
||||
expect(call.uids).to.have.lengthOf(1);
|
||||
});
|
||||
});
|
||||
|
||||
it('should not return invalid calls', async () => {
|
||||
await request
|
||||
.get(api('media-calls.info'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
callId: 'invalid.call',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(404);
|
||||
});
|
||||
|
||||
it('should not return calls from other users', async () => {
|
||||
await request
|
||||
.get(api('media-calls.info'))
|
||||
.set(userCredentials)
|
||||
.query({
|
||||
callId: 'rocketchat.internal.call.test',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(404);
|
||||
});
|
||||
});
|
||||
});
|
||||
3809
apps/meteor/tests/end-to-end/api/methods.ts
Normal file
3809
apps/meteor/tests/end-to-end/api/methods.ts
Normal file
File diff suppressed because it is too large
Load Diff
162
apps/meteor/tests/end-to-end/api/methods/2fa-enable.ts
Normal file
162
apps/meteor/tests/end-to-end/api/methods/2fa-enable.ts
Normal file
@@ -0,0 +1,162 @@
|
||||
import type { IUser } from '@rocket.chat/core-typings';
|
||||
import { Random } from '@rocket.chat/random';
|
||||
import { expect } from 'chai';
|
||||
import { before, describe, it, after } from 'mocha';
|
||||
import speakeasy from 'speakeasy';
|
||||
|
||||
import { getCredentials, methodCall, request } from '../../../data/api-data';
|
||||
import { password } from '../../../data/user';
|
||||
import { createUser, deleteUser, login } from '../../../data/users.helper';
|
||||
|
||||
describe('2fa:enable', function () {
|
||||
this.retries(0);
|
||||
let totpSecret: string;
|
||||
let user1: IUser;
|
||||
let user2: IUser;
|
||||
let user3: IUser;
|
||||
let user1Credentials: { 'X-Auth-Token': string; 'X-User-Id': string };
|
||||
let user2Credentials: { 'X-Auth-Token': string; 'X-User-Id': string };
|
||||
let user3Credentials: { 'X-Auth-Token': string; 'X-User-Id': string };
|
||||
|
||||
before((done) => getCredentials(done));
|
||||
|
||||
before('create user', async () => {
|
||||
[user1, user2, user3] = await Promise.all([
|
||||
createUser({ username: Random.id(), email: `${Random.id()}@example.com`, verified: true }),
|
||||
createUser({ username: Random.id(), email: `${Random.id()}@example.com}`, verified: true }),
|
||||
createUser({ username: Random.id(), email: `${Random.id()}@example.com}`, verified: false }),
|
||||
]);
|
||||
[user1Credentials, user2Credentials, user3Credentials] = await Promise.all([
|
||||
login(user1.username, password),
|
||||
login(user2.username, password),
|
||||
login(user3.username, password),
|
||||
]);
|
||||
});
|
||||
|
||||
after('remove user', async () => Promise.all([deleteUser(user1), deleteUser(user2), deleteUser(user3)]));
|
||||
|
||||
it('should return error when user is not logged in', async () => {
|
||||
await request
|
||||
.post(methodCall('2fa:enable'))
|
||||
.send({
|
||||
message: JSON.stringify({
|
||||
msg: 'method',
|
||||
id: 'id1',
|
||||
method: '2fa:enable',
|
||||
params: [],
|
||||
}),
|
||||
})
|
||||
.expect(401)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('status', 'error');
|
||||
});
|
||||
});
|
||||
|
||||
it('should return secret and qr code url even when user has unverified email', async () => {
|
||||
await request
|
||||
.post(methodCall('2fa:enable'))
|
||||
.set(user3Credentials)
|
||||
.send({
|
||||
message: JSON.stringify({
|
||||
msg: 'method',
|
||||
id: 'id1',
|
||||
method: '2fa:enable',
|
||||
params: [],
|
||||
}),
|
||||
})
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
const parsedBody = JSON.parse(res.body.message);
|
||||
expect(parsedBody).to.have.property('result');
|
||||
expect(parsedBody.result).to.have.property('secret').of.a('string');
|
||||
expect(parsedBody.result)
|
||||
.to.have.property('url')
|
||||
.of.a('string')
|
||||
.match(/^otpauth:\/\//);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return secret and qr code url when 2fa is disabled on user', async () => {
|
||||
await request
|
||||
.post(methodCall('2fa:enable'))
|
||||
.set(user1Credentials)
|
||||
.send({
|
||||
message: JSON.stringify({
|
||||
msg: 'method',
|
||||
id: 'id1',
|
||||
method: '2fa:enable',
|
||||
params: [],
|
||||
}),
|
||||
})
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
const parsedBody = JSON.parse(res.body.message);
|
||||
expect(parsedBody).to.have.property('result');
|
||||
expect(parsedBody.result).to.have.property('secret').of.a('string');
|
||||
expect(parsedBody.result)
|
||||
.to.have.property('url')
|
||||
.of.a('string')
|
||||
.match(/^otpauth:\/\//);
|
||||
});
|
||||
});
|
||||
|
||||
it('should enable 2fa on the user', async () => {
|
||||
const enableResponse = await request
|
||||
.post(methodCall('2fa:enable'))
|
||||
.set(user2Credentials)
|
||||
.send({
|
||||
message: JSON.stringify({
|
||||
msg: 'method',
|
||||
id: 'id2',
|
||||
method: '2fa:enable',
|
||||
params: [],
|
||||
}),
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
const enableData = JSON.parse(enableResponse.body.message);
|
||||
totpSecret = enableData.result.secret;
|
||||
|
||||
await request
|
||||
.post(methodCall('2fa:validateTempToken'))
|
||||
.set(user2Credentials)
|
||||
.send({
|
||||
message: JSON.stringify({
|
||||
msg: 'method',
|
||||
id: 'id3',
|
||||
method: '2fa:validateTempToken',
|
||||
params: [speakeasy.totp({ secret: totpSecret, encoding: 'base32' })],
|
||||
}),
|
||||
})
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
const parsedBody = JSON.parse(res.body.message);
|
||||
expect(parsedBody).to.have.property('result');
|
||||
expect(parsedBody.result).to.have.property('codes').of.a('array');
|
||||
});
|
||||
});
|
||||
|
||||
it('should return error when 2fa is already enabled on the user', async () => {
|
||||
await request
|
||||
.post(methodCall('2fa:enable'))
|
||||
.set(user2Credentials)
|
||||
.send({
|
||||
message: JSON.stringify({
|
||||
msg: 'method',
|
||||
id: 'id4',
|
||||
method: '2fa:enable',
|
||||
params: [],
|
||||
}),
|
||||
})
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
const parsedBody = JSON.parse(res.body.message);
|
||||
expect(parsedBody).to.have.property('error');
|
||||
expect(parsedBody).to.not.have.property('result');
|
||||
});
|
||||
});
|
||||
});
|
||||
795
apps/meteor/tests/end-to-end/api/miscellaneous.ts
Normal file
795
apps/meteor/tests/end-to-end/api/miscellaneous.ts
Normal file
@@ -0,0 +1,795 @@
|
||||
import type { Credentials } from '@rocket.chat/api-client';
|
||||
import type { IInstanceStatus, IRoom, ITeam, IUser } from '@rocket.chat/core-typings';
|
||||
import { TeamType } from '@rocket.chat/core-typings';
|
||||
import type { IInstance } from '@rocket.chat/rest-typings';
|
||||
import { AssertionError, expect } from 'chai';
|
||||
import { after, before, describe, it } from 'mocha';
|
||||
import { MongoClient } from 'mongodb';
|
||||
|
||||
import { getCredentials, api, request, credentials } from '../../data/api-data';
|
||||
import { updatePermission, updateSetting } from '../../data/permissions.helper';
|
||||
import { createRoom, deleteRoom } from '../../data/rooms.helper';
|
||||
import { createTeam, deleteTeam } from '../../data/teams.helper';
|
||||
import { adminEmail, adminUsername, adminPassword, password } from '../../data/user';
|
||||
import type { TestUser } from '../../data/users.helper';
|
||||
import { createUser, deleteUser, login as doLogin } from '../../data/users.helper';
|
||||
import { IS_EE, URL_MONGODB } from '../../e2e/config/constants';
|
||||
|
||||
describe('miscellaneous', () => {
|
||||
before((done) => getCredentials(done));
|
||||
|
||||
describe('API default', () => {
|
||||
// Required by mobile apps
|
||||
describe('/info', () => {
|
||||
let version: string;
|
||||
it('should return "version", "build", "commit" and "marketplaceApiVersion" when the user is logged in', async () => {
|
||||
const res = await request.get('/api/info').set(credentials).expect('Content-Type', 'application/json').expect(200);
|
||||
|
||||
expect(res.body).to.have.property('version').and.to.be.a('string');
|
||||
expect(res.body.info).to.have.property('version').and.to.be.a('string');
|
||||
expect(res.body.info).to.have.property('build').and.to.be.an('object');
|
||||
expect(res.body.info).to.have.property('commit').and.to.be.an('object');
|
||||
expect(res.body.info).to.have.property('marketplaceApiVersion').and.to.be.a('string');
|
||||
version = res.body.info.version;
|
||||
});
|
||||
it('should return only "version" and the version should not have patch info when the user is not logged in', async () => {
|
||||
const res = await request.get('/api/info').expect('Content-Type', 'application/json').expect(200);
|
||||
|
||||
expect(res.body).to.have.property('version');
|
||||
expect(res.body).to.not.have.property('info');
|
||||
expect(res.body.version).to.be.equal(version.replace(/(\d+\.\d+).*/, '$1'));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('/login', () => {
|
||||
expect(credentials).to.have.property('X-Auth-Token').with.lengthOf.at.least(1);
|
||||
expect(credentials).to.have.property('X-User-Id').with.lengthOf.at.least(1);
|
||||
});
|
||||
|
||||
it('/login (wrapper username)', async () => {
|
||||
const res = await request
|
||||
.post(api('login'))
|
||||
.send({
|
||||
user: {
|
||||
username: adminUsername,
|
||||
},
|
||||
password: adminPassword,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
expect(res.body).to.have.property('status', 'success');
|
||||
expect(res.body).to.have.property('data').and.to.be.an('object');
|
||||
expect(res.body.data).to.have.property('userId');
|
||||
expect(res.body.data).to.have.property('authToken');
|
||||
expect(res.body.data).to.have.property('me');
|
||||
});
|
||||
|
||||
it('/login (wrapper email)', async () => {
|
||||
const res = await request
|
||||
.post(api('login'))
|
||||
.send({
|
||||
user: {
|
||||
email: adminEmail,
|
||||
},
|
||||
password: adminPassword,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
expect(res.body).to.have.property('status', 'success');
|
||||
expect(res.body).to.have.property('data').and.to.be.an('object');
|
||||
expect(res.body.data).to.have.property('userId');
|
||||
expect(res.body.data).to.have.property('authToken');
|
||||
expect(res.body.data).to.have.property('me');
|
||||
});
|
||||
|
||||
it('/login by user', async () => {
|
||||
const res = await request
|
||||
.post(api('login'))
|
||||
.send({
|
||||
user: adminEmail,
|
||||
password: adminPassword,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
expect(res.body).to.have.property('status', 'success');
|
||||
expect(res.body).to.have.property('data').and.to.be.an('object');
|
||||
expect(res.body.data).to.have.property('userId');
|
||||
expect(res.body.data).to.have.property('authToken');
|
||||
expect(res.body.data).to.have.property('me');
|
||||
expect(res.body.data.me.services).to.not.have.nested.property('password.bcrypt');
|
||||
expect(res.body.data.me.services).to.have.nested.property('password.exists', true);
|
||||
});
|
||||
|
||||
it('/login by username', async () => {
|
||||
const res = await request
|
||||
.post(api('login'))
|
||||
.send({
|
||||
username: adminUsername,
|
||||
password: adminPassword,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
expect(res.body).to.have.property('status', 'success');
|
||||
expect(res.body).to.have.property('data').and.to.be.an('object');
|
||||
expect(res.body.data).to.have.property('userId');
|
||||
expect(res.body.data).to.have.property('authToken');
|
||||
expect(res.body.data).to.have.property('me');
|
||||
expect(res.body.data.me.services).to.not.have.nested.property('password.bcrypt');
|
||||
expect(res.body.data.me.services).to.have.nested.property('password.exists', true);
|
||||
});
|
||||
|
||||
it('/me', async () => {
|
||||
const user = await createUser();
|
||||
const userCredentials = await doLogin(user.username, password);
|
||||
|
||||
await request
|
||||
.get(api('me'))
|
||||
.set(userCredentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
const allUserPreferencesKeys = [
|
||||
'alsoSendThreadToChannel',
|
||||
// 'language',
|
||||
'newRoomNotification',
|
||||
'newMessageNotification',
|
||||
'showThreadsInMainChannel',
|
||||
// 'clockMode',
|
||||
'useEmojis',
|
||||
'convertAsciiEmoji',
|
||||
'saveMobileBandwidth',
|
||||
'collapseMediaByDefault',
|
||||
'autoImageLoad',
|
||||
'emailNotificationMode',
|
||||
'unreadAlert',
|
||||
'masterVolume',
|
||||
'notificationsSoundVolume',
|
||||
'voipRingerVolume',
|
||||
'omnichannelTranscriptEmail',
|
||||
IS_EE ? 'omnichannelTranscriptPDF' : false,
|
||||
'desktopNotifications',
|
||||
'pushNotifications',
|
||||
'enableAutoAway',
|
||||
// 'highlights',
|
||||
'desktopNotificationRequireInteraction',
|
||||
'hideUsernames',
|
||||
'hideRoles',
|
||||
'displayAvatars',
|
||||
'hideFlexTab',
|
||||
'sendOnEnter',
|
||||
'idleTimeLimit',
|
||||
'sidebarShowFavorites',
|
||||
'sidebarShowUnread',
|
||||
'themeAppearence',
|
||||
'sidebarSortby',
|
||||
'sidebarViewMode',
|
||||
'sidebarDisplayAvatar',
|
||||
'sidebarGroupByType',
|
||||
'sidebarSectionsOrder',
|
||||
'muteFocusedConversations',
|
||||
'notifyCalendarEvents',
|
||||
'enableMobileRinging',
|
||||
'featuresPreview',
|
||||
'desktopNotificationVoiceCalls',
|
||||
].filter((p) => Boolean(p));
|
||||
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('_id', user._id);
|
||||
expect(res.body).to.have.property('username', user.username);
|
||||
expect(res.body).to.have.property('active');
|
||||
expect(res.body).to.have.property('name');
|
||||
expect(res.body).to.have.property('roles').and.to.be.an('array');
|
||||
expect(res.body).to.have.nested.property('emails[0].address', user.emails[0].address);
|
||||
expect(res.body).to.have.nested.property('settings.preferences').and.to.be.an('object');
|
||||
expect(res.body.settings.preferences).to.have.all.keys(allUserPreferencesKeys);
|
||||
expect(res.body).to.have.property('isOAuthUser', false);
|
||||
expect(res.body.services).to.not.have.nested.property('password.bcrypt');
|
||||
expect(res.body.services).to.have.nested.property('password.exists', true);
|
||||
});
|
||||
|
||||
await deleteUser(user);
|
||||
});
|
||||
|
||||
describe('/directory', () => {
|
||||
let user: TestUser<IUser>;
|
||||
let userWithLeftoverFederationData: TestUser<IUser>;
|
||||
let testChannel: IRoom;
|
||||
let testGroup: IRoom;
|
||||
let normalUserCredentials: Credentials;
|
||||
const teamName = `new-team-name-${Date.now()}` as const;
|
||||
let teamCreated: ITeam;
|
||||
|
||||
before(async () => {
|
||||
await updatePermission('create-team', ['admin', 'user']);
|
||||
user = await createUser();
|
||||
userWithLeftoverFederationData = await createUser();
|
||||
normalUserCredentials = await doLogin(user.username, password);
|
||||
[testChannel, testGroup, teamCreated] = await Promise.all([
|
||||
createRoom({ name: `channel.test.${Date.now()}`, type: 'c' }).then((res) => res.body.channel),
|
||||
createRoom({ name: `group.test.${Date.now()}`, type: 'p' }).then((res) => res.body.group),
|
||||
createTeam(normalUserCredentials, teamName, TeamType.PUBLIC),
|
||||
]);
|
||||
|
||||
const connection = await MongoClient.connect(URL_MONGODB);
|
||||
const updateResult = await connection
|
||||
.db()
|
||||
.collection('users')
|
||||
.updateOne({ _id: userWithLeftoverFederationData._id as any }, { $set: { federation: { origin: 'example.com' } } });
|
||||
await connection.close();
|
||||
expect(updateResult.modifiedCount).to.equal(1);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await Promise.all([
|
||||
deleteTeam(normalUserCredentials, teamName),
|
||||
deleteUser(user),
|
||||
deleteUser(userWithLeftoverFederationData),
|
||||
deleteRoom({ type: 'c', roomId: testChannel._id }),
|
||||
deleteRoom({ type: 'p', roomId: testGroup._id }),
|
||||
updatePermission('create-team', ['admin', 'user']),
|
||||
]);
|
||||
});
|
||||
|
||||
it('should return an array(result) when search by user and execute successfully', async () => {
|
||||
const res = await request
|
||||
.get(api('directory'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
text: user.username,
|
||||
type: 'users',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('result').and.to.be.an('array');
|
||||
expect(res.body).to.have.property('offset');
|
||||
expect(res.body).to.have.property('total');
|
||||
expect(res.body).to.have.property('count');
|
||||
expect(res.body.result[0]).to.have.property('_id');
|
||||
expect(res.body.result[0]).to.have.property('createdAt');
|
||||
expect(res.body.result[0]).to.have.property('username');
|
||||
expect(res.body.result[0]).to.have.property('emails').and.to.be.an('array');
|
||||
expect(res.body.result[0]).to.have.property('name');
|
||||
});
|
||||
|
||||
it('should not return the emails field for non admins', async () => {
|
||||
const res = await request
|
||||
.get(api('directory'))
|
||||
.set(normalUserCredentials)
|
||||
.query({
|
||||
text: user.username,
|
||||
type: 'users',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('result').and.to.be.an('array');
|
||||
expect(res.body).to.have.property('offset');
|
||||
expect(res.body).to.have.property('total');
|
||||
expect(res.body).to.have.property('count');
|
||||
expect(res.body.result[0]).to.have.property('_id');
|
||||
expect(res.body.result[0]).to.have.property('createdAt');
|
||||
expect(res.body.result[0]).to.have.property('username');
|
||||
expect(res.body.result[0]).to.not.have.property('emails');
|
||||
expect(res.body.result[0]).to.have.property('name');
|
||||
});
|
||||
it('should find local users that still have leftover federation data', async () => {
|
||||
const res = await request
|
||||
.get(api('directory'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
text: userWithLeftoverFederationData.username,
|
||||
type: 'users',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body.result).to.have.lengthOf(1);
|
||||
expect(res.body.result[0]).to.have.property('_id', userWithLeftoverFederationData._id);
|
||||
});
|
||||
|
||||
it('should return an array(result) when search by channel and execute successfully', async () => {
|
||||
const res = await request
|
||||
.get(api('directory'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
text: testChannel.name,
|
||||
type: 'channels',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('offset');
|
||||
expect(res.body).to.have.property('total');
|
||||
expect(res.body).to.have.property('count');
|
||||
expect(res.body).to.have.property('result').and.to.be.an('array');
|
||||
expect(res.body.result[0]).to.have.property('_id');
|
||||
expect(res.body.result[0]).to.have.property('name');
|
||||
expect(res.body.result[0]).to.have.property('usersCount').and.to.be.an('number');
|
||||
expect(res.body.result[0]).to.have.property('ts');
|
||||
});
|
||||
|
||||
it('should return private group when search by channel and execute successfully', async () => {
|
||||
await request
|
||||
.get(api('directory'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
text: testGroup.name,
|
||||
type: 'channels',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('offset');
|
||||
expect(res.body).to.have.property('total');
|
||||
expect(res.body).to.have.property('count');
|
||||
expect(res.body).to.have.property('result').and.to.be.an('array');
|
||||
expect(res.body.result[0]).to.have.property('_id', testGroup._id);
|
||||
expect(res.body.result[0]).to.have.property('t', 'p');
|
||||
expect(res.body.result[0]).to.have.property('name');
|
||||
expect(res.body.result[0]).to.have.property('usersCount').and.to.be.an('number');
|
||||
expect(res.body.result[0]).to.have.property('ts');
|
||||
});
|
||||
});
|
||||
|
||||
it('should return an array(result) when search by channel with sort params correctly and execute successfully', async () => {
|
||||
const res = await request
|
||||
.get(api('directory'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
text: testChannel.name,
|
||||
type: 'channels',
|
||||
sort: JSON.stringify({
|
||||
name: 1,
|
||||
}),
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('offset');
|
||||
expect(res.body).to.have.property('total');
|
||||
expect(res.body).to.have.property('count');
|
||||
expect(res.body).to.have.property('result').and.to.be.an('array');
|
||||
expect(res.body.result[0]).to.have.property('_id');
|
||||
expect(res.body.result[0]).to.have.property('name');
|
||||
expect(res.body.result[0]).to.have.property('usersCount').and.to.be.an('number');
|
||||
expect(res.body.result[0]).to.have.property('ts');
|
||||
});
|
||||
it('should return an error when send invalid query', async () => {
|
||||
const res = await request
|
||||
.get(api('directory'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
text: 'invalid channel',
|
||||
type: 'invalid',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
|
||||
expect(res.body).to.have.property('success', false);
|
||||
});
|
||||
it('should return an error when have more than one sort parameter', async () => {
|
||||
const res = await request
|
||||
.get(api('directory'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
text: testChannel.name,
|
||||
type: 'channels',
|
||||
sort: JSON.stringify({
|
||||
name: 1,
|
||||
test: 1,
|
||||
}),
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
|
||||
expect(res.body).to.have.property('success', false);
|
||||
});
|
||||
|
||||
it('should return an object containing rooms and totalCount from teams', async () => {
|
||||
const res = await request
|
||||
.get(api('directory'))
|
||||
.set(normalUserCredentials)
|
||||
.query({
|
||||
text: '',
|
||||
type: 'teams',
|
||||
sort: JSON.stringify({
|
||||
name: 1,
|
||||
}),
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
expect(res.body).to.have.property('result');
|
||||
expect(res.body.result).to.be.an(`array`);
|
||||
expect(res.body).to.have.property('total');
|
||||
expect(res.body.total).to.be.an('number');
|
||||
expect(res.body.result[0]).to.have.property('_id', teamCreated.roomId);
|
||||
expect(res.body.result[0]).to.have.property('fname');
|
||||
expect(res.body.result[0]).to.have.property('teamMain');
|
||||
expect(res.body.result[0]).to.have.property('name');
|
||||
expect(res.body.result[0]).to.have.property('t');
|
||||
expect(res.body.result[0]).to.have.property('usersCount');
|
||||
expect(res.body.result[0]).to.have.property('ts');
|
||||
expect(res.body.result[0]).to.have.property('teamId');
|
||||
expect(res.body.result[0]).to.have.property('default');
|
||||
expect(res.body.result[0]).to.have.property('roomsCount');
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/spotlight]', () => {
|
||||
let user: TestUser<IUser>;
|
||||
let userCredentials: Credentials;
|
||||
let testChannel: IRoom;
|
||||
let testTeam: ITeam;
|
||||
let testChannelSpecialChars: IRoom;
|
||||
const fnameSpecialCharsRoom = `test ГДΕληνικά-${Date.now()}`;
|
||||
const teamName = `team-test-${Date.now()}`;
|
||||
|
||||
before(async () => {
|
||||
user = await createUser();
|
||||
userCredentials = await doLogin(user.username, password);
|
||||
await updateSetting('UI_Allow_room_names_with_special_chars', true);
|
||||
testChannelSpecialChars = (await createRoom({ type: 'c', name: fnameSpecialCharsRoom, credentials: userCredentials })).body.channel;
|
||||
testChannel = (await createRoom({ type: 'c', name: `channel.test.${Date.now()}`, credentials: userCredentials })).body.channel;
|
||||
testTeam = await createTeam(userCredentials, teamName, TeamType.PUBLIC);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await Promise.all([
|
||||
deleteUser(user),
|
||||
updateSetting('UI_Allow_room_names_with_special_chars', false),
|
||||
deleteTeam(userCredentials, teamName),
|
||||
]);
|
||||
});
|
||||
|
||||
it('should fail when does not have query param', async () => {
|
||||
const res = await request.get(api('spotlight')).set(credentials).expect('Content-Type', 'application/json').expect(400);
|
||||
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error');
|
||||
});
|
||||
it('should return object inside users array when search by a valid user', async () => {
|
||||
const res = await request
|
||||
.get(api('spotlight'))
|
||||
.query({
|
||||
query: `@${adminUsername}`,
|
||||
})
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('users').and.to.be.an('array');
|
||||
expect(res.body.users[0]).to.have.property('_id');
|
||||
expect(res.body.users[0]).to.have.property('name');
|
||||
expect(res.body.users[0]).to.have.property('username');
|
||||
expect(res.body.users[0]).to.have.property('status');
|
||||
expect(res.body).to.have.property('rooms').and.to.be.an('array');
|
||||
});
|
||||
it('must return the object inside the room array when searching for a valid room and that user is not a member of it', async () => {
|
||||
const res = await request
|
||||
.get(api('spotlight'))
|
||||
.query({
|
||||
query: `#${testChannel.name}`,
|
||||
})
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('users').and.to.be.an('array');
|
||||
expect(res.body).to.have.property('rooms').and.to.be.an('array');
|
||||
expect(res.body.rooms[0]).to.have.property('_id');
|
||||
expect(res.body.rooms[0]).to.have.property('name');
|
||||
expect(res.body.rooms[0]).to.have.property('t');
|
||||
});
|
||||
it('must return the teamMain property when searching for a valid team that the user is not a member of', async () => {
|
||||
const res = await request
|
||||
.get(api('spotlight'))
|
||||
.query({
|
||||
query: `${testTeam.name}`,
|
||||
})
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('users').and.to.be.an('array');
|
||||
expect(res.body).to.have.property('rooms').and.to.be.an('array');
|
||||
expect(res.body.rooms[0]).to.have.property('_id');
|
||||
expect(res.body.rooms[0]).to.have.property('name');
|
||||
expect(res.body.rooms[0]).to.have.property('t');
|
||||
expect(res.body.rooms[0]).to.have.property('teamMain');
|
||||
});
|
||||
it('must return rooms when searching for a valid fname', async () => {
|
||||
const res = await request
|
||||
.get(api('spotlight'))
|
||||
.query({
|
||||
query: `#${fnameSpecialCharsRoom}`,
|
||||
})
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('users').and.to.be.an('array');
|
||||
expect(res.body).to.have.property('rooms').and.to.be.an('array');
|
||||
expect(res.body.rooms[0]).to.have.property('_id', testChannelSpecialChars._id);
|
||||
expect(res.body.rooms[0]).to.have.property('name', testChannelSpecialChars.name);
|
||||
expect(res.body.rooms[0]).to.have.property('t', testChannelSpecialChars.t);
|
||||
});
|
||||
it('should not return users when the type param disables user search', async () => {
|
||||
const res = await request
|
||||
.get(api('spotlight'))
|
||||
.query({
|
||||
query: `${adminUsername}`,
|
||||
type: JSON.stringify({ users: false, rooms: true }),
|
||||
})
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('users').and.to.be.an('array').that.is.empty;
|
||||
expect(res.body).to.have.property('rooms').and.to.be.an('array');
|
||||
});
|
||||
it('should exclude usernames passed in the usernames param from the results', async () => {
|
||||
const res = await request
|
||||
.get(api('spotlight'))
|
||||
.query({
|
||||
// Use a non-exact (prefix) query so the regex search path runs; the exact-username
|
||||
// match branch in Spotlight.searchUsers does not honor the usernames exclusion list.
|
||||
query: adminUsername.slice(0, -2),
|
||||
usernames: adminUsername,
|
||||
})
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('users').and.to.be.an('array');
|
||||
expect(res.body.users.map((u: { username: string }) => u.username)).to.not.include(adminUsername);
|
||||
});
|
||||
describe('anonymous (unauthenticated) requests', () => {
|
||||
after(() => updateSetting('Accounts_AllowAnonymousRead', false));
|
||||
|
||||
it('should return no rooms when anonymous read is disabled', async () => {
|
||||
await updateSetting('Accounts_AllowAnonymousRead', false);
|
||||
|
||||
const res = await request
|
||||
.get(api('spotlight'))
|
||||
.query({
|
||||
query: `#${testChannel.name}`,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('rooms').and.to.be.an('array').that.is.empty;
|
||||
expect(res.body).to.have.property('users').and.to.be.an('array').that.is.empty;
|
||||
});
|
||||
|
||||
// An unprefixed query keeps `type.users` enabled, so it also runs the user search with no
|
||||
// user id - the code path that used to throw before the anonymous callers were guarded.
|
||||
it('should return no rooms or users for an unprefixed query when anonymous read is disabled', async () => {
|
||||
await updateSetting('Accounts_AllowAnonymousRead', false);
|
||||
|
||||
const res = await request
|
||||
.get(api('spotlight'))
|
||||
.query({
|
||||
query: testChannel.name,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('rooms').and.to.be.an('array').that.is.empty;
|
||||
expect(res.body).to.have.property('users').and.to.be.an('array').that.is.empty;
|
||||
});
|
||||
|
||||
it('should return public rooms but no users when anonymous read is enabled', async () => {
|
||||
await updateSetting('Accounts_AllowAnonymousRead', true);
|
||||
|
||||
const res = await request
|
||||
.get(api('spotlight'))
|
||||
.query({
|
||||
query: `#${testChannel.name}`,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body.rooms.map((r: { _id: string }) => r._id)).to.include(testChannel._id);
|
||||
expect(res.body).to.have.property('users').and.to.be.an('array').that.is.empty;
|
||||
});
|
||||
|
||||
it('should return public rooms but no users for an unprefixed query when anonymous read is enabled', async () => {
|
||||
await updateSetting('Accounts_AllowAnonymousRead', true);
|
||||
|
||||
const res = await request
|
||||
.get(api('spotlight'))
|
||||
.query({
|
||||
query: testChannel.name,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body.rooms.map((r: { _id: string }) => r._id)).to.include(testChannel._id);
|
||||
expect(res.body).to.have.property('users').and.to.be.an('array').that.is.empty;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/instances.get]', () => {
|
||||
let unauthorizedUserCredentials: Credentials;
|
||||
before(async () => {
|
||||
const createdUser = await createUser();
|
||||
unauthorizedUserCredentials = await doLogin(createdUser.username, password);
|
||||
});
|
||||
|
||||
it('should fail if user is logged in but is unauthorized', async () => {
|
||||
const res = await request
|
||||
.get(api('instances.get'))
|
||||
.set(unauthorizedUserCredentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(403);
|
||||
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error', 'User does not have the permissions required for this action [error-unauthorized]');
|
||||
});
|
||||
|
||||
it('should fail if not logged in', async () => {
|
||||
const res = await request.get(api('instances.get')).expect('Content-Type', 'application/json').expect(401);
|
||||
|
||||
expect(res.body).to.have.property('status', 'error');
|
||||
expect(res.body).to.have.property('message');
|
||||
});
|
||||
|
||||
it('should return instances if user is logged in and is authorized', async () => {
|
||||
const res = await request.get(api('instances.get')).set(credentials).expect(200);
|
||||
|
||||
expect(res.body).to.have.property('success', true);
|
||||
|
||||
expect(res.body).to.have.property('instances').and.to.be.an('array').with.lengthOf(1);
|
||||
|
||||
const instances = res.body.instances as IInstance[];
|
||||
|
||||
const instanceName = IS_EE ? 'ddp-streamer' : 'rocket.chat';
|
||||
|
||||
const instance = instances.find((i): i is IInstance & { instanceRecord: IInstanceStatus } => i.instanceRecord?.name === instanceName);
|
||||
|
||||
if (!instance) throw new AssertionError(`no instance named "${instanceName}"`);
|
||||
|
||||
expect(instance).to.have.property('instanceRecord');
|
||||
expect(instance).to.have.property('currentStatus');
|
||||
|
||||
expect(instance.currentStatus).to.have.property('connected');
|
||||
|
||||
expect(instance.instanceRecord).to.have.property('_id');
|
||||
expect(instance.instanceRecord).to.have.property('extraInformation');
|
||||
expect(instance.instanceRecord).to.have.property('name');
|
||||
expect(instance.instanceRecord).to.have.property('pid');
|
||||
|
||||
if (!IS_EE) {
|
||||
expect(instance).to.have.property('address');
|
||||
|
||||
expect(instance.currentStatus).to.have.property('lastHeartbeatTime');
|
||||
expect(instance.currentStatus).to.have.property('local');
|
||||
|
||||
const { extraInformation } = instance.instanceRecord;
|
||||
|
||||
expect(extraInformation).to.have.property('host');
|
||||
expect(extraInformation).to.have.property('port');
|
||||
expect(extraInformation).to.have.property('os').and.to.have.property('cpus').to.be.a('number');
|
||||
expect(extraInformation).to.have.property('nodeVersion');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/shield.svg]', () => {
|
||||
before(() => updateSetting('API_Enable_Shields', false));
|
||||
|
||||
after(() => updateSetting('API_Enable_Shields', true));
|
||||
|
||||
it('should fail if API_Enable_Shields is disabled', async () => {
|
||||
const res = await request
|
||||
.get(api('shield.svg'))
|
||||
.query({
|
||||
type: 'online',
|
||||
icon: true,
|
||||
channel: 'general',
|
||||
name: 'Rocket.Chat',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('errorType', 'error-endpoint-disabled');
|
||||
});
|
||||
|
||||
it('should succeed if API_Enable_Shields is enabled', async () => {
|
||||
await updateSetting('API_Enable_Shields', true);
|
||||
|
||||
await request
|
||||
.get(api('shield.svg'))
|
||||
.query({
|
||||
type: 'online',
|
||||
icon: true,
|
||||
channel: 'general',
|
||||
name: 'Rocket.Chat',
|
||||
})
|
||||
.expect('Content-Type', 'image/svg+xml;charset=utf-8')
|
||||
.expect(200);
|
||||
});
|
||||
});
|
||||
|
||||
describe('/pw.getPolicy', () => {
|
||||
it('should return policies', async () => {
|
||||
const res = await request.get(api('pw.getPolicy')).set(credentials).expect('Content-Type', 'application/json').expect(200);
|
||||
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('enabled');
|
||||
expect(res.body).to.have.property('policy').and.to.be.an('array');
|
||||
});
|
||||
});
|
||||
|
||||
describe('/fingerprint', () => {
|
||||
let unauthorizedUser: TestUser<IUser>;
|
||||
let unauthorizedUserCredentials: Credentials;
|
||||
|
||||
before(async () => {
|
||||
unauthorizedUser = await createUser();
|
||||
unauthorizedUserCredentials = await doLogin(unauthorizedUser.username, password);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await deleteUser(unauthorizedUser);
|
||||
});
|
||||
|
||||
it('should return 401 when called without authentication', async () => {
|
||||
const res = await request.post(api('fingerprint')).send({ setDeploymentAs: 'updated-configuration' });
|
||||
|
||||
expect(res.status).to.equal(401);
|
||||
expect(res.body).to.have.property('status', 'error');
|
||||
});
|
||||
|
||||
it('should return 403 when a user without the manage-cloud permission tries to acknowledge a deployment configuration change', async () => {
|
||||
const res = await request
|
||||
.post(api('fingerprint'))
|
||||
.set(unauthorizedUserCredentials)
|
||||
.send({ setDeploymentAs: 'updated-configuration' });
|
||||
|
||||
expect(res.status).to.equal(403);
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error', 'User does not have the permissions required for this action [error-unauthorized]');
|
||||
});
|
||||
|
||||
it('should return 403 when a user without the manage-cloud permission tries to deregister the workspace as a new workspace', async () => {
|
||||
const res = await request.post(api('fingerprint')).set(unauthorizedUserCredentials).send({ setDeploymentAs: 'new-workspace' });
|
||||
|
||||
expect(res.status).to.equal(403);
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error', 'User does not have the permissions required for this action [error-unauthorized]');
|
||||
});
|
||||
|
||||
it('should return 200 when a user with the manage-cloud permission acknowledges a deployment configuration change', async () => {
|
||||
const res = await request.post(api('fingerprint')).set(credentials).send({ setDeploymentAs: 'updated-configuration' });
|
||||
|
||||
expect(res.status).to.equal(200);
|
||||
expect(res.body).to.have.property('success', true);
|
||||
});
|
||||
});
|
||||
});
|
||||
814
apps/meteor/tests/end-to-end/api/moderation.ts
Normal file
814
apps/meteor/tests/end-to-end/api/moderation.ts
Normal file
@@ -0,0 +1,814 @@
|
||||
import type { IMessage, IModerationAudit, IModerationReport, IUser } from '@rocket.chat/core-typings';
|
||||
import { expect } from 'chai';
|
||||
import { after, before, describe, it } from 'mocha';
|
||||
import type { Response } from 'supertest';
|
||||
|
||||
import { getCredentials, api, request, credentials } from '../../data/api-data';
|
||||
import { createUser, deleteUser } from '../../data/users.helper';
|
||||
|
||||
const makeModerationApiRequest = async (
|
||||
url: 'moderation.reportUser' | 'moderation.user.reportsByUserId',
|
||||
method: 'get' | 'post',
|
||||
data?: any,
|
||||
) => {
|
||||
let res: any;
|
||||
|
||||
if (method === 'get') {
|
||||
res = await request.get(api(url)).set(credentials).query(data);
|
||||
} else if (method === 'post') {
|
||||
res = await request.post(api(url)).set(credentials).send(data);
|
||||
}
|
||||
|
||||
return res.body;
|
||||
};
|
||||
|
||||
const reportUser = (userId: string, reason: string) => makeModerationApiRequest('moderation.reportUser', 'post', { userId, reason });
|
||||
|
||||
const getUsersReports = (userId: string) => makeModerationApiRequest('moderation.user.reportsByUserId', 'get', { userId });
|
||||
|
||||
describe('[Moderation]', () => {
|
||||
before((done) => getCredentials(done));
|
||||
|
||||
describe('[/moderation.reportsByUsers]', () => {
|
||||
it('should return an array of reports', async () => {
|
||||
await request
|
||||
.get(api('moderation.reportsByUsers'))
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('reports').and.to.be.an('array');
|
||||
});
|
||||
});
|
||||
|
||||
it('should return an array of reports even requested with count and offset params', async () => {
|
||||
await request
|
||||
.get(api('moderation.reportsByUsers'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
count: 5,
|
||||
offset: 0,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('reports').and.to.be.an('array');
|
||||
});
|
||||
});
|
||||
|
||||
it('should return an array of reports even requested with oldest param', async () => {
|
||||
await request
|
||||
.get(api('moderation.reportsByUsers'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
oldest: new Date(),
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('reports').and.to.be.an('array');
|
||||
});
|
||||
});
|
||||
|
||||
it('should return an array of reports even requested with latest param', async () => {
|
||||
await request
|
||||
.get(api('moderation.reportsByUsers'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
latest: new Date(),
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('reports').and.to.be.an('array');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/moderation.userReports]', () => {
|
||||
it('should return an array of reports', async () => {
|
||||
await request
|
||||
.get(api('moderation.userReports'))
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('reports').and.to.be.an('array');
|
||||
});
|
||||
});
|
||||
|
||||
it('should return an array of reports even requested with count and offset params', async () => {
|
||||
await request
|
||||
.get(api('moderation.userReports'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
count: 5,
|
||||
offset: 0,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('reports').and.to.be.an('array');
|
||||
});
|
||||
});
|
||||
|
||||
it('should return an array of reports even requested with oldest param', async () => {
|
||||
await request
|
||||
.get(api('moderation.userReports'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
oldest: new Date(),
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('reports').and.to.be.an('array');
|
||||
});
|
||||
});
|
||||
|
||||
it('should return an array of reports even requested with latest param', async () => {
|
||||
await request
|
||||
.get(api('moderation.userReports'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
latest: new Date(),
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('reports').and.to.be.an('array');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// test for testing out the moderation.dismissReports endpoint
|
||||
|
||||
describe('[/moderation.dismissReports]', () => {
|
||||
let reportedMessage: IModerationAudit;
|
||||
let message: IMessage;
|
||||
|
||||
// post a new message to the channel 'general' by sending a request to chat.postMessage
|
||||
before(async () => {
|
||||
await request
|
||||
.post(api('chat.sendMessage'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
message: {
|
||||
rid: 'GENERAL',
|
||||
msg: 'Sample message 0',
|
||||
},
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
message = res.body.message;
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await request
|
||||
.post(api('chat.reportMessage'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
messageId: message._id,
|
||||
description: 'sample report',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await request
|
||||
.get(api('moderation.reportsByUsers'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
count: 5,
|
||||
offset: 0,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('reports').and.to.be.an('array');
|
||||
reportedMessage = res.body.reports[0];
|
||||
});
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await request
|
||||
.post(api('chat.delete'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
roomId: 'GENERAL',
|
||||
msgId: message._id,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should hide reports of a user', async () => {
|
||||
await request
|
||||
.post(api('moderation.dismissReports'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
userId: reportedMessage.userId,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should hide reports of a message', async () => {
|
||||
await request
|
||||
.post(api('moderation.dismissReports'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
msgId: message._id,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return an error when the userId && msgId is not provided', async () => {
|
||||
await request
|
||||
.post(api('moderation.dismissReports'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
userId: '',
|
||||
msgId: '',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error').and.to.be.a('string');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/moderation.user.reportsByUserId]', () => {
|
||||
let reportedUser: IUser;
|
||||
|
||||
before(async () => {
|
||||
reportedUser = await createUser();
|
||||
await reportUser(reportedUser._id, 'sample report');
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await deleteUser(reportedUser);
|
||||
});
|
||||
|
||||
it('should return an array of reports', async () => {
|
||||
await request
|
||||
.get(api('moderation.user.reportsByUserId'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
userId: reportedUser._id,
|
||||
count: 5,
|
||||
offset: 0,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect(async (res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('reports').and.to.be.an('array').and.to.have.lengthOf(1);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return an error when the userId is not provided', async () => {
|
||||
await request
|
||||
.get(api('moderation.user.reportsByUserId'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
userId: '',
|
||||
count: 5,
|
||||
offset: 0,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.expect(async (res: Response) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error');
|
||||
expect(res.body).to.have.property('errorType', 'invalid-params');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/moderation.dismissUserReports', () => {
|
||||
let reportedUser: IUser;
|
||||
|
||||
before(async () => {
|
||||
reportedUser = await createUser();
|
||||
await reportUser(reportedUser._id, 'sample report');
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await deleteUser(reportedUser);
|
||||
});
|
||||
|
||||
it('should hide reports of a user', async () => {
|
||||
await request
|
||||
.post(api('moderation.dismissUserReports'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
userId: reportedUser._id,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
});
|
||||
|
||||
await getUsersReports(reportedUser._id).then((res) => {
|
||||
expect(res.reports).to.have.lengthOf(0);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return an error when the userId is not provided', async () => {
|
||||
await request
|
||||
.post(api('moderation.dismissUserReports'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
userId: '',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error').and.to.be.a('string');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// test for testing out the moderation.reports endpoint
|
||||
|
||||
describe('[/moderation.reports]', () => {
|
||||
let message: IMessage;
|
||||
|
||||
// post a new message to the channel 'general' by sending a request to chat.postMessage
|
||||
before(async () => {
|
||||
await request
|
||||
.post(api('chat.postMessage'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
channel: 'general',
|
||||
text: 'messageId',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('message').and.to.be.an('object');
|
||||
message = res.body.message;
|
||||
});
|
||||
});
|
||||
|
||||
// create a reported message by sending a request to chat.reportMessage
|
||||
before(async () => {
|
||||
await request
|
||||
.post(api('chat.reportMessage'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
messageId: message._id,
|
||||
description: 'sample report',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
});
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await request
|
||||
.post(api('chat.delete'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
roomId: 'GENERAL',
|
||||
msgId: message._id,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return the reports for a message', async () => {
|
||||
await request
|
||||
.get(api('moderation.reports'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
msgId: message._id,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('reports').and.to.be.an('array');
|
||||
});
|
||||
});
|
||||
|
||||
it('should return an error when the msgId is not provided', async () => {
|
||||
await request
|
||||
.get(api('moderation.reports'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
msgId: '',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error').and.to.be.a('string');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// test for testing out the moderation.reportInfo endpoint
|
||||
|
||||
describe('[/moderation.reportInfo]', () => {
|
||||
let message: IMessage;
|
||||
let reportedMessage: IModerationReport;
|
||||
|
||||
// post a new message to the channel 'general' by sending a request to chat.postMessage
|
||||
before(async () => {
|
||||
await request
|
||||
.post(api('chat.postMessage'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
channel: 'general',
|
||||
text: 'messageId',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('message').and.to.be.an('object');
|
||||
message = res.body.message;
|
||||
});
|
||||
});
|
||||
|
||||
// create a reported message by sending a request to chat.reportMessage
|
||||
before(async () => {
|
||||
await request
|
||||
.post(api('chat.reportMessage'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
messageId: message._id,
|
||||
description: 'sample report',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
});
|
||||
});
|
||||
|
||||
// get the report information by sending a request to moderation.reports
|
||||
before(async () => {
|
||||
await request
|
||||
.get(api('moderation.reports'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
msgId: message._id,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('reports').and.to.be.an('array');
|
||||
reportedMessage = res.body.reports[0];
|
||||
});
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await request
|
||||
.post(api('chat.delete'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
roomId: 'GENERAL',
|
||||
msgId: message._id,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return the report information', async () => {
|
||||
await request
|
||||
.get(api('moderation.reportInfo'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
reportId: reportedMessage._id,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('report').and.to.be.an('object');
|
||||
expect(res.body.report).to.have.property('_id', reportedMessage._id);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return an error when the reportId is not provided', async () => {
|
||||
await request
|
||||
.get(api('moderation.reportInfo'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
reportId: '',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error').and.to.be.a('string');
|
||||
});
|
||||
});
|
||||
|
||||
it('should return an error when the reportId is invalid', async () => {
|
||||
await request
|
||||
.get(api('moderation.reportInfo'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
reportId: 'invalid',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error').and.to.be.a('string');
|
||||
});
|
||||
});
|
||||
|
||||
it('should return an error when the reportId is not found', async () => {
|
||||
await request
|
||||
.get(api('moderation.reportInfo'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
reportId: '123456789012345678901234',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error').and.to.be.a('string');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// test for testing out the moderation.user.reportedMessages endpoint
|
||||
|
||||
describe('[/moderation.user.reportedMessages]', () => {
|
||||
let message: IMessage;
|
||||
let reportedMessage: IModerationReport;
|
||||
|
||||
// post a new message to the channel 'general' by sending a request to chat.postMessage
|
||||
before(async () => {
|
||||
await request
|
||||
.post(api('chat.postMessage'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
channel: 'general',
|
||||
text: 'messageId',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('message').and.to.be.an('object');
|
||||
message = res.body.message;
|
||||
});
|
||||
});
|
||||
|
||||
// create a reported message by sending a request to chat.reportMessage
|
||||
before(async () => {
|
||||
await request
|
||||
.post(api('chat.reportMessage'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
messageId: message._id,
|
||||
description: 'sample report',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
});
|
||||
});
|
||||
|
||||
// get the report information by sending a request to moderation.reports
|
||||
before(async () => {
|
||||
await request
|
||||
.get(api('moderation.reports'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
msgId: message._id,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('reports').and.to.be.an('array');
|
||||
reportedMessage = res.body.reports[0];
|
||||
});
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await request
|
||||
.post(api('chat.delete'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
roomId: 'GENERAL',
|
||||
msgId: message._id,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return the message history', async () => {
|
||||
await request
|
||||
.get(api('moderation.user.reportedMessages'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
userId: reportedMessage.reportedBy._id,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('messages').and.to.be.an('array');
|
||||
});
|
||||
});
|
||||
|
||||
it('should return an error when the userId is not provided', async () => {
|
||||
await request
|
||||
.get(api('moderation.user.reportedMessages'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
userId: '',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error').and.to.be.a('string');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// test for testing out the moderation.user.deleteReportedMessages endpoint
|
||||
|
||||
describe('[/moderation.user.deleteReportedMessages]', () => {
|
||||
let message: IMessage;
|
||||
let reportedMessage: IModerationReport;
|
||||
|
||||
// post a new message to the channel 'general' by sending a request to chat.postMessage
|
||||
before(async () => {
|
||||
await request
|
||||
.post(api('chat.postMessage'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
channel: 'general',
|
||||
text: 'messageId',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('message').and.to.be.an('object');
|
||||
message = res.body.message;
|
||||
});
|
||||
});
|
||||
|
||||
// create a reported message by sending a request to chat.reportMessage
|
||||
before(async () => {
|
||||
await request
|
||||
.post(api('chat.reportMessage'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
messageId: message._id,
|
||||
description: 'sample report',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
});
|
||||
});
|
||||
|
||||
// get the report information by sending a request to moderation.reports
|
||||
before(async () => {
|
||||
await request
|
||||
.get(api('moderation.reports'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
msgId: message._id,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('reports').and.to.be.an('array');
|
||||
reportedMessage = res.body.reports[0];
|
||||
});
|
||||
});
|
||||
|
||||
it('should delete the message history', async () => {
|
||||
await request
|
||||
.post(api('moderation.user.deleteReportedMessages'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
userId: reportedMessage.reportedBy._id,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return an error when the userId is not provided', async () => {
|
||||
await request
|
||||
.post(api('moderation.user.deleteReportedMessages'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
userId: '',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error').and.to.be.a('string');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/moderation.reportUser]', () => {
|
||||
let userToBeReported: IUser;
|
||||
|
||||
before(async () => {
|
||||
userToBeReported = await createUser();
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await deleteUser(userToBeReported);
|
||||
});
|
||||
|
||||
it('should report an user', async () => {
|
||||
await request
|
||||
.post(api('moderation.reportUser'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
userId: userToBeReported?._id,
|
||||
description: 'sample report',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail to report an user if not provided description', async () => {
|
||||
await request
|
||||
.post(api('moderation.reportUser'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
userId: userToBeReported?._id,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error');
|
||||
expect(res.body).to.have.property('errorType', 'invalid-params');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
426
apps/meteor/tests/end-to-end/api/oauth-server.ts
Normal file
426
apps/meteor/tests/end-to-end/api/oauth-server.ts
Normal file
@@ -0,0 +1,426 @@
|
||||
import { expect } from 'chai';
|
||||
import { after, before, describe, it } from 'mocha';
|
||||
import type { Response } from 'supertest';
|
||||
|
||||
import { getCredentials, api, request, credentials } from '../../data/api-data';
|
||||
import { password } from '../../data/user';
|
||||
import { createUser, deleteUser, login } from '../../data/users.helper';
|
||||
|
||||
async function authorizeAndExchange(loginToken: string, cId: string, cSecret: string, redirectUri: string) {
|
||||
const authRes = await request
|
||||
.post(`/oauth/authorize`)
|
||||
.type('form')
|
||||
.send({
|
||||
token: loginToken,
|
||||
client_id: cId,
|
||||
response_type: 'code',
|
||||
redirect_uri: redirectUri,
|
||||
state: 'test-state',
|
||||
allow: 'yes',
|
||||
})
|
||||
.expect(302);
|
||||
|
||||
const location = new URL(authRes.headers.location);
|
||||
const code = location.searchParams.get('code') as string;
|
||||
|
||||
const tokenRes = await request
|
||||
.post(`/oauth/token`)
|
||||
.type('form')
|
||||
.send({
|
||||
grant_type: 'authorization_code',
|
||||
code,
|
||||
client_id: cId,
|
||||
client_secret: cSecret,
|
||||
redirect_uri: redirectUri,
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
return { accessToken: tokenRes.body.access_token as string, refreshToken: tokenRes.body.refresh_token as string };
|
||||
}
|
||||
|
||||
describe('[OAuth Server]', () => {
|
||||
let oAuthAppId: string;
|
||||
let clientId: string;
|
||||
let clientSecret: string;
|
||||
let code: string;
|
||||
let refreshToken: string;
|
||||
let accessToken: string;
|
||||
let refreshedAccessToken: string;
|
||||
const redirectUri = 'http://asd.com';
|
||||
|
||||
before((done) => getCredentials(done));
|
||||
|
||||
after(async () => {
|
||||
await request
|
||||
.post(api('oauth-apps.delete'))
|
||||
.set(credentials)
|
||||
.send({ appId: oAuthAppId })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
});
|
||||
|
||||
describe('[/oauth-apps.create]', () => {
|
||||
it('should create the oauth app', async () => {
|
||||
const data = {
|
||||
name: 'api-test',
|
||||
redirectUri: 'http://test.com,http://asd.com',
|
||||
active: true,
|
||||
};
|
||||
|
||||
await request
|
||||
.post(api('oauth-apps.create'))
|
||||
.set(credentials)
|
||||
.send(data)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('application');
|
||||
expect(res.body.application).to.have.property('_id');
|
||||
expect(res.body.application).to.have.property('name', data.name);
|
||||
expect(res.body.application).to.have.property('redirectUri', data.redirectUri);
|
||||
expect(res.body.application).to.have.property('active', data.active);
|
||||
expect(res.body.application).to.have.property('clientId');
|
||||
expect(res.body.application).to.have.property('clientSecret');
|
||||
oAuthAppId = res.body.application._id;
|
||||
clientId = res.body.application.clientId;
|
||||
clientSecret = res.body.application.clientSecret;
|
||||
});
|
||||
});
|
||||
|
||||
it('should authorize oauth to retrieve code', async () => {
|
||||
const params = new URLSearchParams({
|
||||
scope: 'user',
|
||||
response_type: 'token,code',
|
||||
response_mode: 'form_post',
|
||||
state: 'xus2t6ix57g',
|
||||
});
|
||||
|
||||
await request
|
||||
.post(`/oauth/authorize?${params.toString()}`)
|
||||
.type('form')
|
||||
.send({
|
||||
token: credentials['X-Auth-Token'],
|
||||
client_id: clientId,
|
||||
response_type: 'code',
|
||||
redirect_uri: redirectUri,
|
||||
allow: 'yes',
|
||||
})
|
||||
.expect(302)
|
||||
.expect((res: Response) => {
|
||||
expect(res.headers).to.have.property('location');
|
||||
const location = new URL(res.headers.location);
|
||||
expect(location.origin).to.be.equal(redirectUri);
|
||||
expect(location.searchParams.get('code')).to.be.string;
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
code = location.searchParams.get('code')!;
|
||||
});
|
||||
});
|
||||
|
||||
it('should use code to retrieve access_token', async () => {
|
||||
await request
|
||||
.post(`/oauth/token`)
|
||||
.type('form')
|
||||
.send({
|
||||
grant_type: 'authorization_code',
|
||||
code,
|
||||
client_id: clientId,
|
||||
client_secret: clientSecret,
|
||||
redirect_uri: redirectUri,
|
||||
})
|
||||
.expect('Content-Type', 'application/json; charset=utf-8')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('token_type', 'Bearer');
|
||||
expect(res.body).to.have.property('access_token');
|
||||
expect(res.body).to.have.property('expires_in');
|
||||
expect(res.body).to.have.property('refresh_token');
|
||||
accessToken = res.body.access_token;
|
||||
refreshToken = res.body.refresh_token;
|
||||
});
|
||||
});
|
||||
|
||||
it('should return bad request if payload has non string parameters in refresh_token grant', async () => {
|
||||
await request
|
||||
.post(`/oauth/token`)
|
||||
.send({
|
||||
grant_type: 'refresh_token',
|
||||
client_id: { $ne: null },
|
||||
client_secret: { $ne: null },
|
||||
refresh_token: { $ne: null },
|
||||
})
|
||||
.expect((res: Response) => {
|
||||
expect(res.status).to.be.equal(400);
|
||||
expect(res.body).to.have.property('error').that.is.a('string').and.equal('invalid_request');
|
||||
});
|
||||
});
|
||||
|
||||
it('should return bad request if payload has non string parameters in authorization_code grant', async () => {
|
||||
await request
|
||||
.post(`/oauth/token`)
|
||||
.send({
|
||||
grant_type: 'authorization_code',
|
||||
client_id: { $ne: null },
|
||||
client_secret: { $ne: null },
|
||||
code: { $ne: null },
|
||||
redirect_uri: { $ne: null },
|
||||
})
|
||||
.expect((res: Response) => {
|
||||
expect(res.status).to.be.equal(400);
|
||||
expect(res.body).to.have.property('error').that.is.a('string').and.equal('invalid_request');
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to refresh the access_token', async () => {
|
||||
await request
|
||||
.post(`/oauth/token`)
|
||||
.type('form')
|
||||
.send({
|
||||
grant_type: 'refresh_token',
|
||||
refresh_token: refreshToken,
|
||||
client_id: clientId,
|
||||
client_secret: clientSecret,
|
||||
})
|
||||
.expect('Content-Type', 'application/json; charset=utf-8')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('token_type', 'Bearer');
|
||||
expect(res.body).to.have.property('access_token').and.not.be.equal(accessToken);
|
||||
expect(res.body).to.have.property('expires_in');
|
||||
expect(res.body).to.have.property('refresh_token');
|
||||
refreshedAccessToken = res.body.access_token;
|
||||
});
|
||||
});
|
||||
|
||||
it('should not be able to get user info with old access_token', async () => {
|
||||
await request.get(`/oauth/userinfo`).auth(accessToken, { type: 'bearer' }).expect(401);
|
||||
});
|
||||
|
||||
it('should be able to get user info with refreshed access_token', async () => {
|
||||
await request
|
||||
.get(`/oauth/userinfo`)
|
||||
.auth(refreshedAccessToken, { type: 'bearer' })
|
||||
.expect('Content-Type', 'application/json; charset=utf-8')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('sub', 'rocketchat.internal.admin.test');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('[oauth credentials]', () => {
|
||||
let accessToken: string;
|
||||
|
||||
before(() => {
|
||||
accessToken = refreshedAccessToken;
|
||||
});
|
||||
|
||||
it('should be able to use oauth credentials to access v1 endpoints (/v1/me)', async () => {
|
||||
await request
|
||||
.get(api('me'))
|
||||
.auth(accessToken, { type: 'bearer' })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('_id', 'rocketchat.internal.admin.test');
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to use oauth credentials to access v1 endpoints (/v1/users.info)', async () => {
|
||||
await request
|
||||
.get(api('users.info'))
|
||||
.query({ username: 'rocketchat.internal.admin.test' })
|
||||
.auth(accessToken, { type: 'bearer' })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.nested.property('user._id', 'rocketchat.internal.admin.test');
|
||||
});
|
||||
});
|
||||
|
||||
const malformedTokenPayloads = [
|
||||
{ query: { 'access_token[$ne]': 'null' }, description: '$ne operator' },
|
||||
{ query: { 'access_token[$exists]': 'true' }, description: '$exists operator' },
|
||||
{ query: { 'access_token[$gt]': '' }, description: '$gt operator' },
|
||||
{ query: { 'access_token[$regex]': '.*' }, description: '$regex operator' },
|
||||
{ query: { access_token: 'invalid-token' }, description: 'invalid string token' },
|
||||
];
|
||||
|
||||
malformedTokenPayloads.forEach(({ query, description }) => {
|
||||
it(`should reject access_token with ${description}`, async () => {
|
||||
await request
|
||||
.get(api('me'))
|
||||
.query(query)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(401)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('status', 'error');
|
||||
expect(res.body).to.have.property('message', 'You must be logged in to do this.');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('[user deactivation revokes OAuth tokens]', () => {
|
||||
let testUser: Awaited<ReturnType<typeof createUser>>;
|
||||
let testUserCredentials: { 'X-Auth-Token': string; 'X-User-Id': string };
|
||||
let deactivationClientId: string;
|
||||
let deactivationClientSecret: string;
|
||||
let deactivationAppId: string;
|
||||
let userAccessToken: string;
|
||||
let userRefreshToken: string;
|
||||
const redirectUri = 'http://asd.com';
|
||||
|
||||
before(async () => {
|
||||
testUser = await createUser();
|
||||
testUserCredentials = await login(testUser.username, password);
|
||||
|
||||
const appRes = await request
|
||||
.post(api('oauth-apps.create'))
|
||||
.set(credentials)
|
||||
.send({ name: 'deactivation-test-app', redirectUri: `http://test.com,${redirectUri}`, active: true })
|
||||
.expect(200);
|
||||
|
||||
deactivationAppId = appRes.body.application._id;
|
||||
deactivationClientId = appRes.body.application.clientId;
|
||||
deactivationClientSecret = appRes.body.application.clientSecret;
|
||||
|
||||
const tokens = await authorizeAndExchange(
|
||||
testUserCredentials['X-Auth-Token'],
|
||||
deactivationClientId,
|
||||
deactivationClientSecret,
|
||||
redirectUri,
|
||||
);
|
||||
userAccessToken = tokens.accessToken;
|
||||
userRefreshToken = tokens.refreshToken;
|
||||
|
||||
// Verify tokens work before deactivation
|
||||
await request.get(api('me')).auth(userAccessToken, { type: 'bearer' }).expect(200);
|
||||
|
||||
// Deactivate the user
|
||||
await request.post(api('users.setActiveStatus')).set(credentials).send({ userId: testUser._id, activeStatus: false }).expect(200);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await request.post(api('oauth-apps.delete')).set(credentials).send({ appId: deactivationAppId }).expect(200);
|
||||
await deleteUser(testUser);
|
||||
});
|
||||
|
||||
it('should reject the access token after user deactivation', async () => {
|
||||
await request.get(api('me')).auth(userAccessToken, { type: 'bearer' }).expect(401);
|
||||
});
|
||||
|
||||
it('should reject the access token on /oauth/userinfo after user deactivation', async () => {
|
||||
await request.get(`/oauth/userinfo`).auth(userAccessToken, { type: 'bearer' }).expect(401);
|
||||
});
|
||||
|
||||
it('should reject the refresh token grant after user deactivation', async () => {
|
||||
await request
|
||||
.post(`/oauth/token`)
|
||||
.type('form')
|
||||
.send({
|
||||
grant_type: 'refresh_token',
|
||||
refresh_token: userRefreshToken,
|
||||
client_id: deactivationClientId,
|
||||
client_secret: deactivationClientSecret,
|
||||
})
|
||||
.expect((res: Response) => {
|
||||
expect(res.status).to.not.equal(200);
|
||||
expect(res.body).to.have.property('error');
|
||||
expect(res.body).to.not.have.property('access_token');
|
||||
});
|
||||
});
|
||||
|
||||
it('should still reject the access token after user reactivation (token was deleted, not just blocked)', async () => {
|
||||
await request.post(api('users.setActiveStatus')).set(credentials).send({ userId: testUser._id, activeStatus: true }).expect(200);
|
||||
|
||||
await request.get(api('me')).auth(userAccessToken, { type: 'bearer' }).expect(401);
|
||||
|
||||
// Reactivated user can obtain new tokens via a fresh OAuth flow
|
||||
const reactivatedCredentials = await login(testUser.username, password);
|
||||
const newTokens = await authorizeAndExchange(
|
||||
reactivatedCredentials['X-Auth-Token'],
|
||||
deactivationClientId,
|
||||
deactivationClientSecret,
|
||||
redirectUri,
|
||||
);
|
||||
|
||||
await request
|
||||
.get(api('me'))
|
||||
.auth(newTokens.accessToken, { type: 'bearer' })
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('_id', testUser._id);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('[users.deactivateIdle revokes OAuth tokens]', () => {
|
||||
let idleUser: Awaited<ReturnType<typeof createUser>>;
|
||||
let idleUserCredentials: { 'X-Auth-Token': string; 'X-User-Id': string };
|
||||
let idleClientId: string;
|
||||
let idleClientSecret: string;
|
||||
let idleAppId: string;
|
||||
let idleAccessToken: string;
|
||||
let idleRefreshToken: string;
|
||||
const redirectUri = 'http://asd.com';
|
||||
|
||||
before(async () => {
|
||||
idleUser = await createUser();
|
||||
idleUserCredentials = await login(idleUser.username, password);
|
||||
|
||||
const appRes = await request
|
||||
.post(api('oauth-apps.create'))
|
||||
.set(credentials)
|
||||
.send({ name: 'idle-deactivation-test-app', redirectUri: `http://test.com,${redirectUri}`, active: true })
|
||||
.expect(200);
|
||||
|
||||
idleAppId = appRes.body.application._id;
|
||||
idleClientId = appRes.body.application.clientId;
|
||||
idleClientSecret = appRes.body.application.clientSecret;
|
||||
|
||||
const tokens = await authorizeAndExchange(idleUserCredentials['X-Auth-Token'], idleClientId, idleClientSecret, redirectUri);
|
||||
idleAccessToken = tokens.accessToken;
|
||||
idleRefreshToken = tokens.refreshToken;
|
||||
|
||||
// Verify tokens work before deactivation
|
||||
await request.get(api('me')).auth(idleAccessToken, { type: 'bearer' }).expect(200);
|
||||
|
||||
// Deactivate via deactivateIdle using daysIdle=0 to catch all users with no recent login
|
||||
await request
|
||||
.post(api('users.deactivateIdle'))
|
||||
.set(credentials)
|
||||
.send({ daysIdle: 0, role: idleUser.roles?.[0] ?? 'user' })
|
||||
.expect(200);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await request.post(api('oauth-apps.delete')).set(credentials).send({ appId: idleAppId }).expect(200);
|
||||
await deleteUser(idleUser);
|
||||
});
|
||||
|
||||
it('should reject the access token after idle deactivation', async () => {
|
||||
await request.get(api('me')).auth(idleAccessToken, { type: 'bearer' }).expect(401);
|
||||
});
|
||||
|
||||
it('should reject the refresh token grant after idle deactivation', async () => {
|
||||
await request
|
||||
.post(`/oauth/token`)
|
||||
.type('form')
|
||||
.send({
|
||||
grant_type: 'refresh_token',
|
||||
refresh_token: idleRefreshToken,
|
||||
client_id: idleClientId,
|
||||
client_secret: idleClientSecret,
|
||||
})
|
||||
.expect((res: Response) => {
|
||||
expect(res.status).to.not.equal(200);
|
||||
expect(res.body).to.have.property('error');
|
||||
expect(res.body).to.not.have.property('access_token');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
420
apps/meteor/tests/end-to-end/api/oauthapps.ts
Normal file
420
apps/meteor/tests/end-to-end/api/oauthapps.ts
Normal file
@@ -0,0 +1,420 @@
|
||||
import type { IOAuthApps } from '@rocket.chat/core-typings';
|
||||
import { expect } from 'chai';
|
||||
import { before, describe, it, after } from 'mocha';
|
||||
|
||||
import { getCredentials, api, request, credentials } from '../../data/api-data';
|
||||
import { updatePermission } from '../../data/permissions.helper';
|
||||
|
||||
describe('[OAuthApps]', () => {
|
||||
const createdAppsIds: IOAuthApps['_id'][] = [];
|
||||
|
||||
before((done) => getCredentials(done));
|
||||
|
||||
after(() =>
|
||||
Promise.all([
|
||||
updatePermission('manage-oauth-apps', ['admin']),
|
||||
...createdAppsIds.map((appId) =>
|
||||
request.post(api(`oauth-apps.delete`)).set(credentials).send({
|
||||
appId,
|
||||
}),
|
||||
),
|
||||
]),
|
||||
);
|
||||
|
||||
describe('[/oauth-apps.list]', () => {
|
||||
it('should return an error when the user does not have the necessary permission', async () => {
|
||||
await updatePermission('manage-oauth-apps', []);
|
||||
|
||||
const res = await request.get(api('oauth-apps.list')).set(credentials).expect(403);
|
||||
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body.error).to.be.equal('User does not have the permissions required for this action [error-unauthorized]');
|
||||
});
|
||||
|
||||
it('should return an array of oauth apps', async () => {
|
||||
await updatePermission('manage-oauth-apps', ['admin']);
|
||||
|
||||
const res = await request.get(api('oauth-apps.list')).set(credentials).expect(200);
|
||||
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('oauthApps').and.to.be.an('array');
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/oauth-apps.create]', () => {
|
||||
it('should return an error when the user does not have the necessary permission', async () => {
|
||||
await updatePermission('manage-oauth-apps', []);
|
||||
|
||||
await request
|
||||
.post(api('oauth-apps.create'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
name: 'error',
|
||||
redirectUri: 'error',
|
||||
active: false,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(403)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body.error).to.be.equal('User does not have the permissions required for this action [error-unauthorized]');
|
||||
});
|
||||
await updatePermission('manage-oauth-apps', ['admin']);
|
||||
});
|
||||
|
||||
it("should return an error when the 'name' property is invalid", async () => {
|
||||
await request
|
||||
.post(api('oauth-apps.create'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
name: '',
|
||||
redirectUri: 'error',
|
||||
active: false,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('errorType', 'error-invalid-name');
|
||||
});
|
||||
});
|
||||
|
||||
it("should return an error when the 'redirectUri' property is invalid", async () => {
|
||||
await request
|
||||
.post(api('oauth-apps.create'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
name: 'error',
|
||||
redirectUri: '',
|
||||
active: false,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('errorType', 'error-invalid-redirectUri');
|
||||
});
|
||||
});
|
||||
|
||||
it("should return an error when the 'active' property is not a boolean", async () => {
|
||||
await request
|
||||
.post(api('oauth-apps.create'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
name: 'error',
|
||||
redirectUri: 'error',
|
||||
active: 'anything',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('errorType', 'invalid-params');
|
||||
});
|
||||
});
|
||||
|
||||
it('should create an oauthApp', async () => {
|
||||
const name = `new app ${Date.now()}`;
|
||||
const redirectUri = 'http://localhost:3000';
|
||||
const active = true;
|
||||
|
||||
await request
|
||||
.post(api('oauth-apps.create'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
name,
|
||||
redirectUri,
|
||||
active,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.nested.property('application.name', name);
|
||||
expect(res.body).to.have.nested.property('application.redirectUri', redirectUri);
|
||||
expect(res.body).to.have.nested.property('application.active', active);
|
||||
createdAppsIds.push(res.body.application._id);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/oauth-apps.get]', () => {
|
||||
let clientId = '';
|
||||
let _id = '';
|
||||
let clientSecret = '';
|
||||
|
||||
before(async () => {
|
||||
await updatePermission('manage-oauth-apps', ['admin']);
|
||||
|
||||
const res = await request
|
||||
.post(api('oauth-apps.create'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
name: `new app ${Date.now()}`,
|
||||
redirectUri: 'http://localhost:3000',
|
||||
active: true,
|
||||
});
|
||||
|
||||
if (res.statusCode !== 200 || !res.body?.success || !res.body.application?._id || !res.body.application?.clientId) {
|
||||
console.error(res);
|
||||
throw new Error('Failed to create oauth app for tests');
|
||||
}
|
||||
|
||||
clientId = res.body.application.clientId;
|
||||
_id = res.body.application._id;
|
||||
clientSecret = res.body.application.clientSecret;
|
||||
createdAppsIds.push(_id);
|
||||
});
|
||||
after(() => updatePermission('manage-oauth-apps', ['admin']));
|
||||
|
||||
it('should return a single oauthApp by client id', () => {
|
||||
return request
|
||||
.get(api('oauth-apps.get'))
|
||||
.query({ clientId })
|
||||
.set(credentials)
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('oauthApp');
|
||||
expect(res.body.oauthApp._id).to.be.equal(_id);
|
||||
expect(res.body.oauthApp).to.have.property('clientSecret');
|
||||
|
||||
if (clientSecret) {
|
||||
expect(res.body.oauthApp.clientSecret).to.be.equal(clientSecret);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should return a single oauthApp by _id', () => {
|
||||
return request
|
||||
.get(api('oauth-apps.get'))
|
||||
.query({ _id })
|
||||
.set(credentials)
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('oauthApp');
|
||||
expect(res.body.oauthApp._id).to.be.equal(_id);
|
||||
expect(res.body.oauthApp.clientId).to.be.equal(clientId);
|
||||
expect(res.body.oauthApp).to.have.property('clientSecret');
|
||||
if (clientSecret) {
|
||||
expect(res.body.oauthApp.clientSecret).to.be.equal(clientSecret);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should return only non sensitive information if user does not have the permission to manage oauth apps when searching by clientId', async () => {
|
||||
await updatePermission('manage-oauth-apps', []);
|
||||
await request
|
||||
.get(api('oauth-apps.get'))
|
||||
.query({ clientId })
|
||||
.set(credentials)
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('oauthApp');
|
||||
expect(res.body.oauthApp._id).to.be.equal(_id);
|
||||
expect(res.body.oauthApp.clientId).to.be.equal(clientId);
|
||||
expect(res.body.oauthApp).to.not.have.property('clientSecret');
|
||||
});
|
||||
});
|
||||
|
||||
it('should return only non sensitive information if user does not have the permission to manage oauth apps when searching by _id', async () => {
|
||||
await updatePermission('manage-oauth-apps', []);
|
||||
await request
|
||||
.get(api('oauth-apps.get'))
|
||||
.query({ _id })
|
||||
.set(credentials)
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('oauthApp');
|
||||
expect(res.body.oauthApp._id).to.be.equal(_id);
|
||||
expect(res.body.oauthApp.clientId).to.be.equal(clientId);
|
||||
expect(res.body.oauthApp).to.not.have.property('clientSecret');
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail returning an oauth app when an invalid id is provided (avoid NoSQL injections)', () => {
|
||||
return request
|
||||
.get(api('oauth-apps.get'))
|
||||
.query({ _id: { $ne: '' } })
|
||||
.set(credentials)
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('errorType', 'error-invalid-params');
|
||||
expect(res.body).to.have.property('error');
|
||||
expect(res.body.error).to.include('must be string').and.include('must match exactly one schema in oneOf');
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail returning an oauth app when an invalid id string is provided (avoid NoSQL injections)', () => {
|
||||
return request
|
||||
.get(api('oauth-apps.get'))
|
||||
.query({ _id: '{ "$ne": "" }' })
|
||||
.set(credentials)
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error', 'OAuth app not found.');
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail returning an oauth app when an invalid clientId is provided (avoid NoSQL injections)', () => {
|
||||
return request
|
||||
.get(api('oauth-apps.get'))
|
||||
.query({ clientId: { $ne: '' } })
|
||||
.set(credentials)
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('errorType', 'error-invalid-params');
|
||||
expect(res.body).to.have.property('error');
|
||||
expect(res.body.error).to.include('must be string').and.include('must match exactly one schema in oneOf');
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail returning an oauth app when an invalid clientId string is provided (avoid NoSQL injections)', () => {
|
||||
return request
|
||||
.get(api('oauth-apps.get'))
|
||||
.query({ clientId: '{ "$ne": "" }' })
|
||||
.set(credentials)
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error', 'OAuth app not found.');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/oauth-apps.update]', () => {
|
||||
let appId: IOAuthApps['_id'];
|
||||
|
||||
before(async () => {
|
||||
await updatePermission('manage-oauth-apps', ['admin']);
|
||||
const name = 'test-oauth-app';
|
||||
const redirectUri = 'https://test.com';
|
||||
const active = true;
|
||||
const res = await request
|
||||
.post(api('oauth-apps.create'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
name,
|
||||
redirectUri,
|
||||
active,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
appId = res.body.application._id;
|
||||
createdAppsIds.push(appId);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await updatePermission('manage-oauth-apps', ['admin']);
|
||||
});
|
||||
|
||||
it("should update an app's name, its Active and Redirect URI fields correctly by its id", async () => {
|
||||
const name = `new app ${Date.now()}`;
|
||||
const redirectUri = 'http://localhost:3000';
|
||||
const active = false;
|
||||
|
||||
await request
|
||||
.post(api(`oauth-apps.update`))
|
||||
.set(credentials)
|
||||
.send({
|
||||
appId,
|
||||
name,
|
||||
redirectUri,
|
||||
active,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('active', active);
|
||||
expect(res.body).to.have.property('redirectUri', redirectUri);
|
||||
expect(res.body).to.have.property('name', name);
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail updating an app if user does NOT have the manage-oauth-apps permission', async () => {
|
||||
const name = `new app ${Date.now()}`;
|
||||
const redirectUri = 'http://localhost:3000';
|
||||
const active = false;
|
||||
|
||||
await updatePermission('manage-oauth-apps', []);
|
||||
await request
|
||||
.post(api(`oauth-apps.update`))
|
||||
.set(credentials)
|
||||
.send({
|
||||
appId,
|
||||
name,
|
||||
redirectUri,
|
||||
active,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(403)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body.error).to.be.equal('User does not have the permissions required for this action [error-unauthorized]');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/oauth-apps.delete]', () => {
|
||||
let appId: IOAuthApps['_id'];
|
||||
|
||||
before(async () => {
|
||||
await updatePermission('manage-oauth-apps', ['admin']);
|
||||
const name = 'test-oauth-app';
|
||||
const redirectUri = 'https://test.com';
|
||||
const active = true;
|
||||
const res = await request
|
||||
.post(api('oauth-apps.create'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
name,
|
||||
redirectUri,
|
||||
active,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
appId = res.body.application._id;
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await updatePermission('manage-oauth-apps', ['admin']);
|
||||
});
|
||||
|
||||
it('should delete an app by its id', async () => {
|
||||
await request
|
||||
.post(api(`oauth-apps.delete`))
|
||||
.set(credentials)
|
||||
.send({
|
||||
appId,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.equals(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail deleting an app by its id if user does NOT have the manage-oauth-apps permission', async () => {
|
||||
await updatePermission('manage-oauth-apps', []);
|
||||
await request
|
||||
.post(api(`oauth-apps.delete`))
|
||||
.set(credentials)
|
||||
.send({
|
||||
appId,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(403)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body.error).to.be.equal('User does not have the permissions required for this action [error-unauthorized]');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
651
apps/meteor/tests/end-to-end/api/outgoing-integrations.ts
Normal file
651
apps/meteor/tests/end-to-end/api/outgoing-integrations.ts
Normal file
@@ -0,0 +1,651 @@
|
||||
import type { Credentials } from '@rocket.chat/api-client';
|
||||
import type { IIntegration, IUser } from '@rocket.chat/core-typings';
|
||||
import { assert, expect } from 'chai';
|
||||
import { after, afterEach, before, describe, it } from 'mocha';
|
||||
|
||||
import { getCredentials, api, request, credentials } from '../../data/api-data';
|
||||
import { createIntegration, removeIntegration } from '../../data/integration.helper';
|
||||
import { updatePermission } from '../../data/permissions.helper';
|
||||
import { password } from '../../data/user';
|
||||
import type { TestUser } from '../../data/users.helper';
|
||||
import { createUser, deleteUser, login } from '../../data/users.helper';
|
||||
|
||||
describe('[Outgoing Integrations]', () => {
|
||||
let integrationCreatedByAnUser: IIntegration;
|
||||
let user: TestUser<IUser>;
|
||||
let userCredentials: Credentials;
|
||||
|
||||
before((done) => getCredentials(done));
|
||||
|
||||
before(async () => {
|
||||
user = await createUser();
|
||||
userCredentials = await login(user.username, password);
|
||||
await updatePermission('manage-outgoing-integrations', ['user']);
|
||||
integrationCreatedByAnUser = await createIntegration(
|
||||
{
|
||||
type: 'webhook-outgoing',
|
||||
name: 'Guggy',
|
||||
enabled: true,
|
||||
username: 'rocket.cat',
|
||||
urls: ['http://text2gif.guggy.com/guggify'],
|
||||
scriptEnabled: false,
|
||||
channel: '#general',
|
||||
triggerWords: ['!guggy'],
|
||||
alias: 'guggy',
|
||||
avatar: 'http://res.guggy.com/logo_128.png',
|
||||
emoji: ':ghost:',
|
||||
event: 'sendMessage',
|
||||
},
|
||||
userCredentials,
|
||||
);
|
||||
});
|
||||
|
||||
before(async () => {
|
||||
await Promise.all([
|
||||
updatePermission('manage-incoming-integrations', []),
|
||||
updatePermission('manage-own-incoming-integrations', []),
|
||||
updatePermission('manage-own-outgoing-integrations', []),
|
||||
updatePermission('manage-outgoing-integrations', []),
|
||||
]);
|
||||
});
|
||||
|
||||
after(async () =>
|
||||
Promise.all([
|
||||
updatePermission('manage-incoming-integrations', ['admin']),
|
||||
updatePermission('manage-own-incoming-integrations', ['admin']),
|
||||
updatePermission('manage-own-outgoing-integrations', ['admin']),
|
||||
updatePermission('manage-outgoing-integrations', ['admin']),
|
||||
deleteUser(user),
|
||||
]),
|
||||
);
|
||||
|
||||
let integration: IIntegration;
|
||||
|
||||
describe('[/integrations.create]', () => {
|
||||
it('should return an error when the user DOES NOT have the permission "manage-outgoing-integrations" to add an outgoing integration', (done) => {
|
||||
void updatePermission('manage-outgoing-integrations', []).then(() => {
|
||||
void request
|
||||
.post(api('integrations.create'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
type: 'webhook-outgoing',
|
||||
name: 'Guggy',
|
||||
enabled: true,
|
||||
username: 'rocket.cat',
|
||||
urls: ['http://text2gif.guggy.com/guggify'],
|
||||
scriptEnabled: false,
|
||||
channel: '#general',
|
||||
triggerWords: ['!guggy'],
|
||||
alias: 'guggy',
|
||||
avatar: 'http://res.guggy.com/logo_128.png',
|
||||
emoji: ':ghost:',
|
||||
event: 'sendMessage',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('errorType', 'not_authorized');
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return an error when the user DOES NOT have the permission "manage-own-outgoing-integrations" to add an outgoing integration', (done) => {
|
||||
void updatePermission('manage-own-outgoing-integrations', []).then(() => {
|
||||
void request
|
||||
.post(api('integrations.create'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
type: 'webhook-outgoing',
|
||||
name: 'Guggy',
|
||||
enabled: true,
|
||||
username: 'rocket.cat',
|
||||
urls: ['http://text2gif.guggy.com/guggify'],
|
||||
scriptEnabled: false,
|
||||
channel: '#general',
|
||||
triggerWords: ['!guggy'],
|
||||
alias: 'guggy',
|
||||
avatar: 'http://res.guggy.com/logo_128.png',
|
||||
emoji: ':ghost:',
|
||||
event: 'sendMessage',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('errorType', 'not_authorized');
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return an error when the user sends an invalid type of integration', (done) => {
|
||||
void request
|
||||
.post(api('integrations.create'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
type: 'webhook-outgoing-invalid',
|
||||
name: 'Guggy',
|
||||
enabled: true,
|
||||
username: 'rocket.cat',
|
||||
urls: ['http://text2gif.guggy.com/guggify'],
|
||||
scriptEnabled: false,
|
||||
channel: '#general',
|
||||
triggerWords: ['!guggy'],
|
||||
alias: 'guggy',
|
||||
avatar: 'http://res.guggy.com/logo_128.png',
|
||||
emoji: ':ghost:',
|
||||
event: 'sendMessage',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error', 'Invalid integration type.');
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
|
||||
it('should add the integration successfully when the user ONLY has the permission "manage-outgoing-integrations" to add an outgoing integration', (done) => {
|
||||
let integrationId: IIntegration['_id'];
|
||||
void updatePermission('manage-outgoing-integrations', ['admin']).then(() => {
|
||||
void request
|
||||
.post(api('integrations.create'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
type: 'webhook-outgoing',
|
||||
name: 'Guggy',
|
||||
enabled: true,
|
||||
username: 'rocket.cat',
|
||||
urls: ['http://text2gif.guggy.com/guggify'],
|
||||
scriptEnabled: false,
|
||||
channel: '#general',
|
||||
triggerWords: ['!guggy'],
|
||||
alias: 'guggy',
|
||||
avatar: 'http://res.guggy.com/logo_128.png',
|
||||
emoji: ':ghost:',
|
||||
event: 'sendMessage',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('integration').and.to.be.an('object');
|
||||
integrationId = res.body.integration._id;
|
||||
})
|
||||
.end(() => removeIntegration(integrationId, 'outgoing').then(done));
|
||||
});
|
||||
});
|
||||
|
||||
it('should add the integration successfully when the user ONLY has the permission "manage-own-outgoing-integrations" to add an outgoing integration', (done) => {
|
||||
void updatePermission('manage-outgoing-integrations', []).then(() => {
|
||||
void updatePermission('manage-own-outgoing-integrations', ['admin']).then(() => {
|
||||
void request
|
||||
.post(api('integrations.create'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
type: 'webhook-outgoing',
|
||||
name: 'Guggy',
|
||||
enabled: true,
|
||||
username: 'rocket.cat',
|
||||
urls: ['http://text2gif.guggy.com/guggify'],
|
||||
scriptEnabled: false,
|
||||
channel: '#general',
|
||||
triggerWords: ['!guggy'],
|
||||
alias: 'guggy',
|
||||
avatar: 'http://res.guggy.com/logo_128.png',
|
||||
emoji: ':ghost:',
|
||||
event: 'sendMessage',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('integration').and.to.be.an('object');
|
||||
integration = res.body.integration;
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should create an outgoing integration successfully', (done) => {
|
||||
let integrationId: IIntegration['_id'];
|
||||
void request
|
||||
.post(api('integrations.create'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
type: 'webhook-outgoing',
|
||||
name: 'Guggy',
|
||||
enabled: true,
|
||||
username: 'rocket.cat',
|
||||
urls: ['http://text2gif.guggy.com/guggify'],
|
||||
scriptEnabled: false,
|
||||
channel: '#general',
|
||||
triggerWords: ['!guggy'],
|
||||
alias: 'guggy',
|
||||
avatar: 'http://res.guggy.com/logo_128.png',
|
||||
emoji: ':ghost:',
|
||||
event: 'sendMessage',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.nested.property('integration.name', 'Guggy');
|
||||
expect(res.body).to.have.nested.property('integration.type', 'webhook-outgoing');
|
||||
expect(res.body).to.have.nested.property('integration.enabled', true);
|
||||
expect(res.body).to.have.nested.property('integration.username', 'rocket.cat');
|
||||
expect(res.body).to.have.nested.property('integration.event', 'sendMessage');
|
||||
integrationId = res.body.integration._id;
|
||||
})
|
||||
.end(() => removeIntegration(integrationId, 'outgoing').then(done));
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/integrations.list]', () => {
|
||||
it('should return the list of outgoing integrations', (done) => {
|
||||
void request
|
||||
.get(api('integrations.list'))
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
const integrationCreatedByAdmin = (res.body.integrations as IIntegration[]).find(
|
||||
(createdIntegration) => createdIntegration._id === integration._id,
|
||||
);
|
||||
assert.isDefined(integrationCreatedByAdmin);
|
||||
expect(integrationCreatedByAdmin).to.be.an('object');
|
||||
expect(integrationCreatedByAdmin._id).to.be.equal(integration._id);
|
||||
expect(res.body).to.have.property('offset');
|
||||
expect(res.body).to.have.property('items');
|
||||
expect(res.body).to.have.property('total');
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
|
||||
it('should return the list create by the user only', (done) => {
|
||||
void updatePermission('manage-outgoing-integrations', []).then(() => {
|
||||
void updatePermission('manage-own-outgoing-integrations', ['user']).then(() => {
|
||||
void request
|
||||
.get(api('integrations.list'))
|
||||
.set(userCredentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
const integrationCreatedByAdmin = (res.body.integrations as IIntegration[]).find(
|
||||
(createdIntegration) => createdIntegration._id === integration._id,
|
||||
);
|
||||
expect(integrationCreatedByAdmin).to.be.equal(undefined);
|
||||
expect(res.body).to.have.property('offset');
|
||||
expect(res.body).to.have.property('items');
|
||||
expect(res.body).to.have.property('total');
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should return unauthorized error when the user does not have any integrations permissions', async () => {
|
||||
await Promise.all([
|
||||
updatePermission('manage-incoming-integrations', []),
|
||||
updatePermission('manage-own-incoming-integrations', []),
|
||||
updatePermission('manage-outgoing-integrations', []),
|
||||
updatePermission('manage-outgoing-integrations', []),
|
||||
]);
|
||||
|
||||
await request
|
||||
.get(api('integrations.list'))
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(403)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error', 'User does not have the permissions required for this action [error-unauthorized]');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/integrations.history]', () => {
|
||||
it('should return an error when the user DOES NOT the necessary permission', async () => {
|
||||
await updatePermission('manage-outgoing-integrations', []);
|
||||
await updatePermission('manage-own-outgoing-integrations', []);
|
||||
await request
|
||||
.get(api('integrations.history'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
id: integration._id,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(403)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error', 'User does not have the permissions required for this action [error-unauthorized]');
|
||||
});
|
||||
});
|
||||
|
||||
it('should return the history of outgoing integrations', (done) => {
|
||||
void updatePermission('manage-outgoing-integrations', ['admin']).then(() => {
|
||||
void request
|
||||
.get(api('integrations.history'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
id: integration._id,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('history').and.to.be.an('array');
|
||||
expect(res.body).to.have.property('offset');
|
||||
expect(res.body).to.have.property('items');
|
||||
expect(res.body).to.have.property('total');
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/integrations.get]', () => {
|
||||
it('should return an error when the required "integrationId" query parameters is not sent', (done) => {
|
||||
void request
|
||||
.get(api('integrations.get'))
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error', `must have required property 'integrationId'`);
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
|
||||
it('should return an error when the user DOES NOT have the permission "manage-outgoing-integrations" to get an outgoing integration', (done) => {
|
||||
void updatePermission('manage-outgoing-integrations', []).then(() => {
|
||||
void request
|
||||
.get(api('integrations.get'))
|
||||
.query({ integrationId: integration._id })
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error', 'not-authorized');
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return an error when the user DOES NOT have the permission "manage-outgoing-integrations" to get an outgoing integration created by another user', (done) => {
|
||||
void updatePermission('manage-outgoing-integrations', []).then(() => {
|
||||
void request
|
||||
.get(api('integrations.get'))
|
||||
.query({ integrationId: integrationCreatedByAnUser._id })
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error', 'not-authorized');
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return an error when the user sends an invalid integration', (done) => {
|
||||
void updatePermission('manage-outgoing-integrations', ['admin']).then(() => {
|
||||
void request
|
||||
.get(api('integrations.get'))
|
||||
.query({ integrationId: 'invalid' })
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error', 'The integration does not exists.');
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return the integration successfully when the user is able to see only your own integrations', (done) => {
|
||||
void updatePermission('manage-outgoing-integrations', [])
|
||||
.then(() => updatePermission('manage-own-outgoing-integrations', ['user']))
|
||||
.then(() => {
|
||||
void request
|
||||
.get(api('integrations.get'))
|
||||
.query({ integrationId: integrationCreatedByAnUser._id })
|
||||
.set(userCredentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('integration');
|
||||
expect(res.body.integration._id).to.be.equal(integrationCreatedByAnUser._id);
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return the integration successfully', (done) => {
|
||||
void updatePermission('manage-outgoing-integrations', ['admin']).then(() => {
|
||||
void request
|
||||
.get(api('integrations.get'))
|
||||
.query({ integrationId: integration._id })
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('integration');
|
||||
expect(res.body.integration._id).to.be.equal(integration._id);
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/integrations.remove]', () => {
|
||||
it('should return an error when the user DOES NOT have the permission "manage-outgoing-integrations" to remove an outgoing integration', (done) => {
|
||||
void updatePermission('manage-outgoing-integrations', []).then(() => {
|
||||
void request
|
||||
.post(api('integrations.remove'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
integrationId: integration._id,
|
||||
type: 'webhook-outgoing',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(403)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error', 'User does not have the permissions required for this action [error-unauthorized]');
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return an error when the user DOES NOT have the permission "manage-own-outgoing-integrations" to remove an outgoing integration', (done) => {
|
||||
void updatePermission('manage-own-incoming-integrations', []).then(() => {
|
||||
void request
|
||||
.post(api('integrations.remove'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
integrationId: integration._id,
|
||||
type: 'webhook-outgoing',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(403)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error', 'User does not have the permissions required for this action [error-unauthorized]');
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return an error when the user sends an invalid type of integration', (done) => {
|
||||
void updatePermission('manage-own-outgoing-integrations', ['admin']).then(() => {
|
||||
void request
|
||||
.post(api('integrations.remove'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
integrationId: integration._id,
|
||||
type: 'invalid-type',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error').include(`must match exactly one schema in oneOf`);
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('should remove the integration successfully when the user at least one of the necessary permission to remove an outgoing integration', (done) => {
|
||||
void updatePermission('manage-outgoing-integrations', ['admin']).then(() => {
|
||||
void request
|
||||
.post(api('integrations.remove'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
integrationId: integration._id,
|
||||
type: integration.type,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('the normal user should remove the integration successfully when the user have the "manage-own-outgoing-integrations" to remove an outgoing integration', (done) => {
|
||||
void updatePermission('manage-own-outgoing-integrations', ['user']).then(() => {
|
||||
void request
|
||||
.post(api('integrations.remove'))
|
||||
.set(userCredentials)
|
||||
.send({
|
||||
integrationId: integrationCreatedByAnUser._id,
|
||||
type: integrationCreatedByAnUser.type,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/integrations.clearHistory] and [/integrations.replayOutgoing]', () => {
|
||||
let integrationId: IIntegration['_id'];
|
||||
|
||||
before(async () => {
|
||||
await updatePermission('manage-outgoing-integrations', ['admin']);
|
||||
const created = await createIntegration(
|
||||
{
|
||||
type: 'webhook-outgoing',
|
||||
name: 'clear-replay-test',
|
||||
enabled: true,
|
||||
username: 'rocket.cat',
|
||||
urls: ['http://example.com/hook'],
|
||||
scriptEnabled: false,
|
||||
channel: '#general',
|
||||
triggerWords: ['!clearreplaytest'],
|
||||
event: 'sendMessage',
|
||||
},
|
||||
credentials,
|
||||
);
|
||||
integrationId = created._id;
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await updatePermission('manage-outgoing-integrations', ['admin']);
|
||||
await removeIntegration(integrationId, 'outgoing');
|
||||
await Promise.all([updatePermission('manage-outgoing-integrations', []), updatePermission('manage-own-outgoing-integrations', [])]);
|
||||
});
|
||||
|
||||
// the shared method enforces the permission (no route-level permissionsRequired), so a permission
|
||||
// failure surfaces as 400 + errorType 'not_authorized', matching integrations.create above.
|
||||
afterEach(() => updatePermission('manage-outgoing-integrations', ['admin']));
|
||||
|
||||
describe('[/integrations.clearHistory]', () => {
|
||||
it('should fail when unauthenticated', () => request.post(api('integrations.clearHistory')).send({ integrationId }).expect(401));
|
||||
|
||||
it('should fail with 400 when integrationId is not provided', () =>
|
||||
request.post(api('integrations.clearHistory')).set(credentials).send({}).expect(400));
|
||||
|
||||
it('should fail with 400 when the integration does not exist', () =>
|
||||
request
|
||||
.post(api('integrations.clearHistory'))
|
||||
.set(credentials)
|
||||
.send({ integrationId: 'does-not-exist' })
|
||||
.expect(400)
|
||||
.expect((res) => expect(res.body).to.have.property('success', false)));
|
||||
|
||||
it('should fail with 400 when the user lacks the manage-outgoing-integrations permission', async () => {
|
||||
await Promise.all([updatePermission('manage-outgoing-integrations', []), updatePermission('manage-own-outgoing-integrations', [])]);
|
||||
await request
|
||||
.post(api('integrations.clearHistory'))
|
||||
.set(credentials)
|
||||
.send({ integrationId })
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('errorType', 'not_authorized');
|
||||
});
|
||||
});
|
||||
|
||||
it('should clear the integration history', () =>
|
||||
request
|
||||
.post(api('integrations.clearHistory'))
|
||||
.set(credentials)
|
||||
.send({ integrationId })
|
||||
.expect(200)
|
||||
.expect((res) => expect(res.body).to.have.property('success', true)));
|
||||
});
|
||||
|
||||
describe('[/integrations.replayOutgoing]', () => {
|
||||
it('should fail when unauthenticated', () =>
|
||||
request.post(api('integrations.replayOutgoing')).send({ integrationId, historyId: 'x' }).expect(401));
|
||||
|
||||
it('should fail with 400 when required params are missing', () =>
|
||||
request.post(api('integrations.replayOutgoing')).set(credentials).send({ integrationId }).expect(400));
|
||||
|
||||
it('should fail with 400 when the integration does not exist', () =>
|
||||
request
|
||||
.post(api('integrations.replayOutgoing'))
|
||||
.set(credentials)
|
||||
.send({ integrationId: 'does-not-exist', historyId: 'x' })
|
||||
.expect(400)
|
||||
.expect((res) => expect(res.body).to.have.property('success', false)));
|
||||
|
||||
it('should fail with 400 when the history does not exist', () =>
|
||||
request
|
||||
.post(api('integrations.replayOutgoing'))
|
||||
.set(credentials)
|
||||
.send({ integrationId, historyId: 'does-not-exist' })
|
||||
.expect(400)
|
||||
.expect((res) => expect(res.body).to.have.property('success', false)));
|
||||
|
||||
it('should fail with 400 when the user lacks permission (integration becomes inaccessible)', async () => {
|
||||
await Promise.all([updatePermission('manage-outgoing-integrations', []), updatePermission('manage-own-outgoing-integrations', [])]);
|
||||
await request
|
||||
.post(api('integrations.replayOutgoing'))
|
||||
.set(credentials)
|
||||
.send({ integrationId, historyId: 'x' })
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('errorType', 'error-invalid-integration');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
270
apps/meteor/tests/end-to-end/api/permissions.ts
Normal file
270
apps/meteor/tests/end-to-end/api/permissions.ts
Normal file
@@ -0,0 +1,270 @@
|
||||
import type { Credentials } from '@rocket.chat/api-client';
|
||||
import type { IUser } from '@rocket.chat/core-typings';
|
||||
import { expect } from 'chai';
|
||||
import { before, describe, it, after } from 'mocha';
|
||||
|
||||
import { getCredentials, api, request, credentials } from '../../data/api-data';
|
||||
import { updatePermission } from '../../data/permissions.helper';
|
||||
import { password } from '../../data/user';
|
||||
import { createUser, deleteUser, login } from '../../data/users.helper';
|
||||
import type { TestUser } from '../../data/users.helper.js';
|
||||
|
||||
describe('[Permissions]', () => {
|
||||
before((done) => getCredentials(done));
|
||||
|
||||
after(() => updatePermission('add-oauth-service', ['admin']));
|
||||
|
||||
describe('[/permissions.listAll]', () => {
|
||||
it('should return an array with update and remove properties', async () => {
|
||||
const res = await request.get(api('permissions.listAll')).set(credentials).expect('Content-Type', 'application/json').expect(200);
|
||||
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('update').and.to.be.an('array');
|
||||
expect(res.body).to.have.property('remove').and.to.be.an('array');
|
||||
});
|
||||
|
||||
it('should return an array with update and remov properties when search by "updatedSince" query parameter', async () => {
|
||||
const res = await request
|
||||
.get(api('permissions.listAll'))
|
||||
.query({ updatedSince: '2018-11-27T13:52:01Z' })
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('update').and.to.be.an('array');
|
||||
expect(res.body).to.have.property('remove').and.to.be.an('array');
|
||||
});
|
||||
|
||||
it('should return an error when updatedSince query parameter is not a valid ISODate string', async () => {
|
||||
const res = await request
|
||||
.get(api('permissions.listAll'))
|
||||
.query({ updatedSince: 'fsafdf' })
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
|
||||
expect(res.body).to.have.property('success', false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/permissions.update]', () => {
|
||||
let testUser: TestUser<IUser>;
|
||||
let testUserCredentials: Credentials;
|
||||
before(async () => {
|
||||
testUser = await createUser();
|
||||
testUserCredentials = await login(testUser.username, password);
|
||||
await updatePermission('access-permissions', ['admin']);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await updatePermission('access-permissions', ['admin']);
|
||||
await deleteUser(testUser);
|
||||
});
|
||||
|
||||
it('should change the permissions on the server', async () => {
|
||||
const permissions = [
|
||||
{
|
||||
_id: 'add-oauth-service',
|
||||
roles: ['admin', 'user'],
|
||||
},
|
||||
];
|
||||
|
||||
const res = await request
|
||||
.post(api('permissions.update'))
|
||||
.set(credentials)
|
||||
.send({ permissions })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('permissions');
|
||||
|
||||
const firstElement = res.body.permissions[0];
|
||||
expect(firstElement).to.have.property('_id');
|
||||
expect(firstElement).to.have.property('roles').and.to.be.a('array');
|
||||
expect(firstElement).to.have.property('_updatedAt');
|
||||
});
|
||||
|
||||
it('should 400 when trying to set an unknown permission', async () => {
|
||||
const permissions = [
|
||||
{
|
||||
_id: 'this-permission-does-not-exist',
|
||||
roles: ['admin'],
|
||||
},
|
||||
];
|
||||
|
||||
const res = await request
|
||||
.post(api('permissions.update'))
|
||||
.set(credentials)
|
||||
.send({ permissions })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
|
||||
expect(res.body).to.have.property('success', false);
|
||||
});
|
||||
|
||||
it('should 400 when trying to assign a permission to an unknown role', async () => {
|
||||
const permissions = [
|
||||
{
|
||||
_id: 'add-oauth-service',
|
||||
roles: ['this-role-does-not-exist'],
|
||||
},
|
||||
];
|
||||
|
||||
const res = await request
|
||||
.post(api('permissions.update'))
|
||||
.set(credentials)
|
||||
.send({ permissions })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
|
||||
expect(res.body).to.have.property('success', false);
|
||||
});
|
||||
|
||||
it('should 400 when trying to set permissions to a string', async () => {
|
||||
const permissions = '';
|
||||
|
||||
const res = await request
|
||||
.post(api('permissions.update'))
|
||||
.set(credentials)
|
||||
.send({ permissions })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
|
||||
expect(res.body).to.have.property('success', false);
|
||||
});
|
||||
it('should fail updating permission if user does NOT have the access-permissions permission', async () => {
|
||||
const permissions = [
|
||||
{
|
||||
_id: 'add-oauth-service',
|
||||
roles: ['admin', 'user'],
|
||||
},
|
||||
];
|
||||
await request
|
||||
.post(api('permissions.update'))
|
||||
.set(testUserCredentials)
|
||||
.send({ permissions })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(403)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error', 'User does not have the permissions required for this action [error-unauthorized]');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/permissions.addRole]', () => {
|
||||
const testPermission = 'add-oauth-service';
|
||||
const testRole = 'moderator';
|
||||
let testUser: TestUser<IUser>;
|
||||
let testUserCredentials: Credentials;
|
||||
|
||||
before(async () => {
|
||||
await updatePermission('access-permissions', ['admin']);
|
||||
testUser = await createUser();
|
||||
testUserCredentials = await login(testUser.username, password);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await request.post(api('permissions.removeRole')).set(credentials).send({ permissionId: testPermission, role: testRole });
|
||||
await deleteUser(testUser);
|
||||
await updatePermission('access-permissions', ['admin']);
|
||||
});
|
||||
|
||||
it('should fail when unauthenticated', () =>
|
||||
request.post(api('permissions.addRole')).send({ permissionId: testPermission, role: testRole }).expect(401));
|
||||
|
||||
it('should fail with 400 when the permission does not exist', () =>
|
||||
request
|
||||
.post(api('permissions.addRole'))
|
||||
.set(credentials)
|
||||
.send({ permissionId: 'this-permission-does-not-exist', role: testRole })
|
||||
.expect(400)
|
||||
.expect((res) => expect(res.body).to.have.property('success', false)));
|
||||
|
||||
it('should fail with 400 when the role does not exist', () =>
|
||||
request
|
||||
.post(api('permissions.addRole'))
|
||||
.set(credentials)
|
||||
.send({ permissionId: testPermission, role: 'this-role-does-not-exist' })
|
||||
.expect(400)
|
||||
.expect((res) => expect(res.body).to.have.property('success', false)));
|
||||
|
||||
it('should fail with 403 when the user lacks the access-permissions permission', () =>
|
||||
request
|
||||
.post(api('permissions.addRole'))
|
||||
.set(testUserCredentials)
|
||||
.send({ permissionId: testPermission, role: testRole })
|
||||
.expect(403)
|
||||
.expect((res) => expect(res.body).to.have.property('success', false)));
|
||||
|
||||
it('should add the role to the permission', async () => {
|
||||
await request
|
||||
.post(api('permissions.addRole'))
|
||||
.set(credentials)
|
||||
.send({ permissionId: testPermission, role: testRole })
|
||||
.expect(200)
|
||||
.expect((res) => expect(res.body).to.have.property('success', true));
|
||||
|
||||
await request
|
||||
.get(api('permissions.listAll'))
|
||||
.set(credentials)
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
const permission = res.body.update.find((p: { _id: string; roles: string[] }) => p._id === testPermission);
|
||||
expect(permission).to.be.an('object');
|
||||
expect(permission.roles).to.include(testRole);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/permissions.removeRole]', () => {
|
||||
const testPermission = 'add-oauth-service';
|
||||
const testRole = 'moderator';
|
||||
let testUser: TestUser<IUser>;
|
||||
let testUserCredentials: Credentials;
|
||||
|
||||
before(async () => {
|
||||
await updatePermission('access-permissions', ['admin']);
|
||||
testUser = await createUser();
|
||||
testUserCredentials = await login(testUser.username, password);
|
||||
await request.post(api('permissions.addRole')).set(credentials).send({ permissionId: testPermission, role: testRole });
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await deleteUser(testUser);
|
||||
await updatePermission('access-permissions', ['admin']);
|
||||
});
|
||||
|
||||
it('should fail when unauthenticated', () =>
|
||||
request.post(api('permissions.removeRole')).send({ permissionId: testPermission, role: testRole }).expect(401));
|
||||
|
||||
it('should fail with 403 when the user lacks the access-permissions permission', () =>
|
||||
request
|
||||
.post(api('permissions.removeRole'))
|
||||
.set(testUserCredentials)
|
||||
.send({ permissionId: testPermission, role: testRole })
|
||||
.expect(403)
|
||||
.expect((res) => expect(res.body).to.have.property('success', false)));
|
||||
|
||||
it('should remove the role from the permission', async () => {
|
||||
await request
|
||||
.post(api('permissions.removeRole'))
|
||||
.set(credentials)
|
||||
.send({ permissionId: testPermission, role: testRole })
|
||||
.expect(200)
|
||||
.expect((res) => expect(res.body).to.have.property('success', true));
|
||||
|
||||
await request
|
||||
.get(api('permissions.listAll'))
|
||||
.set(credentials)
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
const permission = res.body.update.find((p: { _id: string; roles: string[] }) => p._id === testPermission);
|
||||
expect(permission).to.be.an('object');
|
||||
expect(permission.roles).to.not.include(testRole);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
130
apps/meteor/tests/end-to-end/api/presence.ts
Normal file
130
apps/meteor/tests/end-to-end/api/presence.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
import { expect } from 'chai';
|
||||
import { before, describe, it, after } from 'mocha';
|
||||
import type { Response } from 'supertest';
|
||||
|
||||
import { getCredentials, api, request, credentials } from '../../data/api-data';
|
||||
import { updatePermission } from '../../data/permissions.helper';
|
||||
import { password } from '../../data/user';
|
||||
import { createUser, deleteUser, login } from '../../data/users.helper';
|
||||
|
||||
describe('[Presence]', () => {
|
||||
let createdUser: any;
|
||||
|
||||
before((done) => getCredentials(done));
|
||||
|
||||
let unauthorizedUserCredentials: any;
|
||||
before(async () => {
|
||||
createdUser = await createUser();
|
||||
unauthorizedUserCredentials = await login(createdUser.username, password);
|
||||
});
|
||||
|
||||
after(() => Promise.all([updatePermission('manage-user-status', ['admin']), deleteUser(createdUser)]));
|
||||
|
||||
describe('[/presence.getConnections]', () => {
|
||||
it('should throw an error if not authenticated', async () => {
|
||||
await request
|
||||
.get(api('presence.getConnections'))
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(401)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('status', 'error');
|
||||
expect(res.body).to.have.property('message');
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw an error if user is unauthorized', async () => {
|
||||
await request
|
||||
.get(api('presence.getConnections'))
|
||||
.set(unauthorizedUserCredentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(403)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error');
|
||||
});
|
||||
});
|
||||
|
||||
it("should throw an error if doesn't have required permission", async () => {
|
||||
await updatePermission('manage-user-status', []);
|
||||
|
||||
await request
|
||||
.get(api('presence.getConnections'))
|
||||
.set(unauthorizedUserCredentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(403)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error');
|
||||
});
|
||||
|
||||
await updatePermission('manage-user-status', ['admin']);
|
||||
});
|
||||
|
||||
it('should return current and max connections of 200', async () => {
|
||||
await request
|
||||
.get(api('presence.getConnections'))
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('current').to.be.a('number');
|
||||
expect(res.body).to.have.property('max').to.be.a('number').and.to.be.equal(200);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/presence.enableBroadcast]', () => {
|
||||
it('should throw an error if not authenticated', async () => {
|
||||
await request
|
||||
.post(api('presence.enableBroadcast'))
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(401)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('status', 'error');
|
||||
expect(res.body).to.have.property('message');
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw an error if user is unauthorized', async () => {
|
||||
await request
|
||||
.post(api('presence.enableBroadcast'))
|
||||
.set(unauthorizedUserCredentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(403)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error');
|
||||
});
|
||||
});
|
||||
|
||||
it("should throw an error if doesn't have required permission", async () => {
|
||||
await updatePermission('manage-user-status', []);
|
||||
|
||||
await request
|
||||
.post(api('presence.enableBroadcast'))
|
||||
.set(unauthorizedUserCredentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(403)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error');
|
||||
});
|
||||
|
||||
await updatePermission('manage-user-status', ['admin']);
|
||||
});
|
||||
|
||||
it('should return success', async () => {
|
||||
await request
|
||||
.post(api('presence.enableBroadcast'))
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// describe('[/presence.enableBroadcast]', () => {});
|
||||
});
|
||||
286
apps/meteor/tests/end-to-end/api/push.ts
Normal file
286
apps/meteor/tests/end-to-end/api/push.ts
Normal file
@@ -0,0 +1,286 @@
|
||||
import { expect } from 'chai';
|
||||
import { before, describe, it, after } from 'mocha';
|
||||
|
||||
import { getCredentials, api, request, credentials } from '../../data/api-data';
|
||||
import { updateSetting } from '../../data/permissions.helper';
|
||||
|
||||
describe('[Push]', () => {
|
||||
before((done) => getCredentials(done));
|
||||
|
||||
describe('POST [/push.token]', () => {
|
||||
it('should succeed with a valid gcm token', async () => {
|
||||
await request
|
||||
.post(api('push.token'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
type: 'gcm',
|
||||
value: 'token',
|
||||
appName: 'com.example.rocketchat',
|
||||
})
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('result').and.to.be.an('object');
|
||||
});
|
||||
});
|
||||
|
||||
it('should succeed with a valid apn token', async () => {
|
||||
await request
|
||||
.post(api('push.token'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
type: 'apn',
|
||||
value: 'token',
|
||||
appName: 'com.example.rocketchat',
|
||||
})
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('result').and.to.be.an('object');
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail if not logged in', async () => {
|
||||
await request
|
||||
.post(api('push.token'))
|
||||
.send({
|
||||
type: 'gcm',
|
||||
value: 'token',
|
||||
appName: 'com.example.rocketchat',
|
||||
})
|
||||
.expect(401)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('status', 'error');
|
||||
expect(res.body).to.have.property('message');
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail if missing type', async () => {
|
||||
await request
|
||||
.post(api('push.token'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
value: 'token',
|
||||
appName: 'com.example.rocketchat',
|
||||
})
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('errorType', 'invalid-params');
|
||||
expect(res.body).to.have.property('error').that.includes(`must have required property 'type'`);
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail if missing value', async () => {
|
||||
await request
|
||||
.post(api('push.token'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
type: 'gcm',
|
||||
appName: 'com.example.rocketchat',
|
||||
})
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('errorType', 'invalid-params');
|
||||
expect(res.body).to.have.property('error').that.includes(`must have required property 'value'`);
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail if missing appName', async () => {
|
||||
await request
|
||||
.post(api('push.token'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
type: 'gcm',
|
||||
value: 'token',
|
||||
})
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('errorType', 'invalid-params');
|
||||
expect(res.body).to.have.property('error').that.includes(`must have required property 'appName'`);
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail if type param is unknown', async () => {
|
||||
await request
|
||||
.post(api('push.token'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
type: 'unknownPlatform',
|
||||
value: 'token',
|
||||
appName: 'com.example.rocketchat',
|
||||
})
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('errorType', 'invalid-params');
|
||||
expect(res.body).to.have.property('error').that.includes(`must be equal to one of the allowed values`);
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail if token param is empty', async () => {
|
||||
await request
|
||||
.post(api('push.token'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
type: 'gcm',
|
||||
appName: 'com.example.rocketchat',
|
||||
value: '',
|
||||
})
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('errorType', 'invalid-params');
|
||||
expect(res.body).to.have.property('error').that.includes(`must NOT have fewer than 1 characters`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('DELETE [/push.token]', () => {
|
||||
it('should fail if not logged in', async () => {
|
||||
await request
|
||||
.delete(api('push.token'))
|
||||
.send({
|
||||
token: 'token',
|
||||
})
|
||||
.expect(401)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('status', 'error');
|
||||
expect(res.body).to.have.property('message');
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail if missing token key', async () => {
|
||||
await request
|
||||
.delete(api('push.token'))
|
||||
.set(credentials)
|
||||
.send({})
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('errorType', 'invalid-params');
|
||||
expect(res.body).to.have.property('error').that.includes(`must have required property 'token'`);
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail if token is empty', async () => {
|
||||
await request
|
||||
.delete(api('push.token'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
token: '',
|
||||
})
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('errorType', 'invalid-params');
|
||||
expect(res.body).to.have.property('error').that.includes(`must NOT have fewer than 1 characters`);
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail if token is invalid', async () => {
|
||||
await request
|
||||
.delete(api('push.token'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
token: '123',
|
||||
})
|
||||
.expect(404)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
});
|
||||
});
|
||||
|
||||
it('should delete a token if valid', async () => {
|
||||
await request
|
||||
.delete(api('push.token'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
token: 'token',
|
||||
})
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail if token is already deleted', async () => {
|
||||
await request
|
||||
.delete(api('push.token'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
token: 'token',
|
||||
})
|
||||
.expect(404)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/push.test]', () => {
|
||||
before(() => updateSetting('Push_enable', false));
|
||||
|
||||
it('should fail if not logged in', async () => {
|
||||
await request
|
||||
.post(api('push.test'))
|
||||
.expect(401)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('status', 'error');
|
||||
expect(res.body).to.have.property('message');
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail if push is disabled', async () => {
|
||||
await request
|
||||
.post(api('push.test'))
|
||||
.set(credentials)
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('errorType', 'error-push-disabled');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/push.info]', () => {
|
||||
before(() => Promise.all([updateSetting('Push_gateway', 'https://random-gateway.rocket.chat')]));
|
||||
|
||||
after(() => Promise.all([updateSetting('Push_gateway', 'https://gateway.rocket.chat')]));
|
||||
|
||||
it('should fail if not logged in', async () => {
|
||||
await request
|
||||
.get(api('push.info'))
|
||||
.expect(401)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('status', 'error');
|
||||
expect(res.body).to.have.property('message');
|
||||
});
|
||||
});
|
||||
|
||||
it('should succesfully retrieve non default push notification info', async () => {
|
||||
await request
|
||||
.get(api('push.info'))
|
||||
.set(credentials)
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('pushGatewayEnabled', false);
|
||||
expect(res.body).to.have.property('defaultPushGateway', false);
|
||||
});
|
||||
});
|
||||
|
||||
it('should succesfully retrieve default push notification info', async () => {
|
||||
await updateSetting('Push_gateway', 'https://gateway.rocket.chat');
|
||||
await request
|
||||
.get(api('push.info'))
|
||||
.set(credentials)
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('pushGatewayEnabled', false);
|
||||
expect(res.body).to.have.property('defaultPushGateway', true);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
575
apps/meteor/tests/end-to-end/api/roles.ts
Normal file
575
apps/meteor/tests/end-to-end/api/roles.ts
Normal file
@@ -0,0 +1,575 @@
|
||||
import type { IUser } from '@rocket.chat/core-typings';
|
||||
import { expect } from 'chai';
|
||||
import { after, before, describe, it } from 'mocha';
|
||||
import type { Response } from 'supertest';
|
||||
|
||||
import { getCredentials, api, request, credentials } from '../../data/api-data';
|
||||
import { updatePermission } from '../../data/permissions.helper';
|
||||
import { password, adminUsername } from '../../data/user';
|
||||
import { createUser, deleteUser, login } from '../../data/users.helper';
|
||||
|
||||
describe('[Roles]', function () {
|
||||
this.retries(0);
|
||||
const isEnterprise = Boolean(process.env.IS_EE);
|
||||
|
||||
before((done) => getCredentials(done));
|
||||
|
||||
describe.skip('[/roles.create]', () => {
|
||||
const testRoleName = `role.test.${Date.now()}`;
|
||||
it('should throw an error when not running EE to create a role', async function () {
|
||||
// TODO this is not the right way to do it. We're doing this way for now just because we have separate CI jobs for EE and CE,
|
||||
// ideally we should have a single CI job that adds a license and runs both CE and EE tests.
|
||||
if (isEnterprise) {
|
||||
this.skip();
|
||||
}
|
||||
await request
|
||||
.post(api('roles.create'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
name: testRoleName,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error', 'This is an enterprise feature [error-action-not-allowed]');
|
||||
expect(res.body).to.have.property('errorType', 'error-action-not-allowed');
|
||||
});
|
||||
});
|
||||
|
||||
it('should successfully create a role in EE', async function () {
|
||||
// TODO this is not the right way to do it. We're doing this way for now just because we have separate CI jobs for EE and CE,
|
||||
// ideally we should have a single CI job that adds a license and runs both CE and EE tests.
|
||||
if (!isEnterprise) {
|
||||
this.skip();
|
||||
}
|
||||
await request
|
||||
.post(api('roles.create'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
name: testRoleName,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('role');
|
||||
expect(res.body.role).to.have.property('name', testRoleName);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/roles.update]', () => {
|
||||
const testRoleName = `role.test.${Date.now()}`;
|
||||
const newTestRoleName = `role.test.updated.${Date.now()}`;
|
||||
let testRoleId = '';
|
||||
|
||||
before('Create a new role with Users scope', async () => {
|
||||
if (!isEnterprise) {
|
||||
return;
|
||||
}
|
||||
|
||||
await request
|
||||
.post(api('roles.create'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
name: testRoleName,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('role');
|
||||
expect(res.body.role).to.have.property('name', testRoleName);
|
||||
testRoleId = res.body.role._id;
|
||||
});
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
if (!isEnterprise) {
|
||||
return;
|
||||
}
|
||||
await request.post(api('roles.delete')).set(credentials).send({
|
||||
roleId: testRoleId,
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw an error when not running EE to update a role', async function () {
|
||||
// TODO this is not the right way to do it. We're doing this way for now just because we have separate CI jobs for EE and CE,
|
||||
// ideally we should have a single CI job that adds a license and runs both CE and EE tests.
|
||||
if (isEnterprise) {
|
||||
this.skip();
|
||||
}
|
||||
await request
|
||||
.post(api('roles.update'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
name: testRoleName,
|
||||
roleId: testRoleId,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error', 'This is an enterprise feature [error-action-not-allowed]');
|
||||
expect(res.body).to.have.property('errorType', 'error-action-not-allowed');
|
||||
});
|
||||
});
|
||||
|
||||
it('should successfully update a role in EE', async function () {
|
||||
// TODO this is not the right way to do it. We're doing this way for now just because we have separate CI jobs for EE and CE,
|
||||
// ideally we should have a single CI job that adds a license and runs both CE and EE tests.
|
||||
if (!isEnterprise) {
|
||||
this.skip();
|
||||
}
|
||||
|
||||
await request
|
||||
.post(api('roles.update'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
name: newTestRoleName,
|
||||
roleId: testRoleId,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('role');
|
||||
expect(res.body.role).to.have.property('name', newTestRoleName);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/roles.getUsersInRole]', () => {
|
||||
let testUser: IUser;
|
||||
let testUserCredentials: { 'X-Auth-Token': string; 'X-User-Id': string };
|
||||
const testRoleName = `role.getUsersInRole.${Date.now()}`;
|
||||
let testRoleId = '';
|
||||
|
||||
before(async () => {
|
||||
await updatePermission('access-permissions', ['admin']);
|
||||
testUser = await createUser();
|
||||
testUserCredentials = await login(testUser.username, password);
|
||||
|
||||
if (!isEnterprise) {
|
||||
return;
|
||||
}
|
||||
|
||||
await request
|
||||
.post(api('roles.create'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
name: testRoleName,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('role');
|
||||
expect(res.body.role).to.have.property('name', testRoleName);
|
||||
testRoleId = res.body.role._id;
|
||||
});
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await deleteUser(testUser);
|
||||
if (!isEnterprise) {
|
||||
return;
|
||||
}
|
||||
|
||||
await request.post(api('roles.delete')).set(credentials).send({
|
||||
roleId: testRoleId,
|
||||
});
|
||||
});
|
||||
|
||||
it('should successfully get an empty list of users in a role if no user has been assigned to it', async function () {
|
||||
if (!isEnterprise) {
|
||||
this.skip();
|
||||
}
|
||||
|
||||
await request
|
||||
.get(api('roles.getUsersInRole'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
role: testRoleId,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('total', 0);
|
||||
expect(res.body).to.have.property('users').that.is.an('array').that.is.empty;
|
||||
});
|
||||
});
|
||||
|
||||
it('should successfully get a list of users in a role', async function () {
|
||||
if (!isEnterprise) {
|
||||
this.skip();
|
||||
}
|
||||
|
||||
await request
|
||||
.post(api('roles.addUserToRole'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
roleId: testRoleId,
|
||||
username: adminUsername,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
});
|
||||
|
||||
await request
|
||||
.get(api('roles.getUsersInRole'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
role: testRoleId,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('total', 1);
|
||||
expect(res.body).to.have.property('users').that.is.an('array').of.length(1);
|
||||
expect(res.body.users[0]).to.have.property('_id', credentials['X-User-Id']);
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail getting a list of users in a role in case an invalid role is provided', async function () {
|
||||
if (!isEnterprise) {
|
||||
this.skip();
|
||||
}
|
||||
|
||||
await request
|
||||
.get(api('roles.getUsersInRole'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
role: 'invalid-role',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('errorType', 'error-invalid-roleId');
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail when user does NOT have the access-permissions permission', async () => {
|
||||
await request
|
||||
.get(api('roles.getUsersInRole'))
|
||||
.set(testUserCredentials)
|
||||
.query({
|
||||
role: 'admin',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(403)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error', 'User does not have the permissions required for this action [error-unauthorized]');
|
||||
});
|
||||
});
|
||||
|
||||
(isEnterprise ? it : it.skip)('should fail getting a list of users in a role in case a role name is provided', async () => {
|
||||
await request
|
||||
.get(api('roles.getUsersInRole'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
role: testRoleName,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('errorType', 'error-invalid-roleId');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/roles.delete]', () => {
|
||||
let testUser: IUser;
|
||||
let testUserCredentials: { 'X-Auth-Token': string; 'X-User-Id': string };
|
||||
const testRoleName = `role.delete.${Date.now()}`;
|
||||
let testRoleId = '';
|
||||
|
||||
before(async () => {
|
||||
if (!isEnterprise) {
|
||||
return;
|
||||
}
|
||||
|
||||
testUser = await createUser();
|
||||
testUserCredentials = await login(testUser.username, password);
|
||||
await updatePermission('access-permissions', ['admin']);
|
||||
await request
|
||||
.post(api('roles.create'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
name: testRoleName,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('role');
|
||||
expect(res.body.role).to.have.property('name', testRoleName);
|
||||
testRoleId = res.body.role._id;
|
||||
});
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
if (!isEnterprise) {
|
||||
return;
|
||||
}
|
||||
await deleteUser(testUser);
|
||||
});
|
||||
|
||||
it('should fail deleting a role when user does NOT have the access-permissions permission', async function () {
|
||||
// TODO this is not the right way to do it. We're doing this way for now just because we have separate CI jobs for EE and CE,
|
||||
// ideally we should have a single CI job that adds a license and runs both CE and EE tests.
|
||||
if (!isEnterprise) {
|
||||
this.skip();
|
||||
}
|
||||
|
||||
await request
|
||||
.post(api('roles.delete'))
|
||||
.set(testUserCredentials)
|
||||
.send({
|
||||
roleId: testRoleId,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(403)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error', 'User does not have the permissions required for this action [error-unauthorized]');
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail deleting a role in EE in case an invalid role is provided', async function () {
|
||||
// TODO this is not the right way to do it. We're doing this way for now just because we have separate CI jobs for EE and CE,
|
||||
// ideally we should have a single CI job that adds a license and runs both CE and EE tests.
|
||||
if (!isEnterprise) {
|
||||
this.skip();
|
||||
}
|
||||
|
||||
await request
|
||||
.post(api('roles.delete'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
roleId: 'invalid-role',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('errorType', 'error-invalid-roleId');
|
||||
expect(res.body).to.have.property('error', 'This role does not exist [error-invalid-roleId]');
|
||||
});
|
||||
});
|
||||
|
||||
it('should successfully delete a role in EE', async function () {
|
||||
// TODO this is not the right way to do it. We're doing this way for now just because we have separate CI jobs for EE and CE,
|
||||
// ideally we should have a single CI job that adds a license and runs both CE and EE tests.
|
||||
if (!isEnterprise) {
|
||||
this.skip();
|
||||
}
|
||||
|
||||
await request
|
||||
.post(api('roles.delete'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
roleId: testRoleId,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/roles.removeUserFromRole]', () => {
|
||||
let testUser: IUser;
|
||||
let testUserCredentials: { 'X-Auth-Token': string; 'X-User-Id': string };
|
||||
const testRoleName = `role.removeUsersFromRole.${Date.now()}`;
|
||||
let testRoleId = '';
|
||||
|
||||
before(async () => {
|
||||
await updatePermission('access-permissions', ['admin']);
|
||||
testUser = await createUser();
|
||||
testUserCredentials = await login(testUser.username, password);
|
||||
if (!isEnterprise) {
|
||||
return;
|
||||
}
|
||||
|
||||
await request
|
||||
.post(api('roles.create'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
name: testRoleName,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('role');
|
||||
expect(res.body.role).to.have.property('name', testRoleName);
|
||||
testRoleId = res.body.role._id;
|
||||
});
|
||||
await request
|
||||
.post(api('roles.addUserToRole'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
roleId: testRoleId,
|
||||
username: testUser.username,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
});
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await request.post(api('roles.delete')).set(credentials).send({
|
||||
roleId: testRoleId,
|
||||
});
|
||||
await deleteUser(testUser);
|
||||
});
|
||||
|
||||
it('should fail removing a user from a role when user does NOT have the access-permissions permission', async () => {
|
||||
await request
|
||||
.post(api('roles.removeUserFromRole'))
|
||||
.set(testUserCredentials)
|
||||
.send({
|
||||
roleId: testRoleId,
|
||||
username: testUser.username,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(403)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error', 'User does not have the permissions required for this action [error-unauthorized]');
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail removing an invalid user from a role', async function () {
|
||||
// TODO this is not the right way to do it. We're doing this way for now just because we have separate CI jobs for EE and CE,
|
||||
// ideally we should have a single CI job that adds a license and runs both CE and EE tests.
|
||||
if (!isEnterprise) {
|
||||
this.skip();
|
||||
}
|
||||
|
||||
await request
|
||||
.post(api('roles.removeUserFromRole'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
roleId: testRoleId,
|
||||
username: 'invalid-username',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('errorType', 'error-invalid-user');
|
||||
expect(res.body).to.have.property('error', 'There is no user with this username [error-invalid-user]');
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail removing a user from an invalid role', async function () {
|
||||
// TODO this is not the right way to do it. We're doing this way for now just because we have separate CI jobs for EE and CE,
|
||||
// ideally we should have a single CI job that adds a license and runs both CE and EE tests.
|
||||
if (!isEnterprise) {
|
||||
this.skip();
|
||||
}
|
||||
|
||||
await request
|
||||
.post(api('roles.removeUserFromRole'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
roleId: 'invalid-role',
|
||||
username: testUser.username,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('errorType', 'error-invalid-roleId');
|
||||
expect(res.body).to.have.property('error', 'This role does not exist [error-invalid-roleId]');
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail removing a user from a role they do not have', async function () {
|
||||
// TODO this is not the right way to do it. We're doing this way for now just because we have separate CI jobs for EE and CE,
|
||||
// ideally we should have a single CI job that adds a license and runs both CE and EE tests.
|
||||
if (!isEnterprise) {
|
||||
this.skip();
|
||||
}
|
||||
|
||||
await request
|
||||
.post(api('roles.removeUserFromRole'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
roleId: 'admin',
|
||||
username: testUser.username,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('errorType', 'error-user-not-in-role');
|
||||
expect(res.body).to.have.property('error', 'User is not in this role [error-user-not-in-role]');
|
||||
});
|
||||
});
|
||||
|
||||
it('should successfully remove a user from a role', async function () {
|
||||
// TODO this is not the right way to do it. We're doing this way for now just because we have separate CI jobs for EE and CE,
|
||||
// ideally we should have a single CI job that adds a license and runs both CE and EE tests.
|
||||
if (!isEnterprise) {
|
||||
this.skip();
|
||||
}
|
||||
|
||||
await request
|
||||
.post(api('roles.removeUserFromRole'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
roleId: testRoleId,
|
||||
username: testUser.username,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
});
|
||||
await request
|
||||
.get(api('roles.getUsersInRole'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
role: testRoleId,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('total', 0);
|
||||
expect(res.body).to.have.property('users');
|
||||
expect(res.body.users).to.be.an('array').that.is.empty;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/roles.getUsersInPublicRoles]', () => {
|
||||
it('should return public roles', async () => {
|
||||
await request
|
||||
.get(api('roles.getUsersInPublicRoles'))
|
||||
.set(credentials)
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('users');
|
||||
expect(res.body.users).to.be.an('array');
|
||||
// payload schema is checked in the endpoint
|
||||
expect(res.body.users.length).to.be.greaterThan(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
5630
apps/meteor/tests/end-to-end/api/rooms.ts
Normal file
5630
apps/meteor/tests/end-to-end/api/rooms.ts
Normal file
File diff suppressed because it is too large
Load Diff
861
apps/meteor/tests/end-to-end/api/settings.ts
Normal file
861
apps/meteor/tests/end-to-end/api/settings.ts
Normal file
@@ -0,0 +1,861 @@
|
||||
import type { IServerEvents, LoginServiceConfiguration } from '@rocket.chat/core-typings';
|
||||
import { expect } from 'chai';
|
||||
import { before, describe, it, after, afterEach } from 'mocha';
|
||||
|
||||
import { getCredentials, api, request, credentials } from '../../data/api-data';
|
||||
import { updatePermission, updateSetting, getSettingValueById } from '../../data/permissions.helper';
|
||||
import { IS_EE } from '../../e2e/config/constants';
|
||||
|
||||
describe('[Settings]', () => {
|
||||
before((done) => getCredentials(done));
|
||||
|
||||
describe('[/settings.public]', () => {
|
||||
it('should return public settings', async () => {
|
||||
const res = await request.get(api('settings.public')).expect('Content-Type', 'application/json').expect(200);
|
||||
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('settings');
|
||||
expect(res.body).to.have.property('count');
|
||||
});
|
||||
it('should return public settings even requested with count and offset params', async () => {
|
||||
const res = await request
|
||||
.get(api('settings.public'))
|
||||
.query({
|
||||
count: 5,
|
||||
offset: 0,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
expect(res.body).to.have.property('success').and.to.be.true;
|
||||
expect(res.body).to.have.property('settings').and.to.be.an('array').and.to.have.lengthOf(5);
|
||||
expect(res.body).to.have.property('count').and.to.be.a('number').and.to.equal(5);
|
||||
});
|
||||
it('should return public settings even requested with _id param', async () => {
|
||||
const res = await request
|
||||
.get(api('settings.public'))
|
||||
.query({
|
||||
_id: 'Site_Url',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
expect(res.body).to.have.property('success').and.to.be.true;
|
||||
expect(res.body).to.have.property('settings').and.to.be.an('array').and.to.have.lengthOf(1);
|
||||
expect(res.body).to.have.property('count').and.to.be.a('number').and.to.equal(1);
|
||||
});
|
||||
it('should return public settings even requested with _id param as an array', async () => {
|
||||
const res = await request
|
||||
.get(api('settings.public'))
|
||||
.query({
|
||||
_id: 'Site_Url,LDAP_Enable',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
expect(res.body).to.have.property('success').and.to.be.true;
|
||||
expect(res.body).to.have.property('settings').and.to.be.an('array').and.to.have.lengthOf(2);
|
||||
expect(res.body).to.have.property('count').and.to.be.a('number').and.to.equal(2);
|
||||
});
|
||||
it('should return an empty response when requesting public settings with a broken _id param', async () => {
|
||||
const res = await request
|
||||
.get(api('settings.public'))
|
||||
.query({
|
||||
_id: 10,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
expect(res.body).to.have.property('success').and.to.be.true;
|
||||
expect(res.body).to.have.property('settings').and.to.be.an('array').and.to.be.empty;
|
||||
expect(res.body).to.have.property('count').and.to.be.a('number').and.to.equal(0);
|
||||
expect(res.body).to.have.property('offset').and.to.be.a('number').and.to.equal(0);
|
||||
expect(res.body).to.have.property('total').and.to.be.a('number').and.to.equal(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/settings]', () => {
|
||||
it('should return private settings', async () => {
|
||||
const res = await request.get(api('settings')).set(credentials).expect('Content-Type', 'application/json').expect(200);
|
||||
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('settings');
|
||||
expect(res.body).to.have.property('count');
|
||||
});
|
||||
it('should return the default values of the settings when includeDefaults is true', async () => {
|
||||
return request
|
||||
.get(api('settings'))
|
||||
.query({ includeDefaults: true })
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('settings');
|
||||
expect(res.body).to.have.property('count');
|
||||
expect(res.body.settings[0]).to.have.property('packageValue');
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST (bulk)', () => {
|
||||
before(async () => {
|
||||
await updatePermission('edit-privileged-setting', ['admin']);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await updatePermission('edit-privileged-setting', ['admin']);
|
||||
});
|
||||
|
||||
it('should return unauthorized when not authenticated', async () => {
|
||||
await request
|
||||
.post(api('settings'))
|
||||
.send({ settings: [{ _id: 'LDAP_Enable', value: false }] })
|
||||
.expect(401);
|
||||
});
|
||||
|
||||
it('should return bad request when settings is missing', async () => {
|
||||
await request.post(api('settings')).set(credentials).send({}).expect(400);
|
||||
});
|
||||
|
||||
it('should return bad request when settings is empty', async () => {
|
||||
await request.post(api('settings')).set(credentials).send({ settings: [] }).expect(400);
|
||||
});
|
||||
|
||||
it('should return bad request when an item is missing value', async () => {
|
||||
await request
|
||||
.post(api('settings'))
|
||||
.set(credentials)
|
||||
.send({ settings: [{ _id: 'LDAP_Enable' }] })
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
it('should reject an empty integer value without changing the setting', async () => {
|
||||
const settingId = 'UserData_ProcessingFrequency';
|
||||
const originalValue = await getSettingValueById(settingId);
|
||||
|
||||
await request
|
||||
.post(api('settings'))
|
||||
.set(credentials)
|
||||
.send({ settings: [{ _id: settingId, value: null }] })
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
});
|
||||
|
||||
expect(await getSettingValueById(settingId)).to.equal(originalValue);
|
||||
});
|
||||
|
||||
it('should successfully update multiple settings in a single request', async () => {
|
||||
await request
|
||||
.post(api('settings'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
settings: [
|
||||
{ _id: 'LDAP_Enable', value: false },
|
||||
{ _id: 'Accounts_Default_User_Preferences_masterVolume', value: 50 },
|
||||
],
|
||||
})
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
});
|
||||
|
||||
await request
|
||||
.get(api('settings/LDAP_Enable'))
|
||||
.set(credentials)
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('value', false);
|
||||
});
|
||||
|
||||
await request
|
||||
.get(api('settings/Accounts_Default_User_Preferences_masterVolume'))
|
||||
.set(credentials)
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('value', 50);
|
||||
});
|
||||
});
|
||||
|
||||
it('should accept an optional editor for a setting', async () => {
|
||||
await request
|
||||
.post(api('settings'))
|
||||
.set(credentials)
|
||||
.send({ settings: [{ _id: 'Livechat_title_color', value: '#000000', editor: 'color' }] })
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
});
|
||||
|
||||
await request
|
||||
.get(api('settings/Livechat_title_color'))
|
||||
.set(credentials)
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('value', '#000000');
|
||||
});
|
||||
});
|
||||
|
||||
it('should return bad request when editor is an empty string', async () => {
|
||||
await request
|
||||
.post(api('settings'))
|
||||
.set(credentials)
|
||||
.send({ settings: [{ _id: 'Livechat_title_color', value: '#C1272D', editor: '' }] })
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
it('should fail when the user does not have edit-privileged-setting permission', async () => {
|
||||
await updatePermission('edit-privileged-setting', []);
|
||||
await request
|
||||
.post(api('settings'))
|
||||
.set(credentials)
|
||||
.send({ settings: [{ _id: 'LDAP_Enable', value: false }] })
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body.error).to.match(/error-action-not-allowed/);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/settings/:_id]', () => {
|
||||
before(async () => {
|
||||
await updatePermission('view-privileged-setting', ['admin']);
|
||||
await updatePermission('edit-privileged-setting', ['admin']);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await updatePermission('view-privileged-setting', ['admin']);
|
||||
await updatePermission('edit-privileged-setting', ['admin']);
|
||||
});
|
||||
|
||||
it('should successfully return one setting (GET)', async () => {
|
||||
return request
|
||||
.get(api('settings/Site_Url'))
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('_id', 'Site_Url');
|
||||
expect(res.body).to.have.property('value');
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail returning a setting if user does NOT have the view-privileged-setting permission (GET)', async () => {
|
||||
await updatePermission('view-privileged-setting', []);
|
||||
return request
|
||||
.get(api('settings/Site_Url'))
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(403)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error', 'User does not have the permissions required for this action [error-unauthorized]');
|
||||
});
|
||||
});
|
||||
|
||||
it('should successfully set the value of a setting (POST)', async () => {
|
||||
return request
|
||||
.post(api('settings/LDAP_Enable'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
value: false,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail updating the value of a setting less than its minValue (POST)', async () => {
|
||||
return request
|
||||
.post(api('settings/Accounts_Default_User_Preferences_masterVolume'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
value: '-1',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property(
|
||||
'error',
|
||||
'Value for setting Accounts_Default_User_Preferences_masterVolume must be greater than or equal to 0 [error-invalid-setting-value]',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail updating the value of a setting greater than its maxValue (POST)', async () => {
|
||||
return request
|
||||
.post(api('settings/Accounts_Default_User_Preferences_masterVolume'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
value: '101',
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property(
|
||||
'error',
|
||||
'Value for setting Accounts_Default_User_Preferences_masterVolume must be less than or equal to 100 [error-invalid-setting-value]',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail updating the value of a setting if user does NOT have the edit-privileged-setting permission (POST)', async () => {
|
||||
await updatePermission('edit-privileged-setting', []);
|
||||
return request
|
||||
.post(api('settings/LDAP_Enable'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
value: false,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(403)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error', 'User does not have the permissions required for this action [error-unauthorized]');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('[Room_Toolbox_Layout]', () => {
|
||||
const validLayout = [
|
||||
{ roomType: ['c', 'p'], maxVisibleNormal: 3, items: [{ id: 'members-list', featured: true, order: 1 }] },
|
||||
{ roomType: ['d'], maxVisibleNormal: 1 },
|
||||
];
|
||||
|
||||
const saveLayout = (value: string) => request.post(api('settings/Room_Toolbox_Layout')).set(credentials).send({ value });
|
||||
|
||||
after(() => updateSetting('Room_Toolbox_Layout', ''));
|
||||
|
||||
it('should reject a value that is not valid JSON', async () => {
|
||||
await saveLayout('not json {')
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error', 'Room_Toolbox_Layout_Invalid');
|
||||
expect(res.body).to.have.property('errorType', 'error-setting-validation-failed');
|
||||
});
|
||||
});
|
||||
|
||||
it('should reject an entry declaring an unsupported room type', async () => {
|
||||
await saveLayout(JSON.stringify([{ roomType: ['l'] }]))
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error', 'Room_Toolbox_Layout_Invalid');
|
||||
expect(res.body).to.have.property('errorType', 'error-setting-validation-failed');
|
||||
});
|
||||
});
|
||||
|
||||
it('should reject entries claiming the same room type', async () => {
|
||||
await saveLayout(JSON.stringify([{ roomType: ['c', 'p'] }, { roomType: ['c'] }]))
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error', 'Room_Toolbox_Layout_Invalid');
|
||||
expect(res.body).to.have.property('errorType', 'error-setting-validation-failed');
|
||||
});
|
||||
});
|
||||
|
||||
it('should reject an entry repeating a room type', async () => {
|
||||
await saveLayout(JSON.stringify([{ roomType: ['c', 'c'] }]))
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error', 'Room_Toolbox_Layout_Invalid');
|
||||
expect(res.body).to.have.property('errorType', 'error-setting-validation-failed');
|
||||
});
|
||||
});
|
||||
|
||||
it('should reject an entry carrying an unknown key', async () => {
|
||||
await saveLayout(JSON.stringify([{ roomType: ['c'], somethingElse: true }]))
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error', 'Room_Toolbox_Layout_Invalid');
|
||||
expect(res.body).to.have.property('errorType', 'error-setting-validation-failed');
|
||||
});
|
||||
});
|
||||
|
||||
it('should accept a document matching the schema', async () => {
|
||||
await saveLayout(JSON.stringify(validLayout))
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should accept an empty value, leaving the setting unconfigured', async () => {
|
||||
await saveLayout('')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/service.configurations]', () => {
|
||||
it('should return service configurations', async () => {
|
||||
const res = await request.get(api('service.configurations')).set(credentials).expect('Content-Type', 'application/json').expect(200);
|
||||
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('configurations');
|
||||
});
|
||||
|
||||
describe('With OAuth enabled', () => {
|
||||
before(() => updateSetting('Accounts_OAuth_Google', true));
|
||||
|
||||
after(() => updateSetting('Accounts_OAuth_Google', false));
|
||||
|
||||
it('should include the OAuth service in the response', (done) => {
|
||||
// wait 3 seconds before getting the service list so the server has had time to update it
|
||||
setTimeout(() => {
|
||||
void request
|
||||
.get(api('service.configurations'))
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('configurations');
|
||||
|
||||
expect((res.body.configurations as LoginServiceConfiguration[]).find(({ service }) => service === 'google')).to.exist;
|
||||
})
|
||||
.end(done);
|
||||
}, 3000);
|
||||
});
|
||||
});
|
||||
|
||||
describe('With OAuth disabled', () => {
|
||||
before(() => updateSetting('Accounts_OAuth_Google', false));
|
||||
|
||||
after(() => updateSetting('Accounts_OAuth_Google', false));
|
||||
|
||||
it('should not include the OAuth service in the response', (done) => {
|
||||
// wait 3 seconds before getting the service list so the server has had time to update it
|
||||
setTimeout(() => {
|
||||
void request
|
||||
.get(api('service.configurations'))
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('configurations');
|
||||
|
||||
expect((res.body.configurations as LoginServiceConfiguration[]).find(({ service }) => service === 'google')).to.not.exist;
|
||||
})
|
||||
.end(done);
|
||||
}, 3000);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('/settings.oauth', () => {
|
||||
it('should have return list of available oauth services when user is not logged', async () => {
|
||||
const res = await request.get(api('settings.oauth')).expect('Content-Type', 'application/json').expect(200);
|
||||
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('services').and.to.be.an('array');
|
||||
});
|
||||
|
||||
it('should have return list of available oauth services when user is logged', async () => {
|
||||
const res = await request.get(api('settings.oauth')).set(credentials).expect('Content-Type', 'application/json').expect(200);
|
||||
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('services').and.to.be.an('array');
|
||||
});
|
||||
});
|
||||
|
||||
(IS_EE ? describe : describe.skip)('/audit.settings', () => {
|
||||
const formatDate = (date: Date) => date.toISOString();
|
||||
let startDate: Date;
|
||||
let endDate: Date;
|
||||
|
||||
before(async () => {
|
||||
// Changing these settings to true and then false ensures that we have audited events
|
||||
// So we can test filtering reliably
|
||||
startDate = new Date();
|
||||
await Promise.all([updateSetting('Accounts_AllowAnonymousRead', true), updateSetting('Accounts_AllowDeleteOwnAccount', true)]);
|
||||
|
||||
await Promise.all([updateSetting('Accounts_AllowAnonymousRead', false), updateSetting('Accounts_AllowDeleteOwnAccount', false)]);
|
||||
endDate = new Date();
|
||||
});
|
||||
|
||||
it('should return list of settings changed (no filters)', async () => {
|
||||
await request
|
||||
.get(api('audit.settings'))
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('events').and.to.be.an('array');
|
||||
expect(res.body.events.length).to.be.greaterThanOrEqual(4);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return list of settings between date ranges', async () => {
|
||||
await request
|
||||
.get(api('audit.settings'))
|
||||
.query({ start: formatDate(startDate), end: formatDate(endDate) })
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('events').and.to.be.an('array');
|
||||
expect(res.body.events.length).to.be.greaterThanOrEqual(4);
|
||||
(res.body.events as IServerEvents['settings.changed'][]).forEach((event) => {
|
||||
expect(new Date(event.ts).getTime()).to.be.greaterThanOrEqual(startDate.getTime());
|
||||
expect(new Date(event.ts).getTime()).to.be.lessThanOrEqual(endDate.getTime());
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw error when sending invalid dates', async () => {
|
||||
const startDate = new Date();
|
||||
const endDate = 'abcdef';
|
||||
|
||||
await request
|
||||
.get(api('audit.settings'))
|
||||
.query({ start: formatDate(startDate), end: endDate })
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error', 'The "end" query parameter must be a valid date.');
|
||||
});
|
||||
});
|
||||
|
||||
it('should return list of settings changed filtered by an actor', async () => {
|
||||
await request
|
||||
.get(api('audit.settings'))
|
||||
.query({ actor: { type: 'user' }, count: 10 })
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('events').and.to.be.an('array');
|
||||
expect(res.body.events.length).to.be.greaterThanOrEqual(1);
|
||||
(res.body.events as IServerEvents['settings.changed'][]).forEach((sEvent) => expect(sEvent.actor.type).to.be.equal('user'));
|
||||
});
|
||||
});
|
||||
|
||||
it('should return list of changes of an specific setting', async () => {
|
||||
await request
|
||||
.get(api('audit.settings'))
|
||||
.query({ settingId: 'Site_Url' })
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('events').and.to.be.an('array');
|
||||
(res.body.events as IServerEvents['settings.changed'][]).forEach((event) => {
|
||||
expect(event.data[0].key).to.be.equal('id');
|
||||
expect(event.data[0].value).to.be.equal('Site_Url');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should return list of changes filtered by an actor between date ranges', async () => {
|
||||
await request
|
||||
.get(api('audit.settings'))
|
||||
.query({ actor: { type: 'user' }, start: formatDate(startDate), end: formatDate(endDate) })
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('events').and.to.be.an('array');
|
||||
expect(res.body.events.length).to.be.greaterThanOrEqual(4);
|
||||
(res.body.events as IServerEvents['settings.changed'][]).forEach((event) => {
|
||||
expect(event.actor.type).to.be.equal('user');
|
||||
expect(new Date(event.ts).getTime()).to.be.greaterThanOrEqual(startDate.getTime());
|
||||
expect(new Date(event.ts).getTime()).to.be.lessThanOrEqual(endDate.getTime());
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Masking sensitive settings', () => {
|
||||
let originalSmtpPassword: string | undefined;
|
||||
let originalSmtpUsername: string | undefined;
|
||||
let maskingStart: Date;
|
||||
let maskingEnd: Date;
|
||||
const testPassword = 'testpassword123';
|
||||
const testUsername = 'testuser@example.com';
|
||||
|
||||
before(async () => {
|
||||
maskingStart = new Date();
|
||||
originalSmtpPassword = (await getSettingValueById('SMTP_Password')) as string | undefined;
|
||||
originalSmtpUsername = (await getSettingValueById('SMTP_Username')) as string | undefined;
|
||||
|
||||
await updateSetting('SMTP_Password', testPassword);
|
||||
await updateSetting('SMTP_Username', testUsername);
|
||||
maskingEnd = new Date();
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await updateSetting('SMTP_Password', originalSmtpPassword || '');
|
||||
await updateSetting('SMTP_Username', originalSmtpUsername || '');
|
||||
});
|
||||
|
||||
it('should mask sensitive settings in audit logs', async () => {
|
||||
await request
|
||||
.get(api('audit.settings'))
|
||||
.query({ settingId: 'SMTP_Password', start: formatDate(maskingStart), end: formatDate(maskingEnd) })
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('events').and.to.be.an('array');
|
||||
expect(res.body.events.length).to.be.greaterThanOrEqual(1);
|
||||
|
||||
const passwordEvents = res.body.events as IServerEvents['settings.changed'][];
|
||||
const passwordEvent = passwordEvents.find((event) => {
|
||||
const settingId = event.data.find((item) => item.key === 'id')?.value;
|
||||
return settingId === 'SMTP_Password';
|
||||
});
|
||||
|
||||
expect(passwordEvent).to.exist;
|
||||
|
||||
if (passwordEvent) {
|
||||
const previous = passwordEvent.data.find((item) => item.key === 'previous')?.value;
|
||||
const current = passwordEvent.data.find((item) => item.key === 'current')?.value;
|
||||
|
||||
if (previous && typeof previous === 'string' && previous.length > 0) {
|
||||
expect(previous).to.include('*');
|
||||
expect(previous).to.not.equal(testPassword);
|
||||
if (previous.length > 8) {
|
||||
expect(previous.substring(0, 3)).to.equal(originalSmtpPassword?.substring(0, 3));
|
||||
expect(previous.substring(3)).to.match(/^\*+$/);
|
||||
}
|
||||
}
|
||||
|
||||
if (current && typeof current === 'string' && current.length > 0) {
|
||||
expect(current).to.include('*');
|
||||
expect(current).to.not.equal(testPassword);
|
||||
if (testPassword.length > 8) {
|
||||
expect(current.substring(0, 3)).to.equal(testPassword.substring(0, 3));
|
||||
expect(current.substring(3)).to.match(/^\*+$/);
|
||||
} else {
|
||||
expect(current).to.match(/^\*+$/);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
await request
|
||||
.get(api('audit.settings'))
|
||||
.query({ settingId: 'SMTP_Username', start: formatDate(maskingStart), end: formatDate(maskingEnd) })
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('events').and.to.be.an('array');
|
||||
expect(res.body.events.length).to.be.greaterThanOrEqual(1);
|
||||
|
||||
const usernameEvents = res.body.events as IServerEvents['settings.changed'][];
|
||||
const usernameEvent = usernameEvents.find((event) => {
|
||||
const settingId = event.data.find((item) => item.key === 'id')?.value;
|
||||
return settingId === 'SMTP_Username';
|
||||
});
|
||||
|
||||
expect(usernameEvent).to.exist;
|
||||
|
||||
if (usernameEvent) {
|
||||
const previous = usernameEvent.data.find((item) => item.key === 'previous')?.value;
|
||||
const current = usernameEvent.data.find((item) => item.key === 'current')?.value;
|
||||
|
||||
if (previous && typeof previous === 'string' && previous.length > 0) {
|
||||
expect(previous).to.include('*');
|
||||
expect(previous).to.not.equal(originalSmtpUsername);
|
||||
}
|
||||
|
||||
if (current && typeof current === 'string' && current.length > 0) {
|
||||
expect(current).to.include('*');
|
||||
expect(current).to.not.equal(testUsername);
|
||||
if (testUsername.length > 8) {
|
||||
expect(current.substring(0, 3)).to.equal(testUsername.substring(0, 3));
|
||||
expect(current.substring(3)).to.match(/^\*+$/);
|
||||
} else {
|
||||
expect(current).to.match(/^\*+$/);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Sorting', () => {
|
||||
const isSortedAscending = (events: IServerEvents['settings.changed'][]) =>
|
||||
events.every((event, i) => i === 0 || new Date(event.ts).getTime() >= new Date(events[i - 1].ts).getTime());
|
||||
|
||||
const isSortedDescending = (events: IServerEvents['settings.changed'][]) =>
|
||||
events.every((event, i) => i === 0 || new Date(event.ts).getTime() <= new Date(events[i - 1].ts).getTime());
|
||||
|
||||
it('should sort by timestamp ascending', async () => {
|
||||
const response = await request
|
||||
.get(api('audit.settings'))
|
||||
.query({ sort: JSON.stringify({ ts: 1 }), start: formatDate(startDate), end: formatDate(endDate) })
|
||||
.set(credentials)
|
||||
.expect(200);
|
||||
|
||||
expect(response.body).to.have.property('success', true);
|
||||
expect(response.body.events).to.have.length.greaterThanOrEqual(2);
|
||||
expect(isSortedAscending(response.body.events)).to.be.true;
|
||||
});
|
||||
|
||||
it('should sort by timestamp descending', async () => {
|
||||
const response = await request
|
||||
.get(api('audit.settings'))
|
||||
.query({ sort: JSON.stringify({ ts: -1 }), start: formatDate(startDate), end: formatDate(endDate) })
|
||||
.set(credentials)
|
||||
.expect(200);
|
||||
|
||||
expect(response.body).to.have.property('success', true);
|
||||
expect(response.body.events).to.have.length.greaterThanOrEqual(2);
|
||||
expect(isSortedDescending(response.body.events)).to.be.true;
|
||||
});
|
||||
|
||||
it('should sort by timestamp descending by default', async () => {
|
||||
const response = await request.get(api('audit.settings')).set(credentials).expect(200);
|
||||
|
||||
expect(response.body).to.have.property('success', true);
|
||||
expect(response.body.events).to.have.length.greaterThanOrEqual(2);
|
||||
expect(isSortedDescending(response.body.events)).to.be.true;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Custom OAuth admin endpoints', () => {
|
||||
const oauthName = 'apitest';
|
||||
const enableSettingId = 'Accounts_OAuth_Custom-Apitest';
|
||||
|
||||
before(() => updatePermission('add-oauth-service', ['admin']));
|
||||
|
||||
// guarantee the permission is restored even if a permission-denied assertion fails mid-test,
|
||||
// so a failure cannot cascade into the following cases
|
||||
afterEach(() => updatePermission('add-oauth-service', ['admin']));
|
||||
|
||||
after(async () => {
|
||||
await updatePermission('add-oauth-service', ['admin']);
|
||||
// best-effort cleanup in case a test left the custom service behind
|
||||
await request.post(api('settings.removeCustomOAuth')).set(credentials).send({ name: oauthName });
|
||||
});
|
||||
|
||||
describe('[/settings.addCustomOAuth]', () => {
|
||||
it('should fail when unauthenticated', () => request.post(api('settings.addCustomOAuth')).send({ name: oauthName }).expect(401));
|
||||
|
||||
it('should fail when the "name" param is not provided', () =>
|
||||
request.post(api('settings.addCustomOAuth')).set(credentials).send({}).expect(400));
|
||||
|
||||
it('should fail when the user does not have the add-oauth-service permission', async () => {
|
||||
await updatePermission('add-oauth-service', []);
|
||||
await request
|
||||
.post(api('settings.addCustomOAuth'))
|
||||
.set(credentials)
|
||||
.send({ name: oauthName })
|
||||
.expect((res) => {
|
||||
expect(res.status).to.equal(403);
|
||||
expect(res.body).to.have.property('success', false);
|
||||
});
|
||||
});
|
||||
|
||||
it('should add a custom oauth service', async () => {
|
||||
await request
|
||||
.post(api('settings.addCustomOAuth'))
|
||||
.set(credentials)
|
||||
.send({ name: oauthName })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
});
|
||||
|
||||
const value = await getSettingValueById(enableSettingId);
|
||||
expect(value).to.be.a('boolean');
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/settings.refreshOAuthServices]', () => {
|
||||
it('should fail when unauthenticated', () => request.post(api('settings.refreshOAuthServices')).expect(401));
|
||||
|
||||
it('should fail when the user does not have the add-oauth-service permission', async () => {
|
||||
await updatePermission('add-oauth-service', []);
|
||||
await request
|
||||
.post(api('settings.refreshOAuthServices'))
|
||||
.set(credentials)
|
||||
.expect((res) => {
|
||||
expect(res.status).to.equal(403);
|
||||
expect(res.body).to.have.property('success', false);
|
||||
});
|
||||
});
|
||||
|
||||
it('should refresh the oauth services', () =>
|
||||
request
|
||||
.post(api('settings.refreshOAuthServices'))
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('[/settings.removeCustomOAuth]', () => {
|
||||
it('should fail when unauthenticated', () => request.post(api('settings.removeCustomOAuth')).send({ name: oauthName }).expect(401));
|
||||
|
||||
it('should fail when the "name" param is not provided', () =>
|
||||
request.post(api('settings.removeCustomOAuth')).set(credentials).send({}).expect(400));
|
||||
|
||||
it('should fail when the "name" normalizes to an empty string', () =>
|
||||
request
|
||||
.post(api('settings.removeCustomOAuth'))
|
||||
.set(credentials)
|
||||
.send({ name: '!!!' })
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
}));
|
||||
|
||||
it('should fail when the user does not have the add-oauth-service permission', async () => {
|
||||
await updatePermission('add-oauth-service', []);
|
||||
await request
|
||||
.post(api('settings.removeCustomOAuth'))
|
||||
.set(credentials)
|
||||
.send({ name: oauthName })
|
||||
.expect((res) => {
|
||||
expect(res.status).to.equal(403);
|
||||
expect(res.body).to.have.property('success', false);
|
||||
});
|
||||
});
|
||||
|
||||
it('should remove a custom oauth service', async () => {
|
||||
await request
|
||||
.post(api('settings.removeCustomOAuth'))
|
||||
.set(credentials)
|
||||
.send({ name: oauthName })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
});
|
||||
|
||||
// the provider settings should be gone: settings/:_id answers 400 for an unknown id
|
||||
await request.get(`/api/v1/settings/${enableSettingId}`).set(credentials).expect(400);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
87
apps/meteor/tests/end-to-end/api/setupWizard.ts
Normal file
87
apps/meteor/tests/end-to-end/api/setupWizard.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import type { ISetting } from '@rocket.chat/core-typings';
|
||||
import { expect } from 'chai';
|
||||
import { after, before, describe, it } from 'mocha';
|
||||
import type { Response } from 'supertest';
|
||||
|
||||
import { getCredentials, api, request, credentials } from '../../data/api-data';
|
||||
import { getSettingValueById, updateSetting } from '../../data/permissions.helper';
|
||||
|
||||
describe('[SetupWizard]', function () {
|
||||
this.retries(0);
|
||||
|
||||
before((done) => getCredentials(done));
|
||||
|
||||
describe('[/setupWizard.parameters]', () => {
|
||||
it('should return the wizard parameters without authentication', async () => {
|
||||
return request
|
||||
.get(api('setupWizard.parameters'))
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('serverAlreadyRegistered').that.is.a('boolean');
|
||||
expect(res.body).to.have.property('settings').that.is.an('array');
|
||||
});
|
||||
});
|
||||
|
||||
it('should only return settings that belong to the wizard', async () => {
|
||||
return request
|
||||
.get(api('setupWizard.parameters'))
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body.settings).to.not.be.empty;
|
||||
res.body.settings.forEach((setting: Record<string, unknown>) => {
|
||||
expect(setting).to.have.property('wizard');
|
||||
expect(setting).to.have.property('_id');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should return the wizard parameters when authenticated', async () => {
|
||||
return request
|
||||
.get(api('setupWizard.parameters'))
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('settings').that.is.an('array');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the setup wizard is already completed', () => {
|
||||
let previousState: ISetting['value'];
|
||||
|
||||
before(async () => {
|
||||
previousState = await getSettingValueById('Show_Setup_Wizard');
|
||||
await updateSetting('Show_Setup_Wizard', 'completed');
|
||||
});
|
||||
|
||||
after(async () => updateSetting('Show_Setup_Wizard', previousState));
|
||||
|
||||
it('should not expose the wizard parameters to unauthenticated requests', async () => {
|
||||
return request
|
||||
.get(api('setupWizard.parameters'))
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(403)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.not.have.property('settings');
|
||||
expect(res.body).to.not.have.property('serverAlreadyRegistered');
|
||||
});
|
||||
});
|
||||
|
||||
it('should not expose the wizard parameters to authenticated requests', async () => {
|
||||
return request
|
||||
.get(api('setupWizard.parameters'))
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(403)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.not.have.property('settings');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
334
apps/meteor/tests/end-to-end/api/sidebar-custom-categories.ts
Normal file
334
apps/meteor/tests/end-to-end/api/sidebar-custom-categories.ts
Normal file
@@ -0,0 +1,334 @@
|
||||
import type { Credentials } from '@rocket.chat/api-client';
|
||||
import type { ISidebarCategory, IUser } from '@rocket.chat/core-typings';
|
||||
import { Random } from '@rocket.chat/random';
|
||||
import { expect } from 'chai';
|
||||
import { after, before, describe, it } from 'mocha';
|
||||
|
||||
import { getCredentials, api, request, credentials } from '../../data/api-data';
|
||||
import { createRoom, deleteRoom, getSubscriptionByRoomId, addUserToRoom } from '../../data/rooms.helper';
|
||||
import { password } from '../../data/user';
|
||||
import { createUser, login, deleteUser } from '../../data/users.helper';
|
||||
import { IS_EE } from '../../e2e/config/constants';
|
||||
|
||||
const experimentalEndpoint = (path: string) => `/api/experimental/${path}`;
|
||||
|
||||
/**
|
||||
* Persistence + validation of the `sidebarCategories` user preference (custom sidebar categories).
|
||||
*
|
||||
* The feature has no dedicated endpoint — categories are stored on the user preferences and round-trip
|
||||
* through `users.setPreferences` / `users.getPreferences`. The REST schema validates each entry with
|
||||
* `required: ['_id', 'name']` and `additionalProperties: false`, so malformed payloads are rejected.
|
||||
*/
|
||||
describe('[Sidebar Custom Categories]', () => {
|
||||
let testUser: IUser;
|
||||
let testUserCredentials: Credentials;
|
||||
|
||||
const category = (overrides: Partial<ISidebarCategory> = {}): ISidebarCategory => ({
|
||||
_id: Random.id(),
|
||||
name: `category-${Random.id()}`,
|
||||
...overrides,
|
||||
});
|
||||
|
||||
before((done) => getCredentials(done));
|
||||
|
||||
before(async () => {
|
||||
testUser = await createUser();
|
||||
testUserCredentials = await login(testUser.username, password);
|
||||
});
|
||||
|
||||
after(() => deleteUser(testUser));
|
||||
|
||||
const setCategories = (data: unknown, asCredentials: Credentials = testUserCredentials) =>
|
||||
request
|
||||
.post(api('users.setPreferences'))
|
||||
.set(asCredentials)
|
||||
.send({ data: { sidebarCategories: data } });
|
||||
|
||||
describe('persistence', () => {
|
||||
it('should persist a minimal category (only the required _id + name)', async () => {
|
||||
const categories = [category({ name: 'Projects' })];
|
||||
|
||||
await setCategories(categories)
|
||||
.expect(200)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body.user.settings.preferences).to.have.property('sidebarCategories').that.is.an('array').with.lengthOf(1);
|
||||
});
|
||||
|
||||
await request
|
||||
.get(api('users.getPreferences'))
|
||||
.set(testUserCredentials)
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body.preferences.sidebarCategories).to.deep.equal(categories);
|
||||
});
|
||||
});
|
||||
|
||||
it('should persist the optional showUnreads field verbatim', async () => {
|
||||
const categories = [category({ name: 'Team', showUnreads: false }), category({ name: 'Personal', showUnreads: true })];
|
||||
|
||||
await setCategories(categories).expect(200);
|
||||
|
||||
await request
|
||||
.get(api('users.getPreferences'))
|
||||
.set(testUserCredentials)
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body.preferences.sidebarCategories).to.deep.equal(categories);
|
||||
});
|
||||
});
|
||||
|
||||
it('should preserve the array order (order is the render order)', async () => {
|
||||
const categories = [category({ name: 'A' }), category({ name: 'B' }), category({ name: 'C' })];
|
||||
|
||||
await setCategories(categories).expect(200);
|
||||
|
||||
await request
|
||||
.get(api('users.getPreferences'))
|
||||
.set(testUserCredentials)
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body.preferences.sidebarCategories.map((c: ISidebarCategory) => c.name)).to.deep.equal(['A', 'B', 'C']);
|
||||
});
|
||||
});
|
||||
|
||||
it('should replace the whole array on each write (read-modify-write semantics)', async () => {
|
||||
await setCategories([category({ name: 'first' })]).expect(200);
|
||||
const replacement = [category({ name: 'second' })];
|
||||
await setCategories(replacement).expect(200);
|
||||
|
||||
await request
|
||||
.get(api('users.getPreferences'))
|
||||
.set(testUserCredentials)
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body.preferences.sidebarCategories).to.have.lengthOf(1);
|
||||
expect(res.body.preferences.sidebarCategories[0]).to.have.property('name', 'second');
|
||||
});
|
||||
});
|
||||
|
||||
it('should accept an empty array (clearing all categories)', async () => {
|
||||
await setCategories([category({ name: 'temp' })]).expect(200);
|
||||
await setCategories([]).expect(200);
|
||||
|
||||
await request
|
||||
.get(api('users.getPreferences'))
|
||||
.set(testUserCredentials)
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body.preferences.sidebarCategories).to.be.an('array').with.lengthOf(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('validation', () => {
|
||||
const expectInvalid = (res: { body: { success: boolean; errorType: string } }) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('errorType', 'invalid-params');
|
||||
};
|
||||
|
||||
// NOTE: the shared ajv instance runs with `coerceTypes`, so a scalar type mismatch on a coercible
|
||||
// field (e.g. a numeric `name`, or a numeric entry in `rooms`) is coerced to the schema type and
|
||||
// accepted rather than rejected. The assertions below therefore cover the constraints coercion
|
||||
// cannot satisfy: missing required fields, unknown properties, a non-array root, and a value that
|
||||
// cannot be coerced to the target type.
|
||||
|
||||
it('should reject an entry missing the required "name"', async () => {
|
||||
await setCategories([{ _id: Random.id() }])
|
||||
.expect(400)
|
||||
.expect(expectInvalid);
|
||||
});
|
||||
|
||||
it('should reject an entry missing the required "_id"', async () => {
|
||||
await setCategories([{ name: 'no-id' }])
|
||||
.expect(400)
|
||||
.expect(expectInvalid);
|
||||
});
|
||||
|
||||
it('should reject an unknown property on an entry (additionalProperties: false)', async () => {
|
||||
await setCategories([{ _id: Random.id(), name: 'x', color: 'red' }])
|
||||
.expect(400)
|
||||
.expect(expectInvalid);
|
||||
});
|
||||
|
||||
it('should reject a non-array value', async () => {
|
||||
await setCategories({ _id: Random.id(), name: 'not-an-array' }).expect(400).expect(expectInvalid);
|
||||
});
|
||||
|
||||
it('should reject a "showUnreads" that cannot be coerced to a boolean', async () => {
|
||||
await setCategories([{ _id: Random.id(), name: 'x', showUnreads: 'yes' }])
|
||||
.expect(400)
|
||||
.expect(expectInvalid);
|
||||
});
|
||||
|
||||
it('should return 401 when not authenticated', async () => {
|
||||
await request
|
||||
.post(api('users.setPreferences'))
|
||||
.send({ data: { sidebarCategories: [] } })
|
||||
.expect(401);
|
||||
});
|
||||
});
|
||||
|
||||
describe('permissions', () => {
|
||||
it("should let an admin set another user's sidebarCategories", async () => {
|
||||
const categories = [category({ name: 'set-by-admin' })];
|
||||
|
||||
await request
|
||||
.post(api('users.setPreferences'))
|
||||
.set(credentials)
|
||||
.send({ userId: testUser._id, data: { sidebarCategories: categories } })
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
});
|
||||
|
||||
await request
|
||||
.get(api('users.getPreferences'))
|
||||
.set(testUserCredentials)
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body.preferences.sidebarCategories).to.deep.equal(categories);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
(IS_EE ? describe : describe.skip)('[Experimental] rooms.setCategory', () => {
|
||||
let catId: string;
|
||||
let roomId: string;
|
||||
let roomId2: string;
|
||||
|
||||
const setCategory = (body: object, creds: Credentials = testUserCredentials) =>
|
||||
request.post(experimentalEndpoint('rooms.setCategory')).set(creds).send(body);
|
||||
|
||||
before(async () => {
|
||||
catId = Random.id();
|
||||
await setCategories([{ _id: catId, name: 'E2E Cat' }]);
|
||||
|
||||
// Create rooms as admin (regular users lack create-c permission by default),
|
||||
// then invite the test user so they are subscribed.
|
||||
const r1 = await createRoom({ type: 'c', name: `setcat-${Random.id()}` }).expect(200);
|
||||
roomId = r1.body.channel._id;
|
||||
await addUserToRoom({ usernames: [testUser.username as string], rid: roomId, type: 'c' });
|
||||
|
||||
const r2 = await createRoom({ type: 'c', name: `setcat-${Random.id()}` }).expect(200);
|
||||
roomId2 = r2.body.channel._id;
|
||||
await addUserToRoom({ usernames: [testUser.username as string], rid: roomId2, type: 'c' });
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await setCategories([]);
|
||||
await Promise.all([
|
||||
...(roomId ? [deleteRoom({ type: 'c', roomId })] : []),
|
||||
...(roomId2 ? [deleteRoom({ type: 'c', roomId: roomId2 })] : []),
|
||||
]);
|
||||
});
|
||||
|
||||
it('should assign a room to a category and persist it on the subscription', async () => {
|
||||
await setCategory({ roomIds: [roomId], category: catId })
|
||||
.expect(200)
|
||||
.expect((res) => expect(res.body).to.have.property('success', true));
|
||||
|
||||
const sub = await getSubscriptionByRoomId(roomId, testUserCredentials);
|
||||
expect(sub).to.have.property('category', catId);
|
||||
});
|
||||
|
||||
it('should unassign a room from its category when category is null', async () => {
|
||||
await setCategory({ roomIds: [roomId], category: catId }).expect(200);
|
||||
await setCategory({ roomIds: [roomId], category: null }).expect(200);
|
||||
|
||||
const sub = await getSubscriptionByRoomId(roomId, testUserCredentials);
|
||||
expect(sub).to.not.have.property('category');
|
||||
});
|
||||
|
||||
it('should assign multiple rooms to a category in one call', async () => {
|
||||
await setCategory({ roomIds: [roomId, roomId2], category: catId }).expect(200);
|
||||
|
||||
const [s1, s2] = await Promise.all([
|
||||
getSubscriptionByRoomId(roomId, testUserCredentials),
|
||||
getSubscriptionByRoomId(roomId2, testUserCredentials),
|
||||
]);
|
||||
expect(s1).to.have.property('category', catId);
|
||||
expect(s2).to.have.property('category', catId);
|
||||
});
|
||||
|
||||
it('should reject duplicate roomIds', async () => {
|
||||
await setCategory({ roomIds: [roomId, roomId], category: catId }).expect(400);
|
||||
});
|
||||
|
||||
it('should return 401 when not authenticated', async () => {
|
||||
await request
|
||||
.post(experimentalEndpoint('rooms.setCategory'))
|
||||
.send({ roomIds: [roomId], category: catId })
|
||||
.expect(401);
|
||||
});
|
||||
|
||||
describe('validation', () => {
|
||||
it('should reject a missing roomIds field', async () => {
|
||||
await setCategory({ category: catId }).expect(400);
|
||||
});
|
||||
|
||||
it('should reject an empty roomIds array (minItems: 1)', async () => {
|
||||
await setCategory({ roomIds: [], category: catId }).expect(400);
|
||||
});
|
||||
|
||||
it('should reject a missing category field', async () => {
|
||||
await setCategory({ roomIds: [roomId] }).expect(400);
|
||||
});
|
||||
|
||||
it('should reject unknown properties (additionalProperties: false)', async () => {
|
||||
await setCategory({ roomIds: [roomId], category: catId, extra: true }).expect(400);
|
||||
});
|
||||
});
|
||||
|
||||
describe('business logic', () => {
|
||||
it('should reject a category id not found in the user preferences', async () => {
|
||||
await setCategory({ roomIds: [roomId], category: Random.id() })
|
||||
.expect(400)
|
||||
.expect((res) => expect(res.body).to.have.property('success', false));
|
||||
});
|
||||
|
||||
it('should silently skip a room the user is not subscribed to', async () => {
|
||||
const adminRoom = await createRoom({ type: 'c', name: `admin-only-${Random.id()}` }).expect(200);
|
||||
const adminRoomId = adminRoom.body.channel._id;
|
||||
try {
|
||||
await setCategory({ roomIds: [adminRoomId], category: catId }).expect(200);
|
||||
// The subscription for adminRoom belongs to the admin, not testUser — setCategoryByRoomIdsAndUserId
|
||||
// filters by userId, so the unsubscribed room is silently ignored.
|
||||
const sub = await getSubscriptionByRoomId(adminRoomId, credentials);
|
||||
expect(sub).to.not.have.property('category');
|
||||
} finally {
|
||||
await deleteRoom({ type: 'c', roomId: adminRoomId });
|
||||
}
|
||||
});
|
||||
|
||||
it('should remove a room from favorites when assigning it to a category', async () => {
|
||||
// First favorite the room
|
||||
await request.post(api('rooms.favorite')).set(testUserCredentials).send({ roomId, favorite: true }).expect(200);
|
||||
let sub = await getSubscriptionByRoomId(roomId, testUserCredentials);
|
||||
expect(sub).to.have.property('f', true);
|
||||
expect(sub).to.not.have.property('category');
|
||||
|
||||
// Now assign it to a category — server atomically unsets f
|
||||
await setCategory({ roomIds: [roomId], category: catId }).expect(200);
|
||||
sub = await getSubscriptionByRoomId(roomId, testUserCredentials);
|
||||
expect(sub).to.have.property('category', catId);
|
||||
expect(sub).to.have.property('f', false);
|
||||
});
|
||||
|
||||
it('should remove a room from its category when favoriting it', async () => {
|
||||
// First assign the room to a category
|
||||
await setCategory({ roomIds: [roomId], category: catId }).expect(200);
|
||||
let sub = await getSubscriptionByRoomId(roomId, testUserCredentials);
|
||||
expect(sub).to.have.property('category', catId);
|
||||
|
||||
// Now favorite it — server atomically unsets category
|
||||
await request.post(api('rooms.favorite')).set(testUserCredentials).send({ roomId, favorite: true }).expect(200);
|
||||
sub = await getSubscriptionByRoomId(roomId, testUserCredentials);
|
||||
expect(sub).to.have.property('f', true);
|
||||
expect(sub).to.not.have.property('category');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
86
apps/meteor/tests/end-to-end/api/statistics.ts
Normal file
86
apps/meteor/tests/end-to-end/api/statistics.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import { expect } from 'chai';
|
||||
import { before, describe, it, after } from 'mocha';
|
||||
|
||||
import { getCredentials, api, request, credentials } from '../../data/api-data';
|
||||
import { updatePermission } from '../../data/permissions.helper';
|
||||
|
||||
describe('[Statistics]', () => {
|
||||
before((done) => getCredentials(done));
|
||||
|
||||
after(() => updatePermission('view-statistics', ['admin']));
|
||||
|
||||
describe('[/statistics]', () => {
|
||||
let lastUptime: unknown;
|
||||
it('should return an error when the user does not have the necessary permission', async () => {
|
||||
await updatePermission('view-statistics', []);
|
||||
|
||||
const res = await request.get(api('statistics')).set(credentials).expect(400);
|
||||
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body.error).to.be.equal('error-not-allowed');
|
||||
});
|
||||
|
||||
it('should return an object with the statistics', async () => {
|
||||
await updatePermission('view-statistics', ['admin']);
|
||||
|
||||
const res = await request.get(api('statistics')).set(credentials).expect(200);
|
||||
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('process');
|
||||
expect(res.body.process).to.have.property('uptime');
|
||||
|
||||
lastUptime = res.body.process.uptime;
|
||||
});
|
||||
|
||||
it('should update the statistics when is provided the "refresh:true" query parameter', async () => {
|
||||
const res = await request.get(api('statistics')).query({ refresh: 'true' }).set(credentials).expect(200);
|
||||
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('process');
|
||||
expect(res.body.process).to.have.property('uptime');
|
||||
expect(lastUptime).to.not.be.equal(res.body.process.uptime);
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/statistics.list]', () => {
|
||||
it('should return an error when the user does not have the necessary permission', async () => {
|
||||
await updatePermission('view-statistics', []);
|
||||
|
||||
const res = await request.get(api('statistics.list')).set(credentials).expect(400);
|
||||
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body.error).to.be.equal('error-not-allowed');
|
||||
});
|
||||
|
||||
it('should return an array with the statistics', async () => {
|
||||
await updatePermission('view-statistics', ['admin']);
|
||||
|
||||
const res = await request.get(api('statistics.list')).set(credentials).expect(200);
|
||||
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('statistics').and.to.be.an('array');
|
||||
expect(res.body).to.have.property('offset');
|
||||
expect(res.body).to.have.property('total');
|
||||
expect(res.body).to.have.property('count');
|
||||
});
|
||||
|
||||
it('should return an array with the statistics even requested with count and offset params', async () => {
|
||||
await updatePermission('view-statistics', ['admin']);
|
||||
|
||||
const res = await request
|
||||
.get(api('statistics.list'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
count: 5,
|
||||
offset: 0,
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('statistics').and.to.be.an('array');
|
||||
expect(res.body).to.have.property('offset');
|
||||
expect(res.body).to.have.property('total');
|
||||
expect(res.body).to.have.property('count');
|
||||
});
|
||||
});
|
||||
});
|
||||
373
apps/meteor/tests/end-to-end/api/status-visibility.ts
Normal file
373
apps/meteor/tests/end-to-end/api/status-visibility.ts
Normal file
@@ -0,0 +1,373 @@
|
||||
import type { Credentials } from '@rocket.chat/api-client';
|
||||
import type { IRoom, ITeam, IUser } from '@rocket.chat/core-typings';
|
||||
import { TeamType, UserStatus } from '@rocket.chat/core-typings';
|
||||
import { expect } from 'chai';
|
||||
import { after, before, describe, it } from 'mocha';
|
||||
|
||||
import { api, credentials, getCredentials, request } from '../../data/api-data';
|
||||
import { updateEESetting } from '../../data/permissions.helper';
|
||||
import { createRoom, deleteRoom } from '../../data/rooms.helper';
|
||||
import { createTeam, deleteTeam } from '../../data/teams.helper';
|
||||
import { password } from '../../data/user';
|
||||
import { createUser, deleteUser, login, setUserStatus } from '../../data/users.helper';
|
||||
import { IS_EE } from '../../e2e/config/constants';
|
||||
|
||||
(IS_EE ? describe : describe.skip)('[Status Visibility] (Enterprise Only)', function () {
|
||||
this.retries(0);
|
||||
|
||||
let hider: IUser & { username: string };
|
||||
let viewer: IUser & { username: string };
|
||||
let bystander: IUser & { username: string };
|
||||
let hiderCredentials: Credentials;
|
||||
let viewerCredentials: Credentials;
|
||||
let bystanderCredentials: Credentials;
|
||||
let channel: IRoom;
|
||||
let group: IRoom;
|
||||
let team: ITeam;
|
||||
|
||||
const usernamesOf = (members: { username?: string }[]) => members.map((member) => member.username);
|
||||
const statusOf = (members: { username?: string; status?: string }[], username: string) =>
|
||||
members.find((member) => member.username === username)?.status;
|
||||
|
||||
before((done) => getCredentials(done));
|
||||
|
||||
before(async () => {
|
||||
await updateEESetting('Accounts_StatusVisibility_Enabled', true);
|
||||
|
||||
[hider, viewer, bystander] = (await Promise.all([
|
||||
createUser({ joinDefaultChannels: false }),
|
||||
createUser({ joinDefaultChannels: false }),
|
||||
createUser({ joinDefaultChannels: false }),
|
||||
])) as (IUser & { username: string })[];
|
||||
|
||||
[hiderCredentials, viewerCredentials, bystanderCredentials] = await Promise.all([
|
||||
login(hider.username, password),
|
||||
login(viewer.username, password),
|
||||
login(bystander.username, password),
|
||||
]);
|
||||
|
||||
await setUserStatus(hiderCredentials, UserStatus.ONLINE);
|
||||
|
||||
await request
|
||||
.post(api('users.setPreferences'))
|
||||
.set(hiderCredentials)
|
||||
.send({ data: { statusVisibilityDenied: [viewer.username] } })
|
||||
.expect(200);
|
||||
|
||||
const members = [hider.username, viewer.username, bystander.username];
|
||||
|
||||
channel = (await createRoom({ type: 'c', name: `status-visibility-c-${Date.now()}`, members, credentials })).body.channel;
|
||||
group = (await createRoom({ type: 'p', name: `status-visibility-p-${Date.now()}`, members, credentials })).body.group;
|
||||
team = await createTeam(credentials, `status-visibility-t-${Date.now()}`, TeamType.PUBLIC, members);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await Promise.all([deleteRoom({ type: 'c', roomId: channel._id }), deleteRoom({ type: 'p', roomId: group._id })]);
|
||||
await deleteTeam(credentials, team.name);
|
||||
await Promise.all([deleteUser(hider), deleteUser(viewer), deleteUser(bystander)]);
|
||||
await updateEESetting('Accounts_StatusVisibility_Enabled', false);
|
||||
});
|
||||
|
||||
describe('[/rooms.membersOrderedByRole]', () => {
|
||||
it('should not return the hider when the viewer filters by online', async () => {
|
||||
await request
|
||||
.get(api('rooms.membersOrderedByRole'))
|
||||
.set(viewerCredentials)
|
||||
.query({ 'roomId': channel._id, 'status[]': UserStatus.ONLINE })
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(usernamesOf(res.body.members)).to.not.include(hider.username);
|
||||
expect(res.body.total).to.be.equal(res.body.members.length);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return the hider as online to a user they do not hide from', async () => {
|
||||
await request
|
||||
.get(api('rooms.membersOrderedByRole'))
|
||||
.set(bystanderCredentials)
|
||||
.query({ 'roomId': channel._id, 'status[]': UserStatus.ONLINE })
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(statusOf(res.body.members, hider.username)).to.be.equal(UserStatus.ONLINE);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return the hider as offline when the viewer does not filter', async () => {
|
||||
await request
|
||||
.get(api('rooms.membersOrderedByRole'))
|
||||
.set(viewerCredentials)
|
||||
.query({ roomId: channel._id })
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(usernamesOf(res.body.members)).to.include(hider.username);
|
||||
expect(statusOf(res.body.members, hider.username)).to.be.equal(UserStatus.OFFLINE);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return the hider when the viewer filters by offline', async () => {
|
||||
await request
|
||||
.get(api('rooms.membersOrderedByRole'))
|
||||
.set(viewerCredentials)
|
||||
.query({ 'roomId': channel._id, 'status[]': UserStatus.OFFLINE })
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(usernamesOf(res.body.members)).to.include(hider.username);
|
||||
expect(statusOf(res.body.members, hider.username)).to.be.equal(UserStatus.OFFLINE);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/channels.members]', () => {
|
||||
it('should not return the hider when the viewer filters by online', async () => {
|
||||
await request
|
||||
.get(api('channels.members'))
|
||||
.set(viewerCredentials)
|
||||
.query({ 'roomId': channel._id, 'status[]': UserStatus.ONLINE })
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(usernamesOf(res.body.members)).to.not.include(hider.username);
|
||||
expect(res.body.total).to.be.equal(res.body.members.length);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return the hider as online to a user they do not hide from', async () => {
|
||||
await request
|
||||
.get(api('channels.members'))
|
||||
.set(bystanderCredentials)
|
||||
.query({ 'roomId': channel._id, 'status[]': UserStatus.ONLINE })
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(statusOf(res.body.members, hider.username)).to.be.equal(UserStatus.ONLINE);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/channels.online]', () => {
|
||||
it('should not return the hider to the viewer', async () => {
|
||||
await request
|
||||
.get(api('channels.online'))
|
||||
.set(viewerCredentials)
|
||||
.query({ _id: channel._id })
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(usernamesOf(res.body.online)).to.not.include(hider.username);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return the hider to a user they do not hide from', async () => {
|
||||
await request
|
||||
.get(api('channels.online'))
|
||||
.set(bystanderCredentials)
|
||||
.query({ _id: channel._id })
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(usernamesOf(res.body.online)).to.include(hider.username);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/groups.members]', () => {
|
||||
it('should not return the hider when the viewer filters by online', async () => {
|
||||
await request
|
||||
.get(api('groups.members'))
|
||||
.set(viewerCredentials)
|
||||
.query({ 'roomId': group._id, 'status[]': UserStatus.ONLINE })
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(usernamesOf(res.body.members)).to.not.include(hider.username);
|
||||
expect(res.body.total).to.be.equal(res.body.members.length);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return the hider as online to a user they do not hide from', async () => {
|
||||
await request
|
||||
.get(api('groups.members'))
|
||||
.set(bystanderCredentials)
|
||||
.query({ 'roomId': group._id, 'status[]': UserStatus.ONLINE })
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(statusOf(res.body.members, hider.username)).to.be.equal(UserStatus.ONLINE);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/groups.online]', () => {
|
||||
it('should not return the hider to the viewer', async () => {
|
||||
await request
|
||||
.get(api('groups.online'))
|
||||
.set(viewerCredentials)
|
||||
.query({ _id: group._id })
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(usernamesOf(res.body.online)).to.not.include(hider.username);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return the hider to a user they do not hide from', async () => {
|
||||
await request
|
||||
.get(api('groups.online'))
|
||||
.set(bystanderCredentials)
|
||||
.query({ _id: group._id })
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(usernamesOf(res.body.online)).to.include(hider.username);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/teams.members]', () => {
|
||||
const teamUsernamesOf = (members: { user: IUser }[]) => members.map(({ user }) => user.username);
|
||||
|
||||
it('should not return the hider when the viewer filters by online', async () => {
|
||||
await request
|
||||
.get(api('teams.members'))
|
||||
.set(viewerCredentials)
|
||||
.query({ 'teamId': team._id, 'status[]': UserStatus.ONLINE })
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(teamUsernamesOf(res.body.members)).to.not.include(hider.username);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return the hider to a user they do not hide from', async () => {
|
||||
await request
|
||||
.get(api('teams.members'))
|
||||
.set(bystanderCredentials)
|
||||
.query({ 'teamId': team._id, 'status[]': UserStatus.ONLINE })
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(teamUsernamesOf(res.body.members)).to.include(hider.username);
|
||||
});
|
||||
});
|
||||
|
||||
it('should not expose the block list of any member', async () => {
|
||||
await request
|
||||
.get(api('teams.members'))
|
||||
.set(viewerCredentials)
|
||||
.query({ teamId: team._id })
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
res.body.members.forEach(({ user }: { user: IUser }) => {
|
||||
expect(user.settings?.preferences ?? {}).to.not.have.property('statusVisibilityDenied');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/users.getStatus]', () => {
|
||||
it('should report the hider as offline to the viewer', async () => {
|
||||
await request
|
||||
.get(api('users.getStatus'))
|
||||
.set(viewerCredentials)
|
||||
.query({ userId: hider._id })
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body.status).to.be.equal(UserStatus.OFFLINE);
|
||||
expect(res.body).to.not.have.property('statusSource');
|
||||
});
|
||||
});
|
||||
|
||||
it('should report the real status to a user they do not hide from', async () => {
|
||||
await request
|
||||
.get(api('users.getStatus'))
|
||||
.set(bystanderCredentials)
|
||||
.query({ userId: hider._id })
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body.status).to.be.equal(UserStatus.ONLINE);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/users.getPresence]', () => {
|
||||
it('should report the hider as offline to the viewer', async () => {
|
||||
await request
|
||||
.get(api('users.getPresence'))
|
||||
.set(viewerCredentials)
|
||||
.query({ userId: hider._id })
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body.presence).to.be.equal(UserStatus.OFFLINE);
|
||||
});
|
||||
});
|
||||
|
||||
it('should report the real presence to a user they do not hide from', async () => {
|
||||
await request
|
||||
.get(api('users.getPresence'))
|
||||
.set(bystanderCredentials)
|
||||
.query({ userId: hider._id })
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body.presence).to.be.equal(UserStatus.ONLINE);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/users.autocomplete]', () => {
|
||||
const selector = (conditions?: object) => JSON.stringify({ term: hider.username, exceptions: [], ...(conditions && { conditions }) });
|
||||
|
||||
it('should list the hider as offline to the viewer', async () => {
|
||||
await request
|
||||
.get(api('users.autocomplete'))
|
||||
.set(viewerCredentials)
|
||||
.query({ selector: selector() })
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(statusOf(res.body.items, hider.username)).to.be.equal(UserStatus.OFFLINE);
|
||||
});
|
||||
});
|
||||
|
||||
it('should list the real status to a user they do not hide from', async () => {
|
||||
await request
|
||||
.get(api('users.autocomplete'))
|
||||
.set(bystanderCredentials)
|
||||
.query({ selector: selector() })
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(statusOf(res.body.items, hider.username)).to.be.equal(UserStatus.ONLINE);
|
||||
});
|
||||
});
|
||||
|
||||
it('should not return the hider when the viewer filters by online through the selector conditions', async () => {
|
||||
await request
|
||||
.get(api('users.autocomplete'))
|
||||
.set(viewerCredentials)
|
||||
.query({ selector: selector({ status: UserStatus.ONLINE }) })
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(usernamesOf(res.body.items)).to.not.include(hider.username);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return the hider through the selector conditions to a user they do not hide from', async () => {
|
||||
await request
|
||||
.get(api('users.autocomplete'))
|
||||
.set(bystanderCredentials)
|
||||
.query({ selector: selector({ status: UserStatus.ONLINE }) })
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(usernamesOf(res.body.items)).to.include(hider.username);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/im.members]', () => {
|
||||
let dm: IRoom;
|
||||
|
||||
before(async () => {
|
||||
dm = (await createRoom({ type: 'd', username: hider.username, credentials: viewerCredentials })).body.room;
|
||||
});
|
||||
|
||||
it('should return the hider when the viewer filters by offline', async () => {
|
||||
await request
|
||||
.get(api('im.members'))
|
||||
.set(viewerCredentials)
|
||||
.query({ 'roomId': dm._id, 'status[]': UserStatus.OFFLINE })
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(usernamesOf(res.body.members)).to.include(hider.username);
|
||||
expect(statusOf(res.body.members, hider.username)).to.be.equal(UserStatus.OFFLINE);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
432
apps/meteor/tests/end-to-end/api/subscriptions.ts
Normal file
432
apps/meteor/tests/end-to-end/api/subscriptions.ts
Normal file
@@ -0,0 +1,432 @@
|
||||
import type { Credentials } from '@rocket.chat/api-client';
|
||||
import type { IRoom, IThreadMessage, IUser } from '@rocket.chat/core-typings';
|
||||
import { expect } from 'chai';
|
||||
import { after, before, describe, it } from 'mocha';
|
||||
|
||||
import { getCredentials, api, request, credentials } from '../../data/api-data';
|
||||
import { createRoom, deleteRoom } from '../../data/rooms.helper';
|
||||
import { adminUsername } from '../../data/user';
|
||||
import type { TestUser } from '../../data/users.helper';
|
||||
import { createUser, deleteUser, login } from '../../data/users.helper';
|
||||
|
||||
describe('[Subscriptions]', () => {
|
||||
before((done) => getCredentials(done));
|
||||
|
||||
let testChannel: IRoom;
|
||||
|
||||
before(async () => {
|
||||
testChannel = (await createRoom({ type: 'c', name: `channel.test.${Date.now()}` })).body.channel;
|
||||
});
|
||||
|
||||
after(() => deleteRoom({ type: 'c', roomId: testChannel._id }));
|
||||
|
||||
it('/subscriptions.get', async () => {
|
||||
const res = await request.get(api('subscriptions.get')).set(credentials).expect('Content-Type', 'application/json').expect(200);
|
||||
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('update');
|
||||
expect(res.body).to.have.property('remove');
|
||||
});
|
||||
|
||||
it('/subscriptions.get?updatedSince', async () => {
|
||||
const res = await request
|
||||
.get(api('subscriptions.get'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
updatedSince: new Date(),
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('update').that.have.lengthOf(0);
|
||||
expect(res.body).to.have.property('remove').that.have.lengthOf(0);
|
||||
});
|
||||
|
||||
describe('/subscriptions.getOne', () => {
|
||||
it('should fail if no roomId provided', async () => {
|
||||
const res = await request.get(api('subscriptions.getOne')).set(credentials).expect('Content-Type', 'application/json').expect(400);
|
||||
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error', "must have required property 'roomId'");
|
||||
});
|
||||
|
||||
it('should fail if not logged in', async () => {
|
||||
const res = await request
|
||||
.get(api('subscriptions.getOne'))
|
||||
.query({
|
||||
roomId: testChannel._id,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(401);
|
||||
|
||||
expect(res.body).to.have.property('status', 'error');
|
||||
expect(res.body).to.have.property('message');
|
||||
});
|
||||
|
||||
it('should return the subscription with success', async () => {
|
||||
const res = await request
|
||||
.get(api('subscriptions.getOne'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
roomId: testChannel._id,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('subscription').and.to.be.an('object');
|
||||
});
|
||||
|
||||
it('should keep subscription as read after sending a message', async () => {
|
||||
await request
|
||||
.get(api('subscriptions.getOne'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
roomId: testChannel._id,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('subscription').and.to.be.an('object');
|
||||
expect(res.body.subscription).to.have.property('alert', false);
|
||||
});
|
||||
|
||||
await request
|
||||
.post(api('chat.sendMessage'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
message: {
|
||||
rid: testChannel._id,
|
||||
msg: 'Sample message',
|
||||
},
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('message').and.to.be.an('object');
|
||||
});
|
||||
|
||||
await request
|
||||
.get(api('subscriptions.getOne'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
roomId: testChannel._id,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('subscription').and.to.be.an('object');
|
||||
expect(res.body.subscription).to.have.property('alert', false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/subscriptions.read]', () => {
|
||||
let testChannel: IRoom;
|
||||
let testGroup: IRoom;
|
||||
let testDM: IRoom;
|
||||
let user: TestUser<IUser>;
|
||||
|
||||
before(async () => {
|
||||
user = await createUser();
|
||||
testChannel = (await createRoom({ type: 'c', name: `channel.test.${Date.now()}` })).body.channel;
|
||||
testGroup = (await createRoom({ type: 'p', name: `group.test.${Date.now()}` })).body.group;
|
||||
testDM = (await createRoom({ type: 'd', username: user.username })).body.room;
|
||||
});
|
||||
|
||||
after(() =>
|
||||
Promise.all([
|
||||
deleteRoom({ type: 'd', roomId: testDM._id }),
|
||||
deleteRoom({ type: 'c', roomId: testChannel._id }),
|
||||
deleteRoom({ type: 'p', roomId: testGroup._id }),
|
||||
deleteUser(user),
|
||||
]),
|
||||
);
|
||||
|
||||
it('should mark public channels as read', async () => {
|
||||
const res = await request
|
||||
.post(api('subscriptions.read'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
rid: testChannel._id,
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
expect(res.body).to.have.property('success', true);
|
||||
});
|
||||
|
||||
it('should mark groups as read', async () => {
|
||||
const res = await request
|
||||
.post(api('subscriptions.read'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
rid: testGroup._id,
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
expect(res.body).to.have.property('success', true);
|
||||
});
|
||||
|
||||
it('should mark DMs as read', async () => {
|
||||
const res = await request
|
||||
.post(api('subscriptions.read'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
rid: testDM._id,
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
expect(res.body).to.have.property('success', true);
|
||||
});
|
||||
|
||||
it('should fail on two params with different ids', async () => {
|
||||
const res = await request
|
||||
.post(api('subscriptions.read'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
rid: testDM._id,
|
||||
roomId: testChannel._id,
|
||||
})
|
||||
.expect(400);
|
||||
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('errorType', 'invalid-params');
|
||||
});
|
||||
|
||||
it('should fail on mark inexistent public channel as read', async () => {
|
||||
const res = await request
|
||||
.post(api('subscriptions.read'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
rid: 'foobar123-somechannel',
|
||||
})
|
||||
.expect(400);
|
||||
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error', 'error-invalid-subscription');
|
||||
});
|
||||
|
||||
it('should fail on mark inexistent group as read', async () => {
|
||||
const res = await request
|
||||
.post(api('subscriptions.read'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
rid: 'foobar123-somegroup',
|
||||
})
|
||||
.expect(400);
|
||||
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error', 'error-invalid-subscription');
|
||||
});
|
||||
|
||||
it('should fail on mark inexistent DM as read', async () => {
|
||||
const res = await request
|
||||
.post(api('subscriptions.read'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
rid: 'foobar123-somedm',
|
||||
})
|
||||
.expect(400);
|
||||
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error', 'error-invalid-subscription');
|
||||
});
|
||||
|
||||
it('should fail on invalid params', async () => {
|
||||
const res = await request
|
||||
.post(api('subscriptions.read'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
rid: 12345,
|
||||
})
|
||||
.expect(400);
|
||||
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error', 'error-invalid-subscription');
|
||||
});
|
||||
|
||||
it('should fail on empty params', async () => {
|
||||
const res = await request.post(api('subscriptions.read')).set(credentials).send({}).expect(400);
|
||||
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('errorType', 'invalid-params');
|
||||
});
|
||||
|
||||
describe('should handle threads correctly', () => {
|
||||
let threadId: IThreadMessage['_id'];
|
||||
let user: TestUser<IUser>;
|
||||
let threadUserCredentials: Credentials;
|
||||
|
||||
before(async () => {
|
||||
user = await createUser({ username: 'testthread123', password: 'testthread123' });
|
||||
threadUserCredentials = await login('testthread123', 'testthread123');
|
||||
|
||||
const res = await request
|
||||
.post(api('chat.sendMessage'))
|
||||
.set(threadUserCredentials)
|
||||
.send({
|
||||
message: {
|
||||
rid: testChannel._id,
|
||||
msg: 'Starting a Thread',
|
||||
},
|
||||
});
|
||||
|
||||
threadId = res.body.message._id;
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await deleteUser(user);
|
||||
});
|
||||
|
||||
it('should mark threads as read', async () => {
|
||||
await request
|
||||
.post(api('chat.sendMessage'))
|
||||
.set(threadUserCredentials)
|
||||
.send({
|
||||
message: {
|
||||
rid: testChannel._id,
|
||||
msg: `@${adminUsername} making admin follow this thread`,
|
||||
tmid: threadId,
|
||||
},
|
||||
});
|
||||
await request
|
||||
.post(api('subscriptions.read'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
rid: testChannel._id,
|
||||
readThreads: true,
|
||||
})
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
});
|
||||
|
||||
await request
|
||||
.get(api('subscriptions.getOne'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
roomId: testChannel._id,
|
||||
})
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body.subscription).to.not.have.property('tunread');
|
||||
});
|
||||
});
|
||||
|
||||
it('should not mark threads as read', async () => {
|
||||
await request
|
||||
.post(api('chat.sendMessage'))
|
||||
.set(threadUserCredentials)
|
||||
.send({
|
||||
message: {
|
||||
rid: testChannel._id,
|
||||
msg: `@${adminUsername} making admin follow this thread`,
|
||||
tmid: threadId,
|
||||
},
|
||||
});
|
||||
await request
|
||||
.post(api('subscriptions.read'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
rid: testChannel._id,
|
||||
readThreads: false,
|
||||
})
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
});
|
||||
|
||||
await request
|
||||
.get(api('subscriptions.getOne'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
roomId: testChannel._id,
|
||||
})
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body.subscription).to.have.property('tunread');
|
||||
expect(res.body.subscription.tunread).to.be.an('array');
|
||||
expect(res.body.subscription.tunread).to.deep.equal([threadId]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('[/subscriptions.unread]', () => {
|
||||
let testChannel: IRoom;
|
||||
|
||||
before(async () => {
|
||||
testChannel = (await createRoom({ type: 'c', name: `channel.test.${Date.now()}` })).body.channel;
|
||||
});
|
||||
|
||||
after(() => deleteRoom({ type: 'c', roomId: testChannel._id }));
|
||||
|
||||
it('should fail when there are no messages on an channel', async () => {
|
||||
const res = await request
|
||||
.post(api('subscriptions.unread'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
roomId: testChannel._id,
|
||||
})
|
||||
.expect(400);
|
||||
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error');
|
||||
expect(res.body).to.have.property('errorType', 'error-no-message-for-unread');
|
||||
});
|
||||
|
||||
it('sending message', async () => {
|
||||
const res = await request
|
||||
.post(api('chat.sendMessage'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
message: {
|
||||
rid: testChannel._id,
|
||||
msg: 'Sample message',
|
||||
},
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('message').and.to.be.an('object');
|
||||
});
|
||||
|
||||
it('should return success: true when make as unread successfully', async () => {
|
||||
const res = await request
|
||||
.post(api('subscriptions.unread'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
roomId: testChannel._id,
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
expect(res.body).to.have.property('success', true);
|
||||
});
|
||||
|
||||
it('should fail on invalid params', async () => {
|
||||
const res = await request
|
||||
.post(api('subscriptions.unread'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
roomId: 12345,
|
||||
})
|
||||
.expect(400);
|
||||
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error');
|
||||
});
|
||||
|
||||
it('should fail on empty params', async () => {
|
||||
const res = await request.post(api('subscriptions.unread')).set(credentials).send({}).expect(400);
|
||||
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error');
|
||||
});
|
||||
});
|
||||
});
|
||||
3019
apps/meteor/tests/end-to-end/api/teams.ts
Normal file
3019
apps/meteor/tests/end-to-end/api/teams.ts
Normal file
File diff suppressed because it is too large
Load Diff
6546
apps/meteor/tests/end-to-end/api/users.ts
Normal file
6546
apps/meteor/tests/end-to-end/api/users.ts
Normal file
File diff suppressed because it is too large
Load Diff
39
apps/meteor/tests/end-to-end/api/webdav.ts
Normal file
39
apps/meteor/tests/end-to-end/api/webdav.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { expect } from 'chai';
|
||||
import { before, describe, it } from 'mocha';
|
||||
|
||||
import { getCredentials, api, request, credentials } from '../../data/api-data';
|
||||
|
||||
describe('[Webdav]', () => {
|
||||
before((done) => getCredentials(done));
|
||||
|
||||
describe('/webdav.getMyAccounts', () => {
|
||||
it('should return my webdav accounts', async () => {
|
||||
const res = await request.get(api('webdav.getMyAccounts')).set(credentials).expect(200);
|
||||
|
||||
expect(res.body).to.have.property('success', true);
|
||||
expect(res.body).to.have.property('accounts').and.to.be.a('array');
|
||||
});
|
||||
});
|
||||
|
||||
describe('/webdav.removeWebdavAccount', () => {
|
||||
it('should return an error when send an invalid request', async () => {
|
||||
const res = await request.post(api('webdav.removeWebdavAccount')).set(credentials).send({}).expect(400);
|
||||
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error');
|
||||
});
|
||||
|
||||
it('should return an error when using an invalid account id', async () => {
|
||||
const res = await request
|
||||
.post(api('webdav.removeWebdavAccount'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
accountId: {},
|
||||
})
|
||||
.expect(400);
|
||||
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('error');
|
||||
});
|
||||
});
|
||||
});
|
||||
33
apps/meteor/tests/end-to-end/apps/app-api-endpoint-params.ts
Normal file
33
apps/meteor/tests/end-to-end/apps/app-api-endpoint-params.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import type { App } from '@rocket.chat/core-typings';
|
||||
import { expect } from 'chai';
|
||||
import { after, before, describe, it } from 'mocha';
|
||||
|
||||
import { getCredentials, request, credentials } from '../../data/api-data';
|
||||
import { appAPIParameterTest } from '../../data/apps/app-packages';
|
||||
import { apps } from '../../data/apps/apps-data';
|
||||
import { cleanupApps, installLocalTestPackage } from '../../data/apps/helper';
|
||||
import { IS_EE } from '../../e2e/config/constants';
|
||||
|
||||
(IS_EE ? describe : describe.skip)('Apps - API Endpoint Parameter Parsing', () => {
|
||||
let app: App;
|
||||
|
||||
before((done) => getCredentials(done));
|
||||
|
||||
before(async () => {
|
||||
await cleanupApps();
|
||||
app = await installLocalTestPackage(appAPIParameterTest);
|
||||
});
|
||||
|
||||
after(() => cleanupApps());
|
||||
|
||||
it('should correctly parse URL parameters in API endpoint', async () => {
|
||||
const param1Value = 'test-value-1';
|
||||
const param2Value = 'test-value-2';
|
||||
|
||||
const response = await request.get(apps(`/public/${app.id}/api/${param1Value}/${param2Value}/test`)).set(credentials);
|
||||
|
||||
expect(response.status, 'API endpoint request failed').to.equal(200);
|
||||
expect(response.header['content-type'], 'Content-Type header is incorrect').to.include('text/plain');
|
||||
expect(response.text, 'Response body does not contain correct parameters').to.equal(`Param1: ${param1Value}, Param2: ${param2Value}`);
|
||||
});
|
||||
});
|
||||
109
apps/meteor/tests/end-to-end/apps/app-logs-distinct-values.ts
Normal file
109
apps/meteor/tests/end-to-end/apps/app-logs-distinct-values.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
import { expect } from 'chai';
|
||||
import { after, before, describe, it } from 'mocha';
|
||||
|
||||
import { getCredentials, request, credentials } from '../../data/api-data';
|
||||
import { apps } from '../../data/apps/apps-data';
|
||||
import { installTestApp, cleanupApps } from '../../data/apps/helper';
|
||||
import { updatePermission } from '../../data/permissions.helper';
|
||||
import { IS_EE } from '../../e2e/config/constants';
|
||||
|
||||
(IS_EE ? describe : describe.skip)('Apps - Logs Distinct Values', () => {
|
||||
let appId: string;
|
||||
|
||||
before((done) => getCredentials(done));
|
||||
|
||||
before(async () => {
|
||||
await cleanupApps();
|
||||
const app = await installTestApp();
|
||||
appId = app.id;
|
||||
});
|
||||
|
||||
after(() => Promise.all([cleanupApps(), updatePermission('manage-apps', ['admin'])]));
|
||||
|
||||
it('should return distinct values successfully', (done) => {
|
||||
void request
|
||||
.get(apps(`/${appId}/logs/distinctValues`))
|
||||
.set(credentials)
|
||||
.expect(200)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.a.property('success', true);
|
||||
expect(res.body).to.have.a.property('instanceIds').that.is.an('array');
|
||||
expect(res.body).to.have.a.property('methods').that.is.an('array');
|
||||
|
||||
const { instanceIds, methods } = res.body;
|
||||
const uniqueInstanceIds = new Set(instanceIds);
|
||||
const uniqueMethods = new Set(methods);
|
||||
|
||||
// All instance IDs should be unique
|
||||
expect(instanceIds).to.have.lengthOf(uniqueInstanceIds.size);
|
||||
|
||||
// All methods should be unique
|
||||
expect(methods).to.have.lengthOf(uniqueMethods.size);
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
|
||||
it('should require authentication', (done) => {
|
||||
void request
|
||||
.get(apps(`/${appId}/logs/distinctValues`))
|
||||
.expect(401)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.a.property('success', false);
|
||||
expect(res.body).to.have.a.property('error');
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
|
||||
it('should require manage-apps permission', (done) => {
|
||||
void updatePermission('manage-apps', []).then(
|
||||
() =>
|
||||
void request
|
||||
.get(apps(`/${appId}/logs/distinctValues`))
|
||||
.set(credentials)
|
||||
.expect(403)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.a.property('success', false);
|
||||
})
|
||||
// Doing the `updatePermission` call here pollutes the error reporting, but we shouldn't call `done` before doing it.
|
||||
.end((err) => void updatePermission('manage-apps', ['admin']).then(() => void done(err))),
|
||||
);
|
||||
});
|
||||
|
||||
it('should return 404 for non-existent app', (done) => {
|
||||
void request
|
||||
.get(apps('/non-existent-app-id/logs/distinctValues'))
|
||||
.set(credentials)
|
||||
.expect(404)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.a.property('success', false);
|
||||
expect(res.body).to.have.a.property('error');
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
|
||||
it('should return empty arrays when no logs exist', (done) => {
|
||||
// Clean up all apps first to ensure no logs
|
||||
void cleanupApps().then(async () => {
|
||||
// Install a fresh app
|
||||
const app = await installTestApp();
|
||||
void request
|
||||
.get(apps(`/${app.id}/logs/distinctValues`))
|
||||
.set(credentials)
|
||||
.expect(200)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.a.property('success', true);
|
||||
expect(res.body).to.have.a.property('instanceIds').that.is.an('array');
|
||||
expect(res.body).to.have.a.property('methods').that.is.an('array');
|
||||
// Could be empty or have values from other tests
|
||||
expect(res.body.instanceIds).to.satisfy((arr: any[]) => Array.isArray(arr));
|
||||
expect(res.body.methods).to.satisfy((arr: any[]) => Array.isArray(arr));
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
399
apps/meteor/tests/end-to-end/apps/app-logs-export.ts
Normal file
399
apps/meteor/tests/end-to-end/apps/app-logs-export.ts
Normal file
@@ -0,0 +1,399 @@
|
||||
import type { ILoggerStorageEntry } from '@rocket.chat/apps/dist/server/logging/ILoggerStorageEntry';
|
||||
import type { App } from '@rocket.chat/core-typings';
|
||||
import { expect } from 'chai';
|
||||
import { after, before, describe, it } from 'mocha';
|
||||
|
||||
import { getCredentials, request, credentials, getInstanceId } from '../../data/api-data';
|
||||
import { apps } from '../../data/apps/apps-data';
|
||||
import { installTestApp, cleanupApps } from '../../data/apps/helper';
|
||||
import { IS_EE } from '../../e2e/config/constants';
|
||||
|
||||
(IS_EE ? describe : describe.skip)('Apps - Logs Export', () => {
|
||||
let app: App;
|
||||
|
||||
before((done) => getCredentials(done));
|
||||
|
||||
before(async () => {
|
||||
await cleanupApps();
|
||||
app = await installTestApp();
|
||||
});
|
||||
|
||||
after(() => cleanupApps());
|
||||
|
||||
it('should throw an error when trying to export logs for an invalid app', (done) => {
|
||||
void request
|
||||
.get(apps('/invalid-id/export-logs'))
|
||||
.query({ type: 'json' })
|
||||
.set(credentials)
|
||||
.expect(404)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.a.property('success', false);
|
||||
expect(res.body.error).to.be.equal('No App found by the id of: invalid-id');
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
|
||||
it('should export app logs as JSON successfully', (done) => {
|
||||
void request
|
||||
.get(apps(`/${app.id}/export-logs`))
|
||||
.query({ type: 'json' })
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'text/plain')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.headers).to.have.a.property('content-disposition');
|
||||
expect(res.headers['content-disposition']).to.include('attachment');
|
||||
expect(res.headers['content-disposition']).to.include(`app-logs-${app.id}`);
|
||||
expect(res.headers['content-disposition']).to.include('.json');
|
||||
expect(res.headers).to.have.a.property('content-length');
|
||||
|
||||
// Verify the content is valid JSON
|
||||
expect(() => JSON.parse(res.text)).to.not.throw();
|
||||
const logs = JSON.parse(res.text);
|
||||
expect(logs).to.be.an('array');
|
||||
expect(logs[0]).to.have.a.property('_id');
|
||||
expect(logs[0]).to.have.a.property('appId', app.id);
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
|
||||
it('should export app logs as CSV successfully', (done) => {
|
||||
void request
|
||||
.get(apps(`/${app.id}/export-logs`))
|
||||
.query({ type: 'csv' })
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'text/plain')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.headers).to.have.a.property('content-disposition');
|
||||
expect(res.headers['content-disposition']).to.include('attachment');
|
||||
expect(res.headers['content-disposition']).to.include(`app-logs-${app.id}`);
|
||||
expect(res.headers['content-disposition']).to.include('.csv');
|
||||
expect(res.headers).to.have.a.property('content-length');
|
||||
|
||||
// Verify the content is valid CSV (should have headers and at least one row)
|
||||
const lines = res.text.split('\n');
|
||||
expect(lines.length).to.be.at.least(1); // At least header row
|
||||
if (lines.length > 1 && lines[0]) {
|
||||
// Check that headers contain expected fields
|
||||
expect(lines[0]).to.include('_id');
|
||||
expect(lines[0]).to.include('appId');
|
||||
}
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
|
||||
it('should throw error when type is not specified', (done) => {
|
||||
void request
|
||||
.get(apps(`/${app.id}/export-logs`))
|
||||
.set(credentials)
|
||||
.expect(400)
|
||||
.end(done);
|
||||
});
|
||||
|
||||
it('should export app logs with pagination parameters', (done) => {
|
||||
void request
|
||||
.get(apps(`/${app.id}/export-logs`))
|
||||
.query({ count: 5, type: 'json' })
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'text/plain')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
const logs = JSON.parse(res.text);
|
||||
expect(logs).to.be.an('array');
|
||||
expect(logs.length).to.be.at.most(5);
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
|
||||
it('should export app logs with sorting parameters', (done) => {
|
||||
void request
|
||||
.get(apps(`/${app.id}/export-logs`))
|
||||
.query({
|
||||
sort: JSON.stringify({ _updatedAt: 1 }),
|
||||
type: 'json',
|
||||
})
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'text/plain')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
const logs = JSON.parse(res.text);
|
||||
expect(logs).to.be.an('array');
|
||||
// Verify sorting by checking if timestamps are in ascending order
|
||||
if (logs.length > 1) {
|
||||
for (let i = 1; i < logs.length; i++) {
|
||||
const prevTime = new Date(logs[i - 1]._updatedAt).getTime();
|
||||
const currTime = new Date(logs[i]._updatedAt).getTime();
|
||||
expect(currTime).to.be.at.least(prevTime);
|
||||
}
|
||||
}
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
|
||||
it('should export app logs filtered by logLevel', (done) => {
|
||||
void request
|
||||
.get(apps(`/${app.id}/export-logs`))
|
||||
.query({ logLevel: '2', type: 'json' })
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'text/plain')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
const logs = JSON.parse(res.text);
|
||||
expect(logs).to.be.an('array');
|
||||
|
||||
logs.forEach((log: ILoggerStorageEntry) => {
|
||||
const entry = log.entries.find((entry) => ['error', 'warn', 'info', 'log', 'debug', 'success'].includes(entry.severity));
|
||||
expect(entry).to.exist;
|
||||
});
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
|
||||
it('should export app logs filtered by method', (done) => {
|
||||
void request
|
||||
.get(apps(`/${app.id}/export-logs`))
|
||||
.query({ method: 'app:construct', type: 'json' })
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'text/plain')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
const logs = JSON.parse(res.text);
|
||||
expect(logs).to.be.an('array');
|
||||
|
||||
logs.forEach((log: ILoggerStorageEntry) => {
|
||||
expect(log.method).to.equal('app:construct');
|
||||
});
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
|
||||
it('should export app logs filtered by instance id', (done) => {
|
||||
const instanceId = getInstanceId();
|
||||
void request
|
||||
.get(apps(`/${app.id}/export-logs`))
|
||||
.query({ instanceId, type: 'json' })
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'text/plain')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
const logs = JSON.parse(res.text);
|
||||
expect(logs).to.be.an('array');
|
||||
|
||||
logs.forEach((log: ILoggerStorageEntry) => {
|
||||
expect(log.instanceId).to.equal(instanceId);
|
||||
});
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
|
||||
it('should export app logs filtered by date range', (done) => {
|
||||
const startDate = new Date();
|
||||
startDate.setDate(startDate.getDate() - 1); // 1 day ago
|
||||
const endDate = new Date();
|
||||
|
||||
void request
|
||||
.get(apps(`/${app.id}/export-logs`))
|
||||
.query({
|
||||
startDate: startDate.toISOString(),
|
||||
endDate: endDate.toISOString(),
|
||||
type: 'json',
|
||||
})
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'text/plain')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
const logs = JSON.parse(res.text);
|
||||
expect(logs).to.be.an('array');
|
||||
|
||||
// Verify that all returned logs are within the date range
|
||||
logs.forEach((log: ILoggerStorageEntry) => {
|
||||
const logDate = new Date(log._createdAt);
|
||||
expect(logDate).to.be.above(startDate).and.below(endDate);
|
||||
});
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
|
||||
it('should export app logs with combined filters', (done) => {
|
||||
const startDate = new Date();
|
||||
startDate.setDate(startDate.getDate() - 1); // 1 day ago
|
||||
const endDate = new Date();
|
||||
|
||||
void request
|
||||
.get(apps(`/${app.id}/export-logs`))
|
||||
.query({
|
||||
logLevel: '2',
|
||||
method: 'app:construct',
|
||||
startDate: startDate.toISOString(),
|
||||
endDate: endDate.toISOString(),
|
||||
type: 'json',
|
||||
count: 10,
|
||||
})
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'text/plain')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
const logs = JSON.parse(res.text);
|
||||
expect(logs).to.be.an('array');
|
||||
expect(logs.length).to.be.at.most(10);
|
||||
|
||||
// Verify that all returned logs match all filter criteria
|
||||
logs.forEach((log: ILoggerStorageEntry) => {
|
||||
expect(log.method).to.equal('app:construct');
|
||||
|
||||
const logDate = new Date(log._createdAt);
|
||||
expect(logDate >= startDate && logDate <= endDate).to.be.true;
|
||||
|
||||
const entry = log.entries.find((entry) => ['error', 'warn', 'info', 'log', 'debug', 'success'].includes(entry.severity));
|
||||
expect(entry).to.exist;
|
||||
});
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
|
||||
it('should return an error when no logs are found for the specified criteria', (done) => {
|
||||
const futureDate = new Date();
|
||||
futureDate.setFullYear(futureDate.getFullYear() + 1);
|
||||
|
||||
void request
|
||||
.get(apps(`/${app.id}/export-logs`))
|
||||
.query({
|
||||
startDate: futureDate.toISOString(),
|
||||
type: 'json',
|
||||
})
|
||||
.set(credentials)
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.a.property('success', false);
|
||||
expect(res.body.error).to.be.equal('No logs found for the specified criteria');
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
|
||||
it('should respect the maximum limit of 2000 logs', (done) => {
|
||||
void request
|
||||
.get(apps(`/${app.id}/export-logs`))
|
||||
.query({ count: 5000, type: 'json' })
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'text/plain')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
const logs = JSON.parse(res.text);
|
||||
expect(logs).to.be.an('array');
|
||||
expect(logs.length).to.be.at.most(2000);
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
|
||||
it('should reject invalid logLevel value', (done) => {
|
||||
void request
|
||||
.get(apps(`/${app.id}/export-logs`))
|
||||
.query({ logLevel: 'debug' })
|
||||
.set(credentials)
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.a.property('success', false);
|
||||
expect(res.body).to.have.a.property('error').that.is.not.empty;
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
|
||||
it('should reject invalid date format', (done) => {
|
||||
void request
|
||||
.get(apps(`/${app.id}/export-logs`))
|
||||
.query({ startDate: 'invalid-date' })
|
||||
.set(credentials)
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.a.property('success', false);
|
||||
expect(res.body).to.have.a.property('error').that.is.not.empty;
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
|
||||
it('should reject invalid date range', (done) => {
|
||||
const startDate = new Date();
|
||||
const endDate = new Date();
|
||||
endDate.setDate(endDate.getDate() - 1); // endDate before startDate
|
||||
|
||||
void request
|
||||
.get(apps(`/${app.id}/export-logs`))
|
||||
.query({
|
||||
startDate: startDate.toISOString(),
|
||||
endDate: endDate.toISOString(),
|
||||
})
|
||||
.set(credentials)
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.a.property('success', false);
|
||||
expect(res.body).to.have.a.property('error').that.is.not.empty;
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
|
||||
it('should reject invalid additional properties', (done) => {
|
||||
void request
|
||||
.get(apps(`/${app.id}/export-logs`))
|
||||
.query({ invalidProperty: 'value' })
|
||||
.set(credentials)
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.a.property('success', false);
|
||||
expect(res.body).to.have.a.property('error').that.is.not.empty;
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
|
||||
it('should handle authentication requirement', (done) => {
|
||||
void request
|
||||
.get(apps(`/${app.id}/export-logs`))
|
||||
.query({ type: 'json' })
|
||||
.expect(401)
|
||||
.end(done);
|
||||
});
|
||||
|
||||
it('should handle authentication via cookie', (done) => {
|
||||
void request
|
||||
.get(apps(`/${app.id}/export-logs`))
|
||||
.query({ type: 'json' })
|
||||
.set({ cookie: `rc_uid=${credentials['X-User-Id']}; rc_token=${credentials['X-Auth-Token']}` })
|
||||
.expect(200)
|
||||
.end(done);
|
||||
});
|
||||
|
||||
it('should include proper filename with timestamp in Content-Disposition header', (done) => {
|
||||
void request
|
||||
.get(apps(`/${app.id}/export-logs`))
|
||||
.query({ type: 'json' })
|
||||
.set(credentials)
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
const contentDisposition = res.headers['content-disposition'];
|
||||
expect(contentDisposition).to.include('attachment');
|
||||
expect(contentDisposition).to.include(`app-logs-${app.id}`);
|
||||
expect(contentDisposition).to.include('.json');
|
||||
|
||||
// Check that timestamp is included (format: YYYY-MM-DDTHH-MM-SS)
|
||||
const timestampRegex = /\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}/;
|
||||
expect(contentDisposition).to.match(timestampRegex);
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
|
||||
it('should include Content-Length header', (done) => {
|
||||
void request
|
||||
.get(apps(`/${app.id}/export-logs`))
|
||||
.query({ type: 'json' })
|
||||
.set(credentials)
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.headers).to.have.a.property('content-length');
|
||||
const contentLength = parseInt(res.headers['content-length'], 10);
|
||||
expect(contentLength).to.be.a('number');
|
||||
expect(contentLength).to.be.greaterThan(0);
|
||||
expect(contentLength).to.equal(Buffer.byteLength(res.text, 'utf8'));
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
});
|
||||
240
apps/meteor/tests/end-to-end/apps/app-logs-general.ts
Normal file
240
apps/meteor/tests/end-to-end/apps/app-logs-general.ts
Normal file
@@ -0,0 +1,240 @@
|
||||
import type { ILoggerStorageEntry } from '@rocket.chat/apps/dist/server/logging/ILoggerStorageEntry';
|
||||
import type { App } from '@rocket.chat/core-typings';
|
||||
import { expect } from 'chai';
|
||||
import { after, before, describe, it } from 'mocha';
|
||||
|
||||
import { getCredentials, request, credentials } from '../../data/api-data';
|
||||
import { apps } from '../../data/apps/apps-data';
|
||||
import { installTestApp, cleanupApps } from '../../data/apps/helper';
|
||||
import { updatePermission } from '../../data/permissions.helper';
|
||||
import { IS_EE } from '../../e2e/config/constants';
|
||||
|
||||
(IS_EE ? describe : describe.skip)('Apps - General Logs (GET /api/apps/logs)', () => {
|
||||
let app: App;
|
||||
|
||||
before((done) => getCredentials(done));
|
||||
|
||||
before(async () => {
|
||||
await cleanupApps();
|
||||
app = await installTestApp();
|
||||
});
|
||||
|
||||
after(() => Promise.all([cleanupApps(), updatePermission('manage-apps', ['admin'])]));
|
||||
|
||||
it('should return logs for all apps successfully', (done) => {
|
||||
void request
|
||||
.get(apps('/logs'))
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.a.property('success', true);
|
||||
expect(res.body).to.have.a.property('logs').that.is.an('array').with.a.lengthOf.at.least(1);
|
||||
expect(res.body).to.have.a.property('count').that.is.a('number');
|
||||
expect(res.body).to.have.a.property('total').that.is.a('number');
|
||||
expect(res.body).to.have.a.property('offset').that.is.a('number');
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
|
||||
it('should require authentication', (done) => {
|
||||
void request
|
||||
.get(apps('/logs'))
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(401)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.a.property('success', false);
|
||||
expect(res.body).to.have.a.property('error');
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
|
||||
it('should require manage-apps permission', (done) => {
|
||||
void updatePermission('manage-apps', []).then(
|
||||
() =>
|
||||
void request
|
||||
.get(apps('/logs'))
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(403)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.a.property('success', false);
|
||||
})
|
||||
.end((err) => void updatePermission('manage-apps', ['admin']).then(() => void done(err))),
|
||||
);
|
||||
});
|
||||
|
||||
it('should return logs with pagination', (done) => {
|
||||
void request
|
||||
.get(apps('/logs'))
|
||||
.query({ count: 1, offset: 0 })
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.a.property('success', true);
|
||||
expect(res.body.logs).to.be.an('array').with.a.lengthOf(1);
|
||||
expect(res.body.count).to.be.equal(1);
|
||||
expect(res.body.offset).to.be.equal(0);
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
|
||||
it('should return logs with sorting', (done) => {
|
||||
void request
|
||||
.get(apps('/logs'))
|
||||
.query({ sort: JSON.stringify({ _updatedAt: -1 }) })
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.a.property('success', true);
|
||||
expect(res.body.logs).to.be.an('array').with.a.lengthOf.at.least(1);
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
|
||||
it('should return logs filtered by appId', (done) => {
|
||||
void request
|
||||
.get(apps('/logs'))
|
||||
.query({ appId: app.id })
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.a.property('success', true);
|
||||
expect(res.body.logs).to.be.an('array').with.a.lengthOf.at.least(1);
|
||||
|
||||
res.body.logs.forEach((log: ILoggerStorageEntry) => {
|
||||
expect(log.appId).to.equal(app.id);
|
||||
});
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
|
||||
it('should return logs filtered by method', (done) => {
|
||||
void request
|
||||
.get(apps('/logs'))
|
||||
.query({ method: 'app:construct' })
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.a.property('success', true);
|
||||
expect(res.body.logs).to.be.an('array').with.a.lengthOf.at.least(1);
|
||||
|
||||
res.body.logs.forEach((log: ILoggerStorageEntry) => {
|
||||
expect(log.method).to.equal('app:construct');
|
||||
});
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
|
||||
it('should return logs filtered by logLevel', (done) => {
|
||||
void request
|
||||
.get(apps('/logs'))
|
||||
.query({ logLevel: '2' })
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.a.property('success', true);
|
||||
expect(res.body.logs).to.be.an('array').with.a.lengthOf.at.least(1);
|
||||
|
||||
res.body.logs.forEach((log: ILoggerStorageEntry) => {
|
||||
const entry = log.entries.find((entry) => ['error', 'warn', 'info', 'log', 'debug', 'success'].includes(entry.severity));
|
||||
expect(entry).to.exist;
|
||||
});
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
|
||||
it('should return logs filtered by date range', (done) => {
|
||||
const startDate = new Date();
|
||||
startDate.setDate(startDate.getDate() - 1); // 1 day ago
|
||||
const endDate = new Date();
|
||||
|
||||
void request
|
||||
.get(apps('/logs'))
|
||||
.query({
|
||||
startDate: startDate.toISOString(),
|
||||
endDate: endDate.toISOString(),
|
||||
})
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.a.property('success', true);
|
||||
expect(res.body.logs).to.be.an('array').with.a.lengthOf.at.least(1);
|
||||
|
||||
res.body.logs.forEach((log: ILoggerStorageEntry) => {
|
||||
const logDate = new Date(log._createdAt);
|
||||
expect(logDate).to.be.above(startDate).and.below(endDate);
|
||||
});
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
|
||||
it('should reject invalid logLevel value', (done) => {
|
||||
void request
|
||||
.get(apps('/logs'))
|
||||
.query({ logLevel: 'debug' })
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.a.property('success', false);
|
||||
expect(res.body).to.have.a.property('error').that.is.not.empty;
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
|
||||
it('should reject invalid date format', (done) => {
|
||||
void request
|
||||
.get(apps('/logs'))
|
||||
.query({ startDate: 'invalid-date' })
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.a.property('success', false);
|
||||
expect(res.body).to.have.a.property('error').that.is.not.empty;
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
|
||||
it('should reject invalid date range', (done) => {
|
||||
const startDate = new Date();
|
||||
const endDate = new Date();
|
||||
endDate.setDate(endDate.getDate() - 1); // endDate before startDate
|
||||
|
||||
void request
|
||||
.get(apps('/logs'))
|
||||
.query({
|
||||
startDate: startDate.toISOString(),
|
||||
endDate: endDate.toISOString(),
|
||||
})
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.a.property('success', false);
|
||||
expect(res.body).to.have.a.property('error').that.is.not.empty;
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
|
||||
it('should reject invalid additional properties', (done) => {
|
||||
void request
|
||||
.get(apps('/logs'))
|
||||
.query({ invalidProperty: 'value' })
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.a.property('success', false);
|
||||
expect(res.body).to.have.a.property('error').that.is.not.empty;
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,68 @@
|
||||
import type { ILoggerStorageEntry } from '@rocket.chat/apps/dist/server/logging/ILoggerStorageEntry';
|
||||
import { expect } from 'chai';
|
||||
import { after, before, describe, it } from 'mocha';
|
||||
|
||||
import { getCredentials, request, credentials } from '../../data/api-data';
|
||||
import { appCausingNestedRequests } from '../../data/apps/app-packages';
|
||||
import { apps } from '../../data/apps/apps-data';
|
||||
import { cleanupApps, installLocalTestPackage } from '../../data/apps/helper';
|
||||
import { loadHistory } from '../../data/rooms.helper';
|
||||
import { executeAppSlashCommand } from '../../data/slashcommands.helpers';
|
||||
import { IS_EE } from '../../e2e/config/constants';
|
||||
|
||||
(IS_EE ? describe : describe.skip)('Apps - Logs Nested Requests', () => {
|
||||
before((done) => getCredentials(done));
|
||||
|
||||
after(() => cleanupApps());
|
||||
|
||||
it('should log nested requests without errors', async () => {
|
||||
const app = await installLocalTestPackage(appCausingNestedRequests);
|
||||
|
||||
const roomId = 'GENERAL';
|
||||
const execId = Math.random().toString(36).substring(2, 15);
|
||||
|
||||
const slashcommandRes = await executeAppSlashCommand('nest', roomId, execId);
|
||||
|
||||
expect(slashcommandRes.status, 'Slashcommand execution failed').to.equal(200);
|
||||
|
||||
const { messages } = await loadHistory(roomId);
|
||||
|
||||
const targetMessage = messages.find((msg) => msg.msg === `nested_test_message ${execId}`);
|
||||
|
||||
expect(targetMessage, 'Target message from nested request not found').to.exist;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- we're checking existence above
|
||||
const { _id: messageId } = targetMessage!;
|
||||
|
||||
const logsRes = await request.get(apps(`/${app.id}/logs`)).set(credentials);
|
||||
|
||||
expect(logsRes.status, 'Fetching app logs failed').to.equal(200);
|
||||
expect(logsRes.body, 'Apps logs response success is not true').to.have.a.property('success', true);
|
||||
expect(logsRes.body, 'Apps logs response logs property is empty or missing')
|
||||
.to.have.a.property('logs')
|
||||
.that.is.an('array')
|
||||
.with.lengthOf.greaterThan(0);
|
||||
|
||||
let slashcommandExecutionLog: ILoggerStorageEntry | undefined;
|
||||
let postMessageExecutionLog: ILoggerStorageEntry | undefined;
|
||||
|
||||
logsRes.body.logs.forEach((log: ILoggerStorageEntry) => {
|
||||
if ((log.method as string) === 'slashcommand:nest:executor' && log.entries.find((entry) => entry.args.includes(messageId))) {
|
||||
slashcommandExecutionLog = log;
|
||||
return;
|
||||
}
|
||||
|
||||
if ((log.method as string) === 'app:executePostMessageSent' && log.entries.find((entry) => entry.args.includes(messageId))) {
|
||||
postMessageExecutionLog = log;
|
||||
}
|
||||
});
|
||||
|
||||
expect(postMessageExecutionLog, 'IPostMessageSent execution log not found').to.exist;
|
||||
|
||||
expect(slashcommandExecutionLog, 'Slashcommand execution log not found').to.exist;
|
||||
expect(
|
||||
slashcommandExecutionLog?.entries.filter((entry) => entry.args.includes(execId)),
|
||||
`Slashcommand execution log does not contain a matching execId "${execId}"`,
|
||||
).to.have.lengthOf(1);
|
||||
});
|
||||
});
|
||||
288
apps/meteor/tests/end-to-end/apps/app-logs.ts
Normal file
288
apps/meteor/tests/end-to-end/apps/app-logs.ts
Normal file
@@ -0,0 +1,288 @@
|
||||
import type { ILoggerStorageEntry } from '@rocket.chat/apps/dist/server/logging/ILoggerStorageEntry';
|
||||
import type { App } from '@rocket.chat/core-typings';
|
||||
import { expect } from 'chai';
|
||||
import { after, before, describe, it } from 'mocha';
|
||||
|
||||
import { getCredentials, request, credentials } from '../../data/api-data';
|
||||
import { apps } from '../../data/apps/apps-data';
|
||||
import { installTestApp, cleanupApps } from '../../data/apps/helper';
|
||||
import { updatePermission } from '../../data/permissions.helper';
|
||||
import { IS_EE } from '../../e2e/config/constants';
|
||||
|
||||
(IS_EE ? describe : describe.skip)('Apps - Logs', () => {
|
||||
let app: App;
|
||||
|
||||
before((done) => getCredentials(done));
|
||||
|
||||
before(async () => {
|
||||
await cleanupApps();
|
||||
app = await installTestApp();
|
||||
});
|
||||
|
||||
after(() => cleanupApps());
|
||||
|
||||
it('should throw an error when trying to get logs for an invalid app', (done) => {
|
||||
void request
|
||||
.get(apps('/invalid-id/logs'))
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(404)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.a.property('success', false);
|
||||
expect(res.body).to.not.have.a.property('logs');
|
||||
expect(res.body.error).to.be.equal('No App found by the id of: invalid-id');
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
|
||||
it('should return app logs successfully', (done) => {
|
||||
void request
|
||||
.get(apps(`/${app.id}/logs`))
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.a.property('success', true);
|
||||
expect(res.body).to.have.a.property('logs').that.is.an('array').with.a.lengthOf.at.least(1).and.at.most(50);
|
||||
expect(res.body).to.have.a.property('count').that.is.a('number');
|
||||
expect(res.body).to.have.a.property('total').that.is.a('number');
|
||||
expect(res.body).to.have.a.property('offset').that.is.a('number');
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
|
||||
it('should return app logs with pagination', (done) => {
|
||||
void request
|
||||
.get(apps(`/${app.id}/logs`))
|
||||
.query({ count: 1, offset: 0 })
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.a.property('success', true);
|
||||
expect(res.body.logs).to.be.an('array').with.a.lengthOf(1);
|
||||
expect(res.body.count).to.be.equal(1);
|
||||
expect(res.body.offset).to.be.equal(0);
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
|
||||
it('should return app logs with sorting', (done) => {
|
||||
void request
|
||||
.get(apps(`/${app.id}/logs`))
|
||||
.query({ sort: JSON.stringify({ _updatedAt: -1 }) })
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.a.property('success', true);
|
||||
expect(res.body.logs).to.be.an('array').with.a.lengthOf.at.least(1).and.at.most(50);
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
|
||||
it('should return app logs filtered by logLevel', (done) => {
|
||||
void request
|
||||
.get(apps(`/${app.id}/logs`))
|
||||
.query({ logLevel: '2' })
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.a.property('success', true);
|
||||
expect(res.body.logs).to.be.an('array').with.a.lengthOf.at.least(1).and.at.most(50);
|
||||
|
||||
res.body.logs.forEach((log: ILoggerStorageEntry) => {
|
||||
const entry = log.entries.find((entry) => ['error', 'warn', 'info', 'log', 'debug', 'success'].includes(entry.severity));
|
||||
|
||||
expect(entry).to.exist;
|
||||
});
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
|
||||
it('should return app logs filtered by method', (done) => {
|
||||
void request
|
||||
.get(apps(`/${app.id}/logs`))
|
||||
.query({ method: 'app:construct' })
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.a.property('success', true);
|
||||
expect(res.body.logs).to.be.an('array').with.a.lengthOf.at.least(1).and.at.most(50);
|
||||
|
||||
res.body.logs.forEach((log: ILoggerStorageEntry) => {
|
||||
expect(log.method).to.equal('app:construct');
|
||||
});
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
|
||||
it('should return app logs filtered by date range', (done) => {
|
||||
const startDate = new Date();
|
||||
startDate.setDate(startDate.getDate() - 1); // 1 day ago
|
||||
const endDate = new Date();
|
||||
|
||||
void request
|
||||
.get(apps(`/${app.id}/logs`))
|
||||
.query({
|
||||
startDate: startDate.toISOString(),
|
||||
endDate: endDate.toISOString(),
|
||||
})
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.a.property('success', true);
|
||||
expect(res.body.logs).to.be.an('array').with.a.lengthOf.at.least(1).and.at.most(50);
|
||||
|
||||
// Verify that all returned logs are within the date range
|
||||
res.body.logs.forEach((log: ILoggerStorageEntry) => {
|
||||
const logDate = new Date(log._createdAt);
|
||||
|
||||
expect(logDate).to.be.above(startDate).and.below(endDate);
|
||||
});
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
|
||||
it('should return app logs with combined filters', (done) => {
|
||||
const startDate = new Date();
|
||||
startDate.setDate(startDate.getDate() - 1); // 1 day ago
|
||||
const endDate = new Date();
|
||||
|
||||
void request
|
||||
.get(apps(`/${app.id}/logs`))
|
||||
.query({
|
||||
logLevel: '2',
|
||||
method: 'app:construct',
|
||||
startDate: startDate.toISOString(),
|
||||
endDate: endDate.toISOString(),
|
||||
})
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.a.property('success', true);
|
||||
expect(res.body.logs).to.be.an('array').with.a.lengthOf.at.least(1).and.at.most(50);
|
||||
|
||||
// Verify that all returned logs match all filter criteria
|
||||
res.body.logs.forEach((log: ILoggerStorageEntry) => {
|
||||
expect(log.method).to.equal('app:construct');
|
||||
|
||||
const logDate = new Date(log._createdAt);
|
||||
expect(logDate >= startDate && logDate <= endDate).to.be.true;
|
||||
|
||||
const entry = log.entries.find((entry) => ['error', 'warn', 'info', 'log', 'debug', 'success'].includes(entry.severity));
|
||||
|
||||
expect(entry).to.exist;
|
||||
});
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
|
||||
it('should reject invalid logLevel value', (done) => {
|
||||
void request
|
||||
.get(apps(`/${app.id}/logs`))
|
||||
.query({ logLevel: 'debug' })
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.a.property('success', false);
|
||||
expect(res.body).to.have.a.property('error').that.is.not.empty;
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
|
||||
it('should reject invalid date format', (done) => {
|
||||
void request
|
||||
.get(apps(`/${app.id}/logs`))
|
||||
.query({ startDate: 'invalid-date' })
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.a.property('success', false);
|
||||
expect(res.body).to.have.a.property('error').that.is.not.empty;
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
|
||||
it('should reject invalid date range', (done) => {
|
||||
const startDate = new Date();
|
||||
const endDate = new Date();
|
||||
endDate.setDate(endDate.getDate() - 1); // endDate before startDate
|
||||
|
||||
void request
|
||||
.get(apps(`/${app.id}/logs`))
|
||||
.query({
|
||||
startDate: startDate.toISOString(),
|
||||
endDate: endDate.toISOString(),
|
||||
})
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.a.property('success', false);
|
||||
expect(res.body).to.have.a.property('error').that.is.not.empty;
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
|
||||
it('should reject invalid additional properties', (done) => {
|
||||
void request
|
||||
.get(apps(`/${app.id}/logs`))
|
||||
.query({ invalidProperty: 'value' })
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.a.property('success', false);
|
||||
expect(res.body).to.have.a.property('error').that.is.not.empty;
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
|
||||
it('should reject appId query parameter', (done) => {
|
||||
void request
|
||||
.get(apps(`/${app.id}/logs`))
|
||||
.query({ appId: 'invalid-id' })
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(404)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.a.property('success', false);
|
||||
expect(res.body).to.have.a.property('error', 'Invalid query parameter "appId": invalid-id');
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
|
||||
it('should require authentication', (done) => {
|
||||
void request
|
||||
.get(apps(`/${app.id}/logs`))
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(401)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.a.property('success', false);
|
||||
expect(res.body).to.have.a.property('error');
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
|
||||
it('should require manage-apps permission', (done) => {
|
||||
void updatePermission('manage-apps', []).then(
|
||||
() =>
|
||||
void request
|
||||
.get(apps(`/${app.id}/logs`))
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(403)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.a.property('success', false);
|
||||
})
|
||||
.end((err) => void updatePermission('manage-apps', ['admin']).then(() => void done(err))),
|
||||
);
|
||||
});
|
||||
});
|
||||
49
apps/meteor/tests/end-to-end/apps/app-message-reactions.ts
Normal file
49
apps/meteor/tests/end-to-end/apps/app-message-reactions.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { expect } from 'chai';
|
||||
import { after, before, describe, it } from 'mocha';
|
||||
|
||||
import { getCredentials } from '../../data/api-data';
|
||||
import { messageReactionTest } from '../../data/apps/app-packages';
|
||||
import { cleanupApps, installLocalTestPackage } from '../../data/apps/helper';
|
||||
import { sendSimpleMessage, getMessageById } from '../../data/chat.helper';
|
||||
import { createRoom, deleteRoom } from '../../data/rooms.helper';
|
||||
import { executeAppSlashCommand } from '../../data/slashcommands.helpers';
|
||||
import { IS_EE } from '../../e2e/config/constants';
|
||||
|
||||
(IS_EE ? describe : describe.skip)('Apps - Message Reactions', () => {
|
||||
let roomId: string;
|
||||
|
||||
before((done) => getCredentials(done));
|
||||
|
||||
before(async () => {
|
||||
await cleanupApps();
|
||||
await installLocalTestPackage(messageReactionTest);
|
||||
|
||||
const res = await createRoom({ type: 'c', name: `apps-reactions-test-${Date.now()}` });
|
||||
roomId = res.body.channel._id;
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await deleteRoom({ type: 'c', roomId });
|
||||
await cleanupApps();
|
||||
});
|
||||
|
||||
it('should add and remove a thumbs-up reaction to a message', async () => {
|
||||
const sendRes = await sendSimpleMessage({ roomId, text: 'reaction test message' });
|
||||
const messageId = sendRes.body.message._id;
|
||||
|
||||
const slashRes = await executeAppSlashCommand('msg-update', roomId, `add ${messageId}`);
|
||||
expect(slashRes.status, 'Slash command to add reaction failed').to.equal(200);
|
||||
|
||||
let message = await getMessageById({ msgId: messageId });
|
||||
expect(message.reactions, 'Message reactions should exist after adding').to.exist;
|
||||
expect(message.reactions, 'Message should have a thumbs-up reaction').to.have.property(':+1:');
|
||||
|
||||
// Now remove the reaction
|
||||
const removeRes = await executeAppSlashCommand('msg-update', roomId, `remove ${messageId}`);
|
||||
expect(removeRes.status, 'Slash command to remove reaction failed').to.equal(200);
|
||||
|
||||
message = await getMessageById({ msgId: messageId });
|
||||
const hasThumbsUp = Boolean(message.reactions && ':+1:' in message.reactions);
|
||||
expect(hasThumbsUp, 'Thumbs-up reaction should have been removed').to.be.false;
|
||||
});
|
||||
});
|
||||
239
apps/meteor/tests/end-to-end/apps/app-resolve-visitor.ts
Normal file
239
apps/meteor/tests/end-to-end/apps/app-resolve-visitor.ts
Normal file
@@ -0,0 +1,239 @@
|
||||
import type { App, ILivechatVisitor } from '@rocket.chat/core-typings';
|
||||
import { expect } from 'chai';
|
||||
import { after, before, describe, it } from 'mocha';
|
||||
import { MongoClient } from 'mongodb';
|
||||
|
||||
import { getCredentials, request, credentials } from '../../data/api-data';
|
||||
import { appExternalIdTest } from '../../data/apps/app-packages';
|
||||
import { apps } from '../../data/apps/apps-data';
|
||||
import { cleanupApps } from '../../data/apps/helper';
|
||||
import { createVisitor, createVisitorWithCustomData, createAgent, makeAgentAvailable } from '../../data/livechat/rooms';
|
||||
import { updateSetting } from '../../data/permissions.helper';
|
||||
import { IS_EE, URL_MONGODB } from '../../e2e/config/constants';
|
||||
|
||||
(IS_EE ? describe : describe.skip)('Apps - resolveVisitor API', () => {
|
||||
let app: App;
|
||||
|
||||
before((done) => getCredentials(done));
|
||||
|
||||
before(async () => {
|
||||
await updateSetting('Livechat_enabled', true);
|
||||
await createAgent();
|
||||
await makeAgentAvailable();
|
||||
|
||||
const installResponse = await request.post(apps()).set(credentials).attach('app', appExternalIdTest);
|
||||
if (!installResponse.body.success) {
|
||||
throw new Error(`Failed to install test app: ${installResponse.body.error}`);
|
||||
}
|
||||
|
||||
app = installResponse.body.app;
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await cleanupApps();
|
||||
});
|
||||
|
||||
const callResolveVisitor = async (
|
||||
externalId: { entityId: string; metadata?: Record<string, unknown> },
|
||||
contactData?: { phone?: string; email?: string },
|
||||
) => {
|
||||
return request
|
||||
.post(apps(`/public/${app.id}/resolve-visitor`))
|
||||
.set(credentials)
|
||||
.send({ externalId, phone: contactData?.phone, email: contactData?.email });
|
||||
};
|
||||
|
||||
const callUpdateExternalId = async (visitorId: string, externalId: { entityId: string; metadata?: Record<string, unknown> }) => {
|
||||
return request
|
||||
.post(apps(`/public/${app.id}/update-external-id`))
|
||||
.set(credentials)
|
||||
.send({ visitorId, externalId });
|
||||
};
|
||||
|
||||
describe('externalId lookup', () => {
|
||||
it('should return null when externalId does not exist', async () => {
|
||||
const response = await callResolveVisitor({ entityId: 'nonexistent-id' });
|
||||
|
||||
expect(response.status).to.equal(200);
|
||||
expect(response.body.visitor).to.be.null;
|
||||
});
|
||||
|
||||
it('should find visitor directly by externalId', async () => {
|
||||
const phone = `+1${Date.now()}`;
|
||||
const visitor = await createVisitor(undefined, undefined, undefined, phone);
|
||||
|
||||
const externalId = { entityId: `id-${Date.now()}`, metadata: { username: '@user' } };
|
||||
await callResolveVisitor(externalId, { phone });
|
||||
|
||||
const response = await callResolveVisitor(externalId);
|
||||
|
||||
expect(response.status).to.equal(200);
|
||||
expect(response.body.visitor.id).to.equal(visitor._id);
|
||||
const appExternalId = response.body.visitor.externalIds.find((e: { appId: string }) => e.appId === app.id);
|
||||
expect(appExternalId.entityId).to.equal(externalId.entityId);
|
||||
expect(appExternalId.metadata.username).to.equal(externalId.metadata.username);
|
||||
});
|
||||
|
||||
it('should not update externalId when found by lookup', async () => {
|
||||
const phone = `+2${Date.now()}`;
|
||||
await createVisitor(undefined, undefined, undefined, phone);
|
||||
|
||||
const externalId = { entityId: `id-${Date.now()}`, metadata: { username: '@original' } };
|
||||
await callResolveVisitor(externalId, { phone });
|
||||
|
||||
// resolveVisitor is for resolving/enriching visitors, not for updating existing data.
|
||||
// When found by externalId, it returns the visitor as-is without modifications.
|
||||
// To update visitor data, apps should use other methods like ILivechatUpdater.
|
||||
const response = await callResolveVisitor({ entityId: externalId.entityId, metadata: { username: '@changed' } });
|
||||
|
||||
const appExternalId = response.body.visitor.externalIds.find((e: { appId: string }) => e.appId === app.id);
|
||||
expect(appExternalId.metadata.username).to.equal('@original');
|
||||
});
|
||||
});
|
||||
|
||||
describe('phone fallback', () => {
|
||||
it('should find visitor by phone and save externalId', async () => {
|
||||
const phone = `+3${Date.now()}`;
|
||||
const visitor = await createVisitor(undefined, undefined, undefined, phone);
|
||||
|
||||
const externalId = { entityId: `id-${Date.now()}`, metadata: { username: '@user' } };
|
||||
const response = await callResolveVisitor(externalId, { phone });
|
||||
|
||||
expect(response.status).to.equal(200);
|
||||
expect(response.body.visitor.id).to.equal(visitor._id);
|
||||
const appExternalId = response.body.visitor.externalIds.find((e: { appId: string }) => e.appId === app.id);
|
||||
expect(appExternalId.entityId).to.equal(externalId.entityId);
|
||||
expect(appExternalId.metadata.username).to.equal(externalId.metadata.username);
|
||||
});
|
||||
|
||||
it('should overwrite externalId when called with different entityId', async () => {
|
||||
const phone = `+4${Date.now()}`;
|
||||
await createVisitor(undefined, undefined, undefined, phone);
|
||||
|
||||
await callResolveVisitor({ entityId: `first-${Date.now()}`, metadata: { username: '@first' } }, { phone });
|
||||
|
||||
const newExternalId = { entityId: `second-${Date.now()}`, metadata: { username: '@second' } };
|
||||
const response = await callResolveVisitor(newExternalId, { phone });
|
||||
|
||||
const appExternalId = response.body.visitor.externalIds.find((e: { appId: string }) => e.appId === app.id);
|
||||
expect(appExternalId.entityId).to.equal(newExternalId.entityId);
|
||||
expect(appExternalId.metadata.username).to.equal('@second');
|
||||
});
|
||||
|
||||
it('should return null when phone not found', async () => {
|
||||
const response = await callResolveVisitor({ entityId: `id-${Date.now()}` }, { phone: '+0000000000' });
|
||||
|
||||
expect(response.status).to.equal(200);
|
||||
expect(response.body.visitor).to.be.null;
|
||||
});
|
||||
|
||||
it('should not use phone fallback when phone is empty', async () => {
|
||||
const response = await callResolveVisitor({ entityId: `id-${Date.now()}` }, { phone: '' });
|
||||
|
||||
expect(response.status).to.equal(200);
|
||||
expect(response.body.visitor).to.be.null;
|
||||
});
|
||||
});
|
||||
|
||||
describe('email fallback', () => {
|
||||
it('should find visitor by email and save externalId', async () => {
|
||||
const email = `test-${Date.now()}@example.com`;
|
||||
const visitor = await createVisitor(undefined, undefined, email);
|
||||
|
||||
const externalId = { entityId: `id-email-${Date.now()}`, metadata: { username: '@emailuser' } };
|
||||
const response = await callResolveVisitor(externalId, { email });
|
||||
|
||||
expect(response.status).to.equal(200);
|
||||
expect(response.body.visitor.id).to.equal(visitor._id);
|
||||
const appExternalId = response.body.visitor.externalIds.find((e: { appId: string }) => e.appId === app.id);
|
||||
expect(appExternalId.entityId).to.equal(externalId.entityId);
|
||||
expect(appExternalId.metadata.username).to.equal(externalId.metadata.username);
|
||||
});
|
||||
|
||||
it('should overwrite externalId when called with different entityId via email', async () => {
|
||||
const email = `test-${Date.now()}@example.com`;
|
||||
await createVisitor(undefined, undefined, email);
|
||||
|
||||
await callResolveVisitor({ entityId: `first-email-${Date.now()}`, metadata: { username: '@first' } }, { email });
|
||||
|
||||
const newExternalId = { entityId: `second-email-${Date.now()}`, metadata: { username: '@second' } };
|
||||
const response = await callResolveVisitor(newExternalId, { email });
|
||||
|
||||
const appExternalId = response.body.visitor.externalIds.find((e: { appId: string }) => e.appId === app.id);
|
||||
expect(appExternalId.entityId).to.equal(newExternalId.entityId);
|
||||
expect(appExternalId.metadata.username).to.equal('@second');
|
||||
});
|
||||
|
||||
it('should return null when email not found', async () => {
|
||||
const response = await callResolveVisitor({ entityId: `id-${Date.now()}` }, { email: 'notfound@example.com' });
|
||||
|
||||
expect(response.status).to.equal(200);
|
||||
expect(response.body.visitor).to.be.null;
|
||||
});
|
||||
|
||||
it('should not use email fallback when email is empty', async () => {
|
||||
const response = await callResolveVisitor({ entityId: `id-${Date.now()}` }, { email: '' });
|
||||
|
||||
expect(response.status).to.equal(200);
|
||||
expect(response.body.visitor).to.be.null;
|
||||
});
|
||||
});
|
||||
|
||||
describe('cross-appId lookup', () => {
|
||||
// Direct MongoDB to inject externalId with a different appId, avoiding the need to install a second test app
|
||||
const addExternalIdToVisitor = async (visitorId: string, externalId: { appId: string; entityId: string }) => {
|
||||
const connection = await MongoClient.connect(URL_MONGODB);
|
||||
try {
|
||||
await connection
|
||||
.db()
|
||||
.collection<ILivechatVisitor>('rocketchat_livechat_visitor')
|
||||
.updateOne({ _id: visitorId }, { $push: { externalIds: externalId } });
|
||||
} finally {
|
||||
await connection.close();
|
||||
}
|
||||
};
|
||||
|
||||
it('should find visitor by entityId even when externalId was saved by different appId', async () => {
|
||||
// Create visitor without phone to ensure we're testing entityId lookup only
|
||||
const visitor = await createVisitorWithCustomData({ ignorePhone: true });
|
||||
|
||||
const oldAppId = 'old-app-version-id-12345';
|
||||
const entityId = `cross-app-${Date.now()}`;
|
||||
await addExternalIdToVisitor(visitor._id, { appId: oldAppId, entityId });
|
||||
|
||||
// App searches by entityId only - should find visitor even though appId is different
|
||||
const response = await callResolveVisitor({ entityId });
|
||||
|
||||
expect(response.status).to.equal(200);
|
||||
expect(response.body.visitor.id).to.equal(visitor._id);
|
||||
const oldExternalId = response.body.visitor.externalIds.find((e: { appId: string }) => e.appId === oldAppId);
|
||||
expect(oldExternalId.entityId).to.equal(entityId);
|
||||
});
|
||||
|
||||
it('should allow app to add its own externalId using updateVisitorExternalId', async () => {
|
||||
const visitor = await createVisitorWithCustomData({ ignorePhone: true });
|
||||
|
||||
const oldAppId = 'old-app-version-id-67890';
|
||||
const entityId = `cross-app-update-${Date.now()}`;
|
||||
await addExternalIdToVisitor(visitor._id, { appId: oldAppId, entityId });
|
||||
|
||||
const lookupResponse = await callResolveVisitor({ entityId });
|
||||
expect(lookupResponse.body.visitor.id).to.equal(visitor._id);
|
||||
|
||||
const newExternalId = { entityId, metadata: { username: '@newversion' } };
|
||||
const updateResponse = await callUpdateExternalId(visitor._id, newExternalId);
|
||||
|
||||
expect(updateResponse.status).to.equal(200);
|
||||
expect(updateResponse.body.visitor.id).to.equal(visitor._id);
|
||||
|
||||
const oldEntry = updateResponse.body.visitor.externalIds.find((e: { appId: string }) => e.appId === oldAppId);
|
||||
const newEntry = updateResponse.body.visitor.externalIds.find((e: { appId: string }) => e.appId === app.id);
|
||||
|
||||
expect(oldEntry).to.exist;
|
||||
expect(oldEntry.entityId).to.equal(entityId);
|
||||
expect(newEntry).to.exist;
|
||||
expect(newEntry.entityId).to.equal(entityId);
|
||||
expect(newEntry.metadata.username).to.equal('@newversion');
|
||||
});
|
||||
});
|
||||
});
|
||||
56
apps/meteor/tests/end-to-end/apps/apps-hooks-file-upload.ts
Normal file
56
apps/meteor/tests/end-to-end/apps/apps-hooks-file-upload.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import type { IRoom } from '@rocket.chat/core-typings';
|
||||
import { expect } from 'chai';
|
||||
import { after, before, describe, it } from 'mocha';
|
||||
import type { Response } from 'supertest';
|
||||
|
||||
import { getCredentials, request, credentials, api } from '../../data/api-data';
|
||||
import { appImplementsIPreFileUpload } from '../../data/apps/app-packages';
|
||||
import { cleanupApps, installLocalTestPackage } from '../../data/apps/helper';
|
||||
import { createRoom, deleteRoom } from '../../data/rooms.helper';
|
||||
import { IS_EE } from '../../e2e/config/constants';
|
||||
|
||||
(IS_EE ? describe : describe.skip)('[Apps Hooks - File Upload]', () => {
|
||||
before((done) => getCredentials(done));
|
||||
|
||||
describe('IPreFileUpload', () => {
|
||||
let room: IRoom;
|
||||
|
||||
before(async () => {
|
||||
await cleanupApps();
|
||||
await installLocalTestPackage(appImplementsIPreFileUpload);
|
||||
room = await createRoom({ type: 'c', name: `file-upload-hook-${Date.now()}` }).then((res) => res.body.channel);
|
||||
});
|
||||
|
||||
after(() => Promise.all([deleteRoom({ type: 'c' as const, roomId: room._id }), cleanupApps()]));
|
||||
|
||||
it('should be capable of rejecting an upload based on app logic', async () => {
|
||||
const fileContents = 'I want to be rejected by the app';
|
||||
|
||||
await request
|
||||
.post(api(`rooms.media/${room._id}`))
|
||||
.set(credentials)
|
||||
.attach('file', Buffer.from(fileContents), { filename: 'test-should-reject' })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
expect(res.body).to.have.property('errorType', 'error-app-prevented');
|
||||
expect(res.body).to.have.property('error').that.is.string(fileContents);
|
||||
});
|
||||
});
|
||||
|
||||
it('should not prevent an unrelated file upload', async () => {
|
||||
const fileContents = 'I should not be rejected';
|
||||
|
||||
await request
|
||||
.post(api(`rooms.media/${room._id}`))
|
||||
.set(credentials)
|
||||
.attach('file', Buffer.from(fileContents), { filename: 'test-file' })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res: Response) => {
|
||||
expect(res.body).to.have.property('success', true);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
50
apps/meteor/tests/end-to-end/apps/apps-uninstall.ts
Normal file
50
apps/meteor/tests/end-to-end/apps/apps-uninstall.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import type { App } from '@rocket.chat/core-typings';
|
||||
import { expect } from 'chai';
|
||||
import { after, before, describe, it } from 'mocha';
|
||||
|
||||
import { getCredentials, request, credentials } from '../../data/api-data';
|
||||
import { apps } from '../../data/apps/apps-data';
|
||||
import { installTestApp, cleanupApps } from '../../data/apps/helper';
|
||||
import { IS_EE } from '../../e2e/config/constants';
|
||||
|
||||
(IS_EE ? describe : describe.skip)('Apps - Uninstall', () => {
|
||||
let app: App;
|
||||
|
||||
before((done) => getCredentials(done));
|
||||
|
||||
before(async () => {
|
||||
await cleanupApps();
|
||||
app = await installTestApp();
|
||||
});
|
||||
|
||||
after(() => cleanupApps());
|
||||
|
||||
describe('[Uninstall]', () => {
|
||||
it('should throw an error when trying to uninstall an invalid app', (done) => {
|
||||
void request
|
||||
.delete(apps('/invalid-id'))
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(404)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.a.property('success', false);
|
||||
expect(res.body.error).to.be.equal('No App found by the id of: invalid-id');
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
it('should remove the app successfully', (done) => {
|
||||
void request
|
||||
.delete(apps(`/${app.id}`))
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.a.property('success', true);
|
||||
expect(res.body).to.have.a.property('app');
|
||||
expect(res.body.app.id).to.be.equal(app.id);
|
||||
expect(res.body.app.status).to.be.equal('disabled');
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
75
apps/meteor/tests/end-to-end/apps/apps-update.ts
Normal file
75
apps/meteor/tests/end-to-end/apps/apps-update.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import type { App } from '@rocket.chat/core-typings';
|
||||
import { expect } from 'chai';
|
||||
import { after, before, describe, it } from 'mocha';
|
||||
|
||||
import { getCredentials, request, credentials } from '../../data/api-data';
|
||||
import { appUpdateTest, appUpdateTestBroken, appUpdateTestFaulty } from '../../data/apps/app-packages';
|
||||
import { apps } from '../../data/apps/apps-data';
|
||||
import { cleanupApps, installLocalTestPackage } from '../../data/apps/helper';
|
||||
import { getUserByUsername } from '../../data/users.helper';
|
||||
import { IS_EE } from '../../e2e/config/constants';
|
||||
|
||||
const APP_USERNAME = 'app-update-test.bot';
|
||||
|
||||
(IS_EE ? describe : describe.skip)('Apps - Update', () => {
|
||||
let app: App;
|
||||
|
||||
before((done) => getCredentials(done));
|
||||
|
||||
before(async () => {
|
||||
await cleanupApps();
|
||||
app = await installLocalTestPackage(appUpdateTest);
|
||||
});
|
||||
|
||||
after(() => cleanupApps());
|
||||
|
||||
describe('[App user presence]', () => {
|
||||
it('should keep the app user online after the app is updated', async () => {
|
||||
const appUserBefore = await getUserByUsername(APP_USERNAME);
|
||||
expect(appUserBefore.status).to.be.equal('online');
|
||||
|
||||
await request
|
||||
.post(apps(`/${app.id}`))
|
||||
.set(credentials)
|
||||
.attach('app', appUpdateTest)
|
||||
.expect(200);
|
||||
|
||||
const appUserAfter = await getUserByUsername(APP_USERNAME);
|
||||
expect(appUserAfter.status).to.be.equal('online');
|
||||
});
|
||||
|
||||
it('should leave the app user unchanged when the update fails to apply', async () => {
|
||||
const appUserBefore = await getUserByUsername(APP_USERNAME);
|
||||
expect(appUserBefore.status).to.be.equal('online');
|
||||
|
||||
await request
|
||||
.post(apps(`/${app.id}`))
|
||||
.set(credentials)
|
||||
.attach('app', appUpdateTestBroken)
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.property('success', false);
|
||||
});
|
||||
|
||||
const appUserAfter = await getUserByUsername(APP_USERNAME);
|
||||
expect(appUserAfter.status).to.be.equal('online');
|
||||
});
|
||||
|
||||
it('should take the app user offline when the updated app ends up disabled', async () => {
|
||||
const appUserBefore = await getUserByUsername(APP_USERNAME);
|
||||
expect(appUserBefore.status).to.be.equal('online');
|
||||
|
||||
await request
|
||||
.post(apps(`/${app.id}`))
|
||||
.set(credentials)
|
||||
.attach('app', appUpdateTestFaulty)
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body.app).to.have.property('status', 'disabled');
|
||||
});
|
||||
|
||||
const appUserAfter = await getUserByUsername(APP_USERNAME);
|
||||
expect(appUserAfter.status).to.be.equal('offline');
|
||||
});
|
||||
});
|
||||
});
|
||||
167
apps/meteor/tests/end-to-end/apps/installation.ts
Normal file
167
apps/meteor/tests/end-to-end/apps/installation.ts
Normal file
@@ -0,0 +1,167 @@
|
||||
import { expect } from 'chai';
|
||||
import { after, before, describe, it } from 'mocha';
|
||||
|
||||
import { getCredentials, request, credentials, api } from '../../data/api-data';
|
||||
import { APP_URL, APP_NAME, apps } from '../../data/apps/apps-data';
|
||||
import { cleanupApps } from '../../data/apps/helper';
|
||||
import { updatePermission } from '../../data/permissions.helper';
|
||||
import { getUserByUsername } from '../../data/users.helper';
|
||||
import { IS_EE } from '../../e2e/config/constants';
|
||||
|
||||
const APP_USERNAME = 'appsrocketchattester.bot';
|
||||
|
||||
describe('Apps - Installation', () => {
|
||||
before((done) => getCredentials(done));
|
||||
|
||||
before(async () => cleanupApps());
|
||||
|
||||
after(() => Promise.all([cleanupApps(), updatePermission('manage-apps', ['admin'])]));
|
||||
|
||||
let app: any;
|
||||
|
||||
describe('[Installation]', () => {
|
||||
it('should throw an error when trying to install an app and the apps framework is enabled but the user does not have the permission', (done) => {
|
||||
void updatePermission('manage-apps', []).then(() => {
|
||||
void request
|
||||
.post(apps())
|
||||
.set(credentials)
|
||||
.send({
|
||||
url: APP_URL,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(403)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.a.property('success', false);
|
||||
expect(res.body.error).to.be.equal('User does not have the permissions required for this action [error-unauthorized]');
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
});
|
||||
(IS_EE ? it : it.skip)('should succesfully install an app from a URL in EE, which should be auto-enabled', (done) => {
|
||||
void updatePermission('manage-apps', ['admin']).then(() => {
|
||||
void request
|
||||
.post(apps())
|
||||
.set(credentials)
|
||||
.send({
|
||||
url: APP_URL,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.a.property('success', true);
|
||||
expect(res.body).to.have.a.property('app');
|
||||
expect(res.body.app).to.have.a.property('id');
|
||||
expect(res.body.app).to.have.a.property('version');
|
||||
expect(res.body.app).to.have.a.property('status').and.to.be.equal('manually_enabled');
|
||||
|
||||
app = res.body.app;
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
});
|
||||
(!IS_EE ? it : it.skip)('should succesfully install an app from a URL in CE, which should not be enabled', (done) => {
|
||||
void updatePermission('manage-apps', ['admin']).then(() => {
|
||||
void request
|
||||
.post(apps())
|
||||
.set(credentials)
|
||||
.send({
|
||||
url: APP_URL,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.a.property('success', true);
|
||||
expect(res.body).to.have.a.property('app');
|
||||
expect(res.body.app).to.have.a.property('id');
|
||||
expect(res.body.app).to.have.a.property('version');
|
||||
expect(res.body.app).to.have.a.property('status').and.to.be.equal('initialized');
|
||||
|
||||
app = res.body.app;
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
});
|
||||
it('should have created the app user successfully', (done) => {
|
||||
void getUserByUsername(APP_USERNAME)
|
||||
.then((user) => {
|
||||
expect(user.username).to.be.equal(APP_USERNAME);
|
||||
})
|
||||
.then(done);
|
||||
});
|
||||
it('should successfully get app details by id', (done) => {
|
||||
void request
|
||||
.get(apps(`/${app.id}`))
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.a.property('success', true);
|
||||
expect(res.body).to.have.a.property('app');
|
||||
expect(res.body.app).to.have.a.property('name', APP_NAME);
|
||||
expect(res.body.app).to.have.a.property('version');
|
||||
expect(res.body.app).to.have.a.property('status');
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
it('should successfully get app status by id', (done) => {
|
||||
void request
|
||||
.get(apps(`/${app.id}/status`))
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.a.property('success', true);
|
||||
expect(res.body).to.have.a.property('status');
|
||||
expect(res.body.status).to.not.be.empty;
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
(IS_EE ? describe : describe.skip)('Slash commands registration', () => {
|
||||
it('should have created the "test-simple" slash command successfully', (done) => {
|
||||
void request
|
||||
.get(api('commands.get'))
|
||||
.query({ command: 'test-simple' })
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.a.property('success', true);
|
||||
expect(res.body).to.have.a.property('command');
|
||||
expect(res.body.command.command).to.be.equal('test-simple');
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
it('should have created the "test-with-arguments" slash command successfully', (done) => {
|
||||
void request
|
||||
.get(api('commands.get'))
|
||||
.query({ command: 'test-with-arguments' })
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.a.property('success', true);
|
||||
expect(res.body).to.have.a.property('command');
|
||||
expect(res.body.command.command).to.be.equal('test-with-arguments');
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
});
|
||||
(IS_EE ? describe : describe.skip)('Video Conf Provider registration', () => {
|
||||
it('should have created two video conf provider successfully', (done) => {
|
||||
void request
|
||||
.get(api('video-conference.providers'))
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.a.property('success', true);
|
||||
expect(res.body).to.have.a.property('data').that.is.an('array').with.lengthOf(3);
|
||||
expect(res.body.data[0]).to.be.an('object').with.a.property('key').equal('test');
|
||||
expect(res.body.data[1]).to.be.an('object').with.a.property('key').equal('persistentchat');
|
||||
expect(res.body.data[2]).to.be.an('object').with.a.property('key').equal('unconfigured');
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
102
apps/meteor/tests/end-to-end/apps/presence-state.ts
Normal file
102
apps/meteor/tests/end-to-end/apps/presence-state.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
import type { App } from '@rocket.chat/core-typings';
|
||||
import { expect } from 'chai';
|
||||
import { after, afterEach, before, describe, it } from 'mocha';
|
||||
|
||||
import { getCredentials, request, credentials } from '../../data/api-data';
|
||||
import { appPresenceStateTest } from '../../data/apps/app-packages';
|
||||
import { apps } from '../../data/apps/apps-data';
|
||||
import { cleanupApps, installLocalTestPackage } from '../../data/apps/helper';
|
||||
import { adminUsername } from '../../data/user';
|
||||
import { getUserByUsername } from '../../data/users.helper';
|
||||
import { IS_EE } from '../../e2e/config/constants';
|
||||
|
||||
(IS_EE ? describe : describe.skip)('Apps - Presence State Bridge', () => {
|
||||
let app: App;
|
||||
|
||||
before((done) => getCredentials(done));
|
||||
|
||||
before(async () => {
|
||||
await cleanupApps();
|
||||
app = await installLocalTestPackage(appPresenceStateTest);
|
||||
});
|
||||
|
||||
after(() => cleanupApps());
|
||||
|
||||
afterEach(async () => {
|
||||
const user = await getUserByUsername(adminUsername);
|
||||
await request
|
||||
.post(apps(`/public/${app.id}/end-active-state`))
|
||||
.set(credentials)
|
||||
.send({ userId: user._id })
|
||||
.expect(200);
|
||||
});
|
||||
|
||||
describe('[setActiveState]', () => {
|
||||
it('should set the user presence with status text and source', async () => {
|
||||
const user = await getUserByUsername(adminUsername);
|
||||
|
||||
await request
|
||||
.post(apps(`/public/${app.id}/set-active-state`))
|
||||
.set(credentials)
|
||||
.send({
|
||||
userId: user._id,
|
||||
statusDefault: 'busy',
|
||||
statusText: 'In a meeting',
|
||||
statusSource: 'internal',
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
const updatedUser = await getUserByUsername(adminUsername);
|
||||
expect(updatedUser.statusText).to.equal('In a meeting');
|
||||
expect(updatedUser.statusSource).to.equal('internal');
|
||||
});
|
||||
});
|
||||
|
||||
describe('[endActiveState]', () => {
|
||||
it('should restore the displaced same-priority state and reset once the queue is exhausted', async () => {
|
||||
const user = await getUserByUsername(adminUsername);
|
||||
|
||||
await request
|
||||
.post(apps(`/public/${app.id}/set-active-state`))
|
||||
.set(credentials)
|
||||
.send({
|
||||
userId: user._id,
|
||||
statusDefault: 'busy',
|
||||
statusText: 'In a meeting',
|
||||
statusSource: 'internal',
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
await request
|
||||
.post(apps(`/public/${app.id}/set-active-state`))
|
||||
.set(credentials)
|
||||
.send({
|
||||
userId: user._id,
|
||||
statusDefault: 'busy',
|
||||
statusText: 'On a call',
|
||||
statusSource: 'internal',
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
await request
|
||||
.post(apps(`/public/${app.id}/end-active-state`))
|
||||
.set(credentials)
|
||||
.send({ userId: user._id })
|
||||
.expect(200);
|
||||
|
||||
const restoredUser = await getUserByUsername(adminUsername);
|
||||
expect(restoredUser.statusText).to.equal('In a meeting');
|
||||
expect(restoredUser.statusSource).to.equal('internal');
|
||||
|
||||
await request
|
||||
.post(apps(`/public/${app.id}/end-active-state`))
|
||||
.set(credentials)
|
||||
.send({ userId: user._id })
|
||||
.expect(200);
|
||||
|
||||
const clearedUser = await getUserByUsername(adminUsername);
|
||||
expect(clearedUser.statusText).to.equal('');
|
||||
expect(clearedUser.statusSource).to.be.undefined;
|
||||
});
|
||||
});
|
||||
});
|
||||
167
apps/meteor/tests/end-to-end/apps/send-messages-as-user.ts
Normal file
167
apps/meteor/tests/end-to-end/apps/send-messages-as-user.ts
Normal file
@@ -0,0 +1,167 @@
|
||||
import type { Credentials } from '@rocket.chat/api-client';
|
||||
import type { App, IMessage, IRoom, IUser } from '@rocket.chat/core-typings';
|
||||
import { expect } from 'chai';
|
||||
import { after, before, describe, it } from 'mocha';
|
||||
|
||||
import { getCredentials, request, credentials } from '../../data/api-data';
|
||||
import { apps } from '../../data/apps/apps-data';
|
||||
import { cleanupApps, installTestApp } from '../../data/apps/helper';
|
||||
import { getMessageById } from '../../data/chat.helper';
|
||||
import { createRoom, deleteRoom } from '../../data/rooms.helper';
|
||||
import { adminUsername, password } from '../../data/user';
|
||||
import type { TestUser } from '../../data/users.helper';
|
||||
import { createUser, deleteUser, login } from '../../data/users.helper';
|
||||
import { IS_EE } from '../../e2e/config/constants';
|
||||
|
||||
(IS_EE ? describe : describe.skip)('Apps - Send Messages As User', () => {
|
||||
let app: App;
|
||||
|
||||
before((done) => getCredentials(done));
|
||||
before(async () => {
|
||||
await cleanupApps();
|
||||
app = await installTestApp();
|
||||
});
|
||||
|
||||
after(() => cleanupApps());
|
||||
|
||||
describe('[Send Message as user]', () => {
|
||||
it('should return an error when the room is not found', (done) => {
|
||||
void request
|
||||
.post(apps(`/public/${app.id}/send-message-as-user`))
|
||||
.send({
|
||||
roomId: 'invalid-room',
|
||||
})
|
||||
.set(credentials)
|
||||
.expect(404)
|
||||
.expect((err: unknown, res: undefined) => {
|
||||
expect(err).to.have.a.property('error');
|
||||
expect(res).to.be.equal(undefined);
|
||||
expect((err as { error?: any }).error).to.have.a.property('text');
|
||||
expect((err as { error?: any }).error.text).to.be.equal('Room "invalid-room" could not be found');
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
it('should return an error when the user is not found', (done) => {
|
||||
void request
|
||||
.post(apps(`/public/${app.id}/send-message-as-user?userId=invalid-user`))
|
||||
.send({
|
||||
roomId: 'GENERAL',
|
||||
})
|
||||
.set(credentials)
|
||||
.expect(404)
|
||||
.expect((err: unknown, res: undefined) => {
|
||||
expect(err).to.have.a.property('error');
|
||||
expect(res).to.be.equal(undefined);
|
||||
expect((err as { error?: any }).error).to.have.a.property('text');
|
||||
expect((err as { error?: any }).error.text).to.be.equal('User with id "invalid-user" could not be found');
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
describe('Send to a Public Channel', () => {
|
||||
let publicMessageId: IMessage['_id'];
|
||||
it('should send a message as app user', (done) => {
|
||||
void request
|
||||
.post(apps(`/public/${app.id}/send-message-as-user?userId=${adminUsername}`))
|
||||
.set(credentials)
|
||||
.send({
|
||||
roomId: 'GENERAL',
|
||||
})
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
const response = JSON.parse(res.text);
|
||||
expect(response).to.have.a.property('messageId');
|
||||
publicMessageId = response.messageId;
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
it('should be a valid message', async () => {
|
||||
const message = await getMessageById({ msgId: publicMessageId });
|
||||
expect(message.msg).to.be.equal('Executing send-message-as-user test endpoint');
|
||||
});
|
||||
});
|
||||
describe('Send to a Private Channel', () => {
|
||||
let privateMessageId: IMessage['_id'];
|
||||
let group: IRoom;
|
||||
let user: TestUser<IUser>;
|
||||
let userCredentials: Credentials;
|
||||
|
||||
before(async () => {
|
||||
group = (
|
||||
await createRoom({
|
||||
type: 'p',
|
||||
name: `apps-e2etest-room-${Date.now()}`,
|
||||
})
|
||||
).body.group;
|
||||
user = await createUser();
|
||||
userCredentials = await login(user.username, password);
|
||||
});
|
||||
|
||||
after(() => Promise.all([deleteRoom({ type: 'p', roomId: group._id }), deleteUser(user)]));
|
||||
|
||||
it('should return 500 when sending a message as user that has no permissions', (done) => {
|
||||
void request
|
||||
.post(apps(`/public/${app.id}/send-message-as-user?userId=${user._id}`))
|
||||
.set(userCredentials)
|
||||
.send({
|
||||
roomId: group._id,
|
||||
})
|
||||
.expect(500)
|
||||
.end(done);
|
||||
});
|
||||
it('should send a message as app user', (done) => {
|
||||
void request
|
||||
.post(apps(`/public/${app.id}/send-message-as-user?userId=${adminUsername}`))
|
||||
.set(credentials)
|
||||
.send({
|
||||
roomId: group._id,
|
||||
})
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
const response = JSON.parse(res.text);
|
||||
expect(response).to.have.a.property('messageId');
|
||||
privateMessageId = response.messageId;
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
it('should be a valid message', async () => {
|
||||
const message = await getMessageById({ msgId: privateMessageId });
|
||||
expect(message.msg).to.be.equal('Executing send-message-as-user test endpoint');
|
||||
});
|
||||
});
|
||||
describe('Send to a DM Channel', () => {
|
||||
let DMMessageId: IMessage['_id'];
|
||||
let dmRoom: IRoom;
|
||||
|
||||
before(async () => {
|
||||
dmRoom = (
|
||||
await createRoom({
|
||||
type: 'd',
|
||||
username: 'rocket.cat',
|
||||
})
|
||||
).body.room;
|
||||
});
|
||||
|
||||
after(() => deleteRoom({ type: 'd', roomId: dmRoom._id }));
|
||||
|
||||
it('should send a message as app user', (done) => {
|
||||
void request
|
||||
.post(apps(`/public/${app.id}/send-message-as-user?userId=${adminUsername}`))
|
||||
.set(credentials)
|
||||
.send({
|
||||
roomId: dmRoom._id,
|
||||
})
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
const response = JSON.parse(res.text);
|
||||
expect(response).to.have.a.property('messageId');
|
||||
DMMessageId = response.messageId;
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
it('should be a valid message', async () => {
|
||||
const message = await getMessageById({ msgId: DMMessageId });
|
||||
expect(message.msg).to.be.equal('Executing send-message-as-user test endpoint');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
133
apps/meteor/tests/end-to-end/apps/send-messages.ts
Normal file
133
apps/meteor/tests/end-to-end/apps/send-messages.ts
Normal file
@@ -0,0 +1,133 @@
|
||||
import type { App, IMessage, IRoom } from '@rocket.chat/core-typings';
|
||||
import { expect } from 'chai';
|
||||
import { after, before, describe, it } from 'mocha';
|
||||
|
||||
import { getCredentials, request, credentials } from '../../data/api-data';
|
||||
import { apps } from '../../data/apps/apps-data';
|
||||
import { cleanupApps, installTestApp } from '../../data/apps/helper';
|
||||
import { getMessageById } from '../../data/chat.helper';
|
||||
import { createRoom, deleteRoom } from '../../data/rooms.helper';
|
||||
import { IS_EE } from '../../e2e/config/constants';
|
||||
|
||||
(IS_EE ? describe : describe.skip)('Apps - Send Messages As APP User', () => {
|
||||
let app: App;
|
||||
|
||||
before((done) => getCredentials(done));
|
||||
before(async () => {
|
||||
await cleanupApps();
|
||||
app = await installTestApp();
|
||||
});
|
||||
|
||||
after(() => cleanupApps());
|
||||
|
||||
describe('[Send Message as app user]', () => {
|
||||
it('should return an error when the room is not found', (done) => {
|
||||
void request
|
||||
.post(apps(`/public/${app.id}/send-message-as-app-user`))
|
||||
.send({
|
||||
roomId: 'invalid-room',
|
||||
})
|
||||
.set(credentials)
|
||||
.expect(404)
|
||||
.expect((err: unknown, res: undefined) => {
|
||||
expect(err).to.have.a.property('error');
|
||||
expect(res).to.be.equal(undefined);
|
||||
expect((err as { error?: any }).error).to.have.a.property('text');
|
||||
expect((err as { error?: any }).error.text).to.be.equal('Room "invalid-room" could not be found');
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
describe('Send to a Public Channel', () => {
|
||||
let publicMessageId: IMessage['_id'];
|
||||
it('should send a message as app user', (done) => {
|
||||
void request
|
||||
.post(apps(`/public/${app.id}/send-message-as-app-user`))
|
||||
.set(credentials)
|
||||
.send({
|
||||
roomId: 'GENERAL',
|
||||
})
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
const response = JSON.parse(res.text);
|
||||
expect(response).to.have.a.property('messageId');
|
||||
publicMessageId = response.messageId;
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
it('should be a valid message', async () => {
|
||||
const message = await getMessageById({ msgId: publicMessageId });
|
||||
expect(message.msg).to.be.equal('Executing send-message-as-app-user test endpoint');
|
||||
});
|
||||
});
|
||||
describe('Send to a Private Channel', () => {
|
||||
let privateMessageId: IMessage['_id'];
|
||||
let group: IRoom;
|
||||
|
||||
before(async () => {
|
||||
group = (
|
||||
await createRoom({
|
||||
type: 'p',
|
||||
name: `apps-e2etest-room-${Date.now()}`,
|
||||
})
|
||||
).body.group;
|
||||
});
|
||||
|
||||
after(() => deleteRoom({ type: 'p', roomId: group._id }));
|
||||
|
||||
it('should send a message as app user', (done) => {
|
||||
void request
|
||||
.post(apps(`/public/${app.id}/send-message-as-app-user`))
|
||||
.set(credentials)
|
||||
.send({
|
||||
roomId: group._id,
|
||||
})
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
const response = JSON.parse(res.text);
|
||||
expect(response).to.have.a.property('messageId');
|
||||
privateMessageId = response.messageId;
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
it('should be a valid message', async () => {
|
||||
const message = await getMessageById({ msgId: privateMessageId });
|
||||
expect(message.msg).to.be.equal('Executing send-message-as-app-user test endpoint');
|
||||
});
|
||||
});
|
||||
describe('Send to a DM Channel', () => {
|
||||
let DMMessageId: IMessage['_id'];
|
||||
let dmRoom: IRoom;
|
||||
|
||||
before(async () => {
|
||||
dmRoom = (
|
||||
await createRoom({
|
||||
type: 'd',
|
||||
username: 'rocket.cat',
|
||||
})
|
||||
).body.room;
|
||||
});
|
||||
|
||||
after(() => deleteRoom({ type: 'd', roomId: dmRoom._id }));
|
||||
|
||||
it('should send a message as app user', (done) => {
|
||||
void request
|
||||
.post(apps(`/public/${app.id}/send-message-as-app-user`))
|
||||
.set(credentials)
|
||||
.send({
|
||||
roomId: dmRoom._id,
|
||||
})
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
const response = JSON.parse(res.text);
|
||||
expect(response).to.have.a.property('messageId');
|
||||
DMMessageId = response.messageId;
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
it('should be a valid message', async () => {
|
||||
const message = await getMessageById({ msgId: DMMessageId });
|
||||
expect(message.msg).to.be.equal('Executing send-message-as-app-user test endpoint');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,81 @@
|
||||
import type { IMessage } from '@rocket.chat/core-typings';
|
||||
import { expect } from 'chai';
|
||||
import { after, before, describe, it } from 'mocha';
|
||||
|
||||
import { getCredentials, request, credentials, api } from '../../data/api-data';
|
||||
import { cleanupApps, installTestApp } from '../../data/apps/helper';
|
||||
import { IS_EE } from '../../e2e/config/constants';
|
||||
|
||||
(IS_EE ? describe : describe.skip)('Apps - Slash Command "test-simple"', () => {
|
||||
before((done) => getCredentials(done));
|
||||
before(async () => {
|
||||
await cleanupApps();
|
||||
await installTestApp();
|
||||
});
|
||||
|
||||
after(() => cleanupApps());
|
||||
|
||||
describe('[Slash command "test-simple"]', () => {
|
||||
it('should return an error when no command is provided', (done) => {
|
||||
void request
|
||||
.post(api('commands.run'))
|
||||
.send({
|
||||
roomId: 'GENERAL',
|
||||
command: null,
|
||||
})
|
||||
.set(credentials)
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.a.property('success', false);
|
||||
expect(res.body.error).to.include('must NOT have fewer than 1 characters');
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
it('should return an error when the command does not exist', (done) => {
|
||||
void request
|
||||
.post(api('commands.run'))
|
||||
.send({
|
||||
roomId: 'GENERAL',
|
||||
command: 'invalid-command',
|
||||
})
|
||||
.set(credentials)
|
||||
.expect(400)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.a.property('success', false);
|
||||
expect(res.body.error).to.be.equal('The command provided does not exist (or is disabled).');
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
it('should execute the slash command successfully', (done) => {
|
||||
void request
|
||||
.post(api('commands.run'))
|
||||
.send({
|
||||
roomId: 'GENERAL',
|
||||
command: 'test-simple',
|
||||
})
|
||||
.set(credentials)
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.a.property('success', true);
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
it('should have sent the message correctly', (done) => {
|
||||
void request
|
||||
.get(api('chat.search'))
|
||||
.query({
|
||||
roomId: 'GENERAL',
|
||||
searchText: "Slashcommand 'test-simple' successfully executed",
|
||||
})
|
||||
.set(credentials)
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
const message = (res.body.messages as IMessage[]).find(
|
||||
(message) => message.msg === "Slashcommand 'test-simple' successfully executed",
|
||||
);
|
||||
expect(message).to.not.be.equal(undefined);
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,52 @@
|
||||
import type { IMessage } from '@rocket.chat/core-typings';
|
||||
import { expect } from 'chai';
|
||||
import { after, before, describe, it } from 'mocha';
|
||||
|
||||
import { getCredentials, request, credentials, api } from '../../data/api-data';
|
||||
import { cleanupApps, installTestApp } from '../../data/apps/helper';
|
||||
import { IS_EE } from '../../e2e/config/constants';
|
||||
|
||||
(IS_EE ? describe : describe.skip)('Apps - Slash Command "test-with-arguments"', () => {
|
||||
before((done) => getCredentials(done));
|
||||
before(async () => {
|
||||
await cleanupApps();
|
||||
await installTestApp();
|
||||
});
|
||||
|
||||
after(() => cleanupApps());
|
||||
|
||||
describe('[Slash command "test-with-arguments"]', () => {
|
||||
const params = 'argument';
|
||||
it('should execute the slash command successfully', (done) => {
|
||||
void request
|
||||
.post(api('commands.run'))
|
||||
.send({
|
||||
roomId: 'GENERAL',
|
||||
command: 'test-with-arguments',
|
||||
params,
|
||||
})
|
||||
.set(credentials)
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).to.have.a.property('success', true);
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
it('should have sent the message correctly', (done) => {
|
||||
const searchText = `Slashcommand \'test-with-arguments\' successfully executed with arguments: "${params}"`;
|
||||
void request
|
||||
.get(api('chat.search'))
|
||||
.query({
|
||||
roomId: 'GENERAL',
|
||||
searchText,
|
||||
})
|
||||
.set(credentials)
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
const message = (res.body.messages as IMessage[]).find((message) => message.msg === searchText);
|
||||
expect(message).to.not.be.equal(undefined);
|
||||
})
|
||||
.end(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user