From 674cdab17ad1bb219b3746c374dd91347f4bc267 Mon Sep 17 00:00:00 2001 From: Sebastian Mendel Date: Sun, 2 Aug 2026 12:36:28 +0200 Subject: [PATCH] [TASK] Report the API error code and HTTP status on failed requests The failure output so far consisted of the API's message alone. TER answers every masked server-side exception with the same generic message, so that line cannot tell two unrelated defects apart: {"status": 500, "code": 1603956982, "message": "An error occured on handling the request."} The code identifies the branch that produced the response and is the part that makes such a failure diagnosable, so report it next to the HTTP status: Reason: An error occured on handling the request. (HTTP 500, code 1603956982) The reason is now built by RequestService::createFailureReason(), which is covered by unit tests. Codes that carry no information - absent, empty, 0 or non-scalar - are left out, so a response without one only gains the status. The fallback for a body with no usable message changes wording from "Unknown (Status 502)" to "Unknown (HTTP 502)", which keeps the suffix identical across all three cases. Signed-off-by: Sebastian Mendel --- src/Service/RequestService.php | 28 ++++++++- tests/Unit/Service/RequestServiceTest.php | 73 +++++++++++++++++++++++ 2 files changed, 98 insertions(+), 3 deletions(-) create mode 100644 tests/Unit/Service/RequestServiceTest.php diff --git a/src/Service/RequestService.php b/src/Service/RequestService.php index 9916122..172d834 100644 --- a/src/Service/RequestService.php +++ b/src/Service/RequestService.php @@ -74,9 +74,7 @@ public function run(): bool $this->consoleWriter->writeSuccess(); $this->consoleWriter->writeFormattedResult($content); } else { - $this->consoleWriter->writeFailure( - (string)($content['error_description'] ?? $content['message'] ?? 'Unknown (Status ' . $status . ')') - ); + $this->consoleWriter->writeFailure(self::createFailureReason($content, $status)); return false; } } catch (ExceptionInterface|\InvalidArgumentException $e) { @@ -86,4 +84,28 @@ public function run(): bool return true; } + + /** + * Describe a failed request for the console. + * + * Next to the API's message, this reports the HTTP status and the API's own + * error code. The code is what identifies the failing branch on the server: + * TER answers `{"status": 500, "code": 1603956982, "message": "An error + * occured on handling the request."}` for every masked exception, so the + * message alone cannot tell two unrelated defects apart. + * + * @param array $content Decoded response body + */ + public static function createFailureReason(array $content, int $status): string + { + $reason = (string)($content['error_description'] ?? $content['message'] ?? ''); + $details = ['HTTP ' . $status]; + $code = $content['code'] ?? null; + + if ((is_int($code) || is_string($code)) && !in_array((string)$code, ['', '0'], true)) { + $details[] = 'code ' . $code; + } + + return ($reason !== '' ? $reason : 'Unknown') . ' (' . implode(', ', $details) . ')'; + } } diff --git a/tests/Unit/Service/RequestServiceTest.php b/tests/Unit/Service/RequestServiceTest.php new file mode 100644 index 0000000..0fd86cd --- /dev/null +++ b/tests/Unit/Service/RequestServiceTest.php @@ -0,0 +1,73 @@ + $content + */ + #[Test] + #[DataProvider('createFailureReasonTestDataProvider')] + public function createFailureReasonTest(array $content, int $status, string $expected): void + { + self::assertSame($expected, RequestService::createFailureReason($content, $status)); + } + + /** + * Data provider for createFailureReasonTest + */ + public static function createFailureReasonTestDataProvider(): \Generator + { + yield 'Message and error code' => [ + ['status' => 500, 'code' => 1603956982, 'message' => 'An error occured on handling the request.'], + 500, + 'An error occured on handling the request. (HTTP 500, code 1603956982)', + ]; + yield 'Message without error code' => [ + ['message' => 'Extension key not found.'], + 404, + 'Extension key not found. (HTTP 404)', + ]; + yield 'OAuth style error description wins over message' => [ + ['error_description' => 'The access token is invalid.', 'message' => 'Unauthorized'], + 401, + 'The access token is invalid. (HTTP 401)', + ]; + yield 'Empty response body' => [ + [], + 502, + 'Unknown (HTTP 502)', + ]; + yield 'Error code sent as string' => [ + ['message' => 'Denied.', 'code' => 'invalid_grant'], + 400, + 'Denied. (HTTP 400, code invalid_grant)', + ]; + yield 'Meaningless error code is omitted' => [ + ['message' => 'Denied.', 'code' => 0], + 400, + 'Denied. (HTTP 400)', + ]; + yield 'Non scalar error code is omitted' => [ + ['message' => 'Denied.', 'code' => ['nested']], + 400, + 'Denied. (HTTP 400)', + ]; + } +}