mirror of
https://github.com/owncloud/ocis.git
synced 2026-10-23 23:29:02 +03:00
1447 lines
41 KiB
PHP
1447 lines
41 KiB
PHP
<?php declare(strict_types=1);
|
|
/**
|
|
* ownCloud
|
|
*
|
|
* @author Sajan Gurung <sajan@jankaritech.com>
|
|
* @copyright Copyright (c) 2024 Sajan Gurung sajan@jankaritech.com
|
|
*
|
|
* This code 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 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/>
|
|
*
|
|
*/
|
|
|
|
use Behat\Behat\Hook\Scope\BeforeScenarioScope;
|
|
use Behat\Behat\Context\Context;
|
|
use Behat\Gherkin\Node\PyStringNode;
|
|
use Behat\Gherkin\Node\TableNode;
|
|
use GuzzleHttp\Exception\GuzzleException;
|
|
use PHPUnit\Framework\Assert;
|
|
use TestHelpers\CliHelper;
|
|
use TestHelpers\OcisConfigHelper;
|
|
use TestHelpers\BehatHelper;
|
|
use Psr\Http\Message\ResponseInterface;
|
|
|
|
/**
|
|
* CLI context
|
|
*/
|
|
class CliContext implements Context {
|
|
private FeatureContext $featureContext;
|
|
private SpacesContext $spacesContext;
|
|
|
|
/**
|
|
* @BeforeScenario
|
|
*
|
|
* @param BeforeScenarioScope $scope
|
|
*
|
|
* @return void
|
|
*/
|
|
public function before(BeforeScenarioScope $scope): void {
|
|
// Get the environment
|
|
$environment = $scope->getEnvironment();
|
|
// Get all the contexts you need in this context
|
|
$this->featureContext = BehatHelper::getContext($scope, $environment, 'FeatureContext');
|
|
$this->spacesContext = BehatHelper::getContext($scope, $environment, 'SpacesContext');
|
|
}
|
|
|
|
/**
|
|
* @Given the administrator has cleaned the search service data
|
|
*
|
|
* @return void
|
|
*/
|
|
public function theAdministratorHasCleanedTheSearchServiceData(): void {
|
|
$dataPath = $this->featureContext->getOcisDataPath() . "/search";
|
|
\exec("rm -rf $dataPath");
|
|
}
|
|
|
|
/**
|
|
* @Given the administrator has stopped the server
|
|
*
|
|
* @return void
|
|
*/
|
|
public function theAdministratorHasStoppedTheServer(): void {
|
|
$response = OcisConfigHelper::stopOcis();
|
|
$this->featureContext->theHTTPStatusCodeShouldBe(200, '', $response);
|
|
}
|
|
|
|
/**
|
|
* @Given /^the administrator (?:starts|has started) the server$/
|
|
*
|
|
* @return void
|
|
*/
|
|
public function theAdministratorHasStartedTheServer(): void {
|
|
$response = OcisConfigHelper::startOcis();
|
|
$this->featureContext->theHTTPStatusCodeShouldBe(200, '', $response);
|
|
}
|
|
|
|
/**
|
|
* @When /^the administrator resets the password of (non-existing|existing) user "([^"]*)" to "([^"]*)" using the CLI$/
|
|
*
|
|
* @param string $status
|
|
* @param string $user
|
|
* @param string $password
|
|
*
|
|
* @return void
|
|
*/
|
|
public function theAdministratorResetsThePasswordOfUserUsingTheCLI(
|
|
string $status,
|
|
string $user,
|
|
string $password,
|
|
): void {
|
|
$command = "idm resetpassword -u $user";
|
|
$body = [
|
|
"command" => $command,
|
|
"inputs" => [$password, $password],
|
|
];
|
|
|
|
$this->featureContext->setResponse(CliHelper::runCommand($body));
|
|
if ($status === "non-existing") {
|
|
return;
|
|
}
|
|
$this->featureContext->updateUserPassword($user, $password);
|
|
}
|
|
|
|
/**
|
|
* @When the administrator deletes the empty trashbin folders using the CLI
|
|
*
|
|
* @return void
|
|
*/
|
|
public function theAdministratorDeletesEmptyTrashbinFoldersUsingTheCli(): void {
|
|
$path = $this->featureContext->getStorageUsersRoot();
|
|
$command = "trash purge-empty-dirs -p $path --dry-run=false";
|
|
$body = [
|
|
"command" => $command,
|
|
];
|
|
$this->featureContext->setResponse(CliHelper::runCommand($body));
|
|
}
|
|
|
|
/**
|
|
* @When the administrator checks the backup consistency using the CLI
|
|
*
|
|
* @return void
|
|
*/
|
|
public function theAdministratorChecksTheBackupConsistencyUsingTheCli(): void {
|
|
$path = $this->featureContext->getStorageUsersRoot();
|
|
$command = "backup consistency -p $path";
|
|
$body = [
|
|
"command" => $command,
|
|
];
|
|
$this->featureContext->setResponse(CliHelper::runCommand($body));
|
|
}
|
|
|
|
/**
|
|
* @When the administrator lists all the unified roles using the CLI
|
|
*
|
|
* @return void
|
|
*/
|
|
public function theAdministratorListsAllTheUnifiedRolesUsingTheCli(): void {
|
|
$command = "graph list-unified-roles";
|
|
$body = [
|
|
"command" => $command,
|
|
];
|
|
$this->featureContext->setResponse(CliHelper::runCommand($body));
|
|
}
|
|
|
|
/**
|
|
* @When the administrator creates auth-app token for user :user with expiration time :expirationTime using the auth-app CLI
|
|
* @When the administrator tries to create auth-app token for user :user with expiration time :expirationTime using the auth-app CLI
|
|
*
|
|
* @param string $user
|
|
* @param string $expirationTime
|
|
*
|
|
* @return void
|
|
*/
|
|
public function theAdministratorCreatesAppTokenForUserWithExpirationTimeUsingTheAuthAppCLI(
|
|
string $user,
|
|
string $expirationTime,
|
|
): void {
|
|
$user = $this->featureContext->getActualUserName($user);
|
|
$command = "auth-app create --user-name=$user --expiration=$expirationTime";
|
|
$body = [
|
|
"command" => $command,
|
|
];
|
|
$this->featureContext->setResponse(CliHelper::runCommand($body));
|
|
}
|
|
|
|
/**
|
|
* @Given the administrator has created app token for user :user with expiration time :expirationTime using the auth-app CLI
|
|
*
|
|
* @param string $user
|
|
* @param string $expirationTime
|
|
*
|
|
* @return void
|
|
*/
|
|
public function theAdministratorHasCreatedAppTokenForUserWithExpirationTimeUsingTheAuthAppCli(
|
|
$user,
|
|
$expirationTime,
|
|
): void {
|
|
$user = $this->featureContext->getActualUserName($user);
|
|
$command = "auth-app create --user-name=$user --expiration=$expirationTime";
|
|
$body = [
|
|
"command" => $command,
|
|
];
|
|
$response = CliHelper::runCommand($body);
|
|
$this->featureContext->theHTTPStatusCodeShouldBe(200, '', $response);
|
|
$jsonResponse = $this->featureContext->getJsonDecodedResponse($response);
|
|
Assert::assertSame("OK", $jsonResponse["status"]);
|
|
Assert::assertSame(
|
|
0,
|
|
$jsonResponse["exitCode"],
|
|
"Expected exit code to be 0, but got " . $jsonResponse["exitCode"],
|
|
);
|
|
$output = $this->featureContext->substituteInLineCodes("App token created for $user");
|
|
Assert::assertStringContainsString($output, $jsonResponse["message"]);
|
|
}
|
|
|
|
/**
|
|
* @Given user :user has created app token with expiration time :expirationTime using the auth-app CLI
|
|
*
|
|
* @param string $user
|
|
* @param string $expirationTime
|
|
*
|
|
* @return void
|
|
*/
|
|
public function userHasCreatedAppTokenWithExpirationTimeUsingTheAuthAppCLI(
|
|
string $user,
|
|
string $expirationTime,
|
|
): void {
|
|
$user = $this->featureContext->getActualUserName($user);
|
|
$command = "auth-app create --user-name=$user --expiration=$expirationTime";
|
|
$body = [
|
|
"command" => $command,
|
|
];
|
|
|
|
$response = CliHelper::runCommand($body);
|
|
$this->featureContext->theHTTPStatusCodeShouldBe(200, '', $response);
|
|
$jsonResponse = $this->featureContext->getJsonDecodedResponse($response);
|
|
Assert::assertSame("OK", $jsonResponse["status"]);
|
|
Assert::assertSame(
|
|
0,
|
|
$jsonResponse["exitCode"],
|
|
"Expected exit code to be 0, but got " . $jsonResponse["exitCode"],
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @When the administrator removes all the file versions using the CLI
|
|
*
|
|
* @return void
|
|
*/
|
|
public function theAdministratorRemovesAllVersionsOfResources(): void {
|
|
$path = $this->featureContext->getStorageUsersRoot();
|
|
$command = "revisions purge -p $path --dry-run=false";
|
|
$body = [
|
|
"command" => $command,
|
|
];
|
|
$this->featureContext->setResponse(CliHelper::runCommand($body));
|
|
}
|
|
|
|
/**
|
|
* @When the administrator removes the versions of file :file of user :user from space :space using the CLI
|
|
*
|
|
* @param string $file
|
|
* @param string $user
|
|
* @param string $space
|
|
*
|
|
* @return void
|
|
*/
|
|
public function theAdministratorRemovesTheVersionsOfFileUsingFileId($file, $user, $space): void {
|
|
$path = $this->featureContext->getStorageUsersRoot();
|
|
$fileId = $this->spacesContext->getFileId($user, $space, $file);
|
|
$command = "revisions purge -p $path -r $fileId --dry-run=false";
|
|
$body = [
|
|
"command" => $command,
|
|
];
|
|
$this->featureContext->setResponse(CliHelper::runCommand($body));
|
|
}
|
|
|
|
/**
|
|
* @When /^the administrator reindexes all spaces using the CLI$/
|
|
*
|
|
* @return void
|
|
*/
|
|
public function theAdministratorReindexesAllSpacesUsingTheCli(): void {
|
|
$command = "search index --all-spaces";
|
|
$body = [
|
|
"command" => $command,
|
|
];
|
|
$this->featureContext->setResponse(CliHelper::runCommand($body));
|
|
}
|
|
|
|
/**
|
|
* @When /^the administrator reindexes a space "([^"]*)" using the CLI$/
|
|
*
|
|
* @param string $spaceName
|
|
*
|
|
* @return void
|
|
*/
|
|
public function theAdministratorReindexesASpaceUsingTheCli(string $spaceName): void {
|
|
$spaceId = $this->spacesContext->getSpaceIdByName($this->featureContext->getAdminUsername(), $spaceName);
|
|
$command = "search index --space $spaceId";
|
|
$body = [
|
|
"command" => $command,
|
|
];
|
|
$this->featureContext->setResponse(CliHelper::runCommand($body));
|
|
}
|
|
|
|
/**
|
|
* @When the administrator optimizes the search index using the CLI
|
|
*
|
|
* @return void
|
|
*/
|
|
public function theAdministratorOptimizesTheSearchIndexUsingTheCli(): void {
|
|
$command = "search optimize";
|
|
$body = [
|
|
"command" => $command,
|
|
];
|
|
$this->featureContext->setResponse(CliHelper::runCommand($body));
|
|
}
|
|
|
|
/**
|
|
* @When the administrator removes the file versions of space :space using the CLI
|
|
*
|
|
* @param string $space
|
|
*
|
|
* @return void
|
|
*/
|
|
public function theAdministratorRemovesTheVersionsOfFilesInSpaceUsingSpaceId(string $space): void {
|
|
$path = $this->featureContext->getStorageUsersRoot();
|
|
$adminUsername = $this->featureContext->getAdminUsername();
|
|
$spaceId = $this->spacesContext->getSpaceIdByName($adminUsername, $space);
|
|
$command = "revisions purge -p $path -r $spaceId --dry-run=false";
|
|
$body = [
|
|
"command" => $command,
|
|
];
|
|
$this->featureContext->setResponse(CliHelper::runCommand($body));
|
|
}
|
|
|
|
/**
|
|
* @Then /^the command should be (successful|unsuccessful)$/
|
|
*
|
|
* @param string $successfulOrNot
|
|
*
|
|
* @return void
|
|
*/
|
|
public function theCommandShouldBeSuccessful(string $successfulOrNot): void {
|
|
$this->featureContext->theHTTPStatusCodeShouldBe(200);
|
|
$jsonResponse = $this->featureContext->getJsonDecodedResponse();
|
|
|
|
$expectedStatus = 'OK';
|
|
$expectedExitCode = 0;
|
|
if ($successfulOrNot === "unsuccessful") {
|
|
$expectedStatus = "ERROR";
|
|
$expectedExitCode = 1;
|
|
}
|
|
|
|
Assert::assertSame($expectedStatus, $jsonResponse["status"]);
|
|
Assert::assertSame(
|
|
$expectedExitCode,
|
|
$jsonResponse["exitCode"],
|
|
"Expected exit code to be 0, but got " . $jsonResponse["exitCode"],
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @Then /^the command output (should|should not) contain "([^"]*)"$/
|
|
*
|
|
* @param string $shouldOrNot
|
|
* @param string $output
|
|
*
|
|
* @return void
|
|
*/
|
|
public function theCommandOutputShouldContain(string $shouldOrNot, string $output): void {
|
|
$response = $this->featureContext->getResponse();
|
|
$jsonResponse = $this->featureContext->getJsonDecodedResponse($response);
|
|
$output = $this->featureContext->substituteInLineCodes($output);
|
|
|
|
if ($shouldOrNot === "should") {
|
|
Assert::assertStringContainsString($output, $jsonResponse["message"]);
|
|
} else {
|
|
Assert::assertStringNotContainsString($output, $jsonResponse["message"]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @Then the command output should include the following roles:
|
|
*
|
|
* @param TableNode $table
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function theCommandOutputShouldIncludeTheFollowingRoles(TableNode $table): void {
|
|
$this->featureContext->verifyTableNodeColumns(
|
|
$table,
|
|
['LABEL', 'ENABLED', 'DESCRIPTION'],
|
|
);
|
|
|
|
$expectedRoles = $table->getColumnsHash();
|
|
$response = $this->featureContext->getResponse();
|
|
$decodedResponse = $this->featureContext->getJsonDecodedResponse($response);
|
|
|
|
// Regex pattern to extract LABEL, ENABLED, and DESCRIPTION
|
|
$pattern = '/│\s*\d+\s*│\s*([^\|]+?)\s*│\s*([a-f0-9\-]{36})\s*│\s*(enabled|disabled)\s*│\s*(.*?)\s*│/i';
|
|
preg_match_all($pattern, $decodedResponse['message'], $matches, PREG_SET_ORDER);
|
|
|
|
$actualRoles = [];
|
|
foreach ($matches as $match) {
|
|
$actualRoles[] = [
|
|
'LABEL' => trim($match[1]),
|
|
'ENABLED' => trim($match[3]),
|
|
'DESCRIPTION' => trim($match[4]),
|
|
];
|
|
}
|
|
|
|
// Compare expected roles with actual roles by LABEL and assert equality
|
|
foreach ($expectedRoles as $expected) {
|
|
$label = $expected['LABEL'];
|
|
$actual = null;
|
|
foreach ($actualRoles as $role) {
|
|
if ($role['LABEL'] === $label) {
|
|
$actual = $role;
|
|
break;
|
|
}
|
|
}
|
|
|
|
Assert::assertNotNull(
|
|
$actual,
|
|
"Role with LABEL '$label' not found in command output.",
|
|
);
|
|
|
|
Assert::assertEquals(
|
|
$expected,
|
|
$actual,
|
|
"Mismatch for LABEL '$label':\nExpected: " . json_encode($expected) .
|
|
"\nActual: " . json_encode($actual),
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @When the administrator lists all the upload sessions
|
|
* @When the administrator lists all the upload sessions with flag :flag
|
|
*
|
|
* @param string|null $flag
|
|
*
|
|
* @return void
|
|
*/
|
|
public function theAdministratorListsAllTheUploadSessions(?string $flag = null): void {
|
|
if ($flag) {
|
|
$flag = "--$flag";
|
|
}
|
|
$command = "storage-users uploads sessions --json $flag";
|
|
$body = [
|
|
"command" => $command,
|
|
];
|
|
$this->featureContext->setResponse(CliHelper::runCommand($body));
|
|
}
|
|
|
|
/**
|
|
* @When the administrator cleans upload sessions with the following flags:
|
|
*
|
|
* @param TableNode $table
|
|
*
|
|
* @return void
|
|
*/
|
|
public function theAdministratorCleansUploadSessionsWithTheFollowingFlags(TableNode $table): void {
|
|
$flag = "";
|
|
foreach ($table->getRows() as $row) {
|
|
$flag .= "--$row[0] ";
|
|
}
|
|
$flagString = trim($flag);
|
|
$command = "storage-users uploads sessions $flagString --clean --json";
|
|
$body = [
|
|
"command" => $command,
|
|
];
|
|
$this->featureContext->setResponse(CliHelper::runCommand($body));
|
|
}
|
|
|
|
/**
|
|
* @When the administrator restarts the upload sessions that are in postprocessing
|
|
*
|
|
* @return void
|
|
*/
|
|
public function theAdministratorRestartsTheUploadSessionsThatAreInPostprocessing(): void {
|
|
$command = "storage-users uploads sessions --processing --restart --json";
|
|
$body = [
|
|
"command" => $command,
|
|
];
|
|
$this->featureContext->setResponse(CliHelper::runCommand($body));
|
|
}
|
|
|
|
/**
|
|
* @When /^the administrator (restarts|resumes) the upload session of file "([^"]*)" using the CLI$/
|
|
*
|
|
* @param string $flag
|
|
* @param string $file
|
|
*
|
|
* @return void
|
|
* @throws JsonException
|
|
*/
|
|
public function theAdministratorResumesOrRestartsUploadSessionOfFileUsingTheCLI(string $flag, string $file): void {
|
|
$flag = rtrim($flag, "s");
|
|
$response = CliHelper::runCommand(["command" => "storage-users uploads sessions --json"]);
|
|
$this->featureContext->theHTTPStatusCodeShouldBe(200, '', $response);
|
|
$responseArray = $this->getJSONDecodedCliMessage($response);
|
|
|
|
foreach ($responseArray as $item) {
|
|
if ($item->filename === $file) {
|
|
$uploadId = $item->id;
|
|
break;
|
|
}
|
|
}
|
|
|
|
$command = "storage-users uploads sessions --id=$uploadId --$flag --json";
|
|
$body = [
|
|
"command" => $command,
|
|
];
|
|
$this->featureContext->setResponse(CliHelper::runCommand($body));
|
|
}
|
|
|
|
/**
|
|
* @When /^the administrator (resumes|restarts) the upload session of file "([^"]*)" using postprocessing command$/
|
|
*
|
|
* @param string $flag
|
|
* @param string $file
|
|
*
|
|
* @return void
|
|
* @throws JsonException
|
|
*/
|
|
public function theAdministratorResumesOrRestartsUploadSessionOfFileUsingPostprocessingCommand(
|
|
string $flag,
|
|
string $file,
|
|
): void {
|
|
$uploadSessions = CliHelper::runCommand(["command" => "storage-users uploads sessions --json"]);
|
|
$this->featureContext->theHTTPStatusCodeShouldBe(200, '', $uploadSessions);
|
|
$responseArray = $this->getJSONDecodedCliMessage($uploadSessions);
|
|
|
|
foreach ($responseArray as $item) {
|
|
if ($item->filename === $file) {
|
|
$uploadId = $item->id;
|
|
break;
|
|
}
|
|
}
|
|
|
|
$command = "postprocessing resume" . ($flag === "restarts" ? " -r" : "") . " -u $uploadId";
|
|
$body = [
|
|
"command" => $command,
|
|
];
|
|
$this->featureContext->setResponse(CliHelper::runCommand($body));
|
|
}
|
|
|
|
/**
|
|
* @Then /^the CLI response (should|should not) contain these entries:$/
|
|
*
|
|
* @param string $shouldOrNot
|
|
* @param TableNode $table
|
|
*
|
|
* @return void
|
|
* @throws JsonException
|
|
*/
|
|
public function theCLIResponseShouldContainTheseEntries(string $shouldOrNot, TableNode $table): void {
|
|
$expectedFiles = $table->getColumn(0);
|
|
$responseArray = $this->getJSONDecodedCliMessage($this->featureContext->getResponse());
|
|
|
|
$resourceNames = [];
|
|
foreach ($responseArray as $item) {
|
|
if (isset($item->filename)) {
|
|
$resourceNames[] = $item->filename;
|
|
}
|
|
}
|
|
|
|
if ($shouldOrNot === "should not") {
|
|
foreach ($expectedFiles as $expectedFile) {
|
|
Assert::assertNotTrue(
|
|
\in_array($expectedFile, $resourceNames),
|
|
"The resource '$expectedFile' was found in the response.",
|
|
);
|
|
}
|
|
} else {
|
|
foreach ($expectedFiles as $expectedFile) {
|
|
Assert::assertTrue(
|
|
\in_array($expectedFile, $resourceNames),
|
|
"The resource '$expectedFile' was not found in the response.",
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @Given the administrator has waited until the file :filename has finished processing
|
|
*
|
|
* @param string $filename
|
|
*
|
|
* @return void
|
|
* @throws JsonException
|
|
*/
|
|
public function theAdministratorHasWaitedUntilFileHasFinishedProcessing(string $filename): void {
|
|
$timeout = 30; // seconds
|
|
$interval = 1; // seconds
|
|
$startTime = \time();
|
|
|
|
// Poll until file is no longer in --processing list (allows time for state transitions).
|
|
// Replaces fixed wait that assumed deterministic timing.
|
|
while (true) {
|
|
$this->theAdministratorListsAllTheUploadSessions("processing");
|
|
$this->featureContext->theHTTPStatusCodeShouldBe(200);
|
|
$responseArray = $this->getJSONDecodedCliMessage($this->featureContext->getResponse());
|
|
|
|
$found = false;
|
|
foreach ($responseArray as $item) {
|
|
if (isset($item->filename) && $item->filename === $filename) {
|
|
$found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!$found) {
|
|
// File is no longer in processing list - abort completed and state updated
|
|
return;
|
|
}
|
|
|
|
$elapsed = \time() - $startTime;
|
|
if ($elapsed >= $timeout) {
|
|
Assert::fail(
|
|
"Timeout after {$timeout}s: file '{$filename}' is still in processing upload sessions list. " .
|
|
"Virus scan + abort may not have completed within timeout period.",
|
|
);
|
|
}
|
|
|
|
\sleep($interval);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param ResponseInterface $response
|
|
*
|
|
* @return array
|
|
* @throws JsonException
|
|
*/
|
|
public function getJSONDecodedCliMessage(ResponseInterface $response): array {
|
|
$responseBody = $this->featureContext->getJsonDecodedResponse($response);
|
|
|
|
// $responseBody["message"] contains a message info with the array of output json of the upload sessions command
|
|
// Example Output: "INFO memory is not limited, skipping package=github.com/KimMachineGun/automemlimit/memlimit [{<output-json>}]"
|
|
// So, only extracting the array of output json from the message
|
|
\preg_match('/(\[.*\])/', $responseBody["message"], $matches);
|
|
return isset($matches[1]) ? \json_decode($matches[1], null, 512, JSON_THROW_ON_ERROR) : [];
|
|
}
|
|
|
|
/**
|
|
* @AfterScenario @cli-uploads-sessions
|
|
*
|
|
* @return void
|
|
*/
|
|
public function cleanUploadsSessions(): void {
|
|
$command = "storage-users uploads sessions --clean";
|
|
$body = [
|
|
"command" => $command,
|
|
];
|
|
$response = CliHelper::runCommand($body);
|
|
Assert::assertEquals("200", $response->getStatusCode(), "Failed to clean upload sessions");
|
|
}
|
|
|
|
/**
|
|
* @AfterScenario @cli-stale-uploads
|
|
*
|
|
* @return void
|
|
*/
|
|
public function cleanUpStaleUploads(): void {
|
|
$response = $this->deleteStaleUploads();
|
|
$this->featureContext->theHTTPStatusCodeShouldBe(200, "Failed to cleanup stale upload", $response);
|
|
}
|
|
|
|
/**
|
|
* @When /^the administrator triggers "([^"]*)" email notifications using the CLI$/
|
|
*
|
|
* @param string $interval
|
|
*
|
|
* @return void
|
|
* @throws GuzzleException
|
|
*/
|
|
public function theAdministratorTriggersEmailNotificationsUsingTheCLI(string $interval): void {
|
|
$command = "notifications send-email --$interval";
|
|
$body = [
|
|
"command" => $command,
|
|
];
|
|
|
|
$this->featureContext->setResponse(CliHelper::runCommand($body));
|
|
}
|
|
|
|
/**
|
|
* @Given the administrator has created stale upload
|
|
*
|
|
* @return void
|
|
*/
|
|
public function theAdministratorHasCreatedStaleUpload(): void {
|
|
$folderPath = $this->featureContext->getStorageUsersRoot() . "/uploads";
|
|
$infoFiles = glob($folderPath . '/*.info');
|
|
foreach ($infoFiles as $file) {
|
|
if (!unlink($file)) {
|
|
Assert::fail("Fail to delete info file");
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param string|null $spaceId
|
|
*
|
|
* @return ResponseInterface
|
|
* @throws GuzzleException
|
|
*/
|
|
protected function listStaleUploads(?string $spaceId = null): ResponseInterface {
|
|
$command = "storage-users uploads delete-stale-nodes --dry-run=true";
|
|
|
|
if ($spaceId !== null) {
|
|
$command .= " --spaceid=$spaceId";
|
|
}
|
|
|
|
$body = [
|
|
"command" => $command,
|
|
];
|
|
return CliHelper::runCommand($body);
|
|
}
|
|
|
|
/**
|
|
* @param string|null $spaceId
|
|
*
|
|
* @return ResponseInterface
|
|
* @throws GuzzleException
|
|
*/
|
|
protected function deleteStaleUploads(?string $spaceId = null): ResponseInterface {
|
|
$command = "storage-users uploads delete-stale-nodes --dry-run=false";
|
|
if ($spaceId !== null) {
|
|
$command .= " --spaceid=$spaceId";
|
|
}
|
|
|
|
$body = [
|
|
"command" => $command,
|
|
];
|
|
return CliHelper::runCommand($body);
|
|
}
|
|
|
|
/**
|
|
* @When the administrator lists all the stale uploads
|
|
*
|
|
* @return void
|
|
*/
|
|
public function theAdministratorListsAllTheStaleUploads(): void {
|
|
$this->featureContext->setResponse($this->listStaleUploads());
|
|
}
|
|
|
|
/**
|
|
* @When the administrator lists all the stale uploads of space :space owned by user :user
|
|
*
|
|
* @param string $spaceName
|
|
* @param string $user
|
|
*
|
|
* @return void
|
|
*/
|
|
public function theAdministratorListsTheStaleUploadsOfSpace(
|
|
string $spaceName,
|
|
string $user,
|
|
): void {
|
|
$spaceOwnerId = $this->spacesContext->getSpaceOwnerUserIdByName(
|
|
$user,
|
|
$spaceName,
|
|
);
|
|
$this->featureContext->setResponse($this->listStaleUploads($spaceOwnerId));
|
|
}
|
|
|
|
/**
|
|
* @Then the CLI response should contain the following message:
|
|
*
|
|
* @param PyStringNode $content
|
|
*
|
|
* @return void
|
|
*/
|
|
public function theCLIResponseShouldContainTheseMessage(PyStringNode $content): void {
|
|
$response = $this->featureContext->getJsonDecodedResponseBodyContent();
|
|
$expectedMessage = str_replace("\r\n", "\n", trim($content->getRaw()));
|
|
$actualMessage = str_replace("\r\n", "\n", trim($response->message ?? ''));
|
|
|
|
Assert::assertStringContainsString(
|
|
$expectedMessage,
|
|
$actualMessage,
|
|
"Expected cli output to contain '$expectedMessage', but got: '$actualMessage'",
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @When the administrator deletes all the stale uploads
|
|
*
|
|
* @return void
|
|
*/
|
|
public function theAdministratorDeletesAllTheStaleUploads(): void {
|
|
$this->featureContext->setResponse($this->deleteStaleUploads());
|
|
}
|
|
|
|
/**
|
|
* @When the administrator deletes all the stale uploads of space :spaceName owned by user :user
|
|
*
|
|
* @param string $spaceName
|
|
* @param string $user
|
|
*
|
|
* @return void
|
|
*/
|
|
public function theAdministratorDeletesTheStaleUploadsOfSpaceOwnedByUser(
|
|
string $spaceName,
|
|
string $user,
|
|
): void {
|
|
$spaceOwnerId = $this->spacesContext->getSpaceOwnerUserIdByName(
|
|
$user,
|
|
$spaceName,
|
|
);
|
|
$this->featureContext->setResponse($this->deleteStaleUploads($spaceOwnerId));
|
|
}
|
|
|
|
/**
|
|
* @Then there should be :number stale uploads
|
|
* @Then there should be :number stale uploads of space :spaceName owned by user :user
|
|
*
|
|
* @param int $number
|
|
* @param string $spaceName
|
|
* @param string $user
|
|
*
|
|
* @return void
|
|
* @throws GuzzleException
|
|
*/
|
|
public function thereShouldBeStaleUploadsOfSpaceOwnedByUser(
|
|
int $number,
|
|
string $spaceName = '',
|
|
string $user = '',
|
|
): void {
|
|
$spaceOwnerId = null;
|
|
if ($spaceName !== '' && $user !== '') {
|
|
$spaceOwnerId = $this->spacesContext->getSpaceOwnerUserIdByName(
|
|
$user,
|
|
$spaceName,
|
|
);
|
|
}
|
|
|
|
$response = $this->listStaleUploads($spaceOwnerId);
|
|
$jsonDecodedResponse = $this->featureContext->getJsonDecodedResponseBodyContent($response);
|
|
$this->featureContext->theHTTPStatusCodeShouldBe(200, "", $response);
|
|
|
|
$expectedMessage = "Total stale nodes: $number";
|
|
|
|
Assert::assertStringContainsString(
|
|
$expectedMessage,
|
|
$jsonDecodedResponse->message ?? '',
|
|
"Expected message to contain '$expectedMessage', but got: " . ($jsonDecodedResponse->message ?? 'null'),
|
|
);
|
|
|
|
}
|
|
|
|
/**
|
|
* @When the administrator lists all the trashed resources of space :space owned by user :user
|
|
*
|
|
* @param string $spaceName
|
|
* @param string $user
|
|
*
|
|
* @return void
|
|
*/
|
|
public function theAdministratorListsAllTrashedResourceOfSpaceOwnedByUser(
|
|
string $spaceName,
|
|
string $user,
|
|
): void {
|
|
$spaceOwnerId = $this->spacesContext->getSpaceOwnerUserIdByName(
|
|
$user,
|
|
$spaceName,
|
|
);
|
|
$this->featureContext->setResponse($this->listTrashedResource($spaceOwnerId));
|
|
}
|
|
|
|
/**
|
|
* @param string $spaceId
|
|
*
|
|
* @return ResponseInterface
|
|
* @throws GuzzleException
|
|
*/
|
|
protected function listTrashedResource(string $spaceId): ResponseInterface {
|
|
$body = [
|
|
"command" => "storage-users trash-bin list $spaceId",
|
|
];
|
|
return CliHelper::runCommand($body);
|
|
}
|
|
|
|
/**
|
|
* @param ResponseInterface|null $response
|
|
*
|
|
* @return array
|
|
*/
|
|
protected function getTrashedResourceFromCliCommandResponse(
|
|
?ResponseInterface $response = null,
|
|
): array {
|
|
$responseArray = $this->featureContext->getJsonDecodedResponseBodyContent($response);
|
|
$lines = explode("\n", $responseArray->message);
|
|
$items = [];
|
|
$totalCount = 0;
|
|
foreach ($lines as $line) {
|
|
if (preg_match(
|
|
'/^\s*\│\s*([a-f0-9\-]{36})\s*\│\s*(.*?)\s*\│\s*(file|folder)\s*\│\s*([\d\-T:Z]+)\s*\│/',
|
|
$line,
|
|
$matches,
|
|
)
|
|
) {
|
|
$items[] = [
|
|
'itemID' => $matches[1],
|
|
'path' => $matches[2],
|
|
'type' => $matches[3],
|
|
'delete at' => $matches[4],
|
|
];
|
|
}
|
|
|
|
if (preg_match('/total count:\s*(\d+)/', $line, $countMatch)) {
|
|
$totalCount = (int)$countMatch[1];
|
|
}
|
|
}
|
|
return [$items, $totalCount];
|
|
}
|
|
|
|
/**
|
|
* @Then /^the command output should contain "([^"]*)" trashed resources with the following information:$/
|
|
*
|
|
* @param int $count
|
|
* @param TableNode $table
|
|
*
|
|
* @return void
|
|
*/
|
|
public function theCommandOutputShouldContainTheFollowingTrashResource(
|
|
int $count,
|
|
TableNode $table,
|
|
): void {
|
|
[$items, $totalCount] = $this->getTrashedResourceFromCliCommandResponse();
|
|
|
|
Assert::assertSame($totalCount, $count, "Expected total trashed resource");
|
|
|
|
foreach ($table->getHash() as $expectedRow) {
|
|
$matchFound = false;
|
|
|
|
foreach ($items as $item) {
|
|
if ($item['path'] === $expectedRow['resource']
|
|
&& $item['type'] === $expectedRow['type']
|
|
) {
|
|
$matchFound = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
Assert::assertTrue(
|
|
$matchFound,
|
|
"Could not find expected resource '{$expectedRow['resource']}' of type '{$expectedRow['type']}'",
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @When the administrator restores all the trashed resources of space :space owned by user :user
|
|
*
|
|
* @param string $spaceName
|
|
* @param string $user
|
|
*
|
|
* @return void
|
|
*/
|
|
public function theAdministratorRestoresAllTheTrashedResourcesOfSpaceOwnedByUser(
|
|
string $spaceName,
|
|
string $user,
|
|
): void {
|
|
$spaceOwnerId = $this->spacesContext->getSpaceOwnerUserIdByName(
|
|
$user,
|
|
$spaceName,
|
|
);
|
|
$body = [
|
|
"command" => "storage-users trash-bin restore-all -y $spaceOwnerId",
|
|
];
|
|
$this->featureContext->setResponse(CliHelper::runCommand($body));
|
|
}
|
|
|
|
/**
|
|
* @Then there should be no trashed resources of space :spaceName owned by user :user
|
|
*
|
|
* @param string $spaceName
|
|
* @param string $user
|
|
*
|
|
* @return void
|
|
* @throws GuzzleException
|
|
*/
|
|
public function thereShouldBeNoTrashedResourcesOfSpaceOwnedByUser(
|
|
string $spaceName,
|
|
string $user,
|
|
): void {
|
|
$spaceOwnerId = $this->spacesContext->getSpaceOwnerUserIdByName(
|
|
$user,
|
|
$spaceName,
|
|
);
|
|
$response = $this->listTrashedResource($spaceOwnerId);
|
|
[$items, $totalCount] = $this->getTrashedResourceFromCliCommandResponse($response);
|
|
|
|
Assert::assertSame($totalCount, 0, "Expected total trashed resource");
|
|
}
|
|
|
|
/**
|
|
* @Then there should be :number trashed resources of space :spaceName owned by user :user:
|
|
*
|
|
* @param int $number
|
|
* @param string $spaceName
|
|
* @param string $user
|
|
* @param TableNode $table
|
|
*
|
|
* @return void
|
|
* @throws GuzzleException
|
|
*/
|
|
public function thereShouldBeTrashedResourcesOfSpaceOwnedByUser(
|
|
int $number,
|
|
string $spaceName,
|
|
string $user,
|
|
TableNode $table,
|
|
): void {
|
|
$spaceOwnerId = $this->spacesContext->getSpaceOwnerUserIdByName(
|
|
$user,
|
|
$spaceName,
|
|
);
|
|
$response = $this->listTrashedResource($spaceOwnerId);
|
|
[$items, $totalCount] = $this->getTrashedResourceFromCliCommandResponse($response);
|
|
|
|
Assert::assertSame($totalCount, $number, "Expected total trashed resource");
|
|
|
|
foreach ($table->getHash() as $expectedRow) {
|
|
$matchFound = false;
|
|
|
|
foreach ($items as $item) {
|
|
if ($item['path'] === $expectedRow['resource']
|
|
&& $item['type'] === $expectedRow['type']
|
|
) {
|
|
$matchFound = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
Assert::assertTrue(
|
|
$matchFound,
|
|
"Could not find expected resource '{$expectedRow['resource']}' of type '{$expectedRow['type']}'",
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @When the administrator restores the trashed resources :resource of space :space owned by user :user
|
|
*
|
|
* @param string $resource
|
|
* @param string $spaceName
|
|
* @param string $user
|
|
*
|
|
* @return void
|
|
*/
|
|
public function theAdministratorRestoresTheTrashedResourcesOfSpaceOwnedByUser(
|
|
string $resource,
|
|
string $spaceName,
|
|
string $user,
|
|
): void {
|
|
$spaceOwnerId = $this->spacesContext->getSpaceOwnerUserIdByName(
|
|
$user,
|
|
$spaceName,
|
|
);
|
|
$response = $this->listTrashedResource($spaceOwnerId);
|
|
[$items, $totalCount] = $this->getTrashedResourceFromCliCommandResponse($response);
|
|
$trashItemId = null;
|
|
foreach ($items as $item) {
|
|
if ($item['path'] === $resource) {
|
|
$trashItemId = $item['itemID'];
|
|
break;
|
|
}
|
|
}
|
|
$body = [
|
|
"command" => "storage-users trash-bin restore $spaceOwnerId $trashItemId",
|
|
];
|
|
$this->featureContext->setResponse(CliHelper::runCommand($body));
|
|
}
|
|
|
|
/**
|
|
* @When the administrator purges the expired trash resources
|
|
*
|
|
* @return void
|
|
* @throws GuzzleException
|
|
*/
|
|
public function theAdministratorPurgesTheExpiredTrashResources(): void {
|
|
$body = [
|
|
"command" => "storage-users trash-bin purge-expired",
|
|
];
|
|
$this->featureContext->setResponse(CliHelper::runCommand($body));
|
|
}
|
|
|
|
/**
|
|
* @When the administrator resumes all the upload sessions using the CLI
|
|
*
|
|
* @return void
|
|
* @throws GuzzleException
|
|
*/
|
|
public function theAdministratorResumesAllTheUploadSessionsUsingTheCLI(): void {
|
|
$command = "storage-users uploads sessions --resume --json";
|
|
$body = [
|
|
"command" => $command,
|
|
];
|
|
$this->featureContext->setResponse(CliHelper::runCommand($body));
|
|
}
|
|
|
|
/**
|
|
* @When the administrator resumes all the upload session using the post processing command
|
|
*
|
|
* @return void
|
|
* @throws GuzzleException
|
|
*/
|
|
public function theAdministratorResumesAllUploadSessionUsingPostprocessingCommand(): void {
|
|
$command = "postprocessing resume";
|
|
$body = [
|
|
"command" => $command,
|
|
];
|
|
$this->featureContext->setResponse(CliHelper::runCommand($body));
|
|
}
|
|
|
|
/**
|
|
* @When the administrator restarts the expired upload sessions using the CLI
|
|
*
|
|
* @return void
|
|
* @throws GuzzleException
|
|
*/
|
|
public function theAdministratorRestartsTheExpiredUploadSessionsUsingTheCLI(): void {
|
|
$command = "storage-users uploads sessions --expired --restart --json";
|
|
$body = [
|
|
"command" => $command,
|
|
];
|
|
$this->featureContext->setResponse(CliHelper::runCommand($body));
|
|
}
|
|
|
|
/**
|
|
* @When administrator deletes :space space using the CLI
|
|
*
|
|
* @param string $space
|
|
*
|
|
* @return void
|
|
* @throws GuzzleException
|
|
*/
|
|
public function theAdministratorDeletesSpaceUsingTheCLI(string $space): void {
|
|
$command = "storage-users spaces purge -t=" . strtolower($space) . " -r=0s -v -d=false";
|
|
$body = [
|
|
"command" => $command,
|
|
];
|
|
$this->featureContext->setResponse(CliHelper::runCommand($body));
|
|
}
|
|
|
|
/**
|
|
* @When administrator deletes :space space of user :user with space-id using the CLI
|
|
*
|
|
* @param string $spaceName
|
|
* @param string $user
|
|
*
|
|
* @return void
|
|
* @throws GuzzleException
|
|
*/
|
|
public function administratorDeletesSpaceWithSpaceIdUsingTheCli(string $spaceName, string $user): void {
|
|
$space = $this->featureContext->spacesContext->getSpaceByName($user, $spaceName);
|
|
|
|
if ($spaceName === "Personal") {
|
|
$spaceType = "personal";
|
|
} else {
|
|
$spaceType = 'project';
|
|
}
|
|
|
|
$command = "storage-users spaces purge -t=$spaceType -s=" . $space['id'] . " -r=0s -v -d=false";
|
|
$body = [
|
|
"command" => $command,
|
|
];
|
|
$this->featureContext->setResponse(CliHelper::runCommand($body));
|
|
}
|
|
|
|
/**
|
|
* @When administrator deletes :space space with :retentionPeriod retention period using the CLI
|
|
*
|
|
* @param string $space
|
|
* @param string $retentionPeriod
|
|
*
|
|
* @return void
|
|
* @throws GuzzleException
|
|
*/
|
|
public function administratorDeletesSpaceWithRetentionPeriodOfUsingTheCli(
|
|
string $space,
|
|
string $retentionPeriod,
|
|
): void {
|
|
$command = "storage-users spaces purge -t=" . strtolower($space) . " -r=$retentionPeriod -v -d=false";
|
|
$body = [
|
|
"command" => $command,
|
|
];
|
|
$this->featureContext->setResponse(CliHelper::runCommand($body));
|
|
}
|
|
|
|
/**
|
|
* @Given the administrator has configured service account credentials
|
|
*
|
|
* @return void
|
|
* @throws GuzzleException
|
|
*/
|
|
public function theAdministratorHasConfiguredServiceAccountCredentials(): void {
|
|
$envs = [
|
|
"OCIS_SERVICE_ACCOUNT_ID" => "service-account-id",
|
|
"OCIS_SERVICE_ACCOUNT_SECRET" => "service-account-secret",
|
|
];
|
|
$response = OcisConfigHelper::reConfigureOcis($envs);
|
|
$this->featureContext->theHTTPStatusCodeShouldBe(
|
|
200,
|
|
"Failed to configure service account credentials",
|
|
$response,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @When the administrator runs clean-orphaned-grants in non-dry-run mode
|
|
*
|
|
* @return void
|
|
*/
|
|
public function theAdministratorRunsCleanOrphanedGrantsInNonDryRunMode(): void {
|
|
$command = "shares clean-orphaned-grants"
|
|
. " --service-account-id=service-account-id"
|
|
. " --service-account-secret=service-account-secret"
|
|
. " --dry-run=false";
|
|
$body = ["command" => $command];
|
|
$this->featureContext->setResponse(CliHelper::runCommand($body));
|
|
}
|
|
|
|
/**
|
|
* @When the administrator runs clean-orphaned-grants for space :spaceName owned by :user
|
|
*
|
|
* @param string $spaceName
|
|
* @param string $user
|
|
*
|
|
* @return void
|
|
*/
|
|
public function theAdministratorRunsCleanOrphanedGrantsForSpaceOwnedBy(
|
|
string $spaceName,
|
|
string $user,
|
|
): void {
|
|
$spaceId = $this->spacesContext->getSpaceIdByName($user, $spaceName);
|
|
$command = "shares clean-orphaned-grants"
|
|
. " --service-account-id=service-account-id"
|
|
. " --service-account-secret=service-account-secret"
|
|
. " --space-id=" . $spaceId
|
|
. " --dry-run=false";
|
|
$body = ["command" => $command];
|
|
$this->featureContext->setResponse(CliHelper::runCommand($body));
|
|
}
|
|
|
|
/**
|
|
* @When the administrator runs clean-orphaned-grants with force flag
|
|
*
|
|
* @return void
|
|
*/
|
|
public function theAdministratorRunsCleanOrphanedGrantsWithForceFlag(): void {
|
|
$command = "shares clean-orphaned-grants"
|
|
. " --service-account-id=service-account-id"
|
|
. " --service-account-secret=service-account-secret"
|
|
. " --force --dry-run=false";
|
|
$body = ["command" => $command];
|
|
$this->featureContext->setResponse(CliHelper::runCommand($body));
|
|
}
|
|
|
|
/**
|
|
* @Given the share grants for space :spaceName owned by :user have been orphaned
|
|
*
|
|
* @param string $spaceName
|
|
* @param string $user
|
|
*
|
|
* @return void
|
|
* @throws GuzzleException
|
|
*/
|
|
public function theShareGrantsForSpaceHaveBeenOrphaned(string $spaceName, string $user): void {
|
|
$parts = \explode('$', $this->spacesContext->getSpaceIdByName($user, $spaceName));
|
|
$spaceOpaqueId = $parts[1] ?? '';
|
|
$deadSpaceId = '00000000-0000-0000-0000-000000000000';
|
|
|
|
$blobsRoot = $this->featureContext->getOcisDataPath() . '/storage/metadata/spaces/js';
|
|
$found = false;
|
|
foreach (['jsoncs3-share-manager-metadata', 'oncs3-share-manager-metadata'] as $metaSpace) {
|
|
$blobDir = $blobsRoot . '/' . $metaSpace . '/blobs';
|
|
if (!\is_dir($blobDir)) {
|
|
continue;
|
|
}
|
|
// walk all blob files recursively (blobs/{hex}/{hex}/{hex}/{hex}/{filename})
|
|
$dirIterator = new \RecursiveDirectoryIterator($blobDir, \RecursiveDirectoryIterator::SKIP_DOTS);
|
|
$fileIterator = new \RecursiveIteratorIterator($dirIterator);
|
|
foreach ($fileIterator as $file) {
|
|
if (!$file->isFile()) {
|
|
continue;
|
|
}
|
|
$data = \json_decode(\file_get_contents($file->getPathname()), true);
|
|
if (!isset($data['Shares'])) {
|
|
continue;
|
|
}
|
|
$matched = false;
|
|
foreach ($data['Shares'] as $key => $share) {
|
|
if (($share['resource_id']['space_id'] ?? '') !== $spaceOpaqueId) {
|
|
continue;
|
|
}
|
|
$data['Shares'][$key]['resource_id']['space_id'] = $deadSpaceId;
|
|
$matched = true;
|
|
}
|
|
if (!$matched) {
|
|
continue;
|
|
}
|
|
\file_put_contents(
|
|
$file->getPathname(),
|
|
\json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE),
|
|
);
|
|
$found = true;
|
|
}
|
|
}
|
|
|
|
Assert::assertTrue($found, "Could not find blob for space $spaceName ($spaceOpaqueId)");
|
|
}
|
|
|
|
/**
|
|
* @When the administrator checks the blobstore using the CLI
|
|
*
|
|
* @return void
|
|
*/
|
|
public function theAdministratorChecksTheBlobstoreUsingTheCli(): void {
|
|
$body = ["command" => "storage-users blobstore check"];
|
|
$this->featureContext->setResponse(CliHelper::runCommand($body));
|
|
}
|
|
|
|
/**
|
|
* @When the administrator checks the blobstore with blob size :blobSize using the CLI
|
|
*
|
|
* @param string $blobSize
|
|
*
|
|
* @return void
|
|
*/
|
|
public function theAdministratorChecksTheBlobstoreWithBlobSizeUsingTheCli(string $blobSize): void {
|
|
$body = ["command" => "storage-users blobstore check --blob-size=$blobSize"];
|
|
$this->featureContext->setResponse(CliHelper::runCommand($body));
|
|
}
|
|
|
|
/**
|
|
* @When the administrator tries to get a blob from the blobstore using the CLI
|
|
*
|
|
* @return void
|
|
*/
|
|
public function theAdministratorTriesToGetABlobFromTheBlobstoreUsingTheCli(): void {
|
|
$body = ["command" => "storage-users blobstore get"];
|
|
$this->featureContext->setResponse(CliHelper::runCommand($body));
|
|
}
|
|
|
|
/**
|
|
* @When the administrator tries to get a non-existent blob from the blobstore using the CLI
|
|
*
|
|
* @return void
|
|
*/
|
|
public function theAdministratorTriesToGetANonExistentBlobFromTheBlobstoreUsingTheCli(): void {
|
|
$body = [
|
|
"command" => "storage-users blobstore get"
|
|
. " --blob-id=00000000-0000-0000-0000-000000000001"
|
|
. " --space-id=00000000-0000-0000-0000-000000000002",
|
|
];
|
|
$this->featureContext->setResponse(CliHelper::runCommand($body));
|
|
}
|
|
|
|
/**
|
|
* Finds the most recently written blob in the storage-users blobstore.
|
|
* Scoped to the users storage root to avoid picking blobs from metadata storage.
|
|
*
|
|
* @return string absolute path to a real blob file
|
|
*/
|
|
private function findNewestBlobPath(): string {
|
|
$storageRoot = $this->featureContext->getStorageUsersRoot();
|
|
// Async postprocessing may not have moved the blob to its final
|
|
// location yet — poll until the directory appears or we give up.
|
|
$blobDirs = [];
|
|
$retried = 0;
|
|
do {
|
|
$blobDirs = \glob($storageRoot . '/spaces/*/*/blobs', GLOB_ONLYDIR) ?: [];
|
|
if (!empty($blobDirs)) {
|
|
break;
|
|
}
|
|
$retried++;
|
|
echo "Blobstore directories not found under $storageRoot, retrying ($retried)...\n";
|
|
\usleep(500 * 1000);
|
|
} while ($retried < STANDARD_RETRY_COUNT);
|
|
|
|
Assert::assertNotEmpty($blobDirs, "No blobstore directory found under $storageRoot");
|
|
|
|
$newestPath = null;
|
|
$newestMtime = 0;
|
|
foreach ($blobDirs as $blobsDir) {
|
|
$iterator = new \RecursiveIteratorIterator(
|
|
new \RecursiveDirectoryIterator($blobsDir, \RecursiveDirectoryIterator::SKIP_DOTS),
|
|
);
|
|
foreach ($iterator as $file) {
|
|
if (!$file->isFile()) {
|
|
continue;
|
|
}
|
|
$mtime = $file->getMTime();
|
|
if ($mtime > $newestMtime) {
|
|
$newestMtime = $mtime;
|
|
$newestPath = $file->getPathname();
|
|
}
|
|
}
|
|
}
|
|
|
|
Assert::assertNotNull($newestPath, "No blob file found under $storageRoot");
|
|
return $newestPath;
|
|
}
|
|
|
|
/**
|
|
* @When the administrator gets a blob from the blobstore using the --path flag via the CLI
|
|
*
|
|
* @return void
|
|
*/
|
|
public function theAdministratorGetsABlobFromTheBlobstoreUsingThePathFlagViaTheCli(): void {
|
|
$blobPath = $this->findNewestBlobPath();
|
|
$body = ["command" => "storage-users blobstore get --path=" . $blobPath];
|
|
$this->featureContext->setResponse(CliHelper::runCommand($body));
|
|
}
|
|
|
|
/**
|
|
* @When the administrator tries to get a blob from the blobstore with path :path using the CLI
|
|
*
|
|
* @param string $path
|
|
*
|
|
* @return void
|
|
*/
|
|
public function theAdministratorTriesToGetABlobWithPathUsingTheCli(string $path): void {
|
|
$body = ["command" => "storage-users blobstore get --path=" . $path];
|
|
$this->featureContext->setResponse(CliHelper::runCommand($body));
|
|
}
|
|
|
|
/**
|
|
* @When the administrator gets a blob from the blobstore by ID using the CLI
|
|
*
|
|
* @return void
|
|
*/
|
|
public function theAdministratorGetsABlobFromTheBlobstoreByIdUsingTheCli(): void {
|
|
$blobPath = $this->findNewestBlobPath();
|
|
|
|
// The blob path is "<root>/spaces/<pathifiedSpaceID>/blobs/<pathifiedBlobID>".
|
|
// The raw space/blob IDs are recovered by stripping the pathify separators,
|
|
// which is exactly what the CLI's depathify does on the way back in.
|
|
[, $rest] = \explode('/spaces/', $blobPath, 2);
|
|
[$pathifiedSpaceID, $pathifiedBlobID] = \explode('/blobs/', $rest, 2);
|
|
$spaceId = \str_replace('/', '', $pathifiedSpaceID);
|
|
$blobId = \str_replace('/', '', $pathifiedBlobID);
|
|
|
|
$body = [
|
|
"command" => "storage-users blobstore get"
|
|
. " --blob-id=" . $blobId
|
|
. " --space-id=" . $spaceId,
|
|
];
|
|
$this->featureContext->setResponse(CliHelper::runCommand($body));
|
|
}
|
|
}
|