createUser(['ROLE_DOCTOR']), 'دکتر تست محیط'); $this->em->persist($doctor); $this->em->flush(); return $doctor; } private function makeClinic(): Clinic { $clinic = new Clinic($this->createUser(['ROLE_CLINIC'])); $clinic->setName('کلینیک تست محیط'); $this->em->persist($clinic); $this->em->flush(); return $clinic; } private function uniqueSlot(): int { return strtotime('+90 days') + random_int(0, 500_000) * 7; } public function testPersonalBookingIsOwnedByTheDoctor(): void { $doctor = $this->makeDoctor(); $start = $this->uniqueSlot(); $appointment = $this->newAppointment($doctor, $this->createUser(), $start, $start + 900); $this->em->persist($appointment); $this->em->flush(); self::assertSame('doctor', $appointment->getEntityType()); self::assertSame($doctor->getId(), $appointment->getEntityId()); } public function testClinicBookingIsOwnedByTheClinic(): void { $doctor = $this->makeDoctor(); $clinic = $this->makeClinic(); $clinic->getDoctors()->add($doctor); $this->em->flush(); $start = $this->uniqueSlot(); $appointment = $this->newAppointment($doctor, $this->createUser(), $start, $start + 900, $clinic); $this->em->persist($appointment); $this->em->flush(); self::assertSame('clinic', $appointment->getEntityType()); self::assertSame($clinic->getId(), $appointment->getEntityId()); } /** یک پزشک در دو محیط = دو مالک متفاوت برای دو نوبت. */ public function testSameDoctorGetsDifferentOwnersInDifferentEnvironments(): void { $doctor = $this->makeDoctor(); $clinic = $this->makeClinic(); $clinic->getDoctors()->add($doctor); $this->em->flush(); $personal = $this->newAppointment($doctor, $this->createUser(), $this->uniqueSlot(), $this->uniqueSlot() + 900); $inClinic = $this->newAppointment($doctor, $this->createUser(), $this->uniqueSlot(), $this->uniqueSlot() + 900, $clinic); $this->em->persist($personal); $this->em->persist($inClinic); $this->em->flush(); self::assertSame(['doctor', $doctor->getId()], [$personal->getEntityType(), $personal->getEntityId()]); self::assertSame(['clinic', $clinic->getId()], [$inClinic->getEntityType(), $inClinic->getEntityId()]); } /** ❌ محیط حل‌نشده هرگز ذخیره نمی‌شود — نه با شناسهٔ صفر، نه بی‌صدا. */ public function testAssigningAnUnresolvedContextIsRejected(): void { $doctor = $this->makeDoctor(); $start = $this->uniqueSlot(); $appointment = new Appointment($doctor, $this->createUser(), $start, $start + 900); $this->expectException(\InvalidArgumentException::class); $appointment->assignTenant(EntityContext::unknown()); } /** * ⚠️ حالت مرزی: دو پزشکِ متفاوت در یک کلینیک، هر کدام برنامهٔ هفتگی خودشان — * کلید یکتا باید doctor_id را هم داشته باشد وگرنه پزشک دوم رد می‌شود. */ public function testTwoDoctorsInOneClinicEachKeepTheirOwnSchedule(): void { $clinic = $this->makeClinic(); $doctorA = $this->makeDoctor(); $doctorB = $this->makeDoctor(); $clinic->getDoctors()->add($doctorA); $clinic->getDoctors()->add($doctorB); $this->em->flush(); $this->em->persist($this->newWeeklySchedule($doctorA, [], $clinic)); $this->em->persist($this->newWeeklySchedule($doctorB, [], $clinic)); $this->em->flush(); $rows = $this->em->getRepository(WeeklySchedule::class) ->findBy(['entityType' => 'clinic', 'entityId' => $clinic->getId()]); self::assertCount(2, $rows); } /** همان پزشک، همان محیط، دو برنامه — هنوز غیرممکن است. */ public function testOneSchedulePerEnvironmentPerDoctorIsStillEnforced(): void { $doctor = $this->makeDoctor(); $this->em->persist($this->newWeeklySchedule($doctor, [])); $this->em->flush(); $this->em->persist($this->newWeeklySchedule($doctor, [])); $this->expectException(UniqueConstraintViolationException::class); $this->em->flush(); } }