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.

3072 lines
101 KiB

  1. <?php
  2. /**
  3. * @author Joas Schilling <coding@schilljs.com>
  4. * @author Thomas Müller <thomas.mueller@tmit.eu>
  5. *
  6. * @copyright Copyright (c) 2016, ownCloud, Inc.
  7. * @license AGPL-3.0
  8. *
  9. * This code is free software: you can redistribute it and/or modify
  10. * it under the terms of the GNU Affero General Public License, version 3,
  11. * as published by the Free Software Foundation.
  12. *
  13. * This program is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU Affero General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU Affero General Public License, version 3,
  19. * along with this program. If not, see <http://www.gnu.org/licenses/>
  20. *
  21. */
  22. require __DIR__ . '/../../vendor/autoload.php';
  23. use Behat\Behat\Context\Context;
  24. use Behat\Behat\Context\SnippetAcceptingContext;
  25. use Behat\Behat\Hook\Scope\BeforeScenarioScope;
  26. use Behat\Gherkin\Node\TableNode;
  27. use GuzzleHttp\Client;
  28. use GuzzleHttp\Cookie\CookieJar;
  29. use GuzzleHttp\Exception\ClientException;
  30. use PHPUnit\Framework\Assert;
  31. use Psr\Http\Message\ResponseInterface;
  32. /**
  33. * Defines application features from the specific context.
  34. */
  35. class FeatureContext implements Context, SnippetAcceptingContext {
  36. public const TEST_PASSWORD = '123456';
  37. /** @var string[] */
  38. protected static $identifierToToken;
  39. /** @var string[] */
  40. protected static $tokenToIdentifier;
  41. /** @var array[] */
  42. protected static $identifierToAvatar;
  43. /** @var string[] */
  44. protected static $sessionIdToUser;
  45. /** @var string[] */
  46. protected static $userToSessionId;
  47. /** @var int[] */
  48. protected static $userToAttendeeId;
  49. /** @var string[] */
  50. protected static $messages;
  51. protected static $textToMessageId;
  52. /** @var array[] */
  53. protected static $messageIdToText;
  54. /** @var int[] */
  55. protected static $remoteToInviteId;
  56. /** @var string[] */
  57. protected static $inviteIdToRemote;
  58. /** @var int[] */
  59. protected static $questionToPollId;
  60. protected static $permissionsMap = [
  61. 'D' => 0, // PERMISSIONS_DEFAULT
  62. 'C' => 1, // PERMISSIONS_CUSTOM
  63. 'S' => 2, // PERMISSIONS_CALL_START
  64. 'J' => 4, // PERMISSIONS_CALL_JOIN
  65. 'L' => 8, // PERMISSIONS_LOBBY_IGNORE
  66. 'A' => 16, // PERMISSIONS_PUBLISH_AUDIO
  67. 'V' => 32, // PERMISSIONS_PUBLISH_VIDEO
  68. 'P' => 64, // PERMISSIONS_PUBLISH_SCREEN
  69. 'M' => 128, // PERMISSIONS_CHAT
  70. ];
  71. /** @var string */
  72. protected $currentUser;
  73. /** @var ResponseInterface */
  74. private $response;
  75. /** @var CookieJar[] */
  76. private $cookieJars;
  77. /** @var string */
  78. protected $baseUrl;
  79. /** @var string */
  80. protected $baseRemoteUrl;
  81. /** @var string */
  82. protected $lastEtag;
  83. /** @var array */
  84. protected $createdUsers = [];
  85. /** @var array */
  86. protected $createdGroups = [];
  87. /** @var array */
  88. protected $createdGuestAccountUsers = [];
  89. /** @var array */
  90. protected $changedConfigs = [];
  91. /** @var SharingContext */
  92. private $sharingContext;
  93. /** @var null|bool */
  94. private $guestsAppWasEnabled = null;
  95. /** @var string */
  96. private $guestsOldWhitelist;
  97. use CommandLineTrait;
  98. public static function getTokenForIdentifier(string $identifier) {
  99. return self::$identifierToToken[$identifier];
  100. }
  101. public function getAttendeeId(string $type, string $id, string $room, string $user = null) {
  102. if (!isset(self::$userToAttendeeId[$room][$type][$id])) {
  103. if ($user !== null) {
  104. $this->userLoadsAttendeeIdsInRoom($user, $room, 'v4');
  105. } else {
  106. throw new \Exception('Attendee id unknown, please call userLoadsAttendeeIdsInRoom with a user that has access before');
  107. }
  108. }
  109. return self::$userToAttendeeId[$room][$type][$id];
  110. }
  111. /**
  112. * FeatureContext constructor.
  113. */
  114. public function __construct() {
  115. $this->cookieJars = [];
  116. $this->baseUrl = getenv('TEST_SERVER_URL');
  117. $this->baseRemoteUrl = getenv('TEST_REMOTE_URL');
  118. $this->guestsAppWasEnabled = null;
  119. }
  120. /**
  121. * @BeforeScenario
  122. */
  123. public function setUp() {
  124. self::$identifierToToken = [];
  125. self::$tokenToIdentifier = [];
  126. self::$sessionIdToUser = [];
  127. self::$userToSessionId = [];
  128. self::$userToAttendeeId = [];
  129. self::$textToMessageId = [];
  130. self::$messageIdToText = [];
  131. self::$questionToPollId = [];
  132. $this->createdUsers = [];
  133. $this->createdGroups = [];
  134. $this->createdGuestAccountUsers = [];
  135. }
  136. /**
  137. * @BeforeScenario
  138. */
  139. public function getOtherRequiredSiblingContexts(BeforeScenarioScope $scope) {
  140. $environment = $scope->getEnvironment();
  141. $this->sharingContext = $environment->getContext("SharingContext");
  142. }
  143. /**
  144. * @AfterScenario
  145. */
  146. public function tearDown() {
  147. foreach ($this->createdUsers as $user) {
  148. $this->deleteUser($user);
  149. }
  150. foreach ($this->createdGroups as $group) {
  151. $this->deleteGroup($group);
  152. }
  153. foreach ($this->createdGuestAccountUsers as $user) {
  154. $this->deleteUser($user);
  155. }
  156. }
  157. /**
  158. * @Then /^user "([^"]*)" cannot find any listed rooms \((v4)\)$/
  159. *
  160. * @param string $user
  161. * @param string $apiVersion
  162. */
  163. public function userCannotFindAnyListedRooms(string $user, string $apiVersion): void {
  164. $this->userCanFindListedRoomsWithTerm($user, '', $apiVersion, null);
  165. }
  166. /**
  167. * @Then /^user "([^"]*)" cannot find any listed rooms with (\d+) \((v4)\)$/
  168. *
  169. * @param string $user
  170. * @param int $statusCode
  171. * @param string $apiVersion
  172. */
  173. public function userCannotFindAnyListedRoomsWithStatus(string $user, int $statusCode, string $apiVersion): void {
  174. $this->setCurrentUser($user);
  175. $this->sendRequest('GET', '/apps/spreed/api/' . $apiVersion . '/listed-room');
  176. $this->assertStatusCode($this->response, $statusCode);
  177. }
  178. /**
  179. * @Then /^user "([^"]*)" cannot find any listed rooms with term "([^"]*)" \((v4)\)$/
  180. *
  181. * @param string $user
  182. * @param string $term
  183. * @param string $apiVersion
  184. */
  185. public function userCannotFindAnyListedRoomsWithTerm(string $user, string $term, string $apiVersion): void {
  186. $this->userCanFindListedRoomsWithTerm($user, $term, $apiVersion, null);
  187. }
  188. /**
  189. * @Then /^user "([^"]*)" can find listed rooms \((v4)\)$/
  190. *
  191. * @param string $user
  192. * @param string $apiVersion
  193. * @param TableNode|null $formData
  194. */
  195. public function userCanFindListedRooms(string $user, string $apiVersion, TableNode $formData = null): void {
  196. $this->userCanFindListedRoomsWithTerm($user, '', $apiVersion, $formData);
  197. }
  198. /**
  199. * @Then /^user "([^"]*)" can find listed rooms with term "([^"]*)" \((v4)\)$/
  200. *
  201. * @param string $user
  202. * @param string $term
  203. * @param string $apiVersion
  204. * @param TableNode|null $formData
  205. */
  206. public function userCanFindListedRoomsWithTerm(string $user, string $term, string $apiVersion, TableNode $formData = null): void {
  207. $this->setCurrentUser($user);
  208. $suffix = '';
  209. if ($term !== '') {
  210. $suffix = '?searchTerm=' . \rawurlencode($term);
  211. }
  212. $this->sendRequest('GET', '/apps/spreed/api/' . $apiVersion . '/listed-room' . $suffix);
  213. $this->assertStatusCode($this->response, 200);
  214. $rooms = $this->getDataFromResponse($this->response);
  215. if ($formData === null) {
  216. Assert::assertEmpty($rooms);
  217. return;
  218. }
  219. $this->assertRooms($rooms, $formData);
  220. }
  221. /**
  222. * @Then /^user "([^"]*)" is participant of the following (unordered )?rooms \((v4)\)$/
  223. *
  224. * @param string $user
  225. * @param string $shouldOrder
  226. * @param string $apiVersion
  227. * @param TableNode|null $formData
  228. */
  229. public function userIsParticipantOfRooms(string $user, string $shouldOrder, string $apiVersion, TableNode $formData = null): void {
  230. $this->setCurrentUser($user);
  231. $this->sendRequest('GET', '/apps/spreed/api/' . $apiVersion . '/room');
  232. $this->assertStatusCode($this->response, 200);
  233. $rooms = $this->getDataFromResponse($this->response);
  234. $rooms = array_filter($rooms, function ($room) {
  235. return $room['type'] !== 4;
  236. });
  237. if ($formData === null) {
  238. Assert::assertEmpty($rooms);
  239. return;
  240. }
  241. $this->assertRooms($rooms, $formData, $shouldOrder !== '');
  242. }
  243. /**
  244. * @param array $rooms
  245. * @param TableNode $formData
  246. */
  247. private function assertRooms($rooms, TableNode $formData, bool $shouldOrder = false) {
  248. Assert::assertCount(count($formData->getHash()), $rooms, 'Room count does not match');
  249. $expected = $formData->getHash();
  250. if ($shouldOrder) {
  251. $sorter = static function (array $roomA, array $roomB): int {
  252. return $roomA['id'] < $roomB['id'] ? -1 : 1;
  253. };
  254. usort($expected, $sorter);
  255. usort($rooms, $sorter);
  256. }
  257. Assert::assertEquals($expected, array_map(function ($room, $expectedRoom) {
  258. if (!isset(self::$identifierToToken[$room['name']])) {
  259. self::$identifierToToken[$room['name']] = $room['token'];
  260. }
  261. if (!isset(self::$tokenToIdentifier[$room['token']])) {
  262. self::$tokenToIdentifier[$room['token']] = $room['name'];
  263. }
  264. $data = [];
  265. if (isset($expectedRoom['id'])) {
  266. $data['id'] = self::$tokenToIdentifier[$room['token']];
  267. }
  268. if (isset($expectedRoom['name'])) {
  269. $data['name'] = $room['name'];
  270. // Breakout room regex
  271. if (strpos($expectedRoom['name'], '/') === 0 && preg_match($expectedRoom['name'], $room['name'])) {
  272. $data['name'] = $expectedRoom['name'];
  273. }
  274. }
  275. if (isset($expectedRoom['description'])) {
  276. $data['description'] = $room['description'];
  277. }
  278. if (isset($expectedRoom['type'])) {
  279. $data['type'] = (string) $room['type'];
  280. }
  281. if (isset($expectedRoom['hasPassword'])) {
  282. $data['hasPassword'] = (string) $room['hasPassword'];
  283. }
  284. if (isset($expectedRoom['readOnly'])) {
  285. $data['readOnly'] = (string) $room['readOnly'];
  286. }
  287. if (isset($expectedRoom['listable'])) {
  288. $data['listable'] = (string) $room['listable'];
  289. }
  290. if (isset($expectedRoom['participantType'])) {
  291. $data['participantType'] = (string) $room['participantType'];
  292. }
  293. if (isset($expectedRoom['sipEnabled'])) {
  294. $data['sipEnabled'] = (string) $room['sipEnabled'];
  295. }
  296. if (isset($expectedRoom['callFlag'])) {
  297. $data['callFlag'] = (int) $room['callFlag'];
  298. }
  299. if (isset($expectedRoom['lobbyState'])) {
  300. $data['lobbyState'] = (int) $room['lobbyState'];
  301. }
  302. if (isset($expectedRoom['breakoutRoomMode'])) {
  303. $data['breakoutRoomMode'] = (int) $room['breakoutRoomMode'];
  304. }
  305. if (isset($expectedRoom['breakoutRoomStatus'])) {
  306. $data['breakoutRoomStatus'] = (int) $room['breakoutRoomStatus'];
  307. }
  308. if (isset($expectedRoom['attendeePin'])) {
  309. $data['attendeePin'] = $room['attendeePin'] ? '**PIN**' : '';
  310. }
  311. if (isset($expectedRoom['lastMessage'])) {
  312. $data['lastMessage'] = $room['lastMessage'] ? $room['lastMessage']['message'] : '';
  313. }
  314. if (isset($expectedRoom['unreadMessages'])) {
  315. $data['unreadMessages'] = (int) $room['unreadMessages'];
  316. }
  317. if (isset($expectedRoom['unreadMention'])) {
  318. $data['unreadMention'] = (int) $room['unreadMention'];
  319. }
  320. if (isset($expectedRoom['unreadMentionDirect'])) {
  321. $data['unreadMentionDirect'] = (int) $room['unreadMentionDirect'];
  322. }
  323. if (isset($expectedRoom['messageExpiration'])) {
  324. $data['messageExpiration'] = (int) $room['messageExpiration'];
  325. }
  326. if (isset($expectedRoom['participants'])) {
  327. throw new \Exception('participants key needs to be checked via participants endpoint');
  328. }
  329. return $data;
  330. }, $rooms, $formData->getHash()));
  331. }
  332. /**
  333. * @Then /^user "([^"]*)" has the following invitations \((v1)\)$/
  334. *
  335. * @param string $user
  336. * @param string $apiVersion
  337. * @param TableNode|null $formData
  338. */
  339. public function userHasInvites(string $user, string $apiVersion, TableNode $formData = null): void {
  340. $this->setCurrentUser($user);
  341. $this->sendRequest('GET', '/apps/spreed/api/' . $apiVersion . '/federation/invitation');
  342. $this->assertStatusCode($this->response, 200);
  343. $invites = $this->getDataFromResponse($this->response);
  344. if ($formData === null) {
  345. Assert::assertEmpty($invites);
  346. return;
  347. }
  348. $this->assertInvites($invites, $formData);
  349. foreach ($invites as $data) {
  350. self::$remoteToInviteId[$this->translateRemoteServer($data['remote_server']) . '::' . self::$tokenToIdentifier[$data['remote_token']]] = $data['id'];
  351. self::$inviteIdToRemote[$data['id']] = $this->translateRemoteServer($data['remote_server']) . '::' . self::$tokenToIdentifier[$data['remote_token']];
  352. }
  353. }
  354. /**
  355. * @Then /^user "([^"]*)" (accepts|declines) invite to room "([^"]*)" of server "([^"]*)" \((v1)\)$/
  356. *
  357. * @param string $user
  358. * @param string $roomName
  359. * @param string $server
  360. * @param string $apiVersion
  361. * @param TableNode|null $formData
  362. */
  363. public function userAcceptsDeclinesRemoteInvite(string $user, string $acceptsDeclines, string $roomName, string $server, string $apiVersion, TableNode $formData = null): void {
  364. $inviteId = self::$remoteToInviteId[$server . '::' . $roomName];
  365. $verb = $acceptsDeclines === 'accepts' ? 'POST' : 'DELETE';
  366. $this->setCurrentUser($user);
  367. if ($server === 'LOCAL') {
  368. $this->sendRemoteRequest($verb, '/apps/spreed/api/' . $apiVersion . '/federation/invitation/' . $inviteId);
  369. }
  370. $this->assertStatusCode($this->response, 200);
  371. }
  372. /**
  373. * @param array $invites
  374. * @param TableNode $formData
  375. */
  376. private function assertInvites($invites, TableNode $formData) {
  377. Assert::assertCount(count($formData->getHash()), $invites, 'Invite count does not match');
  378. Assert::assertEquals($formData->getHash(), array_map(function ($invite, $expectedInvite) {
  379. $data = [];
  380. if (isset($expectedInvite['id'])) {
  381. $data['id'] = self::$tokenToIdentifier[$invite['token']];
  382. }
  383. if (isset($expectedInvite['access_token'])) {
  384. $data['access_token'] = (string) $invite['access_token'];
  385. }
  386. if (isset($expectedInvite['remote_token'])) {
  387. $data['remote_token'] = self::$tokenToIdentifier[$invite['remote_token']] ?? 'unknown-token';
  388. }
  389. if (isset($expectedInvite['remote_server'])) {
  390. $data['remote_server'] = $this->translateRemoteServer($invite['remote_server']);
  391. }
  392. return $data;
  393. }, $invites, $formData->getHash()));
  394. }
  395. protected function translateRemoteServer(string $server): string {
  396. $server = str_replace('http://', '', $server);
  397. if ($server === 'localhost:8080') {
  398. return 'LOCAL';
  399. }
  400. if ($server === 'localhost:8180') {
  401. return 'REMOTE';
  402. }
  403. return 'unknown-server';
  404. }
  405. /**
  406. * @Then /^user "([^"]*)" (is|is not) participant of room "([^"]*)" \((v4)\)$/
  407. *
  408. * @param string $user
  409. * @param string $isOrNotParticipant
  410. * @param string $identifier
  411. * @param string $apiVersion
  412. * @param TableNode|null $formData
  413. */
  414. public function userIsParticipantOfRoom(string $user, string $isOrNotParticipant, string $identifier, string $apiVersion, TableNode $formData = null): void {
  415. if (strpos($user, 'guest') === 0) {
  416. $this->guestIsParticipantOfRoom($user, $isOrNotParticipant, $identifier, $apiVersion, $formData);
  417. return;
  418. }
  419. $this->setCurrentUser($user);
  420. $this->sendRequest('GET', '/apps/spreed/api/' . $apiVersion . '/room');
  421. $this->assertStatusCode($this->response, 200);
  422. $isParticipant = $isOrNotParticipant === 'is';
  423. $rooms = $this->getDataFromResponse($this->response);
  424. $rooms = array_filter($rooms, function ($room) {
  425. return $room['type'] !== 4;
  426. });
  427. if ($isParticipant) {
  428. Assert::assertNotEmpty($rooms);
  429. }
  430. foreach ($rooms as $room) {
  431. if (self::$tokenToIdentifier[$room['token']] === $identifier) {
  432. Assert::assertEquals($isParticipant, true, 'Room ' . $identifier . ' found in user´s room list');
  433. if ($formData) {
  434. $this->assertRooms([$room], $formData);
  435. }
  436. return;
  437. }
  438. }
  439. Assert::assertEquals($isParticipant, false, 'Room ' . $identifier . ' not found in user´s room list');
  440. }
  441. /**
  442. * @Then /^user "([^"]*)" sees the following attendees in room "([^"]*)" with (\d+) \((v4)\)$/
  443. *
  444. * @param string $user
  445. * @param string $identifier
  446. * @param int $statusCode
  447. * @param string $apiVersion
  448. * @param TableNode $formData
  449. */
  450. public function userSeesAttendeesInRoom(string $user, string $identifier, int $statusCode, string $apiVersion, TableNode $formData = null): void {
  451. $this->setCurrentUser($user);
  452. $this->sendRequest('GET', '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier] . '/participants');
  453. $this->assertStatusCode($this->response, $statusCode);
  454. if ($formData instanceof TableNode) {
  455. $attendees = $this->getDataFromResponse($this->response);
  456. $expectedKeys = array_flip($formData->getRows()[0]);
  457. $result = [];
  458. foreach ($attendees as $attendee) {
  459. $data = [];
  460. if (isset($expectedKeys['actorType'])) {
  461. $data['actorType'] = $attendee['actorType'];
  462. }
  463. if (isset($expectedKeys['actorId'])) {
  464. $data['actorId'] = $attendee['actorId'];
  465. }
  466. if (isset($expectedKeys['participantType'])) {
  467. $data['participantType'] = (string) $attendee['participantType'];
  468. }
  469. if (isset($expectedKeys['inCall'])) {
  470. $data['inCall'] = (string) $attendee['inCall'];
  471. }
  472. if (isset($expectedKeys['attendeePin'])) {
  473. $data['attendeePin'] = $attendee['attendeePin'] ? '**PIN**' : '';
  474. }
  475. if (isset($expectedKeys['permissions'])) {
  476. $data['permissions'] = (string) $attendee['permissions'];
  477. }
  478. if (isset($expectedKeys['attendeePermissions'])) {
  479. $data['attendeePermissions'] = (string) $attendee['attendeePermissions'];
  480. }
  481. if (!isset(self::$userToAttendeeId[$identifier][$attendee['actorType']])) {
  482. self::$userToAttendeeId[$identifier][$attendee['actorType']] = [];
  483. }
  484. self::$userToAttendeeId[$identifier][$attendee['actorType']][$attendee['actorId']] = $attendee['attendeeId'];
  485. $result[] = $data;
  486. }
  487. $expected = array_map(function ($attendee, $actual) {
  488. if (isset($attendee['actorId']) && substr($attendee['actorId'], 0, strlen('"guest')) === '"guest') {
  489. $attendee['actorId'] = sha1(self::$userToSessionId[trim($attendee['actorId'], '"')]);
  490. }
  491. if (isset($attendee['actorId'], $attendee['actorType']) && $attendee['actorType'] === 'federated_users') {
  492. $attendee['actorId'] .= '@' . rtrim($this->baseRemoteUrl, '/');
  493. }
  494. // Breakout room regex
  495. if (isset($attendee['actorId']) && strpos($attendee['actorId'], '/') === 0 && preg_match($attendee['actorId'], $actual['actorId'])) {
  496. $attendee['actorId'] = $actual['actorId'];
  497. }
  498. if (isset($attendee['participantType'])) {
  499. $attendee['participantType'] = (string)$this->mapParticipantTypeTestInput($attendee['participantType']);
  500. }
  501. return $attendee;
  502. }, $formData->getHash(), $result);
  503. $result = array_map(function ($attendee) {
  504. if (isset($attendee['permissions'])) {
  505. $attendee['permissions'] = $this->mapPermissionsAPIOutput($attendee['permissions']);
  506. }
  507. if (isset($attendee['attendeePermissions'])) {
  508. $attendee['attendeePermissions'] = $this->mapPermissionsAPIOutput($attendee['attendeePermissions']);
  509. }
  510. return $attendee;
  511. }, $result);
  512. usort($expected, [self::class, 'sortAttendees']);
  513. usort($result, [self::class, 'sortAttendees']);
  514. Assert::assertEquals($expected, $result);
  515. } else {
  516. Assert::assertNull($formData);
  517. }
  518. }
  519. /**
  520. * @Then /^user "([^"]*)" loads attendees attendee ids in room "([^"]*)" \((v4)\)$/
  521. *
  522. * @param string $user
  523. * @param string $identifier
  524. * @param string $apiVersion
  525. */
  526. public function userLoadsAttendeeIdsInRoom(string $user, string $identifier, string $apiVersion, TableNode $formData = null): void {
  527. $this->setCurrentUser($user);
  528. $this->sendRequest('GET', '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier] . '/participants');
  529. $this->assertStatusCode($this->response, 200);
  530. $attendees = $this->getDataFromResponse($this->response);
  531. foreach ($attendees as $attendee) {
  532. if (!isset(self::$userToAttendeeId[$identifier][$attendee['actorType']])) {
  533. self::$userToAttendeeId[$identifier][$attendee['actorType']] = [];
  534. }
  535. self::$userToAttendeeId[$identifier][$attendee['actorType']][$attendee['actorId']] = $attendee['attendeeId'];
  536. }
  537. }
  538. protected static function sortAttendees(array $a1, array $a2): int {
  539. if (array_key_exists('participantType', $a1) && array_key_exists('participantType', $a2) && $a1['participantType'] !== $a2['participantType']) {
  540. return $a1['participantType'] <=> $a2['participantType'];
  541. }
  542. if ($a1['actorType'] !== $a2['actorType']) {
  543. return $a1['actorType'] <=> $a2['actorType'];
  544. }
  545. return $a1['actorId'] <=> $a2['actorId'];
  546. }
  547. private function mapParticipantTypeTestInput($participantType) {
  548. if (is_numeric($participantType)) {
  549. return $participantType;
  550. }
  551. switch ($participantType) {
  552. case 'OWNER': return 1;
  553. case 'MODERATOR': return 2;
  554. case 'USER': return 3;
  555. case 'GUEST': return 4;
  556. case 'USER_SELF_JOINED': return 5;
  557. case 'GUEST_MODERATOR': return 6;
  558. }
  559. Assert::fail('Invalid test input value for participant type');
  560. }
  561. private function mapPermissionsTestInput($permissions): int {
  562. if (is_numeric($permissions)) {
  563. return $permissions;
  564. }
  565. $numericPermissions = 0;
  566. foreach (self::$permissionsMap as $char => $int) {
  567. if (strpos($permissions, $char) !== false) {
  568. $numericPermissions += $int;
  569. $permissions = str_replace($char, '', $permissions);
  570. }
  571. }
  572. if (trim($permissions) !== '') {
  573. Assert::fail('Invalid test input value for permissions');
  574. }
  575. return $numericPermissions;
  576. }
  577. private function mapPermissionsAPIOutput($permissions): string {
  578. $permissions = (int) $permissions;
  579. $permissionsString = !$permissions ? 'D' : '';
  580. foreach (self::$permissionsMap as $char => $int) {
  581. if ($permissions & $int) {
  582. $permissionsString .= $char;
  583. $permissions &= ~ $int;
  584. }
  585. }
  586. if ($permissions !== 0) {
  587. Assert::fail('Invalid API output value for permissions');
  588. }
  589. return $permissionsString;
  590. }
  591. /**
  592. * @param string $guest
  593. * @param string $isOrNotParticipant
  594. * @param string $identifier
  595. * @param string $apiVersion
  596. * @param TableNode|null $formData
  597. */
  598. private function guestIsParticipantOfRoom(string $guest, string $isOrNotParticipant, string $identifier, string $apiVersion, TableNode $formData = null): void {
  599. $this->setCurrentUser($guest);
  600. $this->sendRequest('GET', '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier]);
  601. $response = $this->getDataFromResponse($this->response);
  602. $isParticipant = $isOrNotParticipant === 'is';
  603. if ($formData) {
  604. $rooms = [$response];
  605. $this->assertRooms($rooms, $formData);
  606. }
  607. if ($isParticipant) {
  608. $this->assertStatusCode($this->response, 200);
  609. Assert::assertEquals(self::$userToSessionId[$guest], $response['sessionId']);
  610. return;
  611. }
  612. if ($this->response->getStatusCode() === 200) {
  613. // Public rooms can always be got, but if the guest is not a
  614. // participant the sessionId will be 0.
  615. Assert::assertEquals(0, $response['sessionId']);
  616. return;
  617. }
  618. $this->assertStatusCode($this->response, 404);
  619. }
  620. /**
  621. * @Then /^user "([^"]*)" creates room "([^"]*)" \((v4)\)$/
  622. *
  623. * @param string $user
  624. * @param string $identifier
  625. * @param string $apiVersion
  626. * @param TableNode|null $formData
  627. */
  628. public function userCreatesRoom(string $user, string $identifier, string $apiVersion, TableNode $formData = null): void {
  629. $this->userCreatesRoomWith($user, $identifier, 201, $apiVersion, $formData);
  630. }
  631. /**
  632. * @Then /^user "([^"]*)" creates room "([^"]*)" with (\d+) \((v4)\)$/
  633. *
  634. * @param string $user
  635. * @param string $identifier
  636. * @param int $statusCode
  637. * @param string $apiVersion
  638. * @param TableNode|null $formData
  639. */
  640. public function userCreatesRoomWith(string $user, string $identifier, int $statusCode, string $apiVersion = 'v1', TableNode $formData = null): void {
  641. $this->setCurrentUser($user);
  642. $this->sendRequest('POST', '/apps/spreed/api/' . $apiVersion . '/room', $formData);
  643. $this->assertStatusCode($this->response, $statusCode);
  644. $response = $this->getDataFromResponse($this->response);
  645. if ($statusCode === 201) {
  646. self::$identifierToToken[$identifier] = $response['token'];
  647. self::$tokenToIdentifier[$response['token']] = $identifier;
  648. }
  649. }
  650. /**
  651. * @Then /^user "([^"]*)" tries to create room with (\d+) \((v4)\)$/
  652. *
  653. * @param string $user
  654. * @param int $statusCode
  655. * @param string $apiVersion
  656. * @param TableNode|null $formData
  657. */
  658. public function userTriesToCreateRoom(string $user, int $statusCode, string $apiVersion = 'v1', TableNode $formData = null): void {
  659. $this->setCurrentUser($user);
  660. $this->sendRequest('POST', '/apps/spreed/api/' . $apiVersion . '/room', $formData);
  661. $this->assertStatusCode($this->response, $statusCode);
  662. }
  663. /**
  664. * @Then /^user "([^"]*)" gets the room for path "([^"]*)" with (\d+) \((v1)\)$/
  665. *
  666. * @param string $user
  667. * @param string $path
  668. * @param int $statusCode
  669. * @param string $apiVersion
  670. */
  671. public function userGetsTheRoomForPath(string $user, string $path, int $statusCode, string $apiVersion): void {
  672. $fileId = $this->getFileIdForPath($user, $path);
  673. $this->setCurrentUser($user);
  674. $this->sendRequest('GET', '/apps/spreed/api/' . $apiVersion . '/file/' . $fileId);
  675. $this->assertStatusCode($this->response, $statusCode);
  676. if ($statusCode !== 200) {
  677. return;
  678. }
  679. $response = $this->getDataFromResponse($this->response);
  680. $identifier = 'file ' . $path . ' room';
  681. self::$identifierToToken[$identifier] = $response['token'];
  682. self::$tokenToIdentifier[$response['token']] = $identifier;
  683. }
  684. /**
  685. * @param string $user
  686. * @param string $path
  687. * @return int
  688. */
  689. private function getFileIdForPath($user, $path) {
  690. $this->currentUser = $user;
  691. $url = "/$user/$path";
  692. $headers = [];
  693. $headers['Depth'] = 0;
  694. $body = '<d:propfind xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns">' .
  695. ' <d:prop>' .
  696. ' <oc:fileid/>' .
  697. ' </d:prop>' .
  698. '</d:propfind>';
  699. $this->sendingToDav('PROPFIND', $url, $headers, $body);
  700. $this->assertStatusCode($this->response, 207);
  701. $xmlResponse = simplexml_load_string($this->response->getBody());
  702. $xmlResponse->registerXPathNamespace('oc', 'http://owncloud.org/ns');
  703. return (int)$xmlResponse->xpath('//oc:fileid')[0];
  704. }
  705. /**
  706. * @param string $verb
  707. * @param string $url
  708. * @param array $headers
  709. * @param string $body
  710. */
  711. private function sendingToDav(string $verb, string $url, array $headers = null, string $body = null) {
  712. $fullUrl = $this->baseUrl . 'remote.php/dav/files' . $url;
  713. $client = new Client();
  714. $options = [];
  715. if ($this->currentUser === 'admin') {
  716. $options['auth'] = 'admin';
  717. } else {
  718. $options['auth'] = [$this->currentUser, self::TEST_PASSWORD];
  719. }
  720. $options['headers'] = [
  721. 'OCS_APIREQUEST' => 'true'
  722. ];
  723. if ($headers !== null) {
  724. $options['headers'] = array_merge($options['headers'], $headers);
  725. }
  726. if ($body !== null) {
  727. $options['body'] = $body;
  728. }
  729. try {
  730. $this->response = $client->{$verb}($fullUrl, $options);
  731. } catch (GuzzleHttp\Exception\ClientException $ex) {
  732. $this->response = $ex->getResponse();
  733. }
  734. }
  735. /**
  736. * @Then /^user "([^"]*)" gets the room for last share with (\d+) \((v1)\)$/
  737. *
  738. * @param string $user
  739. * @param int $statusCode
  740. * @param string $apiVersion
  741. */
  742. public function userGetsTheRoomForLastShare(string $user, int $statusCode, string $apiVersion): void {
  743. $shareToken = $this->sharingContext->getLastShareToken();
  744. $this->setCurrentUser($user);
  745. $this->sendRequest('GET', '/apps/spreed/api/' . $apiVersion . '/publicshare/' . $shareToken);
  746. $this->assertStatusCode($this->response, $statusCode);
  747. if ($statusCode !== 200) {
  748. return;
  749. }
  750. $response = $this->getDataFromResponse($this->response);
  751. $identifier = 'file last share room';
  752. self::$identifierToToken[$identifier] = $response['token'];
  753. self::$tokenToIdentifier[$response['token']] = $identifier;
  754. }
  755. /**
  756. * @Then /^user "([^"]*)" creates the password request room for last share with (\d+) \((v1)\)$/
  757. *
  758. * @param string $user
  759. * @param int $statusCode
  760. * @param string $apiVersion
  761. */
  762. public function userCreatesThePasswordRequestRoomForLastShare(string $user, int $statusCode, string $apiVersion): void {
  763. $shareToken = $this->sharingContext->getLastShareToken();
  764. $this->setCurrentUser($user);
  765. $this->sendRequest('POST', '/apps/spreed/api/' . $apiVersion . '/publicshareauth', ['shareToken' => $shareToken]);
  766. $this->assertStatusCode($this->response, $statusCode);
  767. if ($statusCode !== 201) {
  768. return;
  769. }
  770. $response = $this->getDataFromResponse($this->response);
  771. $identifier = 'password request for last share room';
  772. self::$identifierToToken[$identifier] = $response['token'];
  773. self::$tokenToIdentifier[$response['token']] = $identifier;
  774. }
  775. /**
  776. * @Then /^user "([^"]*)" joins room "([^"]*)" with (\d+) \((v4)\)$/
  777. *
  778. * @param string $user
  779. * @param string $identifier
  780. * @param int $statusCode
  781. * @param string $apiVersion
  782. * @param TableNode|null $formData
  783. */
  784. public function userJoinsRoom(string $user, string $identifier, int $statusCode, string $apiVersion, TableNode $formData = null): void {
  785. $this->setCurrentUser($user);
  786. $this->sendRequest(
  787. 'POST', '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier] . '/participants/active',
  788. $formData
  789. );
  790. $this->assertStatusCode($this->response, $statusCode);
  791. if ($statusCode !== 200) {
  792. return;
  793. }
  794. $response = $this->getDataFromResponse($this->response);
  795. if (array_key_exists('sessionId', $response)) {
  796. // In the chat guest users are identified by their sessionId. The
  797. // sessionId is larger than the size of the actorId column in the
  798. // database, though, so the ID stored in the database and returned
  799. // in chat messages is a hashed version instead.
  800. self::$sessionIdToUser[sha1($response['sessionId'])] = $user;
  801. self::$userToSessionId[$user] = $response['sessionId'];
  802. }
  803. }
  804. /**
  805. * @Then /^user "([^"]*)" sets notifications to (default|disabled|mention|all) for room "([^"]*)" \((v4)\)$/
  806. *
  807. * @param string $user
  808. * @param string $level
  809. * @param string $identifier
  810. * @param string $apiVersion
  811. */
  812. public function userSetsNotificationLevelForRoom(string $user, string $level, string $identifier, string $apiVersion): void {
  813. $this->setCurrentUser($user);
  814. $intLevel = 0; // default
  815. if ($level === 'disabled') {
  816. $intLevel = 3;
  817. } elseif ($level === 'mention') {
  818. $intLevel = 2;
  819. } elseif ($level === 'all') {
  820. $intLevel = 1;
  821. }
  822. $this->sendRequest(
  823. 'POST', '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier] . '/notify',
  824. new TableNode([
  825. ['level', $intLevel],
  826. ])
  827. );
  828. $this->assertStatusCode($this->response, 200);
  829. }
  830. /**
  831. * @Then /^user "([^"]*)" leaves room "([^"]*)" with (\d+) \((v4)\)$/
  832. *
  833. * @param string $user
  834. * @param string $identifier
  835. * @param int $statusCode
  836. * @param string $apiVersion
  837. */
  838. public function userExitsRoom(string $user, string $identifier, int $statusCode, string $apiVersion): void {
  839. $this->setCurrentUser($user);
  840. $this->sendRequest('DELETE', '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier] . '/participants/active');
  841. $this->assertStatusCode($this->response, $statusCode);
  842. }
  843. /**
  844. * @Then /^user "([^"]*)" removes themselves from room "([^"]*)" with (\d+) \((v4)\)$/
  845. *
  846. * @param string $user
  847. * @param string $identifier
  848. * @param int $statusCode
  849. * @param string $apiVersion
  850. */
  851. public function userLeavesRoom(string $user, string $identifier, int $statusCode, string $apiVersion): void {
  852. $this->setCurrentUser($user);
  853. $this->sendRequest('DELETE', '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier] . '/participants/self');
  854. $this->assertStatusCode($this->response, $statusCode);
  855. }
  856. /**
  857. * @Then /^user "([^"]*)" removes "([^"]*)" from room "([^"]*)" with (\d+) \((v4)\)$/
  858. *
  859. * @param string $user
  860. * @param string $toRemove
  861. * @param string $identifier
  862. * @param int $statusCode
  863. * @param string $apiVersion
  864. */
  865. public function userRemovesUserFromRoom(string $user, string $toRemove, string $identifier, int $statusCode, string$apiVersion): void {
  866. if ($toRemove === 'stranger') {
  867. $attendeeId = 123456789;
  868. } else {
  869. $attendeeId = $this->getAttendeeId('users', $toRemove, $identifier, $statusCode === 200 ? $user : null);
  870. }
  871. $this->setCurrentUser($user);
  872. $this->sendRequest(
  873. 'DELETE', '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier] . '/attendees',
  874. new TableNode([['attendeeId', $attendeeId]])
  875. );
  876. $this->assertStatusCode($this->response, $statusCode);
  877. }
  878. /**
  879. * @Then /^user "([^"]*)" removes (user|group|email) "([^"]*)" from room "([^"]*)" with (\d+) \((v4)\)$/
  880. *
  881. * @param string $user
  882. * @param string $actorType
  883. * @param string $actorId
  884. * @param string $identifier
  885. * @param int $statusCode
  886. * @param string $apiVersion
  887. */
  888. public function userRemovesAttendeeFromRoom(string $user, string $actorType, string $actorId, string $identifier, int $statusCode, string$apiVersion): void {
  889. if ($actorId === 'stranger') {
  890. $attendeeId = 123456789;
  891. } else {
  892. $attendeeId = $this->getAttendeeId($actorType . 's', $actorId, $identifier, $statusCode === 200 ? $user : null);
  893. }
  894. $this->setCurrentUser($user);
  895. $this->sendRequest(
  896. 'DELETE', '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier] . '/attendees',
  897. new TableNode([['attendeeId', $attendeeId]])
  898. );
  899. $this->assertStatusCode($this->response, $statusCode);
  900. }
  901. /**
  902. * @Then /^user "([^"]*)" deletes room "([^"]*)" with (\d+) \((v4)\)$/
  903. *
  904. * @param string $user
  905. * @param string $identifier
  906. * @param int $statusCode
  907. * @param string $apiVersion
  908. */
  909. public function userDeletesRoom(string $user, string $identifier, int $statusCode, string $apiVersion): void {
  910. $this->setCurrentUser($user);
  911. $this->sendRequest('DELETE', '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier]);
  912. $this->assertStatusCode($this->response, $statusCode);
  913. }
  914. /**
  915. * @Then /^user "([^"]*)" gets room "([^"]*)" with (\d+) \((v4)\)$/
  916. *
  917. * @param string $user
  918. * @param string $identifier
  919. * @param int $statusCode
  920. * @param string $apiVersion
  921. */
  922. public function userGetsRoom(string $user, string $identifier, int $statusCode, string $apiVersion): void {
  923. $this->setCurrentUser($user);
  924. $this->sendRequest('GET', '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier]);
  925. $this->assertStatusCode($this->response, $statusCode);
  926. }
  927. /**
  928. * @Then /^user "([^"]*)" renames room "([^"]*)" to "([^"]*)" with (\d+) \((v4)\)$/
  929. *
  930. * @param string $user
  931. * @param string $identifier
  932. * @param string $newName
  933. * @param int $statusCode
  934. * @param string $apiVersion
  935. */
  936. public function userRenamesRoom(string $user, string $identifier, string $newName, int $statusCode, string $apiVersion): void {
  937. $this->setCurrentUser($user);
  938. $this->sendRequest(
  939. 'PUT', '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier],
  940. new TableNode([['roomName', $newName]])
  941. );
  942. $this->assertStatusCode($this->response, $statusCode);
  943. }
  944. /**
  945. * @When /^user "([^"]*)" sets description for room "([^"]*)" to "([^"]*)" with (\d+) \((v4)\)$/
  946. *
  947. * @param string $user
  948. * @param string $identifier
  949. * @param string $description
  950. * @param int $statusCode
  951. * @param string $apiVersion
  952. * @param TableNode
  953. */
  954. public function userSetsDescriptionForRoomTo(string $user, string $identifier, string $description, int $statusCode, string $apiVersion): void {
  955. $this->setCurrentUser($user);
  956. $this->sendRequest(
  957. 'PUT', '/apps/spreed/api/' .$apiVersion . '/room/' . self::$identifierToToken[$identifier] . '/description',
  958. new TableNode([['description', $description]])
  959. );
  960. $this->assertStatusCode($this->response, $statusCode);
  961. }
  962. /**
  963. * @When /^user "([^"]*)" sets password "([^"]*)" for room "([^"]*)" with (\d+) \((v4)\)$/
  964. *
  965. * @param string $user
  966. * @param string $password
  967. * @param string $identifier
  968. * @param int $statusCode
  969. * @param string $apiVersion
  970. */
  971. public function userSetsTheRoomPassword(string $user, string $password, string $identifier, int $statusCode, string $apiVersion): void {
  972. $this->setCurrentUser($user);
  973. $this->sendRequest(
  974. 'PUT', '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier] . '/password',
  975. new TableNode([['password', $password]])
  976. );
  977. $this->assertStatusCode($this->response, $statusCode);
  978. }
  979. /**
  980. * @When /^user "([^"]*)" sets lobby state for room "([^"]*)" to "([^"]*)" with (\d+) \((v4)\)$/
  981. *
  982. * @param string $user
  983. * @param string $identifier
  984. * @param string $lobbyStateString
  985. * @param int $statusCode
  986. * @param string $apiVersion
  987. */
  988. public function userSetsLobbyStateForRoomTo(string $user, string $identifier, string $lobbyStateString, int $statusCode, string $apiVersion): void {
  989. if ($lobbyStateString === 'no lobby') {
  990. $lobbyState = 0;
  991. } elseif ($lobbyStateString === 'non moderators') {
  992. $lobbyState = 1;
  993. } else {
  994. Assert::fail('Invalid lobby state');
  995. }
  996. $this->setCurrentUser($user);
  997. $this->sendRequest(
  998. 'PUT', '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier] . '/webinar/lobby',
  999. new TableNode([['state', $lobbyState]])
  1000. );
  1001. $this->assertStatusCode($this->response, $statusCode);
  1002. }
  1003. /**
  1004. * @When /^user "([^"]*)" sets lobby state for room "([^"]*)" to "([^"]*)" for (\d+) seconds with (\d+) \((v4)\)$/
  1005. *
  1006. * @param string $user
  1007. * @param string $identifier
  1008. * @param string $lobbyStateString
  1009. * @param int $lobbyTimer
  1010. * @param int $statusCode
  1011. * @param string $apiVersion
  1012. */
  1013. public function userSetsLobbyStateAndTimerForRoom(string $user, string $identifier, string $lobbyStateString, int $lobbyTimer, int $statusCode, string $apiVersion): void {
  1014. if ($lobbyStateString === 'no lobby') {
  1015. $lobbyState = 0;
  1016. } elseif ($lobbyStateString === 'non moderators') {
  1017. $lobbyState = 1;
  1018. } else {
  1019. Assert::fail('Invalid lobby state');
  1020. }
  1021. $this->setCurrentUser($user);
  1022. $this->sendRequest(
  1023. 'PUT', '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier] . '/webinar/lobby',
  1024. new TableNode([['state', $lobbyState], ['timer', time() + $lobbyTimer]])
  1025. );
  1026. $this->assertStatusCode($this->response, $statusCode);
  1027. }
  1028. /**
  1029. * @When /^user "([^"]*)" sets SIP state for room "([^"]*)" to "([^"]*)" with (\d+) \((v4)\)$/
  1030. *
  1031. * @param string $user
  1032. * @param string $identifier
  1033. * @param string $SIPStateString
  1034. * @param int $statusCode
  1035. * @param string $apiVersion
  1036. */
  1037. public function userSetsSIPStateForRoomTo(string $user, string $identifier, string $SIPStateString, int $statusCode, string $apiVersion): void {
  1038. if ($SIPStateString === 'disabled') {
  1039. $SIPState = 0;
  1040. } elseif ($SIPStateString === 'enabled') {
  1041. $SIPState = 1;
  1042. } elseif ($SIPStateString === 'no pin') {
  1043. $SIPState = 2;
  1044. } else {
  1045. Assert::fail('Invalid SIP state');
  1046. }
  1047. $this->setCurrentUser($user);
  1048. $this->sendRequest(
  1049. 'PUT', '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier] . '/webinar/sip',
  1050. new TableNode([['state', $SIPState]])
  1051. );
  1052. $this->assertStatusCode($this->response, $statusCode);
  1053. }
  1054. /**
  1055. * @Then /^user "([^"]*)" makes room "([^"]*)" (public|private) with (\d+) \((v4)\)$/
  1056. *
  1057. * @param string $user
  1058. * @param string $identifier
  1059. * @param string $newType
  1060. * @param int $statusCode
  1061. * @param string $apiVersion
  1062. */
  1063. public function userChangesTypeOfTheRoom(string $user, string $identifier, string $newType, int $statusCode, string $apiVersion): void {
  1064. $this->setCurrentUser($user);
  1065. $this->sendRequest(
  1066. $newType === 'public' ? 'POST' : 'DELETE',
  1067. '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier] . '/public'
  1068. );
  1069. $this->assertStatusCode($this->response, $statusCode);
  1070. }
  1071. /**
  1072. * @Then /^user "([^"]*)" (locks|unlocks) room "([^"]*)" with (\d+) \((v4)\)$/
  1073. *
  1074. * @param string $user
  1075. * @param string $newState
  1076. * @param string $identifier
  1077. * @param int $statusCode
  1078. * @param string $apiVersion
  1079. */
  1080. public function userChangesReadOnlyStateOfTheRoom(string $user, string $newState, string $identifier, int $statusCode, string $apiVersion): void {
  1081. $this->setCurrentUser($user);
  1082. $this->sendRequest(
  1083. 'PUT', '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier] . '/read-only',
  1084. new TableNode([['state', $newState === 'unlocks' ? 0 : 1]])
  1085. );
  1086. $this->assertStatusCode($this->response, $statusCode);
  1087. }
  1088. /**
  1089. * @Then /^user "([^"]*)" allows listing room "([^"]*)" for "(none|users|all|\d+)" with (\d+) \((v4)\)$/
  1090. *
  1091. * @param string $user
  1092. * @param string $identifier
  1093. * @param string|int $newState
  1094. * @param int $statusCode
  1095. * @param string $apiVersion
  1096. */
  1097. public function userChangesListableScopeOfTheRoom(string $user, string $identifier, $newState, int $statusCode, string $apiVersion): void {
  1098. $this->setCurrentUser($user);
  1099. if ($newState === 'none') {
  1100. $newStateValue = 0; // Room::LISTABLE_NONE
  1101. } elseif ($newState === 'users') {
  1102. $newStateValue = 1; // Room::LISTABLE_USERS
  1103. } elseif ($newState === 'all') {
  1104. $newStateValue = 2; // Room::LISTABLE_ALL
  1105. } elseif (is_numeric($newState)) {
  1106. $newStateValue = (int)$newState;
  1107. } else {
  1108. Assert::fail('Invalid listable scope value');
  1109. }
  1110. $this->sendRequest(
  1111. 'PUT', '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier] . '/listable',
  1112. new TableNode([['scope', $newStateValue]])
  1113. );
  1114. $this->assertStatusCode($this->response, $statusCode);
  1115. }
  1116. /**
  1117. * @Then /^user "([^"]*)" adds (user|group|email|circle|remote) "([^"]*)" to room "([^"]*)" with (\d+) \((v4)\)$/
  1118. *
  1119. * @param string $user
  1120. * @param string $newType
  1121. * @param string $newId
  1122. * @param string $identifier
  1123. * @param int $statusCode
  1124. * @param string $apiVersion
  1125. */
  1126. public function userAddAttendeeToRoom(string $user, string $newType, string $newId, string $identifier, int $statusCode, string $apiVersion): void {
  1127. $this->setCurrentUser($user);
  1128. if ($newType === 'remote') {
  1129. $newId .= '@' . $this->baseRemoteUrl;
  1130. }
  1131. $this->sendRequest(
  1132. 'POST', '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier] . '/participants',
  1133. new TableNode([
  1134. ['source', $newType . 's'],
  1135. ['newParticipant', $newId],
  1136. ])
  1137. );
  1138. $this->assertStatusCode($this->response, $statusCode);
  1139. }
  1140. /**
  1141. * @Then /^user "([^"]*)" (promotes|demotes) "([^"]*)" in room "([^"]*)" with (\d+) \((v4)\)$/
  1142. *
  1143. * @param string $user
  1144. * @param string $isPromotion
  1145. * @param string $participant
  1146. * @param string $identifier
  1147. * @param int $statusCode
  1148. * @param string $apiVersion
  1149. */
  1150. public function userPromoteDemoteInRoom(string $user, string $isPromotion, string $participant, string $identifier, int $statusCode, string $apiVersion): void {
  1151. if ($participant === 'stranger') {
  1152. $attendeeId = 123456789;
  1153. } elseif (strpos($participant, 'guest') === 0) {
  1154. $sessionId = self::$userToSessionId[$participant];
  1155. $attendeeId = $this->getAttendeeId('guests', sha1($sessionId), $identifier, $statusCode === 200 ? $user : null);
  1156. } else {
  1157. $attendeeId = $this->getAttendeeId('users', $participant, $identifier, $statusCode === 200 ? $user : null);
  1158. }
  1159. $requestParameters = [['attendeeId', $attendeeId]];
  1160. $this->setCurrentUser($user);
  1161. $this->sendRequest(
  1162. $isPromotion === 'promotes' ? 'POST' : 'DELETE',
  1163. '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier] . '/moderators',
  1164. new TableNode($requestParameters)
  1165. );
  1166. $this->assertStatusCode($this->response, $statusCode);
  1167. }
  1168. /**
  1169. * @When /^user "([^"]*)" sets permissions for "([^"]*)" in room "([^"]*)" to "([^"]*)" with (\d+) \((v4)\)$/
  1170. *
  1171. * @param string $user
  1172. * @param string $participant
  1173. * @param string $identifier
  1174. * @param string $permissionsString
  1175. * @param int $statusCode
  1176. * @param string $apiVersion
  1177. */
  1178. public function userSetsPermissionsForInRoomTo(string $user, string $participant, string $identifier, string $permissionsString, int $statusCode, string $apiVersion): void {
  1179. if ($participant === 'stranger') {
  1180. $attendeeId = 123456789;
  1181. } elseif (strpos($participant, 'guest') === 0) {
  1182. $sessionId = self::$userToSessionId[$participant];
  1183. $attendeeId = $this->getAttendeeId('guests', sha1($sessionId), $identifier, $statusCode === 200 ? $user : null);
  1184. } else {
  1185. $attendeeId = $this->getAttendeeId('users', $participant, $identifier, $statusCode === 200 ? $user : null);
  1186. }
  1187. $permissions = $this->mapPermissionsTestInput($permissionsString);
  1188. $requestParameters = [
  1189. ['attendeeId', $attendeeId],
  1190. ['permissions', $permissions],
  1191. ['method', 'set'],
  1192. ];
  1193. $this->setCurrentUser($user);
  1194. $this->sendRequest(
  1195. 'PUT', '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier] . '/attendees/permissions',
  1196. new TableNode($requestParameters)
  1197. );
  1198. $this->assertStatusCode($this->response, $statusCode);
  1199. }
  1200. /**
  1201. * @When /^user "([^"]*)" sets (call|default) permissions for room "([^"]*)" to "([^"]*)" with (\d+) \((v4)\)$/
  1202. *
  1203. * @param string $user
  1204. * @param string $mode
  1205. * @param string $identifier
  1206. * @param string $permissionsString
  1207. * @param int $statusCode
  1208. * @param string $apiVersion
  1209. */
  1210. public function userSetsPermissionsForRoomTo(string $user, string $mode, string $identifier, string $permissionsString, int $statusCode, string $apiVersion): void {
  1211. $permissions = $this->mapPermissionsTestInput($permissionsString);
  1212. $requestParameters = [
  1213. ['permissions', $permissions],
  1214. ];
  1215. $this->setCurrentUser($user);
  1216. $this->sendRequest(
  1217. 'PUT', '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier] . '/permissions/' . $mode,
  1218. new TableNode($requestParameters)
  1219. );
  1220. $this->assertStatusCode($this->response, $statusCode);
  1221. }
  1222. /**
  1223. * @Then /^user "([^"]*)" joins call "([^"]*)" with (\d+) \((v4)\)$/
  1224. *
  1225. * @param string $user
  1226. * @param string $identifier
  1227. * @param int $statusCode
  1228. * @param string $apiVersion
  1229. * @param TableNode|null $formData
  1230. */
  1231. public function userJoinsCall(string $user, string $identifier, int $statusCode, string $apiVersion, TableNode $formData = null): void {
  1232. $this->setCurrentUser($user);
  1233. $this->sendRequest(
  1234. 'POST', '/apps/spreed/api/' . $apiVersion . '/call/' . self::$identifierToToken[$identifier],
  1235. $formData
  1236. );
  1237. $this->assertStatusCode($this->response, $statusCode);
  1238. $response = $this->getDataFromResponse($this->response);
  1239. if (array_key_exists('sessionId', $response)) {
  1240. // In the chat guest users are identified by their sessionId. The
  1241. // sessionId is larger than the size of the actorId column in the
  1242. // database, though, so the ID stored in the database and returned
  1243. // in chat messages is a hashed version instead.
  1244. self::$sessionIdToUser[sha1($response['sessionId'])] = $user;
  1245. self::$userToSessionId[$user] = $response['sessionId'];
  1246. }
  1247. }
  1248. /**
  1249. * @Then /^user "([^"]*)" updates call flags in room "([^"]*)" to "([^"]*)" with (\d+) \((v4)\)$/
  1250. *
  1251. * @param string $user
  1252. * @param string $identifier
  1253. * @param string $flags
  1254. * @param int $statusCode
  1255. * @param string $apiVersion
  1256. */
  1257. public function userUpdatesCallFlagsInRoomTo(string $user, string $identifier, string $flags, int $statusCode, string $apiVersion): void {
  1258. $this->setCurrentUser($user);
  1259. $this->sendRequest(
  1260. 'PUT', '/apps/spreed/api/' . $apiVersion . '/call/' . self::$identifierToToken[$identifier],
  1261. new TableNode([['flags', $flags]])
  1262. );
  1263. $this->assertStatusCode($this->response, $statusCode);
  1264. }
  1265. /**
  1266. * @Then /^user "([^"]*)" pings (user|guest) "([^"]*)" to join call "([^"]*)" with (\d+) \((v4)\)$/
  1267. *
  1268. * @param string $user
  1269. * @param string $actorType
  1270. * @param string $actorId
  1271. * @param string $identifier
  1272. * @param int $statusCode
  1273. * @param string $apiVersion
  1274. */
  1275. public function userPingsAttendeeInRoomTo(string $user, string $actorType, string $actorId, string $identifier, int $statusCode, string $apiVersion): void {
  1276. $this->setCurrentUser($user);
  1277. $this->sendRequest('POST', '/apps/spreed/api/' . $apiVersion . '/call/' . self::$identifierToToken[$identifier] . '/ring/' . self::$userToAttendeeId[$identifier][$actorType . 's'][$actorId]);
  1278. $this->assertStatusCode($this->response, $statusCode);
  1279. }
  1280. /**
  1281. * @Then /^user "([^"]*)" leaves call "([^"]*)" with (\d+) \((v4)\)$/
  1282. *
  1283. * @param string $user
  1284. * @param string $identifier
  1285. * @param int $statusCode
  1286. * @param string $apiVersion
  1287. */
  1288. public function userLeavesCall(string $user, string $identifier, int $statusCode, string $apiVersion): void {
  1289. $this->setCurrentUser($user);
  1290. $this->sendRequest('DELETE', '/apps/spreed/api/' . $apiVersion . '/call/' . self::$identifierToToken[$identifier]);
  1291. $this->assertStatusCode($this->response, $statusCode);
  1292. }
  1293. /**
  1294. * @Then /^user "([^"]*)" ends call "([^"]*)" with (\d+) \((v4)\)$/
  1295. *
  1296. * @param string $user
  1297. * @param string $identifier
  1298. * @param int $statusCode
  1299. * @param string $apiVersion
  1300. */
  1301. public function userEndsCall(string $user, string $identifier, int $statusCode, string $apiVersion): void {
  1302. $requestParameters = [
  1303. ['all', true],
  1304. ];
  1305. $this->setCurrentUser($user);
  1306. $this->sendRequest(
  1307. 'DELETE', '/apps/spreed/api/' . $apiVersion . '/call/' . self::$identifierToToken[$identifier],
  1308. new TableNode($requestParameters)
  1309. );
  1310. $this->assertStatusCode($this->response, $statusCode);
  1311. }
  1312. /**
  1313. * @Then /^user "([^"]*)" sees (\d+) peers in call "([^"]*)" with (\d+) \((v4)\)$/
  1314. *
  1315. * @param string $user
  1316. * @param int $numPeers
  1317. * @param string $identifier
  1318. * @param int $statusCode
  1319. * @param string $apiVersion
  1320. */
  1321. public function userSeesPeersInCall(string $user, int $numPeers, string $identifier, int $statusCode, string $apiVersion): void {
  1322. $this->setCurrentUser($user);
  1323. $this->sendRequest('GET', '/apps/spreed/api/' . $apiVersion . '/call/' . self::$identifierToToken[$identifier]);
  1324. $this->assertStatusCode($this->response, $statusCode);
  1325. if ($statusCode === 200) {
  1326. $response = $this->getDataFromResponse($this->response);
  1327. Assert::assertCount((int) $numPeers, $response);
  1328. } else {
  1329. Assert::assertEquals((int) $numPeers, 0);
  1330. }
  1331. }
  1332. /**
  1333. * @Then /^user "([^"]*)" (silent sends|sends) message "([^"]*)" to room "([^"]*)" with (\d+)(?: \((v1)\))?$/
  1334. *
  1335. * @param string $user
  1336. * @param string $sendingMode
  1337. * @param string $message
  1338. * @param string $identifier
  1339. * @param string $statusCode
  1340. * @param string $apiVersion
  1341. */
  1342. public function userSendsMessageToRoom(string $user, string $sendingMode, string $message, string $identifier, string $statusCode, string $apiVersion = 'v1') {
  1343. if ($sendingMode === 'silent sends') {
  1344. $body = new TableNode([['message', $message], ['silent', true]]);
  1345. } else {
  1346. $body = new TableNode([['message', $message]]);
  1347. }
  1348. $this->setCurrentUser($user);
  1349. $this->sendRequest(
  1350. 'POST', '/apps/spreed/api/' . $apiVersion . '/chat/' . self::$identifierToToken[$identifier],
  1351. $body
  1352. );
  1353. $this->assertStatusCode($this->response, $statusCode);
  1354. sleep(1); // make sure Postgres manages the order of the messages
  1355. $response = $this->getDataFromResponse($this->response);
  1356. if (isset($response['id'])) {
  1357. self::$textToMessageId[$message] = $response['id'];
  1358. self::$messageIdToText[$response['id']] = $message;
  1359. }
  1360. }
  1361. /**
  1362. * @Then /^user "([^"]*)" shares rich-object "([^"]*)" "([^"]*)" '([^']*)' to room "([^"]*)" with (\d+)(?: \((v1)\))?$/
  1363. *
  1364. * @param string $user
  1365. * @param string $type
  1366. * @param string $id
  1367. * @param string $metaData
  1368. * @param string $identifier
  1369. * @param string $statusCode
  1370. * @param string $apiVersion
  1371. */
  1372. public function userSharesRichObjectToRoom($user, $type, $id, $metaData, $identifier, $statusCode, $apiVersion = 'v1') {
  1373. $this->setCurrentUser($user);
  1374. $this->sendRequest(
  1375. 'POST', '/apps/spreed/api/' . $apiVersion . '/chat/' . self::$identifierToToken[$identifier] . '/share',
  1376. new TableNode([
  1377. ['objectType', $type],
  1378. ['objectId', $id],
  1379. ['metaData', $metaData],
  1380. ])
  1381. );
  1382. $this->assertStatusCode($this->response, $statusCode);
  1383. sleep(1); // make sure Postgres manages the order of the messages
  1384. $response = $this->getDataFromResponse($this->response);
  1385. if (isset($response['id'])) {
  1386. self::$textToMessageId['shared::' . $type . '::' . $id] = $response['id'];
  1387. }
  1388. }
  1389. /**
  1390. * @Then /^user "([^"]*)" creates a poll in room "([^"]*)" with (\d+)(?: \((v1)\))?$/
  1391. *
  1392. * @param string $user
  1393. * @param string $identifier
  1394. * @param string $statusCode
  1395. * @param string $apiVersion
  1396. */
  1397. public function createPoll(string $user, string $identifier, string $statusCode, string $apiVersion = 'v1', TableNode $formData = null): void {
  1398. $data = $formData->getRowsHash();
  1399. $data['options'] = json_decode($data['options'], true);
  1400. if ($data['resultMode'] === 'public') {
  1401. $data['resultMode'] = 0;
  1402. } elseif ($data['resultMode'] === 'hidden') {
  1403. $data['resultMode'] = 1;
  1404. } else {
  1405. throw new \Exception('Invalid result mode');
  1406. }
  1407. if ($data['maxVotes'] === 'unlimited') {
  1408. $data['maxVotes'] = 0;
  1409. }
  1410. $this->setCurrentUser($user);
  1411. $this->sendRequest(
  1412. 'POST', '/apps/spreed/api/' . $apiVersion . '/poll/' . self::$identifierToToken[$identifier],
  1413. $data
  1414. );
  1415. $this->assertStatusCode($this->response, $statusCode);
  1416. if ($statusCode !== '201') {
  1417. return;
  1418. }
  1419. $response = $this->getDataFromResponse($this->response);
  1420. if (isset($response['id'])) {
  1421. self::$questionToPollId[$data['question']] = $response['id'];
  1422. }
  1423. }
  1424. /**
  1425. * @Then /^user "([^"]*)" sees poll "([^"]*)" in room "([^"]*)" with (\d+)(?: \((v1)\))?$/
  1426. *
  1427. * @param string $user
  1428. * @param string $question
  1429. * @param string $identifier
  1430. * @param string $statusCode
  1431. * @param string $apiVersion
  1432. * @param ?TableNode $formData
  1433. */
  1434. public function userSeesPollInRoom(string $user, string $question, string $identifier, string $statusCode, string $apiVersion = 'v1', TableNode $formData = null): void {
  1435. $this->setCurrentUser($user);
  1436. $this->sendRequest('GET', '/apps/spreed/api/' . $apiVersion . '/poll/' . self::$identifierToToken[$identifier] . '/' . self::$questionToPollId[$question]);
  1437. $this->assertStatusCode($this->response, $statusCode);
  1438. if ($statusCode === '200' || $formData instanceof TableNode) {
  1439. $expected = $this->preparePollExpectedData($formData->getRowsHash());
  1440. $response = $this->getDataFromResponse($this->response);
  1441. $this->assertPollEquals($expected, $response);
  1442. }
  1443. }
  1444. /**
  1445. * @Then /^user "([^"]*)" closes poll "([^"]*)" in room "([^"]*)" with (\d+)(?: \((v1)\))?$/
  1446. *
  1447. * @param string $user
  1448. * @param string $question
  1449. * @param string $identifier
  1450. * @param string $statusCode
  1451. * @param string $apiVersion
  1452. * @param ?TableNode $formData
  1453. */
  1454. public function userClosesPollInRoom(string $user, string $question, string $identifier, string $statusCode, string $apiVersion = 'v1', TableNode $formData = null): void {
  1455. $this->setCurrentUser($user);
  1456. $this->sendRequest('DELETE', '/apps/spreed/api/' . $apiVersion . '/poll/' . self::$identifierToToken[$identifier] . '/' . self::$questionToPollId[$question]);
  1457. $this->assertStatusCode($this->response, $statusCode);
  1458. if ($statusCode !== '200') {
  1459. return;
  1460. }
  1461. $expected = $this->preparePollExpectedData($formData->getRowsHash());
  1462. $response = $this->getDataFromResponse($this->response);
  1463. $this->assertPollEquals($expected, $response);
  1464. }
  1465. /**
  1466. * @Then /^user "([^"]*)" votes for options "([^"]*)" on poll "([^"]*)" in room "([^"]*)" with (\d+)(?: \((v1)\))?$/
  1467. *
  1468. * @param string $user
  1469. * @param string $options
  1470. * @param string $question
  1471. * @param string $identifier
  1472. * @param string $statusCode
  1473. * @param string $apiVersion
  1474. * @param ?TableNode $formData
  1475. */
  1476. public function userVotesPollInRoom(string $user, string $options, string $question, string $identifier, string $statusCode, string $apiVersion = 'v1', TableNode $formData = null): void {
  1477. $data = [
  1478. 'optionIds' => json_decode($options, true),
  1479. ];
  1480. $this->setCurrentUser($user);
  1481. $this->sendRequest(
  1482. 'POST', '/apps/spreed/api/' . $apiVersion . '/poll/' . self::$identifierToToken[$identifier] . '/' . self::$questionToPollId[$question],
  1483. $data
  1484. );
  1485. $this->assertStatusCode($this->response, $statusCode);
  1486. if ($statusCode !== '200' && $statusCode !== '201') {
  1487. return;
  1488. }
  1489. $expected = $this->preparePollExpectedData($formData->getRowsHash());
  1490. $response = $this->getDataFromResponse($this->response);
  1491. $this->assertPollEquals($expected, $response);
  1492. }
  1493. protected function assertPollEquals(array $expected, array $response): void {
  1494. if (isset($expected['details'])) {
  1495. $response['details'] = array_map(static function (array $detail): array {
  1496. unset($detail['id']);
  1497. return $detail;
  1498. }, $response['details']);
  1499. }
  1500. Assert::assertEquals($expected, $response);
  1501. }
  1502. protected function preparePollExpectedData(array $expected): array {
  1503. if ($expected['resultMode'] === 'public') {
  1504. $expected['resultMode'] = 0;
  1505. } elseif ($expected['resultMode'] === 'hidden') {
  1506. $expected['resultMode'] = 1;
  1507. }
  1508. if ($expected['maxVotes'] === 'unlimited') {
  1509. $expected['maxVotes'] = 0;
  1510. }
  1511. if ($expected['status'] === 'open') {
  1512. $expected['status'] = 0;
  1513. } elseif ($expected['status'] === 'closed') {
  1514. $expected['status'] = 1;
  1515. }
  1516. if ($expected['votedSelf'] === 'not voted') {
  1517. $expected['votedSelf'] = [];
  1518. } else {
  1519. $expected['votedSelf'] = json_decode($expected['votedSelf'], true);
  1520. }
  1521. if (isset($expected['votes'])) {
  1522. $expected['votes'] = json_decode($expected['votes'], true);
  1523. }
  1524. if (isset($expected['details'])) {
  1525. $expected['details'] = json_decode($expected['details'], true);
  1526. }
  1527. $expected['numVoters'] = (int) $expected['numVoters'];
  1528. $expected['options'] = json_decode($expected['options'], true);
  1529. $result = preg_match('/POLL_ID\(([^)]+)\)/', $expected['id'], $matches);
  1530. if ($result) {
  1531. $expected['id'] = self::$questionToPollId[$matches[1]];
  1532. }
  1533. return $expected;
  1534. }
  1535. /**
  1536. * @Then /^user "([^"]*)" sees the following entry when loading the list of dashboard widgets(?: \((v1)\))$/
  1537. *
  1538. * @param string $user
  1539. * @param string $apiVersion
  1540. * @param ?TableNode $formData
  1541. */
  1542. public function userGetsDashboardWidgets($user, $apiVersion = 'v1', TableNode $formData = null): void {
  1543. $this->setCurrentUser($user);
  1544. $this->sendRequest('GET', '/apps/dashboard/api/' . $apiVersion . '/widgets');
  1545. $this->assertStatusCode($this->response, 200);
  1546. $data = $this->getDataFromResponse($this->response);
  1547. $expectedWidgets = $formData->getColumnsHash();
  1548. foreach ($expectedWidgets as $widget) {
  1549. $id = $widget['id'];
  1550. Assert::assertArrayHasKey($widget['id'], $data);
  1551. $widgetIconUrl = $widget['icon_url'];
  1552. $dataIconUrl = $data[$id]['icon_url'];
  1553. unset($widget['icon_url'], $data[$id]['icon_url']);
  1554. $widget['item_icons_round'] = (bool) $widget['item_icons_round'];
  1555. $widget['order'] = (int) $widget['order'];
  1556. $widget['widget_url'] = str_replace('{$BASE_URL}', $this->baseUrl, $widget['widget_url']);
  1557. $widget['buttons'] = str_replace('{$BASE_URL}', $this->baseUrl, $widget['buttons']);
  1558. $widget['buttons'] = json_decode($widget['buttons'], true);
  1559. Assert::assertEquals($widget, $data[$id], 'Mismatch of data for widget ' . $id);
  1560. Assert::assertStringEndsWith($widgetIconUrl, $dataIconUrl, 'Mismatch of icon URL for widget ' . $id);
  1561. }
  1562. }
  1563. /**
  1564. * @Then /^user "([^"]*)" sees the following entries for dashboard widgets "([^"]*)"(?: \((v1)\))$/
  1565. *
  1566. * @param string $user
  1567. * @param string $widgetId
  1568. * @param string $apiVersion
  1569. * @param ?TableNode $formData
  1570. */
  1571. public function userGetsDashboardWidgetItems($user, $widgetId, $apiVersion = 'v1', TableNode $formData = null): void {
  1572. $this->setCurrentUser($user);
  1573. $this->sendRequest('GET', '/apps/dashboard/api/' . $apiVersion . '/widget-items?widgets[]=' . $widgetId);
  1574. $this->assertStatusCode($this->response, 200);
  1575. $data = $this->getDataFromResponse($this->response);
  1576. Assert::assertArrayHasKey($widgetId, $data);
  1577. $expectedItems = $formData->getColumnsHash();
  1578. if (empty($expectedItems)) {
  1579. Assert::assertEmpty($data[$widgetId]);
  1580. return;
  1581. }
  1582. Assert::assertCount(count($expectedItems), $data[$widgetId]);
  1583. foreach ($expectedItems as $key => $item) {
  1584. $token = self::$identifierToToken[$item['link']];
  1585. $item['link'] = $this->baseUrl . 'index.php/call/' . $token;
  1586. $item['iconUrl'] = str_replace('{$BASE_URL}', $this->baseUrl, $item['iconUrl']);
  1587. $item['iconUrl'] = str_replace('{token}', $token, $item['iconUrl']);
  1588. Assert::assertEquals($item, $data[$widgetId][$key], 'Wrong details for item #' . $key);
  1589. }
  1590. }
  1591. /**
  1592. * @Then /^user "([^"]*)" deletes message "([^"]*)" from room "([^"]*)" with (\d+)(?: \((v1)\))?$/
  1593. *
  1594. * @param string $user
  1595. * @param string $message
  1596. * @param string $identifier
  1597. * @param string $statusCode
  1598. * @param string $apiVersion
  1599. */
  1600. public function userDeletesMessageFromRoom($user, $message, $identifier, $statusCode, $apiVersion = 'v1') {
  1601. $this->setCurrentUser($user);
  1602. $this->sendRequest(
  1603. 'DELETE', '/apps/spreed/api/' . $apiVersion . '/chat/' . self::$identifierToToken[$identifier] . '/' . self::$textToMessageId[$message],
  1604. new TableNode([['message', $message]])
  1605. );
  1606. $this->assertStatusCode($this->response, $statusCode);
  1607. }
  1608. /**
  1609. * @Then /^user "([^"]*)" deletes chat history for room "([^"]*)" with (\d+)(?: \((v1)\))?$/
  1610. *
  1611. * @param string $user
  1612. * @param string $identifier
  1613. * @param int $statusCode
  1614. * @param string $apiVersion
  1615. */
  1616. public function userDeletesHistoryFromRoom(string $user, string $identifier, int $statusCode, string $apiVersion = 'v1'): void {
  1617. $this->setCurrentUser($user);
  1618. $this->sendRequest(
  1619. 'DELETE', '/apps/spreed/api/' . $apiVersion . '/chat/' . self::$identifierToToken[$identifier]
  1620. );
  1621. $this->assertStatusCode($this->response, $statusCode);
  1622. }
  1623. /**
  1624. * @Then /^user "([^"]*)" reads message "([^"]*)" in room "([^"]*)" with (\d+)(?: \((v1)\))?$/
  1625. *
  1626. * @param string $user
  1627. * @param string $message
  1628. * @param string $identifier
  1629. * @param string $statusCode
  1630. * @param string $apiVersion
  1631. */
  1632. public function userReadsMessageInRoom($user, $message, $identifier, $statusCode, $apiVersion = 'v1') {
  1633. $this->setCurrentUser($user);
  1634. $this->sendRequest(
  1635. 'POST', '/apps/spreed/api/' . $apiVersion . '/chat/' . self::$identifierToToken[$identifier] . '/read',
  1636. new TableNode([['lastReadMessage', self::$textToMessageId[$message]]])
  1637. );
  1638. $this->assertStatusCode($this->response, $statusCode);
  1639. }
  1640. /**
  1641. * @Then /^user "([^"]*)" marks room "([^"]*)" as unread with (\d+)(?: \((v1)\))?$/
  1642. *
  1643. * @param string $user
  1644. * @param string $identifier
  1645. * @param string $statusCode
  1646. * @param string $apiVersion
  1647. */
  1648. public function userMarkUnreadRoom($user, $identifier, $statusCode, $apiVersion = 'v1') {
  1649. $this->setCurrentUser($user);
  1650. $this->sendRequest(
  1651. 'DELETE', '/apps/spreed/api/' . $apiVersion . '/chat/' . self::$identifierToToken[$identifier] . '/read',
  1652. );
  1653. $this->assertStatusCode($this->response, $statusCode);
  1654. }
  1655. /**
  1656. * @Then /^user "([^"]*)" sends message "([^"]*)" with reference id "([^"]*)" to room "([^"]*)" with (\d+)(?: \((v1)\))?$/
  1657. *
  1658. * @param string $user
  1659. * @param string $message
  1660. * @param string $referenceId
  1661. * @param string $identifier
  1662. * @param string $statusCode
  1663. * @param string $apiVersion
  1664. */
  1665. public function userSendsMessageWithReferenceIdToRoom($user, $message, $referenceId, $identifier, $statusCode, $apiVersion = 'v1') {
  1666. $this->setCurrentUser($user);
  1667. $this->sendRequest(
  1668. 'POST', '/apps/spreed/api/' . $apiVersion . '/chat/' . self::$identifierToToken[$identifier],
  1669. new TableNode([['message', $message], ['referenceId', $referenceId]])
  1670. );
  1671. $this->assertStatusCode($this->response, $statusCode);
  1672. sleep(1); // make sure Postgres manages the order of the messages
  1673. $response = $this->getDataFromResponse($this->response);
  1674. if (isset($response['id'])) {
  1675. self::$textToMessageId[$message] = $response['id'];
  1676. }
  1677. Assert::assertStringStartsWith($response['referenceId'], $referenceId);
  1678. }
  1679. /**
  1680. * @Then /^user "([^"]*)" sends reply "([^"]*)" on message "([^"]*)" to room "([^"]*)" with (\d+)(?: \((v1)\))?$/
  1681. *
  1682. * @param string $user
  1683. * @param string $reply
  1684. * @param string $message
  1685. * @param string $identifier
  1686. * @param string $statusCode
  1687. * @param string $apiVersion
  1688. */
  1689. public function userSendsReplyToRoom($user, $reply, $message, $identifier, $statusCode, $apiVersion = 'v1') {
  1690. $replyTo = self::$textToMessageId[$message];
  1691. $this->setCurrentUser($user);
  1692. $this->sendRequest(
  1693. 'POST', '/apps/spreed/api/' . $apiVersion . '/chat/' . self::$identifierToToken[$identifier],
  1694. new TableNode([['message', $reply], ['replyTo', $replyTo]])
  1695. );
  1696. $this->assertStatusCode($this->response, $statusCode);
  1697. sleep(1); // make sure Postgres manages the order of the messages
  1698. $response = $this->getDataFromResponse($this->response);
  1699. if (isset($response['id'])) {
  1700. self::$textToMessageId[$reply] = $response['id'];
  1701. }
  1702. }
  1703. /**
  1704. * @Then /^user "([^"]*)" sees the following messages in room "([^"]*)" with (\d+)(?: \((v1)\))?$/
  1705. *
  1706. * @param string $user
  1707. * @param string $identifier
  1708. * @param string $statusCode
  1709. * @param string $apiVersion
  1710. */
  1711. public function userSeesTheFollowingMessagesInRoom($user, $identifier, $statusCode, $apiVersion = 'v1', TableNode $formData = null) {
  1712. $this->setCurrentUser($user);
  1713. $this->sendRequest('GET', '/apps/spreed/api/' . $apiVersion . '/chat/' . self::$identifierToToken[$identifier] . '?lookIntoFuture=0');
  1714. $this->assertStatusCode($this->response, $statusCode);
  1715. $this->compareDataResponse($formData);
  1716. }
  1717. /**
  1718. * @Then /^user "([^"]*)" searches for "([^"]*)" in room "([^"]*)" with (\d+)(?: \((v1)\))?$/
  1719. *
  1720. * @param string $user
  1721. * @param string $search
  1722. * @param string $identifier
  1723. * @param string $statusCode
  1724. * @param string $apiVersion
  1725. */
  1726. public function userSearchesInRoom(string $user, string $search, string $identifier, $statusCode, string $apiVersion = 'v1', TableNode $formData = null): void {
  1727. $this->setCurrentUser($user);
  1728. $this->sendRequest('GET', '/search/providers/talk-message-current/search?term=' . $search . '&from=' . '/call/' . self::$identifierToToken[$identifier]);
  1729. $this->assertStatusCode($this->response, $statusCode);
  1730. if ($statusCode !== '200') {
  1731. return;
  1732. }
  1733. $this->compareSearchResponse($formData);
  1734. }
  1735. /**
  1736. * @Then /^user "([^"]*)" sees the following shared (media|audio|voice|file|deckcard|location|other) in room "([^"]*)" with (\d+)(?: \((v1)\))?$/
  1737. *
  1738. * @param string $user
  1739. * @param string $objectType
  1740. * @param string $identifier
  1741. * @param string $statusCode
  1742. * @param string $apiVersion
  1743. */
  1744. public function userSeesTheFollowingSharedMediaInRoom($user, $objectType, $identifier, $statusCode, $apiVersion = 'v1', TableNode $formData = null): void {
  1745. $this->setCurrentUser($user);
  1746. $this->sendRequest('GET', '/apps/spreed/api/' . $apiVersion . '/chat/' . self::$identifierToToken[$identifier] . '/share?objectType=' . $objectType);
  1747. $this->assertStatusCode($this->response, $statusCode);
  1748. $this->compareDataResponse($formData);
  1749. }
  1750. /**
  1751. * @Then /^user "([^"]*)" received a system messages in room "([^"]*)" to delete "([^"]*)"(?: \((v1)\))?$/
  1752. *
  1753. * @param string $user
  1754. * @param string $identifier
  1755. * @param string $message
  1756. * @param string $apiVersion
  1757. */
  1758. public function userReceivedDeleteMessage($user, $identifier, $message, $apiVersion = 'v1') {
  1759. $this->setCurrentUser($user);
  1760. $this->sendRequest('GET', '/apps/spreed/api/' . $apiVersion . '/chat/' . self::$identifierToToken[$identifier] . '?lookIntoFuture=0');
  1761. $this->assertStatusCode($this->response, 200);
  1762. $actual = $this->getDataFromResponse($this->response);
  1763. foreach ($actual as $m) {
  1764. if ($m['systemMessage'] === 'message_deleted') {
  1765. if (isset($m['parent']['id']) && $m['parent']['id'] === self::$textToMessageId[$message]) {
  1766. return;
  1767. }
  1768. }
  1769. }
  1770. Assert::fail('Missing message_deleted system message for "' . $message . '"');
  1771. }
  1772. /**
  1773. * @Then /^user "([^"]*)" sees the following messages in room "([^"]*)" starting with "([^"]*)" with (\d+)(?: \((v1)\))?$/
  1774. *
  1775. * @param string $user
  1776. * @param string $identifier
  1777. * @param string $knownMessage
  1778. * @param string $statusCode
  1779. * @param string $apiVersion
  1780. * @param TableNode|null $formData
  1781. */
  1782. public function userAwaitsTheFollowingMessagesInRoom($user, $identifier, $knownMessage, $statusCode, $apiVersion = 'v1', TableNode $formData = null) {
  1783. $this->setCurrentUser($user);
  1784. $this->sendRequest('GET', '/apps/spreed/api/' . $apiVersion . '/chat/' . self::$identifierToToken[$identifier] . '?lookIntoFuture=1&includeLastKnown=1&lastKnownMessageId=' . self::$textToMessageId[$knownMessage]);
  1785. $this->assertStatusCode($this->response, $statusCode);
  1786. $this->compareDataResponse($formData);
  1787. }
  1788. /**
  1789. * @param TableNode|null $formData
  1790. */
  1791. protected function compareDataResponse(TableNode $formData = null) {
  1792. $actual = $this->getDataFromResponse($this->response);
  1793. $messages = [];
  1794. array_map(function (array $message) use (&$messages) {
  1795. // Filter out system messages
  1796. if ($message['systemMessage'] === '') {
  1797. $messages[] = $message;
  1798. }
  1799. }, $actual);
  1800. foreach ($messages as $message) {
  1801. // Include the received messages in the list of messages used for
  1802. // replies; this is needed to get special messages not explicitly
  1803. // sent like those for shared files.
  1804. self::$textToMessageId[$message['message']] = $message['id'];
  1805. if ($message['message'] === '{file}' && isset($message['messageParameters']['file']['name'])) {
  1806. self::$textToMessageId['shared::file::' . $message['messageParameters']['file']['name']] = $message['id'];
  1807. }
  1808. }
  1809. if ($formData === null) {
  1810. Assert::assertEmpty($messages);
  1811. return;
  1812. }
  1813. $includeParents = in_array('parentMessage', $formData->getRow(0), true);
  1814. $includeReferenceId = in_array('referenceId', $formData->getRow(0), true);
  1815. $includeReactions = in_array('reactions', $formData->getRow(0), true);
  1816. $includeReactionsSelf = in_array('reactionsSelf', $formData->getRow(0), true);
  1817. $expected = $formData->getHash();
  1818. $count = count($expected);
  1819. Assert::assertCount($count, $messages, 'Message count does not match');
  1820. for ($i = 0; $i < $count; $i++) {
  1821. if ($expected[$i]['messageParameters'] === '"IGNORE"') {
  1822. $messages[$i]['messageParameters'] = 'IGNORE';
  1823. }
  1824. $result = preg_match('/POLL_ID\(([^)]+)\)/', $expected[$i]['messageParameters'], $matches);
  1825. if ($result) {
  1826. $expected[$i]['messageParameters'] = str_replace($matches[0], '"' . self::$questionToPollId[$matches[1]] . '"', $expected[$i]['messageParameters']);
  1827. }
  1828. }
  1829. Assert::assertEquals($expected, array_map(function ($message) use ($includeParents, $includeReferenceId, $includeReactions, $includeReactionsSelf) {
  1830. $data = [
  1831. 'room' => self::$tokenToIdentifier[$message['token']],
  1832. 'actorType' => $message['actorType'],
  1833. 'actorId' => ($message['actorType'] === 'guests')? self::$sessionIdToUser[$message['actorId']]: $message['actorId'],
  1834. 'actorDisplayName' => $message['actorDisplayName'],
  1835. // TODO test timestamp; it may require using Runkit, php-timecop
  1836. // or something like that.
  1837. 'message' => $message['message'],
  1838. 'messageParameters' => json_encode($message['messageParameters']),
  1839. ];
  1840. if ($includeParents) {
  1841. $data['parentMessage'] = $message['parent']['message'] ?? '';
  1842. }
  1843. if ($includeReferenceId) {
  1844. $data['referenceId'] = $message['referenceId'];
  1845. }
  1846. if ($includeReactions) {
  1847. $data['reactions'] = json_encode($message['reactions'], JSON_UNESCAPED_UNICODE);
  1848. }
  1849. if ($includeReactionsSelf) {
  1850. if (isset($message['reactionsSelf'])) {
  1851. $data['reactionsSelf'] = json_encode($message['reactionsSelf'], JSON_UNESCAPED_UNICODE);
  1852. } else {
  1853. $data['reactionsSelf'] = null;
  1854. }
  1855. }
  1856. return $data;
  1857. }, $messages));
  1858. }
  1859. /**
  1860. * @param TableNode|null $formData
  1861. */
  1862. protected function compareSearchResponse(TableNode $formData = null) {
  1863. $messages = $this->getDataFromResponse($this->response)['entries'];
  1864. if ($formData === null) {
  1865. Assert::assertEmpty($messages);
  1866. return;
  1867. }
  1868. $expected = array_map(static function (array $message) {
  1869. $message['attributes.conversation'] = self::$identifierToToken[$message['attributes.conversation']];
  1870. $message['attributes.messageId'] = self::$textToMessageId[$message['attributes.messageId']];
  1871. return $message;
  1872. }, $formData->getHash());
  1873. $count = count($expected);
  1874. Assert::assertCount($count, $messages, 'Message count does not match');
  1875. Assert::assertEquals($expected, array_map(static function ($message) {
  1876. return [
  1877. 'title' => $message['title'],
  1878. 'subline' => $message['subline'],
  1879. 'attributes.conversation' => $message['attributes']['conversation'],
  1880. 'attributes.messageId' => $message['attributes']['messageId'],
  1881. ];
  1882. }, $messages));
  1883. }
  1884. /**
  1885. * @Then /^user "([^"]*)" sees the following system messages in room "([^"]*)" with (\d+)(?: \((v1)\))?$/
  1886. *
  1887. * @param string $user
  1888. * @param string $identifier
  1889. * @param string $statusCode
  1890. * @param string $apiVersion
  1891. */
  1892. public function userSeesTheFollowingSystemMessagesInRoom($user, $identifier, $statusCode, $apiVersion = 'v1', TableNode $formData = null) {
  1893. $this->setCurrentUser($user);
  1894. $this->sendRequest('GET', '/apps/spreed/api/' . $apiVersion . '/chat/' . self::$identifierToToken[$identifier] . '?lookIntoFuture=0');
  1895. $this->assertStatusCode($this->response, $statusCode);
  1896. $messages = $this->getDataFromResponse($this->response);
  1897. $messages = array_filter($messages, function (array $message) {
  1898. return $message['systemMessage'] !== '';
  1899. });
  1900. // Fix index gaps after the array_filter above
  1901. $messages = array_values($messages);
  1902. foreach ($messages as $systemMessage) {
  1903. // Include the received system messages in the list of messages used
  1904. // for replies.
  1905. self::$textToMessageId[$systemMessage['systemMessage']] = $systemMessage['id'];
  1906. }
  1907. if ($formData === null) {
  1908. Assert::assertEmpty($messages);
  1909. return;
  1910. }
  1911. $expected = $formData->getHash();
  1912. Assert::assertCount(count($expected), $messages, 'Message count does not match');
  1913. Assert::assertEquals($expected, array_map(function ($message, $expected) {
  1914. $data = [
  1915. 'room' => self::$tokenToIdentifier[$message['token']],
  1916. 'actorType' => (string) $message['actorType'],
  1917. 'actorId' => ($message['actorType'] === 'guests') ? self::$sessionIdToUser[$message['actorId']] : (string) $message['actorId'],
  1918. 'systemMessage' => (string) $message['systemMessage'],
  1919. ];
  1920. if (isset($expected['actorDisplayName'])) {
  1921. $data['actorDisplayName'] = $message['actorDisplayName'];
  1922. }
  1923. if (isset($expected['message'])) {
  1924. $data['message'] = $message['message'];
  1925. }
  1926. if (isset($expected['messageParameters'])) {
  1927. $data['messageParameters'] = json_encode($message['messageParameters']);
  1928. }
  1929. return $data;
  1930. }, $messages, $expected));
  1931. }
  1932. /**
  1933. * @Then /^user "([^"]*)" gets the following candidate mentions in room "([^"]*)" for "([^"]*)" with (\d+)(?: \((v1)\))?$/
  1934. *
  1935. * @param string $user
  1936. * @param string $identifier
  1937. * @param string $search
  1938. * @param string $statusCode
  1939. * @param string $apiVersion
  1940. * @param TableNode|null $formData
  1941. */
  1942. public function userGetsTheFollowingCandidateMentionsInRoomFor($user, $identifier, $search, $statusCode, $apiVersion = 'v1', TableNode $formData = null) {
  1943. $this->setCurrentUser($user);
  1944. $this->sendRequest('GET', '/apps/spreed/api/' . $apiVersion . '/chat/' . self::$identifierToToken[$identifier] . '/mentions?search=' . $search);
  1945. $this->assertStatusCode($this->response, $statusCode);
  1946. $mentions = $this->getDataFromResponse($this->response);
  1947. if ($formData === null) {
  1948. Assert::assertEmpty($mentions);
  1949. return;
  1950. }
  1951. Assert::assertCount(count($formData->getHash()), $mentions, 'Mentions count does not match');
  1952. usort($mentions, function ($a, $b) {
  1953. if ($a['source'] === $b['source']) {
  1954. return $a['label'] <=> $b['label'];
  1955. }
  1956. return $a['source'] <=> $b['source'];
  1957. });
  1958. $expected = $formData->getHash();
  1959. usort($expected, function ($a, $b) {
  1960. if ($a['source'] === $b['source']) {
  1961. return $a['label'] <=> $b['label'];
  1962. }
  1963. return $a['source'] <=> $b['source'];
  1964. });
  1965. foreach ($expected as $key => $row) {
  1966. if ($row['id'] === 'GUEST_ID') {
  1967. Assert::assertRegExp('/^guest\/[0-9a-f]{40}$/', $mentions[$key]['id']);
  1968. $mentions[$key]['id'] = 'GUEST_ID';
  1969. }
  1970. if (array_key_exists('avatar', $row)) {
  1971. Assert::assertRegExp('/' . self::$identifierToToken[$row['avatar']] . '\/avatar/', $mentions[$key]['avatar']);
  1972. unset($row['avatar']);
  1973. }
  1974. unset($mentions[$key]['avatar'], );
  1975. Assert::assertEquals($row, $mentions[$key]);
  1976. }
  1977. }
  1978. /**
  1979. * @Then /^guest "([^"]*)" sets name to "([^"]*)" in room "([^"]*)" with (\d+)(?: \((v1)\))?$/
  1980. *
  1981. * @param string $user
  1982. * @param string $name
  1983. * @param string $identifier
  1984. * @param string $statusCode
  1985. * @param string $apiVersion
  1986. */
  1987. public function guestSetsName($user, $name, $identifier, $statusCode, $apiVersion = 'v1') {
  1988. $this->setCurrentUser($user);
  1989. $this->sendRequest(
  1990. 'POST', '/apps/spreed/api/' . $apiVersion . '/guest/' . self::$identifierToToken[$identifier] . '/name',
  1991. new TableNode([['displayName', $name]])
  1992. );
  1993. $this->assertStatusCode($this->response, $statusCode);
  1994. }
  1995. /**
  1996. * @Then /^last response has (no) last common read message header$/
  1997. *
  1998. * @param string $no
  1999. */
  2000. public function hasNoChatLastCommonReadHeader($no) {
  2001. 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'));
  2002. }
  2003. /**
  2004. * @Then /^last response has last common read message header (set to|less than) "([^"]*)"$/
  2005. *
  2006. * @param string $setOrLower
  2007. * @param string $message
  2008. */
  2009. public function hasChatLastCommonReadHeader($setOrLower, $message) {
  2010. Assert::assertArrayHasKey('X-Chat-Last-Common-Read', $this->response->getHeaders());
  2011. if ($setOrLower === 'set to') {
  2012. Assert::assertEquals(self::$textToMessageId[$message], $this->response->getHeader('X-Chat-Last-Common-Read')[0]);
  2013. } else {
  2014. // 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
  2015. Assert::assertLessThan(self::$textToMessageId[$message], $this->response->getHeader('X-Chat-Last-Common-Read')[0]);
  2016. }
  2017. }
  2018. /**
  2019. * @Then /^user "([^"]*)" creates (\d+) (automatic|manual|free) breakout rooms for "([^"]*)" with (\d+) \((v1)\)$/
  2020. *
  2021. * @param string $user
  2022. * @param int $amount
  2023. * @param string $modeString
  2024. * @param string $roomName
  2025. * @param int $status
  2026. * @param string $apiVersion
  2027. * @param TableNode|null $formData
  2028. */
  2029. public function userCreatesBreakoutRooms(string $user, int $amount, string $modeString, string $roomName, int $status, string $apiVersion, TableNode $formData = null): void {
  2030. switch ($modeString) {
  2031. case 'automatic':
  2032. $mode = 1;
  2033. break;
  2034. case 'manual':
  2035. $mode = 2;
  2036. break;
  2037. case 'free':
  2038. $mode = 3;
  2039. break;
  2040. default:
  2041. throw new \InvalidArgumentException('Invalid breakout room mode: ' . $modeString);
  2042. }
  2043. $data = [
  2044. 'mode' => $mode,
  2045. 'amount' => $amount,
  2046. ];
  2047. if ($modeString === 'manual' && $formData instanceof TableNode) {
  2048. $mapArray = [];
  2049. foreach ($formData->getRowsHash() as $attendee => $roomNumber) {
  2050. [$type, $id] = explode('::', $attendee);
  2051. $attendeeId = $this->getAttendeeId($type, $id, $roomName);
  2052. $mapArray[$attendeeId] = (int) $roomNumber;
  2053. }
  2054. $data['attendeeMap'] = json_encode($mapArray, JSON_THROW_ON_ERROR);
  2055. }
  2056. $this->setCurrentUser($user);
  2057. $this->sendRequest('POST', '/apps/spreed/api/' . $apiVersion . '/breakout-rooms/' . self::$identifierToToken[$roomName], $data);
  2058. $this->assertStatusCode($this->response, $status);
  2059. }
  2060. /**
  2061. * @Then /^user "([^"]*)" broadcasts message "([^"]*)" to room "([^"]*)" with (\d+)(?: \((v1)\))?$/
  2062. *
  2063. * @param string $user
  2064. * @param string $message
  2065. * @param string $identifier
  2066. * @param string $statusCode
  2067. * @param string $apiVersion
  2068. */
  2069. public function userBroadcastsMessageToBreakoutRooms(string $user, string $message, string $identifier, string $statusCode, string $apiVersion = 'v1') {
  2070. $body = new TableNode([['message', $message]]);
  2071. $this->setCurrentUser($user);
  2072. $this->sendRequest(
  2073. 'POST', '/apps/spreed/api/' . $apiVersion . '/breakout-rooms/' . self::$identifierToToken[$identifier] . '/broadcast',
  2074. $body
  2075. );
  2076. $this->assertStatusCode($this->response, $statusCode);
  2077. sleep(1); // make sure Postgres manages the order of the messages
  2078. }
  2079. /**
  2080. * @Then /^user "([^"]*)" (starts|stops) breakout rooms in room "([^"]*)" with (\d+)(?: \((v1)\))?$/
  2081. *
  2082. * @param string $user
  2083. * @param string $startStop
  2084. * @param string $identifier
  2085. * @param string $statusCode
  2086. * @param string $apiVersion
  2087. */
  2088. public function userStartsOrStopsBreakoutRooms(string $user, string $startStop, string $identifier, string $statusCode, string $apiVersion = 'v1') {
  2089. $this->setCurrentUser($user);
  2090. $this->sendRequest(
  2091. $startStop === 'starts' ? 'POST' : 'DELETE',
  2092. '/apps/spreed/api/' . $apiVersion . '/breakout-rooms/' . self::$identifierToToken[$identifier] . '/rooms'
  2093. );
  2094. $this->assertStatusCode($this->response, $statusCode);
  2095. }
  2096. /**
  2097. * @Then /^user "([^"]*)" (requests assistance|cancels request for assistance) in room "([^"]*)" with (\d+)(?: \((v1)\))?$/
  2098. *
  2099. * @param string $user
  2100. * @param string $requestCancel
  2101. * @param string $identifier
  2102. * @param string $statusCode
  2103. * @param string $apiVersion
  2104. */
  2105. public function userRequestsOrCancelsAssistanceInBreakoutRooms(string $user, string $requestCancel, string $identifier, string $statusCode, string $apiVersion = 'v1') {
  2106. $this->setCurrentUser($user);
  2107. $this->sendRequest(
  2108. $requestCancel === 'requests assistance' ? 'POST' : 'DELETE',
  2109. '/apps/spreed/api/' . $apiVersion . '/breakout-rooms/' . self::$identifierToToken[$identifier] . '/request-assistance'
  2110. );
  2111. $this->assertStatusCode($this->response, $statusCode);
  2112. }
  2113. /**
  2114. * @Then /^user "([^"]*)" sets setting "([^"]*)" to "([^"]*)" with (\d+)(?: \((v1)\))?$/
  2115. *
  2116. * @param string $user
  2117. * @param string $setting
  2118. * @param string $value
  2119. * @param string $statusCode
  2120. * @param string $apiVersion
  2121. */
  2122. public function userSetting($user, $setting, $value, $statusCode, $apiVersion = 'v1') {
  2123. $this->setCurrentUser($user);
  2124. $this->sendRequest(
  2125. 'POST', '/apps/spreed/api/' . $apiVersion . '/settings/user',
  2126. new TableNode([['key', $setting], ['value', $value]])
  2127. );
  2128. $this->assertStatusCode($this->response, $statusCode);
  2129. }
  2130. /**
  2131. * @Then /^user "([^"]*)" has capability "([^"]*)" set to "([^"]*)"$/
  2132. *
  2133. * @param string $user
  2134. * @param string $capability
  2135. * @param string $value
  2136. */
  2137. public function userCheckCapability($user, $capability, $value) {
  2138. $this->setCurrentUser($user);
  2139. $this->sendRequest(
  2140. 'GET', '/cloud/capabilities'
  2141. );
  2142. $data = $this->getDataFromResponse($this->response);
  2143. $capabilities = $data['capabilities'];
  2144. $keys = explode('=>', $capability);
  2145. $finalKey = array_pop($keys);
  2146. $cur = $capabilities;
  2147. foreach ($keys as $key) {
  2148. Assert::assertArrayHasKey($key, $cur);
  2149. $cur = $cur[$key];
  2150. }
  2151. Assert::assertEquals($value, $cur[$finalKey]);
  2152. }
  2153. /**
  2154. * Parses the xml answer to get the array of users returned.
  2155. * @param ResponseInterface $response
  2156. * @return array
  2157. */
  2158. protected function getDataFromResponse(ResponseInterface $response) {
  2159. $jsonBody = json_decode($response->getBody()->getContents(), true);
  2160. return $jsonBody['ocs']['data'];
  2161. }
  2162. /**
  2163. * @Then /^status code is ([0-9]*)$/
  2164. *
  2165. * @param int $statusCode
  2166. */
  2167. public function isStatusCode($statusCode) {
  2168. $this->assertStatusCode($this->response, $statusCode);
  2169. }
  2170. /**
  2171. * @Given the following :appId app config is set
  2172. *
  2173. * @param TableNode $formData
  2174. */
  2175. public function setAppConfig(string $appId, TableNode $formData): void {
  2176. $currentUser = $this->currentUser;
  2177. $this->setCurrentUser('admin');
  2178. foreach ($formData->getRows() as $row) {
  2179. $this->sendRequest('POST', '/apps/provisioning_api/api/v1/config/apps/' . $appId . '/' . $row[0], [
  2180. 'value' => $row[1],
  2181. ]);
  2182. $this->changedConfigs[$appId][] = $row[0];
  2183. }
  2184. $this->setCurrentUser($currentUser);
  2185. }
  2186. /**
  2187. * @Then user :user has the following notifications
  2188. *
  2189. * @param string $user
  2190. * @param TableNode|null $body
  2191. */
  2192. public function userNotifications(string $user, TableNode $body = null): void {
  2193. $this->setCurrentUser($user);
  2194. $this->sendRequest(
  2195. 'GET', '/apps/notifications/api/v2/notifications'
  2196. );
  2197. $data = $this->getDataFromResponse($this->response);
  2198. if ($body === null) {
  2199. Assert::assertCount(0, $data);
  2200. return;
  2201. }
  2202. $this->assertNotifications($data, $body);
  2203. }
  2204. private function assertNotifications($notifications, TableNode $formData) {
  2205. Assert::assertCount(count($formData->getHash()), $notifications, 'Notifications count does not match');
  2206. Assert::assertEquals($formData->getHash(), array_map(function ($notification, $expectedNotification) {
  2207. $data = [];
  2208. if (isset($expectedNotification['object_id'])) {
  2209. if (strpos($notification['object_id'], '/') !== false) {
  2210. [$roomToken, $message] = explode('/', $notification['object_id']);
  2211. $data['object_id'] = self::$tokenToIdentifier[$roomToken] . '/' . self::$messageIdToText[$message] ?? 'UNKNOWN_MESSAGE';
  2212. } elseif (strpos($expectedNotification['object_id'], 'INVITE_ID') !== false) {
  2213. $data['object_id'] = 'INVITE_ID(' . self::$inviteIdToRemote[$notification['object_id']] . ')';
  2214. } else {
  2215. [$roomToken,] = explode('/', $notification['object_id']);
  2216. $data['object_id'] = self::$tokenToIdentifier[$roomToken];
  2217. }
  2218. }
  2219. if (isset($expectedNotification['subject'])) {
  2220. $data['subject'] = (string) $notification['subject'];
  2221. }
  2222. if (isset($expectedNotification['object_type'])) {
  2223. $data['object_type'] = (string) $notification['object_type'];
  2224. }
  2225. if (isset($expectedNotification['app'])) {
  2226. $data['app'] = (string) $notification['app'];
  2227. }
  2228. return $data;
  2229. }, $notifications, $formData->getHash()));
  2230. }
  2231. /**
  2232. * @Given /^guest accounts can be created$/
  2233. *
  2234. * @param TableNode $formData
  2235. */
  2236. public function allowGuestAccountsCreation(): void {
  2237. $currentUser = $this->currentUser;
  2238. $this->setCurrentUser('admin');
  2239. // save old state and restore at the end
  2240. $this->sendRequest('GET', '/cloud/apps?filter=enabled');
  2241. $this->assertStatusCode($this->response, 200);
  2242. $data = $this->getDataFromResponse($this->response);
  2243. $this->guestsAppWasEnabled = in_array('guests', $data['apps'], true);
  2244. if (!$this->guestsAppWasEnabled) {
  2245. // enable guests app
  2246. /*
  2247. $this->sendRequest('POST', '/cloud/apps/guests');
  2248. $this->assertStatusCode($this->response, 200);
  2249. */
  2250. // seems using provisioning API doesn't create tables...
  2251. $this->runOcc(['app:enable', 'guests']);
  2252. }
  2253. // save previously set whitelist
  2254. $this->sendRequest('GET', '/apps/provisioning_api/api/v1/config/apps/guests/whitelist');
  2255. $this->assertStatusCode($this->response, 200);
  2256. $this->guestsOldWhitelist = $this->getDataFromResponse($this->response)['data'];
  2257. // set whitelist to allow spreed only
  2258. $this->sendRequest('POST', '/apps/provisioning_api/api/v1/config/apps/guests/whitelist', [
  2259. 'value' => 'spreed',
  2260. ]);
  2261. $this->setCurrentUser($currentUser);
  2262. }
  2263. /**
  2264. * @BeforeScenario
  2265. * @AfterScenario
  2266. */
  2267. public function resetSpreedAppData() {
  2268. $currentUser = $this->currentUser;
  2269. $this->setCurrentUser('admin');
  2270. $this->sendRequest('DELETE', '/apps/spreedcheats/');
  2271. foreach ($this->changedConfigs as $appId => $configs) {
  2272. foreach ($configs as $config) {
  2273. $this->sendRequest('DELETE', '/apps/provisioning_api/api/v1/config/apps/' . $appId . '/' . $config);
  2274. }
  2275. }
  2276. $this->setCurrentUser($currentUser);
  2277. }
  2278. /**
  2279. * @AfterScenario
  2280. */
  2281. public function resetGuestsAppState() {
  2282. if ($this->guestsAppWasEnabled === null) {
  2283. // guests app was not touched
  2284. return;
  2285. }
  2286. $currentUser = $this->currentUser;
  2287. $this->setCurrentUser('admin');
  2288. if ($this->guestsOldWhitelist) {
  2289. // restore old whitelist
  2290. $this->sendRequest('POST', '/apps/provisioning_api/api/v1/config/apps/guests/whitelist', [
  2291. 'value' => $this->guestsOldWhitelist,
  2292. ]);
  2293. } else {
  2294. // restore to default
  2295. $this->sendRequest('DELETE', '/apps/provisioning_api/api/v1/config/apps/guests/whitelist');
  2296. }
  2297. // restore app's enabled state
  2298. $this->sendRequest($this->guestsAppWasEnabled ? 'POST' : 'DELETE', '/cloud/apps/guests');
  2299. $this->setCurrentUser($currentUser);
  2300. $this->guestsAppWasEnabled = null;
  2301. }
  2302. /*
  2303. * User management
  2304. */
  2305. /**
  2306. * @Given /^as user "([^"]*)"$/
  2307. * @param string $user
  2308. */
  2309. public function setCurrentUser($user) {
  2310. $this->currentUser = $user;
  2311. }
  2312. /**
  2313. * @Given /^user "([^"]*)" exists$/
  2314. * @param string $user
  2315. */
  2316. public function assureUserExists($user) {
  2317. $response = $this->userExists($user);
  2318. if ($response->getStatusCode() !== 200) {
  2319. $this->createUser($user);
  2320. // Set a display name different than the user ID to be able to
  2321. // ensure in the tests that the right value was returned.
  2322. $this->setUserDisplayName($user);
  2323. $response = $this->userExists($user);
  2324. $this->assertStatusCode($response, 200);
  2325. }
  2326. }
  2327. /**
  2328. * @Given /^user "([^"]*)" is a guest account user/
  2329. * @param string $email email address
  2330. */
  2331. public function createGuestUser($email) {
  2332. $currentUser = $this->currentUser;
  2333. $this->setCurrentUser('admin');
  2334. // in case it exists
  2335. $this->deleteUser($email);
  2336. $lastCode = $this->runOcc([
  2337. 'guests:add',
  2338. // creator user
  2339. 'admin',
  2340. // email
  2341. $email,
  2342. '--display-name',
  2343. $email . '-displayname',
  2344. '--password-from-env',
  2345. ], [
  2346. 'OC_PASS' => self::TEST_PASSWORD,
  2347. ]);
  2348. Assert::assertEquals(0, $lastCode, 'Guest creation succeeded for ' . $email);
  2349. $this->createdGuestAccountUsers[] = $email;
  2350. $this->setCurrentUser($currentUser);
  2351. }
  2352. private function userExists($user) {
  2353. $currentUser = $this->currentUser;
  2354. $this->setCurrentUser('admin');
  2355. $this->sendRequest('GET', '/cloud/users/' . $user);
  2356. $this->setCurrentUser($currentUser);
  2357. return $this->response;
  2358. }
  2359. private function createUser($user) {
  2360. $currentUser = $this->currentUser;
  2361. $this->setCurrentUser('admin');
  2362. $this->sendRequest('POST', '/cloud/users', [
  2363. 'userid' => $user,
  2364. 'password' => self::TEST_PASSWORD,
  2365. ]);
  2366. $this->assertStatusCode($this->response, 200, 'Failed to create user');
  2367. //Quick hack to login once with the current user
  2368. $this->setCurrentUser($user);
  2369. $this->sendRequest('GET', '/cloud/users' . '/' . $user);
  2370. $this->assertStatusCode($this->response, 200, 'Failed to do first login');
  2371. $this->createdUsers[] = $user;
  2372. $this->setCurrentUser($currentUser);
  2373. }
  2374. /**
  2375. * @Given /^user "([^"]*)" is deleted$/
  2376. * @param string $user
  2377. */
  2378. public function userIsDeleted($user) {
  2379. $deleted = false;
  2380. $this->deleteUser($user);
  2381. $response = $this->userExists($user);
  2382. $deleted = $response->getStatusCode() === 404;
  2383. if (!$deleted) {
  2384. Assert::fail("User $user exists");
  2385. }
  2386. }
  2387. private function deleteUser($user) {
  2388. $currentUser = $this->currentUser;
  2389. $this->setCurrentUser('admin');
  2390. $this->sendRequest('DELETE', '/cloud/users/' . $user);
  2391. $this->setCurrentUser($currentUser);
  2392. unset($this->createdUsers[array_search($user, $this->createdUsers, true)]);
  2393. return $this->response;
  2394. }
  2395. private function setUserDisplayName($user) {
  2396. $currentUser = $this->currentUser;
  2397. $this->setCurrentUser('admin');
  2398. $this->sendRequest('PUT', '/cloud/users/' . $user, [
  2399. 'key' => 'displayname',
  2400. 'value' => $user . '-displayname'
  2401. ]);
  2402. $this->setCurrentUser($currentUser);
  2403. }
  2404. /**
  2405. * @Given /^group "([^"]*)" exists$/
  2406. * @param string $group
  2407. */
  2408. public function assureGroupExists($group) {
  2409. $currentUser = $this->currentUser;
  2410. $this->setCurrentUser('admin');
  2411. $this->sendRequest('POST', '/cloud/groups', [
  2412. 'groupid' => $group,
  2413. ]);
  2414. $jsonBody = json_decode($this->response->getBody()->getContents(), true);
  2415. if (isset($jsonBody['ocs']['meta'])) {
  2416. // 102 = group exists
  2417. // 200 = created with success
  2418. Assert::assertContains(
  2419. $jsonBody['ocs']['meta']['statuscode'],
  2420. [102, 200],
  2421. $jsonBody['ocs']['meta']['message']
  2422. );
  2423. } else {
  2424. throw new \Exception('Invalid response when create group');
  2425. }
  2426. $this->setCurrentUser($currentUser);
  2427. $this->createdGroups[] = $group;
  2428. }
  2429. private function deleteGroup($group) {
  2430. $currentUser = $this->currentUser;
  2431. $this->setCurrentUser('admin');
  2432. $this->sendRequest('DELETE', '/cloud/groups/' . $group);
  2433. $this->setCurrentUser($currentUser);
  2434. unset($this->createdGroups[array_search($group, $this->createdGroups, true)]);
  2435. }
  2436. /**
  2437. * @When /^user "([^"]*)" is member of group "([^"]*)"$/
  2438. * @param string $user
  2439. * @param string $group
  2440. */
  2441. public function addingUserToGroup($user, $group) {
  2442. $currentUser = $this->currentUser;
  2443. $this->setCurrentUser('admin');
  2444. $this->sendRequest('POST', "/cloud/users/$user/groups", [
  2445. 'groupid' => $group,
  2446. ]);
  2447. $this->assertStatusCode($this->response, 200);
  2448. $this->setCurrentUser($currentUser);
  2449. }
  2450. /**
  2451. * @When /^user "([^"]*)" is not member of group "([^"]*)"$/
  2452. * @param string $user
  2453. * @param string $group
  2454. */
  2455. public function removeUserFromGroup($user, $group) {
  2456. $currentUser = $this->currentUser;
  2457. $this->setCurrentUser('admin');
  2458. $this->sendRequest('DELETE', "/cloud/users/$user/groups", [
  2459. 'groupid' => $group,
  2460. ]);
  2461. $this->assertStatusCode($this->response, 200);
  2462. $this->setCurrentUser($currentUser);
  2463. }
  2464. /**
  2465. * @Given /^user "([^"]*)" (delete react|react) with "([^"]*)" on message "([^"]*)" to room "([^"]*)" with (\d+)(?: \((v1)\))?$/
  2466. */
  2467. public function userReactWithOnMessageToRoomWith(string $user, string $action, string $reaction, string $message, string $identifier, int $statusCode, string $apiVersion = 'v1', TableNode $formData = null): void {
  2468. $token = self::$identifierToToken[$identifier];
  2469. $messageId = self::$textToMessageId[$message];
  2470. $this->setCurrentUser($user);
  2471. $verb = $action === 'react' ? 'POST' : 'DELETE';
  2472. $this->sendRequest($verb, '/apps/spreed/api/' . $apiVersion . '/reaction/' . $token . '/' . $messageId, [
  2473. 'reaction' => $reaction
  2474. ]);
  2475. $this->assertStatusCode($this->response, $statusCode);
  2476. $this->assertReactionList($formData);
  2477. }
  2478. /**
  2479. * @Given /^user "([^"]*)" retrieve reactions "([^"]*)" of message "([^"]*)" in room "([^"]*)" with (\d+)(?: \((v1)\))?$/
  2480. */
  2481. public function userRetrieveReactionsOfMessageInRoomWith(string $user, string $reaction, string $message, string $identifier, int $statusCode, string $apiVersion = 'v1', ?TableNode $formData = null): void {
  2482. $token = self::$identifierToToken[$identifier];
  2483. $messageId = self::$textToMessageId[$message];
  2484. $this->setCurrentUser($user);
  2485. $reaction = $reaction !== 'all' ? '?reaction=' . $reaction : '';
  2486. $this->sendRequest('GET', '/apps/spreed/api/' . $apiVersion . '/reaction/' . $token . '/' . $messageId . $reaction);
  2487. $this->assertStatusCode($this->response, $statusCode);
  2488. $this->assertReactionList($formData);
  2489. }
  2490. private function assertReactionList(?TableNode $formData): void {
  2491. $expected = [];
  2492. if (!$formData instanceof TableNode) {
  2493. return;
  2494. }
  2495. foreach ($formData->getHash() as $row) {
  2496. $reaction = $row['reaction'];
  2497. unset($row['reaction']);
  2498. $expected[$reaction][] = $row;
  2499. }
  2500. $result = $this->getDataFromResponse($this->response);
  2501. $result = array_map(static function ($reaction, $list) use ($expected): array {
  2502. $list = array_map(function ($reaction) {
  2503. unset($reaction['timestamp']);
  2504. $reaction['actorId'] = ($reaction['actorType'] === 'guests') ? self::$sessionIdToUser[$reaction['actorId']] : (string) $reaction['actorId'];
  2505. return $reaction;
  2506. }, $list);
  2507. Assert::assertArrayHasKey($reaction, $expected, 'Not expected reaction: ' . $reaction);
  2508. Assert::assertCount(count($list), $expected[$reaction], 'Reaction count by type does not match');
  2509. usort($expected[$reaction], [self::class, 'sortAttendees']);
  2510. usort($list, [self::class, 'sortAttendees']);
  2511. Assert::assertEquals($expected[$reaction], $list, 'Reaction list by type does not match');
  2512. return $list;
  2513. }, array_keys($result), array_values($result));
  2514. Assert::assertCount(count($expected), $result, 'Reaction count does not match');
  2515. }
  2516. /**
  2517. * @Given user :user set the message expiration to :messageExpiration of room :identifier with :statusCode (:apiVersion)
  2518. */
  2519. public function userSetTheMessageExpirationToXWithStatusCode(string $user, int $messageExpiration, string $identifier, int $statusCode, string $apiVersion = 'v4'): void {
  2520. $this->setCurrentUser($user);
  2521. $this->sendRequest('POST', '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier] . '/message-expiration', [
  2522. 'seconds' => $messageExpiration,
  2523. ]);
  2524. $this->assertStatusCode($this->response, $statusCode);
  2525. }
  2526. /**
  2527. * @When wait for :seconds second
  2528. */
  2529. public function waitForXSecond($seconds): void {
  2530. sleep($seconds);
  2531. }
  2532. /**
  2533. * @When wait for :seconds seconds
  2534. */
  2535. public function waitForXSeconds($seconds): void {
  2536. sleep($seconds);
  2537. }
  2538. /**
  2539. * @When apply message expiration job manually
  2540. */
  2541. public function applyMessageExpirationJob(): void {
  2542. $currentUser = $this->currentUser;
  2543. $this->setCurrentUser('admin');
  2544. $this->sendRequest('GET', '/apps/spreedcheats/get_message_expiration_job');
  2545. $response = $this->getDataFromResponse($this->response);
  2546. Assert::assertIsArray($response, 'Job not found');
  2547. Assert::assertArrayHasKey('id', $response, 'Job not found');
  2548. $this->runOcc(['background-job:execute', $response['id'], '--force-execute']);
  2549. $this->setCurrentUser($currentUser);
  2550. }
  2551. /*
  2552. * Requests
  2553. */
  2554. /**
  2555. * @Given /^user "([^"]*)" logs in$/
  2556. * @param string $user
  2557. */
  2558. public function userLogsIn(string $user) {
  2559. $loginUrl = $this->baseUrl . '/login';
  2560. $cookieJar = $this->getUserCookieJar($user);
  2561. // Request a new session and extract CSRF token
  2562. $client = new Client();
  2563. $this->response = $client->get(
  2564. $loginUrl,
  2565. [
  2566. 'cookies' => $cookieJar,
  2567. ]
  2568. );
  2569. $requestToken = $this->extractRequestTokenFromResponse($this->response);
  2570. // Login and extract new token
  2571. $password = ($user === 'admin') ? 'admin' : self::TEST_PASSWORD;
  2572. $client = new Client();
  2573. $this->response = $client->post(
  2574. $loginUrl,
  2575. [
  2576. 'form_params' => [
  2577. 'user' => $user,
  2578. 'password' => $password,
  2579. 'requesttoken' => $requestToken,
  2580. ],
  2581. 'cookies' => $cookieJar,
  2582. ]
  2583. );
  2584. $this->assertStatusCode($this->response, 200);
  2585. }
  2586. /**
  2587. * @When /^user "([^"]*)" uploads file "([^"]*)" as avatar of room "([^"]*)" with (\d+)(?: \((v1)\))?$/
  2588. */
  2589. public function userSendTheFileAsAvatarOfRoom(string $user, string $file, string $identifier, int $statusCode, string $apiVersion = 'v1'): void {
  2590. $this->setCurrentUser($user);
  2591. $options = [
  2592. 'multipart' => [
  2593. [
  2594. 'name' => 'file',
  2595. 'contents' => $file !== 'invalid' ? fopen(__DIR__ . '/../../../..' . $file, 'r') : '',
  2596. ],
  2597. ],
  2598. ];
  2599. $this->sendRequest('POST', '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier] . '/avatar', null, [], $options);
  2600. $this->assertStatusCode($this->response, $statusCode);
  2601. }
  2602. /**
  2603. * @When /^the room "([^"]*)" has an avatar with (\d+)(?: \((v1)\))?$/
  2604. */
  2605. public function theRoomNeedToHaveAnAvatarWithStatusCode(string $identifier, int $statusCode, string $apiVersion = 'v1'): void {
  2606. $this->sendRequest('GET', '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier] . '/avatar');
  2607. $this->assertStatusCode($this->response, $statusCode);
  2608. }
  2609. /**
  2610. * @When /^user "([^"]*)" delete the avatar of room "([^"]*)" with (\d+)(?: \((v1)\))?$/
  2611. */
  2612. public function userDeleteTheAvatarOfRoom(string $user, string $identifier, int $statusCode, string $apiVersion = 'v1'): void {
  2613. $this->setCurrentUser($user);
  2614. $this->sendRequest('DELETE', '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier] . '/avatar');
  2615. $this->assertStatusCode($this->response, $statusCode);
  2616. }
  2617. /**
  2618. * @param ResponseInterface $response
  2619. * @return string
  2620. */
  2621. private function extractRequestTokenFromResponse(ResponseInterface $response): string {
  2622. return substr(preg_replace('/(.*)data-requesttoken="(.*)">(.*)/sm', '\2', $response->getBody()->getContents()), 0, 89);
  2623. }
  2624. /**
  2625. * @When /^sending "([^"]*)" to "([^"]*)" with$/
  2626. * @param string $verb
  2627. * @param string $url
  2628. * @param TableNode|array|null $body
  2629. * @param array $headers
  2630. */
  2631. public function sendRequest($verb, $url, $body = null, array $headers = [], array $options = []) {
  2632. $fullUrl = $this->baseUrl . 'ocs/v2.php' . $url;
  2633. $this->sendRequestFullUrl($verb, $fullUrl, $body, $headers, $options);
  2634. }
  2635. /**
  2636. * @param string $verb
  2637. * @param string $url
  2638. * @param TableNode|array|null $body
  2639. * @param array $headers
  2640. */
  2641. public function sendRemoteRequest($verb, $url, $body = null, array $headers = []) {
  2642. $fullUrl = $this->baseRemoteUrl . 'ocs/v2.php' . $url;
  2643. $this->sendRequestFullUrl($verb, $fullUrl, $body, $headers);
  2644. }
  2645. /**
  2646. * @param string $verb
  2647. * @param string $fullUrl
  2648. * @param TableNode|array|null $body
  2649. * @param array $headers
  2650. */
  2651. public function sendRequestFullUrl($verb, $fullUrl, $body = null, array $headers = [], array $options = []) {
  2652. $client = new Client();
  2653. $options = array_merge($options, ['cookies' => $this->getUserCookieJar($this->currentUser)]);
  2654. if ($this->currentUser === 'admin') {
  2655. $options['auth'] = ['admin', 'admin'];
  2656. } elseif (strpos($this->currentUser, 'guest') !== 0) {
  2657. $options['auth'] = [$this->currentUser, self::TEST_PASSWORD];
  2658. }
  2659. if ($body instanceof TableNode) {
  2660. $fd = $body->getRowsHash();
  2661. $options['form_params'] = $fd;
  2662. } elseif (is_array($body)) {
  2663. $options['form_params'] = $body;
  2664. }
  2665. $options['headers'] = array_merge($headers, [
  2666. 'OCS-ApiRequest' => 'true',
  2667. 'Accept' => 'application/json',
  2668. ]);
  2669. try {
  2670. $this->response = $client->{$verb}($fullUrl, $options);
  2671. } catch (ClientException $ex) {
  2672. $this->response = $ex->getResponse();
  2673. } catch (\GuzzleHttp\Exception\ServerException $ex) {
  2674. $this->response = $ex->getResponse();
  2675. }
  2676. }
  2677. protected function getUserCookieJar($user) {
  2678. if (!isset($this->cookieJars[$user])) {
  2679. $this->cookieJars[$user] = new CookieJar();
  2680. }
  2681. return $this->cookieJars[$user];
  2682. }
  2683. /**
  2684. * @param ResponseInterface $response
  2685. * @param int $statusCode
  2686. * @param string $message
  2687. */
  2688. protected function assertStatusCode(ResponseInterface $response, int $statusCode, string $message = '') {
  2689. if ($statusCode !== $response->getStatusCode()) {
  2690. $content = $this->response->getBody()->getContents();
  2691. Assert::assertEquals(
  2692. $statusCode,
  2693. $response->getStatusCode(),
  2694. $message . ($message ? ': ' : '') . $content
  2695. );
  2696. } else {
  2697. Assert::assertEquals($statusCode, $response->getStatusCode(), $message);
  2698. }
  2699. }
  2700. }