snapshot of https://github.com/RocketChat/Rocket.Chat.git (develop, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 1s
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 1s
This commit is contained in:
264
apps/meteor/tests/unit/server/lib/rooms/banUserFromRoom.spec.ts
Normal file
264
apps/meteor/tests/unit/server/lib/rooms/banUserFromRoom.spec.ts
Normal file
@@ -0,0 +1,264 @@
|
||||
import { expect } from 'chai';
|
||||
import { beforeEach, describe, it } from 'mocha';
|
||||
import p from 'proxyquire';
|
||||
import sinon from 'sinon';
|
||||
|
||||
const modelsMock = {
|
||||
Rooms: {
|
||||
findOneById: sinon.stub(),
|
||||
incUsersCountById: sinon.stub(),
|
||||
},
|
||||
Subscriptions: {
|
||||
findOneByRoomIdAndUserId: sinon.stub(),
|
||||
banByRoomIdAndUserId: sinon.stub(),
|
||||
},
|
||||
Users: {
|
||||
removeRoomByUserId: sinon.stub(),
|
||||
},
|
||||
};
|
||||
|
||||
const messageMock = {
|
||||
saveSystemMessage: sinon.stub(),
|
||||
};
|
||||
|
||||
const removeUserFromRolesAsyncMock = sinon.stub();
|
||||
const notifyOnRoomChangedByIdMock = sinon.stub();
|
||||
const notifyOnSubscriptionChangedMock = sinon.stub();
|
||||
const afterBanFromRoomCallbackMock = { run: sinon.stub() };
|
||||
|
||||
const meteorErrorMock = class extends Error {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
}
|
||||
};
|
||||
|
||||
const { performUserBan, banUserFromRoom } = p.noCallThru().load('../../../../../server/lib/rooms/banUserFromRoom.ts', {
|
||||
'@rocket.chat/models': modelsMock,
|
||||
'@rocket.chat/core-services': { Message: messageMock },
|
||||
'meteor/meteor': { Meteor: { Error: meteorErrorMock } },
|
||||
'../callbacks/afterBanFromRoomCallback': { afterBanFromRoomCallback: afterBanFromRoomCallbackMock },
|
||||
'../roles/removeUserFromRoles': { removeUserFromRolesAsync: removeUserFromRolesAsyncMock },
|
||||
'../notifyListener': {
|
||||
notifyOnRoomChangedById: notifyOnRoomChangedByIdMock,
|
||||
notifyOnSubscriptionChanged: notifyOnSubscriptionChangedMock,
|
||||
},
|
||||
});
|
||||
|
||||
describe('banUserFromRoom', () => {
|
||||
const mockByUser = { _id: 'admin1', username: 'admin' };
|
||||
|
||||
beforeEach(() => {
|
||||
modelsMock.Rooms.findOneById.reset();
|
||||
modelsMock.Rooms.incUsersCountById.reset();
|
||||
modelsMock.Subscriptions.findOneByRoomIdAndUserId.reset();
|
||||
modelsMock.Subscriptions.banByRoomIdAndUserId.reset();
|
||||
modelsMock.Users.removeRoomByUserId.reset();
|
||||
messageMock.saveSystemMessage.reset();
|
||||
removeUserFromRolesAsyncMock.reset();
|
||||
notifyOnRoomChangedByIdMock.reset();
|
||||
notifyOnSubscriptionChangedMock.reset();
|
||||
afterBanFromRoomCallbackMock.run.reset();
|
||||
});
|
||||
|
||||
describe('performUserBan', () => {
|
||||
it('should return early if no subscription exists', async () => {
|
||||
modelsMock.Subscriptions.findOneByRoomIdAndUserId.resolves(null);
|
||||
|
||||
const room = { _id: 'room1', t: 'c' };
|
||||
const user = { _id: 'user1', username: 'testuser' };
|
||||
|
||||
await performUserBan(room, user, mockByUser);
|
||||
|
||||
expect(modelsMock.Subscriptions.banByRoomIdAndUserId.called).to.be.false;
|
||||
expect(modelsMock.Users.removeRoomByUserId.called).to.be.false;
|
||||
});
|
||||
|
||||
it('should throw if user has no username', async () => {
|
||||
modelsMock.Subscriptions.findOneByRoomIdAndUserId.resolves({ _id: 'sub1' });
|
||||
|
||||
const room = { _id: 'room1', t: 'c' };
|
||||
const user = { _id: 'user1' }; // no username
|
||||
|
||||
await expect(performUserBan(room, user, mockByUser)).to.be.rejectedWith('User must have a username to be banned from the room');
|
||||
});
|
||||
|
||||
it('should return early if subscription is already BANNED', async () => {
|
||||
modelsMock.Subscriptions.findOneByRoomIdAndUserId.resolves({ _id: 'sub1', status: 'BANNED' });
|
||||
|
||||
const room = { _id: 'room1', t: 'c' };
|
||||
const user = { _id: 'user1', username: 'testuser' };
|
||||
|
||||
await performUserBan(room, user, mockByUser);
|
||||
|
||||
expect(modelsMock.Subscriptions.banByRoomIdAndUserId.called).to.be.false;
|
||||
});
|
||||
|
||||
it('should ban the user subscription', async () => {
|
||||
modelsMock.Subscriptions.findOneByRoomIdAndUserId.resolves({ _id: 'sub1' });
|
||||
modelsMock.Subscriptions.banByRoomIdAndUserId.resolves();
|
||||
modelsMock.Users.removeRoomByUserId.resolves();
|
||||
modelsMock.Rooms.incUsersCountById.resolves();
|
||||
removeUserFromRolesAsyncMock.resolves();
|
||||
messageMock.saveSystemMessage.resolves();
|
||||
|
||||
const room = { _id: 'room1', t: 'c' };
|
||||
const user = { _id: 'user1', username: 'testuser' };
|
||||
|
||||
await performUserBan(room, user, mockByUser);
|
||||
|
||||
expect(modelsMock.Subscriptions.banByRoomIdAndUserId.calledWith('room1', 'user1')).to.be.true;
|
||||
});
|
||||
|
||||
it('should remove the room from user __rooms array', async () => {
|
||||
modelsMock.Subscriptions.findOneByRoomIdAndUserId.resolves({ _id: 'sub1' });
|
||||
modelsMock.Subscriptions.banByRoomIdAndUserId.resolves();
|
||||
modelsMock.Users.removeRoomByUserId.resolves();
|
||||
modelsMock.Rooms.incUsersCountById.resolves();
|
||||
removeUserFromRolesAsyncMock.resolves();
|
||||
messageMock.saveSystemMessage.resolves();
|
||||
|
||||
const room = { _id: 'room1', t: 'c' };
|
||||
const user = { _id: 'user1', username: 'testuser' };
|
||||
|
||||
await performUserBan(room, user, mockByUser);
|
||||
|
||||
expect(modelsMock.Users.removeRoomByUserId.calledWith('user1', 'room1')).to.be.true;
|
||||
});
|
||||
|
||||
it('should decrement the room user count', async () => {
|
||||
modelsMock.Subscriptions.findOneByRoomIdAndUserId.resolves({ _id: 'sub1' });
|
||||
modelsMock.Subscriptions.banByRoomIdAndUserId.resolves();
|
||||
modelsMock.Users.removeRoomByUserId.resolves();
|
||||
modelsMock.Rooms.incUsersCountById.resolves();
|
||||
removeUserFromRolesAsyncMock.resolves();
|
||||
messageMock.saveSystemMessage.resolves();
|
||||
|
||||
const room = { _id: 'room1', t: 'c' };
|
||||
const user = { _id: 'user1', username: 'testuser' };
|
||||
|
||||
await performUserBan(room, user, mockByUser);
|
||||
|
||||
expect(modelsMock.Rooms.incUsersCountById.calledWith('room1', -1)).to.be.true;
|
||||
});
|
||||
|
||||
it('should remove room-scoped roles for channels', async () => {
|
||||
modelsMock.Subscriptions.findOneByRoomIdAndUserId.resolves({ _id: 'sub1' });
|
||||
modelsMock.Subscriptions.banByRoomIdAndUserId.resolves();
|
||||
modelsMock.Users.removeRoomByUserId.resolves();
|
||||
modelsMock.Rooms.incUsersCountById.resolves();
|
||||
removeUserFromRolesAsyncMock.resolves();
|
||||
messageMock.saveSystemMessage.resolves();
|
||||
|
||||
const room = { _id: 'room1', t: 'c' };
|
||||
const user = { _id: 'user1', username: 'testuser' };
|
||||
|
||||
await performUserBan(room, user, mockByUser);
|
||||
|
||||
expect(removeUserFromRolesAsyncMock.calledWith('user1', ['moderator', 'owner', 'leader'], 'room1')).to.be.true;
|
||||
});
|
||||
|
||||
it('should remove room-scoped roles for private rooms', async () => {
|
||||
modelsMock.Subscriptions.findOneByRoomIdAndUserId.resolves({ _id: 'sub1' });
|
||||
modelsMock.Subscriptions.banByRoomIdAndUserId.resolves();
|
||||
modelsMock.Users.removeRoomByUserId.resolves();
|
||||
modelsMock.Rooms.incUsersCountById.resolves();
|
||||
removeUserFromRolesAsyncMock.resolves();
|
||||
messageMock.saveSystemMessage.resolves();
|
||||
|
||||
const room = { _id: 'room1', t: 'p' };
|
||||
const user = { _id: 'user1', username: 'testuser' };
|
||||
|
||||
await performUserBan(room, user, mockByUser);
|
||||
|
||||
expect(removeUserFromRolesAsyncMock.calledWith('user1', ['moderator', 'owner', 'leader'], 'room1')).to.be.true;
|
||||
});
|
||||
|
||||
it('should NOT remove room-scoped roles for DMs', async () => {
|
||||
modelsMock.Subscriptions.findOneByRoomIdAndUserId.resolves({ _id: 'sub1' });
|
||||
modelsMock.Subscriptions.banByRoomIdAndUserId.resolves();
|
||||
modelsMock.Users.removeRoomByUserId.resolves();
|
||||
modelsMock.Rooms.incUsersCountById.resolves();
|
||||
messageMock.saveSystemMessage.resolves();
|
||||
|
||||
const room = { _id: 'room1', t: 'd' };
|
||||
const user = { _id: 'user1', username: 'testuser' };
|
||||
|
||||
await performUserBan(room, user, mockByUser);
|
||||
|
||||
expect(removeUserFromRolesAsyncMock.called).to.be.false;
|
||||
});
|
||||
|
||||
it('should save system message including who banned', async () => {
|
||||
modelsMock.Subscriptions.findOneByRoomIdAndUserId.resolves({ _id: 'sub1' });
|
||||
modelsMock.Subscriptions.banByRoomIdAndUserId.resolves();
|
||||
modelsMock.Users.removeRoomByUserId.resolves();
|
||||
modelsMock.Rooms.incUsersCountById.resolves();
|
||||
removeUserFromRolesAsyncMock.resolves();
|
||||
messageMock.saveSystemMessage.resolves();
|
||||
|
||||
const room = { _id: 'room1', t: 'c' };
|
||||
const user = { _id: 'user1', username: 'testuser' };
|
||||
const byUser = { _id: 'moderator2', username: 'mod' };
|
||||
|
||||
await performUserBan(room, user, byUser);
|
||||
|
||||
expect(messageMock.saveSystemMessage.calledOnce).to.be.true;
|
||||
const { args } = messageMock.saveSystemMessage.firstCall;
|
||||
expect(args[0]).to.equal('user-banned');
|
||||
expect(args[1]).to.equal('room1');
|
||||
expect(args[2]).to.equal('testuser');
|
||||
expect(args[4]).to.deep.include({ u: byUser });
|
||||
});
|
||||
|
||||
it('should notify subscription as removed so client drops the stream', async () => {
|
||||
const subscription = { _id: 'sub1', rid: 'room1', u: { _id: 'user1' } };
|
||||
modelsMock.Subscriptions.findOneByRoomIdAndUserId.resolves(subscription);
|
||||
modelsMock.Subscriptions.banByRoomIdAndUserId.resolves();
|
||||
modelsMock.Users.removeRoomByUserId.resolves();
|
||||
modelsMock.Rooms.incUsersCountById.resolves();
|
||||
removeUserFromRolesAsyncMock.resolves();
|
||||
messageMock.saveSystemMessage.resolves();
|
||||
|
||||
const room = { _id: 'room1', t: 'c' };
|
||||
const user = { _id: 'user1', username: 'testuser' };
|
||||
|
||||
await performUserBan(room, user, mockByUser);
|
||||
|
||||
expect(notifyOnSubscriptionChangedMock.calledWith(subscription, 'removed')).to.be.true;
|
||||
expect(notifyOnRoomChangedByIdMock.calledWith('room1')).to.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
describe('banUserFromRoom', () => {
|
||||
it('should throw if room does not exist', async () => {
|
||||
modelsMock.Rooms.findOneById.resolves(null);
|
||||
|
||||
const user = { _id: 'user1', username: 'testuser' };
|
||||
|
||||
await expect(banUserFromRoom('room1', user, mockByUser)).to.be.rejectedWith('error-invalid-room');
|
||||
});
|
||||
|
||||
it('should call performUserBan with correct arguments', async () => {
|
||||
const room = { _id: 'room1', t: 'c' };
|
||||
modelsMock.Rooms.findOneById.resolves(room);
|
||||
modelsMock.Subscriptions.findOneByRoomIdAndUserId.resolves({ _id: 'sub1' });
|
||||
modelsMock.Subscriptions.banByRoomIdAndUserId.resolves();
|
||||
modelsMock.Users.removeRoomByUserId.resolves();
|
||||
modelsMock.Rooms.incUsersCountById.resolves();
|
||||
removeUserFromRolesAsyncMock.resolves();
|
||||
messageMock.saveSystemMessage.resolves();
|
||||
|
||||
const user = { _id: 'user1', username: 'testuser' };
|
||||
|
||||
await banUserFromRoom('room1', user, mockByUser);
|
||||
|
||||
expect(modelsMock.Subscriptions.banByRoomIdAndUserId.calledWith('room1', 'user1')).to.be.true;
|
||||
expect(afterBanFromRoomCallbackMock.run.calledOnce).to.be.true;
|
||||
expect(afterBanFromRoomCallbackMock.run.firstCall.args[0]).to.deep.include({
|
||||
bannedUser: user,
|
||||
userWhoBanned: mockByUser,
|
||||
});
|
||||
expect(afterBanFromRoomCallbackMock.run.firstCall.args[1]).to.equal(room);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,107 @@
|
||||
import { expect } from 'chai';
|
||||
import { describe, it, beforeEach } from 'mocha';
|
||||
import proxyquire from 'proxyquire';
|
||||
import Sinon from 'sinon';
|
||||
|
||||
const RoomsStub = {
|
||||
findByTypeAndNameOrId: Sinon.stub(),
|
||||
findOneDirectRoomContainingAllUserIDs: Sinon.stub(),
|
||||
};
|
||||
|
||||
const UsersStub = {
|
||||
findOneById: Sinon.stub(),
|
||||
findUsersByUsernames: Sinon.stub(),
|
||||
};
|
||||
|
||||
const { findDirectRoomByIdentifier } = proxyquire.noCallThru().load('../../../../../server/lib/rooms/findDirectRoomByIdentifier.ts', {
|
||||
'@rocket.chat/models': { Rooms: RoomsStub, Users: UsersStub },
|
||||
});
|
||||
|
||||
const cursorOf = (docs: unknown[]) => ({ toArray: async () => docs });
|
||||
|
||||
describe('findDirectRoomByIdentifier', () => {
|
||||
beforeEach(() => {
|
||||
RoomsStub.findByTypeAndNameOrId.reset();
|
||||
RoomsStub.findOneDirectRoomContainingAllUserIDs.reset();
|
||||
UsersStub.findOneById.reset();
|
||||
UsersStub.findUsersByUsernames.reset();
|
||||
|
||||
RoomsStub.findByTypeAndNameOrId.resolves(null);
|
||||
UsersStub.findOneById.resolves({ _id: 'me', username: 'me' });
|
||||
});
|
||||
|
||||
it('should return the room when the identifier is a room id', async () => {
|
||||
const room = { _id: 'rid1', t: 'd' };
|
||||
RoomsStub.findByTypeAndNameOrId.resolves(room);
|
||||
|
||||
expect(await findDirectRoomByIdentifier('rid1', 'me')).to.equal(room);
|
||||
expect(UsersStub.findUsersByUsernames.called).to.be.false;
|
||||
});
|
||||
|
||||
it('should resolve a two-person DM by the other username, over sorted uids', async () => {
|
||||
const room = { _id: 'rid1', t: 'd' };
|
||||
UsersStub.findUsersByUsernames.returns(cursorOf([{ _id: 'zeta' }, { _id: 'alpha' }]));
|
||||
RoomsStub.findOneDirectRoomContainingAllUserIDs.resolves(room);
|
||||
|
||||
expect(await findDirectRoomByIdentifier('alice', 'me')).to.equal(room);
|
||||
expect(RoomsStub.findOneDirectRoomContainingAllUserIDs.calledWith(['alpha', 'zeta'])).to.be.true;
|
||||
});
|
||||
|
||||
it('should include the caller in the member set exactly once for a self-DM', async () => {
|
||||
UsersStub.findUsersByUsernames.returns(cursorOf([{ _id: 'me' }]));
|
||||
RoomsStub.findOneDirectRoomContainingAllUserIDs.resolves({ _id: 'self', t: 'd' });
|
||||
|
||||
await findDirectRoomByIdentifier('me', 'me');
|
||||
|
||||
expect(UsersStub.findUsersByUsernames.firstCall.args[0]).to.deep.equal(['me']);
|
||||
expect(RoomsStub.findOneDirectRoomContainingAllUserIDs.calledWith(['me'])).to.be.true;
|
||||
});
|
||||
|
||||
it('should resolve a group DM from a comma-separated identifier', async () => {
|
||||
UsersStub.findUsersByUsernames.returns(cursorOf([{ _id: 'me' }, { _id: 'a' }, { _id: 'b' }]));
|
||||
RoomsStub.findOneDirectRoomContainingAllUserIDs.resolves({ _id: 'group', t: 'd' });
|
||||
|
||||
await findDirectRoomByIdentifier('a,b', 'me');
|
||||
|
||||
expect(UsersStub.findUsersByUsernames.firstCall.args[0]).to.deep.equal(['me', 'a', 'b']);
|
||||
expect(RoomsStub.findOneDirectRoomContainingAllUserIDs.calledWith(['a', 'b', 'me'])).to.be.true;
|
||||
});
|
||||
|
||||
it('should not look the identifier up as a room id when it is a username list', async () => {
|
||||
UsersStub.findUsersByUsernames.returns(cursorOf([{ _id: 'me' }, { _id: 'a' }, { _id: 'b' }]));
|
||||
RoomsStub.findOneDirectRoomContainingAllUserIDs.resolves({ _id: 'group', t: 'd' });
|
||||
|
||||
await findDirectRoomByIdentifier('a,b', 'me');
|
||||
|
||||
expect(RoomsStub.findByTypeAndNameOrId.called).to.be.false;
|
||||
});
|
||||
|
||||
it('should trim whitespace around each username in the list', async () => {
|
||||
UsersStub.findUsersByUsernames.returns(cursorOf([{ _id: 'me' }, { _id: 'a' }, { _id: 'b' }]));
|
||||
RoomsStub.findOneDirectRoomContainingAllUserIDs.resolves({ _id: 'group', t: 'd' });
|
||||
|
||||
await findDirectRoomByIdentifier('a, b', 'me');
|
||||
|
||||
expect(UsersStub.findUsersByUsernames.firstCall.args[0]).to.deep.equal(['me', 'a', 'b']);
|
||||
});
|
||||
|
||||
it('should return null when any username does not resolve to a user', async () => {
|
||||
UsersStub.findUsersByUsernames.returns(cursorOf([{ _id: 'me' }]));
|
||||
|
||||
expect(await findDirectRoomByIdentifier('ghost', 'me')).to.be.null;
|
||||
expect(RoomsStub.findOneDirectRoomContainingAllUserIDs.called).to.be.false;
|
||||
});
|
||||
|
||||
it('should return null when no direct room contains that member set', async () => {
|
||||
UsersStub.findUsersByUsernames.returns(cursorOf([{ _id: 'me' }, { _id: 'alice' }]));
|
||||
RoomsStub.findOneDirectRoomContainingAllUserIDs.resolves(null);
|
||||
|
||||
expect(await findDirectRoomByIdentifier('alice', 'me')).to.be.null;
|
||||
});
|
||||
|
||||
it('should return null when the caller has no username', async () => {
|
||||
UsersStub.findOneById.resolves({ _id: 'me' });
|
||||
|
||||
expect(await findDirectRoomByIdentifier('alice', 'me')).to.be.null;
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,518 @@
|
||||
import { expect } from 'chai';
|
||||
import { describe, it, beforeEach, afterEach } from 'mocha';
|
||||
import proxyquire from 'proxyquire';
|
||||
import Sinon from 'sinon';
|
||||
|
||||
type RoomType = 'c' | 'p' | 'd' | 'l';
|
||||
|
||||
interface IUser {
|
||||
_id: string;
|
||||
username: string;
|
||||
federated?: boolean;
|
||||
federation?: any;
|
||||
}
|
||||
|
||||
interface IRoom {
|
||||
_id: string;
|
||||
t: RoomType;
|
||||
name?: string;
|
||||
}
|
||||
|
||||
type GetRoomByNameOrIdWithOptionToJoinFn = (params: {
|
||||
user: Pick<IUser, '_id' | 'username' | 'federated' | 'federation'>;
|
||||
nameOrId: string;
|
||||
type?: RoomType;
|
||||
tryDirectByUserIdOnly?: boolean;
|
||||
joinChannel?: boolean;
|
||||
errorOnEmpty?: boolean;
|
||||
}) => Promise<IRoom | null>;
|
||||
|
||||
const RoomsStub = {
|
||||
findOneByIdOrName: Sinon.stub(),
|
||||
findOneDirectRoomContainingAllUserIDs: Sinon.stub(),
|
||||
findOneById: Sinon.stub(),
|
||||
};
|
||||
|
||||
const SubscriptionsStub = {
|
||||
findOneByRoomIdAndUserId: Sinon.stub(),
|
||||
};
|
||||
|
||||
const UsersStub = {
|
||||
findOneById: Sinon.stub(),
|
||||
findOne: Sinon.stub(),
|
||||
};
|
||||
|
||||
const RoomServiceStub = {
|
||||
join: Sinon.stub().resolves(),
|
||||
};
|
||||
|
||||
const MeteorStub = {
|
||||
Meteor: {
|
||||
Error: Sinon.stub().callsFake(function (this: any, code: string) {
|
||||
this.error = code;
|
||||
this.errorType = 'Meteor.Error';
|
||||
} as any),
|
||||
},
|
||||
};
|
||||
|
||||
const createDirectMessageStub = Sinon.stub().resolves({ rid: 'newDirectRoomId' });
|
||||
|
||||
const isObjectMock = {
|
||||
isObject(obj: unknown) {
|
||||
return obj !== null && typeof obj === 'object';
|
||||
},
|
||||
};
|
||||
|
||||
const {
|
||||
getRoomByNameOrIdWithOptionToJoin,
|
||||
}: {
|
||||
getRoomByNameOrIdWithOptionToJoin: GetRoomByNameOrIdWithOptionToJoinFn;
|
||||
} = proxyquire.noCallThru().load('../../../../../server/lib/rooms/getRoomByNameOrIdWithOptionToJoin.ts', {
|
||||
'@rocket.chat/models': {
|
||||
Rooms: RoomsStub,
|
||||
Subscriptions: SubscriptionsStub,
|
||||
Users: UsersStub,
|
||||
},
|
||||
'@rocket.chat/core-services': {
|
||||
Room: RoomServiceStub,
|
||||
},
|
||||
'../../../lib/utils/isObject': isObjectMock,
|
||||
'../../meteor-methods/messages/createDirectMessage': {
|
||||
createDirectMessage: createDirectMessageStub,
|
||||
},
|
||||
'meteor/meteor': MeteorStub,
|
||||
}) as any;
|
||||
|
||||
describe('getRoomByNameOrIdWithOptionToJoin', () => {
|
||||
const baseUser: IUser = {
|
||||
_id: 'userId',
|
||||
username: 'user',
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
RoomsStub.findOneByIdOrName.reset();
|
||||
RoomsStub.findOneDirectRoomContainingAllUserIDs.reset();
|
||||
RoomsStub.findOneById.reset();
|
||||
|
||||
SubscriptionsStub.findOneByRoomIdAndUserId.reset();
|
||||
|
||||
UsersStub.findOneById.reset();
|
||||
UsersStub.findOne.reset();
|
||||
|
||||
RoomServiceStub.join.reset();
|
||||
RoomServiceStub.join.resolves();
|
||||
|
||||
MeteorStub.Meteor.Error.resetBehavior();
|
||||
MeteorStub.Meteor.Error.resetHistory();
|
||||
MeteorStub.Meteor.Error.callsFake(function (this: any, code: string) {
|
||||
this.error = code;
|
||||
this.errorType = 'Meteor.Error';
|
||||
} as any);
|
||||
|
||||
createDirectMessageStub.reset();
|
||||
createDirectMessageStub.resolves({ rid: 'newDirectRoomId' });
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
Sinon.restore();
|
||||
});
|
||||
|
||||
describe('channel / group resolution', () => {
|
||||
it('should strip leading # and lookup by id or name', async () => {
|
||||
const room: IRoom = { _id: 'room1', t: 'c', name: 'general' };
|
||||
RoomsStub.findOneByIdOrName.resolves(room);
|
||||
SubscriptionsStub.findOneByRoomIdAndUserId.resolves(null);
|
||||
|
||||
const result = await getRoomByNameOrIdWithOptionToJoin({
|
||||
user: baseUser,
|
||||
nameOrId: '#general',
|
||||
type: undefined,
|
||||
tryDirectByUserIdOnly: false,
|
||||
joinChannel: false,
|
||||
errorOnEmpty: true,
|
||||
});
|
||||
|
||||
expect(RoomsStub.findOneByIdOrName.calledOnceWithExactly('general')).to.equal(true);
|
||||
expect(result).to.equal(room);
|
||||
});
|
||||
|
||||
it('should treat name without # as channel/group and call findOneByIdOrName', async () => {
|
||||
const room: IRoom = { _id: 'room2', t: 'c', name: 'random' };
|
||||
RoomsStub.findOneByIdOrName.resolves(room);
|
||||
SubscriptionsStub.findOneByRoomIdAndUserId.resolves(null);
|
||||
|
||||
const result = await getRoomByNameOrIdWithOptionToJoin({
|
||||
user: baseUser,
|
||||
nameOrId: 'random',
|
||||
type: undefined,
|
||||
tryDirectByUserIdOnly: false,
|
||||
joinChannel: false,
|
||||
errorOnEmpty: true,
|
||||
});
|
||||
|
||||
expect(RoomsStub.findOneByIdOrName.calledOnceWithExactly('random')).to.equal(true);
|
||||
expect(result).to.equal(room);
|
||||
});
|
||||
|
||||
it('should throw Meteor.Error("invalid-channel") when channel not found and errorOnEmpty=true', async () => {
|
||||
RoomsStub.findOneByIdOrName.resolves(null);
|
||||
|
||||
let caught: any;
|
||||
try {
|
||||
await getRoomByNameOrIdWithOptionToJoin({
|
||||
user: baseUser,
|
||||
nameOrId: 'nonexistent',
|
||||
type: undefined,
|
||||
tryDirectByUserIdOnly: false,
|
||||
joinChannel: false,
|
||||
errorOnEmpty: true,
|
||||
});
|
||||
} catch (e) {
|
||||
caught = e;
|
||||
}
|
||||
|
||||
expect(caught).to.be.an('object');
|
||||
expect(caught.error).to.equal('invalid-channel');
|
||||
});
|
||||
|
||||
it('should return null when channel not found and errorOnEmpty=false', async () => {
|
||||
RoomsStub.findOneByIdOrName.resolves(null);
|
||||
|
||||
const result = await getRoomByNameOrIdWithOptionToJoin({
|
||||
user: baseUser,
|
||||
nameOrId: 'nonexistent',
|
||||
type: undefined,
|
||||
tryDirectByUserIdOnly: false,
|
||||
joinChannel: false,
|
||||
errorOnEmpty: false,
|
||||
});
|
||||
|
||||
expect(result).to.equal(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('type filtering', () => {
|
||||
it('should return room when type matches', async () => {
|
||||
const room: IRoom = { _id: 'room3', t: 'c', name: 'dev' };
|
||||
RoomsStub.findOneByIdOrName.resolves(room);
|
||||
SubscriptionsStub.findOneByRoomIdAndUserId.resolves(null);
|
||||
|
||||
const result = await getRoomByNameOrIdWithOptionToJoin({
|
||||
user: baseUser,
|
||||
nameOrId: 'dev',
|
||||
type: 'c',
|
||||
tryDirectByUserIdOnly: false,
|
||||
joinChannel: false,
|
||||
errorOnEmpty: true,
|
||||
});
|
||||
|
||||
expect(result).to.equal(room);
|
||||
});
|
||||
|
||||
it('should throw Meteor.Error when type does not match and errorOnEmpty=true', async () => {
|
||||
const room: IRoom = { _id: 'room4', t: 'p', name: 'private' };
|
||||
RoomsStub.findOneByIdOrName.resolves(room);
|
||||
SubscriptionsStub.findOneByRoomIdAndUserId.resolves(null);
|
||||
|
||||
let caught: any;
|
||||
try {
|
||||
await getRoomByNameOrIdWithOptionToJoin({
|
||||
user: baseUser,
|
||||
nameOrId: 'private',
|
||||
type: 'c',
|
||||
tryDirectByUserIdOnly: false,
|
||||
joinChannel: false,
|
||||
errorOnEmpty: true,
|
||||
});
|
||||
} catch (e) {
|
||||
caught = e;
|
||||
}
|
||||
|
||||
expect(caught).to.be.an('object');
|
||||
expect(caught.error).to.equal('invalid-channel');
|
||||
});
|
||||
|
||||
it('should return null when type does not match and errorOnEmpty=false', async () => {
|
||||
const room: IRoom = { _id: 'room5', t: 'p', name: 'private' };
|
||||
RoomsStub.findOneByIdOrName.resolves(room);
|
||||
SubscriptionsStub.findOneByRoomIdAndUserId.resolves(null);
|
||||
|
||||
const result = await getRoomByNameOrIdWithOptionToJoin({
|
||||
user: baseUser,
|
||||
nameOrId: 'private',
|
||||
type: 'c',
|
||||
tryDirectByUserIdOnly: false,
|
||||
joinChannel: false,
|
||||
errorOnEmpty: false,
|
||||
});
|
||||
|
||||
expect(result).to.equal(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('joining channel behaviour', () => {
|
||||
it('should call Room.join when room is channel, joinChannel=true, and user not subscribed', async () => {
|
||||
const room: IRoom = { _id: 'room6', t: 'c', name: 'joinable' };
|
||||
RoomsStub.findOneByIdOrName.resolves(room);
|
||||
SubscriptionsStub.findOneByRoomIdAndUserId.resolves(null);
|
||||
|
||||
const result = await getRoomByNameOrIdWithOptionToJoin({
|
||||
user: baseUser,
|
||||
nameOrId: 'joinable',
|
||||
type: 'c',
|
||||
tryDirectByUserIdOnly: false,
|
||||
joinChannel: true,
|
||||
errorOnEmpty: true,
|
||||
});
|
||||
|
||||
expect(SubscriptionsStub.findOneByRoomIdAndUserId.calledOnceWithExactly('room6', baseUser._id, { projection: { _id: 1 } })).to.equal(
|
||||
true,
|
||||
);
|
||||
expect(
|
||||
RoomServiceStub.join.calledOnceWithExactly({
|
||||
room,
|
||||
user: baseUser,
|
||||
}),
|
||||
).to.equal(true);
|
||||
expect(result).to.equal(room);
|
||||
});
|
||||
|
||||
it('should not call Room.join when joinChannel=false', async () => {
|
||||
const room: IRoom = { _id: 'room7', t: 'c', name: 'nojoin' };
|
||||
RoomsStub.findOneByIdOrName.resolves(room);
|
||||
|
||||
const result = await getRoomByNameOrIdWithOptionToJoin({
|
||||
user: baseUser,
|
||||
nameOrId: 'nojoin',
|
||||
type: 'c',
|
||||
tryDirectByUserIdOnly: false,
|
||||
joinChannel: false,
|
||||
errorOnEmpty: true,
|
||||
});
|
||||
|
||||
expect(SubscriptionsStub.findOneByRoomIdAndUserId.notCalled).to.equal(true);
|
||||
expect(RoomServiceStub.join.notCalled).to.equal(true);
|
||||
expect(result).to.equal(room);
|
||||
});
|
||||
|
||||
it('should not call Room.join when room type is not channel', async () => {
|
||||
const room: IRoom = { _id: 'room8', t: 'p', name: 'private' };
|
||||
RoomsStub.findOneByIdOrName.resolves(room);
|
||||
|
||||
const result = await getRoomByNameOrIdWithOptionToJoin({
|
||||
user: baseUser,
|
||||
nameOrId: 'private',
|
||||
type: 'p',
|
||||
tryDirectByUserIdOnly: false,
|
||||
joinChannel: true,
|
||||
errorOnEmpty: true,
|
||||
});
|
||||
|
||||
expect(SubscriptionsStub.findOneByRoomIdAndUserId.notCalled).to.equal(true);
|
||||
expect(RoomServiceStub.join.notCalled).to.equal(true);
|
||||
expect(result).to.equal(room);
|
||||
});
|
||||
|
||||
it('should not call Room.join when user already subscribed', async () => {
|
||||
const room: IRoom = { _id: 'room9', t: 'c', name: 'joined' };
|
||||
RoomsStub.findOneByIdOrName.resolves(room);
|
||||
SubscriptionsStub.findOneByRoomIdAndUserId.resolves({ _id: 'subId' });
|
||||
|
||||
const result = await getRoomByNameOrIdWithOptionToJoin({
|
||||
user: baseUser,
|
||||
nameOrId: 'joined',
|
||||
type: 'c',
|
||||
tryDirectByUserIdOnly: false,
|
||||
joinChannel: true,
|
||||
errorOnEmpty: true,
|
||||
});
|
||||
|
||||
expect(SubscriptionsStub.findOneByRoomIdAndUserId.calledOnceWithExactly('room9', baseUser._id, { projection: { _id: 1 } })).to.equal(
|
||||
true,
|
||||
);
|
||||
expect(RoomServiceStub.join.notCalled).to.equal(true);
|
||||
expect(result).to.equal(room);
|
||||
});
|
||||
});
|
||||
|
||||
describe('direct message resolution by username', () => {
|
||||
it('should find direct room with existing roomUser and existing DM room', async () => {
|
||||
const otherUser: IUser = { _id: 'otherId', username: 'other' };
|
||||
|
||||
UsersStub.findOne.resolves(otherUser);
|
||||
const dmRoom: IRoom = { _id: 'dm1', t: 'd' };
|
||||
RoomsStub.findOneDirectRoomContainingAllUserIDs.resolves(dmRoom);
|
||||
|
||||
const result = await getRoomByNameOrIdWithOptionToJoin({
|
||||
user: baseUser,
|
||||
nameOrId: '@other',
|
||||
type: 'd',
|
||||
tryDirectByUserIdOnly: false,
|
||||
joinChannel: true,
|
||||
errorOnEmpty: true,
|
||||
});
|
||||
|
||||
expect(
|
||||
UsersStub.findOne.calledOnceWithExactly({
|
||||
$or: [{ _id: 'other' }, { username: 'other' }],
|
||||
}),
|
||||
).to.equal(true);
|
||||
expect(RoomsStub.findOneDirectRoomContainingAllUserIDs.calledOnce).to.equal(true);
|
||||
|
||||
const callArgs = RoomsStub.findOneDirectRoomContainingAllUserIDs.getCall(0).args[0];
|
||||
expect(callArgs).to.be.an('array');
|
||||
expect(callArgs.sort()).to.deep.equal(['otherId', 'userId'].sort());
|
||||
expect(result).to.equal(dmRoom);
|
||||
});
|
||||
|
||||
it('should strip leading @ when resolving DM', async () => {
|
||||
const otherUser: IUser = { _id: 'otherId2', username: 'other2' };
|
||||
UsersStub.findOne.resolves(otherUser);
|
||||
const dmRoom: IRoom = { _id: 'dm2', t: 'd' };
|
||||
RoomsStub.findOneDirectRoomContainingAllUserIDs.resolves(dmRoom);
|
||||
|
||||
const result = await getRoomByNameOrIdWithOptionToJoin({
|
||||
user: baseUser,
|
||||
nameOrId: '@other2',
|
||||
type: undefined,
|
||||
tryDirectByUserIdOnly: false,
|
||||
joinChannel: true,
|
||||
errorOnEmpty: true,
|
||||
});
|
||||
|
||||
expect(
|
||||
UsersStub.findOne.calledOnceWithExactly({
|
||||
$or: [{ _id: 'other2' }, { username: 'other2' }],
|
||||
}),
|
||||
).to.equal(true);
|
||||
expect(result).to.equal(dmRoom);
|
||||
});
|
||||
|
||||
it('should treat nameOrId as DM when type is d even without @', async () => {
|
||||
const otherUser: IUser = { _id: 'otherId3', username: 'plain' };
|
||||
UsersStub.findOne.resolves(otherUser);
|
||||
const dmRoom: IRoom = { _id: 'dm3', t: 'd' };
|
||||
RoomsStub.findOneDirectRoomContainingAllUserIDs.resolves(dmRoom);
|
||||
|
||||
const result = await getRoomByNameOrIdWithOptionToJoin({
|
||||
user: baseUser,
|
||||
nameOrId: 'plain',
|
||||
type: 'd',
|
||||
tryDirectByUserIdOnly: false,
|
||||
joinChannel: true,
|
||||
errorOnEmpty: true,
|
||||
});
|
||||
|
||||
expect(
|
||||
UsersStub.findOne.calledOnceWithExactly({
|
||||
$or: [{ _id: 'plain' }, { username: 'plain' }],
|
||||
}),
|
||||
).to.equal(true);
|
||||
expect(result).to.equal(dmRoom);
|
||||
});
|
||||
});
|
||||
|
||||
describe('direct message resolution with tryDirectByUserIdOnly', () => {
|
||||
it('should lookup user by id only when tryDirectByUserIdOnly=true', async () => {
|
||||
const otherUser: IUser = { _id: 'idOnly', username: 'ignored' };
|
||||
UsersStub.findOneById.resolves(otherUser);
|
||||
const dmRoom: IRoom = { _id: 'dm4', t: 'd' };
|
||||
RoomsStub.findOneDirectRoomContainingAllUserIDs.resolves(dmRoom);
|
||||
|
||||
const result = await getRoomByNameOrIdWithOptionToJoin({
|
||||
user: baseUser,
|
||||
nameOrId: 'idOnly',
|
||||
type: 'd',
|
||||
tryDirectByUserIdOnly: true,
|
||||
joinChannel: true,
|
||||
errorOnEmpty: true,
|
||||
});
|
||||
|
||||
expect(UsersStub.findOneById.calledOnceWithExactly('idOnly')).to.equal(true);
|
||||
expect(UsersStub.findOne.notCalled).to.equal(true);
|
||||
expect(result).to.equal(dmRoom);
|
||||
});
|
||||
|
||||
it('should fallback to Rooms.findOneById when roomUser is falsy but room exists by id', async () => {
|
||||
UsersStub.findOneById.resolves(null);
|
||||
const dmRoom: IRoom = { _id: 'idRoom', t: 'd' };
|
||||
RoomsStub.findOneById.resolves(dmRoom);
|
||||
|
||||
const result = await getRoomByNameOrIdWithOptionToJoin({
|
||||
user: baseUser,
|
||||
nameOrId: 'idRoom',
|
||||
type: 'd',
|
||||
tryDirectByUserIdOnly: true,
|
||||
joinChannel: true,
|
||||
errorOnEmpty: true,
|
||||
});
|
||||
|
||||
expect(RoomsStub.findOneById.calledOnceWithExactly('idRoom')).to.equal(true);
|
||||
expect(result).to.equal(dmRoom);
|
||||
});
|
||||
});
|
||||
|
||||
describe('direct message creation when roomUser exists but room does not', () => {
|
||||
it('should throw Meteor.Error when roomUser not found and errorOnEmpty=true', async () => {
|
||||
UsersStub.findOne.resolves(null);
|
||||
RoomsStub.findOneById.resolves(null);
|
||||
RoomsStub.findOneDirectRoomContainingAllUserIDs.resolves(null);
|
||||
|
||||
let caught: any;
|
||||
try {
|
||||
await getRoomByNameOrIdWithOptionToJoin({
|
||||
user: baseUser,
|
||||
nameOrId: '@missing',
|
||||
type: 'd',
|
||||
tryDirectByUserIdOnly: false,
|
||||
joinChannel: true,
|
||||
errorOnEmpty: true,
|
||||
});
|
||||
} catch (e) {
|
||||
caught = e;
|
||||
}
|
||||
|
||||
expect(caught).to.be.an('object');
|
||||
expect(caught.error).to.equal('invalid-channel');
|
||||
expect(createDirectMessageStub.notCalled).to.equal(true);
|
||||
});
|
||||
|
||||
it('should return null when roomUser not found and errorOnEmpty=false', async () => {
|
||||
UsersStub.findOne.resolves(null);
|
||||
RoomsStub.findOneById.resolves(null);
|
||||
RoomsStub.findOneDirectRoomContainingAllUserIDs.resolves(null);
|
||||
|
||||
const result = await getRoomByNameOrIdWithOptionToJoin({
|
||||
user: baseUser,
|
||||
nameOrId: '@missing',
|
||||
type: 'd',
|
||||
tryDirectByUserIdOnly: false,
|
||||
joinChannel: true,
|
||||
errorOnEmpty: false,
|
||||
});
|
||||
|
||||
expect(result).to.equal(null);
|
||||
expect(createDirectMessageStub.notCalled).to.equal(true);
|
||||
});
|
||||
|
||||
it('should create a new direct message when roomUser exists and room is not found', async () => {
|
||||
const otherUser: IUser = { _id: 'otherCreate', username: 'createMe' };
|
||||
UsersStub.findOne.resolves(otherUser);
|
||||
RoomsStub.findOneDirectRoomContainingAllUserIDs.resolves(null);
|
||||
RoomsStub.findOneById.resolves({ _id: 'newDirectRoomId', t: 'd' });
|
||||
|
||||
const result = await getRoomByNameOrIdWithOptionToJoin({
|
||||
user: baseUser,
|
||||
nameOrId: '@createMe',
|
||||
type: 'd',
|
||||
tryDirectByUserIdOnly: false,
|
||||
joinChannel: true,
|
||||
errorOnEmpty: true,
|
||||
});
|
||||
|
||||
expect(createDirectMessageStub.calledOnceWithExactly(['createMe'], baseUser._id)).to.equal(true);
|
||||
expect(RoomsStub.findOneById.calledWith('newDirectRoomId')).to.equal(true);
|
||||
expect(result).to.deep.equal({ _id: 'newDirectRoomId', t: 'd' });
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user