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 testTheSameResourceCannotBeBookedTwiceOnOneDay(): void { $desk = $this->resource('D1'); $date = new \DateTimeImmutable('2026-10-01'); $this->persist(new Booking($this->user('first@ep-reisen.de'), $desk, $date)); $this->em->flush(); $this->persist(new Booking($this->user('second@ep-reisen.de'), $desk, $date)); // The controller catches exactly this and re-renders the fragment with an inline error, // rather than pre-checking, which would be racy. $this->expectException(UniqueConstraintViolationException::class); $this->em->flush(); } public function testTheSameResourceOnAnotherDayIsFine(): void { $desk = $this->resource('D1'); $user = $this->user('first@ep-reisen.de'); $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 testATimeComponentDoesNotCreateASecondSlotForTheSameDay(): void { $desk = $this->resource('D1'); $this->persist(new Booking($this->user('first@ep-reisen.de'), $desk, new \DateTimeImmutable('2026-10-01 08:00'))); $this->em->flush(); $this->persist(new Booking($this->user('second@ep-reisen.de'), $desk, new \DateTimeImmutable('2026-10-01 17:30'))); $this->expectException(UniqueConstraintViolationException::class); $this->em->flush(); } public function testFindForDateCarriesTheBookingHolderForTheTeamView(): void { $this->persist(new Booking($this->user('anna@ep-reisen.de', '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 testCountPerDateInRangeIsKeyedByDay(): void { $user = $this->user('anna@ep-reisen.de'); $this->persist(new Booking($user, $this->resource('D1'), new \DateTimeImmutable('2026-10-01'))); $this->persist(new Booking($this->user('bea@ep-reisen.de'), $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('anna@ep-reisen.de'), $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('anna@ep-reisen.de'); $this->persist(new Booking($anna, $this->resource('D1'), new \DateTimeImmutable('2026-10-01'))); $this->persist(new Booking($this->user('bea@ep-reisen.de'), $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; } }