632 lines
20 KiB
TypeScript
632 lines
20 KiB
TypeScript
import { expect } from '@jest/globals';
|
|
import { container } from '../../src/config/dependency-injector.config';
|
|
import { INTERNAL_CONFIG } from '../../src/config/internal-config';
|
|
import { TokenService } from '../../src/services';
|
|
import {
|
|
MeetingEndAction,
|
|
MeetRecordingAccess,
|
|
MeetRecordingInfo,
|
|
MeetRecordingStatus,
|
|
MeetRoom,
|
|
MeetRoomAutoDeletionPolicy,
|
|
MeetRoomConfig,
|
|
MeetRoomDeletionPolicyWithMeeting,
|
|
MeetRoomDeletionPolicyWithRecordings,
|
|
MeetRoomStatus,
|
|
ParticipantPermissions,
|
|
ParticipantRole
|
|
} from '@openvidu-meet/typings';
|
|
|
|
export const expectErrorResponse = (
|
|
response: any,
|
|
status = 422,
|
|
error = 'Unprocessable Entity',
|
|
message = 'Invalid request',
|
|
details?: Array<{ field?: string; message: string }>
|
|
) => {
|
|
expect(response.status).toBe(status);
|
|
expect(response.body).toMatchObject({
|
|
...(error ? { error } : {}),
|
|
...(message ? { message } : {})
|
|
});
|
|
|
|
if (details === undefined) {
|
|
expect(response.body.details).toBeUndefined();
|
|
return;
|
|
}
|
|
|
|
expect(Array.isArray(response.body.details)).toBe(true);
|
|
expect(response.body.details).toEqual(
|
|
expect.arrayContaining(
|
|
details.map((d) => {
|
|
const matcher: any = { message: expect.stringContaining(d.message) };
|
|
|
|
if (d.field !== undefined) {
|
|
matcher.field = d.field;
|
|
}
|
|
|
|
return expect.objectContaining(matcher);
|
|
})
|
|
)
|
|
);
|
|
};
|
|
|
|
export const expectValidationError = (response: any, field: string, message: string) => {
|
|
expectErrorResponse(response, 422, 'Unprocessable Entity', 'Invalid request', [{ field, message }]);
|
|
};
|
|
|
|
/**
|
|
* Asserts that a rooms response matches the expected values for testing purposes.
|
|
* Validates the room array length and pagination properties.
|
|
*
|
|
* @param body - The API response body to validate
|
|
* @param expectedRoomLength - The expected number of rooms in the response
|
|
* @param expectedMaxItems - The expected maximum number of items in pagination
|
|
* @param expectedTruncated - The expected value for pagination.isTruncated flag
|
|
* @param expectedNextPageToken - The expected presence of pagination.nextPageToken
|
|
* (if true, expects nextPageToken to be defined;
|
|
* if false, expects nextPageToken to be undefined)
|
|
*/
|
|
export const expectSuccessRoomsResponse = (
|
|
response: any,
|
|
expectedRoomLength: number,
|
|
expectedMaxItems: number,
|
|
expectedTruncated: boolean,
|
|
expectedNextPageToken: boolean
|
|
) => {
|
|
const { body } = response;
|
|
expect(response.status).toBe(200);
|
|
expect(body).toBeDefined();
|
|
expect(body.rooms).toBeDefined();
|
|
expect(Array.isArray(body.rooms)).toBe(true);
|
|
expect(body.rooms.length).toBe(expectedRoomLength);
|
|
expect(body.pagination).toBeDefined();
|
|
expect(body.pagination.isTruncated).toBe(expectedTruncated);
|
|
|
|
expectedNextPageToken
|
|
? expect(body.pagination.nextPageToken).toBeDefined()
|
|
: expect(body.pagination.nextPageToken).toBeUndefined();
|
|
expect(body.pagination.maxItems).toBe(expectedMaxItems);
|
|
};
|
|
|
|
export const expectSuccessRoomResponse = (
|
|
response: any,
|
|
roomName: string,
|
|
autoDeletionDate?: number,
|
|
config?: MeetRoomConfig
|
|
) => {
|
|
expect(response.status).toBe(200);
|
|
expectValidRoom(response.body, roomName, config, autoDeletionDate);
|
|
};
|
|
|
|
export const expectSuccessRoomConfigResponse = (response: any, config: MeetRoomConfig) => {
|
|
expect(response.status).toBe(200);
|
|
expect(response.body).toBeDefined();
|
|
expect(response.body).toEqual(config);
|
|
};
|
|
|
|
export const expectValidRoom = (
|
|
room: MeetRoom,
|
|
name: string,
|
|
config?: MeetRoomConfig,
|
|
autoDeletionDate?: number,
|
|
autoDeletionPolicy?: MeetRoomAutoDeletionPolicy,
|
|
status?: MeetRoomStatus,
|
|
meetingEndAction?: MeetingEndAction
|
|
) => {
|
|
expect(room).toBeDefined();
|
|
|
|
expect(room.roomId).toBeDefined();
|
|
expect(room.roomName).toBeDefined();
|
|
expect(room.roomName).toBe(name);
|
|
expect(room.roomId).not.toBe('');
|
|
expect(room.roomId).toContain(room.roomName.replace(/\s+/g, '')); // Ensure roomId contains the name without spaces
|
|
expect(room.creationDate).toBeDefined();
|
|
|
|
if (autoDeletionDate !== undefined) {
|
|
expect(room.autoDeletionDate).toBeDefined();
|
|
expect(room.autoDeletionDate).toBe(autoDeletionDate);
|
|
} else {
|
|
expect(room.autoDeletionDate).toBeUndefined();
|
|
}
|
|
|
|
if (autoDeletionPolicy !== undefined) {
|
|
expect(room.autoDeletionPolicy).toBeDefined();
|
|
expect(room.autoDeletionPolicy).toEqual(autoDeletionPolicy);
|
|
} else {
|
|
expect(room.autoDeletionPolicy).toEqual({
|
|
withMeeting: MeetRoomDeletionPolicyWithMeeting.WHEN_MEETING_ENDS,
|
|
withRecordings: MeetRoomDeletionPolicyWithRecordings.CLOSE
|
|
});
|
|
}
|
|
|
|
expect(room.config).toBeDefined();
|
|
|
|
if (config !== undefined) {
|
|
expect(room.config).toEqual(config);
|
|
} else {
|
|
expect(room.config).toEqual({
|
|
recording: {
|
|
enabled: true,
|
|
allowAccessTo: MeetRecordingAccess.ADMIN_MODERATOR_SPEAKER
|
|
},
|
|
chat: { enabled: true },
|
|
virtualBackground: { enabled: true }
|
|
});
|
|
}
|
|
|
|
expect(room.moderatorUrl).toBeDefined();
|
|
expect(room.speakerUrl).toBeDefined();
|
|
expect(room.moderatorUrl).toContain(room.roomId);
|
|
expect(room.speakerUrl).toContain(room.roomId);
|
|
|
|
expect(room.status).toBeDefined();
|
|
expect(room.status).toEqual(status || MeetRoomStatus.OPEN);
|
|
expect(room.meetingEndAction).toBeDefined();
|
|
expect(room.meetingEndAction).toEqual(meetingEndAction || MeetingEndAction.NONE);
|
|
};
|
|
|
|
export const expectValidRecording = (
|
|
recording: MeetRecordingInfo,
|
|
recordingId: string,
|
|
roomId: string,
|
|
roomName: string,
|
|
status: MeetRecordingStatus
|
|
) => {
|
|
expect(recording).toBeDefined();
|
|
expect(recording.recordingId).toBeDefined();
|
|
expect(recording.roomId).toBeDefined();
|
|
expect(recording.roomName).toBeDefined();
|
|
expect(recording.recordingId).toBe(recordingId);
|
|
expect(recording.roomId).toBe(roomId);
|
|
expect(recording.roomName).toBe(roomName);
|
|
expect(recording.startDate).toBeDefined();
|
|
expect(recording.status).toBeDefined();
|
|
expect(recording.status).toBe(status);
|
|
expect(recording.filename).toBeDefined();
|
|
expect(recording.details).toBeDefined();
|
|
};
|
|
|
|
export const expectValidRoomWithFields = (room: MeetRoom, fields: string[] = []) => {
|
|
expect(room).toBeDefined();
|
|
expectObjectFields(room, fields);
|
|
};
|
|
|
|
export const expectValidRecordingWithFields = (rec: MeetRecordingInfo, fields: string[] = []) => {
|
|
expect(rec).toBeDefined();
|
|
expectObjectFields(rec, fields);
|
|
};
|
|
|
|
const expectObjectFields = (obj: any, present: string[] = [], absent: string[] = []) => {
|
|
present.forEach((key) => {
|
|
expect(obj).toHaveProperty(key);
|
|
expect((obj as any)[key]).not.toBeUndefined();
|
|
});
|
|
absent.forEach((key) => {
|
|
// Si la propiedad existe, debe ser undefined
|
|
expect(Object.prototype.hasOwnProperty.call(obj, key) ? (obj as any)[key] : undefined).toBeUndefined();
|
|
});
|
|
};
|
|
|
|
// Validate recording location header in the response
|
|
export const expectValidRecordingLocationHeader = (response: any) => {
|
|
const locationHeader = response.headers.location;
|
|
expect(locationHeader).toBeDefined();
|
|
const locationHeaderUrl = new URL(locationHeader);
|
|
expect(locationHeaderUrl.pathname).toBe(
|
|
`${INTERNAL_CONFIG.API_BASE_PATH_V1}/recordings/${response.body.recordingId}`
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Validates a successful recording media response, supporting edge cases and range requests.
|
|
*
|
|
* @param response - The HTTP response object to validate
|
|
* @param range - Optional range header that was sent in the request
|
|
* @param fullSize - Optional total file size for range validation
|
|
* @param options - Optional configuration to handle edge cases:
|
|
* - allowSizeDifference: Allows a difference between content-length and actual body size (default: false)
|
|
* - ignoreRangeFormat: Ignores exact range format checking (useful for adjusted ranges) (default: false)
|
|
* - expectedStatus: Override the expected status code (default: auto-determined based on range)
|
|
*/
|
|
export const expectSuccessRecordingMediaResponse = (
|
|
response: any,
|
|
range?: string,
|
|
fullSize?: number,
|
|
options?: {
|
|
allowSizeDifference?: boolean;
|
|
ignoreRangeFormat?: boolean;
|
|
expectedStatus?: number;
|
|
}
|
|
) => {
|
|
// Default options
|
|
const opts = {
|
|
allowSizeDifference: false,
|
|
ignoreRangeFormat: false,
|
|
...options
|
|
};
|
|
|
|
// Determine expected status
|
|
const expectedStatus = opts.expectedStatus ?? (range ? 206 : 200);
|
|
|
|
// Basic validations for any successful response
|
|
expect(response.status).toBe(expectedStatus);
|
|
expect(response.headers['content-type']).toBe('video/mp4');
|
|
expect(response.headers['accept-ranges']).toBe('bytes');
|
|
expect(response.headers['content-length']).toBeDefined();
|
|
expect(parseInt(response.headers['content-length'])).toBeGreaterThan(0);
|
|
expect(response.headers['cache-control']).toBeDefined();
|
|
|
|
// Verify response is binary data with some size
|
|
expect(response.body).toBeInstanceOf(Buffer);
|
|
expect(response.body.length).toBeGreaterThan(0);
|
|
|
|
// Handle range responses (206 Partial Content)
|
|
if (range && expectedStatus === 206) {
|
|
// Verify the content-range header
|
|
expect(response.headers['content-range']).toBeDefined();
|
|
|
|
// If ignoreRangeFormat is true, only check the format of the content-range header
|
|
if (opts.ignoreRangeFormat) {
|
|
expect(response.headers['content-range']).toMatch(/^bytes \d+-\d+\/\d+$/);
|
|
|
|
if (fullSize) {
|
|
// Verify the total size in content-range header
|
|
const totalSizeMatch = response.headers['content-range'].match(/\/(\d+)$/);
|
|
|
|
if (totalSizeMatch) {
|
|
expect(parseInt(totalSizeMatch[1])).toBe(fullSize);
|
|
}
|
|
}
|
|
} else {
|
|
// Extract the requested range from the request header
|
|
const rangeMatch = range.match(/^bytes=(\d+)-(\d*)$/);
|
|
|
|
if (!rangeMatch) {
|
|
throw new Error(`Invalid range format: ${range}`);
|
|
}
|
|
|
|
const requestedStart = parseInt(rangeMatch[1]);
|
|
const requestedEnd = rangeMatch[2] ? parseInt(rangeMatch[2]) : fullSize ? fullSize - 1 : undefined;
|
|
|
|
expect(requestedStart).not.toBeNaN();
|
|
|
|
// Verify the range in the response
|
|
const contentRangeMatch = response.headers['content-range'].match(/^bytes (\d+)-(\d+)\/(\d+)$/);
|
|
|
|
if (!contentRangeMatch) {
|
|
throw new Error(`Invalid content-range format: ${response.headers['content-range']}`);
|
|
}
|
|
|
|
const actualStart = parseInt(contentRangeMatch[1]);
|
|
const actualEnd = parseInt(contentRangeMatch[2]);
|
|
const actualTotal = parseInt(contentRangeMatch[3]);
|
|
|
|
// Verify the start matches
|
|
expect(actualStart).toBe(requestedStart);
|
|
|
|
// If full size is provided, verify the total is correct
|
|
if (fullSize) {
|
|
expect(actualTotal).toBe(fullSize);
|
|
|
|
// The end may be adjusted if it exceeds the total size
|
|
if (requestedEnd !== undefined && requestedEnd >= fullSize) {
|
|
expect(actualEnd).toBe(fullSize - 1);
|
|
} else if (requestedEnd !== undefined) {
|
|
expect(actualEnd).toBe(requestedEnd);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Verify that Content-Length is consistent
|
|
const declaredLength = parseInt(response.headers['content-length']);
|
|
expect(declaredLength).toBeGreaterThan(0);
|
|
|
|
// If size differences are not allowed, body length must match exactly
|
|
if (!opts.allowSizeDifference) {
|
|
expect(response.body.length).toBe(declaredLength);
|
|
} else {
|
|
// Allow some difference but ensure it's within a reasonable tolerance
|
|
const bodyLength = response.body.length;
|
|
const diff = Math.abs(bodyLength - declaredLength);
|
|
const tolerance = Math.max(declaredLength * 0.05, 10); // 5% or at least 10 bytes
|
|
|
|
expect(diff).toBeLessThanOrEqual(tolerance);
|
|
}
|
|
} else if (expectedStatus === 200) {
|
|
// For full content responses
|
|
const declaredLength = parseInt(response.headers['content-length']);
|
|
|
|
if (!opts.allowSizeDifference) {
|
|
expect(response.body.length).toBe(declaredLength);
|
|
}
|
|
|
|
// If full size is provided, content-length must match
|
|
if (fullSize !== undefined) {
|
|
expect(declaredLength).toBe(fullSize);
|
|
}
|
|
}
|
|
};
|
|
|
|
export const expectValidStartRecordingResponse = (response: any, roomId: string, roomName: string) => {
|
|
expect(response.status).toBe(201);
|
|
expect(response.body).toHaveProperty('recordingId');
|
|
|
|
expectValidRecordingLocationHeader(response);
|
|
|
|
const recordingId = response.body.recordingId;
|
|
expect(recordingId).toBeDefined();
|
|
|
|
expect(recordingId).toContain(roomId);
|
|
expect(response.body).toHaveProperty('roomId', roomId);
|
|
expect(response.body).toHaveProperty('roomName', roomName);
|
|
expect(response.body).toHaveProperty('startDate');
|
|
expect(response.body).toHaveProperty('status', 'active');
|
|
expect(response.body).toHaveProperty('filename');
|
|
expect(response.body).not.toHaveProperty('duration');
|
|
expect(response.body).not.toHaveProperty('endDate');
|
|
expect(response.body).not.toHaveProperty('size');
|
|
};
|
|
|
|
export const expectValidStopRecordingResponse = (
|
|
response: any,
|
|
recordingId: string,
|
|
roomId: string,
|
|
roomName: string
|
|
) => {
|
|
expect(response.status).toBe(202);
|
|
expect(response.body).toBeDefined();
|
|
expect(response.body).toHaveProperty('recordingId', recordingId);
|
|
expect([MeetRecordingStatus.COMPLETE, MeetRecordingStatus.ENDING]).toContain(response.body.status);
|
|
expect(response.body).toHaveProperty('roomId', roomId);
|
|
expect(response.body).toHaveProperty('roomName', roomName);
|
|
expect(response.body).toHaveProperty('filename');
|
|
expect(response.body).toHaveProperty('startDate');
|
|
expect(response.body).toHaveProperty('duration', expect.any(Number));
|
|
|
|
expectValidRecordingLocationHeader(response);
|
|
};
|
|
|
|
export const expectValidGetRecordingResponse = (
|
|
response: any,
|
|
recordingId: string,
|
|
roomId: string,
|
|
roomName: string,
|
|
status?: MeetRecordingStatus,
|
|
maxSecDuration?: number
|
|
) => {
|
|
expect(response.status).toBe(200);
|
|
expect(response.body).toBeDefined();
|
|
const body = response.body;
|
|
|
|
expect(body).toMatchObject({ recordingId, roomId, roomName });
|
|
|
|
const isRecFinished =
|
|
status &&
|
|
(status === MeetRecordingStatus.COMPLETE ||
|
|
status === MeetRecordingStatus.ABORTED ||
|
|
status === MeetRecordingStatus.FAILED ||
|
|
status === MeetRecordingStatus.LIMIT_REACHED);
|
|
expect(body).toEqual(
|
|
expect.objectContaining({
|
|
recordingId: expect.stringMatching(new RegExp(`^${recordingId}$`)),
|
|
roomId: expect.stringMatching(new RegExp(`^${roomId}$`)),
|
|
roomName: expect.stringMatching(new RegExp(`^${roomName}$`)),
|
|
...(isRecFinished ? { status: expect.any(String) } : {}),
|
|
...(isRecFinished ? { duration: expect.any(Number) } : {}),
|
|
...(isRecFinished ? { startDate: expect.any(Number) } : {}),
|
|
...(isRecFinished ? { endDate: expect.any(Number) } : {}),
|
|
...(isRecFinished ? { size: expect.any(Number) } : {}),
|
|
filename: expect.any(String),
|
|
...(isRecFinished ? { details: expect.any(String) } : {})
|
|
})
|
|
);
|
|
|
|
expect(body.status).toBeDefined();
|
|
|
|
if (status !== undefined) {
|
|
expect(body.status).toBe(status);
|
|
}
|
|
|
|
if (isRecFinished) {
|
|
expect(body.endDate).toBeGreaterThanOrEqual(body.startDate);
|
|
expect(body.duration).toBeGreaterThanOrEqual(0);
|
|
}
|
|
|
|
if (isRecFinished && maxSecDuration) {
|
|
expect(body.duration).toBeLessThanOrEqual(maxSecDuration);
|
|
|
|
const computedSec = (body.endDate - body.startDate) / 1000;
|
|
const diffSec = Math.abs(maxSecDuration - computedSec);
|
|
// Estimate 5 seconds of tolerace because of time to start/stop recording
|
|
expect(diffSec).toBeLessThanOrEqual(5);
|
|
}
|
|
};
|
|
|
|
export const expectSuccessListRecordingResponse = (
|
|
response: any,
|
|
recordingLength: number,
|
|
isTruncated: boolean,
|
|
nextPageToken: boolean,
|
|
maxItems = 10
|
|
) => {
|
|
expect(response.status).toBe(200);
|
|
expect(response.body).toBeDefined();
|
|
expect(response.body.recordings).toBeDefined();
|
|
expect(Array.isArray(response.body.recordings)).toBe(true);
|
|
expect(response.body.recordings.length).toBe(recordingLength);
|
|
expect(response.body.pagination).toBeDefined();
|
|
expect(response.body.pagination.isTruncated).toBe(isTruncated);
|
|
|
|
if (nextPageToken) {
|
|
expect(response.body.pagination.nextPageToken).toBeDefined();
|
|
} else {
|
|
expect(response.body.pagination.nextPageToken).toBeUndefined();
|
|
}
|
|
|
|
expect(response.body.pagination.maxItems).toBeDefined();
|
|
expect(response.body.pagination.maxItems).toBeGreaterThan(0);
|
|
expect(response.body.pagination.maxItems).toBeLessThanOrEqual(100);
|
|
expect(response.body.pagination.maxItems).toBe(maxItems);
|
|
};
|
|
|
|
export const expectValidGetRecordingUrlResponse = (response: any, recordingId: string) => {
|
|
expect(response.status).toBe(200);
|
|
const recordingUrl = response.body.url;
|
|
expect(recordingUrl).toBeDefined();
|
|
|
|
const parsedUrl = new URL(recordingUrl);
|
|
expect(parsedUrl.pathname).toBe(`/recording/${recordingId}`);
|
|
expect(parsedUrl.searchParams.get('secret')).toBeDefined();
|
|
};
|
|
|
|
export const expectValidRoomRolesAndPermissionsResponse = (response: any, roomId: string) => {
|
|
expect(response.status).toBe(200);
|
|
expect(response.body).toEqual(
|
|
expect.arrayContaining([
|
|
{
|
|
role: ParticipantRole.MODERATOR,
|
|
permissions: getPermissions(roomId, ParticipantRole.MODERATOR)
|
|
},
|
|
{
|
|
role: ParticipantRole.SPEAKER,
|
|
permissions: getPermissions(roomId, ParticipantRole.SPEAKER)
|
|
}
|
|
])
|
|
);
|
|
};
|
|
|
|
export const expectValidRoomRoleAndPermissionsResponse = (
|
|
response: any,
|
|
roomId: string,
|
|
participantRole: ParticipantRole
|
|
) => {
|
|
expect(response.status).toBe(200);
|
|
expect(response.body).toEqual({
|
|
role: participantRole,
|
|
permissions: getPermissions(roomId, participantRole)
|
|
});
|
|
};
|
|
|
|
export const getPermissions = (
|
|
roomId: string,
|
|
role: ParticipantRole,
|
|
addJoinPermission = true
|
|
): ParticipantPermissions => {
|
|
switch (role) {
|
|
case ParticipantRole.MODERATOR:
|
|
return {
|
|
livekit: {
|
|
roomJoin: addJoinPermission,
|
|
room: roomId,
|
|
canPublish: true,
|
|
canSubscribe: true,
|
|
canPublishData: true,
|
|
canUpdateOwnMetadata: true
|
|
},
|
|
openvidu: {
|
|
canRecord: true,
|
|
canChat: true,
|
|
canChangeVirtualBackground: true
|
|
}
|
|
};
|
|
case ParticipantRole.SPEAKER:
|
|
return {
|
|
livekit: {
|
|
roomJoin: addJoinPermission,
|
|
room: roomId,
|
|
canPublish: true,
|
|
canSubscribe: true,
|
|
canPublishData: true,
|
|
canUpdateOwnMetadata: true
|
|
},
|
|
openvidu: {
|
|
canRecord: false,
|
|
canChat: true,
|
|
canChangeVirtualBackground: true
|
|
}
|
|
};
|
|
default:
|
|
throw new Error(`Unknown role ${role}`);
|
|
}
|
|
};
|
|
|
|
export const expectValidParticipantTokenResponse = (
|
|
response: any,
|
|
roomId: string,
|
|
participantRole: ParticipantRole,
|
|
participantName?: string,
|
|
participantIdentity?: string,
|
|
otherRoles: ParticipantRole[] = []
|
|
) => {
|
|
expect(response.status).toBe(200);
|
|
expect(response.body).toHaveProperty('token');
|
|
|
|
const token = response.body.token;
|
|
const decodedToken = decodeJWTToken(token);
|
|
|
|
const permissions = getPermissions(roomId, participantRole, !!participantName);
|
|
const rolesAndPermissions = otherRoles.map((role) => ({
|
|
role,
|
|
permissions: getPermissions(roomId, role, !!participantName).openvidu
|
|
}));
|
|
|
|
if (!rolesAndPermissions.some((r) => r.role === participantRole)) {
|
|
rolesAndPermissions.push({
|
|
role: participantRole,
|
|
permissions: permissions.openvidu
|
|
});
|
|
}
|
|
|
|
if (participantName) {
|
|
expect(decodedToken).toHaveProperty('name', participantName);
|
|
expect(decodedToken).toHaveProperty('sub');
|
|
|
|
if (participantIdentity) {
|
|
expect(decodedToken.sub).toBe(participantIdentity);
|
|
} else {
|
|
expect(decodedToken.sub).toContain(participantName.replace(/\s+/g, '')); // Ensure sub contains the name without spaces
|
|
}
|
|
} else {
|
|
expect(decodedToken).not.toHaveProperty('name');
|
|
expect(decodedToken).not.toHaveProperty('sub');
|
|
}
|
|
|
|
expect(decodedToken).toHaveProperty('video', permissions.livekit);
|
|
expect(decodedToken).toHaveProperty('metadata');
|
|
const metadata = JSON.parse(decodedToken.metadata || '{}');
|
|
expect(metadata).toHaveProperty('roles');
|
|
expect(metadata.roles).toEqual(expect.arrayContaining(rolesAndPermissions));
|
|
expect(metadata).toHaveProperty('selectedRole', participantRole);
|
|
};
|
|
|
|
export const expectValidRecordingTokenResponse = (
|
|
response: any,
|
|
roomId: string,
|
|
participantRole: ParticipantRole,
|
|
canRetrieveRecordings: boolean,
|
|
canDeleteRecordings: boolean
|
|
) => {
|
|
expect(response.status).toBe(200);
|
|
expect(response.body).toHaveProperty('token');
|
|
|
|
const token = response.body.token;
|
|
const decodedToken = decodeJWTToken(token);
|
|
|
|
expect(decodedToken).toHaveProperty('video', {
|
|
room: roomId
|
|
});
|
|
expect(decodedToken).toHaveProperty('metadata');
|
|
const metadata = JSON.parse(decodedToken.metadata || '{}');
|
|
expect(metadata).toHaveProperty('role', participantRole);
|
|
expect(metadata).toHaveProperty('recordingPermissions', {
|
|
canRetrieveRecordings,
|
|
canDeleteRecordings
|
|
});
|
|
};
|
|
|
|
const decodeJWTToken = (token: string) => {
|
|
const tokenService = container.get(TokenService);
|
|
return tokenService.getClaimsIgnoringExpiration(token);
|
|
};
|