diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 42ca41d..4975c48 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -7,14 +7,14 @@ jobs: name: Lint code with PHP-CS-Fixer runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install dependencies uses: php-actions/composer@v6 with: command: install only_args: -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist --ignore-platform-reqs - php_version: 8.1 + php_version: 8.3 - name: Run PHP-CS-Fixer run: vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.php --diff --dry-run @@ -23,14 +23,14 @@ jobs: name: Lint code with PHP CodeSniffer runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install dependencies uses: php-actions/composer@v6 with: command: install only_args: -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist --ignore-platform-reqs - php_version: 8.1 + php_version: 8.3 - name: Run PHP CodeSniffer run: vendor/bin/phpcs --extensions=php diff --git a/.github/workflows/unittests.yml b/.github/workflows/unittests.yml index 22f103d..b040f3d 100644 --- a/.github/workflows/unittests.yml +++ b/.github/workflows/unittests.yml @@ -11,24 +11,94 @@ jobs: - php: 8.1 laravel: 10.* testbench: 8.* + phpunit-config: 'phpunit-10.xml' composer-flag: '--prefer-stable' - php: 8.2 laravel: 10.* testbench: 8.* + phpunit-config: 'phpunit-10.xml' + composer-flag: '--prefer-stable' + - php: 8.3 + laravel: 10.* + testbench: 8.* + phpunit-config: 'phpunit-10.xml' composer-flag: '--prefer-stable' - php: 8.1 laravel: 10.* testbench: 8.* + phpunit-config: 'phpunit-10.xml' composer-flag: '--prefer-lowest' - php: 8.2 laravel: 10.* testbench: 8.* + phpunit-config: 'phpunit-10.xml' + composer-flag: '--prefer-lowest' + - php: 8.3 + laravel: 10.* + testbench: 8.* + phpunit-config: 'phpunit-10.xml' + composer-flag: '--prefer-lowest' + # Laravel 11.* + - php: 8.2 + laravel: 11.* + testbench: 9.* + phpunit-config: 'phpunit.xml' + composer-flag: '--prefer-stable' + - php: 8.3 + laravel: 11.* + testbench: 9.* + phpunit-config: 'phpunit.xml' + composer-flag: '--prefer-stable' + - php: 8.2 + laravel: 11.* + testbench: 9.* + phpunit-config: 'phpunit.xml' + composer-flag: '--prefer-lowest' + - php: 8.2 + laravel: 11.* + testbench: 9.* + phpunit-config: 'phpunit.xml' + composer-flag: '--prefer-lowest' + - php: 8.4 + laravel: 11.* + testbench: 9.* + phpunit-config: 'phpunit.xml' + composer-flag: '--prefer-stable' + # Laravel 12.* + - php: 8.2 + laravel: 12.* + testbench: 10.* + phpunit-config: 'phpunit.xml' + composer-flag: '--prefer-stable' + - php: 8.3 + laravel: 12.* + testbench: 10.* + phpunit-config: 'phpunit.xml' + composer-flag: '--prefer-stable' + - php: 8.4 + laravel: 12.* + testbench: 10.* + phpunit-config: 'phpunit.xml' + composer-flag: '--prefer-stable' + - php: 8.2 + laravel: 12.* + testbench: 10.* + phpunit-config: 'phpunit.xml' + composer-flag: '--prefer-lowest' + - php: 8.3 + laravel: 12.* + testbench: 10.* + phpunit-config: 'phpunit.xml' + composer-flag: '--prefer-lowest' + - php: 8.4 + laravel: 12.* + testbench: 10.* + phpunit-config: 'phpunit.xml' composer-flag: '--prefer-lowest' - runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -44,7 +114,9 @@ jobs: run: composer update ${{ matrix.composer-flag }} --prefer-dist --no-interaction - name: Run PHPUnit - run: XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-text --coverage-clover=coverage.xml + run: XDEBUG_MODE=coverage vendor/bin/phpunit --config="${{ matrix.phpunit-config }}" --coverage-text --coverage-clover=coverage.xml - name: Upload coverage reports to Codecov - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v5 + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/composer.json b/composer.json index cf0eb77..774b9eb 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,14 @@ { "name": "korridor/laravel-model-validation-rules", "description": "A laravel validation rule that uses eloquent to validate if a model exists", - "keywords": ["validation", "laravel", "rule", "model", "exist", "eloquent"], + "keywords": [ + "validation", + "laravel", + "rule", + "model", + "exist", + "eloquent" + ], "homepage": "https://github.com/korridor/laravel-model-validation-rules", "authors": [ { @@ -12,12 +19,12 @@ "license": "MIT", "require": { "php": ">=8.1", - "illuminate/support": "^10", - "illuminate/database": "^10" + "illuminate/support": "^10|^11|^12.0", + "illuminate/database": "^10|^11|^12.0" }, "require-dev": { - "orchestra/testbench": "^8.0", - "phpunit/phpunit": "^10", + "orchestra/testbench": "^8.0|^9.0|^10.0", + "phpunit/phpunit": "^10|^11.5.3", "friendsofphp/php-cs-fixer": "^3.6", "squizlabs/php_codesniffer": "^3.5" }, @@ -46,5 +53,7 @@ }, "config": { "sort-packages": true - } + }, + "minimum-stability": "dev", + "prefer-stable": true } diff --git a/docker/Dockerfile b/docker/Dockerfile index a5d71e1..86e2c84 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,4 +1,4 @@ -FROM php:8.1-cli +FROM php:8.3-cli ADD https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions /usr/local/bin/ diff --git a/phpunit-10.xml b/phpunit-10.xml new file mode 100644 index 0000000..ac59508 --- /dev/null +++ b/phpunit-10.xml @@ -0,0 +1,19 @@ + + + + + src/ + + + + + tests/Feature + + + + + + diff --git a/phpunit.xml b/phpunit.xml index ac59508..ab1c97a 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,19 +1,16 @@ - - - - src/ - - - - - tests/Feature - - - - - + + + + tests/Feature + + + + + + + + src/ + + diff --git a/readme.md b/readme.md index 291bc54..fdb5a82 100644 --- a/readme.md +++ b/readme.md @@ -15,6 +15,9 @@ It uses Eloquent models instead of directly querying the database. - Soft deletes are working out of the box. - Logic implemented into the models work in the validation as well. (multi tenancy system, etc.) +> [!NOTE] +> Check out **solidtime - The modern Open Source Time-Tracker** at [solidtime.io](https://www.solidtime.io) + ## Installation You can install the package via composer with following command: @@ -33,7 +36,9 @@ composer require korridor/laravel-model-validation-rules "^2.1" This package is tested for the following Laravel and PHP versions: - - 10.* (PHP 8.1, 8.2) + - 12.* (PHP 8.2, 8.3, 8.4) + - 11.* (PHP 8.2, 8.3, 8.4) + - 10.* (PHP 8.1, 8.2, 8.3) ## Usage examples diff --git a/src/Rules/ExistsEloquent.php b/src/Rules/ExistsEloquent.php index 8ccd714..56efc55 100644 --- a/src/Rules/ExistsEloquent.php +++ b/src/Rules/ExistsEloquent.php @@ -8,6 +8,7 @@ use Illuminate\Contracts\Validation\ValidationRule; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; +use Illuminate\Support\Str; class ExistsEloquent implements ValidationRule { @@ -46,6 +47,18 @@ class ExistsEloquent implements ValidationRule */ private ?string $customMessageTranslationKey = null; + /** + * Include soft deleted models in the query. + * + * @var bool + */ + private bool $includeSoftDeleted = false; + + /** + * @var bool Whether the key field is of type UUID + */ + private bool $isFieldUuid = false; + /** * Create a new rule instance. * @@ -60,6 +73,18 @@ public function __construct(string $model, ?string $key = null, ?Closure $builde $this->setBuilderClosure($builderClosure); } + /** + * Create a new rule instance. + * + * @param class-string $model Class name of model + * @param string|null $key Relevant key in the model + * @param Closure|null $builderClosure Closure that can extend the eloquent builder + */ + public static function make(string $model, ?string $key = null, ?Closure $builderClosure = null): self + { + return new self($model, $key, $builderClosure); + } + /** * Set a custom validation message. * @@ -86,6 +111,20 @@ public function withCustomTranslation(string $translationKey): self return $this; } + /** + * The field has the data type UUID. + * If the field is not a UUID, the validation will fail, before the query is executed. + * This is useful for example for Postgres databases where queries fail if a field with UUID data type is queried with a non-UUID value. + * + * @return $this + */ + public function uuid(): self + { + $this->isFieldUuid = true; + + return $this; + } + /** * Determine if the validation rule passes. * @@ -97,6 +136,12 @@ public function withCustomTranslation(string $translationKey): self */ public function validate(string $attribute, mixed $value, Closure $fail): void { + if ($this->isFieldUuid) { + if (!is_string($value) || !Str::isUuid($value)) { + $this->fail($attribute, $value, $fail); + return; + } + } /** @var Model|Builder $builder */ $builder = new $this->model(); $modelKeyName = $builder->getKeyName(); @@ -110,16 +155,26 @@ public function validate(string $attribute, mixed $value, Closure $fail): void $builder = $builderClosure($builder); } + if ($this->includeSoftDeleted) { + $builder = $builder->withTrashed(); + } + if ($builder->doesntExist()) { - if ($this->customMessage !== null) { - $fail($this->customMessage); - } else { - $fail($this->customMessageTranslationKey ?? 'modelValidationRules::validation.exists_model')->translate([ - 'attribute' => $attribute, - 'model' => strtolower(class_basename($this->model)), - 'value' => $value, - ]); - } + $this->fail($attribute, $value, $fail); + return; + } + } + + private function fail(string $attribute, mixed $value, Closure $fail): void + { + if ($this->customMessage !== null) { + $fail($this->customMessage); + } else { + $fail($this->customMessageTranslationKey ?? 'modelValidationRules::validation.exists_model')->translate([ + 'attribute' => $attribute, + 'model' => strtolower(class_basename($this->model)), + 'value' => $value, + ]); } } @@ -141,4 +196,26 @@ public function query(Closure $builderClosure): self return $this; } + + /** + * Activate or deactivate including soft deleted models in the query. + * + * @param bool $includeSoftDeleted + * @return void + */ + public function setIncludeSoftDeleted(bool $includeSoftDeleted): void + { + $this->includeSoftDeleted = $includeSoftDeleted; + } + + /** + * Activate including soft deleted models in the query. + * @return $this + */ + public function includeSoftDeleted(): self + { + $this->setIncludeSoftDeleted(true); + + return $this; + } } diff --git a/src/Rules/UniqueEloquent.php b/src/Rules/UniqueEloquent.php index 3769f4a..bad589e 100644 --- a/src/Rules/UniqueEloquent.php +++ b/src/Rules/UniqueEloquent.php @@ -8,6 +8,7 @@ use Illuminate\Contracts\Validation\ValidationRule; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; +use Illuminate\Support\Str; class UniqueEloquent implements ValidationRule { @@ -56,12 +57,24 @@ class UniqueEloquent implements ValidationRule */ private ?string $customMessageTranslationKey = null; + /** + * Include soft deleted models in the query. + * + * @var bool + */ + private bool $includeSoftDeleted = false; + + /** + * @var bool Whether the ID is a UUID + */ + private bool $isFieldUuid = false; + /** * UniqueEloquent constructor. * - * @param class-string $model Class name of model. - * @param string|null $key Relevant key in the model. - * @param Closure|null $builderClosure Closure that can extend the eloquent builder + * @param class-string $model Class name of model. + * @param string|null $key Relevant key in the model. + * @param Closure|null $builderClosure Closure that can extend the eloquent builder */ public function __construct(string $model, ?string $key = null, ?Closure $builderClosure = null) { @@ -70,17 +83,32 @@ public function __construct(string $model, ?string $key = null, ?Closure $builde $this->setBuilderClosure($builderClosure); } + /** + * @param class-string $model Class name of model. + * @param string|null $key Relevant key in the model. + * @param Closure|null $builderClosure Closure that can extend the eloquent builder + */ + public static function make(string $model, ?string $key = null, ?Closure $builderClosure = null): self + { + return new self($model, $key, $builderClosure); + } + /** * Determine if the validation rule passes. * - * @param string $attribute - * @param mixed $value - * @param Closure $fail + * @param string $attribute + * @param mixed $value + * @param Closure $fail * * @return void */ public function validate(string $attribute, mixed $value, Closure $fail): void { + if ($this->isFieldUuid) { + if (!is_string($value) || !Str::isUuid($value)) { + return; + } + } /** @var Model|Builder $builder */ $builder = new $this->model(); $modelKeyName = $builder->getKeyName(); @@ -97,15 +125,20 @@ public function validate(string $attribute, mixed $value, Closure $fail): void ); } + if ($this->includeSoftDeleted) { + $builder = $builder->withTrashed(); + } + if ($builder->exists()) { if ($this->customMessage !== null) { $fail($this->customMessage); } else { - $fail($this->customMessageTranslationKey ?? 'modelValidationRules::validation.unique_model')->translate([ - 'attribute' => $attribute, - 'model' => strtolower(class_basename($this->model)), - 'value' => $value, - ]); + $fail($this->customMessageTranslationKey ?? 'modelValidationRules::validation.unique_model') + ->translate([ + 'attribute' => $attribute, + 'model' => strtolower(class_basename($this->model)), + 'value' => $value, + ]); } } } @@ -113,7 +146,7 @@ public function validate(string $attribute, mixed $value, Closure $fail): void /** * Set a custom validation message. * - * @param string $message + * @param string $message * @return $this */ public function withMessage(string $message): self @@ -126,7 +159,7 @@ public function withMessage(string $message): self /** * Set a translated custom validation message. * - * @param string $translationKey + * @param string $translationKey * @return $this */ public function withCustomTranslation(string $translationKey): self @@ -139,7 +172,7 @@ public function withCustomTranslation(string $translationKey): self /** * Set a closure that can extend the eloquent builder. * - * @param Closure|null $builderClosure + * @param Closure|null $builderClosure */ public function setBuilderClosure(?Closure $builderClosure): void { @@ -147,7 +180,7 @@ public function setBuilderClosure(?Closure $builderClosure): void } /** - * @param Closure $builderClosure + * @param Closure $builderClosure * @return $this */ public function query(Closure $builderClosure): self @@ -158,8 +191,8 @@ public function query(Closure $builderClosure): self } /** - * @param mixed $id - * @param string|null $column + * @param mixed $id + * @param string|null $column */ public function setIgnore(mixed $id, ?string $column = null): void { @@ -169,7 +202,7 @@ public function setIgnore(mixed $id, ?string $column = null): void /** * @param mixed $id - * @param string|null $column + * @param string|null $column * @return UniqueEloquent */ public function ignore(mixed $id, ?string $column = null): self @@ -178,4 +211,41 @@ public function ignore(mixed $id, ?string $column = null): self return $this; } + + /** + * Activate or deactivate including soft deleted models in the query. + * + * @param bool $includeSoftDeleted + * @return void + */ + public function setIncludeSoftDeleted(bool $includeSoftDeleted): void + { + $this->includeSoftDeleted = $includeSoftDeleted; + } + + /** + * The field has the data type UUID. + * If a value is not a UUID, the validation will be skipped. + * This is useful for example for Postgres databases where queries fail if a field with UUID data type is queried with a non-UUID value. + * + * @return $this + */ + public function uuid(): self + { + $this->isFieldUuid = true; + + return $this; + } + + /** + * Activate including soft deleted models in the query. + * + * @return $this + */ + public function includeSoftDeleted(): self + { + $this->setIncludeSoftDeleted(true); + + return $this; + } } diff --git a/tests/Feature/ExistsEloquentTest.php b/tests/Feature/ExistsEloquentTest.php index 6d3447e..685dcab 100644 --- a/tests/Feature/ExistsEloquentTest.php +++ b/tests/Feature/ExistsEloquentTest.php @@ -6,6 +6,7 @@ use Illuminate\Database\Eloquent\Builder; use Illuminate\Foundation\Testing\RefreshDatabase; +use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Lang; use Illuminate\Support\Facades\Validator; use Illuminate\Support\Str; @@ -30,12 +31,15 @@ public function testValidationFailsIfEntryDoesNotExistInDatabase(): void ], [ 'id' => [new ExistsEloquent(User::class)] ]); + $this->db->enableQueryLog(); // Act $isValid = $validator->passes(); $messages = $validator->messages()->toArray(); // Assert + $queryLog = $this->db->getQueryLog(); + $this->assertCount(1, $queryLog); $this->assertFalse($isValid); $this->assertEquals('The resource does not exist.', $messages['id'][0]); } @@ -255,6 +259,79 @@ public function testValidationPassesIfRuleChecksThatFactExistsAndBelongsToUserUs $this->assertCount(1, Fact::all()); } + /* + * Tests for includeSoftDeleted + */ + + public function testValidationSucceedsIfSoftDeletedEntryExistInDatabaseAndIncludeSoftDeletedFlagIsActive(): void + { + // Arrange + User::create([ + 'id' => 6, + 'other_id' => null, + 'name' => 'Testname', + 'email' => 'name@test.com', + 'password' => bcrypt('secret'), + 'remember_token' => Str::random(10), + ]); + $fact = Fact::create([ + 'id' => 1, + 'user_id' => 6, + 'type' => 'type1', + 'description' => 'Long desc', + ]); + $fact->delete(); + + $validator = Validator::make([ + 'id' => 1, + ], [ + 'id' => [(new ExistsEloquent(Fact::class))->includeSoftDeleted()] + ]); + + // Act + $isValid = $validator->passes(); + $messages = $validator->messages()->toArray(); + + // Assert + $this->assertTrue($isValid); + $this->assertArrayNotHasKey('id', $messages); + $this->assertCount(1, User::withTrashed()->get()); + $this->assertCount(1, User::all()); + $this->assertCount(1, Fact::withTrashed()->get()); + $this->assertCount(0, Fact::all()); + } + + public function testValidationFailsIfSoftDeletedEntryDoesNotExistInDatabaseAndIncludeSoftDeletedFlagIsActive(): void + { + // Arrange + User::create([ + 'id' => 6, + 'other_id' => null, + 'name' => 'Testname', + 'email' => 'name@test.com', + 'password' => bcrypt('secret'), + 'remember_token' => Str::random(10), + ]); + + $validator = Validator::make([ + 'id' => 1, + ], [ + 'id' => [(new ExistsEloquent(Fact::class))->includeSoftDeleted()] + ]); + + // Act + $isValid = $validator->passes(); + $messages = $validator->messages()->toArray(); + + // Assert + $this->assertFalse($isValid); + $this->assertEquals('The resource does not exist.', $messages['id'][0]); + $this->assertCount(1, User::withTrashed()->get()); + $this->assertCount(1, User::all()); + $this->assertCount(0, Fact::withTrashed()->get()); + $this->assertCount(0, Fact::all()); + } + /* * Test language support */ @@ -332,4 +409,62 @@ public function testValidationMessageIsLaravelTranslationIfCustomTranslationIsSe $this->assertFalse($isValid); $this->assertEquals('A user with the id "1" does not exist. / Test', $messages['id'][0]); } + + public function testFunctionMakeIsIdenticalToConstructor(): void + { + // Arrange + $message = 'Test'; + $closure = function (Builder $builder) { + return $builder->where('user_id', 6); + }; + + // Act + $rule1 = ExistsEloquent::make(User::class, 'other_id', $closure)->withMessage($message); + $rule2 = (new ExistsEloquent(User::class, 'other_id', $closure))->withMessage($message); + + // Assert + $this->assertEquals($rule1, $rule2); + } + + public function testUuidOptionMakesRuleFailIfValueIsNotUuidBeforeQueryingTheDatabase(): void + { + // Arrange + $validator = Validator::make([ + 'id' => 'not-a-uuid', + ], [ + 'id' => [(new ExistsEloquent(User::class))->uuid()] + ]); + $this->db->enableQueryLog(); + + // Act + $isValid = $validator->passes(); + $messages = $validator->messages()->toArray(); + + // Assert + $queryLog = $this->db->getQueryLog(); + $this->assertCount(0, $queryLog); + $this->assertFalse($isValid); + $this->assertEquals('The resource does not exist.', $messages['id'][0]); + } + + public function testUuidOptionMakesRuleFailIfValueIsNotStringBeforeQueryingTheDatabase(): void + { + // Arrange + $validator = Validator::make([ + 'id' => 1, + ], [ + 'id' => [(new ExistsEloquent(User::class))->uuid()] + ]); + $this->db->enableQueryLog(); + + // Act + $isValid = $validator->passes(); + $messages = $validator->messages()->toArray(); + + // Assert + $queryLog = $this->db->getQueryLog(); + $this->assertCount(0, $queryLog); + $this->assertFalse($isValid); + $this->assertEquals('The resource does not exist.', $messages['id'][0]); + } } diff --git a/tests/Feature/UniqueEloquentTest.php b/tests/Feature/UniqueEloquentTest.php index 20766a8..b640296 100644 --- a/tests/Feature/UniqueEloquentTest.php +++ b/tests/Feature/UniqueEloquentTest.php @@ -257,6 +257,79 @@ public function testValidationFailsIfRuleChecksThatFactExistsAndBelongsToUserUsi $this->assertCount(1, Fact::all()); } + /* + * Tests for includeSoftDeleted + */ + + public function testValidationSucceedsIfSoftDeletedEntryDoesNotExistInDatabaseAndIncludeSoftDeletedFlagIsActive(): void + { + // Arrange + User::create([ + 'id' => 6, + 'other_id' => null, + 'name' => 'Testname', + 'email' => 'name@test.com', + 'password' => bcrypt('secret'), + 'remember_token' => Str::random(10), + ]); + + $validator = Validator::make([ + 'id' => 1, + ], [ + 'id' => [(new UniqueEloquent(Fact::class))->includeSoftDeleted()] + ]); + + // Act + $isValid = $validator->passes(); + $messages = $validator->messages()->toArray(); + + // Assert + $this->assertTrue($isValid); + $this->assertArrayNotHasKey('id', $messages); + $this->assertCount(1, User::withTrashed()->get()); + $this->assertCount(1, User::all()); + $this->assertCount(0, Fact::withTrashed()->get()); + $this->assertCount(0, Fact::all()); + } + + public function testValidationFailsIfSoftDeletedEntryDoesExistInDatabaseAndIncludeSoftDeletedFlagIsActive(): void + { + // Arrange + User::create([ + 'id' => 6, + 'other_id' => null, + 'name' => 'Testname', + 'email' => 'name@test.com', + 'password' => bcrypt('secret'), + 'remember_token' => Str::random(10), + ]); + $fact = Fact::create([ + 'id' => 1, + 'user_id' => 6, + 'type' => 'type1', + 'description' => 'Long desc', + ]); + $fact->delete(); + + $validator = Validator::make([ + 'id' => 1, + ], [ + 'id' => [(new UniqueEloquent(Fact::class))->includeSoftDeleted()] + ]); + + // Act + $isValid = $validator->passes(); + $messages = $validator->messages()->toArray(); + + // Assert + $this->assertFalse($isValid); + $this->assertEquals('The resource already exists.', $messages['id'][0]); + $this->assertCount(1, User::withTrashed()->get()); + $this->assertCount(1, User::all()); + $this->assertCount(1, Fact::withTrashed()->get()); + $this->assertCount(0, Fact::all()); + } + /* * Test language support */ @@ -427,4 +500,62 @@ public function testIgnoringEntryWithGivenIdColumn(): void $this->assertTrue($isValid); $this->assertArrayNotHasKey('id', $messages); } + + public function testFunctionMakeIsIdenticalToConstructor(): void + { + // Arrange + $message = 'Test'; + $closure = function (Builder $builder) { + return $builder->where('user_id', 6); + }; + + // Act + $rule1 = UniqueEloquent::make(User::class, 'other_id', $closure)->withMessage($message); + $rule2 = (new UniqueEloquent(User::class, 'other_id', $closure))->withMessage($message); + + // Assert + $this->assertEquals($rule1, $rule2); + } + + public function testUuidOptionSkipsValidationIfValueIsNotUuid(): void + { + // Arrange + $validator = Validator::make([ + 'id' => 'not-a-uuid', + ], [ + 'id' => [(new UniqueEloquent(User::class))->uuid()] + ]); + $this->db->enableQueryLog(); + + // Act + $isValid = $validator->passes(); + $messages = $validator->messages()->toArray(); + + // Assert + $queryLog = $this->db->getQueryLog(); + $this->assertCount(0, $queryLog); + $this->assertTrue($isValid); + $this->assertArrayNotHasKey('id', $messages); + } + + public function testUuidOptionSkipsValidationIfValueIsNotString(): void + { + // Arrange + $validator = Validator::make([ + 'id' => 1, + ], [ + 'id' => [(new UniqueEloquent(User::class))->uuid()] + ]); + $this->db->enableQueryLog(); + + // Act + $isValid = $validator->passes(); + $messages = $validator->messages()->toArray(); + + // Assert + $queryLog = $this->db->getQueryLog(); + $this->assertCount(0, $queryLog); + $this->assertTrue($isValid); + $this->assertArrayNotHasKey('id', $messages); + } } diff --git a/tests/TestCase.php b/tests/TestCase.php index a89b984..6cda415 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -6,6 +6,7 @@ use Illuminate\Container\Container; use Illuminate\Database\Capsule\Manager; +use Illuminate\Database\Connection; use Illuminate\Database\Schema\Blueprint; use Illuminate\Events\Dispatcher; use Illuminate\Foundation\Application; @@ -14,6 +15,8 @@ abstract class TestCase extends Orchestra { + protected Connection $db; + public function setUp(): void { parent::setUp(); @@ -57,5 +60,6 @@ protected function setUpDatabase(): void $table->softDeletes(); $table->timestamps(); }); + $this->db = $manager->getConnection(); } }