From 2f99f9ee3714effc72bd48d466e33b69bf686439 Mon Sep 17 00:00:00 2001 From: Luis Amorim Date: Fri, 18 Sep 2026 11:27:19 -0300 Subject: [PATCH 01/10] test(behat): add PDF_BASE64 placeholder for demo fixtures Provide an inline data-URI from small_valid.pdf so scenarios can send PDFs without nested HTTP to the PHP built-in server. Signed-off-by: Luis Amorim --- .../features/bootstrap/FeatureContext.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/integration/features/bootstrap/FeatureContext.php b/tests/integration/features/bootstrap/FeatureContext.php index 2c1136be15..6cb80e5489 100644 --- a/tests/integration/features/bootstrap/FeatureContext.php +++ b/tests/integration/features/bootstrap/FeatureContext.php @@ -59,6 +59,20 @@ protected function parseText(string $text): string { return $text; } + /** + * Inline PDF fixture for Behat requests. + * + * Prefer this over {"url":".../develop/pdf"} so request-signature does not + * HTTP-call the same PHP built-in server (which requires PHP_CLI_SERVER_WORKERS + * and is a known source of cURL 52 Empty reply flakes). + */ + private function getDemoPdfBase64(): string { + $pdfPath = __DIR__ . '/../../../php/fixtures/pdfs/small_valid.pdf'; + $contents = file_get_contents($pdfPath); + Assert::assertNotFalse($contents, 'Demo PDF fixture missing: ' . $pdfPath); + return 'data:application/pdf;base64,' . base64_encode($contents); + } + #[Given('I fetch the signer UUID from opened email')] public function iFetchTheLinkOnOpenedEmail(): void { if (!$this->openedEmailStorage->hasOpenedEmail()) { From e0d6da17ffa1aa86cb0775a5b61719151801b41b Mon Sep 17 00:00:00 2001 From: Luis Amorim Date: Fri, 18 Sep 2026 11:27:34 -0300 Subject: [PATCH 02/10] test(behat): disable PHP built-in server workers With fixtures no longer nesting HTTP to develop/pdf, a single-process php -S is enough and avoids experimental PHP_CLI_SERVER_WORKERS flakes. Signed-off-by: Luis Amorim --- tests/integration/config/behat.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/integration/config/behat.yml b/tests/integration/config/behat.yml index 6fb644805f..bab02ebc0e 100644 --- a/tests/integration/config/behat.yml +++ b/tests/integration/config/behat.yml @@ -16,7 +16,10 @@ default: base_url: http://mailpit:8025 PhpBuiltin\Server: runAs: www-data - workers: 10 + # Keep single-process built-in server. Multi-worker (PHP_CLI_SERVER_WORKERS) + # is experimental and flakes with cURL 52 under Behat load. Fixtures use + # inline instead of nested HTTP to /develop/pdf. + workers: 0 jarnaiz\JUnitFormatter\JUnitFormatterExtension: filename: report.xml outputDir: '%paths.base%/../output/' From 709e6ca52c996b41ee66df69dd1b7e94268b6d96 Mon Sep 17 00:00:00 2001 From: Luis Amorim Date: Fri, 18 Sep 2026 11:34:08 -0300 Subject: [PATCH 03/10] fix(behat): keep two PHP built-in server workers workers: 0 deadlocks any nested self-HTTP on php -S. Use the minimum of two workers while fixtures stay on inline PDF base64 to avoid the previous cURL 52 flakes from workers: 10. Signed-off-by: Luis Amorim --- tests/integration/config/behat.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/integration/config/behat.yml b/tests/integration/config/behat.yml index bab02ebc0e..0b3b9cbfd2 100644 --- a/tests/integration/config/behat.yml +++ b/tests/integration/config/behat.yml @@ -16,10 +16,11 @@ default: base_url: http://mailpit:8025 PhpBuiltin\Server: runAs: www-data - # Keep single-process built-in server. Multi-worker (PHP_CLI_SERVER_WORKERS) - # is experimental and flakes with cURL 52 under Behat load. Fixtures use - # inline instead of nested HTTP to /develop/pdf. - workers: 0 + # Keep the minimum workers needed for occasional nested HTTP to the same + # PHP built-in server (single-process deadlocks on self-requests). Prefer + # inline fixtures over url→/develop/pdf to avoid load-driven + # cURL 52 flakes from higher PHP_CLI_SERVER_WORKERS values. + workers: 2 jarnaiz\JUnitFormatter\JUnitFormatterExtension: filename: report.xml outputDir: '%paths.base%/../output/' From 9ede8358542c5ecd38b52ff1f1c9ee5ea6a9ac91 Mon Sep 17 00:00:00 2001 From: Luis Amorim Date: Fri, 18 Sep 2026 11:37:49 -0300 Subject: [PATCH 04/10] test(behat): cover request-signature url via separate fixture server Serve small_valid.pdf from a second local php -S process and add a scenario that posts {"url":""} so url download stays covered without nested HTTP to the Behat Nextcloud server. Signed-off-by: Luis Amorim --- .../features/bootstrap/FeatureContext.php | 14 ++- .../features/bootstrap/FixtureHttpServer.php | 107 ++++++++++++++++++ 2 files changed, 117 insertions(+), 4 deletions(-) create mode 100644 tests/integration/features/bootstrap/FixtureHttpServer.php diff --git a/tests/integration/features/bootstrap/FeatureContext.php b/tests/integration/features/bootstrap/FeatureContext.php index 6cb80e5489..1c36617716 100644 --- a/tests/integration/features/bootstrap/FeatureContext.php +++ b/tests/integration/features/bootstrap/FeatureContext.php @@ -7,6 +7,7 @@ */ use Behat\Gherkin\Node\TableNode; +use Behat\Hook\AfterSuite; use Behat\Hook\BeforeScenario; use Behat\Hook\BeforeSuite; use Behat\Step\Given; @@ -25,10 +26,16 @@ class FeatureContext extends NextcloudApiContext implements OpenedEmailStorageAw #[BeforeSuite()] public static function beforeSuite(BeforeSuiteScope $scope):void { parent::beforeSuite($scope); + FixtureHttpServer::start(); self::runCommand('config:system:set debug --value true --type boolean'); self::runCommand('app:enable --force notifications'); } + #[AfterSuite()] + public static function afterSuite(): void { + FixtureHttpServer::stop(); + } + #[BeforeScenario()] public static function beforeScenario(): void { parent::beforeScenario(); @@ -60,11 +67,10 @@ protected function parseText(string $text): string { } /** - * Inline PDF fixture for Behat requests. + * Inline PDF fixture for Behat requests that do not need to exercise url download. * - * Prefer this over {"url":".../develop/pdf"} so request-signature does not - * HTTP-call the same PHP built-in server (which requires PHP_CLI_SERVER_WORKERS - * and is a known source of cURL 52 Empty reply flakes). + * Prefer this over url→the Behat Nextcloud server. For url coverage use , + * which is served by FixtureHttpServer on a separate local port. */ private function getDemoPdfBase64(): string { $pdfPath = __DIR__ . '/../../../php/fixtures/pdfs/small_valid.pdf'; diff --git a/tests/integration/features/bootstrap/FixtureHttpServer.php b/tests/integration/features/bootstrap/FixtureHttpServer.php new file mode 100644 index 0000000000..ce1a75f16d --- /dev/null +++ b/tests/integration/features/bootstrap/FixtureHttpServer.php @@ -0,0 +1,107 @@ + /dev/null 2>&1 & echo $!', + escapeshellarg(self::$host), + self::$port, + escapeshellarg(self::$documentRoot) + ); + + self::$pid = trim((string) shell_exec($cmd)); + if (self::$pid === '' || !ctype_digit(self::$pid)) { + throw new RuntimeException('Failed to start fixture HTTP server process.'); + } + + for ($attempt = 0; $attempt < 30; $attempt++) { + usleep(100000); + $socket = @fsockopen(self::$host, self::$port); + if (is_resource($socket)) { + fclose($socket); + register_shutdown_function(static function (): void { + self::stop(); + }); + return; + } + } + + self::stop(); + throw new RuntimeException('Fixture HTTP server did not become ready in time.'); + } + + public static function stop(): void { + if (self::isRunning()) { + exec('kill ' . self::$pid); + } + self::$pid = '0'; + self::$port = 0; + } + + public static function getPdfUrl(): string { + if (!self::isRunning() || self::$port <= 0) { + throw new RuntimeException('Fixture HTTP server is not running.'); + } + + return sprintf('http://%s:%d/small_valid.pdf', self::$host, self::$port); + } + + private static function isRunning(): bool { + if (self::$pid === '' || self::$pid === '0') { + return false; + } + + exec(sprintf('ps %d', (int) self::$pid), $result); + + return count($result) > 1; + } + + private static function findOpenPort(string $host): int { + $server = @stream_socket_server('tcp://' . $host . ':0', $errno, $errstr); + if ($server === false) { + throw new RuntimeException('Unable to allocate local port for fixture HTTP server: ' . $errstr); + } + + $name = stream_socket_get_name($server, false); + fclose($server); + + if (!is_string($name) || !str_contains($name, ':')) { + throw new RuntimeException('Unable to detect allocated local port for fixture HTTP server.'); + } + + $parts = explode(':', $name); + $port = (int) end($parts); + if ($port <= 0) { + throw new RuntimeException('Invalid allocated port for fixture HTTP server.'); + } + + return $port; + } +} From 4ce42644d8b38377f13cc93a2f0e05b9adefadbc Mon Sep 17 00:00:00 2001 From: Luis Amorim Date: Fri, 18 Sep 2026 11:47:02 -0300 Subject: [PATCH 05/10] style(behat): apply php-cs-fixer to FixtureHttpServer Signed-off-by: Luis Amorim --- tests/integration/features/bootstrap/FixtureHttpServer.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/integration/features/bootstrap/FixtureHttpServer.php b/tests/integration/features/bootstrap/FixtureHttpServer.php index ce1a75f16d..c8f0ae7229 100644 --- a/tests/integration/features/bootstrap/FixtureHttpServer.php +++ b/tests/integration/features/bootstrap/FixtureHttpServer.php @@ -36,7 +36,7 @@ public static function start(): void { escapeshellarg(self::$documentRoot) ); - self::$pid = trim((string) shell_exec($cmd)); + self::$pid = trim((string)shell_exec($cmd)); if (self::$pid === '' || !ctype_digit(self::$pid)) { throw new RuntimeException('Failed to start fixture HTTP server process.'); } @@ -78,7 +78,7 @@ private static function isRunning(): bool { return false; } - exec(sprintf('ps %d', (int) self::$pid), $result); + exec(sprintf('ps %d', (int)self::$pid), $result); return count($result) > 1; } @@ -97,7 +97,7 @@ private static function findOpenPort(string $host): int { } $parts = explode(':', $name); - $port = (int) end($parts); + $port = (int)end($parts); if ($port <= 0) { throw new RuntimeException('Invalid allocated port for fixture HTTP server.'); } From c14ff51c3c6eee4c9101fe994effe2b269a7edff Mon Sep 17 00:00:00 2001 From: Luis Amorim Date: Fri, 18 Sep 2026 15:32:41 -0300 Subject: [PATCH 06/10] test(behat): retry ConnectException against PHP built-in server Failsafe for residual cURL 52 empty-reply flakes during Behat HTTP bursts; retries up to three times with backoff and STDERR diagnostics. Signed-off-by: Luis Amorim --- .../features/bootstrap/FeatureContext.php | 37 ++++++--- .../bootstrap/TransientConnectionRetry.php | 50 ++++++++++++ .../TransientConnectionRetryTest.php | 76 +++++++++++++++++++ 3 files changed, 153 insertions(+), 10 deletions(-) create mode 100644 tests/integration/features/bootstrap/TransientConnectionRetry.php create mode 100644 tests/integration/features/bootstrap/TransientConnectionRetryTest.php diff --git a/tests/integration/features/bootstrap/FeatureContext.php b/tests/integration/features/bootstrap/FeatureContext.php index 1c36617716..cdde146871 100644 --- a/tests/integration/features/bootstrap/FeatureContext.php +++ b/tests/integration/features/bootstrap/FeatureContext.php @@ -6,6 +6,7 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ +use Behat\Gherkin\Node\PyStringNode; use Behat\Gherkin\Node\TableNode; use Behat\Hook\AfterSuite; use Behat\Hook\BeforeScenario; @@ -53,6 +54,20 @@ protected function beforeRequest(string $fullUrl, array $options): array { return [$fullUrl, $options]; } + /** + * Retry transient connection drops from the PHP built-in server (cURL 52). + * + * Intentionally has no #[Given] attribute: Behat keeps the parent step + * definition, and PHP dispatches to this override on the FeatureContext instance. + * + * @param TableNode|PyStringNode|array|null $body + */ + public function sendRequest(string $verb, string $url, $body = null, array $headers = [], array $options = []): void { + TransientConnectionRetry::run( + fn () => parent::sendRequest($verb, $url, $body, $headers, $options) + ); + } + protected function parseText(string $text): string { $fields = $this->fields; $fields['BASE_URL'] = $this->baseUrl . '/index.php'; @@ -229,16 +244,18 @@ public function fetchWebDavPropertyTo(string $property, string $alias): void { } private function davRequest(string $user, string $method, string $path, ?string $body = null, array $headers = []): void { - $client = new \GuzzleHttp\Client(); - try { - $this->response = $client->request($method, $this->baseUrl . '/remote.php/dav/files/' . $user . '/' . $path, [ - 'auth' => [$user === 'admin' ? 'admin' : $user, $user === 'admin' ? $this->adminPassword : $this->testPassword], - 'headers' => $headers, - 'body' => $body, - ]); - } catch (\GuzzleHttp\Exception\ClientException $ex) { - $this->response = $ex->getResponse(); - } + TransientConnectionRetry::run(function () use ($user, $method, $path, $body, $headers): void { + $client = new \GuzzleHttp\Client(); + try { + $this->response = $client->request($method, $this->baseUrl . '/remote.php/dav/files/' . $user . '/' . $path, [ + 'auth' => [$user === 'admin' ? 'admin' : $user, $user === 'admin' ? $this->adminPassword : $this->testPassword], + 'headers' => $headers, + 'body' => $body, + ]); + } catch (\GuzzleHttp\Exception\ClientException $ex) { + $this->response = $ex->getResponse(); + } + }); } private function parseXml(): \SimpleXMLElement { diff --git a/tests/integration/features/bootstrap/TransientConnectionRetry.php b/tests/integration/features/bootstrap/TransientConnectionRetry.php new file mode 100644 index 0000000000..465917386d --- /dev/null +++ b/tests/integration/features/bootstrap/TransientConnectionRetry.php @@ -0,0 +1,50 @@ += $maxAttempts) { + throw $exception; + } + fwrite( + STDERR, + sprintf( + "[behat] transient connection error (attempt %d/%d): %s\n", + $attempt, + $maxAttempts, + $exception->getMessage() + ) + ); + usleep($baseDelayMicros * $attempt); + } + } + } +} diff --git a/tests/integration/features/bootstrap/TransientConnectionRetryTest.php b/tests/integration/features/bootstrap/TransientConnectionRetryTest.php new file mode 100644 index 0000000000..9931e4b992 --- /dev/null +++ b/tests/integration/features/bootstrap/TransientConnectionRetryTest.php @@ -0,0 +1,76 @@ +assertSame('ok', $result); + $this->assertSame(1, $calls); + } + + public function testRetriesConnectExceptionThenSucceeds(): void { + $calls = 0; + $result = TransientConnectionRetry::run(function () use (&$calls): string { + $calls++; + if ($calls < 3) { + throw new ConnectException( + 'cURL error 52: Empty reply from server', + new Request('POST', 'http://localhost/ocs/v2.php/apps/libresign/api/v1/request-signature') + ); + } + return 'recovered'; + }, 3, 1); + + $this->assertSame('recovered', $result); + $this->assertSame(3, $calls); + } + + public function testRethrowsAfterExhaustingAttempts(): void { + $calls = 0; + $this->expectException(ConnectException::class); + $this->expectExceptionMessage('Empty reply from server'); + + try { + TransientConnectionRetry::run(function () use (&$calls): void { + $calls++; + throw new ConnectException( + 'cURL error 52: Empty reply from server', + new Request('POST', 'http://localhost/test') + ); + }, 3, 1); + } finally { + $this->assertSame(3, $calls); + } + } + + public function testDoesNotRetryNonConnectExceptions(): void { + $calls = 0; + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('boom'); + + try { + TransientConnectionRetry::run(function () use (&$calls): void { + $calls++; + throw new RuntimeException('boom'); + }, 3, 1); + } finally { + $this->assertSame(1, $calls); + } + } +} From 8867938363d7677745dfe167d6975929ef75632d Mon Sep 17 00:00:00 2001 From: Luis Amorim Date: Fri, 18 Sep 2026 22:55:07 -0300 Subject: [PATCH 07/10] revert(behat): remove TransientConnectionRetry workaround Retrying around cURL 52/7 hides the real failure and is unsafe for POST requests such as request-signature. Prefer diagnosing PHP built-in server death via behat-builtin-extension verbose mode. Signed-off-by: Luis Amorim --- .../features/bootstrap/FeatureContext.php | 37 +++------ .../bootstrap/TransientConnectionRetry.php | 50 ------------ .../TransientConnectionRetryTest.php | 76 ------------------- 3 files changed, 10 insertions(+), 153 deletions(-) delete mode 100644 tests/integration/features/bootstrap/TransientConnectionRetry.php delete mode 100644 tests/integration/features/bootstrap/TransientConnectionRetryTest.php diff --git a/tests/integration/features/bootstrap/FeatureContext.php b/tests/integration/features/bootstrap/FeatureContext.php index cdde146871..1c36617716 100644 --- a/tests/integration/features/bootstrap/FeatureContext.php +++ b/tests/integration/features/bootstrap/FeatureContext.php @@ -6,7 +6,6 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -use Behat\Gherkin\Node\PyStringNode; use Behat\Gherkin\Node\TableNode; use Behat\Hook\AfterSuite; use Behat\Hook\BeforeScenario; @@ -54,20 +53,6 @@ protected function beforeRequest(string $fullUrl, array $options): array { return [$fullUrl, $options]; } - /** - * Retry transient connection drops from the PHP built-in server (cURL 52). - * - * Intentionally has no #[Given] attribute: Behat keeps the parent step - * definition, and PHP dispatches to this override on the FeatureContext instance. - * - * @param TableNode|PyStringNode|array|null $body - */ - public function sendRequest(string $verb, string $url, $body = null, array $headers = [], array $options = []): void { - TransientConnectionRetry::run( - fn () => parent::sendRequest($verb, $url, $body, $headers, $options) - ); - } - protected function parseText(string $text): string { $fields = $this->fields; $fields['BASE_URL'] = $this->baseUrl . '/index.php'; @@ -244,18 +229,16 @@ public function fetchWebDavPropertyTo(string $property, string $alias): void { } private function davRequest(string $user, string $method, string $path, ?string $body = null, array $headers = []): void { - TransientConnectionRetry::run(function () use ($user, $method, $path, $body, $headers): void { - $client = new \GuzzleHttp\Client(); - try { - $this->response = $client->request($method, $this->baseUrl . '/remote.php/dav/files/' . $user . '/' . $path, [ - 'auth' => [$user === 'admin' ? 'admin' : $user, $user === 'admin' ? $this->adminPassword : $this->testPassword], - 'headers' => $headers, - 'body' => $body, - ]); - } catch (\GuzzleHttp\Exception\ClientException $ex) { - $this->response = $ex->getResponse(); - } - }); + $client = new \GuzzleHttp\Client(); + try { + $this->response = $client->request($method, $this->baseUrl . '/remote.php/dav/files/' . $user . '/' . $path, [ + 'auth' => [$user === 'admin' ? 'admin' : $user, $user === 'admin' ? $this->adminPassword : $this->testPassword], + 'headers' => $headers, + 'body' => $body, + ]); + } catch (\GuzzleHttp\Exception\ClientException $ex) { + $this->response = $ex->getResponse(); + } } private function parseXml(): \SimpleXMLElement { diff --git a/tests/integration/features/bootstrap/TransientConnectionRetry.php b/tests/integration/features/bootstrap/TransientConnectionRetry.php deleted file mode 100644 index 465917386d..0000000000 --- a/tests/integration/features/bootstrap/TransientConnectionRetry.php +++ /dev/null @@ -1,50 +0,0 @@ -= $maxAttempts) { - throw $exception; - } - fwrite( - STDERR, - sprintf( - "[behat] transient connection error (attempt %d/%d): %s\n", - $attempt, - $maxAttempts, - $exception->getMessage() - ) - ); - usleep($baseDelayMicros * $attempt); - } - } - } -} diff --git a/tests/integration/features/bootstrap/TransientConnectionRetryTest.php b/tests/integration/features/bootstrap/TransientConnectionRetryTest.php deleted file mode 100644 index 9931e4b992..0000000000 --- a/tests/integration/features/bootstrap/TransientConnectionRetryTest.php +++ /dev/null @@ -1,76 +0,0 @@ -assertSame('ok', $result); - $this->assertSame(1, $calls); - } - - public function testRetriesConnectExceptionThenSucceeds(): void { - $calls = 0; - $result = TransientConnectionRetry::run(function () use (&$calls): string { - $calls++; - if ($calls < 3) { - throw new ConnectException( - 'cURL error 52: Empty reply from server', - new Request('POST', 'http://localhost/ocs/v2.php/apps/libresign/api/v1/request-signature') - ); - } - return 'recovered'; - }, 3, 1); - - $this->assertSame('recovered', $result); - $this->assertSame(3, $calls); - } - - public function testRethrowsAfterExhaustingAttempts(): void { - $calls = 0; - $this->expectException(ConnectException::class); - $this->expectExceptionMessage('Empty reply from server'); - - try { - TransientConnectionRetry::run(function () use (&$calls): void { - $calls++; - throw new ConnectException( - 'cURL error 52: Empty reply from server', - new Request('POST', 'http://localhost/test') - ); - }, 3, 1); - } finally { - $this->assertSame(3, $calls); - } - } - - public function testDoesNotRetryNonConnectExceptions(): void { - $calls = 0; - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('boom'); - - try { - TransientConnectionRetry::run(function () use (&$calls): void { - $calls++; - throw new RuntimeException('boom'); - }, 3, 1); - } finally { - $this->assertSame(1, $calls); - } - } -} From 64d856041127f8d8051f44eceb37690cc16739f7 Mon Sep 17 00:00:00 2001 From: Luis Amorim Date: Sun, 20 Sep 2026 16:09:58 -0300 Subject: [PATCH 08/10] chore(behat): bump nextcloud-behat to 1.6.1 for builtin 0.7.0 chore(behat): bump nextcloud-behat to 1.6.1 for builtin 0.7.0 Unlock verbose PHP built-in server diagnostics so Behat CI can show PID, logs, and exit status on cURL 52 flakes. Signed-off-by: Luis Amorim [skip ci] --- tests/integration/composer.lock | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/tests/integration/composer.lock b/tests/integration/composer.lock index d3e9b43c47..12cbc02a09 100644 --- a/tests/integration/composer.lock +++ b/tests/integration/composer.lock @@ -905,16 +905,16 @@ }, { "name": "libresign/behat-builtin-extension", - "version": "v0.6.3", + "version": "v0.7.0", "source": { "type": "git", "url": "https://github.com/LibreSign/behat-builtin-extension.git", - "reference": "d9ddb309bbf36826f47318ca74d67c9e6c5714f9" + "reference": "fbb27f1573ab646f774daab8f4a6c5d5e2c25102" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/LibreSign/behat-builtin-extension/zipball/d9ddb309bbf36826f47318ca74d67c9e6c5714f9", - "reference": "d9ddb309bbf36826f47318ca74d67c9e6c5714f9", + "url": "https://api.github.com/repos/LibreSign/behat-builtin-extension/zipball/fbb27f1573ab646f774daab8f4a6c5d5e2c25102", + "reference": "fbb27f1573ab646f774daab8f4a6c5d5e2c25102", "shasum": "" }, "require": { @@ -945,9 +945,9 @@ "description": "Behat extension to run php built-in web server", "support": { "issues": "https://github.com/LibreSign/behat-builtin-extension/issues", - "source": "https://github.com/LibreSign/behat-builtin-extension/tree/v0.6.3" + "source": "https://github.com/LibreSign/behat-builtin-extension/tree/v0.7.0" }, - "time": "2024-10-31T18:50:11+00:00" + "time": "2026-09-19T19:17:52+00:00" }, { "name": "libresign/behat-tsa-extension", @@ -1128,24 +1128,24 @@ }, { "name": "libresign/nextcloud-behat", - "version": "v1.6.0", + "version": "v1.6.1", "source": { "type": "git", "url": "https://github.com/LibreSign/nextcloud-behat.git", - "reference": "617ffaae34ebf0e3794a09123a376c2598008f48" + "reference": "c650035f5e61d19110b8d62ca4a8a073ad7fad12" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/LibreSign/nextcloud-behat/zipball/617ffaae34ebf0e3794a09123a376c2598008f48", - "reference": "617ffaae34ebf0e3794a09123a376c2598008f48", + "url": "https://api.github.com/repos/LibreSign/nextcloud-behat/zipball/c650035f5e61d19110b8d62ca4a8a073ad7fad12", + "reference": "c650035f5e61d19110b8d62ca4a8a073ad7fad12", "shasum": "" }, "require": { "behat/behat": "^3.29", "estahn/json-query-wrapper": "*", "guzzlehttp/guzzle": "^7.10", - "libresign/behat-builtin-extension": "^0.6.3", - "php": ">=8.1", + "libresign/behat-builtin-extension": "^0.7.0", + "php": ">=8.2", "phpunit/phpunit": "*" }, "require-dev": { @@ -1182,9 +1182,9 @@ ], "support": { "issues": "https://github.com/LibreSign/nextcloud-behat/issues", - "source": "https://github.com/LibreSign/nextcloud-behat/tree/v1.6.0" + "source": "https://github.com/LibreSign/nextcloud-behat/tree/v1.6.1" }, - "time": "2026-04-11T00:33:49+00:00" + "time": "2026-09-20T16:52:51+00:00" }, { "name": "myclabs/deep-copy", From 9f48f90270f3a470aaf6e641ce0d88c958b5e9b1 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Sun, 20 Sep 2026 17:21:52 -0300 Subject: [PATCH 09/10] fix(behat): complete #8430 backport for stable33 Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- tests/integration/config/behat.yml | 2 +- .../features/account/signature.feature | 2 +- .../features/bootstrap/FeatureContext.php | 7 ++- .../features/bootstrap/FixtureHttpServer.php | 2 +- .../features/file/envelope.feature | 6 +- tests/integration/features/file/list.feature | 12 ++-- .../features/file/thumbnail.feature | 2 +- .../features/file/validate.feature | 6 +- .../custom_message_notification.feature | 8 +-- .../features/page/validate.feature | 4 +- .../integration/features/sign/cancel.feature | 8 +-- .../integration/features/sign/request.feature | 55 ++++++++++++------- .../features/sign/sequential_signing.feature | 4 +- .../integration/features/sign/signed.feature | 8 +-- tests/integration/features/sign/tsa.feature | 4 +- 15 files changed, 73 insertions(+), 57 deletions(-) diff --git a/tests/integration/config/behat.yml b/tests/integration/config/behat.yml index 0b3b9cbfd2..3a38b111f3 100644 --- a/tests/integration/config/behat.yml +++ b/tests/integration/config/behat.yml @@ -18,7 +18,7 @@ default: runAs: www-data # Keep the minimum workers needed for occasional nested HTTP to the same # PHP built-in server (single-process deadlocks on self-requests). Prefer - # inline fixtures over url→/develop/pdf to avoid load-driven + # inline fixtures over url→/develop/pdf to avoid load-driven # cURL 52 flakes from higher PHP_CLI_SERVER_WORKERS values. workers: 2 jarnaiz\JUnitFormatter\JUnitFormatterExtension: diff --git a/tests/integration/features/account/signature.feature b/tests/integration/features/account/signature.feature index 014c12c23b..361bec1fd4 100644 --- a/tests/integration/features/account/signature.feature +++ b/tests/integration/features/account/signature.feature @@ -257,7 +257,7 @@ Feature: account/signature And sending "post" to ocs "/apps/provisioning_api/api/v1/config/apps/libresign/identify_methods" | value | (string)[{"name":"email","enabled":true,"mandatory":true,"can_create_account":false}] | And sending "post" to ocs "/apps/libresign/api/v1/request-signature" - | file | {"url":"/apps/libresign/develop/pdf"} | + | file | {"base64":""} | | signers | [{"identifyMethods":[{"method":"email","value":"signer@test.coop"}]}] | | name | document | When as user "" diff --git a/tests/integration/features/bootstrap/FeatureContext.php b/tests/integration/features/bootstrap/FeatureContext.php index 1c36617716..c4b5eb9c58 100644 --- a/tests/integration/features/bootstrap/FeatureContext.php +++ b/tests/integration/features/bootstrap/FeatureContext.php @@ -28,6 +28,7 @@ public static function beforeSuite(BeforeSuiteScope $scope):void { parent::beforeSuite($scope); FixtureHttpServer::start(); self::runCommand('config:system:set debug --value true --type boolean'); + self::runCommand('config:system:set allow_local_remote_servers --value true --type boolean'); self::runCommand('app:enable --force notifications'); } @@ -57,6 +58,8 @@ protected function parseText(string $text): string { $fields = $this->fields; $fields['BASE_URL'] = $this->baseUrl . '/index.php'; $fields['TSA_URL'] = getenv('LIBRESIGN_TSA_URL') ?: 'https://freetsa.org/tsr'; + $fields['SMALL_VALID_PDF_BASE64'] = $this->getSmallValidPdfBase64(); + $fields['SMALL_VALID_PDF_URL'] = FixtureHttpServer::getSmallValidPdfUrl(); foreach ($fields as $key => $value) { $patterns[] = '/<' . $key . '>/'; $replacements[] = $value; @@ -69,10 +72,10 @@ protected function parseText(string $text): string { /** * Inline PDF fixture for Behat requests that do not need to exercise url download. * - * Prefer this over url→the Behat Nextcloud server. For url coverage use , + * Prefer this over url→the Behat Nextcloud server. For url coverage use , * which is served by FixtureHttpServer on a separate local port. */ - private function getDemoPdfBase64(): string { + private function getSmallValidPdfBase64(): string { $pdfPath = __DIR__ . '/../../../php/fixtures/pdfs/small_valid.pdf'; $contents = file_get_contents($pdfPath); Assert::assertNotFalse($contents, 'Demo PDF fixture missing: ' . $pdfPath); diff --git a/tests/integration/features/bootstrap/FixtureHttpServer.php b/tests/integration/features/bootstrap/FixtureHttpServer.php index c8f0ae7229..11b561ee1b 100644 --- a/tests/integration/features/bootstrap/FixtureHttpServer.php +++ b/tests/integration/features/bootstrap/FixtureHttpServer.php @@ -65,7 +65,7 @@ public static function stop(): void { self::$port = 0; } - public static function getPdfUrl(): string { + public static function getSmallValidPdfUrl(): string { if (!self::isRunning() || self::$port <= 0) { throw new RuntimeException('Fixture HTTP server is not running.'); } diff --git a/tests/integration/features/file/envelope.feature b/tests/integration/features/file/envelope.feature index f8da38ead1..212780f254 100644 --- a/tests/integration/features/file/envelope.feature +++ b/tests/integration/features/file/envelope.feature @@ -6,7 +6,7 @@ Feature: envelope And sending "post" to ocs "/apps/libresign/api/v1/admin/certificate/openssl" | rootCert | {"commonName":"test"} | When sending "post" to ocs "/apps/libresign/api/v1/file" - | files | [{"url":"/apps/libresign/develop/pdf"},{"url":"/apps/libresign/develop/pdf"}] | + | files | [{"base64":""},{"base64":""}] | | name | Contract Package | Then the response should have a status code 422 And the response should be a JSON array with the following mandatory values @@ -43,7 +43,7 @@ Feature: envelope And sending "post" to ocs "/apps/libresign/api/v1/admin/certificate/openssl" | rootCert | {"commonName":"test"} | When sending "post" to ocs "/apps/libresign/api/v1/file" - | files | [{"url":"/apps/libresign/develop/pdf"},{"url":"/apps/libresign/develop/pdf"},{"url":"/apps/libresign/develop/pdf"}] | + | files | [{"base64":""},{"base64":""},{"base64":""}] | | name | Too Many Files | Then the response should have a status code 422 And the response should be a JSON array with the following mandatory values @@ -55,7 +55,7 @@ Feature: envelope And sending "post" to ocs "/apps/libresign/api/v1/admin/certificate/openssl" | rootCert | {"commonName":"test"} | When sending "post" to ocs "/apps/libresign/api/v1/file" - | file | {"url":"/apps/libresign/develop/pdf"} | + | file | {"base64":""} | | name | Single Document | Then the response should have a status code 200 And the response should be a JSON array with the following mandatory values diff --git a/tests/integration/features/file/list.feature b/tests/integration/features/file/list.feature index 7016abfbc5..268c775423 100644 --- a/tests/integration/features/file/list.feature +++ b/tests/integration/features/file/list.feature @@ -6,7 +6,7 @@ Feature: file-list And set the email of user "signer1" to "signer1@domain.test" And set the email of user "signer2" to "" And sending "post" to ocs "/apps/libresign/api/v1/request-signature" - | file | {"url":"/apps/libresign/develop/pdf"} | + | file | {"base64":""} | | signers | [{"identifyMethods":[{"method":"email","value":"signer1@domain.test"}]},{"identifyMethods":[{"method":"account","value":"signer2"}]}] | | name | document | And the response should have a status code 200 @@ -47,25 +47,25 @@ Feature: file-list And sending "post" to ocs "/apps/provisioning_api/api/v1/config/apps/libresign/identify_methods" | value | (string)[{"name":"email","enabled":true,"mandatory":true,"can_create_account":false}] | And sending "post" to ocs "/apps/libresign/api/v1/request-signature" - | file | {"url":"/apps/libresign/develop/pdf"} | + | file | {"base64":""} | | signers | [{"identifyMethods":[{"method":"email","value":"signer1@domain.test"}]}] | | name | document | And the response should have a status code 200 And sending "post" to ocs "/apps/libresign/api/v1/request-signature" - | file | {"url":"/apps/libresign/develop/pdf"} | + | file | {"base64":""} | | signers | [{"identifyMethods":[{"method":"email","value":"signer1@domain.test"}]}] | | name | document | And the response should have a status code 200 And sending "post" to ocs "/apps/libresign/api/v1/request-signature" - | file | {"url":"/apps/libresign/develop/pdf"} | + | file | {"base64":""} | | signers | [{"identifyMethods":[{"method":"email","value":"signer1@domain.test"}]}] | | name | document | And sending "post" to ocs "/apps/libresign/api/v1/request-signature" - | file | {"url":"/apps/libresign/develop/pdf"} | + | file | {"base64":""} | | signers | [{"identifyMethods":[{"method":"email","value":"signer1@domain.test"}]}] | | name | document | And sending "post" to ocs "/apps/libresign/api/v1/request-signature" - | file | {"url":"/apps/libresign/develop/pdf"} | + | file | {"base64":""} | | signers | [{"identifyMethods":[{"method":"email","value":"signer1@domain.test"}]}] | | name | document | And the response should have a status code 200 diff --git a/tests/integration/features/file/thumbnail.feature b/tests/integration/features/file/thumbnail.feature index 4a40958f81..5194fa9449 100644 --- a/tests/integration/features/file/thumbnail.feature +++ b/tests/integration/features/file/thumbnail.feature @@ -6,7 +6,7 @@ Feature: file-thumbnail And sending "post" to ocs "/apps/provisioning_api/api/v1/config/apps/libresign/identify_methods" | value | (string)[{"name":"account","enabled":true,"mandatory":true}] | And sending "post" to ocs "/apps/libresign/api/v1/request-signature" - | file | {"url":"/apps/libresign/develop/pdf"} | + | file | {"base64":""} | | signers | [{"identifyMethods":[{"method":"account","value":"signer1"}]}] | | name | document | And the response should have a status code 200 diff --git a/tests/integration/features/file/validate.feature b/tests/integration/features/file/validate.feature index 46bce3182d..afdab13745 100644 --- a/tests/integration/features/file/validate.feature +++ b/tests/integration/features/file/validate.feature @@ -5,7 +5,7 @@ Feature: validate And sending "post" to ocs "/apps/libresign/api/v1/admin/certificate/openssl" | rootCert | {"commonName":"test"} | When sending "post" to ocs "/apps/libresign/api/v1/request-signature" - | file | {"url":"/apps/libresign/develop/pdf"} | + | file | {"base64":""} | | signers | [{"identifyMethods":[{"method":"account","value":"admin"}]},{"identifyMethods":[{"method":"account","value":"signer1"}]}] | | status | 0 | | name | Visible elements validation | @@ -71,7 +71,7 @@ Feature: validate | value | (string)[{"name":"account","enabled":true,"mandatory":true,"signatureMethods":{"clickToSign":{"enabled":true}}}] | And user "signer1" exists When sending "post" to ocs "/apps/libresign/api/v1/request-signature" - | file | {"url":"/apps/libresign/develop/pdf"} | + | file | {"base64":""} | | signers | [{"identifyMethods":[{"method":"account","value":"signer1"}]}] | | name | Document Name | Then the response should have a status code 200 @@ -115,7 +115,7 @@ Feature: validate And sending "delete" to ocs "/apps/provisioning_api/api/v1/config/apps/libresign/make_validation_url_private" When sending "post" to ocs "/apps/libresign/api/v1/request-signature" - | file | {"url":"/apps/libresign/develop/pdf"} | + | file | {"base64":""} | | signers | [{"identifyMethods":[{"method":"account","value":"admin"}]}] | | name | document | And the response should have a status code 200 diff --git a/tests/integration/features/notification/custom_message_notification.feature b/tests/integration/features/notification/custom_message_notification.feature index 014f7ad880..04b1d5819a 100644 --- a/tests/integration/features/notification/custom_message_notification.feature +++ b/tests/integration/features/notification/custom_message_notification.feature @@ -15,7 +15,7 @@ Feature: Custom message for signers Scenario: Account method - default message without custom description When sending "post" to ocs "/apps/libresign/api/v1/request-signature" - | file | {"url":"/apps/libresign/develop/pdf"} | + | file | {"base64":""} | | name | Document without custom message | | signers | [{"identifyMethods":[{"method":"account","value":"signer1"}]}] | Then the response should have a status code 200 @@ -25,7 +25,7 @@ Feature: Custom message for signers Scenario: Account method - custom description in email When sending "post" to ocs "/apps/libresign/api/v1/request-signature" - | file | {"url":"/apps/libresign/develop/pdf"} | + | file | {"base64":""} | | name | Document with custom message | | signers | [{"identifyMethods":[{"method":"account","value":"signer1"}],"description":"Please review section 3 and the appendix before signing."}] | Then the response should have a status code 200 @@ -36,14 +36,14 @@ Feature: Custom message for signers Scenario: Email method - default notification When sending "post" to ocs "/apps/libresign/api/v1/request-signature" - | file | {"url":"/apps/libresign/develop/pdf"} | + | file | {"base64":""} | | name | Document for email method | | signers | [{"identifyMethods":[{"method":"email","value":"external@domain.test"}],"displayName":"External Signer"}] | Then the response should have a status code 200 Scenario: Email method - custom description via reminder Given sending "post" to ocs "/apps/libresign/api/v1/request-signature" - | file | {"url":"/apps/libresign/develop/pdf"} | + | file | {"base64":""} | | name | Document for email with description | | signers | [{"identifyMethods":[{"method":"email","value":"external@domain.test"}],"displayName":"External Signer","description":"Urgent: Please sign by end of day."}] | And the response should have a status code 200 diff --git a/tests/integration/features/page/validate.feature b/tests/integration/features/page/validate.feature index 5c2811b0e6..19512c1390 100644 --- a/tests/integration/features/page/validate.feature +++ b/tests/integration/features/page/validate.feature @@ -7,7 +7,7 @@ Feature: page/validate And sending "delete" to ocs "/apps/provisioning_api/api/v1/config/apps/libresign/make_validation_url_private" When sending "post" to ocs "/apps/libresign/api/v1/request-signature" - | file | {"url":"/apps/libresign/develop/pdf"} | + | file | {"base64":""} | | signers | [{"identifyMethods":[{"method":"account","value":"admin"}]}] | | name | document | And the response should have a status code 200 @@ -75,7 +75,7 @@ Feature: page/validate | value | (string)[{"name":"email","enabled":true,"mandatory":true,"signatureMethods":{"clickToSign":{"enabled":true}},"can_create_account":false}] | And my inbox is empty When sending "post" to ocs "/apps/libresign/api/v1/request-signature" - | file | {"url":"/apps/libresign/develop/pdf"} | + | file | {"base64":""} | | signers | [{"displayName":"External Signer","identifyMethods":[{"method":"email","value":"external@domain.test"}]}] | | name | external-email-pdf | | settings | {"folderName":"rm-target-folder"} | diff --git a/tests/integration/features/sign/cancel.feature b/tests/integration/features/sign/cancel.feature index 6f09322047..92a8108189 100644 --- a/tests/integration/features/sign/cancel.feature +++ b/tests/integration/features/sign/cancel.feature @@ -5,7 +5,7 @@ Feature: sign-request-cancel And reset notifications of user "signer1" And run the command "libresign:configure:openssl --cn test" with result code 0 And sending "post" to ocs "/apps/libresign/api/v1/request-signature" - | file | {"url":"/apps/libresign/develop/pdf"} | + | file | {"base64":""} | | signers | [{"identifyMethods":[{"method":"account","value":"signer1"}]}] | | name | document | And the response should have a status code 200 @@ -26,7 +26,7 @@ Feature: sign-request-cancel And reset notifications of user "signer1" And run the command "libresign:configure:openssl --cn test" with result code 0 And sending "post" to ocs "/apps/libresign/api/v1/request-signature" - | file | {"url":"/apps/libresign/develop/pdf"} | + | file | {"base64":""} | | signers | [{"identifyMethods":[{"method":"account","value":"signer1"}],"notify":false}] | | name | document | | status | 0 | @@ -47,7 +47,7 @@ Feature: sign-request-cancel And user "signer1" exists And run the command "libresign:configure:openssl --cn test" with result code 0 And sending "post" to ocs "/apps/libresign/api/v1/request-signature" - | file | {"url":"/apps/libresign/develop/pdf"} | + | file | {"base64":""} | | signers | [{"identifyMethods":[{"method":"account","value":"signer1"}]}] | | name | document | When sending "get" to ocs "/apps/libresign/api/v1/file/list?details=1" @@ -63,7 +63,7 @@ Feature: sign-request-cancel And user "signer1" exists And run the command "libresign:configure:openssl --cn test" with result code 0 And sending "post" to ocs "/apps/libresign/api/v1/request-signature" - | file | {"url":"/apps/libresign/develop/pdf"} | + | file | {"base64":""} | | signers | [{"identifyMethods":[{"method":"account","value":"signer1"}]}] | | name | document | And the response should have a status code 200 diff --git a/tests/integration/features/sign/request.feature b/tests/integration/features/sign/request.feature index bc03c46f77..d061d5a2be 100644 --- a/tests/integration/features/sign/request.feature +++ b/tests/integration/features/sign/request.feature @@ -42,7 +42,7 @@ Feature: request-signature And sending "post" to ocs "/apps/libresign/api/v1/admin/certificate/openssl" | rootCert | {"commonName":"test"} | And sending "post" to ocs "/apps/libresign/api/v1/request-signature" - | file | {"url":"/apps/libresign/develop/pdf"} | + | file | {"base64":""} | | signers | [{"identifyMethods":[{"method":"account","value":"signer1"}]}] | | name | document | And the response should have a status code 200 @@ -79,7 +79,7 @@ Feature: request-signature And sending "post" to ocs "/apps/libresign/api/v1/admin/certificate/openssl" | rootCert | {"commonName":"test"} | And sending "post" to ocs "/apps/libresign/api/v1/request-signature" - | file | {"url":"/apps/libresign/develop/pdf"} | + | file | {"base64":""} | | signers | [{"identifyMethods":[{"method":"account","value":"signer1"}]}] | | name | document | And the response should have a status code 200 @@ -109,7 +109,7 @@ Feature: request-signature And reset notifications of user "signer1" And my inbox is empty And sending "post" to ocs "/apps/libresign/api/v1/request-signature" - | file | {"url":"/apps/libresign/develop/pdf"} | + | file | {"base64":""} | | signers | [{"identifyMethods":[{"method":"account","value":"signer1"}]}] | | name | document | Then the response should have a status code 200 @@ -132,7 +132,7 @@ Feature: request-signature And set the email of user "signer1" to "signer1@domain.test" And my inbox is empty And sending "post" to ocs "/apps/libresign/api/v1/request-signature" - | file | {"url":"/apps/libresign/develop/pdf"} | + | file | {"base64":""} | | signers | [{"identifyMethods":[{"method":"email","value":"signer1@domain.test"}]}] | | name | document | Then the response should have a status code 200 @@ -156,7 +156,7 @@ Feature: request-signature | rootCert | {"commonName":"test"} | And run the command "config:app:set libresign maximum_validity --value=1 --type=integer" with result code 0 When sending "post" to ocs "/apps/libresign/api/v1/request-signature" - | file | {"url":"/apps/libresign/develop/pdf"} | + | file | {"base64":""} | | signers | [{"identifyMethods":[{"method":"email","value":"signer2@domain.test"}]}] | | name | document | Then the response should have a status code 200 @@ -180,7 +180,7 @@ Feature: request-signature And sending "post" to ocs "/apps/provisioning_api/api/v1/config/apps/libresign/identify_methods" | value | (string)[{"name":"email","enabled":true,"mandatory":true,"can_create_account":false}] | And sending "post" to ocs "/apps/libresign/api/v1/request-signature" - | file | {"url":"/apps/libresign/develop/pdf"} | + | file | {"base64":""} | | signers | [{"identifyMethods":[{"method":"email","value":"signer2@domain.test"}]}] | | name | document | And the response should have a status code 200 @@ -232,7 +232,7 @@ Feature: request-signature And sending "post" to ocs "/apps/provisioning_api/api/v1/config/apps/libresign/identify_methods" | value | (string)[{"name":"account","enabled":true}] | When sending "post" to ocs "/apps/libresign/api/v1/request-signature" - | file | {"url":"/apps/libresign/develop/pdf"} | + | file | {"base64":""} | | signers | [{"identifyMethods":[{"method":"account","value":"signer2"}]}] | | name | document | Then the response should be a JSON array with the following mandatory values @@ -248,7 +248,7 @@ Feature: request-signature And reset notifications of user "signer1" And my inbox is empty When sending "post" to ocs "/apps/libresign/api/v1/request-signature" - | file | {"url":"/apps/libresign/develop/pdf"} | + | file | {"base64":""} | | signers | [{"identifyMethods":[{"method":"account","value":"signer1"}]}] | | name | document | Then the response should have a status code 200 @@ -284,7 +284,7 @@ Feature: request-signature And sending "post" to ocs "/apps/libresign/api/v1/admin/certificate/openssl" | rootCert | {"commonName":"test"} | When sending "post" to ocs "/apps/libresign/api/v1/request-signature" - | file | {"url":"/apps/libresign/develop/pdf"} | + | file | {"base64":""} | | signers | [{"identifyMethods":[{"method":"account","value":"invaliddomain.test"}]}] | | name | document | Then the response should have a status code 422 @@ -297,7 +297,7 @@ Feature: request-signature And sending "post" to ocs "/apps/libresign/api/v1/admin/certificate/openssl" | rootCert | {"commonName":"test"} | When sending "post" to ocs "/apps/libresign/api/v1/request-signature" - | file | {"url":"/apps/libresign/develop/pdf"} | + | file | {"base64":""} | | signers | [{"identifyMethods":[{"method":"account","value":"signer3@domain.test"}]}] | | name | document | Then the response should have a status code 422 @@ -312,7 +312,7 @@ Feature: request-signature | rootCert | {"commonName":"test"} | And my inbox is empty When sending "post" to ocs "/apps/libresign/api/v1/request-signature" - | file | {"url":"/apps/libresign/develop/pdf"} | + | file | {"base64":""} | | signers | [{"identifyMethods":[{"method":"email","value":"signer2@domain.test"}]}] | | name | document | Then the response should have a status code 200 @@ -328,7 +328,7 @@ Feature: request-signature And reset notifications of user "signer1" And my inbox is empty When sending "post" to ocs "/apps/libresign/api/v1/request-signature" - | file | {"url":"/apps/libresign/develop/pdf"} | + | file | {"base64":""} | | signers | [{"identifyMethods":[{"method":"account","value":"signer1"}]}] | | name | document | Then the response should have a status code 200 @@ -346,7 +346,7 @@ Feature: request-signature And set the email of user "signer1" to "signer1@domain.test" And my inbox is empty When sending "post" to ocs "/apps/libresign/api/v1/request-signature" - | file | {"url":"/apps/libresign/develop/pdf"} | + | file | {"base64":""} | | signers | [{"identifyMethods":[{"method":"email","value":"signer1@domain.test"}]}] | | name | document | Then the response should have a status code 200 @@ -360,7 +360,7 @@ Feature: request-signature And sending "post" to ocs "/apps/provisioning_api/api/v1/config/apps/libresign/identify_methods" | value | (string)[{"name":"email","enabled":true,"mandatory":true,"can_create_account":false}] | And I send a file to be signed - | file | {"url":"/apps/libresign/develop/pdf"} | + | file | {"base64":""} | | signers | [{"identifyMethods":[{"method":"email","value":"signer1@domain.test"}]}] | | status | 0 | | name | document | @@ -390,7 +390,7 @@ Feature: request-signature And set the email of user "signer1" to "" And my inbox is empty When sending "post" to ocs "/apps/libresign/api/v1/request-signature" - | file | {"url":"/apps/libresign/develop/pdf"} | + | file | {"base64":""} | | signers | [{"identifyMethods":[{"method":"email","value":"signer1@domain.test"}]},{"identifyMethods":[{"method":"account","value":"signer1"}]}] | | name | document | Then the response should have a status code 200 @@ -409,7 +409,7 @@ Feature: request-signature | value | (string)[{"name":"email","enabled":true,"mandatory":true,"signatureMethods":{"emailToken":{"enabled":true}},"can_create_account":false}] | And my inbox is empty When sending "post" to ocs "/apps/libresign/api/v1/request-signature" - | file | {"url":"/apps/libresign/develop/pdf"} | + | file | {"base64":""} | | signers | [{"identifyMethods":[{"method":"email","value":"11111@domain.test"}]},{"identifyMethods":[{"method":"email","value":"22222@domain.test"}]}] | | name | document | Then the response should have a status code 200 @@ -451,7 +451,7 @@ Feature: request-signature | value | (string)[{"name":"email","enabled":true,"mandatory":true,"signatureMethods":{"emailToken":{"enabled":true}},"can_create_account":false}] | And my inbox is empty When sending "post" to ocs "/apps/libresign/api/v1/request-signature" - | file | {"url":"/apps/libresign/develop/pdf"} | + | file | {"base64":""} | | signers | [{"identifyMethods":[{"method":"email","value":"11111@domain.test"}]}] | | name | document | Then the response should have a status code 200 @@ -496,7 +496,7 @@ Feature: request-signature | value | (string)[{"name":"email","enabled":true,"mandatory":true,"signatureMethods":{"emailToken":{"enabled":true}},"can_create_account":false}] | And my inbox is empty When sending "post" to ocs "/apps/libresign/api/v1/request-signature" - | file | {"url":"/apps/libresign/develop/pdf"} | + | file | {"base64":""} | | signers | [{"identifyMethods":[{"method":"email","value":"11111@domain.test"}]}] | | name | document | Then the response should have a status code 200 @@ -535,7 +535,7 @@ Feature: request-signature And user "signer1" exists And as user "admin" When I send a file to be signed - | file | {"url":"/apps/libresign/develop/pdf"} | + | file | {"base64":""} | | signers | [{"identifyMethods":[{"method":"email","value":"signer1@domain.test"}]},{"identifyMethods":[{"method":"account","value":"signer1"}]}] | | name | document | And fetch field "(FILE_UUID)ocs.data.uuid" from previous JSON response @@ -576,14 +576,27 @@ Feature: request-signature And sending "post" to ocs "/apps/provisioning_api/api/v1/config/apps/libresign/identify_methods" | value | (string)[{"name":"email","enabled":true,"mandatory":true,"signatureMethods":{"emailToken":{"enabled":true}}}] | When sending "post" to ocs "/apps/libresign/api/v1/request-signature" - | file | {"url":"/apps/libresign/develop/pdf"} | + | file | {"base64":""} | | signers | [{"identifyMethods":[{"method":"email","value":"signer1@domain.test"}]}] | | name | document | | status | 0 | And there should be 0 emails in my inbox When sending "post" to ocs "/apps/libresign/api/v1/request-signature" - | file | {"url":"/apps/libresign/develop/pdf"} | + | file | {"base64":""} | | signers | [{"identifyMethods":[{"method":"email","value":"signer1@domain.test"}]}] | | name | document | | status | 1 | And there should be 1 email in my inbox + + Scenario: Create signature request from a remote PDF URL + Given as user "admin" + And sending "post" to ocs "/apps/libresign/api/v1/admin/certificate/openssl" + | rootCert | {"commonName":"test"} | + When sending "post" to ocs "/apps/libresign/api/v1/request-signature" + | file | {"url":""} | + | signers | [{"identifyMethods":[{"method":"email","value":"signer-url@domain.test"}]}] | + | name | document-from-url | + Then the response should have a status code 200 + And the response should be a JSON array with the following mandatory values + | key | value | + | (jq).ocs.data.name | document-from-url | diff --git a/tests/integration/features/sign/sequential_signing.feature b/tests/integration/features/sign/sequential_signing.feature index fb2d6ca2fd..c32cfc55e5 100644 --- a/tests/integration/features/sign/sequential_signing.feature +++ b/tests/integration/features/sign/sequential_signing.feature @@ -14,7 +14,7 @@ Feature: sequential-signing Given user "signer1" exists And user "signer2" exists When sending "post" to ocs "/apps/libresign/api/v1/request-signature" - | file | {"url":"/apps/libresign/develop/pdf"} | + | file | {"base64":""} | | signers | [{"identifyMethods":[{"method":"account","value":"signer1"}]},{"identifyMethods":[{"method":"account","value":"signer2"}]}] | | name | Parallel Document | Then the response should have a status code 200 @@ -41,7 +41,7 @@ Feature: sequential-signing | mode | ordered_numeric | And the response should have a status code 200 When sending "post" to ocs "/apps/libresign/api/v1/request-signature" - | file | {"url":"/apps/libresign/develop/pdf"} | + | file | {"base64":""} | | signers | [{"identifyMethods":[{"method":"account","value":"signer1"}],"signingOrder":1},{"identifyMethods":[{"method":"account","value":"signer2"}],"signingOrder":2}] | | name | Sequential Document | Then the response should have a status code 200 diff --git a/tests/integration/features/sign/signed.feature b/tests/integration/features/sign/signed.feature index 8ac1ff9d5c..be6fcb063d 100644 --- a/tests/integration/features/sign/signed.feature +++ b/tests/integration/features/sign/signed.feature @@ -18,7 +18,7 @@ Feature: signed | value | (string)[{"name":"account","enabled":true,"mandatory":true,"signatureMethods":{"password":{"name":"password","enabled":true}},"signatureMethodEnabled":"password"}] | And the response should have a status code 200 When sending "post" to ocs "/apps/libresign/api/v1/request-signature" - | file | {"url":"/apps/libresign/develop/pdf"} | + | file | {"base64":""} | | signers | [{"displayName": "Signer Name","description": "Please, sign this document","identifyMethods": [{"method": "account", "value": "signer1"}]}] | | name | Document Name | And the response should have a status code 200 @@ -71,7 +71,7 @@ Feature: signed And reset notifications of user "admin" And reset activity of user "admin" When sending "post" to ocs "/apps/libresign/api/v1/request-signature" - | file | {"url":"/apps/libresign/develop/pdf"} | + | file | {"base64":""} | | signers | [{"displayName": "Signer Name","identifyMethods": [{"method": "account", "value": "signer1"}]},{"displayName": "Admin","identifyMethods": [{"method": "account", "value": "admin"}]}] | | name | Document Name | And the response should have a status code 200 @@ -136,7 +136,7 @@ Feature: signed And reset notifications of user "admin" And reset activity of user "admin" When sending "post" to ocs "/apps/libresign/api/v1/request-signature" - | file | {"url":"/apps/libresign/develop/pdf"} | + | file | {"base64":""} | | signers | [{"displayName": "Signer Name","identifyMethods": [{"method": "account", "value": "signer1"}]},{"displayName": "Admin","identifyMethods": [{"method": "account", "value": "admin"}]}] | | name | Document Name | And the response should have a status code 200 @@ -191,7 +191,7 @@ Feature: signed And reset notifications of user "admin" And reset activity of user "admin" When sending "post" to ocs "/apps/libresign/api/v1/request-signature" - | file | {"url":"/apps/libresign/develop/pdf"} | + | file | {"base64":""} | | signers | [{"displayName": "Signer Name","identifyMethods": [{"method": "email", "value": "unauthenticated@email.tld"}]}] | | name | Document Name | And the response should have a status code 200 diff --git a/tests/integration/features/sign/tsa.feature b/tests/integration/features/sign/tsa.feature index 71b83ddbbc..5950974b20 100644 --- a/tests/integration/features/sign/tsa.feature +++ b/tests/integration/features/sign/tsa.feature @@ -19,7 +19,7 @@ Feature: TSA Integration - End-to-End Workflow | value | (string)[{"name":"account","enabled":true,"mandatory":true,"signatureMethods":{"clickToSign":{"enabled":true}},"signatureMethodEnabled":"clickToSign"}] | And the response should have a status code 200 When sending "post" to ocs "/apps/libresign/api/v1/request-signature" - | file | {"url":"/apps/libresign/develop/pdf"} | + | file | {"base64":""} | | signers | [{"displayName": "TSA Signer","identifyMethods": [{"method": "account", "value": "signer1"}]}] | | name | TSA Document Test | Then the response should have a status code 200 @@ -61,7 +61,7 @@ Feature: TSA Integration - End-to-End Workflow And sending "post" to ocs "/apps/provisioning_api/api/v1/config/apps/libresign/identify_methods" | value | (string)[{"name":"account","enabled":true,"mandatory":true,"signatureMethods":{"clickToSign":{"enabled":true}},"signatureMethodEnabled":"clickToSign"}] | When sending "post" to ocs "/apps/libresign/api/v1/request-signature" - | file | {"url":"/apps/libresign/develop/pdf"} | + | file | {"base64":""} | | signers | [{"identifyMethods": [{"method": "account", "value": "signer1"}]}] | | name | TSA Error Test | And as user "signer1" From a9f41521164210c33ca6561632c7e9f895c3dea7 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Sun, 20 Sep 2026 17:49:05 -0300 Subject: [PATCH 10/10] fix(behat): complete envelope fixture backport for stable33 Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .../features/security/envelope_authorization.feature | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration/features/security/envelope_authorization.feature b/tests/integration/features/security/envelope_authorization.feature index ad1a595c2a..b079025be4 100644 --- a/tests/integration/features/security/envelope_authorization.feature +++ b/tests/integration/features/security/envelope_authorization.feature @@ -9,7 +9,7 @@ Feature: envelope authorization Scenario: A requester cannot modify another requester's draft envelope Given sending "post" to ocs "/apps/libresign/api/v1/file" - | files | [{"url":"/apps/libresign/develop/pdf","name":"Contract.pdf"},{"url":"/apps/libresign/develop/pdf","name":"Annex.pdf"}] | + | files | [{"base64":"","name":"Contract.pdf"},{"base64":"","name":"Annex.pdf"}] | | name | Owner Envelope | And the response should have a status code 200 And fetch field "(ENVELOPE_UUID)ocs.data.uuid" from previous JSON response @@ -22,7 +22,7 @@ Feature: envelope authorization Scenario: The envelope owner reaches upload validation Given sending "post" to ocs "/apps/libresign/api/v1/file" - | files | [{"url":"/apps/libresign/develop/pdf","name":"Contract.pdf"},{"url":"/apps/libresign/develop/pdf","name":"Annex.pdf"}] | + | files | [{"base64":"","name":"Contract.pdf"},{"base64":"","name":"Annex.pdf"}] | | name | Owner Envelope | And the response should have a status code 200 And fetch field "(ENVELOPE_UUID)ocs.data.uuid" from previous JSON response