Files
upstream-rocketchat/apps/meteor/server/api/lib/eraseTeam.spec.ts
upstream-mirror 30810eecd7
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 2s
snapshot of https://github.com/RocketChat/Rocket.Chat.git (develop, 2026-09-16; blobs > 131072B omitted)
2026-09-16 01:21:02 +05:00

293 lines
9.9 KiB
TypeScript

import { expect } from 'chai';
import proxyquireRaw from 'proxyquire';
import * as sinon from 'sinon';
const proxyquire = proxyquireRaw.noCallThru();
type Stubbed = { [k: string]: any };
describe('eraseTeam (TypeScript) module', () => {
let sandbox: sinon.SinonSandbox;
let stubs: Stubbed;
let subject: any;
beforeEach(() => {
sandbox = sinon.createSandbox();
stubs = {
'Team': {
getMatchingTeamRooms: sandbox.stub().resolves([]),
unsetTeamIdOfRooms: sandbox.stub().resolves(),
removeAllMembersFromTeam: sandbox.stub().resolves(),
deleteById: sandbox.stub().resolves(),
},
'Users': {
findOneById: sandbox.stub().resolves(null),
},
'Rooms': {
findOneById: sandbox.stub().resolves(null),
},
'eraseRoomStub': sandbox.stub().resolves(true),
'deleteRoomStub': sandbox.stub().resolves(),
'../../lib/logger/system': {
SystemLogger: {
error: sandbox.stub(),
},
},
'@rocket.chat/apps': {
AppEvents: {
IPreRoomDeletePrevent: 'IPreRoomDeletePrevent',
IPostRoomDeleted: 'IPostRoomDeleted',
},
Apps: {
self: {
isLoaded: () => false,
triggerEvent: sandbox.stub().resolves(false),
},
},
},
'@rocket.chat/models': {
Rooms: {
findOneById: (...args: any[]) => stubs.Rooms.findOneById(...args),
},
Users: {
findOneById: (...args: any[]) => stubs.Users.findOneById(...args),
},
},
'@rocket.chat/core-services': {
MeteorError: (function () {
class MeteorError extends Error {
public error: string | undefined;
public details: any;
constructor(message?: string, error?: string, details?: any) {
super(message);
this.error = error;
this.details = details;
}
}
return MeteorError;
})(),
},
};
subject = proxyquire('./eraseTeam', {
'@rocket.chat/apps': stubs['@rocket.chat/apps'],
'@rocket.chat/models': stubs['@rocket.chat/models'],
'../../lib/eraseRoom': { __esModule: true, eraseRoom: stubs.eraseRoomStub },
'../../lib/rooms/deleteRoom': { __esModule: true, deleteRoom: stubs.deleteRoomStub },
'../../lib/logger/system': stubs['../../lib/logger/system'],
'@rocket.chat/core-services': {
MeteorError: stubs['@rocket.chat/core-services'].MeteorError,
Team: stubs.Team,
},
});
});
afterEach(() => {
sandbox.restore();
});
describe('eraseTeamShared', () => {
it('throws when user is undefined', async () => {
// eslint-disable-next-line @typescript-eslint/no-empty-function
await expect(subject.eraseTeamShared(undefined, { _id: 'team1', roomId: 'teamRoom' }, [], () => {})).to.be.rejected;
});
it('erases provided rooms (excluding team.roomId) and cleans up team', async () => {
const team = { _id: 'team-id', roomId: 'team-room' };
const user = { _id: 'user-1', username: 'u' };
stubs.Team.getMatchingTeamRooms.resolves(['room-1', 'room-2', team.roomId]);
stubs.Rooms.findOneById.withArgs(team.roomId).resolves({ _id: team.roomId });
const erased: Array<{ rid: string; user: any }> = [];
const eraseRoomFn = async (rid: string, user: any) => {
erased.push({ rid, user });
};
await subject.eraseTeamShared(user, team, ['room-1', 'room-2', team.roomId], eraseRoomFn);
expect(erased.some((r) => r.rid === 'room-1')).to.be.true;
expect(erased.some((r) => r.rid === 'room-2')).to.be.true;
sinon.assert.calledOnce(stubs.Team.unsetTeamIdOfRooms);
expect(erased.some((r) => r.rid === team.roomId)).to.be.true;
sinon.assert.calledOnce(stubs.Team.removeAllMembersFromTeam);
sinon.assert.calledOnce(stubs.Team.deleteById);
});
it('skips the main room but still deletes the team when the main room no longer exists', async () => {
const team = { _id: 'team-id', roomId: 'team-room' };
const user = { _id: 'user-1', username: 'u' };
stubs.Team.getMatchingTeamRooms.resolves([]);
stubs.Rooms.findOneById.withArgs(team.roomId).resolves(null);
const eraseRoomFn = sandbox.stub().resolves();
await subject.eraseTeamShared(user, team, [], eraseRoomFn);
sinon.assert.notCalled(eraseRoomFn);
sinon.assert.calledOnce(stubs.Team.unsetTeamIdOfRooms);
sinon.assert.calledOnce(stubs.Team.removeAllMembersFromTeam);
sinon.assert.calledOnce(stubs.Team.deleteById);
});
});
describe('eraseTeam', () => {
it('calls eraseRoom for the team main room (via eraseTeamShared)', async () => {
const team = { _id: 't1', roomId: 't-room' };
const user = { _id: 'u1', username: 'u', name: 'User' };
stubs.Team.getMatchingTeamRooms.resolves([]);
stubs.Rooms.findOneById.withArgs(team.roomId).resolves({ _id: team.roomId });
const { eraseRoomStub } = stubs;
eraseRoomStub.resolves(true);
await subject.eraseTeam(user, team, []);
sinon.assert.calledWith(eraseRoomStub, team.roomId, user);
});
});
describe('eraseTeamOnRelinquishRoomOwnerships', () => {
it('returns successfully deleted room ids only', async () => {
const team = { _id: 't1', roomId: 't-room' };
stubs.Team.getMatchingTeamRooms.resolves(['r1', 'r2']);
stubs.Rooms.findOneById.withArgs('r1').resolves({ _id: 'r1', federated: false });
stubs.Rooms.findOneById.withArgs('r2').resolves(null);
stubs.deleteRoomStub.withArgs('r1').resolves();
stubs.deleteRoomStub.withArgs('r2').rejects(new Error('boom'));
const base = proxyquire('./eraseTeam', {
'@rocket.chat/apps': stubs['@rocket.chat/apps'],
'@rocket.chat/models': stubs['@rocket.chat/models'],
'../../lib/eraseRoom': { __esModule: true, eraseRoom: stubs.eraseRoomStub },
'../../lib/rooms/deleteRoom': { __esModule: true, deleteRoom: stubs.deleteRoomStub },
'../../lib/logger/system': stubs['../../lib/logger/system'],
'@rocket.chat/core-services': {
MeteorError: stubs['@rocket.chat/core-services'].MeteorError,
Team: stubs.Team,
},
});
const result: string[] = await base.eraseTeamOnRelinquishRoomOwnerships(team, ['r1', 'r2']);
expect(result).to.be.an('array').that.includes('r1').and.not.includes('r2');
});
});
describe('eraseRoomLooseValidation', () => {
let baseModule: any;
beforeEach(() => {
baseModule = proxyquire('./eraseTeam', {
'@rocket.chat/apps': stubs['@rocket.chat/apps'],
'@rocket.chat/models': stubs['@rocket.chat/models'],
'../../lib/eraseRoom': { __esModule: true, eraseRoom: stubs.eraseRoomStub },
'../../lib/rooms/deleteRoom': { __esModule: true, deleteRoom: stubs.deleteRoomStub },
'../../lib/logger/system': stubs['../../lib/logger/system'],
'@rocket.chat/core-services': {
MeteorError: stubs['@rocket.chat/core-services'].MeteorError,
Team: stubs.Team,
},
});
});
it('returns false when room not found', async () => {
stubs.Rooms.findOneById.resolves(null);
const res = await baseModule.eraseRoomLooseValidation('does-not-exist');
expect(res).to.be.false;
});
it('returns false when room.federated is true', async () => {
stubs.Rooms.findOneById.resolves({ _id: 'r', federated: true });
const res = await baseModule.eraseRoomLooseValidation('r');
expect(res).to.be.false;
});
it('returns false when app pre-delete prevents deletion', async () => {
const listenerStub = sandbox.stub().resolves(true);
const AppsStub = {
AppEvents: stubs['@rocket.chat/apps'].AppEvents,
Apps: {
self: {
isLoaded: () => true,
triggerEvent: listenerStub,
},
},
};
const m = proxyquire('./eraseTeam', {
'@rocket.chat/apps': AppsStub,
'@rocket.chat/models': stubs['@rocket.chat/models'],
'../../lib/eraseRoom': { __esModule: true, eraseRoom: stubs.eraseRoomStub },
'../../lib/rooms/deleteRoom': { __esModule: true, deleteRoom: stubs.deleteRoomStub },
'../../lib/logger/system': stubs['../../lib/logger/system'],
'@rocket.chat/core-services': {
MeteorError: stubs['@rocket.chat/core-services'].MeteorError,
Team: stubs.Team,
},
});
stubs.Rooms.findOneById.resolves({ _id: 'r', federated: false });
const res = await m.eraseRoomLooseValidation('r');
expect(listenerStub.calledOnce).to.be.true;
expect(res).to.be.false;
});
it('logs and returns false when deleteRoom throws', async () => {
stubs.Rooms.findOneById.resolves({ _id: 'r', federated: false });
stubs.deleteRoomStub.rejects(new Error('boom'));
const m = proxyquire('./eraseTeam', {
'@rocket.chat/apps': stubs['@rocket.chat/apps'],
'@rocket.chat/models': stubs['@rocket.chat/models'],
'../../lib/eraseRoom': { __esModule: true, eraseRoom: stubs.eraseRoomStub },
'../../lib/rooms/deleteRoom': { __esModule: true, deleteRoom: stubs.deleteRoomStub },
'../../lib/logger/system': stubs['../../lib/logger/system'],
'@rocket.chat/core-services': {
MeteorError: stubs['@rocket.chat/core-services'].MeteorError,
Team: stubs.Team,
},
});
const res = await m.eraseRoomLooseValidation('r');
expect(res).to.be.false;
sinon.assert.calledOnce(stubs['../../lib/logger/system'].SystemLogger.error);
});
it('calls post-deleted event and returns true on success', async () => {
const roomEventStub = sandbox.stub().onFirstCall().resolves(false).onSecondCall().resolves();
const AppsStub = {
AppEvents: stubs['@rocket.chat/apps'].AppEvents,
Apps: {
self: {
isLoaded: () => true,
triggerEvent: roomEventStub,
},
},
};
stubs.deleteRoomStub.resolves();
const m = proxyquire('./eraseTeam', {
'@rocket.chat/apps': AppsStub,
'@rocket.chat/models': stubs['@rocket.chat/models'],
'../../lib/eraseRoom': { __esModule: true, eraseRoom: stubs.eraseRoomStub },
'../../lib/rooms/deleteRoom': { __esModule: true, deleteRoom: stubs.deleteRoomStub },
'../../lib/logger/system': stubs['../../lib/logger/system'],
'@rocket.chat/core-services': {
MeteorError: stubs['@rocket.chat/core-services'].MeteorError,
Team: stubs.Team,
},
});
stubs.Rooms.findOneById.resolves({ _id: 'r', federated: false });
const res = await m.eraseRoomLooseValidation('r');
expect(res).to.be.true;
sinon.assert.calledTwice(roomEventStub);
});
});
});