You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

3537 lines
118 KiB

<?php
/**
* @author Joas Schilling <coding@schilljs.com>
* @author Thomas Müller <thomas.mueller@tmit.eu>
*
* @copyright Copyright (c) 2016, ownCloud, Inc.
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
require __DIR__ . '/../../vendor/autoload.php';
use Behat\Behat\Context\Context;
use Behat\Behat\Context\SnippetAcceptingContext;
use Behat\Behat\Hook\Scope\BeforeScenarioScope;
use Behat\Gherkin\Node\TableNode;
use GuzzleHttp\Client;
use GuzzleHttp\Cookie\CookieJar;
use GuzzleHttp\Exception\ClientException;
use PHPUnit\Framework\Assert;
use Psr\Http\Message\ResponseInterface;
/**
* Defines application features from the specific context.
*/
class FeatureContext implements Context, SnippetAcceptingContext {
public const TEST_PASSWORD = '123456';
/** @var string[] */
protected static $identifierToToken;
/** @var string[] */
protected static $identifierToId;
/** @var string[] */
protected static $tokenToIdentifier;
/** @var array[] */
protected static $identifierToAvatar;
/** @var string[] */
protected static $sessionIdToUser;
/** @var string[] */
protected static $userToSessionId;
/** @var int[] */
protected static $userToAttendeeId;
/** @var string[] */
protected static $messages;
protected static $textToMessageId;
/** @var array[] */
protected static $messageIdToText;
/** @var int[] */
protected static $remoteToInviteId;
/** @var string[] */
protected static $inviteIdToRemote;
/** @var int[] */
protected static $questionToPollId;
/** @var array[] */
protected static $lastNotifications;
protected static $permissionsMap = [
'D' => 0, // PERMISSIONS_DEFAULT
'C' => 1, // PERMISSIONS_CUSTOM
'S' => 2, // PERMISSIONS_CALL_START
'J' => 4, // PERMISSIONS_CALL_JOIN
'L' => 8, // PERMISSIONS_LOBBY_IGNORE
'A' => 16, // PERMISSIONS_PUBLISH_AUDIO
'V' => 32, // PERMISSIONS_PUBLISH_VIDEO
'P' => 64, // PERMISSIONS_PUBLISH_SCREEN
'M' => 128, // PERMISSIONS_CHAT
];
/** @var string */
protected $currentUser;
/** @var ResponseInterface */
private $response;
/** @var CookieJar[] */
private $cookieJars;
/** @var string */
protected $baseUrl;
/** @var string */
protected $baseRemoteUrl;
/** @var array */
protected $createdUsers = [];
/** @var array */
protected $createdGroups = [];
/** @var array */
protected $createdGuestAccountUsers = [];
/** @var array */
protected $changedConfigs = [];
/** @var SharingContext */
private $sharingContext;
/** @var null|bool */
private $guestsAppWasEnabled = null;
/** @var string */
private $guestsOldWhitelist;
use CommandLineTrait;
use RecordingTrait;
public static function getTokenForIdentifier(string $identifier) {
return self::$identifierToToken[$identifier];
}
public function getAttendeeId(string $type, string $id, string $room, string $user = null) {
if (!isset(self::$userToAttendeeId[$room][$type][$id])) {
if ($user !== null) {
$this->userLoadsAttendeeIdsInRoom($user, $room, 'v4');
} else {
throw new \Exception('Attendee id unknown, please call userLoadsAttendeeIdsInRoom with a user that has access before');
}
}
if (!isset(self::$userToAttendeeId[$room][$type][$id])) {
throw new \Exception('Attendee id unknown, please call userLoadsAttendeeIdsInRoom with a user that has access before');
}
return self::$userToAttendeeId[$room][$type][$id];
}
/**
* FeatureContext constructor.
*/
public function __construct() {
$this->cookieJars = [];
$this->baseUrl = getenv('TEST_SERVER_URL');
$this->baseRemoteUrl = getenv('TEST_REMOTE_URL');
$this->guestsAppWasEnabled = null;
}
/**
* @BeforeScenario
*/
public function setUp() {
self::$identifierToToken = [];
self::$identifierToId = [];
self::$tokenToIdentifier = [];
self::$sessionIdToUser = [
'cli' => 'cli',
'failed-to-get-session' => 'failed-to-get-session',
];
self::$userToSessionId = [];
self::$userToAttendeeId = [];
self::$textToMessageId = [];
self::$messageIdToText = [];
self::$questionToPollId = [];
self::$lastNotifications = [];
$this->createdUsers = [];
$this->createdGroups = [];
$this->createdGuestAccountUsers = [];
}
/**
* @BeforeScenario
*/
public function getOtherRequiredSiblingContexts(BeforeScenarioScope $scope) {
$environment = $scope->getEnvironment();
$this->sharingContext = $environment->getContext("SharingContext");
}
/**
* @AfterScenario
*/
public function tearDown() {
foreach ($this->createdUsers as $user) {
$this->deleteUser($user);
}
foreach ($this->createdGroups as $group) {
$this->deleteGroup($group);
}
foreach ($this->createdGuestAccountUsers as $user) {
$this->deleteUser($user);
}
}
/**
* @Then /^user "([^"]*)" cannot find any listed rooms \((v4)\)$/
*
* @param string $user
* @param string $apiVersion
*/
public function userCannotFindAnyListedRooms(string $user, string $apiVersion): void {
$this->userCanFindListedRoomsWithTerm($user, '', $apiVersion, null);
}
/**
* @Then /^user "([^"]*)" cannot find any listed rooms with (\d+) \((v4)\)$/
*
* @param string $user
* @param int $statusCode
* @param string $apiVersion
*/
public function userCannotFindAnyListedRoomsWithStatus(string $user, int $statusCode, string $apiVersion): void {
$this->setCurrentUser($user);
$this->sendRequest('GET', '/apps/spreed/api/' . $apiVersion . '/listed-room');
$this->assertStatusCode($this->response, $statusCode);
}
/**
* @Then /^user "([^"]*)" cannot find any listed rooms with term "([^"]*)" \((v4)\)$/
*
* @param string $user
* @param string $term
* @param string $apiVersion
*/
public function userCannotFindAnyListedRoomsWithTerm(string $user, string $term, string $apiVersion): void {
$this->userCanFindListedRoomsWithTerm($user, $term, $apiVersion, null);
}
/**
* @Then /^user "([^"]*)" can find listed rooms \((v4)\)$/
*
* @param string $user
* @param string $apiVersion
* @param TableNode|null $formData
*/
public function userCanFindListedRooms(string $user, string $apiVersion, TableNode $formData = null): void {
$this->userCanFindListedRoomsWithTerm($user, '', $apiVersion, $formData);
}
/**
* @Then /^user "([^"]*)" can find listed rooms with term "([^"]*)" \((v4)\)$/
*
* @param string $user
* @param string $term
* @param string $apiVersion
* @param TableNode|null $formData
*/
public function userCanFindListedRoomsWithTerm(string $user, string $term, string $apiVersion, TableNode $formData = null): void {
$this->setCurrentUser($user);
$suffix = '';
if ($term !== '') {
$suffix = '?searchTerm=' . \rawurlencode($term);
}
$this->sendRequest('GET', '/apps/spreed/api/' . $apiVersion . '/listed-room' . $suffix);
$this->assertStatusCode($this->response, 200);
$rooms = $this->getDataFromResponse($this->response);
if ($formData === null) {
Assert::assertEmpty($rooms);
return;
}
$this->assertRooms($rooms, $formData);
}
/**
* @Then /^user "([^"]*)" is participant of the following (unordered )?rooms \((v4)\)$/
*
* @param string $user
* @param string $shouldOrder
* @param string $apiVersion
* @param TableNode|null $formData
*/
public function userIsParticipantOfRooms(string $user, string $shouldOrder, string $apiVersion, TableNode $formData = null): void {
$this->setCurrentUser($user);
$this->sendRequest('GET', '/apps/spreed/api/' . $apiVersion . '/room');
$this->assertStatusCode($this->response, 200);
$rooms = $this->getDataFromResponse($this->response);
$rooms = array_filter($rooms, function ($room) {
return $room['type'] !== 4;
});
if ($formData === null) {
Assert::assertEmpty($rooms);
return;
}
$this->assertRooms($rooms, $formData, $shouldOrder !== '');
}
/**
* @Then /^user "([^"]*)" sees the following breakout rooms for room "([^"]*)" with (\d+) \((v4)\)$/
*
* @param string $user
* @param string $apiVersion
* @param int $status
* @param TableNode|null $formData
*/
public function userListsBreakoutRooms(string $user, string $identifier, int $status, string $apiVersion, TableNode $formData = null): void {
$token = self::$identifierToToken[$identifier];
$this->setCurrentUser($user);
$this->sendRequest('GET', '/apps/spreed/api/' . $apiVersion . '/room/' . $token . '/breakout-rooms');
$this->assertStatusCode($this->response, $status);
if ($status !== 200) {
return;
}
$rooms = $this->getDataFromResponse($this->response);
$rooms = array_filter($rooms, function ($room) {
return $room['type'] !== 4;
});
if ($formData === null) {
Assert::assertEmpty($rooms);
return;
}
$this->assertRooms($rooms, $formData, true);
}
/**
* @param array $rooms
* @param TableNode $formData
*/
private function assertRooms($rooms, TableNode $formData, bool $shouldOrder = false) {
Assert::assertCount(count($formData->getHash()), $rooms, 'Room count does not match');
$expected = $formData->getHash();
if ($shouldOrder) {
$sorter = static function (array $roomA, array $roomB): int {
if (str_starts_with($roomA['name'], '/')) {
return 1;
}
if (str_starts_with($roomB['name'], '/')) {
return -1;
}
$idA = $roomA['id'] ?? self::$identifierToId[$roomA['name']];
$idB = $roomB['id'] ?? self::$identifierToId[$roomB['name']];
if (isset(self::$identifierToId[$idA])) {
$idA = self::$identifierToId[$idA];
} else {
self::$identifierToId[$roomA['name']] = $idA;
}
if (isset(self::$identifierToId[$idB])) {
$idB = self::$identifierToId[$idB];
} else {
self::$identifierToId[$roomB['name']] = $idB;
}
return $idA < $idB ? -1 : 1;
};
usort($rooms, $sorter);
usort($expected, $sorter);
}
Assert::assertEquals($expected, array_map(function ($room, $expectedRoom) {
if (!isset(self::$identifierToToken[$room['name']])) {
self::$identifierToToken[$room['name']] = $room['token'];
}
if (!isset(self::$tokenToIdentifier[$room['token']])) {
self::$tokenToIdentifier[$room['token']] = $room['name'];
}
$data = [];
if (isset($expectedRoom['id'])) {
$data['id'] = self::$tokenToIdentifier[$room['token']];
}
if (isset($expectedRoom['name'])) {
$data['name'] = $room['name'];
// Breakout room regex
if (strpos($expectedRoom['name'], '/') === 0 && preg_match($expectedRoom['name'], $room['name'])) {
$data['name'] = $expectedRoom['name'];
}
}
if (isset($expectedRoom['description'])) {
$data['description'] = $room['description'];
}
if (isset($expectedRoom['type'])) {
$data['type'] = (string) $room['type'];
}
if (isset($expectedRoom['hasPassword'])) {
$data['hasPassword'] = (string) $room['hasPassword'];
}
if (isset($expectedRoom['readOnly'])) {
$data['readOnly'] = (string) $room['readOnly'];
}
if (isset($expectedRoom['listable'])) {
$data['listable'] = (string) $room['listable'];
}
if (isset($expectedRoom['participantType'])) {
$data['participantType'] = (string) $room['participantType'];
}
if (isset($expectedRoom['sipEnabled'])) {
$data['sipEnabled'] = (string) $room['sipEnabled'];
}
if (isset($expectedRoom['callFlag'])) {
$data['callFlag'] = (int) $room['callFlag'];
}
if (isset($expectedRoom['lobbyState'])) {
$data['lobbyState'] = (int) $room['lobbyState'];
}
if (isset($expectedRoom['breakoutRoomMode'])) {
$data['breakoutRoomMode'] = (int) $room['breakoutRoomMode'];
}
if (isset($expectedRoom['breakoutRoomStatus'])) {
$data['breakoutRoomStatus'] = (int) $room['breakoutRoomStatus'];
}
if (isset($expectedRoom['attendeePin'])) {
$data['attendeePin'] = $room['attendeePin'] ? '**PIN**' : '';
}
if (isset($expectedRoom['lastMessage'])) {
$data['lastMessage'] = $room['lastMessage'] ? $room['lastMessage']['message'] : '';
}
if (isset($expectedRoom['unreadMessages'])) {
$data['unreadMessages'] = (int) $room['unreadMessages'];
}
if (isset($expectedRoom['unreadMention'])) {
$data['unreadMention'] = (int) $room['unreadMention'];
}
if (isset($expectedRoom['unreadMentionDirect'])) {
$data['unreadMentionDirect'] = (int) $room['unreadMentionDirect'];
}
if (isset($expectedRoom['messageExpiration'])) {
$data['messageExpiration'] = (int) $room['messageExpiration'];
}
if (isset($expectedRoom['callRecording'])) {
$data['callRecording'] = (int) $room['callRecording'];
}
if (isset($expectedRoom['participants'])) {
throw new \Exception('participants key needs to be checked via participants endpoint');
}
return $data;
}, $rooms, $formData->getHash()));
}
/**
* @Then /^user "([^"]*)" has the following invitations \((v1)\)$/
*
* @param string $user
* @param string $apiVersion
* @param TableNode|null $formData
*/
public function userHasInvites(string $user, string $apiVersion, TableNode $formData = null): void {
$this->setCurrentUser($user);
$this->sendRequest('GET', '/apps/spreed/api/' . $apiVersion . '/federation/invitation');
$this->assertStatusCode($this->response, 200);
$invites = $this->getDataFromResponse($this->response);
if ($formData === null) {
Assert::assertEmpty($invites);
return;
}
$this->assertInvites($invites, $formData);
foreach ($invites as $data) {
self::$remoteToInviteId[$this->translateRemoteServer($data['remote_server']) . '::' . self::$tokenToIdentifier[$data['remote_token']]] = $data['id'];
self::$inviteIdToRemote[$data['id']] = $this->translateRemoteServer($data['remote_server']) . '::' . self::$tokenToIdentifier[$data['remote_token']];
}
}
/**
* @Then /^user "([^"]*)" (accepts|declines) invite to room "([^"]*)" of server "([^"]*)" \((v1)\)$/
*
* @param string $user
* @param string $roomName
* @param string $server
* @param string $apiVersion
* @param TableNode|null $formData
*/
public function userAcceptsDeclinesRemoteInvite(string $user, string $acceptsDeclines, string $roomName, string $server, string $apiVersion, TableNode $formData = null): void {
$inviteId = self::$remoteToInviteId[$server . '::' . $roomName];
$verb = $acceptsDeclines === 'accepts' ? 'POST' : 'DELETE';
$this->setCurrentUser($user);
if ($server === 'LOCAL') {
$this->sendRemoteRequest($verb, '/apps/spreed/api/' . $apiVersion . '/federation/invitation/' . $inviteId);
}
$this->assertStatusCode($this->response, 200);
}
/**
* @param array $invites
* @param TableNode $formData
*/
private function assertInvites($invites, TableNode $formData) {
Assert::assertCount(count($formData->getHash()), $invites, 'Invite count does not match');
Assert::assertEquals($formData->getHash(), array_map(function ($invite, $expectedInvite) {
$data = [];
if (isset($expectedInvite['id'])) {
$data['id'] = self::$tokenToIdentifier[$invite['token']];
}
if (isset($expectedInvite['access_token'])) {
$data['access_token'] = (string) $invite['access_token'];
}
if (isset($expectedInvite['remote_token'])) {
$data['remote_token'] = self::$tokenToIdentifier[$invite['remote_token']] ?? 'unknown-token';
}
if (isset($expectedInvite['remote_server'])) {
$data['remote_server'] = $this->translateRemoteServer($invite['remote_server']);
}
return $data;
}, $invites, $formData->getHash()));
}
protected function translateRemoteServer(string $server): string {
$server = str_replace('http://', '', $server);
if ($server === 'localhost:8080') {
return 'LOCAL';
}
if ($server === 'localhost:8180') {
return 'REMOTE';
}
return 'unknown-server';
}
/**
* @Then /^user "([^"]*)" (is|is not) participant of room "([^"]*)" \((v4)\)$/
*
* @param string $user
* @param string $isOrNotParticipant
* @param string $identifier
* @param string $apiVersion
* @param TableNode|null $formData
*/
public function userIsParticipantOfRoom(string $user, string $isOrNotParticipant, string $identifier, string $apiVersion, TableNode $formData = null): void {
if (strpos($user, 'guest') === 0) {
$this->guestIsParticipantOfRoom($user, $isOrNotParticipant, $identifier, $apiVersion, $formData);
return;
}
$this->setCurrentUser($user);
$this->sendRequest('GET', '/apps/spreed/api/' . $apiVersion . '/room');
$this->assertStatusCode($this->response, 200);
$isParticipant = $isOrNotParticipant === 'is';
$rooms = $this->getDataFromResponse($this->response);
$rooms = array_filter($rooms, function ($room) {
return $room['type'] !== 4;
});
if ($isParticipant) {
Assert::assertNotEmpty($rooms);
}
foreach ($rooms as $room) {
if (self::$tokenToIdentifier[$room['token']] === $identifier) {
Assert::assertEquals($isParticipant, true, 'Room ' . $identifier . ' found in user´s room list');
if ($formData) {
$this->assertRooms([$room], $formData);
}
return;
}
}
Assert::assertEquals($isParticipant, false, 'Room ' . $identifier . ' not found in user´s room list');
}
/**
* @Then /^user "([^"]*)" sees the following attendees in room "([^"]*)" with (\d+) \((v4)\)$/
*
* @param string $user
* @param string $identifier
* @param int $statusCode
* @param string $apiVersion
* @param TableNode $formData
*/
public function userSeesAttendeesInRoom(string $user, string $identifier, int $statusCode, string $apiVersion, TableNode $formData = null): void {
$this->setCurrentUser($user);
$this->sendRequest('GET', '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier] . '/participants');
$this->assertStatusCode($this->response, $statusCode);
if ($formData instanceof TableNode) {
$attendees = $this->getDataFromResponse($this->response);
} else {
$attendees = [];
}
$this->assertAttendeeList($identifier, $formData, $attendees);
}
/**
* @Then /^user "([^"]*)" sees the following attendees in breakout rooms for room "([^"]*)" with (\d+) \((v4)\)$/
*
* @param string $user
* @param string $identifier
* @param int $statusCode
* @param string $apiVersion
* @param TableNode $formData
*/
public function userSeesAttendeesInBreakoutRoomsForRoom(string $user, string $identifier, int $statusCode, string $apiVersion, TableNode $formData = null): void {
$this->setCurrentUser($user);
$this->sendRequest('GET', '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier] . '/breakout-rooms/participants');
$this->assertStatusCode($this->response, $statusCode);
if ($formData instanceof TableNode) {
$attendees = $this->getDataFromResponse($this->response);
} else {
$attendees = [];
}
$this->assertAttendeeList($identifier, $formData, $attendees);
}
protected function assertAttendeeList(string $identifier, ?TableNode $formData, array $attendees): void {
if ($formData instanceof TableNode) {
$expectedKeys = array_flip($formData->getRows()[0]);
$result = [];
foreach ($attendees as $attendee) {
$data = [];
if (isset($expectedKeys['roomToken'])) {
$data['roomToken'] = self::$tokenToIdentifier[$attendee['roomToken']];
}
if (isset($expectedKeys['actorType'])) {
$data['actorType'] = $attendee['actorType'];
}
if (isset($expectedKeys['actorId'])) {
$data['actorId'] = $attendee['actorId'];
}
if (isset($expectedKeys['participantType'])) {
$data['participantType'] = (string) $attendee['participantType'];
}
if (isset($expectedKeys['inCall'])) {
$data['inCall'] = (string) $attendee['inCall'];
}
if (isset($expectedKeys['attendeePin'])) {
$data['attendeePin'] = $attendee['attendeePin'] ? '**PIN**' : '';
}
if (isset($expectedKeys['permissions'])) {
$data['permissions'] = (string) $attendee['permissions'];
}
if (isset($expectedKeys['attendeePermissions'])) {
$data['attendeePermissions'] = (string) $attendee['attendeePermissions'];
}
if (!isset(self::$userToAttendeeId[$identifier][$attendee['actorType']])) {
self::$userToAttendeeId[$identifier][$attendee['actorType']] = [];
}
self::$userToAttendeeId[$identifier][$attendee['actorType']][$attendee['actorId']] = $attendee['attendeeId'];
$result[] = $data;
}
$expected = array_map(function ($attendee, $actual) {
if (isset($attendee['actorId']) && substr($attendee['actorId'], 0, strlen('"guest')) === '"guest') {
$attendee['actorId'] = sha1(self::$userToSessionId[trim($attendee['actorId'], '"')]);
}
if (isset($attendee['actorId'], $attendee['actorType']) && $attendee['actorType'] === 'federated_users') {
$attendee['actorId'] .= '@' . rtrim($this->baseRemoteUrl, '/');
}
// Breakout room regex
if (isset($attendee['actorId']) && strpos($attendee['actorId'], '/') === 0 && preg_match($attendee['actorId'], $actual['actorId'])) {
$attendee['actorId'] = $actual['actorId'];
}
if (isset($attendee['participantType'])) {
$attendee['participantType'] = (string)$this->mapParticipantTypeTestInput($attendee['participantType']);
}
return $attendee;
}, $formData->getHash(), $result);
$result = array_map(function ($attendee) {
if (isset($attendee['permissions'])) {
$attendee['permissions'] = $this->mapPermissionsAPIOutput($attendee['permissions']);
}
if (isset($attendee['attendeePermissions'])) {
$attendee['attendeePermissions'] = $this->mapPermissionsAPIOutput($attendee['attendeePermissions']);
}
return $attendee;
}, $result);
usort($expected, [self::class, 'sortAttendees']);
usort($result, [self::class, 'sortAttendees']);
Assert::assertEquals($expected, $result);
} else {
Assert::assertNull($formData);
}
}
/**
* @Then /^user "([^"]*)" loads attendees attendee ids in room "([^"]*)" \((v4)\)$/
*
* @param string $user
* @param string $identifier
* @param string $apiVersion
*/
public function userLoadsAttendeeIdsInRoom(string $user, string $identifier, string $apiVersion, TableNode $formData = null): void {
$this->setCurrentUser($user);
$this->sendRequest('GET', '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier] . '/participants');
$this->assertStatusCode($this->response, 200);
$attendees = $this->getDataFromResponse($this->response);
foreach ($attendees as $attendee) {
if (!isset(self::$userToAttendeeId[$identifier][$attendee['actorType']])) {
self::$userToAttendeeId[$identifier][$attendee['actorType']] = [];
}
self::$userToAttendeeId[$identifier][$attendee['actorType']][$attendee['actorId']] = $attendee['attendeeId'];
}
}
protected static function sortAttendees(array $a1, array $a2): int {
if (array_key_exists('roomToken', $a1) && array_key_exists('roomToken', $a2) && $a1['roomToken'] !== $a2['roomToken']) {
return $a1['roomToken'] <=> $a2['roomToken'];
}
if (array_key_exists('participantType', $a1) && array_key_exists('participantType', $a2) && $a1['participantType'] !== $a2['participantType']) {
return $a1['participantType'] <=> $a2['participantType'];
}
if ($a1['actorType'] !== $a2['actorType']) {
return $a1['actorType'] <=> $a2['actorType'];
}
return $a1['actorId'] <=> $a2['actorId'];
}
private function mapParticipantTypeTestInput($participantType) {
if (is_numeric($participantType)) {
return $participantType;
}
switch ($participantType) {
case 'OWNER': return 1;
case 'MODERATOR': return 2;
case 'USER': return 3;
case 'GUEST': return 4;
case 'USER_SELF_JOINED': return 5;
case 'GUEST_MODERATOR': return 6;
}
Assert::fail('Invalid test input value for participant type');
}
private function mapPermissionsTestInput($permissions): int {
if (is_numeric($permissions)) {
return $permissions;
}
$numericPermissions = 0;
foreach (self::$permissionsMap as $char => $int) {
if (strpos($permissions, $char) !== false) {
$numericPermissions += $int;
$permissions = str_replace($char, '', $permissions);
}
}
if (trim($permissions) !== '') {
Assert::fail('Invalid test input value for permissions');
}
return $numericPermissions;
}
private function mapPermissionsAPIOutput($permissions): string {
$permissions = (int) $permissions;
$permissionsString = !$permissions ? 'D' : '';
foreach (self::$permissionsMap as $char => $int) {
if ($permissions & $int) {
$permissionsString .= $char;
$permissions &= ~ $int;
}
}
if ($permissions !== 0) {
Assert::fail('Invalid API output value for permissions');
}
return $permissionsString;
}
/**
* @param string $guest
* @param string $isOrNotParticipant
* @param string $identifier
* @param string $apiVersion
* @param TableNode|null $formData
*/
private function guestIsParticipantOfRoom(string $guest, string $isOrNotParticipant, string $identifier, string $apiVersion, TableNode $formData = null): void {
$this->setCurrentUser($guest);
$this->sendRequest('GET', '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier]);
$response = $this->getDataFromResponse($this->response);
$isParticipant = $isOrNotParticipant === 'is';
if ($formData) {
$rooms = [$response];
$this->assertRooms($rooms, $formData);
}
if ($isParticipant) {
$this->assertStatusCode($this->response, 200);
Assert::assertEquals(self::$userToSessionId[$guest], $response['sessionId']);
return;
}
if ($this->response->getStatusCode() === 200) {
// Public rooms can always be got, but if the guest is not a
// participant the sessionId will be 0.
Assert::assertEquals(0, $response['sessionId']);
return;
}
$this->assertStatusCode($this->response, 404);
}
/**
* @Then /^user "([^"]*)" creates room "([^"]*)" \((v4)\)$/
*
* @param string $user
* @param string $identifier
* @param string $apiVersion
* @param TableNode|null $formData
*/
public function userCreatesRoom(string $user, string $identifier, string $apiVersion, TableNode $formData = null): void {
$this->userCreatesRoomWith($user, $identifier, 201, $apiVersion, $formData);
}
/**
* @Then /^user "([^"]*)" creates room "([^"]*)" with (\d+) \((v4)\)$/
*
* @param string $user
* @param string $identifier
* @param int $statusCode
* @param string $apiVersion
* @param TableNode|null $formData
*/
public function userCreatesRoomWith(string $user, string $identifier, int $statusCode, string $apiVersion = 'v1', TableNode $formData = null): void {
$body = $formData->getRowsHash();
if (isset($body['objectType'], $body['objectId']) && $body['objectType'] === 'room') {
$result = preg_match('/ROOM\(([^)]+)\)/', $body['objectId'], $matches);
if ($result && isset(self::$identifierToToken[$matches[1]])) {
$body['objectId'] = self::$identifierToToken[$matches[1]];
} elseif ($result) {
throw new \InvalidArgumentException('Could not find parent room');
}
}
$this->setCurrentUser($user);
$this->sendRequest('POST', '/apps/spreed/api/' . $apiVersion . '/room', $body);
$this->assertStatusCode($this->response, $statusCode);
$response = $this->getDataFromResponse($this->response);
if ($statusCode === 201) {
self::$identifierToToken[$identifier] = $response['token'];
self::$identifierToId[$identifier] = $response['id'];
self::$tokenToIdentifier[$response['token']] = $identifier;
}
}
/**
* @Then /^user "([^"]*)" tries to create room with (\d+) \((v4)\)$/
*
* @param string $user
* @param int $statusCode
* @param string $apiVersion
* @param TableNode|null $formData
*/
public function userTriesToCreateRoom(string $user, int $statusCode, string $apiVersion = 'v1', TableNode $formData = null): void {
$this->setCurrentUser($user);
$this->sendRequest('POST', '/apps/spreed/api/' . $apiVersion . '/room', $formData);
$this->assertStatusCode($this->response, $statusCode);
}
/**
* @Then /^user "([^"]*)" gets the room for path "([^"]*)" with (\d+) \((v1)\)$/
*
* @param string $user
* @param string $path
* @param int $statusCode
* @param string $apiVersion
*/
public function userGetsTheRoomForPath(string $user, string $path, int $statusCode, string $apiVersion): void {
$fileId = $this->getFileIdForPath($user, $path);
$this->setCurrentUser($user);
$this->sendRequest('GET', '/apps/spreed/api/' . $apiVersion . '/file/' . $fileId);
$this->assertStatusCode($this->response, $statusCode);
if ($statusCode !== 200) {
return;
}
$response = $this->getDataFromResponse($this->response);
$identifier = 'file ' . $path . ' room';
self::$identifierToToken[$identifier] = $response['token'];
self::$tokenToIdentifier[$response['token']] = $identifier;
}
/**
* @param string $user
* @param string $path
* @return int
*/
private function getFileIdForPath($user, $path) {
$this->currentUser = $user;
$url = "/$user/$path";
$headers = [];
$headers['Depth'] = 0;
$body = '<d:propfind xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns">' .
' <d:prop>' .
' <oc:fileid/>' .
' </d:prop>' .
'</d:propfind>';
$this->sendingToDav('PROPFIND', $url, $headers, $body);
$this->assertStatusCode($this->response, 207);
$xmlResponse = simplexml_load_string($this->response->getBody());
$xmlResponse->registerXPathNamespace('oc', 'http://owncloud.org/ns');
return (int)$xmlResponse->xpath('//oc:fileid')[0];
}
/**
* @param string $verb
* @param string $url
* @param array $headers
* @param string $body
*/
private function sendingToDav(string $verb, string $url, array $headers = null, string $body = null) {
$fullUrl = $this->baseUrl . 'remote.php/dav/files' . $url;
$client = new Client();
$options = [];
if ($this->currentUser === 'admin') {
$options['auth'] = 'admin';
} else {
$options['auth'] = [$this->currentUser, self::TEST_PASSWORD];
}
$options['headers'] = [
'OCS_APIREQUEST' => 'true'
];
if ($headers !== null) {
$options['headers'] = array_merge($options['headers'], $headers);
}
if ($body !== null) {
$options['body'] = $body;
}
try {
$this->response = $client->{$verb}($fullUrl, $options);
} catch (GuzzleHttp\Exception\ClientException $ex) {
$this->response = $ex->getResponse();
}
}
/**
* @Then /^user "([^"]*)" gets the room for last share with (\d+) \((v1)\)$/
*
* @param string $user
* @param int $statusCode
* @param string $apiVersion
*/
public function userGetsTheRoomForLastShare(string $user, int $statusCode, string $apiVersion): void {
$shareToken = $this->sharingContext->getLastShareToken();
$this->setCurrentUser($user);
$this->sendRequest('GET', '/apps/spreed/api/' . $apiVersion . '/publicshare/' . $shareToken);
$this->assertStatusCode($this->response, $statusCode);
if ($statusCode !== 200) {
return;
}
$response = $this->getDataFromResponse($this->response);
$identifier = 'file last share room';
self::$identifierToToken[$identifier] = $response['token'];
self::$tokenToIdentifier[$response['token']] = $identifier;
}
/**
* @Then /^user "([^"]*)" creates the password request room for last share with (\d+) \((v1)\)$/
*
* @param string $user
* @param int $statusCode
* @param string $apiVersion
*/
public function userCreatesThePasswordRequestRoomForLastShare(string $user, int $statusCode, string $apiVersion): void {
$shareToken = $this->sharingContext->getLastShareToken();
$this->setCurrentUser($user);
$this->sendRequest('POST', '/apps/spreed/api/' . $apiVersion . '/publicshareauth', ['shareToken' => $shareToken]);
$this->assertStatusCode($this->response, $statusCode);
if ($statusCode !== 201) {
return;
}
$response = $this->getDataFromResponse($this->response);
$identifier = 'password request for last share room';
self::$identifierToToken[$identifier] = $response['token'];
self::$tokenToIdentifier[$response['token']] = $identifier;
}
/**
* @Then /^user "([^"]*)" joins room "([^"]*)" with (\d+) \((v4)\)$/
*
* @param string $user
* @param string $identifier
* @param int $statusCode
* @param string $apiVersion
* @param TableNode|null $formData
*/
public function userJoinsRoom(string $user, string $identifier, int $statusCode, string $apiVersion, TableNode $formData = null): void {
$this->setCurrentUser($user);
$this->sendRequest(
'POST', '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier] . '/participants/active',
$formData
);
$this->assertStatusCode($this->response, $statusCode);
if ($statusCode !== 200) {
return;
}
$response = $this->getDataFromResponse($this->response);
if (array_key_exists('sessionId', $response)) {
// In the chat guest users are identified by their sessionId. The
// sessionId is larger than the size of the actorId column in the
// database, though, so the ID stored in the database and returned
// in chat messages is a hashed version instead.
self::$sessionIdToUser[sha1($response['sessionId'])] = $user;
self::$userToSessionId[$user] = $response['sessionId'];
if (!isset(self::$userToAttendeeId[$identifier][$response['actorType']])) {
self::$userToAttendeeId[$identifier][$response['actorType']] = [];
}
self::$userToAttendeeId[$identifier][$response['actorType']][$response['actorId']] = $response['attendeeId'];
}
}
/**
* @Then /^user "([^"]*)" sets notifications to (default|disabled|mention|all) for room "([^"]*)" \((v4)\)$/
*
* @param string $user
* @param string $level
* @param string $identifier
* @param string $apiVersion
*/
public function userSetsNotificationLevelForRoom(string $user, string $level, string $identifier, string $apiVersion): void {
$this->setCurrentUser($user);
$intLevel = 0; // default
if ($level === 'disabled') {
$intLevel = 3;
} elseif ($level === 'mention') {
$intLevel = 2;
} elseif ($level === 'all') {
$intLevel = 1;
}
$this->sendRequest(
'POST', '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier] . '/notify',
new TableNode([
['level', $intLevel],
])
);
$this->assertStatusCode($this->response, 200);
}
/**
* @Then /^user "([^"]*)" leaves room "([^"]*)" with (\d+) \((v4)\)$/
*
* @param string $user
* @param string $identifier
* @param int $statusCode
* @param string $apiVersion
*/
public function userExitsRoom(string $user, string $identifier, int $statusCode, string $apiVersion): void {
$this->setCurrentUser($user);
$this->sendRequest('DELETE', '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier] . '/participants/active');
$this->assertStatusCode($this->response, $statusCode);
}
/**
* @Then /^user "([^"]*)" removes themselves from room "([^"]*)" with (\d+) \((v4)\)$/
*
* @param string $user
* @param string $identifier
* @param int $statusCode
* @param string $apiVersion
*/
public function userLeavesRoom(string $user, string $identifier, int $statusCode, string $apiVersion): void {
$this->setCurrentUser($user);
$this->sendRequest('DELETE', '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier] . '/participants/self');
$this->assertStatusCode($this->response, $statusCode);
}
/**
* @Then /^user "([^"]*)" removes "([^"]*)" from room "([^"]*)" with (\d+) \((v4)\)$/
*
* @param string $user
* @param string $toRemove
* @param string $identifier
* @param int $statusCode
* @param string $apiVersion
*/
public function userRemovesUserFromRoom(string $user, string $toRemove, string $identifier, int $statusCode, string$apiVersion): void {
if ($toRemove === 'stranger') {
$attendeeId = 123456789;
} else {
$attendeeId = $this->getAttendeeId('users', $toRemove, $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 "([^"]*)" 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)\)$/
*
* @param string $user
* @param string $identifier
* @param int $statusCode
* @param string $apiVersion
*/
public function userDeletesRoom(string $user, string $identifier, int $statusCode, string $apiVersion): void {
$this->setCurrentUser($user);
$this->sendRequest('DELETE', '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier]);
$this->assertStatusCode($this->response, $statusCode);
}
/**
* @Then /^user "([^"]*)" gets room "([^"]*)" with (\d+) \((v4)\)$/
*
* @param string $user
* @param string $identifier
* @param int $statusCode
* @param string $apiVersion
*/
public function userGetsRoom(string $user, string $identifier, int $statusCode, string $apiVersion = 'v4', TableNode $formData = null): void {
$this->setCurrentUser($user);
$this->sendRequest('GET', '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier]);
$this->assertStatusCode($this->response, $statusCode);
if ($formData instanceof TableNode) {
$xpectedAttributes = $formData->getRowsHash();
$actual = $this->getDataFromResponse($this->response);
foreach ($xpectedAttributes as $attribute => $expectedValue) {
if ($expectedValue === 'NOT_EMPTY') {
Assert::assertNotEmpty($actual[$attribute]);
continue;
}
Assert::assertEquals($expectedValue, $actual[$attribute]);
}
}
}
/**
* @Then /^user "([^"]*)" renames room "([^"]*)" to "([^"]*)" with (\d+) \((v4)\)$/
*
* @param string $user
* @param string $identifier
* @param string $newName
* @param int $statusCode
* @param string $apiVersion
*/
public function userRenamesRoom(string $user, string $identifier, string $newName, int $statusCode, string $apiVersion): void {
$this->setCurrentUser($user);
$this->sendRequest(
'PUT', '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier],
new TableNode([['roomName', $newName]])
);
$this->assertStatusCode($this->response, $statusCode);
}
/**
* @When /^user "([^"]*)" sets description for room "([^"]*)" to "([^"]*)" with (\d+) \((v4)\)$/
*
* @param string $user
* @param string $identifier
* @param string $description
* @param int $statusCode
* @param string $apiVersion
* @param TableNode
*/
public function userSetsDescriptionForRoomTo(string $user, string $identifier, string $description, int $statusCode, string $apiVersion): void {
$this->setCurrentUser($user);
$this->sendRequest(
'PUT', '/apps/spreed/api/' .$apiVersion . '/room/' . self::$identifierToToken[$identifier] . '/description',
new TableNode([['description', $description]])
);
$this->assertStatusCode($this->response, $statusCode);
}
/**
* @When /^user "([^"]*)" sets password "([^"]*)" for room "([^"]*)" with (\d+) \((v4)\)$/
*
* @param string $user
* @param string $password
* @param string $identifier
* @param int $statusCode
* @param string $apiVersion
*/
public function userSetsTheRoomPassword(string $user, string $password, string $identifier, int $statusCode, string $apiVersion): void {
$this->setCurrentUser($user);
$this->sendRequest(
'PUT', '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier] . '/password',
new TableNode([['password', $password]])
);
$this->assertStatusCode($this->response, $statusCode);
}
/**
* @When /^user "([^"]*)" sets lobby state for room "([^"]*)" to "([^"]*)" with (\d+) \((v4)\)$/
*
* @param string $user
* @param string $identifier
* @param string $lobbyStateString
* @param int $statusCode
* @param string $apiVersion
*/
public function userSetsLobbyStateForRoomTo(string $user, string $identifier, string $lobbyStateString, int $statusCode, string $apiVersion): void {
if ($lobbyStateString === 'no lobby') {
$lobbyState = 0;
} elseif ($lobbyStateString === 'non moderators') {
$lobbyState = 1;
} else {
Assert::fail('Invalid lobby state');
}
$this->setCurrentUser($user);
$this->sendRequest(
'PUT', '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier] . '/webinar/lobby',
new TableNode([['state', $lobbyState]])
);
$this->assertStatusCode($this->response, $statusCode);
}
/**
* @When /^user "([^"]*)" sets lobby state for room "([^"]*)" to "([^"]*)" for (\d+) seconds with (\d+) \((v4)\)$/
*
* @param string $user
* @param string $identifier
* @param string $lobbyStateString
* @param int $lobbyTimer
* @param int $statusCode
* @param string $apiVersion
*/
public function userSetsLobbyStateAndTimerForRoom(string $user, string $identifier, string $lobbyStateString, int $lobbyTimer, int $statusCode, string $apiVersion): void {
if ($lobbyStateString === 'no lobby') {
$lobbyState = 0;
} elseif ($lobbyStateString === 'non moderators') {
$lobbyState = 1;
} else {
Assert::fail('Invalid lobby state');
}
$this->setCurrentUser($user);
$this->sendRequest(
'PUT', '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier] . '/webinar/lobby',
new TableNode([['state', $lobbyState], ['timer', time() + $lobbyTimer]])
);
$this->assertStatusCode($this->response, $statusCode);
}
/**
* @When /^user "([^"]*)" sets SIP state for room "([^"]*)" to "([^"]*)" with (\d+) \((v4)\)$/
*
* @param string $user
* @param string $identifier
* @param string $SIPStateString
* @param int $statusCode
* @param string $apiVersion
*/
public function userSetsSIPStateForRoomTo(string $user, string $identifier, string $SIPStateString, int $statusCode, string $apiVersion): void {
if ($SIPStateString === 'disabled') {
$SIPState = 0;
} elseif ($SIPStateString === 'enabled') {
$SIPState = 1;
} elseif ($SIPStateString === 'no pin') {
$SIPState = 2;
} else {
Assert::fail('Invalid SIP state');
}
$this->setCurrentUser($user);
$this->sendRequest(
'PUT', '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier] . '/webinar/sip',
new TableNode([['state', $SIPState]])
);
$this->assertStatusCode($this->response, $statusCode);
}
/**
* @Then /^user "([^"]*)" makes room "([^"]*)" (public|private) with (\d+) \((v4)\)$/
*
* @param string $user
* @param string $identifier
* @param string $newType
* @param int $statusCode
* @param string $apiVersion
*/
public function userChangesTypeOfTheRoom(string $user, string $identifier, string $newType, int $statusCode, string $apiVersion): void {
$this->setCurrentUser($user);
$this->sendRequest(
$newType === 'public' ? 'POST' : 'DELETE',
'/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier] . '/public'
);
$this->assertStatusCode($this->response, $statusCode);
}
/**
* @Then /^user "([^"]*)" (locks|unlocks) room "([^"]*)" with (\d+) \((v4)\)$/
*
* @param string $user
* @param string $newState
* @param string $identifier
* @param int $statusCode
* @param string $apiVersion
*/
public function userChangesReadOnlyStateOfTheRoom(string $user, string $newState, string $identifier, int $statusCode, string $apiVersion): void {
$this->setCurrentUser($user);
$this->sendRequest(
'PUT', '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier] . '/read-only',
new TableNode([['state', $newState === 'unlocks' ? 0 : 1]])
);
$this->assertStatusCode($this->response, $statusCode);
}
/**
* @Then /^user "([^"]*)" allows listing room "([^"]*)" for "(none|users|all|\d+)" with (\d+) \((v4)\)$/
*
* @param string $user
* @param string $identifier
* @param string|int $newState
* @param int $statusCode
* @param string $apiVersion
*/
public function userChangesListableScopeOfTheRoom(string $user, string $identifier, $newState, int $statusCode, string $apiVersion): void {
$this->setCurrentUser($user);
if ($newState === 'none') {
$newStateValue = 0; // Room::LISTABLE_NONE
} elseif ($newState === 'users') {
$newStateValue = 1; // Room::LISTABLE_USERS
} elseif ($newState === 'all') {
$newStateValue = 2; // Room::LISTABLE_ALL
} elseif (is_numeric($newState)) {
$newStateValue = (int)$newState;
} else {
Assert::fail('Invalid listable scope value');
}
$this->sendRequest(
'PUT', '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier] . '/listable',
new TableNode([['scope', $newStateValue]])
);
$this->assertStatusCode($this->response, $statusCode);
}
/**
* @Then /^user "([^"]*)" adds (user|group|email|circle|remote) "([^"]*)" to room "([^"]*)" with (\d+) \((v4)\)$/
*
* @param string $user
* @param string $newType
* @param string $newId
* @param string $identifier
* @param int $statusCode
* @param string $apiVersion
*/
public function userAddAttendeeToRoom(string $user, string $newType, string $newId, string $identifier, int $statusCode, string $apiVersion): void {
$this->setCurrentUser($user);
if ($newType === 'remote') {
$newId .= '@' . $this->baseRemoteUrl;
}
$this->sendRequest(
'POST', '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier] . '/participants',
new TableNode([
['source', $newType . 's'],
['newParticipant', $newId],
])
);
$this->assertStatusCode($this->response, $statusCode);
}
/**
* @Then /^user "([^"]*)" (promotes|demotes) "([^"]*)" in room "([^"]*)" with (\d+) \((v4)\)$/
*
* @param string $user
* @param string $isPromotion
* @param string $participant
* @param string $identifier
* @param int $statusCode
* @param string $apiVersion
*/
public function userPromoteDemoteInRoom(string $user, string $isPromotion, string $participant, string $identifier, int $statusCode, string $apiVersion): void {
if ($participant === 'stranger') {
$attendeeId = 123456789;
} elseif (strpos($participant, 'guest') === 0) {
$sessionId = self::$userToSessionId[$participant];
$attendeeId = $this->getAttendeeId('guests', sha1($sessionId), $identifier, $statusCode === 200 ? $user : null);
} else {
$attendeeId = $this->getAttendeeId('users', $participant, $identifier, $statusCode === 200 ? $user : null);
}
$requestParameters = [['attendeeId', $attendeeId]];
$this->setCurrentUser($user);
$this->sendRequest(
$isPromotion === 'promotes' ? 'POST' : 'DELETE',
'/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier] . '/moderators',
new TableNode($requestParameters)
);
$this->assertStatusCode($this->response, $statusCode);
}
/**
* @When /^user "([^"]*)" sets permissions for "([^"]*)" in room "([^"]*)" to "([^"]*)" with (\d+) \((v4)\)$/
*
* @param string $user
* @param string $participant
* @param string $identifier
* @param string $permissionsString
* @param int $statusCode
* @param string $apiVersion
*/
public function userSetsPermissionsForInRoomTo(string $user, string $participant, string $identifier, string $permissionsString, int $statusCode, string $apiVersion): void {
if ($participant === 'stranger') {
$attendeeId = 123456789;
} elseif (strpos($participant, 'guest') === 0) {
$sessionId = self::$userToSessionId[$participant];
$attendeeId = $this->getAttendeeId('guests', sha1($sessionId), $identifier, $statusCode === 200 ? $user : null);
} else {
$attendeeId = $this->getAttendeeId('users', $participant, $identifier, $statusCode === 200 ? $user : null);
}
$permissions = $this->mapPermissionsTestInput($permissionsString);
$requestParameters = [
['attendeeId', $attendeeId],
['permissions', $permissions],
['method', 'set'],
];
$this->setCurrentUser($user);
$this->sendRequest(
'PUT', '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier] . '/attendees/permissions',
new TableNode($requestParameters)
);
$this->assertStatusCode($this->response, $statusCode);
}
/**
* @When /^user "([^"]*)" sets (call|default) permissions for room "([^"]*)" to "([^"]*)" with (\d+) \((v4)\)$/
*
* @param string $user
* @param string $mode
* @param string $identifier
* @param string $permissionsString
* @param int $statusCode
* @param string $apiVersion
*/
public function userSetsPermissionsForRoomTo(string $user, string $mode, string $identifier, string $permissionsString, int $statusCode, string $apiVersion): void {
$permissions = $this->mapPermissionsTestInput($permissionsString);
$requestParameters = [
['permissions', $permissions],
];
$this->setCurrentUser($user);
$this->sendRequest(
'PUT', '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier] . '/permissions/' . $mode,
new TableNode($requestParameters)
);
$this->assertStatusCode($this->response, $statusCode);
}
/**
* @Then /^user "([^"]*)" joins call "([^"]*)" with (\d+) \((v4)\)$/
*
* @param string $user
* @param string $identifier
* @param int $statusCode
* @param string $apiVersion
* @param TableNode|null $formData
*/
public function userJoinsCall(string $user, string $identifier, int $statusCode, string $apiVersion, TableNode $formData = null): void {
$this->setCurrentUser($user);
$this->sendRequest(
'POST', '/apps/spreed/api/' . $apiVersion . '/call/' . self::$identifierToToken[$identifier],
$formData
);
$this->assertStatusCode($this->response, $statusCode);
$response = $this->getDataFromResponse($this->response);
if (array_key_exists('sessionId', $response)) {
// In the chat guest users are identified by their sessionId. The
// sessionId is larger than the size of the actorId column in the
// database, though, so the ID stored in the database and returned
// in chat messages is a hashed version instead.
self::$sessionIdToUser[sha1($response['sessionId'])] = $user;
self::$userToSessionId[$user] = $response['sessionId'];
}
}
/**
* @Then /^user "([^"]*)" updates call flags in room "([^"]*)" to "([^"]*)" with (\d+) \((v4)\)$/
*
* @param string $user
* @param string $identifier
* @param string $flags
* @param int $statusCode
* @param string $apiVersion
*/
public function userUpdatesCallFlagsInRoomTo(string $user, string $identifier, string $flags, int $statusCode, string $apiVersion): void {
$this->setCurrentUser($user);
$this->sendRequest(
'PUT', '/apps/spreed/api/' . $apiVersion . '/call/' . self::$identifierToToken[$identifier],
new TableNode([['flags', $flags]])
);
$this->assertStatusCode($this->response, $statusCode);
}
/**
* @Then /^user "([^"]*)" pings (user|guest) "([^"]*)" to join call "([^"]*)" with (\d+) \((v4)\)$/
*
* @param string $user
* @param string $actorType
* @param string $actorId
* @param string $identifier
* @param int $statusCode
* @param string $apiVersion
*/
public function userPingsAttendeeInRoomTo(string $user, string $actorType, string $actorId, string $identifier, int $statusCode, string $apiVersion): void {
$this->setCurrentUser($user);
$this->sendRequest('POST', '/apps/spreed/api/' . $apiVersion . '/call/' . self::$identifierToToken[$identifier] . '/ring/' . self::$userToAttendeeId[$identifier][$actorType . 's'][$actorId]);
$this->assertStatusCode($this->response, $statusCode);
}
/**
* @Then /^user "([^"]*)" leaves call "([^"]*)" with (\d+) \((v4)\)$/
*
* @param string $user
* @param string $identifier
* @param int $statusCode
* @param string $apiVersion
*/
public function userLeavesCall(string $user, string $identifier, int $statusCode, string $apiVersion): void {
$this->setCurrentUser($user);
$this->sendRequest('DELETE', '/apps/spreed/api/' . $apiVersion . '/call/' . self::$identifierToToken[$identifier]);
$this->assertStatusCode($this->response, $statusCode);
}
/**
* @Then /^user "([^"]*)" ends call "([^"]*)" with (\d+) \((v4)\)$/
*
* @param string $user
* @param string $identifier
* @param int $statusCode
* @param string $apiVersion
*/
public function userEndsCall(string $user, string $identifier, int $statusCode, string $apiVersion): void {
$requestParameters = [
['all', true],
];
$this->setCurrentUser($user);
$this->sendRequest(
'DELETE', '/apps/spreed/api/' . $apiVersion . '/call/' . self::$identifierToToken[$identifier],
new TableNode($requestParameters)
);
$this->assertStatusCode($this->response, $statusCode);
}
/**
* @Then /^user "([^"]*)" sees (\d+) peers in call "([^"]*)" with (\d+) \((v4)\)$/
*
* @param string $user
* @param int $numPeers
* @param string $identifier
* @param int $statusCode
* @param string $apiVersion
*/
public function userSeesPeersInCall(string $user, int $numPeers, string $identifier, int $statusCode, string $apiVersion): void {
$this->setCurrentUser($user);
$this->sendRequest('GET', '/apps/spreed/api/' . $apiVersion . '/call/' . self::$identifierToToken[$identifier]);
$this->assertStatusCode($this->response, $statusCode);
if ($statusCode === 200) {
$response = $this->getDataFromResponse($this->response);
Assert::assertCount((int) $numPeers, $response);
} else {
Assert::assertEquals((int) $numPeers, 0);
}
}
/**
* @Then /^user "([^"]*)" (silent sends|sends) message ("[^"]*"|'[^']*') to room "([^"]*)" with (\d+)(?: \((v1)\))?$/
*
* @param string $user
* @param string $sendingMode
* @param string $message
* @param string $identifier
* @param string $statusCode
* @param string $apiVersion
*/
public function userSendsMessageToRoom(string $user, string $sendingMode, string $message, string $identifier, string $statusCode, string $apiVersion = 'v1') {
$message = substr($message, 1, -1);
if ($sendingMode === 'silent sends') {
$body = new TableNode([['message', $message], ['silent', true]]);
} else {
$body = new TableNode([['message', $message]]);
}
$this->setCurrentUser($user);
$this->sendRequest(
'POST', '/apps/spreed/api/' . $apiVersion . '/chat/' . self::$identifierToToken[$identifier],
$body
);
$this->assertStatusCode($this->response, $statusCode);
sleep(1); // make sure Postgres manages the order of the messages
$response = $this->getDataFromResponse($this->response);
if (isset($response['id'])) {
self::$textToMessageId[$message] = $response['id'];
self::$messageIdToText[$response['id']] = $message;
}
}
/**
* @Then /^user "([^"]*)" shares rich-object "([^"]*)" "([^"]*)" '([^']*)' to room "([^"]*)" with (\d+)(?: \((v1)\))?$/
*
* @param string $user
* @param string $type
* @param string $id
* @param string $metaData
* @param string $identifier
* @param string $statusCode
* @param string $apiVersion
*/
public function userSharesRichObjectToRoom($user, $type, $id, $metaData, $identifier, $statusCode, $apiVersion = 'v1') {
$this->setCurrentUser($user);
$this->sendRequest(
'POST', '/apps/spreed/api/' . $apiVersion . '/chat/' . self::$identifierToToken[$identifier] . '/share',
new TableNode([
['objectType', $type],
['objectId', $id],
['metaData', $metaData],
])
);
$this->assertStatusCode($this->response, $statusCode);
sleep(1); // make sure Postgres manages the order of the messages
$response = $this->getDataFromResponse($this->response);
if (isset($response['id'])) {
self::$textToMessageId['shared::' . $type . '::' . $id] = $response['id'];
self::$messageIdToText[$response['id']] = 'shared::' . $type . '::' . $id;
}
}
/**
* @Then /^user "([^"]*)" creates a poll in room "([^"]*)" with (\d+)(?: \((v1)\))?$/
*
* @param string $user
* @param string $identifier
* @param string $statusCode
* @param string $apiVersion
*/
public function createPoll(string $user, string $identifier, string $statusCode, string $apiVersion = 'v1', TableNode $formData = null): void {
$data = $formData->getRowsHash();
$data['options'] = json_decode($data['options'], true);
if ($data['resultMode'] === 'public') {
$data['resultMode'] = 0;
} elseif ($data['resultMode'] === 'hidden') {
$data['resultMode'] = 1;
} else {
throw new \Exception('Invalid result mode');
}
if ($data['maxVotes'] === 'unlimited') {
$data['maxVotes'] = 0;
}
$this->setCurrentUser($user);
$this->sendRequest(
'POST', '/apps/spreed/api/' . $apiVersion . '/poll/' . self::$identifierToToken[$identifier],
$data
);
$this->assertStatusCode($this->response, $statusCode);
if ($statusCode !== '201') {
return;
}
$response = $this->getDataFromResponse($this->response);
if (isset($response['id'])) {
self::$questionToPollId[$data['question']] = $response['id'];
}
}
/**
* @Then /^user "([^"]*)" sees poll "([^"]*)" in room "([^"]*)" with (\d+)(?: \((v1)\))?$/
*
* @param string $user
* @param string $question
* @param string $identifier
* @param string $statusCode
* @param string $apiVersion
* @param ?TableNode $formData
*/
public function userSeesPollInRoom(string $user, string $question, string $identifier, string $statusCode, string $apiVersion = 'v1', TableNode $formData = null): void {
$this->setCurrentUser($user);
$this->sendRequest('GET', '/apps/spreed/api/' . $apiVersion . '/poll/' . self::$identifierToToken[$identifier] . '/' . self::$questionToPollId[$question]);
$this->assertStatusCode($this->response, $statusCode);
if ($statusCode === '200' || $formData instanceof TableNode) {
$expected = $this->preparePollExpectedData($formData->getRowsHash());
$response = $this->getDataFromResponse($this->response);
$this->assertPollEquals($expected, $response);
}
}
/**
* @Then /^user "([^"]*)" closes poll "([^"]*)" in room "([^"]*)" with (\d+)(?: \((v1)\))?$/
*
* @param string $user
* @param string $question
* @param string $identifier
* @param string $statusCode
* @param string $apiVersion
* @param ?TableNode $formData
*/
public function userClosesPollInRoom(string $user, string $question, string $identifier, string $statusCode, string $apiVersion = 'v1', TableNode $formData = null): void {
$this->setCurrentUser($user);
$this->sendRequest('DELETE', '/apps/spreed/api/' . $apiVersion . '/poll/' . self::$identifierToToken[$identifier] . '/' . self::$questionToPollId[$question]);
$this->assertStatusCode($this->response, $statusCode);
if ($statusCode !== '200') {
return;
}
$expected = $this->preparePollExpectedData($formData->getRowsHash());
$response = $this->getDataFromResponse($this->response);
$this->assertPollEquals($expected, $response);
}
/**
* @Then /^user "([^"]*)" votes for options "([^"]*)" on poll "([^"]*)" in room "([^"]*)" with (\d+)(?: \((v1)\))?$/
*
* @param string $user
* @param string $options
* @param string $question
* @param string $identifier
* @param string $statusCode
* @param string $apiVersion
* @param ?TableNode $formData
*/
public function userVotesPollInRoom(string $user, string $options, string $question, string $identifier, string $statusCode, string $apiVersion = 'v1', TableNode $formData = null): void {
$data = [
'optionIds' => json_decode($options, true),
];
$this->setCurrentUser($user);
$this->sendRequest(
'POST', '/apps/spreed/api/' . $apiVersion . '/poll/' . self::$identifierToToken[$identifier] . '/' . self::$questionToPollId[$question],
$data
);
$this->assertStatusCode($this->response, $statusCode);
if ($statusCode !== '200' && $statusCode !== '201') {
return;
}
$expected = $this->preparePollExpectedData($formData->getRowsHash());
$response = $this->getDataFromResponse($this->response);
$this->assertPollEquals($expected, $response);
}
protected function assertPollEquals(array $expected, array $response): void {
if (isset($expected['details'])) {
$response['details'] = array_map(static function (array $detail): array {
unset($detail['id']);
return $detail;
}, $response['details']);
}
Assert::assertEquals($expected, $response);
}
protected function preparePollExpectedData(array $expected): array {
if ($expected['resultMode'] === 'public') {
$expected['resultMode'] = 0;
} elseif ($expected['resultMode'] === 'hidden') {
$expected['resultMode'] = 1;
}
if ($expected['maxVotes'] === 'unlimited') {
$expected['maxVotes'] = 0;
}
if ($expected['status'] === 'open') {
$expected['status'] = 0;
} elseif ($expected['status'] === 'closed') {
$expected['status'] = 1;
}
if ($expected['votedSelf'] === 'not voted') {
$expected['votedSelf'] = [];
} else {
$expected['votedSelf'] = json_decode($expected['votedSelf'], true);
}
if (isset($expected['votes'])) {
$expected['votes'] = json_decode($expected['votes'], true);
}
if (isset($expected['details'])) {
$expected['details'] = json_decode($expected['details'], true);
}
$expected['numVoters'] = (int) $expected['numVoters'];
$expected['options'] = json_decode($expected['options'], true);
$result = preg_match('/POLL_ID\(([^)]+)\)/', $expected['id'], $matches);
if ($result) {
$expected['id'] = self::$questionToPollId[$matches[1]];
}
return $expected;
}
/**
* @Then /^user "([^"]*)" sees the following entry when loading the list of dashboard widgets(?: \((v1)\))$/
*
* @param string $user
* @param string $apiVersion
* @param ?TableNode $formData
*/
public function userGetsDashboardWidgets($user, $apiVersion = 'v1', TableNode $formData = null): void {
$this->setCurrentUser($user);
$this->sendRequest('GET', '/apps/dashboard/api/' . $apiVersion . '/widgets');
$this->assertStatusCode($this->response, 200);
$data = $this->getDataFromResponse($this->response);
$expectedWidgets = $formData->getColumnsHash();
foreach ($expectedWidgets as $widget) {
$id = $widget['id'];
Assert::assertArrayHasKey($widget['id'], $data);
$widgetIconUrl = $widget['icon_url'];
$dataIconUrl = $data[$id]['icon_url'];
unset($widget['icon_url'], $data[$id]['icon_url']);
$widget['item_icons_round'] = (bool) $widget['item_icons_round'];
$widget['order'] = (int) $widget['order'];
$widget['widget_url'] = str_replace('{$BASE_URL}', $this->baseUrl, $widget['widget_url']);
$widget['buttons'] = str_replace('{$BASE_URL}', $this->baseUrl, $widget['buttons']);
$widget['buttons'] = json_decode($widget['buttons'], true);
Assert::assertEquals($widget, $data[$id], 'Mismatch of data for widget ' . $id);
Assert::assertStringEndsWith($widgetIconUrl, $dataIconUrl, 'Mismatch of icon URL for widget ' . $id);
}
}
/**
* @Then /^user "([^"]*)" sees the following entries for dashboard widgets "([^"]*)"(?: \((v1)\))$/
*
* @param string $user
* @param string $widgetId
* @param string $apiVersion
* @param ?TableNode $formData
*/
public function userGetsDashboardWidgetItems($user, $widgetId, $apiVersion = 'v1', TableNode $formData = null): void {
$this->setCurrentUser($user);
$this->sendRequest('GET', '/apps/dashboard/api/' . $apiVersion . '/widget-items?widgets[]=' . $widgetId);
$this->assertStatusCode($this->response, 200);
$data = $this->getDataFromResponse($this->response);
Assert::assertArrayHasKey($widgetId, $data);
$expectedItems = $formData->getColumnsHash();
if (empty($expectedItems)) {
Assert::assertEmpty($data[$widgetId]);
return;
}
Assert::assertCount(count($expectedItems), $data[$widgetId]);
foreach ($expectedItems as $key => $item) {
$token = self::$identifierToToken[$item['link']];
$item['link'] = $this->baseUrl . 'index.php/call/' . $token;
$item['iconUrl'] = str_replace('{$BASE_URL}', $this->baseUrl, $item['iconUrl']);
$item['iconUrl'] = str_replace('{token}', $token, $item['iconUrl']);
Assert::assertMatchesRegularExpression('/\?v=\w{8}$/', $data[$widgetId][$key]['iconUrl']);
preg_match('/(?<version>\?v=\w{8})$/', $data[$widgetId][$key]['iconUrl'], $matches);
$item['iconUrl'] = str_replace('{version}', $matches['version'], $item['iconUrl']);
Assert::assertEquals($item, $data[$widgetId][$key], 'Wrong details for item #' . $key);
}
}
/**
* @Then /^user "([^"]*)" deletes message "([^"]*)" from room "([^"]*)" with (\d+)(?: \((v1)\))?$/
*
* @param string $user
* @param string $message
* @param string $identifier
* @param string $statusCode
* @param string $apiVersion
*/
public function userDeletesMessageFromRoom($user, $message, $identifier, $statusCode, $apiVersion = 'v1') {
$this->setCurrentUser($user);
$this->sendRequest(
'DELETE', '/apps/spreed/api/' . $apiVersion . '/chat/' . self::$identifierToToken[$identifier] . '/' . self::$textToMessageId[$message],
new TableNode([['message', $message]])
);
$this->assertStatusCode($this->response, $statusCode);
}
/**
* @Then /^user "([^"]*)" deletes chat history for room "([^"]*)" with (\d+)(?: \((v1)\))?$/
*
* @param string $user
* @param string $identifier
* @param int $statusCode
* @param string $apiVersion
*/
public function userDeletesHistoryFromRoom(string $user, string $identifier, int $statusCode, string $apiVersion = 'v1'): void {
$this->setCurrentUser($user);
$this->sendRequest(
'DELETE', '/apps/spreed/api/' . $apiVersion . '/chat/' . self::$identifierToToken[$identifier]
);
$this->assertStatusCode($this->response, $statusCode);
}
/**
* @Then /^user "([^"]*)" reads message "([^"]*)" in room "([^"]*)" with (\d+)(?: \((v1)\))?$/
*
* @param string $user
* @param string $message
* @param string $identifier
* @param string $statusCode
* @param string $apiVersion
*/
public function userReadsMessageInRoom($user, $message, $identifier, $statusCode, $apiVersion = 'v1') {
$this->setCurrentUser($user);
$this->sendRequest(
'POST', '/apps/spreed/api/' . $apiVersion . '/chat/' . self::$identifierToToken[$identifier] . '/read',
new TableNode([['lastReadMessage', self::$textToMessageId[$message]]])
);
$this->assertStatusCode($this->response, $statusCode);
}
/**
* @Then /^user "([^"]*)" marks room "([^"]*)" as unread with (\d+)(?: \((v1)\))?$/
*
* @param string $user
* @param string $identifier
* @param string $statusCode
* @param string $apiVersion
*/
public function userMarkUnreadRoom($user, $identifier, $statusCode, $apiVersion = 'v1') {
$this->setCurrentUser($user);
$this->sendRequest(
'DELETE', '/apps/spreed/api/' . $apiVersion . '/chat/' . self::$identifierToToken[$identifier] . '/read',
);
$this->assertStatusCode($this->response, $statusCode);
}
/**
* @Then /^user "([^"]*)" sends message "([^"]*)" with reference id "([^"]*)" to room "([^"]*)" with (\d+)(?: \((v1)\))?$/
*
* @param string $user
* @param string $message
* @param string $referenceId
* @param string $identifier
* @param string $statusCode
* @param string $apiVersion
*/
public function userSendsMessageWithReferenceIdToRoom($user, $message, $referenceId, $identifier, $statusCode, $apiVersion = 'v1') {
$this->setCurrentUser($user);
$this->sendRequest(
'POST', '/apps/spreed/api/' . $apiVersion . '/chat/' . self::$identifierToToken[$identifier],
new TableNode([['message', $message], ['referenceId', $referenceId]])
);
$this->assertStatusCode($this->response, $statusCode);
sleep(1); // make sure Postgres manages the order of the messages
$response = $this->getDataFromResponse($this->response);
if (isset($response['id'])) {
self::$textToMessageId[$message] = $response['id'];
self::$messageIdToText[$response['id']] = $message;
}
Assert::assertStringStartsWith($response['referenceId'], $referenceId);
}
/**
* @Then /^user "([^"]*)" sends reply ("[^"]*"|'[^']*') on message ("[^"]*"|'[^']*') to room "([^"]*)" with (\d+)(?: \((v1)\))?$/
*
* @param string $user
* @param string $reply
* @param string $message
* @param string $identifier
* @param string $statusCode
* @param string $apiVersion
*/
public function userSendsReplyToRoom($user, $reply, $message, $identifier, $statusCode, $apiVersion = 'v1') {
$reply = substr($reply, 1, -1);
$message = substr($message, 1, -1);
$replyTo = self::$textToMessageId[$message];
$this->setCurrentUser($user);
$this->sendRequest(
'POST', '/apps/spreed/api/' . $apiVersion . '/chat/' . self::$identifierToToken[$identifier],
new TableNode([['message', $reply], ['replyTo', $replyTo]])
);
$this->assertStatusCode($this->response, $statusCode);
sleep(1); // make sure Postgres manages the order of the messages
$response = $this->getDataFromResponse($this->response);
if (isset($response['id'])) {
self::$textToMessageId[$reply] = $response['id'];
self::$messageIdToText[$response['id']] = $reply;
}
}
/**
* @Then /^user "([^"]*)" sees the following messages in room "([^"]*)" with (\d+)(?: \((v1)\))?$/
*
* @param string $user
* @param string $identifier
* @param string $statusCode
* @param string $apiVersion
*/
public function userSeesTheFollowingMessagesInRoom($user, $identifier, $statusCode, $apiVersion = 'v1', TableNode $formData = null) {
$this->setCurrentUser($user);
$this->sendRequest('GET', '/apps/spreed/api/' . $apiVersion . '/chat/' . self::$identifierToToken[$identifier] . '?lookIntoFuture=0');
$this->assertStatusCode($this->response, $statusCode);
$this->compareDataResponse($formData);
}
/**
* @Then /^user "([^"]*)" searches for "([^"]*)" in room "([^"]*)" with (\d+)(?: \((v1)\))?$/
*
* @param string $user
* @param string $search
* @param string $identifier
* @param string $statusCode
* @param string $apiVersion
*/
public function userSearchesInRoom(string $user, string $search, string $identifier, $statusCode, string $apiVersion = 'v1', TableNode $formData = null): void {
$this->setCurrentUser($user);
$this->sendRequest('GET', '/search/providers/talk-message-current/search?term=' . $search . '&from=' . '/call/' . self::$identifierToToken[$identifier]);
$this->assertStatusCode($this->response, $statusCode);
if ($statusCode !== '200') {
return;
}
$this->compareSearchResponse($formData);
}
/**
* @Then /^user "([^"]*)" sees the following shared (media|audio|voice|file|deckcard|location|other) in room "([^"]*)" with (\d+)(?: \((v1)\))?$/
*
* @param string $user
* @param string $objectType
* @param string $identifier
* @param string $statusCode
* @param string $apiVersion
*/
public function userSeesTheFollowingSharedMediaInRoom($user, $objectType, $identifier, $statusCode, $apiVersion = 'v1', TableNode $formData = null): void {
$this->setCurrentUser($user);
$this->sendRequest('GET', '/apps/spreed/api/' . $apiVersion . '/chat/' . self::$identifierToToken[$identifier] . '/share?objectType=' . $objectType);
$this->assertStatusCode($this->response, $statusCode);
$this->compareDataResponse($formData);
}
/**
* @Then /^user "([^"]*)" sees the following shared summarized overview in room "([^"]*)" with (\d+)(?: \((v1)\))?$/
*
* @param string $user
* @param string $identifier
* @param string $statusCode
* @param string $apiVersion
*/
public function userSeesTheFollowingSharedOverviewMediaInRoom($user, $identifier, $statusCode, $apiVersion = 'v1', TableNode $formData = null): void {
$this->setCurrentUser($user);
$this->sendRequest('GET', '/apps/spreed/api/' . $apiVersion . '/chat/' . self::$identifierToToken[$identifier] . '/share/overview');
$this->assertStatusCode($this->response, $statusCode);
$overview = $this->getDataFromResponse($this->response);
$expected = $formData->getRowsHash();
$summarized = array_map(function ($type) {
return (string) count($type);
}, $overview);
Assert::assertEquals($expected, $summarized);
}
/**
* @Then /^user "([^"]*)" received a system messages in room "([^"]*)" to delete "([^"]*)"(?: \((v1)\))?$/
*
* @param string $user
* @param string $identifier
* @param string $message
* @param string $apiVersion
*/
public function userReceivedDeleteMessage($user, $identifier, $message, $apiVersion = 'v1') {
$this->setCurrentUser($user);
$this->sendRequest('GET', '/apps/spreed/api/' . $apiVersion . '/chat/' . self::$identifierToToken[$identifier] . '?lookIntoFuture=0');
$this->assertStatusCode($this->response, 200);
$actual = $this->getDataFromResponse($this->response);
foreach ($actual as $m) {
if ($m['systemMessage'] === 'message_deleted') {
if (isset($m['parent']['id']) && $m['parent']['id'] === self::$textToMessageId[$message]) {
return;
}
}
}
Assert::fail('Missing message_deleted system message for "' . $message . '"');
}
/**
* @Then /^user "([^"]*)" sees the following messages in room "([^"]*)" starting with "([^"]*)" with (\d+)(?: \((v1)\))?$/
*
* @param string $user
* @param string $identifier
* @param string $knownMessage
* @param string $statusCode
* @param string $apiVersion
* @param TableNode|null $formData
*/
public function userAwaitsTheFollowingMessagesInRoom($user, $identifier, $knownMessage, $statusCode, $apiVersion = 'v1', TableNode $formData = null) {
$this->setCurrentUser($user);
$this->sendRequest('GET', '/apps/spreed/api/' . $apiVersion . '/chat/' . self::$identifierToToken[$identifier] . '?lookIntoFuture=1&includeLastKnown=1&lastKnownMessageId=' . self::$textToMessageId[$knownMessage]);
$this->assertStatusCode($this->response, $statusCode);
$this->compareDataResponse($formData);
}
/**
* @param TableNode|null $formData
*/
protected function compareDataResponse(TableNode $formData = null) {
$actual = $this->getDataFromResponse($this->response);
$messages = [];
array_map(function (array $message) use (&$messages) {
// Filter out system messages
if ($message['systemMessage'] === '') {
$messages[] = $message;
}
}, $actual);
foreach ($messages as $message) {
// Include the received messages in the list of messages used for
// replies; this is needed to get special messages not explicitly
// sent like those for shared files.
self::$textToMessageId[$message['message']] = $message['id'];
if ($message['message'] === '{file}' && isset($message['messageParameters']['file']['name'])) {
self::$textToMessageId['shared::file::' . $message['messageParameters']['file']['name']] = $message['id'];
self::$messageIdToText[$message['id']] = 'shared::file::' . $message['messageParameters']['file']['name'];
}
}
if ($formData === null) {
Assert::assertEmpty($messages);
return;
}
$includeParents = in_array('parentMessage', $formData->getRow(0), true);
$includeReferenceId = in_array('referenceId', $formData->getRow(0), true);
$includeReactions = in_array('reactions', $formData->getRow(0), true);
$includeReactionsSelf = in_array('reactionsSelf', $formData->getRow(0), true);
$expected = $formData->getHash();
$count = count($expected);
Assert::assertCount($count, $messages, 'Message count does not match');
for ($i = 0; $i < $count; $i++) {
if ($expected[$i]['messageParameters'] === '"IGNORE"') {
$messages[$i]['messageParameters'] = 'IGNORE';
}
$result = preg_match('/POLL_ID\(([^)]+)\)/', $expected[$i]['messageParameters'], $matches);
if ($result) {
$expected[$i]['messageParameters'] = str_replace($matches[0], '"' . self::$questionToPollId[$matches[1]] . '"', $expected[$i]['messageParameters']);
}
if (isset($messages[$i]['messageParameters']['object']['icon-url'])) {
$result = preg_match('/"\{VALIDATE_ICON_URL_PATTERN\}"/', $expected[$i]['messageParameters'], $matches);
if ($result) {
Assert::assertMatchesRegularExpression('/avatar(\?v=\w+)?/', $messages[$i]['messageParameters']['object']['icon-url']);
$expected[$i]['messageParameters'] = str_replace($matches[0], json_encode($messages[$i]['messageParameters']['object']['icon-url']), $expected[$i]['messageParameters']);
}
}
}
Assert::assertEquals($expected, array_map(function ($message) use ($includeParents, $includeReferenceId, $includeReactions, $includeReactionsSelf) {
$data = [
'room' => self::$tokenToIdentifier[$message['token']],
'actorType' => $message['actorType'],
'actorId' => ($message['actorType'] === 'guests')? self::$sessionIdToUser[$message['actorId']]: $message['actorId'],
'actorDisplayName' => $message['actorDisplayName'],
// TODO test timestamp; it may require using Runkit, php-timecop
// or something like that.
'message' => $message['message'],
'messageParameters' => json_encode($message['messageParameters']),
];
if ($includeParents) {
$data['parentMessage'] = $message['parent']['message'] ?? '';
}
if ($includeReferenceId) {
$data['referenceId'] = $message['referenceId'];
}
if ($includeReactions) {
$data['reactions'] = json_encode($message['reactions'], JSON_UNESCAPED_UNICODE);
}
if ($includeReactionsSelf) {
if (isset($message['reactionsSelf'])) {
$data['reactionsSelf'] = json_encode($message['reactionsSelf'], JSON_UNESCAPED_UNICODE);
} else {
$data['reactionsSelf'] = null;
}
}
return $data;
}, $messages));
}
/**
* @param TableNode|null $formData
*/
protected function compareSearchResponse(TableNode $formData = null) {
$messages = $this->getDataFromResponse($this->response)['entries'];
if ($formData === null) {
Assert::assertEmpty($messages);
return;
}
$expected = array_map(static function (array $message) {
$message['attributes.conversation'] = self::$identifierToToken[$message['attributes.conversation']];
$message['attributes.messageId'] = self::$textToMessageId[$message['attributes.messageId']];
return $message;
}, $formData->getHash());
$count = count($expected);
Assert::assertCount($count, $messages, 'Message count does not match');
Assert::assertEquals($expected, array_map(static function ($message) {
return [
'title' => $message['title'],
'subline' => $message['subline'],
'attributes.conversation' => $message['attributes']['conversation'],
'attributes.messageId' => $message['attributes']['messageId'],
];
}, $messages));
}
/**
* @Then /^user "([^"]*)" sees the following system messages in room "([^"]*)" with (\d+)(?: \((v1)\))?$/
*
* @param string $user
* @param string $identifier
* @param string $statusCode
* @param string $apiVersion
*/
public function userSeesTheFollowingSystemMessagesInRoom($user, $identifier, $statusCode, $apiVersion = 'v1', TableNode $formData = null) {
$this->setCurrentUser($user);
$this->sendRequest('GET', '/apps/spreed/api/' . $apiVersion . '/chat/' . self::$identifierToToken[$identifier] . '?lookIntoFuture=0');
$this->assertStatusCode($this->response, $statusCode);
$messages = $this->getDataFromResponse($this->response);
$messages = array_filter($messages, function (array $message) {
return $message['systemMessage'] !== '';
});
// Fix index gaps after the array_filter above
$messages = array_values($messages);
foreach ($messages as $systemMessage) {
// Include the received system messages in the list of messages used
// for replies.
self::$textToMessageId[$systemMessage['systemMessage']] = $systemMessage['id'];
self::$messageIdToText[$systemMessage['id']] = $systemMessage['systemMessage'];
}
if ($formData === null) {
Assert::assertEmpty($messages);
return;
}
$expected = $formData->getHash();
Assert::assertCount(count($expected), $messages, 'Message count does not match');
Assert::assertEquals($expected, array_map(function ($message, $expected) {
$data = [
'room' => self::$tokenToIdentifier[$message['token']],
'actorType' => (string) $message['actorType'],
'actorId' => ($message['actorType'] === 'guests') ? self::$sessionIdToUser[$message['actorId']] : (string) $message['actorId'],
'systemMessage' => (string) $message['systemMessage'],
];
if (isset($expected['actorDisplayName'])) {
$data['actorDisplayName'] = $message['actorDisplayName'];
}
if (isset($expected['message'])) {
$data['message'] = $message['message'];
}
if (isset($expected['messageParameters'])) {
$data['messageParameters'] = json_encode($message['messageParameters']);
}
return $data;
}, $messages, $expected));
}
/**
* @Then /^user "([^"]*)" gets the following candidate mentions in room "([^"]*)" for "([^"]*)" with (\d+)(?: \((v1)\))?$/
*
* @param string $user
* @param string $identifier
* @param string $search
* @param string $statusCode
* @param string $apiVersion
* @param TableNode|null $formData
*/
public function userGetsTheFollowingCandidateMentionsInRoomFor($user, $identifier, $search, $statusCode, $apiVersion = 'v1', TableNode $formData = null) {
$this->setCurrentUser($user);
$this->sendRequest('GET', '/apps/spreed/api/' . $apiVersion . '/chat/' . self::$identifierToToken[$identifier] . '/mentions?search=' . $search);
$this->assertStatusCode($this->response, $statusCode);
$mentions = $this->getDataFromResponse($this->response);
if ($formData === null) {
Assert::assertEmpty($mentions);
return;
}
Assert::assertCount(count($formData->getHash()), $mentions, 'Mentions count does not match');
usort($mentions, function ($a, $b) {
if ($a['source'] === $b['source']) {
return $a['label'] <=> $b['label'];
}
return $a['source'] <=> $b['source'];
});
$expected = $formData->getHash();
usort($expected, function ($a, $b) {
if ($a['source'] === $b['source']) {
return $a['label'] <=> $b['label'];
}
return $a['source'] <=> $b['source'];
});
foreach ($expected as $key => $row) {
if ($row['id'] === 'GUEST_ID') {
Assert::assertRegExp('/^guest\/[0-9a-f]{40}$/', $mentions[$key]['id']);
$mentions[$key]['id'] = 'GUEST_ID';
}
if (array_key_exists('avatar', $row)) {
Assert::assertRegExp('/' . self::$identifierToToken[$row['avatar']] . '\/avatar/', $mentions[$key]['avatar']);
unset($row['avatar']);
}
unset($mentions[$key]['avatar'], );
Assert::assertEquals($row, $mentions[$key]);
}
}
/**
* @Then /^user "([^"]*)" gets the following collaborator suggestions in room "([^"]*)" for "([^"]*)" with (\d+)$/
*
* @param string $user
* @param string $identifier
* @param string $search
* @param string $statusCode
* @param string $apiVersion
* @param TableNode|null $formData
*/
public function userGetsTheFollowingCollaboratorSuggestions($user, $identifier, $search, $statusCode, $apiVersion = 'v1', TableNode $formData = null) {
$this->setCurrentUser($user);
$this->sendRequest('GET', '/core/autocomplete/get?search=' . $search . '&itemType=call&itemId=' . self::$identifierToToken[$identifier] . '&shareTypes[]=0&shareTypes[]=1&shareTypes[]=7&shareTypes[]=4');
$this->assertStatusCode($this->response, $statusCode);
$mentions = $this->getDataFromResponse($this->response);
if ($formData === null) {
Assert::assertEmpty($mentions);
return;
}
Assert::assertCount(count($formData->getHash()), $mentions, 'Mentions count does not match');
usort($mentions, function ($a, $b) {
if ($a['source'] === $b['source']) {
return $a['label'] <=> $b['label'];
}
return $a['source'] <=> $b['source'];
});
$expected = $formData->getHash();
usort($expected, function ($a, $b) {
if ($a['source'] === $b['source']) {
return $a['label'] <=> $b['label'];
}
return $a['source'] <=> $b['source'];
});
foreach ($expected as $key => $row) {
unset($mentions[$key]['icon']);
unset($mentions[$key]['status']);
unset($mentions[$key]['subline']);
unset($mentions[$key]['shareWithDisplayNameUnique']);
Assert::assertEquals($row, $mentions[$key]);
}
}
/**
* @Then /^guest "([^"]*)" sets name to "([^"]*)" in room "([^"]*)" with (\d+)(?: \((v1)\))?$/
*
* @param string $user
* @param string $name
* @param string $identifier
* @param string $statusCode
* @param string $apiVersion
*/
public function guestSetsName($user, $name, $identifier, $statusCode, $apiVersion = 'v1') {
$this->setCurrentUser($user);
$this->sendRequest(
'POST', '/apps/spreed/api/' . $apiVersion . '/guest/' . self::$identifierToToken[$identifier] . '/name',
new TableNode([['displayName', $name]])
);
$this->assertStatusCode($this->response, $statusCode);
}
/**
* @Then /^last response has (no) last common read message header$/
*
* @param string $no
*/
public function hasNoChatLastCommonReadHeader($no) {
Assert::assertArrayNotHasKey('X-Chat-Last-Common-Read', $this->response->getHeaders(), 'X-Chat-Last-Common-Read is set to ' . ($this->response->getHeader('X-Chat-Last-Common-Read')[0] ?? '0'));
}
/**
* @Then /^last response has last common read message header (set to|less than) "([^"]*)"$/
*
* @param string $setOrLower
* @param string $message
*/
public function hasChatLastCommonReadHeader($setOrLower, $message) {
Assert::assertArrayHasKey('X-Chat-Last-Common-Read', $this->response->getHeaders());
if ($setOrLower === 'set to') {
Assert::assertEquals(self::$textToMessageId[$message], $this->response->getHeader('X-Chat-Last-Common-Read')[0]);
} else {
// Less than might be required for the first message, because the last read message before is the join/room creation message and we don't know that ID
Assert::assertLessThan(self::$textToMessageId[$message], $this->response->getHeader('X-Chat-Last-Common-Read')[0]);
}
}
/**
* @Then /^user "([^"]*)" creates (\d+) (automatic|manual|free) breakout rooms for "([^"]*)" with (\d+) \((v1)\)$/
*
* @param string $user
* @param int $amount
* @param string $modeString
* @param string $identifier
* @param int $status
* @param string $apiVersion
* @param TableNode|null $formData
*/
public function userCreatesBreakoutRooms(string $user, int $amount, string $modeString, string $identifier, int $status, string $apiVersion, TableNode $formData = null): void {
switch ($modeString) {
case 'automatic':
$mode = 1;
break;
case 'manual':
$mode = 2;
break;
case 'free':
$mode = 3;
break;
default:
throw new \InvalidArgumentException('Invalid breakout room mode: ' . $modeString);
}
$data = [
'mode' => $mode,
'amount' => $amount,
];
if ($modeString === 'manual' && $formData instanceof TableNode) {
$mapArray = [];
foreach ($formData->getRowsHash() as $attendee => $roomNumber) {
[$type, $id] = explode('::', $attendee);
$attendeeId = $this->getAttendeeId($type, $id, $identifier);
$mapArray[$attendeeId] = (int) $roomNumber;
}
$data['attendeeMap'] = json_encode($mapArray, JSON_THROW_ON_ERROR);
}
$this->setCurrentUser($user);
$this->sendRequest('POST', '/apps/spreed/api/' . $apiVersion . '/breakout-rooms/' . self::$identifierToToken[$identifier], $data);
$this->assertStatusCode($this->response, $status);
}
/**
* @Then /^user "([^"]*)" moves participants into different breakout rooms for "([^"]*)" with (\d+) \((v1)\)$/
*
* @param string $user
* @param string $identifier
* @param int $status
* @param string $apiVersion
* @param TableNode|null $formData
*/
public function userMovesParticipantsInsideBreakoutRooms(string $user, string $identifier, int $status, string $apiVersion, TableNode $formData = null): void {
$data = [];
if ($formData instanceof TableNode) {
$mapArray = [];
foreach ($formData->getRowsHash() as $attendee => $roomNumber) {
[$type, $id] = explode('::', $attendee);
$attendeeId = $this->getAttendeeId($type, $id, $identifier);
$mapArray[$attendeeId] = (int) $roomNumber;
}
$data['attendeeMap'] = json_encode($mapArray, JSON_THROW_ON_ERROR);
}
$this->setCurrentUser($user);
$this->sendRequest('POST', '/apps/spreed/api/' . $apiVersion . '/breakout-rooms/' . self::$identifierToToken[$identifier] . '/attendees', $data);
$this->assertStatusCode($this->response, $status);
}
/**
* @Then /^user "([^"]*)" broadcasts message "([^"]*)" to room "([^"]*)" with (\d+)(?: \((v1)\))?$/
*
* @param string $user
* @param string $message
* @param string $identifier
* @param string $statusCode
* @param string $apiVersion
*/
public function userBroadcastsMessageToBreakoutRooms(string $user, string $message, string $identifier, string $statusCode, string $apiVersion = 'v1') {
$body = new TableNode([['message', $message]]);
$this->setCurrentUser($user);
$this->sendRequest(
'POST', '/apps/spreed/api/' . $apiVersion . '/breakout-rooms/' . self::$identifierToToken[$identifier] . '/broadcast',
$body
);
$this->assertStatusCode($this->response, $statusCode);
sleep(1); // make sure Postgres manages the order of the messages
}
/**
* @Then /^user "([^"]*)" (starts|stops) breakout rooms in room "([^"]*)" with (\d+)(?: \((v1)\))?$/
*
* @param string $user
* @param string $startStop
* @param string $identifier
* @param string $statusCode
* @param string $apiVersion
*/
public function userStartsOrStopsBreakoutRooms(string $user, string $startStop, string $identifier, string $statusCode, string $apiVersion = 'v1') {
$this->setCurrentUser($user);
$this->sendRequest(
$startStop === 'starts' ? 'POST' : 'DELETE',
'/apps/spreed/api/' . $apiVersion . '/breakout-rooms/' . self::$identifierToToken[$identifier] . '/rooms'
);
$this->assertStatusCode($this->response, $statusCode);
}
/**
* @Then /^user "([^"]*)" switches in room "([^"]*)" to breakout room "([^"]*)" with (\d+)(?: \((v1)\))?$/
*
* @param string $user
* @param string $identifier
* @param string $target
* @param string $statusCode
* @param string $apiVersion
*/
public function userSwitchesBreakoutRoom(string $user, string $identifier, string $target, string $statusCode, string $apiVersion = 'v1') {
$this->setCurrentUser($user);
$this->sendRequest(
'POST',
'/apps/spreed/api/' . $apiVersion . '/breakout-rooms/' . self::$identifierToToken[$identifier] . '/switch',
[
'target' => self::$identifierToToken[$target],
]
);
$this->assertStatusCode($this->response, $statusCode);
}
/**
* @Then /^user "([^"]*)" (requests assistance|cancels request for assistance) in room "([^"]*)" with (\d+)(?: \((v1)\))?$/
*
* @param string $user
* @param string $requestCancel
* @param string $identifier
* @param string $statusCode
* @param string $apiVersion
*/
public function userRequestsOrCancelsAssistanceInBreakoutRooms(string $user, string $requestCancel, string $identifier, string $statusCode, string $apiVersion = 'v1') {
$this->setCurrentUser($user);
$this->sendRequest(
$requestCancel === 'requests assistance' ? 'POST' : 'DELETE',
'/apps/spreed/api/' . $apiVersion . '/breakout-rooms/' . self::$identifierToToken[$identifier] . '/request-assistance'
);
$this->assertStatusCode($this->response, $statusCode);
}
/**
* @Then /^user "([^"]*)" sets setting "([^"]*)" to "([^"]*)" with (\d+)(?: \((v1)\))?$/
*
* @param string $user
* @param string $setting
* @param string $value
* @param string $statusCode
* @param string $apiVersion
*/
public function userSetting($user, $setting, $value, $statusCode, $apiVersion = 'v1') {
$this->setCurrentUser($user);
$this->sendRequest(
'POST', '/apps/spreed/api/' . $apiVersion . '/settings/user',
new TableNode([['key', $setting], ['value', $value]])
);
$this->assertStatusCode($this->response, $statusCode);
}
/**
* @Then /^user "([^"]*)" has capability "([^"]*)" set to "([^"]*)"$/
*
* @param string $user
* @param string $capability
* @param string $value
*/
public function userCheckCapability($user, $capability, $value) {
$this->setCurrentUser($user);
$this->sendRequest(
'GET', '/cloud/capabilities'
);
$data = $this->getDataFromResponse($this->response);
$capabilities = $data['capabilities'];
$keys = explode('=>', $capability);
$finalKey = array_pop($keys);
$cur = $capabilities;
foreach ($keys as $key) {
Assert::assertArrayHasKey($key, $cur);
$cur = $cur[$key];
}
Assert::assertEquals($value, $cur[$finalKey]);
}
/**
* Parses the xml answer to get the array of users returned.
* @param ResponseInterface $response
* @return array
*/
protected function getDataFromResponse(ResponseInterface $response) {
$jsonBody = json_decode($response->getBody()->getContents(), true);
return $jsonBody['ocs']['data'];
}
/**
* @Then /^status code is ([0-9]*)$/
*
* @param int $statusCode
*/
public function isStatusCode($statusCode) {
$this->assertStatusCode($this->response, $statusCode);
}
/**
* @Given the following :appId app config is set
*
* @param TableNode $formData
*/
public function setAppConfig(string $appId, TableNode $formData): void {
$currentUser = $this->currentUser;
$this->setCurrentUser('admin');
foreach ($formData->getRows() as $row) {
$this->sendRequest('POST', '/apps/provisioning_api/api/v1/config/apps/' . $appId . '/' . $row[0], [
'value' => $row[1],
]);
$this->changedConfigs[$appId][] = $row[0];
}
$this->setCurrentUser($currentUser);
}
/**
* @Then user :user has the following notifications
*
* @param string $user
* @param TableNode|null $body
*/
public function userNotifications(string $user, TableNode $body = null): void {
$this->setCurrentUser($user);
$this->sendRequest(
'GET', '/apps/notifications/api/v2/notifications'
);
$data = $this->getDataFromResponse($this->response);
if ($body === null) {
self::$lastNotifications = [];
Assert::assertCount(0, $data);
return;
}
$this->assertNotifications($data, $body);
self::$lastNotifications = $data;
}
private function assertNotifications($notifications, TableNode $formData) {
Assert::assertCount(count($formData->getHash()), $notifications, 'Notifications count does not match');
Assert::assertEquals($formData->getHash(), array_map(function ($notification, $expectedNotification) {
$data = [];
if (isset($expectedNotification['object_id'])) {
if (strpos($notification['object_id'], '/') !== false) {
[$roomToken, $message] = explode('/', $notification['object_id']);
$data['object_id'] = self::$tokenToIdentifier[$roomToken] . '/' . self::$messageIdToText[$message] ?? 'UNKNOWN_MESSAGE';
} elseif (strpos($expectedNotification['object_id'], 'INVITE_ID') !== false) {
$data['object_id'] = 'INVITE_ID(' . self::$inviteIdToRemote[$notification['object_id']] . ')';
} else {
[$roomToken,] = explode('/', $notification['object_id']);
$data['object_id'] = self::$tokenToIdentifier[$roomToken];
}
}
if (isset($expectedNotification['subject'])) {
$data['subject'] = (string) $notification['subject'];
}
if (isset($expectedNotification['message'])) {
$data['message'] = (string) $notification['message'];
$result = preg_match('/ROOM\(([^)]+)\)/', $expectedNotification['message'], $matches);
if ($result && isset(self::$identifierToToken[$matches[1]])) {
$data['message'] = str_replace(self::$identifierToToken[$matches[1]], $matches[0], $data['message']);
}
}
if (isset($expectedNotification['object_type'])) {
$data['object_type'] = (string) $notification['object_type'];
}
if (isset($expectedNotification['app'])) {
$data['app'] = (string) $notification['app'];
}
return $data;
}, $notifications, $formData->getHash()));
}
/**
* @Given /^guest accounts can be created$/
*
* @param TableNode $formData
*/
public function allowGuestAccountsCreation(): void {
$currentUser = $this->currentUser;
$this->setCurrentUser('admin');
// save old state and restore at the end
$this->sendRequest('GET', '/cloud/apps?filter=enabled');
$this->assertStatusCode($this->response, 200);
$data = $this->getDataFromResponse($this->response);
$this->guestsAppWasEnabled = in_array('guests', $data['apps'], true);
if (!$this->guestsAppWasEnabled) {
// enable guests app
/*
$this->sendRequest('POST', '/cloud/apps/guests');
$this->assertStatusCode($this->response, 200);
*/
// seems using provisioning API doesn't create tables...
$this->runOcc(['app:enable', 'guests']);
}
// save previously set whitelist
$this->sendRequest('GET', '/apps/provisioning_api/api/v1/config/apps/guests/whitelist');
$this->assertStatusCode($this->response, 200);
$this->guestsOldWhitelist = $this->getDataFromResponse($this->response)['data'];
// set whitelist to allow spreed only
$this->sendRequest('POST', '/apps/provisioning_api/api/v1/config/apps/guests/whitelist', [
'value' => 'spreed',
]);
$this->setCurrentUser($currentUser);
}
/**
* @BeforeScenario
* @AfterScenario
*/
public function resetSpreedAppData() {
$currentUser = $this->currentUser;
$this->setCurrentUser('admin');
$this->sendRequest('DELETE', '/apps/spreedcheats/');
foreach ($this->changedConfigs as $appId => $configs) {
foreach ($configs as $config) {
$this->sendRequest('DELETE', '/apps/provisioning_api/api/v1/config/apps/' . $appId . '/' . $config);
}
}
$this->setCurrentUser($currentUser);
}
/**
* @AfterScenario
*/
public function resetGuestsAppState() {
if ($this->guestsAppWasEnabled === null) {
// guests app was not touched
return;
}
$currentUser = $this->currentUser;
$this->setCurrentUser('admin');
if ($this->guestsOldWhitelist) {
// restore old whitelist
$this->sendRequest('POST', '/apps/provisioning_api/api/v1/config/apps/guests/whitelist', [
'value' => $this->guestsOldWhitelist,
]);
} else {
// restore to default
$this->sendRequest('DELETE', '/apps/provisioning_api/api/v1/config/apps/guests/whitelist');
}
// restore app's enabled state
$this->sendRequest($this->guestsAppWasEnabled ? 'POST' : 'DELETE', '/cloud/apps/guests');
$this->setCurrentUser($currentUser);
$this->guestsAppWasEnabled = null;
}
/*
* User management
*/
/**
* @Given /^as user "([^"]*)"$/
* @param string $user
*/
public function setCurrentUser($user) {
$this->currentUser = $user;
}
/**
* @Given /^user "([^"]*)" exists$/
* @param string $user
*/
public function assureUserExists($user) {
$response = $this->userExists($user);
if ($response->getStatusCode() !== 200) {
$this->createUser($user);
// Set a display name different than the user ID to be able to
// ensure in the tests that the right value was returned.
$this->setUserDisplayName($user);
$response = $this->userExists($user);
$this->assertStatusCode($response, 200);
}
}
/**
* @Given /^user "([^"]*)" is a guest account user/
* @param string $email email address
*/
public function createGuestUser($email) {
$currentUser = $this->currentUser;
$this->setCurrentUser('admin');
// in case it exists
$this->deleteUser($email);
$lastCode = $this->runOcc([
'guests:add',
// creator user
'admin',
// email
$email,
'--display-name',
$email . '-displayname',
'--password-from-env',
], [
'OC_PASS' => self::TEST_PASSWORD,
]);
Assert::assertEquals(0, $lastCode, 'Guest creation succeeded for ' . $email);
$this->createdGuestAccountUsers[] = $email;
$this->setCurrentUser($currentUser);
}
private function userExists($user) {
$currentUser = $this->currentUser;
$this->setCurrentUser('admin');
$this->sendRequest('GET', '/cloud/users/' . $user);
$this->setCurrentUser($currentUser);
return $this->response;
}
private function createUser($user) {
$currentUser = $this->currentUser;
$this->setCurrentUser('admin');
$this->sendRequest('POST', '/cloud/users', [
'userid' => $user,
'password' => self::TEST_PASSWORD,
]);
$this->assertStatusCode($this->response, 200, 'Failed to create user');
//Quick hack to login once with the current user
$this->setCurrentUser($user);
$this->sendRequest('GET', '/cloud/users' . '/' . $user);
$this->assertStatusCode($this->response, 200, 'Failed to do first login');
$this->createdUsers[] = $user;
$this->setCurrentUser($currentUser);
}
/**
* @Given /^user "([^"]*)" is deleted$/
* @param string $user
*/
public function userIsDeleted($user) {
$deleted = false;
$this->deleteUser($user);
$response = $this->userExists($user);
$deleted = $response->getStatusCode() === 404;
if (!$deleted) {
Assert::fail("User $user exists");
}
}
private function deleteUser($user) {
$currentUser = $this->currentUser;
$this->setCurrentUser('admin');
$this->sendRequest('DELETE', '/cloud/users/' . $user);
$this->setCurrentUser($currentUser);
unset($this->createdUsers[array_search($user, $this->createdUsers, true)]);
return $this->response;
}
private function setUserDisplayName($user) {
$currentUser = $this->currentUser;
$this->setCurrentUser('admin');
$this->sendRequest('PUT', '/cloud/users/' . $user, [
'key' => 'displayname',
'value' => $user . '-displayname'
]);
$this->setCurrentUser($currentUser);
}
/**
* @Given /^group "([^"]*)" exists$/
* @param string $group
*/
public function assureGroupExists($group) {
$currentUser = $this->currentUser;
$this->setCurrentUser('admin');
$this->sendRequest('POST', '/cloud/groups', [
'groupid' => $group,
]);
$jsonBody = json_decode($this->response->getBody()->getContents(), true);
if (isset($jsonBody['ocs']['meta'])) {
// 102 = group exists
// 200 = created with success
Assert::assertContains(
$jsonBody['ocs']['meta']['statuscode'],
[102, 200],
$jsonBody['ocs']['meta']['message']
);
} else {
throw new \Exception('Invalid response when create group');
}
$this->setCurrentUser($currentUser);
$this->createdGroups[] = $group;
}
private function deleteGroup($group) {
$currentUser = $this->currentUser;
$this->setCurrentUser('admin');
$this->sendRequest('DELETE', '/cloud/groups/' . $group);
$this->setCurrentUser($currentUser);
unset($this->createdGroups[array_search($group, $this->createdGroups, true)]);
}
/**
* @When /^user "([^"]*)" is member of group "([^"]*)"$/
* @param string $user
* @param string $group
*/
public function addingUserToGroup($user, $group) {
$currentUser = $this->currentUser;
$this->setCurrentUser('admin');
$this->sendRequest('POST', "/cloud/users/$user/groups", [
'groupid' => $group,
]);
$this->assertStatusCode($this->response, 200);
$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);
}
/**
* @Given /^user "([^"]*)" (delete react|react) with "([^"]*)" on message "([^"]*)" to room "([^"]*)" with (\d+)(?: \((v1)\))?$/
*/
public function userReactWithOnMessageToRoomWith(string $user, string $action, string $reaction, string $message, string $identifier, int $statusCode, string $apiVersion = 'v1', TableNode $formData = null): void {
$token = self::$identifierToToken[$identifier];
$messageId = self::$textToMessageId[$message];
$this->setCurrentUser($user);
$verb = $action === 'react' ? 'POST' : 'DELETE';
$this->sendRequest($verb, '/apps/spreed/api/' . $apiVersion . '/reaction/' . $token . '/' . $messageId, [
'reaction' => $reaction
]);
$this->assertStatusCode($this->response, $statusCode);
$this->assertReactionList($formData);
}
/**
* @Given /^user "([^"]*)" retrieve reactions "([^"]*)" of message "([^"]*)" in room "([^"]*)" with (\d+)(?: \((v1)\))?$/
*/
public function userRetrieveReactionsOfMessageInRoomWith(string $user, string $reaction, string $message, string $identifier, int $statusCode, string $apiVersion = 'v1', ?TableNode $formData = null): void {
$token = self::$identifierToToken[$identifier];
$messageId = self::$textToMessageId[$message];
$this->setCurrentUser($user);
$reaction = $reaction !== 'all' ? '?reaction=' . $reaction : '';
$this->sendRequest('GET', '/apps/spreed/api/' . $apiVersion . '/reaction/' . $token . '/' . $messageId . $reaction);
$this->assertStatusCode($this->response, $statusCode);
$this->assertReactionList($formData);
}
private function assertReactionList(?TableNode $formData): void {
$expected = [];
if (!$formData instanceof TableNode) {
return;
}
foreach ($formData->getHash() as $row) {
$reaction = $row['reaction'];
unset($row['reaction']);
$expected[$reaction][] = $row;
}
$result = $this->getDataFromResponse($this->response);
$result = array_map(static function ($reaction, $list) use ($expected): array {
$list = array_map(function ($reaction) {
unset($reaction['timestamp']);
$reaction['actorId'] = ($reaction['actorType'] === 'guests') ? self::$sessionIdToUser[$reaction['actorId']] : (string) $reaction['actorId'];
return $reaction;
}, $list);
Assert::assertArrayHasKey($reaction, $expected, 'Not expected reaction: ' . $reaction);
Assert::assertCount(count($list), $expected[$reaction], 'Reaction count by type does not match');
usort($expected[$reaction], [self::class, 'sortAttendees']);
usort($list, [self::class, 'sortAttendees']);
Assert::assertEquals($expected[$reaction], $list, 'Reaction list by type does not match');
return $list;
}, array_keys($result), array_values($result));
Assert::assertCount(count($expected), $result, 'Reaction count does not match');
}
/**
* @Given user :user set the message expiration to :messageExpiration of room :identifier with :statusCode (:apiVersion)
*/
public function userSetTheMessageExpirationToXWithStatusCode(string $user, int $messageExpiration, string $identifier, int $statusCode, string $apiVersion = 'v4'): void {
$this->setCurrentUser($user);
$this->sendRequest('POST', '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier] . '/message-expiration', [
'seconds' => $messageExpiration,
]);
$this->assertStatusCode($this->response, $statusCode);
}
/**
* @When wait for :seconds second
*/
public function waitForXSecond($seconds): void {
sleep($seconds);
}
/**
* @When wait for :seconds seconds
*/
public function waitForXSeconds($seconds): void {
sleep($seconds);
}
/*
* Requests
*/
/**
* @Given /^user "([^"]*)" logs in$/
* @param string $user
*/
public function userLogsIn(string $user) {
$loginUrl = $this->baseUrl . '/login';
$cookieJar = $this->getUserCookieJar($user);
// Request a new session and extract CSRF token
$client = new Client();
$this->response = $client->get(
$loginUrl,
[
'cookies' => $cookieJar,
]
);
$requestToken = $this->extractRequestTokenFromResponse($this->response);
// Login and extract new token
$password = ($user === 'admin') ? 'admin' : self::TEST_PASSWORD;
$client = new Client();
$this->response = $client->post(
$loginUrl,
[
'form_params' => [
'user' => $user,
'password' => $password,
'requesttoken' => $requestToken,
],
'cookies' => $cookieJar,
]
);
$this->assertStatusCode($this->response, 200);
}
/**
* @When /^user "([^"]*)" uploads file "([^"]*)" as avatar of room "([^"]*)" with (\d+)(?: \((v1)\))?$/
*/
public function userSendTheFileAsAvatarOfRoom(string $user, string $file, string $identifier, int $statusCode, string $apiVersion = 'v1'): void {
$this->setCurrentUser($user);
$options = [
'multipart' => [
[
'name' => 'file',
'contents' => $file !== 'invalid' ? fopen(__DIR__ . '/../../../..' . $file, 'r') : '',
],
],
];
$this->sendRequest('POST', '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier] . '/avatar', null, [], $options);
$this->assertStatusCode($this->response, $statusCode);
}
/**
* @When /^the room "([^"]*)" has an avatar with (\d+)(?: \((v1)\))?$/
*/
public function theRoomHasAnAvatarWithStatusCode(string $identifier, int $statusCode, string $apiVersion = 'v1'): void {
$this->sendRequest('GET', '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier] . '/avatar');
$this->assertStatusCode($this->response, $statusCode);
}
/**
* @When /^the room "([^"]*)" has an svg as avatar with (\d+)(?: \((v1)\))?$/
*/
public function theRoomHasASvgAvatarWithStatusCode(string $identifier, int $statusCode, string $apiVersion = 'v1'): void {
$this->theRoomHasNoSvgAvatarWithStatusCode($identifier, $statusCode, $apiVersion, true);
}
/**
* @When /^the room "([^"]*)" has not an svg as avatar with (\d+)(?: \((v1)\))?$/
*/
public function theRoomHasNoSvgAvatarWithStatusCode(string $identifier, int $statusCode, string $apiVersion = 'v1', bool $expectedToBeSvg = false): void {
$this->theRoomHasAnAvatarWithStatusCode($identifier, $statusCode, $apiVersion);
$content = $this->response->getBody()->getContents();
try {
simplexml_load_string($content);
$actualIsSvg = true;
} catch (\Throwable $th) {
$actualIsSvg = false;
}
if ($expectedToBeSvg) {
Assert::assertEquals($expectedToBeSvg, $actualIsSvg, 'The room avatar needs to be a XML file');
} else {
Assert::assertEquals($expectedToBeSvg, $actualIsSvg, 'The room avatar can not be a XML file');
}
}
/**
* @When /^the avatar svg of room "([^"]*)" contains the string "([^"]*)"(?: \((v1)\))?$/
*/
public function theAvatarSvgOfRoomContainsTheString(string $identifier, string $string, string $apiVersion = 'v1'): void {
$this->sendRequest('GET', '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier] . '/avatar');
$content = $this->response->getBody()->getContents();
try {
simplexml_load_string($content);
} catch (\Throwable $th) {
throw new Exception('The avatar needs to be a XML');
}
Assert::stringContains($content, $string);
}
/**
* @When /^user "([^"]*)" delete the avatar of room "([^"]*)" with (\d+)(?: \((v1)\))?$/
*/
public function userDeleteTheAvatarOfRoom(string $user, string $identifier, int $statusCode, string $apiVersion = 'v1'): void {
$this->setCurrentUser($user);
$this->sendRequest('DELETE', '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier] . '/avatar');
$this->assertStatusCode($this->response, $statusCode);
}
/**
* @When /^user "([^"]*)" starts "(invalid|audio|video)" recording in room "([^"]*)" with (\d+)(?: \((v1)\))?$/
*/
public function userStartRecordingInRoom(string $user, string $recordingType, string $identifier, int $statusCode, string $apiVersion = 'v1'): void {
$recordingTypes = [
'invalid' => -1,
'video' => 1,
'audio' => 2,
];
$data = [
'status' => $recordingTypes[$recordingType]
];
$this->setCurrentUser($user);
$roomToken = self::$identifierToToken[$identifier];
$this->sendRequest('POST', '/apps/spreed/api/' . $apiVersion . '/recording/' . $roomToken, $data);
$this->assertStatusCode($this->response, $statusCode);
}
/**
* @When /^user "([^"]*)" stops recording in room "([^"]*)" with (\d+)(?: \((v1)\))?$/
*/
public function userStopRecordingInRoom(string $user, string $identifier, int $statusCode, string $apiVersion = 'v1'): void {
$this->setCurrentUser($user);
$roomToken = self::$identifierToToken[$identifier];
$this->sendRequest('DELETE', '/apps/spreed/api/' . $apiVersion . '/recording/' . $roomToken);
$this->assertStatusCode($this->response, $statusCode);
}
/**
* @When /^user "([^"]*)" store recording file "([^"]*)" in room "([^"]*)" with (\d+)(?: \((v1)\))?$/
*/
public function userStoreRecordingFileInRoom(string $user, string $file, string $identifier, int $statusCode, string $apiVersion = 'v1'): void {
$this->setCurrentUser($user);
$recordingServerSharedSecret = 'the secret';
$this->setAppConfig('spreed', new TableNode([['recording_servers', json_encode(['secret' => $recordingServerSharedSecret])]]));
$validRandom = md5((string) rand());
$validChecksum = hash_hmac('sha256', $validRandom . self::$identifierToToken[$identifier], $recordingServerSharedSecret);
$headers = [
'TALK_RECORDING_RANDOM' => $validRandom,
'TALK_RECORDING_CHECKSUM' => $validChecksum,
];
$options = ['multipart' => [['name' => 'owner', 'contents' => $user]]];
if ($file === 'invalid') {
// Create invalid content
$options['multipart'][] = [
'name' => 'file',
'contents' => '',
];
} elseif ($file === 'big') {
// More details about MAX_FILE_SIZE follow the link:
// https://www.php.net/manual/en/features.file-upload.post-method.php
$options['multipart'][] = [
'name' => 'MAX_FILE_SIZE',
'contents' => 1, // Limit the max file size to 1
];
// Create file with big content
$contents = tmpfile();
fwrite($contents, 'fake content'); // Bigger than 1
$options['multipart'][] = [
'name' => 'file',
'contents' => $contents,
'filename' => 'audio.ogg', // to get the mimetype by extension and do the upload
];
} else {
// Upload a file
$options['multipart'][] = [
'name' => 'file',
'contents' => fopen(__DIR__ . '/../../../..' . $file, 'r'),
];
}
$this->sendRequest(
'POST',
'/apps/spreed/api/' . $apiVersion . '/recording/' . self::$identifierToToken[$identifier] . '/store',
null,
$headers,
$options
);
$this->assertStatusCode($this->response, $statusCode);
}
/**
* @Then /^user "([^"]*)" shares file from the (first|last) notification to room "([^"]*)" with (\d+)(?: \((v1)\))?$/
*
* @param string $user
* @param string $firstLast
* @param string $identifier
* @param int $status
* @param string $apiVersion
*/
public function userShareLastNotificationFile(string $user, string $firstLast, string $identifier, int $status, string $apiVersion): void {
$this->setCurrentUser($user);
if (empty(self::$lastNotifications)) {
throw new \RuntimeException('No notification data loaded, call userNotifications() before');
}
if ($firstLast === 'last') {
$lastNotification = end(self::$lastNotifications);
} else {
$lastNotification = reset(self::$lastNotifications);
}
$data = [
'fileId' => $lastNotification['messageRichParameters']['file']['id'],
'timestamp' => (new \DateTime($lastNotification['datetime']))->getTimestamp(),
];
$this->sendRequest(
'POST',
'/apps/spreed/api/' . $apiVersion . '/recording/' . self::$identifierToToken[$identifier] . '/share-chat',
$data
);
$this->assertStatusCode($this->response, $status);
}
/**
* @When /^run transcript background jobs$/
*/
public function runTranscriptBackgroundJobs(): void {
$this->runOcc(['background-job:list', '--output=json_pretty', '--class=OC\SpeechToText\TranscriptionJob']);
$list = json_decode($this->lastStdOut, true, 512, JSON_THROW_ON_ERROR);
Assert::assertNotEmpty($list, 'List of OC\SpeechToText\TranscriptionJob should not be empty');
foreach ($list as $job) {
$this->runOcc(['background-job:execute', (string) $job['id']]);
if ($this->lastStdErr) {
throw new \RuntimeException($this->lastStdErr);
}
}
}
/**
* @When /^user "([^"]*)" set status to "([^"]*)" with (\d+)(?: \((v1)\))?$/
*/
public function setUserStatus(string $user, string $status, int $statusCode, string $apiVersion = 'v1'): void {
$this->setCurrentUser($user);
$this->sendRequest(
'PUT',
'/apps/user_status/api/' . $apiVersion . '/user_status/status',
new TableNode([['statusType', $status]])
);
$this->assertStatusCode($this->response, $statusCode);
}
/**
* @Then the response error matches with :error
*/
public function assertResponseErrorMatchesWith(string $error): void {
$responseData = $this->getDataFromResponse($this->response);
Assert::assertEquals(['error' => $error], $responseData);
}
/**
* @param ResponseInterface $response
* @return string
*/
private function extractRequestTokenFromResponse(ResponseInterface $response): string {
return substr(preg_replace('/(.*)data-requesttoken="(.*)">(.*)/sm', '\2', $response->getBody()->getContents()), 0, 89);
}
/**
* @When /^sending "([^"]*)" to "([^"]*)" with$/
* @param string $verb
* @param string $url
* @param TableNode|array|null $body
* @param array $headers
*/
public function sendRequest($verb, $url, $body = null, array $headers = [], array $options = []) {
$fullUrl = $this->baseUrl . 'ocs/v2.php' . $url;
$this->sendRequestFullUrl($verb, $fullUrl, $body, $headers, $options);
}
/**
* @param string $verb
* @param string $url
* @param TableNode|array|null $body
* @param array $headers
*/
public function sendRemoteRequest($verb, $url, $body = null, array $headers = []) {
$fullUrl = $this->baseRemoteUrl . 'ocs/v2.php' . $url;
$this->sendRequestFullUrl($verb, $fullUrl, $body, $headers);
}
/**
* @param string $verb
* @param string $fullUrl
* @param TableNode|array|null $body
* @param array $headers
*/
public function sendRequestFullUrl($verb, $fullUrl, $body = null, array $headers = [], array $options = []) {
$client = new Client();
$options = array_merge($options, ['cookies' => $this->getUserCookieJar($this->currentUser)]);
if ($this->currentUser === 'admin') {
$options['auth'] = ['admin', 'admin'];
} elseif (strpos($this->currentUser, 'guest') !== 0) {
$options['auth'] = [$this->currentUser, self::TEST_PASSWORD];
}
if ($body instanceof TableNode) {
$fd = $body->getRowsHash();
$options['form_params'] = $fd;
} elseif (is_array($body)) {
$options['form_params'] = $body;
} elseif (is_string($body)) {
$options['body'] = $body;
}
$options['headers'] = array_merge($headers, [
'OCS-ApiRequest' => 'true',
'Accept' => 'application/json',
]);
try {
$this->response = $client->{$verb}($fullUrl, $options);
} catch (ClientException $ex) {
$this->response = $ex->getResponse();
} catch (\GuzzleHttp\Exception\ServerException $ex) {
$this->response = $ex->getResponse();
}
}
protected function getUserCookieJar($user) {
if (!isset($this->cookieJars[$user])) {
$this->cookieJars[$user] = new CookieJar();
}
return $this->cookieJars[$user];
}
/**
* @param ResponseInterface $response
* @param int $statusCode
* @param string $message
*/
protected function assertStatusCode(ResponseInterface $response, int $statusCode, string $message = '') {
if ($statusCode !== $response->getStatusCode()) {
$content = $this->response->getBody()->getContents();
Assert::assertEquals(
$statusCode,
$response->getStatusCode(),
$message . ($message ? ': ' : '') . $content
);
} else {
Assert::assertEquals($statusCode, $response->getStatusCode(), $message);
}
}
}