diff --git a/src/app/Http/Controllers/API/V4/OpenViduController.php b/src/app/Http/Controllers/API/V4/OpenViduController.php index 8273720b..f3e374c0 100644 --- a/src/app/Http/Controllers/API/V4/OpenViduController.php +++ b/src/app/Http/Controllers/API/V4/OpenViduController.php @@ -1,388 +1,390 @@ first(); // This isn't a room, bye bye if (!$room) { return $this->errorResponse(404, \trans('meet.room-not-found')); } $user = Auth::guard()->user(); // Only the room owner can do it if (!$user || $user->id != $room->user_id) { return $this->errorResponse(403); } if (!$room->requestAccept($reqid)) { return $this->errorResponse(500, \trans('meet.session-request-accept-error')); } return response()->json(['status' => 'success']); } /** * Denying the room join request. * * @param string $id Room identifier (name) * @param string $reqid Request identifier * * @return \Illuminate\Http\JsonResponse */ public function denyJoinRequest($id, $reqid) { $room = Room::where('name', $id)->first(); // This isn't a room, bye bye if (!$room) { return $this->errorResponse(404, \trans('meet.room-not-found')); } $user = Auth::guard()->user(); // Only the room owner can do it if (!$user || $user->id != $room->user_id) { return $this->errorResponse(403); } if (!$room->requestDeny($reqid)) { return $this->errorResponse(500, \trans('meet.session-request-deny-error')); } return response()->json(['status' => 'success']); } /** * Close the room session. * * @param string $id Room identifier (name) * * @return \Illuminate\Http\JsonResponse */ public function closeRoom($id) { $room = Room::where('name', $id)->first(); // This isn't a room, bye bye if (!$room) { return $this->errorResponse(404, \trans('meet.room-not-found')); } $user = Auth::guard()->user(); // Only the room owner can do it if (!$user || $user->id != $room->user_id) { return $this->errorResponse(403); } if (!$room->deleteSession()) { return $this->errorResponse(500, \trans('meet.session-close-error')); } return response()->json([ 'status' => 'success', 'message' => __('meet.session-close-success'), ]); } /** * Accepting the room join request. * * @param string $id Room identifier (name) * @param string $conn Connection identifier * * @return \Illuminate\Http\JsonResponse */ public function dismissConnection($id, $conn) { $room = Room::where('name', $id)->first(); // This isn't a room, bye bye if (!$room) { return $this->errorResponse(404, \trans('meet.room-not-found')); } $user = Auth::guard()->user(); // Only the room owner can do it if (!$user || $user->id != $room->user_id) { return $this->errorResponse(403); } if (!$room->closeOVConnection($conn)) { return $this->errorResponse(500, \trans('meet.session-dismiss-connection-error')); } return response()->json(['status' => 'success']); } /** * Listing of rooms that belong to the current user. * * @return \Illuminate\Http\JsonResponse */ public function index() { $user = Auth::guard()->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 beta entitlement for the room owner $sku = \App\Sku::where('title', 'meet')->first(); if ($sku && !$room->owner->entitlements()->where('sku_id', $sku->id)->first()) { return $this->errorResponse(404, \trans('meet.room-not-found')); } $user = Auth::guard()->user(); $isOwner = $user && $user->id == $room->user_id; // 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(423, \trans('meet.session-not-found')); + return $this->errorResponse(422, \trans('meet.session-not-found'), ['code' => 323]); } // The room owner can create the session on request if (empty(request()->input('init'))) { - return $this->errorResponse(424, \trans('meet.session-not-found')); + 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')); } } $password = (string) $room->getSetting('password'); $config = [ 'locked' => $room->getSetting('locked') === '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(425, \trans('meet.session-password-error'), ['config' => $config]); + 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(426, $error, ['config' => $config]); + 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(426, $error, ['config' => $config]); + 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(426, $error, ['config' => $config]); + return $this->errorResponse(422, $error, $response + ['code' => 326]); } // Send the request (signal) to the owner $result = $room->signal('joinRequest', $request, Room::ROLE_MODERATOR); } - return $this->errorResponse(427, $error, ['config' => $config]); + return $this->errorResponse(422, $error, $response + ['code' => 327]); } } // Create session token for the current user/connection $response = $room->getSessionToken($isOwner ? Room::ROLE_MODERATOR : Room::ROLE_PUBLISHER); if (empty($response)) { return $this->errorResponse(500, \trans('meet.session-join-error')); } // Create session token for screen sharing connection if (!empty(request()->input('screenShare'))) { $add_token = $room->getSessionToken(Room::ROLE_PUBLISHER); $response['shareToken'] = $add_token['token']; } // Tell the UI who's the room owner $response['owner'] = $isOwner; // Append the room configuration $response['config'] = $config; return response()->json($response); } /** * 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; 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 * * @param \Illuminate\Http\Request $request The API request. * * @return \Illuminate\Http\Response The response */ public function webhook(Request $request) { \Log::debug($request->getContent()); switch ((string) $request->input('event')) { case 'sessionDestroyed': // When all participants left the room OpenVidu dispatches sessionDestroyed // event. We'll remove the session reference from the database. $sessionId = $request->input('sessionId'); $room = Room::where('session_id', $sessionId)->first(); if ($room) { $room->session_id = null; $room->save(); } break; } return response('Success', 200); } } diff --git a/src/resources/vue/Meet/Room.vue b/src/resources/vue/Meet/Room.vue index a7daa277..77ba8621 100644 --- a/src/resources/vue/Meet/Room.vue +++ b/src/resources/vue/Meet/Room.vue @@ -1,515 +1,521 @@ Set up your session None {{ mic.label }} None {{ cam.label }} JOIN NOW - I'm the owner + I'm the owner JOIN Room closed × The session has been closed by the room owner. diff --git a/src/resources/vue/Widgets/StatusMessage.vue b/src/resources/vue/Widgets/StatusMessage.vue index 71935346..80fd3120 100644 --- a/src/resources/vue/Widgets/StatusMessage.vue +++ b/src/resources/vue/Widgets/StatusMessage.vue @@ -1,49 +1,49 @@ {{ statusLabel() }} - - {{ statusLabel() }} + + {{ statusLabel() }} diff --git a/src/tests/Browser/Meet/RoomControlsTest.php b/src/tests/Browser/Meet/RoomControlsTest.php index 67f1f0c0..ab47aed6 100644 --- a/src/tests/Browser/Meet/RoomControlsTest.php +++ b/src/tests/Browser/Meet/RoomControlsTest.php @@ -1,342 +1,338 @@ clearBetaEntitlements(); } public function tearDown(): void { $this->clearBetaEntitlements(); parent::tearDown(); } /** * Test fullscreen buttons * * @group openvidu */ public function testFullscreen(): void { // TODO: This test does not work in headless mode $this->markTestIncomplete(); // Make sure there's no session yet $room = Room::where('name', 'john')->first(); if ($room->session_id) { $room->session_id = null; $room->save(); } $this->assignBetaEntitlement('john@kolab.org', 'meet'); $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 muting audio/video * * @group openvidu */ public function testNicknameAndMuting(): void { // Make sure there's no session yet $room = Room::where('name', 'john')->first(); if ($room->session_id) { $room->session_id = null; $room->save(); } $this->assignBetaEntitlement('john@kolab.org', 'meet'); $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') ->click('@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', '') ->click('@setup-button') ->waitFor('@session'); // Assert current UI state $owner->assertToolbar([ 'audio' => RoomPage::BUTTON_ACTIVE | RoomPage::BUTTON_ENABLED, 'video' => RoomPage::BUTTON_ACTIVE | RoomPage::BUTTON_ENABLED, 'screen' => RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_ENABLED, 'chat' => RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_ENABLED, 'fullscreen' => RoomPage::BUTTON_ACTIVE | RoomPage::BUTTON_ENABLED, 'security' => RoomPage::BUTTON_ACTIVE | RoomPage::BUTTON_ENABLED, 'logout' => RoomPage::BUTTON_ACTIVE | RoomPage::BUTTON_ENABLED, ]) ->whenAvailable('div.meet-video.publisher', function (Browser $browser) { $browser->assertVisible('video') ->assertAudioMuted('video', true) ->assertSeeIn('.nickname', 'john') - ->assertMissing('.nickname button') ->assertVisible('.controls button.link-fullscreen') ->assertMissing('.controls button.link-audio') ->assertMissing('.status .status-audio') ->assertMissing('.status .status-video'); }) ->whenAvailable('div.meet-video:not(.publisher)', function (Browser $browser) { $browser->assertMissing('video') - ->assertMissing('.nickname') + ->assertVisible('.nickname') ->assertVisible('.controls button.link-fullscreen') ->assertVisible('.controls button.link-audio') ->assertVisible('.status .status-audio') ->assertVisible('.status .status-video'); }) ->assertElementsCount('@session div.meet-video', 2); // Assert current UI state $guest->assertToolbar([ 'audio' => RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_DISABLED, 'video' => RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_DISABLED, 'screen' => RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_ENABLED, 'chat' => RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_ENABLED, 'fullscreen' => RoomPage::BUTTON_ACTIVE | RoomPage::BUTTON_ENABLED, 'logout' => RoomPage::BUTTON_ACTIVE | RoomPage::BUTTON_ENABLED, ]) ->whenAvailable('div.meet-video.publisher', function (Browser $browser) { $browser->assertVisible('video') //->assertAudioMuted('video', true) - ->assertVisible('.nickname button') - ->assertMissing('.nickname span') ->assertVisible('.controls button.link-fullscreen') ->assertMissing('.controls button.link-audio') ->assertVisible('.status .status-audio') ->assertVisible('.status .status-video'); }) ->whenAvailable('div.meet-video:not(.publisher)', function (Browser $browser) { $browser->assertVisible('video') ->assertSeeIn('.nickname', 'john') - ->assertMissing('.nickname button') ->assertVisible('.controls button.link-fullscreen') ->assertVisible('.controls button.link-audio') ->assertMissing('.status .status-audio') ->assertMissing('.status .status-video'); }) ->assertElementsCount('@session div.meet-video', 2); // Test nickname change propagation // Use script() because type() does not work with this contenteditable widget $guest->setNickname('div.meet-video.publisher', 'guest'); $owner->waitFor('div.meet-video:not(.publisher) .nickname') ->assertSeeIn('div.meet-video:not(.publisher) .nickname', 'guest'); // Test muting audio $owner->click('@menu button.link-audio') ->assertToolbarButtonState('audio', RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_ENABLED) ->assertVisible('div.meet-video.publisher .status .status-audio'); // FIXME: It looks that we can't just check the element state // We might consider using OpenVidu API to make sure $guest->waitFor('div.meet-video:not(.publisher) .status .status-audio'); // Test unmuting audio $owner->click('@menu button.link-audio') ->assertToolbarButtonState('audio', RoomPage::BUTTON_ACTIVE | RoomPage::BUTTON_ENABLED) ->assertMissing('div.meet-video.publisher .status .status-audio'); $guest->waitUntilMissing('div.meet-video:not(.publisher) .status .status-audio'); // Test muting video $owner->click('@menu button.link-video') ->assertToolbarButtonState('video', RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_ENABLED) ->assertVisible('div.meet-video.publisher .status .status-video'); // FIXME: It looks that we can't just check the element state // We might consider using OpenVidu API to make sure $guest->waitFor('div.meet-video:not(.publisher) .status .status-video'); // Test unmuting video $owner->click('@menu button.link-video') ->assertToolbarButtonState('video', RoomPage::BUTTON_ACTIVE | RoomPage::BUTTON_ENABLED) ->assertMissing('div.meet-video.publisher .status .status-video'); $guest->waitUntilMissing('div.meet-video:not(.publisher) .status .status-video'); // Test muting other user $guest->with('div.meet-video:not(.publisher)', 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 text chat * * @group openvidu * @depends testNicknameAndMuting */ public function testChat(): void { $this->assignBetaEntitlement('john@kolab.org', 'meet'); $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') ->click('@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', '') ->click('@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', '') ->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') ->assertSeeIn('@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.publisher', '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 */ public function testShareScreen(): void { // It looks that screen sharing API is not available in headless chrome // Note that other tests already assert that the button is disabled $this->markTestIncomplete(); } } diff --git a/src/tests/Browser/Meet/RoomSecurityTest.php b/src/tests/Browser/Meet/RoomSecurityTest.php index bb7c3392..675596ed 100644 --- a/src/tests/Browser/Meet/RoomSecurityTest.php +++ b/src/tests/Browser/Meet/RoomSecurityTest.php @@ -1,248 +1,248 @@ clearBetaEntitlements(); $this->assignBetaEntitlement('john@kolab.org', 'meet'); $room = Room::where('name', 'john')->first(); $room->setSettings(['password' => null, 'locked' => null]); } public function tearDown(): void { $this->clearBetaEntitlements(); $room = Room::where('name', 'john')->first(); $room->setSettings(['password' => null, 'locked' => null]); parent::tearDown(); } /** * Test password protected room * * @group openvidu */ public function testRoomPassword(): void { $this->browse(function (Browser $owner, Browser $guest) { // Make sure there's no session yet $room = Room::where('name', 'john')->first(); if ($room->session_id) { $room->session_id = null; $room->save(); } // 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') ->click('@setup-button') ->waitFor('@session') // Enter Security option dialog ->click('@menu button.link-security') ->with(new Dialog('#security-options-dialog'), function (Browser $browser) use ($room) { $browser->assertSeeIn('@title', 'Security 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.text-danger', "Please, provide a valid password.") + ->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 ->click('@setup-button') ->waitFor('#setup-password.is-invalid') // Try to join with a valid password ->type('#setup-password', 'pass') ->click('@setup-button') ->waitFor('@session'); // Test removing the password $owner->click('@menu button.link-security') ->with(new Dialog('#security-options-dialog'), function (Browser $browser) use ($room) { $browser->assertSeeIn('@title', 'Security 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 * * @group openvidu */ public function testLockedRoom(): void { $this->browse(function (Browser $owner, Browser $guest) { // Make sure there's no session yet $room = Room::where('name', 'john')->first(); if ($room->session_id) { $room->session_id = null; $room->save(); } // 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') ->click('@setup-button') ->waitFor('@session') // Enter Security option dialog ->click('@menu button.link-security') ->with(new Dialog('#security-options-dialog'), function (Browser $browser) use ($room) { $browser->assertSeeIn('@title', 'Security 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') ->assertSeeIn('@setup-button:not([disabled]).btn-success', 'JOIN NOW') // try without the nickname ->click('@setup-button') ->waitFor('@setup-nickname-input.is-invalid') ->assertSeeIn( - '@setup-status-message.text-danger', + '@setup-status-message', "The room is locked. Please, enter your name and try again." ) ->assertMissing('@setup-password-input') ->assertSeeIn('@setup-button:not([disabled]).btn-success', 'JOIN NOW') ->type('@setup-nickname-input', 'Guest') ->click('@setup-button') ->assertMissing('@setup-nickname-input.is-invalid') ->waitFor('@setup-button[disabled]') - ->assertSeeIn('@setup-status-message.text-danger', "Waiting for permission to join the room."); + ->assertSeeIn('@setup-status-message', "Waiting for permission to join the room."); // 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 accepting the request $guest->refresh() ->waitFor('@setup-form') ->waitUntilMissing('@setup-status-message.loading') ->type('@setup-nickname-input', 'guest') ->click('@setup-button') ->waitFor('@setup-button[disabled]') - ->assertSeeIn('@setup-status-message.text-danger', "Waiting for permission to join the room."); + ->assertSeeIn('@setup-status-message', "Waiting for permission to join the room."); $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(.publisher)') ->assertSeeIn('@session .meet-video:not(.publisher) a.nickname', 'John') // TODO: Assert title and icon ->click('@session .meet-video:not(.publisher) a.nickname') ->pause(100) ->assertMissing('.dropdown-menu'); // Test dismissing the participant $owner->click('@session .meet-video:not(.publisher) a.nickname') ->waitFor('@session .meet-video:not(.publisher) .dropdown-menu') ->assertSeeIn('@session .meet-video:not(.publisher) .dropdown-menu > .action-dismiss', 'Dismiss') ->click('@session .meet-video:not(.publisher) .dropdown-menu > .action-dismiss') ->waitUntilMissing('.dropdown-menu') ->waitUntilMissing('@session .meet-video:not(.publisher)'); // 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'); }); }); } } diff --git a/src/tests/Browser/Meet/RoomSetupTest.php b/src/tests/Browser/Meet/RoomSetupTest.php index 1fbc42e8..05fe91ba 100644 --- a/src/tests/Browser/Meet/RoomSetupTest.php +++ b/src/tests/Browser/Meet/RoomSetupTest.php @@ -1,283 +1,282 @@ clearBetaEntitlements(); } public function tearDown(): void { $this->clearBetaEntitlements(); parent::tearDown(); } /** * Test non-existing room * * @group openvidu */ 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']); }); 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.") ->assertVisible('@setup-button[disabled]'); }); } /** * Test the room setup page * * @group openvidu */ public function testRoomSetup(): void { // Make sure there's no session yet $room = Room::where('name', 'john')->first(); if ($room->session_id) { $room->session_id = null; $room->save(); } $this->assignBetaEntitlement('john@kolab.org', 'meet'); $this->browse(function (Browser $browser) { $browser->visit(new RoomPage('john')) ->within(new Menu(), function ($browser) { $browser->assertMenuItems(['signup', 'explore', 'blog', 'support', 'login']); }); 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 * @depends testRoomSetup */ public function testTwoUsersInARoom(): void { $this->assignBetaEntitlement('john@kolab.org', 'meet'); $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") ->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') ->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') // Join the room ->click('@setup-button') ->waitFor('@session') ->assertMissing('@setup-form') ->whenAvailable('div.meet-video.publisher', function (Browser $browser) { $browser->assertVisible('video') ->assertSeeIn('.nickname', 'john') ->assertVisible('.controls button.link-fullscreen') ->assertMissing('.controls button.link-audio') ->assertMissing('.status .status-audio') ->assertMissing('.status .status-video'); }) ->within(new Menu(), function ($browser) { $browser->assertMenuItems(['explore', 'blog', 'support', 'dashboard', 'logout']); }); if ($browser->isDesktop()) { $browser->within(new Menu('footer'), function ($browser) { $browser->assertMenuItems(['explore', 'blog', 'support', 'tos', 'dashboard', 'logout']); }); } // 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', '') ->click('@setup-button') ->waitFor('@session') ->assertMissing('@setup-form') ->whenAvailable('div.meet-video.publisher', function (Browser $browser) { $browser->assertVisible('video') - ->assertVisible('.nickname button') - ->assertMissing('.nickname span') + ->assertVisible('.nickname') ->assertVisible('.controls button.link-fullscreen') ->assertMissing('.controls button.link-audio') ->assertVisible('.status .status-audio') ->assertVisible('.status .status-video'); }) ->whenAvailable('div.meet-video:not(.publisher)', function (Browser $browser) { $browser->assertVisible('video') ->assertSeeIn('.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) ->within(new Menu(), function ($browser) { $browser->assertMenuItems(['explore', 'blog', 'support', 'signup', 'login']); }); if ($guest->isDesktop()) { $guest->within(new Menu('footer'), function ($browser) { $browser->assertMenuItems(['explore', 'blog', 'support', 'tos', 'signup', 'login']); }); } // Check guest's elements in the owner's window $browser->waitFor('@session div.meet-video:nth-child(2)') ->assertElementsCount('@session div.meet-video', 2) ->whenAvailable('div.meet-video:not(.publisher)', function (Browser $browser) { $browser->assertMissing('video') - ->assertMissing('.nickname') + ->assertVisible('.nickname') ->assertVisible('.controls button.link-fullscreen') ->assertVisible('.controls button.link-audio') ->assertVisible('.status .status-audio') ->assertVisible('.status .status-video'); }); // 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 div.meet-video:nth-child(2)'); // 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', '') ->click('@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'); }); } } diff --git a/src/tests/Feature/Controller/OpenViduTest.php b/src/tests/Feature/Controller/OpenViduTest.php index da7864d3..dd726e97 100644 --- a/src/tests/Feature/Controller/OpenViduTest.php +++ b/src/tests/Feature/Controller/OpenViduTest.php @@ -1,503 +1,527 @@ clearBetaEntitlements(); $room = Room::where('name', 'john')->first(); $room->setSettings(['password' => null, 'locked' => null]); } public function tearDown(): void { $this->clearBetaEntitlements(); $room = Room::where('name', 'john')->first(); $room->setSettings(['password' => null, 'locked' => null]); parent::tearDown(); } /** * Test listing user rooms * * @group openvidu */ 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->assertStatus(401); // John has one room $response = $this->actingAs($john)->get("api/v4/openvidu/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->assertStatus(200); $json = $response->json(); $this->assertSame(1, $json['count']); $this->assertCount(1, $json['list']); $this->assertRegExp('/^[0-9a-z-]{11}$/', $json['list'][0]['name']); } /** * Test joining the room * * @group openvidu */ 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->assignBetaEntitlement($john, 'meet'); // Unauth access, no session yet $response = $this->post("api/v4/openvidu/rooms/{$room->name}"); - $response->assertStatus(423); + $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->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->assertStatus(423); + $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->assertStatus(424); + $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->assertStatus(200); $json = $response->json(); $session_id = $room->fresh()->session_id; $this->assertSame(Room::ROLE_MODERATOR, $json['role']); $this->assertSame($session_id, $json['session']); $this->assertTrue(is_string($session_id) && !empty($session_id)); $this->assertTrue(strpos($json['token'], 'wss://') === 0); $this->assertTrue(!array_key_exists('shareToken', $json)); $john_token = $json['token']; // Non-owner, now the session exists $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}"); $response->assertStatus(200); $json = $response->json(); $this->assertSame(Room::ROLE_PUBLISHER, $json['role']); $this->assertSame($session_id, $json['session']); $this->assertTrue(strpos($json['token'], 'wss://') === 0); $this->assertTrue($json['token'] != $john_token); $this->assertTrue(!array_key_exists('shareToken', $json)); $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->assertStatus(425); + $response->assertStatus(422); $json = $response->json(); - $this->assertCount(3, $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->assertStatus(425); + $response->assertStatus(422); + + $json = $response->json(); + $this->assertSame(325, $json['code']); // Non-owner, password protected room, valid password provided $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}", ['password' => 'pass']); $response->assertStatus(200); $json = $response->json(); $this->assertSame($session_id, $json['session']); // Make sure the room owner can access the password protected room w/o password $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}"); $response->assertStatus(200); } /** * Test locked room and join requests * * @group openvidu */ 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->assignBetaEntitlement($john, 'meet'); // 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->assertStatus(200); // Non-owner, locked room, invalid/missing input $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}"); - $response->assertStatus(426); + $response->assertStatus(422); $json = $response->json(); - $this->assertCount(3, $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' => '-----']; $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}", $post); - $response->assertStatus(426); + $response->assertStatus(422); + + $json = $response->json(); + $this->assertSame(326, $json['code']); // Non-owner, locked room, invalid requestId $post = ['nickname' => 'name', 'picture' => '-----']; $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}", $post); - $response->assertStatus(426); + $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->assertStatus(427); + $response->assertStatus(422); $json = $response->json(); - $this->assertCount(3, $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']); // TODO: How do we assert that a signal has been sent to the owner? // Test denying a request // Unknown room $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/unknown/request/unknown/deny"); $response->assertStatus(404); // Unknown request Id $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}/request/unknown/deny"); $response->assertStatus(500); $json = $response->json(); $this->assertCount(2, $json); $this->assertSame('error', $json['status']); $this->assertSame('Failed to deny the join request.', $json['message']); // Non-owner access forbidden $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}/request/{$reqId}/deny"); $response->assertStatus(403); // Valid request $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}/request/{$reqId}/deny"); $response->assertStatus(200); $json = $response->json(); $this->assertSame('success', $json['status']); // Non-owner, locked room, join request denied $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}", $post); - $response->assertStatus(427); + $response->assertStatus(422); + + $json = $response->json(); + $this->assertSame(327, $json['code']); // Test accepting a request // Unknown room $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/unknown/request/unknown/accept"); $response->assertStatus(404); // Unknown request Id $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}/request/unknown/accept"); $response->assertStatus(500); $json = $response->json(); $this->assertCount(2, $json); $this->assertSame('error', $json['status']); $this->assertSame('Failed to accept the join request.', $json['message']); // Non-owner access forbidden $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}/request/{$reqId}/accept"); $response->assertStatus(403); // Valid request $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}/request/{$reqId}/accept"); $response->assertStatus(200); $json = $response->json(); $this->assertSame('success', $json['status']); // Non-owner, locked room, join request accepted $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}", $post); $response->assertStatus(200); $json = $response->json(); $this->assertSame(Room::ROLE_PUBLISHER, $json['role']); $this->assertTrue(strpos($json['token'], 'wss://') === 0); // TODO: Test a scenario where both password and lock are enabled } /** * Test joining the room * * @group openvidu * @depends testJoinRoom */ public function testJoinRoomGuest(): void { $this->assignBetaEntitlement('john@kolab.org', 'meet'); // There's no asy 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 $response = $this->post("api/v4/openvidu/rooms/{$room->name}", ['screenShare' => 1]); $response->assertStatus(200); $json = $response->json(); $this->assertSame(Room::ROLE_PUBLISHER, $json['role']); $this->assertSame($room->session_id, $json['session']); $this->assertTrue(strpos($json['token'], 'wss://') === 0); $this->assertTrue(strpos($json['shareToken'], 'wss://') === 0); $this->assertTrue($json['shareToken'] != $json['token']); } /** * Test closing the room (session) * * @group openvidu * @depends testJoinRoom */ public function testCloseRoom(): 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}/close", []); $response->assertStatus(401); // Non-existing room name $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/non-existing/close", []); $response->assertStatus(404); // Non-owner $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}/close", []); $response->assertStatus(403); // Room owner $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}/close", []); $response->assertStatus(200); $json = $response->json(); $this->assertNull($room->fresh()->session_id); $this->assertSame('success', $json['status']); $this->assertSame("The session has been closed successfully.", $json['message']); $this->assertCount(2, $json); // TODO: Test if the session is removed from the OpenVidu server too // Test error handling when it's not possible to delete the session on // the OpenVidu server (use fake session_id) $room->session_id = 'aaa'; $room->save(); $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}/close", []); $response->assertStatus(500); $json = $response->json(); $this->assertSame('aaa', $room->fresh()->session_id); $this->assertSame('error', $json['status']); $this->assertSame("Failed to close the session.", $json['message']); $this->assertCount(2, $json); } /** * Test dismissing a participant (closing a connection) * * @group openvidu */ public function testDismissConnection(): 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->assignBetaEntitlement($john, 'meet'); // First we create the session $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}", ['init' => 1]); $response->assertStatus(200); $json = $response->json(); // And the other user connection $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}", ['init' => 1]); $response->assertStatus(200); $json = $response->json(); $conn_id = $json['connectionId']; $room->refresh(); $conn_data = $room->getOVConnection($conn_id); $this->assertSame($conn_id, $conn_data['connectionId']); // Non-existing room name $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/non-existing/connections/{$conn_id}/dismiss"); $response->assertStatus(404); // TODO: Test accessing an existing room of deleted owner // Non-existing connection $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}/connections/123/dismiss"); $response->assertStatus(500); $json = $response->json(); $this->assertCount(2, $json); $this->assertSame('error', $json['status']); $this->assertSame('Failed to dismiss the connection.', $json['message']); // Non-owner access $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}/connections/{$conn_id}/dismiss"); $response->assertStatus(403); // Expected success $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}/connections/{$conn_id}/dismiss"); $response->assertStatus(200); $json = $response->json(); $this->assertSame('success', $json['status']); $this->assertNull($room->getOVConnection($conn_id)); } /** * Test configuring the room (session) * * @group openvidu */ 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->assertStatus(401); // Non-existing room name $response = $this->actingAs($john)->post("api/v4/openvidu/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->assertStatus(403); // Room owner $response = $this->actingAs($john)->post("api/v4/openvidu/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->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->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->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')); } }
The session has been closed by the room owner.
') ->click('@setup-button') ->assertMissing('@setup-nickname-input.is-invalid') ->waitFor('@setup-button[disabled]') - ->assertSeeIn('@setup-status-message.text-danger', "Waiting for permission to join the room."); + ->assertSeeIn('@setup-status-message', "Waiting for permission to join the room."); // 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 accepting the request $guest->refresh() ->waitFor('@setup-form') ->waitUntilMissing('@setup-status-message.loading') ->type('@setup-nickname-input', 'guest') ->click('@setup-button') ->waitFor('@setup-button[disabled]') - ->assertSeeIn('@setup-status-message.text-danger', "Waiting for permission to join the room."); + ->assertSeeIn('@setup-status-message', "Waiting for permission to join the room."); $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(.publisher)') ->assertSeeIn('@session .meet-video:not(.publisher) a.nickname', 'John') // TODO: Assert title and icon ->click('@session .meet-video:not(.publisher) a.nickname') ->pause(100) ->assertMissing('.dropdown-menu'); // Test dismissing the participant $owner->click('@session .meet-video:not(.publisher) a.nickname') ->waitFor('@session .meet-video:not(.publisher) .dropdown-menu') ->assertSeeIn('@session .meet-video:not(.publisher) .dropdown-menu > .action-dismiss', 'Dismiss') ->click('@session .meet-video:not(.publisher) .dropdown-menu > .action-dismiss') ->waitUntilMissing('.dropdown-menu') ->waitUntilMissing('@session .meet-video:not(.publisher)'); // 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'); }); }); } } diff --git a/src/tests/Browser/Meet/RoomSetupTest.php b/src/tests/Browser/Meet/RoomSetupTest.php index 1fbc42e8..05fe91ba 100644 --- a/src/tests/Browser/Meet/RoomSetupTest.php +++ b/src/tests/Browser/Meet/RoomSetupTest.php @@ -1,283 +1,282 @@ clearBetaEntitlements(); } public function tearDown(): void { $this->clearBetaEntitlements(); parent::tearDown(); } /** * Test non-existing room * * @group openvidu */ 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']); }); 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.") ->assertVisible('@setup-button[disabled]'); }); } /** * Test the room setup page * * @group openvidu */ public function testRoomSetup(): void { // Make sure there's no session yet $room = Room::where('name', 'john')->first(); if ($room->session_id) { $room->session_id = null; $room->save(); } $this->assignBetaEntitlement('john@kolab.org', 'meet'); $this->browse(function (Browser $browser) { $browser->visit(new RoomPage('john')) ->within(new Menu(), function ($browser) { $browser->assertMenuItems(['signup', 'explore', 'blog', 'support', 'login']); }); 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 * @depends testRoomSetup */ public function testTwoUsersInARoom(): void { $this->assignBetaEntitlement('john@kolab.org', 'meet'); $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") ->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') ->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') // Join the room ->click('@setup-button') ->waitFor('@session') ->assertMissing('@setup-form') ->whenAvailable('div.meet-video.publisher', function (Browser $browser) { $browser->assertVisible('video') ->assertSeeIn('.nickname', 'john') ->assertVisible('.controls button.link-fullscreen') ->assertMissing('.controls button.link-audio') ->assertMissing('.status .status-audio') ->assertMissing('.status .status-video'); }) ->within(new Menu(), function ($browser) { $browser->assertMenuItems(['explore', 'blog', 'support', 'dashboard', 'logout']); }); if ($browser->isDesktop()) { $browser->within(new Menu('footer'), function ($browser) { $browser->assertMenuItems(['explore', 'blog', 'support', 'tos', 'dashboard', 'logout']); }); } // 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', '') ->click('@setup-button') ->waitFor('@session') ->assertMissing('@setup-form') ->whenAvailable('div.meet-video.publisher', function (Browser $browser) { $browser->assertVisible('video') - ->assertVisible('.nickname button') - ->assertMissing('.nickname span') + ->assertVisible('.nickname') ->assertVisible('.controls button.link-fullscreen') ->assertMissing('.controls button.link-audio') ->assertVisible('.status .status-audio') ->assertVisible('.status .status-video'); }) ->whenAvailable('div.meet-video:not(.publisher)', function (Browser $browser) { $browser->assertVisible('video') ->assertSeeIn('.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) ->within(new Menu(), function ($browser) { $browser->assertMenuItems(['explore', 'blog', 'support', 'signup', 'login']); }); if ($guest->isDesktop()) { $guest->within(new Menu('footer'), function ($browser) { $browser->assertMenuItems(['explore', 'blog', 'support', 'tos', 'signup', 'login']); }); } // Check guest's elements in the owner's window $browser->waitFor('@session div.meet-video:nth-child(2)') ->assertElementsCount('@session div.meet-video', 2) ->whenAvailable('div.meet-video:not(.publisher)', function (Browser $browser) { $browser->assertMissing('video') - ->assertMissing('.nickname') + ->assertVisible('.nickname') ->assertVisible('.controls button.link-fullscreen') ->assertVisible('.controls button.link-audio') ->assertVisible('.status .status-audio') ->assertVisible('.status .status-video'); }); // 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 div.meet-video:nth-child(2)'); // 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', '') ->click('@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'); }); } } diff --git a/src/tests/Feature/Controller/OpenViduTest.php b/src/tests/Feature/Controller/OpenViduTest.php index da7864d3..dd726e97 100644 --- a/src/tests/Feature/Controller/OpenViduTest.php +++ b/src/tests/Feature/Controller/OpenViduTest.php @@ -1,503 +1,527 @@ clearBetaEntitlements(); $room = Room::where('name', 'john')->first(); $room->setSettings(['password' => null, 'locked' => null]); } public function tearDown(): void { $this->clearBetaEntitlements(); $room = Room::where('name', 'john')->first(); $room->setSettings(['password' => null, 'locked' => null]); parent::tearDown(); } /** * Test listing user rooms * * @group openvidu */ 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->assertStatus(401); // John has one room $response = $this->actingAs($john)->get("api/v4/openvidu/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->assertStatus(200); $json = $response->json(); $this->assertSame(1, $json['count']); $this->assertCount(1, $json['list']); $this->assertRegExp('/^[0-9a-z-]{11}$/', $json['list'][0]['name']); } /** * Test joining the room * * @group openvidu */ 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->assignBetaEntitlement($john, 'meet'); // Unauth access, no session yet $response = $this->post("api/v4/openvidu/rooms/{$room->name}"); - $response->assertStatus(423); + $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->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->assertStatus(423); + $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->assertStatus(424); + $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->assertStatus(200); $json = $response->json(); $session_id = $room->fresh()->session_id; $this->assertSame(Room::ROLE_MODERATOR, $json['role']); $this->assertSame($session_id, $json['session']); $this->assertTrue(is_string($session_id) && !empty($session_id)); $this->assertTrue(strpos($json['token'], 'wss://') === 0); $this->assertTrue(!array_key_exists('shareToken', $json)); $john_token = $json['token']; // Non-owner, now the session exists $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}"); $response->assertStatus(200); $json = $response->json(); $this->assertSame(Room::ROLE_PUBLISHER, $json['role']); $this->assertSame($session_id, $json['session']); $this->assertTrue(strpos($json['token'], 'wss://') === 0); $this->assertTrue($json['token'] != $john_token); $this->assertTrue(!array_key_exists('shareToken', $json)); $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->assertStatus(425); + $response->assertStatus(422); $json = $response->json(); - $this->assertCount(3, $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->assertStatus(425); + $response->assertStatus(422); + + $json = $response->json(); + $this->assertSame(325, $json['code']); // Non-owner, password protected room, valid password provided $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}", ['password' => 'pass']); $response->assertStatus(200); $json = $response->json(); $this->assertSame($session_id, $json['session']); // Make sure the room owner can access the password protected room w/o password $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}"); $response->assertStatus(200); } /** * Test locked room and join requests * * @group openvidu */ 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->assignBetaEntitlement($john, 'meet'); // 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->assertStatus(200); // Non-owner, locked room, invalid/missing input $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}"); - $response->assertStatus(426); + $response->assertStatus(422); $json = $response->json(); - $this->assertCount(3, $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' => '-----']; $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}", $post); - $response->assertStatus(426); + $response->assertStatus(422); + + $json = $response->json(); + $this->assertSame(326, $json['code']); // Non-owner, locked room, invalid requestId $post = ['nickname' => 'name', 'picture' => '-----']; $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}", $post); - $response->assertStatus(426); + $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->assertStatus(427); + $response->assertStatus(422); $json = $response->json(); - $this->assertCount(3, $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']); // TODO: How do we assert that a signal has been sent to the owner? // Test denying a request // Unknown room $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/unknown/request/unknown/deny"); $response->assertStatus(404); // Unknown request Id $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}/request/unknown/deny"); $response->assertStatus(500); $json = $response->json(); $this->assertCount(2, $json); $this->assertSame('error', $json['status']); $this->assertSame('Failed to deny the join request.', $json['message']); // Non-owner access forbidden $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}/request/{$reqId}/deny"); $response->assertStatus(403); // Valid request $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}/request/{$reqId}/deny"); $response->assertStatus(200); $json = $response->json(); $this->assertSame('success', $json['status']); // Non-owner, locked room, join request denied $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}", $post); - $response->assertStatus(427); + $response->assertStatus(422); + + $json = $response->json(); + $this->assertSame(327, $json['code']); // Test accepting a request // Unknown room $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/unknown/request/unknown/accept"); $response->assertStatus(404); // Unknown request Id $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}/request/unknown/accept"); $response->assertStatus(500); $json = $response->json(); $this->assertCount(2, $json); $this->assertSame('error', $json['status']); $this->assertSame('Failed to accept the join request.', $json['message']); // Non-owner access forbidden $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}/request/{$reqId}/accept"); $response->assertStatus(403); // Valid request $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}/request/{$reqId}/accept"); $response->assertStatus(200); $json = $response->json(); $this->assertSame('success', $json['status']); // Non-owner, locked room, join request accepted $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}", $post); $response->assertStatus(200); $json = $response->json(); $this->assertSame(Room::ROLE_PUBLISHER, $json['role']); $this->assertTrue(strpos($json['token'], 'wss://') === 0); // TODO: Test a scenario where both password and lock are enabled } /** * Test joining the room * * @group openvidu * @depends testJoinRoom */ public function testJoinRoomGuest(): void { $this->assignBetaEntitlement('john@kolab.org', 'meet'); // There's no asy 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 $response = $this->post("api/v4/openvidu/rooms/{$room->name}", ['screenShare' => 1]); $response->assertStatus(200); $json = $response->json(); $this->assertSame(Room::ROLE_PUBLISHER, $json['role']); $this->assertSame($room->session_id, $json['session']); $this->assertTrue(strpos($json['token'], 'wss://') === 0); $this->assertTrue(strpos($json['shareToken'], 'wss://') === 0); $this->assertTrue($json['shareToken'] != $json['token']); } /** * Test closing the room (session) * * @group openvidu * @depends testJoinRoom */ public function testCloseRoom(): 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}/close", []); $response->assertStatus(401); // Non-existing room name $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/non-existing/close", []); $response->assertStatus(404); // Non-owner $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}/close", []); $response->assertStatus(403); // Room owner $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}/close", []); $response->assertStatus(200); $json = $response->json(); $this->assertNull($room->fresh()->session_id); $this->assertSame('success', $json['status']); $this->assertSame("The session has been closed successfully.", $json['message']); $this->assertCount(2, $json); // TODO: Test if the session is removed from the OpenVidu server too // Test error handling when it's not possible to delete the session on // the OpenVidu server (use fake session_id) $room->session_id = 'aaa'; $room->save(); $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}/close", []); $response->assertStatus(500); $json = $response->json(); $this->assertSame('aaa', $room->fresh()->session_id); $this->assertSame('error', $json['status']); $this->assertSame("Failed to close the session.", $json['message']); $this->assertCount(2, $json); } /** * Test dismissing a participant (closing a connection) * * @group openvidu */ public function testDismissConnection(): 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->assignBetaEntitlement($john, 'meet'); // First we create the session $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}", ['init' => 1]); $response->assertStatus(200); $json = $response->json(); // And the other user connection $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}", ['init' => 1]); $response->assertStatus(200); $json = $response->json(); $conn_id = $json['connectionId']; $room->refresh(); $conn_data = $room->getOVConnection($conn_id); $this->assertSame($conn_id, $conn_data['connectionId']); // Non-existing room name $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/non-existing/connections/{$conn_id}/dismiss"); $response->assertStatus(404); // TODO: Test accessing an existing room of deleted owner // Non-existing connection $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}/connections/123/dismiss"); $response->assertStatus(500); $json = $response->json(); $this->assertCount(2, $json); $this->assertSame('error', $json['status']); $this->assertSame('Failed to dismiss the connection.', $json['message']); // Non-owner access $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}/connections/{$conn_id}/dismiss"); $response->assertStatus(403); // Expected success $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}/connections/{$conn_id}/dismiss"); $response->assertStatus(200); $json = $response->json(); $this->assertSame('success', $json['status']); $this->assertNull($room->getOVConnection($conn_id)); } /** * Test configuring the room (session) * * @group openvidu */ 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->assertStatus(401); // Non-existing room name $response = $this->actingAs($john)->post("api/v4/openvidu/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->assertStatus(403); // Room owner $response = $this->actingAs($john)->post("api/v4/openvidu/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->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->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->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')); } }