158 lines
6.1 KiB
PHP
158 lines
6.1 KiB
PHP
<?php
|
|
|
|
namespace App\Tests\Appointment;
|
|
|
|
use App\Appointment\Entity\Appointment;
|
|
use App\Auth\Entity\User;
|
|
use App\Doctor\Entity\Doctor;
|
|
use App\Shared\Constant\ErrorCodes;
|
|
use App\Tests\ApiTestCase;
|
|
use App\UserProfile\Entity\UserProfile;
|
|
|
|
/**
|
|
* POST /api/v1/my/appointment must identify the patient by national code.
|
|
* National code is stored on the patient's UserProfile (profiles.national_code,
|
|
* unique), so the patient User — and thus the case-file — is resolved by that
|
|
* profile first. One person keeps a single record even when booked under
|
|
* several mobiles.
|
|
*/
|
|
class AppointmentNationalCodeTest extends ApiTestCase
|
|
{
|
|
/** @return array{0: User, 1: Doctor} */
|
|
private function doctor(): array
|
|
{
|
|
$owner = $this->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']);
|
|
}
|
|
}
|