From f1d46dacb6f5d0250cd916d410cf10b0cf4e5c55 Mon Sep 17 00:00:00 2001 From: icefox Date: Thu, 19 Feb 2026 08:44:49 -0300 Subject: [PATCH] ValidationFailure tests --- composer.json | 3 +- composer.lock | 76 ++++++++++++++- src/DataObject.php | 10 +- tests/Classes/FailsReturnsDefault.php | 23 +++++ tests/Classes/FailsReturnsNull.php | 23 +++++ tests/Classes/FailsWithHttpResponse.php | 26 +++++ tests/FailedValidation/FailsMethodTest.php | 108 +++++++++++++++++++++ 7 files changed, 264 insertions(+), 5 deletions(-) create mode 100644 tests/Classes/FailsReturnsDefault.php create mode 100644 tests/Classes/FailsReturnsNull.php create mode 100644 tests/Classes/FailsWithHttpResponse.php create mode 100644 tests/FailedValidation/FailsMethodTest.php diff --git a/composer.json b/composer.json index ad9b9d4..be192b0 100644 --- a/composer.json +++ b/composer.json @@ -11,7 +11,8 @@ "pestphp/pest": "^4.4", "phpstan/phpstan": "^2.1", "friendsofphp/php-cs-fixer": "^3.94", - "orchestra/testbench": "^9.16" + "orchestra/testbench": "^9.16", + "pestphp/pest-plugin-laravel": "^4.0" }, "license": "GPL-2.0-only", "autoload": { diff --git a/composer.lock b/composer.lock index 8b91f11..27eafed 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "1d9eef5574135e39ab7eaa6beae3fdad", + "content-hash": "336ed1e898bc39b0e9990becc327415c", "packages": [ { "name": "brick/math", @@ -7992,6 +7992,80 @@ ], "time": "2025-08-20T13:10:51+00:00" }, + { + "name": "pestphp/pest-plugin-laravel", + "version": "v4.0.0", + "source": { + "type": "git", + "url": "https://github.com/pestphp/pest-plugin-laravel.git", + "reference": "e12a07046b826a40b1c8632fd7b80d6b8d7b628e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pestphp/pest-plugin-laravel/zipball/e12a07046b826a40b1c8632fd7b80d6b8d7b628e", + "reference": "e12a07046b826a40b1c8632fd7b80d6b8d7b628e", + "shasum": "" + }, + "require": { + "laravel/framework": "^11.45.2|^12.25.0", + "pestphp/pest": "^4.0.0", + "php": "^8.3.0" + }, + "require-dev": { + "laravel/dusk": "^8.3.3", + "orchestra/testbench": "^9.13.0|^10.5.0", + "pestphp/pest-dev-tools": "^4.0.0" + }, + "type": "library", + "extra": { + "pest": { + "plugins": [ + "Pest\\Laravel\\Plugin" + ] + }, + "laravel": { + "providers": [ + "Pest\\Laravel\\PestServiceProvider" + ] + } + }, + "autoload": { + "files": [ + "src/Autoload.php" + ], + "psr-4": { + "Pest\\Laravel\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The Pest Laravel Plugin", + "keywords": [ + "framework", + "laravel", + "pest", + "php", + "test", + "testing", + "unit" + ], + "support": { + "source": "https://github.com/pestphp/pest-plugin-laravel/tree/v4.0.0" + }, + "funding": [ + { + "url": "https://www.paypal.com/paypalme/enunomaduro", + "type": "custom" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + } + ], + "time": "2025-08-20T12:46:37+00:00" + }, { "name": "pestphp/pest-plugin-mutate", "version": "v4.0.1", diff --git a/src/DataObject.php b/src/DataObject.php index 5487e00..163dbe4 100644 --- a/src/DataObject.php +++ b/src/DataObject.php @@ -43,7 +43,7 @@ trait DataObject /** * @param array $input */ - public static function fromArray(array $input): static + public static function fromArray(array $input): ?static { $parameters = RuleFactory::getParametersMeta(static::class); foreach ($parameters as $parameter) { @@ -72,8 +72,7 @@ trait DataObject $validator = static::withValidator($input, $rules); if ($validator->fails()) { - $exception = new ValidationException($validator); - throw $exception; + return static::fails($validator); } $mappedInput = []; @@ -103,6 +102,11 @@ trait DataObject return []; } + public static function fails(Validator $validator): ?static + { + throw new ValidationException($validator); + } + /** * @return array> */ diff --git a/tests/Classes/FailsReturnsDefault.php b/tests/Classes/FailsReturnsDefault.php new file mode 100644 index 0000000..7fdba3a --- /dev/null +++ b/tests/Classes/FailsReturnsDefault.php @@ -0,0 +1,23 @@ +json(['errors' => $validator->errors()], 422) + ); + } +} diff --git a/tests/FailedValidation/FailsMethodTest.php b/tests/FailedValidation/FailsMethodTest.php new file mode 100644 index 0000000..11b2ca1 --- /dev/null +++ b/tests/FailedValidation/FailsMethodTest.php @@ -0,0 +1,108 @@ + 0, + 'float' => 3.14, + 'bool' => true, + ]); + })->toThrow(ValidationException::class); + }); + + it('returns null when fails() returns null', function () { + $result = FailsReturnsNull::fromArray([ + 'int' => 0, + ]); + + expect($result)->toBeNull(); + }); + + it('returns static instance when fails() returns an object', function () { + $result = FailsReturnsDefault::fromArray([ + 'int' => 0, + ]); + + expect($result)->toBeInstanceOf(FailsReturnsDefault::class); + expect($result->string)->toBe('default_value'); + expect($result->int)->toBe(42); + }); +}); + +describe('HTTP request handling', function () { + + beforeEach(function () { + \Illuminate\Support\Facades\Route::post('/test-validation-exception', function () { + PrimitiveData::fromArray([ + 'int' => 0, + 'float' => 3.14, + 'bool' => true, + ]); + return response()->json(['success' => true]); + }); + + \Illuminate\Support\Facades\Route::post('/test-http-response-exception', function () { + FailsWithHttpResponse::fromArray([ + 'int' => 0, + ]); + return response()->json(['success' => true]); + }); + + \Illuminate\Support\Facades\Route::post('/test-validation-exception-html', function () { + PrimitiveData::fromArray([ + 'int' => 0, + 'float' => 3.14, + 'bool' => true, + ]); + return response('success'); + }); + }); + + it('returns 422 with errors when ValidationException is thrown in JSON request', function () { + $response = $this->postJson('/test-validation-exception', [ + 'int' => 0, + 'float' => 3.14, + 'bool' => true, + ]); + + $response->assertStatus(422); + $response->assertJsonValidationErrors(['string']); + }); + + it('returns custom JSON response when HttpResponseException is thrown', function () { + $response = $this->postJson('/test-http-response-exception', [ + 'int' => 0, + ]); + + $response->assertStatus(422); + $response->assertJsonStructure(['errors']); + $response->assertJsonFragment([ + 'errors' => [ + 'string' => ['The string field is required.'], + ], + ]); + }); + + it('redirects back with session errors when ValidationException is thrown in text/html request', function () { + $response = $this->post('/test-validation-exception-html', [ + 'int' => 0, + 'float' => 3.14, + 'bool' => true, + ], [ + 'Accept' => 'text/html', + ]); + + $response->assertRedirect(); + $response->assertSessionHasErrors(['string']); + }); +});