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.feature92
-rw-r--r--build/integration/features/bootstrap/Activity.php32
-rw-r--r--build/integration/features/bootstrap/AppConfiguration.php32
-rw-r--r--build/integration/features/bootstrap/Auth.php37
-rw-r--r--build/integration/features/bootstrap/Avatar.php53
-rw-r--r--build/integration/features/bootstrap/BasicStructure.php134
-rw-r--r--build/integration/features/bootstrap/CalDavContext.php212
-rw-r--r--build/integration/features/bootstrap/CapabilitiesContext.php37
-rw-r--r--build/integration/features/bootstrap/CardDavContext.php100
-rw-r--r--build/integration/features/bootstrap/ChecksumsContext.php68
-rw-r--r--build/integration/features/bootstrap/CollaborationContext.php206
-rw-r--r--build/integration/features/bootstrap/CommandLine.php31
-rw-r--r--build/integration/features/bootstrap/CommandLineContext.php48
-rw-r--r--build/integration/features/bootstrap/CommentsContext.php72
-rw-r--r--build/integration/features/bootstrap/ContactsMenu.php51
-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.php74
-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.php36
-rw-r--r--build/integration/features/bootstrap/FederationContext.php173
-rw-r--r--build/integration/features/bootstrap/FilesDropContext.php67
-rw-r--r--build/integration/features/bootstrap/LDAPContext.php31
-rw-r--r--build/integration/features/bootstrap/Mail.php28
-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.php248
-rw-r--r--build/integration/features/bootstrap/RateLimitingContext.php31
-rw-r--r--build/integration/features/bootstrap/RemoteContext.php39
-rw-r--r--build/integration/features/bootstrap/RoutingContext.php19
-rw-r--r--build/integration/features/bootstrap/Search.php24
-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.php167
-rw-r--r--build/integration/features/bootstrap/SharingContext.php32
-rw-r--r--build/integration/features/bootstrap/TagsContext.php38
-rw-r--r--build/integration/features/bootstrap/TalkContext.php24
-rw-r--r--build/integration/features/bootstrap/Theming.php49
-rw-r--r--build/integration/features/bootstrap/Trashbin.php32
-rw-r--r--build/integration/features/bootstrap/WebDav.php623
-rw-r--r--build/integration/features/caldav.feature61
-rw-r--r--build/integration/features/carddav.feature66
-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.feature194
-rw-r--r--build/integration/features/dav-v2.feature83
-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.feature1505
-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.feature565
-rw-r--r--build/integration/features/trashbin.feature81
-rw-r--r--build/integration/features/webdav-related.feature610
62 files changed, 3618 insertions, 4685 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 f7926615c01..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:
@@ -8,7 +10,7 @@ Feature: avatar
Then The following headers should be set
| Content-Type | image/png |
| X-NC-IsCustomAvatar | 0 |
- And last avatar is a square of size 128
+ And last avatar is a square of size 512
And last avatar is not a single color
Scenario: get default user avatar as an anonymous user
@@ -16,37 +18,69 @@ Feature: avatar
Then The following headers should be set
| Content-Type | image/png |
| X-NC-IsCustomAvatar | 0 |
- And last avatar is a square of size 128
+ And last avatar is a square of size 512
And last avatar is not a single color
- Scenario: get temporary user avatar before cropping it
+ Scenario: get temporary non-square user avatar before cropping it
Given Logging in using web as "user0"
- And logged in user posts temporary avatar from file "data/green-square-256.png"
+ And logged in user posts temporary avatar from file "data/coloured-pattern-non-square.png"
When logged in user gets temporary avatar
Then The following headers should be set
| Content-Type | image/png |
# "last avatar" also includes the last temporary avatar
- And last avatar is a square of size 256
- And last avatar is a single "#00FF00" color
+ And last avatar is not a square
+ And last avatar is not a single color
- Scenario: get user avatar before cropping it
+ Scenario: get non-square user avatar before cropping it
Given Logging in using web as "user0"
- And logged in user posts temporary avatar from file "data/green-square-256.png"
- # Avatar needs to be cropped to finish setting it even if it is squared
+ And logged in user posts temporary avatar from file "data/coloured-pattern-non-square.png"
+ # Avatar needs to be cropped to finish setting it
When user "user0" gets avatar for user "user0"
Then The following headers should be set
| Content-Type | image/png |
| X-NC-IsCustomAvatar | 0 |
- And last avatar is a square of size 128
+ And last avatar is a square of size 512
And last avatar is not a single color
+ Scenario: set square user avatar from file
+ Given Logging in using web as "user0"
+ When logged in user posts temporary avatar from file "data/green-square-256.png"
+ And user "user0" gets avatar for user "user0"
+ And The following headers should be set
+ | Content-Type | image/png |
+ | X-NC-IsCustomAvatar | 1 |
+ # Last avatar size is 512 by default when getting avatar without size parameter
+ And last avatar is a square of size 512
+ And last avatar is a single "#00FF00" color
+ And user "anonymous" gets avatar for user "user0"
+ And The following headers should be set
+ | Content-Type | image/png |
+ | X-NC-IsCustomAvatar | 1 |
+ And last avatar is a square of size 512
+ And last avatar is a single "#00FF00" color
+ Scenario: set square user avatar from internal path
+ Given user "user0" uploads file "data/green-square-256.png" to "/internal-green-square-256.png"
+ And Logging in using web as "user0"
+ When logged in user posts temporary avatar from internal path "internal-green-square-256.png"
+ And user "user0" gets avatar for user "user0" with size "64"
+ And The following headers should be set
+ | Content-Type | image/png |
+ | X-NC-IsCustomAvatar | 1 |
+ And last avatar is a square of size 64
+ And last avatar is a single "#00FF00" color
+ And user "anonymous" gets avatar for user "user0" with size "64"
+ And The following headers should be set
+ | Content-Type | image/png |
+ | X-NC-IsCustomAvatar | 1 |
+ And last avatar is a square of size 64
+ And last avatar is a single "#00FF00" color
- Scenario: set user avatar from file
+ Scenario: set non-square user avatar from file
Given Logging in using web as "user0"
- When logged in user posts temporary avatar from file "data/coloured-pattern.png"
+ When logged in user posts temporary avatar from file "data/coloured-pattern-non-square.png"
And logged in user crops temporary avatar
| x | 384 |
| y | 256 |
@@ -57,19 +91,19 @@ Feature: avatar
And The following headers should be set
| Content-Type | image/png |
| X-NC-IsCustomAvatar | 1 |
- And last avatar is a square of size 128
+ And last avatar is a square of size 512
And last avatar is a single "#FF0000" color
And user "anonymous" gets avatar for user "user0"
And The following headers should be set
| Content-Type | image/png |
| X-NC-IsCustomAvatar | 1 |
- And last avatar is a square of size 128
+ And last avatar is a square of size 512
And last avatar is a single "#FF0000" color
- Scenario: set user avatar from internal path
- Given user "user0" uploads file "data/coloured-pattern.png" to "/internal-coloured-pattern.png"
+ Scenario: set non-square user avatar from internal path
+ Given user "user0" uploads file "data/coloured-pattern-non-square.png" to "/internal-coloured-pattern-non-square.png"
And Logging in using web as "user0"
- When logged in user posts temporary avatar from internal path "internal-coloured-pattern.png"
+ When logged in user posts temporary avatar from internal path "internal-coloured-pattern-non-square.png"
And logged in user crops temporary avatar
| x | 704 |
| y | 320 |
@@ -91,7 +125,7 @@ Feature: avatar
Scenario: cropped user avatar needs to be squared
Given Logging in using web as "user0"
- And logged in user posts temporary avatar from file "data/coloured-pattern.png"
+ And logged in user posts temporary avatar from file "data/coloured-pattern-non-square.png"
When logged in user crops temporary avatar with 400
| x | 384 |
| y | 256 |
@@ -102,7 +136,7 @@ Feature: avatar
Scenario: delete user avatar
Given Logging in using web as "user0"
- And logged in user posts temporary avatar from file "data/coloured-pattern.png"
+ And logged in user posts temporary avatar from file "data/coloured-pattern-non-square.png"
And logged in user crops temporary avatar
| x | 384 |
| y | 256 |
@@ -112,33 +146,33 @@ Feature: avatar
And The following headers should be set
| Content-Type | image/png |
| X-NC-IsCustomAvatar | 1 |
- And last avatar is a square of size 128
+ And last avatar is a square of size 512
And last avatar is a single "#FF0000" color
And user "anonymous" gets avatar for user "user0"
And The following headers should be set
| Content-Type | image/png |
| X-NC-IsCustomAvatar | 1 |
- And last avatar is a square of size 128
+ And last avatar is a square of size 512
And last avatar is a single "#FF0000" color
When logged in user deletes the user avatar
Then user "user0" gets avatar for user "user0"
And The following headers should be set
| Content-Type | image/png |
| X-NC-IsCustomAvatar | 0 |
- And last avatar is a square of size 128
+ And last avatar is a square of size 512
And last avatar is not a single color
And user "anonymous" gets avatar for user "user0"
And The following headers should be set
| Content-Type | image/png |
| X-NC-IsCustomAvatar | 0 |
- And last avatar is a square of size 128
+ And last avatar is a square of size 512
And last avatar is not a single color
Scenario: get user avatar with a larger size than the original one
Given Logging in using web as "user0"
- And logged in user posts temporary avatar from file "data/coloured-pattern.png"
+ And logged in user posts temporary avatar from file "data/coloured-pattern-non-square.png"
And logged in user crops temporary avatar
| x | 384 |
| y | 256 |
@@ -148,12 +182,12 @@ Feature: avatar
Then The following headers should be set
| Content-Type | image/png |
| X-NC-IsCustomAvatar | 1 |
- And last avatar is a square of size 192
+ And last avatar is a square of size 512
And last avatar is a single "#FF0000" color
Scenario: get user avatar with a smaller size than the original one
Given Logging in using web as "user0"
- And logged in user posts temporary avatar from file "data/coloured-pattern.png"
+ And logged in user posts temporary avatar from file "data/coloured-pattern-non-square.png"
And logged in user crops temporary avatar
| x | 384 |
| y | 256 |
@@ -163,7 +197,7 @@ Feature: avatar
Then The following headers should be set
| Content-Type | image/png |
| X-NC-IsCustomAvatar | 1 |
- And last avatar is a square of size 96
+ And last avatar is a square of size 512
And last avatar is a single "#FF0000" color
@@ -172,12 +206,12 @@ Feature: avatar
When user "user0" gets avatar for guest "guest0"
Then The following headers should be set
| Content-Type | image/png |
- And last avatar is a square of size 128
+ And last avatar is a square of size 512
And last avatar is not a single color
Scenario: get default guest avatar as an anonymous user
When user "anonymous" gets avatar for guest "guest0"
Then The following headers should be set
| Content-Type | image/png |
- And last avatar is a square of size 128
+ And last avatar is a square of size 512
And last avatar is not a single color
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 772370a85ba..e8580ed537b 100644
--- a/build/integration/features/bootstrap/AppConfiguration.php
+++ b/build/integration/features/bootstrap/AppConfiguration.php
@@ -1,29 +1,9 @@
<?php
+
/**
- *
- *
- * @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) <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;
@@ -96,9 +76,9 @@ trait AppConfiguration {
$this->sendingTo('get', '/cloud/apps?filter=enabled');
$this->theHTTPStatusCodeShouldBe('200');
if ($enabled) {
- Assert::assertContains('testing', $this->response->getBody()->getContents());
+ Assert::assertStringContainsString('testing', $this->response->getBody()->getContents());
} else {
- Assert::assertNotContains('testing', $this->response->getBody()->getContents());
+ Assert::assertStringNotContainsString('testing', $this->response->getBody()->getContents());
}
}
diff --git a/build/integration/features/bootstrap/Auth.php b/build/integration/features/bootstrap/Auth.php
index c621ef3572d..aeaade85383 100644
--- a/build/integration/features/bootstrap/Auth.php
+++ b/build/integration/features/bootstrap/Auth.php
@@ -1,35 +1,14 @@
<?php
+
/**
- *
- *
- * @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';
@@ -225,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, [
@@ -244,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 388715340c6..beebf1c024a 100644
--- a/build/integration/features/bootstrap/Avatar.php
+++ b/build/integration/features/bootstrap/Avatar.php
@@ -1,34 +1,16 @@
<?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;
require __DIR__ . '/../../vendor/autoload.php';
trait Avatar {
-
- /** @var string **/
+ /** @var string * */
private $lastAvatar;
/** @AfterScenario **/
@@ -110,7 +92,7 @@ trait Avatar {
* @param string $source
*/
public function loggedInUserPostsTemporaryAvatarFromFile(string $source) {
- $file = \GuzzleHttp\Psr7\stream_for(fopen($source, 'r'));
+ $file = \GuzzleHttp\Psr7\Utils::streamFor(fopen($source, 'r'));
$this->sendingAToWithRequesttoken('POST', '/index.php/avatar',
[
@@ -173,13 +155,22 @@ trait Avatar {
* @param string size
*/
public function lastAvatarIsASquareOfSize(string $size) {
- list($width, $height) = getimagesizefromstring($this->lastAvatar);
+ [$width, $height] = getimagesizefromstring($this->lastAvatar);
- Assert::assertEquals($width, $height, 'Avatar is not a square');
+ Assert::assertEquals($width, $height, 'Expected avatar to be a square');
Assert::assertEquals($size, $width);
}
/**
+ * @Then last avatar is not a square
+ */
+ public function lastAvatarIsNotASquare() {
+ [$width, $height] = getimagesizefromstring($this->lastAvatar);
+
+ Assert::assertNotEquals($width, $height, 'Expected avatar to not be a square');
+ }
+
+ /**
* @Then last avatar is not a single color
*/
public function lastAvatarIsNotASingleColor() {
@@ -250,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;
}
@@ -261,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 5b01e80707d..59a4312913e 100644
--- a/build/integration/features/bootstrap/BasicStructure.php
+++ b/build/integration/features/bootstrap/BasicStructure.php
@@ -1,41 +1,15 @@
<?php
+
/**
- *
- *
- * @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) <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;
@@ -46,6 +20,7 @@ trait BasicStructure {
use Avatar;
use Download;
use Mail;
+ use Theming;
/** @var string */
private $currentUser = '';
@@ -74,7 +49,6 @@ trait BasicStructure {
protected $remoteBaseUrl;
public function __construct($baseUrl, $admin, $regular_user_password) {
-
// Initialize your context here
$this->baseUrl = $baseUrl;
$this->adminUser = $admin;
@@ -149,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;
}
/**
@@ -181,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();
@@ -199,6 +177,42 @@ trait BasicStructure {
$this->response = $client->request($verb, $fullUrl, $options);
} catch (ClientException $ex) {
$this->response = $ex->getResponse();
+ } catch (ServerException $ex) {
+ $this->response = $ex->getResponse();
+ }
+ }
+
+ /**
+ * @param string $verb
+ * @param string $url
+ * @param TableNode|array|null $body
+ * @param array $headers
+ */
+ protected function sendRequestForJSON(string $verb, string $url, $body = null, array $headers = []): void {
+ $fullUrl = $this->baseUrl . "v{$this->apiVersion}.php" . $url;
+ $client = new Client();
+ $options = [];
+ if ($this->currentUser === 'admin') {
+ $options['auth'] = ['admin', 'admin'];
+ } elseif (strpos($this->currentUser, 'anonymous') !== 0) {
+ $options['auth'] = [$this->currentUser, $this->regularUser];
+ }
+ if ($body instanceof TableNode) {
+ $fd = $body->getRowsHash();
+ $options['form_params'] = $fd;
+ } elseif (is_array($body)) {
+ $options['form_params'] = $body;
+ }
+
+ $options['headers'] = array_merge($headers, [
+ 'OCS-ApiRequest' => 'true',
+ 'Accept' => 'application/json',
+ ]);
+
+ try {
+ $this->response = $client->{$verb}($fullUrl, $options);
+ } catch (ClientException $ex) {
+ $this->response = $ex->getResponse();
}
}
@@ -274,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(
@@ -297,6 +312,9 @@ trait BasicStructure {
'requesttoken' => $this->requestToken,
],
'cookies' => $this->cookieJar,
+ 'headers' => [
+ 'Origin' => $baseUrl,
+ ],
]
);
$this->extracRequestTokenFromResponse($response);
@@ -322,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();
@@ -410,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);
}
@@ -453,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");
}
/**
@@ -473,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');
}
}
@@ -492,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) {
@@ -504,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 b1981568e44..459c35089fa 100644
--- a/build/integration/features/bootstrap/CalDavContext.php
+++ b/build/integration/features/bootstrap/CalDavContext.php
@@ -1,37 +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;
@@ -61,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,
@@ -70,6 +51,9 @@ class CalDavContext implements \Behat\Behat\Context\Context {
'admin',
'admin',
],
+ 'headers' => [
+ 'X-NC-CalDAV-No-Trashbin' => '1',
+ ]
]
);
} catch (\GuzzleHttp\Exception\ClientException $e) {
@@ -103,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
@@ -170,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(
@@ -193,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(
@@ -231,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 aa5a040161b..7d09ab6ddcf 100644
--- a/build/integration/features/bootstrap/CapabilitiesContext.php
+++ b/build/integration/features/bootstrap/CapabilitiesContext.php
@@ -1,30 +1,9 @@
<?php
+
/**
- *
- *
- * @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) <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 1f555ed8140..733c98dca02 100644
--- a/build/integration/features/bootstrap/CardDavContext.php
+++ b/build/integration/features/bootstrap/CardDavContext.php
@@ -1,36 +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;
@@ -129,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(
@@ -142,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>',
@@ -209,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(
@@ -242,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 {
@@ -268,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 {
@@ -312,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 e8cfdee2eea..c8abf91127e 100644
--- a/build/integration/features/bootstrap/ChecksumsContext.php
+++ b/build/integration/features/bootstrap/ChecksumsContext.php
@@ -1,28 +1,9 @@
<?php
+
/**
- *
- *
- * @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;
@@ -79,7 +60,7 @@ class ChecksumsContext implements \Behat\Behat\Context\Context {
* @param string $checksum
*/
public function userUploadsFileToWithChecksum($user, $source, $destination, $checksum) {
- $file = \GuzzleHttp\Psr7\stream_for(fopen($source, 'r'));
+ $file = \GuzzleHttp\Psr7\Utils::streamFor(fopen($source, 'r'));
try {
$this->response = $this->client->put(
$this->baseUrl . '/remote.php/webdav' . $destination,
@@ -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
new file mode 100644
index 00000000000..27fa1795c5d
--- /dev/null
+++ b/build/integration/features/bootstrap/CollaborationContext.php
@@ -0,0 +1,206 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * 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;
+use GuzzleHttp\Client;
+use PHPUnit\Framework\Assert;
+
+require __DIR__ . '/../../vendor/autoload.php';
+
+class CollaborationContext implements Context {
+ use Provisioning;
+ use AppConfiguration;
+ use WebDav;
+
+ /**
+ * @Then /^get autocomplete for "([^"]*)"$/
+ * @param TableNode|null $formData
+ */
+ public function getAutocompleteForUser(string $search, TableNode $formData): void {
+ $this->getAutocompleteWithType(0, $search, $formData);
+ }
+
+ /**
+ * @Then /^get email autocomplete for "([^"]*)"$/
+ * @param TableNode|null $formData
+ */
+ public function getAutocompleteForEmail(string $search, TableNode $formData): void {
+ $this->getAutocompleteWithType(4, $search, $formData);
+ }
+
+ private function getAutocompleteWithType(int $type, string $search, TableNode $formData): void {
+ $query = $search === 'null' ? null : $search;
+
+ $this->sendRequestForJSON('GET', '/core/autocomplete/get?itemType=files&itemId=123&shareTypes[]=' . $type . '&search=' . $query, [
+ 'itemType' => 'files',
+ 'itemId' => '123',
+ 'search' => $query,
+ ]);
+ $this->theHTTPStatusCodeShouldBe(200);
+
+ $data = json_decode($this->response->getBody()->getContents(), true);
+ $suggestions = $data['ocs']['data'];
+
+ Assert::assertCount(count($formData->getHash()), $suggestions, 'Suggestion count does not match');
+ Assert::assertEquals($formData->getHash(), array_map(static function ($suggestion, $expected) {
+ $data = [];
+ if (isset($expected['id'])) {
+ $data['id'] = $suggestion['id'];
+ }
+ if (isset($expected['source'])) {
+ $data['source'] = $suggestion['source'];
+ }
+ if (isset($expected['status'])) {
+ $data['status'] = json_encode($suggestion['status']);
+ }
+ return $data;
+ }, $suggestions, $formData->getHash()));
+ }
+
+ /**
+ * @Given /^there is a contact in an addressbook$/
+ */
+ public function thereIsAContactInAnAddressbook() {
+ $this->usingNewDavPath();
+ 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');
+ } 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();
+ }
+
+ try {
+ $destination = '/users/admin/myaddressbook/contact1.vcf';
+ $data = <<<EOF
+BEGIN:VCARD
+VERSION:4.0
+PRODID:-//Nextcloud Contacts v4.0.2
+UID:a0f4088a-4dca-4308-9b63-09a1ebcf78f3
+FN:A person
+ADR;TYPE=HOME:;;;;;;
+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');
+ } 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();
+ }
+ }
+
+ protected function resetAppConfigs(): void {
+ $this->deleteServerConfig('core', 'shareapi_allow_share_dialog_user_enumeration');
+ $this->deleteServerConfig('core', 'shareapi_restrict_user_enumeration_to_group');
+ $this->deleteServerConfig('core', 'shareapi_restrict_user_enumeration_to_phone');
+ $this->deleteServerConfig('core', 'shareapi_restrict_user_enumeration_full_match');
+ $this->deleteServerConfig('core', 'shareapi_restrict_user_enumeration_full_match_userid');
+ $this->deleteServerConfig('core', 'shareapi_restrict_user_enumeration_full_match_email');
+ $this->deleteServerConfig('core', 'shareapi_restrict_user_enumeration_full_match_ignore_second_dn');
+ $this->deleteServerConfig('core', 'shareapi_only_share_with_group_members');
+ }
+
+ /**
+ * @Given /^user "([^"]*)" has status "([^"]*)"$/
+ * @param string $user
+ * @param string $status
+ */
+ public function assureUserHasStatus($user, $status) {
+ $fullUrl = $this->baseUrl . "v{$this->apiVersion}.php/apps/user_status/api/v1/user_status/status";
+ $client = new Client();
+ $options = [
+ 'headers' => [
+ 'OCS-APIREQUEST' => 'true',
+ ],
+ ];
+ if ($user === 'admin') {
+ $options['auth'] = $this->adminUser;
+ } else {
+ $options['auth'] = [$user, $this->regularUser];
+ }
+
+ $options['form_params'] = [
+ 'statusType' => $status
+ ];
+
+ $this->response = $client->put($fullUrl, $options);
+ $this->theHTTPStatusCodeShouldBe(200);
+
+ $fullUrl = $this->baseUrl . "v{$this->apiVersion}.php/apps/user_status/api/v1/user_status";
+ unset($options['form_params']);
+ $this->response = $client->get($fullUrl, $options);
+ $this->theHTTPStatusCodeShouldBe(200);
+
+ $returnedStatus = json_decode(json_encode(simplexml_load_string($this->response->getBody()->getContents())->data), true)['status'];
+ Assert::assertEquals($status, $returnedStatus);
+ }
+
+ /**
+ * @param string $user
+ * @return null|array
+ */
+ public function getStatusList(string $user): ?array {
+ $fullUrl = $this->baseUrl . "v{$this->apiVersion}.php/apps/user_status/api/v1/statuses";
+ $client = new Client();
+ $options = [
+ 'headers' => [
+ 'OCS-APIREQUEST' => 'true',
+ ],
+ ];
+ if ($user === 'admin') {
+ $options['auth'] = $this->adminUser;
+ } else {
+ $options['auth'] = [$user, $this->regularUser];
+ }
+
+ $this->response = $client->get($fullUrl, $options);
+ $this->theHTTPStatusCodeShouldBe(200);
+
+ $contents = $this->response->getBody()->getContents();
+ return json_decode(json_encode(simplexml_load_string($contents)->data), true);
+ }
+
+ /**
+ * @Given /^user statuses for "([^"]*)" list "([^"]*)" with status "([^"]*)"$/
+ * @param string $user
+ * @param string $statusUser
+ * @param string $status
+ */
+ public function assertStatusesList(string $user, string $statusUser, string $status): void {
+ $statusList = $this->getStatusList($user);
+ Assert::assertArrayHasKey('element', $statusList, 'Returned status list empty or broken');
+ if (array_key_exists('userId', $statusList['element'])) {
+ // If only one user has a status set, the API returns their status directly
+ Assert::assertArrayHasKey('status', $statusList['element'], 'Returned status list empty or broken');
+ $filteredStatusList = [ $statusList['element']['userId'] => $statusList['element']['status'] ];
+ } else {
+ // If more than one user have their status set, the API returns an array of their statuses
+ $filteredStatusList = array_column($statusList['element'], 'status', 'userId');
+ }
+ Assert::assertArrayHasKey($statusUser, $filteredStatusList, 'User not listed in statuses: ' . $statusUser);
+ Assert::assertEquals($status, $filteredStatusList[$statusUser]);
+ }
+
+ /**
+ * @Given /^user statuses for "([^"]*)" are empty$/
+ * @param string $user
+ */
+ public function assertStatusesEmpty(string $user): void {
+ $statusList = $this->getStatusList($user);
+ Assert::assertEmpty($statusList);
+ }
+}
diff --git a/build/integration/features/bootstrap/CommandLine.php b/build/integration/features/bootstrap/CommandLine.php
index fa773ea91cd..924d723daa6 100644
--- a/build/integration/features/bootstrap/CommandLine.php
+++ b/build/integration/features/bootstrap/CommandLine.php
@@ -1,29 +1,10 @@
<?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;
require __DIR__ . '/../../vendor/autoload.php';
@@ -142,13 +123,13 @@ trait CommandLine {
* @Then /^the command output contains the text "([^"]*)"$/
*/
public function theCommandOutputContainsTheText($text) {
- Assert::assertContains($text, $this->lastStdOut, 'The command did not output the expected text on stdout');
+ Assert::assertStringContainsString($text, $this->lastStdOut, 'The command did not output the expected text on stdout');
}
/**
* @Then /^the command error output contains the text "([^"]*)"$/
*/
public function theCommandErrorOutputContainsTheText($text) {
- Assert::assertContains($text, $this->lastStdErr, 'The command did not output the expected text on stderr');
+ Assert::assertStringContainsString($text, $this->lastStdErr, 'The command did not output the expected text on stderr');
}
}
diff --git a/build/integration/features/bootstrap/CommandLineContext.php b/build/integration/features/bootstrap/CommandLineContext.php
index 2c434a1b89f..e7764356270 100644
--- a/build/integration/features/bootstrap/CommandLineContext.php
+++ b/build/integration/features/bootstrap/CommandLineContext.php
@@ -1,32 +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;
@@ -36,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, '/') . '/';
@@ -60,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) {
@@ -70,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) {
@@ -98,7 +85,7 @@ class CommandLineContext implements \Behat\Behat\Context\Context {
}
/**
- * @When /^transferring ownership from "([^"]+)" to "([^"]+)"/
+ * @When /^transferring ownership from "([^"]+)" to "([^"]+)"$/
*/
public function transferringOwnership($user1, $user2) {
if ($this->runOcc(['files:transfer-ownership', $user1, $user2]) === 0) {
@@ -110,7 +97,7 @@ class CommandLineContext implements \Behat\Behat\Context\Context {
}
/**
- * @When /^transferring ownership of path "([^"]+)" from "([^"]+)" to "([^"]+)"/
+ * @When /^transferring ownership of path "([^"]+)" from "([^"]+)" to "([^"]+)"$/
*/
public function transferringOwnershipPath($path, $user1, $user2) {
$path = '--path=' . $path;
@@ -122,7 +109,6 @@ class CommandLineContext implements \Behat\Behat\Context\Context {
}
}
-
/**
* @When /^using received transfer folder of "([^"]+)" as dav path$/
*/
@@ -136,6 +122,6 @@ class CommandLineContext implements \Behat\Behat\Context\Context {
* @Then /^transfer folder name contains "([^"]+)"$/
*/
public function transferFolderNameContains($text) {
- Assert::assertContains($text, $this->lastTransferPath);
+ Assert::assertStringContainsString($text, $this->lastTransferPath);
}
}
diff --git a/build/integration/features/bootstrap/CommentsContext.php b/build/integration/features/bootstrap/CommentsContext.php
index ebd7d5697e5..53001b1c204 100644
--- a/build/integration/features/bootstrap/CommentsContext.php
+++ b/build/integration/features/bootstrap/CommentsContext.php
@@ -1,30 +1,10 @@
<?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';
class CommentsContext implements \Behat\Behat\Context\Context {
@@ -50,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();
@@ -128,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() . ')');
}
}
@@ -170,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'];
+ $this->commentId = (int)($this->getValueFromNamedEntries('{DAV:}response {DAV:}propstat {DAV:}prop {http://owncloud.org/ns}id', $this->response ?? []) ?? 0);
}
}
@@ -228,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() . ')');
}
}
@@ -239,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)) {
@@ -264,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 . ')');
}
}
@@ -294,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
new file mode 100644
index 00000000000..f6bf6b9422b
--- /dev/null
+++ b/build/integration/features/bootstrap/ContactsMenu.php
@@ -0,0 +1,51 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+use PHPUnit\Framework\Assert;
+
+trait ContactsMenu {
+ // BasicStructure trait is expected to be used in the class that uses this
+ // trait.
+
+ /**
+ * @When /^searching for contacts matching with "([^"]*)"$/
+ *
+ * @param string $filter
+ */
+ public function searchingForContactsMatchingWith(string $filter) {
+ $url = '/index.php/contactsmenu/contacts';
+
+ $parameters[] = 'filter=' . $filter;
+
+ $url .= '?' . implode('&', $parameters);
+
+ $this->sendingAToWithRequesttoken('POST', $url);
+ }
+
+ /**
+ * @Then /^the list of searched contacts has "(\d+)" contacts$/
+ */
+ public function theListOfSearchedContactsHasContacts(int $count) {
+ $this->theHTTPStatusCodeShouldBe(200);
+
+ $searchedContacts = json_decode($this->response->getBody(), $asAssociativeArray = true)['contacts'];
+
+ Assert::assertEquals($count, count($searchedContacts));
+ }
+
+ /**
+ * @Then /^searched contact "(\d+)" is named "([^"]*)"$/
+ *
+ * @param int $index
+ * @param string $expectedName
+ */
+ public function searchedContactXIsNamed(int $index, string $expectedName) {
+ $searchedContacts = json_decode($this->response->getBody(), $asAssociativeArray = true)['contacts'];
+ $searchedContact = $searchedContacts[$index];
+
+ Assert::assertEquals($expectedName, $searchedContact['fullName']);
+ }
+}
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 71a96eb585f..549a033346e 100644
--- a/build/integration/features/bootstrap/Download.php
+++ b/build/integration/features/bootstrap/Download.php
@@ -1,34 +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) <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 **/
@@ -40,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);
@@ -58,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'
);
}
@@ -73,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'
);
}
@@ -97,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'];
@@ -117,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'
);
}
@@ -137,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 1cc1a5fe7a7..32387869edd 100644
--- a/build/integration/features/bootstrap/FakeSMTPHelper.php
+++ b/build/integration/features/bootstrap/FakeSMTPHelper.php
@@ -1,25 +1,8 @@
<?php
+
/**
- *
- *
- * @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 e9c486daa4d..ab37556f931 100644
--- a/build/integration/features/bootstrap/FeatureContext.php
+++ b/build/integration/features/bootstrap/FeatureContext.php
@@ -1,39 +1,29 @@
<?php
+
/**
- *
- *
- * @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 41581110bdf..95dc8119ad6 100644
--- a/build/integration/features/bootstrap/FederationContext.php
+++ b/build/integration/features/bootstrap/FederationContext.php
@@ -1,33 +1,14 @@
<?php
+
/**
- *
- *
- * @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';
@@ -37,6 +18,43 @@ require __DIR__ . '/../../vendor/autoload.php';
class FederationContext implements Context, SnippetAcceptingContext {
use WebDav;
use AppConfiguration;
+ use CommandLine;
+
+ /** @var string */
+ private static $phpFederatedServerPid = '';
+
+ /** @var string */
+ private $lastAcceptedRemoteShareId;
+
+ /**
+ * @BeforeScenario
+ * @AfterScenario
+ *
+ * The server is started also after the scenarios to ensure that it is
+ * properly cleaned up if stopped.
+ */
+ public function startFederatedServer() {
+ if (self::$phpFederatedServerPid !== '') {
+ return;
+ }
+
+ $port = getenv('PORT_FED');
+
+ self::$phpFederatedServerPid = exec('PHP_CLI_SERVER_WORKERS=2 php -S localhost:' . $port . ' -t ../../ >/dev/null & echo $!');
+ }
+
+ /**
+ * @BeforeScenario
+ */
+ public function cleanupRemoteStorages() {
+ // Ensure that dangling remote storages from previous tests will not
+ // interfere with the current scenario.
+ // The storages must be cleaned before each scenario; they can not be
+ // cleaned after each scenario, as this hook is executed before the hook
+ // that removes the users, so the shares would be still valid and thus
+ // the storages would not be dangling yet.
+ $this->runOcc(['sharing:cleanup-remote-storages']);
+ }
/**
* @Given /^User "([^"]*)" from server "(LOCAL|REMOTE)" shares "([^"]*)" with user "([^"]*)" from server "(LOCAL|REMOTE)"$/
@@ -48,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);
@@ -69,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);
@@ -80,6 +98,37 @@ class FederationContext implements Context, SnippetAcceptingContext {
}
/**
+ * @Then remote share :count is returned with
+ *
+ * @param int $number
+ * @param TableNode $body
+ */
+ public function remoteShareXIsReturnedWith(int $number, TableNode $body) {
+ $this->theHTTPStatusCodeShouldBe('200');
+ $this->theOCSStatusCodeShouldBe('100');
+
+ if (!($body instanceof TableNode)) {
+ return;
+ }
+
+ $returnedShare = $this->getXmlResponse()->data[0];
+ if ($returnedShare->element) {
+ $returnedShare = $returnedShare->element[$number];
+ }
+
+ $defaultExpectedFields = [
+ 'id' => 'A_NUMBER',
+ 'remote_id' => 'A_NUMBER',
+ 'accepted' => '1',
+ ];
+ $expectedFields = array_merge($defaultExpectedFields, $body->getRowsHash());
+
+ foreach ($expectedFields as $field => $value) {
+ $this->assertFieldIsInReturnedShare($field, $value, $returnedShare);
+ }
+ }
+
+ /**
* @When /^User "([^"]*)" from server "(LOCAL|REMOTE)" accepts last pending share$/
* @param string $user
* @param string $server
@@ -87,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;
@@ -95,10 +144,78 @@ class FederationContext implements Context, SnippetAcceptingContext {
$this->theHTTPStatusCodeShouldBe('200');
$this->theOCSStatusCodeShouldBe('100');
$this->usingServer($previous);
+
+ $this->lastAcceptedRemoteShareId = $share_id;
+ }
+
+ /**
+ * @When /^user "([^"]*)" deletes last accepted remote share$/
+ * @param string $user
+ */
+ public function deleteLastAcceptedRemoteShare($user) {
+ $this->asAn($user);
+ $this->sendingToWith('DELETE', '/apps/files_sharing/api/v1/remote_shares/' . $this->lastAcceptedRemoteShareId, null);
+ }
+
+ /**
+ * @When /^remote server is stopped$/
+ */
+ public function remoteServerIsStopped() {
+ if (self::$phpFederatedServerPid === '') {
+ return;
+ }
+
+ exec('kill ' . self::$phpFederatedServerPid);
+
+ 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 a596cbf5be8..0c437f28a72 100644
--- a/build/integration/features/bootstrap/FilesDropContext.php
+++ b/build/integration/features/bootstrap/FilesDropContext.php
@@ -1,27 +1,8 @@
<?php
+
/**
- *
- *
- * @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,13 +26,17 @@ 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',
];
- $options['body'] = \GuzzleHttp\Psr7\stream_for($content);
+
+ if ($nickname) {
+ $options['headers']['X-NC-NICKNAME'] = $nickname;
+ }
+
+ $options['body'] = \GuzzleHttp\Psr7\Utils::streamFor($content);
try {
$this->response = $client->request('PUT', $fullUrl, $options);
@@ -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 78276f13828..986dced77a1 100644
--- a/build/integration/features/bootstrap/LDAPContext.php
+++ b/build/integration/features/bootstrap/LDAPContext.php
@@ -1,29 +1,9 @@
<?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) <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;
use PHPUnit\Framework\Assert;
@@ -105,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'],
@@ -142,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 d347636c8aa..d48ed6399c5 100644
--- a/build/integration/features/bootstrap/Mail.php
+++ b/build/integration/features/bootstrap/Mail.php
@@ -1,28 +1,10 @@
<?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
// trait.
@@ -39,7 +21,7 @@ trait Mail {
return;
}
- exec("kill " . $this->fakeSmtpServerPid);
+ exec('kill ' . $this->fakeSmtpServerPid);
$this->invokingTheCommand('config:system:delete mail_smtpport');
}
@@ -52,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 0ec19f27c60..935ad2a4a1d 100644
--- a/build/integration/features/bootstrap/Provisioning.php
+++ b/build/integration/features/bootstrap/Provisioning.php
@@ -1,36 +1,12 @@
<?php
+
/**
- *
- *
- * @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) <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>
- *
- * @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,13 +127,17 @@ 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";
$client = new Client();
$options = [];
- $options['auth'] = $this->adminUser;
+ if ($this->currentUser === 'admin') {
+ $options['auth'] = $this->adminUser;
+ } else {
+ $options['auth'] = [$this->currentUser, $this->regularUser];
+ }
$options['headers'] = [
'OCS-APIREQUEST' => 'true',
];
@@ -165,21 +145,119 @@ trait Provisioning {
$response = $client->get($fullUrl, $options);
foreach ($settings->getRows() as $setting) {
$value = json_decode(json_encode(simplexml_load_string($response->getBody())->data->{$setting[0]}), 1);
+ 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), 'Data wrong for field: ' . $setting[0]);
+ }
+ } elseif (isset($value[0])) {
+ Assert::assertEqualsCanonicalizing($setting[1], $value[0], 'Data wrong for field: ' . $setting[0]);
+ } else {
+ 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]);
+ }
+ }
+ }
+
+ /**
+ * @Then /^group "([^"]*)" has$/
+ *
+ * @param string $user
+ * @param TableNode|null $settings
+ */
+ public function groupHasSetting($group, $settings) {
+ $fullUrl = $this->baseUrl . "v{$this->apiVersion}.php/cloud/groups/details?search=$group";
+ $client = new Client();
+ $options = [];
+ if ($this->currentUser === 'admin') {
+ $options['auth'] = $this->adminUser;
+ } else {
+ $options['auth'] = [$this->currentUser, $this->regularUser];
+ }
+ $options['headers'] = [
+ 'OCS-APIREQUEST' => 'true',
+ ];
+
+ $response = $client->get($fullUrl, $options);
+ $groupDetails = simplexml_load_string($response->getBody())->data[0]->groups[0]->element;
+ foreach ($settings->getRows() as $setting) {
+ $value = json_decode(json_encode($groupDetails->{$setting[0]}), 1);
if (isset($value[0])) {
- Assert::assertEquals($setting[1], $value[0], "", 0.0, 10, true);
+ Assert::assertEqualsCanonicalizing($setting[1], $value[0]);
} else {
Assert::assertEquals('', $setting[1]);
}
}
}
+
+ /**
+ * @Then /^user "([^"]*)" has editable fields$/
+ *
+ * @param string $user
+ * @param TableNode|null $fields
+ */
+ public function userHasEditableFields($user, $fields) {
+ $fullUrl = $this->baseUrl . "v{$this->apiVersion}.php/cloud/user/fields";
+ if ($user !== 'self') {
+ $fullUrl .= '/' . $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',
+ ];
+
+ $response = $client->get($fullUrl, $options);
+ $fieldsArray = json_decode(json_encode(simplexml_load_string($response->getBody())->data->element), 1);
+
+ $expectedFields = $fields->getRows();
+ $expectedFields = $this->simplifyArray($expectedFields);
+ Assert::assertEquals($expectedFields, $fieldsArray);
+ }
+
/**
* @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 = [];
@@ -206,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;
@@ -214,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;
@@ -222,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;
@@ -230,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;
@@ -299,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);
@@ -328,7 +406,7 @@ trait Provisioning {
$this->response = $client->get($fullUrl, $options);
$groups = [$group];
$respondedArray = $this->getArrayOfGroupsResponded($this->response);
- Assert::assertNotEquals($groups, $respondedArray, "", 0.0, 10, true);
+ Assert::assertNotEqualsCanonicalizing($groups, $respondedArray);
Assert::assertEquals(200, $this->response->getStatusCode());
}
@@ -478,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;
}
@@ -499,7 +577,7 @@ trait Provisioning {
return;
}
$previous_user = $this->currentUser;
- $this->currentUser = "admin";
+ $this->currentUser = 'admin';
$this->deletingTheGroup($group);
$this->currentUser = $previous_user;
try {
@@ -580,23 +658,23 @@ 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);
- Assert::assertEquals($usersSimplified, $respondedArray, "", 0.0, 10, true);
+ Assert::assertEqualsCanonicalizing($usersSimplified, $respondedArray);
}
}
/**
* @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);
@@ -606,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);
@@ -620,46 +698,46 @@ 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);
- Assert::assertEquals($groupsSimplified, $respondedArray, "", 0.0, 10, true);
+ Assert::assertEqualsCanonicalizing($groupsSimplified, $respondedArray);
}
}
/**
* @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);
- Assert::assertEquals($groupsSimplified, $respondedArray, "", 0.0, 10, true);
+ Assert::assertEqualsCanonicalizing($groupsSimplified, $respondedArray);
}
}
/**
* @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);
- Assert::assertEquals($appsSimplified, $respondedArray, "", 0.0, 10, true);
+ Assert::assertEqualsCanonicalizing($appsSimplified, $respondedArray);
}
}
/**
* @Then /^subadmin users returned are$/
- * @param \Behat\Gherkin\Node\TableNode|null $groupsList
+ * @param TableNode|null $groupsList
*/
public function theSubadminUsersShouldBe($groupsList) {
$this->theSubadminGroupsShouldBe($groupsList);
@@ -731,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') {
@@ -752,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') {
@@ -776,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') {
@@ -829,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);
}
/**
@@ -838,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);
}
/**
@@ -902,4 +980,38 @@ trait Provisioning {
}
$this->usingServer($previousServer);
}
+
+ /**
+ * @Then /^user "([^"]*)" has not$/
+ */
+ public function userHasNotSetting($user, TableNode $settings) {
+ $fullUrl = $this->baseUrl . "v{$this->apiVersion}.php/cloud/users/$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',
+ ];
+
+ $response = $client->get($fullUrl, $options);
+ foreach ($settings->getRows() as $setting) {
+ $value = json_decode(json_encode(simplexml_load_string($response->getBody())->data->{$setting[0]}), 1);
+ if (isset($value[0])) {
+ if (in_array($setting[0], ['additional_mail', 'additional_mailScope'], true)) {
+ $expectedValues = explode(';', $setting[1]);
+ foreach ($expectedValues as $expected) {
+ Assert::assertFalse(in_array($expected, $value, true));
+ }
+ } else {
+ Assert::assertNotEqualsCanonicalizing($setting[1], $value[0]);
+ }
+ } else {
+ Assert::assertNotEquals('', $setting[1]);
+ }
+ }
+ }
}
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 38f3ab76487..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 John Molakvoæ (skjnldsv) <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) {
@@ -138,7 +121,13 @@ class RemoteContext implements Context {
* @param string $value
*/
public function hasCapability($key, $value) {
- $capabilities = $this->getApiClient()->getCapabilities();
+ try {
+ $capabilities = $this->getApiClient()->getCapabilities();
+ } catch (\Exception $e) {
+ Assert::assertInstanceOf($value, $e);
+ $this->lastException = $e;
+ return;
+ }
$current = $capabilities;
$parts = explode('.', $key);
foreach ($parts as $part) {
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 72a20a08ce8..49a4fe92822 100644
--- a/build/integration/features/bootstrap/Search.php
+++ b/build/integration/features/bootstrap/Search.php
@@ -1,31 +1,13 @@
<?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;
trait Search {
-
// BasicStructure trait is expected to be used in the class that uses this
// trait.
diff --git a/build/integration/features/bootstrap/SetupContext.php b/build/integration/features/bootstrap/SetupContext.php
index 39925374ac4..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 c1db2d57e05..37e0e63e547 100644
--- a/build/integration/features/bootstrap/ShareesContext.php
+++ b/build/integration/features/bootstrap/ShareesContext.php
@@ -1,26 +1,9 @@
<?php
+
/**
- *
- *
- * @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 c2d16f19e75..0cc490ff110 100644
--- a/build/integration/features/bootstrap/Sharing.php
+++ b/build/integration/features/bootstrap/Sharing.php
@@ -1,35 +1,9 @@
<?php
+
/**
- *
- *
- * @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) <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,19 +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) {
+ $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 = [
@@ -309,10 +290,14 @@ trait Sharing {
$body['permissions'] = $permissions;
}
+ if ($viewOnly === true) {
+ $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();
@@ -323,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 {
@@ -342,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;
}
@@ -402,13 +391,17 @@ trait Sharing {
}
/**
- * @Given /^(file|folder|entry) "([^"]*)" of user "([^"]*)" is shared with user "([^"]*)"( with permissions ([\d]*))?$/
+ * @Given /^(file|folder|entry) "([^"]*)" of user "([^"]*)" is shared with user "([^"]*)"( with permissions ([\d]*))?( view-only)?$/
*
* @param string $filepath
* @param string $user1
* @param string $user2
*/
- public function assureFileIsShared($entry, $filepath, $user1, $user2, $withPerms = null, $permissions = null) {
+ public function assureFileIsShared($entry, $filepath, $user1, $user2, $withPerms = null, $permissions = null, $viewOnly = null) {
+ // when view-only is set, permissions is empty string instead of null...
+ if ($permissions === '') {
+ $permissions = null;
+ }
$fullUrl = $this->baseUrl . "v{$this->apiVersion}.php/apps/files_sharing/api/v{$this->sharingApiVersion}/shares" . "?path=$filepath";
$client = new Client();
$options = [];
@@ -424,20 +417,24 @@ trait Sharing {
if ($this->isUserOrGroupInSharedData($user2, $permissions)) {
return;
} else {
- $this->createShare($user1, $filepath, 0, $user2, null, null, $permissions);
+ $this->createShare($user1, $filepath, 0, $user2, null, null, $permissions, $viewOnly !== null);
}
$this->response = $client->get($fullUrl, $options);
Assert::assertEquals(true, $this->isUserOrGroupInSharedData($user2, $permissions));
}
/**
- * @Given /^(file|folder|entry) "([^"]*)" of user "([^"]*)" is shared with group "([^"]*)"( with permissions ([\d]*))?$/
+ * @Given /^(file|folder|entry) "([^"]*)" of user "([^"]*)" is shared with group "([^"]*)"( with permissions ([\d]*))?( view-only)?$/
*
* @param string $filepath
* @param string $user
* @param string $group
*/
- public function assureFileIsSharedWithGroup($entry, $filepath, $user, $group, $withPerms = null, $permissions = null) {
+ public function assureFileIsSharedWithGroup($entry, $filepath, $user, $group, $withPerms = null, $permissions = null, $viewOnly = null) {
+ // when view-only is set, permissions is empty string instead of null...
+ if ($permissions === '') {
+ $permissions = null;
+ }
$fullUrl = $this->baseUrl . "v{$this->apiVersion}.php/apps/files_sharing/api/v{$this->sharingApiVersion}/shares" . "?path=$filepath";
$client = new Client();
$options = [];
@@ -453,7 +450,7 @@ trait Sharing {
if ($this->isUserOrGroupInSharedData($group, $permissions)) {
return;
} else {
- $this->createShare($user, $filepath, 1, $group, null, null, $permissions);
+ $this->createShare($user, $filepath, 1, $group, null, null, $permissions, $viewOnly !== null);
}
$this->response = $client->get($fullUrl, $options);
Assert::assertEquals(true, $this->isUserOrGroupInSharedData($group, $permissions));
@@ -465,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);
}
/**
@@ -474,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);
}
/**
@@ -506,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");
@@ -567,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 {
@@ -608,12 +605,12 @@ trait Sharing {
return;
}
- if (!array_key_exists($field, $returnedShare)) {
+ if (!property_exists($returnedShare, $field)) {
Assert::fail("$field was not found in response");
}
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') {
@@ -679,14 +676,14 @@ trait Sharing {
* @When save last share id
*/
public function saveLastShareId() {
- $this->savedShareId = $this->lastShareData['data']['id'];
+ $this->savedShareId = ($this->lastShareData['data']['id'] ?? null);
}
/**
* @Then share ids should match
*/
public function shareIdsShouldMatch() {
- if ($this->savedShareId !== $this->lastShareData['data']['id']) {
+ if ($this->savedShareId !== ($this->lastShareData['data']['id'] ?? null)) {
throw new \Exception('Expected the same link share to be returned');
}
}
diff --git a/build/integration/features/bootstrap/SharingContext.php b/build/integration/features/bootstrap/SharingContext.php
index be719cbf379..a9dd99108a9 100644
--- a/build/integration/features/bootstrap/SharingContext.php
+++ b/build/integration/features/bootstrap/SharingContext.php
@@ -1,26 +1,8 @@
<?php
+
/**
- *
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Daniel Calviño Sánchez <danxuliu@gmail.com>
- * @author John Molakvoæ (skjnldsv) <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,15 +18,21 @@ class SharingContext implements Context, SnippetAcceptingContext {
use Trashbin;
use AppConfiguration;
use CommandLine;
+ use Activity;
protected function resetAppConfigs() {
$this->deleteServerConfig('core', 'shareapi_default_permissions');
$this->deleteServerConfig('core', 'shareapi_default_internal_expire_date');
$this->deleteServerConfig('core', 'shareapi_internal_expire_after_n_days');
$this->deleteServerConfig('core', 'internal_defaultExpDays');
+ $this->deleteServerConfig('core', 'shareapi_enforce_links_password');
$this->deleteServerConfig('core', 'shareapi_default_expire_date');
$this->deleteServerConfig('core', 'shareapi_expire_after_n_days');
$this->deleteServerConfig('core', 'link_defaultExpDays');
- $this->deleteServerConfig('sharebymail', 'enforcePasswordProtection');
+ $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 a490a5e6701..c64626de68d 100644
--- a/build/integration/features/bootstrap/TagsContext.php
+++ b/build/integration/features/bootstrap/TagsContext.php
@@ -1,32 +1,10 @@
<?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';
use Behat\Gherkin\Node\TableNode;
@@ -268,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)
)
@@ -278,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 bc61c87ebab..6f351c30ccf 100644
--- a/build/integration/features/bootstrap/TalkContext.php
+++ b/build/integration/features/bootstrap/TalkContext.php
@@ -1,30 +1,12 @@
<?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;
class TalkContext implements Context {
-
/**
* @BeforeFeature @Talk
* @BeforeScenario @Talk
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 46eeabb4214..dfcc23289a7 100644
--- a/build/integration/features/bootstrap/Trashbin.php
+++ b/build/integration/features/bootstrap/Trashbin.php
@@ -1,28 +1,11 @@
<?php
+
/**
- * @copyright Copyright (c) 2017, ownCloud GmbH.
- *
- * @author Daniel Calviño Sánchez <danxuliu@gmail.com>
- * @author John Molakvoæ (skjnldsv) <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;
require __DIR__ . '/../../vendor/autoload.php';
@@ -31,7 +14,6 @@ require __DIR__ . '/../../vendor/autoload.php';
* Trashbin functions
*/
trait Trashbin {
-
// WebDav trait is expected to be used in the class that uses this trait.
/**
@@ -116,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');
}
}
}
@@ -139,7 +121,7 @@ trait Trashbin {
return $item['{http://nextcloud.org/ns}trashbin-filename'];
}, $elementList));
- Assert::assertArraySubset([$name], array_values($trashContent));
+ AssertArraySubset::assertArraySubset([$name], array_values($trashContent));
}
/**
diff --git a/build/integration/features/bootstrap/WebDav.php b/build/integration/features/bootstrap/WebDav.php
index de277b2278d..2cb37002ac0 100644
--- a/build/integration/features/bootstrap/WebDav.php
+++ b/build/integration/features/bootstrap/WebDav.php
@@ -1,40 +1,14 @@
<?php
+
/**
- *
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author David Toledo <dtoledo@solidgear.es>
- * @author Joas Schilling <coding@schilljs.com>
- * @author John Molakvoæ (skjnldsv) <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;
@@ -44,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 "([^"]*)"$/
@@ -66,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;
}
@@ -74,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;
}
@@ -86,11 +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";
}
$client = new GClient();
$options = [
@@ -99,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);
@@ -114,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());
}
@@ -128,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();
}
@@ -158,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);
}
/**
@@ -167,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);
}
/**
@@ -185,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 = [
@@ -193,9 +177,8 @@ trait WebDav {
'Range' => $range
]
];
- $options['auth'] = [$token, ""];
- $this->response = $client->request("GET", $fullUrl, $options);
+ $this->response = $client->request('GET', $fullUrl, $options);
}
/**
@@ -207,6 +190,45 @@ trait WebDav {
}
/**
+ * @Then /^File "([^"]*)" should have prop "([^"]*):([^"]*)" equal to "([^"]*)"$/
+ * @param string $file
+ * @param string $prefix
+ * @param string $prop
+ * @param string $value
+ */
+ 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"];
+ Assert::assertEquals($property, $value);
+ }
+
+ /**
+ * @Then /^Image search should work$/
+ */
+ public function search(): void {
+ $this->searchFile($this->currentUser);
+ Assert::assertEquals(207, $this->response->getStatusCode());
+ }
+
+ /**
+ * @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
@@ -218,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
*/
@@ -230,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
@@ -291,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)) {
@@ -322,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 = [];
@@ -379,6 +481,128 @@ trait WebDav {
return $response;
}
+ /**
+ * Returns the elements of a profind command
+ * @param string $properties properties which needs to be included in the report
+ * @param string $filterRules filter-rules to choose what needs to appear in the report
+ */
+ public function propfindFile(string $user, string $path, string $properties = '') {
+ $client = $this->getSabreClient($user);
+
+ $body = '<?xml version="1.0" encoding="utf-8" ?>
+ <d:propfind xmlns:d="DAV:"
+ xmlns:oc="http://owncloud.org/ns"
+ xmlns:nc="http://nextcloud.org/ns"
+ xmlns:ocs="http://open-collaboration-services.org/ns">
+ <d:prop>
+ ' . $properties . '
+ </d:prop>
+ </d:propfind>';
+
+ $response = $client->request('PROPFIND', $this->makeSabrePath($user, $path), $body);
+ $parsedResponse = $client->parseMultistatus($response['body']);
+ return $parsedResponse;
+ }
+
+ /**
+ * Returns the elements of a searc command
+ * @param string $properties properties which needs to be included in the report
+ * @param string $filterRules filter-rules to choose what needs to appear in the report
+ */
+ public function searchFile(string $user, ?string $properties = null, ?string $scope = null, ?string $condition = null) {
+ $client = $this->getSabreClient($user);
+
+ if ($properties === null) {
+ $properties = '<oc:fileid /> <d:getlastmodified /> <d:getetag /> <d:getcontenttype /> <d:getcontentlength /> <nc:has-preview /> <oc:favorite /> <d:resourcetype />';
+ }
+
+ if ($condition === null) {
+ $condition = '<d:and>
+ <d:or>
+ <d:eq>
+ <d:prop>
+ <d:getcontenttype/>
+ </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/>
+ </d:prop>
+ <d:literal>video/quicktime</d:literal>
+ </d:eq>
+ </d:or>
+ <d:eq>
+ <d:prop>
+ <oc:owner-id/>
+ </d:prop>
+ <d:literal>' . $user . '</d:literal>
+ </d:eq>
+</d:and>';
+ }
+
+ if ($scope === null) {
+ $scope = '<d:href>/files/' . $user . '</d:href><d:depth>infinity</d:depth>';
+ }
+
+ $body = '<?xml version="1.0" encoding="UTF-8"?>
+<d:searchrequest xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns" xmlns:nc="http://nextcloud.org/ns" xmlns:ns="https://github.com/icewind1991/SearchDAV/ns" xmlns:ocs="http://open-collaboration-services.org/ns">
+ <d:basicsearch>
+ <d:select>
+ <d:prop>' . $properties . '</d:prop>
+ </d:select>
+ <d:from><d:scope>' . $scope . '</d:scope></d:from>
+ <d:where>' . $condition . '</d:where>
+ <d:orderby>
+ <d:order>
+ <d:prop><d:getlastmodified/></d:prop>
+ <d:descending/>
+ </d:order>
+ </d:orderby>
+ <d:limit>
+ <d:nresults>35</d:nresults>
+ <ns:firstresult>0</ns:firstresult>
+ </d:limit>
+ </d:basicsearch>
+</d:searchrequest>';
+
+ try {
+ $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
+ $this->response = $e->getResponse();
+ } catch (\GuzzleHttp\Exception\ClientException $e) {
+ // 4xx responses cause a client exception
+ $this->response = $e->getResponse();
+ }
+ }
+
/* Returns the elements of a report command
* @param string $user
* @param string $path
@@ -407,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);
}
}
@@ -440,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');
}
}
}
@@ -455,11 +679,14 @@ trait WebDav {
* @param string $destination
*/
public function userUploadsAFileTo($user, $source, $destination) {
- $file = \GuzzleHttp\Psr7\stream_for(fopen($source, 'r'));
+ $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) {
- // 4xx and 5xx responses cause an exception
+ // 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();
}
}
@@ -471,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);
}
@@ -484,11 +711,14 @@ trait WebDav {
* @When User :user uploads file with content :content to :destination
*/
public function userUploadsAFileWithContentTo($user, $content, $destination) {
- $file = \GuzzleHttp\Psr7\stream_for($content);
+ $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) {
- // 4xx and 5xx responses cause an exception
+ // 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();
}
}
@@ -503,7 +733,10 @@ trait WebDav {
try {
$this->response = $this->makeDavRequest($user, 'DELETE', $file, []);
} catch (\GuzzleHttp\Exception\ServerException $e) {
- // 4xx and 5xx responses cause an exception
+ // 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();
}
}
@@ -516,43 +749,86 @@ 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) {
- // 4xx and 5xx responses cause an exception
+ // 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();
}
}
/**
- * @Given user :user uploads chunk file :num of :total with :data to :destination
+ * @Given user :user uploads bulked files :name1 with :content1 and :name2 with :content2 and :name3 with :content3
* @param string $user
- * @param int $num
- * @param int $total
- * @param string $data
- * @param string $destination
+ * @param string $name1
+ * @param string $content1
+ * @param string $name2
+ * @param string $content2
+ * @param string $name3
+ * @param string $content3
*/
- public function userUploadsChunkFileOfWithToWithChecksum($user, $num, $total, $data, $destination) {
- $num -= 1;
- $data = \GuzzleHttp\Psr7\stream_for($data);
- $file = $destination . '-chunking-42-' . $total . '-' . $num;
- $this->makeDavRequest($user, 'PUT', $file, ['OC-Chunked' => '1'], $data, "uploads");
+ public function userUploadsBulkedFiles($user, $name1, $content1, $name2, $content2, $name3, $content3) {
+ $boundary = 'boundary_azertyuiop';
+
+ $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 .= "\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 .= "\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 .= "\r\n";
+ $body .= $content3 . "\r\n";
+ $body .= '--' . $boundary . "--\r\n";
+
+ $stream = fopen('php://temp', 'r+');
+ fwrite($stream, $body);
+ rewind($stream);
+
+ $client = new GClient();
+ $options = [
+ 'auth' => [$user, $this->regularUser],
+ 'headers' => [
+ '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);
}
/**
* @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');
}
/**
* @Given user :user uploads new chunk file :num with :data to id :id
*/
public function userUploadsNewChunkFileOfWithToId($user, $num, $data, $id) {
- $data = \GuzzleHttp\Psr7\stream_for($data);
+ $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');
}
/**
@@ -563,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');
}
/**
@@ -577,20 +853,78 @@ 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 "([^"]*)"$/
*/
public function downloadingFileAs($fileName, $user) {
try {
$this->response = $this->makeDavRequest($user, 'GET', $fileName, []);
- } catch (\GuzzleHttp\Exception\ServerException $ex) {
- $this->response = $ex->getResponse();
+ } 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();
}
}
@@ -684,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() {
@@ -711,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');
}
}
}
@@ -728,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);
}
}
@@ -744,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'];
}
@@ -767,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 16c165b6bab..00000000000
--- a/build/integration/features/carddav.feature
+++ /dev/null
@@ -1,66 +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-Download-Options|noopen|
- |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-Download-Options|noopen|
- |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
new file mode 100644
index 00000000000..772c0e5405c
--- /dev/null
+++ b/build/integration/features/contacts-menu.feature
@@ -0,0 +1,194 @@
+# 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
+ Given user "user0" exists
+ And user "user1" exists
+ 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 "1" contacts
+ And searched contact "0" is named "Test name"
+
+ Scenario: users can be searched by email
+ Given user "user0" exists
+ And user "user1" exists
+ 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 "1" contacts
+ And searched contact "0" is named "user1"
+
+ Scenario: users can not be searched by id
+ Given user "user0" exists
+ And user "user1" exists
+ 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 "user"
+ Then the list of searched contacts has "0" contacts
+
+ Scenario: search several users
+ Given user "user0" exists
+ And user "user1" exists
+ And user "user2" exists
+ And user "user3" exists
+ And user "user4" exists
+ And user "user5" exists
+ And As an "admin"
+ And sending "PUT" to "/cloud/users/user1" with
+ | key | displayname |
+ | value | Test name |
+ And sending "PUT" to "/cloud/users/user2" with
+ | key | email |
+ | value | test@example.com |
+ And sending "PUT" to "/cloud/users/user3" with
+ | key | displayname |
+ | value | Unmatched name |
+ And sending "PUT" to "/cloud/users/user4" with
+ | key | email |
+ | value | unmatched@example.com |
+ And sending "PUT" to "/cloud/users/user5" with
+ | key | displayname |
+ | value | Another test name |
+ And sending "PUT" to "/cloud/users/user5" with
+ | key | email |
+ | value | another_test@example.com |
+ When Logging in using web as "user0"
+ And searching for contacts matching with "test"
+ Then the list of searched contacts has "3" contacts
+ # Results are sorted alphabetically
+ And searched contact "0" is named "Another test name"
+ 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
+ And user "user2" exists
+ And Logging in using web as "user1"
+ And Sending a "PUT" to "/settings/users/user1/settings" with requesttoken
+ | displayname | Test name |
+ | 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 | v2-federated |
+ When Logging in using web as "user0"
+ And searching for contacts matching with "test"
+ # 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
+ And user "user1" exists
+ And user "user2" exists
+ And Logging in using web as "user1"
+ And Sending a "PUT" to "/settings/users/user1/settings" with requesttoken
+ | email | test@example.com |
+ | 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 | 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
+ And user "user1" exists
+ And user "user2" exists
+ And Logging in using web as "user1"
+ And Sending a "PUT" to "/settings/users/user1/settings" with requesttoken
+ | displayname | Test name |
+ | displaynameScope | v2-federated |
+ | email | test@example.com |
+ | 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 | v2-private |
+ | email | another_test@example.com |
+ | 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
+ # Disabled because it regularly fails on drone:
+ # And searched contact "0" is named ""
+ And searched contact "1" is named "Test name"
+
+
+
+ Scenario: users can be searched by display name if visibility is increased again
+ 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
+ | displayname | Test name |
+ | displaynameScope | v2-private |
+ And Sending a "PUT" to "/settings/users/user1/settings" with requesttoken
+ | 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 "Test name"
+
+ Scenario: users can be searched by email if visibility is increased again
+ 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
+ | email | test@example.com |
+ | emailScope | v2-private |
+ And Sending a "PUT" to "/settings/users/user1/settings" with requesttoken
+ | 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"
+
+
+
+ Scenario: users can not be searched by display name 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
+ | 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"
+ # 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 | 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"
+ # 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 5405510283f..00000000000
--- a/build/integration/features/dav-v2.feature
+++ /dev/null
@@ -1,83 +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-Download-Options|noopen|
- |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"
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 59a873dd816..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 storages
- 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 717aa04e4bd..8fcfb076497 100644
--- a/build/integration/features/provisioning-v1.feature
+++ b/build/integration/features/provisioning-v1.feature
@@ -1,615 +1,892 @@
+# 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 |
-
- 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 | 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 |
- | phone | +4971125242890 |
- | address | Foo Bar Town |
- | website | https://nextcloud.com |
- | twitter | Nextcloud |
-
- 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
-
- 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
-
- 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 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 "997"
- And the HTTP status code should be "401"
-
- 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
- | accessibility |
- | 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 "997"
- Then the HTTP status code should be "401"
- 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 "997"
- Then the HTTP status code should be "401"
- 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 "997"
- And the HTTP status code should be "401"
- 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 "997"
- And the HTTP status code should be "401"
- 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 "403"
-
+ 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"
+ And user "brand-new-user" exists
+ Then user "brand-new-user" has editable fields
+ | displayname |
+ | email |
+ | additional_mail |
+ | phone |
+ | address |
+ | website |
+ | 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 |
+ | email |
+ | additional_mail |
+ | phone |
+ | address |
+ | website |
+ | twitter |
+ | bluesky |
+ | fediverse |
+ | organisation |
+ | role |
+ | headline |
+ | biography |
+ | profile_enabled |
+ | pronouns |
+ Then user "self" has editable fields
+ | displayname |
+ | email |
+ | additional_mail |
+ | phone |
+ | 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"
+ 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 "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 |
+ 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 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 |
+ | 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
+ 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 | 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.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 | 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.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 | noreply4@nextcloud.com |
+ | value | v2-published |
+ 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 |
+ | additional_mailScope | v2-federated;v2-published |
+
+ 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 "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.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.reply5@nextcloud.com |
+ | value | invalid |
+ Then the OCS status code should be "102"
+ And the HTTP status code should be "200"
+
+ Scenario: Delete a user account multi-value property 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.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 | 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.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 | noreply7@nextcloud.com |
+ Then user "brand-new-user" has not
+ | 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 b18453cf3ec..00000000000
--- a/build/integration/features/transfer-ownership.feature
+++ /dev/null
@@ -1,565 +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 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 66652e6fa26..00000000000
--- a/build/integration/features/webdav-related.feature
+++ /dev/null
@@ -1,610 +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-Download-Options|noopen|
- |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"