buckets/tests/Repository/BucketRepositoryTest.php

176 lines
6.1 KiB
PHP

<?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 App\Tests\Concerns\CreatesUsers;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
final class BucketRepositoryTest extends KernelTestCase
{
use CreatesUsers;
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.',
);
}
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);
$scenario->setOwner($this->persistOwner());
$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;
}
}