createUser(['ROLE_DOCTOR']); $doctor = new Doctor($owner, 'دکتر'); $this->em->persist($doctor); $this->em->flush(); return [$owner, $doctor]; } private function nationalCode(): string { return '00' . str_pad((string) random_int(0, 99_999_999), 8, '0', STR_PAD_LEFT); } private function mobile(): string { return '09' . str_pad((string) random_int(0, 999_999_999), 9, '0', STR_PAD_LEFT); } private function body(string $doctorUuid, string $mobile, string $nationalCode): array { $start = time() + 86_400 + random_int(0, 3_600) * 100; return [ 'doctor_uuid' => $doctorUuid, 'slot_start' => $start, 'slot_end' => $start + 1_800, 'patient_mobile' => $mobile, 'patient_name' => 'بیمار تست', 'patient_national_code' => $nationalCode, ]; } private function profileByNationalCode(string $nc): ?UserProfile { return $this->em->getRepository(UserProfile::class)->findOneBy(['nationalCode' => $nc]); } public function testStoresNationalCodeOnProfile(): void { [$owner, $doctor] = $this->doctor(); $nc = $this->nationalCode(); $mobile = $this->mobile(); $this->authJson('POST', '/api/v1/my/appointment', $owner, $this->body($doctor->getUuid(), $mobile, $nc)); self::assertSame(201, $this->responseCode()); $profile = $this->profileByNationalCode($nc); self::assertNotNull($profile); self::assertSame($mobile, $profile->getUser()->getMobileNumber()); } public function testSameNationalCodeDifferentMobileReusesSinglePatient(): void { [$owner, $doctor] = $this->doctor(); $nc = $this->nationalCode(); // First booking under mobile A creates the patient + profile. $this->authJson('POST', '/api/v1/my/appointment', $owner, $this->body($doctor->getUuid(), $this->mobile(), $nc)); self::assertSame(201, $this->responseCode()); // Second booking under a *different* mobile but the same national code // must resolve to the same patient — the case-file stays unique. $this->authJson('POST', '/api/v1/my/appointment', $owner, $this->body($doctor->getUuid(), $this->mobile(), $nc)); self::assertSame(201, $this->responseCode()); $profiles = $this->em->getRepository(UserProfile::class)->findBy(['nationalCode' => $nc]); self::assertCount(1, $profiles); } /** * The reported bug: a patient already exists whose national code sits on * their profile. Booking under a brand-new mobile with that same national * code must attach to the existing patient, not stamp the code onto a new * user. */ public function testExistingProfileNationalCodeIsReusedNotDuplicated(): void { [$owner, $doctor] = $this->doctor(); $nc = $this->nationalCode(); // Existing patient with the national code on their profile. $existing = $this->createUser(['ROLE_USER'], $this->mobile()); $profile = new UserProfile($existing); $profile->setNationalCode($nc); $this->em->persist($profile); $this->em->flush(); // Book under a different mobile but the same national code. $res = $this->authJson('POST', '/api/v1/my/appointment', $owner, $this->body($doctor->getUuid(), $this->mobile(), $nc)); self::assertSame(201, $this->responseCode()); // No duplicate profile, and the appointment is attached to the existing user. $profiles = $this->em->getRepository(UserProfile::class)->findBy(['nationalCode' => $nc]); self::assertCount(1, $profiles); $appointment = $this->em->getRepository(Appointment::class)->findOneBy(['uuid' => $res['data']['uuid']]); self::assertSame($existing->getId(), $appointment->getUser()->getId()); } public function testMissingNationalCodeIs422(): void { [$owner, $doctor] = $this->doctor(); $body = $this->body($doctor->getUuid(), $this->mobile(), $this->nationalCode()); unset($body['patient_national_code']); $this->authJson('POST', '/api/v1/my/appointment', $owner, $body); self::assertSame(422, $this->responseCode()); } public function testInvalidNationalCodeIs422(): void { [$owner, $doctor] = $this->doctor(); $this->authJson('POST', '/api/v1/my/appointment', $owner, $this->body($doctor->getUuid(), $this->mobile(), '123')); self::assertSame(422, $this->responseCode()); } public function testMobileBelongingToAnotherNationalCodeIsRejected(): void { [$owner, $doctor] = $this->doctor(); $mobile = $this->mobile(); // First booking binds this mobile to national code A. $this->authJson('POST', '/api/v1/my/appointment', $owner, $this->body($doctor->getUuid(), $mobile, $this->nationalCode())); self::assertSame(201, $this->responseCode()); // Same mobile, a *different* national code → identity conflict. $res = $this->authJson('POST', '/api/v1/my/appointment', $owner, $this->body($doctor->getUuid(), $mobile, $this->nationalCode())); self::assertSame(422, $this->responseCode()); self::assertSame(ErrorCodes::ERR_PROFILE_MOBILE_TAKEN, $res['errors'][0]['code']); } }