snapshot of https://github.com/RocketChat/Rocket.Chat.git (develop, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 1s

This commit is contained in:
upstream-mirror
2026-09-16 01:21:02 +05:00
commit 30810eecd7
10161 changed files with 987110 additions and 0 deletions

View File

@@ -0,0 +1,127 @@
import { expect } from 'chai';
import { BeforeSaveBadWords } from '../../../../../../server/services/messages/hooks/BeforeSaveBadWords';
const createMessage = (msg?: string) => ({
_id: 'random',
rid: 'GENERAL',
ts: new Date(),
u: {
_id: 'userId',
username: 'username',
},
_updatedAt: new Date(),
msg: msg as string,
});
describe('Filter bad words before saving message', () => {
it('should return same message if bad words not configured', async () => {
const badWords = new BeforeSaveBadWords();
const message = createMessage('hell');
const result = await badWords.filterBadWords({ message });
return expect(result.msg).to.be.equal('hell');
});
it("should return same message if no 'msg' property provided", async () => {
const badWords = new BeforeSaveBadWords();
const message = createMessage();
const result = await badWords.filterBadWords({ message });
return expect(result.msg).to.be.equal(undefined);
});
it('should return filter bad words from message when configured', async () => {
const badWords = new BeforeSaveBadWords();
await badWords.configure();
const message = createMessage('hell');
const result = await badWords.filterBadWords({ message });
return expect(result.msg).to.equal('****');
});
it('should return same message if bad words disabled after configured', async () => {
const badWords = new BeforeSaveBadWords();
await badWords.configure();
badWords.disable();
const message = createMessage('hell');
const result = await badWords.filterBadWords({ message });
return expect(result.msg).to.be.equal('hell');
});
it('should filter custom bad words', async () => {
const badWords = new BeforeSaveBadWords();
await badWords.configure('good');
const message = createMessage('good');
const result = await badWords.filterBadWords({ message });
return expect(result.msg).to.be.equal('****');
});
it('should allow custom good words', async () => {
const badWords = new BeforeSaveBadWords();
await badWords.configure(undefined, 'hell');
const message = createMessage('hell');
const result = await badWords.filterBadWords({ message });
return expect(result.msg).to.be.equal('hell');
});
it('should filter non ascii bad words', async () => {
const badWords = new BeforeSaveBadWords();
await badWords.configure('バカ');
const message = createMessage('hell is バカ');
const result = await badWords.filterBadWords({ message });
return expect(result.msg).to.be.equal('**** is **');
});
it('should filter just the non ascii bad words', async () => {
const badWords = new BeforeSaveBadWords();
await badWords.configure('バカ');
const message = createMessage('バカ');
const result = await badWords.filterBadWords({ message });
return expect(result.msg).to.be.equal('**');
});
it('should filter non ascii bad words with punctuation', async () => {
const badWords = new BeforeSaveBadWords();
await badWords.configure('バカ');
const message = createMessage('バカ.');
const result = await badWords.filterBadWords({ message });
return expect(result.msg).to.be.equal('**.');
});
it('should not filter non ascii bad words, if part of another word ', async () => {
const badWords = new BeforeSaveBadWords();
await badWords.configure('バカ');
const message = createMessage('TESTバカTEST');
const result = await badWords.filterBadWords({ message });
return expect(result.msg).to.be.equal('TESTバカTEST');
});
});

View File

@@ -0,0 +1,104 @@
import { api, MeteorError } from '@rocket.chat/core-services';
import { expect } from 'chai';
import { before, describe } from 'mocha';
import { BeforeSaveCheckMAC } from '../../../../../../server/services/messages/hooks/BeforeSaveCheckMAC';
import { BrokerMocked } from '../../../../../mocks/server/BrokerMocked';
const createMessage = (msg?: string, extra: any = {}) => ({
_id: 'random',
rid: 'GENERAL',
ts: new Date(),
u: {
_id: 'userId',
username: 'username',
},
_updatedAt: new Date(),
msg: msg as string,
...extra,
});
const createRoom = (extra: any = {}): any => ({
_id: 'GENERAL',
t: 'c',
u: {
_id: 'userId',
username: 'username',
name: 'name',
},
msgs: 1,
usersCount: 1,
_updatedAt: new Date(),
...extra,
});
const broker = new BrokerMocked();
describe('Check MAC', () => {
before(() => {
api.setBroker(broker as any);
});
it('should do nothing if not omnichannel room', async () => {
const checkMAC = new BeforeSaveCheckMAC();
const empty = await checkMAC.isWithinLimits({
message: createMessage('hey'),
room: createRoom(),
});
expect(empty).to.not.have.value;
});
it('should do nothing if message from visitor', async () => {
const checkMAC = new BeforeSaveCheckMAC();
const empty = await checkMAC.isWithinLimits({
message: createMessage('hey', { token: 'hash' }),
room: createRoom({ t: 'l' }),
});
expect(empty).to.not.have.value;
});
it('should do nothing if it is a system message', async () => {
const checkMAC = new BeforeSaveCheckMAC();
const empty = await checkMAC.isWithinLimits({
message: createMessage('hey', { t: 'uj' }),
room: createRoom({ t: 'l' }),
});
expect(empty).to.not.have.value;
});
it('should throw an error if not within limits', () => {
const checkMAC = new BeforeSaveCheckMAC();
broker.mockServices({
'omnichannel.isWithinMACLimit': async () => false,
});
void expect(
checkMAC.isWithinLimits({
message: createMessage('hey'),
room: createRoom({ t: 'l' }),
}),
).to.be.rejectedWith(MeteorError, 'error-mac-limit-reached');
});
it('should work if MAC within limits', async () => {
const checkMAC = new BeforeSaveCheckMAC();
broker.mockServices({
'omnichannel.isWithinMACLimit': async () => true,
});
const empty = await checkMAC.isWithinLimits({
message: createMessage('hey'),
room: createRoom({ t: 'l' }),
});
expect(empty).to.not.have.value;
});
});

View File

@@ -0,0 +1,734 @@
import type { IMessage } from '@rocket.chat/core-typings';
import { expect } from 'chai';
import { BeforeSaveJumpToMessage } from '../../../../../../server/services/messages/hooks/BeforeSaveJumpToMessage';
const createMessage = (msg?: string, extra: Partial<IMessage> = {}) => ({
_id: 'random',
rid: 'GENERAL',
ts: new Date(),
u: {
_id: 'userId',
username: 'username',
},
_updatedAt: new Date(),
msg: msg as string,
...extra,
});
const createUser = (username?: string) => ({
_id: 'userId',
username,
name: 'name',
language: 'en',
});
const createRoom = (extra: any = {}): any => ({
_id: 'GENERAL',
t: 'c',
u: {
_id: 'userId',
username: 'username',
name: 'name',
},
msgs: 1,
usersCount: 1,
_updatedAt: new Date(),
...extra,
});
const countDeep = (msg: any, deep = 1): number => {
if (!msg) {
return deep - 1;
}
if (Array.isArray(msg?.attachments) && msg.attachments.length > 0) {
return msg.attachments.reduce((count: number, att: any) => Math.max(countDeep(att, deep + 1), count), 0);
}
return deep - 1;
};
describe('Create attachments for message URLs', () => {
it('should return message without attatchment and URLs if no URL provided', async () => {
const jumpToMessage = new BeforeSaveJumpToMessage({
getMessages: async () => [createMessage('linked message', { _id: 'linked' })],
getRooms: async () => [createRoom()],
canAccessRoom: async () => true,
getUserAvatarURL: () => 'url',
});
const message = await jumpToMessage.createAttachmentForMessageURLs({
message: createMessage('hey'),
user: createUser(),
config: {
chainLimit: 10,
siteUrl: 'https://open.rocket.chat',
useRealName: true,
},
});
return expect(message).to.not.have.property('urls');
});
it('should do nothing if URL is not from SiteUrl', async () => {
const jumpToMessage = new BeforeSaveJumpToMessage({
getMessages: async () => [createMessage('linked message', { _id: 'linked' })],
getRooms: async () => [createRoom()],
canAccessRoom: async () => true,
getUserAvatarURL: () => 'url',
});
const message = await jumpToMessage.createAttachmentForMessageURLs({
message: createMessage('hey', {
urls: [
{
url: 'https://google.com',
meta: {},
},
],
}),
user: createUser(),
config: {
chainLimit: 10,
siteUrl: 'https://open.rocket.chat',
useRealName: true,
},
});
expect(message).to.have.property('urls').of.length(1);
expect(message).to.not.have.property('attachments');
});
it('should do nothing if URL is from SiteUrl but not have a query string', async () => {
const jumpToMessage = new BeforeSaveJumpToMessage({
getMessages: async () => [createMessage('linked message', { _id: 'linked' })],
getRooms: async () => [createRoom()],
canAccessRoom: async () => true,
getUserAvatarURL: () => 'url',
});
const message = await jumpToMessage.createAttachmentForMessageURLs({
message: createMessage('hey', {
urls: [
{
url: 'https://open.rocket.chat',
meta: {},
},
],
}),
user: createUser(),
config: {
chainLimit: 10,
siteUrl: 'https://open.rocket.chat',
useRealName: true,
},
});
expect(message).to.have.property('urls').of.length(1);
expect(message).to.not.have.property('attachments');
});
it('should do nothing if URL is from SiteUrl but not have a msgId query string', async () => {
const jumpToMessage = new BeforeSaveJumpToMessage({
getMessages: async () => [createMessage('linked message', { _id: 'linked' })],
getRooms: async () => [createRoom()],
canAccessRoom: async () => true,
getUserAvatarURL: () => 'url',
});
const message = await jumpToMessage.createAttachmentForMessageURLs({
message: createMessage('hey', {
urls: [
{
url: 'https://open.rocket.chat/?token=value',
meta: {},
},
],
}),
user: createUser(),
config: {
chainLimit: 10,
siteUrl: 'https://open.rocket.chat',
useRealName: true,
},
});
expect(message).to.have.property('urls').of.length(1);
expect(message).to.not.have.property('attachments');
});
it('should do nothing if it do not find a msg from the URL', async () => {
const jumpToMessage = new BeforeSaveJumpToMessage({
getMessages: async () => [],
getRooms: async () => [createRoom()],
canAccessRoom: async () => true,
getUserAvatarURL: () => 'url',
});
const message = await jumpToMessage.createAttachmentForMessageURLs({
message: createMessage('hey', {
urls: [
{
url: 'https://open.rocket.chat/?msg=value',
meta: {},
},
],
}),
user: createUser(),
config: {
chainLimit: 10,
siteUrl: 'https://open.rocket.chat',
useRealName: true,
},
});
expect(message).to.have.property('urls').of.length(1);
expect(message).to.not.have.property('attachments');
});
it('should do nothing if it cannot find the room of the message from the URL', async () => {
const jumpToMessage = new BeforeSaveJumpToMessage({
getMessages: async () => [createMessage('linked message', { _id: 'linked' })],
getRooms: async () => [],
canAccessRoom: async () => true,
getUserAvatarURL: () => 'url',
});
const message = await jumpToMessage.createAttachmentForMessageURLs({
message: createMessage('hey', {
urls: [
{
url: 'https://open.rocket.chat/?msg=value',
meta: {},
},
],
}),
user: createUser(),
config: {
chainLimit: 10,
siteUrl: 'https://open.rocket.chat',
useRealName: true,
},
});
expect(message).to.have.property('urls').of.length(1);
expect(message).to.not.have.property('attachments');
});
it('should do nothing if user dont have access to the room of the message from the URL', async () => {
const jumpToMessage = new BeforeSaveJumpToMessage({
getMessages: async () => [createMessage('linked message', { _id: 'linked' })],
getRooms: async () => [createRoom()],
canAccessRoom: async () => false,
getUserAvatarURL: () => 'url',
});
const message = await jumpToMessage.createAttachmentForMessageURLs({
message: createMessage('hey', {
urls: [
{
url: 'https://open.rocket.chat/?msg=value',
meta: {},
},
],
}),
user: createUser(),
config: {
chainLimit: 10,
siteUrl: 'https://open.rocket.chat',
useRealName: true,
},
});
expect(message).to.have.property('urls').of.length(1);
expect(message).to.not.have.property('attachments');
});
it('should not duplicate quote attachment from the message if message_link is the same as the URL', async () => {
const jumpToMessage = new BeforeSaveJumpToMessage({
getMessages: async () => [createMessage('linked message', { _id: 'linked' })],
getRooms: async () => [createRoom()],
canAccessRoom: async () => true,
getUserAvatarURL: () => 'url',
});
const message = await jumpToMessage.createAttachmentForMessageURLs({
message: createMessage('hey', {
urls: [
{
url: 'https://open.rocket.chat/linked?msg=linked',
meta: {},
},
],
attachments: [
{
text: 'old attachment',
author_name: 'username',
author_icon: 'url',
message_link: 'https://open.rocket.chat/linked?msg=linked',
ts: new Date(),
},
],
}),
user: createUser(),
config: {
chainLimit: 10,
siteUrl: 'https://open.rocket.chat',
useRealName: true,
},
});
expect(message).to.have.property('urls').and.to.have.lengthOf(1);
const [url] = message.urls ?? [];
expect(url).to.include({
url: 'https://open.rocket.chat/linked?msg=linked',
ignoreParse: true,
});
expect(message).to.have.property('attachments').and.to.have.lengthOf(1);
const [attachment] = message.attachments ?? [];
expect(attachment).to.have.property('text', 'linked message');
});
it('should remove existing quote attachments provided in the message if they are not in the urls field', async () => {
const jumpToMessage = new BeforeSaveJumpToMessage({
getMessages: async () => [createMessage('linked message', { _id: 'linked' })],
getRooms: async () => [createRoom()],
canAccessRoom: async () => true,
getUserAvatarURL: () => 'url',
});
const message = await jumpToMessage.createAttachmentForMessageURLs({
message: createMessage('hey', {
urls: [],
attachments: [
{
text: 'old attachment',
author_name: 'username',
author_icon: 'url',
message_link: 'https://open.rocket.chat/linked?msg=linked',
ts: new Date(),
},
],
}),
user: createUser(),
config: {
chainLimit: 10,
siteUrl: 'https://open.rocket.chat',
useRealName: true,
},
});
expect(message).to.have.property('attachments').and.to.have.lengthOf(0);
});
it('should not consider attachments with undefined message_link as quotes', async () => {
const jumpToMessage = new BeforeSaveJumpToMessage({
getMessages: async () => [createMessage('linked message', { _id: 'linked' })],
getRooms: async () => [createRoom()],
canAccessRoom: async () => true,
getUserAvatarURL: () => 'url',
});
const message = await jumpToMessage.createAttachmentForMessageURLs({
message: createMessage('hey', {
urls: [],
attachments: [
{
text: 'old attachment',
author_name: 'username',
author_icon: 'url',
message_link: undefined,
ts: new Date(),
},
],
}),
user: createUser(),
config: {
chainLimit: 10,
siteUrl: 'https://open.rocket.chat',
useRealName: true,
},
});
expect(message).to.have.property('attachments').and.to.have.lengthOf(1);
const [attachment] = message.attachments ?? [];
expect(attachment).to.have.property('text', 'old attachment');
});
it('should return an attachment with the message content if a message URL is provided', async () => {
const jumpToMessage = new BeforeSaveJumpToMessage({
getMessages: async () => [createMessage('linked message', { _id: 'linked' })],
getRooms: async () => [createRoom()],
canAccessRoom: async () => true,
getUserAvatarURL: () => 'url',
});
const message = await jumpToMessage.createAttachmentForMessageURLs({
message: createMessage('hey', {
urls: [
{
url: 'https://open.rocket.chat/linked?msg=linked',
meta: {},
},
],
}),
user: createUser(),
config: {
chainLimit: 10,
siteUrl: 'https://open.rocket.chat',
useRealName: true,
},
});
expect(message).to.have.property('urls').and.to.have.lengthOf(1);
const [url] = message.urls ?? [];
expect(url).to.include({
url: 'https://open.rocket.chat/linked?msg=linked',
ignoreParse: true,
});
expect(message).to.have.property('attachments').and.to.have.lengthOf(1);
const [attachment] = message.attachments ?? [];
expect(attachment).to.have.property('text', 'linked message');
});
it('should respect chain limit config', async () => {
const jumpToMessage = new BeforeSaveJumpToMessage({
getMessages: async () => [
createMessage('linked message', {
_id: 'linked',
attachments: [
{
text: 'chained 1',
author_name: 'username',
author_icon: 'url',
ts: new Date(),
message_link: 'https://open.rocket.chat/linked?msg=linkedMsgId',
attachments: [
{
text: 'chained 2',
author_name: 'username',
author_icon: 'url',
message_link: 'https://open.rocket.chat/linked?msg=linkedMsgId',
ts: new Date(),
attachments: [
{
text: 'chained 3',
author_name: 'username',
author_icon: 'url',
message_link: 'https://open.rocket.chat/linked?msg=linkedMsgId',
ts: new Date(),
},
],
},
],
},
],
}),
],
getRooms: async () => [createRoom()],
canAccessRoom: async () => true,
getUserAvatarURL: () => 'url',
});
const message = await jumpToMessage.createAttachmentForMessageURLs({
message: createMessage('hey', {
urls: [
{
url: 'https://open.rocket.chat/linked?msg=linked',
meta: {},
},
],
}),
user: createUser(),
config: {
chainLimit: 3,
siteUrl: 'https://open.rocket.chat',
useRealName: true,
},
});
expect(message).to.have.property('urls').and.to.have.lengthOf(1);
expect(message).to.have.property('attachments').and.to.have.lengthOf(1);
const deep = countDeep(message);
expect(deep).to.be.eq(3);
});
it('should respect chain limit config but keep the file attachments of last chained message', async () => {
const jumpToMessage = new BeforeSaveJumpToMessage({
getMessages: async () => [
createMessage('linked message', {
_id: 'linked',
attachments: [
{
text: 'chained 1',
author_name: 'username',
author_icon: 'url',
ts: new Date(),
message_link: 'https://open.rocket.chat/linked?msg=linkedMsgId',
attachments: [
{
text: 'chained 2',
author_name: 'username',
author_icon: 'url',
message_link: 'https://open.rocket.chat/linked?msg=linkedMsgId',
ts: new Date(),
attachments: [
{
ts: new Date(),
title: 'file.png',
title_link: 'title-link',
title_link_download: true,
image_dimensions: {
width: 480,
height: 269,
},
image_preview: 'image-preview',
image_url: 'image-url',
image_type: 'image/png',
image_size: 68016,
type: 'file',
description: 'chained 3 - file',
descriptionMd: [
{
type: 'PARAGRAPH',
value: [
{
type: 'PLAIN_TEXT',
value: 'chained 3 - file',
},
],
},
],
},
],
},
],
},
],
}),
],
getRooms: async () => [createRoom()],
canAccessRoom: async () => true,
getUserAvatarURL: () => 'url',
});
const message = await jumpToMessage.createAttachmentForMessageURLs({
message: createMessage('hey', {
urls: [
{
url: 'https://open.rocket.chat/linked?msg=linked',
meta: {},
},
],
}),
user: createUser(),
config: {
chainLimit: 3,
siteUrl: 'https://open.rocket.chat',
useRealName: true,
},
});
expect(message).to.have.property('urls').and.to.have.lengthOf(1);
expect(message).to.have.property('attachments').and.to.have.lengthOf(1);
const deep = countDeep(message);
expect(deep).to.be.eq(4, 'The chain limit should be respected, but the file attachments of the last chained message should be kept.');
});
it('should create the attachment if cannot access room but message has a livechat token', async () => {
const jumpToMessage = new BeforeSaveJumpToMessage({
getMessages: async () => [createMessage('linked message', { _id: 'linked' })],
getRooms: async () => [createRoom({ t: 'l', v: { token: 'livechatToken' } })],
canAccessRoom: async () => false,
getUserAvatarURL: () => 'url',
});
const message = await jumpToMessage.createAttachmentForMessageURLs({
message: createMessage('hey', {
urls: [
{
url: 'https://open.rocket.chat/linked?msg=linked',
meta: {},
},
],
token: 'livechatToken',
}),
user: createUser(),
config: {
chainLimit: 10,
siteUrl: 'https://open.rocket.chat',
useRealName: true,
},
});
expect(message).to.have.property('urls').and.to.have.lengthOf(1);
const [url] = message.urls ?? [];
expect(url).to.include({
url: 'https://open.rocket.chat/linked?msg=linked',
ignoreParse: true,
});
expect(message).to.have.property('attachments').and.to.have.lengthOf(1);
const [attachment] = message.attachments ?? [];
expect(attachment).to.have.property('text', 'linked message');
});
it('should do nothing if cannot access room but message has a livechat token but is not from the room does not have a token', async () => {
const jumpToMessage = new BeforeSaveJumpToMessage({
getMessages: async () => [createMessage('linked message', { _id: 'linked' })],
getRooms: async () => [createRoom({ t: 'l' })],
canAccessRoom: async () => false,
getUserAvatarURL: () => 'url',
});
const message = await jumpToMessage.createAttachmentForMessageURLs({
message: createMessage('hey', {
urls: [
{
url: 'https://open.rocket.chat/linked?msg=linked',
meta: {},
},
],
token: 'another-token',
}),
user: createUser(),
config: {
chainLimit: 10,
siteUrl: 'https://open.rocket.chat',
useRealName: true,
},
});
expect(message).to.have.property('urls').and.to.have.lengthOf(1);
expect(message).to.not.have.property('attachments');
});
it('should remove the clean up the attachments of the quoted message property if chain limit < 2', async () => {
const jumpToMessage = new BeforeSaveJumpToMessage({
getMessages: async () => [
createMessage('linked message', {
_id: 'linkedMsgId',
attachments: [
{
text: 'chained 1',
author_name: 'username',
author_icon: 'url',
message_link: 'https://open.rocket.chat/linked?msg=linkedMsgId1',
ts: new Date(),
},
],
}),
],
getRooms: async () => [createRoom()],
canAccessRoom: async () => true,
getUserAvatarURL: () => 'url',
});
const message = await jumpToMessage.createAttachmentForMessageURLs({
message: createMessage('hey', {
urls: [
{
url: 'https://open.rocket.chat/linked?msg=linkedMsgId',
meta: {},
},
],
}),
user: createUser(),
config: {
chainLimit: 1,
siteUrl: 'https://open.rocket.chat',
useRealName: true,
},
});
expect(message).to.have.property('urls').and.to.have.lengthOf(1);
expect(message).to.have.property('attachments').and.to.have.lengthOf(1);
const deep = countDeep(message);
expect(deep).to.be.eq(1);
const [attachment] = message.attachments ?? [];
expect(attachment).to.have.property('attachments').and.to.have.lengthOf(0);
expect(attachment).to.include({
text: 'linked message',
author_name: 'username',
author_icon: 'url',
message_link: 'https://open.rocket.chat/linked?msg=linkedMsgId',
});
});
it('should work for multiple URLs', async () => {
const jumpToMessage = new BeforeSaveJumpToMessage({
getMessages: async () => {
return [
createMessage('first message', {
_id: 'msg1',
}),
createMessage('second message', {
_id: 'msg2',
}),
];
},
getRooms: async () => [createRoom()],
canAccessRoom: async () => true,
getUserAvatarURL: () => 'url',
});
const message = await jumpToMessage.createAttachmentForMessageURLs({
message: createMessage('hey', {
urls: [
{
url: 'https://open.rocket.chat/linked?msg=msg1',
meta: {},
},
{
url: 'https://open.rocket.chat/linked?msg=msg2',
meta: {},
},
],
}),
user: createUser(),
config: {
chainLimit: 1,
siteUrl: 'https://open.rocket.chat',
useRealName: true,
},
});
expect(message).to.have.property('urls').and.to.have.lengthOf(2);
expect(message).to.have.property('attachments').and.to.have.lengthOf(2);
const deep = countDeep(message);
expect(deep).to.be.eq(1);
const [att1, att2] = message.attachments ?? [];
expect(att1).to.include({
text: 'first message',
message_link: 'https://open.rocket.chat/linked?msg=msg1',
});
expect(att2).to.include({
text: 'second message',
message_link: 'https://open.rocket.chat/linked?msg=msg2',
});
});
});

View File

@@ -0,0 +1,110 @@
import { expect } from 'chai';
import { beforeEach } from 'mocha';
import { BeforeSaveMarkdownParser } from '../../../../../../server/services/messages/hooks/BeforeSaveMarkdownParser';
const createMessage = (msg?: string, extra: any = {}) => ({
_id: 'random',
rid: 'GENERAL',
ts: new Date(),
u: {
_id: 'userId',
username: 'username',
},
_updatedAt: new Date(),
msg: msg as string,
...extra,
});
describe('Markdown parser', () => {
beforeEach(() => {
delete process.env.MESSAGE_MAX_PARSE_LENGTH;
});
afterEach(() => {
delete process.env.MESSAGE_MAX_PARSE_LENGTH;
});
it('should do nothing if markdown parser is disabled', async () => {
const markdownParser = new BeforeSaveMarkdownParser(false);
const message = await markdownParser.parseMarkdown({
message: createMessage('hey'),
config: {},
});
expect(message).to.not.have.property('md');
});
it('should do nothing for E2E messages', async () => {
const markdownParser = new BeforeSaveMarkdownParser(true);
const message = await markdownParser.parseMarkdown({
message: createMessage('hey', { t: 'e2e' }),
config: {},
});
expect(message).to.not.have.property('md');
});
it('should skip parsing when msg exceeds MESSAGE_MAX_PARSE_LENGTH', async () => {
process.env.MESSAGE_MAX_PARSE_LENGTH = '10';
const markdownParser = new BeforeSaveMarkdownParser(true);
const message = await markdownParser.parseMarkdown({
message: createMessage('a'.repeat(11)),
config: {},
});
expect(message).to.not.have.property('md');
});
it('should parse normally when msg is within MESSAGE_MAX_PARSE_LENGTH', async () => {
process.env.MESSAGE_MAX_PARSE_LENGTH = '100';
const markdownParser = new BeforeSaveMarkdownParser(true);
const message = await markdownParser.parseMarkdown({
message: createMessage('short msg'),
config: {},
});
expect(message).to.have.property('md');
});
it('should parse normally when MESSAGE_MAX_PARSE_LENGTH is 0', async () => {
process.env.MESSAGE_MAX_PARSE_LENGTH = '0';
const markdownParser = new BeforeSaveMarkdownParser(true);
const message = await markdownParser.parseMarkdown({
message: createMessage('short msg'),
config: {},
});
expect(message).to.have.property('md');
});
it('should parse markdown on the first attachment only', async () => {
const markdownParser = new BeforeSaveMarkdownParser(true);
const message = await markdownParser.parseMarkdown({
message: createMessage('hey', {
attachments: [
{
description: 'hey ho',
},
{
description: 'lets go',
},
],
}),
config: {},
});
expect(message).to.have.property('md');
const [attachment1, attachment2] = message.attachments || [];
expect(attachment1).to.have.property('descriptionMd');
expect(attachment2).to.not.have.property('descriptionMd');
});
});

View File

@@ -0,0 +1,114 @@
import { expect } from 'chai';
import proxyquire from 'proxyquire';
const Authorization = {
hasPermission: async (_uid: string, _permission: string, _room?: string): Promise<boolean> => true,
};
class MeteorError extends Error {}
const { BeforeSavePreventMention } = proxyquire
.noCallThru()
.load('../../../../../../server/services/messages/hooks/BeforeSavePreventMention', {
'@rocket.chat/core-services': {
Authorization,
MeteorError,
},
'../../../lib/i18n': {
i18n: {
t: (s: any) => s,
},
},
});
describe('Prevent mention on messages', () => {
it('should return void if message has no mentions', async () => {
const preventMention = new BeforeSavePreventMention();
return expect(
preventMention.preventMention({
message: { rid: 'GENERAL', msg: 'hey', u: { _id: 'random' } },
user: { _id: 'userId', language: 'en' },
mention: 'all',
permission: 'mention-all',
}),
).to.eventually.be.true;
});
it("should return void if message doesnt't have @all mention", () => {
const preventMention = new BeforeSavePreventMention();
return expect(
preventMention.preventMention({
message: { rid: 'GENERAL', msg: 'hey', mentions: [{ _id: 'here' }], u: { _id: 'random' } },
user: { _id: 'userId', language: 'en' },
mention: 'all',
permission: 'mention-all',
}),
).to.eventually.be.true;
});
it("should return void if message doesnt't have @here mention", () => {
const preventMention = new BeforeSavePreventMention();
return expect(
preventMention.preventMention({
message: { rid: 'GENERAL', msg: 'hey', mentions: [{ _id: 'all' }], u: { _id: 'random' } },
user: { _id: 'userId', language: 'en' },
mention: 'all',
permission: 'mention-here',
}),
).to.eventually.be.true;
});
it('should return true if user has required permission', () => {
const preventMention = new BeforeSavePreventMention();
Authorization.hasPermission = async () => true;
return expect(
preventMention.preventMention({
message: { rid: 'GENERAL', msg: 'hey', mentions: [{ _id: 'all' }], u: { _id: 'random' } },
user: { _id: 'userId', language: 'en' },
mention: 'all',
permission: 'mention-all',
}),
).to.eventually.be.true;
});
it('should return void if user has required permission on room scope', () => {
const preventMention = new BeforeSavePreventMention();
Authorization.hasPermission = async (_uid, _permission, room) => {
if (room) {
return true;
}
return false;
};
return expect(
preventMention.preventMention({
message: { rid: 'GENERAL', msg: 'hey', mentions: [{ _id: 'all' }], u: { _id: 'random' } },
user: { _id: 'userId', language: 'en' },
mention: 'all',
permission: 'mention-all',
}),
).to.eventually.be.true;
});
it("should throw if user doesn't have required permissions", () => {
Authorization.hasPermission = async (_uid, _permission, _room) => false;
const preventMention = new BeforeSavePreventMention();
return expect(
preventMention.preventMention({
message: { rid: 'GENERAL', msg: 'hey', mentions: [{ _id: 'all' }], u: { _id: 'random' } },
user: { _id: 'userId', language: 'en' },
mention: 'all',
permission: 'mention-all',
}),
).to.be.rejectedWith(MeteorError);
});
});

View File

@@ -0,0 +1,92 @@
import { expect } from 'chai';
import { BeforeSaveSpotify } from '../../../../../../server/services/messages/hooks/BeforeSaveSpotify';
const createMessage = (msg?: string, extra: any = {}) => ({
_id: 'random',
rid: 'GENERAL',
ts: new Date(),
u: {
_id: 'userId',
username: 'username',
},
_updatedAt: new Date(),
msg: msg as string,
...extra,
});
describe('Convert Spotify syntax to URLs', () => {
it('should return no URLs if no Spotify syntax provided', async () => {
const spotify = new BeforeSaveSpotify();
const message = await spotify.convertSpotifyLinks({
message: createMessage('hey'),
});
return expect(message).to.not.have.property('urls');
});
it('should return no URLs if an undefined message is provided', async () => {
const spotify = new BeforeSaveSpotify();
const message = await spotify.convertSpotifyLinks({
message: createMessage(),
});
return expect(message).to.not.have.property('urls');
});
it('should not return a Spotify URL if some Spotify syntax is provided within a code block', async () => {
const spotify = new BeforeSaveSpotify();
const message = await spotify.convertSpotifyLinks({
message: createMessage('test\n```\nspotify:track:1q6IK1l4qpYykOaWaLJkWG\n```'),
});
return expect(message).to.not.have.property('urls');
});
it('should not return a Spotify URL if some Spotify syntax is provided within a inline code', async () => {
const spotify = new BeforeSaveSpotify();
const message = await spotify.convertSpotifyLinks({
message: createMessage('test `spotify:track:1q6IK1l4qpYykOaWaLJkWG` ok'),
});
return expect(message).to.not.have.property('urls');
});
it('should return a Spotify URL if some Spotify syntax is provided', async () => {
const spotify = new BeforeSaveSpotify();
const message = await spotify.convertSpotifyLinks({
message: createMessage('spotify:track:1q6IK1l4qpYykOaWaLJkWG'),
});
expect(message).to.have.property('urls').and.to.have.lengthOf(1);
const [url] = message.urls ?? [];
expect(url).to.have.property('url', 'https://open.spotify.com/track/1q6IK1l4qpYykOaWaLJkWG');
expect(url).to.have.property('source', 'spotify:track:1q6IK1l4qpYykOaWaLJkWG');
});
it('should append a Spotify URL when Spotify syntax is provided with already existing URLs', async () => {
const spotify = new BeforeSaveSpotify();
const message = await spotify.convertSpotifyLinks({
message: createMessage('spotify:track:1q6IK1l4qpYykOaWaLJkWG', {
urls: [{ url: 'https://rocket.chat' }],
}),
});
expect(message).to.have.property('urls').and.to.have.lengthOf(2);
const [url1, url2] = message.urls ?? [];
expect(url1).to.have.property('url', 'https://rocket.chat');
expect(url2).to.have.property('url', 'https://open.spotify.com/track/1q6IK1l4qpYykOaWaLJkWG');
expect(url2).to.have.property('source', 'spotify:track:1q6IK1l4qpYykOaWaLJkWG');
});
});