OverwriteRules

This commit is contained in:
icefox 2026-02-18 21:57:12 -03:00
parent 3a26a2e0c2
commit 709201547c
No known key found for this signature in database
8 changed files with 252 additions and 19 deletions

View file

@ -2,12 +2,10 @@
namespace Tests;
use Icefox\DTO\Support\RuleFactory;
use Illuminate\Validation\ValidationException;
use Tests\Classes\ArrayDataObject;
use Tests\Classes\CollectionDataObject;
use Tests\Classes\FromInputObject;
use Tests\Classes\ObjectWithoutMapper;
use Tests\Classes\OptionalData;
use Tests\Classes\OptionalNullableData;
use Tests\Classes\PrimitiveData;
@ -16,7 +14,7 @@ use Tests\Classes\WithMapperObject;
describe('primitive data test', function () {
it('creates required rules', function () {
$rules = RuleFactory::buildRules(RuleFactory::getParametersMeta(PrimitiveData::class), '');
$rules = PrimitiveData::getRules();
expect($rules)->toMatchArray([
'string' => ['required'],
'int' => ['required', 'numeric'],
@ -42,7 +40,7 @@ describe('primitive data test', function () {
describe('optional data', function () {
it('creates optional rules', function () {
$rules = RuleFactory::buildRules(RuleFactory::getParametersMeta(OptionalData::class), '');
$rules = OptionalData::getRules();
expect($rules)->toMatchArray([
'string' => ['sometimes'],
'int' => ['sometimes', 'numeric'],
@ -63,7 +61,7 @@ describe('optional data', function () {
describe('nullable data', function () {
it('creates nullable rules', function () {
$rules = RuleFactory::buildRules(RuleFactory::getParametersMeta(OptionalNullableData::class), '');
$rules = OptionalNullableData::getRules();
expect($rules)->toMatchArray([
'string' => ['required'],
'int' => ['nullable', 'numeric'],
@ -93,10 +91,7 @@ describe('nullable data', function () {
describe('reference other DataObject', function () {
it('creates recursive rules', function () {
$rules = RuleFactory::buildRules(
RuleFactory::getParametersMeta(RecursiveDataObject::class),
'',
);
$rules = RecursiveDataObject::getRules();
expect($rules)->toMatchArray([
'string' => ['required'],
'extra.string' => ['required'],
@ -109,7 +104,7 @@ describe('reference other DataObject', function () {
describe('primitive array', function () {
it('creates array rules', function () {
$rules = RuleFactory::buildRules(RuleFactory::getParametersMeta(ArrayDataObject::class), '');
$rules = ArrayDataObject::getRules();
expect($rules)->toMatchArray([
'values' => ['required', 'array'],
'values.*' => ['required', 'numeric'],
@ -120,10 +115,7 @@ describe('primitive array', function () {
describe('object array', function () {
it('creates array rules', function () {
$rules = RuleFactory::buildRules(
RuleFactory::getParametersMeta(CollectionDataObject::class),
'',
);
$rules = CollectionDataObject::getRules();
expect($rules)->toMatchArray([
'values' => ['required', 'array'],
'values.*' => ['required'],
@ -139,7 +131,7 @@ describe('can map input names', function () {
it('creates rules with property names', function () {
$rules = RuleFactory::buildRules(RuleFactory::getParametersMeta(FromInputObject::class), '');
$rules = FromInputObject::getRules();
expect($rules)->toMatchArray([
'text' => ['required' ],
'standard' => ['required', 'numeric'],

120
tests/Rules/RulesTest.php Normal file
View file

@ -0,0 +1,120 @@
<?php
declare(strict_types=1);
namespace Tests\Rules;
use Icefox\DTO\Support\RuleFactory;
use Illuminate\Validation\ValidationException;
use Tests\Rules\WithEmptyOverwriteRules;
use Tests\Rules\WithMergedRules;
use Tests\Rules\WithOverwriteRules;
describe('rules array shape', function () {
it('returns inferred rules shape from RuleFactory::infer (inferred only)', function () {
$parameters = RuleFactory::getParametersMeta(WithMergedRules::class);
$rules = RuleFactory::infer($parameters, '');
expect($rules)->toBe([
'value' => ['required', 'numeric'],
]);
});
it('returns inferred rules shape regardless of OverwriteRules attribute', function () {
$parameters = RuleFactory::getParametersMeta(WithOverwriteRules::class);
$rules = RuleFactory::infer($parameters, '');
expect($rules)->toBe([
'value' => ['required', 'numeric'],
]);
});
});
describe('getRules method', function () {
it('returns merged rules from DataObject::getRules()', function () {
$rules = WithMergedRules::getRules();
expect($rules)->toBe([
'value' => ['required', 'numeric', 'max:20'],
]);
});
it('returns only custom rules from DataObject::getRules() with OverwriteRules', function () {
$rules = WithOverwriteRules::getRules();
expect($rules)->toBe([
'value' => ['numeric', 'max:20'],
]);
});
it('returns empty rules from DataObject::getRules() with OverwriteRules and no custom rules', function () {
$rules = WithEmptyOverwriteRules::getRules();
expect($rules)->toBe([]);
});
});
describe('rules merging', function () {
it('merges custom rules with inferred rules by default', function () {
$object = WithMergedRules::fromArray([
'value' => 10,
]);
expect($object->value)->toBe(10);
});
it('fails validation when merged rule is violated', function () {
expect(fn() => WithMergedRules::fromArray([
'value' => 25,
]))->toThrow(ValidationException::class);
});
it('fails validation when required rule is violated (inferred)', function () {
expect(fn() => WithMergedRules::fromArray([
]))->toThrow(ValidationException::class);
});
});
describe('rules overwrite', function () {
it('uses only custom rules when OverwriteRules attribute is present', function () {
$object = WithOverwriteRules::fromArray([
'value' => 10,
]);
expect($object->value)->toBe(10);
});
it('fails validation when custom rule is violated', function () {
expect(fn() => WithOverwriteRules::fromArray([
'value' => 25,
]))->toThrow(ValidationException::class);
});
it('does not enforce inferred required rule when overwritten', function () {
$object = WithOverwriteRules::fromArray([]);
expect($object)->toBeInstanceOf(WithOverwriteRules::class);
});
it('does not enforce inferred numeric rule when overwritten', function () {
$rules = WithOverwriteRules::rules();
expect($rules)->toHaveKey('value');
expect($rules['value'])->toBe(['numeric', 'max:20']);
});
});
describe('empty rules overwrite', function () {
it('allows any value when rules are empty with OverwriteRules', function () {
$object = WithEmptyOverwriteRules::fromArray([
'value' => 999,
]);
expect($object->value)->toBe(999);
});
it('allows missing value when rules are empty with OverwriteRules', function () {
$object = WithEmptyOverwriteRules::fromArray([]);
expect($object)->toBeInstanceOf(WithEmptyOverwriteRules::class);
});
});

View file

@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace Tests\Rules;
use Icefox\DTO\Attributes\OverwriteRules;
use Icefox\DTO\DataObject;
readonly class WithEmptyOverwriteRules
{
use DataObject;
public function __construct(
public int $value,
) {}
#[OverwriteRules]
public static function rules(): array
{
return [];
}
}

View file

@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace Tests\Rules;
use Icefox\DTO\DataObject;
readonly class WithMergedRules
{
use DataObject;
public function __construct(
public int $value,
) {}
public static function rules(): array
{
return [
'value' => ['max:20'],
];
}
}

View file

@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
namespace Tests\Rules;
use Icefox\DTO\Attributes\OverwriteRules;
use Icefox\DTO\DataObject;
readonly class WithOverwriteRules
{
use DataObject;
public function __construct(
public int $value,
) {}
#[OverwriteRules]
public static function rules(): array
{
return [
'value' => ['numeric', 'max:20'],
];
}
}