Browse Source

Merge pull request #4810 from nextcloud/feature/1329/sync-group-members-with-conversation-participants

Sync group members with conversation participants
pull/5460/head
Joas Schilling 5 years ago
committed by GitHub
parent
commit
24bcefa5cd
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      lib/AppInfo/Application.php
  2. 39
      lib/Collaboration/Collaborators/Listener.php
  3. 9
      lib/Command/Room/TRoomCommand.php
  4. 32
      lib/Controller/RoomController.php
  5. 117
      lib/Listener/GroupMembershipListener.php
  6. 17
      lib/Manager.php
  7. 1
      lib/Model/Attendee.php
  8. 98
      lib/Service/ParticipantService.php
  9. 17
      src/components/RightSidebar/Participants/CurrentParticipants/CurrentParticipants.vue
  10. 27
      src/components/RightSidebar/Participants/ParticipantsList/Participant/Participant.vue
  11. 1
      src/constants.js
  12. 40
      tests/integration/features/bootstrap/FeatureContext.php
  13. 2
      tests/integration/features/command/create.feature
  14. 1
      tests/integration/features/conversation/add-participant.feature
  15. 264
      tests/integration/features/conversation/group-participants.feature

5
lib/AppInfo/Application.php

@ -45,6 +45,7 @@ use OCA\Talk\Flow\Operation;
use OCA\Talk\Listener\BeforeUserLoggedOutListener;
use OCA\Talk\Listener\CSPListener;
use OCA\Talk\Listener\FeaturePolicyListener;
use OCA\Talk\Listener\GroupMembershipListener;
use OCA\Talk\Listener\RestrictStartingCalls as RestrictStartingCallsListener;
use OCA\Talk\Listener\UserDeletedListener;
use OCA\Talk\Listener\UserDisplayNameListener;
@ -71,6 +72,8 @@ use OCP\AppFramework\Bootstrap\IRegistrationContext;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\Collaboration\Resources\IProviderManager;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\Group\Events\UserAddedEvent;
use OCP\Group\Events\UserRemovedEvent;
use OCP\IServerContainer;
use OCP\IUser;
use OCP\Security\CSP\AddContentSecurityPolicyEvent;
@ -95,6 +98,8 @@ class Application extends App implements IBootstrap {
$context->registerEventListener(AddContentSecurityPolicyEvent::class, CSPListener::class);
$context->registerEventListener(AddFeaturePolicyEvent::class, FeaturePolicyListener::class);
$context->registerEventListener(UserDeletedEvent::class, UserDeletedListener::class);
$context->registerEventListener(UserAddedEvent::class, GroupMembershipListener::class);
$context->registerEventListener(UserRemovedEvent::class, GroupMembershipListener::class);
$context->registerEventListener(BeforeUserLoggedOutEvent::class, BeforeUserLoggedOutListener::class);
$context->registerEventListener(BeforeTemplateRenderedEvent::class, PublicShareTemplateLoader::class);
$context->registerEventListener(BeforeTemplateRenderedEvent::class, PublicShareAuthTemplateLoader::class);

39
lib/Collaboration/Collaborators/Listener.php

@ -27,6 +27,7 @@ use OCA\Talk\Config;
use OCA\Talk\Exceptions\ParticipantNotFoundException;
use OCA\Talk\Exceptions\RoomNotFoundException;
use OCA\Talk\Manager;
use OCA\Talk\Model\Attendee;
use OCA\Talk\Participant;
use OCA\Talk\Room;
use OCP\Collaboration\AutoComplete\AutoCompleteEvent;
@ -61,7 +62,7 @@ class Listener {
public static function register(IEventDispatcher $dispatcher): void {
$dispatcher->addListener(IManager::class . '::filterResults', static function (AutoCompleteEvent $event) {
/** @var self $listener */
$listener = \OC::$server->query(self::class);
$listener = \OC::$server->get(self::class);
if ($event->getItemType() !== 'call') {
return;
@ -82,28 +83,28 @@ class Listener {
}
if (!empty($results['groups'])) {
$results['groups'] = array_filter($results['groups'], [$this, 'filterGroupResult']);
$results['groups'] = array_filter($results['groups'], [$this, 'filterBlockedGroupResult']);
}
if (!empty($results['exact']['groups'])) {
$results['exact']['groups'] = array_filter($results['exact']['groups'], [$this, 'filterGroupResult']);
$results['exact']['groups'] = array_filter($results['exact']['groups'], [$this, 'filterBlockedGroupResult']);
}
if (!empty($results['users'])) {
$results['users'] = array_filter($results['users'], [$this, 'filterUserResult']);
$results['users'] = array_filter($results['users'], [$this, 'filterBlockedUserResult']);
}
if (!empty($results['exact']['users'])) {
$results['exact']['users'] = array_filter($results['exact']['users'], [$this, 'filterUserResult']);
$results['exact']['users'] = array_filter($results['exact']['users'], [$this, 'filterBlockedUserResult']);
}
return $results;
}
protected function filterUserResult(array $result): bool {
protected function filterBlockedUserResult(array $result): bool {
$user = $this->userManager->get($result['value']['shareWith']);
return $user instanceof IUser && !$this->config->isDisabledForUser($user);
}
protected function filterGroupResult(array $result): bool {
protected function filterBlockedGroupResult(array $result): bool {
return \in_array($result['value']['shareWith'], $this->allowedGroupIds, true);
}
@ -114,17 +115,24 @@ class Listener {
return $results;
}
if (!empty($results['groups'])) {
$results['groups'] = array_filter($results['groups'], [$this, 'filterParticipantGroupResult']);
}
if (!empty($results['exact']['groups'])) {
$results['exact']['groups'] = array_filter($results['exact']['groups'], [$this, 'filterParticipantGroupResult']);
}
if (!empty($results['users'])) {
$results['users'] = array_filter($results['users'], [$this, 'filterParticipantResult']);
$results['users'] = array_filter($results['users'], [$this, 'filterParticipantUserResult']);
}
if (!empty($results['exact']['users'])) {
$results['exact']['users'] = array_filter($results['exact']['users'], [$this, 'filterParticipantResult']);
$results['exact']['users'] = array_filter($results['exact']['users'], [$this, 'filterParticipantUserResult']);
}
return $results;
}
protected function filterParticipantResult(array $result): bool {
protected function filterParticipantUserResult(array $result): bool {
$userId = $result['value']['shareWith'];
try {
@ -138,4 +146,15 @@ class Listener {
return true;
}
}
protected function filterParticipantGroupResult(array $result): bool {
$groupId = $result['value']['shareWith'];
try {
$this->room->getParticipantByActor(Attendee::ACTOR_GROUPS, $groupId);
return false;
} catch (ParticipantNotFoundException $e) {
return true;
}
}
}

9
lib/Command/Room/TRoomCommand.php

@ -230,21 +230,14 @@ trait TRoomCommand {
return;
}
$users = [];
foreach ($groupIds as $groupId) {
$group = $this->groupManager->get($groupId);
if ($group === null) {
throw new InvalidArgumentException(sprintf("Group '%s' not found.", $groupId));
}
$groupUsers = array_map(function (IUser $user) {
return $user->getUID();
}, $group->getUsers());
$users = array_merge($users, array_values($groupUsers));
$this->participantService->addGroup($room, $group);
}
$this->addRoomParticipants($room, array_unique($users));
}
/**

32
lib/Controller/RoomController.php

@ -673,23 +673,7 @@ class RoomController extends AEnvironmentAwareController {
// Create the room
$name = $this->roomService->prepareConversationName($targetGroup->getDisplayName());
$room = $this->roomService->createConversation(Room::GROUP_CALL, $name, $currentUser);
$usersInGroup = $targetGroup->getUsers();
$participants = [];
foreach ($usersInGroup as $user) {
if ($currentUser->getUID() === $user->getUID()) {
// Owner is already added.
continue;
}
$participants[] = [
'actorType' => Attendee::ACTOR_USERS,
'actorId' => $user->getUID(),
'displayName' => $user->getDisplayName(),
];
}
$this->participantService->addUsers($room, $participants);
$this->participantService->addGroup($room, $targetGroup);
return new DataResponse($this->formatRoom($room, $room->getParticipant($currentUser->getUID(), false)), Http::STATUS_CREATED);
}
@ -995,6 +979,8 @@ class RoomController extends AEnvironmentAwareController {
continue;
}
$result['displayName'] = $participant->getAttendee()->getDisplayName();
} elseif ($participant->getAttendee()->getActorType() === Attendee::ACTOR_GROUPS) {
$result['displayName'] = $participant->getAttendee()->getDisplayName();
}
@ -1050,14 +1036,7 @@ class RoomController extends AEnvironmentAwareController {
return new DataResponse([], Http::STATUS_NOT_FOUND);
}
$usersInGroup = $group->getUsers();
foreach ($usersInGroup as $user) {
$participantsToAdd[] = [
'actorType' => Attendee::ACTOR_USERS,
'actorId' => $user->getUID(),
'displayName' => $user->getDisplayName(),
];
}
$this->participantService->addGroup($this->room, $group, $participants);
} elseif ($source === 'circles') {
if (!$this->appManager->isEnabledForUser('circles')) {
return new DataResponse([], Http::STATUS_BAD_REQUEST);
@ -1458,6 +1437,9 @@ class RoomController extends AEnvironmentAwareController {
if ($session instanceof Session && $currentSessionId === $session->getSessionId()) {
return new DataResponse([], Http::STATUS_FORBIDDEN);
}
} elseif ($attendee->getActorType() === Attendee::ACTOR_GROUPS) {
// Can not promote/demote groups
return new DataResponse([], Http::STATUS_BAD_REQUEST);
}
if ($promote === $targetParticipant->hasModeratorPermissions()) {

117
lib/Listener/GroupMembershipListener.php

@ -0,0 +1,117 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2020 Joas Schilling <coding@schilljs.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Talk\Listener;
use OCA\Talk\Exceptions\ParticipantNotFoundException;
use OCA\Talk\Manager;
use OCA\Talk\Model\Attendee;
use OCA\Talk\Participant;
use OCA\Talk\Room;
use OCA\Talk\Service\ParticipantService;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
use OCP\Group\Events\UserAddedEvent;
use OCP\Group\Events\UserRemovedEvent;
use OCP\IGroup;
use OCP\IGroupManager;
use OCP\IUser;
class GroupMembershipListener implements IEventListener {
/** @var IGroupManager */
private $groupManager;
/** @var Manager */
private $manager;
/** @var ParticipantService */
private $participantService;
public function __construct(IGroupManager $groupManager,
Manager $manager,
ParticipantService $participantService) {
$this->groupManager = $groupManager;
$this->manager = $manager;
$this->participantService = $participantService;
}
public function handle(Event $event): void {
if ($event instanceof UserAddedEvent) {
$this->addNewMemberToRooms($event->getGroup(), $event->getUser());
}
if ($event instanceof UserRemovedEvent) {
$this->removeFormerMemberFromRooms($event->getGroup(), $event->getUser());
}
}
protected function addNewMemberToRooms(IGroup $group, IUser $user): void {
$rooms = $this->manager->getRoomsForActor(Attendee::ACTOR_GROUPS, $group->getGID());
foreach ($rooms as $room) {
try {
$participant = $room->getParticipant($user->getUID());
if ($participant->getAttendee()->getParticipantType() === Participant::USER_SELF_JOINED) {
$this->participantService->updateParticipantType($room, $participant, Participant::USER);
}
} catch (ParticipantNotFoundException $e) {
$this->participantService->addUsers($room, [[
'actorType' => Attendee::ACTOR_USERS,
'actorId' => $user->getUID(),
'displayName' => $user->getDisplayName(),
]]);
}
}
}
protected function removeFormerMemberFromRooms(IGroup $group, IUser $user): void {
$rooms = $this->manager->getRoomsForActor(Attendee::ACTOR_GROUPS, $group->getGID());
if (empty($rooms)) {
return;
}
$userGroupIds = $this->groupManager->getUserGroupIds($user);
$furtherMemberships = [];
foreach ($userGroupIds as $groupId) {
$groupRooms = $this->manager->getRoomsForActor(Attendee::ACTOR_GROUPS, $groupId);
foreach ($groupRooms as $room) {
$furtherMemberships[$room->getId()] = true;
}
}
$rooms = array_filter($rooms, static function (Room $room) use ($furtherMemberships) {
// Only delete from rooms where the user is not member via another group
return !isset($furtherMemberships[$room->getId()]);
});
foreach ($rooms as $room) {
try {
$participant = $room->getParticipant($user->getUID());
$participantType = $participant->getAttendee()->getParticipantType();
if ($participantType === Participant::USER) {
$this->participantService->removeUser($room, $user, Room::PARTICIPANT_REMOVED);
}
} catch (ParticipantNotFoundException $e) {
}
}
}
}

17
lib/Manager.php

@ -304,14 +304,25 @@ class Manager {
* @return Room[]
*/
public function getRoomsForUser(string $userId, array $sessionIds = [], bool $includeLastMessage = false): array {
return $this->getRoomsForActor(Attendee::ACTOR_USERS, $userId, $sessionIds, $includeLastMessage);
}
/**
* @param string $actorType
* @param string $actorId
* @param array $sessionIds A list of talk sessions to consider for loading (otherwise no session is loaded)
* @param bool $includeLastMessage
* @return Room[]
*/
public function getRoomsForActor(string $actorType, string $actorId, array $sessionIds = [], bool $includeLastMessage = false): array {
$query = $this->db->getQueryBuilder();
$helper = new SelectHelper();
$helper->selectRoomsTable($query);
$helper->selectAttendeesTable($query);
$query->from('talk_rooms', 'r')
->leftJoin('r', 'talk_attendees', 'a', $query->expr()->andX(
$query->expr()->eq('a.actor_id', $query->createNamedParameter($userId)),
$query->expr()->eq('a.actor_type', $query->createNamedParameter(Attendee::ACTOR_USERS)),
$query->expr()->eq('a.actor_id', $query->createNamedParameter($actorId)),
$query->expr()->eq('a.actor_type', $query->createNamedParameter($actorType)),
$query->expr()->eq('a.room_id', 'r.id')
))
->where($query->expr()->isNotNull('a.id'));
@ -337,7 +348,7 @@ class Manager {
}
$room = $this->createRoomObject($row);
if ($userId !== null && isset($row['actor_id'])) {
if ($actorType === Attendee::ACTOR_USERS && isset($row['actor_id'])) {
$room->setParticipant($row['actor_id'], $this->createParticipantObject($room, $row));
}
$rooms[] = $room;

1
lib/Model/Attendee.php

@ -52,6 +52,7 @@ use OCP\AppFramework\Db\Entity;
*/
class Attendee extends Entity {
public const ACTOR_USERS = 'users';
public const ACTOR_GROUPS = 'groups';
public const ACTOR_GUESTS = 'guests';
public const ACTOR_EMAILS = 'emails';

98
lib/Service/ParticipantService.php

@ -50,6 +50,7 @@ use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\IConfig;
use OCP\IDBConnection;
use OCP\IGroup;
use OCP\IUser;
use OCP\IUserManager;
use OCP\IGroupManager;
@ -105,6 +106,12 @@ class ParticipantService {
public function updateParticipantType(Room $room, Participant $participant, int $participantType): void {
$attendee = $participant->getAttendee();
if ($attendee->getActorType() === Attendee::ACTOR_GROUPS) {
// Can not promote/demote groups
return;
}
$oldType = $attendee->getParticipantType();
$event = new ModifyParticipantEvent($room, $participant, 'type', $participantType, $oldType);
@ -300,6 +307,60 @@ class ParticipantService {
$this->dispatcher->dispatch(Room::EVENT_AFTER_USERS_ADD, $event);
}
/**
* @param Room $room
* @param IGroup $group
* @param Participant[] $existingParticipants
*/
public function addGroup(Room $room, IGroup $group, array $existingParticipants = []): void {
$usersInGroup = $group->getUsers();
if (empty($existingParticipants)) {
$existingParticipants = $this->getParticipantsForRoom($room);
}
$participantsByUserId = [];
foreach ($existingParticipants as $participant) {
if ($participant->getAttendee()->getActorType() === Attendee::ACTOR_USERS) {
$participantsByUserId[$participant->getAttendee()->getActorId()] = $participant;
}
}
$newParticipants = [];
foreach ($usersInGroup as $user) {
$existingParticipant = $participantsByUserId[$user->getUID()] ?? null;
if ($existingParticipant instanceof Participant) {
if ($existingParticipant->getAttendee()->getParticipantType() === Participant::USER_SELF_JOINED) {
$this->updateParticipantType($room, $existingParticipant, Participant::USER);
}
// Participant is already in the conversation, so skip them.
continue;
}
$newParticipants[] = [
'actorType' => Attendee::ACTOR_USERS,
'actorId' => $user->getUID(),
'displayName' => $user->getDisplayName(),
];
}
try {
$this->attendeeMapper->findByActor($room->getId(), Attendee::ACTOR_GROUPS, $group->getGID());
} catch (DoesNotExistException $e) {
$attendee = new Attendee();
$attendee->setRoomId($room->getId());
$attendee->setActorType(Attendee::ACTOR_GROUPS);
$attendee->setActorId($group->getGID());
$attendee->setDisplayName($group->getDisplayName());
$attendee->setParticipantType(Participant::USER);
$attendee->setReadPrivacy(Participant::PRIVACY_PRIVATE);
$this->attendeeMapper->insert($attendee);
}
$this->addUsers($room, $newParticipants);
}
/**
* @param Room $room
* @param string $email
@ -412,6 +473,43 @@ class ParticipantService {
} else {
$this->dispatcher->dispatch(Room::EVENT_AFTER_PARTICIPANT_REMOVE, $event);
}
if ($participant->getAttendee()->getActorType() === Attendee::ACTOR_GROUPS) {
$this->removeGroupMembers($room, $participant, $reason);
}
}
public function removeGroupMembers(Room $room, Participant $removedGroupParticipant, string $reason): void {
$removedGroup = $this->groupManager->get($removedGroupParticipant->getAttendee()->getActorId());
if (!$removedGroup instanceof IGroup) {
return;
}
$attendeeGroups = $this->attendeeMapper->getActorsByType($room->getId(), Attendee::ACTOR_GROUPS);
$groupsInRoom = [];
foreach ($attendeeGroups as $attendee) {
$groupsInRoom[] = $attendee->getActorId();
}
foreach ($removedGroup->getUsers() as $user) {
try {
$participant = $room->getParticipant($user->getUID());
} catch (ParticipantNotFoundException $e) {
continue;
}
$userGroups = $this->groupManager->getUserGroupIds($user);
$stillHasGroup = array_intersect($userGroups, $groupsInRoom);
if (!empty($stillHasGroup)) {
continue;
}
if ($participant->getAttendee()->getParticipantType() === Participant::USER) {
// Only remove normal users, not moderators/admins
$this->removeAttendee($room, $participant, $reason);
}
}
}
public function removeUser(Room $room, IUser $user, string $reason): void {

17
src/components/RightSidebar/Participants/CurrentParticipants/CurrentParticipants.vue

@ -32,7 +32,7 @@
<script>
import ParticipantsList from '../ParticipantsList/ParticipantsList'
import { PARTICIPANT } from '../../../../constants'
import { ATTENDEE, PARTICIPANT } from '../../../../constants'
import UserStatus from '../../../../mixins/userStatus'
import Hint from '../../../Hint'
import { subscribe, unsubscribe } from '@nextcloud/event-bus'
@ -112,6 +112,7 @@ export default {
/**
* Sort two participants by:
* - participants before groups
* - online status
* - in call
* - who raised hand first
@ -123,13 +124,27 @@ export default {
* @param {int} participant1.participantType First participant type
* @param {string} participant1.sessionId First participant session
* @param {string} participant1.displayName First participant display name
* @param {string} participant1.status First participant user status
* @param {string} participant1.actorType First participant actor type
* @param {int} participant1.inCall First participant in call flag
* @param {object} participant2 Second participant
* @param {int} participant2.participantType Second participant type
* @param {string} participant2.sessionId Second participant session
* @param {string} participant2.displayName Second participant display name
* @param {string} participant2.actorType Second participant actor type
* @param {string} participant2.status Second participant user status
* @param {int} participant2.inCall Second participant in call flag
* @returns {number}
*/
sortParticipants(participant1, participant2) {
const p1IsGroup = participant1.actorType === ATTENDEE.ACTOR_TYPE.GROUPS
const p2IsGroup = participant2.actorType === ATTENDEE.ACTOR_TYPE.GROUPS
if (p1IsGroup !== p2IsGroup) {
// Groups below participants
return p2IsGroup ? -1 : 1
}
let session1 = participant1.sessionId
let session2 = participant2.sessionId
if (participant1.status === 'offline') {

27
src/components/RightSidebar/Participants/ParticipantsList/Participant/Participant.vue

@ -49,7 +49,7 @@
class="participant-row__user-wrapper"
:class="{
'has-call-icon': callIcon,
'has-menu-icon': canModerate && !isSearched
'has-menu-icon': canBeModerated && !isSearched
}">
<div
ref="userName"
@ -95,9 +95,10 @@
decorative />
</div>
<Actions
v-if="canModerate && !isSearched"
v-if="canBeModerated && !isSearched"
container="#content-vue"
:aria-label="participantSettingsAriaLabel"
:force-menu="true"
class="participant-row__actions">
<ActionText
v-if="attendeePin"
@ -123,12 +124,18 @@
@click="resendInvitation">
{{ t('spreed', 'Resend invitation') }}
</ActionButton>
<ActionSeparator />
<ActionSeparator
v-if="attendeePin || canBePromoted || canBeDemoted || isEmailActor" />
<ActionButton
icon="icon-delete"
:close-after-click="true"
@click="removeParticipant">
{{ t('spreed', 'Remove participant') }}
<template v-if="isGroup">
{{ t('spreed', 'Remove group and members') }}
</template>
<template v-else>
{{ t('spreed', 'Remove participant') }}
</template>
</ActionButton>
</Actions>
<div v-if="isSelected" class="icon-checkmark participant-row__utils utils__checkmark" />
@ -380,6 +387,9 @@ export default {
isGuest() {
return [PARTICIPANT.TYPE.GUEST, PARTICIPANT.TYPE.GUEST_MODERATOR].indexOf(this.participantType) !== -1
},
isGroup() {
return this.participant.actorType === ATTENDEE.ACTOR_TYPE.GROUPS
},
isModerator() {
return this.participantTypeIsModerator(this.participantType)
},
@ -387,15 +397,18 @@ export default {
return this.isModerator
&& [CONVERSATION.TYPE.ONE_TO_ONE, CONVERSATION.TYPE.CHANGELOG].indexOf(this.conversation.type) === -1
},
canModerate() {
canBeModerated() {
return this.participantType !== PARTICIPANT.TYPE.OWNER && !this.isSelf && this.selfIsModerator
},
canBeDemoted() {
return this.canModerate
return this.canBeModerated
&& [PARTICIPANT.TYPE.MODERATOR, PARTICIPANT.TYPE.GUEST_MODERATOR].indexOf(this.participantType) !== -1
&& !this.isGroup
},
canBePromoted() {
return this.canModerate && !this.isModerator
return this.canBeModerated
&& !this.isModerator
&& !this.isGroup
},
preloadedUserStatus() {
if (this.participant.hasOwnProperty('statusMessage')) {

1
src/constants.js

@ -51,6 +51,7 @@ export const ATTENDEE = {
USERS: 'users',
GUESTS: 'guests',
EMAILS: 'emails',
GROUPS: 'groups',
},
}
export const PARTICIPANT = {

40
tests/integration/features/bootstrap/FeatureContext.php

@ -776,6 +776,31 @@ class FeatureContext implements Context, SnippetAcceptingContext {
$this->assertStatusCode($this->response, $statusCode);
}
/**
* @Then /^user "([^"]*)" removes (user|group|email) "([^"]*)" from room "([^"]*)" with (\d+) \((v4)\)$/
*
* @param string $user
* @param string $actorType
* @param string $actorId
* @param string $identifier
* @param int $statusCode
* @param string $apiVersion
*/
public function userRemovesAttendeeFromRoom(string $user, string $actorType, string $actorId, string $identifier, int $statusCode, string$apiVersion): void {
if ($actorId === 'stranger') {
$attendeeId = 123456789;
} else {
$attendeeId = $this->getAttendeeId($actorType . 's', $actorId, $identifier, $statusCode === 200 ? $user : null);
}
$this->setCurrentUser($user);
$this->sendRequest(
'DELETE', '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier] . '/attendees',
new TableNode([['attendeeId', $attendeeId]])
);
$this->assertStatusCode($this->response, $statusCode);
}
/**
* @Then /^user "([^"]*)" deletes room "([^"]*)" with (\d+) \((v4)\)$/
*
@ -1825,6 +1850,21 @@ class FeatureContext implements Context, SnippetAcceptingContext {
$this->setCurrentUser($currentUser);
}
/**
* @When /^user "([^"]*)" is not member of group "([^"]*)"$/
* @param string $user
* @param string $group
*/
public function removeUserFromGroup($user, $group) {
$currentUser = $this->currentUser;
$this->setCurrentUser('admin');
$this->sendRequest('DELETE', "/cloud/users/$user/groups", [
'groupid' => $group,
]);
$this->assertStatusCode($this->response, 200);
$this->setCurrentUser($currentUser);
}
/*
* Requests
*/

2
tests/integration/features/command/create.feature

@ -52,6 +52,7 @@ Feature: create
And user "participant1" sees the following attendees in room "room1" with 200 (v4)
| actorType | actorId | participantType |
| users | participant1 | 1 |
| groups | group1 | 3 |
| users | participant2 | 3 |
Scenario: Create a public room for participant1 as owner group1 as users with password and readonly and listable
@ -67,6 +68,7 @@ Feature: create
And user "participant1" sees the following attendees in room "room1" with 200 (v4)
| actorType | actorId | participantType |
| users | participant1 | 1 |
| groups | group1 | 3 |
| users | participant2 | 3 |

1
tests/integration/features/conversation/add-participant.feature

@ -105,6 +105,7 @@ Feature: public
| actorType | actorId | participantType |
| users | participant1 | 1 |
| users | participant2 | 3 |
| groups | group1 | 3 |
| users | participant3 | 3 |
And user "participant3" is participant of the following rooms (v4)
| id | type | participantType |

264
tests/integration/features/conversation/group-participants.feature

@ -0,0 +1,264 @@
Feature: public
Background:
Given user "participant1" exists
Given user "participant2" exists
Given user "participant3" exists
And group "group1" exists
And group "group2" exists
And user "participant2" is member of group "group1"
Scenario: Owner invites a group
Given user "participant1" creates room "room" (v4)
| roomType | 3 |
| roomName | room |
And user "participant1" sees the following attendees in room "room" with 200 (v4)
| actorType | actorId | participantType |
| users | participant1 | 1 |
And user "participant1" adds group "group1" to room "room" with 200 (v4)
And user "participant1" sees the following attendees in room "room" with 200 (v4)
| actorType | actorId | participantType |
| users | participant1 | 1 |
| groups | group1 | 3 |
| users | participant2 | 3 |
Scenario: Owner start a chat with a group
Given user "participant1" creates room "room" (v4)
| roomType | 2 |
| source | group |
| invite |group1 |
And user "participant1" sees the following attendees in room "room" with 200 (v4)
| actorType | actorId | participantType |
| users | participant1 | 1 |
| groups | group1 | 3 |
| users | participant2 | 3 |
Scenario: User is added to a group which is a member of a chat
Given user "participant1" creates room "room" (v4)
| roomType | 2 |
| source | group |
| invite |group1 |
And user "participant1" sees the following attendees in room "room" with 200 (v4)
| actorType | actorId | participantType |
| users | participant1 | 1 |
| groups | group1 | 3 |
| users | participant2 | 3 |
And user "participant3" is member of group "group1"
And user "participant1" sees the following attendees in room "room" with 200 (v4)
| actorType | actorId | participantType |
| users | participant1 | 1 |
| groups | group1 | 3 |
| users | participant2 | 3 |
| users | participant3 | 3 |
Scenario: User is removed from a group which is a member of a chat
Given user "participant1" creates room "room" (v4)
| roomType | 2 |
| source | group |
| invite |group1 |
And user "participant1" sees the following attendees in room "room" with 200 (v4)
| actorType | actorId | participantType |
| users | participant1 | 1 |
| groups | group1 | 3 |
| users | participant2 | 3 |
And user "participant2" is not member of group "group1"
And user "participant1" sees the following attendees in room "room" with 200 (v4)
| actorType | actorId | participantType |
| users | participant1 | 1 |
| groups | group1 | 3 |
Scenario: User is removed from a group which is a member of a chat but has a second group
Given user "participant1" creates room "room" (v4)
| roomType | 2 |
| source | group |
| invite |group1 |
And user "participant2" is member of group "group2"
And user "participant1" sees the following attendees in room "room" with 200 (v4)
| actorType | actorId | participantType |
| users | participant1 | 1 |
| groups | group1 | 3 |
| users | participant2 | 3 |
And user "participant1" adds group "group2" to room "room" with 200 (v4)
And user "participant1" sees the following attendees in room "room" with 200 (v4)
| actorType | actorId | participantType |
| users | participant1 | 1 |
| groups | group1 | 3 |
| users | participant2 | 3 |
| groups | group2 | 3 |
And user "participant2" is not member of group "group1"
And user "participant1" sees the following attendees in room "room" with 200 (v4)
| actorType | actorId | participantType |
| users | participant1 | 1 |
| groups | group1 | 3 |
| users | participant2 | 3 |
| groups | group2 | 3 |
Scenario: User is not removed from a chat when one group is removed but has a second group
Given user "participant1" creates room "room" (v4)
| roomType | 2 |
| source | group |
| invite |group1 |
And user "participant2" is member of group "group2"
And user "participant1" sees the following attendees in room "room" with 200 (v4)
| actorType | actorId | participantType |
| users | participant1 | 1 |
| groups | group1 | 3 |
| users | participant2 | 3 |
And user "participant1" adds group "group2" to room "room" with 200 (v4)
And user "participant1" sees the following attendees in room "room" with 200 (v4)
| actorType | actorId | participantType |
| users | participant1 | 1 |
| groups | group1 | 3 |
| users | participant2 | 3 |
| groups | group2 | 3 |
And user "participant1" removes group "group1" from room "room" with 200 (v4)
And user "participant1" sees the following attendees in room "room" with 200 (v4)
| actorType | actorId | participantType |
| users | participant1 | 1 |
| users | participant2 | 3 |
| groups | group2 | 3 |
Scenario: User is removed from when their last group which is a member of a chat is removed
Given user "participant1" creates room "room" (v4)
| roomType | 2 |
| source | group |
| invite |group1 |
And user "participant1" sees the following attendees in room "room" with 200 (v4)
| actorType | actorId | participantType |
| users | participant1 | 1 |
| groups | group1 | 3 |
| users | participant2 | 3 |
And user "participant1" removes group "group1" from room "room" with 200 (v4)
And user "participant1" sees the following attendees in room "room" with 200 (v4)
| actorType | actorId | participantType |
| users | participant1 | 1 |
Scenario: Moderator is removed from a group which is a member of a chat but stays in the chat
Given user "participant1" creates room "room" (v4)
| roomType | 2 |
| source | group |
| invite |group1 |
And user "participant1" sees the following attendees in room "room" with 200 (v4)
| actorType | actorId | participantType |
| users | participant1 | 1 |
| groups | group1 | 3 |
| users | participant2 | 3 |
And user "participant1" promotes "participant2" in room "room" with 200 (v4)
And user "participant1" sees the following attendees in room "room" with 200 (v4)
| actorType | actorId | participantType |
| users | participant1 | 1 |
| groups | group1 | 3 |
| users | participant2 | 2 |
And user "participant2" is not member of group "group1"
And user "participant1" sees the following attendees in room "room" with 200 (v4)
| actorType | actorId | participantType |
| users | participant1 | 1 |
| groups | group1 | 3 |
| users | participant2 | 2 |
Scenario: Group of a moderator is removed from a chat but moderator stays in the chat
Given user "participant1" creates room "room" (v4)
| roomType | 2 |
| source | group |
| invite |group1 |
And user "participant1" promotes "participant2" in room "room" with 200 (v4)
And user "participant1" sees the following attendees in room "room" with 200 (v4)
| actorType | actorId | participantType |
| users | participant1 | 1 |
| groups | group1 | 3 |
| users | participant2 | 2 |
And user "participant1" removes group "group1" from room "room" with 200 (v4)
And user "participant1" sees the following attendees in room "room" with 200 (v4)
| actorType | actorId | participantType |
| users | participant1 | 1 |
| users | participant2 | 2 |
Scenario: User that was already a member has their group added to a chat
Given user "participant1" creates room "room" (v4)
| roomType | 3 |
| roomName | room |
And user "participant1" adds user "participant2" to room "room" with 200 (v4)
And user "participant1" sees the following attendees in room "room" with 200 (v4)
| actorType | actorId | participantType |
| users | participant1 | 1 |
| users | participant2 | 3 |
And user "participant1" adds group "group1" to room "room" with 200 (v4)
And user "participant1" sees the following attendees in room "room" with 200 (v4)
| actorType | actorId | participantType |
| users | participant1 | 1 |
| users | participant2 | 3 |
| groups | group1 | 3 |
Scenario: User that was self-joined has their group added to a chat
Given user "participant1" creates room "room" (v4)
| roomType | 3 |
| roomName | room |
And user "participant2" joins room "room" with 200 (v4)
And user "participant1" sees the following attendees in room "room" with 200 (v4)
| actorType | actorId | participantType |
| users | participant1 | 1 |
| users | participant2 | 5 |
And user "participant1" adds group "group1" to room "room" with 200 (v4)
And user "participant1" sees the following attendees in room "room" with 200 (v4)
| actorType | actorId | participantType |
| users | participant1 | 1 |
| users | participant2 | 3 |
| groups | group1 | 3 |
Scenario: User that was already a member is added to a group which is a member of a chat
Given user "participant1" creates room "room" (v4)
| roomType | 2 |
| source | group |
| invite |group1 |
And user "participant1" adds user "participant3" to room "room" with 200 (v4)
And user "participant1" sees the following attendees in room "room" with 200 (v4)
| actorType | actorId | participantType |
| users | participant1 | 1 |
| groups | group1 | 3 |
| users | participant2 | 3 |
| users | participant3 | 3 |
And user "participant3" is member of group "group1"
And user "participant1" sees the following attendees in room "room" with 200 (v4)
| actorType | actorId | participantType |
| users | participant1 | 1 |
| groups | group1 | 3 |
| users | participant2 | 3 |
| users | participant3 | 3 |
Scenario: User that was self-joined is added to a group which is a member of a chat
Given user "participant1" creates room "room" (v4)
| roomType | 3 |
| roomName | room |
And user "participant1" adds group "group1" to room "room" with 200 (v4)
And user "participant3" joins room "room" with 200 (v4)
And user "participant1" sees the following attendees in room "room" with 200 (v4)
| actorType | actorId | participantType |
| users | participant1 | 1 |
| groups | group1 | 3 |
| users | participant2 | 3 |
| users | participant3 | 5 |
And user "participant3" is member of group "group1"
And user "participant1" sees the following attendees in room "room" with 200 (v4)
| actorType | actorId | participantType |
| users | participant1 | 1 |
| groups | group1 | 3 |
| users | participant2 | 3 |
| users | participant3 | 3 |
Scenario: User that was already a member is removed from a group which is a member of a chat
# This might not be what most people desire but fixing this would mean we
# need to keep multiple records per user whether they were added manually before etc.
Given user "participant1" creates room "room" (v4)
| roomType | 3 |
| roomName | room |
And user "participant1" adds user "participant2" to room "room" with 200 (v4)
And user "participant1" adds group "group1" to room "room" with 200 (v4)
And user "participant1" sees the following attendees in room "room" with 200 (v4)
| actorType | actorId | participantType |
| users | participant1 | 1 |
| users | participant2 | 3 |
| groups | group1 | 3 |
And user "participant2" is not member of group "group1"
And user "participant1" sees the following attendees in room "room" with 200 (v4)
| actorType | actorId | participantType |
| users | participant1 | 1 |
| groups | group1 | 3 |
Loading…
Cancel
Save