get(UserRepository::class), static::getContainer()->get(ClinicStaffRepository::class), static::getContainer()->get(UserPasswordHasherInterface::class), ); } private function newStaff(int $entityId, string $name = 'زهرا احمدی'): ClinicStaff { $staff = new ClinicStaff('doctor', $entityId, $name); $this->em->persist($staff); $this->em->flush(); return $staff; } private function newDoctorOwner(): Doctor { $owner = $this->createUser(['ROLE_DOCTOR']); $doctor = new Doctor($owner, 'دکتر تست'); $this->em->persist($doctor); $this->em->flush(); return $doctor; } public function testCreatesNewUserWithStaffRole(): void { $doctor = $this->newDoctorOwner(); $staff = $this->newStaff($doctor->getId()); $mobile = '0912' . str_pad((string) random_int(0, 9_999_999), 7, '0', STR_PAD_LEFT); $user = $this->service()->attachAccount($staff, $mobile, 'Staff@1234', $doctor->getUser()); self::assertContains('ROLE_STAFF', $user->getRoles()); self::assertSame($mobile, $user->getMobileNumber()); self::assertSame('زهرا احمدی', $user->getRealName()); self::assertTrue($staff->hasAccount()); self::assertSame($user->getId(), $staff->getUser()?->getId()); self::assertSame($mobile, $staff->getPhone()); $hasher = static::getContainer()->get(UserPasswordHasherInterface::class); self::assertTrue($hasher->isPasswordValid($user, 'Staff@1234')); } /** کاربر موجود فقط نقش می‌گیرد؛ رمز و نامش پاک نمی‌شود. */ public function testReusesExistingUserAndKeepsPassword(): void { $doctor = $this->newDoctorOwner(); $existing = $this->createUser(['ROLE_USER']); $existing->setRealName('نام قبلی'); $hasher = static::getContainer()->get(UserPasswordHasherInterface::class); $existing->setPasswordHash($hasher->hashPassword($existing, 'Old@12345')); $this->em->flush(); $staff = $this->newStaff($doctor->getId()); $user = $this->service()->attachAccount($staff, $existing->getMobileNumber(), null, $doctor->getUser()); self::assertSame($existing->getId(), $user->getId()); self::assertContains('ROLE_STAFF', $user->getRoles()); self::assertContains('ROLE_USER', $user->getRoles()); self::assertSame('نام قبلی', $user->getRealName()); self::assertTrue($hasher->isPasswordValid($user, 'Old@12345')); } public function testRejectsInvalidMobile(): void { $doctor = $this->newDoctorOwner(); $staff = $this->newStaff($doctor->getId()); $this->expectException(AppException::class); $this->expectExceptionMessage(ErrorCodes::message(ErrorCodes::ERR_STAFF_MOBILE_INVALID)); $this->service()->attachAccount($staff, '12345', 'Staff@1234', $doctor->getUser()); } public function testRejectsOwnerMobile(): void { $doctor = $this->newDoctorOwner(); $staff = $this->newStaff($doctor->getId()); try { $this->service()->attachAccount($staff, $doctor->getUser()->getMobileNumber(), null, $doctor->getUser()); self::fail('owner mobile must be rejected'); } catch (AppException $e) { self::assertSame(ErrorCodes::ERR_STAFF_MOBILE_INVALID, $e->getErrorCode()); self::assertSame(422, $e->getHttpStatus()); } } /** مرزی: همان شماره، همان محیط، ردیف دوم → 409. */ public function testRejectsDuplicateMobileInSameEntity(): void { $doctor = $this->newDoctorOwner(); $first = $this->newStaff($doctor->getId(), 'پرسنل اول'); $second = $this->newStaff($doctor->getId(), 'پرسنل دوم'); $mobile = '0912' . str_pad((string) random_int(0, 9_999_999), 7, '0', STR_PAD_LEFT); $this->service()->attachAccount($first, $mobile, null, $doctor->getUser()); try { $this->service()->attachAccount($second, $mobile, null, $doctor->getUser()); self::fail('duplicate mobile in the same entity must be rejected'); } catch (AppException $e) { self::assertSame(ErrorCodes::ERR_STAFF_MOBILE_TAKEN, $e->getErrorCode()); self::assertSame(409, $e->getHttpStatus()); } } /** ارقام فارسی از هر کلاینتی بیاید، شمارهٔ ذخیره‌شده لاتین است. */ public function testNormalizesPersianDigits(): void { $doctor = $this->newDoctorOwner(); $staff = $this->newStaff($doctor->getId()); $user = $this->service()->attachAccount($staff, '۰۹۱۲۳۴۵۶۷۸۹', null, $doctor->getUser()); self::assertSame('09123456789', $user->getMobileNumber()); } /** تغییر شماره یعنی تغییر نام‌کاربری ورود؛ ردیف پرسنل همان می‌ماند. */ public function testReattachWithNewMobileMovesTheAccount(): void { $doctor = $this->newDoctorOwner(); $staff = $this->newStaff($doctor->getId()); $first = '0912' . str_pad((string) random_int(0, 9_999_999), 7, '0', STR_PAD_LEFT); $second = '0913' . str_pad((string) random_int(0, 9_999_999), 7, '0', STR_PAD_LEFT); $this->service()->attachAccount($staff, $first, null, $doctor->getUser()); $user = $this->service()->attachAccount($staff, $second, null, $doctor->getUser()); self::assertSame($second, $user->getMobileNumber()); self::assertSame($second, $staff->getPhone()); self::assertSame($user->getId(), $staff->getUser()?->getId()); } }