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.

BasicStructure.php 14KB

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