From ced0043771470252b865262ec28085d6484f843a Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Fri, 3 Apr 2026 13:59:34 -0300 Subject: [PATCH 1/4] refactor: migrate nextcloud api context to attributes Signed-off-by: Vitor Mattos --- src/NextcloudApiContext.php | 100 +++++++++++------------------------- 1 file changed, 29 insertions(+), 71 deletions(-) diff --git a/src/NextcloudApiContext.php b/src/NextcloudApiContext.php index 9d8716d..4e0a339 100644 --- a/src/NextcloudApiContext.php +++ b/src/NextcloudApiContext.php @@ -5,6 +5,10 @@ use Behat\Behat\Context\Context; use Behat\Gherkin\Node\PyStringNode; use Behat\Gherkin\Node\TableNode; +use Behat\Hook\AfterScenario; +use Behat\Hook\BeforeScenario; +use Behat\Hook\BeforeSuite; +use Behat\Step\Given; use Behat\Testwork\Hook\Scope\BeforeSuiteScope; use DOMDocument; use Exception; @@ -53,9 +57,7 @@ public function __construct(?array $parameters = []) { } } - /** - * @BeforeSuite - */ + #[BeforeSuite()] public static function beforeSuite(BeforeSuiteScope $scope):void { $whoami = (string) exec('whoami'); if (get_current_user() !== $whoami) { @@ -68,24 +70,18 @@ public static function beforeSuite(BeforeSuiteScope $scope):void { } } - /** - * @BeforeScenario - */ + #[BeforeScenario()] public static function beforeScenario(): void { self::$createdUsers = []; self::$environments = []; } - /** - * @Given as user :user - */ + #[Given('as user :user')] public function setCurrentUser(string $user): void { $this->currentUser = $user; } - /** - * @Given user :user exists - */ + #[Given('user :user exists')] public function assureUserExists(string $user): void { $response = $this->userExists($user); if ($response->getStatusCode() !== 200) { @@ -98,9 +94,7 @@ public function assureUserExists(string $user): void { } } - /** - * @Given guest :guest exists - */ + #[Given('guest :guest exists')] public function assureGuestExists(string $guest): void { $response = $this->userExists($guest); if ($response->getStatusCode() !== 200) { @@ -140,9 +134,7 @@ protected function createUser(string $user): void { $this->setCurrentUser($currentUser); } - /** - * @Given /^set the display name of user "([^"]*)" to "([^"]*)"$/ - */ + #[Given('/^set the display name of user "([^"]*)" to "([^"]*)"$/')] public function setUserDisplayName(string $user, ?string $displayName = null): void { $currentUser = $this->currentUser; $this->setCurrentUser('admin'); @@ -154,9 +146,7 @@ public function setUserDisplayName(string $user, ?string $displayName = null): v $this->setCurrentUser($currentUser); } - /** - * @Given /^set the email of user "([^"]*)" to "([^"]*)"$/ - */ + #[Given('/^set the email of user "([^"]*)" to "([^"]*)"$/')] public function setUserEmail(string $user, string $email): void { $currentUser = $this->currentUser; $this->setCurrentUser('admin'); @@ -171,8 +161,8 @@ public function setUserEmail(string $user, string $email): void { * @param string $verb * @param string $url * @param TableNode|array|null $body - * @Given sending :verb to ocs :url */ + #[Given('sending :verb to ocs :url')] public function sendOCSRequest(string $verb, string $url, $body = null, array $headers = [], array $options = []): void { $url = '/ocs/v2.php' . $url; $headers['OCS-ApiRequest'] = 'true'; @@ -184,8 +174,8 @@ public function sendOCSRequest(string $verb, string $url, $body = null, array $h * @param string $url * @param TableNode|PyStringNode|array|null $body * @param array $headers - * @Given sending :verb to :url */ + #[Given('sending :verb to :url')] public function sendRequest(string $verb, string $url, $body = null, array $headers = [], array $options = []): void { if (!str_starts_with($url, '/')) { $url = '/' . $url; @@ -273,9 +263,7 @@ private function normalizePayloadForRequest(string $verb, array $options): array return $options; } - /** - * @Given /^set the custom http header "([^"]*)" with "([^"]*)" as value to next request$/ - */ + #[Given('/^set the custom http header "([^"]*)" with "([^"]*)" as value to next request$/')] public function setTheCustomHttpHeaderAsValueToNextRequest(string $header, string $value):void { if (empty($value)) { unset($this->customHeaders[$header]); @@ -325,9 +313,7 @@ protected function assertStatusCode(ResponseInterface $response, int $statusCode /** * @throws \InvalidArgumentException */ - /** - * @Given the response should have a status code :code - */ + #[Given('the response should have a status code :code')] public function theResponseShouldHaveStatusCode(string $code): void { $currentCode = $this->response->getStatusCode(); Assert::assertEquals($code, $currentCode, $this->response->getBody()->getContents()); @@ -336,9 +322,7 @@ public function theResponseShouldHaveStatusCode(string $code): void { /** * @throws \InvalidArgumentException */ - /** - * @Given the response should be a JSON array with the following mandatory values - */ + #[Given('the response should be a JSON array with the following mandatory values')] public function theResponseShouldBeAJsonArrayWithTheFollowingMandatoryValues(TableNode $table): void { $this->response->getBody()->seek(0); $expectedValues = $table->getColumnsHash(); @@ -412,9 +396,7 @@ private function validateAsJsonQuery(string $expected, string $actual): void { Assert::assertTrue($result, 'The jq "' . $expected . '" do not match with: ' . $actual); } - /** - * @Given fetch field :path from previous JSON response - */ + #[Given('fetch field :path from previous JSON response')] public function fetchFieldFromPreviousJsonResponse(string $path): void { $this->response->getBody()->seek(0); $body = $this->response->getBody()->getContents(); @@ -445,9 +427,7 @@ public function fetchFieldFromPreviousJsonResponse(string $path): void { $this->fields[$path] = $value; } - /** - * @Given the response should contain the initial state :name with the following values: - */ + #[Given('the response should contain the initial state :name with the following values:')] public function theResponseShouldContainTheInitialStateWithTheFollowingValues(string $name, PyStringNode $expected): void { $this->response->getBody()->seek(0); $html = $this->response->getBody()->getContents(); @@ -476,9 +456,7 @@ public function theResponseShouldContainTheInitialStateWithTheFollowingValues(st } } - /** - * @Given the response should contain the initial state :name json that match with: - */ + #[Given('the response should contain the initial state :name json that match with:')] public function theResponseShouldContainTheInitialStateJsonThatMatchWith(string $name, TableNode $table): void { $this->response->getBody()->seek(0); $html = $this->response->getBody()->getContents(); @@ -499,9 +477,7 @@ public function theResponseShouldContainTheInitialStateJsonThatMatchWith(string $this->jsonStringMatchWith($actual, $expectedValues); } - /** - * @Given the following :appId app config is set - */ + #[Given('the following :appId app config is set')] public function setAppConfig(string $appId, TableNode $formData): void { $currentUser = $this->currentUser; $this->setCurrentUser('admin'); @@ -550,9 +526,7 @@ protected function parseText(string $text): string { return $text; } - /** - * @Given /^run the command "(?P(?:[^"]|\\")*)"$/ - */ + #[Given('/^run the command "(?P(?:[^"]|\\")*)"$/')] public static function runCommand(string $command): array { $console = static::findParentDirContainingFile('console.php'); $console .= '/console.php'; @@ -622,63 +596,47 @@ private static function runBashCommand(string $command): array { ]; } - /** - * @Given the output of the last command should contain the following text: - */ + #[Given('the output of the last command should contain the following text:')] public static function theOutputOfTheLastCommandContains(PyStringNode $text): void { Assert::assertStringContainsString((string) $text, self::$commandOutput, 'The output of the last command does not contain: ' . (string) $text); } - /** - * @Given the output of the last command should be empty - */ + #[Given('the output of the last command should be empty')] public static function theOutputOfTheLastCommandShouldBeEmpty(): void { Assert::assertEmpty(self::$commandOutput, 'The output of the last command should be empty, but got: ' . self::$commandOutput); } - /** - * @Given /^run the command "(?P(?:[^"]|\\")*)" with result code (\d+)$/ - */ + #[Given('/^run the command "(?P(?:[^"]|\\")*)" with result code (\d+)$/')] public static function runCommandWithResultCode(string $command, int $resultCode = 0): void { $return = self::runCommand($command); Assert::assertEquals($resultCode, $return['resultCode'], print_r($return, true)); } - /** - * @Given /^run the bash command "(?P(?:[^"]|\\")*)" with result code (\d+)$/ - */ + #[Given('/^run the bash command "(?P(?:[^"]|\\")*)" with result code (\d+)$/')] public static function runBashCommandWithResultCode(string $command, int $resultCode = 0): void { $return = self::runBashCommand($command); Assert::assertEquals($resultCode, $return['resultCode'], print_r($return, true)); } - /** - * @Given create an environment :name with value :value to be used by occ command - */ + #[Given('create an environment :name with value :value to be used by occ command')] public static function createAnEnvironmentWithValueToBeUsedByOccCommand(string $name, string $value):void { self::$environments[$name] = $value; } - /** - * @Given /^wait for ([0-9]+) (second|seconds)$/ - */ + #[Given('/^wait for ([0-9]+) (second|seconds)$/')] public function waitForXSecond(int $seconds): void { $this->startWaitFor = $seconds; sleep($seconds); } - /** - * @Given /^past ([0-9]+) (second|seconds) since wait step$/ - */ + #[Given('/^past ([0-9]+) (second|seconds) since wait step$/')] public function pastXSecondsSinceWaitStep(int $seconds): void { $currentTime = time(); $startTime = $currentTime - $this->startWaitFor; Assert::assertGreaterThanOrEqual($startTime, $currentTime, 'The current time is not greater than or equal to the start time.'); } - /** - * @AfterScenario - */ + #[AfterScenario()] public function tearDown(): void { self::$environments = []; foreach (self::$createdUsers as $user) { From c118e660356f7af918942d6f98ebbaeb48983f18 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Fri, 3 Apr 2026 13:59:39 -0300 Subject: [PATCH 2/4] test: align feature context overrides Signed-off-by: Vitor Mattos --- features/bootstrap/FeatureContext.php | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/features/bootstrap/FeatureContext.php b/features/bootstrap/FeatureContext.php index f4e1deb..74fa641 100644 --- a/features/bootstrap/FeatureContext.php +++ b/features/bootstrap/FeatureContext.php @@ -2,6 +2,7 @@ use Behat\Gherkin\Node\PyStringNode; use Behat\Gherkin\Node\TableNode; +use Behat\Step\Given; use donatj\MockWebServer\MockWebServer; use donatj\MockWebServer\RequestInfo; use donatj\MockWebServer\Response as MockWebServerResponse; @@ -22,8 +23,8 @@ public function __construct(?array $parameters = []) { /** * @inheritDoc - * @psalm-suppress MissingOverrideAttribute */ + #[\Override] public function setCurrentUser(string $user): void { parent::setCurrentUser($user); Assert::assertEquals($this->currentUser, $user); @@ -31,8 +32,8 @@ public function setCurrentUser(string $user): void { /** * @inheritDoc - * @psalm-suppress MissingOverrideAttribute */ + #[\Override] public function assureUserExists(string $user): void { parent::assureUserExists($user); $lastRequest = $this->getLastREquest(); @@ -57,17 +58,16 @@ private function getLastRequest(): RequestInfo { * When whe run the test suit of this repository at GitHub Actions, is * necessary to consider that we haven't Nextcloud installed and mock * the real path of files. - * @psalm-suppress MissingOverrideAttribute */ + #[\Override] public static function findParentDirContainingFile(string $filename): string { return __DIR__; } /** * @inheritDoc - * @param TableNode|PyStringNode|array|null $body - * @psalm-suppress MissingOverrideAttribute */ + #[\Override] public function sendRequest(string $verb, string $url, $body = null, array $headers = [], array $options = []): void { parent::sendRequest($verb, $url, $body, $headers, $options); $lastRequest = $this->getLastRequest(); @@ -131,9 +131,7 @@ private function hasNestedPayload(array $payload): bool { return false; } - /** - * @Given set the response to: - */ + #[Given('set the response to:')] public function setTheResponseTo(PyStringNode $response): void { // Mock response to be equal to body of request $this->mockServer->setDefaultResponse(new MockWebServerResponse( @@ -143,8 +141,8 @@ public function setTheResponseTo(PyStringNode $response): void { /** * @inheritDoc - * @psalm-suppress MissingOverrideAttribute */ + #[\Override] public function theResponseShouldBeAJsonArrayWithTheFollowingMandatoryValues(TableNode $table): void { $lastRequest = $this->getLastRequest(); $parsedInput = $this->getParsedInputFromRequest($lastRequest); From 8d6ed611884ece00db213ee9f82fcfef56fba333 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Fri, 3 Apr 2026 13:59:44 -0300 Subject: [PATCH 3/4] test: keep raw json body scenario coverage Signed-off-by: Vitor Mattos --- features/test.feature | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/features/test.feature b/features/test.feature index caf5581..8c1d86f 100644 --- a/features/test.feature +++ b/features/test.feature @@ -64,8 +64,8 @@ Feature: Test this extension } """ Then the response should be a JSON array with the following mandatory values - | key | value | - | (jq).status.nested | true | + | key | value | + | (jq).status.nested | true | Scenario: Test response of POST is json When set the response to: From b2d1b1c923e06b82e25daa78b98330ea425cdaa6 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Fri, 3 Apr 2026 13:59:48 -0300 Subject: [PATCH 4/4] build: re-enable override enforcement Signed-off-by: Vitor Mattos --- psalm.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/psalm.xml b/psalm.xml index 4ef4fb0..2f70544 100644 --- a/psalm.xml +++ b/psalm.xml @@ -1,7 +1,6 @@