diff --git a/src/app/Http/Controllers/API/V4/OpenViduController.php b/src/app/Http/Controllers/API/V4/MeetController.php similarity index 98% rename from src/app/Http/Controllers/API/V4/OpenViduController.php rename to src/app/Http/Controllers/API/V4/MeetController.php index 7df5b740..68a2b235 100644 --- a/src/app/Http/Controllers/API/V4/OpenViduController.php +++ b/src/app/Http/Controllers/API/V4/MeetController.php @@ -1,293 +1,293 @@ user(); $rooms = Room::where('user_id', $user->id)->orderBy('name')->get(); if (count($rooms) == 0) { // Create a room for the user (with a random and unique name) while (true) { $name = strtolower(\App\Utils::randStr(3, 3, '-')); if (!Room::where('name', $name)->count()) { break; } } $room = Room::create([ 'name' => $name, 'user_id' => $user->id ]); $rooms = collect([$room]); } $result = [ 'list' => $rooms, 'count' => count($rooms), ]; return response()->json($result); } /** * Join the room session. Each room has one owner, and the room isn't open until the owner * joins (and effectively creates the session). * * @param string $id Room identifier (name) * * @return \Illuminate\Http\JsonResponse */ public function joinRoom($id) { $room = Room::where('name', $id)->first(); // Room does not exist, or the owner is deleted if (!$room || !$room->owner) { return $this->errorResponse(404, \trans('meet.room-not-found')); } // Check if there's still a valid meet entitlement for the room owner if (!$room->owner->hasSku('meet')) { return $this->errorResponse(404, \trans('meet.room-not-found')); } $user = Auth::guard()->user(); $isOwner = $user && $user->id == $room->user_id; $init = !empty(request()->input('init')); // There's no existing session if (!$room->hasSession()) { // Participants can't join the room until the session is created by the owner if (!$isOwner) { return $this->errorResponse(422, \trans('meet.session-not-found'), ['code' => 323]); } // The room owner can create the session on request if (!$init) { return $this->errorResponse(422, \trans('meet.session-not-found'), ['code' => 324]); } $session = $room->createSession(); if (empty($session)) { return $this->errorResponse(500, \trans('meet.session-create-error')); } } $settings = $room->getSettings(['locked', 'nomedia', 'password']); $password = (string) $settings['password']; $config = [ 'locked' => $settings['locked'] === 'true', 'nomedia' => $settings['nomedia'] === 'true', 'password' => $isOwner ? $password : '', 'requires_password' => !$isOwner && strlen($password), ]; $response = ['config' => $config]; // Validate room password if (!$isOwner && strlen($password)) { $request_password = request()->input('password'); if ($request_password !== $password) { return $this->errorResponse(422, \trans('meet.session-password-error'), $response + ['code' => 325]); } } // Handle locked room if (!$isOwner && $config['locked']) { $nickname = request()->input('nickname'); $picture = request()->input('picture'); $requestId = request()->input('requestId'); $request = $requestId ? $room->requestGet($requestId) : null; $error = \trans('meet.session-room-locked-error'); // Request already has been processed (not accepted yet, but it could be denied) if (empty($request['status']) || $request['status'] != Room::REQUEST_ACCEPTED) { if (!$request) { if (empty($nickname) || empty($requestId) || !preg_match('/^[a-z0-9]{8,32}$/i', $requestId)) { return $this->errorResponse(422, $error, $response + ['code' => 326]); } if (empty($picture)) { $svg = file_get_contents(resource_path('images/user.svg')); $picture = 'data:image/svg+xml;base64,' . base64_encode($svg); } elseif (!preg_match('|^data:image/png;base64,[a-zA-Z0-9=+/]+$|', $picture)) { return $this->errorResponse(422, $error, $response + ['code' => 326]); } // TODO: Resize when big/make safe the user picture? $request = ['nickname' => $nickname, 'requestId' => $requestId, 'picture' => $picture]; if (!$room->requestSave($requestId, $request)) { // FIXME: should we use error code 500? return $this->errorResponse(422, $error, $response + ['code' => 326]); } // Send the request (signal) to all moderators $result = $room->signal('joinRequest', $request, Room::ROLE_MODERATOR); } return $this->errorResponse(422, $error, $response + ['code' => 327]); } } // Initialize connection tokens if ($init) { // Choose the connection role $canPublish = !empty(request()->input('canPublish')) && (empty($config['nomedia']) || $isOwner); $role = $canPublish ? Room::ROLE_PUBLISHER : Room::ROLE_SUBSCRIBER; if ($isOwner) { $role |= Room::ROLE_MODERATOR; $role |= Room::ROLE_OWNER; } // Create session token for the current user/connection $response = $room->getSessionToken($role); if (empty($response)) { return $this->errorResponse(500, \trans('meet.session-join-error')); } $response_code = 200; $response['role'] = $role; $response['config'] = $config; } else { $response_code = 422; $response['code'] = 322; } return response()->json($response, $response_code); } /** * Set the domain configuration. * * @param string $id Room identifier (name) * * @return \Illuminate\Http\JsonResponse|void */ public function setRoomConfig($id) { $room = Room::where('name', $id)->first(); // Room does not exist, or the owner is deleted if (!$room || !$room->owner) { return $this->errorResponse(404); } $user = Auth::guard()->user(); // Only room owner can configure the room if ($user->id != $room->user_id) { return $this->errorResponse(403); } $input = request()->input(); $errors = []; foreach ($input as $key => $value) { switch ($key) { case 'password': if ($value === null || $value === '') { $input[$key] = null; } else { // TODO: Do we have to validate the password in any way? } break; case 'locked': $input[$key] = $value ? 'true' : null; break; case 'nomedia': $input[$key] = $value ? 'true' : null; break; default: $errors[$key] = \trans('meet.room-unsupported-option-error'); } } if (!empty($errors)) { return response()->json(['status' => 'error', 'errors' => $errors], 422); } if (!empty($input)) { $room->setSettings($input); } return response()->json([ 'status' => 'success', 'message' => \trans('meet.room-setconfig-success'), ]); } /** - * Webhook as triggered from OpenVidu server + * Webhook as triggered from the Meet server * * @param \Illuminate\Http\Request $request The API request. * * @return \Illuminate\Http\Response The response */ public function webhook(Request $request) { \Log::debug($request->getContent()); // Authenticate the request if ($request->headers->get('X-Auth-Token') != \config('meet.webhook_token')) { return response('Unauthorized', 403); } $sessionId = (string) $request->input('roomId'); $event = (string) $request->input('event'); switch ($event) { case 'roomClosed': // When all participants left the room the server will dispatch roomClosed // event. We'll remove the session reference from the database. $room = Room::where('session_id', $sessionId)->first(); if ($room) { $room->session_id = null; $room->save(); } break; case 'joinRequestAccepted': case 'joinRequestDenied': $room = Room::where('session_id', $sessionId)->first(); if ($room) { $method = $event == 'joinRequestAccepted' ? 'requestAccept' : 'requestDeny'; $room->{$method}($request->input('requestId')); } break; } return response('Success', 200); } } diff --git a/src/app/OpenVidu/Room.php b/src/app/Meet/Room.php similarity index 95% rename from src/app/OpenVidu/Room.php rename to src/app/Meet/Room.php index c251321c..1acec57b 100644 --- a/src/app/OpenVidu/Room.php +++ b/src/app/Meet/Room.php @@ -1,293 +1,293 @@ false, // No exceptions from Guzzle 'base_uri' => \config('meet.api_url'), 'verify' => \config('meet.api_verify_tls'), 'headers' => [ 'X-Auth-Token' => \config('meet.api_token'), ], 'connect_timeout' => 10, 'timeout' => 10, 'on_stats' => function (\GuzzleHttp\TransferStats $stats) { $threshold = \config('logging.slow_log'); if ($threshold && ($sec = $stats->getTransferTime()) > $threshold) { $url = $stats->getEffectiveUri(); $method = $stats->getRequest()->getMethod(); \Log::warning(sprintf("[STATS] %s %s: %.4f sec.", $method, $url, $sec)); } }, ] ); } return self::$client; } /** - * Create a OpenVidu session + * Create a Meet session * * @return array|null Session data on success, NULL otherwise */ public function createSession(): ?array { $params = [ 'json' => [ /* request params here */ ] ]; $response = $this->client()->request('POST', "sessions", $params); if ($response->getStatusCode() !== 200) { $this->logError("Failed to create the meet session", $response); $this->session_id = null; $this->save(); return null; } $session = json_decode($response->getBody(), true); $this->session_id = $session['id']; $this->save(); return $session; } /** - * Create a OpenVidu session (connection) token + * Create a Meet session (connection) token * * @param int $role User role (see self::ROLE_* constants) * * @return array|null Token data on success, NULL otherwise * @throws \Exception if session does not exist */ public function getSessionToken($role = self::ROLE_SUBSCRIBER): ?array { if (!$this->session_id) { throw new \Exception("The room session does not exist"); } $url = 'sessions/' . $this->session_id . '/connection'; $post = [ 'json' => [ 'role' => $role, ] ]; $response = $this->client()->request('POST', $url, $post); if ($response->getStatusCode() == 200) { $json = json_decode($response->getBody(), true); return [ 'token' => $json['token'], 'role' => $role, ]; } $this->logError("Failed to create the meet peer connection", $response); return null; } /** * Check if the room has an active session * * @return bool True when the session exists, False otherwise */ public function hasSession(): bool { if (!$this->session_id) { return false; } $response = $this->client()->request('GET', "sessions/{$this->session_id}"); $this->logError("Failed to check that a meet session exists", $response); return $response->getStatusCode() == 200; } /** * The room owner. * * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ public function owner() { return $this->belongsTo('\App\User', 'user_id', 'id'); } /** * Accept the join request. * * @param string $id Request identifier * * @return bool True on success, False on failure */ public function requestAccept(string $id): bool { $request = Cache::get($this->session_id . '-' . $id); if ($request) { $request['status'] = self::REQUEST_ACCEPTED; return Cache::put($this->session_id . '-' . $id, $request, now()->addHours(1)); } return false; } /** * Deny the join request. * * @param string $id Request identifier * * @return bool True on success, False on failure */ public function requestDeny(string $id): bool { $request = Cache::get($this->session_id . '-' . $id); if ($request) { $request['status'] = self::REQUEST_DENIED; return Cache::put($this->session_id . '-' . $id, $request, now()->addHours(1)); } return false; } /** * Get the join request data. * * @param string $id Request identifier * * @return array|null Request data (e.g. nickname, status, picture?) */ public function requestGet(string $id): ?array { return Cache::get($this->session_id . '-' . $id); } /** * Save the join request. * * @param string $id Request identifier * @param array $request Request data * * @return bool True on success, False on failure */ public function requestSave(string $id, array $request): bool { // We don't really need the picture in the cache // As we use this cache for the request status only unset($request['picture']); return Cache::put($this->session_id . '-' . $id, $request, now()->addHours(1)); } /** * Any (additional) properties of this room. * * @return \Illuminate\Database\Eloquent\Relations\HasMany */ public function settings() { - return $this->hasMany('App\OpenVidu\RoomSetting', 'room_id'); + return $this->hasMany('App\Meet\RoomSetting', 'room_id'); } /** - * Send a OpenVidu signal to the session participants (connections) + * Send a signal to the Meet session participants (peers) * * @param string $name Signal name (type) * @param array $data Signal data array * @param int $target Limit targets by their participant role * * @return bool True on success, False on failure * @throws \Exception if session does not exist */ public function signal(string $name, array $data = [], $target = null): bool { if (!$this->session_id) { throw new \Exception("The room session does not exist"); } $post = [ 'roomId' => $this->session_id, 'type' => $name, 'role' => $target, 'data' => $data, ]; $response = $this->client()->request('POST', 'signal', ['json' => $post]); $this->logError("Failed to send a signal to the meet session", $response); return $response->getStatusCode() == 200; } /** * Log an error for a failed request to the meet server * * @param string $str The error string * @param object $response Guzzle client response */ private function logError(string $str, $response) { $code = $response->getStatusCode(); if ($code != 200) { \Log::error("$str [$code]"); } } } diff --git a/src/app/OpenVidu/RoomSetting.php b/src/app/Meet/RoomSetting.php similarity index 84% rename from src/app/OpenVidu/RoomSetting.php rename to src/app/Meet/RoomSetting.php index c731d707..9581eaf0 100644 --- a/src/app/OpenVidu/RoomSetting.php +++ b/src/app/Meet/RoomSetting.php @@ -1,32 +1,32 @@ belongsTo('\App\OpenVidu\Room', 'room_id', 'id'); + return $this->belongsTo('\App\Meet\Room', 'room_id', 'id'); } } diff --git a/src/app/Observers/OpenVidu/ConnectionObserver.php b/src/app/Observers/OpenVidu/ConnectionObserver.php deleted file mode 100644 index 94c8091e..00000000 --- a/src/app/Observers/OpenVidu/ConnectionObserver.php +++ /dev/null @@ -1,71 +0,0 @@ -role != $connection->getOriginal('role')) { - $params['role'] = $connection->role; - - // TODO: When demoting publisher to subscriber maybe we should - // destroy all streams using REST API. For now we trust the - // participant browser to do this. - } - - // Detect metadata changes for specified properties - $keys = [ - 'hand' => 'bool', - 'language' => '', - ]; - - foreach ($keys as $key => $type) { - $newState = $connection->metadata[$key] ?? null; - $oldState = $this->getOriginal($connection, 'metadata')[$key] ?? null; - - if ($newState !== $oldState) { - $params[$key] = $type == 'bool' ? !empty($newState) : $newState; - } - } - - // Send the signal to all participants - if (!empty($params)) { - $params['connectionId'] = $connection->id; - $connection->room->signal('connectionUpdate', $params); - } - } - - /** - * A wrapper to getOriginal() on an object - * - * @param \App\OpenVidu\Connection $connection The connection. - * @param string $property The property name - * - * @return mixed - */ - private function getOriginal($connection, $property) - { - $original = $connection->getOriginal($property); - - // The original value for a property is in a format stored in database - // I.e. for 'metadata' it is a JSON string instead of an array - if ($property == 'metadata') { - $original = json_decode($original, true); - } - - return $original; - } -} diff --git a/src/app/OpenVidu/Connection.php b/src/app/OpenVidu/Connection.php deleted file mode 100644 index 48bcde76..00000000 --- a/src/app/OpenVidu/Connection.php +++ /dev/null @@ -1,82 +0,0 @@ - 'array', - ]; - - /** - * The room to which this connection belongs. - * - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo - */ - public function room() - { - return $this->belongsTo(Room::class, 'room_id', 'id'); - } - - /** - * Connection role mutator - * - * @throws \Exception - */ - public function setRoleAttribute($role) - { - $new_role = 0; - - $allowed_values = [ - Room::ROLE_SUBSCRIBER, - Room::ROLE_PUBLISHER, - Room::ROLE_MODERATOR, - Room::ROLE_SCREEN, - Room::ROLE_OWNER, - ]; - - foreach ($allowed_values as $value) { - if ($role & $value) { - $new_role |= $value; - $role ^= $value; - } - } - - if ($role > 0) { - throw new \Exception("Invalid connection role: {$role}"); - } - - // It is either screen sharing connection or publisher/subscriber connection - if ($new_role & Room::ROLE_SCREEN) { - if ($new_role & Room::ROLE_PUBLISHER) { - $new_role ^= Room::ROLE_PUBLISHER; - } - if ($new_role & Room::ROLE_SUBSCRIBER) { - $new_role ^= Room::ROLE_SUBSCRIBER; - } - } - - $this->attributes['role'] = $new_role; - } -} diff --git a/src/app/Providers/AppServiceProvider.php b/src/app/Providers/AppServiceProvider.php index d9e3e68d..7dccf87b 100644 --- a/src/app/Providers/AppServiceProvider.php +++ b/src/app/Providers/AppServiceProvider.php @@ -1,161 +1,160 @@ format('Y-m-d h:i:s'); } return $entry; }, $array); return implode(', ', $serialized); } /** * Bootstrap any application services. * * @return void */ public function boot() { \App\Domain::observe(\App\Observers\DomainObserver::class); \App\Entitlement::observe(\App\Observers\EntitlementObserver::class); \App\Group::observe(\App\Observers\GroupObserver::class); - \App\OpenVidu\Connection::observe(\App\Observers\OpenVidu\ConnectionObserver::class); \App\PackageSku::observe(\App\Observers\PackageSkuObserver::class); \App\PlanPackage::observe(\App\Observers\PlanPackageObserver::class); \App\SignupCode::observe(\App\Observers\SignupCodeObserver::class); \App\SignupInvitation::observe(\App\Observers\SignupInvitationObserver::class); \App\Transaction::observe(\App\Observers\TransactionObserver::class); \App\User::observe(\App\Observers\UserObserver::class); \App\UserAlias::observe(\App\Observers\UserAliasObserver::class); \App\UserSetting::observe(\App\Observers\UserSettingObserver::class); \App\VerificationCode::observe(\App\Observers\VerificationCodeObserver::class); \App\Wallet::observe(\App\Observers\WalletObserver::class); \App\PowerDNS\Domain::observe(\App\Observers\PowerDNS\DomainObserver::class); \App\PowerDNS\Record::observe(\App\Observers\PowerDNS\RecordObserver::class); Schema::defaultStringLength(191); // Log SQL queries in debug mode if (\config('app.debug')) { DB::listen(function ($query) { \Log::debug( sprintf( '[SQL] %s [%s]: %.4f sec.', $query->sql, self::serializeSQLBindings($query->bindings), $query->time / 1000 ) ); }); } // Register some template helpers Blade::directive( 'theme_asset', function ($path) { $path = trim($path, '/\'"'); return ""; } ); Builder::macro( 'withEnvTenantContext', function (string $table = null) { $tenantId = \config('app.tenant_id'); if ($tenantId) { /** @var Builder $this */ return $this->where(($table ? "$table." : "") . "tenant_id", $tenantId); } /** @var Builder $this */ return $this->whereNull(($table ? "$table." : "") . "tenant_id"); } ); Builder::macro( 'withObjectTenantContext', function (object $object, string $table = null) { $tenantId = $object->tenant_id; if ($tenantId) { /** @var Builder $this */ return $this->where(($table ? "$table." : "") . "tenant_id", $tenantId); } /** @var Builder $this */ return $this->whereNull(($table ? "$table." : "") . "tenant_id"); } ); Builder::macro( 'withSubjectTenantContext', function (string $table = null) { if ($user = auth()->user()) { $tenantId = $user->tenant_id; } else { $tenantId = \config('app.tenant_id'); } if ($tenantId) { /** @var Builder $this */ return $this->where(($table ? "$table." : "") . "tenant_id", $tenantId); } /** @var Builder $this */ return $this->whereNull(($table ? "$table." : "") . "tenant_id"); } ); // Query builder 'whereLike' mocro Builder::macro( 'whereLike', function (string $column, string $search, int $mode = 0) { $search = addcslashes($search, '%_'); switch ($mode) { case 2: $search .= '%'; break; case 1: $search = '%' . $search; break; default: $search = '%' . $search . '%'; } /** @var Builder $this */ return $this->where($column, 'like', $search); } ); } } diff --git a/src/resources/js/meet/config.js b/src/resources/js/meet/config.js index 8a0c78a6..adf65b34 100644 --- a/src/resources/js/meet/config.js +++ b/src/resources/js/meet/config.js @@ -1,47 +1,47 @@ export default { // Default audio options audioOptions: { autoGainControl: false, echoCancellation: true, noiseSuppression: true, voiceActivatedUnmute: false, // Automatically unmute speaking above noiseThreshold noiseThreshold: -60, // default -60 / This is only for voiceActivatedUnmute and audio-indicator sampleRate: 96000, // will not eat that much bandwith thanks to opus channelCount: 1, // usually mics are mono so this saves bandwidth volume: 1.0, sampleSize: 16, opusStereo: false, // usually mics are mono so this saves bandwidth opusDtx: true, // will save bandwidth opusFec: true, // forward error correction opusPtime: '20', // minimum packet time (3, 5, 10, 20, 40, 60, 120) opusMaxPlaybackRate: 96000 }, // Default video options videoOptions: { resolution: 'medium', aspectRatio: 1.777, // 16 : 9 - frameRate: 15, // Note: OpenVidu default was 30 + frameRate: 15, // Note: OpenVidu's default was 30 simulcast: true }, screenOptions: { resolution: 'veryhigh', frameRate: 5, simulcast: false }, // Simulcast encoding layers and levels simulcastEncodings: [ { scaleResolutionDownBy: 4 }, { scaleResolutionDownBy: 2 }, { scaleResolutionDownBy: 1 } ], // Socket.io request timeout requestTimeout: 20000, transportOptions: { tcp : true } } diff --git a/src/resources/vue/Meet/Room.vue b/src/resources/vue/Meet/Room.vue index e5cc7fa4..ae2c3996 100644 --- a/src/resources/vue/Meet/Room.vue +++ b/src/resources/vue/Meet/Room.vue @@ -1,635 +1,635 @@ {{ session.channel.toUpperCase() }} - {{ $t('form.none') }} - {{ $t('lang.' + code) }} {{ $t('meet.setup-title') }} {{ $t('form.none') }} {{ mic.label }} {{ $t('form.none') }} {{ cam.label }} {{ $t('meet.joinnow') }} {{ $t('meet.imaowner') }} {{ $t('meet.join') }} {{ $t('meet.leave-title') }} {{ $t('meet.leave-body') }} {{ $t('meet.media-title') }} {{ $t('form.none') }} {{ mic.label }} {{ $t('form.none') }} {{ cam.label }} diff --git a/src/resources/vue/Meet/RoomOptions.vue b/src/resources/vue/Meet/RoomOptions.vue index da914251..074b1b6a 100644 --- a/src/resources/vue/Meet/RoomOptions.vue +++ b/src/resources/vue/Meet/RoomOptions.vue @@ -1,113 +1,113 @@ {{ $t('meet.options') }} {{ $t('meet.password') }}: {{ config.password }} {{ $t('meet.password-none') }} {{ $t('btn.save') }} {{ $t('meet.password-clear') }} {{ $t('meet.password-set') }} {{ $t('meet.password-text') }} {{ $t('meet.lock') }}: {{ $t('meet.lock-text') }} {{ $t('meet.nomedia') }}: {{ $t('meet.nomedia-text') }} diff --git a/src/resources/vue/Rooms.vue b/src/resources/vue/Rooms.vue index c0a32f5e..32336886 100644 --- a/src/resources/vue/Rooms.vue +++ b/src/resources/vue/Rooms.vue @@ -1,64 +1,64 @@ {{ $t('meet.title') }} {{ $t('dashboard.beta') }} {{ $t('meet.welcome') }} {{ $t('meet.url') }} {{ href }} {{ $t('meet.notice') }} {{ $t('meet.sharing') }} {{ $t('meet.sharing-text') }} {{ $t('meet.security') }} {{ $t('meet.security-text') }} {{ $t('meet.qa') }} {{ $t('meet.qa-text') }} {{ $t('meet.moderation') }} {{ $t('meet.moderation-text') }} {{ $t('meet.eject') }} {{ $t('meet.eject-text') }} {{ $t('meet.silent') }} {{ $t('meet.silent-text') }} {{ $t('meet.interpreters') }} {{ $t('meet.interpreters-text') }} {{ $t('meet.beta-notice') }} diff --git a/src/routes/api.php b/src/routes/api.php index e8304562..bb31c612 100644 --- a/src/routes/api.php +++ b/src/routes/api.php @@ -1,232 +1,232 @@ 'api', 'prefix' => $prefix . 'api/auth' ], function ($router) { Route::post('login', 'API\AuthController@login'); Route::group( ['middleware' => 'auth:api'], function ($router) { Route::get('info', 'API\AuthController@info'); Route::post('info', 'API\AuthController@info'); Route::post('logout', 'API\AuthController@logout'); Route::post('refresh', 'API\AuthController@refresh'); } ); } ); Route::group( [ 'domain' => \config('app.website_domain'), 'middleware' => 'api', 'prefix' => $prefix . 'api/auth' ], function ($router) { Route::post('password-reset/init', 'API\PasswordResetController@init'); Route::post('password-reset/verify', 'API\PasswordResetController@verify'); Route::post('password-reset', 'API\PasswordResetController@reset'); Route::post('signup/init', 'API\SignupController@init'); Route::get('signup/invitations/{id}', 'API\SignupController@invitation'); Route::get('signup/plans', 'API\SignupController@plans'); Route::post('signup/verify', 'API\SignupController@verify'); Route::post('signup', 'API\SignupController@signup'); } ); Route::group( [ 'domain' => \config('app.website_domain'), 'middleware' => 'auth:api', 'prefix' => $prefix . 'api/v4' ], function () { Route::post('companion/register', 'API\V4\CompanionAppsController@register'); Route::post('auth-attempts/{id}/confirm', 'API\V4\AuthAttemptsController@confirm'); Route::post('auth-attempts/{id}/deny', 'API\V4\AuthAttemptsController@deny'); Route::get('auth-attempts/{id}/details', 'API\V4\AuthAttemptsController@details'); Route::get('auth-attempts', 'API\V4\AuthAttemptsController@index'); Route::apiResource('domains', API\V4\DomainsController::class); Route::get('domains/{id}/confirm', 'API\V4\DomainsController@confirm'); Route::get('domains/{id}/status', 'API\V4\DomainsController@status'); Route::post('domains/{id}/config', 'API\V4\DomainsController@setConfig'); Route::apiResource('groups', API\V4\GroupsController::class); Route::get('groups/{id}/status', 'API\V4\GroupsController@status'); + Route::get('meet/rooms', 'API\V4\MeetController@index'); + Route::post('meet/rooms/{id}/config', 'API\V4\MeetController@setRoomConfig'); + Route::apiResource('packages', API\V4\PackagesController::class); Route::apiResource('skus', API\V4\SkusController::class); Route::apiResource('users', API\V4\UsersController::class); Route::post('users/{id}/config', 'API\V4\UsersController@setConfig'); Route::get('users/{id}/skus', 'API\V4\SkusController@userSkus'); Route::get('users/{id}/status', 'API\V4\UsersController@status'); Route::apiResource('wallets', API\V4\WalletsController::class); Route::get('wallets/{id}/transactions', 'API\V4\WalletsController@transactions'); Route::get('wallets/{id}/receipts', 'API\V4\WalletsController@receipts'); Route::get('wallets/{id}/receipts/{receipt}', 'API\V4\WalletsController@receiptDownload'); Route::post('payments', 'API\V4\PaymentsController@store'); //Route::delete('payments', 'API\V4\PaymentsController@cancel'); Route::get('payments/mandate', 'API\V4\PaymentsController@mandate'); Route::post('payments/mandate', 'API\V4\PaymentsController@mandateCreate'); Route::put('payments/mandate', 'API\V4\PaymentsController@mandateUpdate'); Route::delete('payments/mandate', 'API\V4\PaymentsController@mandateDelete'); Route::get('payments/methods', 'API\V4\PaymentsController@paymentMethods'); Route::get('payments/pending', 'API\V4\PaymentsController@payments'); Route::get('payments/has-pending', 'API\V4\PaymentsController@hasPayments'); - - Route::get('openvidu/rooms', 'API\V4\OpenViduController@index'); - Route::post('openvidu/rooms/{id}/config', 'API\V4\OpenViduController@setRoomConfig'); } ); // Note: In Laravel 7.x we could just use withoutMiddleware() instead of a separate group Route::group( [ 'domain' => \config('app.website_domain'), 'prefix' => $prefix . 'api/v4' ], function () { - Route::post('openvidu/rooms/{id}', 'API\V4\OpenViduController@joinRoom'); + Route::post('meet/rooms/{id}', 'API\V4\MeetController@joinRoom'); } ); Route::group( [ 'domain' => \config('app.website_domain'), 'middleware' => 'api', 'prefix' => $prefix . 'api/v4' ], function ($router) { Route::post('support/request', 'API\V4\SupportController@request'); } ); Route::group( [ 'domain' => \config('app.website_domain'), 'prefix' => $prefix . 'api/webhooks' ], function () { Route::post('payment/{provider}', 'API\V4\PaymentsController@webhook'); - Route::post('meet', 'API\V4\OpenViduController@webhook'); + Route::post('meet', 'API\V4\MeetController@webhook'); Route::get('nginx', 'API\NGINXController@authenticate'); } ); if (\config('app.with_services')) { Route::group( [ 'domain' => 'services.' . \config('app.website_domain'), 'prefix' => $prefix . 'api/webhooks' ], function () { Route::get('nginx', 'API\V4\NGINXController@authenticate'); Route::post('policy/greylist', 'API\V4\PolicyController@greylist'); Route::post('policy/ratelimit', 'API\V4\PolicyController@ratelimit'); Route::post('policy/spf', 'API\V4\PolicyController@senderPolicyFramework'); } ); } if (\config('app.with_admin')) { Route::group( [ 'domain' => 'admin.' . \config('app.website_domain'), 'middleware' => ['auth:api', 'admin'], 'prefix' => $prefix . 'api/v4', ], function () { Route::apiResource('domains', API\V4\Admin\DomainsController::class); Route::post('domains/{id}/suspend', 'API\V4\Admin\DomainsController@suspend'); Route::post('domains/{id}/unsuspend', 'API\V4\Admin\DomainsController@unsuspend'); Route::apiResource('groups', API\V4\Admin\GroupsController::class); Route::post('groups/{id}/suspend', 'API\V4\Admin\GroupsController@suspend'); Route::post('groups/{id}/unsuspend', 'API\V4\Admin\GroupsController@unsuspend'); Route::apiResource('skus', API\V4\Admin\SkusController::class); Route::apiResource('users', API\V4\Admin\UsersController::class); Route::get('users/{id}/discounts', 'API\V4\Reseller\DiscountsController@userDiscounts'); Route::post('users/{id}/reset2FA', 'API\V4\Admin\UsersController@reset2FA'); Route::get('users/{id}/skus', 'API\V4\Admin\SkusController@userSkus'); Route::post('users/{id}/suspend', 'API\V4\Admin\UsersController@suspend'); Route::post('users/{id}/unsuspend', 'API\V4\Admin\UsersController@unsuspend'); Route::apiResource('wallets', API\V4\Admin\WalletsController::class); Route::post('wallets/{id}/one-off', 'API\V4\Admin\WalletsController@oneOff'); Route::get('wallets/{id}/transactions', 'API\V4\Admin\WalletsController@transactions'); Route::get('stats/chart/{chart}', 'API\V4\Admin\StatsController@chart'); } ); } if (\config('app.with_reseller')) { Route::group( [ 'domain' => 'reseller.' . \config('app.website_domain'), 'middleware' => ['auth:api', 'reseller'], 'prefix' => $prefix . 'api/v4', ], function () { Route::apiResource('domains', API\V4\Reseller\DomainsController::class); Route::post('domains/{id}/suspend', 'API\V4\Reseller\DomainsController@suspend'); Route::post('domains/{id}/unsuspend', 'API\V4\Reseller\DomainsController@unsuspend'); Route::apiResource('groups', API\V4\Reseller\GroupsController::class); Route::post('groups/{id}/suspend', 'API\V4\Reseller\GroupsController@suspend'); Route::post('groups/{id}/unsuspend', 'API\V4\Reseller\GroupsController@unsuspend'); Route::apiResource('invitations', API\V4\Reseller\InvitationsController::class); Route::post('invitations/{id}/resend', 'API\V4\Reseller\InvitationsController@resend'); Route::post('payments', 'API\V4\Reseller\PaymentsController@store'); Route::get('payments/mandate', 'API\V4\Reseller\PaymentsController@mandate'); Route::post('payments/mandate', 'API\V4\Reseller\PaymentsController@mandateCreate'); Route::put('payments/mandate', 'API\V4\Reseller\PaymentsController@mandateUpdate'); Route::delete('payments/mandate', 'API\V4\Reseller\PaymentsController@mandateDelete'); Route::get('payments/methods', 'API\V4\Reseller\PaymentsController@paymentMethods'); Route::get('payments/pending', 'API\V4\Reseller\PaymentsController@payments'); Route::get('payments/has-pending', 'API\V4\Reseller\PaymentsController@hasPayments'); Route::apiResource('skus', API\V4\Reseller\SkusController::class); Route::apiResource('users', API\V4\Reseller\UsersController::class); Route::get('users/{id}/discounts', 'API\V4\Reseller\DiscountsController@userDiscounts'); Route::post('users/{id}/reset2FA', 'API\V4\Reseller\UsersController@reset2FA'); Route::get('users/{id}/skus', 'API\V4\Reseller\SkusController@userSkus'); Route::post('users/{id}/suspend', 'API\V4\Reseller\UsersController@suspend'); Route::post('users/{id}/unsuspend', 'API\V4\Reseller\UsersController@unsuspend'); Route::apiResource('wallets', API\V4\Reseller\WalletsController::class); Route::post('wallets/{id}/one-off', 'API\V4\Reseller\WalletsController@oneOff'); Route::get('wallets/{id}/receipts', 'API\V4\Reseller\WalletsController@receipts'); Route::get('wallets/{id}/receipts/{receipt}', 'API\V4\Reseller\WalletsController@receiptDownload'); Route::get('wallets/{id}/transactions', 'API\V4\Reseller\WalletsController@transactions'); Route::get('stats/chart/{chart}', 'API\V4\Reseller\StatsController@chart'); } ); } diff --git a/src/tests/Browser/Meet/RoomControlsTest.php b/src/tests/Browser/Meet/RoomControlsTest.php index c24fbdc7..3990b2ee 100644 --- a/src/tests/Browser/Meet/RoomControlsTest.php +++ b/src/tests/Browser/Meet/RoomControlsTest.php @@ -1,411 +1,411 @@ setupTestRoom(); } public function tearDown(): void { $this->resetTestRoom(); parent::tearDown(); } /** * Test fullscreen buttons * - * @group openvidu + * @group meet */ public function testFullscreen(): void { // TODO: This test does not work in headless mode $this->markTestIncomplete(); /* $this->browse(function (Browser $browser) { // Join the room as an owner (authenticate) $browser->visit(new RoomPage('john')) ->click('@setup-button') ->assertMissing('@toolbar') ->assertMissing('@menu') ->assertMissing('@session') ->assertMissing('@chat') ->assertMissing('@setup-form') ->assertVisible('@login-form') ->submitLogon('john@kolab.org', 'simple123') ->waitFor('@setup-form') ->assertMissing('@login-form') ->waitUntilMissing('@setup-status-message.loading') ->click('@setup-button') ->waitFor('@session') // Test fullscreen for the whole room ->click('@menu button.link-fullscreen.closed') ->assertVisible('@toolbar') ->assertVisible('@session') ->assertMissing('nav') ->assertMissing('@menu button.link-fullscreen.closed') ->click('@menu button.link-fullscreen.open') ->assertVisible('nav') // Test fullscreen for the participant video ->click('@session button.link-fullscreen.closed') ->assertVisible('@session') ->assertMissing('@toolbar') ->assertMissing('nav') ->assertMissing('@session button.link-fullscreen.closed') ->click('@session button.link-fullscreen.open') ->assertVisible('nav') ->assertVisible('@toolbar'); }); */ } /** * Test nickname and audio/video muting/volume controls * - * @group openvidu + * @group meet */ public function testNicknameAndMuting(): void { $this->browse(function (Browser $owner, Browser $guest) { // Join the room as an owner (authenticate) $owner->visit(new RoomPage('john')) ->click('@setup-button') ->submitLogon('john@kolab.org', 'simple123') ->waitFor('@setup-form') ->waitUntilMissing('@setup-status-message.loading') ->type('@setup-nickname-input', 'john') ->keys('@setup-nickname-input', '{enter}') // Test form submit with Enter key ->waitFor('@session'); // In another browser act as a guest $guest->visit(new RoomPage('john')) ->waitFor('@setup-form') ->waitUntilMissing('@setup-status-message.loading') ->assertMissing('@setup-status-message') ->assertSeeIn('@setup-button', "JOIN") // Join the room, disable cam/mic ->select('@setup-mic-select', '') //->select('@setup-cam-select', '') ->clickWhenEnabled('@setup-button') ->waitFor('@session'); // Assert current UI state $owner->assertToolbar([ 'audio' => RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_ENABLED, 'video' => RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_ENABLED, 'screen' => RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_ENABLED, 'chat' => RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_ENABLED, 'fullscreen' => RoomPage::BUTTON_ENABLED, 'options' => RoomPage::BUTTON_ENABLED, 'logout' => RoomPage::BUTTON_ENABLED, ]) ->whenAvailable('div.meet-video.self', function (Browser $browser) { $browser->waitFor('video') ->assertAudioMuted('video', true) ->assertSeeIn('.meet-nickname', 'john') ->assertVisible('.controls button.link-fullscreen') ->assertMissing('.controls button.link-audio') ->assertMissing('.status .status-audio') ->assertMissing('.status .status-video'); }) ->whenAvailable('div.meet-video:not(.self)', function (Browser $browser) { $browser->waitFor('video') ->assertVisible('.meet-nickname') ->assertVisible('.controls button.link-fullscreen') ->assertVisible('.controls button.link-audio') ->assertVisible('.status .status-audio') ->assertMissing('.status .status-video'); }) ->assertElementsCount('@session div.meet-video', 2); // Assert current UI state $guest->assertToolbar([ 'audio' => RoomPage::BUTTON_ACTIVE | RoomPage::BUTTON_ENABLED, 'video' => RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_ENABLED, 'screen' => RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_ENABLED, 'chat' => RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_ENABLED, 'fullscreen' => RoomPage::BUTTON_ENABLED, 'logout' => RoomPage::BUTTON_ENABLED, ]) ->whenAvailable('div.meet-video:not(.self)', function (Browser $browser) { $browser->waitFor('video') ->assertSeeIn('.meet-nickname', 'john') ->assertVisible('.controls button.link-fullscreen') ->assertVisible('.controls button.link-audio') ->assertMissing('.status .status-audio') ->assertMissing('.status .status-video'); }) ->whenAvailable('div.meet-video.self', function (Browser $browser) { $browser->waitFor('video') ->assertVisible('.controls button.link-fullscreen') ->assertMissing('.controls button.link-audio') ->assertVisible('.status .status-audio') ->assertMissing('.status .status-video'); }) ->assertElementsCount('@session div.meet-video', 2); // Test nickname change propagation $guest->setNickname('div.meet-video.self', 'guest'); $owner->waitFor('div.meet-video:not(.self) .meet-nickname') ->assertSeeIn('div.meet-video:not(.self) .meet-nickname', 'guest'); // Test muting audio $owner->click('@menu button.link-audio') ->assertToolbarButtonState('audio', RoomPage::BUTTON_ACTIVE | RoomPage::BUTTON_ENABLED) ->waitFor('div.meet-video.self .status .status-audio'); // FIXME: It looks that we can't just check the element state - // We might consider using OpenVidu API to make sure + // We might consider using some API to make sure $guest->waitFor('div.meet-video:not(.self) .status .status-audio'); // Test unmuting audio $owner->click('@menu button.link-audio') ->assertToolbarButtonState('audio', RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_ENABLED) ->assertMissing('div.meet-video.self .status .status-audio'); $guest->waitUntilMissing('div.meet-video:not(.self) .status .status-audio'); // Test muting audio with a keyboard shortcut (key 'm') $owner->driver->getKeyboard()->sendKeys('m'); $owner->assertToolbarButtonState('audio', RoomPage::BUTTON_ACTIVE | RoomPage::BUTTON_ENABLED) ->assertVisible('div.meet-video.self .status .status-audio'); $guest->waitFor('div.meet-video:not(.self) .status .status-audio') ->assertAudioMuted('div.meet-video:not(.self) video', true); // Test unmuting audio with a keyboard shortcut (key 'm') $owner->driver->getKeyboard()->sendKeys('m'); $owner->assertToolbarButtonState('audio', RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_ENABLED) ->assertMissing('div.meet-video.self .status .status-audio'); $guest->waitUntilMissing('div.meet-video:not(.self) .status .status-audio') ->assertAudioMuted('div.meet-video:not(.self) video', false); // Test muting video $owner->click('@menu button.link-video') ->assertToolbarButtonState('video', RoomPage::BUTTON_ACTIVE | RoomPage::BUTTON_ENABLED) ->assertVisible('div.meet-video.self .status .status-video'); // FIXME: It looks that we can't just check the element state - // We might consider using OpenVidu API to make sure + // We might consider using some API to make sure $guest->waitFor('div.meet-video:not(.self) .status .status-video'); // Test unmuting video $owner->click('@menu button.link-video') ->assertToolbarButtonState('video', RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_ENABLED) ->assertMissing('div.meet-video.self .status .status-video'); $guest->waitUntilMissing('div.meet-video:not(.self) .status .status-video'); // Test muting other user $guest->with('div.meet-video:not(.self)', function (Browser $browser) { $browser->click('.controls button.link-audio') ->assertAudioMuted('video', true) ->assertVisible('.controls button.link-audio.text-danger') ->click('.controls button.link-audio') ->assertAudioMuted('video', false) ->assertVisible('.controls button.link-audio:not(.text-danger)'); }); // Test volume control $guest->mouseover('@menu') ->with('div.meet-video:not(.self)', function (Browser $browser) { $browser->waitUntilMissing('.volume') ->mouseover('.controls button.link-audio') ->waitFor('.volume') ->assertValue('.volume input', '1') ->keys('.volume input', ['{arrow_down}']) ->keys('.volume input', ['{arrow_down}']) ->keys('.volume input', ['{arrow_down}']) ->keys('.volume input', ['{arrow_down}']) ->keys('.volume input', ['{arrow_down}']) ->keys('.volume input', ['{arrow_down}']) ->keys('.volume input', ['{arrow_down}']) ->keys('.volume input', ['{arrow_down}']) ->keys('.volume input', ['{arrow_down}']) ->keys('.volume input', ['{arrow_down}']) ->assertValue('.volume input', '0') ->assertAudioMuted('video', true) ->assertVisible('.controls button.link-audio.text-danger') ->click('.controls button.link-audio') ->assertAudioMuted('video', false) ->assertValue('.volume input', '1') ->click('.controls button.link-audio') ->assertAudioMuted('video', true) ->assertValue('.volume input', '0') ->keys('.volume input', ['{arrow_up}']) ->assertValue('.volume input', '0.1') ->assertAudioMuted('video', false) ->assertVisible('.controls button.link-audio:not(.text-danger)') ->mouseover('.meet-nickname') ->waitUntilMissing('.volume'); }); }); } /** * Test text chat * - * @group openvidu + * @group meet */ public function testChat(): void { $this->browse(function (Browser $owner, Browser $guest) { // Join the room as an owner $owner->visit(new RoomPage('john')) ->waitFor('@setup-form') ->waitUntilMissing('@setup-status-message.loading') ->type('@setup-nickname-input', 'john') ->clickWhenEnabled('@setup-button') ->waitFor('@session'); // In another browser act as a guest $guest->visit(new RoomPage('john')) ->waitFor('@setup-form') ->waitUntilMissing('@setup-status-message.loading') ->assertMissing('@setup-status-message') ->assertSeeIn('@setup-button', "JOIN") // Join the room, disable cam/mic ->select('@setup-mic-select', '') // ->select('@setup-cam-select', '') ->clickWhenEnabled('@setup-button') ->waitFor('@session'); // Test chat elements $owner->click('@menu button.link-chat') ->assertToolbarButtonState('chat', RoomPage::BUTTON_ACTIVE | RoomPage::BUTTON_ENABLED) ->assertVisible('@chat') ->assertVisible('@session') ->assertFocused('@chat-input') ->assertElementsCount('@chat-list .message', 0) ->keys('@chat-input', 'test1', '{enter}') ->assertValue('@chat-input', '') ->waitFor('@chat-list .message') ->assertElementsCount('@chat-list .message', 1) ->assertSeeIn('@chat-list .message .nickname', 'john') ->assertSeeIn('@chat-list .message div:last-child', 'test1'); $guest->waitFor('@menu button.link-chat .badge') ->assertTextRegExp('@menu button.link-chat .badge', '/^1$/') ->click('@menu button.link-chat') ->assertToolbarButtonState('chat', RoomPage::BUTTON_ACTIVE | RoomPage::BUTTON_ENABLED) ->assertMissing('@menu button.link-chat .badge') ->assertVisible('@chat') ->assertVisible('@session') ->assertElementsCount('@chat-list .message', 1) ->assertSeeIn('@chat-list .message .nickname', 'john') ->assertSeeIn('@chat-list .message div:last-child', 'test1'); // Test the number of (hidden) incoming messages $guest->click('@menu button.link-chat') ->assertMissing('@chat'); $owner->keys('@chat-input', 'test2', '{enter}', 'test3', '{enter}') ->assertElementsCount('@chat-list .message', 1) ->assertSeeIn('@chat-list .message .nickname', 'john') ->assertElementsCount('@chat-list .message div', 4) ->assertSeeIn('@chat-list .message div:last-child', 'test3'); $guest->waitFor('@menu button.link-chat .badge') ->assertSeeIn('@menu button.link-chat .badge', '2') ->click('@menu button.link-chat') ->assertElementsCount('@chat-list .message', 1) ->assertSeeIn('@chat-list .message .nickname', 'john') ->assertSeeIn('@chat-list .message div:last-child', 'test3') ->keys('@chat-input', 'guest1', '{enter}') ->assertElementsCount('@chat-list .message', 2) ->assertMissing('@chat-list .message:last-child .nickname') ->assertSeeIn('@chat-list .message:last-child div:last-child', 'guest1'); $owner->assertElementsCount('@chat-list .message', 2) ->assertMissing('@chat-list .message:last-child .nickname') ->assertSeeIn('@chat-list .message:last-child div:last-child', 'guest1'); // Test nickname change is propagated to chat messages $guest->setNickname('div.meet-video.self', 'guest') ->keys('@chat-input', 'guest2', '{enter}') ->assertElementsCount('@chat-list .message', 2) ->assertSeeIn('@chat-list .message:last-child .nickname', 'guest') ->assertSeeIn('@chat-list .message:last-child div:last-child', 'guest2'); $owner->assertElementsCount('@chat-list .message', 2) ->assertSeeIn('@chat-list .message:last-child .nickname', 'guest') ->assertSeeIn('@chat-list .message:last-child div:last-child', 'guest2'); // TODO: Test text chat features, e.g. link handling }); } /** * Test screen sharing * - * @group openvidu + * @group meet */ public function testShareScreen(): void { $this->browse(function (Browser $owner, Browser $guest) { // Join the room as an owner $owner->visit(new RoomPage('john')) ->waitFor('@setup-form') ->waitUntilMissing('@setup-status-message.loading') ->type('@setup-nickname-input', 'john') ->clickWhenEnabled('@setup-button') ->waitFor('@session'); // In another browser act as a guest $guest->visit(new RoomPage('john')) ->waitFor('@setup-form') ->waitUntilMissing('@setup-status-message.loading') // Join the room, disable cam/mic ->select('@setup-mic-select', '') ->select('@setup-cam-select', '') ->clickWhenEnabled('@setup-button') ->waitFor('@session'); // Test screen sharing $owner->assertToolbarButtonState('screen', RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_ENABLED) ->assertElementsCount('@session div.meet-video', 1) ->click('@menu button.link-screen') ->whenAvailable('div.meet-video:not(.self)', function (Browser $browser) { $browser->waitFor('video') ->assertSeeIn('.meet-nickname', 'john') ->assertVisible('.controls button.link-fullscreen') ->assertMissing('.controls button.link-audio') ->assertMissing('.status .status-audio') ->assertMissing('.status .status-video'); }) ->assertElementsCount('@session div.meet-video', 2) ->assertElementsCount('@subscribers .meet-subscriber', 1) ->assertToolbarButtonState('screen', RoomPage::BUTTON_ACTIVE | RoomPage::BUTTON_ENABLED); $guest ->whenAvailable('div.meet-video.screen', function (Browser $browser) { $browser->waitFor('video') ->assertSeeIn('.meet-nickname', 'john') ->assertVisible('.controls button.link-fullscreen') ->assertMissing('.controls button.link-audio') ->assertMissing('.status .status-audio') ->assertMissing('.status .status-video'); }) ->assertElementsCount('@session div.meet-video', 2) ->assertElementsCount('@subscribers .meet-subscriber', 1); }); } } diff --git a/src/tests/Browser/Meet/RoomInterpretersTest.php b/src/tests/Browser/Meet/RoomInterpretersTest.php index 0ccbb6db..78a5d839 100644 --- a/src/tests/Browser/Meet/RoomInterpretersTest.php +++ b/src/tests/Browser/Meet/RoomInterpretersTest.php @@ -1,170 +1,170 @@ setupTestRoom(); } public function tearDown(): void { $this->resetTestRoom(); parent::tearDown(); } /** * Test language interpreted channels functionality * - * @group openvidu + * @group meet */ public function testInterpreters(): void { $this->browse(function (Browser $owner, Browser $interpreter, Browser $guest) { $page = new RoomPage('john'); // Join the room as an owner (authenticate) $owner->visit($page) ->click('@setup-button') ->submitLogon('john@kolab.org', 'simple123') ->waitFor('@setup-form') ->waitUntilMissing('@setup-status-message.loading') ->type('@setup-nickname-input', 'John') ->clickWhenEnabled('@setup-button') ->waitFor('@session'); // In another browser act as a guest-to-be-interpreter $interpreter->visit($page) ->waitFor('@setup-form') ->waitUntilMissing('@setup-status-message.loading') ->assertMissing('@setup-status-message') ->assertSeeIn('@setup-button', "JOIN") ->type('@setup-nickname-input', 'Interpreter') ->clickWhenEnabled('@setup-button') ->waitFor('@session'); // Assert current UI state, make the guest an interpreter $owner->waitFor('@session .meet-video.self') ->waitFor('@session .meet-video:not(.self)') ->assertSeeIn('@session .meet-video:not(.self)', 'Interpreter') ->click('@session .meet-video:not(.self) .meet-nickname') ->whenAvailable('@session .meet-video:not(.self) .dropdown-menu', function ($browser) { $browser->assertVisible('.interpreting') ->assertSeeIn('.interpreting h6', 'Language interpreter') ->select('.interpreting select', 'en'); }) ->assertMissing('@session .meet-video:not(.self) .dropdown-menu') ->waitFor('@session div.meet-subscriber') ->assertUserIcon('@session div.meet-subscriber', RoomPage::ICO_INTERPRETER) ->assertToolbarButtonState('channel', RoomPage::BUTTON_ENABLED) ->assertMissing('@menu button.link-channel .badge'); // Assert current UI state $interpreter->waitFor('@session .meet-video.self svg.interpreter') ->assertUserIcon('@session .meet-video.self', RoomPage::ICO_INTERPRETER) ->assertMissing('@menu button.link-channel') ->assertAudioMuted('@session .meet-video.self video', true) // always muted video of self ->assertAudioMuted('@session .meet-video:not(.self) video', false); // unmuted other publisher (owner) // In another browser act as a guest-subscriber // Test using a channel by subscriber $guest->visit($page) ->waitFor('@setup-form') ->waitUntilMissing('@setup-status-message.loading') ->assertMissing('@setup-status-message') ->assertSeeIn('@setup-button', "JOIN") ->type('@setup-nickname-input', 'Guest') ->select('@setup-mic-select', '') ->select('@setup-cam-select', '') ->clickWhenEnabled('@setup-button') ->waitFor('@session .meet-subscriber.self') // self ->waitFor('@session .meet-subscriber:not(.self)') // interpreter ->waitFor('@session .meet-video') // owner ->waitFor('@menu button.link-channel') ->assertToolbarButtonState('channel', RoomPage::BUTTON_ENABLED) ->assertMissing('@menu button.link-channel .badge') ->assertUserIcon('@session .meet-subscriber.self', RoomPage::ICO_USER) // self ->assertUserIcon('@session .meet-subscriber:not(.self)', RoomPage::ICO_INTERPRETER) // interpreter ->assertMissing('@session .meet-subscriber:not(.self) video') // hidden interpreter video ->assertAudioMuted('@session .meet-video video', false) // unmuted owner ->assertAudioMuted('@session .meet-subscriber:not(.self) video', true) // muted interpreter // select a channel ->click('@menu button.link-channel') ->whenAvailable('#channel-select .dropdown-menu', function ($browser) { $browser->assertSeeIn('a:first-child.active', '- none -') ->assertSeeIn('a:nth-child(2):not(.active)', 'English') ->assertMissing('a:nth-child(3)') ->click('a:nth-child(2)'); }) ->waitFor('@menu button.link-channel .badge') ->assertSeeIn('@menu button.link-channel .badge', 'EN') ->assertAudioMuted('@session .meet-video video', true) // muted owner ->assertAudioMuted('@session .meet-subscriber:not(.self) video', false) // unmuted interpreter // Unselect a channel ->click('@menu button.link-channel') ->whenAvailable('#channel-select .dropdown-menu', function ($browser) { $browser->assertVisible('a:first-child:not(.active)') ->assertVisible('a:nth-child(2).active') ->click('a:first-child'); }) ->waitUntilMissing('@menu button.link-channel .badge') ->assertAudioMuted('@session .meet-video video', false) // unmuted owner ->assertAudioMuted('@session .meet-subscriber:not(.self) video', true); // muted interpreter // Test setting a channel by publisher $owner->assertUserIcon('@session .meet-video.self', RoomPage::ICO_MODERATOR) // self ->assertUserIcon('@session .meet-subscriber:nth-child(1)', RoomPage::ICO_INTERPRETER) // interpreter ->assertUserIcon('@session .meet-subscriber:nth-child(2)', RoomPage::ICO_USER) // guest-subscriber ->assertMissing('@session .meet-subscriber:nth-child(1) video') // hidden interpreter video ->assertAudioMuted('@session .meet-video video', true) // always muted owner's self video ->assertAudioMuted('@session .meet-subscriber:nth-child(1) video', true) // muted interpreter // select a channel ->click('@menu button.link-channel') ->whenAvailable('#channel-select .dropdown-menu', function ($browser) { $browser->assertSeeIn('a:first-child.active', '- none -') ->assertSeeIn('a:nth-child(2):not(.active)', 'English') ->assertMissing('a:nth-child(3)') ->click('a:nth-child(2)'); }) ->waitFor('@menu button.link-channel .badge') ->assertSeeIn('@menu button.link-channel .badge', 'EN') ->assertAudioMuted('@session .meet-video video', true) // always muted self video ->assertAudioMuted('@session .meet-subscriber:nth-child(1) video', false) // unmuted interpreter // Unselect a channel ->click('@menu button.link-channel') ->whenAvailable('#channel-select .dropdown-menu', function ($browser) { $browser->assertVisible('a:first-child:not(.active)') ->assertVisible('a:nth-child(2).active') ->click('a:first-child'); }) ->waitUntilMissing('@menu button.link-channel .badge') ->assertAudioMuted('@session .meet-video video', true) // always muted video of self ->assertAudioMuted('@session .meet-subscriber:nth-child(1) video', true); // muted interpreter // Remove interpreting role $owner->click('@session .meet-subscriber:nth-child(1) .meet-nickname') ->whenAvailable('@session .meet-subscriber:nth-child(1) .dropdown-menu', function ($browser) { $browser->assertSelected('.interpreting select', 'en') ->select('.interpreting select', ''); }) ->waitFor('div.meet-video:not(.self)') ->assertUserIcon('div.meet-video:not(.self)', RoomPage::ICO_USER) ->assertMissing('@menu button.link-channel'); $guest->waitUntilMissing('@menu button.link-channel'); // TODO: Test what happens for users with a selectd channel when the interpreter is removed // TODO: Test two interpreters }); } } diff --git a/src/tests/Browser/Meet/RoomModeratorTest.php b/src/tests/Browser/Meet/RoomModeratorTest.php index f1805ca3..cce8787a 100644 --- a/src/tests/Browser/Meet/RoomModeratorTest.php +++ b/src/tests/Browser/Meet/RoomModeratorTest.php @@ -1,167 +1,167 @@ setupTestRoom(); } public function tearDown(): void { $this->resetTestRoom(); parent::tearDown(); } /** * Test three users in a room, one will be promoted/demoted to/from a moderator * - * @group openvidu + * @group meet */ public function testModeratorPromotion(): void { $this->browse(function (Browser $browser, Browser $guest1, Browser $guest2) { // In one browser window join as a room owner $browser->visit(new RoomPage('john')) ->click('@setup-button') ->submitLogon('john@kolab.org', 'simple123') ->waitFor('@setup-form') ->waitUntilMissing('@setup-status-message.loading') ->type('@setup-nickname-input', 'John') ->select('@setup-mic-select', '') ->select('@setup-cam-select', '') ->clickWhenEnabled('@setup-button') ->waitFor('@session'); // In one browser window join as a guest (to be promoted) $guest1->visit(new RoomPage('john')) ->waitFor('@setup-form') ->waitUntilMissing('@setup-status-message.loading') ->assertMissing('@setup-status-message') ->assertSeeIn('@setup-button', "JOIN") ->type('@setup-nickname-input', 'Guest1') // Join the room, disable cam/mic ->select('@setup-mic-select', '') ->select('@setup-cam-select', '') ->clickWhenEnabled('@setup-button') ->waitFor('@session'); // In one browser window join as a guest $guest2->visit(new RoomPage('john')) ->waitFor('@setup-form') ->waitUntilMissing('@setup-status-message.loading') ->assertMissing('@setup-status-message') ->assertSeeIn('@setup-button', "JOIN") // Join the room, disable mic ->select('@setup-mic-select', '') ->clickWhenEnabled('@setup-button') ->waitFor('@session'); // Assert that only the owner is a moderator right now $guest1->waitFor('@session video') ->assertMissing('@session div.meet-video .meet-nickname') // guest2 ->assertVisible('@session div.meet-subscriber.self svg.user') // self ->assertMissing('@session div.meet-subscriber.self svg.moderator') // self ->assertMissing('@session div.meet-subscriber:not(.self) svg.user') // owner ->assertVisible('@session div.meet-subscriber:not(.self) svg.moderator') // owner ->click('@session div.meet-subscriber.self .meet-nickname') ->whenAvailable('@session div.meet-subscriber.self .dropdown-menu', function (Browser $browser) { $browser->assertMissing('.permissions'); }) ->click('@session div.meet-subscriber:not(.self) .meet-nickname') ->assertMissing('.dropdown-menu'); $guest2->waitFor('@session video') ->assertVisible('@session div.meet-video svg.user') // self ->assertMissing('@session div.meet-video svg.moderator') // self // the following 4 assertions used to be flaky on openvidu // because the order is different all the time ->assertMissing('@session div.meet-subscriber:nth-child(1) svg.user') // owner ->assertVisible('@session div.meet-subscriber:nth-child(1) svg.moderator') // owner ->assertVisible('@session div.meet-subscriber:nth-child(2) svg.user') // guest1 ->assertMissing('@session div.meet-subscriber:nth-child(2) svg.moderator'); // guest1 // Promote guest1 to a moderator $browser->waitFor('@session video') ->assertMissing('@session div.meet-subscriber.self svg.user') // self ->assertVisible('@session div.meet-subscriber.self svg.moderator') // self ->click('@session div.meet-subscriber.self .meet-nickname') ->whenAvailable('@session div.meet-subscriber.self .dropdown-menu', function (Browser $browser) { $browser->assertChecked('.action-role-moderator input') ->assertDisabled('.action-role-moderator input'); }) ->click('@session div.meet-subscriber:not(.self) .meet-nickname') ->whenAvailable('@session div.meet-subscriber:not(.self) .dropdown-menu', function (Browser $browser) { $browser->assertNotChecked('.action-role-moderator input') ->click('.action-role-moderator input'); }); // Assert that we have two moderators now $guest2->waitFor('@session div.meet-subscriber:nth-child(2) svg.moderator') ->assertMissing('@session div.meet-subscriber:nth-child(2) svg.user'); // guest1 $guest1->waitFor('@session div.meet-subscriber.self svg.moderator') ->assertMissing('@session div.meet-subscriber.self svg.user') // self ->assertVisible('@session div.meet-video svg.user') // guest2 ->assertMissing('@session div.meet-video svg.moderator') // guest2 ->assertMissing('@session div.meet-subscriber:not(.self) svg.user') // owner ->assertVisible('@session div.meet-subscriber:not(.self) svg.moderator') // owner ->click('@session div.meet-subscriber:not(.self) .meet-nickname') // owner ->assertMissing('@session div.meet-subscriber:not(.self) .dropdown-menu') ->click('@session div.meet-subscriber.self .meet-nickname') ->whenAvailable('@session div.meet-subscriber.self .dropdown-menu', function (Browser $browser) { $browser->assertChecked('.action-role-moderator input') ->assertEnabled('.action-role-moderator input') ->assertNotChecked('.action-role-publisher input') ->assertEnabled('.action-role-publisher input'); }); $browser->waitFor('@session div.meet-subscriber:not(.self) svg.moderator') ->assertMissing('@session div.meet-subscriber:not(.self) svg.user'); // Check if a moderator can unpublish another user $guest1->click('@session div.meet-video .meet-nickname') ->whenAvailable('@session div.meet-video .dropdown-menu', function (Browser $browser) { $browser->assertNotChecked('.action-role-moderator input') ->assertEnabled('.action-role-moderator input') ->assertChecked('.action-role-publisher input') ->assertEnabled('.action-role-publisher input') ->click('.action-role-publisher input'); }) ->waitUntilMissing('@session div.meet-video'); $guest2->waitUntilMissing('@session div.meet-video'); // Demote guest1 back to a normal user $browser->waitFor('@session div.meet-subscriber:nth-child(3)') ->click('@session') // somehow needed to make the next line invoke the menu ->click('@session div.meet-subscriber:nth-child(2) .meet-nickname') ->whenAvailable('@session div.meet-subscriber:nth-child(2) .dropdown-menu', function ($browser) { $browser->assertChecked('.action-role-moderator input') ->click('.action-role-moderator input'); }) ->waitFor('@session div.meet-subscriber:nth-child(2) svg.user') ->assertMissing('@session div.meet-subscriber:nth-child(2) svg.moderator'); $guest1->waitFor('@session div.meet-subscriber.self svg.user') ->assertMissing('@session div.meet-subscriber.self svg.moderator') ->click('@session div.meet-subscriber.self .meet-nickname') ->whenAvailable('@session .dropdown-menu', function (Browser $browser) { $browser->assertMissing('.permissions'); }); }); } } diff --git a/src/tests/Browser/Meet/RoomOptionsTest.php b/src/tests/Browser/Meet/RoomOptionsTest.php index d569d6bd..19feb4f4 100644 --- a/src/tests/Browser/Meet/RoomOptionsTest.php +++ b/src/tests/Browser/Meet/RoomOptionsTest.php @@ -1,326 +1,326 @@ setupTestRoom(); } public function tearDown(): void { $this->resetTestRoom(); parent::tearDown(); } /** * Test password protected room * - * @group openvidu + * @group meet */ public function testRoomPassword(): void { $this->browse(function (Browser $owner, Browser $guest) { $room = Room::where('name', 'john')->first(); // Join the room as an owner (authenticate) $owner->visit(new RoomPage('john')) ->click('@setup-button') ->submitLogon('john@kolab.org', 'simple123') ->waitFor('@setup-form') ->waitUntilMissing('@setup-status-message.loading') ->assertMissing('@setup-password-input') ->clickWhenEnabled('@setup-button') ->waitFor('@session') // Enter room option dialog ->click('@menu button.link-options') ->with(new Dialog('#room-options-dialog'), function (Browser $browser) use ($room) { $browser->assertSeeIn('@title', 'Room options') ->assertSeeIn('@button-action', 'Close') ->assertElementsCount('.modal-footer button', 1) ->assertSeeIn('#password-input .label', 'Password:') ->assertSeeIn('#password-input-text.text-muted', 'none') ->assertVisible('#password-input + small') ->assertSeeIn('#password-set-btn', 'Set password') ->assertElementsCount('#password-input button', 1) ->assertMissing('#password-input input') // Test setting a password ->click('#password-set-btn') ->assertMissing('#password-input-text') ->assertVisible('#password-input input') ->assertValue('#password-input input', '') ->assertSeeIn('#password-input #password-save-btn', 'Save') ->assertElementsCount('#password-input button', 1) ->type('#password-input input', 'pass') ->click('#password-input #password-save-btn') ->assertToast(Toast::TYPE_SUCCESS, 'Room configuration updated successfully.') ->assertMissing('#password-input input') ->assertSeeIn('#password-input-text:not(.text-muted)', 'pass') ->assertSeeIn('#password-clear-btn.btn-outline-danger', 'Clear password') ->assertElementsCount('#password-input button', 1) ->click('@button-action'); $this->assertSame('pass', $room->fresh()->getSetting('password')); }); // In another browser act as a guest, expect password required $guest->visit(new RoomPage('john')) ->waitFor('@setup-form') ->waitUntilMissing('@setup-status-message.loading') ->assertSeeIn('@setup-status-message', "Please, provide a valid password.") ->assertVisible('@setup-form .input-group:nth-child(4) svg') ->assertAttribute('@setup-form .input-group:nth-child(4) .input-group-text', 'title', 'Password') ->assertAttribute('@setup-password-input', 'placeholder', 'Password') ->assertValue('@setup-password-input', '') ->assertSeeIn('@setup-button', "JOIN") // Try to join w/o password ->clickWhenEnabled('@setup-button') ->waitFor('#setup-password.is-invalid') // Try to join with a valid password ->type('#setup-password', 'pass') ->clickWhenEnabled('@setup-button') ->waitFor('@session'); // Test removing the password $owner->click('@menu button.link-options') ->with(new Dialog('#room-options-dialog'), function (Browser $browser) use ($room) { $browser->assertSeeIn('@title', 'Room options') ->assertSeeIn('#password-input-text:not(.text-muted)', 'pass') ->assertSeeIn('#password-clear-btn.btn-outline-danger', 'Clear password') ->assertElementsCount('#password-input button', 1) ->click('#password-clear-btn') ->assertToast(Toast::TYPE_SUCCESS, "Room configuration updated successfully.") ->assertMissing('#password-input input') ->assertSeeIn('#password-input-text.text-muted', 'none') ->assertSeeIn('#password-set-btn', 'Set password') ->assertElementsCount('#password-input button', 1) ->click('@button-action'); $this->assertSame(null, $room->fresh()->getSetting('password')); }); }); } /** * Test locked room (denying the join request) * - * @group openvidu + * @group meet */ public function testLockedRoomDeny(): void { $this->browse(function (Browser $owner, Browser $guest) { $room = Room::where('name', 'john')->first(); // Join the room as an owner (authenticate) $owner->visit(new RoomPage('john')) // ->click('@setup-button') // ->submitLogon('john@kolab.org', 'simple123') ->waitFor('@setup-form') ->waitUntilMissing('@setup-status-message.loading') ->type('@setup-nickname-input', 'John') ->clickWhenEnabled('@setup-button') ->waitFor('@session') // Enter room option dialog ->click('@menu button.link-options') ->with(new Dialog('#room-options-dialog'), function (Browser $browser) use ($room) { $browser->assertSeeIn('@title', 'Room options') ->assertSeeIn('#room-lock label', 'Locked room:') ->assertVisible('#room-lock input[type=checkbox]:not(:checked)') ->assertVisible('#room-lock + small') // Test setting the lock ->click('#room-lock input') ->assertToast(Toast::TYPE_SUCCESS, "Room configuration updated successfully.") ->click('@button-action'); $this->assertSame('true', $room->fresh()->getSetting('locked')); }); // In another browser act as a guest $guest->visit(new RoomPage('john')) ->waitFor('@setup-form') ->waitUntilMissing('@setup-status-message.loading') ->assertButtonEnabled('@setup-button') ->assertSeeIn('@setup-button.btn-success', 'JOIN NOW') // try without the nickname ->clickWhenEnabled('@setup-button') ->waitFor('@setup-nickname-input.is-invalid') ->assertSeeIn( '@setup-status-message', "The room is locked. Please, enter your name and try again." ) ->assertMissing('@setup-password-input') ->assertButtonEnabled('@setup-button') ->assertSeeIn('@setup-button.btn-success', 'JOIN NOW') ->type('@setup-nickname-input', 'Guest') ->clickWhenEnabled('@setup-button') ->assertMissing('@setup-nickname-input.is-invalid') ->waitForText("Waiting for permission to join the room.") ->assertButtonDisabled('@setup-button'); // Test denying the request (this will also test custom toasts) $owner ->whenAvailable(new Toast(Toast::TYPE_CUSTOM), function ($browser) { $browser->assertToastTitle('Join request') ->assertVisible('.toast-header svg.fa-user') ->assertSeeIn('@message', 'Guest requested to join.') ->assertAttributeRegExp('@message img', 'src', '|^data:image|') ->assertSeeIn('@message button.accept.btn-success', 'Accept') ->assertSeeIn('@message button.deny.btn-danger', 'Deny') ->click('@message button.deny'); }) ->waitUntilMissing('.toast') // wait 10 seconds to make sure the request message does not show up again ->pause(10 * 1000) ->assertMissing('.toast'); }); } /** * Test locked room (accepting the join request, and dismissing a user) * - * @group openvidu + * @group meet */ public function testLockedRoomAcceptAndDismiss(): void { $this->browse(function (Browser $owner, Browser $guest) { $room = Room::where('name', 'john')->first(); // Join the room as an owner (authenticate) $owner->visit(new RoomPage('john')) // ->click('@setup-button') // ->submitLogon('john@kolab.org', 'simple123') ->waitFor('@setup-form') ->waitUntilMissing('@setup-status-message.loading') ->type('@setup-nickname-input', 'John') ->clickWhenEnabled('@setup-button') ->waitFor('@session') // Enter room option dialog ->click('@menu button.link-options') ->with(new Dialog('#room-options-dialog'), function (Browser $browser) use ($room) { $browser->assertSeeIn('@title', 'Room options') ->assertSeeIn('#room-lock label', 'Locked room:') ->assertVisible('#room-lock input[type=checkbox]:not(:checked)') ->assertVisible('#room-lock + small') // Test setting the lock ->click('#room-lock input') ->assertToast(Toast::TYPE_SUCCESS, "Room configuration updated successfully.") ->click('@button-action'); $this->assertSame('true', $room->fresh()->getSetting('locked')); }); // In another browser act as a guest $guest->visit(new RoomPage('john')) ->waitFor('@setup-form') ->waitUntilMissing('@setup-status-message.loading') ->type('@setup-nickname-input', 'guest') ->clickWhenEnabled('@setup-button') ->waitForText("Waiting for permission to join the room.") ->assertButtonDisabled('@setup-button'); $owner ->whenAvailable(new Toast(Toast::TYPE_CUSTOM), function ($browser) { $browser->assertToastTitle('Join request') ->assertSeeIn('@message', 'guest requested to join.') ->click('@message button.accept'); }); // Guest automatically anters the room $guest->waitFor('@session', 12) // make sure he has no access to the Options menu ->waitFor('@session .meet-video:not(.self)') ->assertSeeIn('@session .meet-video:not(.self) .meet-nickname', 'John') // TODO: Assert title and icon ->click('@session .meet-video:not(.self) .meet-nickname') ->pause(100) ->assertMissing('.dropdown-menu'); // Test dismissing the participant $owner->click('@session .meet-video:not(.self) .meet-nickname') ->waitFor('@session .meet-video:not(.self) .dropdown-menu') ->assertSeeIn('@session .meet-video:not(.self) .dropdown-menu > .action-dismiss', 'Dismiss') ->click('@session .meet-video:not(.self) .dropdown-menu > .action-dismiss') ->waitUntilMissing('.dropdown-menu') ->waitUntilMissing('@session .meet-video:not(.self)'); // Expect a "end of session" dialog on the participant side $guest->with(new Dialog('#leave-dialog'), function (Browser $browser) { $browser->assertSeeIn('@title', 'Room closed') ->assertSeeIn('@body', "The session has been closed by the room owner.") ->assertMissing('@button-cancel') ->assertSeeIn('@button-action', 'Close'); }); }); } /** * Test nomedia (subscribers only) feature * - * @group openvidu + * @group meet */ public function testSubscribersOnly(): void { $this->browse(function (Browser $owner, Browser $guest) { $room = Room::where('name', 'john')->first(); // Join the room as an owner (authenticate) $owner->visit(new RoomPage('john')) // ->click('@setup-button') // ->submitLogon('john@kolab.org', 'simple123') ->waitFor('@setup-form') ->waitUntilMissing('@setup-status-message.loading') ->type('@setup-nickname-input', 'John') ->clickWhenEnabled('@setup-button') ->waitFor('@session') // Enter room option dialog ->click('@menu button.link-options') ->with(new Dialog('#room-options-dialog'), function (Browser $browser) use ($room) { $browser->assertSeeIn('@title', 'Room options') ->assertSeeIn('#room-nomedia label', 'Subscribers only:') ->assertVisible('#room-nomedia input[type=checkbox]:not(:checked)') ->assertVisible('#room-nomedia + small') // Test enabling the option ->click('#room-nomedia input') ->assertToast(Toast::TYPE_SUCCESS, "Room configuration updated successfully.") ->click('@button-action'); $this->assertSame('true', $room->fresh()->getSetting('nomedia')); }); // In another browser act as a guest $guest->visit(new RoomPage('john')) ->waitFor('@setup-form') ->waitUntilMissing('@setup-status-message.loading') ->type('@setup-nickname-input', 'John') ->clickWhenEnabled('@setup-button') // expect the owner to have a video, but the guest to have none ->waitFor('@session .meet-video') ->waitFor('@session .meet-subscriber.self'); // Unset the option back $owner->click('@menu button.link-options') ->with(new Dialog('#room-options-dialog'), function (Browser $browser) use ($room) { $browser->assertVisible('#room-nomedia input[type=checkbox]:checked') // Test enabling the option ->click('#room-nomedia input') ->assertToast(Toast::TYPE_SUCCESS, "Room configuration updated successfully.") ->click('@button-action'); $this->assertSame(null, $room->fresh()->getSetting('nomedia')); }); }); } } diff --git a/src/tests/Browser/Meet/RoomQATest.php b/src/tests/Browser/Meet/RoomQATest.php index 276a0b78..05984fa1 100644 --- a/src/tests/Browser/Meet/RoomQATest.php +++ b/src/tests/Browser/Meet/RoomQATest.php @@ -1,139 +1,139 @@ setupTestRoom(); } public function tearDown(): void { $this->resetTestRoom(); parent::tearDown(); } /** * Test Q&A queue * - * @group openvidu + * @group meet */ public function testQA(): void { $this->browse(function (Browser $owner, Browser $guest1, Browser $guest2) { // Join the room as an owner (authenticate) $owner->visit(new RoomPage('john')) ->click('@setup-button') ->submitLogon('john@kolab.org', 'simple123') ->waitFor('@setup-form') ->waitUntilMissing('@setup-status-message.loading') ->select('@setup-mic-select', '') ->select('@setup-cam-select', '') ->type('@setup-nickname-input', 'John') ->clickWhenEnabled('@setup-button') ->waitFor('@session'); // In another browser act as a guest (1) $guest1->visit(new RoomPage('john')) ->waitFor('@setup-form') ->waitUntilMissing('@setup-status-message.loading') ->assertMissing('@setup-status-message') ->assertSeeIn('@setup-button', "JOIN") // Join the room, disable cam/mic ->select('@setup-mic-select', '') ->select('@setup-cam-select', '') ->type('@setup-nickname-input', 'Guest1') ->clickWhenEnabled('@setup-button') ->waitFor('@session'); // Assert current UI state $owner->assertToolbarButtonState('hand', RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_ENABLED) ->waitFor('div.meet-subscriber.self') ->assertMissing('@queue') ->click('@menu button.link-hand') ->waitFor('@queue .dropdown.self.moderated') ->assertSeeIn('@queue .dropdown.self.moderated', 'John') ->assertToolbarButtonState('hand', RoomPage::BUTTON_ACTIVE | RoomPage::BUTTON_ENABLED); // Assert current UI state $guest1->waitFor('@queue .dropdown') ->assertSeeIn('@queue .dropdown', 'John') ->assertElementsCount('@queue .dropdown', 1) ->waitFor('div.meet-subscriber.self') ->click('@menu button.link-hand') ->waitFor('@queue .dropdown.self') ->assertSeeIn('@queue .dropdown.self', 'Guest1') ->assertElementsCount('@queue .dropdown', 2) ->click('@menu button.link-hand') ->waitUntilMissing('@queue .dropdown.self') ->assertElementsCount('@queue .dropdown', 1) ->assertToolbarButtonState('hand', RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_ENABLED); // In another browser act as a guest (2) $guest2->visit(new RoomPage('john')) ->waitFor('@setup-form') ->waitUntilMissing('@setup-status-message.loading') ->assertMissing('@setup-status-message') ->assertSeeIn('@setup-button', "JOIN") ->type('@setup-nickname-input', 'Guest2') ->clickWhenEnabled('@setup-button') ->waitFor('@queue .dropdown') ->waitForTextIn('@queue .dropdown', 'John') ->assertElementsCount('@queue .dropdown', 1) ->assertMissing('@menu button.link-hand'); // Demote the guest (2) to subscriber, assert Hand button in toolbar $owner->click('@session div.meet-video .meet-nickname') ->whenAvailable('@session div.meet-video .dropdown-menu', function ($browser) { $browser->click('.action-role-publisher input'); }); // Guest (2) rises his hand $guest2->waitUntilMissing('@session .meet-video') ->waitFor('@menu button.link-hand') ->assertToolbarButtonState('hand', RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_ENABLED) ->click('@menu button.link-hand') ->waitFor('@queue .dropdown.self') ->assertElementsCount('@queue .dropdown', 2); // Promote guest (2) to publisher $owner->waitFor('@queue .dropdown:not(.self)') ->pause(8000) // wait until it's not moving, otherwise click() will fail ->click('@queue .dropdown:not(.self)') ->whenAvailable('@queue .dropdown:not(.self) .dropdown-menu', function ($browser) { $browser->click('.action-role-publisher input'); }) ->waitUntilMissing('@queue .dropdown:not(.self)') ->waitFor('@session .meet-video'); $guest1->waitFor('@session .meet-video') ->assertElementsCount('@queue .dropdown', 1); $guest2->waitFor('@session .meet-video') ->waitUntilMissing('@queue .dropdown.self') ->assertElementsCount('@queue .dropdown', 1); // Finally, do the same with the owner (last in the queue) $owner->click('@queue .dropdown.self') ->whenAvailable('@queue .dropdown.self .dropdown-menu', function ($browser) { $browser->click('.action-role-publisher input'); }) ->waitUntilMissing('@queue') ->waitFor('@session .meet-video.self'); $guest1->waitUntilMissing('@queue'); $guest2->waitUntilMissing('@queue'); }); } } diff --git a/src/tests/Browser/Meet/RoomSetupTest.php b/src/tests/Browser/Meet/RoomSetupTest.php index c6246b81..98878c66 100644 --- a/src/tests/Browser/Meet/RoomSetupTest.php +++ b/src/tests/Browser/Meet/RoomSetupTest.php @@ -1,570 +1,570 @@ setupTestRoom(); } public function tearDown(): void { $this->resetTestRoom(); parent::tearDown(); } /** * Test non-existing room * - * @group openvidu + * @group meet */ public function testRoomNonExistingRoom(): void { $this->browse(function (Browser $browser) { $browser->visit(new RoomPage('unknown')) ->within(new Menu(), function ($browser) { $browser->assertMenuItems(['signup', 'explore', 'blog', 'support', 'login', 'lang']); }); if ($browser->isDesktop()) { $browser->within(new Menu('footer'), function ($browser) { $browser->assertMenuItems(['signup', 'explore', 'blog', 'support', 'tos', 'login']); }); } else { $browser->assertMissing('#footer-menu .navbar-nav'); } // FIXME: Maybe it would be better to just display the usual 404 Not Found error page? $browser->assertMissing('@toolbar') ->assertMissing('@menu') ->assertMissing('@session') ->assertMissing('@chat') ->assertMissing('@login-form') ->assertVisible('@setup-form') ->assertSeeIn('@setup-status-message', "The room does not exist.") ->assertButtonDisabled('@setup-button'); }); } /** * Test the room setup page * - * @group openvidu + * @group meet */ public function testRoomSetup(): void { $this->browse(function (Browser $browser) { $browser->visit(new RoomPage('john')) ->within(new Menu(), function ($browser) { $browser->assertMenuItems(['signup', 'explore', 'blog', 'support', 'login', 'lang']); }); if ($browser->isDesktop()) { $browser->within(new Menu('footer'), function ($browser) { $browser->assertMenuItems(['signup', 'explore', 'blog', 'support', 'tos', 'login']); }); } else { $browser->assertMissing('#footer-menu .navbar-nav'); } // Note: I've found out that if I have another Chrome instance running // that uses media, here the media devices will not be available // TODO: Test enabling/disabling cam/mic in the setup widget $browser->assertMissing('@toolbar') ->assertMissing('@menu') ->assertMissing('@session') ->assertMissing('@chat') ->assertMissing('@login-form') ->assertVisible('@setup-form') ->assertSeeIn('@setup-title', 'Set up your session') ->assertVisible('@setup-video') ->assertVisible('@setup-form .input-group:nth-child(1) svg') ->assertAttribute('@setup-form .input-group:nth-child(1) .input-group-text', 'title', 'Microphone') ->assertVisible('@setup-mic-select') ->assertVisible('@setup-form .input-group:nth-child(2) svg') ->assertAttribute('@setup-form .input-group:nth-child(2) .input-group-text', 'title', 'Camera') ->assertVisible('@setup-cam-select') ->assertVisible('@setup-form .input-group:nth-child(3) svg') ->assertAttribute('@setup-form .input-group:nth-child(3) .input-group-text', 'title', 'Nickname') ->assertValue('@setup-nickname-input', '') ->assertAttribute('@setup-nickname-input', 'placeholder', 'Your name') ->assertMissing('@setup-password-input') ->assertSeeIn( '@setup-status-message', "The room is closed. Please, wait for the owner to start the session." ) ->assertSeeIn('@setup-button', "I'm the owner"); }); } /** * Test two users in a room (joining/leaving and some basic functionality) * - * @group openvidu + * @group meet * @depends testRoomSetup */ public function testTwoUsersInARoom(): void { $this->browse(function (Browser $browser, Browser $guest) { // In one browser window act as a guest $guest->visit(new RoomPage('john')) ->assertMissing('@toolbar') ->assertMissing('@menu') ->assertMissing('@session') ->assertMissing('@chat') ->assertMissing('@login-form') ->waitFor('@setup-form') ->waitUntilMissing('@setup-status-message.loading') ->assertSeeIn( '@setup-status-message', "The room is closed. Please, wait for the owner to start the session." ) ->assertSeeIn('@setup-button', "I'm the owner"); // In another window join the room as the owner (authenticate) $browser->on(new RoomPage('john')) ->assertSeeIn('@setup-button', "I'm the owner") ->clickWhenEnabled('@setup-button') ->assertMissing('@toolbar') ->assertMissing('@menu') ->assertMissing('@session') ->assertMissing('@chat') ->assertMissing('@setup-form') ->assertVisible('@login-form') ->submitLogon('john@kolab.org', 'simple123') ->waitFor('@setup-form') ->within(new Menu(), function ($browser) { $browser->assertMenuItems(['explore', 'blog', 'support', 'dashboard', 'logout', 'lang']); }); if ($browser->isDesktop()) { $browser->within(new Menu('footer'), function ($browser) { $browser->assertMenuItems(['explore', 'blog', 'support', 'tos', 'dashboard', 'logout']); }); } $browser->assertMissing('@login-form') ->waitUntilMissing('@setup-status-message.loading') ->waitFor('@setup-status-message') ->assertSeeIn('@setup-status-message', "The room is closed. It will be open for others after you join.") ->assertSeeIn('@setup-button', "JOIN") ->type('@setup-nickname-input', 'john') ->clickWhenEnabled('@setup-button') ->waitFor('@session') ->assertMissing('@setup-form') ->whenAvailable('div.meet-video.self', function (Browser $browser) { $browser->waitFor('video') ->assertSeeIn('.meet-nickname', 'john') ->assertVisible('.controls button.link-fullscreen') ->assertMissing('.controls button.link-audio') ->assertMissing('.status .status-audio') ->assertMissing('.status .status-video'); }) ->assertMissing('#header-menu') ->assertSeeIn('@counter', 1); if (!$browser->isPhone()) { $browser->assertMissing('#footer-menu'); } else { $browser->assertVisible('#footer-menu'); } // After the owner "opened the room" guest should be able to join $guest->waitUntilMissing('@setup-status-message', 10) ->assertSeeIn('@setup-button', "JOIN") // Join the room, disable cam/mic ->select('@setup-mic-select', '') //->select('@setup-cam-select', '') ->clickWhenEnabled('@setup-button') ->waitFor('@session') ->assertMissing('@setup-form') ->whenAvailable('div.meet-video.self', function (Browser $browser) { $browser->waitFor('video') ->assertVisible('.meet-nickname') ->assertVisible('.controls button.link-fullscreen') ->assertMissing('.controls button.link-audio') ->assertVisible('.status .status-audio') ->assertMissing('.status .status-video'); }) ->whenAvailable('div.meet-video:not(.self)', function (Browser $browser) { $browser->waitFor('video') ->assertSeeIn('.meet-nickname', 'john') ->assertVisible('.controls button.link-fullscreen') ->assertVisible('.controls button.link-audio') ->assertMissing('.status .status-audio') ->assertMissing('.status .status-video'); }) ->assertElementsCount('@session div.meet-video', 2) ->assertSeeIn('@counter', 2); // Check guest's elements in the owner's window $browser ->whenAvailable('div.meet-video:not(.self)', function (Browser $browser) { $browser->waitFor('video') ->assertVisible('.meet-nickname') ->assertVisible('.controls button.link-fullscreen') ->assertVisible('.controls button.link-audio') ->assertMissing('.controls button.link-setup') ->assertVisible('.status .status-audio') ->assertMissing('.status .status-video'); }) ->assertElementsCount('@session div.meet-video', 2); // Test leaving the room // Guest is leaving $guest->click('@menu button.link-logout') ->waitForLocation('/login') ->assertVisible('#header-menu'); // Expect the participant removed from other users windows $browser->waitUntilMissing('@session div.meet-video:not(.self)') ->assertSeeIn('@counter', 1); // Join the room as guest again $guest->visit(new RoomPage('john')) ->assertMissing('@toolbar') ->assertMissing('@menu') ->assertMissing('@session') ->assertMissing('@chat') ->assertMissing('@login-form') ->waitFor('@setup-form') ->waitUntilMissing('@setup-status-message.loading') ->assertMissing('@setup-status-message') ->assertSeeIn('@setup-button', "JOIN") // Join the room, disable cam/mic ->select('@setup-mic-select', '') //->select('@setup-cam-select', '') ->clickWhenEnabled('@setup-button') ->waitFor('@session'); // Leave the room as the room owner // TODO: Test leaving the room by closing the browser window, // it should not destroy the session $browser->click('@menu button.link-logout') ->waitForLocation('/dashboard'); // Expect other participants be informed about the end of the session $guest->with(new Dialog('#leave-dialog'), function (Browser $browser) { $browser->assertSeeIn('@title', 'Room closed') ->assertSeeIn('@body', "The session has been closed by the room owner.") ->assertMissing('@button-cancel') ->assertSeeIn('@button-action', 'Close') ->click('@button-action'); }) ->assertMissing('#leave-dialog') ->waitForLocation('/login'); }); } /** * Test two subscribers-only users in a room * - * @group openvidu + * @group meet * @depends testTwoUsersInARoom */ public function testSubscribers(): void { $this->browse(function (Browser $browser, Browser $guest) { // Join the room as the owner $browser->visit(new RoomPage('john')) ->waitFor('@setup-form') ->waitUntilMissing('@setup-status-message.loading') ->waitFor('@setup-status-message') ->type('@setup-nickname-input', 'john') ->select('@setup-mic-select', '') ->select('@setup-cam-select', '') ->clickWhenEnabled('@setup-button') ->waitFor('@session') ->assertMissing('@setup-form') ->whenAvailable('@subscribers .meet-subscriber.self', function (Browser $browser) { $browser->assertSeeIn('.meet-nickname', 'john'); }) ->assertElementsCount('@session div.meet-video', 0) ->assertElementsCount('@session video', 0) ->assertElementsCount('@session .meet-subscriber', 1) ->assertToolbar([ 'audio' => RoomPage::BUTTON_ACTIVE | RoomPage::BUTTON_DISABLED, 'video' => RoomPage::BUTTON_ACTIVE | RoomPage::BUTTON_DISABLED, 'screen' => RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_DISABLED, 'hand' => RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_ENABLED, 'chat' => RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_ENABLED, 'fullscreen' => RoomPage::BUTTON_ENABLED, 'options' => RoomPage::BUTTON_ENABLED, 'logout' => RoomPage::BUTTON_ENABLED, ]); // After the owner "opened the room" guest should be able to join // In one browser window act as a guest $guest->visit(new RoomPage('john')) ->waitUntilMissing('@setup-status-message', 10) ->assertSeeIn('@setup-button', "JOIN") // Join the room, disable cam/mic ->select('@setup-mic-select', '') ->select('@setup-cam-select', '') ->clickWhenEnabled('@setup-button') ->waitFor('@session') ->assertMissing('@setup-form') ->whenAvailable('@subscribers .meet-subscriber.self', function (Browser $browser) { $browser->assertVisible('.meet-nickname'); }) ->whenAvailable('@subscribers .meet-subscriber:not(.self)', function (Browser $browser) { $browser->assertSeeIn('.meet-nickname', 'john'); }) ->assertElementsCount('@session div.meet-video', 0) ->assertElementsCount('@session video', 0) ->assertElementsCount('@session div.meet-subscriber', 2) ->assertSeeIn('@counter', 2) ->assertToolbar([ 'audio' => RoomPage::BUTTON_ACTIVE | RoomPage::BUTTON_DISABLED, 'video' => RoomPage::BUTTON_ACTIVE | RoomPage::BUTTON_DISABLED, 'screen' => RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_DISABLED, 'hand' => RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_ENABLED, 'chat' => RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_ENABLED, 'fullscreen' => RoomPage::BUTTON_ENABLED, 'logout' => RoomPage::BUTTON_ENABLED, ]); // Check guest's elements in the owner's window $browser ->whenAvailable('@subscribers .meet-subscriber:not(.self)', function (Browser $browser) { $browser->assertVisible('.meet-nickname'); }) ->assertElementsCount('@session div.meet-video', 0) ->assertElementsCount('@session video', 0) ->assertElementsCount('@session .meet-subscriber', 2) ->assertSeeIn('@counter', 2); // Test leaving the room // Guest is leaving $guest->click('@menu button.link-logout') ->waitForLocation('/login'); // Expect the participant removed from other users windows $browser->waitUntilMissing('@session .meet-subscriber:not(.self)'); }); } /** * Test demoting publisher to a subscriber * - * @group openvidu + * @group meet * @depends testSubscribers */ public function testDemoteToSubscriber(): void { $this->browse(function (Browser $browser, Browser $guest1, Browser $guest2) { // Join the room as the owner $browser->visit(new RoomPage('john')) ->waitFor('@setup-form') ->waitUntilMissing('@setup-status-message.loading') ->waitFor('@setup-status-message') ->type('@setup-nickname-input', 'john') ->clickWhenEnabled('@setup-button') ->waitFor('@session') ->assertMissing('@setup-form') ->waitFor('@session video'); // In one browser window act as a guest $guest1->visit(new RoomPage('john')) ->waitUntilMissing('@setup-status-message', 10) ->assertSeeIn('@setup-button', "JOIN") ->clickWhenEnabled('@setup-button') ->waitFor('@session') ->assertMissing('@setup-form') ->waitFor('div.meet-video.self') ->waitFor('div.meet-video:not(.self)') ->assertElementsCount('@session div.meet-video', 2) ->assertElementsCount('@session video', 2) ->assertElementsCount('@session div.meet-subscriber', 0) // assert there's no moderator-related features for this guess available ->click('@session .meet-video.self .meet-nickname') ->whenAvailable('@session .meet-video.self .dropdown-menu', function (Browser $browser) { $browser->assertMissing('.permissions'); }) ->click('@session .meet-video:not(.self) .meet-nickname') ->pause(50) ->assertMissing('.dropdown-menu'); // Demote the guest to a subscriber $browser ->waitFor('div.meet-video.self video') ->waitFor('div.meet-video:not(.self) video') ->assertElementsCount('@session div.meet-video', 2) ->assertElementsCount('@session video', 2) ->assertElementsCount('@session .meet-subscriber', 0) ->click('@session .meet-video:not(.self) .meet-nickname') ->whenAvailable('@session .meet-video:not(.self) .dropdown-menu', function (Browser $browser) { $browser->assertSeeIn('.action-role-publisher', 'Audio & Video publishing') ->click('.action-role-publisher') ->waitUntilMissing('.dropdown-menu'); }) ->waitUntilMissing('@session .meet-video:not(.self)') ->waitFor('@session div.meet-subscriber') ->assertElementsCount('@session div.meet-video', 1) ->assertElementsCount('@session video', 1) ->assertElementsCount('@session div.meet-subscriber', 1); $guest1 ->waitUntilMissing('@session .meet-video.self') ->waitFor('@session div.meet-subscriber') ->assertElementsCount('@session div.meet-video', 1) ->assertElementsCount('@session video', 1) ->assertElementsCount('@session div.meet-subscriber', 1); // Join as another user to make sure the role change is propagated to new connections $guest2->visit(new RoomPage('john')) ->waitUntilMissing('@setup-status-message', 10) ->assertSeeIn('@setup-button', "JOIN") ->select('@setup-mic-select', '') ->select('@setup-cam-select', '') ->clickWhenEnabled('@setup-button') ->waitFor('@session') ->assertMissing('@setup-form') ->waitFor('div.meet-subscriber:not(.self)') ->assertElementsCount('@session div.meet-video', 1) ->assertElementsCount('@session video', 1) ->assertElementsCount('@session div.meet-subscriber', 2) ->click('@toolbar .link-logout'); // Promote the guest back to a publisher $browser ->click('@session .meet-subscriber .meet-nickname') ->whenAvailable('@session .meet-subscriber .dropdown-menu', function (Browser $browser) { $browser->assertSeeIn('.action-role-publisher', 'Audio & Video publishing') ->assertNotChecked('.action-role-publisher input') ->click('.action-role-publisher') ->waitUntilMissing('.dropdown-menu'); }) ->waitFor('@session .meet-video:not(.self) video') ->assertElementsCount('@session div.meet-video', 2) ->assertElementsCount('@session video', 2) ->assertElementsCount('@session div.meet-subscriber', 0); $guest1 ->with(new Dialog('#media-setup-dialog'), function (Browser $browser) { $browser->assertSeeIn('@title', 'Media setup') ->click('@button-action'); }) ->waitFor('@session .meet-video.self') ->assertElementsCount('@session div.meet-video', 2) ->assertElementsCount('@session video', 2) ->assertElementsCount('@session div.meet-subscriber', 0); // Demote the owner to a subscriber $browser ->click('@session .meet-video.self .meet-nickname') ->whenAvailable('@session .meet-video.self .dropdown-menu', function (Browser $browser) { $browser->assertSeeIn('.action-role-publisher', 'Audio & Video publishing') ->assertChecked('.action-role-publisher input') ->click('.action-role-publisher') ->waitUntilMissing('.dropdown-menu'); }) ->waitUntilMissing('@session .meet-video.self') ->waitFor('@session div.meet-subscriber.self') ->assertElementsCount('@session div.meet-video', 1) ->assertElementsCount('@session video', 1) ->assertElementsCount('@session div.meet-subscriber', 1); // Promote the owner to a publisher $browser ->click('@session .meet-subscriber.self .meet-nickname') ->whenAvailable('@session .meet-subscriber.self .dropdown-menu', function (Browser $browser) { $browser->assertSeeIn('.action-role-publisher', 'Audio & Video publishing') ->assertNotChecked('.action-role-publisher input') ->click('.action-role-publisher') ->waitUntilMissing('.dropdown-menu'); }) ->waitUntilMissing('@session .meet-subscriber.self') ->with(new Dialog('#media-setup-dialog'), function (Browser $browser) { $browser->assertSeeIn('@title', 'Media setup') ->click('@button-action'); }) ->waitFor('@session div.meet-video.self') ->assertElementsCount('@session div.meet-video', 2) ->assertElementsCount('@session video', 2) ->assertElementsCount('@session div.meet-subscriber', 0); }); } /** * Test the media setup dialog * - * @group openvidu + * @group meet * @depends testDemoteToSubscriber */ public function testMediaSetupDialog(): void { $this->browse(function (Browser $browser, $guest) { // Join the room as the owner $browser->visit(new RoomPage('john')) ->waitFor('@setup-form') ->waitUntilMissing('@setup-status-message.loading') ->waitFor('@setup-status-message') ->type('@setup-nickname-input', 'john') ->clickWhenEnabled('@setup-button') ->waitFor('@session') ->assertMissing('@setup-form'); // In one browser window act as a guest $guest->visit(new RoomPage('john')) ->waitUntilMissing('@setup-status-message', 10) ->assertSeeIn('@setup-button', "JOIN") ->select('@setup-mic-select', '') ->select('@setup-cam-select', '') ->clickWhenEnabled('@setup-button') ->waitFor('@session') ->assertMissing('@setup-form'); $browser->waitFor('@session video') ->click('.controls button.link-setup') ->with(new Dialog('#media-setup-dialog'), function (Browser $browser) { $browser->assertSeeIn('@title', 'Media setup') ->assertVisible('form video') ->assertVisible('form > div:nth-child(1) video') ->assertVisible('form > div:nth-child(1) .volume') ->assertVisible('form > div:nth-child(2) svg') ->assertAttribute('form > div:nth-child(2) .input-group-text', 'title', 'Microphone') ->assertVisible('form > div:nth-child(2) select') ->assertVisible('form > div:nth-child(3) svg') ->assertAttribute('form > div:nth-child(3) .input-group-text', 'title', 'Camera') ->assertVisible('form > div:nth-child(3) select') ->assertMissing('@button-cancel') ->assertSeeIn('@button-action', 'Close') ->click('@button-action'); }) ->assertMissing('#media-setup-dialog') // Test mute audio and video ->click('.controls button.link-setup') ->with(new Dialog('#media-setup-dialog'), function (Browser $browser) { $browser->select('form > div:nth-child(2) select', '') ->select('form > div:nth-child(3) select', '') ->click('@button-action'); }) ->assertMissing('#media-setup-dialog') ->assertVisible('@session .meet-video .status .status-audio') ->assertVisible('@session .meet-video .status .status-video'); $guest->waitFor('@session video') ->waitFor('@session .meet-video .status .status-audio') ->assertVisible('@session .meet-video .status .status-video'); }); } } diff --git a/src/tests/Browser/Meet/RoomsTest.php b/src/tests/Browser/Meet/RoomsTest.php index 8e76517b..2956ed12 100644 --- a/src/tests/Browser/Meet/RoomsTest.php +++ b/src/tests/Browser/Meet/RoomsTest.php @@ -1,110 +1,110 @@ clearMeetEntitlements(); } /** * {@inheritDoc} */ public function tearDown(): void { $this->clearMeetEntitlements(); parent::tearDown(); } /** * Test rooms page (unauthenticated and unauthorized) * - * @group openvidu + * @group meet */ public function testRoomsUnauth(): void { // Test that the page requires authentication $this->browse(function (Browser $browser) { $browser->visit('/rooms') ->on(new Home()) // User has no 'meet' entitlement yet, expect redirect to error page ->submitLogon('john@kolab.org', 'simple123', false) ->waitFor('#app > #error-page') ->assertSeeIn('#error-page .code', '403') ->assertSeeIn('#error-page .message', 'Access denied'); }); } /** * Test rooms page * - * @group openvidu + * @group meet */ public function testRooms(): void { $this->browse(function (Browser $browser) { $href = \config('app.url') . '/meet/john'; $john = $this->getTestUser('john@kolab.org'); // User has no 'meet' entitlement yet $browser->visit('/login') ->on(new Home()) ->submitLogon('john@kolab.org', 'simple123', true) ->on(new Dashboard()) ->assertMissing('@links a.link-chat'); // Goto user subscriptions, and enable 'meet' subscription $browser->visit('/user/' . $john->id) ->on(new UserInfo()) ->whenAvailable('@skus', function ($browser) { $browser->click('#sku-input-meet'); }) ->click('button[type=submit]') ->assertToast(Toast::TYPE_SUCCESS, 'User data updated successfully.') ->click('.navbar-brand') ->on(new Dashboard()) ->assertSeeIn('@links a.link-chat', 'Video chat') // Make sure the element also exists on Dashboard page load ->refresh() ->on(new Dashboard()) ->assertSeeIn('@links a.link-chat', 'Video chat'); // Test Video chat page $browser->click('@links a.link-chat') ->waitFor('#meet-rooms') ->waitFor('.card-text a') ->assertSeeIn('.card-title', 'Voice & Video Conferencing') ->assertSeeIn('.card-text a', $href) ->assertAttribute('.card-text a', 'href', '/meet/john') ->click('.card-text a') ->on(new RoomPage('john')) // check that entering the room skips the logon form ->assertMissing('@toolbar') ->assertMissing('@menu') ->assertMissing('@session') ->assertMissing('@chat') ->assertMissing('@login-form') ->assertVisible('@setup-form') ->assertSeeIn('@setup-status-message', "The room is closed. It will be open for others after you join.") ->assertSeeIn('@setup-button', "JOIN") ->click('@setup-button') ->waitFor('@session') ->assertMissing('@setup-form'); }); } } diff --git a/src/tests/Feature/Controller/OpenViduTest.php b/src/tests/Feature/Controller/MeetTest.php similarity index 80% rename from src/tests/Feature/Controller/OpenViduTest.php rename to src/tests/Feature/Controller/MeetTest.php index abcba3fd..45b6bd10 100644 --- a/src/tests/Feature/Controller/OpenViduTest.php +++ b/src/tests/Feature/Controller/MeetTest.php @@ -1,450 +1,450 @@ clearMeetEntitlements(); $room = Room::where('name', 'john')->first(); $room->setSettings(['password' => null, 'locked' => null, 'nomedia' => null]); } public function tearDown(): void { $this->clearMeetEntitlements(); $room = Room::where('name', 'john')->first(); $room->setSettings(['password' => null, 'locked' => null, 'nomedia' => null]); parent::tearDown(); } /** * Test listing user rooms * - * @group openvidu + * @group meet */ public function testIndex(): void { $john = $this->getTestUser('john@kolab.org'); $jack = $this->getTestUser('jack@kolab.org'); Room::where('user_id', $jack->id)->delete(); // Unauth access not allowed - $response = $this->get("api/v4/openvidu/rooms"); + $response = $this->get("api/v4/meet/rooms"); $response->assertStatus(401); // John has one room - $response = $this->actingAs($john)->get("api/v4/openvidu/rooms"); + $response = $this->actingAs($john)->get("api/v4/meet/rooms"); $response->assertStatus(200); $json = $response->json(); $this->assertSame(1, $json['count']); $this->assertCount(1, $json['list']); $this->assertSame('john', $json['list'][0]['name']); // Jack has no room, but it will be auto-created - $response = $this->actingAs($jack)->get("api/v4/openvidu/rooms"); + $response = $this->actingAs($jack)->get("api/v4/meet/rooms"); $response->assertStatus(200); $json = $response->json(); $this->assertSame(1, $json['count']); $this->assertCount(1, $json['list']); $this->assertMatchesRegularExpression('/^[0-9a-z-]{11}$/', $json['list'][0]['name']); } /** * Test joining the room * - * @group openvidu + * @group meet */ public function testJoinRoom(): void { $john = $this->getTestUser('john@kolab.org'); $jack = $this->getTestUser('jack@kolab.org'); $room = Room::where('name', 'john')->first(); $room->session_id = null; $room->save(); $this->assignMeetEntitlement($john); // Unauth access, no session yet - $response = $this->post("api/v4/openvidu/rooms/{$room->name}"); + $response = $this->post("api/v4/meet/rooms/{$room->name}"); $response->assertStatus(422); $json = $response->json(); $this->assertSame(323, $json['code']); // Non-existing room name - $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/non-existing"); + $response = $this->actingAs($john)->post("api/v4/meet/rooms/non-existing"); $response->assertStatus(404); // TODO: Test accessing an existing room of deleted owner // Non-owner, no session yet - $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}"); + $response = $this->actingAs($jack)->post("api/v4/meet/rooms/{$room->name}"); $response->assertStatus(422); $json = $response->json(); $this->assertSame(323, $json['code']); // Room owner, no session yet - $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}"); + $response = $this->actingAs($john)->post("api/v4/meet/rooms/{$room->name}"); $response->assertStatus(422); $json = $response->json(); $this->assertSame(324, $json['code']); - $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}", ['init' => 1]); + $response = $this->actingAs($john)->post("api/v4/meet/rooms/{$room->name}", ['init' => 1]); $response->assertStatus(200); $json = $response->json(); $session_id = $room->fresh()->session_id; $this->assertSame(Room::ROLE_SUBSCRIBER | Room::ROLE_MODERATOR | Room::ROLE_OWNER, $json['role']); $this->assertMatchesRegularExpression('|^wss?://|', $json['token']); $this->assertMatchesRegularExpression('|&roomId=' . $session_id . '|', $json['token']); $john_token = $json['token']; // Non-owner, now the session exists, no 'init' argument - $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}"); + $response = $this->actingAs($jack)->post("api/v4/meet/rooms/{$room->name}"); $response->assertStatus(422); $json = $response->json(); $this->assertSame(322, $json['code']); $this->assertTrue(empty($json['token'])); // Non-owner, now the session exists, with 'init', but no 'canPublish' argument - $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}", ['init' => 1]); + $response = $this->actingAs($jack)->post("api/v4/meet/rooms/{$room->name}", ['init' => 1]); $response->assertStatus(200); $json = $response->json(); $this->assertSame(Room::ROLE_SUBSCRIBER, $json['role']); $this->assertMatchesRegularExpression('|^wss?://|', $json['token']); $this->assertMatchesRegularExpression('|&roomId=' . $session_id . '|', $json['token']); $this->assertTrue($json['token'] != $john_token); // Non-owner, now the session exists, with 'init', and with 'role=PUBLISHER' $post = ['canPublish' => true, 'init' => 1]; - $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}", $post); + $response = $this->actingAs($jack)->post("api/v4/meet/rooms/{$room->name}", $post); $response->assertStatus(200); $json = $response->json(); $this->assertSame(Room::ROLE_PUBLISHER, $json['role']); $this->assertMatchesRegularExpression('|^wss?://|', $json['token']); $this->assertMatchesRegularExpression('|&roomId=' . $session_id . '|', $json['token']); $this->assertTrue($json['token'] != $john_token); $this->assertEmpty($json['config']['password']); $this->assertEmpty($json['config']['requires_password']); // Non-owner, password protected room, password not provided $room->setSettings(['password' => 'pass']); - $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}"); + $response = $this->actingAs($jack)->post("api/v4/meet/rooms/{$room->name}"); $response->assertStatus(422); $json = $response->json(); $this->assertCount(4, $json); $this->assertSame(325, $json['code']); $this->assertSame('error', $json['status']); $this->assertSame('Failed to join the session. Invalid password.', $json['message']); $this->assertEmpty($json['config']['password']); $this->assertTrue($json['config']['requires_password']); // Non-owner, password protected room, invalid provided - $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}", ['password' => 'aa']); + $response = $this->actingAs($jack)->post("api/v4/meet/rooms/{$room->name}", ['password' => 'aa']); $response->assertStatus(422); $json = $response->json(); $this->assertSame(325, $json['code']); // Non-owner, password protected room, valid password provided // TODO: Test without init=1 $post = ['password' => 'pass', 'init' => 'init']; - $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}", $post); + $response = $this->actingAs($jack)->post("api/v4/meet/rooms/{$room->name}", $post); $response->assertStatus(200); // Make sure the room owner can access the password protected room w/o password // TODO: Test without init=1 $post = ['init' => 'init']; - $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}", $post); + $response = $this->actingAs($john)->post("api/v4/meet/rooms/{$room->name}", $post); $response->assertStatus(200); // Test 'nomedia' room option $room->setSettings(['nomedia' => 'true', 'password' => null]); $post = ['init' => 'init', 'canPublish' => true]; - $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}", $post); + $response = $this->actingAs($john)->post("api/v4/meet/rooms/{$room->name}", $post); $response->assertStatus(200); $json = $response->json(); $this->assertSame(Room::ROLE_PUBLISHER & $json['role'], Room::ROLE_PUBLISHER); $post = ['init' => 'init', 'canPublish' => true]; - $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}", $post); + $response = $this->actingAs($jack)->post("api/v4/meet/rooms/{$room->name}", $post); $response->assertStatus(200); $json = $response->json(); $this->assertSame(Room::ROLE_PUBLISHER & $json['role'], 0); } /** * Test locked room and join requests * - * @group openvidu + * @group meet */ public function testJoinRequests(): void { $john = $this->getTestUser('john@kolab.org'); $jack = $this->getTestUser('jack@kolab.org'); $room = Room::where('name', 'john')->first(); $room->session_id = null; $room->save(); $room->setSettings(['password' => null, 'locked' => 'true']); $this->assignMeetEntitlement($john); // Create the session (also makes sure the owner can access a locked room) - $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}", ['init' => 1]); + $response = $this->actingAs($john)->post("api/v4/meet/rooms/{$room->name}", ['init' => 1]); $response->assertStatus(200); // Non-owner, locked room, invalid/missing input - $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}"); + $response = $this->actingAs($jack)->post("api/v4/meet/rooms/{$room->name}"); $response->assertStatus(422); $json = $response->json(); $this->assertCount(4, $json); $this->assertSame(326, $json['code']); $this->assertSame('error', $json['status']); $this->assertSame('Failed to join the session. Room locked.', $json['message']); $this->assertTrue($json['config']['locked']); // Non-owner, locked room, invalid requestId $post = ['nickname' => 'name', 'requestId' => '-----', 'init' => 1]; - $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}", $post); + $response = $this->actingAs($jack)->post("api/v4/meet/rooms/{$room->name}", $post); $response->assertStatus(422); $json = $response->json(); $this->assertSame(326, $json['code']); // Non-owner, locked room, invalid requestId $post = ['nickname' => 'name', 'init' => 1]; - $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}", $post); + $response = $this->actingAs($jack)->post("api/v4/meet/rooms/{$room->name}", $post); $response->assertStatus(422); $json = $response->json(); $this->assertSame(326, $json['code']); // Non-owner, locked room, valid input $reqId = '12345678'; $post = ['nickname' => 'name', 'requestId' => $reqId, 'picture' => 'data:image/png;base64,01234']; - $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}", $post); + $response = $this->actingAs($jack)->post("api/v4/meet/rooms/{$room->name}", $post); $response->assertStatus(422); $json = $response->json(); $this->assertCount(4, $json); $this->assertSame(327, $json['code']); $this->assertSame('error', $json['status']); $this->assertSame('Failed to join the session. Room locked.', $json['message']); $this->assertTrue($json['config']['locked']); $room->refresh(); $request = $room->requestGet($reqId); $this->assertSame($post['nickname'], $request['nickname']); $this->assertSame($post['requestId'], $request['requestId']); $room->requestAccept($reqId); // Non-owner, locked room, join request accepted $post['init'] = 1; $post['canPublish'] = true; - $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}", $post); + $response = $this->actingAs($jack)->post("api/v4/meet/rooms/{$room->name}", $post); $response->assertStatus(200); $json = $response->json(); $this->assertSame(Room::ROLE_PUBLISHER, $json['role']); $this->assertMatchesRegularExpression('|^wss?://|', $json['token']); // TODO: Test a scenario where both password and lock are enabled // TODO: Test accepting/denying as a non-owner moderator // TODO: Test somehow websocket communication $this->markTestIncomplete(); } /** * Test joining the room * - * @group openvidu + * @group meet * @depends testJoinRoom */ public function testJoinRoomGuest(): void { $this->assignMeetEntitlement('john@kolab.org'); // There's no easy way to logout the user in the same test after // using actingAs(). That's why this is moved to a separate test $room = Room::where('name', 'john')->first(); // Guest, request with screenShare token $post = ['canPublish' => true, 'screenShare' => 1, 'init' => 1]; - $response = $this->post("api/v4/openvidu/rooms/{$room->name}", $post); + $response = $this->post("api/v4/meet/rooms/{$room->name}", $post); $response->assertStatus(200); $json = $response->json(); $this->assertSame(Room::ROLE_PUBLISHER, $json['role']); $this->assertMatchesRegularExpression('|^wss?://|', $json['token']); } /** * Test configuring the room (session) * - * @group openvidu + * @group meet */ public function testSetRoomConfig(): void { $john = $this->getTestUser('john@kolab.org'); $jack = $this->getTestUser('jack@kolab.org'); $room = Room::where('name', 'john')->first(); // Unauth access not allowed - $response = $this->post("api/v4/openvidu/rooms/{$room->name}/config", []); + $response = $this->post("api/v4/meet/rooms/{$room->name}/config", []); $response->assertStatus(401); // Non-existing room name - $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/non-existing/config", []); + $response = $this->actingAs($john)->post("api/v4/meet/rooms/non-existing/config", []); $response->assertStatus(404); // TODO: Test a room with a deleted owner // Non-owner - $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}/config", []); + $response = $this->actingAs($jack)->post("api/v4/meet/rooms/{$room->name}/config", []); $response->assertStatus(403); // Room owner - $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}/config", []); + $response = $this->actingAs($john)->post("api/v4/meet/rooms/{$room->name}/config", []); $response->assertStatus(200); $json = $response->json(); $this->assertCount(2, $json); $this->assertSame('success', $json['status']); $this->assertSame("Room configuration updated successfully.", $json['message']); // Set password and room lock $post = ['password' => 'aaa', 'locked' => 1]; - $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}/config", $post); + $response = $this->actingAs($john)->post("api/v4/meet/rooms/{$room->name}/config", $post); $response->assertStatus(200); $json = $response->json(); $this->assertCount(2, $json); $this->assertSame('success', $json['status']); $this->assertSame("Room configuration updated successfully.", $json['message']); $room->refresh(); $this->assertSame('aaa', $room->getSetting('password')); $this->assertSame('true', $room->getSetting('locked')); // Unset password and room lock $post = ['password' => '', 'locked' => 0]; - $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}/config", $post); + $response = $this->actingAs($john)->post("api/v4/meet/rooms/{$room->name}/config", $post); $response->assertStatus(200); $json = $response->json(); $this->assertCount(2, $json); $this->assertSame('success', $json['status']); $this->assertSame("Room configuration updated successfully.", $json['message']); $room->refresh(); $this->assertSame(null, $room->getSetting('password')); $this->assertSame(null, $room->getSetting('locked')); // Test invalid option error $post = ['password' => 'eee', 'unknown' => 0]; - $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}/config", $post); + $response = $this->actingAs($john)->post("api/v4/meet/rooms/{$room->name}/config", $post); $response->assertStatus(422); $json = $response->json(); $this->assertCount(2, $json); $this->assertSame('error', $json['status']); $this->assertSame("Invalid room configuration option.", $json['errors']['unknown']); $room->refresh(); $this->assertSame(null, $room->getSetting('password')); } /** * Test the webhook * - * @group openvidu + * @group meet */ public function testWebhook(): void { $this->assignMeetEntitlement('john@kolab.org'); $john = $this->getTestUser('john@kolab.org'); $room = Room::where('name', 'john')->first(); $headers = ['X-Auth-Token' => \config('meet.webhook_token')]; // First, create the session $post = ['init' => 1]; - $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}", $post); + $response = $this->actingAs($john)->post("api/v4/meet/rooms/{$room->name}", $post); $response->assertStatus(200); $sessionId = $room->fresh()->session_id; // Test accepting a join request $room->requestSave('1234', ['nickname' => 'test']); $post = ['roomId' => $sessionId, 'requestId' => '1234', 'event' => 'joinRequestAccepted']; $response = $this->post("api/webhooks/meet", $post); $response->assertStatus(403); // 403 because no auth token $response = $this->withHeaders($headers)->post("api/webhooks/meet", $post); $response->assertStatus(200); $request = $room->requestGet('1234'); $this->assertSame(Room::REQUEST_ACCEPTED, $request['status']); // Test denying a join request $room->requestSave('1234', ['nickname' => 'test']); $post = ['roomId' => $sessionId, 'requestId' => '1234', 'event' => 'joinRequestDenied']; $response = $this->withHeaders($headers)->post("api/webhooks/meet", $post); $response->assertStatus(200); $request = $room->requestGet('1234'); $this->assertSame(Room::REQUEST_DENIED, $request['status']); // Test closing the session $post = ['roomId' => $sessionId, 'event' => 'roomClosed']; $response = $this->withHeaders($headers)->post("api/webhooks/meet", $post); $response->assertStatus(200); $this->assertNull($room->fresh()->session_id); } } diff --git a/src/tests/TestCaseMeetTrait.php b/src/tests/TestCaseMeetTrait.php index 862ec5d5..2f4a2265 100644 --- a/src/tests/TestCaseMeetTrait.php +++ b/src/tests/TestCaseMeetTrait.php @@ -1,56 +1,56 @@ getTestUser($user); } $user->assignSku(\App\Sku::where('title', 'meet')->first()); } /** * Removes all 'meet' entitlements from the database */ protected function clearMeetEntitlements(): void { $meet_sku = \App\Sku::where('title', 'meet')->first(); \App\Entitlement::where('sku_id', $meet_sku->id)->delete(); } /** * Reset a room after tests */ public function resetTestRoom($room_name = 'john'): void { $this->clearMeetEntitlements(); $room = Room::where('name', $room_name)->first(); $room->setSettings(['password' => null, 'locked' => null, 'nomedia' => null]); if ($room->session_id) { $room->session_id = null; $room->save(); } } /** * Prepare a room for testing */ public function setupTestRoom($room_name = 'john'): void { $this->resetTestRoom($room_name); $this->assignMeetEntitlement('john@kolab.org'); } }
{{ $t('meet.leave-body') }}
{{ $t('meet.welcome') }}
{{ $t('meet.url') }}
{{ href }}
{{ $t('meet.notice') }}
{{ $t('meet.beta-notice') }}
') ->clickWhenEnabled('@setup-button') ->assertMissing('@setup-nickname-input.is-invalid') ->waitForText("Waiting for permission to join the room.") ->assertButtonDisabled('@setup-button'); // Test denying the request (this will also test custom toasts) $owner ->whenAvailable(new Toast(Toast::TYPE_CUSTOM), function ($browser) { $browser->assertToastTitle('Join request') ->assertVisible('.toast-header svg.fa-user') ->assertSeeIn('@message', 'Guest
requested to join.') ->assertAttributeRegExp('@message img', 'src', '|^data:image|') ->assertSeeIn('@message button.accept.btn-success', 'Accept') ->assertSeeIn('@message button.deny.btn-danger', 'Deny') ->click('@message button.deny'); }) ->waitUntilMissing('.toast') // wait 10 seconds to make sure the request message does not show up again ->pause(10 * 1000) ->assertMissing('.toast'); }); } /** * Test locked room (accepting the join request, and dismissing a user) * - * @group openvidu + * @group meet */ public function testLockedRoomAcceptAndDismiss(): void { $this->browse(function (Browser $owner, Browser $guest) { $room = Room::where('name', 'john')->first(); // Join the room as an owner (authenticate) $owner->visit(new RoomPage('john')) // ->click('@setup-button') // ->submitLogon('john@kolab.org', 'simple123') ->waitFor('@setup-form') ->waitUntilMissing('@setup-status-message.loading') ->type('@setup-nickname-input', 'John') ->clickWhenEnabled('@setup-button') ->waitFor('@session') // Enter room option dialog ->click('@menu button.link-options') ->with(new Dialog('#room-options-dialog'), function (Browser $browser) use ($room) { $browser->assertSeeIn('@title', 'Room options') ->assertSeeIn('#room-lock label', 'Locked room:') ->assertVisible('#room-lock input[type=checkbox]:not(:checked)') ->assertVisible('#room-lock + small') // Test setting the lock ->click('#room-lock input') ->assertToast(Toast::TYPE_SUCCESS, "Room configuration updated successfully.") ->click('@button-action'); $this->assertSame('true', $room->fresh()->getSetting('locked')); }); // In another browser act as a guest $guest->visit(new RoomPage('john')) ->waitFor('@setup-form') ->waitUntilMissing('@setup-status-message.loading') ->type('@setup-nickname-input', 'guest') ->clickWhenEnabled('@setup-button') ->waitForText("Waiting for permission to join the room.") ->assertButtonDisabled('@setup-button'); $owner ->whenAvailable(new Toast(Toast::TYPE_CUSTOM), function ($browser) { $browser->assertToastTitle('Join request') ->assertSeeIn('@message', 'guest requested to join.') ->click('@message button.accept'); }); // Guest automatically anters the room $guest->waitFor('@session', 12) // make sure he has no access to the Options menu ->waitFor('@session .meet-video:not(.self)') ->assertSeeIn('@session .meet-video:not(.self) .meet-nickname', 'John') // TODO: Assert title and icon ->click('@session .meet-video:not(.self) .meet-nickname') ->pause(100) ->assertMissing('.dropdown-menu'); // Test dismissing the participant $owner->click('@session .meet-video:not(.self) .meet-nickname') ->waitFor('@session .meet-video:not(.self) .dropdown-menu') ->assertSeeIn('@session .meet-video:not(.self) .dropdown-menu > .action-dismiss', 'Dismiss') ->click('@session .meet-video:not(.self) .dropdown-menu > .action-dismiss') ->waitUntilMissing('.dropdown-menu') ->waitUntilMissing('@session .meet-video:not(.self)'); // Expect a "end of session" dialog on the participant side $guest->with(new Dialog('#leave-dialog'), function (Browser $browser) { $browser->assertSeeIn('@title', 'Room closed') ->assertSeeIn('@body', "The session has been closed by the room owner.") ->assertMissing('@button-cancel') ->assertSeeIn('@button-action', 'Close'); }); }); } /** * Test nomedia (subscribers only) feature * - * @group openvidu + * @group meet */ public function testSubscribersOnly(): void { $this->browse(function (Browser $owner, Browser $guest) { $room = Room::where('name', 'john')->first(); // Join the room as an owner (authenticate) $owner->visit(new RoomPage('john')) // ->click('@setup-button') // ->submitLogon('john@kolab.org', 'simple123') ->waitFor('@setup-form') ->waitUntilMissing('@setup-status-message.loading') ->type('@setup-nickname-input', 'John') ->clickWhenEnabled('@setup-button') ->waitFor('@session') // Enter room option dialog ->click('@menu button.link-options') ->with(new Dialog('#room-options-dialog'), function (Browser $browser) use ($room) { $browser->assertSeeIn('@title', 'Room options') ->assertSeeIn('#room-nomedia label', 'Subscribers only:') ->assertVisible('#room-nomedia input[type=checkbox]:not(:checked)') ->assertVisible('#room-nomedia + small') // Test enabling the option ->click('#room-nomedia input') ->assertToast(Toast::TYPE_SUCCESS, "Room configuration updated successfully.") ->click('@button-action'); $this->assertSame('true', $room->fresh()->getSetting('nomedia')); }); // In another browser act as a guest $guest->visit(new RoomPage('john')) ->waitFor('@setup-form') ->waitUntilMissing('@setup-status-message.loading') ->type('@setup-nickname-input', 'John') ->clickWhenEnabled('@setup-button') // expect the owner to have a video, but the guest to have none ->waitFor('@session .meet-video') ->waitFor('@session .meet-subscriber.self'); // Unset the option back $owner->click('@menu button.link-options') ->with(new Dialog('#room-options-dialog'), function (Browser $browser) use ($room) { $browser->assertVisible('#room-nomedia input[type=checkbox]:checked') // Test enabling the option ->click('#room-nomedia input') ->assertToast(Toast::TYPE_SUCCESS, "Room configuration updated successfully.") ->click('@button-action'); $this->assertSame(null, $room->fresh()->getSetting('nomedia')); }); }); } } diff --git a/src/tests/Browser/Meet/RoomQATest.php b/src/tests/Browser/Meet/RoomQATest.php index 276a0b78..05984fa1 100644 --- a/src/tests/Browser/Meet/RoomQATest.php +++ b/src/tests/Browser/Meet/RoomQATest.php @@ -1,139 +1,139 @@ setupTestRoom(); } public function tearDown(): void { $this->resetTestRoom(); parent::tearDown(); } /** * Test Q&A queue * - * @group openvidu + * @group meet */ public function testQA(): void { $this->browse(function (Browser $owner, Browser $guest1, Browser $guest2) { // Join the room as an owner (authenticate) $owner->visit(new RoomPage('john')) ->click('@setup-button') ->submitLogon('john@kolab.org', 'simple123') ->waitFor('@setup-form') ->waitUntilMissing('@setup-status-message.loading') ->select('@setup-mic-select', '') ->select('@setup-cam-select', '') ->type('@setup-nickname-input', 'John') ->clickWhenEnabled('@setup-button') ->waitFor('@session'); // In another browser act as a guest (1) $guest1->visit(new RoomPage('john')) ->waitFor('@setup-form') ->waitUntilMissing('@setup-status-message.loading') ->assertMissing('@setup-status-message') ->assertSeeIn('@setup-button', "JOIN") // Join the room, disable cam/mic ->select('@setup-mic-select', '') ->select('@setup-cam-select', '') ->type('@setup-nickname-input', 'Guest1') ->clickWhenEnabled('@setup-button') ->waitFor('@session'); // Assert current UI state $owner->assertToolbarButtonState('hand', RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_ENABLED) ->waitFor('div.meet-subscriber.self') ->assertMissing('@queue') ->click('@menu button.link-hand') ->waitFor('@queue .dropdown.self.moderated') ->assertSeeIn('@queue .dropdown.self.moderated', 'John') ->assertToolbarButtonState('hand', RoomPage::BUTTON_ACTIVE | RoomPage::BUTTON_ENABLED); // Assert current UI state $guest1->waitFor('@queue .dropdown') ->assertSeeIn('@queue .dropdown', 'John') ->assertElementsCount('@queue .dropdown', 1) ->waitFor('div.meet-subscriber.self') ->click('@menu button.link-hand') ->waitFor('@queue .dropdown.self') ->assertSeeIn('@queue .dropdown.self', 'Guest1') ->assertElementsCount('@queue .dropdown', 2) ->click('@menu button.link-hand') ->waitUntilMissing('@queue .dropdown.self') ->assertElementsCount('@queue .dropdown', 1) ->assertToolbarButtonState('hand', RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_ENABLED); // In another browser act as a guest (2) $guest2->visit(new RoomPage('john')) ->waitFor('@setup-form') ->waitUntilMissing('@setup-status-message.loading') ->assertMissing('@setup-status-message') ->assertSeeIn('@setup-button', "JOIN") ->type('@setup-nickname-input', 'Guest2') ->clickWhenEnabled('@setup-button') ->waitFor('@queue .dropdown') ->waitForTextIn('@queue .dropdown', 'John') ->assertElementsCount('@queue .dropdown', 1) ->assertMissing('@menu button.link-hand'); // Demote the guest (2) to subscriber, assert Hand button in toolbar $owner->click('@session div.meet-video .meet-nickname') ->whenAvailable('@session div.meet-video .dropdown-menu', function ($browser) { $browser->click('.action-role-publisher input'); }); // Guest (2) rises his hand $guest2->waitUntilMissing('@session .meet-video') ->waitFor('@menu button.link-hand') ->assertToolbarButtonState('hand', RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_ENABLED) ->click('@menu button.link-hand') ->waitFor('@queue .dropdown.self') ->assertElementsCount('@queue .dropdown', 2); // Promote guest (2) to publisher $owner->waitFor('@queue .dropdown:not(.self)') ->pause(8000) // wait until it's not moving, otherwise click() will fail ->click('@queue .dropdown:not(.self)') ->whenAvailable('@queue .dropdown:not(.self) .dropdown-menu', function ($browser) { $browser->click('.action-role-publisher input'); }) ->waitUntilMissing('@queue .dropdown:not(.self)') ->waitFor('@session .meet-video'); $guest1->waitFor('@session .meet-video') ->assertElementsCount('@queue .dropdown', 1); $guest2->waitFor('@session .meet-video') ->waitUntilMissing('@queue .dropdown.self') ->assertElementsCount('@queue .dropdown', 1); // Finally, do the same with the owner (last in the queue) $owner->click('@queue .dropdown.self') ->whenAvailable('@queue .dropdown.self .dropdown-menu', function ($browser) { $browser->click('.action-role-publisher input'); }) ->waitUntilMissing('@queue') ->waitFor('@session .meet-video.self'); $guest1->waitUntilMissing('@queue'); $guest2->waitUntilMissing('@queue'); }); } } diff --git a/src/tests/Browser/Meet/RoomSetupTest.php b/src/tests/Browser/Meet/RoomSetupTest.php index c6246b81..98878c66 100644 --- a/src/tests/Browser/Meet/RoomSetupTest.php +++ b/src/tests/Browser/Meet/RoomSetupTest.php @@ -1,570 +1,570 @@ setupTestRoom(); } public function tearDown(): void { $this->resetTestRoom(); parent::tearDown(); } /** * Test non-existing room * - * @group openvidu + * @group meet */ public function testRoomNonExistingRoom(): void { $this->browse(function (Browser $browser) { $browser->visit(new RoomPage('unknown')) ->within(new Menu(), function ($browser) { $browser->assertMenuItems(['signup', 'explore', 'blog', 'support', 'login', 'lang']); }); if ($browser->isDesktop()) { $browser->within(new Menu('footer'), function ($browser) { $browser->assertMenuItems(['signup', 'explore', 'blog', 'support', 'tos', 'login']); }); } else { $browser->assertMissing('#footer-menu .navbar-nav'); } // FIXME: Maybe it would be better to just display the usual 404 Not Found error page? $browser->assertMissing('@toolbar') ->assertMissing('@menu') ->assertMissing('@session') ->assertMissing('@chat') ->assertMissing('@login-form') ->assertVisible('@setup-form') ->assertSeeIn('@setup-status-message', "The room does not exist.") ->assertButtonDisabled('@setup-button'); }); } /** * Test the room setup page * - * @group openvidu + * @group meet */ public function testRoomSetup(): void { $this->browse(function (Browser $browser) { $browser->visit(new RoomPage('john')) ->within(new Menu(), function ($browser) { $browser->assertMenuItems(['signup', 'explore', 'blog', 'support', 'login', 'lang']); }); if ($browser->isDesktop()) { $browser->within(new Menu('footer'), function ($browser) { $browser->assertMenuItems(['signup', 'explore', 'blog', 'support', 'tos', 'login']); }); } else { $browser->assertMissing('#footer-menu .navbar-nav'); } // Note: I've found out that if I have another Chrome instance running // that uses media, here the media devices will not be available // TODO: Test enabling/disabling cam/mic in the setup widget $browser->assertMissing('@toolbar') ->assertMissing('@menu') ->assertMissing('@session') ->assertMissing('@chat') ->assertMissing('@login-form') ->assertVisible('@setup-form') ->assertSeeIn('@setup-title', 'Set up your session') ->assertVisible('@setup-video') ->assertVisible('@setup-form .input-group:nth-child(1) svg') ->assertAttribute('@setup-form .input-group:nth-child(1) .input-group-text', 'title', 'Microphone') ->assertVisible('@setup-mic-select') ->assertVisible('@setup-form .input-group:nth-child(2) svg') ->assertAttribute('@setup-form .input-group:nth-child(2) .input-group-text', 'title', 'Camera') ->assertVisible('@setup-cam-select') ->assertVisible('@setup-form .input-group:nth-child(3) svg') ->assertAttribute('@setup-form .input-group:nth-child(3) .input-group-text', 'title', 'Nickname') ->assertValue('@setup-nickname-input', '') ->assertAttribute('@setup-nickname-input', 'placeholder', 'Your name') ->assertMissing('@setup-password-input') ->assertSeeIn( '@setup-status-message', "The room is closed. Please, wait for the owner to start the session." ) ->assertSeeIn('@setup-button', "I'm the owner"); }); } /** * Test two users in a room (joining/leaving and some basic functionality) * - * @group openvidu + * @group meet * @depends testRoomSetup */ public function testTwoUsersInARoom(): void { $this->browse(function (Browser $browser, Browser $guest) { // In one browser window act as a guest $guest->visit(new RoomPage('john')) ->assertMissing('@toolbar') ->assertMissing('@menu') ->assertMissing('@session') ->assertMissing('@chat') ->assertMissing('@login-form') ->waitFor('@setup-form') ->waitUntilMissing('@setup-status-message.loading') ->assertSeeIn( '@setup-status-message', "The room is closed. Please, wait for the owner to start the session." ) ->assertSeeIn('@setup-button', "I'm the owner"); // In another window join the room as the owner (authenticate) $browser->on(new RoomPage('john')) ->assertSeeIn('@setup-button', "I'm the owner") ->clickWhenEnabled('@setup-button') ->assertMissing('@toolbar') ->assertMissing('@menu') ->assertMissing('@session') ->assertMissing('@chat') ->assertMissing('@setup-form') ->assertVisible('@login-form') ->submitLogon('john@kolab.org', 'simple123') ->waitFor('@setup-form') ->within(new Menu(), function ($browser) { $browser->assertMenuItems(['explore', 'blog', 'support', 'dashboard', 'logout', 'lang']); }); if ($browser->isDesktop()) { $browser->within(new Menu('footer'), function ($browser) { $browser->assertMenuItems(['explore', 'blog', 'support', 'tos', 'dashboard', 'logout']); }); } $browser->assertMissing('@login-form') ->waitUntilMissing('@setup-status-message.loading') ->waitFor('@setup-status-message') ->assertSeeIn('@setup-status-message', "The room is closed. It will be open for others after you join.") ->assertSeeIn('@setup-button', "JOIN") ->type('@setup-nickname-input', 'john') ->clickWhenEnabled('@setup-button') ->waitFor('@session') ->assertMissing('@setup-form') ->whenAvailable('div.meet-video.self', function (Browser $browser) { $browser->waitFor('video') ->assertSeeIn('.meet-nickname', 'john') ->assertVisible('.controls button.link-fullscreen') ->assertMissing('.controls button.link-audio') ->assertMissing('.status .status-audio') ->assertMissing('.status .status-video'); }) ->assertMissing('#header-menu') ->assertSeeIn('@counter', 1); if (!$browser->isPhone()) { $browser->assertMissing('#footer-menu'); } else { $browser->assertVisible('#footer-menu'); } // After the owner "opened the room" guest should be able to join $guest->waitUntilMissing('@setup-status-message', 10) ->assertSeeIn('@setup-button', "JOIN") // Join the room, disable cam/mic ->select('@setup-mic-select', '') //->select('@setup-cam-select', '') ->clickWhenEnabled('@setup-button') ->waitFor('@session') ->assertMissing('@setup-form') ->whenAvailable('div.meet-video.self', function (Browser $browser) { $browser->waitFor('video') ->assertVisible('.meet-nickname') ->assertVisible('.controls button.link-fullscreen') ->assertMissing('.controls button.link-audio') ->assertVisible('.status .status-audio') ->assertMissing('.status .status-video'); }) ->whenAvailable('div.meet-video:not(.self)', function (Browser $browser) { $browser->waitFor('video') ->assertSeeIn('.meet-nickname', 'john') ->assertVisible('.controls button.link-fullscreen') ->assertVisible('.controls button.link-audio') ->assertMissing('.status .status-audio') ->assertMissing('.status .status-video'); }) ->assertElementsCount('@session div.meet-video', 2) ->assertSeeIn('@counter', 2); // Check guest's elements in the owner's window $browser ->whenAvailable('div.meet-video:not(.self)', function (Browser $browser) { $browser->waitFor('video') ->assertVisible('.meet-nickname') ->assertVisible('.controls button.link-fullscreen') ->assertVisible('.controls button.link-audio') ->assertMissing('.controls button.link-setup') ->assertVisible('.status .status-audio') ->assertMissing('.status .status-video'); }) ->assertElementsCount('@session div.meet-video', 2); // Test leaving the room // Guest is leaving $guest->click('@menu button.link-logout') ->waitForLocation('/login') ->assertVisible('#header-menu'); // Expect the participant removed from other users windows $browser->waitUntilMissing('@session div.meet-video:not(.self)') ->assertSeeIn('@counter', 1); // Join the room as guest again $guest->visit(new RoomPage('john')) ->assertMissing('@toolbar') ->assertMissing('@menu') ->assertMissing('@session') ->assertMissing('@chat') ->assertMissing('@login-form') ->waitFor('@setup-form') ->waitUntilMissing('@setup-status-message.loading') ->assertMissing('@setup-status-message') ->assertSeeIn('@setup-button', "JOIN") // Join the room, disable cam/mic ->select('@setup-mic-select', '') //->select('@setup-cam-select', '') ->clickWhenEnabled('@setup-button') ->waitFor('@session'); // Leave the room as the room owner // TODO: Test leaving the room by closing the browser window, // it should not destroy the session $browser->click('@menu button.link-logout') ->waitForLocation('/dashboard'); // Expect other participants be informed about the end of the session $guest->with(new Dialog('#leave-dialog'), function (Browser $browser) { $browser->assertSeeIn('@title', 'Room closed') ->assertSeeIn('@body', "The session has been closed by the room owner.") ->assertMissing('@button-cancel') ->assertSeeIn('@button-action', 'Close') ->click('@button-action'); }) ->assertMissing('#leave-dialog') ->waitForLocation('/login'); }); } /** * Test two subscribers-only users in a room * - * @group openvidu + * @group meet * @depends testTwoUsersInARoom */ public function testSubscribers(): void { $this->browse(function (Browser $browser, Browser $guest) { // Join the room as the owner $browser->visit(new RoomPage('john')) ->waitFor('@setup-form') ->waitUntilMissing('@setup-status-message.loading') ->waitFor('@setup-status-message') ->type('@setup-nickname-input', 'john') ->select('@setup-mic-select', '') ->select('@setup-cam-select', '') ->clickWhenEnabled('@setup-button') ->waitFor('@session') ->assertMissing('@setup-form') ->whenAvailable('@subscribers .meet-subscriber.self', function (Browser $browser) { $browser->assertSeeIn('.meet-nickname', 'john'); }) ->assertElementsCount('@session div.meet-video', 0) ->assertElementsCount('@session video', 0) ->assertElementsCount('@session .meet-subscriber', 1) ->assertToolbar([ 'audio' => RoomPage::BUTTON_ACTIVE | RoomPage::BUTTON_DISABLED, 'video' => RoomPage::BUTTON_ACTIVE | RoomPage::BUTTON_DISABLED, 'screen' => RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_DISABLED, 'hand' => RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_ENABLED, 'chat' => RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_ENABLED, 'fullscreen' => RoomPage::BUTTON_ENABLED, 'options' => RoomPage::BUTTON_ENABLED, 'logout' => RoomPage::BUTTON_ENABLED, ]); // After the owner "opened the room" guest should be able to join // In one browser window act as a guest $guest->visit(new RoomPage('john')) ->waitUntilMissing('@setup-status-message', 10) ->assertSeeIn('@setup-button', "JOIN") // Join the room, disable cam/mic ->select('@setup-mic-select', '') ->select('@setup-cam-select', '') ->clickWhenEnabled('@setup-button') ->waitFor('@session') ->assertMissing('@setup-form') ->whenAvailable('@subscribers .meet-subscriber.self', function (Browser $browser) { $browser->assertVisible('.meet-nickname'); }) ->whenAvailable('@subscribers .meet-subscriber:not(.self)', function (Browser $browser) { $browser->assertSeeIn('.meet-nickname', 'john'); }) ->assertElementsCount('@session div.meet-video', 0) ->assertElementsCount('@session video', 0) ->assertElementsCount('@session div.meet-subscriber', 2) ->assertSeeIn('@counter', 2) ->assertToolbar([ 'audio' => RoomPage::BUTTON_ACTIVE | RoomPage::BUTTON_DISABLED, 'video' => RoomPage::BUTTON_ACTIVE | RoomPage::BUTTON_DISABLED, 'screen' => RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_DISABLED, 'hand' => RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_ENABLED, 'chat' => RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_ENABLED, 'fullscreen' => RoomPage::BUTTON_ENABLED, 'logout' => RoomPage::BUTTON_ENABLED, ]); // Check guest's elements in the owner's window $browser ->whenAvailable('@subscribers .meet-subscriber:not(.self)', function (Browser $browser) { $browser->assertVisible('.meet-nickname'); }) ->assertElementsCount('@session div.meet-video', 0) ->assertElementsCount('@session video', 0) ->assertElementsCount('@session .meet-subscriber', 2) ->assertSeeIn('@counter', 2); // Test leaving the room // Guest is leaving $guest->click('@menu button.link-logout') ->waitForLocation('/login'); // Expect the participant removed from other users windows $browser->waitUntilMissing('@session .meet-subscriber:not(.self)'); }); } /** * Test demoting publisher to a subscriber * - * @group openvidu + * @group meet * @depends testSubscribers */ public function testDemoteToSubscriber(): void { $this->browse(function (Browser $browser, Browser $guest1, Browser $guest2) { // Join the room as the owner $browser->visit(new RoomPage('john')) ->waitFor('@setup-form') ->waitUntilMissing('@setup-status-message.loading') ->waitFor('@setup-status-message') ->type('@setup-nickname-input', 'john') ->clickWhenEnabled('@setup-button') ->waitFor('@session') ->assertMissing('@setup-form') ->waitFor('@session video'); // In one browser window act as a guest $guest1->visit(new RoomPage('john')) ->waitUntilMissing('@setup-status-message', 10) ->assertSeeIn('@setup-button', "JOIN") ->clickWhenEnabled('@setup-button') ->waitFor('@session') ->assertMissing('@setup-form') ->waitFor('div.meet-video.self') ->waitFor('div.meet-video:not(.self)') ->assertElementsCount('@session div.meet-video', 2) ->assertElementsCount('@session video', 2) ->assertElementsCount('@session div.meet-subscriber', 0) // assert there's no moderator-related features for this guess available ->click('@session .meet-video.self .meet-nickname') ->whenAvailable('@session .meet-video.self .dropdown-menu', function (Browser $browser) { $browser->assertMissing('.permissions'); }) ->click('@session .meet-video:not(.self) .meet-nickname') ->pause(50) ->assertMissing('.dropdown-menu'); // Demote the guest to a subscriber $browser ->waitFor('div.meet-video.self video') ->waitFor('div.meet-video:not(.self) video') ->assertElementsCount('@session div.meet-video', 2) ->assertElementsCount('@session video', 2) ->assertElementsCount('@session .meet-subscriber', 0) ->click('@session .meet-video:not(.self) .meet-nickname') ->whenAvailable('@session .meet-video:not(.self) .dropdown-menu', function (Browser $browser) { $browser->assertSeeIn('.action-role-publisher', 'Audio & Video publishing') ->click('.action-role-publisher') ->waitUntilMissing('.dropdown-menu'); }) ->waitUntilMissing('@session .meet-video:not(.self)') ->waitFor('@session div.meet-subscriber') ->assertElementsCount('@session div.meet-video', 1) ->assertElementsCount('@session video', 1) ->assertElementsCount('@session div.meet-subscriber', 1); $guest1 ->waitUntilMissing('@session .meet-video.self') ->waitFor('@session div.meet-subscriber') ->assertElementsCount('@session div.meet-video', 1) ->assertElementsCount('@session video', 1) ->assertElementsCount('@session div.meet-subscriber', 1); // Join as another user to make sure the role change is propagated to new connections $guest2->visit(new RoomPage('john')) ->waitUntilMissing('@setup-status-message', 10) ->assertSeeIn('@setup-button', "JOIN") ->select('@setup-mic-select', '') ->select('@setup-cam-select', '') ->clickWhenEnabled('@setup-button') ->waitFor('@session') ->assertMissing('@setup-form') ->waitFor('div.meet-subscriber:not(.self)') ->assertElementsCount('@session div.meet-video', 1) ->assertElementsCount('@session video', 1) ->assertElementsCount('@session div.meet-subscriber', 2) ->click('@toolbar .link-logout'); // Promote the guest back to a publisher $browser ->click('@session .meet-subscriber .meet-nickname') ->whenAvailable('@session .meet-subscriber .dropdown-menu', function (Browser $browser) { $browser->assertSeeIn('.action-role-publisher', 'Audio & Video publishing') ->assertNotChecked('.action-role-publisher input') ->click('.action-role-publisher') ->waitUntilMissing('.dropdown-menu'); }) ->waitFor('@session .meet-video:not(.self) video') ->assertElementsCount('@session div.meet-video', 2) ->assertElementsCount('@session video', 2) ->assertElementsCount('@session div.meet-subscriber', 0); $guest1 ->with(new Dialog('#media-setup-dialog'), function (Browser $browser) { $browser->assertSeeIn('@title', 'Media setup') ->click('@button-action'); }) ->waitFor('@session .meet-video.self') ->assertElementsCount('@session div.meet-video', 2) ->assertElementsCount('@session video', 2) ->assertElementsCount('@session div.meet-subscriber', 0); // Demote the owner to a subscriber $browser ->click('@session .meet-video.self .meet-nickname') ->whenAvailable('@session .meet-video.self .dropdown-menu', function (Browser $browser) { $browser->assertSeeIn('.action-role-publisher', 'Audio & Video publishing') ->assertChecked('.action-role-publisher input') ->click('.action-role-publisher') ->waitUntilMissing('.dropdown-menu'); }) ->waitUntilMissing('@session .meet-video.self') ->waitFor('@session div.meet-subscriber.self') ->assertElementsCount('@session div.meet-video', 1) ->assertElementsCount('@session video', 1) ->assertElementsCount('@session div.meet-subscriber', 1); // Promote the owner to a publisher $browser ->click('@session .meet-subscriber.self .meet-nickname') ->whenAvailable('@session .meet-subscriber.self .dropdown-menu', function (Browser $browser) { $browser->assertSeeIn('.action-role-publisher', 'Audio & Video publishing') ->assertNotChecked('.action-role-publisher input') ->click('.action-role-publisher') ->waitUntilMissing('.dropdown-menu'); }) ->waitUntilMissing('@session .meet-subscriber.self') ->with(new Dialog('#media-setup-dialog'), function (Browser $browser) { $browser->assertSeeIn('@title', 'Media setup') ->click('@button-action'); }) ->waitFor('@session div.meet-video.self') ->assertElementsCount('@session div.meet-video', 2) ->assertElementsCount('@session video', 2) ->assertElementsCount('@session div.meet-subscriber', 0); }); } /** * Test the media setup dialog * - * @group openvidu + * @group meet * @depends testDemoteToSubscriber */ public function testMediaSetupDialog(): void { $this->browse(function (Browser $browser, $guest) { // Join the room as the owner $browser->visit(new RoomPage('john')) ->waitFor('@setup-form') ->waitUntilMissing('@setup-status-message.loading') ->waitFor('@setup-status-message') ->type('@setup-nickname-input', 'john') ->clickWhenEnabled('@setup-button') ->waitFor('@session') ->assertMissing('@setup-form'); // In one browser window act as a guest $guest->visit(new RoomPage('john')) ->waitUntilMissing('@setup-status-message', 10) ->assertSeeIn('@setup-button', "JOIN") ->select('@setup-mic-select', '') ->select('@setup-cam-select', '') ->clickWhenEnabled('@setup-button') ->waitFor('@session') ->assertMissing('@setup-form'); $browser->waitFor('@session video') ->click('.controls button.link-setup') ->with(new Dialog('#media-setup-dialog'), function (Browser $browser) { $browser->assertSeeIn('@title', 'Media setup') ->assertVisible('form video') ->assertVisible('form > div:nth-child(1) video') ->assertVisible('form > div:nth-child(1) .volume') ->assertVisible('form > div:nth-child(2) svg') ->assertAttribute('form > div:nth-child(2) .input-group-text', 'title', 'Microphone') ->assertVisible('form > div:nth-child(2) select') ->assertVisible('form > div:nth-child(3) svg') ->assertAttribute('form > div:nth-child(3) .input-group-text', 'title', 'Camera') ->assertVisible('form > div:nth-child(3) select') ->assertMissing('@button-cancel') ->assertSeeIn('@button-action', 'Close') ->click('@button-action'); }) ->assertMissing('#media-setup-dialog') // Test mute audio and video ->click('.controls button.link-setup') ->with(new Dialog('#media-setup-dialog'), function (Browser $browser) { $browser->select('form > div:nth-child(2) select', '') ->select('form > div:nth-child(3) select', '') ->click('@button-action'); }) ->assertMissing('#media-setup-dialog') ->assertVisible('@session .meet-video .status .status-audio') ->assertVisible('@session .meet-video .status .status-video'); $guest->waitFor('@session video') ->waitFor('@session .meet-video .status .status-audio') ->assertVisible('@session .meet-video .status .status-video'); }); } } diff --git a/src/tests/Browser/Meet/RoomsTest.php b/src/tests/Browser/Meet/RoomsTest.php index 8e76517b..2956ed12 100644 --- a/src/tests/Browser/Meet/RoomsTest.php +++ b/src/tests/Browser/Meet/RoomsTest.php @@ -1,110 +1,110 @@ clearMeetEntitlements(); } /** * {@inheritDoc} */ public function tearDown(): void { $this->clearMeetEntitlements(); parent::tearDown(); } /** * Test rooms page (unauthenticated and unauthorized) * - * @group openvidu + * @group meet */ public function testRoomsUnauth(): void { // Test that the page requires authentication $this->browse(function (Browser $browser) { $browser->visit('/rooms') ->on(new Home()) // User has no 'meet' entitlement yet, expect redirect to error page ->submitLogon('john@kolab.org', 'simple123', false) ->waitFor('#app > #error-page') ->assertSeeIn('#error-page .code', '403') ->assertSeeIn('#error-page .message', 'Access denied'); }); } /** * Test rooms page * - * @group openvidu + * @group meet */ public function testRooms(): void { $this->browse(function (Browser $browser) { $href = \config('app.url') . '/meet/john'; $john = $this->getTestUser('john@kolab.org'); // User has no 'meet' entitlement yet $browser->visit('/login') ->on(new Home()) ->submitLogon('john@kolab.org', 'simple123', true) ->on(new Dashboard()) ->assertMissing('@links a.link-chat'); // Goto user subscriptions, and enable 'meet' subscription $browser->visit('/user/' . $john->id) ->on(new UserInfo()) ->whenAvailable('@skus', function ($browser) { $browser->click('#sku-input-meet'); }) ->click('button[type=submit]') ->assertToast(Toast::TYPE_SUCCESS, 'User data updated successfully.') ->click('.navbar-brand') ->on(new Dashboard()) ->assertSeeIn('@links a.link-chat', 'Video chat') // Make sure the element also exists on Dashboard page load ->refresh() ->on(new Dashboard()) ->assertSeeIn('@links a.link-chat', 'Video chat'); // Test Video chat page $browser->click('@links a.link-chat') ->waitFor('#meet-rooms') ->waitFor('.card-text a') ->assertSeeIn('.card-title', 'Voice & Video Conferencing') ->assertSeeIn('.card-text a', $href) ->assertAttribute('.card-text a', 'href', '/meet/john') ->click('.card-text a') ->on(new RoomPage('john')) // check that entering the room skips the logon form ->assertMissing('@toolbar') ->assertMissing('@menu') ->assertMissing('@session') ->assertMissing('@chat') ->assertMissing('@login-form') ->assertVisible('@setup-form') ->assertSeeIn('@setup-status-message', "The room is closed. It will be open for others after you join.") ->assertSeeIn('@setup-button', "JOIN") ->click('@setup-button') ->waitFor('@session') ->assertMissing('@setup-form'); }); } } diff --git a/src/tests/Feature/Controller/OpenViduTest.php b/src/tests/Feature/Controller/MeetTest.php similarity index 80% rename from src/tests/Feature/Controller/OpenViduTest.php rename to src/tests/Feature/Controller/MeetTest.php index abcba3fd..45b6bd10 100644 --- a/src/tests/Feature/Controller/OpenViduTest.php +++ b/src/tests/Feature/Controller/MeetTest.php @@ -1,450 +1,450 @@ clearMeetEntitlements(); $room = Room::where('name', 'john')->first(); $room->setSettings(['password' => null, 'locked' => null, 'nomedia' => null]); } public function tearDown(): void { $this->clearMeetEntitlements(); $room = Room::where('name', 'john')->first(); $room->setSettings(['password' => null, 'locked' => null, 'nomedia' => null]); parent::tearDown(); } /** * Test listing user rooms * - * @group openvidu + * @group meet */ public function testIndex(): void { $john = $this->getTestUser('john@kolab.org'); $jack = $this->getTestUser('jack@kolab.org'); Room::where('user_id', $jack->id)->delete(); // Unauth access not allowed - $response = $this->get("api/v4/openvidu/rooms"); + $response = $this->get("api/v4/meet/rooms"); $response->assertStatus(401); // John has one room - $response = $this->actingAs($john)->get("api/v4/openvidu/rooms"); + $response = $this->actingAs($john)->get("api/v4/meet/rooms"); $response->assertStatus(200); $json = $response->json(); $this->assertSame(1, $json['count']); $this->assertCount(1, $json['list']); $this->assertSame('john', $json['list'][0]['name']); // Jack has no room, but it will be auto-created - $response = $this->actingAs($jack)->get("api/v4/openvidu/rooms"); + $response = $this->actingAs($jack)->get("api/v4/meet/rooms"); $response->assertStatus(200); $json = $response->json(); $this->assertSame(1, $json['count']); $this->assertCount(1, $json['list']); $this->assertMatchesRegularExpression('/^[0-9a-z-]{11}$/', $json['list'][0]['name']); } /** * Test joining the room * - * @group openvidu + * @group meet */ public function testJoinRoom(): void { $john = $this->getTestUser('john@kolab.org'); $jack = $this->getTestUser('jack@kolab.org'); $room = Room::where('name', 'john')->first(); $room->session_id = null; $room->save(); $this->assignMeetEntitlement($john); // Unauth access, no session yet - $response = $this->post("api/v4/openvidu/rooms/{$room->name}"); + $response = $this->post("api/v4/meet/rooms/{$room->name}"); $response->assertStatus(422); $json = $response->json(); $this->assertSame(323, $json['code']); // Non-existing room name - $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/non-existing"); + $response = $this->actingAs($john)->post("api/v4/meet/rooms/non-existing"); $response->assertStatus(404); // TODO: Test accessing an existing room of deleted owner // Non-owner, no session yet - $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}"); + $response = $this->actingAs($jack)->post("api/v4/meet/rooms/{$room->name}"); $response->assertStatus(422); $json = $response->json(); $this->assertSame(323, $json['code']); // Room owner, no session yet - $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}"); + $response = $this->actingAs($john)->post("api/v4/meet/rooms/{$room->name}"); $response->assertStatus(422); $json = $response->json(); $this->assertSame(324, $json['code']); - $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}", ['init' => 1]); + $response = $this->actingAs($john)->post("api/v4/meet/rooms/{$room->name}", ['init' => 1]); $response->assertStatus(200); $json = $response->json(); $session_id = $room->fresh()->session_id; $this->assertSame(Room::ROLE_SUBSCRIBER | Room::ROLE_MODERATOR | Room::ROLE_OWNER, $json['role']); $this->assertMatchesRegularExpression('|^wss?://|', $json['token']); $this->assertMatchesRegularExpression('|&roomId=' . $session_id . '|', $json['token']); $john_token = $json['token']; // Non-owner, now the session exists, no 'init' argument - $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}"); + $response = $this->actingAs($jack)->post("api/v4/meet/rooms/{$room->name}"); $response->assertStatus(422); $json = $response->json(); $this->assertSame(322, $json['code']); $this->assertTrue(empty($json['token'])); // Non-owner, now the session exists, with 'init', but no 'canPublish' argument - $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}", ['init' => 1]); + $response = $this->actingAs($jack)->post("api/v4/meet/rooms/{$room->name}", ['init' => 1]); $response->assertStatus(200); $json = $response->json(); $this->assertSame(Room::ROLE_SUBSCRIBER, $json['role']); $this->assertMatchesRegularExpression('|^wss?://|', $json['token']); $this->assertMatchesRegularExpression('|&roomId=' . $session_id . '|', $json['token']); $this->assertTrue($json['token'] != $john_token); // Non-owner, now the session exists, with 'init', and with 'role=PUBLISHER' $post = ['canPublish' => true, 'init' => 1]; - $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}", $post); + $response = $this->actingAs($jack)->post("api/v4/meet/rooms/{$room->name}", $post); $response->assertStatus(200); $json = $response->json(); $this->assertSame(Room::ROLE_PUBLISHER, $json['role']); $this->assertMatchesRegularExpression('|^wss?://|', $json['token']); $this->assertMatchesRegularExpression('|&roomId=' . $session_id . '|', $json['token']); $this->assertTrue($json['token'] != $john_token); $this->assertEmpty($json['config']['password']); $this->assertEmpty($json['config']['requires_password']); // Non-owner, password protected room, password not provided $room->setSettings(['password' => 'pass']); - $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}"); + $response = $this->actingAs($jack)->post("api/v4/meet/rooms/{$room->name}"); $response->assertStatus(422); $json = $response->json(); $this->assertCount(4, $json); $this->assertSame(325, $json['code']); $this->assertSame('error', $json['status']); $this->assertSame('Failed to join the session. Invalid password.', $json['message']); $this->assertEmpty($json['config']['password']); $this->assertTrue($json['config']['requires_password']); // Non-owner, password protected room, invalid provided - $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}", ['password' => 'aa']); + $response = $this->actingAs($jack)->post("api/v4/meet/rooms/{$room->name}", ['password' => 'aa']); $response->assertStatus(422); $json = $response->json(); $this->assertSame(325, $json['code']); // Non-owner, password protected room, valid password provided // TODO: Test without init=1 $post = ['password' => 'pass', 'init' => 'init']; - $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}", $post); + $response = $this->actingAs($jack)->post("api/v4/meet/rooms/{$room->name}", $post); $response->assertStatus(200); // Make sure the room owner can access the password protected room w/o password // TODO: Test without init=1 $post = ['init' => 'init']; - $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}", $post); + $response = $this->actingAs($john)->post("api/v4/meet/rooms/{$room->name}", $post); $response->assertStatus(200); // Test 'nomedia' room option $room->setSettings(['nomedia' => 'true', 'password' => null]); $post = ['init' => 'init', 'canPublish' => true]; - $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}", $post); + $response = $this->actingAs($john)->post("api/v4/meet/rooms/{$room->name}", $post); $response->assertStatus(200); $json = $response->json(); $this->assertSame(Room::ROLE_PUBLISHER & $json['role'], Room::ROLE_PUBLISHER); $post = ['init' => 'init', 'canPublish' => true]; - $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}", $post); + $response = $this->actingAs($jack)->post("api/v4/meet/rooms/{$room->name}", $post); $response->assertStatus(200); $json = $response->json(); $this->assertSame(Room::ROLE_PUBLISHER & $json['role'], 0); } /** * Test locked room and join requests * - * @group openvidu + * @group meet */ public function testJoinRequests(): void { $john = $this->getTestUser('john@kolab.org'); $jack = $this->getTestUser('jack@kolab.org'); $room = Room::where('name', 'john')->first(); $room->session_id = null; $room->save(); $room->setSettings(['password' => null, 'locked' => 'true']); $this->assignMeetEntitlement($john); // Create the session (also makes sure the owner can access a locked room) - $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}", ['init' => 1]); + $response = $this->actingAs($john)->post("api/v4/meet/rooms/{$room->name}", ['init' => 1]); $response->assertStatus(200); // Non-owner, locked room, invalid/missing input - $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}"); + $response = $this->actingAs($jack)->post("api/v4/meet/rooms/{$room->name}"); $response->assertStatus(422); $json = $response->json(); $this->assertCount(4, $json); $this->assertSame(326, $json['code']); $this->assertSame('error', $json['status']); $this->assertSame('Failed to join the session. Room locked.', $json['message']); $this->assertTrue($json['config']['locked']); // Non-owner, locked room, invalid requestId $post = ['nickname' => 'name', 'requestId' => '-----', 'init' => 1]; - $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}", $post); + $response = $this->actingAs($jack)->post("api/v4/meet/rooms/{$room->name}", $post); $response->assertStatus(422); $json = $response->json(); $this->assertSame(326, $json['code']); // Non-owner, locked room, invalid requestId $post = ['nickname' => 'name', 'init' => 1]; - $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}", $post); + $response = $this->actingAs($jack)->post("api/v4/meet/rooms/{$room->name}", $post); $response->assertStatus(422); $json = $response->json(); $this->assertSame(326, $json['code']); // Non-owner, locked room, valid input $reqId = '12345678'; $post = ['nickname' => 'name', 'requestId' => $reqId, 'picture' => 'data:image/png;base64,01234']; - $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}", $post); + $response = $this->actingAs($jack)->post("api/v4/meet/rooms/{$room->name}", $post); $response->assertStatus(422); $json = $response->json(); $this->assertCount(4, $json); $this->assertSame(327, $json['code']); $this->assertSame('error', $json['status']); $this->assertSame('Failed to join the session. Room locked.', $json['message']); $this->assertTrue($json['config']['locked']); $room->refresh(); $request = $room->requestGet($reqId); $this->assertSame($post['nickname'], $request['nickname']); $this->assertSame($post['requestId'], $request['requestId']); $room->requestAccept($reqId); // Non-owner, locked room, join request accepted $post['init'] = 1; $post['canPublish'] = true; - $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}", $post); + $response = $this->actingAs($jack)->post("api/v4/meet/rooms/{$room->name}", $post); $response->assertStatus(200); $json = $response->json(); $this->assertSame(Room::ROLE_PUBLISHER, $json['role']); $this->assertMatchesRegularExpression('|^wss?://|', $json['token']); // TODO: Test a scenario where both password and lock are enabled // TODO: Test accepting/denying as a non-owner moderator // TODO: Test somehow websocket communication $this->markTestIncomplete(); } /** * Test joining the room * - * @group openvidu + * @group meet * @depends testJoinRoom */ public function testJoinRoomGuest(): void { $this->assignMeetEntitlement('john@kolab.org'); // There's no easy way to logout the user in the same test after // using actingAs(). That's why this is moved to a separate test $room = Room::where('name', 'john')->first(); // Guest, request with screenShare token $post = ['canPublish' => true, 'screenShare' => 1, 'init' => 1]; - $response = $this->post("api/v4/openvidu/rooms/{$room->name}", $post); + $response = $this->post("api/v4/meet/rooms/{$room->name}", $post); $response->assertStatus(200); $json = $response->json(); $this->assertSame(Room::ROLE_PUBLISHER, $json['role']); $this->assertMatchesRegularExpression('|^wss?://|', $json['token']); } /** * Test configuring the room (session) * - * @group openvidu + * @group meet */ public function testSetRoomConfig(): void { $john = $this->getTestUser('john@kolab.org'); $jack = $this->getTestUser('jack@kolab.org'); $room = Room::where('name', 'john')->first(); // Unauth access not allowed - $response = $this->post("api/v4/openvidu/rooms/{$room->name}/config", []); + $response = $this->post("api/v4/meet/rooms/{$room->name}/config", []); $response->assertStatus(401); // Non-existing room name - $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/non-existing/config", []); + $response = $this->actingAs($john)->post("api/v4/meet/rooms/non-existing/config", []); $response->assertStatus(404); // TODO: Test a room with a deleted owner // Non-owner - $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}/config", []); + $response = $this->actingAs($jack)->post("api/v4/meet/rooms/{$room->name}/config", []); $response->assertStatus(403); // Room owner - $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}/config", []); + $response = $this->actingAs($john)->post("api/v4/meet/rooms/{$room->name}/config", []); $response->assertStatus(200); $json = $response->json(); $this->assertCount(2, $json); $this->assertSame('success', $json['status']); $this->assertSame("Room configuration updated successfully.", $json['message']); // Set password and room lock $post = ['password' => 'aaa', 'locked' => 1]; - $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}/config", $post); + $response = $this->actingAs($john)->post("api/v4/meet/rooms/{$room->name}/config", $post); $response->assertStatus(200); $json = $response->json(); $this->assertCount(2, $json); $this->assertSame('success', $json['status']); $this->assertSame("Room configuration updated successfully.", $json['message']); $room->refresh(); $this->assertSame('aaa', $room->getSetting('password')); $this->assertSame('true', $room->getSetting('locked')); // Unset password and room lock $post = ['password' => '', 'locked' => 0]; - $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}/config", $post); + $response = $this->actingAs($john)->post("api/v4/meet/rooms/{$room->name}/config", $post); $response->assertStatus(200); $json = $response->json(); $this->assertCount(2, $json); $this->assertSame('success', $json['status']); $this->assertSame("Room configuration updated successfully.", $json['message']); $room->refresh(); $this->assertSame(null, $room->getSetting('password')); $this->assertSame(null, $room->getSetting('locked')); // Test invalid option error $post = ['password' => 'eee', 'unknown' => 0]; - $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}/config", $post); + $response = $this->actingAs($john)->post("api/v4/meet/rooms/{$room->name}/config", $post); $response->assertStatus(422); $json = $response->json(); $this->assertCount(2, $json); $this->assertSame('error', $json['status']); $this->assertSame("Invalid room configuration option.", $json['errors']['unknown']); $room->refresh(); $this->assertSame(null, $room->getSetting('password')); } /** * Test the webhook * - * @group openvidu + * @group meet */ public function testWebhook(): void { $this->assignMeetEntitlement('john@kolab.org'); $john = $this->getTestUser('john@kolab.org'); $room = Room::where('name', 'john')->first(); $headers = ['X-Auth-Token' => \config('meet.webhook_token')]; // First, create the session $post = ['init' => 1]; - $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}", $post); + $response = $this->actingAs($john)->post("api/v4/meet/rooms/{$room->name}", $post); $response->assertStatus(200); $sessionId = $room->fresh()->session_id; // Test accepting a join request $room->requestSave('1234', ['nickname' => 'test']); $post = ['roomId' => $sessionId, 'requestId' => '1234', 'event' => 'joinRequestAccepted']; $response = $this->post("api/webhooks/meet", $post); $response->assertStatus(403); // 403 because no auth token $response = $this->withHeaders($headers)->post("api/webhooks/meet", $post); $response->assertStatus(200); $request = $room->requestGet('1234'); $this->assertSame(Room::REQUEST_ACCEPTED, $request['status']); // Test denying a join request $room->requestSave('1234', ['nickname' => 'test']); $post = ['roomId' => $sessionId, 'requestId' => '1234', 'event' => 'joinRequestDenied']; $response = $this->withHeaders($headers)->post("api/webhooks/meet", $post); $response->assertStatus(200); $request = $room->requestGet('1234'); $this->assertSame(Room::REQUEST_DENIED, $request['status']); // Test closing the session $post = ['roomId' => $sessionId, 'event' => 'roomClosed']; $response = $this->withHeaders($headers)->post("api/webhooks/meet", $post); $response->assertStatus(200); $this->assertNull($room->fresh()->session_id); } } diff --git a/src/tests/TestCaseMeetTrait.php b/src/tests/TestCaseMeetTrait.php index 862ec5d5..2f4a2265 100644 --- a/src/tests/TestCaseMeetTrait.php +++ b/src/tests/TestCaseMeetTrait.php @@ -1,56 +1,56 @@ getTestUser($user); } $user->assignSku(\App\Sku::where('title', 'meet')->first()); } /** * Removes all 'meet' entitlements from the database */ protected function clearMeetEntitlements(): void { $meet_sku = \App\Sku::where('title', 'meet')->first(); \App\Entitlement::where('sku_id', $meet_sku->id)->delete(); } /** * Reset a room after tests */ public function resetTestRoom($room_name = 'john'): void { $this->clearMeetEntitlements(); $room = Room::where('name', $room_name)->first(); $room->setSettings(['password' => null, 'locked' => null, 'nomedia' => null]); if ($room->session_id) { $room->session_id = null; $room->save(); } } /** * Prepare a room for testing */ public function setupTestRoom($room_name = 'john'): void { $this->resetTestRoom($room_name); $this->assignMeetEntitlement('john@kolab.org'); } }