createUser(['ROLE_USER', 'ROLE_DOCTOR']); $doctor = new Doctor($user, $name); $doctor->setMobileNumber($user->getMobileNumber()); $this->em->persist($doctor); $this->em->flush(); return $doctor; } /** @return array{0: User, 1: Clinic} */ private function makeClinicWith(Doctor ...$doctors): array { $owner = $this->createUser(['ROLE_USER', 'ROLE_CLINIC']); $clinic = new Clinic($owner); $clinic->setName('کلینیک تست'); foreach ($doctors as $d) { $clinic->getDoctors()->add($d); } $this->em->persist($clinic); $this->em->flush(); return [$owner, $clinic]; } private function activeContext(User $user, string $dbUuid): void { static::getContainer()->get(UserActiveContextRepository::class)->upsert($user, $dbUuid); } /** پروندهٔ کلینیکی بیمار + نوبتی که او را به این پزشک وصل می‌کند. */ private function makeClinicRecordFor(Clinic $clinic, Doctor $doctor, ?User $patient = null): PatientRecord { $patient ??= $this->createUser(); $record = new PatientRecord('clinic', $clinic->getId(), $patient, 'system', $clinic->getId()); $this->em->persist($record); $start = strtotime('+60 days') + random_int(0, 500_000) * 7; $appointment = $this->newAppointment($doctor, $patient, $start, $start + 900, $clinic); $this->em->persist($appointment); $this->em->flush(); return $record; } private function uuidsFromList(array $response): array { return array_map(fn(array $row) => $row['uuid'], $response['data'] ?? []); } // ── پزشک عضو ───────────────────────────────────────────────────────────── public function testMemberDoctorSeesOwnPatientsClinicRecord(): void { $doctor = $this->makeDoctor(); [, $clinic] = $this->makeClinicWith($doctor); $record = $this->makeClinicRecordFor($clinic, $doctor); $this->activeContext($doctor->getUser(), $clinic->getUuid()); $res = $this->authJson('GET', '/api/v1/patients?limit=50', $doctor->getUser()); self::assertSame(200, $this->responseCode()); self::assertContains($record->getUuid(), $this->uuidsFromList($res)); } public function testMemberDoctorCannotSeeAnotherDoctorsClinicRecord(): void { $mine = $this->makeDoctor('پزشک من'); $theirs = $this->makeDoctor('پزشک دیگر'); [, $clinic] = $this->makeClinicWith($mine, $theirs); $foreign = $this->makeClinicRecordFor($clinic, $theirs); $this->activeContext($mine->getUser(), $clinic->getUuid()); $res = $this->authJson('GET', '/api/v1/patients?limit=50', $mine->getUser()); self::assertNotContains($foreign->getUuid(), $this->uuidsFromList($res)); $this->authJson('GET', "/api/v1/patient/{$foreign->getUuid()}", $mine->getUser()); self::assertSame(404, $this->responseCode(), 'پروندهٔ بیمارِ پزشک دیگر برای او وجود ندارد'); } public function testMemberDoctorCanOpenAndManageOwnPatientRecord(): void { $doctor = $this->makeDoctor(); [, $clinic] = $this->makeClinicWith($doctor); $record = $this->makeClinicRecordFor($clinic, $doctor); $this->activeContext($doctor->getUser(), $clinic->getUuid()); $this->authJson('GET', "/api/v1/patient/{$record->getUuid()}", $doctor->getUser()); self::assertSame(200, $this->responseCode()); $this->authJson('POST', "/api/v1/patient/{$record->getUuid()}/note", $doctor->getUser(), [ 'body' => 'یادداشت پزشک عضو', ]); self::assertSame(201, $this->responseCode(), 'پزشک عضو فعال پرونده را مدیریت هم می‌کند'); } public function testDoctorInPersonalContextSeesOnlyOwnOfficeRecords(): void { $doctor = $this->makeDoctor(); [, $clinic] = $this->makeClinicWith($doctor); $clinicRecord = $this->makeClinicRecordFor($clinic, $doctor); // بدون محیط فعالِ کلینیک ⇒ مطب شخصی. $this->activeContext($doctor->getUser(), $doctor->getUuid()); $res = $this->authJson('GET', '/api/v1/patients?limit=50', $doctor->getUser()); self::assertSame(200, $this->responseCode()); self::assertNotContains($clinicRecord->getUuid(), $this->uuidsFromList($res)); } // ── پایان همکاری ───────────────────────────────────────────────────────── public function testDeactivatedDoctorLosesClinicRecordsButOwnerKeepsThem(): void { $doctor = $this->makeDoctor(); [$owner, $clinic] = $this->makeClinicWith($doctor); $record = $this->makeClinicRecordFor($clinic, $doctor); $this->activeContext($doctor->getUser(), $clinic->getUuid()); $this->authJson('GET', "/api/v1/patient/{$record->getUuid()}", $doctor->getUser()); self::assertSame(200, $this->responseCode(), 'پزشک فعال دسترسی دارد'); $permissions = static::getContainer()->get(ClinicDoctorPermissionRepository::class); $permissions->getOrCreate($clinic, $doctor)->setActive(false); $this->em->flush(); $this->authJson('GET', "/api/v1/patient/{$record->getUuid()}", $doctor->getUser()); self::assertSame(404, $this->responseCode(), 'بعد از پایان همکاری، دسترسی قطع می‌شود'); $this->authJson('GET', "/api/v1/patient/{$record->getUuid()}", $owner); self::assertSame(200, $this->responseCode(), 'مدیر کلینیک دسترسی کامل دارد'); $this->em->clear(); self::assertNotNull( $this->em->getRepository(PatientRecord::class)->find($record->getId()), 'پرونده حذف یا منتقل نمی‌شود', ); } // ── مدیر کلینیک ────────────────────────────────────────────────────────── public function testClinicOwnerSeesEveryDoctorsRecords(): void { $first = $this->makeDoctor('پزشک اول'); $second = $this->makeDoctor('پزشک دوم'); [$owner, $clinic] = $this->makeClinicWith($first, $second); $firstRecord = $this->makeClinicRecordFor($clinic, $first); $secondRecord = $this->makeClinicRecordFor($clinic, $second); $res = $this->authJson('GET', '/api/v1/patients?limit=50', $owner); $uuids = $this->uuidsFromList($res); self::assertSame(200, $this->responseCode()); self::assertContains($firstRecord->getUuid(), $uuids); self::assertContains($secondRecord->getUuid(), $uuids); } // ── منشی ───────────────────────────────────────────────────────────────── public function testClinicSecretaryIsLimitedToAssignedDoctors(): void { $mine = $this->makeDoctor('پزشک من'); $theirs = $this->makeDoctor('پزشک دیگر'); [, $clinic] = $this->makeClinicWith($mine, $theirs); $ownRecord = $this->makeClinicRecordFor($clinic, $mine); $foreign = $this->makeClinicRecordFor($clinic, $theirs); $secretaryUser = $this->createUser(['ROLE_USER', 'ROLE_SECRETARY']); $relation = new DoctorSecretary($mine, $secretaryUser, DoctorSecretary::OWNER_CLINIC, $clinic); $this->em->persist($relation); $this->em->flush(); $this->activeContext($secretaryUser, $clinic->getUuid()); $res = $this->authJson('GET', '/api/v1/patients?limit=50', $secretaryUser); $uuids = $this->uuidsFromList($res); self::assertSame(200, $this->responseCode()); self::assertContains($ownRecord->getUuid(), $uuids); self::assertNotContains($foreign->getUuid(), $uuids); } // ── قطعی‌کردن نوبت کلینیکی، دیده‌شده توسط هر دو نقش ─────────────────────── public function testConfirmedClinicAppointmentRecordIsVisibleToBothRoles(): void { $doctor = $this->makeDoctor(); [$owner, $clinic] = $this->makeClinicWith($doctor); $patient = $this->createUser(); $start = strtotime('+70 days') + random_int(0, 500_000) * 7; $appointment = $this->newAppointment($doctor, $patient, $start, $start + 900, $clinic); $appointment->setVisitPriceRials(3_000_000); $this->em->persist($appointment); $this->em->flush(); // محیط فعال را قبل از confirm ست کن: آن درخواست EntityManager را پاک می‌کند. $this->activeContext($doctor->getUser(), $clinic->getUuid()); $this->authJson('POST', "/api/v1/appointment/{$appointment->getUuid()}/confirm", $owner, [ 'version' => $appointment->getVersion(), ]); self::assertSame(200, $this->responseCode()); $record = $this->em->getRepository(PatientRecord::class)->findOneBy([ 'entityType' => 'clinic', 'entityId' => $clinic->getId(), 'user' => $patient, ]); self::assertNotNull($record); $this->authJson('GET', "/api/v1/patient/{$record->getUuid()}", $owner); self::assertSame(200, $this->responseCode(), 'مدیر کلینیک'); $this->authJson('GET', "/api/v1/patient/{$record->getUuid()}", $doctor->getUser()); self::assertSame(200, $this->responseCode(), 'پزشکِ همان نوبت'); } }