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[] = []; 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 => 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; }); }); });