Files
upstream-rocketchat/apps/meteor/tests/end-to-end/api/calendar.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 1s
snapshot of https://github.com/RocketChat/Rocket.Chat.git (develop, 2026-09-16; blobs > 131072B omitted)
2026-09-16 01:21:02 +05:00

721 lines
21 KiB
TypeScript

import type { Credentials } from '@rocket.chat/api-client';
import type { IUser } from '@rocket.chat/core-typings';
import { expect } from 'chai';
import { after, before, describe, it } from 'mocha';
import type { Response } from 'supertest';
import { sleep } from '../../../lib/utils/sleep';
import { getCredentials, api, request, credentials } from '../../data/api-data';
import { password } from '../../data/user';
import { createUser, deleteUser, login } from '../../data/users.helper';
import { withTimeout } from '../../data/utils';
import { IS_EE } from '../../e2e/config/constants';
describe('[Calendar Events]', () => {
let user2: IUser;
let userCredentials: Credentials;
before((done) => getCredentials(done));
before(async () => {
user2 = await createUser();
userCredentials = await login(user2.username, password);
});
after(() => deleteUser(user2));
describe('[/calendar-events.create]', () => {
it('should successfully create an event in the calendar', async () => {
let eventId: string | undefined;
await request
.post(api('calendar-events.create'))
.set(credentials)
.send({
startTime: new Date().toISOString(),
subject: 'Subject',
description: 'Description',
reminderMinutesBeforeStart: 10,
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
eventId = res.body.id;
});
after(async () => {
await request.post(api('calendar-events.delete')).set(credentials).send({ eventId });
});
});
it('should fail to create an event without a start time', async () => {
await request
.post(api('calendar-events.create'))
.set(credentials)
.send({
subject: 'Subject',
description: 'Description',
reminderMinutesBeforeStart: 10,
})
.expect('Content-Type', 'application/json')
.expect(400);
});
it('should fail to create an event without a subject', async () => {
await request
.post(api('calendar-events.create'))
.set(credentials)
.send({
description: 'Description',
startTime: new Date().toISOString(),
reminderMinutesBeforeStart: 10,
})
.expect('Content-Type', 'application/json')
.expect(400);
});
it('should fail to create an event without a description', async () => {
await request
.post(api('calendar-events.create'))
.set(credentials)
.send({
subject: 'Subject',
startTime: new Date().toISOString(),
reminderMinutesBeforeStart: 10,
})
.expect('Content-Type', 'application/json')
.expect(400);
});
it('should successfully create an event without reminder information', async () => {
let eventId: string | undefined;
await request
.post(api('calendar-events.create'))
.set(credentials)
.send({
startTime: new Date().toISOString(),
subject: 'Subject',
description: 'Description',
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
eventId = res.body.id;
});
after(async () => {
await request.post(api('calendar-events.delete')).set(credentials).send({ eventId });
});
});
});
describe('[/calendar-events.list]', () => {
const testSubject = `calendar-events.list-${Date.now()}`;
const testSubject2 = `calendar-events.list-${Date.now()}`;
let eventId: string | undefined;
let eventId2: string | undefined;
let eventId3: string | undefined;
before('create sample events', async () => {
await request
.post(api('calendar-events.create'))
.set(credentials)
.send({
startTime: new Date().toISOString(),
subject: testSubject,
description: 'Description',
reminderMinutesBeforeStart: 10,
})
.then((res) => {
eventId = res.body.id;
});
await request
.post(api('calendar-events.create'))
.set(credentials)
.send({
startTime: new Date(Date.now() + 48 * 60 * 60 * 1000).toISOString(),
subject: testSubject2,
description: 'Future Event',
reminderMinutesBeforeStart: 10,
})
.then((res) => {
eventId2 = res.body.id;
});
await request
.post(api('calendar-events.create'))
.set(userCredentials)
.send({
startTime: new Date().toISOString(),
subject: testSubject,
description: 'Description',
reminderMinutesBeforeStart: 10,
})
.then((res) => {
eventId3 = res.body.id;
});
});
after(() =>
Promise.all([
request.post(api('calendar-events.delete')).set(credentials).send({ eventId }),
request.post(api('calendar-events.delete')).set(credentials).send({ eventId: eventId2 }),
request.post(api('calendar-events.delete')).set(userCredentials).send({ eventId: eventId3 }),
]),
);
it('should list only the events with the same date', async () => {
await request
.get(api('calendar-events.list'))
.set(credentials)
.query({
date: new Date().toISOString(),
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('data').that.is.an('array');
const events = res.body.data.map((event: any) => event._id);
expect(events).to.be.an('array').that.includes(eventId);
expect(events).to.not.includes(eventId2);
});
});
it('should nost list events from other users', async () => {
await request
.get(api('calendar-events.list'))
.set(userCredentials)
.query({
date: new Date().toISOString(),
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('data').that.is.an('array');
const events = res.body.data.map((event: any) => event._id);
expect(events).to.be.an('array').that.includes(eventId3);
expect(events).to.not.includes(eventId);
});
});
});
describe('[/calendar-events.info]', () => {
const testSubject = `calendar-events.info-${Date.now()}`;
let eventId: string | undefined;
let eventId2: string | undefined;
before('create sample events', async () => {
await request
.post(api('calendar-events.create'))
.set(credentials)
.send({
startTime: new Date().toISOString(),
subject: testSubject,
description: 'Description',
reminderMinutesBeforeStart: 10,
})
.then((res) => {
eventId = res.body.id;
});
await request
.post(api('calendar-events.create'))
.set(userCredentials)
.send({
startTime: new Date().toISOString(),
subject: testSubject,
description: 'Description',
reminderMinutesBeforeStart: 10,
})
.then((res) => {
eventId2 = res.body.id;
});
});
after(() =>
Promise.all([
request.post(api('calendar-events.delete')).set(credentials).send({ eventId }),
request.post(api('calendar-events.delete')).set(userCredentials).send({ eventId: eventId2 }),
]),
);
it('should return the event information', async () => {
await request
.get(api('calendar-events.info'))
.set(credentials)
.query({
id: eventId,
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('event').that.is.an('object').with.property('subject', testSubject);
});
});
it('should return the event information - regular user', async () => {
await request
.get(api('calendar-events.info'))
.set(userCredentials)
.query({
id: eventId2,
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('event').that.is.an('object').with.property('subject', testSubject);
});
});
it('should fail when querying an invalid event', async () => {
await request
.get(api('calendar-events.info'))
.set(credentials)
.query({
id: 'something-random',
})
.expect('Content-Type', 'application/json')
.expect(400);
});
it('should fail when querying an event from another user', async () => {
await request
.get(api('calendar-events.info'))
.set(credentials)
.query({
id: eventId2,
})
.expect('Content-Type', 'application/json')
.expect(400);
});
});
describe('[/calendar-events.import]', () => {
const eventsToRemove: Record<string, any>[] = [];
after(() =>
Promise.all([
...eventsToRemove.map(({ credentials, eventId }) => request.post(api('calendar-events.delete')).set(credentials).send({ eventId })),
]),
);
it('should successfully import an event to the calendar', async () => {
let eventId: string | undefined;
const externalId = `calendar-events.import-${Date.now()}`;
await request
.post(api('calendar-events.import'))
.set(credentials)
.send({
startTime: new Date().toISOString(),
subject: 'Subject',
description: 'Description',
reminderMinutesBeforeStart: 10,
externalId,
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
eventId = res.body.id;
eventsToRemove.push({ credentials, eventId });
});
});
it('should fail to import an event without an external id', async () => {
await request
.post(api('calendar-events.import'))
.set(credentials)
.send({
startTime: new Date().toISOString(),
subject: 'Subject',
description: 'Description',
reminderMinutesBeforeStart: 10,
})
.expect('Content-Type', 'application/json')
.expect(400);
});
it('should fail to import an event without a start time', async () => {
await request
.post(api('calendar-events.import'))
.set(credentials)
.send({
subject: 'Subject',
description: 'Description',
reminderMinutesBeforeStart: 10,
})
.expect('Content-Type', 'application/json')
.expect(400);
});
it('should fail to import an event without a subject', async () => {
await request
.post(api('calendar-events.import'))
.set(credentials)
.send({
description: 'Description',
startTime: new Date().toISOString(),
reminderMinutesBeforeStart: 10,
})
.expect('Content-Type', 'application/json')
.expect(400);
});
it('should fail to import an event without a description', async () => {
await request
.post(api('calendar-events.import'))
.set(credentials)
.send({
subject: 'Subject',
startTime: new Date().toISOString(),
reminderMinutesBeforeStart: 10,
})
.expect('Content-Type', 'application/json')
.expect(400);
});
it('should successfully import an event without reminder information', async () => {
let eventId: string | undefined;
await request
.post(api('calendar-events.import'))
.set(credentials)
.send({
startTime: new Date().toISOString(),
subject: 'Subject',
description: 'Description',
externalId: `calendar-events.import-external-id-${Date.now()}`,
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
eventId = res.body.id;
});
after(async () => {
await request.post(api('calendar-events.delete')).set(credentials).send({ eventId });
});
});
it('should import a new event even if it was already imported by another user', async () => {
let eventId: string | undefined;
let eventId2: string | undefined;
const externalId = `calendar-events.import-${Date.now()}`;
await request
.post(api('calendar-events.import'))
.set(userCredentials)
.send({
startTime: new Date().toISOString(),
subject: 'First User',
description: 'Description',
reminderMinutesBeforeStart: 10,
externalId,
})
.then((res) => {
eventId = res.body.id;
eventsToRemove.push({ credentials: userCredentials, eventId });
});
await request
.post(api('calendar-events.import'))
.set(credentials)
.send({
startTime: new Date().toISOString(),
subject: 'Second User',
description: 'Description',
reminderMinutesBeforeStart: 10,
externalId,
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
expect(res.body.id).to.not.be.equal(eventId);
eventId2 = res.body.id;
eventsToRemove.push({ credentials, eventId: eventId2 });
});
await request
.get(api('calendar-events.info'))
.set(userCredentials)
.query({ id: eventId })
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('event').that.is.an('object').with.property('subject', 'First User');
});
await request
.get(api('calendar-events.info'))
.set(credentials)
.query({ id: eventId2 })
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('event').that.is.an('object').with.property('subject', 'Second User');
});
});
it('should update an event that has the same external id', async () => {
let eventId: string | undefined;
const externalId = `calendar-events.import-twice-${Date.now()}`;
await request
.post(api('calendar-events.import'))
.set(credentials)
.send({
startTime: new Date().toISOString(),
subject: 'Subject',
description: 'Description',
reminderMinutesBeforeStart: 10,
externalId,
})
.then((res) => {
eventId = res.body.id;
eventsToRemove.push({ credentials, eventId });
});
await request
.post(api('calendar-events.import'))
.set(credentials)
.send({
startTime: new Date().toISOString(),
subject: 'New Subject',
description: 'New Description',
reminderMinutesBeforeStart: 15,
externalId,
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect(async (res: Response) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('id', eventId);
});
await request
.get(api('calendar-events.info'))
.set(credentials)
.query({ id: eventId })
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('event').that.is.an('object').with.property('subject', 'New Subject');
});
});
});
describe('[/calendar-events.update]', () => {
const testSubject = `calendar-events.update-${Date.now()}`;
let eventId: string | undefined;
before('create sample events', async () => {
await request
.post(api('calendar-events.create'))
.set(userCredentials)
.send({
startTime: new Date().toISOString(),
subject: 'Old Subject',
description: 'Old Description',
reminderMinutesBeforeStart: 10,
})
.then((res) => {
eventId = res.body.id;
});
});
after(async () => {
await request.post(api('calendar-events.delete')).set(userCredentials).send({ eventId });
});
it('should update the event with the new data', async () => {
await request
.post(api('calendar-events.update'))
.set(userCredentials)
.send({
eventId,
startTime: new Date().toISOString(),
subject: testSubject,
description: 'New Description',
reminderMinutesBeforeStart: 15,
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect(async (res: Response) => {
expect(res.body).to.have.property('success', true);
});
await request
.get(api('calendar-events.info'))
.set(userCredentials)
.query({ id: eventId })
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('event').that.is.an('object');
expect(res.body.event).to.have.property('subject', testSubject);
expect(res.body.event).to.have.property('description', 'New Description');
});
});
it('should fail to update an event that doesnt exist', async () => {
await request
.post(api('calendar-events.update'))
.set(userCredentials)
.send({
eventId: 'something-random',
startTime: new Date().toISOString(),
subject: testSubject,
description: 'New Description',
reminderMinutesBeforeStart: 15,
})
.expect('Content-Type', 'application/json')
.expect(400);
});
it('should fail to update an event from another user', async () => {
await request
.post(api('calendar-events.update'))
.set(credentials)
.send({
eventId,
startTime: new Date().toISOString(),
subject: 'Another Subject',
description: 'Third Description',
reminderMinutesBeforeStart: 20,
})
.expect('Content-Type', 'application/json')
.expect(400);
});
});
describe('[/calendar-events.delete]', () => {
let eventId: string | undefined;
before('create sample events', async () => {
await request
.post(api('calendar-events.create'))
.set(userCredentials)
.send({
startTime: new Date().toISOString(),
subject: 'Subject',
description: 'Description',
reminderMinutesBeforeStart: 10,
})
.then((res) => {
eventId = res.body.id;
});
});
it('should fail to delete an event from another user', async () => {
await request
.post(api('calendar-events.delete'))
.set(credentials)
.send({
eventId,
})
.expect('Content-Type', 'application/json')
.expect(400);
});
it('should delete the specified event', async () => {
await request
.post(api('calendar-events.delete'))
.set(userCredentials)
.send({
eventId,
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect(async (res: Response) => {
expect(res.body).to.have.property('success', true);
});
await request
.get(api('calendar-events.info'))
.set(userCredentials)
.query({ id: eventId })
.expect('Content-Type', 'application/json')
.expect(400);
});
it('should fail to delete an event that doesnt exist', async () => {
await request
.post(api('calendar-events.delete'))
.set(userCredentials)
.send({
eventId: 'something-random',
})
.expect('Content-Type', 'application/json')
.expect(400);
});
});
(IS_EE ? describe : describe.skip)('[Calendar Events Status Sync]', () => {
before(async () => {
await request.post('/api/v1/users.setStatus').set(userCredentials).send({ status: 'online' }).expect(200);
});
// claim apply/clear land asynchronously, so poll instead of asserting on a fixed sleep
const waitForStatusSource = (
predicate: (source: string | undefined) => boolean,
{ timeout = 15000, interval = 500 } = {},
): Promise<string | undefined> =>
withTimeout(async (signal) => {
for (;;) {
const res = await request.get('/api/v1/users.getStatus').set(userCredentials).expect(200);
const source = res.body.statusSource;
if (predicate(source) || signal.aborted) {
return source;
}
await sleep(interval);
}
}, timeout);
it('should apply a calendar (external) claim for an in-progress event and clear it when removed', async () => {
const now = new Date();
const startTime = new Date(now.getTime() - 1000);
const endTime = new Date(now.getTime() + 60 * 60 * 1000);
const eventPayload = {
startTime: startTime.toISOString(),
endTime: endTime.toISOString(),
subject: 'Subject',
description: 'Description',
reminderMinutesBeforeStart: 1,
busy: true,
};
const createResponse = await request.post('/api/v1/calendar-events.create').set(userCredentials).send(eventPayload).expect(200);
const eventId = createResponse.body.id;
// REST-only user has no DDP session, so assert the claim via statusSource, not display status
expect(await waitForStatusSource((source) => source === 'external')).to.equal('external');
// the claim must carry the event's endTime as its expiry, so the presence engine can auto-clear it
const status = await request.get('/api/v1/users.getStatus').set(userCredentials).expect(200);
expect(new Date(status.body.statusExpiresAt).getTime()).to.equal(endTime.getTime());
await request.post('/api/v1/calendar-events.delete').set(userCredentials).send({ eventId }).expect(200);
expect(await waitForStatusSource((source) => source === undefined)).to.be.undefined;
});
});
});