diff --git a/src/DTO/MFilesError.php b/src/DTO/MFilesError.php new file mode 100644 index 0000000..c5205b4 --- /dev/null +++ b/src/DTO/MFilesError.php @@ -0,0 +1,48 @@ + $this->errorCode, + 'status' => $this->status, + 'url' => $this->url, + 'method' => $this->method, + 'exceptionName' => $this->exceptionName, + 'exceptionMessage' => $this->exceptionMessage, + 'stack' => $this->stack, + ]; + } +} diff --git a/src/Exceptions/MFilesErrorException.php b/src/Exceptions/MFilesErrorException.php new file mode 100644 index 0000000..a6d313e --- /dev/null +++ b/src/Exceptions/MFilesErrorException.php @@ -0,0 +1,17 @@ +exceptionMessage, $error->status); + } +} diff --git a/src/Responses/DownloadFileResponse.php b/src/Responses/DownloadFileResponse.php index 006ce2e..4126400 100644 --- a/src/Responses/DownloadFileResponse.php +++ b/src/Responses/DownloadFileResponse.php @@ -5,12 +5,16 @@ namespace CodebarAg\MFiles\Responses; use CodebarAg\MFiles\DTO\DownloadedFile; +use CodebarAg\MFiles\Exceptions\MFilesErrorException; use Saloon\Http\Response; final class DownloadFileResponse { public static function createDtoFromResponse(Response $response): DownloadedFile { + if (! $response->successful()) { + throw new MFilesErrorException(ErrorResponse::createDtoFromResponse($response)); + } $headers = $response->headers(); $fileContentType = $headers->get('Content-Type'); $fileSize = (int) $headers->get('Content-Length', 0); diff --git a/src/Responses/ErrorResponse.php b/src/Responses/ErrorResponse.php new file mode 100644 index 0000000..e24c45a --- /dev/null +++ b/src/Responses/ErrorResponse.php @@ -0,0 +1,16 @@ +json()); + } +} diff --git a/src/Responses/LogInToVaultResponse.php b/src/Responses/LogInToVaultResponse.php index 7312a4e..6d6fba4 100644 --- a/src/Responses/LogInToVaultResponse.php +++ b/src/Responses/LogInToVaultResponse.php @@ -4,6 +4,7 @@ namespace CodebarAg\MFiles\Responses; +use CodebarAg\MFiles\Exceptions\MFilesErrorException; use Illuminate\Support\Arr; use Saloon\Http\Response; @@ -11,6 +12,10 @@ final class LogInToVaultResponse { public static function createDtoFromResponse(Response $response): ?string { + if (! $response->successful()) { + throw new MFilesErrorException(ErrorResponse::createDtoFromResponse($response)); + } + return Arr::get($response->json(), 'Value'); } } diff --git a/src/Responses/ObjectPropertiesResponse.php b/src/Responses/ObjectPropertiesResponse.php index bc4ef0c..6b68dc9 100644 --- a/src/Responses/ObjectPropertiesResponse.php +++ b/src/Responses/ObjectPropertiesResponse.php @@ -5,12 +5,17 @@ namespace CodebarAg\MFiles\Responses; use CodebarAg\MFiles\DTO\ObjectProperties; +use CodebarAg\MFiles\Exceptions\MFilesErrorException; use Saloon\Http\Response; final class ObjectPropertiesResponse { public static function createDtoFromResponse(Response $response): ObjectProperties { + if (! $response->successful()) { + throw new MFilesErrorException(ErrorResponse::createDtoFromResponse($response)); + } + return ObjectProperties::fromArray($response->json()); } } diff --git a/src/Responses/UploadFileResponse.php b/src/Responses/UploadFileResponse.php index 8b799a5..1dd6787 100644 --- a/src/Responses/UploadFileResponse.php +++ b/src/Responses/UploadFileResponse.php @@ -4,6 +4,7 @@ namespace CodebarAg\MFiles\Responses; +use CodebarAg\MFiles\Exceptions\MFilesErrorException; use Illuminate\Support\Arr; use Illuminate\Support\Str; use Saloon\Http\Response; @@ -12,6 +13,10 @@ final class UploadFileResponse { public static function createDtoFromResponse(Response $response, string $fileName): array { + if (! $response->successful()) { + throw new MFilesErrorException(ErrorResponse::createDtoFromResponse($response)); + } + $data = $response->json(); $data = Arr::add($data, 'Title', Str::beforeLast($fileName, '.')); $data = Arr::add($data, 'Extension', Str::afterLast($fileName, '.')); diff --git a/tests/Unit/DTO/MFilesErrorTest.php b/tests/Unit/DTO/MFilesErrorTest.php new file mode 100644 index 0000000..ca551cc --- /dev/null +++ b/tests/Unit/DTO/MFilesErrorTest.php @@ -0,0 +1,141 @@ +errorCode)->toBe('ERROR_001'); + expect($error->status)->toBe(403); + expect($error->url)->toBe('/session/vaults'); + expect($error->method)->toBe('GET'); + expect($error->exceptionName)->toBe('UnauthorizedAccessException'); + expect($error->exceptionMessage)->toBe('Login to application failed'); + expect($error->stack)->toBe('Stack trace here'); +}); + +it('creates instance with null stack', function () { + $error = new MFilesError( + errorCode: 'ERROR_002', + status: 404, + url: '/objects/123', + method: 'GET', + exceptionName: 'NotFoundException', + exceptionMessage: 'Object not found', + stack: null + ); + + expect($error->stack)->toBeNull(); +}); + +it('creates instance from array with all properties', function () { + $data = [ + 'ErrorCode' => 'ERROR_003', + 'Status' => 500, + 'URL' => '/api/endpoint', + 'Method' => 'POST', + 'Exception' => [ + 'Name' => 'InternalServerError', + 'Message' => 'Internal server error occurred', + ], + 'Stack' => 'Detailed stack trace', + ]; + + $error = MFilesError::fromArray($data); + + expect($error->errorCode)->toBe('ERROR_003'); + expect($error->status)->toBe(500); + expect($error->url)->toBe('/api/endpoint'); + expect($error->method)->toBe('POST'); + expect($error->exceptionName)->toBe('InternalServerError'); + expect($error->exceptionMessage)->toBe('Internal server error occurred'); + expect($error->stack)->toBe('Detailed stack trace'); +}); + +it('creates instance from array with missing optional fields', function () { + $data = [ + 'Status' => 400, + 'URL' => '/test', + 'Method' => 'GET', + 'Exception' => [ + 'Name' => 'BadRequest', + 'Message' => 'Bad request', + ], + ]; + + $error = MFilesError::fromArray($data); + + expect($error->errorCode)->toBe(''); + expect($error->status)->toBe(400); + expect($error->url)->toBe('/test'); + expect($error->method)->toBe('GET'); + expect($error->exceptionName)->toBe('BadRequest'); + expect($error->exceptionMessage)->toBe('Bad request'); + expect($error->stack)->toBeNull(); +}); + +it('creates instance from array with empty exception object', function () { + $data = [ + 'ErrorCode' => 'ERROR_004', + 'Status' => 401, + 'URL' => '/auth', + 'Method' => 'POST', + 'Exception' => [], + 'Stack' => null, + ]; + + $error = MFilesError::fromArray($data); + + expect($error->exceptionName)->toBe(''); + expect($error->exceptionMessage)->toBe(''); +}); + +it('converts instance to array correctly', function () { + $error = new MFilesError( + errorCode: 'ERROR_005', + status: 422, + url: '/validate', + method: 'PUT', + exceptionName: 'ValidationError', + exceptionMessage: 'Validation failed', + stack: 'Error stack' + ); + + $array = $error->toArray(); + + expect($array)->toBe([ + 'errorCode' => 'ERROR_005', + 'status' => 422, + 'url' => '/validate', + 'method' => 'PUT', + 'exceptionName' => 'ValidationError', + 'exceptionMessage' => 'Validation failed', + 'stack' => 'Error stack', + ]); +}); + +it('converts instance to array with null stack', function () { + $error = new MFilesError( + errorCode: 'ERROR_006', + status: 500, + url: '/test', + method: 'DELETE', + exceptionName: 'ServerError', + exceptionMessage: 'Server error', + stack: null + ); + + $array = $error->toArray(); + + expect($array['stack'])->toBeNull(); +}); diff --git a/tests/Unit/Exceptions/MFilesErrorExceptionTest.php b/tests/Unit/Exceptions/MFilesErrorExceptionTest.php new file mode 100644 index 0000000..b4cf2f2 --- /dev/null +++ b/tests/Unit/Exceptions/MFilesErrorExceptionTest.php @@ -0,0 +1,62 @@ +error)->toBe($error); + expect($exception->getMessage())->toBe('Login to application failed'); + expect($exception->getCode())->toBe(403); +}); + +it('extends base Exception class', function () { + $error = new MFilesError( + errorCode: 'ERROR_002', + status: 404, + url: '/objects/123', + method: 'GET', + exceptionName: 'NotFoundException', + exceptionMessage: 'Object not found', + stack: null + ); + + $exception = new MFilesErrorException($error); + + expect($exception)->toBeInstanceOf(Exception::class); +}); + +it('can access error properties through exception', function () { + $error = new MFilesError( + errorCode: 'ERROR_003', + status: 500, + url: '/api/endpoint', + method: 'POST', + exceptionName: 'InternalServerError', + exceptionMessage: 'Internal server error occurred', + stack: 'Detailed stack trace' + ); + + $exception = new MFilesErrorException($error); + + expect($exception->error->errorCode)->toBe('ERROR_003'); + expect($exception->error->status)->toBe(500); + expect($exception->error->url)->toBe('/api/endpoint'); + expect($exception->error->method)->toBe('POST'); + expect($exception->error->exceptionName)->toBe('InternalServerError'); + expect($exception->error->exceptionMessage)->toBe('Internal server error occurred'); + expect($exception->error->stack)->toBe('Detailed stack trace'); +}); diff --git a/tests/Unit/Responses/ErrorResponseTest.php b/tests/Unit/Responses/ErrorResponseTest.php new file mode 100644 index 0000000..435e55a --- /dev/null +++ b/tests/Unit/Responses/ErrorResponseTest.php @@ -0,0 +1,139 @@ + 'ERROR_001', + 'Status' => 403, + 'URL' => '/session/vaults', + 'Method' => 'GET', + 'Exception' => [ + 'Name' => 'UnauthorizedAccessException', + 'Message' => 'Login to application failed', + ], + 'Stack' => 'Stack trace here', + ]; + + $request = new class extends Request + { + protected Method $method = Method::GET; + + public function resolveEndpoint(): string + { + return '/test'; + } + }; + + Saloon::fake([ + get_class($request) => MockResponse::make(status: 403, body: json_encode($json)), + ]); + + $connector = new class extends Connector + { + public function resolveBaseUrl(): string + { + return 'https://test.example.com'; + } + }; + + $response = $connector->send($request); + + $error = ErrorResponse::createDtoFromResponse($response); + + expect($error)->toBeInstanceOf(MFilesError::class); + expect($error->errorCode)->toBe('ERROR_001'); + expect($error->status)->toBe(403); + expect($error->url)->toBe('/session/vaults'); + expect($error->method)->toBe('GET'); + expect($error->exceptionName)->toBe('UnauthorizedAccessException'); + expect($error->exceptionMessage)->toBe('Login to application failed'); + expect($error->stack)->toBe('Stack trace here'); +}); + +it('handles response with missing optional fields', function () { + $json = [ + 'Status' => 400, + 'URL' => '/test', + 'Method' => 'POST', + 'Exception' => [ + 'Name' => 'BadRequest', + 'Message' => 'Bad request', + ], + ]; + + $request = new class extends Request + { + protected Method $method = Method::GET; + + public function resolveEndpoint(): string + { + return '/test'; + } + }; + + Saloon::fake([ + get_class($request) => MockResponse::make(status: 400, body: json_encode($json)), + ]); + + $connector = new class extends Connector + { + public function resolveBaseUrl(): string + { + return 'https://test.example.com'; + } + }; + + $response = $connector->send($request); + + $error = ErrorResponse::createDtoFromResponse($response); + + expect($error->errorCode)->toBe(''); + expect($error->stack)->toBeNull(); +}); + +it('handles empty exception object', function () { + $json = [ + 'Status' => 500, + 'URL' => '/api', + 'Method' => 'GET', + 'Exception' => [], + ]; + + $request = new class extends Request + { + protected Method $method = Method::GET; + + public function resolveEndpoint(): string + { + return '/test'; + } + }; + + Saloon::fake([ + get_class($request) => MockResponse::make(status: 500, body: json_encode($json)), + ]); + + $connector = new class extends Connector + { + public function resolveBaseUrl(): string + { + return 'https://test.example.com'; + } + }; + + $response = $connector->send($request); + + $error = ErrorResponse::createDtoFromResponse($response); + + expect($error->exceptionName)->toBe(''); + expect($error->exceptionMessage)->toBe(''); +});