aboutsummaryrefslogtreecommitdiffstats
path: root/build/integration/features
diff options
context:
space:
mode:
Diffstat (limited to 'build/integration/features')
-rw-r--r--build/integration/features/auth.feature3
-rw-r--r--build/integration/features/avatar.feature2
-rw-r--r--build/integration/features/bootstrap/Activity.php32
-rw-r--r--build/integration/features/bootstrap/AppConfiguration.php28
-rw-r--r--build/integration/features/bootstrap/Auth.php36
-rw-r--r--build/integration/features/bootstrap/Avatar.php36
-rw-r--r--build/integration/features/bootstrap/BasicStructure.php102
-rw-r--r--build/integration/features/bootstrap/CalDavContext.php208
-rw-r--r--build/integration/features/bootstrap/CapabilitiesContext.php37
-rw-r--r--build/integration/features/bootstrap/CardDavContext.php99
-rw-r--r--build/integration/features/bootstrap/ChecksumsContext.php66
-rw-r--r--build/integration/features/bootstrap/CollaborationContext.php28
-rw-r--r--build/integration/features/bootstrap/CommandLine.php26
-rw-r--r--build/integration/features/bootstrap/CommandLineContext.php53
-rw-r--r--build/integration/features/bootstrap/CommentsContext.php71
-rw-r--r--build/integration/features/bootstrap/ContactsMenu.php22
-rw-r--r--build/integration/features/bootstrap/ConversionsContext.php60
-rw-r--r--build/integration/features/bootstrap/DavFeatureContext.php24
-rw-r--r--build/integration/features/bootstrap/Download.php72
-rw-r--r--build/integration/features/bootstrap/ExternalStorage.php123
-rw-r--r--build/integration/features/bootstrap/FakeSMTPHelper.php37
-rw-r--r--build/integration/features/bootstrap/FeatureContext.php35
-rw-r--r--build/integration/features/bootstrap/FederationContext.php84
-rw-r--r--build/integration/features/bootstrap/FilesDropContext.php65
-rw-r--r--build/integration/features/bootstrap/LDAPContext.php30
-rw-r--r--build/integration/features/bootstrap/Mail.php26
-rw-r--r--build/integration/features/bootstrap/MetadataContext.php124
-rw-r--r--build/integration/features/bootstrap/PrincipalPropertySearchContext.php141
-rw-r--r--build/integration/features/bootstrap/Provisioning.php143
-rw-r--r--build/integration/features/bootstrap/RateLimitingContext.php31
-rw-r--r--build/integration/features/bootstrap/RemoteContext.php31
-rw-r--r--build/integration/features/bootstrap/RoutingContext.php19
-rw-r--r--build/integration/features/bootstrap/Search.php22
-rw-r--r--build/integration/features/bootstrap/SetupContext.php22
-rw-r--r--build/integration/features/bootstrap/ShareesContext.php25
-rw-r--r--build/integration/features/bootstrap/Sharing.php140
-rw-r--r--build/integration/features/bootstrap/SharingContext.php28
-rw-r--r--build/integration/features/bootstrap/TagsContext.php37
-rw-r--r--build/integration/features/bootstrap/TalkContext.php22
-rw-r--r--build/integration/features/bootstrap/Theming.php49
-rw-r--r--build/integration/features/bootstrap/Trashbin.php27
-rw-r--r--build/integration/features/bootstrap/WebDav.php450
-rw-r--r--build/integration/features/caldav.feature61
-rw-r--r--build/integration/features/carddav.feature64
-rw-r--r--build/integration/features/checksums.feature76
-rw-r--r--build/integration/features/comments-search.feature271
-rw-r--r--build/integration/features/comments.feature215
-rw-r--r--build/integration/features/contacts-menu.feature64
-rw-r--r--build/integration/features/dav-v2.feature88
-rw-r--r--build/integration/features/download.feature294
-rw-r--r--build/integration/features/external-storage.feature62
-rw-r--r--build/integration/features/favorites.feature149
-rw-r--r--build/integration/features/log-condition.feature39
-rw-r--r--build/integration/features/maintenance-mode.feature8
-rw-r--r--build/integration/features/ocs-v1.feature2
-rw-r--r--build/integration/features/provisioning-v1.feature1489
-rw-r--r--build/integration/features/provisioning-v2.feature3
-rw-r--r--build/integration/features/ratelimiting.feature58
-rw-r--r--build/integration/features/tags.feature462
-rw-r--r--build/integration/features/transfer-ownership.feature594
-rw-r--r--build/integration/features/trashbin.feature81
-rw-r--r--build/integration/features/webdav-related.feature632
62 files changed, 2558 insertions, 4870 deletions
diff --git a/build/integration/features/auth.feature b/build/integration/features/auth.feature
index 679b2465659..f9c8b7d0e46 100644
--- a/build/integration/features/auth.feature
+++ b/build/integration/features/auth.feature
@@ -1,3 +1,6 @@
+# SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
+# SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+# SPDX-License-Identifier: AGPL-3.0-only
Feature: auth
Background:
diff --git a/build/integration/features/avatar.feature b/build/integration/features/avatar.feature
index 06135a25693..4c8c37fb98c 100644
--- a/build/integration/features/avatar.feature
+++ b/build/integration/features/avatar.feature
@@ -1,3 +1,5 @@
+# SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
+# SPDX-License-Identifier: AGPL-3.0-or-later
Feature: avatar
Background:
diff --git a/build/integration/features/bootstrap/Activity.php b/build/integration/features/bootstrap/Activity.php
new file mode 100644
index 00000000000..4172776304d
--- /dev/null
+++ b/build/integration/features/bootstrap/Activity.php
@@ -0,0 +1,32 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+use Behat\Gherkin\Node\TableNode;
+use PHPUnit\Framework\Assert;
+
+trait Activity {
+ use BasicStructure;
+
+ /**
+ * @Then last activity should be
+ * @param TableNode $activity
+ */
+ public function lastActivityIs(TableNode $activity): void {
+ $this->sendRequestForJSON('GET', '/apps/activity/api/v2/activity');
+ $this->theHTTPStatusCodeShouldBe('200');
+ $data = json_decode($this->response->getBody()->getContents(), true);
+ $activities = $data['ocs']['data'];
+ /* Sort by id */
+ uasort($activities, fn ($a, $b) => $a['activity_id'] <=> $b['activity_id']);
+ $lastActivity = array_pop($activities);
+ foreach ($activity->getRowsHash() as $key => $value) {
+ Assert::assertEquals($value, $lastActivity[$key]);
+ }
+ }
+}
diff --git a/build/integration/features/bootstrap/AppConfiguration.php b/build/integration/features/bootstrap/AppConfiguration.php
index 740a8b169a6..e8580ed537b 100644
--- a/build/integration/features/bootstrap/AppConfiguration.php
+++ b/build/integration/features/bootstrap/AppConfiguration.php
@@ -1,29 +1,9 @@
<?php
+
/**
- * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com>
- *
- * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Daniel Calviño Sánchez <danxuliu@gmail.com>
- * @author Joas Schilling <coding@schilljs.com>
- * @author John Molakvoæ <skjnldsv@protonmail.com>
- * @author Sergio Bertolin <sbertolin@solidgear.es>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * 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
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
use Behat\Behat\Hook\Scope\AfterScenarioScope;
use Behat\Behat\Hook\Scope\BeforeScenarioScope;
diff --git a/build/integration/features/bootstrap/Auth.php b/build/integration/features/bootstrap/Auth.php
index a0b02e2b64b..aeaade85383 100644
--- a/build/integration/features/bootstrap/Auth.php
+++ b/build/integration/features/bootstrap/Auth.php
@@ -1,34 +1,14 @@
<?php
+
/**
- * @copyright Copyright (c) 2016 Christoph Wurst <christoph@winzerhof-wurst.at>
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Daniel Kesselberg <mail@danielkesselberg.de>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Lukas Reschke <lukas@statuscode.ch>
- * @author Phil Davis <phil.davis@inf.org>
- * @author Robin Appelman <robin@icewind.nl>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * 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
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
use GuzzleHttp\Client;
+use GuzzleHttp\Cookie\CookieJar;
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Exception\ServerException;
-use GuzzleHttp\Cookie\CookieJar;
require __DIR__ . '/../../vendor/autoload.php';
@@ -224,7 +204,8 @@ trait Auth {
* @param bool $remember
*/
public function aNewBrowserSessionIsStarted($remember = false) {
- $loginUrl = substr($this->baseUrl, 0, -5) . '/login';
+ $baseUrl = substr($this->baseUrl, 0, -5);
+ $loginUrl = $baseUrl . '/login';
// Request a new session and extract CSRF token
$client = new Client();
$response = $client->get($loginUrl, [
@@ -243,6 +224,9 @@ trait Auth {
'requesttoken' => $this->requestToken,
],
'cookies' => $this->cookieJar,
+ 'headers' => [
+ 'Origin' => $baseUrl,
+ ],
]
);
$this->extracRequestTokenFromResponse($response);
diff --git a/build/integration/features/bootstrap/Avatar.php b/build/integration/features/bootstrap/Avatar.php
index 6b8e5d88092..beebf1c024a 100644
--- a/build/integration/features/bootstrap/Avatar.php
+++ b/build/integration/features/bootstrap/Avatar.php
@@ -1,24 +1,8 @@
<?php
+
/**
- * @copyright Copyright (c) 2020, Daniel Calviño Sánchez (danxuliu@gmail.com)
- *
- * @author Daniel Calviño Sánchez <danxuliu@gmail.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * 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
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
use Behat\Gherkin\Node\TableNode;
use PHPUnit\Framework\Assert;
@@ -26,7 +10,7 @@ use PHPUnit\Framework\Assert;
require __DIR__ . '/../../vendor/autoload.php';
trait Avatar {
- /** @var string **/
+ /** @var string * */
private $lastAvatar;
/** @AfterScenario **/
@@ -257,10 +241,10 @@ trait Avatar {
}
private function isSameColor(array $firstColor, array $secondColor, int $allowedDelta = 1) {
- if ($this->isSameColorComponent($firstColor['red'], $secondColor['red'], $allowedDelta) &&
- $this->isSameColorComponent($firstColor['green'], $secondColor['green'], $allowedDelta) &&
- $this->isSameColorComponent($firstColor['blue'], $secondColor['blue'], $allowedDelta) &&
- $this->isSameColorComponent($firstColor['alpha'], $secondColor['alpha'], $allowedDelta)) {
+ if ($this->isSameColorComponent($firstColor['red'], $secondColor['red'], $allowedDelta)
+ && $this->isSameColorComponent($firstColor['green'], $secondColor['green'], $allowedDelta)
+ && $this->isSameColorComponent($firstColor['blue'], $secondColor['blue'], $allowedDelta)
+ && $this->isSameColorComponent($firstColor['alpha'], $secondColor['alpha'], $allowedDelta)) {
return true;
}
@@ -268,8 +252,8 @@ trait Avatar {
}
private function isSameColorComponent(int $firstColorComponent, int $secondColorComponent, int $allowedDelta) {
- if ($firstColorComponent >= ($secondColorComponent - $allowedDelta) &&
- $firstColorComponent <= ($secondColorComponent + $allowedDelta)) {
+ if ($firstColorComponent >= ($secondColorComponent - $allowedDelta)
+ && $firstColorComponent <= ($secondColorComponent + $allowedDelta)) {
return true;
}
diff --git a/build/integration/features/bootstrap/BasicStructure.php b/build/integration/features/bootstrap/BasicStructure.php
index 9060c85c756..59a4312913e 100644
--- a/build/integration/features/bootstrap/BasicStructure.php
+++ b/build/integration/features/bootstrap/BasicStructure.php
@@ -1,40 +1,15 @@
<?php
+
/**
- * @copyright Copyright (c) 2016 Sergio Bertolin <sbertolin@solidgear.es>
- *
- * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Daniel Calviño Sánchez <danxuliu@gmail.com>
- * @author Joas Schilling <coding@schilljs.com>
- * @author John Molakvoæ <skjnldsv@protonmail.com>
- * @author Lukas Reschke <lukas@statuscode.ch>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Robin Appelman <robin@icewind.nl>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- * @author Sergio Bertolin <sbertolin@solidgear.es>
- * @author Sergio Bertolín <sbertolin@solidgear.es>
- * @author Thomas Müller <thomas.mueller@tmit.eu>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * 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
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
use Behat\Gherkin\Node\TableNode;
use GuzzleHttp\Client;
use GuzzleHttp\Cookie\CookieJar;
use GuzzleHttp\Exception\ClientException;
+use GuzzleHttp\Exception\ServerException;
use PHPUnit\Framework\Assert;
use Psr\Http\Message\ResponseInterface;
@@ -45,6 +20,7 @@ trait BasicStructure {
use Avatar;
use Download;
use Mail;
+ use Theming;
/** @var string */
private $currentUser = '';
@@ -147,7 +123,11 @@ trait BasicStructure {
* @return string
*/
public function getOCSResponse($response) {
- return simplexml_load_string($response->getBody())->meta[0]->statuscode;
+ $body = simplexml_load_string((string)$response->getBody());
+ if ($body === false) {
+ throw new \RuntimeException('Could not parse OCS response, body is not valid XML');
+ }
+ return $body->meta[0]->statuscode;
}
/**
@@ -179,7 +159,7 @@ trait BasicStructure {
$options['auth'] = [$this->currentUser, $this->regularUser];
}
$options['headers'] = [
- 'OCS_APIREQUEST' => 'true'
+ 'OCS-APIRequest' => 'true'
];
if ($body instanceof TableNode) {
$fd = $body->getRowsHash();
@@ -197,6 +177,8 @@ trait BasicStructure {
$this->response = $client->request($verb, $fullUrl, $options);
} catch (ClientException $ex) {
$this->response = $ex->getResponse();
+ } catch (ServerException $ex) {
+ $this->response = $ex->getResponse();
}
}
@@ -212,8 +194,8 @@ trait BasicStructure {
$options = [];
if ($this->currentUser === 'admin') {
$options['auth'] = ['admin', 'admin'];
- } elseif (strpos($this->currentUser, 'guest') !== 0) {
- $options['auth'] = [$this->currentUser, self::TEST_PASSWORD];
+ } elseif (strpos($this->currentUser, 'anonymous') !== 0) {
+ $options['auth'] = [$this->currentUser, $this->regularUser];
}
if ($body instanceof TableNode) {
$fd = $body->getRowsHash();
@@ -306,7 +288,8 @@ trait BasicStructure {
* @param string $user
*/
public function loggingInUsingWebAs($user) {
- $loginUrl = substr($this->baseUrl, 0, -5) . '/login';
+ $baseUrl = substr($this->baseUrl, 0, -5);
+ $loginUrl = $baseUrl . '/index.php/login';
// Request a new session and extract CSRF token
$client = new Client();
$response = $client->get(
@@ -329,6 +312,9 @@ trait BasicStructure {
'requesttoken' => $this->requestToken,
],
'cookies' => $this->cookieJar,
+ 'headers' => [
+ 'Origin' => $baseUrl,
+ ],
]
);
$this->extracRequestTokenFromResponse($response);
@@ -354,7 +340,7 @@ trait BasicStructure {
$fd = $body->getRowsHash();
$options['form_params'] = $fd;
} elseif ($body) {
- $options = array_merge($options, $body);
+ $options = array_merge_recursive($options, $body);
}
$client = new Client();
@@ -442,14 +428,14 @@ trait BasicStructure {
}
public function createFileSpecificSize($name, $size) {
- $file = fopen("work/" . "$name", 'w');
+ $file = fopen('work/' . "$name", 'w');
fseek($file, $size - 1, SEEK_CUR);
fwrite($file, 'a'); // write a dummy char at SIZE position
fclose($file);
}
public function createFileWithText($name, $text) {
- $file = fopen("work/" . "$name", 'w');
+ $file = fopen('work/' . "$name", 'w');
fwrite($file, $text);
fclose($file);
}
@@ -485,19 +471,19 @@ trait BasicStructure {
*/
public static function addFilesToSkeleton() {
for ($i = 0; $i < 5; $i++) {
- file_put_contents("../../core/skeleton/" . "textfile" . "$i" . ".txt", "Nextcloud test text file\n");
+ file_put_contents('../../core/skeleton/' . 'textfile' . "$i" . '.txt', "Nextcloud test text file\n");
}
- if (!file_exists("../../core/skeleton/FOLDER")) {
- mkdir("../../core/skeleton/FOLDER", 0777, true);
+ if (!file_exists('../../core/skeleton/FOLDER')) {
+ mkdir('../../core/skeleton/FOLDER', 0777, true);
}
- if (!file_exists("../../core/skeleton/PARENT")) {
- mkdir("../../core/skeleton/PARENT", 0777, true);
+ if (!file_exists('../../core/skeleton/PARENT')) {
+ mkdir('../../core/skeleton/PARENT', 0777, true);
}
- file_put_contents("../../core/skeleton/PARENT/" . "parent.txt", "Nextcloud test text file\n");
- if (!file_exists("../../core/skeleton/PARENT/CHILD")) {
- mkdir("../../core/skeleton/PARENT/CHILD", 0777, true);
+ file_put_contents('../../core/skeleton/PARENT/' . 'parent.txt', "Nextcloud test text file\n");
+ if (!file_exists('../../core/skeleton/PARENT/CHILD')) {
+ mkdir('../../core/skeleton/PARENT/CHILD', 0777, true);
}
- file_put_contents("../../core/skeleton/PARENT/CHILD/" . "child.txt", "Nextcloud test text file\n");
+ file_put_contents('../../core/skeleton/PARENT/CHILD/' . 'child.txt', "Nextcloud test text file\n");
}
/**
@@ -505,18 +491,18 @@ trait BasicStructure {
*/
public static function removeFilesFromSkeleton() {
for ($i = 0; $i < 5; $i++) {
- self::removeFile("../../core/skeleton/", "textfile" . "$i" . ".txt");
+ self::removeFile('../../core/skeleton/', 'textfile' . "$i" . '.txt');
}
- if (is_dir("../../core/skeleton/FOLDER")) {
- rmdir("../../core/skeleton/FOLDER");
+ if (is_dir('../../core/skeleton/FOLDER')) {
+ rmdir('../../core/skeleton/FOLDER');
}
- self::removeFile("../../core/skeleton/PARENT/CHILD/", "child.txt");
- if (is_dir("../../core/skeleton/PARENT/CHILD")) {
- rmdir("../../core/skeleton/PARENT/CHILD");
+ self::removeFile('../../core/skeleton/PARENT/CHILD/', 'child.txt');
+ if (is_dir('../../core/skeleton/PARENT/CHILD')) {
+ rmdir('../../core/skeleton/PARENT/CHILD');
}
- self::removeFile("../../core/skeleton/PARENT/", "parent.txt");
- if (is_dir("../../core/skeleton/PARENT")) {
- rmdir("../../core/skeleton/PARENT");
+ self::removeFile('../../core/skeleton/PARENT/', 'parent.txt');
+ if (is_dir('../../core/skeleton/PARENT')) {
+ rmdir('../../core/skeleton/PARENT');
}
}
@@ -524,7 +510,7 @@ trait BasicStructure {
* @BeforeScenario @local_storage
*/
public static function removeFilesFromLocalStorageBefore() {
- $dir = "./work/local_storage/";
+ $dir = './work/local_storage/';
$di = new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS);
$ri = new RecursiveIteratorIterator($di, RecursiveIteratorIterator::CHILD_FIRST);
foreach ($ri as $file) {
@@ -536,7 +522,7 @@ trait BasicStructure {
* @AfterScenario @local_storage
*/
public static function removeFilesFromLocalStorageAfter() {
- $dir = "./work/local_storage/";
+ $dir = './work/local_storage/';
$di = new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS);
$ri = new RecursiveIteratorIterator($di, RecursiveIteratorIterator::CHILD_FIRST);
foreach ($ri as $file) {
diff --git a/build/integration/features/bootstrap/CalDavContext.php b/build/integration/features/bootstrap/CalDavContext.php
index 49d8c8e5963..459c35089fa 100644
--- a/build/integration/features/bootstrap/CalDavContext.php
+++ b/build/integration/features/bootstrap/CalDavContext.php
@@ -1,36 +1,18 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Lukas Reschke <lukas@statuscode.ch>
- * @author Phil Davis <phil.davis@inf.org>
- * @author Robin Appelman <robin@icewind.nl>
- *
- * @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/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
require __DIR__ . '/../../vendor/autoload.php';
use GuzzleHttp\Client;
+use GuzzleHttp\Exception\GuzzleException;
use Psr\Http\Message\ResponseInterface;
class CalDavContext implements \Behat\Behat\Context\Context {
- /** @var string */
+ /** @var string */
private $baseUrl;
/** @var Client */
private $client;
@@ -60,7 +42,7 @@ class CalDavContext implements \Behat\Behat\Context\Context {
/** @AfterScenario */
public function afterScenario() {
- $davUrl = $this->baseUrl. '/remote.php/dav/calendars/admin/MyCalendar';
+ $davUrl = $this->baseUrl . '/remote.php/dav/calendars/admin/MyCalendar';
try {
$this->client->delete(
$davUrl,
@@ -105,6 +87,119 @@ class CalDavContext implements \Behat\Behat\Context\Context {
}
/**
+ * @When :user requests principal :principal on the endpoint :endpoint
+ */
+ public function requestsPrincipal(string $user, string $principal, string $endpoint): void {
+ $davUrl = $this->baseUrl . $endpoint . $principal;
+
+ $password = ($user === 'admin') ? 'admin' : '123456';
+ try {
+ $this->response = $this->client->request(
+ 'PROPFIND',
+ $davUrl,
+ [
+ 'headers' => [
+ 'Content-Type' => 'application/xml; charset=UTF-8',
+ 'Depth' => 0,
+ ],
+ 'body' => '<x0:propfind xmlns:x0="DAV:"><x0:prop><x0:displayname/><x1:calendar-user-type xmlns:x1="urn:ietf:params:xml:ns:caldav"/><x1:calendar-user-address-set xmlns:x1="urn:ietf:params:xml:ns:caldav"/><x0:principal-URL/><x0:alternate-URI-set/><x2:email-address xmlns:x2="http://sabredav.org/ns"/><x3:language xmlns:x3="http://nextcloud.com/ns"/><x1:calendar-home-set xmlns:x1="urn:ietf:params:xml:ns:caldav"/><x1:schedule-inbox-URL xmlns:x1="urn:ietf:params:xml:ns:caldav"/><x1:schedule-outbox-URL xmlns:x1="urn:ietf:params:xml:ns:caldav"/><x1:schedule-default-calendar-URL xmlns:x1="urn:ietf:params:xml:ns:caldav"/><x3:resource-type xmlns:x3="http://nextcloud.com/ns"/><x3:resource-vehicle-type xmlns:x3="http://nextcloud.com/ns"/><x3:resource-vehicle-make xmlns:x3="http://nextcloud.com/ns"/><x3:resource-vehicle-model xmlns:x3="http://nextcloud.com/ns"/><x3:resource-vehicle-is-electric xmlns:x3="http://nextcloud.com/ns"/><x3:resource-vehicle-range xmlns:x3="http://nextcloud.com/ns"/><x3:resource-vehicle-seating-capacity xmlns:x3="http://nextcloud.com/ns"/><x3:resource-contact-person xmlns:x3="http://nextcloud.com/ns"/><x3:resource-contact-person-vcard xmlns:x3="http://nextcloud.com/ns"/><x3:room-type xmlns:x3="http://nextcloud.com/ns"/><x3:room-seating-capacity xmlns:x3="http://nextcloud.com/ns"/><x3:room-building-address xmlns:x3="http://nextcloud.com/ns"/><x3:room-building-story xmlns:x3="http://nextcloud.com/ns"/><x3:room-building-room-number xmlns:x3="http://nextcloud.com/ns"/><x3:room-features xmlns:x3="http://nextcloud.com/ns"/><x0:principal-collection-set/><x0:supported-report-set/></x0:prop></x0:propfind>',
+ 'auth' => [
+ $user,
+ $password,
+ ],
+ ]
+ );
+ } catch (\GuzzleHttp\Exception\ClientException $e) {
+ $this->response = $e->getResponse();
+ }
+ }
+
+ /**
+ * @Then The CalDAV response should contain a property :key
+ * @throws \Exception
+ */
+ public function theCaldavResponseShouldContainAProperty(string $key): void {
+ /** @var \Sabre\DAV\Xml\Response\MultiStatus $multiStatus */
+ $multiStatus = $this->responseXml['value'];
+ $responses = $multiStatus->getResponses()[0]->getResponseProperties();
+ if (!isset($responses[200])) {
+ throw new \Exception(
+ sprintf(
+ 'Expected code 200 got [%s]',
+ implode(',', array_keys($responses)),
+ )
+ );
+ }
+
+ $props = $responses[200];
+ if (!array_key_exists($key, $props)) {
+ throw new \Exception(
+ sprintf(
+ 'Expected property %s in %s',
+ $key,
+ json_encode($props, JSON_PRETTY_PRINT),
+ )
+ );
+ }
+ }
+
+ /**
+ * @Then The CalDAV response should contain a property :key with a href value :value
+ * @throws \Exception
+ */
+ public function theCaldavResponseShouldContainAPropertyWithHrefValue(
+ string $key,
+ string $value,
+ ): void {
+ /** @var \Sabre\DAV\Xml\Response\MultiStatus $multiStatus */
+ $multiStatus = $this->responseXml['value'];
+ $responses = $multiStatus->getResponses()[0]->getResponseProperties();
+ if (!isset($responses[200])) {
+ throw new \Exception(
+ sprintf(
+ 'Expected code 200 got [%s]',
+ implode(',', array_keys($responses)),
+ )
+ );
+ }
+
+ $props = $responses[200];
+ if (!array_key_exists($key, $props)) {
+ throw new \Exception("Cannot find property \"$key\"");
+ }
+
+ $actualValue = $props[$key]->getHref();
+ if ($actualValue !== $value) {
+ throw new \Exception("Property \"$key\" found with value \"$actualValue\", expected \"$value\"");
+ }
+ }
+
+ /**
+ * @Then The CalDAV response should be multi status
+ * @throws \Exception
+ */
+ public function theCaldavResponseShouldBeMultiStatus(): void {
+ if ($this->response->getStatusCode() !== 207) {
+ throw new \Exception(
+ sprintf(
+ 'Expected code 207 got %s',
+ $this->response->getStatusCode()
+ )
+ );
+ }
+
+ $body = $this->response->getBody()->getContents();
+ if ($body && substr($body, 0, 1) === '<') {
+ $reader = new Sabre\Xml\Reader();
+ $reader->xml($body);
+ $reader->elementMap['{DAV:}multistatus'] = \Sabre\DAV\Xml\Response\MultiStatus::class;
+ $reader->elementMap['{DAV:}response'] = \Sabre\DAV\Xml\Element\Response::class;
+ $reader->elementMap['{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL'] = \Sabre\DAV\Xml\Property\Href::class;
+ $this->responseXml = $reader->parse();
+ }
+ }
+
+ /**
* @Then The CalDAV HTTP status code should be :code
* @param int $code
* @throws \Exception
@@ -172,7 +267,7 @@ class CalDavContext implements \Behat\Behat\Context\Context {
* @param string $name
*/
public function createsACalendarNamed($user, $name) {
- $davUrl = $this->baseUrl . '/remote.php/dav/calendars/'.$user.'/'.$name;
+ $davUrl = $this->baseUrl . '/remote.php/dav/calendars/' . $user . '/' . $name;
$password = ($user === 'admin') ? 'admin' : '123456';
$this->response = $this->client->request(
@@ -195,7 +290,7 @@ class CalDavContext implements \Behat\Behat\Context\Context {
* @param string $name
*/
public function publiclySharesTheCalendarNamed($user, $name) {
- $davUrl = $this->baseUrl . '/remote.php/dav/calendars/'.$user.'/'.$name;
+ $davUrl = $this->baseUrl . '/remote.php/dav/calendars/' . $user . '/' . $name;
$password = ($user === 'admin') ? 'admin' : '123456';
$this->response = $this->client->request(
@@ -233,4 +328,63 @@ class CalDavContext implements \Behat\Behat\Context\Context {
);
}
}
+
+ /**
+ * @When :user sends a create calendar request to :calendar on the endpoint :endpoint
+ */
+ public function sendsCreateCalendarRequest(string $user, string $calendar, string $endpoint) {
+ $davUrl = $this->baseUrl . $endpoint . $calendar;
+ $password = ($user === 'admin') ? 'admin' : '123456';
+
+ try {
+ $this->response = $this->client->request(
+ 'MKCALENDAR',
+ $davUrl,
+ [
+ 'body' => '<c:mkcalendar xmlns:c="urn:ietf:params:xml:ns:caldav" xmlns:d="DAV:" xmlns:a="http://apple.com/ns/ical/" xmlns:o="http://owncloud.org/ns"><d:set><d:prop><d:displayname>test</d:displayname><o:calendar-enabled>1</o:calendar-enabled><a:calendar-color>#21213D</a:calendar-color><c:supported-calendar-component-set><c:comp name="VEVENT"/></c:supported-calendar-component-set></d:prop></d:set></c:mkcalendar>',
+ 'auth' => [
+ $user,
+ $password,
+ ],
+ ]
+ );
+ } catch (GuzzleException $e) {
+ $this->response = $e->getResponse();
+ }
+ }
+
+ /**
+ * @Given :user updates property :key to href :value of principal :principal on the endpoint :endpoint
+ */
+ public function updatesHrefPropertyOfPrincipal(
+ string $user,
+ string $key,
+ string $value,
+ string $principal,
+ string $endpoint,
+ ): void {
+ $davUrl = $this->baseUrl . $endpoint . $principal;
+ $password = ($user === 'admin') ? 'admin' : '123456';
+
+ $propPatch = new \Sabre\DAV\Xml\Request\PropPatch();
+ $propPatch->properties = [$key => new \Sabre\DAV\Xml\Property\Href($value)];
+
+ $xml = new \Sabre\Xml\Service();
+ $body = $xml->write('{DAV:}propertyupdate', $propPatch, '/');
+
+ $this->response = $this->client->request(
+ 'PROPPATCH',
+ $davUrl,
+ [
+ 'headers' => [
+ 'Content-Type' => 'application/xml; charset=UTF-8',
+ ],
+ 'body' => $body,
+ 'auth' => [
+ $user,
+ $password,
+ ],
+ ]
+ );
+ }
}
diff --git a/build/integration/features/bootstrap/CapabilitiesContext.php b/build/integration/features/bootstrap/CapabilitiesContext.php
index 4fdfb3e41b0..7d09ab6ddcf 100644
--- a/build/integration/features/bootstrap/CapabilitiesContext.php
+++ b/build/integration/features/bootstrap/CapabilitiesContext.php
@@ -1,30 +1,9 @@
<?php
+
/**
- * @copyright Copyright (c) 2016 Sergio Bertolin <sbertolin@solidgear.es>
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Daniel Calviño Sánchez <danxuliu@gmail.com>
- * @author Joas Schilling <coding@schilljs.com>
- * @author John Molakvoæ <skjnldsv@protonmail.com>
- * @author Robin Appelman <robin@icewind.nl>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- * @author Sergio Bertolin <sbertolin@solidgear.es>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * 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
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
use Behat\Behat\Context\Context;
use Behat\Behat\Context\SnippetAcceptingContext;
@@ -44,7 +23,9 @@ class CapabilitiesContext implements Context, SnippetAcceptingContext {
* @param \Behat\Gherkin\Node\TableNode|null $formData
*/
public function checkCapabilitiesResponse(\Behat\Gherkin\Node\TableNode $formData) {
- $capabilitiesXML = simplexml_load_string($this->response->getBody())->data->capabilities;
+ $capabilitiesXML = simplexml_load_string($this->response->getBody());
+ Assert::assertNotFalse($capabilitiesXML, 'Failed to fetch capabilities');
+ $capabilitiesXML = $capabilitiesXML->data->capabilities;
foreach ($formData->getHash() as $row) {
$path_to_element = explode('@@@', $row['path_to_element']);
@@ -54,9 +35,9 @@ class CapabilitiesContext implements Context, SnippetAcceptingContext {
}
$answeredValue = (string)$answeredValue;
Assert::assertEquals(
- $row['value'] === "EMPTY" ? '' : $row['value'],
+ $row['value'] === 'EMPTY' ? '' : $row['value'],
$answeredValue,
- "Failed field " . $row['capability'] . " " . $row['path_to_element']
+ 'Failed field ' . $row['capability'] . ' ' . $row['path_to_element']
);
}
}
diff --git a/build/integration/features/bootstrap/CardDavContext.php b/build/integration/features/bootstrap/CardDavContext.php
index 18a9f3dd249..733c98dca02 100644
--- a/build/integration/features/bootstrap/CardDavContext.php
+++ b/build/integration/features/bootstrap/CardDavContext.php
@@ -1,35 +1,18 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Lukas Reschke <lukas@statuscode.ch>
- * @author Phil Davis <phil.davis@inf.org>
- * @author Robin Appelman <robin@icewind.nl>
- *
- * @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/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
require __DIR__ . '/../../vendor/autoload.php';
use GuzzleHttp\Client;
+use GuzzleHttp\Exception\GuzzleException;
use GuzzleHttp\Message\ResponseInterface;
class CardDavContext implements \Behat\Behat\Context\Context {
- /** @var string */
+ /** @var string */
private $baseUrl;
/** @var Client */
private $client;
@@ -128,7 +111,7 @@ class CardDavContext implements \Behat\Behat\Context\Context {
* @throws \Exception
*/
public function createsAnAddressbookNamedWithStatuscode($user, $addressBook, $statusCode) {
- $davUrl = $this->baseUrl . '/remote.php/dav/addressbooks/users/'.$user.'/'.$addressBook;
+ $davUrl = $this->baseUrl . '/remote.php/dav/addressbooks/users/' . $user . '/' . $addressBook;
$password = ($user === 'admin') ? 'admin' : '123456';
$this->response = $this->client->request(
@@ -141,7 +124,7 @@ class CardDavContext implements \Behat\Behat\Context\Context {
<d:prop>
<d:resourcetype>
<d:collection />,<card:addressbook />
- </d:resourcetype>,<d:displayname>'.$addressBook.'</d:displayname>
+ </d:resourcetype>,<d:displayname>' . $addressBook . '</d:displayname>
</d:prop>
</d:set>
</d:mkcol>',
@@ -208,7 +191,7 @@ class CardDavContext implements \Behat\Behat\Context\Context {
* @Given :user uploads the contact :fileName to the addressbook :addressbook
*/
public function uploadsTheContactToTheAddressbook($user, $fileName, $addressBook) {
- $davUrl = $this->baseUrl . '/remote.php/dav/addressbooks/users/'.$user.'/'.$addressBook . '/' . $fileName;
+ $davUrl = $this->baseUrl . '/remote.php/dav/addressbooks/users/' . $user . '/' . $addressBook . '/' . $fileName;
$password = ($user === 'admin') ? 'admin' : '123456';
$this->response = $this->client->request(
@@ -241,7 +224,7 @@ class CardDavContext implements \Behat\Behat\Context\Context {
* @When Exporting the picture of contact :fileName from addressbook :addressBook as user :user
*/
public function whenExportingThePictureOfContactFromAddressbookAsUser($fileName, $addressBook, $user) {
- $davUrl = $this->baseUrl . '/remote.php/dav/addressbooks/users/'.$user.'/'.$addressBook . '/' . $fileName . '?photo=true';
+ $davUrl = $this->baseUrl . '/remote.php/dav/addressbooks/users/' . $user . '/' . $addressBook . '/' . $fileName . '?photo=true';
$password = ($user === 'admin') ? 'admin' : '123456';
try {
@@ -267,7 +250,7 @@ class CardDavContext implements \Behat\Behat\Context\Context {
* @When Downloading the contact :fileName from addressbook :addressBook as user :user
*/
public function whenDownloadingTheContactFromAddressbookAsUser($fileName, $addressBook, $user) {
- $davUrl = $this->baseUrl . '/remote.php/dav/addressbooks/users/'.$user.'/'.$addressBook . '/' . $fileName;
+ $davUrl = $this->baseUrl . '/remote.php/dav/addressbooks/users/' . $user . '/' . $addressBook . '/' . $fileName;
$password = ($user === 'admin') ? 'admin' : '123456';
try {
@@ -311,4 +294,64 @@ class CardDavContext implements \Behat\Behat\Context\Context {
}
}
}
+
+ /**
+ * @When :user sends a create addressbook request to :addressbook on the endpoint :endpoint
+ */
+ public function sendsCreateAddressbookRequest(string $user, string $addressbook, string $endpoint) {
+ $davUrl = $this->baseUrl . $endpoint . $addressbook;
+ $password = ($user === 'admin') ? 'admin' : '123456';
+
+ try {
+ $this->response = $this->client->request(
+ 'MKCOL',
+ $davUrl,
+ [
+ 'body' => '<d:mkcol xmlns:card="urn:ietf:params:xml:ns:carddav"
+ xmlns:d="DAV:">
+ <d:set>
+ <d:prop>
+ <d:resourcetype>
+ <d:collection />,<card:addressbook />
+ </d:resourcetype>,<d:displayname>' . $addressbook . '</d:displayname>
+ </d:prop>
+ </d:set>
+ </d:mkcol>',
+ 'auth' => [
+ $user,
+ $password,
+ ],
+ 'headers' => [
+ 'Content-Type' => 'application/xml;charset=UTF-8',
+ ],
+ ]
+ );
+ } catch (GuzzleException $e) {
+ $this->response = $e->getResponse();
+ }
+ }
+
+ /**
+ * @Then The CardDAV HTTP status code should be :code
+ * @param int $code
+ * @throws \Exception
+ */
+ public function theCarddavHttpStatusCodeShouldBe($code) {
+ if ((int)$code !== $this->response->getStatusCode()) {
+ throw new \Exception(
+ sprintf(
+ 'Expected %s got %s',
+ (int)$code,
+ $this->response->getStatusCode()
+ )
+ );
+ }
+
+ $body = $this->response->getBody()->getContents();
+ if ($body && substr($body, 0, 1) === '<') {
+ $reader = new Sabre\Xml\Reader();
+ $reader->xml($body);
+ $this->responseXml = $reader->parse();
+ }
+ }
}
diff --git a/build/integration/features/bootstrap/ChecksumsContext.php b/build/integration/features/bootstrap/ChecksumsContext.php
index ae44fcb1503..c8abf91127e 100644
--- a/build/integration/features/bootstrap/ChecksumsContext.php
+++ b/build/integration/features/bootstrap/ChecksumsContext.php
@@ -1,28 +1,9 @@
<?php
+
/**
- * @copyright Copyright (c) 2016 Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Phil Davis <phil.davis@inf.org>
- * @author Robin Appelman <robin@icewind.nl>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * 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
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
require __DIR__ . '/../../vendor/autoload.php';
@@ -30,7 +11,7 @@ use GuzzleHttp\Client;
use GuzzleHttp\Message\ResponseInterface;
class ChecksumsContext implements \Behat\Behat\Context\Context {
- /** @var string */
+ /** @var string */
private $baseUrl;
/** @var Client */
private $client;
@@ -107,7 +88,7 @@ class ChecksumsContext implements \Behat\Behat\Context\Context {
*/
public function theWebdavResponseShouldHaveAStatusCode($statusCode) {
if ((int)$statusCode !== $this->response->getStatusCode()) {
- throw new \Exception("Expected $statusCode, got ".$this->response->getStatusCode());
+ throw new \Exception("Expected $statusCode, got " . $this->response->getStatusCode());
}
}
@@ -151,7 +132,7 @@ class ChecksumsContext implements \Behat\Behat\Context\Context {
$checksums = $parsed[0]['value'][1]['value'][0]['value'][0];
if ($checksums['value'][0]['value'] !== $checksum) {
- throw new \Exception("Expected $checksum, got ".$checksums['value'][0]['value']);
+ throw new \Exception("Expected $checksum, got " . $checksums['value'][0]['value']);
}
}
@@ -179,7 +160,7 @@ class ChecksumsContext implements \Behat\Behat\Context\Context {
*/
public function theHeaderChecksumShouldMatch($checksum) {
if ($this->response->getHeader('OC-Checksum')[0] !== $checksum) {
- throw new \Exception("Expected $checksum, got ".$this->response->getHeader('OC-Checksum')[0]);
+ throw new \Exception("Expected $checksum, got " . $this->response->getHeader('OC-Checksum')[0]);
}
}
@@ -219,7 +200,7 @@ class ChecksumsContext implements \Behat\Behat\Context\Context {
$status = $parsed[0]['value'][1]['value'][1]['value'];
if ($status !== 'HTTP/1.1 404 Not Found') {
- throw new \Exception("Expected 'HTTP/1.1 404 Not Found', got ".$status);
+ throw new \Exception("Expected 'HTTP/1.1 404 Not Found', got " . $status);
}
}
@@ -228,34 +209,7 @@ class ChecksumsContext implements \Behat\Behat\Context\Context {
*/
public function theOcChecksumHeaderShouldNotBeThere() {
if ($this->response->hasHeader('OC-Checksum')) {
- throw new \Exception("Expected no checksum header but got ".$this->response->getHeader('OC-Checksum')[0]);
+ throw new \Exception('Expected no checksum header but got ' . $this->response->getHeader('OC-Checksum')[0]);
}
}
-
- /**
- * @Given user :user uploads chunk file :num of :total with :data to :destination with checksum :checksum
- * @param string $user
- * @param int $num
- * @param int $total
- * @param string $data
- * @param string $destination
- * @param string $checksum
- */
- public function userUploadsChunkFileOfWithToWithChecksum($user, $num, $total, $data, $destination, $checksum) {
- $num -= 1;
- $this->response = $this->client->put(
- $this->baseUrl . '/remote.php/webdav' . $destination . '-chunking-42-'.$total.'-'.$num,
- [
- 'auth' => [
- $user,
- $this->getPasswordForUser($user)
- ],
- 'body' => $data,
- 'headers' => [
- 'OC-Checksum' => $checksum,
- 'OC-Chunked' => '1',
- ]
- ]
- );
- }
}
diff --git a/build/integration/features/bootstrap/CollaborationContext.php b/build/integration/features/bootstrap/CollaborationContext.php
index adfc357b0e1..27fa1795c5d 100644
--- a/build/integration/features/bootstrap/CollaborationContext.php
+++ b/build/integration/features/bootstrap/CollaborationContext.php
@@ -3,25 +3,8 @@
declare(strict_types=1);
/**
- * @copyright Copyright (c) 2021, Joas Schilling <coding@schilljs.com>
- *
- * @author Joas Schilling <coding@schilljs.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * 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
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
use Behat\Behat\Context\Context;
use Behat\Gherkin\Node\TableNode;
@@ -73,6 +56,9 @@ class CollaborationContext implements Context {
if (isset($expected['source'])) {
$data['source'] = $suggestion['source'];
}
+ if (isset($expected['status'])) {
+ $data['status'] = json_encode($suggestion['status']);
+ }
return $data;
}, $suggestions, $formData->getHash()));
}
@@ -85,7 +71,7 @@ class CollaborationContext implements Context {
try {
$destination = '/users/admin/myaddressbook';
$data = '<x0:mkcol xmlns:x0="DAV:"><x0:set><x0:prop><x0:resourcetype><x0:collection/><x4:addressbook xmlns:x4="urn:ietf:params:xml:ns:carddav"/></x0:resourcetype><x0:displayname>myaddressbook</x0:displayname></x0:prop></x0:set></x0:mkcol>';
- $this->response = $this->makeDavRequest($this->currentUser, "MKCOL", $destination, ['Content-Type' => 'application/xml'], $data, "addressbooks");
+ $this->response = $this->makeDavRequest($this->currentUser, 'MKCOL', $destination, ['Content-Type' => 'application/xml'], $data, 'addressbooks');
} catch (\GuzzleHttp\Exception\ServerException $e) {
// 5xx responses cause a server exception
$this->response = $e->getResponse();
@@ -107,7 +93,7 @@ EMAIL;TYPE=HOME:user@example.com
REV;VALUE=DATE-AND-OR-TIME:20211130T140111Z
END:VCARD
EOF;
- $this->response = $this->makeDavRequest($this->currentUser, "PUT", $destination, [], $data, "addressbooks");
+ $this->response = $this->makeDavRequest($this->currentUser, 'PUT', $destination, [], $data, 'addressbooks');
} catch (\GuzzleHttp\Exception\ServerException $e) {
// 5xx responses cause a server exception
$this->response = $e->getResponse();
diff --git a/build/integration/features/bootstrap/CommandLine.php b/build/integration/features/bootstrap/CommandLine.php
index cba254551e0..924d723daa6 100644
--- a/build/integration/features/bootstrap/CommandLine.php
+++ b/build/integration/features/bootstrap/CommandLine.php
@@ -1,27 +1,9 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Lukas Reschke <lukas@statuscode.ch>
- * @author Robin Appelman <robin@icewind.nl>
- * @author Vincent Petry <vincent@nextcloud.com>
- *
- * @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/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
use PHPUnit\Framework\Assert;
diff --git a/build/integration/features/bootstrap/CommandLineContext.php b/build/integration/features/bootstrap/CommandLineContext.php
index afe17cd75a4..e7764356270 100644
--- a/build/integration/features/bootstrap/CommandLineContext.php
+++ b/build/integration/features/bootstrap/CommandLineContext.php
@@ -1,31 +1,13 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Daniel Calviño Sánchez <danxuliu@gmail.com>
- * @author Lukas Reschke <lukas@statuscode.ch>
- * @author Stefan Weil <sw@weilnetz.de>
- * @author Sujith H <sharidasan@owncloud.com>
- * @author Vincent Petry <vincent@nextcloud.com>
- *
- * @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/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
require __DIR__ . '/../../vendor/autoload.php';
+use Behat\Behat\Context\Exception\ContextNotFoundException;
use Behat\Behat\Hook\Scope\BeforeScenarioScope;
use PHPUnit\Framework\Assert;
@@ -35,6 +17,8 @@ class CommandLineContext implements \Behat\Behat\Context\Context {
private $lastTransferPath;
private $featureContext;
+ private $localBaseUrl;
+ private $remoteBaseUrl;
public function __construct($ocPath, $baseUrl) {
$this->ocPath = rtrim($ocPath, '/') . '/';
@@ -59,8 +43,12 @@ class CommandLineContext implements \Behat\Behat\Context\Context {
/** @BeforeScenario */
public function gatherContexts(BeforeScenarioScope $scope) {
$environment = $scope->getEnvironment();
- // this should really be "WebDavContext" ...
- $this->featureContext = $environment->getContext('FeatureContext');
+ // this should really be "WebDavContext"
+ try {
+ $this->featureContext = $environment->getContext('FeatureContext');
+ } catch (ContextNotFoundException) {
+ $this->featureContext = $environment->getContext('DavFeatureContext');
+ }
}
private function findLastTransferFolderForUser($sourceUser, $targetUser) {
@@ -69,7 +57,7 @@ class CommandLineContext implements \Behat\Behat\Context\Context {
foreach ($results as $path => $data) {
$path = rawurldecode($path);
$parts = explode(' ', $path);
- if (basename($parts[0]) !== 'transferred') {
+ if (basename($parts[0]) !== 'Transferred') {
continue;
}
if (isset($parts[2]) && $parts[2] === $sourceUser) {
@@ -122,19 +110,6 @@ class CommandLineContext implements \Behat\Behat\Context\Context {
}
/**
- * @When /^transferring ownership of path "([^"]+)" from "([^"]+)" to "([^"]+)" with received shares$/
- */
- public function transferringOwnershipPathWithIncomingShares($path, $user1, $user2) {
- $path = '--path=' . $path;
- if ($this->runOcc(['files:transfer-ownership', $path, $user1, $user2, '--transfer-incoming-shares=1']) === 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) {
diff --git a/build/integration/features/bootstrap/CommentsContext.php b/build/integration/features/bootstrap/CommentsContext.php
index ad2d752b4dd..53001b1c204 100644
--- a/build/integration/features/bootstrap/CommentsContext.php
+++ b/build/integration/features/bootstrap/CommentsContext.php
@@ -1,28 +1,9 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Julius Härtl <jus@bitgrid.net>
- * @author Lukas Reschke <lukas@statuscode.ch>
- * @author Robin Appelman <robin@icewind.nl>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @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/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
require __DIR__ . '/../../vendor/autoload.php';
@@ -49,6 +30,35 @@ class CommentsContext implements \Behat\Behat\Context\Context {
}
}
+ /**
+ * get a named entry from response instead of picking a random entry from values
+ *
+ * @param string $path
+ *
+ * @return array|string
+ * @throws Exception
+ */
+ private function getValueFromNamedEntries(string $path, array $response): mixed {
+ $next = '';
+ if (str_contains($path, ' ')) {
+ [$key, $next] = explode(' ', $path, 2);
+ } else {
+ $key = $path;
+ }
+
+ foreach ($response as $entry) {
+ if ($entry['name'] === $key) {
+ if ($next !== '') {
+ return $this->getValueFromNamedEntries($next, $entry['value']);
+ } else {
+ return $entry['value'];
+ }
+ }
+ }
+
+ return null;
+ }
+
/** @AfterScenario */
public function teardownScenario() {
$client = new \GuzzleHttp\Client();
@@ -127,7 +137,7 @@ class CommentsContext implements \Behat\Behat\Context\Context {
}
if ($res->getStatusCode() !== (int)$statusCode) {
- throw new \Exception("Response status code was not $statusCode (" . $res->getStatusCode() . ")");
+ throw new \Exception("Response status code was not $statusCode (" . $res->getStatusCode() . ')');
}
}
@@ -169,13 +179,13 @@ class CommentsContext implements \Behat\Behat\Context\Context {
}
if ($res->getStatusCode() !== (int)$statusCode) {
- throw new \Exception("Response status code was not $statusCode (" . $res->getStatusCode() . ")");
+ throw new \Exception("Response status code was not $statusCode (" . $res->getStatusCode() . ')');
}
if ($res->getStatusCode() === 207) {
$service = new Sabre\Xml\Service();
$this->response = $service->parse($res->getBody()->getContents());
- $this->commentId = (int) ($this->response[0]['value'][2]['value'][0]['value'][0]['value'] ?? 0);
+ $this->commentId = (int)($this->getValueFromNamedEntries('{DAV:}response {DAV:}propstat {DAV:}prop {http://owncloud.org/ns}id', $this->response ?? []) ?? 0);
}
}
@@ -227,7 +237,7 @@ class CommentsContext implements \Behat\Behat\Context\Context {
}
if ($res->getStatusCode() !== (int)$statusCode) {
- throw new \Exception("Response status code was not $statusCode (" . $res->getStatusCode() . ")");
+ throw new \Exception("Response status code was not $statusCode (" . $res->getStatusCode() . ')');
}
}
@@ -238,7 +248,8 @@ class CommentsContext implements \Behat\Behat\Context\Context {
* @throws \Exception
*/
public function theResponseShouldContainAPropertyWithValue($key, $value) {
- $keys = $this->response[0]['value'][2]['value'][0]['value'];
+ // $keys = $this->response[0]['value'][1]['value'][0]['value'];
+ $keys = $this->getValueFromNamedEntries('{DAV:}response {DAV:}propstat {DAV:}prop', $this->response);
$found = false;
foreach ($keys as $singleKey) {
if ($singleKey['name'] === '{http://owncloud.org/ns}' . substr($key, 3)) {
@@ -263,7 +274,7 @@ class CommentsContext implements \Behat\Behat\Context\Context {
$count = count($this->response);
}
if ($count !== (int)$number) {
- throw new \Exception("Found more comments than $number (" . $count . ")");
+ throw new \Exception("Found more comments than $number (" . $count . ')');
}
}
@@ -293,7 +304,7 @@ class CommentsContext implements \Behat\Behat\Context\Context {
}
if ($res->getStatusCode() !== (int)$statusCode) {
- throw new \Exception("Response status code was not $statusCode (" . $res->getStatusCode() . ")");
+ throw new \Exception("Response status code was not $statusCode (" . $res->getStatusCode() . ')');
}
}
}
diff --git a/build/integration/features/bootstrap/ContactsMenu.php b/build/integration/features/bootstrap/ContactsMenu.php
index 0506d827a39..f6bf6b9422b 100644
--- a/build/integration/features/bootstrap/ContactsMenu.php
+++ b/build/integration/features/bootstrap/ContactsMenu.php
@@ -1,24 +1,8 @@
<?php
+
/**
- * @copyright Copyright (c) 2021 Daniel Calviño Sánchez <danxuliu@gmail.com>
- *
- * @author Daniel Calviño Sánchez <danxuliu@gmail.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * 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
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
use PHPUnit\Framework\Assert;
diff --git a/build/integration/features/bootstrap/ConversionsContext.php b/build/integration/features/bootstrap/ConversionsContext.php
new file mode 100644
index 00000000000..ccd14c460f8
--- /dev/null
+++ b/build/integration/features/bootstrap/ConversionsContext.php
@@ -0,0 +1,60 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+require __DIR__ . '/../../vendor/autoload.php';
+
+use Behat\Behat\Context\Context;
+use Behat\Behat\Context\SnippetAcceptingContext;
+use Behat\Gherkin\Node\TableNode;
+
+class ConversionsContext implements Context, SnippetAcceptingContext {
+ use AppConfiguration;
+ use BasicStructure;
+ use WebDav;
+
+ /** @BeforeScenario */
+ public function setUpScenario() {
+ $this->asAn('admin');
+ $this->setStatusTestingApp(true);
+ }
+
+ /** @AfterScenario */
+ public function tearDownScenario() {
+ $this->asAn('admin');
+ $this->setStatusTestingApp(false);
+ }
+
+ protected function resetAppConfigs() {
+ }
+
+ /**
+ * @When /^user "([^"]*)" converts file "([^"]*)" to "([^"]*)"$/
+ */
+ public function userConvertsTheSavedFileId(string $user, string $path, string $mime) {
+ $this->userConvertsTheSavedFileIdTo($user, $path, $mime, null);
+ }
+
+ /**
+ * @When /^user "([^"]*)" converts file "([^"]*)" to "([^"]*)" and saves it to "([^"]*)"$/
+ */
+ public function userConvertsTheSavedFileIdTo(string $user, string $path, string $mime, ?string $destination) {
+ try {
+ $fileId = $this->getFileIdForPath($user, $path);
+ } catch (Exception $e) {
+ // return a fake value to keep going and be able to test the error
+ $fileId = 0;
+ }
+
+ $data = [['fileId', $fileId], ['targetMimeType', $mime]];
+ if ($destination !== null) {
+ $data[] = ['destination', $destination];
+ }
+
+ $this->asAn($user);
+ $this->sendingToWith('post', '/apps/files/api/v1/convert', new TableNode($data));
+ }
+}
diff --git a/build/integration/features/bootstrap/DavFeatureContext.php b/build/integration/features/bootstrap/DavFeatureContext.php
new file mode 100644
index 00000000000..ec6085cff98
--- /dev/null
+++ b/build/integration/features/bootstrap/DavFeatureContext.php
@@ -0,0 +1,24 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+use Behat\Behat\Context\Context;
+use Behat\Behat\Context\SnippetAcceptingContext;
+
+require __DIR__ . '/../../vendor/autoload.php';
+
+class DavFeatureContext implements Context, SnippetAcceptingContext {
+ use AppConfiguration;
+ use ContactsMenu;
+ use ExternalStorage;
+ use Search;
+ use WebDav;
+ use Trashbin;
+
+ protected function resetAppConfigs() {
+ $this->deleteServerConfig('files_sharing', 'outgoing_server2server_share_enabled');
+ }
+}
diff --git a/build/integration/features/bootstrap/Download.php b/build/integration/features/bootstrap/Download.php
index e5e6dc64853..549a033346e 100644
--- a/build/integration/features/bootstrap/Download.php
+++ b/build/integration/features/bootstrap/Download.php
@@ -1,32 +1,16 @@
<?php
+
/**
- * @copyright Copyright (c) 2018, Daniel Calviño Sánchez (danxuliu@gmail.com)
- *
- * @author Daniel Calviño Sánchez <danxuliu@gmail.com>
- * @author John Molakvoæ <skjnldsv@protonmail.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * 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
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
use PHPUnit\Framework\Assert;
+use Psr\Http\Message\StreamInterface;
require __DIR__ . '/../../vendor/autoload.php';
trait Download {
- /** @var string **/
+ /** @var string * */
private $downloadedFile;
/** @AfterScenario **/
@@ -38,16 +22,16 @@ trait Download {
* @When user :user downloads zip file for entries :entries in folder :folder
*/
public function userDownloadsZipFileForEntriesInFolder($user, $entries, $folder) {
+ $folder = trim($folder, '/');
$this->asAn($user);
- $this->sendingToDirectUrl('GET', "/index.php/apps/files/ajax/download.php?dir=" . $folder . "&files=[" . $entries . "]");
+ $this->sendingToDirectUrl('GET', "/remote.php/dav/files/$user/$folder?accept=zip&files=[" . $entries . ']');
$this->theHTTPStatusCodeShouldBe('200');
-
- $this->getDownloadedFile();
}
private function getDownloadedFile() {
$this->downloadedFile = '';
+ /** @var StreamInterface */
$body = $this->response->getBody();
while (!$body->eof()) {
$this->downloadedFile .= $body->read(8192);
@@ -56,14 +40,28 @@ trait Download {
}
/**
+ * @Then the downloaded file is a zip file
+ */
+ public function theDownloadedFileIsAZipFile() {
+ $this->getDownloadedFile();
+
+ Assert::assertTrue(
+ strpos($this->downloadedFile, "\x50\x4B\x01\x02") !== false,
+ 'File does not contain the central directory file header'
+ );
+ }
+
+ /**
* @Then the downloaded zip file is a zip32 file
*/
public function theDownloadedZipFileIsAZip32File() {
+ $this->theDownloadedFileIsAZipFile();
+
// assertNotContains is not used to prevent the whole file from being
// printed in case of error.
Assert::assertTrue(
strpos($this->downloadedFile, "\x50\x4B\x06\x06") === false,
- "File contains the zip64 end of central dir signature"
+ 'File contains the zip64 end of central dir signature'
);
}
@@ -71,11 +69,13 @@ trait Download {
* @Then the downloaded zip file is a zip64 file
*/
public function theDownloadedZipFileIsAZip64File() {
+ $this->theDownloadedFileIsAZipFile();
+
// assertNotContains is not used to prevent the whole file from being
// printed in case of error.
Assert::assertTrue(
strpos($this->downloadedFile, "\x50\x4B\x06\x06") !== false,
- "File does not contain the zip64 end of central dir signature"
+ 'File does not contain the zip64 end of central dir signature'
);
}
@@ -95,7 +95,7 @@ trait Download {
// in case of error and to be able to get the extra field length.
Assert::assertEquals(
1, preg_match($fileHeaderRegExp, $this->downloadedFile, $matches),
- "Local header for file did not appear once in zip file"
+ 'Local header for file did not appear once in zip file'
);
$extraFieldLength = unpack('vextraFieldLength', $matches[1])['extraFieldLength'];
@@ -115,7 +115,7 @@ trait Download {
// in case of error.
Assert::assertEquals(
1, preg_match($fileHeaderAndContentRegExp, $this->downloadedFile),
- "Local header and contents for file did not appear once in zip file"
+ 'Local header and contents for file did not appear once in zip file'
);
}
@@ -135,7 +135,21 @@ trait Download {
// in case of error.
Assert::assertEquals(
1, preg_match($folderHeaderRegExp, $this->downloadedFile),
- "Local header for folder did not appear once in zip file"
+ 'Local header for folder did not appear once in zip file'
+ );
+ }
+
+ /**
+ * @Then the downloaded file has the content of :sourceFilename from :user data
+ */
+ public function theDownloadedFileHasContentOfUserFile($sourceFilename, $user) {
+ $this->getDownloadedFile();
+ $expectedFileContents = file_get_contents($this->getDataDirectory() . "/$user/files" . $sourceFilename);
+
+ // prevent the whole file from being printed in case of error.
+ Assert::assertEquals(
+ 0, strcmp($expectedFileContents, $this->downloadedFile),
+ 'Downloaded file content does not match local file content'
);
}
}
diff --git a/build/integration/features/bootstrap/ExternalStorage.php b/build/integration/features/bootstrap/ExternalStorage.php
new file mode 100644
index 00000000000..8fe2653a026
--- /dev/null
+++ b/build/integration/features/bootstrap/ExternalStorage.php
@@ -0,0 +1,123 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+use Behat\Gherkin\Node\TableNode;
+use PHPUnit\Framework\Assert;
+
+require __DIR__ . '/../../vendor/autoload.php';
+
+trait ExternalStorage {
+ private array $storageIds = [];
+
+ private array $lastExternalStorageData;
+
+ /**
+ * @AfterScenario
+ **/
+ public function deleteCreatedStorages(): void {
+ foreach ($this->storageIds as $storageId) {
+ $this->deleteStorage($storageId);
+ }
+ $this->storageIds = [];
+ }
+
+ private function deleteStorage(string $storageId): void {
+ // Based on "runOcc" from CommandLine trait
+ $args = ['files_external:delete', '--yes', $storageId];
+ $args = array_map(function ($arg) {
+ return escapeshellarg($arg);
+ }, $args);
+ $args[] = '--no-ansi --no-warnings';
+ $args = implode(' ', $args);
+
+ $descriptor = [
+ 0 => ['pipe', 'r'],
+ 1 => ['pipe', 'w'],
+ 2 => ['pipe', 'w'],
+ ];
+ $process = proc_open('php console.php ' . $args, $descriptor, $pipes, $ocPath = '../..');
+ $lastStdOut = stream_get_contents($pipes[1]);
+ proc_close($process);
+ }
+
+ /**
+ * @When logged in user creates external global storage
+ *
+ * @param TableNode $fields
+ */
+ public function loggedInUserCreatesExternalGlobalStorage(TableNode $fields): void {
+ $this->sendJsonWithRequestTokenAndBasicAuth('POST', '/index.php/apps/files_external/globalstorages', $fields);
+ $this->theHTTPStatusCodeShouldBe('201');
+
+ $this->lastExternalStorageData = json_decode($this->response->getBody(), $asAssociativeArray = true);
+
+ $this->storageIds[] = $this->lastExternalStorageData['id'];
+ }
+
+ /**
+ * @When logged in user updates last external userglobal storage
+ *
+ * @param TableNode $fields
+ */
+ public function loggedInUserUpdatesLastExternalUserglobalStorage(TableNode $fields): void {
+ $this->sendJsonWithRequestTokenAndBasicAuth('PUT', '/index.php/apps/files_external/userglobalstorages/' . $this->lastExternalStorageData['id'], $fields);
+ $this->theHTTPStatusCodeShouldBe('200');
+
+ $this->lastExternalStorageData = json_decode($this->response->getBody(), $asAssociativeArray = true);
+ }
+
+ /**
+ * @Then fields of last external storage match with
+ *
+ * @param TableNode $fields
+ */
+ public function fieldsOfLastExternalStorageMatchWith(TableNode $fields): void {
+ foreach ($fields->getRowsHash() as $expectedField => $expectedValue) {
+ if (!array_key_exists($expectedField, $this->lastExternalStorageData)) {
+ Assert::fail("$expectedField was not found in response");
+ }
+
+ Assert::assertEquals($expectedValue, $this->lastExternalStorageData[$expectedField], "Field '$expectedField' does not match ({$this->lastExternalStorageData[$expectedField]})");
+ }
+ }
+
+ private function sendJsonWithRequestToken(string $method, string $url, TableNode $fields): void {
+ $isFirstField = true;
+ $fieldsAsJsonString = '{';
+ foreach ($fields->getRowsHash() as $key => $value) {
+ $fieldsAsJsonString .= ($isFirstField ? '' : ',') . '"' . $key . '":' . $value;
+ $isFirstField = false;
+ }
+ $fieldsAsJsonString .= '}';
+
+ $body = [
+ 'headers' => [
+ 'Content-Type' => 'application/json',
+ ],
+ 'body' => $fieldsAsJsonString,
+ ];
+ $this->sendingAToWithRequesttoken($method, $url, $body);
+ }
+
+ private function sendJsonWithRequestTokenAndBasicAuth(string $method, string $url, TableNode $fields): void {
+ $isFirstField = true;
+ $fieldsAsJsonString = '{';
+ foreach ($fields->getRowsHash() as $key => $value) {
+ $fieldsAsJsonString .= ($isFirstField ? '' : ',') . '"' . $key . '":' . $value;
+ $isFirstField = false;
+ }
+ $fieldsAsJsonString .= '}';
+
+ $body = [
+ 'headers' => [
+ 'Content-Type' => 'application/json',
+ 'Authorization' => 'Basic ' . base64_encode('admin:admin'),
+ ],
+ 'body' => $fieldsAsJsonString,
+ ];
+ $this->sendingAToWithRequesttoken($method, $url, $body);
+ }
+}
diff --git a/build/integration/features/bootstrap/FakeSMTPHelper.php b/build/integration/features/bootstrap/FakeSMTPHelper.php
index 9d3b5979114..32387869edd 100644
--- a/build/integration/features/bootstrap/FakeSMTPHelper.php
+++ b/build/integration/features/bootstrap/FakeSMTPHelper.php
@@ -1,25 +1,8 @@
<?php
+
/**
- * @copyright Copyright (c) 2016 Daniel Calviño Sánchez <danxuliu@gmail.com>
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Daniel Calviño Sánchez <danxuliu@gmail.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * 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
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
// Code below modified from https://github.com/axllent/fake-smtp/blob/f0856f8a0df6f4ca5a573cf31428c09ebc5b9ea3/fakeSMTP.php,
// which is under the MIT license (https://github.com/axllent/fake-smtp/blob/f0856f8a0df6f4ca5a573cf31428c09ebc5b9ea3/LICENSE)
@@ -52,7 +35,7 @@ class fakeSMTP {
$hasValidTo = false;
$receivingData = false;
$header = true;
- $this->reply('220 '.$this->serverHello);
+ $this->reply('220 ' . $this->serverHello);
$this->mail['ipaddress'] = $this->detectIP();
while ($data = fgets($this->fd)) {
$data = preg_replace('@\r\n@', "\n", $data);
@@ -78,7 +61,7 @@ class fakeSMTP {
$this->reply('250 2.1.5 Ok');
$hasValidTo = true;
} else {
- $this->reply('501 5.1.3 Bad recipient address syntax '.$match[1]);
+ $this->reply('501 5.1.3 Bad recipient address syntax ' . $match[1]);
}
}
} elseif (!$receivingData && preg_match('/^RSET$/i', trim($data))) {
@@ -88,7 +71,7 @@ class fakeSMTP {
} elseif (!$receivingData && preg_match('/^NOOP$/i', trim($data))) {
$this->reply('250 2.0.0 Ok');
} elseif (!$receivingData && preg_match('/^VRFY (.*)/i', trim($data), $match)) {
- $this->reply('250 2.0.0 '.$match[1]);
+ $this->reply('250 2.0.0 ' . $match[1]);
} elseif (!$receivingData && preg_match('/^DATA/i', trim($data))) {
if (!$hasValidTo) {
$this->reply('503 5.5.1 Error: need RCPT command');
@@ -97,7 +80,7 @@ class fakeSMTP {
$receivingData = true;
}
} elseif (!$receivingData && preg_match('/^(HELO|EHLO)/i', $data)) {
- $this->reply('250 HELO '.$this->mail['ipaddress']);
+ $this->reply('250 HELO ' . $this->mail['ipaddress']);
} elseif (!$receivingData && preg_match('/^QUIT/i', trim($data))) {
break;
} elseif (!$receivingData) {
@@ -106,7 +89,7 @@ class fakeSMTP {
} elseif ($receivingData && $data == ".\n") {
/* Email Received, now let's look at it */
$receivingData = false;
- $this->reply('250 2.0.0 Ok: queued as '.$this->generateRandom(10));
+ $this->reply('250 2.0.0 Ok: queued as ' . $this->generateRandom(10));
$splitmail = explode("\n\n", $this->mail['rawEmail'], 2);
if (count($splitmail) == 2) {
$this->mail['emailHeaders'] = $splitmail[0];
@@ -127,14 +110,14 @@ class fakeSMTP {
}
}
/* Say good bye */
- $this->reply('221 2.0.0 Bye '.$this->mail['ipaddress']);
+ $this->reply('221 2.0.0 Bye ' . $this->mail['ipaddress']);
fclose($this->fd);
}
public function log($s) {
if ($this->logFile) {
- file_put_contents($this->logFile, trim($s)."\n", FILE_APPEND);
+ file_put_contents($this->logFile, trim($s) . "\n", FILE_APPEND);
}
}
diff --git a/build/integration/features/bootstrap/FeatureContext.php b/build/integration/features/bootstrap/FeatureContext.php
index a3a600d6625..ab37556f931 100644
--- a/build/integration/features/bootstrap/FeatureContext.php
+++ b/build/integration/features/bootstrap/FeatureContext.php
@@ -1,40 +1,29 @@
<?php
+
/**
- * @copyright Copyright (c) 2016 Thomas Müller <thomas.mueller@tmit.eu>
- *
- * @author Daniel Calviño Sánchez <danxuliu@gmail.com>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Sergio Bertolin <sbertolin@solidgear.es>
- * @author Thomas Müller <thomas.mueller@tmit.eu>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * 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
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
use Behat\Behat\Context\Context;
use Behat\Behat\Context\SnippetAcceptingContext;
require __DIR__ . '/../../vendor/autoload.php';
-
/**
* Features context.
*/
class FeatureContext implements Context, SnippetAcceptingContext {
+ use AppConfiguration;
use ContactsMenu;
+ use ExternalStorage;
use Search;
use WebDav;
use Trashbin;
+
+ protected function resetAppConfigs(): void {
+ $this->deleteServerConfig('bruteForce', 'whitelist_0');
+ $this->deleteServerConfig('bruteForce', 'whitelist_1');
+ $this->deleteServerConfig('bruteforcesettings', 'apply_allowlist_to_ratelimit');
+ }
}
diff --git a/build/integration/features/bootstrap/FederationContext.php b/build/integration/features/bootstrap/FederationContext.php
index 423708adc10..95dc8119ad6 100644
--- a/build/integration/features/bootstrap/FederationContext.php
+++ b/build/integration/features/bootstrap/FederationContext.php
@@ -1,34 +1,14 @@
<?php
+
/**
- * @copyright Copyright (c) 2016 Sergio Bertolin <sbertolin@solidgear.es>
- *
- * @author Bjoern Schiessle <bjoern@schiessle.org>
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Daniel Calviño Sánchez <danxuliu@gmail.com>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Robin Appelman <robin@icewind.nl>
- * @author Sergio Bertolin <sbertolin@solidgear.es>
- * @author Sergio Bertolín <sbertolin@solidgear.es>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * 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
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
use Behat\Behat\Context\Context;
use Behat\Behat\Context\SnippetAcceptingContext;
use Behat\Gherkin\Node\TableNode;
+use PHPUnit\Framework\Assert;
require __DIR__ . '/../../vendor/autoload.php';
@@ -60,7 +40,7 @@ class FederationContext implements Context, SnippetAcceptingContext {
$port = getenv('PORT_FED');
- self::$phpFederatedServerPid = exec('php -S localhost:' . $port . ' -t ../../ >/dev/null & echo $!');
+ self::$phpFederatedServerPid = exec('PHP_CLI_SERVER_WORKERS=2 php -S localhost:' . $port . ' -t ../../ >/dev/null & echo $!');
}
/**
@@ -86,7 +66,7 @@ class FederationContext implements Context, SnippetAcceptingContext {
* @param string $shareeServer "LOCAL" or "REMOTE"
*/
public function federateSharing($sharerUser, $sharerServer, $sharerPath, $shareeUser, $shareeServer) {
- if ($shareeServer == "REMOTE") {
+ if ($shareeServer == 'REMOTE') {
$shareWith = "$shareeUser@" . substr($this->remoteBaseUrl, 0, -4);
} else {
$shareWith = "$shareeUser@" . substr($this->localBaseUrl, 0, -4);
@@ -107,7 +87,7 @@ class FederationContext implements Context, SnippetAcceptingContext {
* @param string $shareeServer "LOCAL" or "REMOTE"
*/
public function federateGroupSharing($sharerUser, $sharerServer, $sharerPath, $shareeGroup, $shareeServer) {
- if ($shareeServer == "REMOTE") {
+ if ($shareeServer == 'REMOTE') {
$shareWith = "$shareeGroup@" . substr($this->remoteBaseUrl, 0, -4);
} else {
$shareWith = "$shareeGroup@" . substr($this->localBaseUrl, 0, -4);
@@ -156,7 +136,7 @@ class FederationContext implements Context, SnippetAcceptingContext {
public function acceptLastPendingShare($user, $server) {
$previous = $this->usingServer($server);
$this->asAn($user);
- $this->sendingToWith('GET', "/apps/files_sharing/api/v1/remote_shares/pending", null);
+ $this->sendingToWith('GET', '/apps/files_sharing/api/v1/remote_shares/pending', null);
$this->theHTTPStatusCodeShouldBe('200');
$this->theOCSStatusCodeShouldBe('100');
$share_id = simplexml_load_string($this->response->getBody())->data[0]->element[0]->id;
@@ -174,7 +154,7 @@ class FederationContext implements Context, SnippetAcceptingContext {
*/
public function deleteLastAcceptedRemoteShare($user) {
$this->asAn($user);
- $this->sendingToWith('DELETE', "/apps/files_sharing/api/v1/remote_shares/" . $this->lastAcceptedRemoteShareId, null);
+ $this->sendingToWith('DELETE', '/apps/files_sharing/api/v1/remote_shares/' . $this->lastAcceptedRemoteShareId, null);
}
/**
@@ -190,8 +170,52 @@ class FederationContext implements Context, SnippetAcceptingContext {
self::$phpFederatedServerPid = '';
}
+ /**
+ * @BeforeScenario @TrustedFederation
+ */
+ public function theServersAreTrustingEachOther() {
+ $this->asAn('admin');
+ // Trust the remote server on the local server
+ $this->usingServer('LOCAL');
+ $this->sendRequestForJSON('POST', '/apps/federation/trusted-servers', ['url' => 'http://localhost:' . getenv('PORT')]);
+ Assert::assertTrue(($this->response->getStatusCode() === 200 || $this->response->getStatusCode() === 409));
+
+ // Trust the local server on the remote server
+ $this->usingServer('REMOTE');
+ $this->sendRequestForJSON('POST', '/apps/federation/trusted-servers', ['url' => 'http://localhost:' . getenv('PORT_FED')]);
+ // If the server is already trusted, we expect a 409
+ Assert::assertTrue(($this->response->getStatusCode() === 200 || $this->response->getStatusCode() === 409));
+ }
+
+ /**
+ * @AfterScenario @TrustedFederation
+ */
+ public function theServersAreNoLongerTrustingEachOther() {
+ $this->asAn('admin');
+ // Untrust the remote servers on the local server
+ $this->usingServer('LOCAL');
+ $this->sendRequestForJSON('GET', '/apps/federation/trusted-servers');
+ $this->theHTTPStatusCodeShouldBe('200');
+ $trustedServersIDs = array_map(fn ($server) => $server->id, json_decode($this->response->getBody())->ocs->data);
+ foreach ($trustedServersIDs as $id) {
+ $this->sendRequestForJSON('DELETE', '/apps/federation/trusted-servers/' . $id);
+ $this->theHTTPStatusCodeShouldBe('200');
+ }
+
+ // Untrust the local server on the remote server
+ $this->usingServer('REMOTE');
+ $this->sendRequestForJSON('GET', '/apps/federation/trusted-servers');
+ $this->theHTTPStatusCodeShouldBe('200');
+ $trustedServersIDs = array_map(fn ($server) => $server->id, json_decode($this->response->getBody())->ocs->data);
+ foreach ($trustedServersIDs as $id) {
+ $this->sendRequestForJSON('DELETE', '/apps/federation/trusted-servers/' . $id);
+ $this->theHTTPStatusCodeShouldBe('200');
+ }
+ }
+
protected function resetAppConfigs() {
$this->deleteServerConfig('files_sharing', 'incoming_server2server_group_share_enabled');
$this->deleteServerConfig('files_sharing', 'outgoing_server2server_group_share_enabled');
+ $this->deleteServerConfig('files_sharing', 'federated_trusted_share_auto_accept');
}
}
diff --git a/build/integration/features/bootstrap/FilesDropContext.php b/build/integration/features/bootstrap/FilesDropContext.php
index a5d4dad14e3..0c437f28a72 100644
--- a/build/integration/features/bootstrap/FilesDropContext.php
+++ b/build/integration/features/bootstrap/FilesDropContext.php
@@ -1,27 +1,8 @@
<?php
+
/**
- * @copyright Copyright (c) 2016 Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Robin Appelman <robin@icewind.nl>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * 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
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
use Behat\Behat\Context\Context;
use Behat\Behat\Context\SnippetAcceptingContext;
@@ -35,7 +16,7 @@ class FilesDropContext implements Context, SnippetAcceptingContext {
/**
* @When Dropping file :path with :content
*/
- public function droppingFileWith($path, $content) {
+ public function droppingFileWith($path, $content, $nickname = null) {
$client = new Client();
$options = [];
if (count($this->lastShareData->data->element) > 0) {
@@ -45,12 +26,16 @@ class FilesDropContext implements Context, SnippetAcceptingContext {
}
$base = substr($this->baseUrl, 0, -4);
- $fullUrl = $base . '/public.php/webdav' . $path;
+ $fullUrl = str_replace('//', '/', $base . "/public.php/dav/files/$token/$path");
- $options['auth'] = [$token, ''];
$options['headers'] = [
- 'X-REQUESTED-WITH' => 'XMLHttpRequest'
+ 'X-REQUESTED-WITH' => 'XMLHttpRequest',
];
+
+ if ($nickname) {
+ $options['headers']['X-NC-NICKNAME'] = $nickname;
+ }
+
$options['body'] = \GuzzleHttp\Psr7\Utils::streamFor($content);
try {
@@ -60,10 +45,19 @@ class FilesDropContext implements Context, SnippetAcceptingContext {
}
}
+
+ /**
+ * @When Dropping file :path with :content as :nickName
+ */
+ public function droppingFileWithAs($path, $content, $nickname) {
+ $this->droppingFileWith($path, $content, $nickname);
+ }
+
+
/**
* @When Creating folder :folder in drop
*/
- public function creatingFolderInDrop($folder) {
+ public function creatingFolderInDrop($folder, $nickname = null) {
$client = new Client();
$options = [];
if (count($this->lastShareData->data->element) > 0) {
@@ -73,17 +67,28 @@ class FilesDropContext implements Context, SnippetAcceptingContext {
}
$base = substr($this->baseUrl, 0, -4);
- $fullUrl = $base . '/public.php/webdav/' . $folder;
+ $fullUrl = str_replace('//', '/', $base . "/public.php/dav/files/$token/$folder");
- $options['auth'] = [$token, ''];
$options['headers'] = [
- 'X-REQUESTED-WITH' => 'XMLHttpRequest'
+ 'X-REQUESTED-WITH' => 'XMLHttpRequest',
];
+ if ($nickname) {
+ $options['headers']['X-NC-NICKNAME'] = $nickname;
+ }
+
try {
$this->response = $client->request('MKCOL', $fullUrl, $options);
} catch (\GuzzleHttp\Exception\ClientException $e) {
$this->response = $e->getResponse();
}
}
+
+
+ /**
+ * @When Creating folder :folder in drop as :nickName
+ */
+ public function creatingFolderInDropWithNickname($folder, $nickname) {
+ return $this->creatingFolderInDrop($folder, $nickname);
+ }
}
diff --git a/build/integration/features/bootstrap/LDAPContext.php b/build/integration/features/bootstrap/LDAPContext.php
index e0315bce84e..986dced77a1 100644
--- a/build/integration/features/bootstrap/LDAPContext.php
+++ b/build/integration/features/bootstrap/LDAPContext.php
@@ -1,27 +1,8 @@
<?php
+
/**
- * @copyright Copyright (c) 2017 Arthur Schiwon <blizzz@arthur-schiwon.de>
- *
- * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author John Molakvoæ <skjnldsv@protonmail.com>
- * @author Robin Appelman <robin@icewind.nl>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * 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
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
use Behat\Behat\Context\Context;
use Behat\Gherkin\Node\TableNode;
@@ -104,7 +85,7 @@ class LDAPContext implements Context {
$this->asAn('admin');
$this->creatingAnLDAPConfigurationAt('/apps/user_ldap/api/v1/config');
$data = new TableNode([
- ['configData[ldapHost]', 'openldap'],
+ ['configData[ldapHost]', getenv('LDAP_HOST') ?: 'openldap'],
['configData[ldapPort]', '389'],
['configData[ldapBase]', 'dc=nextcloud,dc=ci'],
['configData[ldapAgentName]', 'cn=admin,dc=nextcloud,dc=ci'],
@@ -141,6 +122,9 @@ class LDAPContext implements Context {
$this->asAn('admin');
$configData = $table->getRows();
foreach ($configData as &$row) {
+ if (str_contains($row[0], 'Host') && getenv('LDAP_HOST')) {
+ $row[1] = str_replace('openldap', getenv('LDAP_HOST'), $row[1]);
+ }
$row[0] = 'configData[' . $row[0] . ']';
}
$this->settingTheLDAPConfigurationTo(new TableNode($configData));
diff --git a/build/integration/features/bootstrap/Mail.php b/build/integration/features/bootstrap/Mail.php
index c2d9e86275c..d48ed6399c5 100644
--- a/build/integration/features/bootstrap/Mail.php
+++ b/build/integration/features/bootstrap/Mail.php
@@ -1,24 +1,8 @@
<?php
+
/**
- * @copyright Copyright (c) 2020, Daniel Calviño Sánchez (danxuliu@gmail.com)
- *
- * @author Daniel Calviño Sánchez <danxuliu@gmail.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * 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
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
trait Mail {
// CommandLine trait is expected to be used in the class that uses this
@@ -37,7 +21,7 @@ trait Mail {
return;
}
- exec("kill " . $this->fakeSmtpServerPid);
+ exec('kill ' . $this->fakeSmtpServerPid);
$this->invokingTheCommand('config:system:delete mail_smtpport');
}
@@ -50,6 +34,6 @@ trait Mail {
// FakeSMTP uses 2525 instead.
$this->invokingTheCommand('config:system:set mail_smtpport --value=2525 --type integer');
- $this->fakeSmtpServerPid = exec("php features/bootstrap/FakeSMTPHelper.php >/dev/null 2>&1 & echo $!");
+ $this->fakeSmtpServerPid = exec('php features/bootstrap/FakeSMTPHelper.php >/dev/null 2>&1 & echo $!');
}
}
diff --git a/build/integration/features/bootstrap/MetadataContext.php b/build/integration/features/bootstrap/MetadataContext.php
new file mode 100644
index 00000000000..32042590c86
--- /dev/null
+++ b/build/integration/features/bootstrap/MetadataContext.php
@@ -0,0 +1,124 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+use Behat\Behat\Context\Context;
+use Behat\Step\Then;
+use Behat\Step\When;
+use PHPUnit\Framework\Assert;
+use Sabre\DAV\Client as SClient;
+
+require __DIR__ . '/../../vendor/autoload.php';
+
+class MetadataContext implements Context {
+ private string $davPath = '/remote.php/dav';
+
+ public function __construct(
+ private string $baseUrl,
+ private array $admin,
+ private string $regular_user_password,
+ ) {
+ // in case of ci deployment we take the server url from the environment
+ $testServerUrl = getenv('TEST_SERVER_URL');
+ if ($testServerUrl !== false) {
+ $this->baseUrl = substr($testServerUrl, 0, -5);
+ }
+ }
+
+ #[When('User :user sets the :metadataKey prop with value :metadataValue on :fileName')]
+ public function userSetsProp(string $user, string $metadataKey, string $metadataValue, string $fileName) {
+ $client = new SClient([
+ 'baseUri' => $this->baseUrl,
+ 'userName' => $user,
+ 'password' => '123456',
+ 'authType' => SClient::AUTH_BASIC,
+ ]);
+
+ $body = '<?xml version="1.0"?>
+<d:propertyupdate xmlns:d="DAV:" xmlns:nc="http://nextcloud.com/ns">
+ <d:set>
+ <d:prop>
+ <nc:' . $metadataKey . '>' . $metadataValue . '</nc:' . $metadataKey . '>
+ </d:prop>
+ </d:set>
+</d:propertyupdate>';
+
+ $davUrl = $this->getDavUrl($user, $fileName);
+ $client->request('PROPPATCH', $this->baseUrl . $davUrl, $body);
+ }
+
+ #[When('User :user deletes the :metadataKey prop on :fileName')]
+ public function userDeletesProp(string $user, string $metadataKey, string $fileName) {
+ $client = new SClient([
+ 'baseUri' => $this->baseUrl,
+ 'userName' => $user,
+ 'password' => '123456',
+ 'authType' => SClient::AUTH_BASIC,
+ ]);
+
+ $body = '<?xml version="1.0"?>
+<d:propertyupdate xmlns:d="DAV:" xmlns:nc="http://nextcloud.com/ns">
+ <d:remove>
+ <d:prop>
+ <nc:' . $metadataKey . '></nc:' . $metadataKey . '>
+ </d:prop>
+ </d:remove>
+</d:propertyupdate>';
+
+ $davUrl = $this->getDavUrl($user, $fileName);
+ $client->request('PROPPATCH', $this->baseUrl . $davUrl, $body);
+ }
+
+ #[Then('User :user should see the prop :metadataKey equal to :metadataValue for file :fileName')]
+ public function checkPropForFile(string $user, string $metadataKey, string $metadataValue, string $fileName) {
+ $client = new SClient([
+ 'baseUri' => $this->baseUrl,
+ 'userName' => $user,
+ 'password' => '123456',
+ 'authType' => SClient::AUTH_BASIC,
+ ]);
+
+ $body = '<?xml version="1.0"?>
+<d:propfind xmlns:d="DAV:" xmlns:nc="http://nextcloud.com/ns">
+ <d:prop>
+ <nc:' . $metadataKey . '></nc:' . $metadataKey . '>
+ </d:prop>
+</d:propfind>';
+
+ $davUrl = $this->getDavUrl($user, $fileName);
+ $response = $client->request('PROPFIND', $this->baseUrl . $davUrl, $body);
+ $parsedResponse = $client->parseMultistatus($response['body']);
+
+ Assert::assertEquals($parsedResponse[$davUrl]['200']['{http://nextcloud.com/ns}' . $metadataKey], $metadataValue);
+ }
+
+ #[Then('User :user should not see the prop :metadataKey for file :fileName')]
+ public function checkPropDoesNotExistsForFile(string $user, string $metadataKey, string $fileName) {
+ $client = new SClient([
+ 'baseUri' => $this->baseUrl,
+ 'userName' => $user,
+ 'password' => '123456',
+ 'authType' => SClient::AUTH_BASIC,
+ ]);
+
+ $body = '<?xml version="1.0"?>
+<d:propfind xmlns:d="DAV:" xmlns:nc="http://nextcloud.com/ns">
+ <d:prop>
+ <nc:' . $metadataKey . '></nc:' . $metadataKey . '>
+ </d:prop>
+</d:propfind>';
+
+ $davUrl = $this->getDavUrl($user, $fileName);
+ $response = $client->request('PROPFIND', $this->baseUrl . $davUrl, $body);
+ $parsedResponse = $client->parseMultistatus($response['body']);
+
+ Assert::assertEquals($parsedResponse[$davUrl]['404']['{http://nextcloud.com/ns}' . $metadataKey], null);
+ }
+
+ private function getDavUrl(string $user, string $fileName) {
+ return $this->davPath . '/files/' . $user . $fileName;
+ }
+}
diff --git a/build/integration/features/bootstrap/PrincipalPropertySearchContext.php b/build/integration/features/bootstrap/PrincipalPropertySearchContext.php
new file mode 100644
index 00000000000..9dfd9379240
--- /dev/null
+++ b/build/integration/features/bootstrap/PrincipalPropertySearchContext.php
@@ -0,0 +1,141 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+require __DIR__ . '/../../vendor/autoload.php';
+
+use Behat\Behat\Context\Context;
+use GuzzleHttp\BodySummarizer;
+use GuzzleHttp\Client;
+use GuzzleHttp\HandlerStack;
+use GuzzleHttp\Middleware;
+use GuzzleHttp\Utils;
+use Psr\Http\Message\ResponseInterface;
+
+class PrincipalPropertySearchContext implements Context {
+ private string $baseUrl;
+ private Client $client;
+ private ResponseInterface $response;
+
+ public function __construct(string $baseUrl) {
+ $this->baseUrl = $baseUrl;
+
+ // in case of ci deployment we take the server url from the environment
+ $testServerUrl = getenv('TEST_SERVER_URL');
+ if ($testServerUrl !== false) {
+ $this->baseUrl = substr($testServerUrl, 0, -5);
+ }
+ }
+
+ /** @BeforeScenario */
+ public function setUpScenario(): void {
+ $this->client = $this->createGuzzleInstance();
+ }
+
+ /**
+ * Create a Guzzle client with a higher truncateAt value to read full error responses.
+ */
+ private function createGuzzleInstance(): Client {
+ $bodySummarizer = new BodySummarizer(2048);
+
+ $stack = new HandlerStack(Utils::chooseHandler());
+ $stack->push(Middleware::httpErrors($bodySummarizer), 'http_errors');
+ $stack->push(Middleware::redirect(), 'allow_redirects');
+ $stack->push(Middleware::cookies(), 'cookies');
+ $stack->push(Middleware::prepareBody(), 'prepare_body');
+
+ return new Client(['handler' => $stack]);
+ }
+
+ /**
+ * @When searching for a principal matching :match
+ * @param string $match
+ * @throws \Exception
+ */
+ public function principalPropertySearch(string $match) {
+ $davUrl = $this->baseUrl . '/remote.php/dav/';
+ $user = 'admin';
+ $password = 'admin';
+
+ $this->response = $this->client->request(
+ 'REPORT',
+ $davUrl,
+ [
+ 'body' => '<x0:principal-property-search xmlns:x0="DAV:" test="anyof">
+ <x0:property-search>
+ <x0:prop>
+ <x0:displayname/>
+ <x2:email-address xmlns:x2="http://sabredav.org/ns"/>
+ </x0:prop>
+ <x0:match>' . $match . '</x0:match>
+ </x0:property-search>
+ <x0:prop>
+ <x0:displayname/>
+ <x1:calendar-user-type xmlns:x1="urn:ietf:params:xml:ns:caldav"/>
+ <x1:calendar-user-address-set xmlns:x1="urn:ietf:params:xml:ns:caldav"/>
+ <x0:principal-URL/>
+ <x0:alternate-URI-set/>
+ <x2:email-address xmlns:x2="http://sabredav.org/ns"/>
+ <x3:language xmlns:x3="http://nextcloud.com/ns"/>
+ <x1:calendar-home-set xmlns:x1="urn:ietf:params:xml:ns:caldav"/>
+ <x1:schedule-inbox-URL xmlns:x1="urn:ietf:params:xml:ns:caldav"/>
+ <x1:schedule-outbox-URL xmlns:x1="urn:ietf:params:xml:ns:caldav"/>
+ <x1:schedule-default-calendar-URL xmlns:x1="urn:ietf:params:xml:ns:caldav"/>
+ <x3:resource-type xmlns:x3="http://nextcloud.com/ns"/>
+ <x3:resource-vehicle-type xmlns:x3="http://nextcloud.com/ns"/>
+ <x3:resource-vehicle-make xmlns:x3="http://nextcloud.com/ns"/>
+ <x3:resource-vehicle-model xmlns:x3="http://nextcloud.com/ns"/>
+ <x3:resource-vehicle-is-electric xmlns:x3="http://nextcloud.com/ns"/>
+ <x3:resource-vehicle-range xmlns:x3="http://nextcloud.com/ns"/>
+ <x3:resource-vehicle-seating-capacity xmlns:x3="http://nextcloud.com/ns"/>
+ <x3:resource-contact-person xmlns:x3="http://nextcloud.com/ns"/>
+ <x3:resource-contact-person-vcard xmlns:x3="http://nextcloud.com/ns"/>
+ <x3:room-type xmlns:x3="http://nextcloud.com/ns"/>
+ <x3:room-seating-capacity xmlns:x3="http://nextcloud.com/ns"/>
+ <x3:room-building-address xmlns:x3="http://nextcloud.com/ns"/>
+ <x3:room-building-story xmlns:x3="http://nextcloud.com/ns"/>
+ <x3:room-building-room-number xmlns:x3="http://nextcloud.com/ns"/>
+ <x3:room-features xmlns:x3="http://nextcloud.com/ns"/>
+ </x0:prop>
+ <x0:apply-to-principal-collection-set/>
+</x0:principal-property-search>
+',
+ 'auth' => [
+ $user,
+ $password,
+ ],
+ 'headers' => [
+ 'Content-Type' => 'application/xml; charset=UTF-8',
+ 'Depth' => '0',
+ ],
+ ]
+ );
+ }
+
+ /**
+ * @Then The search HTTP status code should be :code
+ * @param string $code
+ * @throws \Exception
+ */
+ public function theHttpStatusCodeShouldBe(string $code): void {
+ if ((int)$code !== $this->response->getStatusCode()) {
+ throw new \Exception('Expected ' . (int)$code . ' got ' . $this->response->getStatusCode());
+ }
+ }
+
+ /**
+ * @Then The search response should contain :needle
+ * @param string $needle
+ * @throws \Exception
+ */
+ public function theResponseShouldContain(string $needle): void {
+ $body = $this->response->getBody()->getContents();
+
+ if (str_contains($body, $needle) === false) {
+ throw new \Exception('Response does not contain "' . $needle . '"');
+ }
+ }
+}
diff --git a/build/integration/features/bootstrap/Provisioning.php b/build/integration/features/bootstrap/Provisioning.php
index 7fb4188f919..935ad2a4a1d 100644
--- a/build/integration/features/bootstrap/Provisioning.php
+++ b/build/integration/features/bootstrap/Provisioning.php
@@ -1,36 +1,12 @@
<?php
+
/**
- * @copyright Copyright (c) 2016 Sergio Bertolin <sbertolin@solidgear.es>
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Daniel Calviño Sánchez <danxuliu@gmail.com>
- * @author Joas Schilling <coding@schilljs.com>
- * @author John Molakvoæ <skjnldsv@protonmail.com>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Robin Appelman <robin@icewind.nl>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- * @author Sergio Bertolin <sbertolin@solidgear.es>
- * @author Sergio Bertolín <sbertolin@solidgear.es>
- * @author Thomas Müller <thomas.mueller@tmit.eu>
- * @author Vincent Petry <vincent@nextcloud.com>
- * @author Jonas Meurer <jonas@freesources.org>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * 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
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
+
+use Behat\Gherkin\Node\TableNode;
use GuzzleHttp\Client;
use GuzzleHttp\Message\ResponseInterface;
use PHPUnit\Framework\Assert;
@@ -61,7 +37,7 @@ trait Provisioning {
$this->userExists($user);
} catch (\GuzzleHttp\Exception\ClientException $ex) {
$previous_user = $this->currentUser;
- $this->currentUser = "admin";
+ $this->currentUser = 'admin';
$this->creatingTheUser($user);
$this->currentUser = $previous_user;
}
@@ -78,7 +54,7 @@ trait Provisioning {
$this->userExists($user);
} catch (\GuzzleHttp\Exception\ClientException $ex) {
$previous_user = $this->currentUser;
- $this->currentUser = "admin";
+ $this->currentUser = 'admin';
$this->creatingTheUser($user, $displayname);
$this->currentUser = $previous_user;
}
@@ -99,7 +75,7 @@ trait Provisioning {
return;
}
$previous_user = $this->currentUser;
- $this->currentUser = "admin";
+ $this->currentUser = 'admin';
$this->deletingTheUser($user);
$this->currentUser = $previous_user;
try {
@@ -151,7 +127,7 @@ trait Provisioning {
* @Then /^user "([^"]*)" has$/
*
* @param string $user
- * @param \Behat\Gherkin\Node\TableNode|null $settings
+ * @param TableNode|null $settings
*/
public function userHasSetting($user, $settings) {
$fullUrl = $this->baseUrl . "v{$this->apiVersion}.php/cloud/users/$user";
@@ -172,12 +148,43 @@ trait Provisioning {
if (isset($value['element']) && in_array($setting[0], ['additional_mail', 'additional_mailScope'], true)) {
$expectedValues = explode(';', $setting[1]);
foreach ($expectedValues as $expected) {
- Assert::assertTrue(in_array($expected, $value['element'], true));
+ Assert::assertTrue(in_array($expected, $value['element'], true), 'Data wrong for field: ' . $setting[0]);
}
} elseif (isset($value[0])) {
- Assert::assertEqualsCanonicalizing($setting[1], $value[0]);
+ Assert::assertEqualsCanonicalizing($setting[1], $value[0], 'Data wrong for field: ' . $setting[0]);
} else {
- Assert::assertEquals('', $setting[1]);
+ Assert::assertEquals('', $setting[1], 'Data wrong for field: ' . $setting[0]);
+ }
+ }
+ }
+
+ /**
+ * @Then /^user "([^"]*)" has the following profile data$/
+ */
+ public function userHasProfileData(string $user, ?TableNode $settings): void {
+ $fullUrl = $this->baseUrl . "v{$this->apiVersion}.php/profile/$user";
+ $client = new Client();
+ $options = [];
+ if ($this->currentUser === 'admin') {
+ $options['auth'] = $this->adminUser;
+ } else {
+ $options['auth'] = [$this->currentUser, $this->regularUser];
+ }
+ $options['headers'] = [
+ 'OCS-APIREQUEST' => 'true',
+ 'Accept' => 'application/json',
+ ];
+
+ $response = $client->get($fullUrl, $options);
+ $body = $response->getBody()->getContents();
+ $data = json_decode($body, true);
+ $data = $data['ocs']['data'];
+ foreach ($settings->getRows() as $setting) {
+ Assert::assertArrayHasKey($setting[0], $data, 'Profile data field missing: ' . $setting[0]);
+ if ($setting[1] === 'NULL') {
+ Assert::assertNull($data[$setting[0]], 'Profile data wrong for field: ' . $setting[0]);
+ } else {
+ Assert::assertEquals($setting[1], $data[$setting[0]], 'Profile data wrong for field: ' . $setting[0]);
}
}
}
@@ -186,7 +193,7 @@ trait Provisioning {
* @Then /^group "([^"]*)" has$/
*
* @param string $user
- * @param \Behat\Gherkin\Node\TableNode|null $settings
+ * @param TableNode|null $settings
*/
public function groupHasSetting($group, $settings) {
$fullUrl = $this->baseUrl . "v{$this->apiVersion}.php/cloud/groups/details?search=$group";
@@ -218,7 +225,7 @@ trait Provisioning {
* @Then /^user "([^"]*)" has editable fields$/
*
* @param string $user
- * @param \Behat\Gherkin\Node\TableNode|null $fields
+ * @param TableNode|null $fields
*/
public function userHasEditableFields($user, $fields) {
$fullUrl = $this->baseUrl . "v{$this->apiVersion}.php/cloud/user/fields";
@@ -248,9 +255,9 @@ trait Provisioning {
* @Then /^search users by phone for region "([^"]*)" with$/
*
* @param string $user
- * @param \Behat\Gherkin\Node\TableNode|null $settings
+ * @param TableNode|null $settings
*/
- public function searchUserByPhone($region, \Behat\Gherkin\Node\TableNode $searchTable) {
+ public function searchUserByPhone($region, TableNode $searchTable) {
$fullUrl = $this->baseUrl . "v{$this->apiVersion}.php/cloud/users/search/by-phone";
$client = new Client();
$options = [];
@@ -277,7 +284,7 @@ trait Provisioning {
public function createUser($user) {
$previous_user = $this->currentUser;
- $this->currentUser = "admin";
+ $this->currentUser = 'admin';
$this->creatingTheUser($user);
$this->userExists($user);
$this->currentUser = $previous_user;
@@ -285,7 +292,7 @@ trait Provisioning {
public function deleteUser($user) {
$previous_user = $this->currentUser;
- $this->currentUser = "admin";
+ $this->currentUser = 'admin';
$this->deletingTheUser($user);
$this->userDoesNotExist($user);
$this->currentUser = $previous_user;
@@ -293,7 +300,7 @@ trait Provisioning {
public function createGroup($group) {
$previous_user = $this->currentUser;
- $this->currentUser = "admin";
+ $this->currentUser = 'admin';
$this->creatingTheGroup($group);
$this->groupExists($group);
$this->currentUser = $previous_user;
@@ -301,7 +308,7 @@ trait Provisioning {
public function deleteGroup($group) {
$previous_user = $this->currentUser;
- $this->currentUser = "admin";
+ $this->currentUser = 'admin';
$this->deletingTheGroup($group);
$this->groupDoesNotExist($group);
$this->currentUser = $previous_user;
@@ -370,7 +377,7 @@ trait Provisioning {
*/
public function assureUserBelongsToGroup($user, $group) {
$previous_user = $this->currentUser;
- $this->currentUser = "admin";
+ $this->currentUser = 'admin';
if (!$this->userBelongsToGroup($user, $group)) {
$this->addingUserToGroup($user, $group);
@@ -549,7 +556,7 @@ trait Provisioning {
$this->groupExists($group);
} catch (\GuzzleHttp\Exception\ClientException $ex) {
$previous_user = $this->currentUser;
- $this->currentUser = "admin";
+ $this->currentUser = 'admin';
$this->creatingTheGroup($group);
$this->currentUser = $previous_user;
}
@@ -570,7 +577,7 @@ trait Provisioning {
return;
}
$previous_user = $this->currentUser;
- $this->currentUser = "admin";
+ $this->currentUser = 'admin';
$this->deletingTheGroup($group);
$this->currentUser = $previous_user;
try {
@@ -651,10 +658,10 @@ trait Provisioning {
/**
* @Then /^users returned are$/
- * @param \Behat\Gherkin\Node\TableNode|null $usersList
+ * @param TableNode|null $usersList
*/
public function theUsersShouldBe($usersList) {
- if ($usersList instanceof \Behat\Gherkin\Node\TableNode) {
+ if ($usersList instanceof TableNode) {
$users = $usersList->getRows();
$usersSimplified = $this->simplifyArray($users);
$respondedArray = $this->getArrayOfUsersResponded($this->response);
@@ -664,10 +671,10 @@ trait Provisioning {
/**
* @Then /^phone matches returned are$/
- * @param \Behat\Gherkin\Node\TableNode|null $usersList
+ * @param TableNode|null $usersList
*/
public function thePhoneUsersShouldBe($usersList) {
- if ($usersList instanceof \Behat\Gherkin\Node\TableNode) {
+ if ($usersList instanceof TableNode) {
$users = $usersList->getRowsHash();
$listCheckedElements = simplexml_load_string($this->response->getBody())->data;
$respondedArray = json_decode(json_encode($listCheckedElements), true);
@@ -677,10 +684,10 @@ trait Provisioning {
/**
* @Then /^detailed users returned are$/
- * @param \Behat\Gherkin\Node\TableNode|null $usersList
+ * @param TableNode|null $usersList
*/
public function theDetailedUsersShouldBe($usersList) {
- if ($usersList instanceof \Behat\Gherkin\Node\TableNode) {
+ if ($usersList instanceof TableNode) {
$users = $usersList->getRows();
$usersSimplified = $this->simplifyArray($users);
$respondedArray = $this->getArrayOfDetailedUsersResponded($this->response);
@@ -691,10 +698,10 @@ trait Provisioning {
/**
* @Then /^groups returned are$/
- * @param \Behat\Gherkin\Node\TableNode|null $groupsList
+ * @param TableNode|null $groupsList
*/
public function theGroupsShouldBe($groupsList) {
- if ($groupsList instanceof \Behat\Gherkin\Node\TableNode) {
+ if ($groupsList instanceof TableNode) {
$groups = $groupsList->getRows();
$groupsSimplified = $this->simplifyArray($groups);
$respondedArray = $this->getArrayOfGroupsResponded($this->response);
@@ -704,10 +711,10 @@ trait Provisioning {
/**
* @Then /^subadmin groups returned are$/
- * @param \Behat\Gherkin\Node\TableNode|null $groupsList
+ * @param TableNode|null $groupsList
*/
public function theSubadminGroupsShouldBe($groupsList) {
- if ($groupsList instanceof \Behat\Gherkin\Node\TableNode) {
+ if ($groupsList instanceof TableNode) {
$groups = $groupsList->getRows();
$groupsSimplified = $this->simplifyArray($groups);
$respondedArray = $this->getArrayOfSubadminsResponded($this->response);
@@ -717,10 +724,10 @@ trait Provisioning {
/**
* @Then /^apps returned are$/
- * @param \Behat\Gherkin\Node\TableNode|null $appList
+ * @param TableNode|null $appList
*/
public function theAppsShouldBe($appList) {
- if ($appList instanceof \Behat\Gherkin\Node\TableNode) {
+ if ($appList instanceof TableNode) {
$apps = $appList->getRows();
$appsSimplified = $this->simplifyArray($apps);
$respondedArray = $this->getArrayOfAppsResponded($this->response);
@@ -730,7 +737,7 @@ trait Provisioning {
/**
* @Then /^subadmin users returned are$/
- * @param \Behat\Gherkin\Node\TableNode|null $groupsList
+ * @param TableNode|null $groupsList
*/
public function theSubadminUsersShouldBe($groupsList) {
$this->theSubadminGroupsShouldBe($groupsList);
@@ -802,7 +809,7 @@ trait Provisioning {
* @param string $app
*/
public function appIsDisabled($app) {
- $fullUrl = $this->baseUrl . "v2.php/cloud/apps?filter=disabled";
+ $fullUrl = $this->baseUrl . 'v2.php/cloud/apps?filter=disabled';
$client = new Client();
$options = [];
if ($this->currentUser === 'admin') {
@@ -823,7 +830,7 @@ trait Provisioning {
* @param string $app
*/
public function appIsEnabled($app) {
- $fullUrl = $this->baseUrl . "v2.php/cloud/apps?filter=enabled";
+ $fullUrl = $this->baseUrl . 'v2.php/cloud/apps?filter=enabled';
$client = new Client();
$options = [];
if ($this->currentUser === 'admin') {
@@ -847,7 +854,7 @@ trait Provisioning {
* @param string $app
*/
public function appIsNotEnabled($app) {
- $fullUrl = $this->baseUrl . "v2.php/cloud/apps?filter=enabled";
+ $fullUrl = $this->baseUrl . 'v2.php/cloud/apps?filter=enabled';
$client = new Client();
$options = [];
if ($this->currentUser === 'admin') {
@@ -900,7 +907,7 @@ trait Provisioning {
$this->response = $client->get($fullUrl, $options);
// boolean to string is integer
- Assert::assertEquals("1", simplexml_load_string($this->response->getBody())->data[0]->enabled);
+ Assert::assertEquals('1', simplexml_load_string($this->response->getBody())->data[0]->enabled);
}
/**
@@ -909,13 +916,13 @@ trait Provisioning {
* @param string $quota
*/
public function userHasAQuotaOf($user, $quota) {
- $body = new \Behat\Gherkin\Node\TableNode([
+ $body = new TableNode([
0 => ['key', 'quota'],
1 => ['value', $quota],
]);
// method used from BasicStructure trait
- $this->sendingToWith("PUT", "/cloud/users/" . $user, $body);
+ $this->sendingToWith('PUT', '/cloud/users/' . $user, $body);
}
/**
@@ -977,7 +984,7 @@ trait Provisioning {
/**
* @Then /^user "([^"]*)" has not$/
*/
- public function userHasNotSetting($user, \Behat\Gherkin\Node\TableNode $settings) {
+ public function userHasNotSetting($user, TableNode $settings) {
$fullUrl = $this->baseUrl . "v{$this->apiVersion}.php/cloud/users/$user";
$client = new Client();
$options = [];
diff --git a/build/integration/features/bootstrap/RateLimitingContext.php b/build/integration/features/bootstrap/RateLimitingContext.php
new file mode 100644
index 00000000000..15c8c5c8379
--- /dev/null
+++ b/build/integration/features/bootstrap/RateLimitingContext.php
@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+use Behat\Behat\Context\Context;
+
+class RateLimitingContext implements Context {
+ use BasicStructure;
+ use CommandLine;
+ use Provisioning;
+
+ /**
+ * @BeforeScenario @RateLimiting
+ */
+ public function enableRateLimiting() {
+ // Enable rate limiting for the tests.
+ // Ratelimiting is disabled by default, so we need to enable it
+ $this->runOcc(['config:system:set', 'ratelimit.protection.enabled', '--value', 'true', '--type', 'bool']);
+ }
+
+ /**
+ * @AfterScenario @RateLimiting
+ */
+ public function disableRateLimiting() {
+ // Restore the default rate limiting configuration.
+ // Ratelimiting is disabled by default, so we need to disable it
+ $this->runOcc(['config:system:set', 'ratelimit.protection.enabled', '--value', 'false', '--type', 'bool']);
+ }
+}
diff --git a/build/integration/features/bootstrap/RemoteContext.php b/build/integration/features/bootstrap/RemoteContext.php
index a3e5e1b5007..6102f686ea7 100644
--- a/build/integration/features/bootstrap/RemoteContext.php
+++ b/build/integration/features/bootstrap/RemoteContext.php
@@ -1,28 +1,11 @@
<?php
+
/**
- * @copyright Copyright (c) 2017 Robin Appelman <robin@icewind.nl>
- *
- * @author Joas Schilling <coding@schilljs.com>
- * @author John Molakvoæ <skjnldsv@protonmail.com>
- * @author Robin Appelman <robin@icewind.nl>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * 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
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
use Behat\Behat\Context\Context;
+use OCP\Http\Client\IClientService;
use PHPUnit\Framework\Assert;
require __DIR__ . '/../../vendor/autoload.php';
@@ -50,7 +33,7 @@ class RemoteContext implements Context {
}
protected function getApiClient() {
- return new \OC\Remote\Api\OCS($this->remoteInstance, $this->credentails, \OC::$server->getHTTPClientService());
+ return new \OC\Remote\Api\OCS($this->remoteInstance, $this->credentails, \OC::$server->get(IClientService::class));
}
/**
@@ -59,14 +42,14 @@ class RemoteContext implements Context {
* @param string $remoteServer "NON_EXISTING" or "REMOTE"
*/
public function selectRemoteInstance($remoteServer) {
- if ($remoteServer == "REMOTE") {
+ if ($remoteServer == 'REMOTE') {
$baseUri = $this->remoteUrl;
} else {
$baseUri = 'nonexistingnextcloudserver.local';
}
$this->lastException = null;
try {
- $this->remoteInstance = new \OC\Remote\Instance($baseUri, \OC::$server->getMemCacheFactory()->createLocal(), \OC::$server->getHTTPClientService());
+ $this->remoteInstance = new \OC\Remote\Instance($baseUri, \OC::$server->getMemCacheFactory()->createLocal(), \OC::$server->get(IClientService::class));
// trigger the status request
$this->remoteInstance->getProtocol();
} catch (\Exception $e) {
diff --git a/build/integration/features/bootstrap/RoutingContext.php b/build/integration/features/bootstrap/RoutingContext.php
new file mode 100644
index 00000000000..762570547e0
--- /dev/null
+++ b/build/integration/features/bootstrap/RoutingContext.php
@@ -0,0 +1,19 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+use Behat\Behat\Context\Context;
+use Behat\Behat\Context\SnippetAcceptingContext;
+
+require __DIR__ . '/../../vendor/autoload.php';
+
+class RoutingContext implements Context, SnippetAcceptingContext {
+ use Provisioning;
+ use AppConfiguration;
+ use CommandLine;
+
+ protected function resetAppConfigs(): void {
+ }
+}
diff --git a/build/integration/features/bootstrap/Search.php b/build/integration/features/bootstrap/Search.php
index c9d5f75a1d3..49a4fe92822 100644
--- a/build/integration/features/bootstrap/Search.php
+++ b/build/integration/features/bootstrap/Search.php
@@ -1,24 +1,8 @@
<?php
+
/**
- * @copyright Copyright (c) 2018, Daniel Calviño Sánchez (danxuliu@gmail.com)
- *
- * @author Daniel Calviño Sánchez <danxuliu@gmail.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * 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
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
use Behat\Gherkin\Node\TableNode;
use PHPUnit\Framework\Assert;
diff --git a/build/integration/features/bootstrap/SetupContext.php b/build/integration/features/bootstrap/SetupContext.php
index 5abdb22ccfc..aa131cec597 100644
--- a/build/integration/features/bootstrap/SetupContext.php
+++ b/build/integration/features/bootstrap/SetupContext.php
@@ -1,24 +1,8 @@
<?php
+
/**
- * @copyright Morris Jobke
- *
- * @author Morris Jobke <hey@morrisjobke.de>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * 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
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
use Behat\Behat\Context\Context;
diff --git a/build/integration/features/bootstrap/ShareesContext.php b/build/integration/features/bootstrap/ShareesContext.php
index 70e78e24929..37e0e63e547 100644
--- a/build/integration/features/bootstrap/ShareesContext.php
+++ b/build/integration/features/bootstrap/ShareesContext.php
@@ -1,26 +1,9 @@
<?php
+
/**
- * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com>
- *
- * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
- * @author Daniel Calviño Sánchez <danxuliu@gmail.com>
- * @author Joas Schilling <coding@schilljs.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * 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
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
use Behat\Behat\Context\Context;
use Behat\Behat\Context\SnippetAcceptingContext;
diff --git a/build/integration/features/bootstrap/Sharing.php b/build/integration/features/bootstrap/Sharing.php
index 2a6a509d65f..0cc490ff110 100644
--- a/build/integration/features/bootstrap/Sharing.php
+++ b/build/integration/features/bootstrap/Sharing.php
@@ -1,35 +1,9 @@
<?php
+
/**
- * @copyright Copyright (c) 2016 Sergio Bertolin <sbertolin@solidgear.es>
- *
- * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Daniel Calviño Sánchez <danxuliu@gmail.com>
- * @author Joas Schilling <coding@schilljs.com>
- * @author John Molakvoæ <skjnldsv@protonmail.com>
- * @author Julius Härtl <jus@bitgrid.net>
- * @author Lukas Reschke <lukas@statuscode.ch>
- * @author Robin Appelman <robin@icewind.nl>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- * @author Sergio Bertolin <sbertolin@solidgear.es>
- * @author Sergio Bertolín <sbertolin@solidgear.es>
- * @author Vincent Petry <vincent@nextcloud.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * 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
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
use Behat\Gherkin\Node\TableNode;
use GuzzleHttp\Client;
@@ -81,13 +55,19 @@ trait Sharing {
$fd = $body->getRowsHash();
if (array_key_exists('expireDate', $fd)) {
$dateModification = $fd['expireDate'];
- $fd['expireDate'] = date('Y-m-d', strtotime($dateModification));
+ if ($dateModification === 'null') {
+ $fd['expireDate'] = null;
+ } elseif (!empty($dateModification)) {
+ $fd['expireDate'] = date('Y-m-d', strtotime($dateModification));
+ } else {
+ $fd['expireDate'] = '';
+ }
}
$options['form_params'] = $fd;
}
try {
- $this->response = $client->request("POST", $fullUrl, $options);
+ $this->response = $client->request('POST', $fullUrl, $options);
} catch (\GuzzleHttp\Exception\ClientException $ex) {
$this->response = $ex->getResponse();
}
@@ -123,7 +103,7 @@ trait Sharing {
public function acceptingLastShare() {
$share_id = $this->lastShareData->data[0]->id;
$url = "/apps/files_sharing/api/v{$this->sharingApiVersion}/shares/pending/$share_id";
- $this->sendingToWith("POST", $url, null);
+ $this->sendingToWith('POST', $url, null);
$this->theHTTPStatusCodeShouldBe('200');
}
@@ -143,7 +123,7 @@ trait Sharing {
$share_id = $this->lastShareData->data[0]->id;
$url = "/apps/files_sharing/api/v{$this->sharingApiVersion}/shares/pending/$share_id";
- $this->sendingToWith("POST", $url, null);
+ $this->sendingToWith('POST', $url, null);
$this->currentUser = $previousUser;
@@ -159,7 +139,7 @@ trait Sharing {
} else {
$url = $this->lastShareData->data->url;
}
- $fullUrl = $url . "/download";
+ $fullUrl = $url . '/download';
$this->checkDownload($fullUrl, null, 'text/plain');
}
@@ -173,7 +153,7 @@ trait Sharing {
$token = $this->lastShareData->data->token;
}
- $fullUrl = substr($this->baseUrl, 0, -4) . "index.php/s/" . $token . "/download";
+ $fullUrl = substr($this->baseUrl, 0, -4) . 'index.php/s/' . $token . '/download';
$this->checkDownload($fullUrl, null, 'text/plain');
}
@@ -187,8 +167,8 @@ trait Sharing {
$token = $this->lastShareData->data->token;
}
- $fullUrl = substr($this->baseUrl, 0, -4) . "public.php/webdav";
- $this->checkDownload($fullUrl, [$token, $password], 'text/plain');
+ $fullUrl = substr($this->baseUrl, 0, -4) . "public.php/dav/files/$token/";
+ $this->checkDownload($fullUrl, ['', $password], 'text/plain');
}
private function checkDownload($url, $auth = null, $mimeType = null) {
@@ -219,7 +199,7 @@ trait Sharing {
* @When /^Adding expiration date to last share$/
*/
public function addingExpirationDate() {
- $share_id = (string) $this->lastShareData->data[0]->id;
+ $share_id = (string)$this->lastShareData->data[0]->id;
$fullUrl = $this->baseUrl . "v{$this->apiVersion}.php/apps/files_sharing/api/v{$this->sharingApiVersion}/shares/$share_id";
$client = new Client();
$options = [];
@@ -228,9 +208,9 @@ trait Sharing {
} else {
$options['auth'] = [$this->currentUser, $this->regularUser];
}
- $date = date('Y-m-d', strtotime("+3 days"));
+ $date = date('Y-m-d', strtotime('+3 days'));
$options['form_params'] = ['expireDate' => $date];
- $this->response = $this->response = $client->request("PUT", $fullUrl, $options);
+ $this->response = $this->response = $client->request('PUT', $fullUrl, $options);
Assert::assertEquals(200, $this->response->getStatusCode());
}
@@ -239,7 +219,7 @@ trait Sharing {
* @param TableNode|null $body
*/
public function updatingLastShare($body) {
- $share_id = (string) $this->lastShareData->data[0]->id;
+ $share_id = (string)$this->lastShareData->data[0]->id;
$fullUrl = $this->baseUrl . "v{$this->apiVersion}.php/apps/files_sharing/api/v{$this->sharingApiVersion}/shares/$share_id";
$client = new Client();
$options = [
@@ -263,20 +243,20 @@ trait Sharing {
}
try {
- $this->response = $client->request("PUT", $fullUrl, $options);
+ $this->response = $client->request('PUT', $fullUrl, $options);
} catch (\GuzzleHttp\Exception\ClientException $ex) {
$this->response = $ex->getResponse();
}
}
public function createShare($user,
- $path = null,
- $shareType = null,
- $shareWith = null,
- $publicUpload = null,
- $password = null,
- $permissions = null,
- $viewOnly = false) {
+ $path = null,
+ $shareType = null,
+ $shareWith = null,
+ $publicUpload = null,
+ $password = null,
+ $permissions = null,
+ $viewOnly = false) {
$fullUrl = $this->baseUrl . "v{$this->apiVersion}.php/apps/files_sharing/api/v{$this->sharingApiVersion}/shares";
$client = new Client();
$options = [
@@ -311,13 +291,13 @@ trait Sharing {
}
if ($viewOnly === true) {
- $body['attributes'] = json_encode([['scope' => 'permissions', 'key' => 'download', 'enabled' => false]]);
+ $body['attributes'] = json_encode([['scope' => 'permissions', 'key' => 'download', 'value' => false]]);
}
$options['form_params'] = $body;
try {
- $this->response = $client->request("POST", $fullUrl, $options);
+ $this->response = $client->request('POST', $fullUrl, $options);
$this->lastShareData = simplexml_load_string($this->response->getBody());
} catch (\GuzzleHttp\Exception\ClientException $ex) {
$this->response = $ex->getResponse();
@@ -328,16 +308,18 @@ trait Sharing {
public function isFieldInResponse($field, $contentExpected) {
$data = simplexml_load_string($this->response->getBody())->data[0];
if ((string)$field == 'expiration') {
- $contentExpected = date('Y-m-d', strtotime($contentExpected)) . " 00:00:00";
+ if (!empty($contentExpected)) {
+ $contentExpected = date('Y-m-d', strtotime($contentExpected)) . ' 00:00:00';
+ }
}
if (count($data->element) > 0) {
foreach ($data as $element) {
- if ($contentExpected == "A_TOKEN") {
+ if ($contentExpected == 'A_TOKEN') {
return (strlen((string)$element->$field) == 15);
- } elseif ($contentExpected == "A_NUMBER") {
+ } elseif ($contentExpected == 'A_NUMBER') {
return is_numeric((string)$element->$field);
- } elseif ($contentExpected == "AN_URL") {
- return $this->isExpectedUrl((string)$element->$field, "index.php/s/");
+ } elseif ($contentExpected == 'AN_URL') {
+ return $this->isExpectedUrl((string)$element->$field, 'index.php/s/');
} elseif ((string)$element->$field == $contentExpected) {
return true;
} else {
@@ -347,14 +329,16 @@ trait Sharing {
return false;
} else {
- if ($contentExpected == "A_TOKEN") {
+ if ($contentExpected == 'A_TOKEN') {
return (strlen((string)$data->$field) == 15);
- } elseif ($contentExpected == "A_NUMBER") {
+ } elseif ($contentExpected == 'A_NUMBER') {
return is_numeric((string)$data->$field);
- } elseif ($contentExpected == "AN_URL") {
- return $this->isExpectedUrl((string)$data->$field, "index.php/s/");
- } elseif ($data->$field == $contentExpected) {
+ } elseif ($contentExpected == 'AN_URL') {
+ return $this->isExpectedUrl((string)$data->$field, 'index.php/s/');
+ } elseif ($contentExpected == $data->$field) {
return true;
+ } else {
+ print($data->$field);
}
return false;
}
@@ -478,7 +462,7 @@ trait Sharing {
public function deletingLastShare() {
$share_id = $this->lastShareData->data[0]->id;
$url = "/apps/files_sharing/api/v{$this->sharingApiVersion}/shares/$share_id";
- $this->sendingToWith("DELETE", $url, null);
+ $this->sendingToWith('DELETE', $url, null);
}
/**
@@ -487,7 +471,7 @@ trait Sharing {
public function gettingInfoOfLastShare() {
$share_id = $this->lastShareData->data[0]->id;
$url = "/apps/files_sharing/api/v{$this->sharingApiVersion}/shares/$share_id";
- $this->sendingToWith("GET", $url, null);
+ $this->sendingToWith('GET', $url, null);
}
/**
@@ -519,13 +503,13 @@ trait Sharing {
$fd = $body->getRowsHash();
foreach ($fd as $field => $value) {
- if (substr($field, 0, 10) === "share_with") {
- $value = str_replace("REMOTE", substr($this->remoteBaseUrl, 0, -5), $value);
- $value = str_replace("LOCAL", substr($this->localBaseUrl, 0, -5), $value);
+ if (substr($field, 0, 10) === 'share_with') {
+ $value = str_replace('REMOTE', substr($this->remoteBaseUrl, 0, -5), $value);
+ $value = str_replace('LOCAL', substr($this->localBaseUrl, 0, -5), $value);
}
- if (substr($field, 0, 6) === "remote") {
- $value = str_replace("REMOTE", substr($this->remoteBaseUrl, 0, -4), $value);
- $value = str_replace("LOCAL", substr($this->localBaseUrl, 0, -4), $value);
+ if (substr($field, 0, 6) === 'remote') {
+ $value = str_replace('REMOTE', substr($this->remoteBaseUrl, 0, -4), $value);
+ $value = str_replace('LOCAL', substr($this->localBaseUrl, 0, -4), $value);
}
if (!$this->isFieldInResponse($field, $value)) {
Assert::fail("$field" . " doesn't have value " . "$value");
@@ -580,18 +564,18 @@ trait Sharing {
];
$expectedFields = array_merge($defaultExpectedFields, $body->getRowsHash());
- if (!array_key_exists('uid_file_owner', $expectedFields) &&
- array_key_exists('uid_owner', $expectedFields)) {
+ if (!array_key_exists('uid_file_owner', $expectedFields)
+ && array_key_exists('uid_owner', $expectedFields)) {
$expectedFields['uid_file_owner'] = $expectedFields['uid_owner'];
}
- if (!array_key_exists('displayname_file_owner', $expectedFields) &&
- array_key_exists('displayname_owner', $expectedFields)) {
+ if (!array_key_exists('displayname_file_owner', $expectedFields)
+ && array_key_exists('displayname_owner', $expectedFields)) {
$expectedFields['displayname_file_owner'] = $expectedFields['displayname_owner'];
}
- if (array_key_exists('share_type', $expectedFields) &&
- $expectedFields['share_type'] == 10 /* IShare::TYPE_ROOM */ &&
- array_key_exists('share_with', $expectedFields)) {
+ if (array_key_exists('share_type', $expectedFields)
+ && $expectedFields['share_type'] == 10 /* IShare::TYPE_ROOM */
+ && array_key_exists('share_with', $expectedFields)) {
if ($expectedFields['share_with'] === 'private_conversation') {
$expectedFields['share_with'] = 'REGEXP /^private_conversation_[0-9a-f]{6}$/';
} else {
@@ -626,7 +610,7 @@ trait Sharing {
}
if ($field === 'expiration' && !empty($contentExpected)) {
- $contentExpected = date('Y-m-d', strtotime($contentExpected)) . " 00:00:00";
+ $contentExpected = date('Y-m-d', strtotime($contentExpected)) . ' 00:00:00';
}
if ($contentExpected === 'A_NUMBER') {
diff --git a/build/integration/features/bootstrap/SharingContext.php b/build/integration/features/bootstrap/SharingContext.php
index f187e89f08f..a9dd99108a9 100644
--- a/build/integration/features/bootstrap/SharingContext.php
+++ b/build/integration/features/bootstrap/SharingContext.php
@@ -1,26 +1,8 @@
<?php
+
/**
- * @copyright Copyright (c) 2016 John Molakvoæ <skjnldsv@protonmail.com>
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Daniel Calviño Sánchez <danxuliu@gmail.com>
- * @author John Molakvoæ <skjnldsv@protonmail.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * 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
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
use Behat\Behat\Context\Context;
use Behat\Behat\Context\SnippetAcceptingContext;
@@ -36,6 +18,7 @@ class SharingContext implements Context, SnippetAcceptingContext {
use Trashbin;
use AppConfiguration;
use CommandLine;
+ use Activity;
protected function resetAppConfigs() {
$this->deleteServerConfig('core', 'shareapi_default_permissions');
@@ -46,6 +29,9 @@ class SharingContext implements Context, SnippetAcceptingContext {
$this->deleteServerConfig('core', 'shareapi_default_expire_date');
$this->deleteServerConfig('core', 'shareapi_expire_after_n_days');
$this->deleteServerConfig('core', 'link_defaultExpDays');
+ $this->deleteServerConfig('core', 'shareapi_allow_federation_on_public_shares');
+ $this->deleteServerConfig('files_sharing', 'outgoing_server2server_share_enabled');
+ $this->deleteServerConfig('core', 'shareapi_allow_view_without_download');
$this->runOcc(['config:system:delete', 'share_folder']);
}
diff --git a/build/integration/features/bootstrap/TagsContext.php b/build/integration/features/bootstrap/TagsContext.php
index ecef9c08b1e..c64626de68d 100644
--- a/build/integration/features/bootstrap/TagsContext.php
+++ b/build/integration/features/bootstrap/TagsContext.php
@@ -1,30 +1,9 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Lukas Reschke <lukas@statuscode.ch>
- * @author Phil Davis <phil.davis@inf.org>
- * @author Robin Appelman <robin@icewind.nl>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- * @author Sergio Bertolin <sbertolin@solidgear.es>
- * @author Vincent Petry <vincent@nextcloud.com>
- *
- * @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/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
require __DIR__ . '/../../vendor/autoload.php';
@@ -267,7 +246,7 @@ class TagsContext implements \Behat\Behat\Context\Context {
if (count($table->getRows()) !== count($tags)) {
throw new \Exception(
sprintf(
- "Expected %s tags, got %s.",
+ 'Expected %s tags, got %s.',
count($table->getRows()),
count($tags)
)
@@ -277,9 +256,9 @@ class TagsContext implements \Behat\Behat\Context\Context {
foreach ($table->getRowsHash() as $rowDisplayName => $row) {
foreach ($tags as $key => $tag) {
if (
- $tag['display-name'] === $rowDisplayName &&
- $tag['user-visible'] === $row[0] &&
- $tag['user-assignable'] === $row[1]
+ $tag['display-name'] === $rowDisplayName
+ && $tag['user-visible'] === $row[0]
+ && $tag['user-assignable'] === $row[1]
) {
unset($tags[$key]);
}
diff --git a/build/integration/features/bootstrap/TalkContext.php b/build/integration/features/bootstrap/TalkContext.php
index 5417c22a058..6f351c30ccf 100644
--- a/build/integration/features/bootstrap/TalkContext.php
+++ b/build/integration/features/bootstrap/TalkContext.php
@@ -1,24 +1,8 @@
<?php
+
/**
- * @copyright Copyright (c) 2020, Daniel Calviño Sánchez (danxuliu@gmail.com)
- *
- * @author Daniel Calviño Sánchez <danxuliu@gmail.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * 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
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
use Behat\Behat\Context\Context;
diff --git a/build/integration/features/bootstrap/Theming.php b/build/integration/features/bootstrap/Theming.php
new file mode 100644
index 00000000000..f44a6533a1b
--- /dev/null
+++ b/build/integration/features/bootstrap/Theming.php
@@ -0,0 +1,49 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+require __DIR__ . '/../../vendor/autoload.php';
+
+trait Theming {
+
+ private bool $undoAllThemingChangesAfterScenario = false;
+
+ /**
+ * @AfterScenario
+ */
+ public function undoAllThemingChanges() {
+ if (!$this->undoAllThemingChangesAfterScenario) {
+ return;
+ }
+
+ $this->loggingInUsingWebAs('admin');
+ $this->sendingAToWithRequesttoken('POST', '/index.php/apps/theming/ajax/undoAllChanges');
+
+ $this->undoAllThemingChangesAfterScenario = false;
+ }
+
+ /**
+ * @When logged in admin uploads theming image for :key from file :source
+ *
+ * @param string $key
+ * @param string $source
+ */
+ public function loggedInAdminUploadsThemingImageForFromFile(string $key, string $source) {
+ $this->undoAllThemingChangesAfterScenario = true;
+
+ $file = \GuzzleHttp\Psr7\Utils::streamFor(fopen($source, 'r'));
+
+ $this->sendingAToWithRequesttoken('POST', '/index.php/apps/theming/ajax/uploadImage?key=' . $key,
+ [
+ 'multipart' => [
+ [
+ 'name' => 'image',
+ 'contents' => $file
+ ]
+ ]
+ ]);
+ $this->theHTTPStatusCodeShouldBe('200');
+ }
+}
diff --git a/build/integration/features/bootstrap/Trashbin.php b/build/integration/features/bootstrap/Trashbin.php
index 19e9b57c3fb..dfcc23289a7 100644
--- a/build/integration/features/bootstrap/Trashbin.php
+++ b/build/integration/features/bootstrap/Trashbin.php
@@ -1,26 +1,9 @@
<?php
+
/**
- * @copyright Copyright (c) 2017, ownCloud GmbH.
- *
- * @author Daniel Calviño Sánchez <danxuliu@gmail.com>
- * @author John Molakvoæ <skjnldsv@protonmail.com>
- * @author Robin Appelman <robin@icewind.nl>
- * @author Vincent Petry <vincent@nextcloud.com>
- *
- * @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/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2017 ownCloud GmbH
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
use DMS\PHPUnitExtensions\ArraySubset\Assert as AssertArraySubset;
use PHPUnit\Framework\Assert;
@@ -115,7 +98,7 @@ trait Trashbin {
foreach ($elementsSimplified as $expectedElement) {
$expectedElement = ltrim($expectedElement, '/');
if (array_search($expectedElement, $trashContent) === false) {
- Assert::fail("$expectedElement" . " is not in trash listing");
+ Assert::fail("$expectedElement" . ' is not in trash listing');
}
}
}
diff --git a/build/integration/features/bootstrap/WebDav.php b/build/integration/features/bootstrap/WebDav.php
index 680db01a260..2cb37002ac0 100644
--- a/build/integration/features/bootstrap/WebDav.php
+++ b/build/integration/features/bootstrap/WebDav.php
@@ -1,39 +1,14 @@
<?php
+
/**
- * @copyright Copyright (c) 2016 Sergio Bertolin <sbertolin@solidgear.es>
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author David Toledo <dtoledo@solidgear.es>
- * @author Joas Schilling <coding@schilljs.com>
- * @author John Molakvoæ <skjnldsv@protonmail.com>
- * @author Lukas Reschke <lukas@statuscode.ch>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Robin Appelman <robin@icewind.nl>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- * @author Sergio Bertolin <sbertolin@solidgear.es>
- * @author Sergio Bertolín <sbertolin@solidgear.es>
- * @author Thomas Müller <thomas.mueller@tmit.eu>
- * @author Vincent Petry <vincent@nextcloud.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * 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
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
+
use GuzzleHttp\Client as GClient;
-use GuzzleHttp\Message\ResponseInterface;
use PHPUnit\Framework\Assert;
+use Psr\Http\Message\ResponseInterface;
use Sabre\DAV\Client as SClient;
use Sabre\DAV\Xml\Property\ResourceType;
@@ -43,16 +18,17 @@ require __DIR__ . '/../../vendor/autoload.php';
trait WebDav {
use Sharing;
- /** @var string */
- private $davPath = "remote.php/webdav";
- /** @var boolean */
- private $usingOldDavPath = true;
+ private string $davPath = 'remote.php/webdav';
+ private bool $usingOldDavPath = true;
+ private ?array $storedETAG = null; // map with user as key and another map as value, which has path as key and etag as value
+ private ?int $storedFileID = null;
/** @var ResponseInterface */
private $response;
- /** @var array map with user as key and another map as value, which has path as key and etag as value */
- private $storedETAG = null;
- /** @var int */
- private $storedFileID = null;
+ private array $parsedResponse = [];
+ private string $s3MultipartDestination;
+ private string $uploadId;
+ /** @var string[] */
+ private array $parts = [];
/**
* @Given /^using dav path "([^"]*)"$/
@@ -65,7 +41,7 @@ trait WebDav {
* @Given /^using old dav path$/
*/
public function usingOldDavPath() {
- $this->davPath = "remote.php/webdav";
+ $this->davPath = 'remote.php/webdav';
$this->usingOldDavPath = true;
}
@@ -73,7 +49,15 @@ trait WebDav {
* @Given /^using new dav path$/
*/
public function usingNewDavPath() {
- $this->davPath = "remote.php/dav";
+ $this->davPath = 'remote.php/dav';
+ $this->usingOldDavPath = false;
+ }
+
+ /**
+ * @Given /^using new public dav path$/
+ */
+ public function usingNewPublicDavPath() {
+ $this->davPath = 'public.php/dav';
$this->usingOldDavPath = false;
}
@@ -85,13 +69,13 @@ trait WebDav {
}
}
- public function makeDavRequest($user, $method, $path, $headers, $body = null, $type = "files") {
- if ($type === "files") {
+ public function makeDavRequest($user, $method, $path, $headers, $body = null, $type = 'files') {
+ if ($type === 'files') {
$fullUrl = substr($this->baseUrl, 0, -4) . $this->getDavFilesPath($user) . "$path";
- } elseif ($type === "uploads") {
+ } elseif ($type === 'uploads') {
$fullUrl = substr($this->baseUrl, 0, -4) . $this->davPath . "$path";
} else {
- $fullUrl = substr($this->baseUrl, 0, -4) . $this->davPath . '/' . $type . "$path";
+ $fullUrl = substr($this->baseUrl, 0, -4) . $this->davPath . '/' . $type . "$path";
}
$client = new GClient();
$options = [
@@ -100,7 +84,7 @@ trait WebDav {
];
if ($user === 'admin') {
$options['auth'] = $this->adminUser;
- } else {
+ } elseif ($user !== '') {
$options['auth'] = [$user, $this->regularUser];
}
return $client->request($method, $fullUrl, $options);
@@ -115,7 +99,7 @@ trait WebDav {
public function userMovedFile($user, $entry, $fileSource, $fileDestination) {
$fullUrl = substr($this->baseUrl, 0, -4) . $this->getDavFilesPath($user);
$headers['Destination'] = $fullUrl . $fileDestination;
- $this->response = $this->makeDavRequest($user, "MOVE", $fileSource, $headers);
+ $this->response = $this->makeDavRequest($user, 'MOVE', $fileSource, $headers);
Assert::assertEquals(201, $this->response->getStatusCode());
}
@@ -129,7 +113,7 @@ trait WebDav {
$fullUrl = substr($this->baseUrl, 0, -4) . $this->getDavFilesPath($user);
$headers['Destination'] = $fullUrl . $fileDestination;
try {
- $this->response = $this->makeDavRequest($user, "MOVE", $fileSource, $headers);
+ $this->response = $this->makeDavRequest($user, 'MOVE', $fileSource, $headers);
} catch (\GuzzleHttp\Exception\ClientException $e) {
$this->response = $e->getResponse();
}
@@ -159,7 +143,7 @@ trait WebDav {
*/
public function downloadFileWithRange($fileSource, $range) {
$headers['Range'] = $range;
- $this->response = $this->makeDavRequest($this->currentUser, "GET", $fileSource, $headers);
+ $this->response = $this->makeDavRequest($this->currentUser, 'GET', $fileSource, $headers);
}
/**
@@ -168,16 +152,15 @@ trait WebDav {
*/
public function downloadPublicFileWithRange($range) {
$token = $this->lastShareData->data->token;
- $fullUrl = substr($this->baseUrl, 0, -4) . "public.php/webdav";
+ $fullUrl = substr($this->baseUrl, 0, -4) . "public.php/dav/files/$token";
$client = new GClient();
$options = [];
- $options['auth'] = [$token, ""];
$options['headers'] = [
'Range' => $range
];
- $this->response = $client->request("GET", $fullUrl, $options);
+ $this->response = $client->request('GET', $fullUrl, $options);
}
/**
@@ -186,7 +169,7 @@ trait WebDav {
*/
public function downloadPublicFileInsideAFolderWithRange($path, $range) {
$token = $this->lastShareData->data->token;
- $fullUrl = substr($this->baseUrl, 0, -4) . "public.php/webdav" . "$path";
+ $fullUrl = substr($this->baseUrl, 0, -4) . "public.php/dav/files/$token/$path";
$client = new GClient();
$options = [
@@ -194,9 +177,8 @@ trait WebDav {
'Range' => $range
]
];
- $options['auth'] = [$token, ""];
- $this->response = $client->request("GET", $fullUrl, $options);
+ $this->response = $client->request('GET', $fullUrl, $options);
}
/**
@@ -216,7 +198,7 @@ trait WebDav {
*/
public function checkPropForFile($file, $prefix, $prop, $value) {
$elementList = $this->propfindFile($this->currentUser, $file, "<$prefix:$prop/>");
- $property = $elementList['/'.$this->getDavFilesPath($this->currentUser).$file][200]["{DAV:}$prop"];
+ $property = $elementList['/' . $this->getDavFilesPath($this->currentUser) . $file][200]["{DAV:}$prop"];
Assert::assertEquals($property, $value);
}
@@ -229,6 +211,24 @@ trait WebDav {
}
/**
+ * @Then /^Favorite search should work$/
+ */
+ public function searchFavorite(): void {
+ $this->searchFile(
+ $this->currentUser,
+ '<oc:favorite/>',
+ null,
+ '<d:eq>
+ <d:prop>
+ <oc:favorite/>
+ </d:prop>
+ <d:literal>yes</d:literal>
+ </d:eq>'
+ );
+ Assert::assertEquals(207, $this->response->getStatusCode());
+ }
+
+ /**
* @Then /^Downloaded content when downloading file "([^"]*)" with range "([^"]*)" should be "([^"]*)"$/
* @param string $fileSource
* @param string $range
@@ -240,6 +240,37 @@ trait WebDav {
}
/**
+ * @When Downloading folder :folderName
+ */
+ public function downloadingFolder(string $folderName) {
+ try {
+ $this->response = $this->makeDavRequest($this->currentUser, 'GET', $folderName, ['Accept' => 'application/zip']);
+ } catch (\GuzzleHttp\Exception\ClientException $e) {
+ $this->response = $e->getResponse();
+ }
+ }
+
+ /**
+ * @When Downloading public folder :folderName
+ */
+ public function downloadPublicFolder(string $folderName) {
+ $token = $this->lastShareData->data->token;
+ $fullUrl = substr($this->baseUrl, 0, -4) . "public.php/dav/files/$token/$folderName";
+
+ $client = new GClient();
+ $options = [];
+ $options['headers'] = [
+ 'Accept' => 'application/zip'
+ ];
+
+ try {
+ $this->response = $client->request('GET', $fullUrl, $options);
+ } catch (\GuzzleHttp\Exception\ClientException $e) {
+ $this->response = $e->getResponse();
+ }
+ }
+
+ /**
* @When Downloading file :fileName
* @param string $fileName
*/
@@ -252,6 +283,42 @@ trait WebDav {
}
/**
+ * @When Downloading public file :filename
+ */
+ public function downloadingPublicFile(string $filename) {
+ $token = $this->lastShareData->data->token;
+ $fullUrl = substr($this->baseUrl, 0, -4) . "public.php/dav/files/$token/$filename";
+
+ $client = new GClient();
+ $options = [
+ 'headers' => [
+ 'X-Requested-With' => 'XMLHttpRequest',
+ ]
+ ];
+
+ try {
+ $this->response = $client->request('GET', $fullUrl, $options);
+ } catch (\GuzzleHttp\Exception\ClientException $e) {
+ $this->response = $e->getResponse();
+ }
+ }
+
+ /**
+ * @When Downloading public file :filename without ajax header
+ */
+ public function downloadingPublicFileWithoutHeader(string $filename) {
+ $token = $this->lastShareData->data->token;
+ $fullUrl = substr($this->baseUrl, 0, -4) . "public.php/dav/files/$token/$filename";
+
+ $client = new GClient();
+ try {
+ $this->response = $client->request('GET', $fullUrl);
+ } catch (\GuzzleHttp\Exception\ClientException $e) {
+ $this->response = $e->getResponse();
+ }
+ }
+
+ /**
* @Then Downloaded content should start with :start
* @param int $start
* @throws \Exception
@@ -313,18 +380,31 @@ trait WebDav {
}
/**
+ * @Then the response should be empty
+ * @throws \Exception
+ */
+ public function theResponseShouldBeEmpty(): void {
+ $response = ($this->response instanceof ResponseInterface) ? $this->convertResponseToDavEntries() : $this->response;
+ if ($response === []) {
+ return;
+ }
+
+ throw new \Exception('response is not empty');
+ }
+
+ /**
* @Then the single response should contain a property :key with value :value
* @param string $key
* @param string $expectedValue
* @throws \Exception
*/
public function theSingleResponseShouldContainAPropertyWithValue($key, $expectedValue) {
- $keys = $this->response;
- if (!array_key_exists($key, $keys)) {
+ $response = ($this->response instanceof ResponseInterface) ? $this->convertResponseToDavSingleEntry() : $this->response;
+ if (!array_key_exists($key, $response)) {
throw new \Exception("Cannot find property \"$key\" with \"$expectedValue\"");
}
- $value = $keys[$key];
+ $value = $response[$key];
if ($value instanceof ResourceType) {
$value = $value->getValue();
if (empty($value)) {
@@ -344,7 +424,7 @@ trait WebDav {
public function theResponseShouldContainAShareTypesPropertyWith($table) {
$keys = $this->response;
if (!array_key_exists('{http://owncloud.org/ns}share-types', $keys)) {
- throw new \Exception("Cannot find property \"{http://owncloud.org/ns}share-types\"");
+ throw new \Exception('Cannot find property "{http://owncloud.org/ns}share-types"');
}
$foundTypes = [];
@@ -445,28 +525,28 @@ trait WebDav {
</d:prop>
<d:literal>image/png</d:literal>
</d:eq>
-
+
<d:eq>
<d:prop>
<d:getcontenttype/>
</d:prop>
<d:literal>image/jpeg</d:literal>
</d:eq>
-
+
<d:eq>
<d:prop>
<d:getcontenttype/>
</d:prop>
<d:literal>image/heic</d:literal>
</d:eq>
-
+
<d:eq>
<d:prop>
<d:getcontenttype/>
</d:prop>
<d:literal>video/mp4</d:literal>
</d:eq>
-
+
<d:eq>
<d:prop>
<d:getcontenttype/>
@@ -509,9 +589,10 @@ trait WebDav {
</d:searchrequest>';
try {
- $this->response = $this->makeDavRequest($user, "SEARCH", '', [
+ $this->response = $this->makeDavRequest($user, 'SEARCH', '', [
'Content-Type' => 'text/xml'
], $body, '');
+
var_dump((string)$this->response->getBody());
} catch (\GuzzleHttp\Exception\ServerException $e) {
// 5xx responses cause a server exception
@@ -550,7 +631,7 @@ trait WebDav {
if ($type === 'files') {
return $this->encodePath($this->getDavFilesPath($user) . $path);
} else {
- return $this->encodePath($this->davPath . '/' . $type . '/' . $user . '/' . $path);
+ return $this->encodePath($this->davPath . '/' . $type . '/' . $user . '/' . $path);
}
}
@@ -583,9 +664,9 @@ trait WebDav {
$elementRows = $expectedElements->getRows();
$elementsSimplified = $this->simplifyArray($elementRows);
foreach ($elementsSimplified as $expectedElement) {
- $webdavPath = "/" . $this->getDavFilesPath($user) . $expectedElement;
+ $webdavPath = '/' . $this->getDavFilesPath($user) . $expectedElement;
if (!array_key_exists($webdavPath, $elementList)) {
- Assert::fail("$webdavPath" . " is not in propfind answer");
+ Assert::fail("$webdavPath" . ' is not in propfind answer');
}
}
}
@@ -600,7 +681,7 @@ trait WebDav {
public function userUploadsAFileTo($user, $source, $destination) {
$file = \GuzzleHttp\Psr7\Utils::streamFor(fopen($source, 'r'));
try {
- $this->response = $this->makeDavRequest($user, "PUT", $destination, [], $file);
+ $this->response = $this->makeDavRequest($user, 'PUT', $destination, [], $file);
} catch (\GuzzleHttp\Exception\ServerException $e) {
// 5xx responses cause a server exception
$this->response = $e->getResponse();
@@ -617,11 +698,11 @@ trait WebDav {
* @param string $destination
*/
public function userAddsAFileTo($user, $bytes, $destination) {
- $filename = "filespecificSize.txt";
+ $filename = 'filespecificSize.txt';
$this->createFileSpecificSize($filename, $bytes);
Assert::assertEquals(1, file_exists("work/$filename"));
$this->userUploadsAFileTo($user, "work/$filename", $destination);
- $this->removeFile("work/", $filename);
+ $this->removeFile('work/', $filename);
$expectedElements = new \Behat\Gherkin\Node\TableNode([["$destination"]]);
$this->checkElementList($user, $expectedElements);
}
@@ -632,7 +713,7 @@ trait WebDav {
public function userUploadsAFileWithContentTo($user, $content, $destination) {
$file = \GuzzleHttp\Psr7\Utils::streamFor($content);
try {
- $this->response = $this->makeDavRequest($user, "PUT", $destination, [], $file);
+ $this->response = $this->makeDavRequest($user, 'PUT', $destination, [], $file);
} catch (\GuzzleHttp\Exception\ServerException $e) {
// 5xx responses cause a server exception
$this->response = $e->getResponse();
@@ -668,7 +749,7 @@ trait WebDav {
public function userCreatedAFolder($user, $destination) {
try {
$destination = '/' . ltrim($destination, '/');
- $this->response = $this->makeDavRequest($user, "MKCOL", $destination, []);
+ $this->response = $this->makeDavRequest($user, 'MKCOL', $destination, []);
} catch (\GuzzleHttp\Exception\ServerException $e) {
// 5xx responses cause a server exception
$this->response = $e->getResponse();
@@ -679,21 +760,6 @@ trait WebDav {
}
/**
- * @Given user :user uploads chunk file :num of :total with :data to :destination
- * @param string $user
- * @param int $num
- * @param int $total
- * @param string $data
- * @param string $destination
- */
- public function userUploadsChunkFileOfWithToWithChecksum($user, $num, $total, $data, $destination) {
- $num -= 1;
- $data = \GuzzleHttp\Psr7\Utils::streamFor($data);
- $file = $destination . '-chunking-42-' . $total . '-' . $num;
- $this->makeDavRequest($user, 'PUT', $file, ['OC-Chunked' => '1'], $data, "uploads");
- }
-
- /**
* @Given user :user uploads bulked files :name1 with :content1 and :name2 with :content2 and :name3 with :content3
* @param string $user
* @param string $name1
@@ -704,31 +770,31 @@ trait WebDav {
* @param string $content3
*/
public function userUploadsBulkedFiles($user, $name1, $content1, $name2, $content2, $name3, $content3) {
- $boundary = "boundary_azertyuiop";
+ $boundary = 'boundary_azertyuiop';
- $body = "";
- $body .= '--'.$boundary."\r\n";
- $body .= "X-File-Path: ".$name1."\r\n";
+ $body = '';
+ $body .= '--' . $boundary . "\r\n";
+ $body .= 'X-File-Path: ' . $name1 . "\r\n";
$body .= "X-File-MD5: f6a6263167c92de8644ac998b3c4e4d1\r\n";
$body .= "X-OC-Mtime: 1111111111\r\n";
- $body .= "Content-Length: ".strlen($content1)."\r\n";
+ $body .= 'Content-Length: ' . strlen($content1) . "\r\n";
$body .= "\r\n";
- $body .= $content1."\r\n";
- $body .= '--'.$boundary."\r\n";
- $body .= "X-File-Path: ".$name2."\r\n";
+ $body .= $content1 . "\r\n";
+ $body .= '--' . $boundary . "\r\n";
+ $body .= 'X-File-Path: ' . $name2 . "\r\n";
$body .= "X-File-MD5: 87c7d4068be07d390a1fffd21bf1e944\r\n";
$body .= "X-OC-Mtime: 2222222222\r\n";
- $body .= "Content-Length: ".strlen($content2)."\r\n";
+ $body .= 'Content-Length: ' . strlen($content2) . "\r\n";
$body .= "\r\n";
- $body .= $content2."\r\n";
- $body .= '--'.$boundary."\r\n";
- $body .= "X-File-Path: ".$name3."\r\n";
+ $body .= $content2 . "\r\n";
+ $body .= '--' . $boundary . "\r\n";
+ $body .= 'X-File-Path: ' . $name3 . "\r\n";
$body .= "X-File-MD5: e86a1cf0678099986a901c79086f5617\r\n";
$body .= "X-File-Mtime: 3333333333\r\n";
- $body .= "Content-Length: ".strlen($content3)."\r\n";
+ $body .= 'Content-Length: ' . strlen($content3) . "\r\n";
$body .= "\r\n";
- $body .= $content3."\r\n";
- $body .= '--'.$boundary."--\r\n";
+ $body .= $content3 . "\r\n";
+ $body .= '--' . $boundary . "--\r\n";
$stream = fopen('php://temp', 'r+');
fwrite($stream, $body);
@@ -738,21 +804,22 @@ trait WebDav {
$options = [
'auth' => [$user, $this->regularUser],
'headers' => [
- 'Content-Type' => 'multipart/related; boundary='.$boundary,
+ 'Content-Type' => 'multipart/related; boundary=' . $boundary,
'Content-Length' => (string)strlen($body),
],
'body' => $body
];
- return $client->request("POST", substr($this->baseUrl, 0, -4) . "remote.php/dav/bulk", $options);
+ return $client->request('POST', substr($this->baseUrl, 0, -4) . 'remote.php/dav/bulk', $options);
}
/**
* @Given user :user creates a new chunking upload with id :id
*/
public function userCreatesANewChunkingUploadWithId($user, $id) {
+ $this->parts = [];
$destination = '/uploads/' . $user . '/' . $id;
- $this->makeDavRequest($user, 'MKCOL', $destination, [], null, "uploads");
+ $this->makeDavRequest($user, 'MKCOL', $destination, [], null, 'uploads');
}
/**
@@ -761,7 +828,7 @@ trait WebDav {
public function userUploadsNewChunkFileOfWithToId($user, $num, $data, $id) {
$data = \GuzzleHttp\Psr7\Utils::streamFor($data);
$destination = '/uploads/' . $user . '/' . $id . '/' . $num;
- $this->makeDavRequest($user, 'PUT', $destination, [], $data, "uploads");
+ $this->makeDavRequest($user, 'PUT', $destination, [], $data, 'uploads');
}
/**
@@ -772,7 +839,7 @@ trait WebDav {
$destination = substr($this->baseUrl, 0, -4) . $this->getDavFilesPath($user) . $dest;
$this->makeDavRequest($user, 'MOVE', $source, [
'Destination' => $destination
- ], null, "uploads");
+ ], null, 'uploads');
}
/**
@@ -786,12 +853,66 @@ trait WebDav {
$this->response = $this->makeDavRequest($user, 'MOVE', $source, [
'Destination' => $destination,
'OC-Total-Length' => $size
- ], null, "uploads");
+ ], null, 'uploads');
} catch (\GuzzleHttp\Exception\BadResponseException $ex) {
$this->response = $ex->getResponse();
}
}
+
+ /**
+ * @Given user :user creates a new chunking v2 upload with id :id and destination :targetDestination
+ */
+ public function userCreatesANewChunkingv2UploadWithIdAndDestination($user, $id, $targetDestination) {
+ $this->s3MultipartDestination = $this->getTargetDestination($user, $targetDestination);
+ $this->newUploadId();
+ $destination = '/uploads/' . $user . '/' . $this->getUploadId($id);
+ $this->response = $this->makeDavRequest($user, 'MKCOL', $destination, [
+ 'Destination' => $this->s3MultipartDestination,
+ ], null, 'uploads');
+ }
+
+ /**
+ * @Given user :user uploads new chunk v2 file :num to id :id
+ */
+ public function userUploadsNewChunkv2FileToIdAndDestination($user, $num, $id) {
+ $data = \GuzzleHttp\Psr7\Utils::streamFor(fopen('/tmp/part-upload-' . $num, 'r'));
+ $destination = '/uploads/' . $user . '/' . $this->getUploadId($id) . '/' . $num;
+ $this->response = $this->makeDavRequest($user, 'PUT', $destination, [
+ 'Destination' => $this->s3MultipartDestination
+ ], $data, 'uploads');
+ }
+
+ /**
+ * @Given user :user moves new chunk v2 file with id :id
+ */
+ public function userMovesNewChunkv2FileWithIdToMychunkedfileAndDestination($user, $id) {
+ $source = '/uploads/' . $user . '/' . $this->getUploadId($id) . '/.file';
+ try {
+ $this->response = $this->makeDavRequest($user, 'MOVE', $source, [
+ 'Destination' => $this->s3MultipartDestination,
+ ], null, 'uploads');
+ } catch (\GuzzleHttp\Exception\ServerException $e) {
+ // 5xx responses cause a server exception
+ $this->response = $e->getResponse();
+ } catch (\GuzzleHttp\Exception\ClientException $e) {
+ // 4xx responses cause a client exception
+ $this->response = $e->getResponse();
+ }
+ }
+
+ private function getTargetDestination(string $user, string $destination): string {
+ return substr($this->baseUrl, 0, -4) . $this->getDavFilesPath($user) . $destination;
+ }
+
+ private function getUploadId(string $id): string {
+ return $id . '-' . $this->uploadId;
+ }
+
+ private function newUploadId() {
+ $this->uploadId = (string)time();
+ }
+
/**
* @Given /^Downloading file "([^"]*)" as "([^"]*)"$/
*/
@@ -897,6 +1018,23 @@ trait WebDav {
}
/**
+ * @When Requesting share note on dav endpoint
+ */
+ public function requestingShareNote() {
+ $propfind = '<d:propfind xmlns:d="DAV:" xmlns:nc="http://nextcloud.org/ns"><d:prop><nc:note /></d:prop></d:propfind>';
+ if (count($this->lastShareData->data->element) > 0) {
+ $token = $this->lastShareData->data[0]->token;
+ } else {
+ $token = $this->lastShareData->data->token;
+ }
+ try {
+ $this->response = $this->makeDavRequest('', 'PROPFIND', $token, [], $propfind);
+ } catch (\GuzzleHttp\Exception\ClientException $e) {
+ $this->response = $e->getResponse();
+ }
+ }
+
+ /**
* @Then there are no duplicate headers
*/
public function thereAreNoDuplicateHeaders() {
@@ -924,9 +1062,9 @@ trait WebDav {
$elementRows = $expectedElements->getRows();
$elementsSimplified = $this->simplifyArray($elementRows);
foreach ($elementsSimplified as $expectedElement) {
- $webdavPath = "/" . $this->getDavFilesPath($user) . $expectedElement;
+ $webdavPath = '/' . $this->getDavFilesPath($user) . $expectedElement;
if (!array_key_exists($webdavPath, $elementList)) {
- Assert::fail("$webdavPath" . " is not in report answer");
+ Assert::fail("$webdavPath" . ' is not in report answer');
}
}
}
@@ -941,12 +1079,12 @@ trait WebDav {
$elementList = $this->listFolder($user, $folder, 1);
$elementListKeys = array_keys($elementList);
array_shift($elementListKeys);
- $davPrefix = "/" . $this->getDavFilesPath($user);
+ $davPrefix = '/' . $this->getDavFilesPath($user);
foreach ($elementListKeys as $element) {
if (substr($element, 0, strlen($davPrefix)) == $davPrefix) {
$element = substr($element, strlen($davPrefix));
}
- $this->userDeletesFile($user, "element", $element);
+ $this->userDeletesFile($user, 'element', $element);
}
}
@@ -957,7 +1095,7 @@ trait WebDav {
* @return int
*/
private function getFileIdForPath($user, $path) {
- $propertiesTable = new \Behat\Gherkin\Node\TableNode([["{http://owncloud.org/ns}fileid"]]);
+ $propertiesTable = new \Behat\Gherkin\Node\TableNode([['{http://owncloud.org/ns}fileid']]);
$this->asGetsPropertiesOfFolderWith($user, 'file', $path, $propertiesTable);
return (int)$this->response['{http://owncloud.org/ns}fileid'];
}
@@ -980,4 +1118,88 @@ trait WebDav {
$currentFileID = $this->getFileIdForPath($user, $path);
Assert::assertEquals($currentFileID, $this->storedFileID);
}
+
+ /**
+ * @Given /^user "([^"]*)" creates a file locally with "([^"]*)" x 5 MB chunks$/
+ */
+ public function userCreatesAFileLocallyWithChunks($arg1, $chunks) {
+ $this->parts = [];
+ for ($i = 1;$i <= (int)$chunks;$i++) {
+ $randomletter = substr(str_shuffle('abcdefghijklmnopqrstuvwxyz'), 0, 1);
+ file_put_contents('/tmp/part-upload-' . $i, str_repeat($randomletter, 5 * 1024 * 1024));
+ $this->parts[] = '/tmp/part-upload-' . $i;
+ }
+ }
+
+ /**
+ * @Given user :user creates the chunk :id with a size of :size MB
+ */
+ public function userCreatesAChunk($user, $id, $size) {
+ $randomletter = substr(str_shuffle('abcdefghijklmnopqrstuvwxyz'), 0, 1);
+ file_put_contents('/tmp/part-upload-' . $id, str_repeat($randomletter, (int)$size * 1024 * 1024));
+ $this->parts[] = '/tmp/part-upload-' . $id;
+ }
+
+ /**
+ * @Then /^Downloaded content should be the created file$/
+ */
+ public function downloadedContentShouldBeTheCreatedFile() {
+ $content = '';
+ sort($this->parts);
+ foreach ($this->parts as $part) {
+ $content .= file_get_contents($part);
+ }
+ Assert::assertEquals($content, (string)$this->response->getBody());
+ }
+
+ /**
+ * @Then /^the S3 multipart upload was successful with status "([^"]*)"$/
+ */
+ public function theSmultipartUploadWasSuccessful($status) {
+ Assert::assertEquals((int)$status, $this->response->getStatusCode());
+ }
+
+ /**
+ * @Then /^the upload should fail on object storage$/
+ */
+ public function theUploadShouldFailOnObjectStorage() {
+ $descriptor = [
+ 0 => ['pipe', 'r'],
+ 1 => ['pipe', 'w'],
+ 2 => ['pipe', 'w'],
+ ];
+ $process = proc_open('php occ config:system:get objectstore --no-ansi', $descriptor, $pipes, '../../');
+ $lastCode = proc_close($process);
+ if ($lastCode === 0) {
+ $this->theHTTPStatusCodeShouldBe(500);
+ }
+ }
+
+ /**
+ * @return array
+ * @throws Exception
+ */
+ private function convertResponseToDavSingleEntry(): array {
+ $results = $this->convertResponseToDavEntries();
+ if (count($results) > 1) {
+ throw new \Exception('result is empty or contain more than one (1) entry');
+ }
+
+ return array_shift($results);
+ }
+
+ /**
+ * @return array
+ */
+ private function convertResponseToDavEntries(): array {
+ $client = $this->getSabreClient($this->currentUser);
+ $parsedResponse = $client->parseMultiStatus((string)$this->response->getBody());
+
+ $results = [];
+ foreach ($parsedResponse as $href => $statusList) {
+ $results[$href] = $statusList[200] ?? [];
+ }
+
+ return $results;
+ }
}
diff --git a/build/integration/features/caldav.feature b/build/integration/features/caldav.feature
deleted file mode 100644
index 2bddbc3e9e4..00000000000
--- a/build/integration/features/caldav.feature
+++ /dev/null
@@ -1,61 +0,0 @@
-Feature: caldav
- Scenario: Accessing a not existing calendar of another user
- Given user "user0" exists
- When "admin" requests calendar "user0/MyCalendar" on the endpoint "/remote.php/dav/calendars/"
- Then The CalDAV HTTP status code should be "404"
- And The exception is "Sabre\DAV\Exception\NotFound"
- And The error message is "Node with name 'MyCalendar' could not be found"
-
- Scenario: Accessing a not shared calendar of another user
- Given user "user0" exists
- Given "admin" creates a calendar named "MyCalendar"
- Given The CalDAV HTTP status code should be "201"
- When "user0" requests calendar "admin/MyCalendar" on the endpoint "/remote.php/dav/calendars/"
- Then The CalDAV HTTP status code should be "404"
- And The exception is "Sabre\DAV\Exception\NotFound"
- And The error message is "Node with name 'MyCalendar' could not be found"
-
- Scenario: Accessing a not shared calendar of another user via the legacy endpoint
- Given user "user0" exists
- Given "admin" creates a calendar named "MyCalendar"
- Given The CalDAV HTTP status code should be "201"
- When "user0" requests calendar "admin/MyCalendar" on the endpoint "/remote.php/caldav/calendars/"
- Then The CalDAV HTTP status code should be "404"
- And The exception is "Sabre\DAV\Exception\NotFound"
- And The error message is "Node with name 'MyCalendar' could not be found"
-
- Scenario: Accessing a not existing calendar of another user
- Given user "user0" exists
- When "user0" requests calendar "admin/MyCalendar" on the endpoint "/remote.php/dav/calendars/"
- Then The CalDAV HTTP status code should be "404"
- And The exception is "Sabre\DAV\Exception\NotFound"
- And The error message is "Node with name 'MyCalendar' could not be found"
-
- Scenario: Accessing a not existing calendar of another user via the legacy endpoint
- Given user "user0" exists
- When "user0" requests calendar "admin/MyCalendar" on the endpoint "/remote.php/caldav/calendars/"
- Then The CalDAV HTTP status code should be "404"
- And The exception is "Sabre\DAV\Exception\NotFound"
- And The error message is "Node with name 'MyCalendar' could not be found"
-
- Scenario: Accessing a not existing calendar of myself
- Given user "user0" exists
- When "user0" requests calendar "admin/MyCalendar" on the endpoint "/remote.php/dav/calendars/"
- Then The CalDAV HTTP status code should be "404"
- And The exception is "Sabre\DAV\Exception\NotFound"
- And The error message is "Node with name 'MyCalendar' could not be found"
-
- Scenario: Creating a new calendar
- When "admin" creates a calendar named "MyCalendar"
- Then The CalDAV HTTP status code should be "201"
- And "admin" requests calendar "admin/MyCalendar" on the endpoint "/remote.php/dav/calendars/"
- Then The CalDAV HTTP status code should be "207"
-
- Scenario: Propfind on public calendar endpoint without calendars
- When "admin" creates a calendar named "MyCalendar"
- Then The CalDAV HTTP status code should be "201"
- And "admin" publicly shares the calendar named "MyCalendar"
- Then The CalDAV HTTP status code should be "202"
- When "admin" requests calendar "/" on the endpoint "/remote.php/dav/public-calendars"
- Then The CalDAV HTTP status code should be "207"
- Then There should be "0" calendars in the response body \ No newline at end of file
diff --git a/build/integration/features/carddav.feature b/build/integration/features/carddav.feature
deleted file mode 100644
index da02096ae02..00000000000
--- a/build/integration/features/carddav.feature
+++ /dev/null
@@ -1,64 +0,0 @@
-Feature: carddav
- Scenario: Accessing a not existing addressbook of another user
- Given user "user0" exists
- When "admin" requests addressbook "user0/MyAddressbook" with statuscode "404" on the endpoint "/remote.php/dav/addressbooks/users/"
- And The CardDAV exception is "Sabre\DAV\Exception\NotFound"
- And The CardDAV error message is "Addressbook with name 'MyAddressbook' could not be found"
-
- Scenario: Accessing a not shared addressbook of another user
- Given user "user0" exists
- Given "admin" creates an addressbook named "MyAddressbook" with statuscode "201"
- When "user0" requests addressbook "admin/MyAddressbook" with statuscode "404" on the endpoint "/remote.php/dav/addressbooks/users/"
- And The CardDAV exception is "Sabre\DAV\Exception\NotFound"
- And The CardDAV error message is "Addressbook with name 'MyAddressbook' could not be found"
-
- Scenario: Accessing a not existing addressbook of another user via legacy endpoint
- Given user "user0" exists
- When "admin" requests addressbook "user0/MyAddressbook" with statuscode "404" on the endpoint "/remote.php/carddav/addressbooks/"
- And The CardDAV exception is "Sabre\DAV\Exception\NotFound"
- And The CardDAV error message is "Addressbook with name 'MyAddressbook' could not be found"
-
- Scenario: Accessing a not shared addressbook of another user via legacy endpoint
- Given user "user0" exists
- Given "admin" creates an addressbook named "MyAddressbook" with statuscode "201"
- When "user0" requests addressbook "admin/MyAddressbook" with statuscode "404" on the endpoint "/remote.php/carddav/addressbooks/"
- And The CardDAV exception is "Sabre\DAV\Exception\NotFound"
- And The CardDAV error message is "Addressbook with name 'MyAddressbook' could not be found"
-
- Scenario: Accessing a not existing addressbook of myself
- Given user "user0" exists
- When "user0" requests addressbook "admin/MyAddressbook" with statuscode "404" on the endpoint "/remote.php/dav/addressbooks/users/"
- And The CardDAV exception is "Sabre\DAV\Exception\NotFound"
- And The CardDAV error message is "Addressbook with name 'MyAddressbook' could not be found"
-
- Scenario: Creating a new addressbook
- When "admin" creates an addressbook named "MyAddressbook" with statuscode "201"
- Then "admin" requests addressbook "admin/MyAddressbook" with statuscode "207" on the endpoint "/remote.php/dav/addressbooks/users/"
-
- Scenario: Accessing ones own contact
- Given "admin" creates an addressbook named "MyAddressbook" with statuscode "201"
- Given "admin" uploads the contact "bjoern.vcf" to the addressbook "MyAddressbook"
- When Downloading the contact "bjoern.vcf" from addressbook "MyAddressbook" as user "admin"
- Then The following HTTP headers should be set
- |Content-Disposition|attachment; filename*=UTF-8''bjoern.vcf; filename="bjoern.vcf"|
- |Content-Type|text/vcard; charset=utf-8|
- |Content-Security-Policy|default-src 'none';|
- |X-Content-Type-Options |nosniff|
- |X-Frame-Options|SAMEORIGIN|
- |X-Permitted-Cross-Domain-Policies|none|
- |X-Robots-Tag|none|
- |X-XSS-Protection|1; mode=block|
-
- Scenario: Exporting the picture of ones own contact
- Given "admin" creates an addressbook named "MyAddressbook" with statuscode "201"
- Given "admin" uploads the contact "bjoern.vcf" to the addressbook "MyAddressbook"
- When Exporting the picture of contact "bjoern.vcf" from addressbook "MyAddressbook" as user "admin"
- Then The following HTTP headers should be set
- |Content-Disposition|attachment; filename=bjoern.vcf.jpg|
- |Content-Type|image/jpeg|
- |Content-Security-Policy|default-src 'none';|
- |X-Content-Type-Options |nosniff|
- |X-Frame-Options|SAMEORIGIN|
- |X-Permitted-Cross-Domain-Policies|none|
- |X-Robots-Tag|none|
- |X-XSS-Protection|1; mode=block|
diff --git a/build/integration/features/checksums.feature b/build/integration/features/checksums.feature
deleted file mode 100644
index d391e93afe8..00000000000
--- a/build/integration/features/checksums.feature
+++ /dev/null
@@ -1,76 +0,0 @@
-Feature: checksums
-
- Scenario: Uploading a file with checksum should work
- Given user "user0" exists
- When user "user0" uploads file "data/textfile.txt" to "/myChecksumFile.txt" with checksum "MD5:d70b40f177b14b470d1756a3c12b963a"
- Then The webdav response should have a status code "201"
-
- Scenario: Uploading a file with checksum should return the checksum in the propfind
- Given user "user0" exists
- And user "user0" uploads file "data/textfile.txt" to "/myChecksumFile.txt" with checksum "MD5:d70b40f177b14b470d1756a3c12b963a"
- When user "user0" request the checksum of "/myChecksumFile.txt" via propfind
- Then The webdav checksum should match "MD5:d70b40f177b14b470d1756a3c12b963a"
-
- Scenario: Uploading a file with checksum should return the checksum in the download header
- Given user "user0" exists
- And user "user0" uploads file "data/textfile.txt" to "/myChecksumFile.txt" with checksum "MD5:d70b40f177b14b470d1756a3c12b963a"
- When user "user0" downloads the file "/myChecksumFile.txt"
- Then The header checksum should match "MD5:d70b40f177b14b470d1756a3c12b963a"
-
- Scenario: Moving a file with checksum should return the checksum in the propfind
- Given user "user0" exists
- And user "user0" uploads file "data/textfile.txt" to "/myChecksumFile.txt" with checksum "MD5:d70b40f177b14b470d1756a3c12b963a"
- When User "user0" moved file "/myChecksumFile.txt" to "/myMovedChecksumFile.txt"
- And user "user0" request the checksum of "/myMovedChecksumFile.txt" via propfind
- Then The webdav checksum should match "MD5:d70b40f177b14b470d1756a3c12b963a"
-
- Scenario: Moving file with checksum should return the checksum in the download header
- Given user "user0" exists
- And user "user0" uploads file "data/textfile.txt" to "/myChecksumFile.txt" with checksum "MD5:d70b40f177b14b470d1756a3c12b963a"
- When User "user0" moved file "/myChecksumFile.txt" to "/myMovedChecksumFile.txt"
- And user "user0" downloads the file "/myMovedChecksumFile.txt"
- Then The header checksum should match "MD5:d70b40f177b14b470d1756a3c12b963a"
-
- Scenario: Copying a file with checksum should return the checksum in the propfind
- Given user "user0" exists
- And user "user0" uploads file "data/textfile.txt" to "/myChecksumFile.txt" with checksum "MD5:d70b40f177b14b470d1756a3c12b963a"
- When User "user0" copied file "/myChecksumFile.txt" to "/myChecksumFileCopy.txt"
- And user "user0" request the checksum of "/myChecksumFileCopy.txt" via propfind
- Then The webdav checksum should match "MD5:d70b40f177b14b470d1756a3c12b963a"
-
- Scenario: Copying file with checksum should return the checksum in the download header
- Given user "user0" exists
- And user "user0" uploads file "data/textfile.txt" to "/myChecksumFile.txt" with checksum "MD5:d70b40f177b14b470d1756a3c12b963a"
- When User "user0" copied file "/myChecksumFile.txt" to "/myChecksumFileCopy.txt"
- And user "user0" downloads the file "/myChecksumFileCopy.txt"
- Then The header checksum should match "MD5:d70b40f177b14b470d1756a3c12b963a"
-
- Scenario: Overwriting a file with checksum should remove the checksum and not return it in the propfind
- Given user "user0" exists
- And user "user0" uploads file "data/textfile.txt" to "/myChecksumFile.txt" with checksum "MD5:d70b40f177b14b470d1756a3c12b963a"
- When user "user0" uploads file "data/textfile.txt" to "/myChecksumFile.txt"
- And user "user0" request the checksum of "/myChecksumFile.txt" via propfind
- Then The webdav checksum should be empty
-
- Scenario: Overwriting a file with checksum should remove the checksum and not return it in the download header
- Given user "user0" exists
- And user "user0" uploads file "data/textfile.txt" to "/myChecksumFile.txt" with checksum "MD5:d70b40f177b14b470d1756a3c12b963a"
- When user "user0" uploads file "data/textfile.txt" to "/myChecksumFile.txt"
- And user "user0" downloads the file "/myChecksumFile.txt"
- Then The OC-Checksum header should not be there
-
- Scenario: Uploading a chunked file with checksum should return the checksum in the propfind
- Given user "user0" exists
- And user "user0" uploads chunk file "1" of "3" with "AAAAA" to "/myChecksumFile.txt" with checksum "MD5:e892fdd61a74bc89cd05673cc2e22f88"
- And user "user0" uploads chunk file "2" of "3" with "BBBBB" to "/myChecksumFile.txt" with checksum "MD5:e892fdd61a74bc89cd05673cc2e22f88"
- And user "user0" uploads chunk file "3" of "3" with "CCCCC" to "/myChecksumFile.txt" with checksum "MD5:e892fdd61a74bc89cd05673cc2e22f88"
- When user "user0" request the checksum of "/myChecksumFile.txt" via propfind
- Then The webdav checksum should match "MD5:e892fdd61a74bc89cd05673cc2e22f88"
-
- Scenario: Uploading a chunked file with checksum should return the checksum in the download header
- Given user "user0" exists
- And user "user0" uploads chunk file "1" of "3" with "AAAAA" to "/myChecksumFile.txt" with checksum "MD5:e892fdd61a74bc89cd05673cc2e22f88"
- And user "user0" uploads chunk file "2" of "3" with "BBBBB" to "/myChecksumFile.txt" with checksum "MD5:e892fdd61a74bc89cd05673cc2e22f88"
- And user "user0" uploads chunk file "3" of "3" with "CCCCC" to "/myChecksumFile.txt" with checksum "MD5:e892fdd61a74bc89cd05673cc2e22f88"
- When user "user0" downloads the file "/myChecksumFile.txt"
- Then The header checksum should match "MD5:e892fdd61a74bc89cd05673cc2e22f88"
diff --git a/build/integration/features/comments-search.feature b/build/integration/features/comments-search.feature
deleted file mode 100644
index a1d116ee3f4..00000000000
--- a/build/integration/features/comments-search.feature
+++ /dev/null
@@ -1,271 +0,0 @@
-Feature: comments-search
-
- Scenario: Search my own comment on a file belonging to myself
- Given user "user0" exists
- And User "user0" uploads file "data/textfile.txt" to "/myFileToComment.txt"
- And "user0" posts a comment with content "My first comment" on the file named "/myFileToComment.txt" it should return "201"
- When Logging in using web as "user0"
- And searching for "first" in app "files"
- Then the list of search results has "1" results
- And search result "0" contains
- | type | comment |
- | comment | My first comment |
- | authorId | user0 |
- | authorName | user0 |
- | path | myFileToComment.txt |
- | fileName | myFileToComment.txt |
- | name | My first comment |
-
- Scenario: Search my own comment on a file shared by someone with me
- Given user "user0" exists
- And user "user1" exists
- And User "user1" uploads file "data/textfile.txt" to "/sharedFileToComment.txt"
- And as "user1" creating a share with
- | path | sharedFileToComment.txt |
- | shareWith | user0 |
- | shareType | 0 |
- And user "user0" accepts last share
- And "user0" posts a comment with content "My first comment" on the file named "/sharedFileToComment.txt" it should return "201"
- When Logging in using web as "user0"
- And searching for "first" in app "files"
- Then the list of search results has "1" results
- And search result "0" contains
- | type | comment |
- | comment | My first comment |
- | authorId | user0 |
- | authorName | user0 |
- | path | sharedFileToComment.txt |
- | fileName | sharedFileToComment.txt |
- | name | My first comment |
-
- Scenario: Search other user's comment on a file shared by me
- Given user "user0" exists
- And user "user1" exists
- And User "user0" uploads file "data/textfile.txt" to "/mySharedFileToComment.txt"
- And as "user0" creating a share with
- | path | mySharedFileToComment.txt |
- | shareWith | user1 |
- | shareType | 0 |
- And user "user1" accepts last share
- And "user1" posts a comment with content "Other's first comment" on the file named "/mySharedFileToComment.txt" it should return "201"
- When Logging in using web as "user0"
- And searching for "first" in app "files"
- Then the list of search results has "1" results
- And search result "0" contains
- | type | comment |
- | comment | Other's first comment |
- | authorId | user1 |
- | authorName | user1 |
- | path | mySharedFileToComment.txt |
- | fileName | mySharedFileToComment.txt |
- | name | Other's first comment |
-
- Scenario: Search other user's comment on a file shared by someone with me
- Given user "user0" exists
- And user "user1" exists
- And User "user1" uploads file "data/textfile.txt" to "/sharedFileToComment.txt"
- And as "user1" creating a share with
- | path | sharedFileToComment.txt |
- | shareWith | user0 |
- | shareType | 0 |
- And user "user0" accepts last share
- And "user1" posts a comment with content "Other's first comment" on the file named "/sharedFileToComment.txt" it should return "201"
- When Logging in using web as "user0"
- And searching for "first" in app "files"
- Then the list of search results has "1" results
- And search result "0" contains
- | type | comment |
- | comment | Other's first comment |
- | authorId | user1 |
- | authorName | user1 |
- | path | sharedFileToComment.txt |
- | fileName | sharedFileToComment.txt |
- | name | Other's first comment |
-
- Scenario: Search several comments on a file belonging to myself
- Given user "user0" exists
- And User "user0" uploads file "data/textfile.txt" to "/myFileToComment.txt"
- And "user0" posts a comment with content "My first comment to be found" on the file named "/myFileToComment.txt" it should return "201"
- And "user0" posts a comment with content "The second comment should not be found" on the file named "/myFileToComment.txt" it should return "201"
- And "user0" posts a comment with content "My third comment to be found" on the file named "/myFileToComment.txt" it should return "201"
- When Logging in using web as "user0"
- And searching for "comment to be found" in app "files"
- Then the list of search results has "2" results
- And search result "0" contains
- | type | comment |
- | comment | My third comment to be found |
- | authorId | user0 |
- | authorName | user0 |
- | path | myFileToComment.txt |
- | fileName | myFileToComment.txt |
- | name | My third comment to be found |
- And search result "1" contains
- | type | comment |
- | comment | My first comment to be found |
- | authorId | user0 |
- | authorName | user0 |
- | path | myFileToComment.txt |
- | fileName | myFileToComment.txt |
- | name | My first comment to be found |
-
- Scenario: Search comment with a large message ellipsized on the right
- Given user "user0" exists
- And User "user0" uploads file "data/textfile.txt" to "/myFileToComment.txt"
- And "user0" posts a comment with content "A very verbose message that is meant to be used to test the ellipsized message returned when searching for long comments" on the file named "/myFileToComment.txt" it should return "201"
- When Logging in using web as "user0"
- And searching for "verbose" in app "files"
- Then the list of search results has "1" results
- And search result "0" contains
- | type | comment |
- | comment | A very verbose message that is meant to… |
- | authorId | user0 |
- | authorName | user0 |
- | path | myFileToComment.txt |
- | fileName | myFileToComment.txt |
- | name | A very verbose message that is meant to be used to test the ellipsized message returned when searching for long comments |
-
- Scenario: Search comment with a large message ellipsized on the left
- Given user "user0" exists
- And User "user0" uploads file "data/textfile.txt" to "/myFileToComment.txt"
- And "user0" posts a comment with content "A very verbose message that is meant to be used to test the ellipsized message returned when searching for long comments" on the file named "/myFileToComment.txt" it should return "201"
- When Logging in using web as "user0"
- And searching for "searching" in app "files"
- Then the list of search results has "1" results
- And search result "0" contains
- | type | comment |
- | comment | …ed message returned when searching for long comments |
- | authorId | user0 |
- | authorName | user0 |
- | path | myFileToComment.txt |
- | fileName | myFileToComment.txt |
- | name | A very verbose message that is meant to be used to test the ellipsized message returned when searching for long comments |
-
- Scenario: Search comment with a large message ellipsized on both ends
- Given user "user0" exists
- And User "user0" uploads file "data/textfile.txt" to "/myFileToComment.txt"
- And "user0" posts a comment with content "A very verbose message that is meant to be used to test the ellipsized message returned when searching for long comments" on the file named "/myFileToComment.txt" it should return "201"
- When Logging in using web as "user0"
- And searching for "ellipsized" in app "files"
- Then the list of search results has "1" results
- And search result "0" contains
- | type | comment |
- | comment | …t to be used to test the ellipsized message returned when se… |
- | authorId | user0 |
- | authorName | user0 |
- | path | myFileToComment.txt |
- | fileName | myFileToComment.txt |
- | name | A very verbose message that is meant to be used to test the ellipsized message returned when searching for long comments |
-
- Scenario: Search comment on a file in a subfolder
- Given user "user0" exists
- And user "user0" created a folder "/subfolder"
- And User "user0" uploads file "data/textfile.txt" to "/subfolder/myFileToComment.txt"
- And "user0" posts a comment with content "My first comment" on the file named "/subfolder/myFileToComment.txt" it should return "201"
- When Logging in using web as "user0"
- And searching for "first" in app "files"
- Then the list of search results has "1" results
- And search result "0" contains
- | type | comment |
- | comment | My first comment |
- | authorId | user0 |
- | authorName | user0 |
- | path | subfolder/myFileToComment.txt |
- | fileName | myFileToComment.txt |
- | name | My first comment |
-
- Scenario: Search several comments
- Given user "user0" exists
- And user "user1" exists
- And User "user0" uploads file "data/textfile.txt" to "/myFileToComment.txt"
- And User "user0" uploads file "data/textfile.txt" to "/mySharedFileToComment.txt"
- And as "user0" creating a share with
- | path | mySharedFileToComment.txt |
- | shareWith | user1 |
- | shareType | 0 |
- And user "user1" accepts last share
- And User "user1" uploads file "data/textfile.txt" to "/sharedFileToComment.txt"
- And as "user1" creating a share with
- | path | sharedFileToComment.txt |
- | shareWith | user0 |
- | shareType | 0 |
- And user "user0" accepts last share
- And "user0" posts a comment with content "My first comment to be found" on the file named "/myFileToComment.txt" it should return "201"
- And "user0" posts a comment with content "The second comment should not be found" on the file named "/myFileToComment.txt" it should return "201"
- And "user0" posts a comment with content "My first comment to be found" on the file named "/mySharedFileToComment.txt" it should return "201"
- And "user1" posts a comment with content "Other's first comment that should not be found" on the file named "/mySharedFileToComment.txt" it should return "201"
- And "user1" posts a comment with content "Other's second comment to be found" on the file named "/mySharedFileToComment.txt" it should return "201"
- And "user0" posts a comment with content "My first comment that should not be found" on the file named "/sharedFileToComment.txt" it should return "201"
- And "user1" posts a comment with content "Other's first comment to be found" on the file named "/sharedFileToComment.txt" it should return "201"
- And "user0" posts a comment with content "My second comment to be found that happens to be more verbose than the others and thus should be ellipsized" on the file named "/sharedFileToComment.txt" it should return "201"
- And "user0" posts a comment with content "My third comment to be found" on the file named "/myFileToComment.txt" it should return "201"
- When Logging in using web as "user0"
- And searching for "comment to be found" in app "files"
- Then the list of search results has "6" results
- And search result "0" contains
- | type | comment |
- | comment | My third comment to be found |
- | authorId | user0 |
- | authorName | user0 |
- | path | myFileToComment.txt |
- | fileName | myFileToComment.txt |
- | name | My third comment to be found |
- And search result "1" contains
- | type | comment |
- | comment | My second comment to be found that happens to be more … |
- | authorId | user0 |
- | authorName | user0 |
- | path | sharedFileToComment.txt |
- | fileName | sharedFileToComment.txt |
- | name | My second comment to be found that happens to be more verbose than the others and thus should be ellipsized |
- And search result "2" contains
- | type | comment |
- | comment | Other's first comment to be found |
- | authorId | user1 |
- | authorName | user1 |
- | path | sharedFileToComment.txt |
- | fileName | sharedFileToComment.txt |
- | name | Other's first comment to be found |
- And search result "3" contains
- | type | comment |
- | comment | Other's second comment to be found |
- | authorId | user1 |
- | authorName | user1 |
- | path | mySharedFileToComment.txt |
- | fileName | mySharedFileToComment.txt |
- | name | Other's second comment to be found |
- And search result "4" contains
- | type | comment |
- | comment | My first comment to be found |
- | authorId | user0 |
- | authorName | user0 |
- | path | mySharedFileToComment.txt |
- | fileName | mySharedFileToComment.txt |
- | name | My first comment to be found |
- And search result "5" contains
- | type | comment |
- | comment | My first comment to be found |
- | authorId | user0 |
- | authorName | user0 |
- | path | myFileToComment.txt |
- | fileName | myFileToComment.txt |
- | name | My first comment to be found |
-
- Scenario: Search comment with a query that also matches a file name
- Given user "user0" exists
- And User "user0" uploads file "data/textfile.txt" to "/myFileToComment.txt"
- And "user0" posts a comment with content "A comment in myFileToComment.txt" on the file named "/myFileToComment.txt" it should return "201"
- When Logging in using web as "user0"
- And searching for "myFileToComment" in app "files"
- Then the list of search results has "2" results
- And search result "0" contains
- | type | file |
- | path | /myFileToComment.txt |
- | name | myFileToComment.txt |
- And search result "1" contains
- | type | comment |
- | comment | A comment in myFileToComment.txt |
- | authorId | user0 |
- | authorName | user0 |
- | path | myFileToComment.txt |
- | fileName | myFileToComment.txt |
- | name | A comment in myFileToComment.txt |
diff --git a/build/integration/features/comments.feature b/build/integration/features/comments.feature
deleted file mode 100644
index 0f3a4cc75cf..00000000000
--- a/build/integration/features/comments.feature
+++ /dev/null
@@ -1,215 +0,0 @@
-Feature: comments
- Scenario: Creating a comment on a file belonging to myself
- Given user "user0" exists
- Given As an "user0"
- Given User "user0" uploads file "data/textfile.txt" to "/myFileToComment.txt"
- When "user0" posts a comment with content "My first comment" on the file named "/myFileToComment.txt" it should return "201"
- Then As "user0" load all the comments of the file named "/myFileToComment.txt" it should return "207"
- And the response should contain a property "oc:parentId" with value "0"
- And the response should contain a property "oc:childrenCount" with value "0"
- And the response should contain a property "oc:verb" with value "comment"
- And the response should contain a property "oc:actorType" with value "users"
- And the response should contain a property "oc:objectType" with value "files"
- And the response should contain a property "oc:message" with value "My first comment"
- And the response should contain a property "oc:actorDisplayName" with value "user0"
- And the response should contain only "1" comments
-
- Scenario: Creating a comment on a shared file belonging to another user
- Given user "user0" exists
- Given user "12345" exists
- Given User "user0" uploads file "data/textfile.txt" to "/myFileToComment.txt"
- Given as "user0" creating a share with
- | path | myFileToComment.txt |
- | shareWith | 12345 |
- | shareType | 0 |
- Given user "12345" accepts last share
- When "12345" posts a comment with content "A comment from another user" on the file named "/myFileToComment.txt" it should return "201"
- Then As "12345" load all the comments of the file named "/myFileToComment.txt" it should return "207"
- And the response should contain a property "oc:parentId" with value "0"
- And the response should contain a property "oc:childrenCount" with value "0"
- And the response should contain a property "oc:verb" with value "comment"
- And the response should contain a property "oc:actorType" with value "users"
- And the response should contain a property "oc:objectType" with value "files"
- And the response should contain a property "oc:message" with value "A comment from another user"
- And the response should contain a property "oc:actorDisplayName" with value "12345"
- And the response should contain only "1" comments
-
- Scenario: Creating a comment on a non-shared file belonging to another user
- Given user "user0" exists
- Given user "user1" exists
- Given User "user0" uploads file "data/textfile.txt" to "/myFileToComment.txt"
- Then "user1" posts a comment with content "My first comment" on the file named "/myFileToComment.txt" it should return "404"
-
- Scenario: Reading comments on a non-shared file belonging to another user
- Given user "user0" exists
- Given user "user1" exists
- Given User "user0" uploads file "data/textfile.txt" to "/myFileToComment.txt"
- Then As "user1" load all the comments of the file named "/myFileToComment.txt" it should return "404"
-
- Scenario: Deleting my own comments on a file belonging to myself
- Given user "user0" exists
- Given As an "user0"
- Given User "user0" uploads file "data/textfile.txt" to "/myFileToComment.txt"
- Given "user0" posts a comment with content "My first comment" on the file named "/myFileToComment.txt" it should return "201"
- When As "user0" load all the comments of the file named "/myFileToComment.txt" it should return "207"
- Then the response should contain a property "oc:parentId" with value "0"
- Then the response should contain a property "oc:childrenCount" with value "0"
- And the response should contain a property "oc:verb" with value "comment"
- And the response should contain a property "oc:actorType" with value "users"
- And the response should contain a property "oc:objectType" with value "files"
- And the response should contain a property "oc:message" with value "My first comment"
- And the response should contain a property "oc:actorDisplayName" with value "user0"
- And the response should contain only "1" comments
- And As "user0" delete the created comment it should return "204"
- And As "user0" load all the comments of the file named "/myFileToComment.txt" it should return "207"
- And the response should contain only "0" comments
-
- Scenario: Deleting my own comments on a file shared by somebody else
- Given user "user0" exists
- Given user "user1" exists
- Given As an "user0"
- Given User "user0" uploads file "data/textfile.txt" to "/myFileToComment.txt"
- Given as "user0" creating a share with
- | path | myFileToComment.txt |
- | shareWith | user1 |
- | shareType | 0 |
- And user "user1" accepts last share
- Given "user1" posts a comment with content "My first comment" on the file named "/myFileToComment.txt" it should return "201"
- When As "user1" load all the comments of the file named "/myFileToComment.txt" it should return "207"
- Then the response should contain a property "oc:parentId" with value "0"
- And the response should contain a property "oc:childrenCount" with value "0"
- And the response should contain a property "oc:verb" with value "comment"
- And the response should contain a property "oc:actorType" with value "users"
- And the response should contain a property "oc:objectType" with value "files"
- And the response should contain a property "oc:message" with value "My first comment"
- And the response should contain a property "oc:actorDisplayName" with value "user1"
- And the response should contain only "1" comments
- And As "user1" delete the created comment it should return "204"
- And As "user1" load all the comments of the file named "/myFileToComment.txt" it should return "207"
- And the response should contain only "0" comments
-
- Scenario: Deleting my own comments on a file unshared by someone else
- Given user "user0" exists
- Given user "user1" exists
- Given User "user0" uploads file "data/textfile.txt" to "/myFileToComment.txt"
- Given as "user0" creating a share with
- | path | myFileToComment.txt |
- | shareWith | user1 |
- | shareType | 0 |
- And user "user1" accepts last share
- Given "user1" posts a comment with content "My first comment" on the file named "/myFileToComment.txt" it should return "201"
- When As "user1" load all the comments of the file named "/myFileToComment.txt" it should return "207"
- Then the response should contain a property "oc:parentId" with value "0"
- And the response should contain a property "oc:childrenCount" with value "0"
- And the response should contain a property "oc:verb" with value "comment"
- And the response should contain a property "oc:actorType" with value "users"
- And the response should contain a property "oc:objectType" with value "files"
- And the response should contain a property "oc:message" with value "My first comment"
- And the response should contain a property "oc:actorDisplayName" with value "user1"
- And the response should contain only "1" comments
- And As "user0" remove all shares from the file named "/myFileToComment.txt"
- And As "user1" delete the created comment it should return "404"
- And As "user1" load all the comments of the file named "/myFileToComment.txt" it should return "404"
-
- Scenario: Edit my own comments on a file belonging to myself
- Given user "user0" exists
- Given User "user0" uploads file "data/textfile.txt" to "/myFileToComment.txt"
- Given "user0" posts a comment with content "My first comment" on the file named "/myFileToComment.txt" it should return "201"
- When As "user0" load all the comments of the file named "/myFileToComment.txt" it should return "207"
- Then the response should contain a property "oc:parentId" with value "0"
- And the response should contain a property "oc:childrenCount" with value "0"
- And the response should contain a property "oc:verb" with value "comment"
- And the response should contain a property "oc:actorType" with value "users"
- And the response should contain a property "oc:objectType" with value "files"
- And the response should contain a property "oc:message" with value "My first comment"
- And the response should contain a property "oc:actorDisplayName" with value "user0"
- And the response should contain only "1" comments
- When As "user0" edit the last created comment and set text to "My edited comment" it should return "207"
- Then As "user0" load all the comments of the file named "/myFileToComment.txt" it should return "207"
- And the response should contain a property "oc:parentId" with value "0"
- And the response should contain a property "oc:childrenCount" with value "0"
- And the response should contain a property "oc:verb" with value "comment"
- And the response should contain a property "oc:actorType" with value "users"
- And the response should contain a property "oc:objectType" with value "files"
- And the response should contain a property "oc:message" with value "My edited comment"
- And the response should contain a property "oc:actorDisplayName" with value "user0"
-
- Scenario: Edit my own comments on a file shared by someone with me
- Given user "user0" exists
- Given user "user1" exists
- Given User "user0" uploads file "data/textfile.txt" to "/myFileToComment.txt"
- Given as "user0" creating a share with
- | path | myFileToComment.txt |
- | shareWith | user1 |
- | shareType | 0 |
- And user "user1" accepts last share
- Given "user1" posts a comment with content "My first comment" on the file named "/myFileToComment.txt" it should return "201"
- When As "user0" load all the comments of the file named "/myFileToComment.txt" it should return "207"
- Then the response should contain a property "oc:parentId" with value "0"
- And the response should contain a property "oc:childrenCount" with value "0"
- And the response should contain a property "oc:verb" with value "comment"
- And the response should contain a property "oc:actorType" with value "users"
- And the response should contain a property "oc:objectType" with value "files"
- And the response should contain a property "oc:message" with value "My first comment"
- And the response should contain a property "oc:actorDisplayName" with value "user1"
- And the response should contain only "1" comments
- Given As "user1" edit the last created comment and set text to "My edited comment" it should return "207"
- Then As "user1" load all the comments of the file named "/myFileToComment.txt" it should return "207"
- And the response should contain a property "oc:parentId" with value "0"
- And the response should contain a property "oc:childrenCount" with value "0"
- And the response should contain a property "oc:verb" with value "comment"
- And the response should contain a property "oc:actorType" with value "users"
- And the response should contain a property "oc:objectType" with value "files"
- And the response should contain a property "oc:message" with value "My edited comment"
- And the response should contain a property "oc:actorDisplayName" with value "user1"
-
- Scenario: Edit my own comments on a file unshared by someone with me
- Given user "user0" exists
- Given user "user1" exists
- Given User "user0" uploads file "data/textfile.txt" to "/myFileToComment.txt"
- Given as "user0" creating a share with
- | path | myFileToComment.txt |
- | shareWith | user1 |
- | shareType | 0 |
- And user "user1" accepts last share
- When "user1" posts a comment with content "My first comment" on the file named "/myFileToComment.txt" it should return "201"
- Then As "user0" load all the comments of the file named "/myFileToComment.txt" it should return "207"
- And the response should contain a property "oc:parentId" with value "0"
- And the response should contain a property "oc:childrenCount" with value "0"
- And the response should contain a property "oc:verb" with value "comment"
- And the response should contain a property "oc:actorType" with value "users"
- And the response should contain a property "oc:objectType" with value "files"
- And the response should contain a property "oc:message" with value "My first comment"
- And the response should contain a property "oc:actorDisplayName" with value "user1"
- And the response should contain only "1" comments
- And As "user0" remove all shares from the file named "/myFileToComment.txt"
- When As "user1" edit the last created comment and set text to "My edited comment" it should return "404"
- Then As "user0" load all the comments of the file named "/myFileToComment.txt" it should return "207"
- And the response should contain a property "oc:parentId" with value "0"
- And the response should contain a property "oc:childrenCount" with value "0"
- And the response should contain a property "oc:verb" with value "comment"
- And the response should contain a property "oc:actorType" with value "users"
- And the response should contain a property "oc:objectType" with value "files"
- And the response should contain a property "oc:message" with value "My first comment"
- And the response should contain a property "oc:actorDisplayName" with value "user1"
-
- Scenario: Edit comments of other users should not be possible
- Given user "user0" exists
- Given user "user1" exists
- Given User "user0" uploads file "data/textfile.txt" to "/myFileToComment.txt"
- Given as "user0" creating a share with
- | path | myFileToComment.txt |
- | shareWith | user1 |
- | shareType | 0 |
- And user "user1" accepts last share
- Given "user1" posts a comment with content "My first comment" on the file named "/myFileToComment.txt" it should return "201"
- When As "user0" load all the comments of the file named "/myFileToComment.txt" it should return "207"
- Then the response should contain a property "oc:parentId" with value "0"
- And the response should contain a property "oc:childrenCount" with value "0"
- And the response should contain a property "oc:verb" with value "comment"
- And the response should contain a property "oc:actorType" with value "users"
- And the response should contain a property "oc:objectType" with value "files"
- And the response should contain a property "oc:message" with value "My first comment"
- And the response should contain a property "oc:actorDisplayName" with value "user1"
- And the response should contain only "1" comments
- Then As "user0" edit the last created comment and set text to "My edited comment" it should return "403"
diff --git a/build/integration/features/contacts-menu.feature b/build/integration/features/contacts-menu.feature
index 845d4d35925..772c0e5405c 100644
--- a/build/integration/features/contacts-menu.feature
+++ b/build/integration/features/contacts-menu.feature
@@ -1,3 +1,5 @@
+# SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
+# SPDX-License-Identifier: AGPL-3.0-or-later
Feature: contacts-menu
Scenario: users can be searched by display name
@@ -69,8 +71,6 @@ Feature: contacts-menu
And searched contact "1" is named "Test name"
And searched contact "2" is named "user2"
-
-
Scenario: users can not be found by display name if visibility is private
Given user "user0" exists
And user "user1" exists
@@ -78,15 +78,16 @@ Feature: contacts-menu
And Logging in using web as "user1"
And Sending a "PUT" to "/settings/users/user1/settings" with requesttoken
| displayname | Test name |
- | displaynameScope | private |
+ | displaynameScope | v2-private |
And Logging in using web as "user2"
And Sending a "PUT" to "/settings/users/user2/settings" with requesttoken
| displayname | Another test name |
- | displaynameScope | contacts |
+ | displaynameScope | v2-federated |
When Logging in using web as "user0"
And searching for contacts matching with "test"
- Then the list of searched contacts has "1" contacts
- And searched contact "0" is named "Another test name"
+ # Disabled because it regularly fails on drone:
+ # Then the list of searched contacts has "1" contacts
+ # And searched contact "0" is named "Another test name"
Scenario: users can not be found by email if visibility is private
Given user "user0" exists
@@ -95,15 +96,16 @@ Feature: contacts-menu
And Logging in using web as "user1"
And Sending a "PUT" to "/settings/users/user1/settings" with requesttoken
| email | test@example.com |
- | emailScope | private |
+ | emailScope | v2-private |
And Logging in using web as "user2"
And Sending a "PUT" to "/settings/users/user2/settings" with requesttoken
| email | another_test@example.com |
- | emailScope | contacts |
- When Logging in using web as "user0"
- And searching for contacts matching with "test"
- Then the list of searched contacts has "1" contacts
- And searched contact "0" is named "user2"
+ | emailScope | v2-federated |
+ # Disabled because it regularly fails on drone:
+ # When Logging in using web as "user0"
+ # And searching for contacts matching with "test"
+ # Then the list of searched contacts has "1" contacts
+ # And searched contact "0" is named "user2"
Scenario: users can be found by other properties if the visibility of one is private
Given user "user0" exists
@@ -112,19 +114,20 @@ Feature: contacts-menu
And Logging in using web as "user1"
And Sending a "PUT" to "/settings/users/user1/settings" with requesttoken
| displayname | Test name |
- | displaynameScope | contacts |
+ | displaynameScope | v2-federated |
| email | test@example.com |
- | emailScope | private |
+ | emailScope | v2-private |
And Logging in using web as "user2"
And Sending a "PUT" to "/settings/users/user2/settings" with requesttoken
| displayname | Another test name |
- | displaynameScope | private |
+ | displaynameScope | v2-private |
| email | another_test@example.com |
- | emailScope | contacts |
+ | emailScope | v2-federated |
When Logging in using web as "user0"
And searching for contacts matching with "test"
Then the list of searched contacts has "2" contacts
- And searched contact "0" is named ""
+ # Disabled because it regularly fails on drone:
+ # And searched contact "0" is named ""
And searched contact "1" is named "Test name"
@@ -135,9 +138,9 @@ Feature: contacts-menu
And Logging in using web as "user1"
And Sending a "PUT" to "/settings/users/user1/settings" with requesttoken
| displayname | Test name |
- | displaynameScope | private |
+ | displaynameScope | v2-private |
And Sending a "PUT" to "/settings/users/user1/settings" with requesttoken
- | displaynameScope | contacts |
+ | displaynameScope | v2-federated |
When Logging in using web as "user0"
And searching for contacts matching with "test"
Then the list of searched contacts has "1" contacts
@@ -149,13 +152,14 @@ Feature: contacts-menu
And Logging in using web as "user1"
And Sending a "PUT" to "/settings/users/user1/settings" with requesttoken
| email | test@example.com |
- | emailScope | private |
+ | emailScope | v2-private |
And Sending a "PUT" to "/settings/users/user1/settings" with requesttoken
- | emailScope | contacts |
- When Logging in using web as "user0"
- And searching for contacts matching with "test"
- Then the list of searched contacts has "1" contacts
- And searched contact "0" is named "user1"
+ | emailScope | v2-federated |
+ # Disabled because it regularly fails on drone:
+ # When Logging in using web as "user0"
+ # And searching for contacts matching with "test"
+ # Then the list of searched contacts has "1" contacts
+ # And searched contact "0" is named "user1"
@@ -164,25 +168,27 @@ Feature: contacts-menu
And user "user1" exists
And Logging in using web as "user1"
And Sending a "PUT" to "/settings/users/user1/settings" with requesttoken
- | displaynameScope | private |
+ | displaynameScope | v2-private |
And As an "admin"
And sending "PUT" to "/cloud/users/user1" with
| key | displayname |
| value | Test name |
When Logging in using web as "user0"
And searching for contacts matching with "test"
- Then the list of searched contacts has "0" contacts
+ # Disabled because it regularly fails on drone:
+ # Then the list of searched contacts has "0" contacts
Scenario: users can not be searched by email if visibility is private even if updated with provisioning
Given user "user0" exists
And user "user1" exists
And Logging in using web as "user1"
And Sending a "PUT" to "/settings/users/user1/settings" with requesttoken
- | emailScope | private |
+ | emailScope | v2-private |
And As an "admin"
And sending "PUT" to "/cloud/users/user1" with
| key | email |
| value | test@example.com |
When Logging in using web as "user0"
And searching for contacts matching with "test"
- Then the list of searched contacts has "0" contacts
+ # Disabled because it regularly fails on drone:
+ # Then the list of searched contacts has "0" contacts
diff --git a/build/integration/features/dav-v2.feature b/build/integration/features/dav-v2.feature
deleted file mode 100644
index 5b5c835e0dd..00000000000
--- a/build/integration/features/dav-v2.feature
+++ /dev/null
@@ -1,88 +0,0 @@
-Feature: dav-v2
- Background:
- Given using api version "1"
-
- Scenario: moving a file new endpoint way
- Given using new dav path
- And As an "admin"
- And user "user0" exists
- When User "user0" moves file "/textfile0.txt" to "/FOLDER/textfile0.txt"
- Then the HTTP status code should be "201"
-
- Scenario: download a file with range using new endpoint
- Given using new dav path
- And As an "admin"
- And user "user0" exists
- And As an "user0"
- When Downloading file "/welcome.txt" with range "bytes=52-78"
- Then Downloaded content should be "example file for developers"
-
- Scenario: Downloading a file on the new endpoint should serve security headers
- Given using new dav path
- And As an "admin"
- When Downloading file "/welcome.txt"
- Then The following headers should be set
- |Content-Disposition|attachment; filename*=UTF-8''welcome.txt; filename="welcome.txt"|
- |Content-Security-Policy|default-src 'none';|
- |X-Content-Type-Options |nosniff|
- |X-Frame-Options|SAMEORIGIN|
- |X-Permitted-Cross-Domain-Policies|none|
- |X-Robots-Tag|none|
- |X-XSS-Protection|1; mode=block|
- And Downloaded content should start with "Welcome to your Nextcloud account!"
-
- Scenario: Doing a GET with a web login should work without CSRF token on the new backend
- Given Logging in using web as "admin"
- When Sending a "GET" to "/remote.php/dav/files/admin/welcome.txt" without requesttoken
- Then Downloaded content should start with "Welcome to your Nextcloud account!"
- Then the HTTP status code should be "200"
-
- Scenario: Doing a GET with a web login should work with CSRF token on the new backend
- Given Logging in using web as "admin"
- When Sending a "GET" to "/remote.php/dav/files/admin/welcome.txt" with requesttoken
- Then Downloaded content should start with "Welcome to your Nextcloud account!"
- Then the HTTP status code should be "200"
-
- Scenario: Doing a PROPFIND with a web login should not work without CSRF token on the new backend
- Given Logging in using web as "admin"
- When Sending a "PROPFIND" to "/remote.php/dav/files/admin/welcome.txt" without requesttoken
- Then the HTTP status code should be "401"
-
- Scenario: Doing a PROPFIND with a web login should work with CSRF token on the new backend
- Given Logging in using web as "admin"
- When Sending a "PROPFIND" to "/remote.php/dav/files/admin/welcome.txt" with requesttoken
- Then the HTTP status code should be "207"
-
- Scenario: Uploading a file having 0B as quota
- Given using new dav path
- And As an "admin"
- And user "user0" exists
- And user "user0" has a quota of "0 B"
- And As an "user0"
- When User "user0" uploads file "data/textfile.txt" to "/asdf.txt"
- Then the HTTP status code should be "507"
-
- Scenario: Uploading a file as recipient using webdav new endpoint having quota
- Given using new dav path
- And As an "admin"
- And user "user0" exists
- And user "user1" exists
- And user "user0" has a quota of "10 MB"
- And user "user1" has a quota of "10 MB"
- And As an "user1"
- And user "user1" created a folder "/testquota"
- And as "user1" creating a share with
- | path | testquota |
- | shareType | 0 |
- | permissions | 31 |
- | shareWith | user0 |
- And user "user0" accepts last share
- And As an "user0"
- When User "user0" uploads file "data/textfile.txt" to "/testquota/asdf.txt"
- Then the HTTP status code should be "201"
-
- Scenario: Create a search query
- Given using new dav path
- And As an "admin"
- When User "user0" uploads file "data/green-square-256.png" to "/image.png"
- When Image search should work
diff --git a/build/integration/features/download.feature b/build/integration/features/download.feature
deleted file mode 100644
index 16d346b0150..00000000000
--- a/build/integration/features/download.feature
+++ /dev/null
@@ -1,294 +0,0 @@
-Feature: download
-
- Scenario: downloading 2 small files returns a zip32
- Given using new dav path
- And user "user0" exists
- And User "user0" copies file "/welcome.txt" to "/welcome2.txt"
- When user "user0" downloads zip file for entries '"welcome.txt","welcome2.txt"' in folder "/"
- Then the downloaded zip file is a zip32 file
- And the downloaded zip file contains a file named "welcome.txt" with the contents of "/welcome.txt" from "user0" data
- And the downloaded zip file contains a file named "welcome2.txt" with the contents of "/welcome2.txt" from "user0" data
-
- Scenario: downloading a small file and a directory returns a zip32
- Given using new dav path
- And user "user0" exists
- And user "user0" created a folder "/emptySubFolder"
- When user "user0" downloads zip file for entries '"welcome.txt","emptySubFolder"' in folder "/"
- Then the downloaded zip file is a zip32 file
- And the downloaded zip file contains a file named "welcome.txt" with the contents of "/welcome.txt" from "user0" data
- And the downloaded zip file contains a folder named "emptySubFolder/"
-
- Scenario: downloading a small file and 2 nested directories returns a zip32
- Given using new dav path
- And user "user0" exists
- And user "user0" created a folder "/subFolder"
- And user "user0" created a folder "/subFolder/emptySubSubFolder"
- When user "user0" downloads zip file for entries '"welcome.txt","subFolder"' in folder "/"
- Then the downloaded zip file is a zip32 file
- And the downloaded zip file contains a file named "welcome.txt" with the contents of "/welcome.txt" from "user0" data
- And the downloaded zip file contains a folder named "subFolder/"
- And the downloaded zip file contains a folder named "subFolder/emptySubSubFolder/"
-
- Scenario: downloading dir with 2 small files returns a zip32
- Given using new dav path
- And user "user0" exists
- And user "user0" created a folder "/sparseFolder"
- And User "user0" copies file "/welcome.txt" to "/sparseFolder/welcome.txt"
- And User "user0" copies file "/welcome.txt" to "/sparseFolder/welcome2.txt"
- When user "user0" downloads zip file for entries '"sparseFolder"' in folder "/"
- Then the downloaded zip file is a zip32 file
- And the downloaded zip file contains a folder named "sparseFolder/"
- And the downloaded zip file contains a file named "sparseFolder/welcome.txt" with the contents of "/sparseFolder/welcome.txt" from "user0" data
- And the downloaded zip file contains a file named "sparseFolder/welcome2.txt" with the contents of "/sparseFolder/welcome2.txt" from "user0" data
-
- Scenario: downloading dir with a small file and a directory returns a zip32
- Given using new dav path
- And user "user0" exists
- And user "user0" created a folder "/sparseFolder"
- And User "user0" copies file "/welcome.txt" to "/sparseFolder/welcome.txt"
- And user "user0" created a folder "/sparseFolder/emptySubFolder"
- When user "user0" downloads zip file for entries '"sparseFolder"' in folder "/"
- Then the downloaded zip file is a zip32 file
- And the downloaded zip file contains a folder named "sparseFolder/"
- And the downloaded zip file contains a file named "sparseFolder/welcome.txt" with the contents of "/sparseFolder/welcome.txt" from "user0" data
- And the downloaded zip file contains a folder named "sparseFolder/emptySubFolder/"
-
- Scenario: downloading dir with a small file and 2 nested directories returns a zip32
- Given using new dav path
- And user "user0" exists
- And user "user0" created a folder "/sparseFolder"
- And User "user0" copies file "/welcome.txt" to "/sparseFolder/welcome.txt"
- And user "user0" created a folder "/sparseFolder/subFolder"
- And user "user0" created a folder "/sparseFolder/subFolder/emptySubSubFolder"
- When user "user0" downloads zip file for entries '"sparseFolder"' in folder "/"
- Then the downloaded zip file is a zip32 file
- And the downloaded zip file contains a folder named "sparseFolder/"
- And the downloaded zip file contains a file named "sparseFolder/welcome.txt" with the contents of "/sparseFolder/welcome.txt" from "user0" data
- And the downloaded zip file contains a folder named "sparseFolder/subFolder/"
- And the downloaded zip file contains a folder named "sparseFolder/subFolder/emptySubSubFolder/"
-
- Scenario: downloading (from folder) 2 small files returns a zip32
- Given using new dav path
- And user "user0" exists
- And user "user0" created a folder "/baseFolder"
- And User "user0" copies file "/welcome.txt" to "/baseFolder/welcome.txt"
- And User "user0" copies file "/welcome.txt" to "/baseFolder/welcome2.txt"
- When user "user0" downloads zip file for entries '"welcome.txt","welcome2.txt"' in folder "/baseFolder/"
- Then the downloaded zip file is a zip32 file
- And the downloaded zip file contains a file named "welcome.txt" with the contents of "/baseFolder/welcome.txt" from "user0" data
- And the downloaded zip file contains a file named "welcome2.txt" with the contents of "/baseFolder/welcome2.txt" from "user0" data
-
- Scenario: downloading (from folder) a small file and a directory returns a zip32
- Given using new dav path
- And user "user0" exists
- And user "user0" created a folder "/baseFolder"
- And User "user0" copies file "/welcome.txt" to "/baseFolder/welcome.txt"
- And user "user0" created a folder "/baseFolder/emptySubFolder"
- When user "user0" downloads zip file for entries '"welcome.txt","emptySubFolder"' in folder "/baseFolder/"
- Then the downloaded zip file is a zip32 file
- And the downloaded zip file contains a file named "welcome.txt" with the contents of "/baseFolder/welcome.txt" from "user0" data
- And the downloaded zip file contains a folder named "emptySubFolder/"
-
- Scenario: downloading (from folder) a small file and 2 nested directories returns a zip32
- Given using new dav path
- And user "user0" exists
- And user "user0" created a folder "/baseFolder"
- And User "user0" copies file "/welcome.txt" to "/baseFolder/welcome.txt"
- And user "user0" created a folder "/baseFolder/subFolder"
- And user "user0" created a folder "/baseFolder/subFolder/emptySubSubFolder"
- When user "user0" downloads zip file for entries '"welcome.txt","subFolder"' in folder "/baseFolder/"
- Then the downloaded zip file is a zip32 file
- And the downloaded zip file contains a file named "welcome.txt" with the contents of "/baseFolder/welcome.txt" from "user0" data
- And the downloaded zip file contains a folder named "subFolder/"
- And the downloaded zip file contains a folder named "subFolder/emptySubSubFolder/"
-
- Scenario: downloading (from folder) dir with 2 small files returns a zip32
- Given using new dav path
- And user "user0" exists
- And user "user0" created a folder "/baseFolder"
- And user "user0" created a folder "/baseFolder/sparseFolder"
- And User "user0" copies file "/welcome.txt" to "/baseFolder/sparseFolder/welcome.txt"
- And User "user0" copies file "/welcome.txt" to "/baseFolder/sparseFolder/welcome2.txt"
- When user "user0" downloads zip file for entries '"sparseFolder"' in folder "/baseFolder/"
- Then the downloaded zip file is a zip32 file
- And the downloaded zip file contains a folder named "sparseFolder/"
- And the downloaded zip file contains a file named "sparseFolder/welcome.txt" with the contents of "/baseFolder/sparseFolder/welcome.txt" from "user0" data
- And the downloaded zip file contains a file named "sparseFolder/welcome2.txt" with the contents of "/baseFolder/sparseFolder/welcome2.txt" from "user0" data
-
- Scenario: downloading (from folder) dir with a small file and a directory returns a zip32
- Given using new dav path
- And user "user0" exists
- And user "user0" created a folder "/baseFolder"
- And user "user0" created a folder "/baseFolder/sparseFolder"
- And User "user0" copies file "/welcome.txt" to "/baseFolder/sparseFolder/welcome.txt"
- And user "user0" created a folder "/baseFolder/sparseFolder/emptySubFolder"
- When user "user0" downloads zip file for entries '"sparseFolder"' in folder "/baseFolder/"
- Then the downloaded zip file is a zip32 file
- And the downloaded zip file contains a folder named "sparseFolder/"
- And the downloaded zip file contains a file named "sparseFolder/welcome.txt" with the contents of "/baseFolder/sparseFolder/welcome.txt" from "user0" data
- And the downloaded zip file contains a folder named "sparseFolder/emptySubFolder/"
-
- Scenario: downloading (from folder) dir with a small file and 2 nested directories returns a zip32
- Given using new dav path
- And user "user0" exists
- And user "user0" created a folder "/baseFolder"
- And user "user0" created a folder "/baseFolder/sparseFolder"
- And User "user0" copies file "/welcome.txt" to "/baseFolder/sparseFolder/welcome.txt"
- And user "user0" created a folder "/baseFolder/sparseFolder/subFolder"
- And user "user0" created a folder "/baseFolder/sparseFolder/subFolder/emptySubSubFolder"
- When user "user0" downloads zip file for entries '"sparseFolder"' in folder "/baseFolder/"
- Then the downloaded zip file is a zip32 file
- And the downloaded zip file contains a folder named "sparseFolder/"
- And the downloaded zip file contains a file named "sparseFolder/welcome.txt" with the contents of "/baseFolder/sparseFolder/welcome.txt" from "user0" data
- And the downloaded zip file contains a folder named "sparseFolder/subFolder/"
- And the downloaded zip file contains a folder named "sparseFolder/subFolder/emptySubSubFolder/"
-
- @large
- Scenario: downloading small file and dir with 65524 small files and 9 nested directories returns a zip32
- Given using new dav path
- And user "user0" exists
- And user "user0" created a folder "/crowdedFolder"
- And user "user0" created a folder "/crowdedFolder/subFolder1"
- And file "/crowdedFolder/subFolder1/test.txt" is created "10000" times in "user0" user data
- And invoking occ with "files:scan --path /user0/files/crowdedFolder/subFolder1"
- And user "user0" created a folder "/crowdedFolder/subFolder2"
- And file "/crowdedFolder/subFolder2/test.txt" is created "10000" times in "user0" user data
- And invoking occ with "files:scan --path /user0/files/crowdedFolder/subFolder2"
- And user "user0" created a folder "/crowdedFolder/subFolder3"
- And file "/crowdedFolder/subFolder3/test.txt" is created "10000" times in "user0" user data
- And invoking occ with "files:scan --path /user0/files/crowdedFolder/subFolder3"
- And user "user0" created a folder "/crowdedFolder/subFolder4"
- And file "/crowdedFolder/subFolder4/test.txt" is created "10000" times in "user0" user data
- And invoking occ with "files:scan --path /user0/files/crowdedFolder/subFolder4"
- And user "user0" created a folder "/crowdedFolder/subFolder5"
- And file "/crowdedFolder/subFolder5/test.txt" is created "10000" times in "user0" user data
- And invoking occ with "files:scan --path /user0/files/crowdedFolder/subFolder5"
- And user "user0" created a folder "/crowdedFolder/subFolder6"
- And file "/crowdedFolder/subFolder6/test.txt" is created "10000" times in "user0" user data
- And invoking occ with "files:scan --path /user0/files/crowdedFolder/subFolder6"
- And user "user0" created a folder "/crowdedFolder/subFolder7"
- And file "/crowdedFolder/subFolder7/test.txt" is created "5524" times in "user0" user data
- And invoking occ with "files:scan --path /user0/files/crowdedFolder/subFolder7"
- And user "user0" created a folder "/crowdedFolder/subFolder7/subSubFolder"
- And user "user0" created a folder "/crowdedFolder/subFolder7/subSubFolder/emptySubSubSubFolder"
- When user "user0" downloads zip file for entries '"welcome.txt","crowdedFolder"' in folder "/"
- Then the downloaded zip file is a zip32 file
- And the downloaded zip file contains a file named "welcome.txt" with the contents of "/welcome.txt" from "user0" data
- And the downloaded zip file contains a folder named "crowdedFolder/"
- And the downloaded zip file contains a folder named "crowdedFolder/subFolder1/"
- And the downloaded zip file contains a file named "crowdedFolder/subFolder1/test.txt-0" with the contents of "/crowdedFolder/subFolder1/test.txt-0" from "user0" data
- And the downloaded zip file contains a file named "crowdedFolder/subFolder7/test.txt-5523" with the contents of "/crowdedFolder/subFolder7/test.txt-5523" from "user0" data
- And the downloaded zip file contains a folder named "crowdedFolder/subFolder7/subSubFolder/emptySubSubSubFolder/"
-
- @large
- Scenario: downloading dir with 65525 small files and 9 nested directories returns a zip32
- Given using new dav path
- And user "user0" exists
- And user "user0" created a folder "/crowdedFolder"
- And user "user0" created a folder "/crowdedFolder/subFolder1"
- And file "/crowdedFolder/subFolder1/test.txt" is created "10000" times in "user0" user data
- And invoking occ with "files:scan --path /user0/files/crowdedFolder/subFolder1"
- And user "user0" created a folder "/crowdedFolder/subFolder2"
- And file "/crowdedFolder/subFolder2/test.txt" is created "10000" times in "user0" user data
- And invoking occ with "files:scan --path /user0/files/crowdedFolder/subFolder2"
- And user "user0" created a folder "/crowdedFolder/subFolder3"
- And file "/crowdedFolder/subFolder3/test.txt" is created "10000" times in "user0" user data
- And invoking occ with "files:scan --path /user0/files/crowdedFolder/subFolder3"
- And user "user0" created a folder "/crowdedFolder/subFolder4"
- And file "/crowdedFolder/subFolder4/test.txt" is created "10000" times in "user0" user data
- And invoking occ with "files:scan --path /user0/files/crowdedFolder/subFolder4"
- And user "user0" created a folder "/crowdedFolder/subFolder5"
- And file "/crowdedFolder/subFolder5/test.txt" is created "10000" times in "user0" user data
- And invoking occ with "files:scan --path /user0/files/crowdedFolder/subFolder5"
- And user "user0" created a folder "/crowdedFolder/subFolder6"
- And file "/crowdedFolder/subFolder6/test.txt" is created "10000" times in "user0" user data
- And invoking occ with "files:scan --path /user0/files/crowdedFolder/subFolder6"
- And user "user0" created a folder "/crowdedFolder/subFolder7"
- And file "/crowdedFolder/subFolder7/test.txt" is created "5525" times in "user0" user data
- And invoking occ with "files:scan --path /user0/files/crowdedFolder/subFolder7"
- And user "user0" created a folder "/crowdedFolder/subFolder7/subSubFolder"
- And user "user0" created a folder "/crowdedFolder/subFolder7/subSubFolder/emptySubSubSubFolder"
- When user "user0" downloads zip file for entries '"crowdedFolder"' in folder "/"
- Then the downloaded zip file is a zip32 file
- And the downloaded zip file contains a folder named "crowdedFolder/"
- And the downloaded zip file contains a folder named "crowdedFolder/subFolder1/"
- And the downloaded zip file contains a file named "crowdedFolder/subFolder1/test.txt-0" with the contents of "/crowdedFolder/subFolder1/test.txt-0" from "user0" data
- And the downloaded zip file contains a file named "crowdedFolder/subFolder7/test.txt-5524" with the contents of "/crowdedFolder/subFolder7/test.txt-5524" from "user0" data
- And the downloaded zip file contains a folder named "crowdedFolder/subFolder7/subSubFolder/emptySubSubSubFolder/"
-
- @large
- Scenario: downloading small file and dir with 65524 small files and 10 nested directories returns a zip64
- Given using new dav path
- And user "user0" exists
- And user "user0" created a folder "/crowdedFolder"
- And user "user0" created a folder "/crowdedFolder/subFolder1"
- And file "/crowdedFolder/subFolder1/test.txt" is created "10000" times in "user0" user data
- And invoking occ with "files:scan --path /user0/files/crowdedFolder/subFolder1"
- And user "user0" created a folder "/crowdedFolder/subFolder2"
- And file "/crowdedFolder/subFolder2/test.txt" is created "10000" times in "user0" user data
- And invoking occ with "files:scan --path /user0/files/crowdedFolder/subFolder2"
- And user "user0" created a folder "/crowdedFolder/subFolder3"
- And file "/crowdedFolder/subFolder3/test.txt" is created "10000" times in "user0" user data
- And invoking occ with "files:scan --path /user0/files/crowdedFolder/subFolder3"
- And user "user0" created a folder "/crowdedFolder/subFolder4"
- And file "/crowdedFolder/subFolder4/test.txt" is created "10000" times in "user0" user data
- And invoking occ with "files:scan --path /user0/files/crowdedFolder/subFolder4"
- And user "user0" created a folder "/crowdedFolder/subFolder5"
- And file "/crowdedFolder/subFolder5/test.txt" is created "10000" times in "user0" user data
- And invoking occ with "files:scan --path /user0/files/crowdedFolder/subFolder5"
- And user "user0" created a folder "/crowdedFolder/subFolder6"
- And file "/crowdedFolder/subFolder6/test.txt" is created "10000" times in "user0" user data
- And invoking occ with "files:scan --path /user0/files/crowdedFolder/subFolder6"
- And user "user0" created a folder "/crowdedFolder/subFolder7"
- And file "/crowdedFolder/subFolder7/test.txt" is created "5524" times in "user0" user data
- And invoking occ with "files:scan --path /user0/files/crowdedFolder/subFolder7"
- And user "user0" created a folder "/crowdedFolder/subFolder7/subSubFolder"
- And user "user0" created a folder "/crowdedFolder/subFolder7/subSubFolder/emptySubSubSubFolder"
- And user "user0" created a folder "/crowdedFolder/subFolder7/emptySubSubFolder"
- When user "user0" downloads zip file for entries '"welcome.txt","crowdedFolder"' in folder "/"
- Then the downloaded zip file is a zip64 file
- And the downloaded zip file contains a file named "welcome.txt" with the contents of "/welcome.txt" from "user0" data
- And the downloaded zip file contains a folder named "crowdedFolder/"
- And the downloaded zip file contains a folder named "crowdedFolder/subFolder1/"
- And the downloaded zip file contains a file named "crowdedFolder/subFolder1/test.txt-0" with the contents of "/crowdedFolder/subFolder1/test.txt-0" from "user0" data
- And the downloaded zip file contains a file named "crowdedFolder/subFolder7/test.txt-5523" with the contents of "/crowdedFolder/subFolder7/test.txt-5523" from "user0" data
- And the downloaded zip file contains a folder named "crowdedFolder/subFolder7/subSubFolder/emptySubSubSubFolder/"
- And the downloaded zip file contains a folder named "crowdedFolder/subFolder7/emptySubSubFolder/"
-
- @large
- Scenario: downloading dir with 65525 small files and 10 nested directories returns a zip64
- Given using new dav path
- And user "user0" exists
- And user "user0" created a folder "/crowdedFolder"
- And user "user0" created a folder "/crowdedFolder/subFolder1"
- And file "/crowdedFolder/subFolder1/test.txt" is created "10000" times in "user0" user data
- And invoking occ with "files:scan --path /user0/files/crowdedFolder/subFolder1"
- And user "user0" created a folder "/crowdedFolder/subFolder2"
- And file "/crowdedFolder/subFolder2/test.txt" is created "10000" times in "user0" user data
- And invoking occ with "files:scan --path /user0/files/crowdedFolder/subFolder2"
- And user "user0" created a folder "/crowdedFolder/subFolder3"
- And file "/crowdedFolder/subFolder3/test.txt" is created "10000" times in "user0" user data
- And invoking occ with "files:scan --path /user0/files/crowdedFolder/subFolder3"
- And user "user0" created a folder "/crowdedFolder/subFolder4"
- And file "/crowdedFolder/subFolder4/test.txt" is created "10000" times in "user0" user data
- And invoking occ with "files:scan --path /user0/files/crowdedFolder/subFolder4"
- And user "user0" created a folder "/crowdedFolder/subFolder5"
- And file "/crowdedFolder/subFolder5/test.txt" is created "10000" times in "user0" user data
- And invoking occ with "files:scan --path /user0/files/crowdedFolder/subFolder5"
- And user "user0" created a folder "/crowdedFolder/subFolder6"
- And file "/crowdedFolder/subFolder6/test.txt" is created "10000" times in "user0" user data
- And invoking occ with "files:scan --path /user0/files/crowdedFolder/subFolder6"
- And user "user0" created a folder "/crowdedFolder/subFolder7"
- And file "/crowdedFolder/subFolder7/test.txt" is created "5525" times in "user0" user data
- And invoking occ with "files:scan --path /user0/files/crowdedFolder/subFolder7"
- And user "user0" created a folder "/crowdedFolder/subFolder7/subSubFolder"
- And user "user0" created a folder "/crowdedFolder/subFolder7/subSubFolder/emptySubSubSubFolder"
- And user "user0" created a folder "/crowdedFolder/subFolder7/emptySubSubFolder"
- When user "user0" downloads zip file for entries '"crowdedFolder"' in folder "/"
- Then the downloaded zip file is a zip64 file
- And the downloaded zip file contains a folder named "crowdedFolder/"
- And the downloaded zip file contains a folder named "crowdedFolder/subFolder1/"
- And the downloaded zip file contains a file named "crowdedFolder/subFolder1/test.txt-0" with the contents of "/crowdedFolder/subFolder1/test.txt-0" from "user0" data
- And the downloaded zip file contains a file named "crowdedFolder/subFolder7/test.txt-5524" with the contents of "/crowdedFolder/subFolder7/test.txt-5524" from "user0" data
- And the downloaded zip file contains a folder named "crowdedFolder/subFolder7/subSubFolder/emptySubSubSubFolder/"
- And the downloaded zip file contains a folder named "crowdedFolder/subFolder7/emptySubSubFolder/"
diff --git a/build/integration/features/external-storage.feature b/build/integration/features/external-storage.feature
deleted file mode 100644
index d92cca3c458..00000000000
--- a/build/integration/features/external-storage.feature
+++ /dev/null
@@ -1,62 +0,0 @@
-Feature: external-storage
- Background:
- Given using api version "1"
- Given using old dav path
-
- @local_storage
- Scenario: Share by link a file inside a local external storage
- Given user "user0" exists
- And user "user1" exists
- And As an "user0"
- And user "user0" created a folder "/local_storage/foo"
- And User "user0" moved file "/textfile0.txt" to "/local_storage/foo/textfile0.txt"
- And folder "/local_storage/foo" of user "user0" is shared with user "user1"
- And As an "user1"
- And accepting last share
- When creating a share with
- | path | foo |
- | shareType | 3 |
- Then the OCS status code should be "100"
- And the HTTP status code should be "200"
- And Share fields of last share match with
- | id | A_NUMBER |
- | url | AN_URL |
- | token | A_TOKEN |
- | mimetype | httpd/unix-directory |
-
- Scenario: Shares don't overwrite external storage
- Given user "user0" exists
- And user "user1" exists
- And As an "user0"
- And User "user0" moved file "/textfile0.txt" to "/local_storage/textfile0.txt"
- And invoking occ with "files_external:create --user user0 test local null::null -c datadir=./build/integration/work/local_storage"
- And invoking occ with "files:scan --path /user0/files/test"
- And as "user0" the file "/local_storage/textfile0.txt" exists
- And as "user0" the folder "/test" exists
- And as "user0" the file "/test/textfile0.txt" exists
- And As an "user1"
- And user "user1" created a folder "/test"
- And User "user1" moved file "/textfile0.txt" to "/test/textfile1.txt"
- And folder "/test" of user "user1" is shared with user "user0"
- And As an "user0"
- Then as "user0" the file "/test/textfile1.txt" does not exist
-
- Scenario: Move a file into storage works
- Given user "user0" exists
- And user "user1" exists
- And As an "user0"
- And user "user0" created a folder "/local_storage/foo1"
- When User "user0" moved file "/textfile0.txt" to "/local_storage/foo1/textfile0.txt"
- Then as "user1" the file "/local_storage/foo1/textfile0.txt" exists
- And as "user0" the file "/local_storage/foo1/textfile0.txt" exists
-
- Scenario: Move a file out of the storage works
- Given user "user0" exists
- And user "user1" exists
- And As an "user0"
- And user "user0" created a folder "/local_storage/foo2"
- And User "user0" moved file "/textfile0.txt" to "/local_storage/foo2/textfile0.txt"
- When User "user1" moved file "/local_storage/foo2/textfile0.txt" to "/local.txt"
- Then as "user1" the file "/local_storage/foo2/textfile0.txt" does not exist
- And as "user0" the file "/local_storage/foo2/textfile0.txt" does not exist
- And as "user1" the file "/local.txt" exists
diff --git a/build/integration/features/favorites.feature b/build/integration/features/favorites.feature
deleted file mode 100644
index 0439ada9d60..00000000000
--- a/build/integration/features/favorites.feature
+++ /dev/null
@@ -1,149 +0,0 @@
-Feature: favorite
- Background:
- Given using api version "1"
-
- Scenario: Favorite a folder
- Given using old dav path
- And As an "admin"
- And user "user0" exists
- When user "user0" favorites element "/FOLDER"
- Then as "user0" gets properties of folder "/FOLDER" with
- |{http://owncloud.org/ns}favorite|
- And the single response should contain a property "{http://owncloud.org/ns}favorite" with value "1"
-
- Scenario: Favorite and unfavorite a folder
- Given using old dav path
- And As an "admin"
- And user "user0" exists
- When user "user0" favorites element "/FOLDER"
- And user "user0" unfavorites element "/FOLDER"
- Then as "user0" gets properties of folder "/FOLDER" with
- |{http://owncloud.org/ns}favorite|
- And the single response should contain a property "{http://owncloud.org/ns}favorite" with value "0"
-
- Scenario: Favorite a file
- Given using old dav path
- And As an "admin"
- And user "user0" exists
- When user "user0" favorites element "/textfile0.txt"
- Then as "user0" gets properties of file "/textfile0.txt" with
- |{http://owncloud.org/ns}favorite|
- And the single response should contain a property "{http://owncloud.org/ns}favorite" with value "1"
-
- Scenario: Favorite and unfavorite a file
- Given using old dav path
- And As an "admin"
- And user "user0" exists
- When user "user0" favorites element "/textfile0.txt"
- And user "user0" unfavorites element "/textfile0.txt"
- Then as "user0" gets properties of file "/textfile0.txt" with
- |{http://owncloud.org/ns}favorite|
- And the single response should contain a property "{http://owncloud.org/ns}favorite" with value "0"
-
- Scenario: Favorite a folder new endpoint
- Given using new dav path
- And As an "admin"
- And user "user0" exists
- When user "user0" favorites element "/FOLDER"
- Then as "user0" gets properties of folder "/FOLDER" with
- |{http://owncloud.org/ns}favorite|
- And the single response should contain a property "{http://owncloud.org/ns}favorite" with value "1"
-
- Scenario: Favorite and unfavorite a folder new endpoint
- Given using new dav path
- And As an "admin"
- And user "user0" exists
- When user "user0" favorites element "/FOLDER"
- And user "user0" unfavorites element "/FOLDER"
- Then as "user0" gets properties of folder "/FOLDER" with
- |{http://owncloud.org/ns}favorite|
- And the single response should contain a property "{http://owncloud.org/ns}favorite" with value "0"
-
- Scenario: Favorite a file new endpoint
- Given using new dav path
- And As an "admin"
- And user "user0" exists
- When user "user0" favorites element "/textfile0.txt"
- Then as "user0" gets properties of file "/textfile0.txt" with
- |{http://owncloud.org/ns}favorite|
- And the single response should contain a property "{http://owncloud.org/ns}favorite" with value "1"
-
- Scenario: Favorite and unfavorite a file new endpoint
- Given using new dav path
- And As an "admin"
- And user "user0" exists
- When user "user0" favorites element "/textfile0.txt"
- And user "user0" unfavorites element "/textfile0.txt"
- Then as "user0" gets properties of file "/textfile0.txt" with
- |{http://owncloud.org/ns}favorite|
- And the single response should contain a property "{http://owncloud.org/ns}favorite" with value "0"
-
- Scenario: Get favorited elements of a folder
- Given using old dav path
- And As an "admin"
- And user "user0" exists
- When user "user0" favorites element "/FOLDER"
- And user "user0" favorites element "/textfile0.txt"
- And user "user0" favorites element "/textfile1.txt"
- Then user "user0" in folder "/" should have favorited the following elements
- | /FOLDER |
- | /textfile0.txt |
- | /textfile1.txt |
-
- Scenario: Get favorited elements of a folder using new path
- Given using new dav path
- And As an "admin"
- And user "user0" exists
- When user "user0" favorites element "/FOLDER"
- And user "user0" favorites element "/textfile0.txt"
- And user "user0" favorites element "/textfile1.txt"
- Then user "user0" in folder "/" should have favorited the following elements
- | /FOLDER |
- | /textfile0.txt |
- | /textfile1.txt |
-
- Scenario: Get favorited elements of a subfolder
- Given using old dav path
- And As an "admin"
- And user "user0" exists
- And user "user0" created a folder "/subfolder"
- And User "user0" moves file "/textfile0.txt" to "/subfolder/textfile0.txt"
- And User "user0" moves file "/textfile1.txt" to "/subfolder/textfile1.txt"
- And User "user0" moves file "/textfile2.txt" to "/subfolder/textfile2.txt"
- When user "user0" favorites element "/subfolder/textfile0.txt"
- And user "user0" favorites element "/subfolder/textfile1.txt"
- And user "user0" favorites element "/subfolder/textfile2.txt"
- And user "user0" unfavorites element "/subfolder/textfile1.txt"
- Then user "user0" in folder "/subfolder" should have favorited the following elements
- | /subfolder/textfile0.txt |
- | /subfolder/textfile2.txt |
-
- Scenario: Get favorited elements of a subfolder using new path
- Given using old dav path
- And As an "admin"
- And user "user0" exists
- And user "user0" created a folder "/subfolder"
- And User "user0" moves file "/textfile0.txt" to "/subfolder/textfile0.txt"
- And User "user0" moves file "/textfile1.txt" to "/subfolder/textfile1.txt"
- And User "user0" moves file "/textfile2.txt" to "/subfolder/textfile2.txt"
- When user "user0" favorites element "/subfolder/textfile0.txt"
- And user "user0" favorites element "/subfolder/textfile1.txt"
- And user "user0" favorites element "/subfolder/textfile2.txt"
- And user "user0" unfavorites element "/subfolder/textfile1.txt"
- Then user "user0" in folder "/subfolder" should have favorited the following elements
- | /subfolder/textfile0.txt |
- | /subfolder/textfile2.txt |
-
- Scenario: moving a favorite file out of a share keeps favorite state
- Given using old dav path
- And As an "admin"
- And user "user0" exists
- And user "user1" exists
- And user "user0" created a folder "/shared"
- And User "user0" moved file "/textfile0.txt" to "/shared/shared_file.txt"
- And folder "/shared" of user "user0" is shared with user "user1"
- And user "user1" accepts last share
- And user "user1" favorites element "/shared/shared_file.txt"
- When User "user1" moved file "/shared/shared_file.txt" to "/taken_out.txt"
- Then user "user1" in folder "/" should have favorited the following elements
- | /taken_out.txt |
diff --git a/build/integration/features/log-condition.feature b/build/integration/features/log-condition.feature
new file mode 100644
index 00000000000..4059db1ebf3
--- /dev/null
+++ b/build/integration/features/log-condition.feature
@@ -0,0 +1,39 @@
+# SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+# SPDX-License-Identifier: AGPL-3.0-or-later
+Feature: log-condition
+
+ Background:
+ Given invoking occ with "config:system:set log.condition matches 0 users 0 --value admin"
+ Then the command was successful
+
+ Scenario: Accessing /status.php with log.condition
+ When requesting "/status.php" with "GET"
+ Then the HTTP status code should be "200"
+
+ Scenario: Accessing /index.php with log.condition
+ When requesting "/index.php" with "GET"
+ Then the HTTP status code should be "200"
+
+ Scenario: Accessing /remote.php/webdav with log.condition
+ When requesting "/remote.php/webdav" with "GET"
+ Then the HTTP status code should be "401"
+
+ Scenario: Accessing /remote.php/dav with log.condition
+ When requesting "/remote.php/dav" with "GET"
+ Then the HTTP status code should be "401"
+
+ Scenario: Accessing /ocs/v1.php with log.condition
+ When requesting "/ocs/v1.php" with "GET"
+ Then the HTTP status code should be "200"
+
+ Scenario: Accessing /ocs/v2.php with log.condition
+ When requesting "/ocs/v2.php" with "GET"
+ Then the HTTP status code should be "404"
+
+ Scenario: Accessing /public.php/webdav with log.condition
+ When requesting "/public.php/webdav" with "GET"
+ Then the HTTP status code should be "401"
+
+ Scenario: Accessing /public.php/dav with log.condition
+ When requesting "/public.php/dav" with "GET"
+ Then the HTTP status code should be "503"
diff --git a/build/integration/features/maintenance-mode.feature b/build/integration/features/maintenance-mode.feature
index 56d3b9c0fb6..72af31f193f 100644
--- a/build/integration/features/maintenance-mode.feature
+++ b/build/integration/features/maintenance-mode.feature
@@ -1,3 +1,5 @@
+# SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
+# SPDX-License-Identifier: AGPL-3.0-or-later
Feature: maintenance-mode
Background:
@@ -39,3 +41,9 @@ Feature: maintenance-mode
Then the HTTP status code should be "503"
Then Maintenance mode is disabled
And the command was successful
+
+ Scenario: Accessing /public.php/dav with maintenance mode enabled
+ When requesting "/public.php/dav" with "GET"
+ Then the HTTP status code should be "503"
+ Then Maintenance mode is disabled
+ And the command was successful
diff --git a/build/integration/features/ocs-v1.feature b/build/integration/features/ocs-v1.feature
index 6075189ddb4..26907580aee 100644
--- a/build/integration/features/ocs-v1.feature
+++ b/build/integration/features/ocs-v1.feature
@@ -1,3 +1,5 @@
+# SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
+# SPDX-License-Identifier: AGPL-3.0-or-later
Feature: ocs
Background:
Given using api version "1"
diff --git a/build/integration/features/provisioning-v1.feature b/build/integration/features/provisioning-v1.feature
index d34e1bceb6a..8fcfb076497 100644
--- a/build/integration/features/provisioning-v1.feature
+++ b/build/integration/features/provisioning-v1.feature
@@ -1,60 +1,66 @@
+# SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+# SPDX-FileCopyrightText: 2015-2016 ownCloud, Inc.
+# SPDX-License-Identifier: AGPL-3.0-only
Feature: provisioning
- Background:
- Given using api version "1"
-
- Scenario: Getting an not existing user
- Given As an "admin"
- When sending "GET" to "/cloud/users/test"
- Then the OCS status code should be "404"
- And the HTTP status code should be "200"
-
- Scenario: Listing all users
- Given As an "admin"
- When sending "GET" to "/cloud/users"
- Then the OCS status code should be "100"
- And the HTTP status code should be "200"
-
- Scenario: Create a user
- Given As an "admin"
- And user "brand-new-user" does not exist
- When sending "POST" to "/cloud/users" with
- | userid | brand-new-user |
- | password | 123456 |
- Then the OCS status code should be "100"
- And the HTTP status code should be "200"
- And user "brand-new-user" exists
-
- Scenario: Create an existing user
- Given As an "admin"
- And user "brand-new-user" exists
- When sending "POST" to "/cloud/users" with
- | userid | brand-new-user |
- | password | 123456 |
- Then the OCS status code should be "102"
- And the HTTP status code should be "200"
- And user "brand-new-user" has
- | id | brand-new-user |
- | displayname | brand-new-user |
- | email | |
- | phone | |
- | address | |
- | website | |
- | twitter | |
-
- Scenario: Get an existing user
- Given As an "admin"
- When sending "GET" to "/cloud/users/brand-new-user"
- Then the OCS status code should be "100"
- And the HTTP status code should be "200"
-
- Scenario: Getting all users
- Given As an "admin"
- And user "brand-new-user" exists
- And user "admin" exists
- When sending "GET" to "/cloud/users"
- Then users returned are
- | brand-new-user |
- | admin |
+ Background:
+ Given using api version "1"
+ Given parameter "whitelist_0" of app "bruteForce" is set to "127.0.0.1"
+ Given parameter "whitelist_1" of app "bruteForce" is set to "::1"
+ Given parameter "apply_allowlist_to_ratelimit" of app "bruteforcesettings" is set to "true"
+
+ Scenario: Getting an not existing user
+ Given As an "admin"
+ When sending "GET" to "/cloud/users/test"
+ Then the OCS status code should be "404"
+ And the HTTP status code should be "200"
+
+ Scenario: Listing all users
+ Given As an "admin"
+ When sending "GET" to "/cloud/users"
+ Then the OCS status code should be "100"
+ And the HTTP status code should be "200"
+
+ Scenario: Create a user
+ Given As an "admin"
+ And user "brand-new-user" does not exist
+ When sending "POST" to "/cloud/users" with
+ | userid | brand-new-user |
+ | password | 123456 |
+ Then the OCS status code should be "100"
+ And the HTTP status code should be "200"
+ And user "brand-new-user" exists
+
+ Scenario: Create an existing user
+ Given As an "admin"
+ And user "brand-new-user" exists
+ When sending "POST" to "/cloud/users" with
+ | userid | brand-new-user |
+ | password | 123456 |
+ Then the OCS status code should be "102"
+ And the HTTP status code should be "200"
+ And user "brand-new-user" has
+ | id | brand-new-user |
+ | displayname | brand-new-user |
+ | email | |
+ | phone | |
+ | address | |
+ | website | |
+ | twitter | |
+
+ Scenario: Get an existing user
+ Given As an "admin"
+ When sending "GET" to "/cloud/users/brand-new-user"
+ Then the OCS status code should be "100"
+ And the HTTP status code should be "200"
+
+ Scenario: Getting all users
+ Given As an "admin"
+ And user "brand-new-user" exists
+ And user "admin" exists
+ When sending "GET" to "/cloud/users"
+ Then users returned are
+ | brand-new-user |
+ | admin |
Scenario: Get editable fields
Given As an "admin"
@@ -66,13 +72,15 @@ Feature: provisioning
| phone |
| address |
| website |
- | twitter |
+ | twitter |
+ | bluesky |
| fediverse |
| organisation |
| role |
| headline |
| biography |
| profile_enabled |
+ | pronouns |
Given As an "brand-new-user"
Then user "brand-new-user" has editable fields
| displayname |
@@ -82,12 +90,14 @@ Feature: provisioning
| address |
| website |
| twitter |
+ | bluesky |
| fediverse |
| organisation |
| role |
| headline |
| biography |
| profile_enabled |
+ | pronouns |
Then user "self" has editable fields
| displayname |
| email |
@@ -96,31 +106,33 @@ Feature: provisioning
| address |
| website |
| twitter |
+ | bluesky |
| fediverse |
| organisation |
| role |
| headline |
| biography |
| profile_enabled |
+ | pronouns |
- Scenario: Edit a user
- Given As an "admin"
- And user "brand-new-user" exists
- When sending "PUT" to "/cloud/users/brand-new-user" with
- | key | displayname |
- | value | Brand New User |
- And the OCS status code should be "100"
- And the HTTP status code should be "200"
- And sending "PUT" to "/cloud/users/brand-new-user" with
- | key | quota |
- | value | 12MB |
- And the OCS status code should be "100"
- And the HTTP status code should be "200"
- And sending "PUT" to "/cloud/users/brand-new-user" with
- | key | email |
- | value | no-reply@nextcloud.com |
- And the OCS status code should be "100"
- And the HTTP status code should be "200"
+ Scenario: Edit a user
+ Given As an "admin"
+ And user "brand-new-user" exists
+ When sending "PUT" to "/cloud/users/brand-new-user" with
+ | key | displayname |
+ | value | Brand New User |
+ And the OCS status code should be "100"
+ And the HTTP status code should be "200"
+ And sending "PUT" to "/cloud/users/brand-new-user" with
+ | key | quota |
+ | value | 12MB |
+ And the OCS status code should be "100"
+ And the HTTP status code should be "200"
+ And sending "PUT" to "/cloud/users/brand-new-user" with
+ | key | email |
+ | value | no-reply@nextcloud.com |
+ And the OCS status code should be "100"
+ And the HTTP status code should be "200"
And sending "PUT" to "/cloud/users/brand-new-user" with
| key | additional_mail |
| value | no.reply@nextcloud.com |
@@ -131,104 +143,147 @@ Feature: provisioning
| value | noreply@nextcloud.com |
And the OCS status code should be "100"
And the HTTP status code should be "200"
- And sending "PUT" to "/cloud/users/brand-new-user" with
- | key | phone |
- | value | +49 711 / 25 24 28-90 |
- And the OCS status code should be "100"
- And the HTTP status code should be "200"
- And sending "PUT" to "/cloud/users/brand-new-user" with
- | key | address |
- | value | Foo Bar Town |
- And the OCS status code should be "100"
- And the HTTP status code should be "200"
- And sending "PUT" to "/cloud/users/brand-new-user" with
- | key | website |
- | value | https://nextcloud.com |
- And the OCS status code should be "100"
- And the HTTP status code should be "200"
- And sending "PUT" to "/cloud/users/brand-new-user" with
- | key | twitter |
- | value | Nextcloud |
- And the OCS status code should be "100"
- And the HTTP status code should be "200"
- Then user "brand-new-user" has
- | id | brand-new-user |
- | displayname | Brand New User |
- | email | no-reply@nextcloud.com |
+ And sending "PUT" to "/cloud/users/brand-new-user" with
+ | key | phone |
+ | value | +49 711 / 25 24 28-90 |
+ And the OCS status code should be "100"
+ And the HTTP status code should be "200"
+ And sending "PUT" to "/cloud/users/brand-new-user" with
+ | key | address |
+ | value | Foo Bar Town |
+ And the OCS status code should be "100"
+ And the HTTP status code should be "200"
+ And sending "PUT" to "/cloud/users/brand-new-user" with
+ | key | website |
+ | value | https://nextcloud.com |
+ And the OCS status code should be "100"
+ And the HTTP status code should be "200"
+ And sending "PUT" to "/cloud/users/brand-new-user" with
+ | key | twitter |
+ | value | Nextcloud |
+ And sending "PUT" to "/cloud/users/brand-new-user" with
+ | key | bluesky |
+ | value | nextcloud.bsky.social |
+ And the OCS status code should be "100"
+ And the HTTP status code should be "200"
+ Then user "brand-new-user" has
+ | id | brand-new-user |
+ | displayname | Brand New User |
+ | email | no-reply@nextcloud.com |
| additional_mail | no.reply@nextcloud.com;noreply@nextcloud.com |
- | phone | +4971125242890 |
- | address | Foo Bar Town |
- | website | https://nextcloud.com |
- | twitter | Nextcloud |
+ | phone | +4971125242890 |
+ | address | Foo Bar Town |
+ | website | https://nextcloud.com |
+ | twitter | Nextcloud |
+ | bluesky | nextcloud.bsky.social |
+ And sending "PUT" to "/cloud/users/brand-new-user" with
+ | key | organisation |
+ | value | Nextcloud GmbH |
+ And sending "PUT" to "/cloud/users/brand-new-user" with
+ | key | role |
+ | value | Engineer |
+ And the OCS status code should be "100"
+ And the HTTP status code should be "200"
+ Then user "brand-new-user" has the following profile data
+ | userId | brand-new-user |
+ | displayname | Brand New User |
+ | organisation | Nextcloud GmbH |
+ | role | Engineer |
+ | address | Foo Bar Town |
+ | timezone | UTC |
+ | timezoneOffset | 0 |
+ | pronouns | NULL |
+
+ Scenario: Edit a user with mixed case emails
+ Given As an "admin"
+ And user "brand-new-user" exists
+ And sending "PUT" to "/cloud/users/brand-new-user" with
+ | key | email |
+ | value | mixed-CASE@Nextcloud.com |
+ And the OCS status code should be "100"
+ And the HTTP status code should be "200"
+ Then user "brand-new-user" has
+ | id | brand-new-user |
+ | email | mixed-case@nextcloud.com |
- Scenario: Edit a user account properties scopes
- Given user "brand-new-user" exists
+ Scenario: Edit a user account properties scopes
+ Given user "brand-new-user" exists
And As an "brand-new-user"
- When sending "PUT" to "/cloud/users/brand-new-user" with
- | key | phoneScope |
- | value | v2-private |
- Then the OCS status code should be "100"
- And the HTTP status code should be "200"
- When sending "PUT" to "/cloud/users/brand-new-user" with
- | key | twitterScope |
- | value | v2-local |
- Then the OCS status code should be "100"
- And the HTTP status code should be "200"
- When sending "PUT" to "/cloud/users/brand-new-user" with
- | key | addressScope |
- | value | v2-federated |
- Then the OCS status code should be "100"
- And the HTTP status code should be "200"
- When sending "PUT" to "/cloud/users/brand-new-user" with
- | key | emailScope |
- | value | v2-published |
- Then the OCS status code should be "100"
- And the HTTP status code should be "200"
- When sending "PUT" to "/cloud/users/brand-new-user" with
- | key | websiteScope |
- | value | public |
- Then the OCS status code should be "100"
- And the HTTP status code should be "200"
- When sending "PUT" to "/cloud/users/brand-new-user" with
- | key | displaynameScope |
- | value | contacts |
- Then the OCS status code should be "100"
- And the HTTP status code should be "200"
- When sending "PUT" to "/cloud/users/brand-new-user" with
- | key | avatarScope |
- | value | private |
- Then the OCS status code should be "100"
- And the HTTP status code should be "200"
- Then user "brand-new-user" has
- | id | brand-new-user |
- | phoneScope | v2-private |
- | twitterScope | v2-local |
- | addressScope | v2-federated |
- | emailScope | v2-published |
- | websiteScope | v2-published |
- | displaynameScope | v2-federated |
- | avatarScope | v2-local |
+ When sending "PUT" to "/cloud/users/brand-new-user" with
+ | key | phoneScope |
+ | value | v2-private |
+ Then the OCS status code should be "100"
+ And the HTTP status code should be "200"
+ When sending "PUT" to "/cloud/users/brand-new-user" with
+ | key | twitterScope |
+ | value | v2-local |
+ Then the OCS status code should be "100"
+ And the HTTP status code should be "200"
+ When sending "PUT" to "/cloud/users/brand-new-user" with
+ | key | blueskyScope |
+ | value | v2-local |
+ Then the OCS status code should be "100"
+ And the HTTP status code should be "200"
+ When sending "PUT" to "/cloud/users/brand-new-user" with
+ | key | addressScope |
+ | value | v2-federated |
+ Then the OCS status code should be "100"
+ And the HTTP status code should be "200"
+ When sending "PUT" to "/cloud/users/brand-new-user" with
+ | key | emailScope |
+ | value | v2-published |
+ Then the OCS status code should be "100"
+ And the HTTP status code should be "200"
+ And sending "PUT" to "/cloud/users/brand-new-user" with
+ | key | email |
+ | value | no-reply@nextcloud.com |
+ And the OCS status code should be "100"
+ And the HTTP status code should be "200"
+ # Duplicating primary address
+ And sending "PUT" to "/cloud/users/brand-new-user" with
+ | key | additional_mail |
+ | value | no-reply@nextcloud.com |
+ And the OCS status code should be "101"
+ And the HTTP status code should be "200"
+ And sending "PUT" to "/cloud/users/brand-new-user" with
+ | key | additional_mail |
+ | value | no.reply2@nextcloud.com |
+ And the OCS status code should be "100"
+ And the HTTP status code should be "200"
+ # Duplicating another additional address
+ And sending "PUT" to "/cloud/users/brand-new-user" with
+ | key | additional_mail |
+ | value | no.reply2@nextcloud.com |
+ And the OCS status code should be "101"
+ And the HTTP status code should be "200"
+ Then user "brand-new-user" has
+ | id | brand-new-user |
+ | phoneScope | v2-private |
+ | twitterScope | v2-local |
+ | blueskyScope | v2-local |
+ | addressScope | v2-federated |
+ | emailScope | v2-published |
Scenario: Edit a user account multivalue property scopes
Given user "brand-new-user" exists
And As an "brand-new-user"
When sending "PUT" to "/cloud/users/brand-new-user" with
| key | additional_mail |
- | value | no.reply@nextcloud.com |
+ | value | no.reply3@nextcloud.com |
And the OCS status code should be "100"
And the HTTP status code should be "200"
And sending "PUT" to "/cloud/users/brand-new-user" with
| key | additional_mail |
- | value | noreply@nextcloud.com |
+ | value | noreply4@nextcloud.com |
And the OCS status code should be "100"
And the HTTP status code should be "200"
When sending "PUT" to "/cloud/users/brand-new-user/additional_mailScope" with
- | key | no.reply@nextcloud.com |
+ | key | no.reply3@nextcloud.com |
| value | v2-federated |
Then the OCS status code should be "100"
And the HTTP status code should be "200"
When sending "PUT" to "/cloud/users/brand-new-user/additional_mailScope" with
- | key | noreply@nextcloud.com |
+ | key | noreply4@nextcloud.com |
| value | v2-published |
Then the OCS status code should be "100"
And the HTTP status code should be "200"
@@ -236,35 +291,35 @@ Feature: provisioning
| id | brand-new-user |
| additional_mailScope | v2-federated;v2-published |
- Scenario: Edit a user account properties scopes with invalid or unsupported value
- Given user "brand-new-user" exists
+ Scenario: Edit a user account properties scopes with invalid or unsupported value
+ Given user "brand-new-user" exists
And As an "brand-new-user"
- When sending "PUT" to "/cloud/users/brand-new-user" with
- | key | phoneScope |
- | value | invalid |
- Then the OCS status code should be "102"
- And the HTTP status code should be "200"
- When sending "PUT" to "/cloud/users/brand-new-user" with
- | key | displaynameScope |
- | value | v2-private |
- Then the OCS status code should be "102"
- And the HTTP status code should be "200"
- When sending "PUT" to "/cloud/users/brand-new-user" with
- | key | emailScope |
- | value | v2-private |
- Then the OCS status code should be "102"
- And the HTTP status code should be "200"
+ When sending "PUT" to "/cloud/users/brand-new-user" with
+ | key | phoneScope |
+ | value | invalid |
+ Then the OCS status code should be "101"
+ And the HTTP status code should be "200"
+ When sending "PUT" to "/cloud/users/brand-new-user" with
+ | key | displaynameScope |
+ | value | v2-private |
+ Then the OCS status code should be "101"
+ And the HTTP status code should be "200"
+ When sending "PUT" to "/cloud/users/brand-new-user" with
+ | key | emailScope |
+ | value | v2-private |
+ Then the OCS status code should be "101"
+ And the HTTP status code should be "200"
Scenario: Edit a user account multi-value property scopes with invalid or unsupported value
Given user "brand-new-user" exists
And As an "brand-new-user"
When sending "PUT" to "/cloud/users/brand-new-user" with
| key | additional_mail |
- | value | no.reply@nextcloud.com |
+ | value | no.reply5@nextcloud.com |
And the OCS status code should be "100"
And the HTTP status code should be "200"
When sending "PUT" to "/cloud/users/brand-new-user/additional_mailScope" with
- | key | no.reply@nextcloud.com |
+ | key | no.reply5@nextcloud.com |
| value | invalid |
Then the OCS status code should be "102"
And the HTTP status code should be "200"
@@ -274,556 +329,564 @@ Feature: provisioning
And As an "brand-new-user"
When sending "PUT" to "/cloud/users/brand-new-user" with
| key | additional_mail |
- | value | no.reply@nextcloud.com |
+ | value | no.reply6@nextcloud.com |
And the OCS status code should be "100"
And the HTTP status code should be "200"
And sending "PUT" to "/cloud/users/brand-new-user" with
| key | additional_mail |
- | value | noreply@nextcloud.com |
+ | value | noreply7@nextcloud.com |
And the OCS status code should be "100"
And the HTTP status code should be "200"
When sending "PUT" to "/cloud/users/brand-new-user/additional_mail" with
- | key | no.reply@nextcloud.com |
+ | key | no.reply6@nextcloud.com |
| value | |
And the OCS status code should be "100"
And the HTTP status code should be "200"
Then user "brand-new-user" has
- | additional_mail | noreply@nextcloud.com |
+ | additional_mail | noreply7@nextcloud.com |
Then user "brand-new-user" has not
- | additional_mail | no.reply@nextcloud.com |
-
- Scenario: An admin cannot edit user account property scopes
- Given As an "admin"
- And user "brand-new-user" exists
- When sending "PUT" to "/cloud/users/brand-new-user" with
- | key | phoneScope |
- | value | v2-private |
- Then the OCS status code should be "103"
- And the HTTP status code should be "200"
-
- Scenario: Search by phone number
- Given As an "admin"
- And user "phone-user" exists
- And sending "PUT" to "/cloud/users/phone-user" with
- | key | phone |
- | value | +49 711 / 25 24 28-90 |
- And the OCS status code should be "100"
- And the HTTP status code should be "200"
- Then search users by phone for region "DE" with
- | random-string1 | 0711 / 123 456 78 |
- | random-string1 | 0711 / 252 428-90 |
- | random-string2 | 0711 / 90-824 252 |
- And the OCS status code should be "100"
- And the HTTP status code should be "200"
- Then phone matches returned are
- | random-string1 | phone-user@localhost:8080 |
-
- Scenario: Create a group
- Given As an "admin"
- And group "new-group" does not exist
- When sending "POST" to "/cloud/groups" with
- | groupid | new-group |
- | password | 123456 |
- Then the OCS status code should be "100"
- And the HTTP status code should be "200"
- And group "new-group" exists
- And group "new-group" has
- | displayname | new-group |
-
- Scenario: Create a group with custom display name
- Given As an "admin"
- And group "new-group" does not exist
- When sending "POST" to "/cloud/groups" with
- | groupid | new-group |
- | password | 123456 |
- | displayname | new-group-displayname |
- Then the OCS status code should be "100"
- And the HTTP status code should be "200"
- And group "new-group" exists
- And group "new-group" has
- | displayname | new-group-displayname |
-
- Scenario: Create a group with special characters
- Given As an "admin"
- And group "España" does not exist
- When sending "POST" to "/cloud/groups" with
- | groupid | España |
- | password | 123456 |
- Then the OCS status code should be "100"
- And the HTTP status code should be "200"
- And group "España" exists
- And group "España" has
- | displayname | España |
-
- Scenario: adding user to a group without sending the group
- Given As an "admin"
- And user "brand-new-user" exists
- When sending "POST" to "/cloud/users/brand-new-user/groups" with
- | groupid | |
- Then the OCS status code should be "101"
- And the HTTP status code should be "200"
-
- Scenario: adding user to a group which doesn't exist
- Given As an "admin"
- And user "brand-new-user" exists
- And group "not-group" does not exist
- When sending "POST" to "/cloud/users/brand-new-user/groups" with
- | groupid | not-group |
- Then the OCS status code should be "102"
- And the HTTP status code should be "200"
-
- Scenario: adding user to a group without privileges
- Given user "brand-new-user" exists
- And As an "brand-new-user"
- When sending "POST" to "/cloud/users/brand-new-user/groups" with
- | groupid | new-group |
- Then the OCS status code should be "403"
- And the HTTP status code should be "200"
-
- Scenario: adding user to a group
- Given As an "admin"
- And user "brand-new-user" exists
- And group "new-group" exists
- When sending "POST" to "/cloud/users/brand-new-user/groups" with
- | groupid | new-group |
- Then the OCS status code should be "100"
- And the HTTP status code should be "200"
-
- Scenario: getting groups of an user
- Given As an "admin"
- And user "brand-new-user" exists
- And group "new-group" exists
- When sending "GET" to "/cloud/users/brand-new-user/groups"
- Then groups returned are
- | new-group |
- And the OCS status code should be "100"
-
- Scenario: adding a user which doesn't exist to a group
- Given As an "admin"
- And user "not-user" does not exist
- And group "new-group" exists
- When sending "POST" to "/cloud/users/not-user/groups" with
- | groupid | new-group |
- Then the OCS status code should be "103"
- And the HTTP status code should be "200"
-
- Scenario: getting a group
- Given As an "admin"
- And group "new-group" exists
- When sending "GET" to "/cloud/groups/new-group"
- Then the OCS status code should be "100"
- And the HTTP status code should be "200"
-
- Scenario: Getting all groups
- Given As an "admin"
- And group "new-group" exists
- And group "admin" exists
- When sending "GET" to "/cloud/groups"
- Then groups returned are
- | España |
- | admin |
- | new-group |
-
- Scenario: create a subadmin
- Given As an "admin"
- And user "brand-new-user" exists
- And group "new-group" exists
- When sending "POST" to "/cloud/users/brand-new-user/subadmins" with
- | groupid | new-group |
- Then the OCS status code should be "100"
- And the HTTP status code should be "200"
-
- Scenario: get users using a subadmin
- Given As an "admin"
- And user "brand-new-user" exists
- And group "new-group" exists
- And user "brand-new-user" belongs to group "new-group"
- And user "brand-new-user" is subadmin of group "new-group"
- And As an "brand-new-user"
- When sending "GET" to "/cloud/users"
- Then users returned are
- | brand-new-user |
- And the OCS status code should be "100"
- And the HTTP status code should be "200"
-
- Scenario: removing a user from a group which doesn't exists
- Given As an "admin"
- And user "brand-new-user" exists
- And group "not-group" does not exist
- When sending "DELETE" to "/cloud/users/brand-new-user/groups" with
- | groupid | not-group |
- Then the OCS status code should be "102"
-
- Scenario: removing a user from a group
- Given As an "admin"
- And user "brand-new-user" exists
- And group "new-group" exists
- And user "brand-new-user" belongs to group "new-group"
- When sending "DELETE" to "/cloud/users/brand-new-user/groups" with
- | groupid | new-group |
- Then the OCS status code should be "100"
- And user "brand-new-user" does not belong to group "new-group"
-
- Scenario: create a subadmin using a user which not exist
- Given As an "admin"
- And user "not-user" does not exist
- And group "new-group" exists
- When sending "POST" to "/cloud/users/not-user/subadmins" with
- | groupid | new-group |
- Then the OCS status code should be "101"
- And the HTTP status code should be "200"
-
- Scenario: create a subadmin using a group which not exist
- Given As an "admin"
- And user "brand-new-user" exists
- And group "not-group" does not exist
- When sending "POST" to "/cloud/users/brand-new-user/subadmins" with
- | groupid | not-group |
- Then the OCS status code should be "102"
- And the HTTP status code should be "200"
-
- Scenario: Getting subadmin groups
- Given As an "admin"
- And user "brand-new-user" exists
- And group "new-group" exists
- When sending "GET" to "/cloud/users/brand-new-user/subadmins"
- Then subadmin groups returned are
- | new-group |
- Then the OCS status code should be "100"
- And the HTTP status code should be "200"
-
- Scenario: Getting subadmin groups of a user which not exist
- Given As an "admin"
- And user "not-user" does not exist
- And group "new-group" exists
- When sending "GET" to "/cloud/users/not-user/subadmins"
- Then the OCS status code should be "404"
- And the HTTP status code should be "200"
-
- Scenario: Getting subadmin users of a group
- Given As an "admin"
- And user "brand-new-user" exists
- And group "new-group" exists
- When sending "GET" to "/cloud/groups/new-group/subadmins"
- Then subadmin users returned are
- | brand-new-user |
- And the OCS status code should be "100"
- And the HTTP status code should be "200"
-
- Scenario: Getting subadmin users of a group which doesn't exist
- Given As an "admin"
- And user "brand-new-user" exists
- And group "not-group" does not exist
- When sending "GET" to "/cloud/groups/not-group/subadmins"
- Then the OCS status code should be "101"
- And the HTTP status code should be "200"
-
- Scenario: Removing subadmin from a group
- Given As an "admin"
- And user "brand-new-user" exists
- And group "new-group" exists
- And user "brand-new-user" is subadmin of group "new-group"
- When sending "DELETE" to "/cloud/users/brand-new-user/subadmins" with
- | groupid | new-group |
- And the OCS status code should be "100"
- And the HTTP status code should be "200"
-
- Scenario: Delete a user
- Given As an "admin"
- And user "brand-new-user" exists
- When sending "DELETE" to "/cloud/users/brand-new-user"
- Then the OCS status code should be "100"
- And the HTTP status code should be "200"
- And user "brand-new-user" does not exist
-
- Scenario: Delete a group
- Given As an "admin"
- And group "new-group" exists
- When sending "DELETE" to "/cloud/groups/new-group"
- Then the OCS status code should be "100"
- And the HTTP status code should be "200"
- And group "new-group" does not exist
-
- Scenario: Delete a group with special characters
- Given As an "admin"
- And group "España" exists
- When sending "DELETE" to "/cloud/groups/España"
- Then the OCS status code should be "100"
- And the HTTP status code should be "200"
- And group "España" does not exist
-
- Scenario: get enabled apps
- Given As an "admin"
- When sending "GET" to "/cloud/apps?filter=enabled"
- Then the OCS status code should be "100"
- And the HTTP status code should be "200"
- And apps returned are
- | cloud_federation_api |
- | comments |
- | contactsinteraction |
- | dashboard |
- | dav |
- | federatedfilesharing |
- | federation |
- | files |
- | files_sharing |
- | files_trashbin |
- | files_versions |
- | lookup_server_connector |
- | provisioning_api |
- | settings |
- | sharebymail |
- | systemtags |
- | theming |
- | twofactor_backupcodes |
- | updatenotification |
- | user_ldap |
- | user_status |
- | viewer |
- | workflowengine |
- | weather_status |
- | files_external |
- | oauth2 |
-
- Scenario: get app info
- Given As an "admin"
- When sending "GET" to "/cloud/apps/files"
- Then the OCS status code should be "100"
- And the HTTP status code should be "200"
-
- Scenario: get app info from app that does not exist
- Given As an "admin"
- When sending "GET" to "/cloud/apps/this_app_should_never_exist"
- Then the OCS status code should be "998"
- And the HTTP status code should be "200"
-
- Scenario: enable an app
- Given As an "admin"
- And app "testing" is disabled
- When sending "POST" to "/cloud/apps/testing"
- Then the OCS status code should be "100"
- And the HTTP status code should be "200"
- And app "testing" is enabled
-
- Scenario: enable an app that does not exist
- Given As an "admin"
- When sending "POST" to "/cloud/apps/this_app_should_never_exist"
- Then the OCS status code should be "998"
- And the HTTP status code should be "200"
-
- Scenario: disable an app
- Given As an "admin"
- And app "testing" is enabled
- When sending "DELETE" to "/cloud/apps/testing"
- Then the OCS status code should be "100"
- And the HTTP status code should be "200"
- And app "testing" is disabled
-
- Scenario: disable an user
- Given As an "admin"
- And user "user1" exists
- When sending "PUT" to "/cloud/users/user1/disable"
- Then the OCS status code should be "100"
- And the HTTP status code should be "200"
- And user "user1" is disabled
-
- Scenario: enable an user
- Given As an "admin"
- And user "user1" exists
- And assure user "user1" is disabled
- When sending "PUT" to "/cloud/users/user1/enable"
- Then the OCS status code should be "100"
- And the HTTP status code should be "200"
- And user "user1" is enabled
-
- Scenario: Subadmin should be able to enable or disable an user in their group
- Given As an "admin"
- And user "subadmin" exists
- And user "user1" exists
- And group "new-group" exists
- And user "subadmin" belongs to group "new-group"
- And user "user1" belongs to group "new-group"
- And Assure user "subadmin" is subadmin of group "new-group"
- And As an "subadmin"
- When sending "PUT" to "/cloud/users/user1/disable"
- Then the OCS status code should be "100"
- Then the HTTP status code should be "200"
- And As an "admin"
- And user "user1" is disabled
-
- Scenario: Subadmin should not be able to enable or disable an user not in their group
- Given As an "admin"
- And user "subadmin" exists
- And user "user1" exists
- And group "new-group" exists
- And group "another-group" exists
- And user "subadmin" belongs to group "new-group"
- And user "user1" belongs to group "another-group"
- And Assure user "subadmin" is subadmin of group "new-group"
- And As an "subadmin"
- When sending "PUT" to "/cloud/users/user1/disable"
- Then the OCS status code should be "998"
- Then the HTTP status code should be "200"
- And As an "admin"
- And user "user1" is enabled
-
- Scenario: Subadmins should not be able to disable users that have admin permissions in their group
- Given As an "admin"
- And user "another-admin" exists
- And user "subadmin" exists
- And group "new-group" exists
- And user "another-admin" belongs to group "admin"
- And user "subadmin" belongs to group "new-group"
- And user "another-admin" belongs to group "new-group"
- And Assure user "subadmin" is subadmin of group "new-group"
- And As an "subadmin"
- When sending "PUT" to "/cloud/users/another-admin/disable"
- Then the OCS status code should be "998"
- Then the HTTP status code should be "200"
- And As an "admin"
- And user "another-admin" is enabled
-
- Scenario: Admin can disable another admin user
- Given As an "admin"
- And user "another-admin" exists
- And user "another-admin" belongs to group "admin"
- When sending "PUT" to "/cloud/users/another-admin/disable"
- Then the OCS status code should be "100"
- Then the HTTP status code should be "200"
- And user "another-admin" is disabled
-
- Scenario: Admin can enable another admin user
- Given As an "admin"
- And user "another-admin" exists
- And user "another-admin" belongs to group "admin"
- And assure user "another-admin" is disabled
- When sending "PUT" to "/cloud/users/another-admin/enable"
- Then the OCS status code should be "100"
- Then the HTTP status code should be "200"
- And user "another-admin" is enabled
-
- Scenario: Admin can disable subadmins in the same group
- Given As an "admin"
- And user "subadmin" exists
- And group "new-group" exists
- And user "subadmin" belongs to group "new-group"
- And user "admin" belongs to group "new-group"
- And Assure user "subadmin" is subadmin of group "new-group"
- When sending "PUT" to "/cloud/users/subadmin/disable"
- Then the OCS status code should be "100"
- Then the HTTP status code should be "200"
- And user "subadmin" is disabled
-
- Scenario: Admin can enable subadmins in the same group
- Given As an "admin"
- And user "subadmin" exists
- And group "new-group" exists
- And user "subadmin" belongs to group "new-group"
- And user "admin" belongs to group "new-group"
- And Assure user "subadmin" is subadmin of group "new-group"
- And assure user "another-admin" is disabled
- When sending "PUT" to "/cloud/users/subadmin/disable"
- Then the OCS status code should be "100"
- Then the HTTP status code should be "200"
- And user "subadmin" is disabled
-
- Scenario: Admin user cannot disable himself
- Given As an "admin"
- And user "another-admin" exists
- And user "another-admin" belongs to group "admin"
- And As an "another-admin"
- When sending "PUT" to "/cloud/users/another-admin/disable"
- Then the OCS status code should be "101"
- And the HTTP status code should be "200"
- And As an "admin"
- And user "another-admin" is enabled
-
- Scenario:Admin user cannot enable himself
- Given As an "admin"
- And user "another-admin" exists
- And user "another-admin" belongs to group "admin"
- And assure user "another-admin" is disabled
- And As an "another-admin"
- When sending "PUT" to "/cloud/users/another-admin/enable"
- And As an "admin"
- Then user "another-admin" is disabled
-
- Scenario: disable an user with a regular user
- Given As an "admin"
- And user "user1" exists
- And user "user2" exists
- And As an "user1"
- When sending "PUT" to "/cloud/users/user2/disable"
- Then the OCS status code should be "403"
- And the HTTP status code should be "200"
- And As an "admin"
- And user "user2" is enabled
-
- Scenario: enable an user with a regular user
- Given As an "admin"
- And user "user1" exists
- And user "user2" exists
- And assure user "user2" is disabled
- And As an "user1"
- When sending "PUT" to "/cloud/users/user2/enable"
- Then the OCS status code should be "403"
- And the HTTP status code should be "200"
- And As an "admin"
- And user "user2" is disabled
-
- Scenario: Subadmin should not be able to disable himself
- Given As an "admin"
- And user "subadmin" exists
- And group "new-group" exists
- And user "subadmin" belongs to group "new-group"
- And Assure user "subadmin" is subadmin of group "new-group"
- And As an "subadmin"
- When sending "PUT" to "/cloud/users/subadmin/disable"
- Then the OCS status code should be "101"
- Then the HTTP status code should be "200"
- And As an "admin"
- And user "subadmin" is enabled
-
- Scenario: Subadmin should not be able to enable himself
- Given As an "admin"
- And user "subadmin" exists
- And group "new-group" exists
- And user "subadmin" belongs to group "new-group"
- And Assure user "subadmin" is subadmin of group "new-group"
- And assure user "subadmin" is disabled
- And As an "subadmin"
- When sending "PUT" to "/cloud/users/subadmin/enabled"
- And As an "admin"
- And user "subadmin" is disabled
-
- Scenario: Making a ocs request with an enabled user
- Given As an "admin"
- And user "user0" exists
- And As an "user0"
- When sending "GET" to "/cloud/capabilities"
- Then the HTTP status code should be "200"
- And the OCS status code should be "100"
-
- Scenario: Making a web request with an enabled user
- Given As an "admin"
- And user "user0" exists
- And As an "user0"
- When sending "GET" with exact url to "/index.php/apps/files"
- Then the HTTP status code should be "200"
-
- Scenario: Making a ocs request with a disabled user
- Given As an "admin"
- And user "user0" exists
- And assure user "user0" is disabled
- And As an "user0"
- When sending "GET" to "/cloud/capabilities"
- Then the OCS status code should be "997"
- And the HTTP status code should be "401"
-
- Scenario: Making a web request with a disabled user
- Given As an "admin"
- And user "user0" exists
- And assure user "user0" is disabled
- And As an "user0"
- When sending "GET" with exact url to "/index.php/apps/files"
- And the HTTP status code should be "401"
+ | additional_mail | no.reply6@nextcloud.com |
+
+ Scenario: An admin cannot edit user account property scopes
+ Given As an "admin"
+ And user "brand-new-user" exists
+ When sending "PUT" to "/cloud/users/brand-new-user" with
+ | key | phoneScope |
+ | value | v2-private |
+ Then the OCS status code should be "113"
+ And the HTTP status code should be "200"
+
+ Scenario: Search by phone number
+ Given As an "admin"
+ And user "phone-user" exists
+ And sending "PUT" to "/cloud/users/phone-user" with
+ | key | phone |
+ | value | +49 711 / 25 24 28-90 |
+ And the OCS status code should be "100"
+ And the HTTP status code should be "200"
+ Then search users by phone for region "DE" with
+ | random-string1 | 0711 / 123 456 78 |
+ | random-string1 | 0711 / 252 428-90 |
+ | random-string2 | 0711 / 90-824 252 |
+ And the OCS status code should be "100"
+ And the HTTP status code should be "200"
+ Then phone matches returned are
+ | random-string1 | phone-user@localhost:8080 |
+
+ Scenario: Create a group
+ Given As an "admin"
+ And group "new-group" does not exist
+ When sending "POST" to "/cloud/groups" with
+ | groupid | new-group |
+ | password | 123456 |
+ Then the OCS status code should be "100"
+ And the HTTP status code should be "200"
+ And group "new-group" exists
+ And group "new-group" has
+ | displayname | new-group |
+
+ Scenario: Create a group with custom display name
+ Given As an "admin"
+ And group "new-group" does not exist
+ When sending "POST" to "/cloud/groups" with
+ | groupid | new-group |
+ | password | 123456 |
+ | displayname | new-group-displayname |
+ Then the OCS status code should be "100"
+ And the HTTP status code should be "200"
+ And group "new-group" exists
+ And group "new-group" has
+ | displayname | new-group-displayname |
+
+ Scenario: Create a group with special characters
+ Given As an "admin"
+ And group "España" does not exist
+ When sending "POST" to "/cloud/groups" with
+ | groupid | España |
+ | password | 123456 |
+ Then the OCS status code should be "100"
+ And the HTTP status code should be "200"
+ And group "España" exists
+ And group "España" has
+ | displayname | España |
+
+ Scenario: adding user to a group without sending the group
+ Given As an "admin"
+ And user "brand-new-user" exists
+ When sending "POST" to "/cloud/users/brand-new-user/groups" with
+ | groupid | |
+ Then the OCS status code should be "101"
+ And the HTTP status code should be "200"
+
+ Scenario: adding user to a group which doesn't exist
+ Given As an "admin"
+ And user "brand-new-user" exists
+ And group "not-group" does not exist
+ When sending "POST" to "/cloud/users/brand-new-user/groups" with
+ | groupid | not-group |
+ Then the OCS status code should be "102"
+ And the HTTP status code should be "200"
+
+ Scenario: adding user to a group without privileges
+ Given user "brand-new-user" exists
+ And As an "brand-new-user"
+ When sending "POST" to "/cloud/users/brand-new-user/groups" with
+ | groupid | new-group |
+ Then the OCS status code should be "403"
+ And the HTTP status code should be "200"
+
+ Scenario: adding user to a group
+ Given As an "admin"
+ And user "brand-new-user" exists
+ And group "new-group" exists
+ When sending "POST" to "/cloud/users/brand-new-user/groups" with
+ | groupid | new-group |
+ Then the OCS status code should be "100"
+ And the HTTP status code should be "200"
+
+ Scenario: getting groups of an user
+ Given As an "admin"
+ And user "brand-new-user" exists
+ And group "new-group" exists
+ When sending "GET" to "/cloud/users/brand-new-user/groups"
+ Then groups returned are
+ | new-group |
+ And the OCS status code should be "100"
+
+ Scenario: adding a user which doesn't exist to a group
+ Given As an "admin"
+ And user "not-user" does not exist
+ And group "new-group" exists
+ When sending "POST" to "/cloud/users/not-user/groups" with
+ | groupid | new-group |
+ Then the OCS status code should be "103"
+ And the HTTP status code should be "200"
+
+ Scenario: getting a group
+ Given As an "admin"
+ And group "new-group" exists
+ When sending "GET" to "/cloud/groups/new-group"
+ Then the OCS status code should be "100"
+ And the HTTP status code should be "200"
+
+ Scenario: Getting all groups
+ Given As an "admin"
+ And group "new-group" exists
+ And group "admin" exists
+ When sending "GET" to "/cloud/groups"
+ Then groups returned are
+ | España |
+ | admin |
+ | hidden_group |
+ | new-group |
+
+ Scenario: create a subadmin
+ Given As an "admin"
+ And user "brand-new-user" exists
+ And group "new-group" exists
+ When sending "POST" to "/cloud/users/brand-new-user/subadmins" with
+ | groupid | new-group |
+ Then the OCS status code should be "100"
+ And the HTTP status code should be "200"
+
+ Scenario: get users using a subadmin
+ Given As an "admin"
+ And user "brand-new-user" exists
+ And group "new-group" exists
+ And user "brand-new-user" belongs to group "new-group"
+ And user "brand-new-user" is subadmin of group "new-group"
+ And As an "brand-new-user"
+ When sending "GET" to "/cloud/users"
+ Then users returned are
+ | brand-new-user |
+ And the OCS status code should be "100"
+ And the HTTP status code should be "200"
+
+ Scenario: removing a user from a group which doesn't exists
+ Given As an "admin"
+ And user "brand-new-user" exists
+ And group "not-group" does not exist
+ When sending "DELETE" to "/cloud/users/brand-new-user/groups" with
+ | groupid | not-group |
+ Then the OCS status code should be "102"
+
+ Scenario: removing a user from a group
+ Given As an "admin"
+ And user "brand-new-user" exists
+ And group "new-group" exists
+ And user "brand-new-user" belongs to group "new-group"
+ When sending "DELETE" to "/cloud/users/brand-new-user/groups" with
+ | groupid | new-group |
+ Then the OCS status code should be "100"
+ And user "brand-new-user" does not belong to group "new-group"
+
+ Scenario: create a subadmin using a user which not exist
+ Given As an "admin"
+ And user "not-user" does not exist
+ And group "new-group" exists
+ When sending "POST" to "/cloud/users/not-user/subadmins" with
+ | groupid | new-group |
+ Then the OCS status code should be "101"
+ And the HTTP status code should be "200"
+
+ Scenario: create a subadmin using a group which not exist
+ Given As an "admin"
+ And user "brand-new-user" exists
+ And group "not-group" does not exist
+ When sending "POST" to "/cloud/users/brand-new-user/subadmins" with
+ | groupid | not-group |
+ Then the OCS status code should be "102"
+ And the HTTP status code should be "200"
+
+ Scenario: Getting subadmin groups
+ Given As an "admin"
+ And user "brand-new-user" exists
+ And group "new-group" exists
+ When sending "GET" to "/cloud/users/brand-new-user/subadmins"
+ Then subadmin groups returned are
+ | new-group |
+ Then the OCS status code should be "100"
+ And the HTTP status code should be "200"
+
+ Scenario: Getting subadmin groups of a user which not exist
+ Given As an "admin"
+ And user "not-user" does not exist
+ And group "new-group" exists
+ When sending "GET" to "/cloud/users/not-user/subadmins"
+ Then the OCS status code should be "404"
+ And the HTTP status code should be "200"
+
+ Scenario: Getting subadmin users of a group
+ Given As an "admin"
+ And user "brand-new-user" exists
+ And group "new-group" exists
+ When sending "GET" to "/cloud/groups/new-group/subadmins"
+ Then subadmin users returned are
+ | brand-new-user |
+ And the OCS status code should be "100"
+ And the HTTP status code should be "200"
+
+ Scenario: Getting subadmin users of a group which doesn't exist
+ Given As an "admin"
+ And user "brand-new-user" exists
+ And group "not-group" does not exist
+ When sending "GET" to "/cloud/groups/not-group/subadmins"
+ Then the OCS status code should be "101"
+ And the HTTP status code should be "200"
+
+ Scenario: Removing subadmin from a group
+ Given As an "admin"
+ And user "brand-new-user" exists
+ And group "new-group" exists
+ And user "brand-new-user" is subadmin of group "new-group"
+ When sending "DELETE" to "/cloud/users/brand-new-user/subadmins" with
+ | groupid | new-group |
+ And the OCS status code should be "100"
+ And the HTTP status code should be "200"
+
+ Scenario: Delete a user
+ Given As an "admin"
+ And user "brand-new-user" exists
+ When sending "DELETE" to "/cloud/users/brand-new-user"
+ Then the OCS status code should be "100"
+ And the HTTP status code should be "200"
+ And user "brand-new-user" does not exist
+
+ Scenario: Delete a group
+ Given As an "admin"
+ And group "new-group" exists
+ When sending "DELETE" to "/cloud/groups/new-group"
+ Then the OCS status code should be "100"
+ And the HTTP status code should be "200"
+ And group "new-group" does not exist
+
+ Scenario: Delete a group with special characters
+ Given As an "admin"
+ And group "España" exists
+ When sending "DELETE" to "/cloud/groups/España"
+ Then the OCS status code should be "100"
+ And the HTTP status code should be "200"
+ And group "España" does not exist
+
+ Scenario: get enabled apps
+ Given As an "admin"
+ When sending "GET" to "/cloud/apps?filter=enabled"
+ Then the OCS status code should be "100"
+ And the HTTP status code should be "200"
+ And apps returned are
+ | cloud_federation_api |
+ | comments |
+ | contactsinteraction |
+ | dashboard |
+ | dav |
+ | federatedfilesharing |
+ | federation |
+ | files |
+ | files_reminders |
+ | files_sharing |
+ | files_trashbin |
+ | files_versions |
+ | lookup_server_connector |
+ | profile |
+ | provisioning_api |
+ | settings |
+ | sharebymail |
+ | systemtags |
+ | testing |
+ | theming |
+ | twofactor_backupcodes |
+ | updatenotification |
+ | user_ldap |
+ | user_status |
+ | viewer |
+ | workflowengine |
+ | webhook_listeners |
+ | weather_status |
+ | files_external |
+ | oauth2 |
+
+ Scenario: get app info
+ Given As an "admin"
+ When sending "GET" to "/cloud/apps/files"
+ Then the OCS status code should be "100"
+ And the HTTP status code should be "200"
+
+ Scenario: get app info from app that does not exist
+ Given As an "admin"
+ When sending "GET" to "/cloud/apps/this_app_should_never_exist"
+ Then the OCS status code should be "998"
+ And the HTTP status code should be "200"
+
+ Scenario: enable an app
+ Given invoking occ with "app:disable testing"
+ Given As an "admin"
+ And app "testing" is disabled
+ When sending "POST" to "/cloud/apps/testing"
+ Then the OCS status code should be "100"
+ And the HTTP status code should be "200"
+ And app "testing" is enabled
+
+ Scenario: enable an app that does not exist
+ Given As an "admin"
+ When sending "POST" to "/cloud/apps/this_app_should_never_exist"
+ Then the OCS status code should be "998"
+ And the HTTP status code should be "200"
+
+ Scenario: disable an app
+ Given invoking occ with "app:enable testing"
+ Given As an "admin"
+ And app "testing" is enabled
+ When sending "DELETE" to "/cloud/apps/testing"
+ Then the OCS status code should be "100"
+ And the HTTP status code should be "200"
+ And app "testing" is disabled
+ Given invoking occ with "app:enable testing"
+
+ Scenario: disable an user
+ Given As an "admin"
+ And user "user1" exists
+ When sending "PUT" to "/cloud/users/user1/disable"
+ Then the OCS status code should be "100"
+ And the HTTP status code should be "200"
+ And user "user1" is disabled
+
+ Scenario: enable an user
+ Given As an "admin"
+ And user "user1" exists
+ And assure user "user1" is disabled
+ When sending "PUT" to "/cloud/users/user1/enable"
+ Then the OCS status code should be "100"
+ And the HTTP status code should be "200"
+ And user "user1" is enabled
+
+ Scenario: Subadmin should be able to enable or disable an user in their group
+ Given As an "admin"
+ And user "subadmin" exists
+ And user "user1" exists
+ And group "new-group" exists
+ And user "subadmin" belongs to group "new-group"
+ And user "user1" belongs to group "new-group"
+ And Assure user "subadmin" is subadmin of group "new-group"
+ And As an "subadmin"
+ When sending "PUT" to "/cloud/users/user1/disable"
+ Then the OCS status code should be "100"
+ Then the HTTP status code should be "200"
+ And As an "admin"
+ And user "user1" is disabled
+
+ Scenario: Subadmin should not be able to enable or disable an user not in their group
+ Given As an "admin"
+ And user "subadmin" exists
+ And user "user1" exists
+ And group "new-group" exists
+ And group "another-group" exists
+ And user "subadmin" belongs to group "new-group"
+ And user "user1" belongs to group "another-group"
+ And Assure user "subadmin" is subadmin of group "new-group"
+ And As an "subadmin"
+ When sending "PUT" to "/cloud/users/user1/disable"
+ Then the OCS status code should be "998"
+ Then the HTTP status code should be "200"
+ And As an "admin"
+ And user "user1" is enabled
+
+ Scenario: Subadmins should not be able to disable users that have admin permissions in their group
+ Given As an "admin"
+ And user "another-admin" exists
+ And user "subadmin" exists
+ And group "new-group" exists
+ And user "another-admin" belongs to group "admin"
+ And user "subadmin" belongs to group "new-group"
+ And user "another-admin" belongs to group "new-group"
+ And Assure user "subadmin" is subadmin of group "new-group"
+ And As an "subadmin"
+ When sending "PUT" to "/cloud/users/another-admin/disable"
+ Then the OCS status code should be "998"
+ Then the HTTP status code should be "200"
+ And As an "admin"
+ And user "another-admin" is enabled
+
+ Scenario: Admin can disable another admin user
+ Given As an "admin"
+ And user "another-admin" exists
+ And user "another-admin" belongs to group "admin"
+ When sending "PUT" to "/cloud/users/another-admin/disable"
+ Then the OCS status code should be "100"
+ Then the HTTP status code should be "200"
+ And user "another-admin" is disabled
+
+ Scenario: Admin can enable another admin user
+ Given As an "admin"
+ And user "another-admin" exists
+ And user "another-admin" belongs to group "admin"
+ And assure user "another-admin" is disabled
+ When sending "PUT" to "/cloud/users/another-admin/enable"
+ Then the OCS status code should be "100"
+ Then the HTTP status code should be "200"
+ And user "another-admin" is enabled
+
+ Scenario: Admin can disable subadmins in the same group
+ Given As an "admin"
+ And user "subadmin" exists
+ And group "new-group" exists
+ And user "subadmin" belongs to group "new-group"
+ And user "admin" belongs to group "new-group"
+ And Assure user "subadmin" is subadmin of group "new-group"
+ When sending "PUT" to "/cloud/users/subadmin/disable"
+ Then the OCS status code should be "100"
+ Then the HTTP status code should be "200"
+ And user "subadmin" is disabled
+
+ Scenario: Admin can enable subadmins in the same group
+ Given As an "admin"
+ And user "subadmin" exists
+ And group "new-group" exists
+ And user "subadmin" belongs to group "new-group"
+ And user "admin" belongs to group "new-group"
+ And Assure user "subadmin" is subadmin of group "new-group"
+ And assure user "another-admin" is disabled
+ When sending "PUT" to "/cloud/users/subadmin/disable"
+ Then the OCS status code should be "100"
+ Then the HTTP status code should be "200"
+ And user "subadmin" is disabled
+
+ Scenario: Admin user cannot disable himself
+ Given As an "admin"
+ And user "another-admin" exists
+ And user "another-admin" belongs to group "admin"
+ And As an "another-admin"
+ When sending "PUT" to "/cloud/users/another-admin/disable"
+ Then the OCS status code should be "101"
+ And the HTTP status code should be "200"
+ And As an "admin"
+ And user "another-admin" is enabled
+
+ Scenario:Admin user cannot enable himself
+ Given As an "admin"
+ And user "another-admin" exists
+ And user "another-admin" belongs to group "admin"
+ And assure user "another-admin" is disabled
+ And As an "another-admin"
+ When sending "PUT" to "/cloud/users/another-admin/enable"
+ And As an "admin"
+ Then user "another-admin" is disabled
+
+ Scenario: disable an user with a regular user
+ Given As an "admin"
+ And user "user1" exists
+ And user "user2" exists
+ And As an "user1"
+ When sending "PUT" to "/cloud/users/user2/disable"
+ Then the OCS status code should be "403"
+ And the HTTP status code should be "200"
+ And As an "admin"
+ And user "user2" is enabled
+
+ Scenario: enable an user with a regular user
+ Given As an "admin"
+ And user "user1" exists
+ And user "user2" exists
+ And assure user "user2" is disabled
+ And As an "user1"
+ When sending "PUT" to "/cloud/users/user2/enable"
+ Then the OCS status code should be "403"
+ And the HTTP status code should be "200"
+ And As an "admin"
+ And user "user2" is disabled
+
+ Scenario: Subadmin should not be able to disable himself
+ Given As an "admin"
+ And user "subadmin" exists
+ And group "new-group" exists
+ And user "subadmin" belongs to group "new-group"
+ And Assure user "subadmin" is subadmin of group "new-group"
+ And As an "subadmin"
+ When sending "PUT" to "/cloud/users/subadmin/disable"
+ Then the OCS status code should be "101"
+ Then the HTTP status code should be "200"
+ And As an "admin"
+ And user "subadmin" is enabled
+
+ Scenario: Subadmin should not be able to enable himself
+ Given As an "admin"
+ And user "subadmin" exists
+ And group "new-group" exists
+ And user "subadmin" belongs to group "new-group"
+ And Assure user "subadmin" is subadmin of group "new-group"
+ And assure user "subadmin" is disabled
+ And As an "subadmin"
+ When sending "PUT" to "/cloud/users/subadmin/enabled"
+ And As an "admin"
+ And user "subadmin" is disabled
+
+ Scenario: Making a ocs request with an enabled user
+ Given As an "admin"
+ And user "user0" exists
+ And As an "user0"
+ When sending "GET" to "/cloud/capabilities"
+ Then the HTTP status code should be "200"
+ And the OCS status code should be "100"
+
+ Scenario: Making a web request with an enabled user
+ Given As an "admin"
+ And user "user0" exists
+ And As an "user0"
+ When sending "GET" with exact url to "/index.php/apps/files"
+ Then the HTTP status code should be "200"
+
+ Scenario: Making a ocs request with a disabled user
+ Given As an "admin"
+ And user "user0" exists
+ And assure user "user0" is disabled
+ And As an "user0"
+ When sending "GET" to "/cloud/capabilities"
+ Then the OCS status code should be "997"
+ And the HTTP status code should be "401"
+
+ Scenario: Making a web request with a disabled user
+ Given As an "admin"
+ And user "user0" exists
+ And assure user "user0" is disabled
+ And As an "user0"
+ When sending "GET" with exact url to "/index.php/apps/files"
+ And the HTTP status code should be "401"
diff --git a/build/integration/features/provisioning-v2.feature b/build/integration/features/provisioning-v2.feature
index 729c812cb8c..1169dc04b5f 100644
--- a/build/integration/features/provisioning-v2.feature
+++ b/build/integration/features/provisioning-v2.feature
@@ -1,3 +1,6 @@
+# SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
+# SPDX-FileCopyrightText: 2015 ownCloud, Inc.
+# SPDX-License-Identifier: AGPL-3.0-only
Feature: provisioning
Background:
Given using api version "2"
diff --git a/build/integration/features/ratelimiting.feature b/build/integration/features/ratelimiting.feature
deleted file mode 100644
index bd8b2e30a73..00000000000
--- a/build/integration/features/ratelimiting.feature
+++ /dev/null
@@ -1,58 +0,0 @@
-Feature: ratelimiting
-
- Background:
- Given user "user0" exists
- Given As an "admin"
- Given app "testing" is enabled
-
- Scenario: Accessing a page with only an AnonRateThrottle as user
- Given user "user0" exists
- # First request should work
- When requesting "/index.php/apps/testing/anonProtected" with "GET" using basic auth
- Then the HTTP status code should be "200"
- # Second one should fail
- When requesting "/index.php/apps/testing/anonProtected" with "GET" using basic auth
- Then the HTTP status code should be "429"
- # After 11 seconds the next request should work
- And Sleep for "11" seconds
- When requesting "/index.php/apps/testing/anonProtected" with "GET" using basic auth
- Then the HTTP status code should be "200"
-
- Scenario: Accessing a page with only an AnonRateThrottle as guest
- Given Sleep for "11" seconds
- # First request should work
- When requesting "/index.php/apps/testing/anonProtected" with "GET"
- Then the HTTP status code should be "200"
- # Second one should fail
- When requesting "/index.php/apps/testing/anonProtected" with "GET" using basic auth
- Then the HTTP status code should be "429"
- # After 11 seconds the next request should work
- And Sleep for "11" seconds
- When requesting "/index.php/apps/testing/anonProtected" with "GET" using basic auth
- Then the HTTP status code should be "200"
-
- Scenario: Accessing a page with UserRateThrottle and AnonRateThrottle
- # First request should work as guest
- When requesting "/index.php/apps/testing/userAndAnonProtected" with "GET"
- Then the HTTP status code should be "200"
- # Second request should fail as guest
- When requesting "/index.php/apps/testing/userAndAnonProtected" with "GET"
- Then the HTTP status code should be "429"
- # First request should work as user
- When requesting "/index.php/apps/testing/userAndAnonProtected" with "GET" using basic auth
- Then the HTTP status code should be "200"
- # Second request should work as user
- When requesting "/index.php/apps/testing/userAndAnonProtected" with "GET" using basic auth
- Then the HTTP status code should be "200"
- # Third request should work as user
- When requesting "/index.php/apps/testing/userAndAnonProtected" with "GET" using basic auth
- Then the HTTP status code should be "200"
- # Fourth request should work as user
- When requesting "/index.php/apps/testing/userAndAnonProtected" with "GET" using basic auth
- Then the HTTP status code should be "200"
- # Fifth request should work as user
- When requesting "/index.php/apps/testing/userAndAnonProtected" with "GET" using basic auth
- Then the HTTP status code should be "200"
- # Sixth request should fail as user
- When requesting "/index.php/apps/testing/userAndAnonProtected" with "GET"
- Then the HTTP status code should be "429"
diff --git a/build/integration/features/tags.feature b/build/integration/features/tags.feature
deleted file mode 100644
index 495008ffdd2..00000000000
--- a/build/integration/features/tags.feature
+++ /dev/null
@@ -1,462 +0,0 @@
-Feature: tags
-
- Scenario: Creating a normal tag as regular user should work
- Given user "user0" exists
- When "user0" creates a "normal" tag with name "MySuperAwesomeTagName"
- Then The response should have a status code "201"
- And The following tags should exist for "admin"
- |MySuperAwesomeTagName|true|true|
- And The following tags should exist for "user0"
- |MySuperAwesomeTagName|true|true|
-
- Scenario: Creating a not user-assignable tag as regular user should fail
- Given user "user0" exists
- When "user0" creates a "not user-assignable" tag with name "MySuperAwesomeTagName"
- Then The response should have a status code "400"
- And "0" tags should exist for "admin"
-
- Scenario: Creating a not user-visible tag as regular user should fail
- Given user "user0" exists
- When "user0" creates a "not user-visible" tag with name "MySuperAwesomeTagName"
- Then The response should have a status code "400"
- And "0" tags should exist for "admin"
-
- Scenario: Creating a not user-assignable tag with groups as admin should work
- Given user "user0" exists
- When "admin" creates a "not user-assignable" tag with name "TagWithGroups" and groups "group1|group2"
- Then The response should have a status code "201"
- And The "not user-assignable" tag with name "TagWithGroups" has the groups "group1|group2"
-
- Scenario: Creating a normal tag with groups as regular user should fail
- Given user "user0" exists
- When "user0" creates a "normal" tag with name "MySuperAwesomeTagName" and groups "group1|group2"
- Then The response should have a status code "400"
- And "0" tags should exist for "user0"
-
- Scenario: Renaming a normal tag as regular user should work
- Given user "user0" exists
- Given "admin" creates a "normal" tag with name "MySuperAwesomeTagName"
- When "user0" edits the tag with name "MySuperAwesomeTagName" and sets its name to "AnotherTagName"
- Then The response should have a status code "207"
- And The following tags should exist for "admin"
- |AnotherTagName|true|true|
-
- Scenario: Renaming a not user-assignable tag as regular user should fail
- Given user "user0" exists
- Given "admin" creates a "not user-assignable" tag with name "MySuperAwesomeTagName"
- When "user0" edits the tag with name "MySuperAwesomeTagName" and sets its name to "AnotherTagName"
- Then The response should have a status code "403"
- And The following tags should exist for "admin"
- |MySuperAwesomeTagName|true|false|
-
- Scenario: Renaming a not user-visible tag as regular user should fail
- Given user "user0" exists
- Given "admin" creates a "not user-visible" tag with name "MySuperAwesomeTagName"
- When "user0" edits the tag with name "MySuperAwesomeTagName" and sets its name to "AnotherTagName"
- Then The response should have a status code "404"
- And The following tags should exist for "admin"
- |MySuperAwesomeTagName|false|true|
-
- Scenario: Editing tag groups as admin should work
- Given user "user0" exists
- Given "admin" creates a "not user-assignable" tag with name "TagWithGroups" and groups "group1|group2"
- When "admin" edits the tag with name "TagWithGroups" and sets its groups to "group1|group3"
- Then The response should have a status code "207"
- And The "not user-assignable" tag with name "TagWithGroups" has the groups "group1|group3"
-
- Scenario: Editing tag groups as regular user should fail
- Given user "user0" exists
- Given "admin" creates a "not user-assignable" tag with name "TagWithGroups"
- When "user0" edits the tag with name "TagWithGroups" and sets its groups to "group1|group3"
- Then The response should have a status code "403"
-
- Scenario: Deleting a normal tag as regular user should fail
- Given user "user0" exists
- Given "admin" creates a "normal" tag with name "MySuperAwesomeTagName"
- When "user0" deletes the tag with name "MySuperAwesomeTagName"
- Then The response should have a status code "403"
- And The following tags should exist for "admin"
- |MySuperAwesomeTagName|true|true|
-
- Scenario: Deleting a not user-assignable tag as regular user should fail
- Given user "user0" exists
- Given "admin" creates a "not user-assignable" tag with name "MySuperAwesomeTagName"
- When "user0" deletes the tag with name "MySuperAwesomeTagName"
- Then The response should have a status code "403"
- And The following tags should exist for "admin"
- |MySuperAwesomeTagName|true|false|
-
- Scenario: Deleting a not user-visible tag as regular user should fail
- Given user "user0" exists
- Given "admin" creates a "not user-visible" tag with name "MySuperAwesomeTagName"
- When "user0" deletes the tag with name "MySuperAwesomeTagName"
- Then The response should have a status code "404"
- And The following tags should exist for "admin"
- |MySuperAwesomeTagName|false|true|
-
- Scenario: Deleting a normal tag as admin should work
- Given "admin" creates a "normal" tag with name "MySuperAwesomeTagName"
- When "admin" deletes the tag with name "MySuperAwesomeTagName"
- Then The response should have a status code "204"
- And "0" tags should exist for "admin"
-
- Scenario: Deleting a not user-assignable tag as admin should work
- Given "admin" creates a "not user-assignable" tag with name "MySuperAwesomeTagName"
- When "admin" deletes the tag with name "MySuperAwesomeTagName"
- Then The response should have a status code "204"
- And "0" tags should exist for "admin"
-
- Scenario: Deleting a not user-visible tag as admin should work
- Given "admin" creates a "not user-visible" tag with name "MySuperAwesomeTagName"
- When "admin" deletes the tag with name "MySuperAwesomeTagName"
- Then The response should have a status code "204"
- And "0" tags should exist for "admin"
-
- Scenario: Assigning a normal tag to a file shared by someone else as regular user should work
- Given user "user0" exists
- Given user "12345" exists
- Given "admin" creates a "normal" tag with name "MySuperAwesomeTagName"
- Given user "user0" uploads file "data/textfile.txt" to "/myFileToTag.txt"
- Given as "user0" creating a share with
- | path | myFileToTag.txt |
- | shareWith | 12345 |
- | shareType | 0 |
- Given user "12345" accepts last share
- When "12345" adds the tag "MySuperAwesomeTagName" to "/myFileToTag.txt" shared by "user0"
- Then The response should have a status code "201"
- And "/myFileToTag.txt" shared by "user0" has the following tags
- |MySuperAwesomeTagName|
-
- Scenario: Assigning a normal tag to a file belonging to someone else as regular user should fail
- Given user "user0" exists
- Given user "user1" exists
- Given "admin" creates a "normal" tag with name "MyFirstTag"
- Given "admin" creates a "normal" tag with name "MySecondTag"
- Given user "user0" uploads file "data/textfile.txt" to "/myFileToTag.txt"
- When "user0" adds the tag "MyFirstTag" to "/myFileToTag.txt" shared by "user0"
- When "user1" adds the tag "MySecondTag" to "/myFileToTag.txt" shared by "user0"
- Then The response should have a status code "404"
- And "/myFileToTag.txt" shared by "user0" has the following tags
- |MyFirstTag|
-
- Scenario: Assigning a not user-assignable tag to a file shared by someone else as regular user should fail
- Given user "user0" exists
- Given user "user1" exists
- Given "admin" creates a "normal" tag with name "MyFirstTag"
- Given "admin" creates a "not user-assignable" tag with name "MySecondTag"
- Given user "user0" uploads file "data/textfile.txt" to "/myFileToTag.txt"
- Given as "user0" creating a share with
- | path | myFileToTag.txt |
- | shareWith | user1 |
- | shareType | 0 |
- Given user "user1" accepts last share
- When "user0" adds the tag "MyFirstTag" to "/myFileToTag.txt" shared by "user0"
- When "user1" adds the tag "MySecondTag" to "/myFileToTag.txt" shared by "user0"
- Then The response should have a status code "403"
- And "/myFileToTag.txt" shared by "user0" has the following tags
- |MyFirstTag|
-
- Scenario: Assigning a not user-assignable tag to a file shared by someone else as regular user belongs to tag's groups should work
- Given user "user0" exists
- Given user "user1" exists
- Given group "group1" exists
- Given user "user1" belongs to group "group1"
- Given "admin" creates a "not user-assignable" tag with name "MySuperAwesomeTagName" and groups "group1"
- Given user "user0" uploads file "data/textfile.txt" to "/myFileToTag.txt"
- Given as "user0" creating a share with
- | path | myFileToTag.txt |
- | shareWith | user1 |
- | shareType | 0 |
- Given user "user1" accepts last share
- When "user1" adds the tag "MySuperAwesomeTagName" to "/myFileToTag.txt" shared by "user0"
- Then The response should have a status code "201"
- And "/myFileToTag.txt" shared by "user0" has the following tags
- |MySuperAwesomeTagName|
-
-
- Scenario: Assigning a not user-visible tag to a file shared by someone else as regular user should fail
- Given user "user0" exists
- Given user "user1" exists
- Given "admin" creates a "normal" tag with name "MyFirstTag"
- Given "admin" creates a "not user-visible" tag with name "MySecondTag"
- Given user "user0" uploads file "data/textfile.txt" to "/myFileToTag.txt"
- Given as "user0" creating a share with
- | path | myFileToTag.txt |
- | shareWith | user1 |
- | shareType | 0 |
- Given user "user1" accepts last share
- When "user0" adds the tag "MyFirstTag" to "/myFileToTag.txt" shared by "user0"
- When "user1" adds the tag "MySecondTag" to "/myFileToTag.txt" shared by "user0"
- Then The response should have a status code "412"
- And "/myFileToTag.txt" shared by "user0" has the following tags
- |MyFirstTag|
-
- Scenario: Assigning a not user-visible tag to a file shared by someone else as admin user should work
- Given user "user0" exists
- Given "admin" creates a "normal" tag with name "MyFirstTag"
- Given "admin" creates a "not user-visible" tag with name "MySecondTag"
- Given user "user0" uploads file "data/textfile.txt" to "/myFileToTag.txt"
- Given as "user0" creating a share with
- | path | myFileToTag.txt |
- | shareWith | admin |
- | shareType | 0 |
- Given user "admin" accepts last share
- When "user0" adds the tag "MyFirstTag" to "/myFileToTag.txt" shared by "user0"
- When "admin" adds the tag "MySecondTag" to "/myFileToTag.txt" shared by "user0"
- Then The response should have a status code "201"
- And "/myFileToTag.txt" shared by "user0" has the following tags for "admin"
- |MyFirstTag|
- |MySecondTag|
- And "/myFileToTag.txt" shared by "user0" has the following tags for "user0"
- |MyFirstTag|
-
- Scenario: Assigning a not user-assignable tag to a file shared by someone else as admin user should worj
- Given user "user0" exists
- Given "admin" creates a "normal" tag with name "MyFirstTag"
- Given "admin" creates a "not user-assignable" tag with name "MySecondTag"
- Given user "user0" uploads file "data/textfile.txt" to "/myFileToTag.txt"
- Given as "user0" creating a share with
- | path | myFileToTag.txt |
- | shareWith | admin |
- | shareType | 0 |
- Given user "admin" accepts last share
- When "user0" adds the tag "MyFirstTag" to "/myFileToTag.txt" shared by "user0"
- When "admin" adds the tag "MySecondTag" to "/myFileToTag.txt" shared by "user0"
- Then The response should have a status code "201"
- And "/myFileToTag.txt" shared by "user0" has the following tags for "admin"
- |MyFirstTag|
- |MySecondTag|
- And "/myFileToTag.txt" shared by "user0" has the following tags for "user0"
- |MyFirstTag|
- |MySecondTag|
-
- Scenario: Unassigning a normal tag from a file shared by someone else as regular user should work
- Given user "user0" exists
- Given user "user1" exists
- Given "admin" creates a "normal" tag with name "MyFirstTag"
- Given "admin" creates a "normal" tag with name "MySecondTag"
- Given user "user0" uploads file "data/textfile.txt" to "/myFileToTag.txt"
- Given as "user0" creating a share with
- | path | myFileToTag.txt |
- | shareWith | user1 |
- | shareType | 0 |
- Given user "user1" accepts last share
- Given "user0" adds the tag "MyFirstTag" to "/myFileToTag.txt" shared by "user0"
- Given "user0" adds the tag "MySecondTag" to "/myFileToTag.txt" shared by "user0"
- When "user1" removes the tag "MyFirstTag" from "/myFileToTag.txt" shared by "user0"
- Then The response should have a status code "204"
- And "/myFileToTag.txt" shared by "user0" has the following tags for "user0"
- |MySecondTag|
-
- Scenario: Unassigning a normal tag from a file unshared by someone else as regular user should fail
- Given user "user0" exists
- Given user "user1" exists
- Given "admin" creates a "normal" tag with name "MyFirstTag"
- Given "admin" creates a "normal" tag with name "MySecondTag"
- Given user "user0" uploads file "data/textfile.txt" to "/myFileToTag.txt"
- Given "user0" adds the tag "MyFirstTag" to "/myFileToTag.txt" shared by "user0"
- Given "user0" adds the tag "MySecondTag" to "/myFileToTag.txt" shared by "user0"
- When "user1" removes the tag "MyFirstTag" from "/myFileToTag.txt" shared by "user0"
- Then The response should have a status code "404"
- And "/myFileToTag.txt" shared by "user0" has the following tags for "user0"
- |MyFirstTag|
- |MySecondTag|
-
- Scenario: Unassigning a not user-visible tag from a file shared by someone else as regular user should fail
- Given user "user0" exists
- Given user "user1" exists
- Given "admin" creates a "not user-visible" tag with name "MyFirstTag"
- Given "admin" creates a "normal" tag with name "MySecondTag"
- Given user "user0" uploads file "data/textfile.txt" to "/myFileToTag.txt"
- Given as "user0" creating a share with
- | path | myFileToTag.txt |
- | shareWith | user1 |
- | shareType | 0 |
- Given user "user1" accepts last share
- Given as "user0" creating a share with
- | path | myFileToTag.txt |
- | shareWith | admin |
- | shareType | 0 |
- Given user "admin" accepts last share
- Given "admin" adds the tag "MyFirstTag" to "/myFileToTag.txt" shared by "user0"
- Given "user0" adds the tag "MySecondTag" to "/myFileToTag.txt" shared by "user0"
- When "user1" removes the tag "MyFirstTag" from "/myFileToTag.txt" shared by "user0"
- Then The response should have a status code "404"
- And "/myFileToTag.txt" shared by "user0" has the following tags for "user0"
- |MySecondTag|
- And "/myFileToTag.txt" shared by "user0" has the following tags for "admin"
- |MyFirstTag|
- |MySecondTag|
-
- Scenario: Unassigning a not user-visible tag from a file shared by someone else as admin should work
- Given user "user0" exists
- Given user "user1" exists
- Given "admin" creates a "not user-visible" tag with name "MyFirstTag"
- Given "admin" creates a "normal" tag with name "MySecondTag"
- Given user "user0" uploads file "data/textfile.txt" to "/myFileToTag.txt"
- Given as "user0" creating a share with
- | path | myFileToTag.txt |
- | shareWith | user1 |
- | shareType | 0 |
- Given user "user1" accepts last share
- Given as "user0" creating a share with
- | path | myFileToTag.txt |
- | shareWith | admin |
- | shareType | 0 |
- Given user "admin" accepts last share
- Given "admin" adds the tag "MyFirstTag" to "/myFileToTag.txt" shared by "user0"
- Given "user0" adds the tag "MySecondTag" to "/myFileToTag.txt" shared by "user0"
- When "admin" removes the tag "MyFirstTag" from "/myFileToTag.txt" shared by "user0"
- Then The response should have a status code "204"
- And "/myFileToTag.txt" shared by "user0" has the following tags for "user0"
- |MySecondTag|
- And "/myFileToTag.txt" shared by "user0" has the following tags for "admin"
- |MySecondTag|
-
- Scenario: Unassigning a not user-visible tag from a file unshared by someone else should fail
- Given user "user0" exists
- Given user "user1" exists
- Given "admin" creates a "not user-visible" tag with name "MyFirstTag"
- Given "admin" creates a "normal" tag with name "MySecondTag"
- Given user "user0" uploads file "data/textfile.txt" to "/myFileToTag.txt"
- Given as "user0" creating a share with
- | path | myFileToTag.txt |
- | shareWith | user1 |
- | shareType | 0 |
- Given user "user1" accepts last share
- Given as "user0" creating a share with
- | path | myFileToTag.txt |
- | shareWith | admin |
- | shareType | 0 |
- Given user "admin" accepts last share
- Given "admin" adds the tag "MyFirstTag" to "/myFileToTag.txt" shared by "user0"
- Given "user0" adds the tag "MySecondTag" to "/myFileToTag.txt" shared by "user0"
- Given As "user0" remove all shares from the file named "/myFileToTag.txt"
- When "admin" removes the tag "MyFirstTag" from "/myFileToTag.txt" shared by "user0"
- Then The response should have a status code "404"
-
- Scenario: Unassigning a not user-assignable tag from a file shared by someone else as regular user should fail
- Given user "user0" exists
- Given user "user1" exists
- Given "admin" creates a "not user-assignable" tag with name "MyFirstTag"
- Given "admin" creates a "normal" tag with name "MySecondTag"
- Given user "user0" uploads file "data/textfile.txt" to "/myFileToTag.txt"
- Given as "user0" creating a share with
- | path | myFileToTag.txt |
- | shareWith | user1 |
- | shareType | 0 |
- Given user "user1" accepts last share
- Given as "user0" creating a share with
- | path | myFileToTag.txt |
- | shareWith | admin |
- | shareType | 0 |
- Given user "admin" accepts last share
- Given "admin" adds the tag "MyFirstTag" to "/myFileToTag.txt" shared by "user0"
- Given "user0" adds the tag "MySecondTag" to "/myFileToTag.txt" shared by "user0"
- When "user1" removes the tag "MyFirstTag" from "/myFileToTag.txt" shared by "user0"
- Then The response should have a status code "403"
- And "/myFileToTag.txt" shared by "user0" has the following tags for "user0"
- |MyFirstTag|
- |MySecondTag|
- And "/myFileToTag.txt" shared by "user0" has the following tags for "admin"
- |MyFirstTag|
- |MySecondTag|
-
- Scenario: Unassigning a not user-assignable tag from a file shared by someone else as admin should work
- Given user "user0" exists
- Given user "user1" exists
- Given "admin" creates a "not user-assignable" tag with name "MyFirstTag"
- Given "admin" creates a "normal" tag with name "MySecondTag"
- Given user "user0" uploads file "data/textfile.txt" to "/myFileToTag.txt"
- Given as "user0" creating a share with
- | path | myFileToTag.txt |
- | shareWith | user1 |
- | shareType | 0 |
- Given user "user1" accepts last share
- Given as "user0" creating a share with
- | path | myFileToTag.txt |
- | shareWith | admin |
- | shareType | 0 |
- Given user "admin" accepts last share
- Given "admin" adds the tag "MyFirstTag" to "/myFileToTag.txt" shared by "user0"
- Given "user0" adds the tag "MySecondTag" to "/myFileToTag.txt" shared by "user0"
- When "admin" removes the tag "MyFirstTag" from "/myFileToTag.txt" shared by "user0"
- Then The response should have a status code "204"
- And "/myFileToTag.txt" shared by "user0" has the following tags for "user0"
- |MySecondTag|
- And "/myFileToTag.txt" shared by "user0" has the following tags for "admin"
- |MySecondTag|
-
- Scenario: Unassigning a not user-assignable tag from a file unshared by someone else should fail
- Given user "user0" exists
- Given user "user1" exists
- Given "admin" creates a "not user-assignable" tag with name "MyFirstTag"
- Given "admin" creates a "normal" tag with name "MySecondTag"
- Given user "user0" uploads file "data/textfile.txt" to "/myFileToTag.txt"
- Given as "user0" creating a share with
- | path | myFileToTag.txt |
- | shareWith | user1 |
- | shareType | 0 |
- Given user "user1" accepts last share
- Given as "user0" creating a share with
- | path | myFileToTag.txt |
- | shareWith | admin |
- | shareType | 0 |
- Given user "admin" accepts last share
- Given "admin" adds the tag "MyFirstTag" to "/myFileToTag.txt" shared by "user0"
- Given "user0" adds the tag "MySecondTag" to "/myFileToTag.txt" shared by "user0"
- Given As "user0" remove all shares from the file named "/myFileToTag.txt"
- When "admin" removes the tag "MyFirstTag" from "/myFileToTag.txt" shared by "user0"
- Then The response should have a status code "404"
-
- Scenario: Overwriting existing normal tags should fail
- Given user "user0" exists
- Given "user0" creates a "normal" tag with name "MyFirstTag"
- When "user0" creates a "normal" tag with name "MyFirstTag"
- Then The response should have a status code "409"
-
- Scenario: Overwriting existing not user-assignable tags should fail
- Given "admin" creates a "not user-assignable" tag with name "MyFirstTag"
- When "admin" creates a "not user-assignable" tag with name "MyFirstTag"
- Then The response should have a status code "409"
-
- Scenario: Overwriting existing not user-visible tags should fail
- Given "admin" creates a "not user-visible" tag with name "MyFirstTag"
- When "admin" creates a "not user-visible" tag with name "MyFirstTag"
- Then The response should have a status code "409"
-
- Scenario: Getting tags only works with access to the file
- Given user "user0" exists
- Given user "user1" exists
- Given "admin" creates a "normal" tag with name "MyFirstTag"
- Given user "user0" uploads file "data/textfile.txt" to "/myFileToTag.txt"
- When "user0" adds the tag "MyFirstTag" to "/myFileToTag.txt" shared by "user0"
- And "/myFileToTag.txt" shared by "user0" has the following tags for "user0"
- |MyFirstTag|
- And "/myFileToTag.txt" shared by "user0" has the following tags for "user1"
- ||
- And The response should have a status code "404"
-
- Scenario: User can assign tags when in the tag's groups
- Given user "user0" exists
- Given group "group1" exists
- Given user "user0" belongs to group "group1"
- When "admin" creates a "not user-assignable" tag with name "TagWithGroups" and groups "group1|group2"
- Then The response should have a status code "201"
- And the user "user0" can assign the "not user-assignable" tag with name "TagWithGroups"
-
- Scenario: User cannot assign tags when not in the tag's groups
- Given user "user0" exists
- When "admin" creates a "not user-assignable" tag with name "TagWithGroups" and groups "group1|group2"
- Then The response should have a status code "201"
- And the user "user0" cannot assign the "not user-assignable" tag with name "TagWithGroups"
-
- Scenario: Assign a normal tag to a file
- Given user "user0" exists
- And "admin" creates a "normal" tag with name "Etiqueta"
- And As an "user0"
- When "user0" adds the tag "Etiqueta" to "/textfile0.txt" owned by "user0"
- Then The response should have a status code "201"
- And "textfile0.txt" owned by "user0" has the following tags
- | Etiqueta |
-
diff --git a/build/integration/features/transfer-ownership.feature b/build/integration/features/transfer-ownership.feature
deleted file mode 100644
index 22e34dcf7af..00000000000
--- a/build/integration/features/transfer-ownership.feature
+++ /dev/null
@@ -1,594 +0,0 @@
-Feature: transfer-ownership
-
- Scenario: transferring ownership of a file
- Given user "user0" exists
- And user "user1" exists
- And User "user0" uploads file "data/textfile.txt" to "/somefile.txt"
- When transferring 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"
- And using old dav path
- And as "user0" the file "/somefile.txt" does not exist
- And using received transfer folder of "user1" as dav path
- And as "user1" the file "/somefile.txt" exists
-
- Scenario: transferring 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 transferring 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"
- And using old dav path
- And as "user0" the folder "/test" does not exist
- And using received transfer folder of "user1" as dav path
- And as "user1" the folder "/test" exists
-
- Scenario: transferring ownership from user with risky display name
- Given user "user0" with displayname "user0 \"risky\"? ヂspḷay 'na|\/|e':.#" 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 transferring 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"
- And transfer folder name contains "transferred from user0 -risky- ヂspḷay -na|-|e- on"
- And using old dav path
- And as "user0" the folder "/test" does not exist
- And using received transfer folder of "user1" as dav path
- And as "user1" the folder "/test" exists
-
- Scenario: transferring 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
- And user "user2" accepts last share
- When transferring 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"
- And using old dav path
- And as "user0" the file "/somefile.txt" does not exist
- And using received transfer folder of "user1" as dav path
- And as "user1" the file "/somefile.txt" exists
- And As an "user1"
- And Getting info of last share
- And the OCS status code should be "100"
- And Share fields of last share match with
- | uid_owner | user1 |
- | uid_file_owner | user1 |
- | share_with | user2 |
-
- Scenario: transferring 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
- And user "user2" accepts last share
- When transferring 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"
- And using old dav path
- And as "user0" the folder "/test" does not exist
- And using received transfer folder of "user1" as dav path
- And as "user1" the folder "/test" exists
- And As an "user1"
- And Getting info of last share
- And the OCS status code should be "100"
- And Share fields of last share match with
- | uid_owner | user1 |
- | uid_file_owner | user1 |
- | share_with | user2 |
-
- Scenario: transferring 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
- And user "user1" accepts last share
- When transferring 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"
- And using old dav path
- And as "user0" the folder "/test" does not exist
- And using received transfer folder of "user1" as dav path
- And as "user1" the folder "/test" exists
- And Getting info of last share
- And the OCS status code should be "404"
-
- Scenario: transferring 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 user "user2" accepts last share
- And folder "/test" of user "user0" is shared with user "user2" with permissions 31
- And user "user2" accepts last share
- When transferring 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"
- And using old dav path
- And as "user0" the folder "/test" does not exist
- And using received transfer folder of "user1" as dav path
- And as "user1" the folder "/test" exists
- And As an "user1"
- And Getting info of last share
- And the OCS status code should be "100"
- And Share fields of last share match with
- | uid_owner | user1 |
- | uid_file_owner | user1 |
- | share_with | user2 |
-
- Scenario: transferring ownership of file shares to user with the same id as the group
- Given user "user0" exists
- And user "test" exists
- And user "user2" exists
- And group "test" exists
- And user "user2" belongs to group "test"
- And User "user0" uploads file "data/textfile.txt" to "/somefile.txt"
- And file "/somefile.txt" of user "user0" is shared with group "test"
- And user "user2" accepts last share
- When transferring ownership from "user0" to "test"
- 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"
- And using old dav path
- And as "user0" the file "/somefile.txt" does not exist
- And using received transfer folder of "user1" as dav path
- And as "test" the file "/somefile.txt" exists
- And As an "test"
- And Getting info of last share
- And the OCS status code should be "100"
- And Share fields of last share match with
- | uid_owner | test |
- | uid_file_owner | test |
- | share_with | test |
-
- Scenario: transferring ownership of folder reshared with another user
- Given user "user0" exists
- And user "user1" exists
- And user "user2" exists
- And user "user3" exists
- And User "user3" created a folder "/test"
- And User "user3" uploads file "data/textfile.txt" to "/test/somefile.txt"
- And folder "/test" of user "user3" is shared with user "user0" with permissions 31
- And user "user0" accepts last share
- And folder "/test" of user "user0" is shared with user "user2" with permissions 31
- And user "user2" accepts last share
- When transferring 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"
- And using old dav path
- And as "user0" the folder "/test" exists
- And using received transfer folder of "user1" as dav path
- And as "user1" the folder "/test" does not exist
- And As an "user0"
- And Getting info of last share
- And the OCS status code should be "100"
- And Share fields of last share match with
- | uid_owner | user0 |
- | uid_file_owner | user3 |
- | share_with | user2 |
-
- Scenario: transferring ownership of folder reshared with group to a user in the group
- Given user "user0" exists
- And user "user1" exists
- And user "user2" exists
- And user "user3" exists
- And group "group1" exists
- And user "user1" belongs to group "group1"
- And User "user3" created a folder "/test"
- And User "user3" uploads file "data/textfile.txt" to "/test/somefile.txt"
- And folder "/test" of user "user3" is shared with user "user0" with permissions 31
- And user "user0" accepts last share
- And folder "/test" of user "user0" is shared with group "group1" with permissions 31
- And user "user1" accepts last share
- When transferring ownership from "user0" to "user1"
- And the command was successful
- And As an "user1"
- Then Downloaded content when downloading file "/test/somefile.txt" with range "bytes=0-6" should be "This is"
- And using old dav path
- And as "user0" the folder "/test" exists
- And using received transfer folder of "user1" as dav path
- And as "user1" the folder "/test" does not exist
- And As an "user1"
- And Getting info of last share
- And the OCS status code should be "100"
- And Share fields of last share match with
- | uid_owner | user1 |
- | uid_file_owner | user3 |
- | share_with | group1 |
-
- Scenario: transferring ownership of folder reshared with group to a user not in the group
- Given user "user0" exists
- And user "user1" exists
- And user "user2" exists
- And user "user3" exists
- And group "group1" exists
- And user "user2" belongs to group "group1"
- And User "user3" created a folder "/test"
- And User "user3" uploads file "data/textfile.txt" to "/test/somefile.txt"
- And folder "/test" of user "user3" is shared with user "user0" with permissions 31
- And user "user0" accepts last share
- And folder "/test" of user "user0" is shared with group "group1" with permissions 31
- And user "user2" accepts last share
- When transferring 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"
- And using old dav path
- And as "user0" the folder "/test" exists
- And using received transfer folder of "user1" as dav path
- And as "user1" the folder "/test" does not exist
- And As an "user0"
- And Getting info of last share
- And the OCS status code should be "100"
- And Share fields of last share match with
- | uid_owner | user0 |
- | uid_file_owner | user3 |
- | share_with | group1 |
-
- Scenario: transferring 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
- And user "user0" accepts last share
- When transferring 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
- And using old dav path
- And as "user0" the folder "/test" exists
- And As an "user2"
- And Getting info of last share
- And the OCS status code should be "100"
- And Share fields of last share match with
- | uid_owner | user2 |
- | uid_file_owner | user2 |
- | share_with | user0 |
-
- @local_storage
- Scenario: transferring ownership does not transfer external storage
- Given user "user0" exists
- And user "user1" exists
- When transferring 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: transferring 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 "user2" accepts last share
- And User "user0" deletes folder "/sub"
- When transferring ownership from "user0" to "user1"
- Then the command was successful
-
- Scenario: transferring ownership fails with invalid source user
- Given user "user0" exists
- When transferring ownership from "invalid_user" to "user0"
- Then the command output contains the text "Unknown source user"
- And the command failed with exit code 1
-
- Scenario: transferring ownership fails with invalid target user
- Given user "user0" exists
- When transferring ownership from "user0" to "invalid_user"
- Then the command output contains the text "Unknown destination user invalid_user"
- And the command failed with exit code 1
-
- Scenario: transferring ownership of a file
- Given user "user0" exists
- And user "user1" exists
- And User "user0" uploads file "data/textfile.txt" to "/somefile.txt"
- When transferring ownership of path "somefile.txt" 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"
- And using old dav path
- And as "user0" the file "/somefile.txt" does not exist
- And using received transfer folder of "user1" as dav path
- And as "user1" the file "/somefile.txt" exists
-
- Scenario: transferring 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 transferring ownership of path "test" 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"
- And using old dav path
- And as "user0" the folder "/test" does not exist
- And using received transfer folder of "user1" as dav path
- And as "user1" the folder "/test" exists
-
- Scenario: transferring ownership from user with risky display name
- Given user "user0" with displayname "user0 \"risky\"? ヂspḷay 'na|\/|e':.#" 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 transferring ownership of path "test" 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"
- And transfer folder name contains "transferred from user0 -risky- ヂspḷay -na|-|e- on"
- And using old dav path
- And as "user0" the folder "/test" does not exist
- And using received transfer folder of "user1" as dav path
- And as "user1" the folder "/test" exists
-
- Scenario: transferring ownership of path does not affect other files
- 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 User "user0" created a folder "/test2"
- And User "user0" uploads file "data/textfile.txt" to "/test2/somefile.txt"
- When transferring ownership of path "test" 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"
- And using old dav path
- And as "user0" the folder "/test" does not exist
- And as "user0" the folder "/test2" exists
- And as "user0" the file "/test2/somefile.txt" exists
- And using received transfer folder of "user1" as dav path
- And as "user1" the folder "/test" exists
- And as "user1" the folder "/test2" does not exist
-
- Scenario: transferring ownership of path does not affect other shares
- 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 User "user0" created a folder "/test2"
- And User "user0" uploads file "data/textfile.txt" to "/test2/sharedfile.txt"
- And file "/test2/sharedfile.txt" of user "user0" is shared with user "user1" with permissions 19
- And user "user1" accepts last share
- When transferring ownership of path "test" 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"
- And using old dav path
- And as "user0" the folder "/test" does not exist
- And as "user0" the folder "/test2" exists
- And as "user0" the file "/test2/sharedfile.txt" exists
- And using received transfer folder of "user1" as dav path
- And as "user1" the folder "/test" exists
- And as "user1" the folder "/test2" does not exist
- And using old dav path
- And as "user1" the file "/sharedfile.txt" exists
- And As an "user1"
- And Getting info of last share
- And the OCS status code should be "100"
- And Share fields of last share match with
- | uid_owner | user0 |
- | uid_file_owner | user0 |
- | share_with | user1 |
-
- Scenario: transferring ownership of file shares
- 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 file "/test/somefile.txt" of user "user0" is shared with user "user2" with permissions 19
- And user "user2" accepts last share
- When transferring ownership of path "test" 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"
- And using old dav path
- And as "user0" the folder "/test" does not exist
- And using received transfer folder of "user1" as dav path
- And as "user1" the folder "/test" exists
- And As an "user1"
- And Getting info of last share
- And the OCS status code should be "100"
- And Share fields of last share match with
- | uid_owner | user1 |
- | uid_file_owner | user1 |
- | share_with | user2 |
-
- Scenario: transferring 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
- And user "user2" accepts last share
- When transferring ownership of path "test" 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"
- And using old dav path
- And as "user0" the folder "/test" does not exist
- And using received transfer folder of "user1" as dav path
- And as "user1" the folder "/test" exists
- And As an "user1"
- And Getting info of last share
- And the OCS status code should be "100"
- And Share fields of last share match with
- | uid_owner | user1 |
- | uid_file_owner | user1 |
- | share_with | user2 |
-
- Scenario: transferring 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
- And user "user1" accepts last share
- When transferring ownership of path "test" 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"
- And using old dav path
- And as "user0" the folder "/test" does not exist
- And using received transfer folder of "user1" as dav path
- And as "user1" the folder "/test" exists
- And Getting info of last share
- And the OCS status code should be "404"
-
- Scenario: transferring 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 user "user2" accepts last share
- And folder "/test" of user "user0" is shared with user "user2" with permissions 31
- And user "user2" accepts last share
- When transferring ownership of path "test" 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"
- And using old dav path
- And as "user0" the folder "/test" does not exist
- And using received transfer folder of "user1" as dav path
- And as "user1" the folder "/test" exists
- And As an "user1"
- And Getting info of last share
- And the OCS status code should be "100"
- And Share fields of last share match with
- | uid_owner | user1 |
- | uid_file_owner | user1 |
- | share_with | user2 |
-
- Scenario: transferring ownership of path fails for reshares
- Given user "user0" exists
- And user "user1" exists
- And user "user2" exists
- And user "user3" exists
- And User "user3" created a folder "/test"
- And User "user3" uploads file "data/textfile.txt" to "/test/somefile.txt"
- And folder "/test" of user "user3" is shared with user "user0" with permissions 31
- And user "user0" accepts last share
- And folder "/test" of user "user0" is shared with user "user2" with permissions 31
- And user "user2" accepts last share
- When transferring ownership of path "test" from "user0" to "user1"
- Then the command failed with exit code 1
- And the command output contains the text "Could not transfer files."
-
- Scenario: transferring 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 User "user0" created a folder "/sub"
- And folder "/test" of user "user2" is shared with user "user0" with permissions 31
- And user "user0" accepts last share
- And User "user0" moved folder "/test" to "/sub/test"
- When transferring ownership of path "sub" 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 "/sub" exists
- And as "user1" the folder "/sub/test" does not exist
- And using old dav path
- And as "user0" the folder "/sub" does not exist
- And Getting info of last share
- And the OCS status code should be "404"
-
- Scenario: transferring ownership transfers received shares into subdir when requested
- Given user "user0" exists
- And user "user1" exists
- And user "user2" exists
- And User "user2" created a folder "/transfer-share"
- And User "user2" created a folder "/do-not-transfer"
- And User "user0" created a folder "/sub"
- And folder "/transfer-share" of user "user2" is shared with user "user0" with permissions 31
- And user "user0" accepts last share
- And User "user0" moved folder "/transfer-share" to "/sub/transfer-share"
- And folder "/do-not-transfer" of user "user2" is shared with user "user0" with permissions 31
- And user "user0" accepts last share
- When transferring ownership of path "sub" from "user0" to "user1" with received shares
- And the command was successful
- And As an "user1"
- And using received transfer folder of "user1" as dav path
- Then as "user1" the folder "/sub" exists
- And as "user1" the folder "/do-not-transfer" does not exist
- And as "user1" the folder "/sub/do-not-transfer" does not exist
- And as "user1" the folder "/sub/transfer-share" exists
- And using old dav path
- And as "user1" the folder "/transfer-share" does not exist
- And as "user1" the folder "/do-not-transfer" does not exist
- And using old dav path
- And as "user0" the folder "/sub" does not exist
- And as "user0" the folder "/do-not-transfer" exists
- And Getting info of last share
- And the OCS status code should be "404"
-
- Scenario: transferring ownership does not transfer external storage
- Given user "user0" exists
- And user "user1" exists
- And User "user0" created a folder "/sub"
- When transferring ownership of path "sub" 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: transferring ownership fails with invalid source user
- Given user "user0" exists
- And User "user0" created a folder "/sub"
- When transferring ownership of path "sub" from "invalid_user" to "user0"
- Then the command output contains the text "Unknown source user"
- And the command failed with exit code 1
-
- Scenario: transferring ownership fails with invalid target user
- Given user "user0" exists
- And User "user0" created a folder "/sub"
- When transferring ownership of path "sub" from "user0" to "invalid_user"
- Then the command output contains the text "Unknown destination user invalid_user"
- And the command failed with exit code 1
-
- Scenario: transferring ownership fails with invalid path
- Given user "user0" exists
- And user "user1" exists
- When transferring ownership of path "test" from "user0" to "user1"
- Then the command output contains the text "Unknown path provided: test"
- And the command failed with exit code 1
diff --git a/build/integration/features/trashbin.feature b/build/integration/features/trashbin.feature
deleted file mode 100644
index 3a9c29f7cb8..00000000000
--- a/build/integration/features/trashbin.feature
+++ /dev/null
@@ -1,81 +0,0 @@
-Feature: trashbin
- Background:
- Given using api version "1"
- And using new dav path
- And As an "admin"
- And app "files_trashbin" is enabled
-
- Scenario: deleting a file moves it to trashbin
- Given As an "admin"
- And user "user0" exists
- When User "user0" deletes file "/textfile0.txt"
- Then user "user0" in trash folder "/" should have 1 element
- And user "user0" in trash folder "/" should have the following elements
- | textfile0.txt |
-
- Scenario: clearing the trashbin
- Given As an "admin"
- And user "user0" exists
- When User "user0" deletes file "/textfile0.txt"
- And User "user0" empties trashbin
- Then user "user0" in trash folder "/" should have 0 elements
-
- Scenario: restoring file from trashbin
- Given As an "admin"
- And user "user0" exists
- When User "user0" deletes file "/textfile0.txt"
- And user "user0" in restores "/textfile0.txt" from trash
- Then user "user0" in trash folder "/" should have 0 elements
- And as "user0" the file "/textfile0.txt" exists
-
- Scenario: deleting and restoring a folder
- Given As an "admin"
- And user "user0" exists
- When User "user0" created a folder "/testfolder"
- And User "user0" moves file "/textfile0.txt" to "/testfolder/textfile0.txt"
- And as "user0" the file "/testfolder/textfile0.txt" exists
- And User "user0" deletes file "/testfolder"
- And user "user0" in trash folder "/" should have 1 element
- And user "user0" in trash folder "/" should have the following elements
- | testfolder |
- And user "user0" in trash folder "/testfolder" should have 1 element
- And user "user0" in trash folder "/testfolder" should have the following elements
- | textfile0.txt |
- And user "user0" in restores "/testfolder" from trash
- Then user "user0" in trash folder "/" should have 0 elements
- And as "user0" the file "/testfolder/textfile0.txt" exists
-
- Scenario: deleting a file from a subfolder and restoring it moves it back to the subfolder
- Given As an "admin"
- And user "user0" exists
- When User "user0" created a folder "/testfolder"
- And User "user0" moves file "/textfile0.txt" to "/testfolder/textfile0.txt"
- And as "user0" the file "/testfolder/textfile0.txt" exists
- And User "user0" deletes file "/testfolder/textfile0.txt"
- And user "user0" in trash folder "/" should have 1 element
- And user "user0" in trash folder "/" should have the following elements
- | textfile0.txt |
- And user "user0" in restores "/textfile0.txt" from trash
- Then user "user0" in trash folder "/" should have 0 elements
- And as "user0" the file "/textfile0.txt" does not exist
- And as "user0" the file "/testfolder/textfile0.txt" exists
-
- Scenario: deleting and a folder and restoring a file inside it
- Given As an "admin"
- And user "user0" exists
- When User "user0" created a folder "/testfolder"
- And User "user0" moves file "/textfile0.txt" to "/testfolder/textfile0.txt"
- And as "user0" the file "/testfolder/textfile0.txt" exists
- And User "user0" deletes file "/testfolder"
- And user "user0" in trash folder "/" should have 1 element
- And user "user0" in trash folder "/" should have the following elements
- | testfolder |
- And user "user0" in trash folder "/testfolder" should have 1 element
- And user "user0" in trash folder "/testfolder" should have the following elements
- | textfile0.txt |
- And user "user0" in restores "/testfolder/textfile0.txt" from trash
- Then user "user0" in trash folder "/" should have 1 elements
- And user "user0" in trash folder "/testfolder" should have 0 element
- And as "user0" the file "/textfile0.txt" exists
-
-
diff --git a/build/integration/features/webdav-related.feature b/build/integration/features/webdav-related.feature
deleted file mode 100644
index efaea1a43c4..00000000000
--- a/build/integration/features/webdav-related.feature
+++ /dev/null
@@ -1,632 +0,0 @@
-Feature: webdav-related
- Background:
- Given using api version "1"
-
- Scenario: Unauthenticated call old dav path
- Given using old dav path
- When connecting to dav endpoint
- Then the HTTP status code should be "401"
- And there are no duplicate headers
- And The following headers should be set
- |WWW-Authenticate|Basic realm="Nextcloud", charset="UTF-8"|
-
- Scenario: Unauthenticated call new dav path
- Given using new dav path
- When connecting to dav endpoint
- Then the HTTP status code should be "401"
- And there are no duplicate headers
- And The following headers should be set
- |WWW-Authenticate|Basic realm="Nextcloud", charset="UTF-8"|
-
- Scenario: Moving a file
- Given using old dav path
- And As an "admin"
- And user "user0" exists
- And As an "user0"
- When User "user0" moves file "/welcome.txt" to "/FOLDER/welcome.txt"
- Then the HTTP status code should be "201"
- And Downloaded content when downloading file "/FOLDER/welcome.txt" with range "bytes=0-6" should be "Welcome"
-
- Scenario: Moving and overwriting a file old way
- Given using old dav path
- And As an "admin"
- And user "user0" exists
- And As an "user0"
- When User "user0" moves file "/welcome.txt" to "/textfile0.txt"
- Then the HTTP status code should be "204"
- And Downloaded content when downloading file "/textfile0.txt" with range "bytes=0-6" should be "Welcome"
-
- Scenario: Moving a file to a folder with no permissions
- Given using old dav path
- And As an "admin"
- And user "user0" exists
- And user "user1" exists
- And As an "user1"
- And user "user1" created a folder "/testshare"
- And as "user1" creating a share with
- | path | testshare |
- | shareType | 0 |
- | permissions | 1 |
- | shareWith | user0 |
- And user "user0" accepts last share
- And As an "user0"
- And User "user0" moves file "/textfile0.txt" to "/testshare/textfile0.txt"
- And the HTTP status code should be "403"
- When Downloading file "/testshare/textfile0.txt"
- Then the HTTP status code should be "404"
-
- Scenario: Moving a file to overwrite a file in a folder with no permissions
- Given using old dav path
- And As an "admin"
- And user "user0" exists
- And user "user1" exists
- And As an "user1"
- And user "user1" created a folder "/testshare"
- And as "user1" creating a share with
- | path | testshare |
- | shareType | 0 |
- | permissions | 1 |
- | shareWith | user0 |
- And user "user0" accepts last share
- And User "user1" copies file "/welcome.txt" to "/testshare/overwritethis.txt"
- And As an "user0"
- When User "user0" moves file "/textfile0.txt" to "/testshare/overwritethis.txt"
- Then the HTTP status code should be "403"
- And Downloaded content when downloading file "/testshare/overwritethis.txt" with range "bytes=0-6" should be "Welcome"
-
- Scenario: Copying a file
- Given using old dav path
- And As an "admin"
- And user "user0" exists
- And As an "user0"
- When User "user0" copies file "/welcome.txt" to "/FOLDER/welcome.txt"
- Then the HTTP status code should be "201"
- And Downloaded content when downloading file "/FOLDER/welcome.txt" with range "bytes=0-6" should be "Welcome"
-
- Scenario: Copying and overwriting a file
- Given using old dav path
- And As an "admin"
- And user "user0" exists
- And As an "user0"
- When User "user0" copies file "/welcome.txt" to "/textfile1.txt"
- Then the HTTP status code should be "204"
- And Downloaded content when downloading file "/textfile1.txt" with range "bytes=0-6" should be "Welcome"
-
- Scenario: Copying a file to a folder with no permissions
- Given using old dav path
- And As an "admin"
- And user "user0" exists
- And user "user1" exists
- And As an "user1"
- And user "user1" created a folder "/testshare"
- And as "user1" creating a share with
- | path | testshare |
- | shareType | 0 |
- | permissions | 1 |
- | shareWith | user0 |
- And user "user0" accepts last share
- And As an "user0"
- When User "user0" copies file "/textfile0.txt" to "/testshare/textfile0.txt"
- Then the HTTP status code should be "403"
- And Downloading file "/testshare/textfile0.txt"
- And the HTTP status code should be "404"
-
- Scenario: Copying a file to overwrite a file into a folder with no permissions
- Given using old dav path
- And As an "admin"
- And user "user0" exists
- And user "user1" exists
- And As an "user1"
- And user "user1" created a folder "/testshare"
- And as "user1" creating a share with
- | path | testshare |
- | shareType | 0 |
- | permissions | 1 |
- | shareWith | user0 |
- And user "user0" accepts last share
- And User "user1" copies file "/welcome.txt" to "/testshare/overwritethis.txt"
- And As an "user0"
- When User "user0" copies file "/textfile0.txt" to "/testshare/overwritethis.txt"
- Then the HTTP status code should be "403"
- And Downloaded content when downloading file "/testshare/overwritethis.txt" with range "bytes=0-6" should be "Welcome"
-
- Scenario: download a file with range
- Given using old dav path
- And As an "admin"
- When Downloading file "/welcome.txt" with range "bytes=52-78"
- Then Downloaded content should be "example file for developers"
-
- Scenario: Upload forbidden if quota is 0
- Given using old dav path
- And As an "admin"
- And user "user0" exists
- And user "user0" has a quota of "0"
- When User "user0" uploads file "data/textfile.txt" to "/asdf.txt"
- Then the HTTP status code should be "507"
-
- Scenario: Retrieving folder quota when no quota is set
- Given using old dav path
- And As an "admin"
- And user "user0" exists
- When user "user0" has unlimited quota
- Then as "user0" gets properties of folder "/" with
- |{DAV:}quota-available-bytes|
- And the single response should contain a property "{DAV:}quota-available-bytes" with value "-3"
-
- Scenario: Retrieving folder quota when quota is set
- Given using old dav path
- And As an "admin"
- And user "user0" exists
- When user "user0" has a quota of "10 MB"
- Then as "user0" gets properties of folder "/" with
- |{DAV:}quota-available-bytes|
- And the single response should contain a property "{DAV:}quota-available-bytes" with value "10485421"
-
- Scenario: Retrieving folder quota of shared folder with quota when no quota is set for recipient
- Given using old dav path
- And As an "admin"
- And user "user0" exists
- And user "user1" exists
- And user "user0" has unlimited quota
- And user "user1" has a quota of "10 MB"
- And As an "user1"
- And user "user1" created a folder "/testquota"
- And as "user1" creating a share with
- | path | testquota |
- | shareType | 0 |
- | permissions | 31 |
- | shareWith | user0 |
- And user "user0" accepts last share
- Then as "user0" gets properties of folder "/testquota" with
- |{DAV:}quota-available-bytes|
- And the single response should contain a property "{DAV:}quota-available-bytes" with value "10485421"
-
- Scenario: Uploading a file as recipient using webdav having quota
- Given using old dav path
- And As an "admin"
- And user "user0" exists
- And user "user1" exists
- And user "user0" has a quota of "10 MB"
- And user "user1" has a quota of "10 MB"
- And As an "user1"
- And user "user1" created a folder "/testquota"
- And as "user1" creating a share with
- | path | testquota |
- | shareType | 0 |
- | permissions | 31 |
- | shareWith | user0 |
- And user "user0" accepts last share
- And As an "user0"
- When User "user0" uploads file "data/textfile.txt" to "/testquota/asdf.txt"
- Then the HTTP status code should be "201"
-
- Scenario: Retrieving folder quota when quota is set and a file was uploaded
- Given using old dav path
- And As an "admin"
- And user "user0" exists
- And user "user0" has a quota of "1 KB"
- And user "user0" adds a file of 93 bytes to "/prueba.txt"
- When as "user0" gets properties of folder "/" with
- |{DAV:}quota-available-bytes|
- Then the single response should contain a property "{DAV:}quota-available-bytes" with value "592"
-
- Scenario: Retrieving folder quota when quota is set and a file was received
- Given using old dav path
- And As an "admin"
- And user "user0" exists
- And user "user1" exists
- And user "user1" has a quota of "1 KB"
- And user "user0" adds a file of 93 bytes to "/user0.txt"
- And file "user0.txt" of user "user0" is shared with user "user1"
- And user "user1" accepts last share
- When as "user1" gets properties of folder "/" with
- |{DAV:}quota-available-bytes|
- Then the single response should contain a property "{DAV:}quota-available-bytes" with value "685"
-
- Scenario: download a public shared file with range
- Given user "user0" exists
- And As an "user0"
- When creating a share with
- | path | welcome.txt |
- | shareType | 3 |
- And Downloading last public shared file with range "bytes=52-78"
- Then Downloaded content should be "example file for developers"
-
- Scenario: download a public shared file inside a folder with range
- Given user "user0" exists
- And As an "user0"
- When creating a share with
- | path | PARENT |
- | shareType | 3 |
- And Downloading last public shared file inside a folder "/parent.txt" with range "bytes=1-8"
- Then Downloaded content should be "extcloud"
-
- Scenario: Downloading a file on the old endpoint should serve security headers
- Given using old dav path
- And As an "admin"
- When Downloading file "/welcome.txt"
- Then The following headers should be set
- |Content-Disposition|attachment; filename*=UTF-8''welcome.txt; filename="welcome.txt"|
- |Content-Security-Policy|default-src 'none';|
- |X-Content-Type-Options |nosniff|
- |X-Frame-Options|SAMEORIGIN|
- |X-Permitted-Cross-Domain-Policies|none|
- |X-Robots-Tag|none|
- |X-XSS-Protection|1; mode=block|
- And Downloaded content should start with "Welcome to your Nextcloud account!"
-
- Scenario: Doing a GET with a web login should work without CSRF token on the old backend
- Given Logging in using web as "admin"
- When Sending a "GET" to "/remote.php/webdav/welcome.txt" without requesttoken
- Then Downloaded content should start with "Welcome to your Nextcloud account!"
- Then the HTTP status code should be "200"
-
- Scenario: Doing a GET with a web login should work with CSRF token on the old backend
- Given Logging in using web as "admin"
- When Sending a "GET" to "/remote.php/webdav/welcome.txt" with requesttoken
- Then Downloaded content should start with "Welcome to your Nextcloud account!"
- Then the HTTP status code should be "200"
-
- Scenario: Doing a PROPFIND with a web login should not work without CSRF token on the old backend
- Given Logging in using web as "admin"
- When Sending a "PROPFIND" to "/remote.php/webdav/welcome.txt" without requesttoken
- Then the HTTP status code should be "401"
-
- Scenario: Doing a PROPFIND with a web login should work with CSRF token on the old backend
- Given Logging in using web as "admin"
- When Sending a "PROPFIND" to "/remote.php/webdav/welcome.txt" with requesttoken
- Then the HTTP status code should be "207"
-
- Scenario: Upload chunked file asc
- Given user "user0" exists
- And user "user0" uploads chunk file "1" of "3" with "AAAAA" to "/myChunkedFile.txt"
- And user "user0" uploads chunk file "2" of "3" with "BBBBB" to "/myChunkedFile.txt"
- And user "user0" uploads chunk file "3" of "3" with "CCCCC" to "/myChunkedFile.txt"
- When As an "user0"
- And Downloading file "/myChunkedFile.txt"
- Then Downloaded content should be "AAAAABBBBBCCCCC"
-
- Scenario: Upload chunked file desc
- Given user "user0" exists
- And user "user0" uploads chunk file "3" of "3" with "CCCCC" to "/myChunkedFile.txt"
- And user "user0" uploads chunk file "2" of "3" with "BBBBB" to "/myChunkedFile.txt"
- And user "user0" uploads chunk file "1" of "3" with "AAAAA" to "/myChunkedFile.txt"
- When As an "user0"
- And Downloading file "/myChunkedFile.txt"
- Then Downloaded content should be "AAAAABBBBBCCCCC"
-
- Scenario: Upload chunked file random
- Given user "user0" exists
- And user "user0" uploads chunk file "2" of "3" with "BBBBB" to "/myChunkedFile.txt"
- And user "user0" uploads chunk file "3" of "3" with "CCCCC" to "/myChunkedFile.txt"
- And user "user0" uploads chunk file "1" of "3" with "AAAAA" to "/myChunkedFile.txt"
- When As an "user0"
- And Downloading file "/myChunkedFile.txt"
- Then Downloaded content should be "AAAAABBBBBCCCCC"
-
- Scenario: A file that is not shared does not have a share-types property
- Given user "user0" exists
- And user "user0" created a folder "/test"
- When as "user0" gets properties of folder "/test" with
- |{http://owncloud.org/ns}share-types|
- Then the response should contain an empty property "{http://owncloud.org/ns}share-types"
-
- Scenario: A file that is shared to a user has a share-types property
- Given user "user0" exists
- And user "user1" exists
- And user "user0" created a folder "/test"
- And as "user0" creating a share with
- | path | test |
- | shareType | 0 |
- | permissions | 31 |
- | shareWith | user1 |
- When as "user0" gets properties of folder "/test" with
- |{http://owncloud.org/ns}share-types|
- Then the response should contain a share-types property with
- | 0 |
-
- Scenario: A file that is shared to a group has a share-types property
- Given user "user0" exists
- And group "group1" exists
- And user "user0" created a folder "/test"
- And as "user0" creating a share with
- | path | test |
- | shareType | 1 |
- | permissions | 31 |
- | shareWith | group1 |
- When as "user0" gets properties of folder "/test" with
- |{http://owncloud.org/ns}share-types|
- Then the response should contain a share-types property with
- | 1 |
-
- Scenario: A file that is shared by link has a share-types property
- Given user "user0" exists
- And user "user0" created a folder "/test"
- And as "user0" creating a share with
- | path | test |
- | shareType | 3 |
- | permissions | 31 |
- When as "user0" gets properties of folder "/test" with
- |{http://owncloud.org/ns}share-types|
- Then the response should contain a share-types property with
- | 3 |
-
- Scenario: A file that is shared by user,group and link has a share-types property
- Given user "user0" exists
- And user "user1" exists
- And group "group2" exists
- And user "user0" created a folder "/test"
- And as "user0" creating a share with
- | path | test |
- | shareType | 0 |
- | permissions | 31 |
- | shareWith | user1 |
- And as "user0" creating a share with
- | path | test |
- | shareType | 1 |
- | permissions | 31 |
- | shareWith | group2 |
- And as "user0" creating a share with
- | path | test |
- | shareType | 3 |
- | permissions | 31 |
- When as "user0" gets properties of folder "/test" with
- |{http://owncloud.org/ns}share-types|
- Then the response should contain a share-types property with
- | 0 |
- | 1 |
- | 3 |
-
- Scenario: Upload chunked file asc with new chunking
- Given using new dav path
- And user "user0" exists
- And user "user0" creates a new chunking upload with id "chunking-42"
- And user "user0" uploads new chunk file "1" with "AAAAA" to id "chunking-42"
- And user "user0" uploads new chunk file "2" with "BBBBB" to id "chunking-42"
- And user "user0" uploads new chunk file "3" with "CCCCC" to id "chunking-42"
- And user "user0" moves new chunk file with id "chunking-42" to "/myChunkedFile.txt"
- When As an "user0"
- And Downloading file "/myChunkedFile.txt"
- Then Downloaded content should be "AAAAABBBBBCCCCC"
-
- Scenario: Upload chunked file desc with new chunking
- Given using new dav path
- And user "user0" exists
- And user "user0" creates a new chunking upload with id "chunking-42"
- And user "user0" uploads new chunk file "3" with "CCCCC" to id "chunking-42"
- And user "user0" uploads new chunk file "2" with "BBBBB" to id "chunking-42"
- And user "user0" uploads new chunk file "1" with "AAAAA" to id "chunking-42"
- And user "user0" moves new chunk file with id "chunking-42" to "/myChunkedFile.txt"
- When As an "user0"
- And Downloading file "/myChunkedFile.txt"
- Then Downloaded content should be "AAAAABBBBBCCCCC"
-
- Scenario: Upload chunked file random with new chunking
- Given using new dav path
- And user "user0" exists
- And user "user0" creates a new chunking upload with id "chunking-42"
- And user "user0" uploads new chunk file "2" with "BBBBB" to id "chunking-42"
- And user "user0" uploads new chunk file "3" with "CCCCC" to id "chunking-42"
- And user "user0" uploads new chunk file "1" with "AAAAA" to id "chunking-42"
- And user "user0" moves new chunk file with id "chunking-42" to "/myChunkedFile.txt"
- When As an "user0"
- And Downloading file "/myChunkedFile.txt"
- Then Downloaded content should be "AAAAABBBBBCCCCC"
-
- Scenario: A disabled user cannot use webdav
- Given user "userToBeDisabled" exists
- And As an "admin"
- And assure user "userToBeDisabled" is disabled
- When Downloading file "/welcome.txt" as "userToBeDisabled"
- Then the HTTP status code should be "503"
-
- Scenario: Copying files into a folder with edit permissions
- Given using dav path "remote.php/webdav"
- And user "user0" exists
- And user "user1" exists
- And As an "user1"
- And user "user1" created a folder "/testcopypermissionsAllowed"
- And as "user1" creating a share with
- | path | testcopypermissionsAllowed |
- | shareType | 0 |
- | permissions | 31 |
- | shareWith | user0 |
- And user "user0" accepts last share
- And User "user0" uploads file with content "copytest" to "/copytest.txt"
- When User "user0" copies file "/copytest.txt" to "/testcopypermissionsAllowed/copytest.txt"
- Then the HTTP status code should be "201"
-
- Scenario: Copying files into a folder without edit permissions
- Given using dav path "remote.php/webdav"
- And user "user0" exists
- And user "user1" exists
- And As an "user1"
- And user "user1" created a folder "/testcopypermissionsNotAllowed"
- And as "user1" creating a share with
- | path | testcopypermissionsNotAllowed |
- | shareType | 0 |
- | permissions | 1 |
- | shareWith | user0 |
- And user "user0" accepts last share
- And User "user0" uploads file with content "copytest" to "/copytest.txt"
- When User "user0" copies file "/copytest.txt" to "/testcopypermissionsNotAllowed/copytest.txt"
- Then the HTTP status code should be "403"
-
- Scenario: Uploading a file as recipient with limited permissions
- Given using new dav path
- And As an "admin"
- And user "user0" exists
- And user "user1" exists
- And user "user0" has a quota of "10 MB"
- And user "user1" has a quota of "10 MB"
- And As an "user1"
- And user "user1" created a folder "/testfolder"
- And as "user1" creating a share with
- | path | testfolder |
- | shareType | 0 |
- | permissions | 23 |
- | shareWith | user0 |
- And user "user0" accepts last share
- And As an "user0"
- And User "user0" uploads file "data/textfile.txt" to "/testfolder/asdf.txt"
- And As an "user1"
- When User "user1" deletes file "/testfolder/asdf.txt"
- Then the HTTP status code should be "204"
-
- Scenario: Creating a folder
- Given using old dav path
- And user "user0" exists
- And user "user0" created a folder "/test_folder"
- When as "user0" gets properties of folder "/test_folder" with
- |{DAV:}resourcetype|
- Then the single response should contain a property "{DAV:}resourcetype" with value "{DAV:}collection"
-
- Scenario: Creating a folder with special chars
- Given using old dav path
- And user "user0" exists
- And user "user0" created a folder "/test_folder:5"
- When as "user0" gets properties of folder "/test_folder:5" with
- |{DAV:}resourcetype|
- Then the single response should contain a property "{DAV:}resourcetype" with value "{DAV:}collection"
-
- Scenario: Removing everything of a folder
- Given using old dav path
- And As an "admin"
- And user "user0" exists
- And As an "user0"
- And User "user0" moves file "/welcome.txt" to "/FOLDER/welcome.txt"
- And user "user0" created a folder "/FOLDER/SUBFOLDER"
- And User "user0" copies file "/textfile0.txt" to "/FOLDER/SUBFOLDER/testfile0.txt"
- When User "user0" deletes everything from folder "/FOLDER/"
- Then user "user0" should see following elements
- | /FOLDER/ |
- | /PARENT/ |
- | /PARENT/parent.txt |
- | /textfile0.txt |
- | /textfile1.txt |
- | /textfile2.txt |
- | /textfile3.txt |
- | /textfile4.txt |
-
- Scenario: Removing everything of a folder using new dav path
- Given using new dav path
- And As an "admin"
- And user "user0" exists
- And As an "user0"
- And User "user0" moves file "/welcome.txt" to "/FOLDER/welcome.txt"
- And user "user0" created a folder "/FOLDER/SUBFOLDER"
- And User "user0" copies file "/textfile0.txt" to "/FOLDER/SUBFOLDER/testfile0.txt"
- When User "user0" deletes everything from folder "/FOLDER/"
- Then user "user0" should see following elements
- | /FOLDER/ |
- | /PARENT/ |
- | /PARENT/parent.txt |
- | /textfile0.txt |
- | /textfile1.txt |
- | /textfile2.txt |
- | /textfile3.txt |
- | /textfile4.txt |
-
- Scenario: Checking file id after a move using new endpoint
- Given using new dav path
- And user "user0" exists
- And User "user0" stores id of file "/textfile0.txt"
- When User "user0" moves file "/textfile0.txt" to "/FOLDER/textfile0.txt"
- Then User "user0" checks id of file "/FOLDER/textfile0.txt"
-
- Scenario: Checking file id after a move overwrite using new chunking endpoint
- Given using new dav path
- And user "user0" exists
- And User "user0" copies file "/textfile0.txt" to "/existingFile.txt"
- And User "user0" stores id of file "/existingFile.txt"
- And user "user0" creates a new chunking upload with id "chunking-42"
- And user "user0" uploads new chunk file "1" with "AAAAA" to id "chunking-42"
- And user "user0" uploads new chunk file "2" with "BBBBB" to id "chunking-42"
- And user "user0" uploads new chunk file "3" with "CCCCC" to id "chunking-42"
- When user "user0" moves new chunk file with id "chunking-42" to "/existingFile.txt"
- Then User "user0" checks id of file "/existingFile.txt"
-
- Scenario: Renaming a folder to a backslash encoded should return an error using old endpoint
- Given using old dav path
- And user "user0" exists
- And user "user0" created a folder "/testshare"
- When User "user0" moves folder "/testshare" to "/%5C"
- Then the HTTP status code should be "400"
-
- Scenario: Renaming a folder beginning with a backslash encoded should return an error using old endpoint
- Given using old dav path
- And user "user0" exists
- And user "user0" created a folder "/testshare"
- When User "user0" moves folder "/testshare" to "/%5Ctestshare"
- Then the HTTP status code should be "400"
-
- Scenario: Renaming a folder including a backslash encoded should return an error using old endpoint
- Given using old dav path
- And user "user0" exists
- And user "user0" created a folder "/testshare"
- When User "user0" moves folder "/testshare" to "/hola%5Chola"
- Then the HTTP status code should be "400"
-
- Scenario: Renaming a folder to a backslash encoded should return an error using new endpoint
- Given using new dav path
- And user "user0" exists
- And user "user0" created a folder "/testshare"
- When User "user0" moves folder "/testshare" to "/%5C"
- Then the HTTP status code should be "400"
-
- Scenario: Renaming a folder beginning with a backslash encoded should return an error using new endpoint
- Given using new dav path
- And user "user0" exists
- And user "user0" created a folder "/testshare"
- When User "user0" moves folder "/testshare" to "/%5Ctestshare"
- Then the HTTP status code should be "400"
-
- Scenario: Renaming a folder including a backslash encoded should return an error using new endpoint
- Given using new dav path
- And user "user0" exists
- And user "user0" created a folder "/testshare"
- When User "user0" moves folder "/testshare" to "/hola%5Chola"
- Then the HTTP status code should be "400"
-
- Scenario: Upload file via new chunking endpoint with wrong size header
- Given using new dav path
- And user "user0" exists
- And user "user0" creates a new chunking upload with id "chunking-42"
- And user "user0" uploads new chunk file "1" with "AAAAA" to id "chunking-42"
- And user "user0" uploads new chunk file "2" with "BBBBB" to id "chunking-42"
- And user "user0" uploads new chunk file "3" with "CCCCC" to id "chunking-42"
- When user "user0" moves new chunk file with id "chunking-42" to "/myChunkedFile.txt" with size 5
- Then the HTTP status code should be "400"
-
- Scenario: Upload file via new chunking endpoint with correct size header
- Given using new dav path
- And user "user0" exists
- And user "user0" creates a new chunking upload with id "chunking-42"
- And user "user0" uploads new chunk file "1" with "AAAAA" to id "chunking-42"
- And user "user0" uploads new chunk file "2" with "BBBBB" to id "chunking-42"
- And user "user0" uploads new chunk file "3" with "CCCCC" to id "chunking-42"
- When user "user0" moves new chunk file with id "chunking-42" to "/myChunkedFile.txt" with size 15
- Then the HTTP status code should be "201"
-
- Scenario: Upload bulked files
- Given user "user0" exists
- And user "user0" uploads bulked files "A.txt" with "AAAAA" and "B.txt" with "BBBBB" and "C.txt" with "CCCCC"
- When As an "user0"
- Then Downloading file "/A.txt"
- And Downloaded content should be "AAAAA"
- And File "/A.txt" should have prop "d:getlastmodified" equal to "Fri, 18 Mar 2005 01:58:31 GMT"
- And Downloading file "/B.txt"
- And Downloaded content should be "BBBBB"
- And File "/B.txt" should have prop "d:getlastmodified" equal to "Sat, 02 Jun 2040 03:57:02 GMT"
- And Downloading file "/C.txt"
- And Downloaded content should be "CCCCC"
- And File "/C.txt" should have prop "d:getlastmodified" equal to "Sun, 18 Aug 2075 05:55:33 GMT"
-
- Scenario: Creating a folder with invalid characters
- Given using new dav path
- And As an "admin"
- And user "user0" exists
- And user "user1" exists
- And As an "user1"
- And user "user1" created a folder "/testshare "
- Then the HTTP status code should be "400"