mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2025-12-30 11:22:31 +00:00
test: Add JaaS-specific tests: join MUC, visitors, maxOccupants. (#16270)
* test: Add tests for joining a JaaS MUC with different token options. * ref: Refactor token generation and usage * ref: Reduce usage of global context * test: Add a maxOccupants jaas test.
This commit is contained in:
@@ -1,47 +1,13 @@
|
||||
# Ignore certificate errors (self-signed certificates)
|
||||
#ALLOW_INSECURE_CERTS=true
|
||||
|
||||
# The base url that will be used for the test (default will be using "https://alpha.jitsi.net")
|
||||
# If there is a tenant in the URL it must end with a slash (e.g. "https://alpha.jitsi.net/sometenant/")
|
||||
#BASE_URL=
|
||||
|
||||
# Room name suffix to use when creating new room names
|
||||
#ROOM_NAME_SUFFIX=
|
||||
|
||||
# Room name prefix to use when creating new room names
|
||||
#ROOM_NAME_PREFIX=
|
||||
|
||||
# To be able to match a domain to a specific address
|
||||
# The format is "MAP example.com 1.2.3.4"
|
||||
#RESOLVER_RULES=
|
||||
|
||||
# Ignore certificate errors (self-signed certificates)
|
||||
#ALLOW_INSECURE_CERTS=true
|
||||
|
||||
# Whether to run the browser in headless mode
|
||||
#HEADLESS=false
|
||||
|
||||
# The path to the browser video capture file
|
||||
#VIDEO_CAPTURE_FILE=tests/resources/FourPeople_1280x720_30.y4m
|
||||
|
||||
# The tenant used when executing the iframeAPI tests, will override any tenant from BASE_URL if any
|
||||
#IFRAME_TENANT=
|
||||
|
||||
# The grid host url (https://mygrid.com/wd/hub)
|
||||
#GRID_HOST_URL=
|
||||
|
||||
# The path to the private key used for generating JWT token (.pk)
|
||||
#JWT_PRIVATE_KEY_PATH=
|
||||
# The kid to use in the token
|
||||
#JWT_KID=
|
||||
|
||||
# An access token to use to create meetings (used for the first participant)
|
||||
#JWT_ACCESS_TOKEN=
|
||||
|
||||
# The count of workers that execute the tests in parallel
|
||||
# MAX_INSTANCES=1
|
||||
|
||||
# The address of the webhooks proxy used to test the webhooks feature (e.g. wss://your.service/?tenant=sometenant)
|
||||
#WEBHOOKS_PROXY_URL=
|
||||
# A shared secret to authenticate the webhook proxy connection
|
||||
#WEBHOOKS_PROXY_SHARED_SECRET=
|
||||
# Whether to use beta for the first participants
|
||||
#BROWSER_CHROME_BETA=false
|
||||
#BROWSER_FF_BETA=false
|
||||
|
||||
# A rest URL to be used by dial-in tests to invite jigasi to the conference
|
||||
#DIAL_IN_REST_URL=
|
||||
@@ -49,12 +15,60 @@
|
||||
# A destination number to dialout, that auto answers and sends media
|
||||
#DIAL_OUT_URL=
|
||||
|
||||
# The grid host url (https://mygrid.com/wd/hub)
|
||||
#GRID_HOST_URL=
|
||||
|
||||
# Whether to run the browser in headless mode
|
||||
#HEADLESS=false
|
||||
|
||||
# The tenant used when executing the iframeAPI tests, will override any tenant from BASE_URL if any
|
||||
#IFRAME_TENANT=
|
||||
|
||||
# Configure properties for jaas-specific tests (specs/jaas). Note that some of the iFrame tests can also
|
||||
# be used to test JaaS, they are configured separately via IFRAME_TENANT, JWT_KID, JWT_PRIVATE_KEY_PATH.
|
||||
# Domain for the JaaS environment, e.g. stage.8x8.vc
|
||||
JAAS_DOMAIN=
|
||||
# The key ID
|
||||
JAAS_KID=
|
||||
# The path to the private key used for generating JWT token (.pk) for jaas-specific tests
|
||||
JAAS_PRIVATE_KEY_PATH=
|
||||
# The JaaS tenant, e.g. vpaas-magic-cookie-abcdabcd1234567890
|
||||
JAAS_TENANT=
|
||||
|
||||
# An access token to use to create meetings (used for the first participant)
|
||||
#JWT_ACCESS_TOKEN=
|
||||
|
||||
# The kid to use in the token for non-jaas-specific tests (though it could be a jaas key).
|
||||
#JWT_KID=
|
||||
|
||||
# The path to the private key used for generating JWT token (.pk) for non-jaas-specific tests (though it could be a
|
||||
# jaas key).
|
||||
#JWT_PRIVATE_KEY_PATH=
|
||||
|
||||
# The count of workers that execute the tests in parallel
|
||||
# MAX_INSTANCES=1
|
||||
|
||||
# To be able to match a domain to a specific address
|
||||
# The format is "MAP example.com 1.2.3.4"
|
||||
#RESOLVER_RULES=
|
||||
|
||||
# Room name prefix to use when creating new room names
|
||||
#ROOM_NAME_PREFIX=
|
||||
|
||||
# Room name suffix to use when creating new room names
|
||||
#ROOM_NAME_SUFFIX=
|
||||
|
||||
# A destination number to dialout, that auto answer and sends media audio and video
|
||||
#SIP_JIBRI_DIAL_OUT_URL=
|
||||
|
||||
# Whether to use beta for the first participants
|
||||
#BROWSER_CHROME_BETA=false
|
||||
#BROWSER_FF_BETA=false
|
||||
# The path to the browser video capture file
|
||||
#VIDEO_CAPTURE_FILE=tests/resources/FourPeople_1280x720_30.y4m
|
||||
|
||||
# A shared secret to authenticate the webhook proxy connection
|
||||
#WEBHOOKS_PROXY_SHARED_SECRET=
|
||||
|
||||
# The address of the webhooks proxy used to test the webhooks feature (e.g. wss://your.service/?tenant=sometenant)
|
||||
#WEBHOOKS_PROXY_URL=
|
||||
|
||||
# A stream key abd broadcast ID that can be used by the tests to stream to YouTube
|
||||
#YTUBE_TEST_STREAM_KEY=
|
||||
|
||||
@@ -24,6 +24,7 @@ import VideoQualityDialog from '../pageobjects/VideoQualityDialog';
|
||||
import Visitors from '../pageobjects/Visitors';
|
||||
|
||||
import { LOG_PREFIX, logInfo } from './browserLogger';
|
||||
import { IToken } from './token';
|
||||
import { IContext, IJoinOptions } from './types';
|
||||
|
||||
export const P1 = 'p1';
|
||||
@@ -49,7 +50,10 @@ export class Participant {
|
||||
*/
|
||||
private _name: string;
|
||||
private _endpointId: string;
|
||||
private _jwt?: string;
|
||||
/**
|
||||
* The token that this participant was initialized with.
|
||||
*/
|
||||
private _token?: IToken;
|
||||
|
||||
/**
|
||||
* The default config to use when joining.
|
||||
@@ -110,11 +114,11 @@ export class Participant {
|
||||
* Creates a participant with given name.
|
||||
*
|
||||
* @param {string} name - The name of the participant.
|
||||
* @param {string }jwt - The jwt if any.
|
||||
* @param {string} token - The token if any.
|
||||
*/
|
||||
constructor(name: string, jwt?: string) {
|
||||
constructor(name: string, token?: IToken) {
|
||||
this._name = name;
|
||||
this._jwt = jwt;
|
||||
this._token = token;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -200,13 +204,13 @@ export class Participant {
|
||||
};
|
||||
}
|
||||
|
||||
if (ctx.iframeAPI) {
|
||||
if (ctx.testProperties.useIFrameApi) {
|
||||
config.room = 'iframeAPITest.html';
|
||||
}
|
||||
|
||||
let url = urlObjectToString(config) || '';
|
||||
|
||||
if (ctx.iframeAPI) {
|
||||
if (ctx.testProperties.useIFrameApi) {
|
||||
const baseUrl = new URL(this.driver.options.baseUrl || '');
|
||||
|
||||
// @ts-ignore
|
||||
@@ -219,8 +223,8 @@ export class Participant {
|
||||
url = `${url}&tenant="${baseUrl.pathname.substring(1)}"`;
|
||||
}
|
||||
}
|
||||
if (this._jwt) {
|
||||
url = `${url}&jwt="${this._jwt}"`;
|
||||
if (this._token?.jwt) {
|
||||
url = `${url}&jwt="${this._token.jwt}"`;
|
||||
}
|
||||
|
||||
if (options.baseUrl) {
|
||||
@@ -231,7 +235,8 @@ export class Participant {
|
||||
|
||||
let urlToLoad = url.startsWith('/') ? url.substring(1) : url;
|
||||
|
||||
if (options.preferGenerateToken && !ctx.iframeAPI && ctx.isJaasAvailable() && process.env.IFRAME_TENANT) {
|
||||
if (options.preferGenerateToken && !ctx.testProperties.useIFrameApi
|
||||
&& process.env.JWT_KID?.startsWith('vpaas-magic-cookie-') && process.env.IFRAME_TENANT) {
|
||||
// This to enables tests like invite, which can force using the jaas auth instead of the provided token
|
||||
urlToLoad = `/${process.env.IFRAME_TENANT}/${urlToLoad}`;
|
||||
}
|
||||
@@ -241,7 +246,7 @@ export class Participant {
|
||||
|
||||
await this.waitForPageToLoad();
|
||||
|
||||
if (ctx.iframeAPI) {
|
||||
if (ctx.testProperties.useIFrameApi) {
|
||||
const mainFrame = this.driver.$('iframe');
|
||||
|
||||
await this.driver.switchFrame(mainFrame);
|
||||
@@ -338,6 +343,10 @@ export class Participant {
|
||||
&& APP.store?.getState()['features/base/participants']?.local?.role === 'moderator');
|
||||
}
|
||||
|
||||
async isVisitor() {
|
||||
return await this.execute(() => APP?.store?.getState()['features/visitors']?.iAmVisitor || false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the meeting supports breakout rooms.
|
||||
*/
|
||||
@@ -447,7 +456,7 @@ export class Participant {
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for number of participants.
|
||||
* Waits until the number of participants is exactly the given number.
|
||||
*
|
||||
* @param {number} number - The number of participant to wait for.
|
||||
* @param {string} msg - A custom message to use.
|
||||
@@ -891,4 +900,11 @@ export class Participant {
|
||||
return this.driver.$(`//span[@id="participant_${endpointId}" and contains(@class, "dominant-speaker")]`)
|
||||
.waitForDisplayed({ timeout: 5_000 });
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the token that this participant was initialized with.
|
||||
*/
|
||||
getToken(): IToken | undefined {
|
||||
return this._token;
|
||||
}
|
||||
}
|
||||
|
||||
43
tests/helpers/TestProperties.ts
Normal file
43
tests/helpers/TestProperties.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* An interface that tests can export (as a TEST_PROPERTIES property) to define what they require.
|
||||
*/
|
||||
export type ITestProperties = {
|
||||
/** The test uses the iFrame API. */
|
||||
useIFrameApi: boolean;
|
||||
/** The test requires jaas, it should be skipped when the jaas configuration is not enabled. */
|
||||
useJaas: boolean;
|
||||
/** The test requires the webhook proxy. */
|
||||
useWebhookProxy: boolean;
|
||||
};
|
||||
|
||||
const defaultProperties: ITestProperties = {
|
||||
useIFrameApi: false,
|
||||
useWebhookProxy: false,
|
||||
useJaas: false
|
||||
};
|
||||
|
||||
const testProperties: Record<string, ITestProperties> = {};
|
||||
|
||||
/**
|
||||
* Set properties for a test file. This was needed because I couldn't find a hook that executes with describe() before
|
||||
* the code in wdio.conf.ts's before() hook. The intention is for tests to execute this directly. The properties don't
|
||||
* change dynamically.
|
||||
*
|
||||
* @param filename the absolute path to the test file
|
||||
* @param properties the properties to set for the test file, defaults will be applied for missing properties
|
||||
*/
|
||||
export function setTestProperties(filename: string, properties: Partial<ITestProperties>): void {
|
||||
if (testProperties[filename]) {
|
||||
console.warn(`Test properties for ${filename} are already set. Overwriting.`);
|
||||
}
|
||||
|
||||
testProperties[filename] = { ...defaultProperties, ...properties };
|
||||
}
|
||||
|
||||
/**
|
||||
* @param testFilePath - The absolute path to the test file
|
||||
* @returns Promise<ITestProperties> - The test properties with defaults applied
|
||||
*/
|
||||
export async function getTestProperties(testFilePath: string): Promise<ITestProperties> {
|
||||
return testProperties[testFilePath] || { ...defaultProperties };
|
||||
}
|
||||
@@ -1,9 +1,7 @@
|
||||
import fs from 'fs';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import process from 'node:process';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { P1, P2, P3, P4, Participant } from './Participant';
|
||||
import { IToken, generateToken } from './token';
|
||||
import { IContext, IJoinOptions } from './types';
|
||||
|
||||
const SUBJECT_XPATH = '//div[starts-with(@class, "subject-text")]';
|
||||
@@ -171,7 +169,7 @@ async function _joinParticipant( // eslint-disable-line max-params
|
||||
const p = ctx[name] as Participant;
|
||||
|
||||
if (p) {
|
||||
if (ctx.iframeAPI) {
|
||||
if (ctx.testProperties.useIFrameApi) {
|
||||
await p.switchInPage();
|
||||
}
|
||||
|
||||
@@ -179,7 +177,7 @@ async function _joinParticipant( // eslint-disable-line max-params
|
||||
return;
|
||||
}
|
||||
|
||||
if (ctx.iframeAPI) {
|
||||
if (ctx.testProperties.useIFrameApi) {
|
||||
// when loading url make sure we are on the top page context or strange errors may occur
|
||||
await p.switchToAPI();
|
||||
}
|
||||
@@ -190,25 +188,33 @@ async function _joinParticipant( // eslint-disable-line max-params
|
||||
// we want the participant instance re-recreated so we clear any kept state, like endpoint ID
|
||||
}
|
||||
|
||||
let jwtToken;
|
||||
let token: IToken = { jwt: '' };
|
||||
|
||||
if (name === P1) {
|
||||
if (!options?.skipFirstModerator) {
|
||||
// we prioritize the access token when iframe is not used and private key is set,
|
||||
// otherwise if private key is not specified we use the access token if set
|
||||
if (process.env.JWT_ACCESS_TOKEN
|
||||
&& ((ctx.jwtPrivateKeyPath && !ctx.iframeAPI && !options?.preferGenerateToken)
|
||||
&& ((ctx.jwtPrivateKeyPath && !ctx.testProperties.useIFrameApi && !options?.preferGenerateToken)
|
||||
|| !ctx.jwtPrivateKeyPath)) {
|
||||
jwtToken = process.env.JWT_ACCESS_TOKEN;
|
||||
token = { jwt: process.env.JWT_ACCESS_TOKEN };
|
||||
} else if (ctx.jwtPrivateKeyPath) {
|
||||
jwtToken = getToken(ctx, name, options);
|
||||
token = generateToken({
|
||||
...options?.tokenOptions,
|
||||
displayName: name,
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if (name === P2) {
|
||||
jwtToken = options?.preferGenerateToken ? getToken(ctx, P2, options) : undefined;
|
||||
if (options?.preferGenerateToken) {
|
||||
token = generateToken({
|
||||
...options?.tokenOptions,
|
||||
displayName: name,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const newParticipant = new Participant(name, jwtToken);
|
||||
const newParticipant = new Participant(name, token);
|
||||
|
||||
// set the new participant instance
|
||||
// @ts-ignore
|
||||
@@ -283,64 +289,6 @@ export async function muteVideoAndCheck(testee: Participant, observer: Participa
|
||||
await observer.getParticipantsPane().assertVideoMuteIconIsDisplayed(testee);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a JWT token for a moderator.
|
||||
*/
|
||||
function getToken(ctx: IContext, displayName: string, options?: IJoinOptions) {
|
||||
const keyid = process.env.JWT_KID;
|
||||
const headers = {
|
||||
algorithm: 'RS256',
|
||||
noTimestamp: true,
|
||||
expiresIn: '24h',
|
||||
keyid
|
||||
};
|
||||
|
||||
if (!keyid) {
|
||||
console.error('JWT_KID is not set');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const key = fs.readFileSync(ctx.jwtPrivateKeyPath);
|
||||
|
||||
const payload = {
|
||||
'aud': 'jitsi',
|
||||
'iss': 'chat',
|
||||
'sub': keyid.substring(0, keyid.indexOf('/')),
|
||||
'context': {
|
||||
'user': {
|
||||
'name': displayName,
|
||||
'id': uuidv4(),
|
||||
'avatar': 'https://avatars0.githubusercontent.com/u/3671647',
|
||||
'email': 'john.doe@jitsi.org'
|
||||
},
|
||||
'group': uuidv4(),
|
||||
'features': {
|
||||
'outbound-call': 'true',
|
||||
'transcription': 'true',
|
||||
'recording': 'true',
|
||||
'sip-outbound-call': true,
|
||||
'livestreaming': true
|
||||
},
|
||||
},
|
||||
'room': '*'
|
||||
};
|
||||
|
||||
// if the moderator is set, or options are missing, we assume moderator
|
||||
if (options?.moderator || !options) {
|
||||
// @ts-ignore
|
||||
payload.context.user.moderator = true;
|
||||
} else if (options.visitor) {
|
||||
// @ts-ignore
|
||||
payload.context.user.role = 'visitor';
|
||||
}
|
||||
|
||||
ctx.data[`${displayName}-jwt-payload`] = payload;
|
||||
|
||||
// @ts-ignore
|
||||
return jwt.sign(payload, key, headers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a JID string.
|
||||
* @param str the string to parse.
|
||||
|
||||
132
tests/helpers/token.ts
Normal file
132
tests/helpers/token.ts
Normal file
@@ -0,0 +1,132 @@
|
||||
import fs from 'fs';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
export type ITokenOptions = {
|
||||
displayName?: string;
|
||||
/**
|
||||
* The duration for which the token is valid, e.g. "1h" for one hour.
|
||||
*/
|
||||
exp?: string;
|
||||
/**
|
||||
* The key ID to use for the token.
|
||||
* If not provided, JWT_KID will be used from the environment variables.
|
||||
*/
|
||||
keyId?: string;
|
||||
/**
|
||||
* The path to the private key file used to sign the token.
|
||||
* If not provided, JWT_PRIVATE_KEY_PATH will be used from the environment variables.
|
||||
*/
|
||||
keyPath?: string;
|
||||
/**
|
||||
* Whether to set the 'moderator' flag.
|
||||
*/
|
||||
moderator?: boolean;
|
||||
/**
|
||||
* The room for which the token is valid, or '*'. Defaults to '*'.
|
||||
*/
|
||||
room?: string;
|
||||
sub?: string;
|
||||
/**
|
||||
* Whether to set the 'visitor' flag.
|
||||
*/
|
||||
visitor?: boolean;
|
||||
};
|
||||
|
||||
export type IToken = {
|
||||
/**
|
||||
* The JWT headers, for easy reference.
|
||||
*/
|
||||
headers?: any;
|
||||
/**
|
||||
* The signed JWT.
|
||||
*/
|
||||
jwt: string;
|
||||
/**
|
||||
* The options used to generate the token.
|
||||
*/
|
||||
options?: ITokenOptions;
|
||||
/**
|
||||
* The token's payload, for easy reference.
|
||||
*/
|
||||
payload?: any;
|
||||
};
|
||||
|
||||
export function generatePayload(options: ITokenOptions): any {
|
||||
const payload = {
|
||||
'aud': 'jitsi',
|
||||
'iss': 'chat',
|
||||
'sub': options?.sub || '',
|
||||
'context': {
|
||||
'user': {
|
||||
'name': options.displayName,
|
||||
'id': uuidv4(),
|
||||
'avatar': 'https://avatars0.githubusercontent.com/u/3671647',
|
||||
'email': 'john.doe@jitsi.org'
|
||||
},
|
||||
'group': uuidv4(),
|
||||
'features': {
|
||||
'outbound-call': 'true',
|
||||
'transcription': 'true',
|
||||
'recording': 'true',
|
||||
'sip-outbound-call': true,
|
||||
'livestreaming': true
|
||||
},
|
||||
},
|
||||
'room': options.room || '*'
|
||||
};
|
||||
|
||||
if (options.moderator) {
|
||||
// @ts-ignore
|
||||
payload.context.user.moderator = true;
|
||||
} else if (options.visitor) {
|
||||
// @ts-ignore
|
||||
payload.context.user.role = 'visitor';
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a signed token.
|
||||
*/
|
||||
export function generateToken(options: ITokenOptions): IToken {
|
||||
const keyId = options.keyId || process.env.JWT_KID;
|
||||
const keyPath = options.keyPath || process.env.JWT_PRIVATE_KEY_PATH;
|
||||
const headers = {
|
||||
algorithm: 'RS256',
|
||||
noTimestamp: true,
|
||||
expiresIn: options.exp || '24h',
|
||||
keyid: keyId,
|
||||
};
|
||||
|
||||
if (!keyId) {
|
||||
throw new Error('JWT_KID is not set');
|
||||
}
|
||||
|
||||
if (!keyPath) {
|
||||
throw new Error('JWT_PRIVATE_KEY_PATH is not set');
|
||||
}
|
||||
|
||||
const key = fs.readFileSync(keyPath);
|
||||
const payload = generatePayload({
|
||||
...options,
|
||||
displayName: options?.displayName || '',
|
||||
sub: keyId.substring(0, keyId.indexOf('/'))
|
||||
});
|
||||
|
||||
return {
|
||||
headers,
|
||||
// @ts-ignore
|
||||
jwt: jwt.sign(payload, key, headers),
|
||||
options,
|
||||
payload
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generated a signed token and return just the JWT string.
|
||||
*/
|
||||
export function generateJwt(options: ITokenOptions): string {
|
||||
return generateToken(options).jwt;
|
||||
}
|
||||
@@ -1,12 +1,16 @@
|
||||
import { IConfig } from '../../react/features/base/config/configType';
|
||||
|
||||
import type { Participant } from './Participant';
|
||||
import { ITestProperties } from './TestProperties';
|
||||
import type WebhookProxy from './WebhookProxy';
|
||||
import { ITokenOptions } from './token';
|
||||
|
||||
export type IContext = {
|
||||
data: any;
|
||||
iframeAPI: boolean;
|
||||
isJaasAvailable: () => boolean;
|
||||
/**
|
||||
* Whether the configuration specifies a JaaS account for the iFrame API tests.
|
||||
*/
|
||||
iFrameUsesJaas: boolean;
|
||||
jwtKid: string;
|
||||
jwtPrivateKeyPath: string;
|
||||
keepAlive: Array<any>;
|
||||
@@ -16,6 +20,7 @@ export type IContext = {
|
||||
p4: Participant;
|
||||
roomName: string;
|
||||
skipSuiteTests: boolean;
|
||||
testProperties: ITestProperties;
|
||||
times: any;
|
||||
webhooksProxy: WebhookProxy;
|
||||
};
|
||||
@@ -37,11 +42,6 @@ export type IJoinOptions = {
|
||||
*/
|
||||
displayName?: string;
|
||||
|
||||
/**
|
||||
* Whether to create a moderator token for joining.
|
||||
*/
|
||||
moderator?: boolean;
|
||||
|
||||
/**
|
||||
* When joining the first participant and jwt singing material is available and a provided token
|
||||
* is available, prefer generating a new token for the first participant.
|
||||
@@ -70,7 +70,7 @@ export type IJoinOptions = {
|
||||
skipWaitToJoin?: boolean;
|
||||
|
||||
/**
|
||||
* Whether to create a visitor token for joining.
|
||||
* Options used when generating a token.
|
||||
*/
|
||||
visitor?: boolean;
|
||||
tokenOptions?: ITokenOptions;
|
||||
};
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
import { expect } from '@wdio/globals';
|
||||
|
||||
import type { Participant } from '../../helpers/Participant';
|
||||
import { setTestProperties } from '../../helpers/TestProperties';
|
||||
import { ensureTwoParticipants } from '../../helpers/participants';
|
||||
import { fetchJson } from '../../helpers/utils';
|
||||
|
||||
setTestProperties(__filename, {
|
||||
useIFrameApi: true,
|
||||
useWebhookProxy: true
|
||||
});
|
||||
|
||||
describe('Chat', () => {
|
||||
it('joining the meeting', async () => {
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
import { isEqual } from 'lodash-es';
|
||||
|
||||
import { P1, P2, Participant } from '../../helpers/Participant';
|
||||
import { setTestProperties } from '../../helpers/TestProperties';
|
||||
import { ensureTwoParticipants, parseJid } from '../../helpers/participants';
|
||||
import { IContext } from '../../helpers/types';
|
||||
|
||||
setTestProperties(__filename, {
|
||||
useIFrameApi: true,
|
||||
useWebhookProxy: true
|
||||
});
|
||||
|
||||
/**
|
||||
* Tests PARTICIPANT_LEFT webhook.
|
||||
*/
|
||||
async function checkParticipantLeftHook(ctx: IContext, p: Participant, reason: string, checkId = false) {
|
||||
async function checkParticipantLeftHook(ctx: IContext, p: Participant, reason: string, checkId = false, conferenceJid: string) {
|
||||
const { webhooksProxy } = ctx;
|
||||
|
||||
if (webhooksProxy) {
|
||||
@@ -28,14 +34,14 @@ async function checkParticipantLeftHook(ctx: IContext, p: Participant, reason: s
|
||||
} = await webhooksProxy.waitForEvent('PARTICIPANT_LEFT');
|
||||
|
||||
expect('PARTICIPANT_LEFT').toBe(event.eventType);
|
||||
expect(event.data.conference).toBe(ctx.data.conferenceJid);
|
||||
expect(event.data.conference).toBe(conferenceJid);
|
||||
expect(event.data.disconnectReason).toBe(reason);
|
||||
expect(event.data.isBreakout).toBe(false);
|
||||
expect(event.data.participantId).toBe(await p.getEndpointId());
|
||||
expect(event.data.name).toBe(p.name);
|
||||
|
||||
if (checkId) {
|
||||
const jwtPayload = ctx.data[`${p.name}-jwt-payload`];
|
||||
const jwtPayload = p.getToken()?.payload;
|
||||
|
||||
expect(event.data.id).toBe(jwtPayload?.context?.user?.id);
|
||||
expect(event.data.group).toBe(jwtPayload?.context?.group);
|
||||
@@ -45,6 +51,8 @@ async function checkParticipantLeftHook(ctx: IContext, p: Participant, reason: s
|
||||
}
|
||||
|
||||
describe('Participants presence', () => {
|
||||
let conferenceJid: string = '';
|
||||
|
||||
it('joining the meeting', async () => {
|
||||
// ensure 2 participants one moderator and one guest, we will load both with iframeAPI
|
||||
await ensureTwoParticipants(ctx);
|
||||
@@ -113,7 +121,7 @@ describe('Participants presence', () => {
|
||||
|
||||
const { node, resource } = parseJid(roomsInfo.jid);
|
||||
|
||||
ctx.data.conferenceJid = roomsInfo.jid.substring(0, roomsInfo.jid.indexOf('/'));
|
||||
conferenceJid = roomsInfo.jid.substring(0, roomsInfo.jid.indexOf('/'));
|
||||
|
||||
const p1EpId = await p1.getEndpointId();
|
||||
|
||||
@@ -135,7 +143,7 @@ describe('Participants presence', () => {
|
||||
} = await webhooksProxy.waitForEvent('ROOM_CREATED');
|
||||
|
||||
expect('ROOM_CREATED').toBe(event.eventType);
|
||||
expect(event.data.conference).toBe(ctx.data.conferenceJid);
|
||||
expect(event.data.conference).toBe(conferenceJid);
|
||||
expect(event.data.isBreakout).toBe(false);
|
||||
}
|
||||
}
|
||||
@@ -231,7 +239,7 @@ describe('Participants presence', () => {
|
||||
|
||||
const roomsInfo = (await p1.getIframeAPI().getRoomsInfo()).rooms[0];
|
||||
|
||||
ctx.data.conferenceJid = roomsInfo.jid.substring(0, roomsInfo.jid.indexOf('/'));
|
||||
conferenceJid = roomsInfo.jid.substring(0, roomsInfo.jid.indexOf('/'));
|
||||
|
||||
await p1.getIframeAPI().addEventListener('participantKickedOut');
|
||||
await p2.getIframeAPI().addEventListener('participantKickedOut');
|
||||
@@ -248,7 +256,7 @@ describe('Participants presence', () => {
|
||||
timeoutMsg: 'participantKickedOut event not received on p2 side'
|
||||
});
|
||||
|
||||
await checkParticipantLeftHook(ctx, p2, 'kicked', true);
|
||||
await checkParticipantLeftHook(ctx, p2, 'kicked', true, conferenceJid);
|
||||
|
||||
expect(eventP1).toBeDefined();
|
||||
expect(eventP2).toBeDefined();
|
||||
@@ -315,7 +323,7 @@ describe('Participants presence', () => {
|
||||
} = await webhooksProxy.waitForEvent('PARTICIPANT_JOINED');
|
||||
|
||||
expect('PARTICIPANT_JOINED').toBe(event.eventType);
|
||||
expect(event.data.conference).toBe(ctx.data.conferenceJid);
|
||||
expect(event.data.conference).toBe(conferenceJid);
|
||||
expect(event.data.isBreakout).toBe(false);
|
||||
expect(event.data.moderator).toBe(false);
|
||||
expect(event.data.name).toBe(await p2.getLocalDisplayName());
|
||||
@@ -385,7 +393,7 @@ describe('Participants presence', () => {
|
||||
expect(eventConferenceLeftP2).toBeDefined();
|
||||
expect(eventConferenceLeftP2.roomName).toBe(roomName);
|
||||
|
||||
await checkParticipantLeftHook(ctx, p2, 'left');
|
||||
await checkParticipantLeftHook(ctx, p2, 'left', false, conferenceJid);
|
||||
|
||||
const eventReadyToCloseP2 = await p2.driver.waitUntil(() => p2.getIframeAPI().getEventResult('readyToClose'), {
|
||||
timeout: 2000,
|
||||
@@ -396,7 +404,7 @@ describe('Participants presence', () => {
|
||||
});
|
||||
|
||||
it('dispose conference', async () => {
|
||||
const { data: { conferenceJid }, p1, roomName, webhooksProxy } = ctx;
|
||||
const { p1, roomName, webhooksProxy } = ctx;
|
||||
|
||||
await p1.switchToAPI();
|
||||
|
||||
@@ -414,7 +422,7 @@ describe('Participants presence', () => {
|
||||
expect(eventConferenceLeft).toBeDefined();
|
||||
expect(eventConferenceLeft.roomName).toBe(roomName);
|
||||
|
||||
await checkParticipantLeftHook(ctx, p1, 'left', true);
|
||||
await checkParticipantLeftHook(ctx, p1, 'left', true, conferenceJid);
|
||||
if (webhooksProxy) {
|
||||
// ROOM_DESTROYED webhook
|
||||
// @ts-ignore
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
import { expect } from '@wdio/globals';
|
||||
|
||||
import type { Participant } from '../../helpers/Participant';
|
||||
import { setTestProperties } from '../../helpers/TestProperties';
|
||||
import type WebhookProxy from '../../helpers/WebhookProxy';
|
||||
import { ensureOneParticipant, ensureTwoParticipants } from '../../helpers/participants';
|
||||
|
||||
setTestProperties(__filename, {
|
||||
useIFrameApi: true,
|
||||
useWebhookProxy: true
|
||||
});
|
||||
|
||||
describe('Transcriptions', () => {
|
||||
it('joining the meeting', async () => {
|
||||
await ensureOneParticipant(ctx);
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import { setTestProperties } from '../../helpers/TestProperties';
|
||||
import { ensureOneParticipant, ensureTwoParticipants } from '../../helpers/participants';
|
||||
|
||||
setTestProperties(__filename, {
|
||||
useIFrameApi: true,
|
||||
useWebhookProxy: true
|
||||
});
|
||||
|
||||
describe('Visitors', () => {
|
||||
it('joining the meeting', async () => {
|
||||
const { webhooksProxy } = ctx;
|
||||
@@ -33,7 +39,7 @@ describe('Visitors', () => {
|
||||
it('visitor joins', async () => {
|
||||
await ensureTwoParticipants(ctx, {
|
||||
preferGenerateToken: true,
|
||||
visitor: true,
|
||||
tokenOptions: { visitor: true },
|
||||
skipInMeetingChecks: true
|
||||
});
|
||||
|
||||
@@ -72,7 +78,7 @@ describe('Visitors', () => {
|
||||
eventType: string;
|
||||
} = await webhooksProxy.waitForEvent('PARTICIPANT_JOINED');
|
||||
|
||||
const jwtPayload = ctx.data[`${p2.name}-jwt-payload`];
|
||||
const jwtPayload = p2.getToken()?.payload;
|
||||
|
||||
expect('PARTICIPANT_JOINED').toBe(event.eventType);
|
||||
expect(event.data.avatar).toBe(jwtPayload.context.user.avatar);
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
import { expect } from '@wdio/globals';
|
||||
|
||||
import { setTestProperties } from '../../helpers/TestProperties';
|
||||
import { ensureOneParticipant, ensureTwoParticipants } from '../../helpers/participants';
|
||||
|
||||
setTestProperties(__filename, {
|
||||
useIFrameApi: true,
|
||||
useWebhookProxy: true
|
||||
});
|
||||
|
||||
describe('Visitors', () => {
|
||||
it('joining the meeting', async () => {
|
||||
const { webhooksProxy } = ctx;
|
||||
@@ -37,7 +43,7 @@ describe('Visitors', () => {
|
||||
it('go live', async () => {
|
||||
await ensureTwoParticipants(ctx, {
|
||||
preferGenerateToken: true,
|
||||
visitor: true,
|
||||
tokenOptions: { visitor: true },
|
||||
skipWaitToJoin: true,
|
||||
skipInMeetingChecks: true
|
||||
});
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { ensureOneParticipant, ensureTwoParticipants, joinSecondParticipant } from '../../helpers/participants';
|
||||
import type SecurityDialog from '../../pageobjects/SecurityDialog';
|
||||
|
||||
let roomKey: string;
|
||||
|
||||
/**
|
||||
* 1. Lock the room (make sure the image changes to locked)
|
||||
* 2. Join with a second browser/tab
|
||||
@@ -29,14 +31,14 @@ describe('Lock Room', () => {
|
||||
const p2PasswordDialog = p2.getPasswordDialog();
|
||||
|
||||
await p2PasswordDialog.waitForDialog();
|
||||
await p2PasswordDialog.submitPassword(`${ctx.data.roomKey}1234`);
|
||||
await p2PasswordDialog.submitPassword(`${roomKey}1234`);
|
||||
|
||||
// give sometime to the password prompt to disappear and send the password
|
||||
await p2.driver.pause(500);
|
||||
|
||||
// wait for password prompt
|
||||
await p2PasswordDialog.waitForDialog();
|
||||
await p2PasswordDialog.submitPassword(ctx.data.roomKey);
|
||||
await p2PasswordDialog.submitPassword(roomKey);
|
||||
|
||||
await p2.waitToJoinMUC();
|
||||
|
||||
@@ -106,7 +108,7 @@ describe('Lock Room', () => {
|
||||
const p2PasswordDialog = p2.getPasswordDialog();
|
||||
|
||||
await p2PasswordDialog.waitForDialog();
|
||||
await p2PasswordDialog.submitPassword(`${ctx.data.roomKey}1234`);
|
||||
await p2PasswordDialog.submitPassword(`${roomKey}1234`);
|
||||
|
||||
// give sometime to the password prompt to disappear and send the password
|
||||
await p2.driver.pause(500);
|
||||
@@ -132,7 +134,7 @@ describe('Lock Room', () => {
|
||||
* Participant1 locks the room.
|
||||
*/
|
||||
async function participant1LockRoom() {
|
||||
ctx.data.roomKey = `${Math.trunc(Math.random() * 1_000_000)}`;
|
||||
roomKey = `${Math.trunc(Math.random() * 1_000_000)}`;
|
||||
|
||||
const { p1 } = ctx;
|
||||
const p1SecurityDialog = p1.getSecurityDialog();
|
||||
@@ -142,7 +144,7 @@ async function participant1LockRoom() {
|
||||
|
||||
await waitForRoomLockState(p1SecurityDialog, false);
|
||||
|
||||
await p1SecurityDialog.addPassword(ctx.data.roomKey);
|
||||
await p1SecurityDialog.addPassword(roomKey);
|
||||
|
||||
await p1SecurityDialog.clickCloseButton();
|
||||
|
||||
|
||||
@@ -77,8 +77,8 @@ describe('AVModeration', () => {
|
||||
});
|
||||
|
||||
it('hangup and change moderator', async () => {
|
||||
// no moderator switching if jaas is available
|
||||
if (ctx.isJaasAvailable()) {
|
||||
// no moderator switching if jaas is available.
|
||||
if (ctx.iFrameUsesJaas) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -195,8 +195,8 @@ describe('Lobby', () => {
|
||||
});
|
||||
|
||||
it('change of moderators in lobby', async () => {
|
||||
// no moderator switching if jaas is available
|
||||
if (ctx.isJaasAvailable()) {
|
||||
// no moderator switching if jaas is available.
|
||||
if (ctx.iFrameUsesJaas) {
|
||||
return;
|
||||
}
|
||||
await hangupAllParticipants();
|
||||
@@ -287,8 +287,8 @@ describe('Lobby', () => {
|
||||
});
|
||||
|
||||
it('moderator leaves while lobby enabled', async () => {
|
||||
// no moderator switching if jaas is available
|
||||
if (ctx.isJaasAvailable()) {
|
||||
// no moderator switching if jaas is available.
|
||||
if (ctx.iFrameUsesJaas) {
|
||||
return;
|
||||
}
|
||||
const { p1, p2, p3 } = ctx;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { Participant } from '../../helpers/Participant';
|
||||
import { setTestProperties } from '../../helpers/TestProperties';
|
||||
import { ensureOneParticipant } from '../../helpers/participants';
|
||||
import {
|
||||
cleanup,
|
||||
@@ -8,6 +9,11 @@ import {
|
||||
waitForAudioFromDialInParticipant
|
||||
} from '../helpers/DialIn';
|
||||
|
||||
setTestProperties(__filename, {
|
||||
useIFrameApi: true,
|
||||
useWebhookProxy: true
|
||||
});
|
||||
|
||||
describe('Invite iframeAPI', () => {
|
||||
it('join participant', async () => {
|
||||
await ensureOneParticipant(ctx);
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import { setTestProperties } from '../../helpers/TestProperties';
|
||||
import { ensureOneParticipant } from '../../helpers/participants';
|
||||
|
||||
setTestProperties(__filename, {
|
||||
useIFrameApi: true,
|
||||
useWebhookProxy: true
|
||||
});
|
||||
|
||||
describe('Recording', () => {
|
||||
it('join participant', async () => {
|
||||
await ensureOneParticipant(ctx);
|
||||
@@ -180,7 +186,7 @@ async function testRecordingStopped(command: boolean) {
|
||||
eventType: string;
|
||||
} = await webhooksProxy.waitForEvent('RECORDING_UPLOADED', 20000);
|
||||
|
||||
const jwtPayload = ctx.data[`${p1.name}-jwt-payload`];
|
||||
const jwtPayload = p1.getToken()?.payload;
|
||||
|
||||
expect(recordingUploadedEvent.data.initiatorId).toBe(jwtPayload?.context?.user?.id);
|
||||
expect(recordingUploadedEvent.data.participants.some(
|
||||
|
||||
51
tests/specs/helpers/jaas.ts
Normal file
51
tests/specs/helpers/jaas.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import { Participant } from '../../helpers/Participant';
|
||||
import { IToken, ITokenOptions, generateToken } from '../../helpers/token';
|
||||
|
||||
/**
|
||||
* Creates a new Participant and joins the MUC with the given name. The jaas-specific properties must be set as
|
||||
* environment variables: JAAS_DOMAIN and JAAS_TENANT, JAAS_KID, JAAS_PRIVATE_KEY_PATH.
|
||||
*
|
||||
* @param roomName The name of the room to join, without the tenant.
|
||||
* @param instanceId This is the "name" passed to the Participant, I think it's used to match against one of the
|
||||
* pre-configured browser instances in wdio? It must be one of 'p1', 'p2', 'p3', or 'p4'. TODO: figure out how this
|
||||
* should be used.
|
||||
* @param token the token to use, if any.
|
||||
*/
|
||||
export async function joinMuc(roomName: string, instanceId: 'p1' | 'p2' | 'p3' | 'p4', token?: IToken) {
|
||||
if (!process.env.JAAS_DOMAIN || !process.env.JAAS_TENANT) {
|
||||
throw new Error('JAAS_DOMAIN and JAAS_TENANT environment variables must be set');
|
||||
}
|
||||
|
||||
// TODO: this should re-use code from Participant (e.g. setting config).
|
||||
let url = `https://${process.env.JAAS_DOMAIN}/${process.env.JAAS_TENANT}/${roomName}`;
|
||||
|
||||
if (token) {
|
||||
url += `?jwt=${token.jwt}`;
|
||||
}
|
||||
url += '#config.prejoinConfig.enabled=false';
|
||||
|
||||
const newParticipant = new Participant(instanceId, token);
|
||||
|
||||
try {
|
||||
await newParticipant.driver.setTimeout({ 'pageLoad': 30000 });
|
||||
await newParticipant.driver.url(url);
|
||||
await newParticipant.waitForPageToLoad();
|
||||
await newParticipant.waitToJoinMUC();
|
||||
} catch (error) {
|
||||
}
|
||||
|
||||
return newParticipant;
|
||||
}
|
||||
|
||||
export function generateJaasToken(options: ITokenOptions): IToken {
|
||||
if (!process.env.JAAS_PRIVATE_KEY_PATH || !process.env.JAAS_KID) {
|
||||
throw new Error('JAAS_PRIVATE_KEY_PATH and JAAS_KID environment variables must be set');
|
||||
}
|
||||
|
||||
// Don't override the keyId and keyPath if they are already set in options, allow tests to set them.
|
||||
return generateToken({
|
||||
...options,
|
||||
keyId: options.keyId || process.env.JAAS_KID,
|
||||
keyPath: options.keyPath || process.env.JAAS_PRIVATE_KEY_PATH,
|
||||
});
|
||||
}
|
||||
63
tests/specs/jaas/joinMuc.spec.ts
Normal file
63
tests/specs/jaas/joinMuc.spec.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { setTestProperties } from '../../helpers/TestProperties';
|
||||
import { joinMuc, generateJaasToken as t } from '../helpers/jaas';
|
||||
|
||||
setTestProperties(__filename, {
|
||||
useJaas: true
|
||||
});
|
||||
|
||||
describe('XMPP login and MUC join test', () => {
|
||||
it('with a valid token (wildcard room)', async () => {
|
||||
const p = await joinMuc(ctx.roomName, 'p1', t({ room: '*' }));
|
||||
|
||||
expect(await p.isInMuc()).toBe(true);
|
||||
expect(await p.isModerator()).toBe(false);
|
||||
});
|
||||
|
||||
it('with a valid token (specific room)', async () => {
|
||||
const p = await joinMuc(ctx.roomName, 'p1', t({ room: ctx.roomName }));
|
||||
|
||||
expect(await p.isInMuc()).toBe(true);
|
||||
expect(await p.isModerator()).toBe(false);
|
||||
});
|
||||
|
||||
it('with a token with bad signature', async () => {
|
||||
const token = t({ room: ctx.roomName });
|
||||
|
||||
token.jwt = token.jwt + 'badSignature';
|
||||
|
||||
const p = await joinMuc(ctx.roomName, 'p1', token);
|
||||
|
||||
expect(Boolean(await p.isInMuc())).toBe(false);
|
||||
});
|
||||
|
||||
it('with an expired token', async () => {
|
||||
const p = await joinMuc(ctx.roomName, 'p1', t({ exp: '-1m' }));
|
||||
|
||||
expect(Boolean(await p.isInMuc())).toBe(false);
|
||||
});
|
||||
|
||||
it('with a token using the wrong key ID', async () => {
|
||||
const p = await joinMuc(ctx.roomName, 'p1', t({ keyId: 'invalid-key-id' }));
|
||||
|
||||
expect(Boolean(await p.isInMuc())).toBe(false);
|
||||
});
|
||||
|
||||
it('with a token for a different room', async () => {
|
||||
const p = await joinMuc(ctx.roomName, 'p1', t({ room: ctx.roomName + 'different' }));
|
||||
|
||||
expect(Boolean(await p.isInMuc())).toBe(false);
|
||||
});
|
||||
|
||||
it('with a moderator token', async () => {
|
||||
const p = await joinMuc(ctx.roomName, 'p1', t({ moderator: true }));
|
||||
|
||||
expect(await p.isInMuc()).toBe(true);
|
||||
expect(await p.isModerator()).toBe(true);
|
||||
});
|
||||
|
||||
// it('without sending a conference-request', async () => {
|
||||
// console.log('Joining a MUC without sending a conference-request');
|
||||
// // TODO verify failure
|
||||
// //expect(await joinMuc(ctx.roomName, 'p1', token)).toBe(true);
|
||||
// });
|
||||
});
|
||||
30
tests/specs/jaas/maxOccupants.spec.ts
Normal file
30
tests/specs/jaas/maxOccupants.spec.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { setTestProperties } from '../../helpers/TestProperties';
|
||||
import { joinMuc, generateJaasToken as t } from '../helpers/jaas';
|
||||
|
||||
setTestProperties(__filename, {
|
||||
useJaas: true,
|
||||
useWebhookProxy: true
|
||||
});
|
||||
|
||||
describe('MaxOccupants limit enforcement', () => {
|
||||
it('test maxOccupants limit', async () => {
|
||||
ctx.webhooksProxy.defaultMeetingSettings = {
|
||||
maxOccupants: 2
|
||||
};
|
||||
|
||||
const p1 = await joinMuc(ctx.roomName, 'p1', t({ room: ctx.roomName }));
|
||||
const p2 = await joinMuc(ctx.roomName, 'p2', t({ room: ctx.roomName }));
|
||||
|
||||
expect(await p1.isInMuc()).toBe(true);
|
||||
expect(await p2.isInMuc()).toBe(true);
|
||||
|
||||
// Third participant should be rejected (exceeding maxOccupants), even if it's a moderator
|
||||
let p3 = await joinMuc(ctx.roomName, 'p3', t({ room: ctx.roomName, moderator: true }));
|
||||
|
||||
expect(Boolean(await p3.isInMuc())).toBe(false);
|
||||
|
||||
await p1.hangup();
|
||||
p3 = await joinMuc(ctx.roomName, 'p3', t({ room: ctx.roomName }));
|
||||
expect(await p3.isInMuc()).toBe(true);
|
||||
});
|
||||
});
|
||||
52
tests/specs/jaas/visitors/participantsSoftLimit.spec.ts
Normal file
52
tests/specs/jaas/visitors/participantsSoftLimit.spec.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { setTestProperties } from '../../../helpers/TestProperties';
|
||||
import { joinMuc, generateJaasToken as t } from '../../helpers/jaas';
|
||||
|
||||
setTestProperties(__filename, {
|
||||
useJaas: true,
|
||||
useWebhookProxy: true
|
||||
});
|
||||
|
||||
describe('Visitors triggered by reaching participantsSoftLimit', () => {
|
||||
it('test participantsSoftLimit', async () => {
|
||||
ctx.webhooksProxy.defaultMeetingSettings = {
|
||||
participantsSoftLimit: 2,
|
||||
visitorsEnabled: true
|
||||
};
|
||||
|
||||
/// XXX the "name" of the participant MUST match one of the "capabilities" defined in wdio. It's not a "participant", it's an instance configuration!
|
||||
const m = await joinMuc(
|
||||
ctx.roomName,
|
||||
'p1',
|
||||
t({ room: ctx.roomName, displayName: 'Mo de Rator', moderator: true })
|
||||
);
|
||||
|
||||
expect(await m.isInMuc()).toBe(true);
|
||||
expect(await m.isModerator()).toBe(true);
|
||||
expect(await m.isVisitor()).toBe(false);
|
||||
console.log('Moderator joined');
|
||||
|
||||
// Joining with a participant token before participantSoftLimit has been reached
|
||||
const p = await joinMuc(
|
||||
ctx.roomName,
|
||||
'p2',
|
||||
t({ room: ctx.roomName, displayName: 'Parti Cipant' })
|
||||
);
|
||||
|
||||
expect(await p.isInMuc()).toBe(true);
|
||||
expect(await p.isModerator()).toBe(false);
|
||||
expect(await p.isVisitor()).toBe(false);
|
||||
console.log('Participant joined');
|
||||
|
||||
// Joining with a participant token after participantSoftLimit has been reached
|
||||
const v = await joinMuc(
|
||||
ctx.roomName,
|
||||
'p3',
|
||||
t({ room: ctx.roomName, displayName: 'Visi Tor' })
|
||||
);
|
||||
|
||||
expect(await v.isInMuc()).toBe(true);
|
||||
expect(await v.isModerator()).toBe(false);
|
||||
expect(await v.isVisitor()).toBe(true);
|
||||
console.log('Visitor joined');
|
||||
});
|
||||
});
|
||||
61
tests/specs/jaas/visitors/visitorTokens.spec.ts
Normal file
61
tests/specs/jaas/visitors/visitorTokens.spec.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { setTestProperties } from '../../../helpers/TestProperties';
|
||||
import { joinMuc, generateJaasToken as t } from '../../helpers/jaas';
|
||||
|
||||
setTestProperties(__filename, {
|
||||
useJaas: true,
|
||||
useWebhookProxy: true
|
||||
});
|
||||
|
||||
describe('Visitors triggered by visitor tokens', () => {
|
||||
it('test visitor tokens', async () => {
|
||||
ctx.webhooksProxy.defaultMeetingSettings = {
|
||||
visitorsEnabled: true
|
||||
};
|
||||
|
||||
const m = await joinMuc(
|
||||
ctx.roomName,
|
||||
'p1',
|
||||
t({ room: ctx.roomName, displayName: 'Mo de Rator', moderator: true })
|
||||
);
|
||||
|
||||
expect(await m.isInMuc()).toBe(true);
|
||||
expect(await m.isModerator()).toBe(true);
|
||||
expect(await m.isVisitor()).toBe(false);
|
||||
console.log('Moderator joined');
|
||||
|
||||
// Joining with a participant token before any visitors
|
||||
const p = await joinMuc(
|
||||
ctx.roomName,
|
||||
'p2',
|
||||
t({ room: ctx.roomName, displayName: 'Parti Cipant' })
|
||||
);
|
||||
|
||||
expect(await p.isInMuc()).toBe(true);
|
||||
expect(await p.isModerator()).toBe(false);
|
||||
expect(await p.isVisitor()).toBe(false);
|
||||
console.log('Participant joined');
|
||||
|
||||
// Joining with a visitor token
|
||||
const v = await joinMuc(
|
||||
ctx.roomName,
|
||||
'p3',
|
||||
t({ room: ctx.roomName, displayName: 'Visi Tor', visitor: true })
|
||||
);
|
||||
|
||||
expect(await v.isInMuc()).toBe(true);
|
||||
expect(await v.isModerator()).toBe(false);
|
||||
expect(await v.isVisitor()).toBe(true);
|
||||
console.log('Visitor joined');
|
||||
|
||||
// Joining with a participant token after visitors...:mindblown:
|
||||
const v2 = await joinMuc(
|
||||
ctx.roomName,
|
||||
'p4',
|
||||
t({ room: ctx.roomName, displayName: 'Visi Tor 2' }));
|
||||
|
||||
expect(await v2.isInMuc()).toBe(true);
|
||||
expect(await v2.isModerator()).toBe(false);
|
||||
expect(await v2.isVisitor()).toBe(true);
|
||||
console.log('Visitor2 joined');
|
||||
});
|
||||
});
|
||||
@@ -5,6 +5,7 @@ import path from 'node:path';
|
||||
import process from 'node:process';
|
||||
import pretty from 'pretty';
|
||||
|
||||
import { getTestProperties } from './helpers/TestProperties';
|
||||
import WebhookProxy from './helpers/WebhookProxy';
|
||||
import { getLogs, initLogger, logInfo } from './helpers/browserLogger';
|
||||
import { IContext } from './helpers/types';
|
||||
@@ -180,7 +181,11 @@ export const config: WebdriverIO.MultiremoteConfig = {
|
||||
console.warn('We expect to run a single suite, but got more than one');
|
||||
}
|
||||
|
||||
const testName = path.basename(specs[0]).replace('.spec.ts', '');
|
||||
const testFilePath = specs[0].replace(/^file:\/\//, '');
|
||||
const testName = path.relative('tests/specs', testFilePath)
|
||||
.replace(/.spec.ts$/, '')
|
||||
.replace(/\//g, '-');
|
||||
const testProperties = await getTestProperties(testFilePath);
|
||||
|
||||
console.log(`Running test: ${testName} via worker: ${cid}`);
|
||||
|
||||
@@ -191,6 +196,7 @@ export const config: WebdriverIO.MultiremoteConfig = {
|
||||
times: {}
|
||||
} as IContext;
|
||||
globalAny.ctx.keepAlive = [];
|
||||
globalAny.ctx.testProperties = testProperties;
|
||||
|
||||
await Promise.all(multiremotebrowser.instances.map(async (instance: string) => {
|
||||
const bInstance = multiremotebrowser.getInstance(instance);
|
||||
@@ -224,13 +230,12 @@ export const config: WebdriverIO.MultiremoteConfig = {
|
||||
globalAny.ctx.roomName = globalAny.ctx.roomName.toLowerCase();
|
||||
globalAny.ctx.jwtPrivateKeyPath = process.env.JWT_PRIVATE_KEY_PATH;
|
||||
globalAny.ctx.jwtKid = process.env.JWT_KID;
|
||||
globalAny.ctx.isJaasAvailable = () => globalAny.ctx.jwtKid?.startsWith('vpaas-magic-cookie-');
|
||||
globalAny.ctx.iFrameUsesJaas = process.env.JWT_PRIVATE_KEY_PATH
|
||||
&& process.env.JWT_KID?.startsWith('vpaas-magic-cookie-');
|
||||
|
||||
// If we are running the iFrameApi tests, we need to mark it as such and if needed to create the proxy
|
||||
// and connect to it.
|
||||
if (testName.startsWith('iFrameApi')) {
|
||||
globalAny.ctx.iframeAPI = true;
|
||||
|
||||
if (testProperties.useWebhookProxy) {
|
||||
if (!globalAny.ctx.webhooksProxy
|
||||
&& process.env.WEBHOOKS_PROXY_URL && process.env.WEBHOOKS_PROXY_SHARED_SECRET) {
|
||||
globalAny.ctx.webhooksProxy = new WebhookProxy(
|
||||
@@ -240,6 +245,20 @@ export const config: WebdriverIO.MultiremoteConfig = {
|
||||
globalAny.ctx.webhooksProxy.connect();
|
||||
}
|
||||
}
|
||||
|
||||
if (testProperties.useWebhookProxy && !globalAny.ctx.webhooksProxy) {
|
||||
console.warn(`WebhookProxy is not available, skipping ${testName}`);
|
||||
globalAny.ctx.skipSuiteTests = true;
|
||||
}
|
||||
|
||||
const isJaasConfigured = process.env.JAAS_DOMAIN && process.env.JAAS_TENANT
|
||||
&& process.env.JAAS_PRIVATE_KEY_PATH && process.env.JAAS_KID;
|
||||
|
||||
if (testProperties.useJaas && !isJaasConfigured) {
|
||||
console.warn(`JaaS is not configured, skipping ${testName}. `
|
||||
+ 'Set JAAS_DOMAIN, JAAS_TENANT, JAAS_KID, and JAAS_PRIVATE_KEY_PATH to enable.');
|
||||
globalAny.ctx.skipSuiteTests = true;
|
||||
}
|
||||
},
|
||||
|
||||
after() {
|
||||
|
||||
Reference in New Issue
Block a user