keep keep keep keep replace keep keep keep keep

// Deprecated: Remove support to usf path

if (!FileUpload.requestCanAccessFiles(req, file)) {

import { expect } from 'chai';

import { getCredentials, api, request, credentials } from '../../data/api-data.js';
import { password } from '../../data/user';
import { closeRoom, createRoom } from '../../data/rooms.helper';
import { imgURL } from '../../data/interactions.js';
import { updateEEPermission, updatePermission, updateSetting } from '../../data/permissions.helper';
import { sendSimpleMessage } from '../../data/chat.helper';
import { createUser, deleteUser, login } from '../../data/users.helper';
import { IS_EE } from '../../e2e/config/constants';

describe('[Rooms]', function () {
	this.retries(0);

	before((done) => getCredentials(done));

	it('/rooms.get', (done) => {
		request
			.get(api('rooms.get'))
			.set(credentials)
			.expect(200)
			.expect((res) => {
				expect(res.body).to.have.property('success', true);
				expect(res.body).to.have.property('update');
				expect(res.body).to.have.property('remove');
			})
			.end(done);
	});

	it('/rooms.get?updatedSince', (done) => {
		request
			.get(api('rooms.get'))
			.set(credentials)
			.query({
				updatedSince: new Date(),
			})
			.expect(200)
			.expect((res) => {
				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);
			})
			.end(done);
	});

	describe('/rooms.saveNotification:', () => {
		let testChannel;
		it('create an channel', (done) => {
			createRoom({ type: 'c', name: `channel.test.${Date.now()}-${Math.random()}` }).end((err, res) => {
				testChannel = res.body.channel;
				done();
			});
		});
		it('/rooms.saveNotification:', (done) => {
			request
				.post(api('rooms.saveNotification'))
				.set(credentials)
				.send({
					roomId: testChannel._id,
					notifications: {
						disableNotifications: '0',
						emailNotifications: 'nothing',
						audioNotificationValue: 'beep',
						desktopNotifications: 'nothing',
						mobilePushNotifications: 'mentions',
					},
				})
				.expect('Content-Type', 'application/json')
				.expect(200)
				.expect((res) => {
					expect(res.body).to.have.property('success', true);
				})
				.end(done);
		});
	});

	describe('/rooms.upload', () => {
		let testChannel;
		let user;
		let userCredentials;

		before(async () => {
			user = await createUser({ joinDefaultChannels: false });
			userCredentials = await login(user.username, password);
		});

		after(async () => {
			await deleteUser(user);
			user = undefined;

			await updateSetting('FileUpload_Restrict_to_room_members', false);
			await updateSetting('FileUpload_ProtectFiles', true);
		});

		const testChannelName = `channel.test.upload.${Date.now()}-${Math.random()}`;

		it('create an channel', (done) => {
			createRoom({ type: 'c', name: testChannelName }).end((err, res) => {
				testChannel = res.body.channel;
				done();
			});
		});
		it("don't upload a file to room with file field other than file", (done) => {
			request
				.post(api(`rooms.upload/${testChannel._id}`))
				.set(credentials)
				.attach('test', imgURL)
				.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-field]');
					expect(res.body).to.have.property('errorType', 'invalid-field');
				})
				.end(done);
		});
		it("don't upload a file to room with empty file", (done) => {
			request
				.post(api(`rooms.upload/${testChannel._id}`))
				.set(credentials)
				.attach('file', '')
				.expect('Content-Type', 'application/json')
				.expect(400)
				.expect((res) => {
					expect(res.body).to.have.property('success', false);
					expect(res.body).to.have.property('error', res.body.error);
				})
				.end(done);
		});
		it("don't upload a file to room with more than 1 file", (done) => {
			request
				.post(api(`rooms.upload/${testChannel._id}`))
				.set(credentials)
				.attach('file', imgURL)
				.attach('file', imgURL)
				.expect('Content-Type', 'application/json')
				.expect(400)
				.expect((res) => {
					expect(res.body).to.have.property('success', false);
					expect(res.body).to.have.property('error', 'Just 1 file is allowed');
				})
				.end(done);
		});

		let fileUrl;
		it('upload a file to room', (done) => {
			request
				.post(api(`rooms.upload/${testChannel._id}`))
				.set(credentials)
				.attach('file', imgURL)
				.expect('Content-Type', 'application/json')
				.expect(200)
				.expect((res) => {
					const { message } = res.body;
					expect(res.body).to.have.property('success', true);
					expect(res.body).to.have.nested.property('message._id', message._id);
					expect(res.body).to.have.nested.property('message.rid', testChannel._id);
					expect(res.body).to.have.nested.property('message.file._id', message.file._id);
					expect(res.body).to.have.nested.property('message.file.type', message.file.type);
					fileUrl = `/file-upload/${message.file._id}/${message.file.name}`;
				})
				.end(done);
		});

		it('should be able to get the file', async () => {
			await request.get(fileUrl).set(credentials).expect('Content-Type', 'image/png').expect(200);
		});

		it('should be able to get the file when no access to the room', async () => {
			await request.get(fileUrl).set(userCredentials).expect('Content-Type', 'image/png').expect(200);
		});

		it('should not be able to get the file when no access to the room if setting blocks', async () => {
			await updateSetting('FileUpload_Restrict_to_room_members', true);
			await request.get(fileUrl).set(userCredentials).expect(403);
		});

		it('should be able to get the file if member and setting blocks outside access', async () => {
			await updateSetting('FileUpload_Restrict_to_room_members', true);
			await request.get(fileUrl).set(credentials).expect('Content-Type', 'image/png').expect(200);
		});

		it('should not be able to get the file without credentials', async () => {
			await request.get(fileUrl).attach('file', imgURL).expect(403);
		});

		it('should be able to get the file without credentials if setting allows', async () => {
			await updateSetting('FileUpload_ProtectFiles', false);
			await request.get(fileUrl).expect('Content-Type', 'image/png').expect(200);
		});
	});

	describe('/rooms.favorite', () => {
		let testChannel;
		const testChannelName = `channel.test.${Date.now()}-${Math.random()}`;
		it('create an channel', (done) => {
			createRoom({ type: 'c', name: testChannelName }).end((err, res) => {
				testChannel = res.body.channel;
				done();
			});
		});
		it('should favorite the room when send favorite: true by roomName', (done) => {
			request
				.post(api('rooms.favorite'))
				.set(credentials)
				.send({
					roomName: testChannelName,
					favorite: true,
				})
				.expect(200)
				.expect((res) => {
					expect(res.body).to.have.property('success', true);
				})
				.end(done);
		});
		it('should unfavorite the room when send favorite: false by roomName', (done) => {
			request
				.post(api('rooms.favorite'))
				.set(credentials)
				.send({
					roomName: testChannelName,
					favorite: false,
				})
				.expect(200)
				.expect((res) => {
					expect(res.body).to.have.property('success', true);
				})
				.end(done);
		});
		it('should favorite the room when send favorite: true by roomId', (done) => {
			request
				.post(api('rooms.favorite'))
				.set(credentials)
				.send({
					roomId: testChannel._id,
					favorite: true,
				})
				.expect(200)
				.expect((res) => {
					expect(res.body).to.have.property('success', true);
				})
				.end(done);
		});

		it('should unfavorite room when send favorite: false by roomId', (done) => {
			request
				.post(api('rooms.favorite'))
				.set(credentials)
				.send({
					roomId: testChannel._id,
					favorite: false,
				})
				.expect(200)
				.expect((res) => {
					expect(res.body).to.have.property('success', true);
				})
				.end(done);
		});

		it('should return an error when send an invalid room', (done) => {
			request
				.post(api('rooms.favorite'))
				.set(credentials)
				.send({
					roomId: 'foo',
					favorite: false,
				})
				.expect(400)
				.expect((res) => {
					expect(res.body).to.have.property('success', false);
					expect(res.body).to.have.property('error');
				})
				.end(done);
		});
	});

	describe('/rooms.nameExists', () => {
		it('should return 401 unauthorized when user is not logged in', (done) => {
			request
				.get(api('rooms.nameExists'))
				.expect('Content-Type', 'application/json')
				.expect(401)
				.expect((res) => {
					expect(res.body).to.have.property('message');
				})
				.end(done);
		});

		// eslint-disable-next-line no-unused-vars
		let testChannel;
		const testChannelName = `channel.test.${Date.now()}-${Math.random()}`;
		it('create an channel', (done) => {
			createRoom({ type: 'c', name: testChannelName }).end((err, res) => {
				testChannel = res.body.channel;
				done();
			});
		});
		it('should return true if this room name exists', (done) => {
			request
				.get(api('rooms.nameExists'))
				.set(credentials)
				.query({
					roomName: testChannelName,
				})
				.expect(200)
				.expect((res) => {
					expect(res.body).to.have.property('success', true);
					expect(res.body).to.have.property('exists', true);
				})
				.end(done);
		});

		it('should return an error when send an invalid room', (done) => {
			request
				.get(api('rooms.nameExists'))
				.set(credentials)
				.query({
					roomId: 'foo',
				})
				.expect(400)
				.expect((res) => {
					expect(res.body).to.have.property('success', false);
					expect(res.body).to.have.property('error');
				})
				.end(done);
		});
	});

	describe('[/rooms.cleanHistory]', () => {
		let publicChannel;
		let privateChannel;
		let directMessageChannel;
		let user;
		beforeEach((done) => {
			const username = `user.test.${Date.now()}`;
			const email = `${username}@rocket.chat`;
			request
				.post(api('users.create'))
				.set(credentials)
				.send({ email, name: username, username, password })
				.end((err, res) => {
					user = res.body.user;
					done(err);
				});
		});

		let userCredentials;
		beforeEach((done) => {
			request
				.post(api('login'))
				.send({
					user: user.username,
					password,
				})
				.expect('Content-Type', 'application/json')
				.expect(200)
				.expect((res) => {
					userCredentials = {};
					userCredentials['X-Auth-Token'] = res.body.data.authToken;
					userCredentials['X-User-Id'] = res.body.data.userId;
				})
				.end(done);
		});
		afterEach((done) => {
			request
				.post(api('users.delete'))
				.set(credentials)
				.send({
					userId: user._id,
				})
				.end(done);
			user = undefined;
		});
		it('create a public channel', (done) => {
			createRoom({ type: 'c', name: `testeChannel${+new Date()}` }).end((err, res) => {
				publicChannel = res.body.channel;
				done();
			});
		});
		it('create a private channel', (done) => {
			createRoom({ type: 'p', name: `testPrivateChannel${+new Date()}` }).end((err, res) => {
				privateChannel = res.body.group;
				done(err);
			});
		});
		it('create a direct message', (done) => {
			createRoom({ type: 'd', username: 'rocket.cat' }).end((err, res) => {
				directMessageChannel = res.body.room.rid;
				done(err);
			});
		});
		it('should return success when send a valid public channel', (done) => {
			request
				.post(api('rooms.cleanHistory'))
				.set(credentials)
				.send({
					roomId: publicChannel._id,
					latest: '2016-12-09T13:42:25.304Z',
					oldest: '2016-08-30T13:42:25.304Z',
				})
				.expect('Content-Type', 'application/json')
				.expect(200)
				.expect((res) => {
					expect(res.body).to.have.property('success', true);
				})
				.end(done);
		});
		it('should successfully delete an image and thumbnail from public channel', (done) => {
			request
				.post(api(`rooms.upload/${publicChannel._id}`))
				.set(credentials)
				.attach('file', imgURL)
				.expect('Content-Type', 'application/json')
				.expect(200)
				.expect((res) => {
					const { message } = res.body;
					expect(res.body).to.have.property('success', true);
					expect(res.body).to.have.nested.property('message._id', message._id);
					expect(res.body).to.have.nested.property('message.rid', publicChannel._id);
					expect(res.body).to.have.nested.property('message.file._id', message.file._id);
					expect(res.body).to.have.nested.property('message.file.type', message.file.type);
				});

			request
				.post(api('rooms.cleanHistory'))
				.set(credentials)
				.send({
					roomId: publicChannel._id,
					latest: '9999-12-31T23:59:59.000Z',
					oldest: '0001-01-01T00:00:00.000Z',
				})
				.expect('Content-Type', 'application/json')
				.expect(200)
				.expect((res) => {
					expect(res.body).to.have.property('success', true);
				});

			request
				.get(api('channels.files'))
				.set(credentials)
				.query({
					roomId: publicChannel._id,
				})
				.expect('Content-Type', 'application/json')
				.expect(200)
				.expect((res) => {
					expect(res.body).to.have.property('success', true);
					expect(res.body).to.have.property('files').and.to.be.an('array');
					expect(res.body.files).to.have.lengthOf(0);
				})
				.end(done);
		});
		it('should return success when send a valid private channel', (done) => {
			request
				.post(api('rooms.cleanHistory'))
				.set(credentials)
				.send({
					roomId: privateChannel._id,
					latest: '2016-12-09T13:42:25.304Z',
					oldest: '2016-08-30T13:42:25.304Z',
				})
				.expect('Content-Type', 'application/json')
				.expect(200)
				.expect((res) => {
					expect(res.body).to.have.property('success', true);
				})
				.end(done);
		});
		it('should return success when send a valid Direct Message channel', (done) => {
			request
				.post(api('rooms.cleanHistory'))
				.set(credentials)
				.send({
					roomId: directMessageChannel,
					latest: '2016-12-09T13:42:25.304Z',
					oldest: '2016-08-30T13:42:25.304Z',
				})
				.expect('Content-Type', 'application/json')
				.expect(200)
				.expect((res) => {
					expect(res.body).to.have.property('success', true);
				})
				.end(done);
		});
		it('should return not allowed error when try deleting messages with user without permission', (done) => {
			request
				.post(api('rooms.cleanHistory'))
				.set(userCredentials)
				.send({
					roomId: directMessageChannel,
					latest: '2016-12-09T13:42:25.304Z',
					oldest: '2016-08-30T13:42:25.304Z',
				})
				.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-allowed');
				})
				.end(done);
		});
	});

	describe('[/rooms.info]', () => {
		let testChannel;
		let testGroup;
		let testDM;
		const expectedKeys = [
			'_id',
			'name',
			'fname',
			't',
			'msgs',
			'usersCount',
			'u',
			'customFields',
			'ts',
			'ro',
			'sysMes',
			'default',
			'_updatedAt',
		];
		const testChannelName = `channel.test.${Date.now()}-${Math.random()}`;
		const testGroupName = `group.test.${Date.now()}-${Math.random()}`;
		after((done) => {
			closeRoom({ type: 'd', roomId: testDM._id }).then(done);
		});
		it('create an channel', (done) => {
			createRoom({ type: 'c', name: testChannelName }).end((err, res) => {
				testChannel = res.body.channel;
				done();
			});
		});
		it('create a group', (done) => {
			createRoom({ type: 'p', name: testGroupName }).end((err, res) => {
				testGroup = res.body.group;
				done();
			});
		});
		it('create a Direct message room with rocket.cat', (done) => {
			createRoom({ type: 'd', username: 'rocket.cat' }).end((err, res) => {
				testDM = res.body.room;
				done();
			});
		});
		it('should return the info about the created channel correctly searching by roomId', (done) => {
			request
				.get(api('rooms.info'))
				.set(credentials)
				.query({
					roomId: testChannel._id,
				})
				.expect(200)
				.expect((res) => {
					expect(res.body).to.have.property('success', true);
					expect(res.body).to.have.property('room').and.to.be.an('object');
					expect(res.body.room).to.have.keys(expectedKeys);
				})
				.end(done);
		});
		it('should return the info about the created channel correctly searching by roomName', (done) => {
			request
				.get(api('rooms.info'))
				.set(credentials)
				.query({
					roomName: testChannel.name,
				})
				.expect(200)
				.expect((res) => {
					expect(res.body).to.have.property('success', true);
					expect(res.body).to.have.property('room').and.to.be.an('object');
					expect(res.body.room).to.have.all.keys(expectedKeys);
				})
				.end(done);
		});
		it('should return the info about the created group correctly searching by roomId', (done) => {
			request
				.get(api('rooms.info'))
				.set(credentials)
				.query({
					roomId: testGroup._id,
				})
				.expect(200)
				.expect((res) => {
					expect(res.body).to.have.property('success', true);
					expect(res.body).to.have.property('room').and.to.be.an('object');
					expect(res.body.room).to.have.all.keys(expectedKeys);
				})
				.end(done);
		});
		it('should return the info about the created group correctly searching by roomName', (done) => {
			request
				.get(api('rooms.info'))
				.set(credentials)
				.query({
					roomName: testGroup.name,
				})
				.expect(200)
				.expect((res) => {
					expect(res.body).to.have.property('success', true);
					expect(res.body).to.have.property('room').and.to.be.an('object');
					expect(res.body.room).to.have.all.keys(expectedKeys);
				})
				.end(done);
		});
		it('should return the info about the created DM correctly searching by roomId', (done) => {
			request
				.get(api('rooms.info'))
				.set(credentials)
				.query({
					roomId: testDM._id,
				})
				.expect(200)
				.expect((res) => {
					expect(res.body).to.have.property('success', true);
					expect(res.body).to.have.property('room').and.to.be.an('object');
				})
				.end(done);
		});
		it('should return name and _id of public channel when it has the "fields" query parameter limiting by name', (done) => {
			request
				.get(api('rooms.info'))
				.set(credentials)
				.query({
					roomId: testChannel._id,
					fields: JSON.stringify({ name: 1 }),
				})
				.expect(200)
				.expect((res) => {
					expect(res.body).to.have.property('success', true);
					expect(res.body).to.have.property('room').and.to.be.an('object');
					expect(res.body.room).to.have.property('name').and.to.be.equal(testChannelName);
					expect(res.body.room).to.have.all.keys(['_id', 'name']);
				})
				.end(done);
		});
	});

	describe('[/rooms.leave]', () => {
		let testChannel;
		let testGroup;
		let testDM;
		const testChannelName = `channel.test.${Date.now()}-${Math.random()}`;
		const testGroupName = `group.test.${Date.now()}-${Math.random()}`;
		after((done) => {
			closeRoom({ type: 'd', roomId: testDM._id }).then(done);
		});
		it('create an channel', (done) => {
			createRoom({ type: 'c', name: testChannelName }).end((err, res) => {
				testChannel = res.body.channel;
				done();
			});
		});
		it('create a group', (done) => {
			createRoom({ type: 'p', name: testGroupName }).end((err, res) => {
				testGroup = res.body.group;
				done();
			});
		});
		it('create a Direct message room with rocket.cat', (done) => {
			createRoom({ type: 'd', username: 'rocket.cat' }).end((err, res) => {
				testDM = res.body.room;
				done();
			});
		});
		it('should return an Error when trying leave a DM room', (done) => {
			request
				.post(api('rooms.leave'))
				.set(credentials)
				.send({
					roomId: testDM._id,
				})
				.expect(400)
				.expect((res) => {
					expect(res.body).to.have.property('success', false);
					expect(res.body).to.have.property('errorType', 'error-not-allowed');
				})
				.end(done);
		});
		it('should return an Error when trying to leave a public channel and you are the last owner', (done) => {
			request
				.post(api('rooms.leave'))
				.set(credentials)
				.send({
					roomId: testChannel._id,
				})
				.expect(400)
				.expect((res) => {
					expect(res.body).to.have.property('success', false);
					expect(res.body).to.have.property('errorType', 'error-you-are-last-owner');
				})
				.end(done);
		});
		it('should return an Error when trying to leave a private group and you are the last owner', (done) => {
			request
				.post(api('rooms.leave'))
				.set(credentials)
				.send({
					roomId: testGroup._id,
				})
				.expect(400)
				.expect((res) => {
					expect(res.body).to.have.property('success', false);
					expect(res.body).to.have.property('errorType', 'error-you-are-last-owner');
				})
				.end(done);
		});
		it('should return an Error when trying to leave a public channel and not have the necessary permission(leave-c)', (done) => {
			updatePermission('leave-c', []).then(() => {
				request
					.post(api('rooms.leave'))
					.set(credentials)
					.send({
						roomId: testChannel._id,
					})
					.expect(400)
					.expect((res) => {
						expect(res.body).to.have.property('success', false);
						expect(res.body).to.have.property('errorType', 'error-not-allowed');
					})
					.end(done);
			});
		});
		it('should return an Error when trying to leave a private group and not have the necessary permission(leave-p)', (done) => {
			updatePermission('leave-p', []).then(() => {
				request
					.post(api('rooms.leave'))
					.set(credentials)
					.send({
						roomId: testGroup._id,
					})
					.expect(400)
					.expect((res) => {
						expect(res.body).to.have.property('success', false);
						expect(res.body).to.have.property('errorType', 'error-not-allowed');
					})
					.end(done);
			});
		});
		it('should leave the public channel when the room has at least another owner and the user has the necessary permission(leave-c)', (done) => {
			updatePermission('leave-c', ['admin']).then(() => {
				request
					.post(api('channels.addAll'))
					.set(credentials)
					.send({
						roomId: testChannel._id,
					})
					.end(() => {
						request
							.post(api('channels.addOwner'))
							.set(credentials)
							.send({
								roomId: testChannel._id,
								userId: 'rocket.cat',
							})
							.end(() => {
								request
									.post(api('rooms.leave'))
									.set(credentials)
									.send({
										roomId: testChannel._id,
									})
									.expect(200)
									.expect((res) => {
										expect(res.body).to.have.property('success', true);
									})
									.end(done);
							});
					});
			});
		});
		it('should leave the private group when the room has at least another owner and the user has the necessary permission(leave-p)', (done) => {
			updatePermission('leave-p', ['admin']).then(() => {
				request
					.post(api('groups.addAll'))
					.set(credentials)
					.send({
						roomId: testGroup._id,
					})
					.end(() => {
						request
							.post(api('groups.addOwner'))
							.set(credentials)
							.send({
								roomId: testGroup._id,
								userId: 'rocket.cat',
							})
							.end(() => {
								request
									.post(api('rooms.leave'))
									.set(credentials)
									.send({
										roomId: testGroup._id,
									})
									.expect(200)
									.expect((res) => {
										expect(res.body).to.have.property('success', true);
									})
									.end(done);
							});
					});
			});
		});
	});

	describe('/rooms.createDiscussion', () => {
		let testChannel;
		const testChannelName = `channel.test.${Date.now()}-${Math.random()}`;
		let messageSent;
		before((done) => {
			createRoom({ type: 'c', name: testChannelName }).end((err, res) => {
				testChannel = res.body.channel;
				sendSimpleMessage({
					roomId: testChannel._id,
				}).end((err, res) => {
					messageSent = res.body.message;
					done();
				});
			});
		});
		it('should throw an error when the user tries to create a discussion and the feature is disabled', (done) => {
			updateSetting('Discussion_enabled', false).then(() => {
				request
					.post(api('rooms.createDiscussion'))
					.set(credentials)
					.send({
						prid: testChannel._id,
						t_name: 'valid name',
					})
					.expect(400)
					.expect((res) => {
						expect(res.body).to.have.property('success', false);
						expect(res.body).to.have.property('errorType', 'error-action-not-allowed');
					})
					.end(() => updateSetting('Discussion_enabled', true).then(done));
			});
		});
		it('should throw an error when the user tries to create a discussion and does not have at least one of the required permissions', (done) => {
			updatePermission('start-discussion', []).then(() => {
				updatePermission('start-discussion-other-user', []).then(() => {
					request
						.post(api('rooms.createDiscussion'))
						.set(credentials)
						.send({
							prid: testChannel._id,
							t_name: 'valid name',
						})
						.expect(400)
						.expect((res) => {
							expect(res.body).to.have.property('success', false);
							expect(res.body).to.have.property('errorType', 'error-action-not-allowed');
						})
						.end(() => {
							updatePermission('start-discussion', ['admin', 'user', 'guest'])
								.then(() => updatePermission('start-discussion-other-user', ['admin', 'user', 'guest']))
								.then(done);
						});
				});
			});
		});
		it('should throw an error when the user tries to create a discussion without the required parameter "prid"', (done) => {
			request
				.post(api('rooms.createDiscussion'))
				.set(credentials)
				.send({})
				.expect(400)
				.expect((res) => {
					expect(res.body).to.have.property('success', false);
					expect(res.body).to.have.property('error', 'Body parameter "prid" is required.');
				})
				.end(done);
		});
		it('should throw an error when the user tries to create a discussion without the required parameter "t_name"', (done) => {
			request
				.post(api('rooms.createDiscussion'))
				.set(credentials)
				.send({
					prid: testChannel._id,
				})
				.expect(400)
				.expect((res) => {
					expect(res.body).to.have.property('success', false);
					expect(res.body).to.have.property('error', 'Body parameter "t_name" is required.');
				})
				.end(done);
		});
		it('should throw an error when the user tries to create a discussion with the required parameter invalid "users"(different from an array)', (done) => {
			request
				.post(api('rooms.createDiscussion'))
				.set(credentials)
				.send({
					prid: testChannel._id,
					t_name: 'valid name',
					users: 'invalid-type-of-users',
				})
				.expect(400)
				.expect((res) => {
					expect(res.body).to.have.property('success', false);
					expect(res.body).to.have.property('error', 'Body parameter "users" must be an array.');
				})
				.end(done);
		});
		it("should throw an error when the user tries to create a discussion with the channel's id invalid", (done) => {
			request
				.post(api('rooms.createDiscussion'))
				.set(credentials)
				.send({
					prid: 'invalid-id',
					t_name: 'valid name',
				})
				.expect(400)
				.expect((res) => {
					expect(res.body).to.have.property('success', false);
					expect(res.body).to.have.property('errorType', 'error-invalid-room');
				})
				.end(done);
		});
		it("should throw an error when the user tries to create a discussion with the message's id invalid", (done) => {
			request
				.post(api('rooms.createDiscussion'))
				.set(credentials)
				.send({
					prid: testChannel._id,
					t_name: 'valid name',
					pmid: 'invalid-message',
				})
				.expect(400)
				.expect((res) => {
					expect(res.body).to.have.property('success', false);
					expect(res.body).to.have.property('errorType', 'error-invalid-message');
				})
				.end(done);
		});
		it('should create a discussion successfully when send only the required parameters', (done) => {
			request
				.post(api('rooms.createDiscussion'))
				.set(credentials)
				.send({
					prid: testChannel._id,
					t_name: `discussion-create-from-tests-${testChannel.name}`,
				})
				.expect(200)
				.expect((res) => {
					expect(res.body).to.have.property('success', true);
					expect(res.body).to.have.property('discussion').and.to.be.an('object');
					expect(res.body.discussion).to.have.property('prid').and.to.be.equal(testChannel._id);
					expect(res.body.discussion).to.have.property('fname').and.to.be.equal(`discussion-create-from-tests-${testChannel.name}`);
				})
				.end(done);
		});
		it('should create a discussion successfully when send the required parameters plus the optional parameter "reply"', (done) => {
			request
				.post(api('rooms.createDiscussion'))
				.set(credentials)
				.send({
					prid: testChannel._id,
					t_name: `discussion-create-from-tests-${testChannel.name}`,
					reply: 'reply from discussion tests',
				})
				.expect(200)
				.expect((res) => {
					expect(res.body).to.have.property('success', true);
					expect(res.body).to.have.property('discussion').and.to.be.an('object');
					expect(res.body.discussion).to.have.property('prid').and.to.be.equal(testChannel._id);
					expect(res.body.discussion).to.have.property('fname').and.to.be.equal(`discussion-create-from-tests-${testChannel.name}`);
				})
				.end(done);
		});
		it('should create a discussion successfully when send the required parameters plus the optional parameter "users"', (done) => {
			request
				.post(api('rooms.createDiscussion'))
				.set(credentials)
				.send({
					prid: testChannel._id,
					t_name: `discussion-create-from-tests-${testChannel.name}`,
					reply: 'reply from discussion tests',
					users: ['rocket.cat'],
				})
				.expect(200)
				.expect((res) => {
					expect(res.body).to.have.property('success', true);
					expect(res.body).to.have.property('discussion').and.to.be.an('object');
					expect(res.body.discussion).to.have.property('prid').and.to.be.equal(testChannel._id);
					expect(res.body.discussion).to.have.property('fname').and.to.be.equal(`discussion-create-from-tests-${testChannel.name}`);
				})
				.end(done);
		});
		it('should create a discussion successfully when send the required parameters plus the optional parameter "pmid"', (done) => {
			request
				.post(api('rooms.createDiscussion'))
				.set(credentials)
				.send({
					prid: testChannel._id,
					t_name: `discussion-create-from-tests-${testChannel.name}`,
					reply: 'reply from discussion tests',
					users: ['rocket.cat'],
					pmid: messageSent._id,
				})
				.expect(200)
				.expect((res) => {
					expect(res.body).to.have.property('success', true);
					expect(res.body).to.have.property('discussion').and.to.be.an('object');
					expect(res.body.discussion).to.have.property('prid').and.to.be.equal(testChannel._id);
					expect(res.body.discussion).to.have.property('fname').and.to.be.equal(`discussion-create-from-tests-${testChannel.name}`);
				})
				.end(done);
		});

		describe('it should create a *private* discussion if the parent channel is public and inside a private team', async () => {
			let privateTeam;

			it('should create a team', (done) => {
				request
					.post(api('teams.create'))
					.set(credentials)
					.send({
						name: `test-team-${Date.now()}`,
						type: 1,
					})
					.expect('Content-Type', 'application/json')
					.expect(200)
					.expect((res) => {
						expect(res.body).to.have.property('success', true);
						expect(res.body).to.have.property('team');
						expect(res.body).to.have.nested.property('team._id');
						privateTeam = res.body.team;
					})
					.end(done);
			});

			it('should add the public channel to the team', (done) => {
				request
					.post(api('teams.addRooms'))
					.set(credentials)
					.send({
						rooms: [testChannel._id],
						teamId: privateTeam._id,
					})
					.expect('Content-Type', 'application/json')
					.expect(200)
					.expect((res) => {
						expect(res.body).to.have.property('success');
					})
					.end(done);
			});

			it('should create a private discussion inside the public channel', (done) => {
				request
					.post(api('rooms.createDiscussion'))
					.set(credentials)
					.send({
						prid: testChannel._id,
						t_name: `discussion-create-from-tests-${testChannel.name}-team`,
					})
					.expect(200)
					.expect((res) => {
						expect(res.body).to.have.property('success', true);
						expect(res.body).to.have.property('discussion').and.to.be.an('object');
						expect(res.body.discussion).to.have.property('prid').and.to.be.equal(testChannel._id);
						expect(res.body.discussion).to.have.property('fname').and.to.be.equal(`discussion-create-from-tests-${testChannel.name}-team`);
						expect(res.body.discussion).to.have.property('t').and.to.be.equal('p');
					})
					.end(done);
			});
		});
	});

	describe('/rooms.getDiscussions', () => {
		let testChannel;
		const testChannelName = `channel.test.getDiscussions${Date.now()}-${Math.random()}`;
		let discussion;
		before((done) => {
			createRoom({ type: 'c', name: testChannelName }).end((err, res) => {
				testChannel = res.body.channel;
				request
					.post(api('rooms.createDiscussion'))
					.set(credentials)
					.send({
						prid: testChannel._id,
						t_name: `discussion-create-from-tests-${testChannel.name}`,
					})
					.end((err, res) => {
						discussion = res.body.discussion;
						done();
					});
			});
		});
		after(() => closeRoom({ type: 'p', roomId: discussion._id }));
		it('should throw an error when the user tries to gets a list of discussion without a required parameter "roomId"', (done) => {
			request
				.get(api('rooms.getDiscussions'))
				.set(credentials)
				.query({})
				.expect(400)
				.expect((res) => {
					expect(res.body).to.have.property('success', false);
					expect(res.body).to.have.property('error', 'The parameter "roomId" or "roomName" is required [error-roomid-param-not-provided]');
				})
				.end(done);
		});
		it('should throw an error when the user tries to gets a list of discussion and he cannot access the room', (done) => {
			updatePermission('view-c-room', []).then(() => {
				request
					.get(api('rooms.getDiscussions'))
					.set(credentials)
					.query({})
					.expect(400)
					.expect((res) => {
						expect(res.body).to.have.property('success', false);
						expect(res.body).to.have.property('error', 'Not Allowed');
					})
					.end(() => updatePermission('view-c-room', ['admin', 'user', 'bot', 'anonymous']).then(done));
			});
		});
		it('should return a list of discussions with ONE discussion', (done) => {
			request
				.get(api('rooms.getDiscussions'))
				.set(credentials)
				.query({
					roomId: testChannel._id,
				})
				.expect(200)
				.expect((res) => {
					expect(res.body).to.have.property('success', true);
					expect(res.body).to.have.property('discussions').and.to.be.an('array');
					expect(res.body.discussions).to.have.lengthOf(1);
				})
				.end(done);
		});
	});

	describe('[/rooms.autocomplete.channelAndPrivate]', () => {
		it('should return an error when the required parameter "selector" is not provided', (done) => {
			request
				.get(api('rooms.autocomplete.channelAndPrivate'))
				.set(credentials)
				.query({})
				.expect('Content-Type', 'application/json')
				.expect(400)
				.expect((res) => {
					expect(res.body).to.have.property('success', false);
					expect(res.body.error).to.be.equal("The 'selector' param is required");
				})
				.end(done);
		});
		it('should return the rooms to fill auto complete', (done) => {
			request
				.get(api('rooms.autocomplete.channelAndPrivate?selector={}'))
				.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('items').and.to.be.an('array');
				})
				.end(done);
		});
	});
	describe('[/rooms.autocomplete.channelAndPrivate.withPagination]', () => {
		it('should return an error when the required parameter "selector" is not provided', (done) => {
			request
				.get(api('rooms.autocomplete.channelAndPrivate.withPagination'))
				.set(credentials)
				.query({})
				.expect('Content-Type', 'application/json')
				.expect(400)
				.expect((res) => {
					expect(res.body).to.have.property('success', false);
					expect(res.body.error).to.be.equal("The 'selector' param is required");
				})
				.end(done);
		});
		it('should return the rooms to fill auto complete', (done) => {
			request
				.get(api('rooms.autocomplete.channelAndPrivate.withPagination?selector={}'))
				.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('items').and.to.be.an('array');
					expect(res.body).to.have.property('total');
				})
				.end(done);
		});
		it('should return the rooms to fill auto complete even requested with count and offset params', (done) => {
			request
				.get(api('rooms.autocomplete.channelAndPrivate.withPagination?selector={}'))
				.set(credentials)
				.query({
					count: 5,
					offset: 0,
				})
				.expect('Content-Type', 'application/json')
				.expect(200)
				.expect((res) => {
					expect(res.body).to.have.property('success', true);
					expect(res.body).to.have.property('items').and.to.be.an('array');
					expect(res.body).to.have.property('total');
				})
				.end(done);
		});
	});

	describe('[/rooms.autocomplete.availableForTeams]', () => {
		it('should return the rooms to fill auto complete', (done) => {
			request
				.get(api('rooms.autocomplete.availableForTeams'))
				.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('items').and.to.be.an('array');
				})
				.end(done);
		});
		it('should return the filtered rooms to fill auto complete', (done) => {
			request
				.get(api('rooms.autocomplete.availableForTeams?name=group'))
				.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('items').and.to.be.an('array');
				})
				.end(done);
		});
	});
	describe('[/rooms.autocomplete.adminRooms]', () => {
		let testGroup;
		const testGroupName = `channel.test.adminRoom${Date.now()}-${Math.random()}`;
		const name = {
			name: testGroupName,
		};
		before((done) => {
			createRoom({ type: 'p', name: testGroupName }).end((err, res) => {
				testGroup = res.body.group;
				request
					.post(api('rooms.createDiscussion'))
					.set(credentials)
					.send({
						prid: testGroup._id,
						t_name: `${testGroupName}-discussion`,
					})
					.end(done);
			});
		});
		(IS_EE ? it : it.skip)('should return an error when the required parameter "selector" is not provided', (done) => {
			updateEEPermission('can-audit', ['admin']).then(() => {
				request
					.get(api('rooms.autocomplete.adminRooms'))
					.set(credentials)
					.query({})
					.expect('Content-Type', 'application/json')
					.expect(400)
					.expect((res) => {
						expect(res.body).to.have.property('success', false);
						expect(res.body.error).to.be.equal("The 'selector' param is required");
					})
					.end(done);
			});
		});
		it('should return the rooms to fill auto complete', (done) => {
			request
				.get(api('rooms.autocomplete.adminRooms?selector={}'))
				.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('items').and.to.be.an('array');
				})
				.end(done);
		});
		it('should return FIX	 the rooms to fill auto complete', (done) => {
			request
				.get(api('rooms.autocomplete.adminRooms?'))
				.set(credentials)
				.query({
					selector: JSON.stringify(name),
				})
				.expect('Content-Type', 'application/json')
				.expect(200)
				.expect((res) => {
					expect(res.body).to.have.property('success', true);
					expect(res.body).to.have.property('items').and.to.be.an('array');
					expect(res.body).to.have.property('items').that.have.lengthOf(2);
				})
				.end(done);
		});
	});
	describe('/rooms.adminRooms', () => {
		const suffix = `test-${Date.now()}`;
		const fnameRoom = `Ελληνικά-${suffix}`;
		const nameRoom = `Ellinika-${suffix}`;

		before((done) => {
			updateSetting('UI_Allow_room_names_with_special_chars', true).then(() => {
				createRoom({ type: 'p', name: fnameRoom }).end(done);
			});
		});

		it('should throw an error when the user tries to gets a list of discussion and he cannot access the room', (done) => {
			updatePermission('view-room-administration', []).then(() => {
				request
					.get(api('rooms.adminRooms'))
					.set(credentials)
					.expect(400)
					.expect((res) => {
						expect(res.body).to.have.property('success', false);
						expect(res.body.error).to.be.equal('error-not-authorized');
					})
					.end(() => updatePermission('view-room-administration', ['admin']).then(done));
			});
		});
		it('should return a list of admin rooms', (done) => {
			request
				.get(api('rooms.adminRooms'))
				.set(credentials)
				.expect(200)
				.expect((res) => {
					expect(res.body).to.have.property('success', true);
					expect(res.body).to.have.property('rooms').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');
				})
				.end(done);
		});
		it('should return a list of admin rooms even requested with count and offset params', (done) => {
			request
				.get(api('rooms.adminRooms'))
				.set(credentials)
				.query({
					count: 5,
					offset: 0,
				})
				.expect(200)
				.expect((res) => {
					expect(res.body).to.have.property('success', true);
					expect(res.body).to.have.property('rooms').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');
				})
				.end(done);
		});
		it('should search the list of admin rooms using non-latin characters when UI_Allow_room_names_with_special_chars setting is toggled', (done) => {
			updateSetting('UI_Allow_room_names_with_special_chars', true).then(() => {
				request
					.get(api('rooms.adminRooms'))
					.set(credentials)
					.query({
						filter: fnameRoom,
					})
					.expect(200)
					.expect((res) => {
						expect(res.body).to.have.property('success', true);
						expect(res.body).to.have.property('rooms').and.to.be.an('array');
						expect(res.body.rooms).to.have.lengthOf(1);
						expect(res.body.rooms[0].fname).to.be.equal(fnameRoom);
						expect(res.body).to.have.property('offset');
						expect(res.body).to.have.property('total');
						expect(res.body).to.have.property('count');
					})
					.end(done);
			});
		});
		it('should search the list of admin rooms using latin characters only when UI_Allow_room_names_with_special_chars setting is disabled', (done) => {
			updateSetting('UI_Allow_room_names_with_special_chars', false).then(() => {
				request
					.get(api('rooms.adminRooms'))
					.set(credentials)
					.query({
						filter: nameRoom,
					})
					.expect(200)
					.expect((res) => {
						expect(res.body).to.have.property('success', true);
						expect(res.body).to.have.property('rooms').and.to.be.an('array');
						expect(res.body.rooms).to.have.lengthOf(1);
						expect(res.body.rooms[0].name).to.be.equal(nameRoom);
						expect(res.body).to.have.property('offset');
						expect(res.body).to.have.property('total');
						expect(res.body).to.have.property('count');
					})
					.end(done);
			});
		});
	});

	describe('update group dms name', () => {
		let testUser;
		let roomId;

		before(async () => {
			testUser = await createUser();

			const rocketcat = 'rocket.cat';
			const usernames = [testUser.username, rocketcat].join(',');

			const result = await request.post(api('dm.create')).set(credentials).send({
				usernames,
			});

			roomId = result.body.room.rid;
		});

		it('should update group name if user changes username', (done) => {
			updateSetting('UI_Use_Real_Name', false).then(() => {
				request
					.post(api('users.update'))
					.set(credentials)
					.send({
						userId: testUser._id,
						data: {
							username: `changed.username.${testUser.username}`,
						},
					})
					.end(() => {
						request
							.get(api('subscriptions.getOne'))
							.set(credentials)
							.query({ roomId })
							.end((err, res) => {
								const { subscription } = res.body;
								expect(subscription.name).to.equal(`rocket.cat,changed.username.${testUser.username}`);
								done();
							});
					});
			});
		});

		it('should update group name if user changes name', (done) => {
			updateSetting('UI_Use_Real_Name', true).then(() => {
				request
					.post(api('users.update'))
					.set(credentials)
					.send({
						userId: testUser._id,
						data: {
							name: `changed.name.${testUser.username}`,
						},
					})
					.end(() => {
						request
							.get(api('subscriptions.getOne'))
							.set(credentials)
							.query({ roomId })
							.end((err, res) => {
								const { subscription } = res.body;
								expect(subscription.fname).to.equal(`changed.name.${testUser.username}, Rocket.Cat`);
								done();
							});
					});
			});
		});
	});

	describe('/rooms.delete', () => {
		let testChannel;
		before('create an channel', async () => {
			const result = await createRoom({ type: 'c', name: `channel.test.${Date.now()}-${Math.random()}` });
			testChannel = result.body.channel;
		});
		it('should throw an error when roomId is not provided', (done) => {
			request
				.post(api('rooms.delete'))
				.set(credentials)
				.send({})
				.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 'roomId' param is required");
				})
				.end(done);
		});
		it('should delete a room when the request is correct', (done) => {
			request
				.post(api('rooms.delete'))
				.set(credentials)
				.send({ roomId: testChannel._id })
				.expect('Content-Type', 'application/json')
				.expect(200)
				.expect((res) => {
					expect(res.body).to.have.property('success', true);
				})
				.end(done);
		});
		it('should throw an error when the room id doesn exist', (done) => {
			request
				.post(api('rooms.delete'))
				.set(credentials)
				.send({ roomId: 'invalid' })
				.expect('Content-Type', 'application/json')
				.expect(400)
				.expect((res) => {
					expect(res.body).to.have.property('success', false);
				})
				.end(done);
		});
	});
});


import { css } from '@rocket.chat/css-in-js';
import { Box, Dropdown } from '@rocket.chat/fuselage';
import { useSetting, useUser } from '@rocket.chat/ui-contexts';
import type { ReactElement } from 'react';
import React, { memo, useRef } from 'react';
import { createPortal } from 'react-dom';

import { UserStatus } from '../../components/UserStatus';
import UserAvatar from '../../components/avatar/UserAvatar';
import UserDropdown from './UserDropdown';
import { useDropdownVisibility } from './hooks/useDropdownVisibility';

const anon = {
	_id: '',
	username: 'Anonymous',
	status: 'online',
	statusText: '',
	avatarETag: undefined,
} as const;

const UserAvatarButton = (): ReactElement => {
	const user = useUser();

	const { status = !user ? 'online' : 'offline', username, avatarETag, statusText } = user || anon;
	const presenceDisabled = useSetting<boolean>('Presence_broadcast_disabled');

	// const allowAnonymousRead = useSetting('Accounts_AllowAnonymousRead');

	const reference = useRef(null);
	const target = useRef(null);
	const { isVisible, toggle } = useDropdownVisibility({ reference, target });

	return (
		<>
			<Box
				position='relative'
				ref={reference}
				onClick={(): void => toggle()}
				className={css`
					cursor: pointer;
				`}
				data-qa='sidebar-avatar-button'
			>
				{username && <UserAvatar size='x24' username={username} etag={avatarETag} />}
				<Box
					className={css`
						bottom: 0;
						right: 0;
					`}
					justifyContent='center'
					alignItems='center'
					display='flex'
					overflow='hidden'
					size={12}
					borderWidth='default'
					position='absolute'
					bg='surface-tint'
					borderColor='extra-light'
					borderRadius='full'
					mie='neg-x2'
					mbe='neg-x2'
				>
					<UserStatus small status={presenceDisabled ? 'disabled' : status} statusText={statusText} />
				</Box>
			</Box>
			{user &&
				isVisible &&
				createPortal(
					<Dropdown reference={reference} ref={target}>
						<UserDropdown user={user} onClose={(): void => toggle(false)} />
					</Dropdown>,
					document.body,
				)}
		</>
	);
};

export default memo(UserAvatarButton);


import type { ReactElement } from 'react';
import React from 'react';
import { useSyncExternalStore } from 'use-sync-external-store/shim';

import * as banners from '../../lib/banners';
import LegacyBanner from './LegacyBanner';
import UiKitBanner from './UiKitBanner';
import { useRemoteBanners } from './hooks/useRemoteBanners';
import { useUserBanners } from './hooks/useUserBanners';

const BannerRegion = (): ReactElement | null => {
	const payload = useSyncExternalStore(...banners.firstSubscription);

	useRemoteBanners();
	useUserBanners();

	if (!payload) {
		return null;
	}

	if (banners.isLegacyPayload(payload)) {
		return <LegacyBanner config={payload} />;
	}

	return <UiKitBanner payload={payload} />;
};

export default BannerRegion;


import { Logger } from '../../../../server/lib/logger/Logger';

export const callbackLogger = new Logger('[Omnichannel] Callback');


/* eslint-env mocha */
import type { Options, Root } from '@rocket.chat/message-parser';
import type { MessageQuoteAttachment } from '@rocket.chat/core-typings';
import { expect } from 'chai';

import { parseMessageQuoteAttachment } from '../../../../../../../client/lib/parseMessageTextToAstMarkdown';

const parseOptions: Options = {
	colors: true,
	emoticons: true,
	katex: {
		dollarSyntax: true,
		parenthesisSyntax: true,
	},
};

const messageParserTokenMessage: Root = [
	{
		type: 'PARAGRAPH',
		value: [
			{
				type: 'PLAIN_TEXT',
				value: 'message ',
			},
			{
				type: 'BOLD',
				value: [
					{
						type: 'PLAIN_TEXT',
						value: 'bold',
					},
				],
			},
			{
				type: 'PLAIN_TEXT',
				value: ' ',
			},
			{
				type: 'ITALIC',
				value: [
					{
						type: 'PLAIN_TEXT',
						value: 'italic',
					},
				],
			},
			{
				type: 'PLAIN_TEXT',
				value: ' and ',
			},
			{
				type: 'STRIKE',
				value: [
					{
						type: 'PLAIN_TEXT',
						value: 'strike',
					},
				],
			},
		],
	},
];

const autoTranslateOptions = {
	autoTranslateEnabled: false,
	translated: false,
};

const quoteMessage = {
	author_name: 'authorName',
	author_link: 'link',
	author_icon: 'icon',
	text: 'message **bold** _italic_ and ~strike~',
	md: messageParserTokenMessage,
};

describe('parseMessageQuoteAttachment', () => {
	it('should return md property populated if the quote is parsed', () => {
		expect(parseMessageQuoteAttachment(quoteMessage, parseOptions, autoTranslateOptions).md).to.deep.equal(messageParserTokenMessage);
	});

	it('should return md property populated if the quote is not parsed', () => {
		expect(
			parseMessageQuoteAttachment(
				{ ...quoteMessage, md: undefined } as unknown as MessageQuoteAttachment,
				parseOptions,
				autoTranslateOptions,
			).md,
		).to.deep.equal(messageParserTokenMessage);
	});

	describe('translated', () => {
		const translatedQuote = {
			...quoteMessage,
			text: 'quote not translated',
			translationProvider: 'provider',
			translations: {
				en: 'quote translated',
			},
		};
		const translatedMessageParsed: Root = [
			{
				type: 'PARAGRAPH',
				value: [
					{
						type: 'PLAIN_TEXT',
						value: 'quote translated',
					},
				],
			},
		];

		const enabledAutoTranslatedOptions = {
			translated: true,
			autoTranslateLanguage: 'en',
		};
		it('should return correct quote translated parsed md when translate is active', () => {
			expect(parseMessageQuoteAttachment(translatedQuote, parseOptions, enabledAutoTranslatedOptions).md).to.deep.equal(
				translatedMessageParsed,
			);
		});

		it('should return text parsed md when translate is active and autoTranslateLanguage is undefined', () => {
			expect(
				parseMessageQuoteAttachment(translatedQuote, parseOptions, { ...enabledAutoTranslatedOptions, autoTranslateLanguage: undefined })
					.md,
			).to.deep.equal([
				{
					type: 'PARAGRAPH',
					value: [
						{
							type: 'PLAIN_TEXT',
							value: 'quote not translated',
						},
					],
				},
			]);
		});

		it('should return correct multiple attachment quote translated parsed md when translate is active', () => {
			const quote = { ...quoteMessage, text: 'text level 2', translations: { en: 'text level 2 translated' } };

			const multipleQuotes = {
				...translatedQuote,
				attachments: [
					{
						...translatedQuote,
						text: 'text',
						translations: {
							en: 'text translated',
						},
						attachments: [quote],
					},
				],
			};
			const multipleQuotesParsed = {
				...translatedQuote,
				md: translatedMessageParsed,
				attachments: [
					{
						...multipleQuotes.attachments[0],
						md: [
							{
								type: 'PARAGRAPH',
								value: [
									{
										type: 'PLAIN_TEXT',
										value: 'text translated',
									},
								],
							},
						],
						attachments: [
							{
								...quote,
								md: [
									{
										type: 'PARAGRAPH',
										value: [
											{
												type: 'PLAIN_TEXT',
												value: 'text level 2 translated',
											},
										],
									},
								],
							},
						],
					},
				],
			};

			expect(parseMessageQuoteAttachment(multipleQuotes, parseOptions, enabledAutoTranslatedOptions)).to.deep.equal(multipleQuotesParsed);
		});
	});
});


import { RoomType } from '@rocket.chat/apps-engine/definition/rooms';
import { expect } from 'chai';
import sinon from 'sinon';
import proxyquire from 'proxyquire';

import { FederationMessageServiceSender } from '../../../../../../../../server/services/federation/application/room/message/sender/MessageServiceSender';

const { FederatedUser } = proxyquire.noCallThru().load('../../../../../../../../server/services/federation/domain/FederatedUser', {
	mongodb: {
		'ObjectId': class ObjectId {
			toHexString(): string {
				return 'hexString';
			}
		},
		'@global': true,
	},
});

const { FederatedRoom } = proxyquire.noCallThru().load('../../../../../../../../server/services/federation/domain/FederatedRoom', {
	mongodb: {
		'ObjectId': class ObjectId {
			toHexString(): string {
				return 'hexString';
			}
		},
		'@global': true,
	},
});

describe('Federation - Application - FederationMessageServiceSender', () => {
	let service: FederationMessageServiceSender;
	const roomAdapter = {
		getFederatedRoomByInternalId: sinon.stub(),
	};
	const userAdapter = {
		getFederatedUserByInternalId: sinon.stub(),
	};
	const settingsAdapter = {
		getHomeServerDomain: sinon.stub().returns('localDomain'),
	};
	const messageAdapter = {
		setExternalFederationEventOnMessageReaction: sinon.stub(),
		unsetExternalFederationEventOnMessageReaction: sinon.stub(),
	};
	const bridge = {
		extractHomeserverOrigin: sinon.stub(),
		sendMessageReaction: sinon.stub(),
		redactEvent: sinon.stub(),
	};

	beforeEach(() => {
		service = new FederationMessageServiceSender(
			roomAdapter as any,
			userAdapter as any,
			settingsAdapter as any,
			messageAdapter as any,
			bridge as any,
		);
	});

	afterEach(() => {
		roomAdapter.getFederatedRoomByInternalId.reset();
		userAdapter.getFederatedUserByInternalId.reset();
		bridge.extractHomeserverOrigin.reset();
		messageAdapter.setExternalFederationEventOnMessageReaction.reset();
		messageAdapter.unsetExternalFederationEventOnMessageReaction.reset();
		bridge.sendMessageReaction.reset();
		bridge.redactEvent.reset();
	});

	describe('#sendExternalMessageReaction()', () => {
		const user = FederatedUser.createInstance('externalInviterId', {
			name: 'normalizedInviterId',
			username: 'normalizedInviterId',
			existsOnlyOnProxyServer: true,
		});
		const room = FederatedRoom.createInstance('externalRoomId', 'normalizedRoomId', user, RoomType.CHANNEL, 'externalRoomName');

		it('should not send the reaction if the internal message does not exists', async () => {
			await service.sendExternalMessageReaction(undefined as any, {} as any, 'reaction');

			expect(bridge.sendMessageReaction.called).to.be.false;
			expect(userAdapter.getFederatedUserByInternalId.called).to.be.false;
		});

		it('should not send the reaction if the internal user does not exists', async () => {
			await service.sendExternalMessageReaction({} as any, undefined as any, 'reaction');

			expect(bridge.sendMessageReaction.called).to.be.false;
			expect(userAdapter.getFederatedUserByInternalId.called).to.be.false;
		});

		it('should not send the reaction if the internal user id does not exists', async () => {
			await service.sendExternalMessageReaction({} as any, {} as any, 'reaction');

			expect(bridge.sendMessageReaction.called).to.be.false;
			expect(userAdapter.getFederatedUserByInternalId.called).to.be.false;
		});

		it('should not send the reaction if the internal message room id does not exists', async () => {
			await service.sendExternalMessageReaction({} as any, { _id: 'id' } as any, 'reaction');

			expect(bridge.sendMessageReaction.called).to.be.false;
			expect(userAdapter.getFederatedUserByInternalId.called).to.be.false;
		});

		it('should not send the reaction the user does not exists', async () => {
			userAdapter.getFederatedUserByInternalId.resolves(undefined);
			await service.sendExternalMessageReaction({ rid: 'roomId' } as any, { _id: 'id' } as any, 'reaction');

			expect(bridge.sendMessageReaction.called).to.be.false;
		});

		it('should not send the reaction the room does not exists', async () => {
			userAdapter.getFederatedUserByInternalId.resolves(user);
			roomAdapter.getFederatedRoomByInternalId.resolves(undefined);
			await service.sendExternalMessageReaction({ rid: 'roomId' } as any, { _id: 'id' } as any, 'reaction');

			expect(bridge.sendMessageReaction.called).to.be.false;
		});

		it('should not send the reaction the the message is not from matrix federation', async () => {
			userAdapter.getFederatedUserByInternalId.resolves(user);
			roomAdapter.getFederatedRoomByInternalId.resolves(room);
			await service.sendExternalMessageReaction({ rid: 'roomId' } as any, { _id: 'id' } as any, 'reaction');

			expect(bridge.sendMessageReaction.called).to.be.false;
		});

		it('should not send the reaction if the user is not from the same home server', async () => {
			bridge.extractHomeserverOrigin.returns('externalDomain');
			roomAdapter.getFederatedRoomByInternalId.resolves(room);
			userAdapter.getFederatedUserByInternalId.resolves(user);
			await service.sendExternalMessageReaction(
				{ rid: 'roomId', federation: { eventId: 'eventId' } } as any,
				{ _id: 'id' } as any,
				'reaction',
			);

			expect(bridge.sendMessageReaction.called).to.be.false;
		});

		it('should send the reaction', async () => {
			bridge.extractHomeserverOrigin.returns('localDomain');
			roomAdapter.getFederatedRoomByInternalId.resolves(room);
			userAdapter.getFederatedUserByInternalId.resolves(user);
			bridge.sendMessageReaction.resolves('returnedEventId');
			await service.sendExternalMessageReaction(
				{ rid: 'roomId', federation: { eventId: 'eventId' } } as any,
				{ _id: 'id' } as any,
				'reaction',
			);

			expect(bridge.sendMessageReaction.calledWith(room.getExternalId(), user.getExternalId(), 'eventId', 'reaction')).to.be.true;
			expect(
				messageAdapter.setExternalFederationEventOnMessageReaction.calledWith(
					user.getUsername(),
					{ rid: 'roomId', federation: { eventId: 'eventId' } },
					'reaction',
					'returnedEventId',
				),
			).to.be.true;
		});
	});

	describe('#sendExternalMessageUnReaction()', () => {
		const user = FederatedUser.createInstance('externalInviterId', {
			name: 'normalizedInviterId',
			username: 'normalizedInviterId',
			existsOnlyOnProxyServer: true,
		});
		const room = FederatedRoom.createInstance('externalRoomId', 'normalizedRoomId', user, RoomType.CHANNEL, 'externalRoomName');

		it('should not send the unreaction if the internal message does not exists', async () => {
			await service.sendExternalMessageUnReaction(undefined as any, {} as any, 'reaction');

			expect(bridge.sendMessageReaction.called).to.be.false;
			expect(userAdapter.getFederatedUserByInternalId.called).to.be.false;
		});

		it('should not send the unreaction if the internal user does not exists', async () => {
			await service.sendExternalMessageUnReaction({} as any, undefined as any, 'reaction');

			expect(bridge.sendMessageReaction.called).to.be.false;
			expect(userAdapter.getFederatedUserByInternalId.called).to.be.false;
		});

		it('should not send the unreaction if the internal user id does not exists', async () => {
			await service.sendExternalMessageUnReaction({} as any, {} as any, 'reaction');

			expect(bridge.sendMessageReaction.called).to.be.false;
			expect(userAdapter.getFederatedUserByInternalId.called).to.be.false;
		});

		it('should not send the unreaction if the internal message room id does not exists', async () => {
			await service.sendExternalMessageUnReaction({} as any, { _id: 'id' } as any, 'reaction');

			expect(bridge.sendMessageReaction.called).to.be.false;
			expect(userAdapter.getFederatedUserByInternalId.called).to.be.false;
		});

		it('should not send the unreaction the user does not exists', async () => {
			userAdapter.getFederatedUserByInternalId.resolves(undefined);
			await service.sendExternalMessageUnReaction({ rid: 'roomId' } as any, { _id: 'id' } as any, 'reaction');

			expect(bridge.sendMessageReaction.called).to.be.false;
		});

		it('should not send the unreaction the room does not exists', async () => {
			userAdapter.getFederatedUserByInternalId.resolves(user);
			roomAdapter.getFederatedRoomByInternalId.resolves(undefined);
			await service.sendExternalMessageUnReaction({ rid: 'roomId' } as any, { _id: 'id' } as any, 'reaction');

			expect(bridge.sendMessageReaction.called).to.be.false;
		});

		it('should not send the unreaction the the message is not from matrix federation', async () => {
			userAdapter.getFederatedUserByInternalId.resolves(user);
			roomAdapter.getFederatedRoomByInternalId.resolves(room);
			await service.sendExternalMessageUnReaction({ rid: 'roomId' } as any, { _id: 'id' } as any, 'reaction');

			expect(bridge.sendMessageReaction.called).to.be.false;
		});

		it('should not send the unreaction if the user is not from the same home server', async () => {
			bridge.extractHomeserverOrigin.onCall(0).returns('localDomain');
			bridge.extractHomeserverOrigin.onCall(1).returns('externalDomain');
			roomAdapter.getFederatedRoomByInternalId.resolves(room);
			userAdapter.getFederatedUserByInternalId.resolves(user);
			await service.sendExternalMessageUnReaction(
				{ rid: 'roomId', federation: { eventId: 'eventId' } } as any,
				{ _id: 'id' } as any,
				'reaction',
			);

			expect(bridge.sendMessageReaction.called).to.be.false;
		});

		it('should not send the unreaction if the user is not from the same home server', async () => {
			bridge.extractHomeserverOrigin.returns('externalDomain');
			roomAdapter.getFederatedRoomByInternalId.resolves(room);
			userAdapter.getFederatedUserByInternalId.resolves(user);
			await service.sendExternalMessageUnReaction(
				{ rid: 'roomId', federation: { eventId: 'eventId' } } as any,
				{ _id: 'id' } as any,
				'reaction',
			);

			expect(bridge.sendMessageReaction.called).to.be.false;
		});

		it('should not send the unreaction if there is no existing reaction', async () => {
			bridge.extractHomeserverOrigin.returns('localDomain');
			roomAdapter.getFederatedRoomByInternalId.resolves(room);
			userAdapter.getFederatedUserByInternalId.resolves(user);
			await service.sendExternalMessageUnReaction(
				{ rid: 'roomId', federation: { eventId: 'eventId' } } as any,
				{ _id: 'id' } as any,
				'reaction',
			);

			expect(bridge.sendMessageReaction.called).to.be.false;
		});

		it('should send the unreaction', async () => {
			const message = {
				rid: 'roomId',
				federation: { eventId: 'eventId' },
				reactions: {
					reaction: {
						federationReactionEventIds: {
							eventId: user.getUsername(),
						},
					},
				},
			} as any;
			bridge.extractHomeserverOrigin.returns('localDomain');
			roomAdapter.getFederatedRoomByInternalId.resolves(room);
			userAdapter.getFederatedUserByInternalId.resolves(user);
			await service.sendExternalMessageUnReaction(message, { _id: 'id', username: user.getUsername() } as any, 'reaction');

			expect(bridge.redactEvent.calledWith(room.getExternalId(), user.getExternalId(), 'eventId')).to.be.true;
			expect(messageAdapter.unsetExternalFederationEventOnMessageReaction.calledWith('eventId', message, 'reaction')).to.be.true;
		});
	});
});


import { Meteor } from 'meteor/meteor';
import { FlowRouter } from 'meteor/kadira:flow-router';

import { RoomManager, MessageAction } from '../../ui-utils/client';
import { messageArgs } from '../../../client/lib/utils/messageArgs';
import { ChatSubscription } from '../../models/client';
import { roomCoordinator } from '../../../client/lib/rooms/roomCoordinator';
import { dispatchToastMessage } from '../../../client/lib/toast';

Meteor.startup(() => {
	MessageAction.addButton({
		id: 'mark-message-as-unread',
		icon: 'flag',
		label: 'Mark_unread',
		context: ['message', 'message-mobile', 'threads'],
		action(_, props) {
			const { message = messageArgs(this).msg } = props;
			return Meteor.call('unreadMessages', message, function (error: unknown) {
				if (error) {
					dispatchToastMessage({ type: 'error', message: error });
					return;
				}
				const subscription = ChatSubscription.findOne({
					rid: message.rid,
				});
				if (subscription == null) {
					return;
				}
				RoomManager.close(subscription.t + subscription.name);
				return FlowRouter.go('home');
			});
		},
		condition({ message, user, room }) {
			const isLivechatRoom = roomCoordinator.isLivechatRoom(room.t);
			if (isLivechatRoom) {
				return false;
			}

			if (!user) {
				return false;
			}

			return message.u._id !== user._id;
		},
		order: 10,
		group: 'menu',
	});
});


import type { IRoom } from '@rocket.chat/core-typings';
import { useUserSubscription } from '@rocket.chat/ui-contexts';
import type { ReactElement } from 'react';
import React from 'react';

import ParentRoom from './ParentRoom';
import ParentRoomWithEndpointData from './ParentRoomWithEndpointData';

type ParentRoomWithDataProps = {
	room: IRoom;
};

const ParentRoomWithData = ({ room }: ParentRoomWithDataProps): ReactElement => {
	const { prid } = room;

	if (!prid) {
		throw new Error('Parent room ID is missing');
	}

	const subscription = useUserSubscription(prid);

	if (subscription) {
		return <ParentRoom room={subscription} />;
	}

	return <ParentRoomWithEndpointData rid={prid} />;
};

export default ParentRoomWithData;


import { Meteor } from 'meteor/meteor';

import { hasPermission } from '../../../../../app/authorization/server';
import { LivechatEnterprise } from '../lib/LivechatEnterprise';

Meteor.methods({
	'livechat:removeMonitor'(username) {
		if (!Meteor.userId() || !hasPermission(Meteor.userId(), 'manage-livechat-monitors')) {
			throw new Meteor.Error('error-not-allowed', 'Not allowed', {
				method: 'livechat:removeMonitor',
			});
		}

		return LivechatEnterprise.removeMonitor(username);
	},
});


it('should be able to get the file', async () => {

it('should be able to get the file when no access to the room', async () => {

keep keep keep keep keep replace keep keep keep


import type { IUser } from '@rocket.chat/core-typings';
import { useEndpoint } from '@rocket.chat/ui-contexts';
import { useCallback, useMemo } from 'react';

import { useScrollableMessageList } from '../../../../../hooks/lists/useScrollableMessageList';
import { useStreamUpdatesForMessageList } from '../../../../../hooks/lists/useStreamUpdatesForMessageList';
import type { ThreadsListOptions } from '../../../../../lib/lists/ThreadsList';
import { ThreadsList } from '../../../../../lib/lists/ThreadsList';
import { getConfig } from '../../../../../lib/utils/getConfig';

export const useThreadsList = (
	options: ThreadsListOptions,
	uid: IUser['_id'] | null,
): {
	threadsList: ThreadsList;
	initialItemCount: number;
	loadMoreItems: (start: number, end: number) => void;
} => {
	const threadsList = useMemo(() => new ThreadsList(options), [options]);

	const getThreadsList = useEndpoint('GET', '/v1/chat.getThreadsList');

	const fetchMessages = useCallback(
		async (start, end) => {
			const { threads, total } = await getThreadsList({
				rid: options.rid,
				type: options.type,
				text: options.text,
				offset: start,
				count: end,
			});

			return {
				items: threads,
				itemCount: total,
			};
		},
		[getThreadsList, options.rid, options.text, options.type],
	);

	const { loadMoreItems, initialItemCount } = useScrollableMessageList(
		threadsList,
		fetchMessages,
		useMemo(() => parseInt(`${getConfig('threadsListSize', 10)}`), []),
	);
	useStreamUpdatesForMessageList(threadsList, uid, options.rid);

	return {
		threadsList,
		loadMoreItems,
		initialItemCount,
	};
};


import type { Select } from '@rocket.chat/fuselage';
import { useToggle } from '@rocket.chat/fuselage-hooks';
import type { ComponentProps, ReactElement } from 'react';
import React, { useCallback, useRef } from 'react';

import type { RadioDropDownProps } from '../../definitions/RadioDropDownDefinitions';
import { isValidReference } from '../../helpers/isValidReference';
import { onMouseEventPreventSideEffects } from '../../helpers/onMouseEventPreventSideEffects';
import DropDownListWrapper from '../DropDownListWrapper';
import RadioButtonList from '../RadioButtonList';
import RadioDropDownAnchor from './RadioDownAnchor';

const RadioDropDown = ({ group, onSelected, ...props }: RadioDropDownProps & Partial<ComponentProps<typeof Select>>): ReactElement => {
	const reference = useRef<HTMLInputElement>(null);
	const [collapsed, toggleCollapsed] = useToggle(false);

	const onClose = useCallback(
		(e) => {
			if (isValidReference(reference, e)) {
				toggleCollapsed(false);
				return;
			}

			onMouseEventPreventSideEffects(e);

			return false;
		},
		[toggleCollapsed],
	);

	return (
		<>
			<RadioDropDownAnchor ref={reference} group={group} onClick={toggleCollapsed as any} {...props} />
			{collapsed && (
				<DropDownListWrapper ref={reference} onClose={onClose}>
					<RadioButtonList group={group} onSelected={onSelected} />
				</DropDownListWrapper>
			)}
		</>
	);
};

export default RadioDropDown;


import { expect } from 'chai';

import { replaceVariables } from '../../../../app/mailer/server/replaceVariables';

describe('Mailer-API', function () {
	describe('replaceVariables', () => {
		const i18n: {
			[key: string]: string;
		} = {
			key: 'value',
		};

		describe('single key', function functionName() {
			it(`should be equal to test ${i18n.key}`, () => {
				expect(`test ${i18n.key}`).to.be.equal(replaceVariables('test {key}', (_match, key) => i18n[key]));
			});
		});

		describe('multiple keys', function functionName() {
			it(`should be equal to test ${i18n.key} and ${i18n.key}`, () => {
				expect(`test ${i18n.key} and ${i18n.key}`).to.be.equal(replaceVariables('test {key} and {key}', (_match, key) => i18n[key]));
			});
		});

		describe('key with a trailing space', function functionName() {
			it(`should be equal to test ${i18n.key}`, () => {
				expect(`test ${i18n.key}`).to.be.equal(replaceVariables('test {key }', (_match, key) => i18n[key]));
			});
		});

		describe('key with a leading space', function functionName() {
			it(`should be equal to test ${i18n.key}`, () => {
				expect(`test ${i18n.key}`).to.be.equal(replaceVariables('test { key}', (_match, key) => i18n[key]));
			});
		});

		describe('key with leading and trailing spaces', function functionName() {
			it(`should be equal to test ${i18n.key}`, () => {
				expect(`test ${i18n.key}`).to.be.equal(replaceVariables('test { key }', (_match, key) => i18n[key]));
			});
		});

		describe('key with multiple words', function functionName() {
			it(`should be equal to test ${i18n.key}`, () => {
				expect(`test ${i18n.key}`).to.be.equal(replaceVariables('test {key ignore}', (_match, key) => i18n[key]));
			});
		});

		describe('key with multiple opening brackets', function functionName() {
			it(`should be equal to test {${i18n.key}`, () => {
				expect(`test {${i18n.key}`).to.be.equal(replaceVariables('test {{key}', (_match, key) => i18n[key]));
			});
		});

		describe('key with multiple closing brackets', function functionName() {
			it(`should be equal to test ${i18n.key}}`, () => {
				expect(`test ${i18n.key}}`).to.be.equal(replaceVariables('test {key}}', (_match, key) => i18n[key]));
			});
		});

		describe('key with multiple opening and closing brackets', function functionName() {
			it(`should be equal to test {${i18n.key}}`, () => {
				expect(`test {${i18n.key}}`).to.be.equal(replaceVariables('test {{key}}', (_match, key) => i18n[key]));
			});
		});
	});
});


it('should not be able to get the file when no access to the room if setting blocks', async () => {

keep keep keep replace keep keep keep keep


Розмову закінчено

Відділ змінено

Користувач приєднався

Користувач вийшов

Ми зараз не в мережі. Будь ласка, залиште повідомлення.


export { default } from './AccountTokensTable';


import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { expect, spy } from 'chai';
import proxyquire from 'proxyquire';
import React from 'react';

const COMPONENT_PATH = '../../../../../../client/sidebar/header/actions/Administration';
const defaultConfig = {
	'@rocket.chat/ui-contexts': {
		useAtLeastOnePermission: () => true,
	},
	'@rocket.chat/fuselage-hooks': {
		useMutableCallback: (cb: () => null) => cb(),
	},
	'../../../../ee/client/hooks/useHasLicenseModule': {
		useHasLicenseModule: () => true,
	},
	'../../../../app/ui-utils/client': {
		AccountBox: {
			getItems: () => [],
		},
	},
	'../../../hooks/useReactiveValue': {
		useReactiveValue: (item: unknown) => item,
	},
	'../hooks/useDropdownVisibility': {
		useDropdownVisibility: () => ({
			isVisible: true,
			toggle: () => null,
		}),
	},
	'../../../components/AdministrationList/AdministrationList': () =>
Administration List
,
};

describe('sidebar/header/actions/Administration', () => {
	it('should render administration list', async () => {
		const Administration = proxyquire.noCallThru().load(COMPONENT_PATH, defaultConfig).default;

		render(<Administration />);

		expect(screen.getByRole('button')).to.exist;
		expect(screen.getByText('Administration List')).to.exist;
	});

	it('should not render administration list when isVisible false', async () => {
		const Administration = proxyquire.noCallThru().load(COMPONENT_PATH, {
			...defaultConfig,
			'../hooks/useDropdownVisibility': {
				useDropdownVisibility: () => ({
					isVisible: false,
					toggle: () => null,
				}),
			},
		}).default;

		render(<Administration />);

		expect(screen.getByRole('button')).to.exist;
		expect(screen.queryByText('Administration List')).to.not.exist;
	});

	it('should render button if has accountBoxItem', async () => {
		const Administration = proxyquire.noCallThru().load(COMPONENT_PATH, {
			...defaultConfig,
			'../../../../app/ui-utils/client': {
				AccountBox: {
					getItems: () => [{}],
				},
			},
			'@rocket.chat/ui-contexts': {
				useAtLeastOnePermission: () => false,
			},
		}).default;

		render(<Administration />);

		expect(screen.getByRole('button')).to.exist;
		expect(screen.getByText('Administration List')).to.exist;
	});

	it('should not render button if does not have permission', async () => {
		const Administration = proxyquire.noCallThru().load(COMPONENT_PATH, {
			...defaultConfig,
			'@rocket.chat/ui-contexts': {
				useAtLeastOnePermission: () => false,
			},
		}).default;

		render(<Administration />);

		expect(screen.queryByText('button')).to.not.exist;
		expect(screen.getByText('Administration List')).to.exist;
	});

	context('when clicked', () => {
		it('should toggle dropdown', async () => {
			const toggle = spy();
			const Administration = proxyquire.noCallThru().load(COMPONENT_PATH, {
				...defaultConfig,
				'../hooks/useDropdownVisibility': {
					useDropdownVisibility: () => ({
						isVisible: true,
						toggle,
					}),
				},
			}).default;

			render(<Administration />);

			userEvent.click(screen.getByRole('button'));
			await waitFor(() => expect(toggle).to.have.been.called());
		});
	});
});


it('should be able to get the file if member and setting blocks outside access', async () => {


export { default } from './AutoTranslateWithData';


import ascii, { asciiRegexp } from './ascii';
import emojis from './emojis';

type EmojiShortname = keyof typeof emojis;
type EmojiASCIICode = keyof typeof ascii;

const shortnamePattern = new RegExp(/:[-+_a-z0-9]+:/, 'gi');
const regAscii = new RegExp(`((\\s|^)${asciiRegexp}(?=\\s|$|[!,.?]))`, 'gi');

const unescaped = {
	'&': '&',
	'&': '&',
	'&': '&',
	'<': '<',
	'<': '<',
	'<': '<',
	'>': '>',
	'>': '>',
	'>': '>',
	'"': '"',
	'"': '"',
	'"': '"',
	''': "'",
	''': "'",
	''': "'",
} as const;

type HTMLEntity = keyof typeof unescaped;

const unescapeHTML = (string: string) => {
	return string.replace(
		/&(?:amp|#38|#x26|lt|#60|#x3C|gt|#62|#x3E|apos|#39|#x27|quot|#34|#x22);/gi,
		(match: string) => unescaped[match as HTMLEntity],
	);
};

const isAscii = (string: string): string is EmojiASCIICode => {
	if (!string?.trim() || !(unescapeHTML(string) in ascii)) {
		return false;
	}
	return true;
};

const shortnameToUnicode = (stringMessage: string) => {
	stringMessage = stringMessage.replace(shortnamePattern, (shortname: string) => emojis[shortname as EmojiShortname] || shortname);
	stringMessage = stringMessage.replace(regAscii, (entire, _dummy1, _dummy2, m3) => {
		return isAscii(m3) ? unescapeHTML(m3) : entire;
	});

	return stringMessage;
};

export default shortnameToUnicode;


import './appleOauthRegisterService';
import './loginHandler';


it('should not be able to get the file without credentials', async () => {

import fs from 'fs';
import stream from 'stream';

import { Meteor } from 'meteor/meteor';
import { Mongo } from 'meteor/mongo';
import streamBuffers from 'stream-buffers';
import Future from 'fibers/future';
import sharp from 'sharp';
import { Cookies } from 'meteor/ostrio:cookies';
import { UploadFS } from 'meteor/jalik:ufs';
import { Match } from 'meteor/check';
import { TAPi18n } from 'meteor/rocketchat:tap-i18n';
import filesize from 'filesize';
import { AppsEngineException } from '@rocket.chat/apps-engine/definition/exceptions';
import { Avatars, UserDataFiles, Uploads, Settings } from '@rocket.chat/models';

import { settings } from '../../../settings/server';
import Users from '../../../models/server/models/Users';
import Rooms from '../../../models/server/models/Rooms';
import Subscriptions from '../../../models/server/models/Subscriptions';
import { mime } from '../../../utils/lib/mimeTypes';
import { hasPermission } from '../../../authorization/server/functions/hasPermission';
import { canAccessRoom } from '../../../authorization/server/functions/canAccessRoom';
import { fileUploadIsValidContentType } from '../../../utils/lib/fileUploadRestrictions';
import { isValidJWT, generateJWT } from '../../../utils/server/lib/JWTHelper';
import { Messages } from '../../../models/server';
import { AppEvents, Apps } from '../../../../ee/server/apps';
import { streamToBuffer } from './streamToBuffer';
import { SystemLogger } from '../../../../server/lib/logger/system';
import { roomCoordinator } from '../../../../server/lib/rooms/roomCoordinator';

const cookie = new Cookies();
let maxFileSize = 0;

settings.watch('FileUpload_MaxFileSize', async function (value) {
	try {
		maxFileSize = parseInt(value);
	} catch (e) {
		maxFileSize = await Settings.findOneById('FileUpload_MaxFileSize').packageValue;
	}
});

const AvatarModel = new Mongo.Collection(Avatars.col.collectionName);
const UserDataFilesModel = new Mongo.Collection(UserDataFiles.col.collectionName);
const UploadsModel = new Mongo.Collection(Uploads.col.collectionName);

export const FileUpload = {
	handlers: {},

	getPath(path = '') {
		return `/file-upload/${path}`;
	},

	configureUploadsStore(store, name, options) {
		const type = name.split(':').pop();
		const stores = UploadFS.getStores();
		delete stores[name];

		return new UploadFS.store[store](
			Object.assign(
				{
					name,
				},
				options,
				FileUpload[`default${type}`](),
			),
		);
	},

	validateFileUpload(fileData) {
		const { file = fileData, content = Buffer.from([]) } = fileData;
		if (!Match.test(file.rid, String)) {
			return false;
		}

		// livechat users can upload files but they don't have an userId
		const user = file.userId ? Meteor.users.findOne(file.userId) : null;

		const room = Rooms.findOneById(file.rid);
		const directMessageAllowed = settings.get('FileUpload_Enabled_Direct');
		const fileUploadAllowed = settings.get('FileUpload_Enabled');
		if (user?.type !== 'app' && canAccessRoom(room, user, file) !== true) {
			return false;
		}
		const language = user ? user.language : 'en';
		if (!fileUploadAllowed) {
			const reason = TAPi18n.__('FileUpload_Disabled', language);
			throw new Meteor.Error('error-file-upload-disabled', reason);
		}

		if (!directMessageAllowed && room.t === 'd') {
			const reason = TAPi18n.__('File_not_allowed_direct_messages', language);
			throw new Meteor.Error('error-direct-message-file-upload-not-allowed', reason);
		}

		// -1 maxFileSize means there is no limit
		if (maxFileSize > -1 && file.size > maxFileSize) {
			const reason = TAPi18n.__(
				'File_exceeds_allowed_size_of_bytes',
				{
					size: filesize(maxFileSize),
				},
				language,
			);
			throw new Meteor.Error('error-file-too-large', reason);
		}

		if (!fileUploadIsValidContentType(file.type)) {
			const reason = TAPi18n.__('File_type_is_not_accepted', language);
			throw new Meteor.Error('error-invalid-file-type', reason);
		}

		// App IPreFileUpload event hook
		try {
			Promise.await(Apps.triggerEvent(AppEvents.IPreFileUpload, { file, content }));
		} catch (error) {
			if (error instanceof AppsEngineException) {
				throw new Meteor.Error('error-app-prevented', error.message);
			}

			throw error;
		}

		return true;
	},

	validateAvatarUpload({ file }) {
		if (!Match.test(file.rid, String) && !Match.test(file.userId, String)) {
			return false;
		}

		const user = file.uid ? Meteor.users.findOne(file.uid, { fields: { language: 1 } }) : null;
		const language = user?.language || 'en';

		// accept only images
		if (!/^image\//.test(file.type)) {
			const reason = TAPi18n.__('File_type_is_not_accepted', language);
			throw new Meteor.Error('error-invalid-file-type', reason);
		}

		// -1 maxFileSize means there is no limit
		if (maxFileSize > -1 && file.size > maxFileSize) {
			const reason = TAPi18n.__(
				'File_exceeds_allowed_size_of_bytes',
				{
					size: filesize(maxFileSize),
				},
				language,
			);
			throw new Meteor.Error('error-file-too-large', reason);
		}

		return true;
	},

	defaultUploads() {
		return {
			collection: UploadsModel,
			filter: new UploadFS.Filter({
				onCheck: FileUpload.validateFileUpload,
			}),
			getPath(file) {
				return `${settings.get('uniqueID')}/uploads/${file.rid}/${file.userId}/${file._id}`;
			},
			onValidate: FileUpload.uploadsOnValidate,
			onRead(fileId, file, req, res) {
				if (!FileUpload.requestCanAccessFiles(req)) {
					res.writeHead(403);
					return false;
				}

				res.setHeader('content-disposition', `attachment; filename="${encodeURIComponent(file.name)}"`);
				return true;
			},
		};
	},

	defaultAvatars() {
		return {
			collection: AvatarModel,
			filter: new UploadFS.Filter({
				onCheck: FileUpload.validateAvatarUpload,
			}),
			getPath(file) {
				const avatarFile = file.rid ? `room-${file.rid}` : file.userId;
				return `${settings.get('uniqueID')}/avatars/${avatarFile}`;
			},
			onValidate: FileUpload.avatarsOnValidate,
			onFinishUpload: FileUpload.avatarsOnFinishUpload,
		};
	},

	defaultUserDataFiles() {
		return {
			collection: UserDataFilesModel,
			getPath(file) {
				return `${settings.get('uniqueID')}/uploads/userData/${file.userId}`;
			},
			onValidate: FileUpload.uploadsOnValidate,
			onRead(fileId, file, req, res) {
				if (!FileUpload.requestCanAccessFiles(req)) {
					res.writeHead(403);
					return false;
				}

				res.setHeader('content-disposition', `attachment; filename="${encodeURIComponent(file.name)}"`);
				return true;
			},
		};
	},

	avatarsOnValidate(file) {
		if (settings.get('Accounts_AvatarResize') !== true) {
			return;
		}

		if (file.rid) {
			if (!hasPermission(Meteor.userId(), 'edit-room-avatar', file.rid)) {
				throw new Meteor.Error('error-not-allowed', 'Change avatar is not allowed');
			}
		} else if (Meteor.userId() !== file.userId && !hasPermission(Meteor.userId(), 'edit-other-user-avatar')) {
			throw new Meteor.Error('error-not-allowed', 'Change avatar is not allowed');
		}

		const tempFilePath = UploadFS.getTempFilePath(file._id);

		const height = settings.get('Accounts_AvatarSize');
		const width = height;
		const future = new Future();

		const s = sharp(tempFilePath);
		if (settings.get('FileUpload_RotateImages') === true) {
			s.rotate();
		}

		s.metadata(
			Meteor.bindEnvironment((err, metadata) => {
				if (!metadata) {
					metadata = {};
				}

				s.resize({
					width,
					height,
					fit: metadata.hasAlpha ? sharp.fit.contain : sharp.fit.cover,
					background: { r: 255, g: 255, b: 255, alpha: metadata.hasAlpha ? 0 : 1 },
				})
					// Use buffer to get the result in memory then replace the existing file
					// There is no option to override a file using this library
					//
					// BY THE SHARP DOCUMENTATION:
					// toBuffer: Write output to a Buffer. JPEG, PNG, WebP, TIFF and RAW output are supported.
					// By default, the format will match the input image, except GIF and SVG input which become PNG output.
					.toBuffer({ resolveWithObject: true })
					.then(
						Meteor.bindEnvironment(({ data, info }) => {
							fs.writeFile(
								tempFilePath,
								data,
								Meteor.bindEnvironment((err) => {
									if (err != null) {
										SystemLogger.error(err);
									}

									this.getCollection().direct.update(
										{ _id: file._id },
										{
											$set: {
												size: info.size,
												...(['gif', 'svg'].includes(metadata.format) ? { type: 'image/png' } : {}),
											},
										},
									);
									future.return();
								}),
							);
						}),
					);
			}),
		);

		return future.wait();
	},

	resizeImagePreview(file) {
		file = Promise.await(Uploads.findOneById(file._id));
		file = FileUpload.addExtensionTo(file);
		const image = FileUpload.getStore('Uploads')._store.getReadStream(file._id, file);

		const transformer = sharp().resize({ width: 32, height: 32, fit: 'inside' }).jpeg().blur();
		const result = transformer.toBuffer().then((out) => out.toString('base64'));
		image.pipe(transformer);
		return result;
	},

	async extractMetadata(file) {
		return sharp(FileUpload.getBufferSync(file)).metadata();
	},

	createImageThumbnail(file) {
		if (!settings.get('Message_Attachments_Thumbnails_Enabled')) {
			return;
		}

		const width = settings.get('Message_Attachments_Thumbnails_Width');
		const height = settings.get('Message_Attachments_Thumbnails_Height');

		if (file.identify.size && file.identify.size.height < height && file.identify.size.width < width) {
			return;
		}

		file = Promise.await(Uploads.findOneById(file._id));
		file = FileUpload.addExtensionTo(file);
		const store = FileUpload.getStore('Uploads');
		const image = store._store.getReadStream(file._id, file);

		const transformer = sharp().resize({ width, height, fit: 'inside' });

		const result = transformer.toBuffer({ resolveWithObject: true }).then(({ data, info: { width, height } }) => ({ data, width, height }));
		image.pipe(transformer);

		return result;
	},

	uploadImageThumbnail(file, buffer, rid, userId) {
		const store = FileUpload.getStore('Uploads');
		const details = {
			name: `thumb-${file.name}`,
			size: buffer.length,
			type: file.type,
			originalFileId: file._id,
			typeGroup: 'thumb',
			rid,
			userId,
		};

		return store.insertSync(details, buffer);
	},

	uploadsOnValidate(file) {
		if (!/^image\/((x-windows-)?bmp|p?jpeg|png|gif|webp)$/.test(file.type)) {
			return;
		}

		const tmpFile = UploadFS.getTempFilePath(file._id);

		const fut = new Future();

		const s = sharp(tmpFile);
		s.metadata(
			Meteor.bindEnvironment((err, metadata) => {
				if (err != null) {
					SystemLogger.error(err);
					return fut.return();
				}

				const rotated = typeof metadata.orientation !== 'undefined' && metadata.orientation !== 1;

				const identify = {
					format: metadata.format,
					size: {
						width: rotated ? metadata.height : metadata.width,
						height: rotated ? metadata.width : metadata.height,
					},
				};

				const reorientation = (cb) => {
					if (!rotated || settings.get('FileUpload_RotateImages') !== true) {
						return cb();
					}
					s.rotate()
						.toFile(`${tmpFile}.tmp`)
						.then(
							Meteor.bindEnvironment(() => {
								fs.unlink(
									tmpFile,
									Meteor.bindEnvironment(() => {
										fs.rename(
											`${tmpFile}.tmp`,
											tmpFile,
											Meteor.bindEnvironment(() => {
												cb();
											}),
										);
									}),
								);
							}),
						)
						.catch((err) => {
							SystemLogger.error(err);
							fut.return();
						});
				};

				reorientation(() => {
					const { size } = fs.lstatSync(tmpFile);
					this.getCollection().direct.update(
						{ _id: file._id },
						{
							$set: { size, identify },
						},
					);

					fut.return();
				});
			}),
		);

		return fut.wait();
	},

	avatarRoomOnFinishUpload(file) {
		if (!hasPermission(Meteor.userId(), 'edit-room-avatar', file.rid)) {
			throw new Meteor.Error('error-not-allowed', 'Change avatar is not allowed');
		}
	},
	avatarsOnFinishUpload(file) {
		if (file.rid) {
			return FileUpload.avatarRoomOnFinishUpload(file);
		}

		if (Meteor.userId() !== file.userId && !hasPermission(Meteor.userId(), 'edit-other-user-avatar')) {
			throw new Meteor.Error('error-not-allowed', 'Change avatar is not allowed');
		}
		// update file record to match user's username
		const user = Users.findOneById(file.userId);
		const oldAvatar = Promise.await(Avatars.findOneByName(user.username));
		if (oldAvatar) {
			Promise.await(Avatars.deleteFile(oldAvatar._id));
		}
		Promise.await(Avatars.updateFileNameById(file._id, user.username));
		// console.log('upload finished ->', file);
	},

	requestCanAccessFiles({ headers = {}, query = {} }, file) {
		if (!settings.get('FileUpload_ProtectFiles')) {
			return true;
		}

		let { rc_uid, rc_token, rc_rid, rc_room_type } = query;
		const { token } = query;

		if (!rc_uid && headers.cookie) {
			rc_uid = cookie.get('rc_uid', headers.cookie);
			rc_token = cookie.get('rc_token', headers.cookie);
			rc_rid = cookie.get('rc_rid', headers.cookie);
			rc_room_type = cookie.get('rc_room_type', headers.cookie);
		}

		const isAuthorizedByRoom = () =>
			rc_room_type && roomCoordinator.getRoomDirectives(rc_room_type)?.canAccessUploadedFile({ rc_uid, rc_rid, rc_token });
		const isAuthorizedByJWT = () =>
			settings.get('FileUpload_Enable_json_web_token_for_files') &&
			token &&
			isValidJWT(token, settings.get('FileUpload_json_web_token_secret_for_files'));

		if (isAuthorizedByRoom() || isAuthorizedByJWT()) {
			return true;
		}

		const uid = rc_uid || headers['x-user-id'];
		const authToken = rc_token || headers['x-auth-token'];

		const user = uid && authToken && Users.findOneByIdAndLoginToken(uid, authToken, { fields: { _id: 1 } });

		if (!user) {
			return false;
		}

		if (!settings.get('FileUpload_Restrict_to_room_members') || !file?.rid) {
			return true;
		}

		const subscription = Subscriptions.findOneByRoomIdAndUserId(file.rid, user._id, { fields: { _id: 1 } });

		if (subscription) {
			return true;
		}

		return false;
	},
	addExtensionTo(file) {
		if (mime.lookup(file.name) === file.type) {
			return file;
		}

		// This file type can be pretty much anything, so it's better if we don't mess with the file extension
		if (file.type !== 'application/octet-stream') {
			const ext = mime.extension(file.type);
			if (ext && new RegExp(`\\.${ext}$`, 'i').test(file.name) === false) {
				file.name = `${file.name}.${ext}`;
			}
		}

		return file;
	},

	getStore(modelName) {
		const storageType = settings.get('FileUpload_Storage_Type');
		const handlerName = `${storageType}:${modelName}`;

		return this.getStoreByName(handlerName);
	},

	getStoreByName(handlerName) {
		if (this.handlers[handlerName] == null) {
			SystemLogger.error(`Upload handler "${handlerName}" does not exists`);
		}
		return this.handlers[handlerName];
	},

	get(file, req, res, next) {
		const store = this.getStoreByName(file.store);
		if (store && store.get) {
			return store.get(file, req, res, next);
		}
		res.writeHead(404);
		res.end();
	},

	getBuffer(file, cb) {
		const store = this.getStoreByName(file.store);

		if (!store || !store.get) {
			cb(new Error('Store is invalid'), null);
		}

		const buffer = new streamBuffers.WritableStreamBuffer({
			initialSize: file.size,
		});

		buffer.on('finish', () => {
			cb(null, buffer.getContents());
		});

		store.copy(file, buffer);
	},

	getBufferSync: Meteor.wrapAsync((file, cb) => FileUpload.getBuffer(file, cb)),

	copy(file, targetFile) {
		const store = this.getStoreByName(file.store);
		const out = fs.createWriteStream(targetFile);

		file = FileUpload.addExtensionTo(file);

		if (store.copy) {
			store.copy(file, out);
			return true;
		}

		return false;
	},

	redirectToFile(fileUrl, req, res) {
		res.removeHeader('Content-Length');
		res.removeHeader('Cache-Control');
		res.setHeader('Location', fileUrl);
		res.writeHead(302);
		res.end();
	},

	proxyFile(fileName, fileUrl, forceDownload, request, req, res) {
		res.setHeader('Content-Disposition', `${forceDownload ? 'attachment' : 'inline'}; filename="${encodeURI(fileName)}"`);

		request.get(fileUrl, (fileRes) => fileRes.pipe(res));
	},

	generateJWTToFileUrls({ rid, userId, fileId }) {
		if (!settings.get('FileUpload_ProtectFiles') || !settings.get('FileUpload_Enable_json_web_token_for_files')) {
			return;
		}
		return generateJWT(
			{
				rid,
				userId,
				fileId,
			},
			settings.get('FileUpload_json_web_token_secret_for_files'),
		);
	},

	removeFilesByRoomId(rid) {
		if (typeof rid !== 'string' || rid.trim().length === 0) {
			return;
		}
		Messages.find(
			{
				rid,
				'file._id': {
					$exists: true,
				},
			},
			{
				fields: {
					'file._id': 1,
				},
			},
		)
			.fetch()
			.forEach((document) => FileUpload.getStore('Uploads').deleteById(document.file._id));
	},
};

export class FileUploadClass {
	constructor({ name, model, store, get, insert, getStore, copy }) {
		this.name = name;
		this.model = model || this.getModelFromName();
		this._store = store || UploadFS.getStore(name);
		this.get = get;
		this.copy = copy;

		if (insert) {
			this.insert = insert;
		}

		if (getStore) {
			this.getStore = getStore;
		}

		FileUpload.handlers[name] = this;

		this.insertSync = Meteor.wrapAsync(this.insert, this);
	}

	getStore() {
		return this._store;
	}

	get store() {
		return this.getStore();
	}

	set store(store) {
		this._store = store;
	}

	getModelFromName() {
		const modelsAvailable = {
			Avatars,
			Uploads,
			UserDataFiles,
		};
		const modelName = this.name.split(':')[1];
		if (!modelsAvailable[modelName]) {
			throw new Error('Invalid Model for FileUpload');
		}
		return modelsAvailable[modelName];
	}

	delete(fileId) {
		// TODO: Remove this method
		if (this.store && this.store.delete) {
			this.store.delete(fileId);
		}

		return Promise.await(this.model.deleteFile(fileId));
	}

	deleteById(fileId) {
		const file = Promise.await(this.model.findOneById(fileId));

		if (!file) {
			return;
		}

		const store = FileUpload.getStoreByName(file.store);

		return store.delete(file._id);
	}

	deleteByName(fileName) {
		const file = Promise.await(this.model.findOneByName(fileName));

		if (!file) {
			return;
		}

		const store = FileUpload.getStoreByName(file.store);

		return store.delete(file._id);
	}

	deleteByRoomId(rid) {
		const file = Promise.await(this.model.findOneByRoomId(rid));

		if (!file) {
			return;
		}

		const store = FileUpload.getStoreByName(file.store);

		return store.delete(file._id);
	}

	_doInsert(fileData, streamOrBuffer, cb) {
		const fileId = this.store.create(fileData);
		const token = this.store.createToken(fileId);
		const tmpFile = UploadFS.getTempFilePath(fileId);

		try {
			if (streamOrBuffer instanceof stream) {
				streamOrBuffer.pipe(fs.createWriteStream(tmpFile));
			} else if (streamOrBuffer instanceof Buffer) {
				fs.writeFileSync(tmpFile, streamOrBuffer);
			} else {
				throw new Error('Invalid file type');
			}

			const file = Meteor.call('ufsComplete', fileId, this.name, token);

			if (cb) {
				cb(null, file);
			}

			return file;
		} catch (e) {
			if (cb) {
				cb(e);
			} else {
				throw e;
			}
		}
	}

	insert(fileData, streamOrBuffer, cb) {
		if (streamOrBuffer instanceof stream) {
			streamOrBuffer = Promise.await(streamToBuffer(streamOrBuffer));
		}

		if (streamOrBuffer instanceof Uint8Array) {
			// Services compat :)
			streamOrBuffer = Buffer.from(streamOrBuffer);
		}

		// Check if the fileData matches store filter
		const filter = this.store.getFilter();
		if (filter && filter.check) {
			filter.check({ file: fileData, content: streamOrBuffer });
		}

		return this._doInsert(fileData, streamOrBuffer, cb);
	}
}