backend: Add webhook integration tests; implement webhook server setup and preferences management
This commit is contained in:
parent
7167cb4445
commit
bcb8c82e59
@ -18,7 +18,14 @@ import {
|
||||
} from '../../src/environment.js';
|
||||
import { createApp, registerDependencies } from '../../src/server.js';
|
||||
import { RecordingService, RoomService } from '../../src/services/index.js';
|
||||
import { AuthMode, AuthType, MeetRoom, MeetRoomOptions, UserRole } from '../../src/typings/ce/index.js';
|
||||
import {
|
||||
AuthMode,
|
||||
AuthType,
|
||||
MeetRoom,
|
||||
MeetRoomOptions,
|
||||
UserRole,
|
||||
WebhookPreferences
|
||||
} from '../../src/typings/ce/index.js';
|
||||
|
||||
const CREDENTIALS = {
|
||||
user: {
|
||||
@ -302,6 +309,17 @@ export const joinFakeParticipant = async (roomId: string, participantName: strin
|
||||
await sleep('1s');
|
||||
};
|
||||
|
||||
export const endMeeting = async (roomId: string, moderatorCookie: string) => {
|
||||
checkAppIsRunning();
|
||||
|
||||
const response = await request(app)
|
||||
.delete(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/meetings/${roomId}`)
|
||||
.set('Cookie', moderatorCookie)
|
||||
.send();
|
||||
await sleep('1s');
|
||||
return response;
|
||||
};
|
||||
|
||||
export const disconnectFakeParticipants = async () => {
|
||||
fakeParticipantsProcesses.forEach((process, participantName) => {
|
||||
process.kill();
|
||||
@ -312,6 +330,18 @@ export const disconnectFakeParticipants = async () => {
|
||||
await sleep('1s');
|
||||
};
|
||||
|
||||
export const updateWebbhookPreferences = async (preferences: WebhookPreferences) => {
|
||||
checkAppIsRunning();
|
||||
|
||||
const userCookie = await loginUserAsRole(UserRole.ADMIN);
|
||||
const response = await request(app)
|
||||
.put(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/preferences/webhooks`)
|
||||
.set('Cookie', userCookie)
|
||||
.send(preferences);
|
||||
|
||||
return response;
|
||||
};
|
||||
|
||||
/**
|
||||
* Generates a token for retrieving/deleting recordings from a room and returns the cookie containing the token
|
||||
*/
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { StringValue } from 'ms';
|
||||
import { MeetRoomHelper } from '../../src/helpers';
|
||||
import { MeetRoom } from '../../src/typings/ce';
|
||||
import { MeetRoom, MeetWebhookEvent } from '../../src/typings/ce';
|
||||
import { expectValidStartRecordingResponse } from './assertion-helpers';
|
||||
import {
|
||||
createRoom,
|
||||
@ -10,6 +10,10 @@ import {
|
||||
startRecording,
|
||||
stopRecording
|
||||
} from './request-helpers';
|
||||
import express, { Request, Response } from 'express';
|
||||
import http from 'http';
|
||||
|
||||
let mockWebhookServer: http.Server;
|
||||
|
||||
export interface RoomData {
|
||||
room: MeetRoom;
|
||||
@ -169,3 +173,31 @@ export const setupMultiRecordingsTestContext = async (
|
||||
|
||||
return testContext;
|
||||
};
|
||||
|
||||
export const startWebhookServer = async (
|
||||
port: number,
|
||||
webhookReceivedCallback: (event: Request) => void
|
||||
): Promise<void> => {
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
|
||||
app.post('/webhook', (req: Request, res: Response) => {
|
||||
webhookReceivedCallback(req);
|
||||
res.status(200).send({ success: true });
|
||||
});
|
||||
|
||||
return new Promise<void>((resolve) => {
|
||||
mockWebhookServer = app.listen(port, () => {
|
||||
console.log(`Webhook server listening on port ${port}`);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const stopWebhookServer = async (): Promise<void> => {
|
||||
if (mockWebhookServer) {
|
||||
await new Promise<void>((resolve) => {
|
||||
mockWebhookServer.close(() => resolve());
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
167
backend/tests/integration/api/webhooks/webhook.test.ts
Normal file
167
backend/tests/integration/api/webhooks/webhook.test.ts
Normal file
@ -0,0 +1,167 @@
|
||||
import { afterAll, beforeAll, beforeEach, describe, expect, it } from '@jest/globals';
|
||||
import { Request } from 'express';
|
||||
import http from 'http';
|
||||
import { container } from '../../../../src/config/dependency-injector.config.js';
|
||||
import { MeetStorageService } from '../../../../src/services/index.js';
|
||||
import {
|
||||
startTestServer,
|
||||
deleteAllRecordings,
|
||||
sleep,
|
||||
endMeeting,
|
||||
updateWebbhookPreferences
|
||||
} from '../../../helpers/request-helpers';
|
||||
import { MeetWebhookEvent, MeetWebhookEventType } from '../../../../src/typings/ce/webhook.model.js';
|
||||
|
||||
import {
|
||||
setupSingleRoom,
|
||||
setupSingleRoomWithRecording,
|
||||
startWebhookServer,
|
||||
stopWebhookServer
|
||||
} from '../../../helpers/test-scenarios.js';
|
||||
import { MeetRecordingInfo, MeetRecordingStatus } from '../../../../src/typings/ce/recording.model.js';
|
||||
|
||||
describe('Webhook Integration Tests', () => {
|
||||
let receivedWebhooks: { headers: http.IncomingHttpHeaders; body: MeetWebhookEvent }[] = [];
|
||||
let storageService: MeetStorageService;
|
||||
|
||||
beforeAll(async () => {
|
||||
startTestServer();
|
||||
storageService = container.get(MeetStorageService);
|
||||
|
||||
// Start test server for webhooks
|
||||
await startWebhookServer(5080, (req: Request) => {
|
||||
receivedWebhooks.push({
|
||||
headers: req.headers,
|
||||
body: req.body
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
receivedWebhooks = [];
|
||||
// Enable webhooks in global preferences
|
||||
await updateWebbhookPreferences({
|
||||
enabled: true,
|
||||
url: `http://localhost:5080/webhook`
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await stopWebhookServer();
|
||||
const defaultPreferences = await storageService['getDefaultPreferences']();
|
||||
await updateWebbhookPreferences(defaultPreferences.webhooksPreferences);
|
||||
await deleteAllRecordings();
|
||||
});
|
||||
|
||||
it('should not send webhooks when disabled', async () => {
|
||||
await updateWebbhookPreferences({
|
||||
enabled: false
|
||||
});
|
||||
|
||||
await setupSingleRoom(true);
|
||||
|
||||
// Wait for the room to be created
|
||||
await sleep('3s');
|
||||
expect(receivedWebhooks.length).toBe(0);
|
||||
});
|
||||
|
||||
it('should send meeting_started webhook when room is created', async () => {
|
||||
const context = await setupSingleRoom(true);
|
||||
const roomData = context.room;
|
||||
|
||||
// Wait for the room to be created
|
||||
await sleep('1s');
|
||||
|
||||
// Verify 'meetingStarted' webhook is sent
|
||||
expect(receivedWebhooks.length).toBeGreaterThanOrEqual(1);
|
||||
const meetingStartedWebhook = receivedWebhooks.find(
|
||||
(w) => w.body.event === MeetWebhookEventType.MEETING_STARTED
|
||||
);
|
||||
expect(meetingStartedWebhook).toBeDefined();
|
||||
expect(meetingStartedWebhook?.body.data.roomId).toBe(roomData.roomId);
|
||||
expect(meetingStartedWebhook?.body.creationDate).toBeLessThanOrEqual(Date.now());
|
||||
expect(meetingStartedWebhook?.body.creationDate).toBeGreaterThanOrEqual(Date.now() - 3000);
|
||||
expect(meetingStartedWebhook?.headers['x-signature']).toBeDefined();
|
||||
expect(meetingStartedWebhook?.headers['x-timestamp']).toBeDefined();
|
||||
});
|
||||
|
||||
it('should send meeting_ended webhook when room is closed', async () => {
|
||||
const context = await setupSingleRoom(true);
|
||||
const roomData = context.room;
|
||||
const moderatorCookie = context.moderatorCookie;
|
||||
|
||||
// Close the room
|
||||
await endMeeting(roomData.roomId, moderatorCookie);
|
||||
|
||||
// Wait for the room to be closed
|
||||
await sleep('1s');
|
||||
|
||||
// Verify 'meetingEnded' webhook is sent
|
||||
expect(receivedWebhooks.length).toBeGreaterThanOrEqual(1);
|
||||
const meetingEndedWebhook = receivedWebhooks.find((w) => w.body.event === MeetWebhookEventType.MEETING_ENDED);
|
||||
expect(meetingEndedWebhook).toBeDefined();
|
||||
expect(meetingEndedWebhook?.body.data.roomId).toBe(roomData.roomId);
|
||||
expect(meetingEndedWebhook?.body.creationDate).toBeLessThanOrEqual(Date.now());
|
||||
expect(meetingEndedWebhook?.body.creationDate).toBeGreaterThanOrEqual(Date.now() - 3000);
|
||||
expect(meetingEndedWebhook?.headers['x-signature']).toBeDefined();
|
||||
expect(meetingEndedWebhook?.headers['x-timestamp']).toBeDefined();
|
||||
});
|
||||
|
||||
it('should send recordingStarted, recordingUpdated and recordingEnded webhooks when recording is started and stopped', async () => {
|
||||
const startDate = Date.now();
|
||||
const context = await setupSingleRoomWithRecording(true, '2s');
|
||||
const roomData = context.room;
|
||||
const recordingId = context.recordingId;
|
||||
|
||||
const recordingWebhooks = receivedWebhooks.filter((w) => w.body.event.startsWith('recording'));
|
||||
// STARTED, ACTIVE, ENDING, COMPLETE
|
||||
expect(recordingWebhooks.length).toBe(4);
|
||||
|
||||
// Check recording_started webhook
|
||||
const recordingStartedWebhook = receivedWebhooks.find(
|
||||
(w) => w.body.event === MeetWebhookEventType.RECORDING_STARTED
|
||||
);
|
||||
|
||||
let data = recordingStartedWebhook?.body.data as MeetRecordingInfo;
|
||||
expect(recordingStartedWebhook).toBeDefined();
|
||||
expect(data.roomId).toBe(roomData.roomId);
|
||||
expect(data.recordingId).toBe(recordingId);
|
||||
expect(recordingStartedWebhook?.body.creationDate).toBeLessThan(Date.now());
|
||||
expect(recordingStartedWebhook?.body.creationDate).toBeGreaterThan(startDate);
|
||||
expect(recordingStartedWebhook?.headers['x-signature']).toBeDefined();
|
||||
expect(recordingStartedWebhook?.headers['x-timestamp']).toBeDefined();
|
||||
expect(recordingStartedWebhook?.body.event).toBe(MeetWebhookEventType.RECORDING_STARTED);
|
||||
expect(data.status).toBe(MeetRecordingStatus.STARTING);
|
||||
|
||||
// Check recording_updated webhook
|
||||
const recordingUpdatedWebhook = receivedWebhooks.find(
|
||||
(w) => w.body.event === MeetWebhookEventType.RECORDING_UPDATED
|
||||
);
|
||||
data = recordingUpdatedWebhook?.body.data as MeetRecordingInfo;
|
||||
expect(recordingUpdatedWebhook).toBeDefined();
|
||||
expect(data.roomId).toBe(roomData.roomId);
|
||||
expect(data.recordingId).toBe(recordingId);
|
||||
expect(recordingUpdatedWebhook?.body.creationDate).toBeLessThan(Date.now());
|
||||
expect(recordingUpdatedWebhook?.body.creationDate).toBeGreaterThan(startDate);
|
||||
expect(recordingUpdatedWebhook?.headers['x-signature']).toBeDefined();
|
||||
expect(recordingUpdatedWebhook?.headers['x-timestamp']).toBeDefined();
|
||||
expect(recordingUpdatedWebhook?.body.event).toBe(MeetWebhookEventType.RECORDING_UPDATED);
|
||||
expect(data.status).toBe(MeetRecordingStatus.ACTIVE);
|
||||
|
||||
// Check recording_ended webhook
|
||||
const recordingEndedWebhook = receivedWebhooks.find(
|
||||
(w) => w.body.event === MeetWebhookEventType.RECORDING_ENDED
|
||||
);
|
||||
data = recordingEndedWebhook?.body.data as MeetRecordingInfo;
|
||||
expect(recordingEndedWebhook).toBeDefined();
|
||||
expect(data.roomId).toBe(roomData.roomId);
|
||||
expect(data.recordingId).toBe(recordingId);
|
||||
expect(recordingEndedWebhook?.body.creationDate).toBeLessThan(Date.now());
|
||||
expect(recordingEndedWebhook?.body.creationDate).toBeGreaterThan(startDate);
|
||||
expect(recordingEndedWebhook?.headers['x-signature']).toBeDefined();
|
||||
expect(recordingEndedWebhook?.headers['x-timestamp']).toBeDefined();
|
||||
expect(recordingEndedWebhook?.body.event).toBe(MeetWebhookEventType.RECORDING_ENDED);
|
||||
expect(data.status).not.toBe(MeetRecordingStatus.ENDING);
|
||||
expect(data.status).toBe(MeetRecordingStatus.COMPLETE);
|
||||
});
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user