From 982387ce91886969fca7e5f05fca460bf3d6d0b8 Mon Sep 17 00:00:00 2001 From: Nicolas Joubert Date: Wed, 30 Sep 2026 11:22:12 +0200 Subject: [PATCH 1/2] fix(task) #25 RequestTask throws an exception when the SOAP call fails, so that the error strategy applies (the process fails with the stop strategy, the error outputs receive the task input with the skip strategy) Co-Authored-By: Claude Opus 5.5 (1M context) --- CHANGELOG.md | 3 ++ docs/cookbooks/soap_enrich_csv.md | 7 +-- docs/reference/tasks/request_task.md | 14 ++--- src/Task/RequestTask.php | 11 ++-- tests/Task/RequestTaskTest.php | 77 ++++++++++++++++++++++++++++ 5 files changed, 94 insertions(+), 18 deletions(-) create mode 100644 tests/Task/RequestTaskTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index da99cde..f5b4e54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ Latest * [#20](https://github.com/cleverage/soap-process-bundle/issues/20) Update quality stack: use Rector `withComposerBased()` sets (removed `SYMFONY_64` / `PHPUNIT_100` sets), declare used Symfony packages and PHPUnit range in composer.json, apply quality tools fixes * [#22](https://github.com/cleverage/soap-process-bundle/issues/22) Add missing documentations: reference pages for Client, RequestTask & RequestTransformer, cookbooks. Harmonize and fix existing documentation. +### Fixes +* [#25](https://github.com/cleverage/soap-process-bundle/issues/25) RequestTask throws an exception when the SOAP call fails, so that the error strategy applies: the process now fails with the `stop` strategy, and the error outputs receive the task input (instead of `false`) with the `skip` strategy + v3.0 ------ diff --git a/docs/cookbooks/soap_enrich_csv.md b/docs/cookbooks/soap_enrich_csv.md index dc45fd8..2b7c73d 100644 --- a/docs/cookbooks/soap_enrich_csv.md +++ b/docs/cookbooks/soap_enrich_csv.md @@ -90,7 +90,7 @@ How it works: and [wrapper](https://github.com/cleverage/process-bundle/blob/main/docs/reference/transformers/wrapper_transformer.md) transformers: `FullCountryInfo` is a document/literal method expecting a single `parameters` structure. - The [RequestTask](../reference/tasks/request_task.md) calls `FullCountryInfo` with these arguments. With - `error_strategy: skip`, a failed call is logged by the task, `false` is sent to the + `error_strategy: skip`, a failed call is logged by the task, the SOAP arguments built from the line are sent to the [LoggerTask](https://github.com/cleverage/process-bundle/blob/main/docs/reference/tasks/logger_task.md) of the `error_outputs`, and the line is not written. - The second TransformerTask extracts the result from the `stdClass` response @@ -99,8 +99,9 @@ How it works: and maps the columns to write with the [CsvWriterTask](https://github.com/cleverage/process-bundle/blob/main/docs/reference/tasks/csv_writer_task.md). -Note that the error output of the RequestTask is `false`, not the CSV line: the details of the failed call (options, -last request and response) are in the log context of the `Empty resultset for query` error logged by the task. +Note that the error output of the RequestTask is its input (the SOAP arguments), not the CSV line: the details of the +failed call (options, last request and response) are in the log context of the `Empty resultset for query` error +logged by the task. To add a single value to the line instead of replacing it, the SOAP call can also be done inside a `mapping` with the [RequestTransformer](../reference/transformers/request_transformer.md) (`soap_request`), e.g. with `keep_input: true`: diff --git a/docs/reference/tasks/request_task.md b/docs/reference/tasks/request_task.md index 4b9ce58..81ffca6 100644 --- a/docs/reference/tasks/request_task.md +++ b/docs/reference/tasks/request_task.md @@ -25,7 +25,7 @@ Possible outputs `mixed`: the result of the SOAP call, usually a `stdClass` (or an array of `stdClass`) built by `SoapClient` from the response. -`false` when the call failed (`SoapFault`), see Notes. +When the call fails, an exception is thrown and the `error_strategy` applies, see Notes. Options ------- @@ -85,12 +85,12 @@ get_order: Notes ----- -* When the call fails (the client returns `false`, see [client](../client.md)), the task: - - logs an error `Empty resultset for query`, with the options and the last request and response in the log context - - sets `false` as error output, so the `error_outputs` tasks receive `false` (not the input of the task) - - with `error_strategy: skip`, skips the `outputs` tasks - - with `error_strategy: stop`, stops the process. No exception is set on the state, so the process is **not** - marked as failed and the console command does not return an error code +* When the call fails (the client returns `false`, see [client](../client.md)), the task logs an error + `Empty resultset for query`, with the options and the last request and response in the log context, then throws a + `RuntimeException` (`Soap call '' on client '' failed`), handled by the `error_strategy`: + - with `error_strategy: skip`, the `outputs` tasks are skipped and the `error_outputs` tasks receive the input of + the task + - with `error_strategy: stop`, the process fails and the console command returns an error code * An exception thrown by the client (e.g. a `SoapFault` when the WSDL cannot be loaded, or a `MissingClientException` for an unknown `client`) is handled as usual by the `error_strategy`. * `soap_call_options` and `soap_call_headers` are set on the client each time the task is executed, including when diff --git a/src/Task/RequestTask.php b/src/Task/RequestTask.php index 428e358..17065b8 100644 --- a/src/Task/RequestTask.php +++ b/src/Task/RequestTask.php @@ -13,7 +13,6 @@ namespace CleverAge\SoapProcessBundle\Task; -use CleverAge\ProcessBundle\Configuration\TaskConfiguration; use CleverAge\ProcessBundle\Model\AbstractConfigurableTask; use CleverAge\ProcessBundle\Model\ProcessState; use CleverAge\SoapProcessBundle\Registry\ClientRegistry; @@ -64,15 +63,11 @@ public function execute(ProcessState $state): void 'last_response_headers' => $client->getLastResponseHeaders(), ]; - $state->setErrorOutput($result); - $this->logger->error('Empty resultset for query', $logContext); - if (TaskConfiguration::STRATEGY_SKIP === $state->getTaskConfiguration()->getErrorStrategy()) { - $state->setSkipped(true); - } elseif (TaskConfiguration::STRATEGY_STOP === $state->getTaskConfiguration()->getErrorStrategy()) { - $state->setStopped(true); - } + // The process manager applies the error strategy: the process fails with "stop", the error outputs + // receive the task input with "skip" + throw new \RuntimeException(\sprintf("Soap call '%s' on client '%s' failed", $options['method'], $options['client'])); } $state->setOutput($result); diff --git a/tests/Task/RequestTaskTest.php b/tests/Task/RequestTaskTest.php new file mode 100644 index 0000000..deee3e9 --- /dev/null +++ b/tests/Task/RequestTaskTest.php @@ -0,0 +1,77 @@ +createState(); + $state->expects($this->once())->method('setOutput')->with($response); + + $task = $this->createTask($response); + $task->initialize($state); + $task->execute($state); + } + + public function testFailedCallThrows(): void + { + $state = $this->createState(); + $state->expects($this->never())->method('setOutput'); + $state->expects($this->never())->method('setErrorOutput'); + $state->expects($this->never())->method('setStopped'); + + $task = $this->createTask(false); + $task->initialize($state); + + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage("Soap call 'FullCountryInfo' on client 'test' failed"); + $task->execute($state); + } + + private function createTask(mixed $callResult): RequestTask + { + $client = $this->createStub(ClientInterface::class); + $client->method('getCode')->willReturn('test'); + $client->method('call')->willReturn($callResult); + + $registry = new ClientRegistry(); + $registry->addClient($client); + + return new RequestTask(new NullLogger(), $registry); + } + + private function createState(): ProcessState&MockObject + { + $state = $this->createMock(ProcessState::class); + $state->method('getContextualizedOptions')->willReturn([ + 'client' => 'test', + 'method' => 'FullCountryInfo', + ]); + $state->method('getInput')->willReturn(['sCountryISOCode' => 'FR']); + + return $state; + } +} From 594b05c7e330143a29f25ec0ff8733c6b1e3199e Mon Sep 17 00:00:00 2001 From: Nicolas Joubert Date: Wed, 30 Sep 2026 11:31:32 +0200 Subject: [PATCH 2/2] test #25 Declare ClientRegistry as used by RequestTaskTest, fixing risky tests when running with coverage Co-Authored-By: Claude Opus 5.5 (1M context) --- tests/Task/RequestTaskTest.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/Task/RequestTaskTest.php b/tests/Task/RequestTaskTest.php index deee3e9..1402e84 100644 --- a/tests/Task/RequestTaskTest.php +++ b/tests/Task/RequestTaskTest.php @@ -18,11 +18,13 @@ use CleverAge\SoapProcessBundle\Registry\ClientRegistry; use CleverAge\SoapProcessBundle\Task\RequestTask; use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\UsesClass; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Psr\Log\NullLogger; #[CoversClass(RequestTask::class)] +#[UsesClass(ClientRegistry::class)] class RequestTaskTest extends TestCase { public function testExecute(): void