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, ]; } public function testCreatesUserWithNationalCode(): void { [$owner, $doctor] = $this->doctor(); $nc = $this->nationalCode(); $this->authJson('POST', '/api/v1/my/appointment', $owner, $this->body($doctor->getUuid(), $this->mobile(), $nc)); self::assertSame(201, $this->responseCode()); $patient = $this->em->getRepository(User::class)->findOneBy(['nationalCode' => $nc]); self::assertNotNull($patient); self::assertSame($nc, $patient->getNationalCode()); } public function testSameNationalCodeDifferentMobileReusesSinglePatient(): void { [$owner, $doctor] = $this->doctor(); $nc = $this->nationalCode(); // First booking under mobile A creates the patient. $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()); $patients = $this->em->getRepository(User::class)->findBy(['nationalCode' => $nc]); self::assertCount(1, $patients); } 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']); } }