Compare commits

..
16 changed files with 294 additions and 146 deletions

No files matched your search

@@ -4,7 +4,6 @@ import (
"errors"
occfg "github.com/opencloud-eu/opencloud/pkg/config"
"github.com/opencloud-eu/opencloud/pkg/shared"
"github.com/opencloud-eu/opencloud/services/activitylog/pkg/config"
"github.com/opencloud-eu/opencloud/services/activitylog/pkg/config/defaults"
@@ -35,8 +34,5 @@ func ParseConfig(cfg *config.Config) error {
// Validate validates the config
func Validate(cfg *config.Config) error {
if cfg.Events.Disabled && cfg.HTTP.Disabled {
return shared.AllComponentsDisabledError(cfg.Service.Name)
}
return nil
}
+7 -4
View File
@@ -10,21 +10,24 @@ file_delete)
user 'user_id' trashed file 'item_id'
file_trash_delete)
user 'user_id' removed file 'item_id' from trashbin
file_read)
user 'user_id' read file 'item_id'
```
Example json:
```
{"RemoteAddr":"","User":"user_id","URL":"","Method":"","UserAgent":"","Time":"","App":"admin_audit","Message":"user 'user_id' trashed file 'item_id'","Action":"file_delete","CLI":false,"Level":1,"Path":"path","Owner":"user_id","FileID":"item_id"}
{"RemoteAddr":"","User":"user_id","URL":"","Method":"","UserAgent":"","Time":"","App":"admin_audit","Message":"user 'user_id' removed file 'item_id' from trashbin","Action":"file_trash_delete","CLI":false,"Level":1,"Path":"path","Owner":"user_id","FileID":"item_id"}
{"RemoteAddr":"","User":"user_id","URL":"","Method":"","UserAgent":"","Time":"","App":"admin_audit","Message":"user 'user_id' read file 'item_id'","Action":"file_read","CLI":false,"Level":1,"Path":"path","Owner":"user_id","FileID":"item_id"}
```
The audit service is not started automatically when running as single binary started via `opencloud server` or when running as docker container and must be started and stopped manually on demand.
The audit service logs:
- File system operations
(create/delete/move; including actions on the trash bin and versioning)
- User management operations
- File system operations
(create/delete/move/read; including actions on the trash bin and versioning)
- User management operations
(creation/deletion of users)
- Sharing operations
- Sharing operations
(user/group sharing, sharing via link, changing permissions, calls to sharing API from clients)
+4
View File
@@ -63,6 +63,10 @@ Store specific notes:
- When using `nats-js-kv` it is recommended to set `OC_CACHE_STORE_NODES` to the same value as `OC_EVENTS_ENDPOINT`. That way the cache uses the same nats instance as the event bus.
- When using the `nats-js-kv` store, it is possible to set `OC_CACHE_DISABLE_PERSISTENCE` to instruct nats to not persist cache data on disc.
### Auto-Accept Shares
When setting the `SHARING_AUTO_ACCEPT_SHARES` to `true` (sharing service), all incoming shares will be accepted automatically. Users can overwrite this setting individually in their profile. The deprecated `FRONTEND_AUTO_ACCEPT_SHARES` is still supported for backwards compatibility.
## Passwords
### The Password Policy
+5 -1
View File
@@ -40,7 +40,11 @@ func ParseConfig(cfg *config.Config) error {
func Validate(cfg *config.Config) error {
if cfg.HTTP.Disabled && cfg.Events.DisabledConsumer {
return shared.AllComponentsDisabledError(cfg.Service.Name)
// might be debatable, but this situation should be treated as an error,
// as the process wouldn't be able to serve either API and would thus be
// completely useless -- in that case, just don't start this service
// in the first place (especially since it's optional)
return shared.AllComponentsDisabledError("graph")
}
if cfg.TokenManager.JWTSecret == "" {
+6 -3
View File
@@ -4,7 +4,6 @@ import (
"errors"
occfg "github.com/opencloud-eu/opencloud/pkg/config"
"github.com/opencloud-eu/opencloud/pkg/shared"
"github.com/opencloud-eu/opencloud/services/policies/pkg/config"
"github.com/opencloud-eu/opencloud/services/policies/pkg/config/defaults"
@@ -34,8 +33,12 @@ func ParseConfig(cfg *config.Config) error {
}
func Validate(cfg *config.Config) error {
if cfg.Events.Disabled && cfg.GRPC.Disabled {
return shared.AllComponentsDisabledError(cfg.Service.Name)
if cfg.GRPC.Disabled && cfg.Events.Disabled {
// might be debatable, but this situation should be treated as an error,
// as the process wouldn't be able to serve either API and would thus be
// completely useless -- in that case, just don't start this service
// in the first place (especially since it's optional)
return errors.New("both gRPC and events APIs are disabled by configuration; at least one must be enabled")
}
return nil
}
@@ -34,10 +34,6 @@ func ParseConfig(cfg *config.Config) error {
}
func Validate(cfg *config.Config) error {
if cfg.Events.Disabled && cfg.GRPC.Disabled {
return shared.AllComponentsDisabledError(cfg.Service.Name)
}
if cfg.TokenManager.JWTSecret == "" {
return shared.MissingJWTTokenError(cfg.Service.Name)
}
+1 -6
View File
@@ -35,12 +35,7 @@ Share behavior can be configured via environment variables:
- Auto-acceptance of shares
- Share permissions and restrictions
### Auto-Accept Shares
When setting the `SHARING_AUTO_ACCEPT_SHARES` to `true` (sharing service), all
incoming shares will be accepted automatically. Users can overwrite this
setting individually in their profile. The deprecated
`FRONTEND_AUTO_ACCEPT_SHARES` is still supported for backwards compatibility.
See the `frontend` service README for more details on share-related configuration options.
## Scalability
+14 -5
View File
@@ -39,16 +39,25 @@ class WaitHelper {
/**
* Repeat $makeAttempt until $shouldStop returns true or the timeout elapses.
*
* @param callable $makeAttempt makes one attempt (e.g. sends a request) and returns its result
* @param callable $shouldStop receives that result, returns true to stop polling
* @param callable $makeAttempt makes one attempt (e.g. sends a request) and returns its result
* @param callable $shouldStop receives that result, returns true to stop polling
* @param int|null $intervalMs pause between attempts in ms; defaults to self::INTERVAL_MS
* @param int|null $timeoutSeconds overall time to keep polling; defaults to self::TIMEOUT_SECONDS
*
* @return mixed the last result from $makeAttempt
*/
public static function waitUntil(callable $makeAttempt, callable $shouldStop): mixed {
$deadline = \microtime(true) + self::TIMEOUT_SECONDS;
public static function waitUntil(
callable $makeAttempt,
callable $shouldStop,
?int $intervalMs = null,
?int $timeoutSeconds = null
): mixed {
$intervalMs ??= self::INTERVAL_MS;
$timeoutSeconds ??= self::TIMEOUT_SECONDS;
$deadline = \microtime(true) + $timeoutSeconds;
$result = $makeAttempt();
while (!$shouldStop($result) && \microtime(true) < $deadline) {
\usleep(self::INTERVAL_MS * 1000);
\usleep($intervalMs * 1000);
$result = $makeAttempt();
}
return $result;
+105 -21
View File
@@ -150,7 +150,7 @@ class SearchContext implements Context {
): void {
// NOTE: because indexing of newly uploaded files or directories with OpenCloud is decoupled and occurs asynchronously
// short wait is necessary before searching
sleep(10);
sleep(2);
// remember the query so "should eventually contain" steps can re-search
$this->lastSearchQuery = [
"user" => $user,
@@ -176,6 +176,25 @@ class SearchContext implements Context {
string $path,
string $user,
TableNode $properties
): void {
$assert = fn () => $this->assertFileOrFolderInSearchResultContainsProperties($path, $user, $properties);
$this->retrySearchUntilSatisfied($assert);
$assert();
}
/**
*
* @param string $path
* @param string $user
* @param TableNode $properties
*
* @return void
* @throws Exception
*/
private function assertFileOrFolderInSearchResultContainsProperties(
string $path,
string $user,
TableNode $properties
): void {
$user = $this->featureContext->getActualUsername($user);
$this->featureContext->verifyTableNodeColumns($properties, ['name', 'value']);
@@ -234,8 +253,7 @@ class SearchContext implements Context {
*
* @throws Exception
*/
#[Then('/^the search result should contain these (?:files|entries) with highlight on keyword "([^"]*)"/')]
public function theSearchResultShouldContainEntriesWithHighlight(
private function assertSearchResultContainsEntriesWithHighlight(
TableNode $expectedFiles,
string $expectedContent
): void {
@@ -284,8 +302,15 @@ class SearchContext implements Context {
): void {
// NOTE: since indexing of newly uploaded files or directories with OpenCloud is decoupled and occurs asynchronously,
// a short wait is necessary before searching
sleep(5);
$response = $this-> searchFiles($user, $pattern, null, $scopeType, $scope, $spaceName);
sleep(2);
$this->lastSearchQuery = [
"user" => $user,
"pattern" => $pattern,
"scopeType" => $scopeType,
"scope" => $scope,
"spaceName" => $spaceName,
];
$response = $this->searchFiles($user, $pattern, null, $scopeType, $scope, $spaceName);
$this->featureContext->setResponse($response);
}
@@ -300,7 +325,7 @@ class SearchContext implements Context {
*
* @return void
*/
private function retrySearchUntilSatisfied(callable $assert): void {
public function retrySearchUntilSatisfied(callable $assert): void {
Assert::assertNotEmpty(
$this->lastSearchQuery,
'No search to retry. Use a "searches for ... using the WebDAV API" step first.'
@@ -310,33 +335,67 @@ class SearchContext implements Context {
fn () => $this->searchFiles(
$query["user"],
$query["pattern"],
$query["limit"],
null,
null,
null,
$query["properties"]
$query["limit"] ?? null,
$query["scopeType"] ?? null,
$query["scope"] ?? null,
$query["spaceName"] ?? null,
$query["properties"] ?? null
),
function ($response) use ($assert) {
$this->featureContext->setResponse($response);
try {
$assert();
return true;
} catch (\Throwable $e) {
} catch (\Throwable) {
return false;
}
}
},
2000,
20
);
$this->featureContext->setResponse($response);
}
/**
*
* @param int $numFiles
*
* @return void
*/
#[Then('the search result should contain :numFiles files/entries')]
public function theSearchResultShouldContainNumEntries(int $numFiles): void {
$assert = fn () => $this->featureContext->checkIFResponseContainsNumberEntries($numFiles);
$this->retrySearchUntilSatisfied($assert);
$assert();
}
/**
*
* @param string $user
* @param int $expectedNumber
* @param TableNode $expectedFiles
*
* @return void
*/
#[Then('the search result of user :user should contain any :expectedNumber of these files/entries:')]
public function theSearchResultShouldContainAnyOfTheseEntries(
string $user,
int $expectedNumber,
TableNode $expectedFiles
): void {
$assert = fn () => $this->featureContext->checkIfSearchResultContainsFiles($user, $expectedNumber, $expectedFiles);
$this->retrySearchUntilSatisfied($assert);
$assert();
}
/**
* @param string $user
* @param TableNode $expectedFiles
*
* @return void
*/
#[Then('/^the search result of user "([^"]*)" should eventually contain only these (?:files|entries):$/')]
public function theSearchResultShouldEventuallyContainOnlyEntries(string $user, TableNode $expectedFiles): void {
#[Then('/^the search result of user "([^"]*)" should contain only these (?:files|entries):$/')]
public function theSearchResultShouldContainOnlyEntries(string $user, TableNode $expectedFiles): void {
$assert = fn () => $this->featureContext->thePropfindResultShouldContainOnlyEntries($user, $expectedFiles);
$this->retrySearchUntilSatisfied($assert);
$assert();
@@ -348,9 +407,31 @@ class SearchContext implements Context {
*
* @return void
*/
#[Then('/^the search result of user "([^"]*)" should eventually contain these (?:files|entries):$/')]
public function theSearchResultShouldEventuallyContainEntries(string $user, TableNode $expectedFiles): void {
$assert = fn () => $this->featureContext->thePropfindResultShouldContainEntries($user, '', $expectedFiles);
#[Then('/^the search result of user "([^"]*)" should contain these (?:files|entries):$/')]
public function theSearchResultShouldContainEntries(string $user, TableNode $expectedFiles): void {
$this->assertSearchResultContainsEntries($user, "", $expectedFiles);
}
/**
* @param string $user
* @param TableNode $expectedFiles
*
* @return void
*/
#[Then('/^the search result of user "([^"]*)" should not contain these (?:files|entries):$/')]
public function theSearchResultShouldNotContainEntries(string $user, TableNode $expectedFiles): void {
$this->assertSearchResultContainsEntries($user, "not", $expectedFiles);
}
/**
* @param string $user
* @param string $shouldOrNot (not|)
* @param TableNode $expectedFiles
*
* @return void
*/
private function assertSearchResultContainsEntries(string $user, string $shouldOrNot, TableNode $expectedFiles): void {
$assert = fn () => $this->featureContext->thePropfindResultShouldContainEntries($user, $shouldOrNot, $expectedFiles);
$this->retrySearchUntilSatisfied($assert);
$assert();
}
@@ -361,9 +442,12 @@ class SearchContext implements Context {
*
* @return void
*/
#[Then('/^the search result should eventually contain these (?:files|entries) with highlight on keyword "([^"]*)"$/')]
public function theSearchResultShouldEventuallyContainEntriesWithHighlight(TableNode $expectedFiles, string $expectedContent): void {
$assert = fn () => $this->theSearchResultShouldContainEntriesWithHighlight($expectedFiles, $expectedContent);
#[Then('/^the search result should contain these (?:files|entries) with highlight on keyword "([^"]*)"$/')]
public function theSearchResultShouldContainEntriesWithHighlight(
TableNode $expectedFiles,
string $expectedContent
): void {
$assert = fn () => $this->assertSearchResultContainsEntriesWithHighlight($expectedFiles, $expectedContent);
$this->retrySearchUntilSatisfied($assert);
$assert();
}
+41 -33
View File
@@ -50,6 +50,7 @@ class SpacesContext implements Context {
private ChecksumContext $checksumContext;
private FilesVersionsContext $filesVersionsContext;
private ArchiverContext $archiverContext;
private SearchContext $searchContext;
/**
* key is space name and value is the username that created the space
@@ -512,6 +513,7 @@ class SpacesContext implements Context {
$this->checksumContext = BehatHelper::getContext($scope, $environment, 'ChecksumContext');
$this->filesVersionsContext = BehatHelper::getContext($scope, $environment, 'FilesVersionsContext');
$this->archiverContext = BehatHelper::getContext($scope, $environment, 'ArchiverContext');
$this->searchContext = BehatHelper::getContext($scope, $environment, 'SearchContext');
}
/**
@@ -3925,41 +3927,47 @@ class SpacesContext implements Context {
*/
#[Then('for user :user the search result should contain space :spaceName')]
public function searchResultShouldContainSpace(string $user, string $spaceName): void {
$responseArray = json_decode(
json_encode(
HttpRequestHelper::getResponseXml($this->featureContext->getResponse())->xpath("//d:response/d:href")
),
true,
512,
JSON_THROW_ON_ERROR
);
Assert::assertNotEmpty($responseArray, "search result is empty");
// searching a space by name goes through the asynchronous file index, so
// re-run the search until the space shows up (or the timeout elapses).
$assert = function () use ($user, $spaceName): void {
$responseArray = json_decode(
json_encode(
HttpRequestHelper::getResponseXml($this->featureContext->getResponse())->xpath("//d:response/d:href")
),
true,
512,
JSON_THROW_ON_ERROR
);
Assert::assertNotEmpty($responseArray, "search result is empty");
// for mountpoint, id looks a little different than for project space
if (str_contains($spaceName, 'mountpoint')) {
$splitSpaceName = explode("/", $spaceName);
$space = $this->getSpaceByName($user, $splitSpaceName[1]);
$splitSpaceId = explode("$", $space['id']);
$spaceId = str_replace('!', '%21', $splitSpaceId[1]);
} else {
$space = $this->getSpaceByName($user, $spaceName);
$spaceId = $space['id'];
}
$suffixPath = $user;
$davPathVersion = $this->featureContext->getDavPathVersion();
if ($davPathVersion === WebDavHelper::DAV_VERSION_SPACES) {
$suffixPath = $spaceId;
}
$topWebDavPath = "/" . WebDavHelper::getDavPath($davPathVersion, $suffixPath);
$spaceFound = false;
foreach ($responseArray as $value) {
if ($topWebDavPath === $value[0]) {
$spaceFound = true;
// for mountpoint, id looks a little different than for project space
if (str_contains($spaceName, 'mountpoint')) {
$splitSpaceName = explode("/", $spaceName);
$space = $this->getSpaceByName($user, $splitSpaceName[1]);
$splitSpaceId = explode("$", $space['id']);
$spaceId = str_replace('!', '%21', $splitSpaceId[1]);
} else {
$space = $this->getSpaceByName($user, $spaceName);
$spaceId = $space['id'];
}
}
Assert::assertTrue($spaceFound, "response does not contain the space '$spaceName'");
$suffixPath = $user;
$davPathVersion = $this->featureContext->getDavPathVersion();
if ($davPathVersion === WebDavHelper::DAV_VERSION_SPACES) {
$suffixPath = $spaceId;
}
$topWebDavPath = "/" . WebDavHelper::getDavPath($davPathVersion, $suffixPath);
$spaceFound = false;
foreach ($responseArray as $value) {
if ($topWebDavPath === $value[0]) {
$spaceFound = true;
}
}
Assert::assertTrue($spaceFound, "response does not contain the space '$spaceName'");
};
$this->searchContext->retrySearchUntilSatisfied($assert);
$assert();
}
/**
+91 -27
View File
@@ -25,6 +25,7 @@ use Behat\Gherkin\Node\TableNode;
use PHPUnit\Framework\Assert;
use Psr\Http\Message\ResponseInterface;
use TestHelpers\GraphHelper;
use TestHelpers\WaitHelper;
use TestHelpers\BehatHelper;
use Behat\Step\Given;
use Behat\Step\Then;
@@ -38,6 +39,7 @@ require_once 'bootstrap.php';
class TagContext implements Context {
private FeatureContext $featureContext;
private SpacesContext $spacesContext;
private array $lastTagsQuery = [];
/**
* This will run before EVERY scenario.
@@ -175,19 +177,79 @@ class TagContext implements Context {
*/
#[When('user :user lists all available tag(s) via the Graph API')]
public function theUserGetsAllAvailableTags(string $user): void {
// Note: after creating or deleting tags, in some cases tags do not appear or disappear immediately,
// So wait is necessary before listing tags
sleep(5);
$this->featureContext->setResponse(
GraphHelper::getTags(
$this->featureContext->getBaseUrl(),
$user,
$this->featureContext->getPasswordForUser($user),
$this->featureContext->getStepLineRef()
)
// Note: after creating or deleting tags, in some cases tags do not appear or disappear immediately
sleep(2);
$this->lastTagsQuery = ["user" => $user];
$this->featureContext->setResponse($this->fetchTags($user));
}
/**
* @param string $user
*
* @return ResponseInterface
* @throws Exception
*/
private function fetchTags(string $user): ResponseInterface {
return GraphHelper::getTags(
$this->featureContext->getBaseUrl(),
$user,
$this->featureContext->getPasswordForUser($user),
$this->featureContext->getStepLineRef()
);
}
/**
* re-run the last tag listing until the assertion passes or the WaitHelper
*
* @param callable $assert
*
* @return void
*/
private function retryTagsUntilSatisfied(callable $assert): void {
Assert::assertNotEmpty(
$this->lastTagsQuery,
'No tag listing to retry. Use a "lists all available tags via the Graph API" step first.'
);
$query = $this->lastTagsQuery;
$response = WaitHelper::waitUntil(
fn () => $this->fetchTags($query["user"]),
function ($response) use ($assert) {
$this->featureContext->setResponse($response);
try {
$assert();
return true;
} catch (\Throwable) {
return false;
}
}
);
$this->featureContext->setResponse($response);
}
/**
*
* @param TableNode $table
*
* @return void
* @throws Exception
*/
#[Then('/^the response should contain following tags:$/')]
public function theResponseShouldContainFollowingTags(TableNode $table): void {
$this->assertResponseContainsFollowingTags("", $table);
}
/**
*
* @param TableNode $table
*
* @return void
* @throws Exception
*/
#[Then('/^the response should not contain following tags:$/')]
public function theResponseShouldNotContainFollowingTags(TableNode $table): void {
$this->assertResponseContainsFollowingTags("not", $table);
}
/**
*
* @param string $shouldOrNot (not|)
@@ -196,27 +258,29 @@ class TagContext implements Context {
* @return void
* @throws Exception
*/
#[Then('/^the response should (not|)\\s?contain following tag(s):$/')]
public function theFollowingTagsShouldExistForUser(string $shouldOrNot, TableNode $table): void {
$rows = $table->getRows();
foreach ($rows as $row) {
private function assertResponseContainsFollowingTags(string $shouldOrNot, TableNode $table): void {
$assert = function () use ($shouldOrNot, $table): void {
$responseArray = $this->featureContext->getJsonDecodedResponse(
$this->featureContext->getResponse()
)['value'];
if ($shouldOrNot === "not") {
Assert::assertFalse(
\in_array($row[0], $responseArray),
"the response should not contain the tag $row[0].\nResponse\n"
. print_r($responseArray, true)
);
} else {
Assert::assertTrue(
\in_array($row[0], $responseArray),
"the response does not contain the tag $row[0].\nResponse\n"
. print_r($responseArray, true)
);
foreach ($table->getRows() as $row) {
if ($shouldOrNot === "not") {
Assert::assertFalse(
\in_array($row[0], $responseArray),
"the response should not contain the tag $row[0].\nResponse\n"
. print_r($responseArray, true)
);
} else {
Assert::assertTrue(
\in_array($row[0], $responseArray),
"the response does not contain the tag $row[0].\nResponse\n"
. print_r($responseArray, true)
);
}
}
}
};
$this->retryTagsUntilSatisfied($assert);
$assert();
}
/**
+4 -4
View File
@@ -4014,7 +4014,7 @@ trait WebDav {
* @return void
* @throws Exception
*/
#[Then('/^the (?:propfind|search) result of user "([^"]*)" should (not|)\\s?contain these (?:files|entries):$/')]
#[Then('/^the propfind result of user "([^"]*)" should (not|)\\s?contain these (?:files|entries):$/')]
public function thePropfindResultShouldContainEntries(
string $user,
string $shouldOrNot,
@@ -4036,7 +4036,7 @@ trait WebDav {
* @return void
* @throws Exception
*/
#[Then('/^the (?:propfind|search) result of user "([^"]*)" should contain only these (?:files|entries):$/')]
#[Then('/^the propfind result of user "([^"]*)" should contain only these (?:files|entries):$/')]
public function thePropfindResultShouldContainOnlyEntries(
string $user,
TableNode $expectedFiles
@@ -4063,7 +4063,7 @@ trait WebDav {
*
* @return void
*/
#[Then('the propfind/search result should contain :numFiles files/entries')]
#[Then('the propfind result should contain :numFiles files/entries')]
public function propfindResultShouldContainNumEntries(int $numFiles): void {
$this->checkIFResponseContainsNumberEntries($numFiles);
}
@@ -4110,7 +4110,7 @@ trait WebDav {
* @return void
* @throws Exception
*/
#[Then('the propfind/search result of user :user should contain any :expectedNumber of these files/entries:')]
#[Then('the propfind result of user :user should contain any :expectedNumber of these files/entries:')]
public function theSearchResultOfUserShouldContainAnyOfTheseEntries(
string $user,
int $expectedNumber,
@@ -186,15 +186,6 @@
- [apiServiceAvailability/serviceAvailabilityCheck.feature:123](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiServiceAvailability/serviceAvailabilityCheck.feature#L123)
#### [Missing properties in REPORT response](https://github.com/owncloud/ocis/issues/9780), [d:getetag property has empty value in REPORT response](https://github.com/owncloud/ocis/issues/9783)
- [apiSearch1/search.feature:437](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch1/search.feature#L437)
- [apiSearch1/search.feature:438](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch1/search.feature#L438)
- [apiSearch1/search.feature:439](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch1/search.feature#L439)
- [apiSearch1/search.feature:465](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch1/search.feature#L465)
- [apiSearch1/search.feature:466](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch1/search.feature#L466)
- [apiSearch1/search.feature:467](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch1/search.feature#L467)
## Scenarios from core API tests that are expected to fail with decomposed storage
### File
@@ -186,15 +186,6 @@
- [apiServiceAvailability/serviceAvailabilityCheck.feature:123](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiServiceAvailability/serviceAvailabilityCheck.feature#L123)
#### [Missing properties in REPORT response](https://github.com/owncloud/ocis/issues/9780), [d:getetag property has empty value in REPORT response](https://github.com/owncloud/ocis/issues/9783)
- [apiSearch1/search.feature:437](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch1/search.feature#L437)
- [apiSearch1/search.feature:438](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch1/search.feature#L438)
- [apiSearch1/search.feature:439](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch1/search.feature#L439)
- [apiSearch1/search.feature:465](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch1/search.feature#L465)
- [apiSearch1/search.feature:466](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch1/search.feature#L466)
- [apiSearch1/search.feature:467](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch1/search.feature#L467)
## Scenarios from core API tests that are expected to fail with posix storage
### File
@@ -409,7 +409,7 @@ Feature: Search
| new |
| spaces |
@issue-4712 @issue-9780 @issue-9781 @issue-9783 @issue-10329
@skip @issue-3501 @issue-9780
Scenario Outline: report extra properties in search entries for a file
Given using <dav-path-version> DAV path
When user "Alice" searches for "*insideTheFo*" using the WebDAV API requesting these properties:
@@ -438,7 +438,7 @@ Feature: Search
| new |
| spaces |
@issue-4712 @issue-9780 @issue-9781 @issue-9783 @issue-10329
@skip @issue-3501 @issue-9780
Scenario Outline: report extra properties in search entries for a folder
Given using <dav-path-version> DAV path
When user "Alice" searches for "*folderMain*" using the WebDAV API requesting these properties:
@@ -16,7 +16,7 @@ Feature: content search
And user "Alice" has uploaded file with content "namaste from nepal" to "hello.txt"
When user "Alice" searches for "Content:hello" using the WebDAV API
Then the HTTP status code should be "207"
And the search result of user "Alice" should eventually contain only these files:
And the search result of user "Alice" should contain only these files:
| keywordAtStart.txt |
| keywordAtMiddle.txt |
| keywordAtLast.txt |
@@ -34,15 +34,15 @@ Feature: content search
And user "Alice" has uploaded file with content "alan@example.org want to say hello" to "findByEmail.docs"
When user "Alice" searches for "Content:k6" using the WebDAV API
Then the HTTP status code should be "207"
And the search result of user "Alice" should eventually contain only these files:
And the search result of user "Alice" should contain only these files:
| wordWithNumber.md |
When user "Alice" searches for "Content:https://opencloud.eu/" using the WebDAV API
Then the HTTP status code should be "207"
And the search result of user "Alice" should eventually contain only these files:
And the search result of user "Alice" should contain only these files:
| findByWebSite.txt |
When user "Alice" searches for "Content:alan@" using the WebDAV API
Then the HTTP status code should be "207"
And the search result of user "Alice" should eventually contain only these files:
And the search result of user "Alice" should contain only these files:
| findByEmail.docs |
Examples:
| dav-path-version |
@@ -71,11 +71,11 @@ Feature: content search
And user "Alice" has uploaded file with content "He has expirience, we must to have, I have to find ...." to "fileWithStopWords.txt"
When user "Alice" searches for 'Content:"he has"' using the WebDAV API
Then the HTTP status code should be "207"
And the search result of user "Alice" should eventually contain only these files:
And the search result of user "Alice" should contain only these files:
| fileWithStopWords.txt |
When user "Alice" searches for 'Content:"I have"' using the WebDAV API
Then the HTTP status code should be "207"
And the search result of user "Alice" should eventually contain only these files:
And the search result of user "Alice" should contain only these files:
| fileWithStopWords.txt |
Examples:
| dav-path-version |
@@ -101,7 +101,7 @@ Feature: content search
And user "Brian" has a share "uploadFolder" synced
When user "Brian" searches for "Content:hello" using the WebDAV API
Then the HTTP status code should be "207"
And the search result of user "Brian" should eventually contain only these files:
And the search result of user "Brian" should contain only these files:
| keywordAtStart.txt |
| keywordAtMiddle.txt |
| keywordAtLast.txt |
@@ -121,7 +121,7 @@ Feature: content search
And user "Alice" has deleted file "keywordAtLast.txt"
When user "Alice" searches for "Content:hello" using the WebDAV API
Then the HTTP status code should be "207"
And the search result of user "Alice" should eventually contain only these files:
And the search result of user "Alice" should contain only these files:
| keywordAtStart.txt |
| keywordAtMiddle.txt |
Examples:
@@ -139,7 +139,7 @@ Feature: content search
And user "Alice" has restored the file with original path "keywordAtStart.txt"
When user "Alice" searches for "Content:hello" using the WebDAV API
Then the HTTP status code should be "207"
And the search result of user "Alice" should eventually contain only these files:
And the search result of user "Alice" should contain only these files:
| keywordAtStart.txt |
Examples:
| dav-path-version |
@@ -154,7 +154,7 @@ Feature: content search
And user "Alice" has restored version index "1" of file "test.txt"
When user "Alice" searches for "Content:hello" using the WebDAV API
Then the HTTP status code should be "207"
And the search result of user "Alice" should eventually contain only these files:
And the search result of user "Alice" should contain only these files:
| test.txt |
Examples:
| dav-path-version |
@@ -175,7 +175,7 @@ Feature: content search
And using <dav-path-version> DAV path
When user "Alice" searches for "Content:hello" using the WebDAV API
Then the HTTP status code should be "207"
And the search result of user "Alice" should eventually contain only these files:
And the search result of user "Alice" should contain only these files:
| keywordAtStart.txt |
| keywordAtMiddle.txt |
| keywordAtLast.txt |
@@ -204,7 +204,7 @@ Feature: content search
And using <dav-path-version> DAV path
When user "Brian" searches for "Content:hello" using the WebDAV API
Then the HTTP status code should be "207"
And the search result of user "Alice" should eventually contain only these files:
And the search result of user "Alice" should contain only these files:
| keywordAtStart.txt |
| keywordAtMiddle.txt |
| keywordAtLast.txt |
@@ -224,7 +224,7 @@ Feature: content search
| technical task.txt | test |
When user "Alice" searches for '<pattern>' using the WebDAV API
Then the HTTP status code should be "207"
And the search result of user "Alice" should eventually contain these entries:
And the search result of user "Alice" should contain these entries:
| <search-result-1> |
| <search-result-2> |
And the search result should contain "<result-count>" entries
@@ -251,7 +251,7 @@ Feature: content search
And user "Alice" has uploaded a file inside space "project-space" with content "this is a simple odt file" to "test-odt-file.odt"
When user "Alice" searches for "Content:simple" using the WebDAV API
Then the HTTP status code should be "207"
And the search result should eventually contain these entries with highlight on keyword "simple"
And the search result should contain these entries with highlight on keyword "simple"
| test-text-file.txt |
| test-pdf-file.pdf |
| test-cpp-file.cpp |