feat: enhance doctor import process with source profile ID for improved idempotency and deduplication
This commit is contained in:
@@ -190,6 +190,69 @@ class DoctorImportTest extends ApiTestCase
|
||||
$this->assertSame('manual', $doctor->getSource());
|
||||
}
|
||||
|
||||
/** UUID تازه برای هر اجرا — یونیکایندکس (source, source_profile_id) تکرار را رد میکند. */
|
||||
private function freshProfileId(): string
|
||||
{
|
||||
return bin2hex(random_bytes(16)); // ۳۲ نویسهٔ hex، منطبق با استخراجکنندهٔ profile id
|
||||
}
|
||||
|
||||
private function payloadWithProfile(string $code, string $profileId): array
|
||||
{
|
||||
$p = $this->importPayload($code);
|
||||
$p['source_ref'] = "https://membersearch.irimc.org/member/profile?id=$profileId";
|
||||
return $p;
|
||||
}
|
||||
|
||||
public function testSameProfileIdWithDifferentCodeUpdatesInsteadOfDuplicating(): void
|
||||
{
|
||||
$admin = $this->createUser(['ROLE_USER', 'ROLE_ADMIN']);
|
||||
$profileId = $this->freshProfileId();
|
||||
$code1 = $this->freshCode();
|
||||
$code2 = $this->freshCode();
|
||||
|
||||
$first = $this->authJson('POST', '/api/v1/admin/doctors/import', $admin, $this->payloadWithProfile($code1, $profileId));
|
||||
$this->assertSame(201, $this->responseCode());
|
||||
|
||||
// همان پروفایل irimc، ولی کد نظام پزشکی متفاوت (نمایش دیگرِ همان پزشک)
|
||||
$second = $this->authJson('POST', '/api/v1/admin/doctors/import', $admin, $this->payloadWithProfile($code2, $profileId));
|
||||
$this->assertSame(200, $this->responseCode());
|
||||
$this->assertFalse($second['data']['created']);
|
||||
$this->assertSame($first['data']['uuid'], $second['data']['uuid']);
|
||||
|
||||
$this->em->clear();
|
||||
$count = $this->em->getRepository(Doctor::class)->count(['source' => 'irimc', 'sourceProfileId' => $profileId]);
|
||||
$this->assertSame(1, $count, 'یک پروفایل نباید دو رکورد بسازد حتی با کد متفاوت');
|
||||
$doctor = $this->em->getRepository(Doctor::class)->findOneBy(['sourceProfileId' => $profileId]);
|
||||
$this->assertSame($code2, $doctor->getMedicalSystemCode(), 'کد باید به آخرین مقدار بهروزرسانی شود');
|
||||
}
|
||||
|
||||
public function testDifferentProfileIdsAreKeptSeparate(): void
|
||||
{
|
||||
$admin = $this->createUser(['ROLE_USER', 'ROLE_ADMIN']);
|
||||
|
||||
// همان نام، ولی دو پروفایل متمایز irimc → دو رکورد، بدون ادغام خودکار
|
||||
$a = $this->authJson('POST', '/api/v1/admin/doctors/import', $admin, $this->payloadWithProfile($this->freshCode(), $this->freshProfileId()));
|
||||
$this->assertSame(201, $this->responseCode());
|
||||
$b = $this->authJson('POST', '/api/v1/admin/doctors/import', $admin, $this->payloadWithProfile($this->freshCode(), $this->freshProfileId()));
|
||||
$this->assertSame(201, $this->responseCode());
|
||||
|
||||
$this->assertTrue($b['data']['created']);
|
||||
$this->assertNotSame($a['data']['uuid'], $b['data']['uuid']);
|
||||
}
|
||||
|
||||
public function testProfileIdIsBackfilledFromSourceRefOnImport(): void
|
||||
{
|
||||
$admin = $this->createUser(['ROLE_USER', 'ROLE_ADMIN']);
|
||||
$profileId = $this->freshProfileId();
|
||||
|
||||
$data = $this->authJson('POST', '/api/v1/admin/doctors/import', $admin, $this->payloadWithProfile($this->freshCode(), $profileId));
|
||||
$this->assertSame(201, $this->responseCode());
|
||||
|
||||
$this->em->clear();
|
||||
$doctor = $this->em->getRepository(Doctor::class)->findOneBy(['uuid' => $data['data']['uuid']]);
|
||||
$this->assertSame($profileId, $doctor->getSourceProfileId());
|
||||
}
|
||||
|
||||
public function testValidationErrors(): void
|
||||
{
|
||||
$admin = $this->createUser(['ROLE_USER', 'ROLE_ADMIN']);
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tests\Doctor;
|
||||
|
||||
use App\Doctor\Service\SourceProfileId;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* شناسهٔ پروفایل، کلید اصلی idempotency ایمپورت است؛ استخراجش باید در برابر تغییر
|
||||
* قالب URL (دامنه/پارامتر اضافه) پایدار بماند و برای قالب ناشناخته null بدهد
|
||||
* تا مسیر روی medical_system_code fallback کند.
|
||||
*/
|
||||
class SourceProfileIdTest extends TestCase
|
||||
{
|
||||
#[DataProvider('refs')]
|
||||
public function testFromRef(?string $ref, ?string $expected): void
|
||||
{
|
||||
$this->assertSame($expected, SourceProfileId::fromRef($ref));
|
||||
}
|
||||
|
||||
public static function refs(): array
|
||||
{
|
||||
$uuid = 'ad600622-b98a-48ac-a448-89ef68fd2c46';
|
||||
|
||||
return [
|
||||
'irimc profile url' => ["https://membersearch.irimc.org/member/profile?id=$uuid", $uuid],
|
||||
'extra query params' => ["https://membersearch.irimc.org/member/profile?lang=fa&id=$uuid&x=1", $uuid],
|
||||
'different host, same id' => ["http://example.test/x?id=$uuid", $uuid],
|
||||
'uppercase normalized to lowercase' => ['?id=' . strtoupper($uuid), $uuid],
|
||||
'no id param' => ['https://membersearch.irimc.org/member/profile', null],
|
||||
'plain code, not a url' => ['42507', null],
|
||||
'empty' => ['', null],
|
||||
'whitespace' => [' ', null],
|
||||
'null' => [null, null],
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user