From bd6e6365179fac7747427bf2456e0e8ad774ed08 Mon Sep 17 00:00:00 2001 From: Nicolas Joubert Date: Thu, 8 Oct 2026 12:11:41 +0200 Subject: [PATCH] #123 HTTP API: read the query string without body, and queue from the query string A request with the parameters in the query string only was refused (422 "Process code is required"), and ?queue=0 was ignored (the process was queued). The form data takes precedence over the query string. Co-Authored-By: Claude Opus 5.5 (1M context) --- CHANGELOG.md | 1 + docs/reference/06-http_api.md | 3 ++- .../HttpProcessExecuteValueResolver.php | 8 +++++-- tests/Functional/HttpProcessExecuteTest.php | 12 ++++++++++ .../HttpProcessExecuteValueResolverTest.php | 24 +++++++++++++++++++ 5 files changed, 45 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c5c14ce..86ec533 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ Latest * [#111](https://github.com/cleverage/ui-process-bundle/issues/111) `ProcessExecutionDurationFilter`: the filter values are bound as DQL parameters (they were concatenated in the DQL; not exploitable, the form field is numeric). Add tests. * [#113](https://github.com/cleverage/ui-process-bundle/issues/113) HTTP API, synchronous execution: the error message is followed by the id of the process execution (`(process execution: )`), to find its logs in the UI; add `ProcessExecutionManager::getLastProcessExecution()`. Add tests. * [#117](https://github.com/cleverage/ui-process-bundle/issues/117) Schedule validators: `every` expressions checked as the Symfony Scheduler does (`PeriodicalTrigger`): expressions such as `0 seconds`, `yesterday` or `monday` were accepted but stopped the `scheduler_cron` worker, ISO 8601 durations (`PT1H`) were refused; message fixed ("is not a valid \"every\" expression"). A "hashed" cron expression is refused (500). No `TypeError` on `null`. Add tests. +* [#123](https://github.com/cleverage/ui-process-bundle/issues/123) HTTP API: the parameters of the query string are also read without body, and `queue` is read from the query string (the form data takes precedence). Add tests. v3.0.2 ------ diff --git a/docs/reference/06-http_api.md b/docs/reference/06-http_api.md index a029c80..414abfb 100644 --- a/docs/reference/06-http_api.md +++ b/docs/reference/06-http_api.md @@ -35,7 +35,8 @@ Parameters can be sent either as a JSON body, or as form data (`application/x-ww | `context` | `object` or JSON `string` | | `{}` | Process context, as key/value pairs. With form data, send one field per value: `context[key]=value`. | | `queue` | `bool` | | `true` | `true`: the process is queued to the `execute_process` transport (see [messenger](08-messenger.md)). `false`: the process is executed during the HTTP request. | -With form data, `code` and `input` can also be passed in the query string. +`code`, `input`, `context` and `queue` can also be passed in the query string, with form data or without body (the form +data takes precedence). Responses --------- diff --git a/src/Http/ValueResolver/HttpProcessExecuteValueResolver.php b/src/Http/ValueResolver/HttpProcessExecuteValueResolver.php index 69e7ac6..9abedbd 100644 --- a/src/Http/ValueResolver/HttpProcessExecuteValueResolver.php +++ b/src/Http/ValueResolver/HttpProcessExecuteValueResolver.php @@ -36,8 +36,10 @@ public function resolve(Request $request, ArgumentMetadata $argument): iterable { try { $hasRequestData = $request->request->count() > 0 || $request->files->count() > 0; + // The query string is read with form data, and also without body + $hasQueryOnly = !$hasRequestData && '' === $request->getContent() && $request->query->count() > 0; - if (!$hasRequestData) { + if (!$hasRequestData && !$hasQueryOnly) { $content = $request->getContent(); if (empty($content)) { return [new HttpProcessExecution()]; @@ -65,7 +67,9 @@ public function resolve(Request $request, ArgumentMetadata $argument): iterable $context = $request->query->all('context'); } - $queue = $request->request->getBoolean('queue', true); + $queue = $request->request->has('queue') + ? $request->request->getBoolean('queue') + : $request->query->getBoolean('queue', true); $httpProcessExecution = new HttpProcessExecution( (string) $code, diff --git a/tests/Functional/HttpProcessExecuteTest.php b/tests/Functional/HttpProcessExecuteTest.php index e728574..32c04f0 100644 --- a/tests/Functional/HttpProcessExecuteTest.php +++ b/tests/Functional/HttpProcessExecuteTest.php @@ -106,6 +106,18 @@ public function testSynchronousExecution(): void self::assertSame([], $this->getDispatchedMessages()); } + /** + * Parameters in the query string only, without body (they were ignored: 422 "Process code is required"). + */ + public function testQueryStringWithoutBody(): void + { + $this->client->request('POST', '/http/process/execute?code=test.process&queue=0', server: ['HTTP_AUTHORIZATION' => 'Bearer '.self::TOKEN]); + + self::assertResponseIsSuccessful(); + self::assertSame('"Process has been proceed well."', $this->client->getResponse()->getContent()); + self::assertSame([], $this->getDispatchedMessages()); + } + public function testFailingSynchronousExecution(): void { $this->execute(['code' => 'test.failing', 'queue' => '0']); diff --git a/tests/Http/ValueResolver/HttpProcessExecuteValueResolverTest.php b/tests/Http/ValueResolver/HttpProcessExecuteValueResolverTest.php index 0e8e540..7dd08de 100644 --- a/tests/Http/ValueResolver/HttpProcessExecuteValueResolverTest.php +++ b/tests/Http/ValueResolver/HttpProcessExecuteValueResolverTest.php @@ -107,6 +107,30 @@ public function testQueryParametersWithFormData(): void ); } + public function testQueryParametersWithoutBody(): void + { + $request = Request::create('/http/process/execute?code=test.process&input=data.csv&context[foo]=bar&queue=0', 'POST'); + + self::assertEquals( + new HttpProcessExecution('test.process', 'data.csv', ['foo' => 'bar'], false), + $this->resolve($request) + ); + } + + public function testQueueFromTheQueryString(): void + { + $request = Request::create('/http/process/execute?queue=0', 'POST', ['code' => 'test.process']); + + self::assertEquals(new HttpProcessExecution('test.process', null, [], false), $this->resolve($request)); + } + + public function testQueueOfTheBodyOverridesTheQueryString(): void + { + $request = Request::create('/http/process/execute?queue=0', 'POST', ['code' => 'test.process', 'queue' => '1']); + + self::assertEquals(new HttpProcessExecution('test.process', null, [], true), $this->resolve($request)); + } + public function testUploadedFileInput(): void { $file = tempnam(sys_get_temp_dir(), 'upload');