From 0cb910b9ec9f33674c15733229e5a9f051a439e5 Mon Sep 17 00:00:00 2001 From: Eleazar Resendez Date: Tue, 9 Dec 2025 10:12:48 -0600 Subject: [PATCH] Enhance error handling in ErrorHandling class and add unit tests for response exceptions --- ProcessMaker/Jobs/ErrorHandling.php | 55 +++++++++++++++++++++-- tests/unit/ErrorHandlingTest.php | 69 +++++++++++++++++++++++++++++ 2 files changed, 121 insertions(+), 3 deletions(-) create mode 100644 tests/unit/ErrorHandlingTest.php diff --git a/ProcessMaker/Jobs/ErrorHandling.php b/ProcessMaker/Jobs/ErrorHandling.php index ea0049a742..f1bb605296 100644 --- a/ProcessMaker/Jobs/ErrorHandling.php +++ b/ProcessMaker/Jobs/ErrorHandling.php @@ -210,10 +210,59 @@ public function setDefaultsFromDataSourceConfig(array $config) public static function convertResponseToException($result) { if ($result['status'] === 'error') { - if (str_starts_with($result['message'], 'Command exceeded timeout of')) { - throw new ScriptTimeoutException($result['message']); + $rawMessage = $result['message'] ?? ''; + if (str_starts_with((string) $rawMessage, 'Command exceeded timeout of')) { + throw new ScriptTimeoutException((string) $rawMessage); + } + + $message = self::extractScriptErrorMessage($result); + + if (empty($message)) { + $message = $rawMessage ?: 'Script execution failed with unknown error'; + } + + throw new ScriptException($message); + } + } + + /** + * Extract a concise error message from the microservice response. + */ + private static function extractScriptErrorMessage(array $result): string + { + $candidates = [ + $result['output']['error'] ?? null, + $result['output']['exception'] ?? null, + $result['output']['stderr'] ?? null, + $result['output']['stdout'] ?? null, + $result['message'] ?? null, + ]; + + foreach ($candidates as $candidate) { + if (is_string($candidate) || is_numeric($candidate)) { + $short = self::shortenMessage((string) $candidate); + if (!empty($short)) { + return $short; + } } - throw new ScriptException($result['message']); } + + return ''; + } + + /** + * Keep only the first line of the error and limit its length to avoid noisy traces. + */ + private static function shortenMessage(string $message): string + { + $firstLine = strtok($message, "\n"); + $firstLine = $firstLine === false ? $message : $firstLine; + $trimmed = trim($firstLine); + + if (strlen($trimmed) > 400) { + return substr($trimmed, 0, 400) . '…'; + } + + return $trimmed; } } diff --git a/tests/unit/ErrorHandlingTest.php b/tests/unit/ErrorHandlingTest.php new file mode 100644 index 0000000000..f6be0bfe7d --- /dev/null +++ b/tests/unit/ErrorHandlingTest.php @@ -0,0 +1,69 @@ + 'error', + 'message' => 'Generic failure', + 'output' => [ + 'error' => 'SMART_EXTRACT_API_HOST is required but could not be resolved', + ], + ]; + + $this->expectException(ScriptException::class); + $this->expectExceptionMessage('SMART_EXTRACT_API_HOST is required but could not be resolved'); + + ErrorHandling::convertResponseToException($result); + } + + public function testPrefersStderrAndKeepsOnlyFirstLine(): void + { + $result = [ + 'status' => 'error', + 'message' => 'fallback message', + 'output' => [ + 'stderr' => "First line of error\nstack trace line 2\nstack trace line 3", + ], + ]; + + $this->expectException(ScriptException::class); + $this->expectExceptionMessage('First line of error'); + + ErrorHandling::convertResponseToException($result); + } + + public function testTimeoutErrorThrowsScriptTimeoutException(): void + { + $result = [ + 'status' => 'error', + 'message' => 'Command exceeded timeout of 120 seconds', + ]; + + $this->expectException(ScriptTimeoutException::class); + $this->expectExceptionMessage('Command exceeded timeout of 120 seconds'); + + ErrorHandling::convertResponseToException($result); + } + + public function testFallsBackToRawMessageWhenNoOutputPresent(): void + { + $result = [ + 'status' => 'error', + 'message' => 'Plain failure', + ]; + + $this->expectException(ScriptException::class); + $this->expectExceptionMessage('Plain failure'); + + ErrorHandling::convertResponseToException($result); + } +}