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; } }