diff --git a/lib/Controller/PageController.php b/lib/Controller/PageController.php index eecd8a634eb..d8826453639 100644 --- a/lib/Controller/PageController.php +++ b/lib/Controller/PageController.php @@ -43,8 +43,10 @@ use OCP\IRequest; use OCP\IURLGenerator; use OCP\IUser; +use OCP\IUserManager; use OCP\IUserSession; use OCP\Notification\IManager as INotificationManager; +use OCP\Profile\IProfileManager; use OCP\Security\Bruteforce\IThrottler; use Psr\Log\LoggerInterface; use SensitiveParameter; @@ -69,6 +71,8 @@ public function __construct( private IThrottler $throttler, protected Config $talkConfig, protected IGroupManager $groupManager, + protected IUserManager $userManager, + protected IProfileManager $profileManager, ) { parent::__construct($appName, $request); } @@ -122,12 +126,6 @@ public function duplicateSession(): Response { return $this->pageHandler(); } - /** - * @param string $token - * @param string $callUser - * @return TemplateResponse|RedirectResponse - * @throws HintException - */ #[NoCSRFRequired] #[PublicPage] #[BruteForceProtection(action: 'talkRoomToken')] @@ -135,15 +133,47 @@ public function duplicateSession(): Response { #[FrontpageRoute(verb: 'GET', url: '/')] public function index(string $token = '', string $callUser = ''): Response { if ($callUser !== '') { + $user = $this->userSession->getUser(); + if (!$user instanceof IUser) { + return new RedirectResponse($this->url->linkToRoute('spreed.Page.meetUser', ['user' => $callUser])); + } $token = ''; } return $this->pageHandler($token, $callUser); } + #[NoCSRFRequired] + #[PublicPage] + #[BruteForceProtection(action: 'callUser')] + #[FrontpageRoute(verb: 'GET', url: '/meet/{user}', root: '')] + public function meetUser(string $user): Response { + $loggedInUser = $this->userSession->getUser(); + if ($loggedInUser instanceof IUser) { + $response = $this->api->createRoom(Room::TYPE_ONE_TO_ONE, $user); + if ($response->getStatus() === Http::STATUS_OK + || $response->getStatus() === Http::STATUS_CREATED) { + $data = $response->getData(); + return $this->redirectToConversation($data['token']); + } + return new RedirectResponse($this->url->linkToRoute('spreed.Page.index')); + } + + $targetUser = $this->userManager->get($user); + if (!$targetUser instanceof IUser + || $this->talkConfig->isNotAllowedToCreateConversations($targetUser) + || !$this->profileManager->isProfileFieldVisible('talk', $targetUser, null)) { + $response = new TemplateResponse('core', '404', [], TemplateResponse::RENDER_AS_GUEST); + $response->throttle(['action' => 'callUser', 'callUser' => $user]); + return $response; + } + + $this->initialState->provideInitialState('meet_target_user_id', $user); + $this->initialState->provideInitialState('meet_target_display_name', $targetUser->getDisplayName()); + + return new TemplateResponse($this->appName, 'meet', [], TemplateResponse::RENDER_AS_GUEST); + } + /** - * @param string $token - * @param string $callUser - * @param string $password * @return TemplateResponse|RedirectResponse * @throws HintException */ @@ -283,7 +313,6 @@ protected function pageHandler( } /** - * @param string $token * @return TemplateResponse|NotFoundResponse */ #[NoCSRFRequired] diff --git a/lib/Controller/RoomController.php b/lib/Controller/RoomController.php index a4762f35fe6..438b34a4472 100644 --- a/lib/Controller/RoomController.php +++ b/lib/Controller/RoomController.php @@ -10,6 +10,7 @@ use OCA\DAV\CalDAV\TimezoneService; use OCA\Talk\Capabilities; +use OCA\Talk\Chat\ChatManager; use OCA\Talk\Config; use OCA\Talk\Events\AAttendeeRemovedEvent; use OCA\Talk\Events\BeforeRoomsFetchEvent; @@ -104,7 +105,11 @@ use OCP\IURLGenerator; use OCP\IUser; use OCP\IUserManager; +use OCP\L10N\IFactory; +use OCP\Profile\IProfileManager; use OCP\Security\Bruteforce\IThrottler; +use OCP\Security\RateLimiting\ILimiter; +use OCP\Security\RateLimiting\IRateLimitExceededException; use OCP\Server; use OCP\User\Events\UserLiveStatusEvent; use OCP\UserStatus\IManager as IUserStatusManager; @@ -160,6 +165,10 @@ public function __construct( protected IL10N $l, protected ThreadService $threadService, protected Forced $forcedParameters, + protected IProfileManager $profileManager, + protected ILimiter $limiter, + protected IFactory $l10nFactory, + protected ChatManager $chatManager, ) { parent::__construct($appName, $request); } @@ -855,6 +864,86 @@ protected function createOneToOneRoom(string $targetUserId): DataResponse { } } + /** + * Create a meet room for a guest reaching out to a user via their public profile + * + * @param string $targetUserId ID of the user to contact + * @param string $message Initial chat message posted in the conversation + * @param string $displayName Guest display name + * @return DataResponse|DataResponse + * + * 201: Room created successfully + * 403: Not allowed to create conversations + * 404: User not found or profile not visible + * 429: Rate limit exceeded + */ + #[PublicPage] + #[BruteForceProtection(action: 'talkRoomToken')] + #[ApiRoute(verb: 'POST', url: '/api/{apiVersion}/meet/{targetUserId}', requirements: [ + 'apiVersion' => '(v4)', + ])] + public function createMeetRoom(string $targetUserId, string $message = '', string $displayName = ''): DataResponse { + try { + $this->limiter->registerAnonRequest( + 'create-anonymous-conversation', + 5, + 60 * 60, + $this->request->getRemoteAddress(), + ); + } catch (IRateLimitExceededException) { + return new DataResponse(null, Http::STATUS_TOO_MANY_REQUESTS); + } + + $user = $this->userManager->get($targetUserId); + if (!$user instanceof IUser) { + $response = new DataResponse(null, Http::STATUS_NOT_FOUND); + $response->throttle(['action' => 'talkRoomToken']); + return $response; + } + + if ($this->talkConfig->isNotAllowedToCreateConversations($user)) { + return new DataResponse(null, Http::STATUS_FORBIDDEN); + } + + if (!$this->profileManager->isProfileFieldVisible('talk', $user, null)) { + $response = new DataResponse(null, Http::STATUS_NOT_FOUND); + $response->throttle(['action' => 'talkRoomToken']); + return $response; + } + + $l = $this->l10nFactory->get('spreed', $this->l10nFactory->getUserLanguage($user)); + + $trimmedDisplayName = trim($displayName); + if ($trimmedDisplayName !== '') { + $roomName = $l->t('Contact request from %s', [$trimmedDisplayName]); + } else { + $roomName = $l->t('Contact request'); + } + + $room = $this->roomService->createConversation( + Room::TYPE_PUBLIC, + $roomName, + $user, + lobbyState: Webinary::LOBBY_NON_MODERATORS, + ); + + $message = trim($message); + if ($message !== '') { + $participant = $this->participantService->joinRoomAsNewGuest($this->roomService, $room, '', true, null, trim($displayName) ?: null); + $this->chatManager->sendMessage( + $room, + $participant, + Attendee::ACTOR_GUESTS, + $participant->getAttendee()->getActorId(), + $message, + $this->timeFactory->getDateTime(), + ); + $this->participantService->leaveRoomAsSession($room, $participant); + } + + return new DataResponse(['token' => $room->getToken()], Http::STATUS_CREATED); + } + /** * Add a room to the favorites * diff --git a/lib/Profile/TalkAction.php b/lib/Profile/TalkAction.php index a91f9778a79..c7c04e970c3 100644 --- a/lib/Profile/TalkAction.php +++ b/lib/Profile/TalkAction.php @@ -51,9 +51,10 @@ public function getDisplayId(): string { #[\Override] public function getTitle(): string { $visitingUser = $this->userSession->getUser(); - if (!$visitingUser || $visitingUser === $this->targetUser) { + if ($visitingUser === $this->targetUser) { return $this->l->t('Open Talk'); } + return $this->l->t('Talk to %s', [$this->targetUser->getDisplayName()]); } @@ -69,17 +70,23 @@ public function getIcon(): string { #[\Override] public function getTarget(): ?string { - $visitingUser = $this->userSession->getUser(); - if ( - !$visitingUser - || $this->config->isDisabledForUser($this->targetUser) - || $this->config->isDisabledForUser($visitingUser) - ) { + if ($this->config->isDisabledForUser($this->targetUser)) { return null; } + + $visitingUser = $this->userSession->getUser(); if ($visitingUser === $this->targetUser) { return $this->urlGenerator->linkToRouteAbsolute('spreed.Page.index'); } - return $this->urlGenerator->linkToRouteAbsolute('spreed.Page.index') . '?callUser=' . $this->targetUser->getUID(); + + if ($visitingUser && $this->config->isDisabledForUser($visitingUser)) { + return null; + } + + if (!$visitingUser && $this->config->isNotAllowedToCreateConversations($this->targetUser)) { + return null; + } + + return $this->urlGenerator->linkToRouteAbsolute('spreed.Page.meetUser', ['user' => $this->targetUser->getUID()]); } } diff --git a/openapi-full.json b/openapi-full.json index 2db353b447e..a4d425099a5 100644 --- a/openapi-full.json +++ b/openapi-full.json @@ -18070,6 +18070,209 @@ } } }, + "/ocs/v2.php/apps/spreed/api/{apiVersion}/meet/{targetUserId}": { + "post": { + "operationId": "room-create-meet-room", + "summary": "Create a meet room for a guest reaching out to a user via their public profile", + "tags": [ + "room" + ], + "security": [ + {}, + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "default": "", + "description": "Initial chat message posted in the conversation" + }, + "displayName": { + "type": "string", + "default": "", + "description": "Guest display name" + } + } + } + } + } + }, + "parameters": [ + { + "name": "apiVersion", + "in": "path", + "required": true, + "schema": { + "type": "string", + "enum": [ + "v4" + ], + "default": "v4" + } + }, + { + "name": "targetUserId", + "in": "path", + "description": "ID of the user to contact", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "201": { + "description": "Room created successfully", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "token" + ], + "properties": { + "token": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "404": { + "description": "User not found or profile not visible", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "nullable": true + } + } + } + } + } + } + } + }, + "403": { + "description": "Not allowed to create conversations", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "nullable": true + } + } + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "nullable": true + } + } + } + } + } + } + } + } + } + } + }, "/ocs/v2.php/apps/spreed/api/{apiVersion}/room/{token}/favorite": { "post": { "operationId": "room-add-to-favorites", diff --git a/openapi.json b/openapi.json index f612b57f35d..e671258cf6d 100644 --- a/openapi.json +++ b/openapi.json @@ -17958,6 +17958,209 @@ } } }, + "/ocs/v2.php/apps/spreed/api/{apiVersion}/meet/{targetUserId}": { + "post": { + "operationId": "room-create-meet-room", + "summary": "Create a meet room for a guest reaching out to a user via their public profile", + "tags": [ + "room" + ], + "security": [ + {}, + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "default": "", + "description": "Initial chat message posted in the conversation" + }, + "displayName": { + "type": "string", + "default": "", + "description": "Guest display name" + } + } + } + } + } + }, + "parameters": [ + { + "name": "apiVersion", + "in": "path", + "required": true, + "schema": { + "type": "string", + "enum": [ + "v4" + ], + "default": "v4" + } + }, + { + "name": "targetUserId", + "in": "path", + "description": "ID of the user to contact", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "201": { + "description": "Room created successfully", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "token" + ], + "properties": { + "token": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "404": { + "description": "User not found or profile not visible", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "nullable": true + } + } + } + } + } + } + } + }, + "403": { + "description": "Not allowed to create conversations", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "nullable": true + } + } + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "nullable": true + } + } + } + } + } + } + } + } + } + } + }, "/ocs/v2.php/apps/spreed/api/{apiVersion}/room/{token}/favorite": { "post": { "operationId": "room-add-to-favorites", diff --git a/rspack.config.js b/rspack.config.js index eaaf40f3733..3d3a3af875b 100644 --- a/rspack.config.js +++ b/rspack.config.js @@ -47,6 +47,7 @@ module.exports = defineConfig((env) => { path.join(__dirname, 'src', 'mainFilesSidebar.js'), path.join(__dirname, 'src', 'mainFilesSidebarLoader.js'), ], + meet: path.join(__dirname, 'src', 'meet.ts'), 'public-share-auth-form': path.join(__dirname, 'src', 'publicShareAuthForm.ts'), 'public-share-auth-sidebar': path.join(__dirname, 'src', 'mainPublicShareAuthSidebar.js'), 'public-share-sidebar': path.join(__dirname, 'src', 'mainPublicShareSidebar.js'), diff --git a/src/meet.ts b/src/meet.ts new file mode 100644 index 00000000000..5b0512c35fd --- /dev/null +++ b/src/meet.ts @@ -0,0 +1,17 @@ +/*! + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { getCSPNonce } from '@nextcloud/auth' +import { generateFilePath } from '@nextcloud/router' +import { createApp } from 'vue' +import MeetView from './views/MeetView.vue' +import { NextcloudGlobalsVuePlugin } from './utils/NextcloudGlobalsVuePlugin.js' + +__webpack_nonce__ = getCSPNonce() +__webpack_public_path__ = generateFilePath('spreed', '', 'js/') + +createApp(MeetView) + .use(NextcloudGlobalsVuePlugin) + .mount('#talk-meet') diff --git a/src/types/openapi/openapi-full.ts b/src/types/openapi/openapi-full.ts index 10c22a32eba..f2b9d57faf2 100644 --- a/src/types/openapi/openapi-full.ts +++ b/src/types/openapi/openapi-full.ts @@ -1168,6 +1168,23 @@ export type paths = { patch?: never; trace?: never; }; + "/ocs/v2.php/apps/spreed/api/{apiVersion}/meet/{targetUserId}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Create a meet room for a guest reaching out to a user via their public profile */ + post: operations["room-create-meet-room"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/ocs/v2.php/apps/spreed/api/{apiVersion}/room/{token}/favorite": { parameters: { query?: never; @@ -9879,6 +9896,97 @@ export interface operations { }; }; }; + "room-create-meet-room": { + parameters: { + query?: never; + header: { + /** @description Required to be true for the API request to pass */ + "OCS-APIRequest": boolean; + }; + path: { + apiVersion: "v4"; + /** @description ID of the user to contact */ + targetUserId: string; + }; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": { + /** + * @description Initial chat message posted in the conversation + * @default + */ + message?: string; + /** + * @description Guest display name + * @default + */ + displayName?: string; + }; + }; + }; + responses: { + /** @description Room created successfully */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + token: string; + }; + }; + }; + }; + }; + /** @description Not allowed to create conversations */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: unknown; + }; + }; + }; + }; + /** @description User not found or profile not visible */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: unknown; + }; + }; + }; + }; + /** @description Rate limit exceeded */ + 429: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: unknown; + }; + }; + }; + }; + }; + }; "room-add-to-favorites": { parameters: { query?: never; diff --git a/src/types/openapi/openapi.ts b/src/types/openapi/openapi.ts index e1fa42d20f8..c6036eae3bb 100644 --- a/src/types/openapi/openapi.ts +++ b/src/types/openapi/openapi.ts @@ -1168,6 +1168,23 @@ export type paths = { patch?: never; trace?: never; }; + "/ocs/v2.php/apps/spreed/api/{apiVersion}/meet/{targetUserId}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Create a meet room for a guest reaching out to a user via their public profile */ + post: operations["room-create-meet-room"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/ocs/v2.php/apps/spreed/api/{apiVersion}/room/{token}/favorite": { parameters: { query?: never; @@ -9312,6 +9329,97 @@ export interface operations { }; }; }; + "room-create-meet-room": { + parameters: { + query?: never; + header: { + /** @description Required to be true for the API request to pass */ + "OCS-APIRequest": boolean; + }; + path: { + apiVersion: "v4"; + /** @description ID of the user to contact */ + targetUserId: string; + }; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": { + /** + * @description Initial chat message posted in the conversation + * @default + */ + message?: string; + /** + * @description Guest display name + * @default + */ + displayName?: string; + }; + }; + }; + responses: { + /** @description Room created successfully */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + token: string; + }; + }; + }; + }; + }; + /** @description Not allowed to create conversations */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: unknown; + }; + }; + }; + }; + /** @description User not found or profile not visible */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: unknown; + }; + }; + }; + }; + /** @description Rate limit exceeded */ + 429: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: unknown; + }; + }; + }; + }; + }; + }; "room-add-to-favorites": { parameters: { query?: never; diff --git a/src/views/MeetView.vue b/src/views/MeetView.vue new file mode 100644 index 00000000000..c725c405551 --- /dev/null +++ b/src/views/MeetView.vue @@ -0,0 +1,96 @@ + + + + + + + diff --git a/templates/meet.php b/templates/meet.php new file mode 100644 index 00000000000..d35f742d659 --- /dev/null +++ b/templates/meet.php @@ -0,0 +1,12 @@ + +
diff --git a/tests/integration/features/bootstrap/FeatureContext.php b/tests/integration/features/bootstrap/FeatureContext.php index fb79ea3c868..3bbff16c26f 100644 --- a/tests/integration/features/bootstrap/FeatureContext.php +++ b/tests/integration/features/bootstrap/FeatureContext.php @@ -1164,6 +1164,48 @@ public function userCreatesRoomWith(string $user, string $identifier, int $statu } } + #[Then('/^user "([^"]*)" sets the Talk profile visibility to "([^"]*)"$/')] + public function userSetsTalkProfileVisibility(string $user, string $visibility): void { + $this->setCurrentUser($user); + $this->sendRequest('PUT', '/profile/' . $user, [ + 'paramId' => 'talk', + 'visibility' => $visibility, + ]); + $this->assertStatusCode($this->response, 200); + } + + #[Then('/^guest creates meet room "([^"]*)" for "([^"]*)" with (\d+) \((v4)\)$/')] + public function guestCreatesMeetRoom(string $identifier, string $targetUserId, int $statusCode, string $apiVersion, ?TableNode $formData = null): void { + $body = []; + if ($formData instanceof TableNode) { + $body = $formData->getRowsHash(); + } + + $this->setCurrentUser(null); + $this->sendRequest('POST', '/apps/spreed/api/' . $apiVersion . '/meet/' . $targetUserId, $body); + $this->assertStatusCode($this->response, $statusCode); + + if ($statusCode === 201) { + $response = $this->getDataFromResponse($this->response); + self::$identifierToToken[$identifier] = $response['token']; + self::$tokenToIdentifier[$response['token']] = $identifier; + + // If a message was sent, register the guest actor ID for message assertions + if (!empty($body['message'])) { + $this->setCurrentUser($targetUserId); + $this->sendRequest('GET', '/apps/spreed/api/v1/chat/' . $response['token'] . '?lookIntoFuture=0'); + $messages = $this->getDataFromResponse($this->response); + foreach ($messages as $message) { + if ($message['actorType'] === 'guests' && $message['systemMessage'] === '') { + self::$sessionIdToUser[$message['actorId']] = 'MEET_GUEST_ACTOR_ID'; + break; + } + } + $this->setCurrentUser(null); + } + } + } + #[Then('/^user "([^"]*)" tries to create room with (\d+) \((v4)\)$/')] public function userTriesToCreateRoom(string $user, int $statusCode, string $apiVersion = 'v1', ?TableNode $formData = null): void { $this->setCurrentUser($user); diff --git a/tests/integration/features/conversation-4/meet.feature b/tests/integration/features/conversation-4/meet.feature new file mode 100644 index 00000000000..42dcc30301d --- /dev/null +++ b/tests/integration/features/conversation-4/meet.feature @@ -0,0 +1,54 @@ +Feature: conversation-4/meet + Background: + Given user "participant1" exists + Given user "participant2" exists + And user "participant1" sets the Talk profile visibility to "show" + + Scenario: Guest creates a meet room + Given guest creates meet room "room" for "participant1" with 201 (v4) + Then user "participant1" is participant of room "room" (v4) + | name | type | lobbyState | + | Contact request | 3 | 1 | + + Scenario: Guest creates a meet room with display name + Given guest creates meet room "room" for "participant1" with 201 (v4) + | displayName | Guest User | + Then user "participant1" is participant of room "room" (v4) + | name | type | lobbyState | + | Contact request from Guest User | 3 | 1 | + + Scenario: Guest creates a meet room with message and display name + Given guest creates meet room "room" for "participant1" with 201 (v4) + | message | Hello, I need help! | + | displayName | Guest User | + Then user "participant1" is participant of room "room" (v4) + | name | type | lobbyState | + | Contact request from Guest User | 3 | 1 | + Then user "participant1" sees the following messages in room "room" with 200 + | room | actorType | actorId | actorDisplayName | message | messageParameters | + | room | guests | MEET_GUEST_ACTOR_ID | Guest User | Hello, I need help! | [] | + + Scenario: Guest creates a meet room with message but no display name + Given guest creates meet room "room" for "participant1" with 201 (v4) + | message | Hello there | + Then user "participant1" sees the following messages in room "room" with 200 + | room | actorType | actorId | actorDisplayName | message | messageParameters | + | room | guests | MEET_GUEST_ACTOR_ID | | Hello there | [] | + + Scenario: Guest creates a meet room without message does not leave a guest participant + Given guest creates meet room "room" for "participant1" with 201 (v4) + Then user "participant1" sees the following attendees in room "room" with 200 (v4) + | actorType | participantType | + | users | 1 | + + Scenario: Guest creates a meet room for a non-existing user + Given guest creates meet room "room" for "non-existing-user" with 404 (v4) + + Scenario: Guest creates a meet room for a user that cannot create conversations + Given the following "spreed" app config is set + | start_conversations | ["admin"] | + Given guest creates meet room "room" for "participant1" with 403 (v4) + + Scenario: Guest cannot create a meet room when Talk profile is hidden + Given user "participant1" sets the Talk profile visibility to "hide" + Given guest creates meet room "room" for "participant1" with 404 (v4)