31 - Add single-overflow-per-scenario invariant
This commit is contained in:
parent
7855e41e84
commit
3163a95f37
7 changed files with 440 additions and 23 deletions
|
|
@ -6,6 +6,7 @@ use App\Enum\BucketAllocationType;
|
||||||
use App\Enum\BucketType;
|
use App\Enum\BucketType;
|
||||||
use App\Repository\BucketRepository;
|
use App\Repository\BucketRepository;
|
||||||
use App\Validator\CompatibleAllocationType;
|
use App\Validator\CompatibleAllocationType;
|
||||||
|
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;
|
||||||
|
|
||||||
|
|
@ -13,6 +14,7 @@ use Symfony\Component\Uid\UuidV7;
|
||||||
#[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]
|
||||||
class Bucket
|
class Bucket
|
||||||
{
|
{
|
||||||
#[ORM\Id]
|
#[ORM\Id]
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,11 @@
|
||||||
namespace App\Repository;
|
namespace App\Repository;
|
||||||
|
|
||||||
use App\Entity\Bucket;
|
use App\Entity\Bucket;
|
||||||
|
use App\Entity\Scenario;
|
||||||
|
use App\Enum\BucketType;
|
||||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||||
use Doctrine\Persistence\ManagerRegistry;
|
use Doctrine\Persistence\ManagerRegistry;
|
||||||
|
use Symfony\Component\Uid\UuidV7;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @extends ServiceEntityRepository<Bucket>
|
* @extends ServiceEntityRepository<Bucket>
|
||||||
|
|
@ -16,28 +19,20 @@ class BucketRepository extends ServiceEntityRepository
|
||||||
parent::__construct($registry, Bucket::class);
|
parent::__construct($registry, Bucket::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
// /**
|
public function countOverflowBucketsForScenario(Scenario $scenario, ?UuidV7 $excludeId = null): int
|
||||||
// * @return Bucket[] Returns an array of Bucket objects
|
{
|
||||||
// */
|
$qb = $this->createQueryBuilder('b')
|
||||||
// public function findByExampleField($value): array
|
->select('COUNT(b.id)')
|
||||||
// {
|
->andWhere('b.scenario = :scenario')
|
||||||
// return $this->createQueryBuilder('b')
|
->andWhere('b.type = :overflow')
|
||||||
// ->andWhere('b.exampleField = :val')
|
->setParameter('scenario', $scenario)
|
||||||
// ->setParameter('val', $value)
|
->setParameter('overflow', BucketType::OVERFLOW);
|
||||||
// ->orderBy('b.id', 'ASC')
|
|
||||||
// ->setMaxResults(10)
|
|
||||||
// ->getQuery()
|
|
||||||
// ->getResult()
|
|
||||||
// ;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// public function findOneBySomeField($value): ?Bucket
|
if (null !== $excludeId) {
|
||||||
// {
|
$qb->andWhere('b.id != :excludeId')
|
||||||
// return $this->createQueryBuilder('b')
|
->setParameter('excludeId', $excludeId, 'uuid');
|
||||||
// ->andWhere('b.exampleField = :val')
|
}
|
||||||
// ->setParameter('val', $value)
|
|
||||||
// ->getQuery()
|
return (int) $qb->getQuery()->getSingleScalarResult();
|
||||||
// ->getOneOrNullResult()
|
}
|
||||||
// ;
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
18
src/Validator/SingleOverflowPerScenario.php
Normal file
18
src/Validator/SingleOverflowPerScenario.php
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Validator;
|
||||||
|
|
||||||
|
use Symfony\Component\Validator\Constraint;
|
||||||
|
|
||||||
|
#[\Attribute(\Attribute::TARGET_CLASS)]
|
||||||
|
final class SingleOverflowPerScenario extends Constraint
|
||||||
|
{
|
||||||
|
public string $message = 'A scenario can only have one overflow bucket.';
|
||||||
|
|
||||||
|
public const string TOO_MANY_OVERFLOW_ERROR = 'TOO_MANY_OVERFLOW_ERROR';
|
||||||
|
|
||||||
|
public function getTargets(): string
|
||||||
|
{
|
||||||
|
return self::CLASS_CONSTRAINT;
|
||||||
|
}
|
||||||
|
}
|
||||||
44
src/Validator/SingleOverflowPerScenarioValidator.php
Normal file
44
src/Validator/SingleOverflowPerScenarioValidator.php
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Validator;
|
||||||
|
|
||||||
|
use App\Entity\Bucket;
|
||||||
|
use App\Enum\BucketType;
|
||||||
|
use App\Repository\BucketRepository;
|
||||||
|
use Symfony\Component\Validator\Constraint;
|
||||||
|
use Symfony\Component\Validator\ConstraintValidator;
|
||||||
|
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
|
||||||
|
|
||||||
|
final class SingleOverflowPerScenarioValidator extends ConstraintValidator
|
||||||
|
{
|
||||||
|
public function __construct(private readonly BucketRepository $bucketRepository)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function validate(mixed $value, Constraint $constraint): void
|
||||||
|
{
|
||||||
|
if (!$constraint instanceof SingleOverflowPerScenario) {
|
||||||
|
throw new UnexpectedTypeException($constraint, SingleOverflowPerScenario::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$value instanceof Bucket) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BucketType::OVERFLOW !== $value->getType()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$existing = $this->bucketRepository->countOverflowBucketsForScenario(
|
||||||
|
$value->getScenario(),
|
||||||
|
$value->getId(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($existing > 0) {
|
||||||
|
$this->context->buildViolation($constraint->message)
|
||||||
|
->atPath('type')
|
||||||
|
->setCode(SingleOverflowPerScenario::TOO_MANY_OVERFLOW_ERROR)
|
||||||
|
->addViolation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
125
tests/Repository/BucketRepositoryTest.php
Normal file
125
tests/Repository/BucketRepositoryTest.php
Normal file
|
|
@ -0,0 +1,125 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Tests\Repository;
|
||||||
|
|
||||||
|
use App\Entity\Bucket;
|
||||||
|
use App\Entity\Scenario;
|
||||||
|
use App\Enum\BucketAllocationType;
|
||||||
|
use App\Enum\BucketType;
|
||||||
|
use App\Repository\BucketRepository;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||||
|
|
||||||
|
final class BucketRepositoryTest extends KernelTestCase
|
||||||
|
{
|
||||||
|
private EntityManagerInterface $em;
|
||||||
|
private BucketRepository $repository;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
self::bootKernel();
|
||||||
|
$this->em = self::getContainer()->get(EntityManagerInterface::class);
|
||||||
|
$this->repository = self::getContainer()->get(BucketRepository::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCountIsZeroWhenScenarioHasNoOverflowBucket(): void
|
||||||
|
{
|
||||||
|
$scenario = $this->persistScenario();
|
||||||
|
|
||||||
|
$this->persistBucket($scenario, BucketType::NEED, 1);
|
||||||
|
$this->persistBucket($scenario, BucketType::WANT, 2);
|
||||||
|
|
||||||
|
self::assertSame(
|
||||||
|
0,
|
||||||
|
$this->repository->countOverflowBucketsForScenario($scenario, null),
|
||||||
|
'A scenario with no OVERFLOW bucket must count zero.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCountIsOneWhenScenarioHasOneOverflowBucket(): void
|
||||||
|
{
|
||||||
|
$scenario = $this->persistScenario();
|
||||||
|
|
||||||
|
$this->persistBucket($scenario, BucketType::NEED, 1);
|
||||||
|
$this->persistBucket($scenario, BucketType::OVERFLOW, 2);
|
||||||
|
|
||||||
|
self::assertSame(
|
||||||
|
1,
|
||||||
|
$this->repository->countOverflowBucketsForScenario($scenario, null),
|
||||||
|
'A scenario with exactly one OVERFLOW bucket must count one.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCountExcludesTheGivenBucketIdFromTheTally(): void
|
||||||
|
{
|
||||||
|
$scenario = $this->persistScenario();
|
||||||
|
|
||||||
|
$overflow = $this->persistBucket($scenario, BucketType::OVERFLOW, 1);
|
||||||
|
|
||||||
|
self::assertSame(
|
||||||
|
0,
|
||||||
|
$this->repository->countOverflowBucketsForScenario($scenario, $overflow->getId()),
|
||||||
|
'Excluding the existing overflow bucket\'s own id must drop the count to zero — '
|
||||||
|
.'this is what allows updating the sole overflow bucket without falsely tripping the rule.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCountIsScopedToTheGivenScenarioOnly(): void
|
||||||
|
{
|
||||||
|
$scenarioA = $this->persistScenario('Scenario A');
|
||||||
|
$scenarioB = $this->persistScenario('Scenario B');
|
||||||
|
|
||||||
|
$this->persistBucket($scenarioA, BucketType::OVERFLOW, 1);
|
||||||
|
|
||||||
|
self::assertSame(
|
||||||
|
0,
|
||||||
|
$this->repository->countOverflowBucketsForScenario($scenarioB, null),
|
||||||
|
'An OVERFLOW bucket belonging to a different scenario must not be counted.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testNeedAndWantBucketsDoNotCountTowardTheOverflowTotal(): void
|
||||||
|
{
|
||||||
|
$scenario = $this->persistScenario();
|
||||||
|
|
||||||
|
$this->persistBucket($scenario, BucketType::NEED, 1);
|
||||||
|
$this->persistBucket($scenario, BucketType::WANT, 2);
|
||||||
|
|
||||||
|
self::assertSame(
|
||||||
|
0,
|
||||||
|
$this->repository->countOverflowBucketsForScenario($scenario, null),
|
||||||
|
'NEED and WANT buckets must never count toward the overflow tally.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function persistScenario(string $name = 'Household Budget'): Scenario
|
||||||
|
{
|
||||||
|
$scenario = new Scenario();
|
||||||
|
$scenario->setName($name);
|
||||||
|
$this->em->persist($scenario);
|
||||||
|
$this->em->flush();
|
||||||
|
|
||||||
|
return $scenario;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function persistBucket(Scenario $scenario, BucketType $type, int $priority): Bucket
|
||||||
|
{
|
||||||
|
$bucket = new Bucket();
|
||||||
|
$bucket->setScenario($scenario);
|
||||||
|
$bucket->setType($type);
|
||||||
|
$bucket->setName('Bucket '.$priority);
|
||||||
|
$bucket->setPriority($priority);
|
||||||
|
$bucket->setSortOrder($priority);
|
||||||
|
$bucket->setAllocationType(
|
||||||
|
BucketType::OVERFLOW === $type ? BucketAllocationType::UNLIMITED : BucketAllocationType::FIXED_LIMIT,
|
||||||
|
);
|
||||||
|
$bucket->setAllocationValue(BucketType::OVERFLOW === $type ? null : 50000);
|
||||||
|
$bucket->setStartingAmount(0);
|
||||||
|
$bucket->setBufferMultiplier('0.00');
|
||||||
|
|
||||||
|
$this->em->persist($bucket);
|
||||||
|
$this->em->flush();
|
||||||
|
|
||||||
|
return $bucket;
|
||||||
|
}
|
||||||
|
}
|
||||||
136
tests/Validator/SingleOverflowPerScenarioTest.php
Normal file
136
tests/Validator/SingleOverflowPerScenarioTest.php
Normal file
|
|
@ -0,0 +1,136 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Tests\Validator;
|
||||||
|
|
||||||
|
use App\Entity\Bucket;
|
||||||
|
use App\Entity\Scenario;
|
||||||
|
use App\Enum\BucketAllocationType;
|
||||||
|
use App\Enum\BucketType;
|
||||||
|
use App\Validator\SingleOverflowPerScenario;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||||
|
use Symfony\Component\Validator\ConstraintViolationListInterface;
|
||||||
|
use Symfony\Component\Validator\Validator\ValidatorInterface;
|
||||||
|
|
||||||
|
final class SingleOverflowPerScenarioTest extends KernelTestCase
|
||||||
|
{
|
||||||
|
private EntityManagerInterface $em;
|
||||||
|
private ValidatorInterface $validator;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
self::bootKernel();
|
||||||
|
$this->em = self::getContainer()->get(EntityManagerInterface::class);
|
||||||
|
$this->validator = self::getContainer()->get(ValidatorInterface::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSecondOverflowBucketOnTheSameScenarioRaisesAViolation(): void
|
||||||
|
{
|
||||||
|
$scenario = new Scenario();
|
||||||
|
$scenario->setName('Household Budget');
|
||||||
|
$this->em->persist($scenario);
|
||||||
|
$this->em->flush();
|
||||||
|
|
||||||
|
$existingOverflow = $this->makeBucket($scenario, BucketType::OVERFLOW, 1);
|
||||||
|
$this->em->persist($existingOverflow);
|
||||||
|
$this->em->flush();
|
||||||
|
|
||||||
|
$secondOverflow = $this->makeBucket($scenario, BucketType::OVERFLOW, 2);
|
||||||
|
|
||||||
|
$violations = $this->validator->validate($secondOverflow);
|
||||||
|
|
||||||
|
$matching = $this->violationsAt($violations, 'type');
|
||||||
|
|
||||||
|
self::assertNotCount(
|
||||||
|
0,
|
||||||
|
$matching,
|
||||||
|
'A second OVERFLOW bucket on a scenario that already has one must raise a violation on type. '
|
||||||
|
.'The existing overflow bucket is persisted-but-uncommitted in this test\'s DAMA transaction — '
|
||||||
|
.'the validator\'s repository query must still see it within the same transaction.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testTheSoleExistingOverflowBucketValidatesWithNoSingleOverflowViolation(): void
|
||||||
|
{
|
||||||
|
$scenario = new Scenario();
|
||||||
|
$scenario->setName('Household Budget');
|
||||||
|
$this->em->persist($scenario);
|
||||||
|
$this->em->flush();
|
||||||
|
|
||||||
|
$overflow = $this->makeBucket($scenario, BucketType::OVERFLOW, 1);
|
||||||
|
$this->em->persist($overflow);
|
||||||
|
$this->em->flush();
|
||||||
|
|
||||||
|
$violations = $this->validator->validate($overflow);
|
||||||
|
|
||||||
|
$matching = $this->violationsAt($violations, 'type');
|
||||||
|
|
||||||
|
self::assertCount(
|
||||||
|
0,
|
||||||
|
$matching,
|
||||||
|
'The single existing OVERFLOW bucket must not raise this constraint against itself '
|
||||||
|
.'(exclude-self-by-id must be working).',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFirstOverflowBucketOnAScenarioWithNoOverflowBucketsIsValid(): void
|
||||||
|
{
|
||||||
|
$scenario = new Scenario();
|
||||||
|
$scenario->setName('Household Budget');
|
||||||
|
$this->em->persist($scenario);
|
||||||
|
$this->em->flush();
|
||||||
|
|
||||||
|
$needBucket = $this->makeBucket($scenario, BucketType::NEED, 1);
|
||||||
|
$this->em->persist($needBucket);
|
||||||
|
$this->em->flush();
|
||||||
|
|
||||||
|
$firstOverflow = $this->makeBucket($scenario, BucketType::OVERFLOW, 2);
|
||||||
|
|
||||||
|
$violations = $this->validator->validate($firstOverflow);
|
||||||
|
|
||||||
|
$matching = $this->violationsAt($violations, 'type');
|
||||||
|
|
||||||
|
self::assertCount(
|
||||||
|
0,
|
||||||
|
$matching,
|
||||||
|
'A brand-new, unpersisted OVERFLOW bucket on a scenario with zero existing OVERFLOW buckets '
|
||||||
|
.'must not raise this constraint — this is the common create-the-first-overflow-bucket flow.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function makeBucket(Scenario $scenario, BucketType $type, int $priority): Bucket
|
||||||
|
{
|
||||||
|
$bucket = new Bucket();
|
||||||
|
$bucket->setScenario($scenario);
|
||||||
|
$bucket->setType($type);
|
||||||
|
$bucket->setName('Bucket '.$priority);
|
||||||
|
$bucket->setPriority($priority);
|
||||||
|
$bucket->setSortOrder($priority);
|
||||||
|
$bucket->setAllocationType(
|
||||||
|
BucketType::OVERFLOW === $type ? BucketAllocationType::UNLIMITED : BucketAllocationType::FIXED_LIMIT,
|
||||||
|
);
|
||||||
|
$bucket->setAllocationValue(BucketType::OVERFLOW === $type ? null : 50000);
|
||||||
|
$bucket->setStartingAmount(0);
|
||||||
|
$bucket->setBufferMultiplier('0.00');
|
||||||
|
|
||||||
|
return $bucket;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return list<\Symfony\Component\Validator\ConstraintViolationInterface>
|
||||||
|
*/
|
||||||
|
private function violationsAt(ConstraintViolationListInterface $violations, string $propertyPath): array
|
||||||
|
{
|
||||||
|
$matching = [];
|
||||||
|
|
||||||
|
foreach ($violations as $violation) {
|
||||||
|
if ($violation->getPropertyPath() === $propertyPath
|
||||||
|
&& $violation->getConstraint() instanceof SingleOverflowPerScenario
|
||||||
|
) {
|
||||||
|
$matching[] = $violation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $matching;
|
||||||
|
}
|
||||||
|
}
|
||||||
97
tests/Validator/SingleOverflowPerScenarioValidatorTest.php
Normal file
97
tests/Validator/SingleOverflowPerScenarioValidatorTest.php
Normal file
|
|
@ -0,0 +1,97 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Tests\Validator;
|
||||||
|
|
||||||
|
use App\Entity\Bucket;
|
||||||
|
use App\Entity\Scenario;
|
||||||
|
use App\Enum\BucketAllocationType;
|
||||||
|
use App\Enum\BucketType;
|
||||||
|
use App\Repository\BucketRepository;
|
||||||
|
use App\Validator\SingleOverflowPerScenario;
|
||||||
|
use App\Validator\SingleOverflowPerScenarioValidator;
|
||||||
|
use PHPUnit\Framework\MockObject\Stub;
|
||||||
|
use Symfony\Component\Validator\ConstraintValidatorInterface;
|
||||||
|
use Symfony\Component\Validator\Test\ConstraintValidatorTestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends ConstraintValidatorTestCase<SingleOverflowPerScenarioValidator>
|
||||||
|
*/
|
||||||
|
final class SingleOverflowPerScenarioValidatorTest extends ConstraintValidatorTestCase
|
||||||
|
{
|
||||||
|
private BucketRepository&Stub $bucketRepository;
|
||||||
|
|
||||||
|
protected function createValidator(): ConstraintValidatorInterface
|
||||||
|
{
|
||||||
|
$this->bucketRepository = $this->createStub(BucketRepository::class);
|
||||||
|
|
||||||
|
return new SingleOverflowPerScenarioValidator($this->bucketRepository);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testOverflowBucketIsValidWhenRepositoryReportsNoOtherOverflowBuckets(): void
|
||||||
|
{
|
||||||
|
$this->bucketRepository
|
||||||
|
->method('countOverflowBucketsForScenario')
|
||||||
|
->willReturn(0);
|
||||||
|
|
||||||
|
$bucket = $this->makeBucket(BucketType::OVERFLOW);
|
||||||
|
|
||||||
|
$this->validator->validate($bucket, new SingleOverflowPerScenario());
|
||||||
|
|
||||||
|
$this->assertNoViolation();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testOverflowBucketIsInvalidWhenRepositoryReportsAnExistingOverflowBucket(): void
|
||||||
|
{
|
||||||
|
$this->bucketRepository
|
||||||
|
->method('countOverflowBucketsForScenario')
|
||||||
|
->willReturn(1);
|
||||||
|
|
||||||
|
$bucket = $this->makeBucket(BucketType::OVERFLOW);
|
||||||
|
|
||||||
|
$this->validator->validate($bucket, new SingleOverflowPerScenario());
|
||||||
|
|
||||||
|
$this->buildViolation('A scenario can only have one overflow bucket.')
|
||||||
|
->atPath('property.path.type')
|
||||||
|
->setCode(SingleOverflowPerScenario::TOO_MANY_OVERFLOW_ERROR)
|
||||||
|
->assertRaised();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testNeedBucketIsValid(): void
|
||||||
|
{
|
||||||
|
$bucket = $this->makeBucket(BucketType::NEED);
|
||||||
|
|
||||||
|
$this->validator->validate($bucket, new SingleOverflowPerScenario());
|
||||||
|
|
||||||
|
$this->assertNoViolation();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testWantBucketIsValid(): void
|
||||||
|
{
|
||||||
|
$bucket = $this->makeBucket(BucketType::WANT);
|
||||||
|
|
||||||
|
$this->validator->validate($bucket, new SingleOverflowPerScenario());
|
||||||
|
|
||||||
|
$this->assertNoViolation();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function makeBucket(BucketType $type): 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(
|
||||||
|
BucketType::OVERFLOW === $type ? BucketAllocationType::UNLIMITED : BucketAllocationType::FIXED_LIMIT,
|
||||||
|
);
|
||||||
|
$bucket->setAllocationValue(BucketType::OVERFLOW === $type ? null : 50000);
|
||||||
|
$bucket->setStartingAmount(0);
|
||||||
|
$bucket->setBufferMultiplier('0.00');
|
||||||
|
|
||||||
|
return $bucket;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue