Skip to content

Commit 8fd3d7e

Browse files
authored
Merge pull request #95 from LibreSign/feat/run-command
feat: run command
2 parents 622be19 + d870a60 commit 8fd3d7e

5 files changed

Lines changed: 175 additions & 6 deletions

File tree

README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,12 @@ Given fetch field :path from previous JSON response
6060
Given the response should contain the initial state :name with the following values:
6161
Given the response should contain the initial state :name json that match with:
6262
Given the following :appId app config is set
63+
Given /^run the command "(?P<command>(?:[^"]|\\")*)"$/
64+
Given the output of the last command should contain the following text:
65+
Given the output of the last command should be empty
66+
Given /^run the command "(?P<command>(?:[^"]|\\")*)" with result code (\d+)$/
67+
Given /^run the bash command "(?P<command>(?:[^"]|\\")*)" with result code (\d+)$/
68+
Given create an environment :name with value :value to be used by occ command
6369
```
6470

6571
## Tips
@@ -83,6 +89,15 @@ Given sending "post" to ocs "/apps/libresign/api/v1/request-signature"
8389
| file | {"base64":""} |
8490
```
8591

92+
### Step: all steps that run commands
93+
94+
Before the command is executed, the following placeholders will be replaced:
95+
96+
| Placeholder | Value |
97+
| -------------------- | ---------------------- |
98+
| `<appRootDir>` | your app root dir |
99+
| `<nextcloudRootDir>` | The Nextcloud root dir |
100+
86101
### Step: `fetch field :path from previous JSON response`
87102

88103
If the json response is an array, you can fetch specific values using this step. The fetched values is stored to be used by other steps.

features/bootstrap/FeatureContext.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,15 @@ private function getLastRequest(): RequestInfo {
5252
return $lastRequest;
5353
}
5454

55+
/**
56+
* When whe run the test suit of this repository at GitHub Actions, is
57+
* necessary to consider that we haven't Nextcloud installed and mock
58+
* the real path of files.
59+
*/
60+
public static function findParentDirContainingFile(string $filename): string {
61+
return __DIR__;
62+
}
63+
5564
/**
5665
* @inheritDoc
5766
*/

features/bootstrap/console.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
/**
4+
* When whe run the test suit of this repository at GitHub Actions, is
5+
* necessary to consider that we haven't Nextcloud installed and mock
6+
* the occ commands execution.
7+
*/
8+
9+
if (in_array('invalid-command', $argv)) {
10+
echo "Invalid command\n";
11+
exit(1);
12+
}
13+
14+
if (getenv('OC_PASS') === '123456') {
15+
echo "I found the environment variable OC_PASS with value 123456\n";
16+
exit;
17+
}

features/test.feature

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,3 +227,34 @@ Feature: Test this extension
227227
Then the response should contain the initial state "appid-json-array" json that match with:
228228
| key | value |
229229
| (jq).[0] | orange |
230+
231+
Scenario: Test list app directory with success
232+
When run the bash command "ls <appRootDir>" with result code 0
233+
Then the output of the last command should contain the following text:
234+
"""
235+
FeatureContext.php
236+
"""
237+
238+
Scenario: Test list Nextcloud directory with success
239+
When run the bash command "ls <nextcloudRootDir>" with result code 0
240+
241+
Scenario: Test run bash command with success
242+
When run the bash command "true" with result code 0
243+
Then the output of the last command should be empty
244+
245+
Scenario: Test run bash command with error
246+
When run the bash command "false" with result code 1
247+
248+
Scenario: Run occ command with success
249+
When run the command "status" with result code 0
250+
251+
Scenario: Run occ command with success
252+
When run the command "invalid-command" with result code 1
253+
254+
Scenario: Create an environment with value to be used by occ command
255+
When create an environment "OC_PASS" with value "123456" to be used by occ command
256+
And run the command "fake-command" with result code 0
257+
Then the output of the last command should contain the following text:
258+
"""
259+
I found the environment variable OC_PASS with value 123456
260+
"""

src/NextcloudApiContext.php

Lines changed: 103 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,13 @@ class NextcloudApiContext implements Context {
3030
protected RunServerListener $server;
3131
protected string $currentUser = '';
3232
protected array $fields = [];
33+
protected static array $environments = [];
34+
protected static string $commandOutput = '';
35+
3336
/**
3437
* @var string[]
3538
*/
36-
protected array $createdUsers = [];
39+
protected static array $createdUsers = [];
3740
protected ResponseInterface $response;
3841
/** @var CookieJar[] */
3942
protected $cookieJars;
@@ -66,8 +69,9 @@ public static function beforeSuite(BeforeSuiteScope $scope):void {
6669
}
6770

6871
#[BeforeScenario()]
69-
public function setUp(): void {
70-
$this->createdUsers = [];
72+
public function beforeScenario(): void {
73+
self::$createdUsers = [];
74+
self::$environments = [];
7175
}
7276

7377
#[Given('as user :user')]
@@ -110,7 +114,7 @@ protected function createUser(string $user): void {
110114
$this->sendOCSRequest('GET', '/cloud/users' . '/' . $user);
111115
$this->assertStatusCode($this->response, 200, 'Failed to do first login');
112116

113-
$this->createdUsers[] = $user;
117+
self::$createdUsers[] = $user;
114118

115119
$this->setCurrentUser($currentUser);
116120
}
@@ -448,9 +452,102 @@ protected function parseText(string $text): string {
448452
return $text;
449453
}
450454

455+
#[Given('/^run the command "(?P<command>(?:[^"]|\\")*)"$/')]
456+
public static function runCommand(string $command): array {
457+
$console = static::findParentDirContainingFile('console.php');
458+
$console .= '/console.php';
459+
$fileOwnerUid = fileowner($console);
460+
if (!is_int($fileOwnerUid)) {
461+
throw new \Exception('The console file owner of ' . $console . ' is not an integer UID.');
462+
}
463+
$owner = posix_getpwuid($fileOwnerUid);
464+
if ($owner === false) {
465+
throw new \Exception('Could not retrieve owner information for UID ' . $fileOwnerUid);
466+
}
467+
$fullCommand = 'php ' . $console . ' ' . $command;
468+
if (!empty(self::$environments)) {
469+
$fullCommand = http_build_query(self::$environments, '', ' ') . ' ' . $fullCommand;
470+
}
471+
if (posix_getuid() !== $owner['uid']) {
472+
$fullCommand = 'runuser -u ' . $owner['name'] . ' -- ' . $fullCommand;
473+
}
474+
$fullCommand .= ' 2>&1';
475+
return self::runBashCommand($fullCommand);
476+
}
477+
478+
public static function findParentDirContainingFile(string $filename): string {
479+
$dir = getcwd();
480+
if (is_bool($dir)) {
481+
throw new \Exception('Could not get current working directory (getcwd() returned false)');
482+
}
483+
484+
while ($dir !== dirname($dir)) {
485+
if (file_exists($dir . DIRECTORY_SEPARATOR . $filename)) {
486+
return $dir;
487+
}
488+
$dir = dirname($dir);
489+
}
490+
491+
throw new \Exception('The file ' . $filename . ' was not found in the parent directories of ' . $dir);
492+
}
493+
494+
private static function runBashCommand(string $command): array {
495+
$command = str_replace('\"', '"', $command);
496+
$patterns = [];
497+
$replacements = [];
498+
$fields = [
499+
'appRootDir' => static::findParentDirContainingFile('appinfo'),
500+
'nextcloudRootDir' => static::findParentDirContainingFile('console.php'),
501+
];
502+
foreach ($fields as $key => $value) {
503+
$patterns[] = '/<' . $key . '>/';
504+
$replacements[] = $value;
505+
}
506+
$command = preg_replace($patterns, $replacements, $command);
507+
if (!is_string($command)) {
508+
throw new \Exception('The command is not a string after preg_replace: ' . print_r($command, true));
509+
}
510+
511+
exec($command, $output, $resultCode);
512+
self::$commandOutput = implode("\n", $output);
513+
return [
514+
'command' => $command,
515+
'output' => $output,
516+
'resultCode' => $resultCode,
517+
];
518+
}
519+
520+
#[Given('the output of the last command should contain the following text:')]
521+
public static function theOutputOfTheLastCommandContains(PyStringNode $text): void {
522+
Assert::assertStringContainsString((string) $text, self::$commandOutput, 'The output of the last command does not contain: ' . $text);
523+
}
524+
525+
#[Given('the output of the last command should be empty')]
526+
public static function theOutputOfTheLastCommandShouldBeEmpty(): void {
527+
Assert::assertEmpty(self::$commandOutput, 'The output of the last command should be empty, but got: ' . self::$commandOutput);
528+
}
529+
530+
#[Given('/^run the command "(?P<command>(?:[^"]|\\")*)" with result code (\d+)$/')]
531+
public static function runCommandWithResultCode(string $command, int $resultCode = 0): void {
532+
$return = self::runCommand($command);
533+
Assert::assertEquals($resultCode, $return['resultCode'], print_r($return, true));
534+
}
535+
536+
#[Given('/^run the bash command "(?P<command>(?:[^"]|\\")*)" with result code (\d+)$/')]
537+
public static function runBashCommandWithResultCode(string $command, int $resultCode = 0): void {
538+
$return = self::runBashCommand($command);
539+
Assert::assertEquals($resultCode, $return['resultCode'], print_r($return, true));
540+
}
541+
542+
#[Given('create an environment :name with value :value to be used by occ command')]
543+
public static function createAnEnvironmentWithValueToBeUsedByOccCommand(string $name, string $value):void {
544+
self::$environments[$name] = $value;
545+
}
546+
451547
#[AfterScenario()]
452548
public function tearDown(): void {
453-
foreach ($this->createdUsers as $user) {
549+
self::$environments = [];
550+
foreach (self::$createdUsers as $user) {
454551
$this->deleteUser($user);
455552
}
456553
}
@@ -461,7 +558,7 @@ protected function deleteUser(string $user): ResponseInterface {
461558
$this->sendOCSRequest('DELETE', '/cloud/users/' . $user);
462559
$this->setCurrentUser($currentUser);
463560

464-
unset($this->createdUsers[array_search($user, $this->createdUsers, true)]);
561+
unset(self::$createdUsers[array_search($user, self::$createdUsers, true)]);
465562

466563
return $this->response;
467564
}

0 commit comments

Comments
 (0)