31 - Add buffer multiplier validation
This commit is contained in:
parent
3163a95f37
commit
a2663be938
5 changed files with 274 additions and 0 deletions
|
|
@ -5,16 +5,19 @@ namespace App\Entity;
|
||||||
use App\Enum\BucketAllocationType;
|
use App\Enum\BucketAllocationType;
|
||||||
use App\Enum\BucketType;
|
use App\Enum\BucketType;
|
||||||
use App\Repository\BucketRepository;
|
use App\Repository\BucketRepository;
|
||||||
|
use App\Validator\BufferOnlyForFixedLimit;
|
||||||
use App\Validator\CompatibleAllocationType;
|
use App\Validator\CompatibleAllocationType;
|
||||||
use App\Validator\SingleOverflowPerScenario;
|
use App\Validator\SingleOverflowPerScenario;
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
use Symfony\Component\Uid\UuidV7;
|
use Symfony\Component\Uid\UuidV7;
|
||||||
|
use Symfony\Component\Validator\Constraints as Assert;
|
||||||
|
|
||||||
#[ORM\Entity(repositoryClass: BucketRepository::class)]
|
#[ORM\Entity(repositoryClass: BucketRepository::class)]
|
||||||
#[ORM\Table(name: 'bucket')]
|
#[ORM\Table(name: 'bucket')]
|
||||||
#[ORM\UniqueConstraint(name: 'unique_scenario_priority', columns: ['scenario_id', 'priority'])]
|
#[ORM\UniqueConstraint(name: 'unique_scenario_priority', columns: ['scenario_id', 'priority'])]
|
||||||
#[CompatibleAllocationType]
|
#[CompatibleAllocationType]
|
||||||
#[SingleOverflowPerScenario]
|
#[SingleOverflowPerScenario]
|
||||||
|
#[BufferOnlyForFixedLimit]
|
||||||
class Bucket
|
class Bucket
|
||||||
{
|
{
|
||||||
#[ORM\Id]
|
#[ORM\Id]
|
||||||
|
|
@ -47,6 +50,7 @@ class Bucket
|
||||||
private int $startingAmount = 0;
|
private int $startingAmount = 0;
|
||||||
|
|
||||||
#[ORM\Column(type: 'decimal', precision: 5, scale: 2, options: ['default' => '0.00'])]
|
#[ORM\Column(type: 'decimal', precision: 5, scale: 2, options: ['default' => '0.00'])]
|
||||||
|
#[Assert\GreaterThanOrEqual(0)]
|
||||||
private string $bufferMultiplier = '0.00';
|
private string $bufferMultiplier = '0.00';
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
|
|
|
||||||
18
src/Validator/BufferOnlyForFixedLimit.php
Normal file
18
src/Validator/BufferOnlyForFixedLimit.php
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Validator;
|
||||||
|
|
||||||
|
use Symfony\Component\Validator\Constraint;
|
||||||
|
|
||||||
|
#[\Attribute(\Attribute::TARGET_CLASS)]
|
||||||
|
final class BufferOnlyForFixedLimit extends Constraint
|
||||||
|
{
|
||||||
|
public string $message = 'The buffer multiplier can only be set for fixed-limit buckets.';
|
||||||
|
|
||||||
|
public const string BUFFER_NOT_ALLOWED_ERROR = 'BUFFER_NOT_ALLOWED_ERROR';
|
||||||
|
|
||||||
|
public function getTargets(): string
|
||||||
|
{
|
||||||
|
return self::CLASS_CONSTRAINT;
|
||||||
|
}
|
||||||
|
}
|
||||||
34
src/Validator/BufferOnlyForFixedLimitValidator.php
Normal file
34
src/Validator/BufferOnlyForFixedLimitValidator.php
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Validator;
|
||||||
|
|
||||||
|
use App\Entity\Bucket;
|
||||||
|
use App\Enum\BucketAllocationType;
|
||||||
|
use Symfony\Component\Validator\Constraint;
|
||||||
|
use Symfony\Component\Validator\ConstraintValidator;
|
||||||
|
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
|
||||||
|
|
||||||
|
final class BufferOnlyForFixedLimitValidator extends ConstraintValidator
|
||||||
|
{
|
||||||
|
public function validate(mixed $value, Constraint $constraint): void
|
||||||
|
{
|
||||||
|
if (!$constraint instanceof BufferOnlyForFixedLimit) {
|
||||||
|
throw new UnexpectedTypeException($constraint, BufferOnlyForFixedLimit::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$value instanceof Bucket) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BucketAllocationType::FIXED_LIMIT === $value->getAllocationType()) {
|
||||||
|
return; // buffer allowed
|
||||||
|
}
|
||||||
|
|
||||||
|
if (0.0 !== (float) $value->getBufferMultiplier()) {
|
||||||
|
$this->context->buildViolation($constraint->message)
|
||||||
|
->atPath('bufferMultiplier')
|
||||||
|
->setCode(BufferOnlyForFixedLimit::BUFFER_NOT_ALLOWED_ERROR)
|
||||||
|
->addViolation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
116
tests/Validator/BufferOnlyForFixedLimitTest.php
Normal file
116
tests/Validator/BufferOnlyForFixedLimitTest.php
Normal file
|
|
@ -0,0 +1,116 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Tests\Validator;
|
||||||
|
|
||||||
|
use App\Entity\Bucket;
|
||||||
|
use App\Entity\Scenario;
|
||||||
|
use App\Enum\BucketAllocationType;
|
||||||
|
use App\Enum\BucketType;
|
||||||
|
use App\Validator\BufferOnlyForFixedLimit;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||||
|
use Symfony\Component\Validator\ConstraintViolationListInterface;
|
||||||
|
use Symfony\Component\Validator\Validator\ValidatorInterface;
|
||||||
|
|
||||||
|
final class BufferOnlyForFixedLimitTest extends KernelTestCase
|
||||||
|
{
|
||||||
|
private ValidatorInterface $validator;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
self::bootKernel();
|
||||||
|
$this->validator = self::getContainer()->get(ValidatorInterface::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFixedLimitBucketWithNonZeroBufferIsValid(): void
|
||||||
|
{
|
||||||
|
$bucket = $this->makeBucket(BucketType::NEED, BucketAllocationType::FIXED_LIMIT, 50000, '1.50');
|
||||||
|
|
||||||
|
$violations = $this->validator->validate($bucket);
|
||||||
|
|
||||||
|
$matching = $this->violationsAt($violations, 'bufferMultiplier', BufferOnlyForFixedLimit::class);
|
||||||
|
|
||||||
|
self::assertCount(
|
||||||
|
0,
|
||||||
|
$matching,
|
||||||
|
'FIXED_LIMIT bucket with a non-zero buffer must not raise BufferOnlyForFixedLimit.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUnlimitedBucketWithNonZeroBufferIsInvalid(): void
|
||||||
|
{
|
||||||
|
$bucket = $this->makeBucket(BucketType::OVERFLOW, BucketAllocationType::UNLIMITED, null, '2.00');
|
||||||
|
|
||||||
|
$violations = $this->validator->validate($bucket);
|
||||||
|
|
||||||
|
$matching = $this->violationsAt($violations, 'bufferMultiplier', BufferOnlyForFixedLimit::class);
|
||||||
|
|
||||||
|
self::assertNotCount(
|
||||||
|
0,
|
||||||
|
$matching,
|
||||||
|
'UNLIMITED bucket with a non-zero buffer must raise a violation on bufferMultiplier.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testNegativeBufferIsInvalid(): void
|
||||||
|
{
|
||||||
|
$bucket = $this->makeBucket(BucketType::NEED, BucketAllocationType::FIXED_LIMIT, 50000, '-1.00');
|
||||||
|
|
||||||
|
$violations = $this->validator->validate($bucket);
|
||||||
|
|
||||||
|
$matching = $this->violationsAt($violations, 'bufferMultiplier');
|
||||||
|
|
||||||
|
self::assertNotCount(
|
||||||
|
0,
|
||||||
|
$matching,
|
||||||
|
'A negative bufferMultiplier must raise a violation on bufferMultiplier (GreaterThanOrEqual(0)).',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function makeBucket(
|
||||||
|
BucketType $type,
|
||||||
|
BucketAllocationType $allocationType,
|
||||||
|
?int $allocationValue,
|
||||||
|
string $bufferMultiplier,
|
||||||
|
): Bucket {
|
||||||
|
$scenario = new Scenario();
|
||||||
|
$scenario->setName('Household Budget');
|
||||||
|
|
||||||
|
$bucket = new Bucket();
|
||||||
|
$bucket->setScenario($scenario);
|
||||||
|
$bucket->setType($type);
|
||||||
|
$bucket->setName('Test Bucket');
|
||||||
|
$bucket->setPriority(1);
|
||||||
|
$bucket->setSortOrder(0);
|
||||||
|
$bucket->setAllocationType($allocationType);
|
||||||
|
$bucket->setAllocationValue($allocationValue);
|
||||||
|
$bucket->setStartingAmount(0);
|
||||||
|
$bucket->setBufferMultiplier($bufferMultiplier);
|
||||||
|
|
||||||
|
return $bucket;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return list<\Symfony\Component\Validator\ConstraintViolationInterface>
|
||||||
|
*/
|
||||||
|
private function violationsAt(
|
||||||
|
ConstraintViolationListInterface $violations,
|
||||||
|
string $propertyPath,
|
||||||
|
?string $constraintClass = null,
|
||||||
|
): array {
|
||||||
|
$matching = [];
|
||||||
|
|
||||||
|
foreach ($violations as $violation) {
|
||||||
|
if ($violation->getPropertyPath() !== $propertyPath) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null !== $constraintClass && !$violation->getConstraint() instanceof $constraintClass) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$matching[] = $violation;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $matching;
|
||||||
|
}
|
||||||
|
}
|
||||||
102
tests/Validator/BufferOnlyForFixedLimitValidatorTest.php
Normal file
102
tests/Validator/BufferOnlyForFixedLimitValidatorTest.php
Normal file
|
|
@ -0,0 +1,102 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Tests\Validator;
|
||||||
|
|
||||||
|
use App\Entity\Bucket;
|
||||||
|
use App\Entity\Scenario;
|
||||||
|
use App\Enum\BucketAllocationType;
|
||||||
|
use App\Enum\BucketType;
|
||||||
|
use App\Validator\BufferOnlyForFixedLimit;
|
||||||
|
use App\Validator\BufferOnlyForFixedLimitValidator;
|
||||||
|
use Symfony\Component\Validator\ConstraintValidatorInterface;
|
||||||
|
use Symfony\Component\Validator\Test\ConstraintValidatorTestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends ConstraintValidatorTestCase<BufferOnlyForFixedLimitValidator>
|
||||||
|
*/
|
||||||
|
final class BufferOnlyForFixedLimitValidatorTest extends ConstraintValidatorTestCase
|
||||||
|
{
|
||||||
|
protected function createValidator(): ConstraintValidatorInterface
|
||||||
|
{
|
||||||
|
return new BufferOnlyForFixedLimitValidator();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFixedLimitWithNonZeroBufferIsValid(): void
|
||||||
|
{
|
||||||
|
$bucket = $this->makeBucket(BucketAllocationType::FIXED_LIMIT, '1.50');
|
||||||
|
|
||||||
|
$this->validator->validate($bucket, new BufferOnlyForFixedLimit());
|
||||||
|
|
||||||
|
$this->assertNoViolation();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFixedLimitWithZeroBufferIsValid(): void
|
||||||
|
{
|
||||||
|
$bucket = $this->makeBucket(BucketAllocationType::FIXED_LIMIT, '0.00');
|
||||||
|
|
||||||
|
$this->validator->validate($bucket, new BufferOnlyForFixedLimit());
|
||||||
|
|
||||||
|
$this->assertNoViolation();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testPercentageWithNonZeroBufferIsInvalid(): void
|
||||||
|
{
|
||||||
|
$bucket = $this->makeBucket(BucketAllocationType::PERCENTAGE, '1.50');
|
||||||
|
|
||||||
|
$this->validator->validate($bucket, new BufferOnlyForFixedLimit());
|
||||||
|
|
||||||
|
$this->buildViolation('The buffer multiplier can only be set for fixed-limit buckets.')
|
||||||
|
->atPath('property.path.bufferMultiplier')
|
||||||
|
->setCode(BufferOnlyForFixedLimit::BUFFER_NOT_ALLOWED_ERROR)
|
||||||
|
->assertRaised();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUnlimitedWithNonZeroBufferIsInvalid(): void
|
||||||
|
{
|
||||||
|
$bucket = $this->makeBucket(BucketAllocationType::UNLIMITED, '2.00');
|
||||||
|
|
||||||
|
$this->validator->validate($bucket, new BufferOnlyForFixedLimit());
|
||||||
|
|
||||||
|
$this->buildViolation('The buffer multiplier can only be set for fixed-limit buckets.')
|
||||||
|
->atPath('property.path.bufferMultiplier')
|
||||||
|
->setCode(BufferOnlyForFixedLimit::BUFFER_NOT_ALLOWED_ERROR)
|
||||||
|
->assertRaised();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testPercentageWithZeroBufferIsValid(): void
|
||||||
|
{
|
||||||
|
$bucket = $this->makeBucket(BucketAllocationType::PERCENTAGE, '0.00');
|
||||||
|
|
||||||
|
$this->validator->validate($bucket, new BufferOnlyForFixedLimit());
|
||||||
|
|
||||||
|
$this->assertNoViolation();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testPercentageWithZeroBufferWithoutDecimalsIsValid(): void
|
||||||
|
{
|
||||||
|
$bucket = $this->makeBucket(BucketAllocationType::PERCENTAGE, '0');
|
||||||
|
|
||||||
|
$this->validator->validate($bucket, new BufferOnlyForFixedLimit());
|
||||||
|
|
||||||
|
$this->assertNoViolation();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function makeBucket(BucketAllocationType $allocationType, string $bufferMultiplier): Bucket
|
||||||
|
{
|
||||||
|
$scenario = new Scenario();
|
||||||
|
$scenario->setName('Household Budget');
|
||||||
|
|
||||||
|
$bucket = new Bucket();
|
||||||
|
$bucket->setScenario($scenario);
|
||||||
|
$bucket->setType(BucketType::NEED);
|
||||||
|
$bucket->setName('Test Bucket');
|
||||||
|
$bucket->setPriority(1);
|
||||||
|
$bucket->setSortOrder(0);
|
||||||
|
$bucket->setAllocationType($allocationType);
|
||||||
|
$bucket->setAllocationValue(BucketAllocationType::FIXED_LIMIT === $allocationType ? 50000 : null);
|
||||||
|
$bucket->setStartingAmount(0);
|
||||||
|
$bucket->setBufferMultiplier($bufferMultiplier);
|
||||||
|
|
||||||
|
return $bucket;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue