createUser(['ROLE_DOCTOR']), 'دکتر تست'); $this->em->persist($doctor); $this->em->flush(); return $doctor; } private function newBooking(Doctor $doctor, int $start): Appointment { return new Appointment($doctor, $this->createUser(), $start, $start + 1_800); } public function testTwoLiveBookingsOnSameSlotViolateUniqueKey(): void { $doctor = $this->makeDoctor(); $start = time() + 86_400; $this->em->persist($this->newBooking($doctor, $start)); $this->em->flush(); $this->em->persist($this->newBooking($doctor, $start)); $this->expectException(UniqueConstraintViolationException::class); $this->em->flush(); } public function testCancelledBookingFreesTheSlot(): void { $doctor = $this->makeDoctor(); $start = time() + 86_400; $first = $this->newBooking($doctor, $start); $this->em->persist($first); $this->em->flush(); $first->transitionTo(Appointment::STATUS_CANCELLED_BY_USER); $this->em->flush(); $second = $this->newBooking($doctor, $start); $this->em->persist($second); $this->em->flush(); $this->assertNotNull($second->getId()); } public function testBookAtomicallyRejectsAlreadyTakenSlot(): void { $doctor = $this->makeDoctor(); $start = time() + 86_400; $repo = $this->em->getRepository(Appointment::class); $first = $this->newBooking($doctor, $start); $first->markPendingWithTtl(Appointment::PAYMENT_TTL); $repo->bookAtomically($first); $this->expectException(SlotTakenException::class); $repo->bookAtomically($this->newBooking($doctor, $start)); } }