BasicStructure.php 16 KB

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