diff --git a/.woodpecker.star b/.woodpecker.star index 40b1aead4e..cb9e921711 100644 --- a/.woodpecker.star +++ b/.woodpecker.star @@ -329,7 +329,6 @@ config = { "OC_ASYNC_UPLOADS": True, "OC_ADD_RUN_SERVICES": "antivirus", }, - "storages": ["decomposed"], }, "multiTenancy": { "suites": [ @@ -1300,8 +1299,7 @@ def localApiTestPipeline(ctx): continue # use decomposed storage if specified in the PR title - # run CLI tests only with decomposed storage - if "[decomposed]" in ctx.build.title.lower() or name.startswith("cli"): + if "[decomposed]" in ctx.build.title.lower(): params["storages"] = ["decomposed"] for storage in params["storages"]: diff --git a/tests/acceptance/bootstrap/CliContext.php b/tests/acceptance/bootstrap/CliContext.php index 650e7e3a6b..057c9e61e3 100644 --- a/tests/acceptance/bootstrap/CliContext.php +++ b/tests/acceptance/bootstrap/CliContext.php @@ -325,6 +325,150 @@ class CliContext implements Context { $this->featureContext->setResponse(CliHelper::runCommand($body)); } + /** + * @When the administrator purges the expired trash-bin items using the CLI + * + * @return void + */ + public function theAdministratorPurgesExpiredTrashBinItemsUsingTheCli(): void { + $command = "storage-users trash-bin purge-expired"; + $body = [ + "command" => $command, + "timeout" => 15 + ]; + $this->featureContext->setResponse(CliHelper::runCommand($body)); + } + + /** + * @When the administrator lists the trash-bin items of space :space using the CLI + * + * @param string $space + * + * @return void + */ + public function theAdministratorListsTrashBinItemsOfSpaceUsingTheCli(string $space): void { + $adminUsername = $this->featureContext->getAdminUsername(); + $spaceId = $this->spacesContext->getSpaceIdByName($adminUsername, $space); + $command = "storage-users trash-bin list $spaceId"; + $body = [ + "command" => $command + ]; + $this->featureContext->setResponse(CliHelper::runCommand($body)); + } + + /** + * @When the administrator lists the trash-bin items of the personal space of user :user using the CLI + * + * @param string $user + * + * @return void + */ + public function theAdministratorListsTrashBinItemsOfPersonalSpaceUsingTheCli(string $user): void { + $spaceId = $this->spacesContext->getSpaceIdByName($user, 'Personal'); + $command = "storage-users trash-bin list $spaceId"; + $body = [ + "command" => $command + ]; + $this->featureContext->setResponse(CliHelper::runCommand($body)); + } + + /** + * @When the administrator restores all the trash-bin items of space :space using the CLI + * + * @param string $space + * + * @return void + */ + public function theAdministratorRestoresAllTrashBinItemsOfSpaceUsingTheCli(string $space): void { + $adminUsername = $this->featureContext->getAdminUsername(); + $spaceId = $this->spacesContext->getSpaceIdByName($adminUsername, $space); + $command = "storage-users trash-bin restore-all $spaceId --yes"; + $body = [ + "command" => $command + ]; + $this->featureContext->setResponse(CliHelper::runCommand($body)); + } + + /** + * @When the administrator restores all the trash-bin items of the personal space of user :user using the CLI + * + * @param string $user + * + * @return void + */ + public function theAdministratorRestoresAllTrashBinItemsOfPersonalSpaceUsingTheCli(string $user): void { + $spaceId = $this->spacesContext->getSpaceIdByName($user, 'Personal'); + $command = "storage-users trash-bin restore-all $spaceId --yes"; + $body = [ + "command" => $command + ]; + $this->featureContext->setResponse(CliHelper::runCommand($body)); + } + + /** + * @When the administrator restores the trash-bin item with file-id :fileId of space :space using the CLI + * + * @param string $fileId + * @param string $space + * + * @return void + */ + public function theAdministratorRestoresTrashBinItemOfSpaceUsingTheCli(string $fileId, string $space): void { + $adminUsername = $this->featureContext->getAdminUsername(); + $spaceId = $this->spacesContext->getSpaceIdByName($adminUsername, $space); + $command = "storage-users trash-bin restore $spaceId $fileId"; + $body = [ + "command" => $command + ]; + $this->featureContext->setResponse(CliHelper::runCommand($body)); + } + + /** + * @When the administrator restores the trash-bin item with file-id :fileId of the personal space of user :user using the CLI + * + * @param string $fileId + * @param string $user + * + * @return void + */ + public function theAdministratorRestoresTrashBinItemOfPersonalSpaceUsingTheCli(string $fileId, string $user): void { + $spaceId = $this->spacesContext->getSpaceIdByName($user, 'Personal'); + $command = "storage-users trash-bin restore $spaceId $fileId"; + $body = [ + "command" => $command + ]; + $this->featureContext->setResponse(CliHelper::runCommand($body)); + } + + /** + * @When the administrator cleans up orphaned shares using the CLI + * + * @return void + */ + public function theAdministratorCleansUpOrphanedSharesUsingTheCli(): void { + $serviceAccountId = getenv('OC_SERVICE_ACCOUNT_ID') ?: 'service-account-id'; + $serviceAccountSecret = getenv('OC_SERVICE_ACCOUNT_SECRET') ?: 'service-account-secret'; + $command = "shares cleanup --service-account-id=$serviceAccountId" + . " --service-account-secret=$serviceAccountSecret"; + $body = [ + "command" => $command + ]; + $this->featureContext->setResponse(CliHelper::runCommand($body)); + } + + /** + * @When the administrator lists the unified roles using the CLI + * + * @return void + */ + public function theAdministratorListsTheUnifiedRolesUsingTheCli(): void { + $command = "graph list-unified-roles"; + $body = [ + "command" => $command + ]; + $this->featureContext->setResponse(CliHelper::runCommand($body)); + } + /** * @Then the command should be successful * diff --git a/tests/acceptance/expected-failures-decomposed-storage.md b/tests/acceptance/expected-failures-decomposed-storage.md index 1223612890..cba79955fd 100644 --- a/tests/acceptance/expected-failures-decomposed-storage.md +++ b/tests/acceptance/expected-failures-decomposed-storage.md @@ -343,6 +343,12 @@ _ocdav: api compatibility, return correct status code_ - [apiSharingNg1/removeAccessToDrive.feature:145](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSharingNg1/removeAccessToDrive.feature#L145) +#### [POSIX: failing CLI commands](https://github.com/opencloud-eu/opencloud/issues/3095) + +- [cliCommands/restoreTrashBinItems.feature:11](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/cliCommands/restoreTrashBinItems.feature#L11) +- [cliCommands/restoreTrashBinItems.feature:23](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/cliCommands/restoreTrashBinItems.feature#L23) +- [cliCommands/sharesCleanup.feature:12](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/cliCommands/sharesCleanup.feature#L12) + ### Won't fix Not everything needs to be implemented for opencloud. diff --git a/tests/acceptance/expected-failures-posix-storage.md b/tests/acceptance/expected-failures-posix-storage.md index 58b9f74e5c..837091c2e0 100644 --- a/tests/acceptance/expected-failures-posix-storage.md +++ b/tests/acceptance/expected-failures-posix-storage.md @@ -345,6 +345,14 @@ _ocdav: api compatibility, return correct status code_ - [apiSharingNg1/removeAccessToDrive.feature:145](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSharingNg1/removeAccessToDrive.feature#L145) +tests/acceptance/features/cliCommands/restoreTrashBinItems.feature + +#### [POSIX: failing CLI commands](https://github.com/opencloud-eu/opencloud/issues/3095) + +- [cliCommands/restoreTrashBinItems.feature:11](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/cliCommands/restoreTrashBinItems.feature#L11) +- [cliCommands/restoreTrashBinItems.feature:23](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/cliCommands/restoreTrashBinItems.feature#L23) +- [cliCommands/sharesCleanup.feature:12](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/cliCommands/sharesCleanup.feature#L12) + ### Won't fix diff --git a/tests/acceptance/features/cliCommands/backupConsistency.feature b/tests/acceptance/features/cliCommands/backupConsistency.feature index 9e97148658..1fb2c62e83 100644 --- a/tests/acceptance/features/cliCommands/backupConsistency.feature +++ b/tests/acceptance/features/cliCommands/backupConsistency.feature @@ -1,4 +1,4 @@ -@env-config +@env-config @skipOnOpencloud-posix-Storage Feature: backup consistency As a user I want to check my data for inconsistencies diff --git a/tests/acceptance/features/cliCommands/cleanupEmptyTrashBin.feature b/tests/acceptance/features/cliCommands/cleanupEmptyTrashBin.feature index bb33c878c1..b37cb0ca6f 100644 --- a/tests/acceptance/features/cliCommands/cleanupEmptyTrashBin.feature +++ b/tests/acceptance/features/cliCommands/cleanupEmptyTrashBin.feature @@ -1,24 +1,16 @@ -@env-config +@env-config @skipOnOpencloud-posix-Storage Feature: delete empty trash bin folder via CLI command Scenario: delete empty trashbin folders - Given the user "Admin" has created a new user with the following attributes: - | userName | Alice | - | displayName | Alice Hansen | - | password | %alt1% | + Given user "Alice" has been created with default attributes And user "Alice" has created the following folders | path | | folder-to-delete | | folder-to-restore | - And user "Alice" has uploaded file with content "test file" to "testfile.txt" And user "Alice" has deleted the following resources | path | | folder-to-delete | | folder-to-restore | - | testfile.txt | - And user "Alice" has restored the folder with original path "folder-to-restore" - And user "Alice" has deleted the folder with original path "folder-to-delete" from the trashbin - And the administrator has stopped the server When the administrator deletes the empty trashbin folders using the CLI Then the command should be successful diff --git a/tests/acceptance/features/cliCommands/graphListUnifiedRoles.feature b/tests/acceptance/features/cliCommands/graphListUnifiedRoles.feature new file mode 100644 index 0000000000..d6a241be95 --- /dev/null +++ b/tests/acceptance/features/cliCommands/graphListUnifiedRoles.feature @@ -0,0 +1,13 @@ +Feature: list unified roles via CLI command + As an admin + I want to list the available unified roles + So that I know their IDs when configuring GRAPH_AVAILABLE_ROLES or writing documentation + + + Scenario: list unified roles + When the administrator lists the unified roles using the CLI + Then the command should be successful + And the command output should contain "Viewer" + And the command output should contain "Editor" + And the command output should contain "SpaceManager" + And the command output should contain "SecureViewer" diff --git a/tests/acceptance/features/cliCommands/listTrashBinItems.feature b/tests/acceptance/features/cliCommands/listTrashBinItems.feature new file mode 100644 index 0000000000..aea89c7378 --- /dev/null +++ b/tests/acceptance/features/cliCommands/listTrashBinItems.feature @@ -0,0 +1,28 @@ +@env-config +Feature: list trash-bin items of a space via CLI command + As an admin + I want to list the trash-bin items of a space + So that I know which items are available to restore + + Background: + Given user "Alice" has been created with default attributes + + + Scenario: list trash-bin items of a personal space + Given user "Alice" has uploaded file with content "some data" to "textfile.txt" + And user "Alice" has deleted file "textfile.txt" + When the administrator lists the trash-bin items of the personal space of user "Alice" using the CLI + Then the command should be successful + And the command output should contain "textfile.txt" + + + Scenario: list trash-bin items of a project space + Given using spaces DAV path + And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API + And user "Alice" has created a space "new-space" with the default quota using the Graph API + And user "Alice" has uploaded a file inside space "new-space" with content "some data" to "textfile.txt" + And we save it into "FILEID" + And user "Alice" deletes file "textfile.txt" from space "new-space" using file-id "<>" + When the administrator lists the trash-bin items of space "new-space" using the CLI + Then the command should be successful + And the command output should contain "textfile.txt" diff --git a/tests/acceptance/features/cliCommands/purgeExpiredTrashBinItems.feature b/tests/acceptance/features/cliCommands/purgeExpiredTrashBinItems.feature new file mode 100644 index 0000000000..c4cdf514d6 --- /dev/null +++ b/tests/acceptance/features/cliCommands/purgeExpiredTrashBinItems.feature @@ -0,0 +1,35 @@ +@env-config +Feature: purge expired trash-bin items via CLI command + As an admin + I want to purge expired trash-bin items + So that the storage does not fill up with old deleted files + + Background: + Given the following configs have been set: + | config | value | + | STORAGE_USERS_PURGE_TRASH_BIN_PERSONAL_DELETE_BEFORE | 1s | + | STORAGE_USERS_PURGE_TRASH_BIN_PROJECT_DELETE_BEFORE | 1s | + And user "Alice" has been created with default attributes + + + Scenario: purge expired trash-bin items of a personal space + Given user "Alice" has uploaded file with content "some data" to "textfile.txt" + And user "Alice" has deleted file "textfile.txt" + And the user waits for 2 seconds + When the administrator purges the expired trash-bin items using the CLI + Then the command should be successful + And user "Alice" lists the resources in the trashbin with depth "1" using the WebDAV API + And as "Alice" file "textfile.txt" should not exist in the trashbin of the space "Personal" + + + Scenario: purge expired trash-bin items of a project space + Given using spaces DAV path + And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API + And user "Alice" has created a space "new-space" with the default quota using the Graph API + And user "Alice" has uploaded a file inside space "new-space" with content "some data" to "textfile.txt" + And we save it into "FILEID" + And user "Alice" deletes file "textfile.txt" from space "new-space" using file-id "<>" + And the user waits for 2 seconds + When the administrator purges the expired trash-bin items using the CLI + Then the command should be successful + And as "Alice" file "textfile.txt" should not exist in the trashbin of the space "new-space" diff --git a/tests/acceptance/features/cliCommands/removeFileVersions.feature b/tests/acceptance/features/cliCommands/removeFileVersions.feature index c203b2e88c..383fadc100 100644 --- a/tests/acceptance/features/cliCommands/removeFileVersions.feature +++ b/tests/acceptance/features/cliCommands/removeFileVersions.feature @@ -1,4 +1,4 @@ -@env-config +@env-config @skipOnOpencloud-posix-Storage Feature: remove file versions via CLI command Background: diff --git a/tests/acceptance/features/cliCommands/restoreTrashBinItems.feature b/tests/acceptance/features/cliCommands/restoreTrashBinItems.feature new file mode 100644 index 0000000000..9e8daf18a4 --- /dev/null +++ b/tests/acceptance/features/cliCommands/restoreTrashBinItems.feature @@ -0,0 +1,67 @@ +@env-config +Feature: restore trash-bin items via CLI command + As an admin + I want to restore a specific trash-bin item or all trash-bin items of a space + So that accidentally deleted files can be recovered individually or in bulk + + Background: + Given user "Alice" has been created with default attributes + + + Scenario: restore a single trash-bin item of a personal space + Given user "Alice" has uploaded file with content "some data" to "textfile.txt" + And we save it into "FILEID" + And user "Alice" has deleted file "textfile.txt" + When the administrator restores the trash-bin item with file-id "<>" of the personal space of user "Alice" using the CLI + Then the command should be successful + And the command output should contain "textfile.txt" + When the administrator lists the trash-bin items of the personal space of user "Alice" using the CLI + Then the command should be successful + And the command output should contain "total count: 0" + + + Scenario: restore a single trash-bin item of a project space + Given using spaces DAV path + And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API + And user "Alice" has created a space "new-space" with the default quota using the Graph API + And user "Alice" has uploaded a file inside space "new-space" with content "some data" to "textfile.txt" + And we save it into "FILEID" + And user "Alice" deletes file "textfile.txt" from space "new-space" using file-id "<>" + When the administrator restores the trash-bin item with file-id "<>" of space "new-space" using the CLI + Then the command should be successful + And the command output should contain "textfile.txt" + When the administrator lists the trash-bin items of space "new-space" using the CLI + Then the command should be successful + And the command output should contain "total count: 0" + + + Scenario: restore all trash-bin items of a personal space + Given using spaces DAV path + And user "Alice" has uploaded file with content "some data" to "textfile.txt" + And user "Alice" has uploaded file with content "some more data" to "anotherfile.txt" + And user "Alice" has deleted file "textfile.txt" + And user "Alice" has deleted file "anotherfile.txt" + When the administrator restores all the trash-bin items of the personal space of user "Alice" using the CLI + Then the command should be successful + And the command output should contain "textfile.txt" + And the command output should contain "anotherfile.txt" + And for user "Alice" the space "Personal" should contain these entries: + | textfile.txt | + | anotherfile.txt | + + + Scenario: restore all trash-bin items of a project space + Given using spaces DAV path + And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API + And user "Alice" has created a space "new-space" with the default quota using the Graph API + And user "Alice" has uploaded a file inside space "new-space" with content "some data" to "textfile.txt" + And we save it into "FILEID" + And user "Alice" has uploaded a file inside space "new-space" with content "some more data" to "anotherfile.txt" + And we save it into "ANOTHER_FILEID" + And user "Alice" deletes file "textfile.txt" from space "new-space" using file-id "<>" + And user "Alice" deletes file "anotherfile.txt" from space "new-space" using file-id "<>" + When the administrator restores all the trash-bin items of space "new-space" using the CLI + Then the command should be successful + And for user "Alice" the space "new-space" should contain these entries: + | textfile.txt | + | anotherfile.txt | diff --git a/tests/acceptance/features/cliCommands/sharesCleanup.feature b/tests/acceptance/features/cliCommands/sharesCleanup.feature new file mode 100644 index 0000000000..10569a94e5 --- /dev/null +++ b/tests/acceptance/features/cliCommands/sharesCleanup.feature @@ -0,0 +1,26 @@ +@env-config +Feature: clean up orphaned shares via CLI command + As an admin + I want to clean up orphaned shares + So that stale share entries do not accumulate in the share manager after a shared resource has been permanently deleted + + Background: + Given user "Alice" has been created with default attributes + And user "Brian" has been created with default attributes + + + Scenario: clean up orphaned shares after a shared folder has been permanently deleted + Given user "Alice" has created folder "/uploadFolder" + And user "Alice" has sent the following resource share invitation: + | resource | uploadFolder | + | space | Personal | + | sharee | Brian | + | shareType | user | + | permissionsRole | Editor | + And user "Alice" has deleted folder "/uploadFolder" + And user "Alice" has deleted the folder with original path "/uploadFolder" from the trashbin + When the administrator cleans up orphaned shares using the CLI + Then the command should be successful + And the command output should contain "shared resource does not exist anymore. cleaning up shares" + When user "Brian" lists the shares shared with her using the Graph API + Then the HTTP status code should be "200" diff --git a/tests/ocwrapper/opencloud/opencloud.go b/tests/ocwrapper/opencloud/opencloud.go index 3191c82dd4..983acb8399 100644 --- a/tests/ocwrapper/opencloud/opencloud.go +++ b/tests/ocwrapper/opencloud/opencloud.go @@ -234,9 +234,14 @@ func waitUntilCompleteShutdown() (bool, string) { return true, "OpenCloud server stopped successfully" } -func RunCommand(command string, inputs []string) (int, string) { +const defaultCommandTimeout = 5 * time.Second + +func RunCommand(command string, inputs []string, timeout time.Duration) (int, string) { logs := new(strings.Builder) - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + if timeout <= 0 { + timeout = defaultCommandTimeout + } + ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() // build the command @@ -254,6 +259,11 @@ func RunCommand(command string, inputs []string) (int, string) { for _, input := range inputs { fmt.Fprintf(ptyF, "%s\n", input) } + copyDone := make(chan struct{}) + go func() { + io.Copy(logs, ptyF) + close(copyDone) + }() var cmdOutput string if err := c.Wait(); err != nil { @@ -261,9 +271,7 @@ func RunCommand(command string, inputs []string) (int, string) { cmdOutput = "Command timed out:\n" } } - - // Copy the logs from the pty - io.Copy(logs, ptyF) + <-copyDone cmdOutput += logs.String() // TODO: find if there is a better way to remove stdins from the output @@ -272,9 +280,12 @@ func RunCommand(command string, inputs []string) (int, string) { return c.ProcessState.ExitCode(), cmdOutput } -func RunRawCommand(command string, inputs []string) (int, string) { +func RunRawCommand(command string, inputs []string, timeout time.Duration) (int, string) { logs := new(strings.Builder) - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + if timeout <= 0 { + timeout = defaultCommandTimeout + } + ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() fmt.Print("Running command: ", command) @@ -290,6 +301,11 @@ func RunRawCommand(command string, inputs []string) (int, string) { for _, input := range inputs { fmt.Fprintf(ptyF, "%s\n", input) } + copyDone := make(chan struct{}) + go func() { + io.Copy(logs, ptyF) + close(copyDone) + }() var cmdOutput string if err := c.Wait(); err != nil { @@ -297,8 +313,7 @@ func RunRawCommand(command string, inputs []string) (int, string) { cmdOutput = "Command timed out:\n" } } - - io.Copy(logs, ptyF) + <-copyDone cmdOutput += logs.String() cmdOutput = strings.TrimLeft(cmdOutput, strings.Join(inputs, "\r\n")) diff --git a/tests/ocwrapper/wrapper/handlers/handler.go b/tests/ocwrapper/wrapper/handlers/handler.go index b144ec82fd..c3d1981544 100644 --- a/tests/ocwrapper/wrapper/handlers/handler.go +++ b/tests/ocwrapper/wrapper/handlers/handler.go @@ -9,6 +9,7 @@ import ( "ocwrapper/common" "ocwrapper/opencloud" "os" + "time" ) type BasicResponse struct { @@ -203,14 +204,19 @@ func CommandHandler(res http.ResponseWriter, req *http.Request) { raw = r } + var timeout time.Duration + if t, ok := body["timeout"].(float64); ok { + timeout = time.Duration(t) * time.Second + } + var exitCode int var output string if raw { - exitCode, output = opencloud.RunRawCommand(command, stdIn) + exitCode, output = opencloud.RunRawCommand(command, stdIn, timeout) } else { - exitCode, output = opencloud.RunCommand(command, stdIn) + exitCode, output = opencloud.RunCommand(command, stdIn, timeout) } sendCmdResponse(res, exitCode, output) }