get(EntityContextResolver::class); } private function makeDoctor(?User $user = null): Doctor { $doctor = new Doctor($user ?? $this->createUser(['ROLE_DOCTOR']), 'دکتر تست'); $this->em->persist($doctor); $this->em->flush(); return $doctor; } private function makeClinic(?User $owner = null): Clinic { $clinic = new Clinic($owner ?? $this->createUser(['ROLE_CLINIC'])); $clinic->setName('کلینیک تست'); $this->em->persist($clinic); $this->em->flush(); return $clinic; } private function joinClinic(Clinic $clinic, Doctor $doctor): void { $clinic->getDoctors()->add($doctor); $this->em->persist(new ClinicDoctorPermission($clinic, $doctor)); $this->em->flush(); } private function setActiveContext(User $user, string $dbUuid, string $dbType): void { $this->em->persist(new UserActiveContext($user, $dbUuid, $dbType)); $this->em->flush(); } private function setClinicContext(User $user, Clinic $clinic): void { $this->setActiveContext($user, $clinic->getUuid(), EntityContext::TYPE_CLINIC); } private function setDoctorContext(User $user, Doctor $doctor): void { $this->setActiveContext($user, $doctor->getUuid(), EntityContext::TYPE_DOCTOR); } // ── سناریو ۱: پزشک مستقل ──────────────────────────────────────────────── public function testIndependentDoctorAlwaysResolvesToOwnPractice(): void { $doctor = $this->makeDoctor(); $context = $this->resolver()->resolve($doctor->getUser()); self::assertSame(['doctor', $doctor->getId()], $context->toEntityPair()); } // ── سناریو ۲: پزشکِ مستقلِ عضو کلینیک ─────────────────────────────────── public function testClinicMemberDoctorResolvesToClinicWhenActiveContextIsClinic(): void { $doctor = $this->makeDoctor(); $clinic = $this->makeClinic(); $this->joinClinic($clinic, $doctor); $this->setClinicContext($doctor->getUser(), $clinic); $context = $this->resolver()->resolve($doctor->getUser()); self::assertSame(['clinic', $clinic->getId()], $context->toEntityPair()); } public function testClinicMemberDoctorFallsBackToOwnPracticeWhenActiveContextIsSelf(): void { $doctor = $this->makeDoctor(); $clinic = $this->makeClinic(); $this->joinClinic($clinic, $doctor); $this->setDoctorContext($doctor->getUser(), $doctor); $context = $this->resolver()->resolve($doctor->getUser()); self::assertSame(['doctor', $doctor->getId()], $context->toEntityPair()); } /** * مجوزِ غیرفعال محیط را عوض نمی‌کند — عضویت در clinic_doctors تعیین‌کنندهٔ * «کجا»ست و ClinicDoctorPermission تعیین‌کنندهٔ «چه کاری». محدودسازی در * PatientRecordScopeResolver اتفاق می‌افتد، نه اینجا. */ public function testInactivePermissionStillResolvesToClinicEnvironment(): void { $doctor = $this->makeDoctor(); $clinic = $this->makeClinic(); $this->joinClinic($clinic, $doctor); $permission = $this->em->getRepository(ClinicDoctorPermission::class) ->findOneBy(['clinic' => $clinic, 'doctor' => $doctor]); $permission->setActive(false); $this->em->flush(); $this->setClinicContext($doctor->getUser(), $clinic); $context = $this->resolver()->resolve($doctor->getUser()); self::assertSame(['clinic', $clinic->getId()], $context->toEntityPair()); } // ── سناریو ۳: پزشکی که هم عضو و هم مالک کلینیک است ────────────────────── public function testDoctorWhoOwnsClinicResolvesToClinicWithoutMembershipRow(): void { $user = $this->createUser(['ROLE_DOCTOR', 'ROLE_CLINIC']); $doctor = $this->makeDoctor($user); $clinic = $this->makeClinic($user); $this->setClinicContext($user, $clinic); $context = $this->resolver()->resolve($user); self::assertSame(['clinic', $clinic->getId()], $context->toEntityPair()); self::assertTrue($this->resolver()->canActInClinic($user, $clinic)); } public function testDoctorWhoOwnsClinicResolvesToOwnPracticeWhenActiveContextIsSelf(): void { $user = $this->createUser(['ROLE_DOCTOR', 'ROLE_CLINIC']); $doctor = $this->makeDoctor($user); $this->makeClinic($user); $this->setDoctorContext($user, $doctor); $context = $this->resolver()->resolve($user); self::assertSame(['doctor', $doctor->getId()], $context->toEntityPair()); } // ── سناریو ۴: مدیر/مالک کلینیک (غیرپزشک) ──────────────────────────────── public function testClinicManagerAlwaysResolvesToClinic(): void { $clinic = $this->makeClinic(); $context = $this->resolver()->resolve($clinic->getUser()); self::assertSame(['clinic', $clinic->getId()], $context->toEntityPair()); } // ── سناریو ۵: منشی ────────────────────────────────────────────────────── public function testSecretaryWithoutActiveContextResolvesToUnknown(): void { $user = $this->createUser(['ROLE_SECRETARY']); $context = $this->resolver()->resolve($user); self::assertSame(EntityContext::TYPE_UNKNOWN, $context->type); self::assertFalse($context->isResolved()); } public function testSecretaryWithActiveClinicRelationResolvesToThatClinic(): void { $doctor = $this->makeDoctor(); $clinic = $this->makeClinic(); $this->joinClinic($clinic, $doctor); $secretary = $this->createUser(['ROLE_SECRETARY']); $this->em->persist(new DoctorSecretary($doctor, $secretary, $clinic)); $this->em->flush(); $this->setClinicContext($secretary, $clinic); $context = $this->resolver()->resolve($secretary); self::assertSame(['clinic', $clinic->getId()], $context->toEntityPair()); } public function testSecretaryWithActiveDoctorRelationResolvesToThatPractice(): void { $doctor = $this->makeDoctor(); $secretary = $this->createUser(['ROLE_SECRETARY']); $this->em->persist(new DoctorSecretary($doctor, $secretary)); $this->em->flush(); $this->setDoctorContext($secretary, $doctor); $context = $this->resolver()->resolve($secretary); self::assertSame(['doctor', $doctor->getId()], $context->toEntityPair()); } /** رابطهٔ غیرفعال = پایان همکاری؛ محیط دیگر حل نمی‌شود. */ public function testSecretaryWithInactiveRelationResolvesToUnknown(): void { $doctor = $this->makeDoctor(); $clinic = $this->makeClinic(); $this->joinClinic($clinic, $doctor); $secretary = $this->createUser(['ROLE_SECRETARY']); $relation = new DoctorSecretary($doctor, $secretary, $clinic); $relation->setActive(false); $this->em->persist($relation); $this->em->flush(); $this->setClinicContext($secretary, $clinic); $context = $this->resolver()->resolve($secretary); self::assertFalse($context->isResolved()); } // ── مسیر clinic_uuid صریح ─────────────────────────────────────────────── public function testExplicitClinicUuidForNonMemberThrowsAccessDenied(): void { $doctor = $this->makeDoctor(); $clinic = $this->makeClinic(); $this->expectException(AppException::class); $this->resolver()->resolve($doctor->getUser(), $clinic->getUuid()); } public function testTryResolveReturnsNullInsteadOfThrowingForNonMember(): void { $doctor = $this->makeDoctor(); $clinic = $this->makeClinic(); self::assertNull($this->resolver()->tryResolve($doctor->getUser(), $clinic->getUuid())); } public function testExplicitClinicUuidWinsOverStoredActiveContext(): void { $doctor = $this->makeDoctor(); $clinicA = $this->makeClinic(); $clinicB = $this->makeClinic(); $this->joinClinic($clinicA, $doctor); $this->joinClinic($clinicB, $doctor); $this->setClinicContext($doctor->getUser(), $clinicA); $context = $this->resolver()->resolve($doctor->getUser(), $clinicB->getUuid()); self::assertSame(['clinic', $clinicB->getId()], $context->toEntityPair()); } }