and search results, so a * phone number or "test" must never be storable. The guard lives on the entity * because eight different call sites construct a Doctor. */ class PollutedNameRejectionTest extends ApiTestCase { public function testDoctorCannotBeCreatedWithPhoneNumberAsName(): void { $this->expectException(AppException::class); new Doctor($this->createUser(['ROLE_DOCTOR']), '09390039833'); } public function testDoctorCannotBeRenamedToPlaceholder(): void { $doctor = new Doctor($this->createUser(['ROLE_DOCTOR']), 'دکتر علی احمدی'); $this->expectException(AppException::class); $doctor->setName('test'); } public function testClinicCannotBeNamedAfterPhoneNumber(): void { $clinic = new Clinic($this->createUser(['ROLE_CLINIC'])); $this->expectException(AppException::class); $clinic->setName('09398631203'); } public function testClinicNameMayStayNullBeforeItIsSet(): void { $clinic = new Clinic($this->createUser(['ROLE_CLINIC'])); $clinic->setName(null); $this->assertNull($clinic->getName()); } public function testAdminCreatingDoctorWithPhoneNameGets422(): void { $admin = $this->createUser(['ROLE_ADMIN']); $this->em->flush(); $this->authJson('POST', '/api/v1/admin/doctors', $admin, [ 'name' => '09390039833', 'mobile_number' => '0912' . random_int(1000000, 9999999), ]); $this->assertSame(422, $this->responseCode()); } /** * ریشهٔ آلودگی تولیدی: دعوت پزشک فقط با موبایل، شماره را به‌عنوان نام می‌نشاند. */ public function testInvitingDoctorByMobileDoesNotUseMobileAsName(): void { $owner = $this->createUser(['ROLE_CLINIC']); $clinic = new Clinic($owner); $this->em->persist($clinic); $this->em->flush(); $mobile = '0912' . random_int(1000000, 9999999); $this->authJson('POST', "/api/v1/admin/clinic/{$clinic->getUuid()}/invite-doctor", $owner, [ 'mobile' => $mobile, ]); $this->assertSame(201, $this->responseCode()); $invitation = $this->em->getRepository(ClinicDoctorInvitation::class) ->findOneBy(['mobile' => $mobile]); $this->assertNotNull($invitation, 'invitation was not created'); $this->client->request('POST', "/api/v1/clinic-invitation/{$invitation->getToken()}/accept"); $this->assertSame(200, $this->responseCode()); $this->em->clear(); $doctor = $this->em->getRepository(Doctor::class)->findOneBy(['mobileNumber' => $mobile]); $this->assertNotNull($doctor, 'invited doctor was not created'); $this->assertNotSame($mobile, $doctor->getName(), 'mobile number leaked into the doctor name'); $this->assertSame('پزشک دعوت‌شده', $doctor->getName()); } public function testInvitedNameIsUsedWhenTheClinicProvidesOne(): void { $owner = $this->createUser(['ROLE_CLINIC']); $clinic = new Clinic($owner); $this->em->persist($clinic); $this->em->flush(); $mobile = '0912' . random_int(1000000, 9999999); $this->authJson('POST', "/api/v1/admin/clinic/{$clinic->getUuid()}/invite-doctor", $owner, [ 'mobile' => $mobile, 'name' => 'دکتر مریم رضایی', ]); $this->assertSame(201, $this->responseCode()); $invitation = $this->em->getRepository(ClinicDoctorInvitation::class) ->findOneBy(['mobile' => $mobile]); $this->client->request('POST', "/api/v1/clinic-invitation/{$invitation->getToken()}/accept"); $this->assertSame(200, $this->responseCode()); $this->em->clear(); $doctor = $this->em->getRepository(Doctor::class)->findOneBy(['mobileNumber' => $mobile]); $this->assertSame('دکتر مریم رضایی', $doctor->getName()); } }