BasicStructure.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549
  1. <?php
  2. /**
  3. * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
  4. * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
  5. * SPDX-License-Identifier: AGPL-3.0-or-later
  6. */
  7. use Behat\Gherkin\Node\TableNode;
  8. use GuzzleHttp\Client;
  9. use GuzzleHttp\Cookie\CookieJar;
  10. use GuzzleHttp\Exception\ClientException;
  11. use PHPUnit\Framework\Assert;
  12. use Psr\Http\Message\ResponseInterface;
  13. require __DIR__ . '/../../vendor/autoload.php';
  14. trait BasicStructure {
  15. use Auth;
  16. use Avatar;
  17. use Download;
  18. use Mail;
  19. /** @var string */
  20. private $currentUser = '';
  21. /** @var string */
  22. private $currentServer = '';
  23. /** @var string */
  24. private $baseUrl = '';
  25. /** @var int */
  26. private $apiVersion = 1;
  27. /** @var ResponseInterface */
  28. private $response = null;
  29. /** @var CookieJar */
  30. private $cookieJar;
  31. /** @var string */
  32. private $requestToken;
  33. protected $adminUser;
  34. protected $regularUser;
  35. protected $localBaseUrl;
  36. protected $remoteBaseUrl;
  37. public function __construct($baseUrl, $admin, $regular_user_password) {
  38. // Initialize your context here
  39. $this->baseUrl = $baseUrl;
  40. $this->adminUser = $admin;
  41. $this->regularUser = $regular_user_password;
  42. $this->localBaseUrl = $this->baseUrl;
  43. $this->remoteBaseUrl = $this->baseUrl;
  44. $this->currentServer = 'LOCAL';
  45. $this->cookieJar = new CookieJar();
  46. // in case of ci deployment we take the server url from the environment
  47. $testServerUrl = getenv('TEST_SERVER_URL');
  48. if ($testServerUrl !== false) {
  49. $this->baseUrl = $testServerUrl;
  50. $this->localBaseUrl = $testServerUrl;
  51. }
  52. // federated server url from the environment
  53. $testRemoteServerUrl = getenv('TEST_SERVER_FED_URL');
  54. if ($testRemoteServerUrl !== false) {
  55. $this->remoteBaseUrl = $testRemoteServerUrl;
  56. }
  57. }
  58. /**
  59. * @Given /^using api version "(\d+)"$/
  60. * @param string $version
  61. */
  62. public function usingApiVersion($version) {
  63. $this->apiVersion = (int)$version;
  64. }
  65. /**
  66. * @Given /^As an "([^"]*)"$/
  67. * @param string $user
  68. */
  69. public function asAn($user) {
  70. $this->currentUser = $user;
  71. }
  72. /**
  73. * @Given /^Using server "(LOCAL|REMOTE)"$/
  74. * @param string $server
  75. * @return string Previous used server
  76. */
  77. public function usingServer($server) {
  78. $previousServer = $this->currentServer;
  79. if ($server === 'LOCAL') {
  80. $this->baseUrl = $this->localBaseUrl;
  81. $this->currentServer = 'LOCAL';
  82. return $previousServer;
  83. } else {
  84. $this->baseUrl = $this->remoteBaseUrl;
  85. $this->currentServer = 'REMOTE';
  86. return $previousServer;
  87. }
  88. }
  89. /**
  90. * @When /^sending "([^"]*)" to "([^"]*)"$/
  91. * @param string $verb
  92. * @param string $url
  93. */
  94. public function sendingTo($verb, $url) {
  95. $this->sendingToWith($verb, $url, null);
  96. }
  97. /**
  98. * Parses the xml answer to get ocs response which doesn't match with
  99. * http one in v1 of the api.
  100. *
  101. * @param ResponseInterface $response
  102. * @return string
  103. */
  104. public function getOCSResponse($response) {
  105. return simplexml_load_string($response->getBody())->meta[0]->statuscode;
  106. }
  107. /**
  108. * This function is needed to use a vertical fashion in the gherkin tables.
  109. *
  110. * @param array $arrayOfArrays
  111. * @return array
  112. */
  113. public function simplifyArray($arrayOfArrays) {
  114. $a = array_map(function ($subArray) {
  115. return $subArray[0];
  116. }, $arrayOfArrays);
  117. return $a;
  118. }
  119. /**
  120. * @When /^sending "([^"]*)" to "([^"]*)" with$/
  121. * @param string $verb
  122. * @param string $url
  123. * @param TableNode $body
  124. */
  125. public function sendingToWith($verb, $url, $body) {
  126. $fullUrl = $this->baseUrl . "v{$this->apiVersion}.php" . $url;
  127. $client = new Client();
  128. $options = [];
  129. if ($this->currentUser === 'admin') {
  130. $options['auth'] = $this->adminUser;
  131. } elseif (strpos($this->currentUser, 'anonymous') !== 0) {
  132. $options['auth'] = [$this->currentUser, $this->regularUser];
  133. }
  134. $options['headers'] = [
  135. 'OCS-APIRequest' => 'true'
  136. ];
  137. if ($body instanceof TableNode) {
  138. $fd = $body->getRowsHash();
  139. $options['form_params'] = $fd;
  140. }
  141. // TODO: Fix this hack!
  142. if ($verb === 'PUT' && $body === null) {
  143. $options['form_params'] = [
  144. 'foo' => 'bar',
  145. ];
  146. }
  147. try {
  148. $this->response = $client->request($verb, $fullUrl, $options);
  149. } catch (ClientException $ex) {
  150. $this->response = $ex->getResponse();
  151. }
  152. }
  153. /**
  154. * @param string $verb
  155. * @param string $url
  156. * @param TableNode|array|null $body
  157. * @param array $headers
  158. */
  159. protected function sendRequestForJSON(string $verb, string $url, $body = null, array $headers = []): void {
  160. $fullUrl = $this->baseUrl . "v{$this->apiVersion}.php" . $url;
  161. $client = new Client();
  162. $options = [];
  163. if ($this->currentUser === 'admin') {
  164. $options['auth'] = ['admin', 'admin'];
  165. } elseif (strpos($this->currentUser, 'guest') !== 0) {
  166. $options['auth'] = [$this->currentUser, self::TEST_PASSWORD];
  167. }
  168. if ($body instanceof TableNode) {
  169. $fd = $body->getRowsHash();
  170. $options['form_params'] = $fd;
  171. } elseif (is_array($body)) {
  172. $options['form_params'] = $body;
  173. }
  174. $options['headers'] = array_merge($headers, [
  175. 'OCS-ApiRequest' => 'true',
  176. 'Accept' => 'application/json',
  177. ]);
  178. try {
  179. $this->response = $client->{$verb}($fullUrl, $options);
  180. } catch (ClientException $ex) {
  181. $this->response = $ex->getResponse();
  182. }
  183. }
  184. /**
  185. * @When /^sending "([^"]*)" with exact url to "([^"]*)"$/
  186. * @param string $verb
  187. * @param string $url
  188. */
  189. public function sendingToDirectUrl($verb, $url) {
  190. $this->sendingToWithDirectUrl($verb, $url, null);
  191. }
  192. public function sendingToWithDirectUrl($verb, $url, $body) {
  193. $fullUrl = substr($this->baseUrl, 0, -5) . $url;
  194. $client = new Client();
  195. $options = [];
  196. if ($this->currentUser === 'admin') {
  197. $options['auth'] = $this->adminUser;
  198. } elseif (strpos($this->currentUser, 'anonymous') !== 0) {
  199. $options['auth'] = [$this->currentUser, $this->regularUser];
  200. }
  201. if ($body instanceof TableNode) {
  202. $fd = $body->getRowsHash();
  203. $options['form_params'] = $fd;
  204. }
  205. try {
  206. $this->response = $client->request($verb, $fullUrl, $options);
  207. } catch (ClientException $ex) {
  208. $this->response = $ex->getResponse();
  209. }
  210. }
  211. public function isExpectedUrl($possibleUrl, $finalPart) {
  212. $baseUrlChopped = substr($this->baseUrl, 0, -4);
  213. $endCharacter = strlen($baseUrlChopped) + strlen($finalPart);
  214. return (substr($possibleUrl, 0, $endCharacter) == "$baseUrlChopped" . "$finalPart");
  215. }
  216. /**
  217. * @Then /^the OCS status code should be "([^"]*)"$/
  218. * @param int $statusCode
  219. */
  220. public function theOCSStatusCodeShouldBe($statusCode) {
  221. Assert::assertEquals($statusCode, $this->getOCSResponse($this->response));
  222. }
  223. /**
  224. * @Then /^the HTTP status code should be "([^"]*)"$/
  225. * @param int $statusCode
  226. */
  227. public function theHTTPStatusCodeShouldBe($statusCode) {
  228. Assert::assertEquals($statusCode, $this->response->getStatusCode());
  229. }
  230. /**
  231. * @Then /^the Content-Type should be "([^"]*)"$/
  232. * @param string $contentType
  233. */
  234. public function theContentTypeShouldbe($contentType) {
  235. Assert::assertEquals($contentType, $this->response->getHeader('Content-Type')[0]);
  236. }
  237. /**
  238. * @param ResponseInterface $response
  239. */
  240. private function extracRequestTokenFromResponse(ResponseInterface $response) {
  241. $this->requestToken = substr(preg_replace('/(.*)data-requesttoken="(.*)">(.*)/sm', '\2', $response->getBody()->getContents()), 0, 89);
  242. }
  243. /**
  244. * @Given Logging in using web as :user
  245. * @param string $user
  246. */
  247. public function loggingInUsingWebAs($user) {
  248. $loginUrl = substr($this->baseUrl, 0, -5) . '/index.php/login';
  249. // Request a new session and extract CSRF token
  250. $client = new Client();
  251. $response = $client->get(
  252. $loginUrl,
  253. [
  254. 'cookies' => $this->cookieJar,
  255. ]
  256. );
  257. $this->extracRequestTokenFromResponse($response);
  258. // Login and extract new token
  259. $password = ($user === 'admin') ? 'admin' : '123456';
  260. $client = new Client();
  261. $response = $client->post(
  262. $loginUrl,
  263. [
  264. 'form_params' => [
  265. 'user' => $user,
  266. 'password' => $password,
  267. 'requesttoken' => $this->requestToken,
  268. ],
  269. 'cookies' => $this->cookieJar,
  270. ]
  271. );
  272. $this->extracRequestTokenFromResponse($response);
  273. }
  274. /**
  275. * @When Sending a :method to :url with requesttoken
  276. * @param string $method
  277. * @param string $url
  278. * @param TableNode|array|null $body
  279. */
  280. public function sendingAToWithRequesttoken($method, $url, $body = null) {
  281. $baseUrl = substr($this->baseUrl, 0, -5);
  282. $options = [
  283. 'cookies' => $this->cookieJar,
  284. 'headers' => [
  285. 'requesttoken' => $this->requestToken
  286. ],
  287. ];
  288. if ($body instanceof TableNode) {
  289. $fd = $body->getRowsHash();
  290. $options['form_params'] = $fd;
  291. } elseif ($body) {
  292. $options = array_merge_recursive($options, $body);
  293. }
  294. $client = new Client();
  295. try {
  296. $this->response = $client->request(
  297. $method,
  298. $baseUrl . $url,
  299. $options
  300. );
  301. } catch (ClientException $e) {
  302. $this->response = $e->getResponse();
  303. }
  304. }
  305. /**
  306. * @When Sending a :method to :url without requesttoken
  307. * @param string $method
  308. * @param string $url
  309. */
  310. public function sendingAToWithoutRequesttoken($method, $url) {
  311. $baseUrl = substr($this->baseUrl, 0, -5);
  312. $client = new Client();
  313. try {
  314. $this->response = $client->request(
  315. $method,
  316. $baseUrl . $url,
  317. [
  318. 'cookies' => $this->cookieJar
  319. ]
  320. );
  321. } catch (ClientException $e) {
  322. $this->response = $e->getResponse();
  323. }
  324. }
  325. public static function removeFile($path, $filename) {
  326. if (file_exists("$path" . "$filename")) {
  327. unlink("$path" . "$filename");
  328. }
  329. }
  330. /**
  331. * @Given User :user modifies text of :filename with text :text
  332. * @param string $user
  333. * @param string $filename
  334. * @param string $text
  335. */
  336. public function modifyTextOfFile($user, $filename, $text) {
  337. self::removeFile($this->getDataDirectory() . "/$user/files", "$filename");
  338. file_put_contents($this->getDataDirectory() . "/$user/files" . "$filename", "$text");
  339. }
  340. private function getDataDirectory() {
  341. // Based on "runOcc" from CommandLine trait
  342. $args = ['config:system:get', 'datadirectory'];
  343. $args = array_map(function ($arg) {
  344. return escapeshellarg($arg);
  345. }, $args);
  346. $args[] = '--no-ansi --no-warnings';
  347. $args = implode(' ', $args);
  348. $descriptor = [
  349. 0 => ['pipe', 'r'],
  350. 1 => ['pipe', 'w'],
  351. 2 => ['pipe', 'w'],
  352. ];
  353. $process = proc_open('php console.php ' . $args, $descriptor, $pipes, $ocPath = '../..');
  354. $lastStdOut = stream_get_contents($pipes[1]);
  355. proc_close($process);
  356. return trim($lastStdOut);
  357. }
  358. /**
  359. * @Given file :filename is created :times times in :user user data
  360. * @param string $filename
  361. * @param string $times
  362. * @param string $user
  363. */
  364. public function fileIsCreatedTimesInUserData($filename, $times, $user) {
  365. for ($i = 0; $i < $times; $i++) {
  366. file_put_contents($this->getDataDirectory() . "/$user/files" . "$filename-$i", "content-$i");
  367. }
  368. }
  369. public function createFileSpecificSize($name, $size) {
  370. $file = fopen('work/' . "$name", 'w');
  371. fseek($file, $size - 1, SEEK_CUR);
  372. fwrite($file, 'a'); // write a dummy char at SIZE position
  373. fclose($file);
  374. }
  375. public function createFileWithText($name, $text) {
  376. $file = fopen('work/' . "$name", 'w');
  377. fwrite($file, $text);
  378. fclose($file);
  379. }
  380. /**
  381. * @Given file :filename of size :size is created in local storage
  382. * @param string $filename
  383. * @param string $size
  384. */
  385. public function fileIsCreatedInLocalStorageWithSize($filename, $size) {
  386. $this->createFileSpecificSize("local_storage/$filename", $size);
  387. }
  388. /**
  389. * @Given file :filename with text :text is created in local storage
  390. * @param string $filename
  391. * @param string $text
  392. */
  393. public function fileIsCreatedInLocalStorageWithText($filename, $text) {
  394. $this->createFileWithText("local_storage/$filename", $text);
  395. }
  396. /**
  397. * @When Sleep for :seconds seconds
  398. * @param int $seconds
  399. */
  400. public function sleepForSeconds($seconds) {
  401. sleep((int)$seconds);
  402. }
  403. /**
  404. * @BeforeSuite
  405. */
  406. public static function addFilesToSkeleton() {
  407. for ($i = 0; $i < 5; $i++) {
  408. file_put_contents('../../core/skeleton/' . 'textfile' . "$i" . '.txt', "Nextcloud test text file\n");
  409. }
  410. if (!file_exists('../../core/skeleton/FOLDER')) {
  411. mkdir('../../core/skeleton/FOLDER', 0777, true);
  412. }
  413. if (!file_exists('../../core/skeleton/PARENT')) {
  414. mkdir('../../core/skeleton/PARENT', 0777, true);
  415. }
  416. file_put_contents('../../core/skeleton/PARENT/' . 'parent.txt', "Nextcloud test text file\n");
  417. if (!file_exists('../../core/skeleton/PARENT/CHILD')) {
  418. mkdir('../../core/skeleton/PARENT/CHILD', 0777, true);
  419. }
  420. file_put_contents('../../core/skeleton/PARENT/CHILD/' . 'child.txt', "Nextcloud test text file\n");
  421. }
  422. /**
  423. * @AfterSuite
  424. */
  425. public static function removeFilesFromSkeleton() {
  426. for ($i = 0; $i < 5; $i++) {
  427. self::removeFile('../../core/skeleton/', 'textfile' . "$i" . '.txt');
  428. }
  429. if (is_dir('../../core/skeleton/FOLDER')) {
  430. rmdir('../../core/skeleton/FOLDER');
  431. }
  432. self::removeFile('../../core/skeleton/PARENT/CHILD/', 'child.txt');
  433. if (is_dir('../../core/skeleton/PARENT/CHILD')) {
  434. rmdir('../../core/skeleton/PARENT/CHILD');
  435. }
  436. self::removeFile('../../core/skeleton/PARENT/', 'parent.txt');
  437. if (is_dir('../../core/skeleton/PARENT')) {
  438. rmdir('../../core/skeleton/PARENT');
  439. }
  440. }
  441. /**
  442. * @BeforeScenario @local_storage
  443. */
  444. public static function removeFilesFromLocalStorageBefore() {
  445. $dir = './work/local_storage/';
  446. $di = new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS);
  447. $ri = new RecursiveIteratorIterator($di, RecursiveIteratorIterator::CHILD_FIRST);
  448. foreach ($ri as $file) {
  449. $file->isDir() ? rmdir($file) : unlink($file);
  450. }
  451. }
  452. /**
  453. * @AfterScenario @local_storage
  454. */
  455. public static function removeFilesFromLocalStorageAfter() {
  456. $dir = './work/local_storage/';
  457. $di = new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS);
  458. $ri = new RecursiveIteratorIterator($di, RecursiveIteratorIterator::CHILD_FIRST);
  459. foreach ($ri as $file) {
  460. $file->isDir() ? rmdir($file) : unlink($file);
  461. }
  462. }
  463. /**
  464. * @Given /^cookies are reset$/
  465. */
  466. public function cookiesAreReset() {
  467. $this->cookieJar = new CookieJar();
  468. }
  469. /**
  470. * @Then The following headers should be set
  471. * @param TableNode $table
  472. * @throws \Exception
  473. */
  474. public function theFollowingHeadersShouldBeSet(TableNode $table) {
  475. foreach ($table->getTable() as $header) {
  476. $headerName = $header[0];
  477. $expectedHeaderValue = $header[1];
  478. $returnedHeader = $this->response->getHeader($headerName)[0];
  479. if ($returnedHeader !== $expectedHeaderValue) {
  480. throw new \Exception(
  481. sprintf(
  482. "Expected value '%s' for header '%s', got '%s'",
  483. $expectedHeaderValue,
  484. $headerName,
  485. $returnedHeader
  486. )
  487. );
  488. }
  489. }
  490. }
  491. }