diff options
author | Vincent Petry <pvince81@owncloud.com> | 2016-11-07 22:52:07 +0100 |
---|---|---|
committer | Lukas Reschke <lukas@statuscode.ch> | 2016-12-19 14:10:12 +0100 |
commit | 55bf9e3f711c5eacfe2c096dd2ee99bc4e7fcf8a (patch) | |
tree | 02739b79b894b8c8d347b70296fe8f0ed26d1581 | |
parent | 9b71ee27ff95336c36d608217d9621110e6674c1 (diff) | |
download | nextcloud-server-55bf9e3f711c5eacfe2c096dd2ee99bc4e7fcf8a.tar.gz nextcloud-server-55bf9e3f711c5eacfe2c096dd2ee99bc4e7fcf8a.zip |
Add transfer ownership integration tests (#26543)
* Add transfer ownership integration tests
* Added more transfer ownership tests and OCC checks
Signed-off-by: Lukas Reschke <lukas@statuscode.ch>
-rw-r--r-- | build/integration/config/behat.yml | 5 | ||||
-rw-r--r-- | build/integration/features/bootstrap/CommandLine.php | 167 | ||||
-rw-r--r-- | build/integration/features/bootstrap/CommandLineContext.php | 99 | ||||
-rw-r--r-- | build/integration/features/bootstrap/WebDav.php | 1 | ||||
-rw-r--r-- | build/integration/features/transfer-ownership.feature | 119 | ||||
-rwxr-xr-x | build/integration/run.sh | 54 |
6 files changed, 405 insertions, 40 deletions
diff --git a/build/integration/config/behat.yml b/build/integration/config/behat.yml index 9ef36f083e9..82ad6eaa170 100644 --- a/build/integration/config/behat.yml +++ b/build/integration/config/behat.yml @@ -22,6 +22,9 @@ default: baseUrl: http://localhost:8080 - ChecksumsContext: baseUrl: http://localhost:8080 + - CommandLineContext: + baseUrl: http://localhost:8080 + ocPath: ../../ federation: paths: - %paths.base%/../federation_features @@ -73,8 +76,6 @@ default: - admin regular_user_password: 123456 - - extensions: jarnaiz\JUnitFormatter\JUnitFormatterExtension: filename: report.xml diff --git a/build/integration/features/bootstrap/CommandLine.php b/build/integration/features/bootstrap/CommandLine.php new file mode 100644 index 00000000000..c0caf5f8e6e --- /dev/null +++ b/build/integration/features/bootstrap/CommandLine.php @@ -0,0 +1,167 @@ +<?php +/** + * @author Vincent Petry <pvince81@owncloud.com> + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +require __DIR__ . '/../../vendor/autoload.php'; + +trait CommandLine { + /** @var int return code of last command */ + private $lastCode; + /** @var string stdout of last command */ + private $lastStdOut; + /** @var string stderr of last command */ + private $lastStdErr; + + /** @var string */ + protected $ocPath = '../..'; + + /** + * Invokes an OCC command + * + * @param string OCC command, the part behind "occ". For example: "files:transfer-ownership" + * @return int exit code + */ + public function runOcc($args = []) { + $args = array_map(function($arg) { + return escapeshellarg($arg); + }, $args); + $args[] = '--no-ansi'; + $args = implode(' ', $args); + + $descriptor = [ + 0 => ['pipe', 'r'], + 1 => ['pipe', 'w'], + 2 => ['pipe', 'w'], + ]; + $process = proc_open('php console.php ' . $args, $descriptor, $pipes, $this->ocPath); + $this->lastStdOut = stream_get_contents($pipes[1]); + $this->lastStdErr = stream_get_contents($pipes[2]); + $this->lastCode = proc_close($process); + return $this->lastCode; + } + + /** + * @Given /^invoking occ with "([^"]*)"$/ + */ + public function invokingTheCommand($cmd) { + $args = explode(' ', $cmd); + $this->runOcc($args); + } + + /** + * Find exception texts in stderr + */ + public function findExceptions() { + $exceptions = []; + $captureNext = false; + // the exception text usually appears after an "[Exception"] row + foreach (explode("\n", $this->lastStdErr) as $line) { + if (preg_match('/\[Exception\]/', $line)) { + $captureNext = true; + continue; + } + if ($captureNext) { + $exceptions[] = trim($line); + $captureNext = false; + } + } + + return $exceptions; + } + + /** + * Finds all lines containing the given text + * + * @param string $input stdout or stderr output + * @param string $text text to search for + * @return array array of lines that matched + */ + public function findLines($input, $text) { + $results = []; + // the exception text usually appears after an "[Exception"] row + foreach (explode("\n", $input) as $line) { + if (strpos($line, $text) >= 0) { + $results[] = $line; + } + } + + return $results; + } + + /** + * @Then /^the command was successful$/ + */ + public function theCommandWasSuccessful() { + $exceptions = $this->findExceptions(); + if ($this->lastCode !== 0) { + $msg = 'The command was not successful, exit code was ' . $this->lastCode . '.'; + if (!empty($exceptions)) { + $msg .= ' Exceptions: ' . implode(', ', $exceptions); + } + throw new \Exception($msg); + } else if (!empty($exceptions)) { + $msg = 'The command was successful but triggered exceptions: ' . implode(', ', $exceptions); + throw new \Exception($msg); + } + } + + /** + * @Then /^the command failed with exit code ([0-9]+)$/ + */ + public function theCommandFailedWithExitCode($exitCode) { + if ($this->lastCode !== (int)$exitCode) { + throw new \Exception('The command was expected to fail with exit code ' . $exitCode . ' but got ' . $this->lastCode); + } + } + + /** + * @Then /^the command failed with exception text "([^"]*)"$/ + */ + public function theCommandFailedWithException($exceptionText) { + $exceptions = $this->findExceptions(); + if (empty($exceptions)) { + throw new \Exception('The command did not throw any exceptions'); + } + + if (!in_array($exceptionText, $exceptions)) { + throw new \Exception('The command did not throw any exception with the text "' . $exceptionText . '"'); + } + } + + /** + * @Then /^the command output contains the text "([^"]*)"$/ + */ + public function theCommandOutputContainsTheText($text) { + $lines = $this->findLines($this->lastStdOut, $text); + if (empty($lines)) { + throw new \Exception('The command did not output the expected text on stdout "' . $exceptionText . '"'); + } + } + + /** + * @Then /^the command error output contains the text "([^"]*)"$/ + */ + public function theCommandErrorOutputContainsTheText($text) { + $lines = $this->findLines($this->lastStdErr, $text); + if (empty($lines)) { + throw new \Exception('The command did not output the expected text on stderr "' . $exceptionText . '"'); + } + } +} diff --git a/build/integration/features/bootstrap/CommandLineContext.php b/build/integration/features/bootstrap/CommandLineContext.php new file mode 100644 index 00000000000..374a14410bf --- /dev/null +++ b/build/integration/features/bootstrap/CommandLineContext.php @@ -0,0 +1,99 @@ +<?php +/** + * @author Vincent Petry <pvince81@owncloud.com> + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +require __DIR__ . '/../../vendor/autoload.php'; + +use Behat\Behat\Hook\Scope\BeforeScenarioScope; + +class CommandLineContext implements \Behat\Behat\Context\Context { + use CommandLine; + + private $lastTransferPath; + + private $featureContext; + + public function __construct($ocPath, $baseUrl) { + $this->ocPath = rtrim($ocPath, '/') . '/'; + $this->localBaseUrl = $baseUrl; + $this->remoteBaseUrl = $baseUrl; + } + + /** @BeforeScenario */ + public function gatherContexts(BeforeScenarioScope $scope) { + $environment = $scope->getEnvironment(); + // this should really be "WebDavContext" ... + $this->featureContext = $environment->getContext('FeatureContext'); + } + + private function findLastTransferFolderForUser($sourceUser, $targetUser) { + $foundPaths = []; + $results = $this->featureContext->listFolder($targetUser, '', 1); + foreach ($results as $path => $data) { + $path = rawurldecode($path); + $parts = explode(' ', $path); + if (basename($parts[0]) !== 'transferred') { + continue; + } + if (isset($parts[2]) && $parts[2] === $sourceUser) { + // store timestamp as key + $foundPaths[] = [ + 'date' => strtotime(dirname($parts[4])), + 'path' => $path, + ]; + } + } + + if (empty($foundPaths)) { + return null; + } + + usort($foundPaths, function($a, $b) { + return $a['date'] - $b['date']; + }); + + $davPath = rtrim($this->featureContext->getDavFilesPath($targetUser), '/'); + + $foundPath = end($foundPaths)['path']; + // strip dav path + return substr($foundPath, strlen($davPath) + 1); + } + + /** + * @When /^transfering ownership from "([^"]+)" to "([^"]+)"/ + */ + public function transferingOwnership($user1, $user2) { + if ($this->runOcc(['files:transfer-ownership', $user1, $user2]) === 0) { + $this->lastTransferPath = $this->findLastTransferFolderForUser($user1, $user2); + } else { + // failure + $this->lastTransferPath = null; + } + } + + /** + * @When /^using received transfer folder of "([^"]+)" as dav path$/ + */ + public function usingTransferFolderAsDavPath($user) { + $davPath = $this->featureContext->getDavFilesPath($user); + $davPath = rtrim($davPath, '/') . $this->lastTransferPath; + $this->featureContext->usingDavPath($davPath); + } +} diff --git a/build/integration/features/bootstrap/WebDav.php b/build/integration/features/bootstrap/WebDav.php index 0a08e764e30..c44a6175579 100644 --- a/build/integration/features/bootstrap/WebDav.php +++ b/build/integration/features/bootstrap/WebDav.php @@ -514,6 +514,7 @@ trait WebDav { */ public function userCreatedAFolder($user, $destination) { try { + $destination = '/' . ltrim($destination, '/'); $this->response = $this->makeDavRequest($user, "MKCOL", $destination, []); } catch (\GuzzleHttp\Exception\ServerException $e) { // 4xx and 5xx responses cause an exception diff --git a/build/integration/features/transfer-ownership.feature b/build/integration/features/transfer-ownership.feature new file mode 100644 index 00000000000..92361f70900 --- /dev/null +++ b/build/integration/features/transfer-ownership.feature @@ -0,0 +1,119 @@ +Feature: transfer-ownership + + Scenario: transfering ownership of a file + Given user "user0" exists + And user "user1" exists + And User "user0" uploads file "data/textfile.txt" to "/somefile.txt" + When transfering ownership from "user0" to "user1" + And the command was successful + And As an "user1" + And using received transfer folder of "user1" as dav path + Then Downloaded content when downloading file "/somefile.txt" with range "bytes=0-6" should be "This is" + + Scenario: transfering ownership of a folder + Given user "user0" exists + And user "user1" exists + And User "user0" created a folder "/test" + And User "user0" uploads file "data/textfile.txt" to "/test/somefile.txt" + When transfering ownership from "user0" to "user1" + And the command was successful + And As an "user1" + And using received transfer folder of "user1" as dav path + Then Downloaded content when downloading file "/test/somefile.txt" with range "bytes=0-6" should be "This is" + + Scenario: transfering ownership of file shares + Given user "user0" exists + And user "user1" exists + And user "user2" exists + And User "user0" uploads file "data/textfile.txt" to "/somefile.txt" + And file "/somefile.txt" of user "user0" is shared with user "user2" with permissions 19 + When transfering ownership from "user0" to "user1" + And the command was successful + And As an "user2" + Then Downloaded content when downloading file "/somefile.txt" with range "bytes=0-6" should be "This is" + + Scenario: transfering ownership of folder shared with third user + Given user "user0" exists + And user "user1" exists + And user "user2" exists + And User "user0" created a folder "/test" + And User "user0" uploads file "data/textfile.txt" to "/test/somefile.txt" + And folder "/test" of user "user0" is shared with user "user2" with permissions 31 + When transfering ownership from "user0" to "user1" + And the command was successful + And As an "user2" + Then Downloaded content when downloading file "/test/somefile.txt" with range "bytes=0-6" should be "This is" + + Scenario: transfering ownership of folder shared with transfer recipient + Given user "user0" exists + And user "user1" exists + And User "user0" created a folder "/test" + And User "user0" uploads file "data/textfile.txt" to "/test/somefile.txt" + And folder "/test" of user "user0" is shared with user "user1" with permissions 31 + When transfering ownership from "user0" to "user1" + And the command was successful + And As an "user1" + Then as "user1" the folder "/test" does not exist + And using received transfer folder of "user1" as dav path + And Downloaded content when downloading file "/test/somefile.txt" with range "bytes=0-6" should be "This is" + + Scenario: transfering ownership of folder doubly shared with third user + Given group "group1" exists + And user "user0" exists + And user "user1" exists + And user "user2" exists + And user "user2" belongs to group "group1" + And User "user0" created a folder "/test" + And User "user0" uploads file "data/textfile.txt" to "/test/somefile.txt" + And folder "/test" of user "user0" is shared with group "group1" with permissions 31 + And folder "/test" of user "user0" is shared with user "user2" with permissions 31 + When transfering ownership from "user0" to "user1" + And the command was successful + And As an "user2" + Then Downloaded content when downloading file "/test/somefile.txt" with range "bytes=0-6" should be "This is" + + Scenario: transfering ownership does not transfer received shares + Given user "user0" exists + And user "user1" exists + And user "user2" exists + And User "user2" created a folder "/test" + And folder "/test" of user "user2" is shared with user "user0" with permissions 31 + When transfering ownership from "user0" to "user1" + And the command was successful + And As an "user1" + And using received transfer folder of "user1" as dav path + Then as "user1" the folder "/test" does not exist + + @local_storage + Scenario: transfering ownership does not transfer external storage + Given user "user0" exists + And user "user1" exists + When transfering ownership from "user0" to "user1" + And the command was successful + And As an "user1" + And using received transfer folder of "user1" as dav path + Then as "user1" the folder "/local_storage" does not exist + + Scenario: transfering ownership does not fail with shared trashed files + Given user "user0" exists + And user "user1" exists + And user "user2" exists + And User "user0" created a folder "/sub" + And User "user0" created a folder "/sub/test" + And folder "/sub/test" of user "user0" is shared with user "user2" with permissions 31 + And User "user0" deletes folder "/sub" + When transfering ownership from "user0" to "user1" + Then the command was successful + + Scenario: transfering ownership fails with invalid source user + Given user "user0" exists + When transfering ownership from "invalid_user" to "user0" + Then the command error output contains the text "Unknown source user" + And the command failed with exit code 1 + + Scenario: transfering ownership fails with invalid target user + Given user "user0" exists + When transfering ownership from "user0" to "invalid_user" + Then the command error output contains the text "Unknown target user" + And the command failed with exit code 1 + diff --git a/build/integration/run.sh b/build/integration/run.sh index 145415fd079..ff5efe0c3ec 100755 --- a/build/integration/run.sh +++ b/build/integration/run.sh @@ -1,31 +1,13 @@ #!/usr/bin/env bash -COMPOSER=$(which composer) +composer install + +OC_PATH=../../ +OCC=${OC_PATH}occ SCENARIO_TO_RUN=$1 HIDE_OC_LOGS=$2 -if [ -x "$COMPOSER" ]; then - echo "Using composer executable $COMPOSER" -else - echo "Could not find composer executable" >&2 - exit 1 -fi - -INSTALLED=$(../../occ status | grep installed: | cut -d " " -f 5) - -if [ "$INSTALLED" == "true" ]; then - # Disable bruteforce protection because the integration tests do trigger them - ../../occ config:system:set auth.bruteforce.protection.enabled --value false --type bool -else - if [ "$SCENARIO_TO_RUN" != "setup_features/setup.feature" ]; then - echo "Nextcloud instance needs to be installed" >&2 - exit 1 - fi -fi - -composer install - # avoid port collision on jenkins - use $EXECUTOR_NUMBER if [ -z "$EXECUTOR_NUMBER" ]; then EXECUTOR_NUMBER=0 @@ -45,34 +27,30 @@ echo $PHPPID_FED export TEST_SERVER_URL="http://localhost:$PORT/ocs/" export TEST_SERVER_FED_URL="http://localhost:$PORT_FED/ocs/" -if [ "$INSTALLED" == "true" ]; then - #Enable external storage app - ../../occ app:enable files_external +#Enable external storage app +$OCC app:enable files_external - mkdir -p work/local_storage - OUTPUT_CREATE_STORAGE=`../../occ files_external:create local_storage local null::null -c datadir=./build/integration/work/local_storage` +mkdir -p work/local_storage +OUTPUT_CREATE_STORAGE=`$OCC files_external:create local_storage local null::null -c datadir=./build/integration/work/local_storage` - ID_STORAGE=`echo $OUTPUT_CREATE_STORAGE | awk {'print $5'}` +ID_STORAGE=`echo $OUTPUT_CREATE_STORAGE | awk {'print $5'}` - ../../occ files_external:option $ID_STORAGE enable_sharing true -fi +$OCC files_external:option $ID_STORAGE enable_sharing true -vendor/bin/behat -f junit -f pretty $SCENARIO_TO_RUN +vendor/bin/behat --strict -f junit -f pretty $SCENARIO_TO_RUN RESULT=$? kill $PHPPID kill $PHPPID_FED -if [ "$INSTALLED" -eq "true" ]; then - ../../occ files_external:delete -y $ID_STORAGE +$OCC files_external:delete -y $ID_STORAGE - #Disable external storage app - ../../occ app:disable files_external -fi +#Disable external storage app +$OCC app:disable files_external if [ -z $HIDE_OC_LOGS ]; then - tail "../../data/nextcloud.log" + tail "${OC_PATH}/data/owncloud.log" fi +echo "runsh: Exit code: $RESULT" exit $RESULT - |