Files
ephub/tests/Repository/BookingRepositoryTest.php
T
2026-09-17 16:10:34 +02:00

204 lines
7.9 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Tests\Repository;
use App\Entity\Booking;
use App\Entity\Resource;
use App\Entity\User;
use App\Enum\ResourceType;
use App\Model\TimeRange;
use App\Repository\BookingRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
/**
* Hits the real database, because these are queries.
*
* Note what is NOT here any more: the double-booking guard. It used to be a unique index on
* (resource, date) and was tested by asserting the insert blew up. A booking is now a time range,
* which no unique index can guard, so the guard lives in BookingManager and is tested there --
* what this class covers is the overlap query that guard relies on.
*
* Each test runs inside a transaction that is rolled back afterwards.
*/
class BookingRepositoryTest extends KernelTestCase
{
private EntityManagerInterface $em;
private BookingRepository $bookings;
protected function setUp(): void
{
self::bootKernel();
$this->em = self::getContainer()->get(EntityManagerInterface::class);
$this->bookings = self::getContainer()->get(BookingRepository::class);
$this->em->getConnection()->beginTransaction();
}
protected function tearDown(): void
{
$this->em->getConnection()->rollBack();
parent::tearDown();
}
public function testFindOverlappingSeesASpanThatCollides(): void
{
$desk = $this->resource('D1');
$date = new \DateTimeImmutable('2026-10-01');
$this->persist(new Booking($this->user('[email protected]'), $desk, $date, TimeRange::fromMinutes(9 * 60, 13 * 60)));
$this->em->flush();
self::assertCount(1, $this->bookings->findOverlapping($desk, $date, TimeRange::fromMinutes(11 * 60, 15 * 60)));
}
public function testFindOverlappingTreatsTouchingSpansAsFree(): void
{
$desk = $this->resource('D1');
$date = new \DateTimeImmutable('2026-10-01');
$this->persist(new Booking($this->user('[email protected]'), $desk, $date, TimeRange::fromMinutes(9 * 60, 13 * 60)));
$this->em->flush();
// Half-open: 09:00-13:00 and 13:00-17:00 do not collide.
self::assertSame([], $this->bookings->findOverlapping($desk, $date, TimeRange::fromMinutes(13 * 60, 17 * 60)));
}
public function testAnAllDayBookingBlocksEverySpan(): void
{
$desk = $this->resource('D1');
$date = new \DateTimeImmutable('2026-10-01');
$this->persist(new Booking($this->user('[email protected]'), $desk, $date));
$this->em->flush();
self::assertCount(1, $this->bookings->findOverlapping($desk, $date, TimeRange::fromMinutes(8 * 60, 8 * 60 + 15)));
}
public function testTheSameResourceOnAnotherDayIsFine(): void
{
$desk = $this->resource('D1');
$user = $this->user('[email protected]');
$this->persist(new Booking($user, $desk, new \DateTimeImmutable('2026-10-01')));
$this->persist(new Booking($user, $desk, new \DateTimeImmutable('2026-10-02')));
$this->em->flush();
self::assertCount(1, $this->bookings->findForDate(new \DateTimeImmutable('2026-10-01')));
}
public function testATimeComponentOnTheDateDoesNotShiftWhichDayABookingIsOn(): void
{
$desk = $this->resource('D1');
$this->persist(new Booking($this->user('[email protected]'), $desk, new \DateTimeImmutable('2026-10-01 17:30')));
$this->em->flush();
$this->em->clear();
self::assertCount(1, $this->bookings->findForDate(new \DateTimeImmutable('2026-10-01')));
}
public function testFindForDateOnlyReturnsWhatOverlapsTheSpan(): void
{
$date = new \DateTimeImmutable('2026-10-01');
$user = $this->user('[email protected]');
$this->persist(new Booking($user, $this->resource('D1'), $date, TimeRange::fromMinutes(9 * 60, 12 * 60)));
$this->persist(new Booking($user, $this->resource('D2'), $date, TimeRange::fromMinutes(14 * 60, 17 * 60)));
$this->em->flush();
$morning = $this->bookings->findForDate($date, TimeRange::fromMinutes(8 * 60, 13 * 60));
self::assertCount(1, $morning);
self::assertSame('D1', $morning[0]->getResource()->getLabel());
self::assertCount(2, $this->bookings->findForDate($date));
}
public function testFindForDateCarriesTheBookingHolderForTheTeamView(): void
{
$this->persist(new Booking($this->user('[email protected]', 'Anna'), $this->resource('D1'), new \DateTimeImmutable('2026-10-01')));
$this->em->flush();
$this->em->clear();
$bookings = $this->bookings->findForDate(new \DateTimeImmutable('2026-10-01'));
self::assertCount(1, $bookings);
self::assertSame('Anna', $bookings[0]->getUser()->getName());
self::assertSame('D1', $bookings[0]->getResource()->getLabel());
}
public function testCountPerDateOnlyCountsWhatOverlapsTheSpan(): void
{
$user = $this->user('[email protected]');
$this->persist(new Booking($user, $this->resource('D1'), new \DateTimeImmutable('2026-10-01'), TimeRange::fromMinutes(9 * 60, 12 * 60)));
$this->persist(new Booking($this->user('[email protected]'), $this->resource('D2'), new \DateTimeImmutable('2026-10-01'), TimeRange::fromMinutes(14 * 60, 17 * 60)));
$this->em->flush();
$counts = $this->bookings->countPerDateInRange(
new \DateTimeImmutable('2026-10-01'),
new \DateTimeImmutable('2026-10-31'),
TimeRange::fromMinutes(8 * 60, 13 * 60),
);
// The calendar number has to mean the same thing as the colours on the plan, so it is
// filtered by the same span.
self::assertSame(['2026-10-01' => 1], $counts);
}
public function testCountPerDateInRangeIsKeyedByDay(): void
{
$user = $this->user('[email protected]');
$this->persist(new Booking($user, $this->resource('D1'), new \DateTimeImmutable('2026-10-01')));
$this->persist(new Booking($this->user('[email protected]'), $this->resource('D2'), new \DateTimeImmutable('2026-10-01')));
$this->persist(new Booking($user, $this->resource('P1', ResourceType::PARKING), new \DateTimeImmutable('2026-10-03')));
$this->em->flush();
$counts = $this->bookings->countPerDateInRange(new \DateTimeImmutable('2026-10-01'), new \DateTimeImmutable('2026-10-31'));
self::assertSame(['2026-10-01' => 2, '2026-10-03' => 1], $counts);
}
public function testCountPerDateIgnoresDaysOutsideTheWindow(): void
{
$this->persist(new Booking($this->user('[email protected]'), $this->resource('D1'), new \DateTimeImmutable('2026-11-05')));
$this->em->flush();
self::assertSame([], $this->bookings->countPerDateInRange(new \DateTimeImmutable('2026-10-01'), new \DateTimeImmutable('2026-10-31')));
}
public function testFindForUserInRangeOnlyReturnsThatPersonsBookings(): void
{
$anna = $this->user('[email protected]');
$this->persist(new Booking($anna, $this->resource('D1'), new \DateTimeImmutable('2026-10-01')));
$this->persist(new Booking($this->user('[email protected]'), $this->resource('D2'), new \DateTimeImmutable('2026-10-01')));
$this->em->flush();
$mine = $this->bookings->findForUserInRange($anna, new \DateTimeImmutable('2026-10-01'), new \DateTimeImmutable('2026-10-31'));
self::assertCount(1, $mine);
self::assertSame('D1', $mine[0]->getResource()->getLabel());
}
private function persist(object $entity): void
{
$this->em->persist($entity);
}
private function user(string $email, string $name = 'Test'): User
{
$user = new User($email, $name);
$this->em->persist($user);
return $user;
}
private function resource(string $label, ResourceType $type = ResourceType::DESK): Resource
{
$resource = new Resource($type, $label);
$this->em->persist($resource);
return $resource;
}
}