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.', ); } public function testFindByScenarioOrderedByPriorityReturnsScenarioBucketsInPriorityOrder(): void { $scenario = $this->persistScenario(); // Inserted out of priority order on purpose. $third = $this->persistBucket($scenario, BucketType::NEED, 3); $first = $this->persistBucket($scenario, BucketType::WANT, 1); $second = $this->persistBucket($scenario, BucketType::OVERFLOW, 2); $buckets = $this->repository->findByScenarioOrderedByPriority($scenario); self::assertSame( [$first->getId(), $second->getId(), $third->getId()], array_map(static fn (Bucket $bucket) => $bucket->getId(), $buckets), 'Buckets must come back ordered by priority ascending, regardless of insertion order.', ); } public function testFindByScenarioOrderedByPriorityReturnsOnlyTheGivenScenariosBuckets(): void { $scenarioA = $this->persistScenario('Scenario A'); $scenarioB = $this->persistScenario('Scenario B'); $bucketA1 = $this->persistBucket($scenarioA, BucketType::NEED, 1); $bucketA2 = $this->persistBucket($scenarioA, BucketType::OVERFLOW, 2); $this->persistBucket($scenarioB, BucketType::NEED, 1); $buckets = $this->repository->findByScenarioOrderedByPriority($scenarioA); self::assertSame( [$bucketA1->getId(), $bucketA2->getId()], array_map(static fn (Bucket $bucket) => $bucket->getId(), $buckets), 'Only the queried scenario\'s buckets must be returned — another scenario\'s buckets must not leak in.', ); } public function testFindByScenarioOrderedByPriorityReturnsEmptyForScenarioWithNoBuckets(): void { $scenario = $this->persistScenario(); self::assertSame( [], $this->repository->findByScenarioOrderedByPriority($scenario), 'A scenario with no buckets must return an empty array.', ); } 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; } }