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; let userWithLeftoverFederationData: TestUser; 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; 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; 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); }); }); });