- Removed individual commands for backfilling specialty parents, surrogate roles, and fixing IRIMC names. - Introduced RepairImportedDoctorsCommand to consolidate functionality. - Implemented a step-based approach for repairs, allowing for idempotent execution. - Added new service classes for handling specific repair steps, including BackfillSpecialtyParentsStep, BackfillSurrogateRoleStep, FixDegreeStep, and StripNameTitleStep. - Created RepairOptions and RepairResult classes to manage step execution options and results. - Updated tests to ensure new command structure and functionality are covered, including idempotency and dry-run behavior. - Added IrimcDegreeMapper for mapping IRIMC titles to degrees.
120 lines
4.8 KiB
PHP
120 lines
4.8 KiB
PHP
<?php
|
|
|
|
namespace App\Tests\Doctor;
|
|
|
|
use App\Doctor\Entity\Doctor;
|
|
use App\Doctor\Service\Repair\DoctorRepairStep;
|
|
use Symfony\Bundle\FrameworkBundle\Console\Application;
|
|
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
|
use Symfony\Component\Console\Tester\CommandTester;
|
|
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
|
|
|
|
/**
|
|
* `app:doctors:repair` جای چهار کامند ترمیمی جداگانه را گرفته است. این تست سه
|
|
* قرارداد را قفل میکند: گامها خودکار کشف میشوند، --dry-run هیچچیز نمینویسد،
|
|
* و اجرای دوم صفر تغییر میدهد (idempotent).
|
|
*/
|
|
class RepairImportedDoctorsCommandTest extends KernelTestCase
|
|
{
|
|
private function tester(): CommandTester
|
|
{
|
|
return new CommandTester(
|
|
(new Application(self::bootKernel()))->find('app:doctors:repair')
|
|
);
|
|
}
|
|
|
|
/** پزشک irimc با نام عنواندار و درجهٔ جابهجا — دقیقاً شکل رکوردهای معیوب. */
|
|
private function makeBrokenDoctor(): Doctor
|
|
{
|
|
$em = static::getContainer()->get('doctrine')->getManager();
|
|
$mobile = '09' . str_pad((string) random_int(0, 999_999_999), 9, '0', STR_PAD_LEFT);
|
|
|
|
$user = new \App\Auth\Entity\User($mobile);
|
|
$user->setStatus(0);
|
|
$em->persist($user);
|
|
|
|
$doctor = new Doctor($user, 'دکتر آزمون ترمیم');
|
|
$doctor->setSource('irimc');
|
|
$doctor->setOwnerStatus('unclaimed');
|
|
$doctor->setInfo('تخصص بیماریهای داخلی دکترای حرفهای پزشکی');
|
|
$doctor->setDegree('expert'); // غلط: باید specialist باشد
|
|
$em->persist($doctor);
|
|
$em->flush();
|
|
|
|
return $doctor;
|
|
}
|
|
|
|
public function testEveryStepIsAutoDiscovered(): void
|
|
{
|
|
self::bootKernel();
|
|
|
|
try {
|
|
$steps = iterator_to_array(
|
|
static::getContainer()->get('test.doctor_repair_steps'),
|
|
false
|
|
);
|
|
} catch (ServiceNotFoundException) {
|
|
// بدون alias عمومی برای تگ، از خروجی --list بهعنوان منبع حقیقت استفاده کن.
|
|
$tester = $this->tester();
|
|
$tester->execute(['--list' => true]);
|
|
$out = $tester->getDisplay();
|
|
|
|
foreach (['names', 'degrees', 'specialty-parents', 'surrogate-role'] as $name) {
|
|
$this->assertStringContainsString($name, $out, "گام «$name» در --list نیست");
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
$names = array_map(fn (DoctorRepairStep $s) => $s->name(), $steps);
|
|
foreach (['names', 'degrees', 'specialty-parents', 'surrogate-role'] as $name) {
|
|
$this->assertContains($name, $names);
|
|
}
|
|
}
|
|
|
|
public function testDryRunWritesNothing(): void
|
|
{
|
|
$doctor = $this->makeBrokenDoctor();
|
|
$em = static::getContainer()->get('doctrine')->getManager();
|
|
$id = $doctor->getId();
|
|
|
|
$tester = $this->tester();
|
|
$tester->execute(['--dry-run' => true, '--only' => 'degrees,names']);
|
|
$tester->assertCommandIsSuccessful();
|
|
|
|
$em->clear();
|
|
$fresh = $em->getRepository(Doctor::class)->find($id);
|
|
|
|
$this->assertSame('expert', $fresh->getDegree(), 'dry-run نباید درجه را عوض کند');
|
|
$this->assertSame('دکتر آزمون ترمیم', $fresh->getName(), 'dry-run نباید نام را عوض کند');
|
|
}
|
|
|
|
public function testRepairFixesDegreeAndNameThenIsIdempotent(): void
|
|
{
|
|
$doctor = $this->makeBrokenDoctor();
|
|
$em = static::getContainer()->get('doctrine')->getManager();
|
|
$id = $doctor->getId();
|
|
|
|
$this->tester()->execute(['--only' => 'degrees,names']);
|
|
|
|
$em->clear();
|
|
$fresh = $em->getRepository(Doctor::class)->find($id);
|
|
$this->assertSame('specialist', $fresh->getDegree(), 'درجه باید از عنوان خام بازمحاسبه شود');
|
|
$this->assertSame('آزمون ترمیم', $fresh->getName(), 'پیشوند «دکتر» باید حذف شود');
|
|
|
|
// اجرای دوم: همان رکورد دیگر نباید در خروجی بیاید.
|
|
$second = $this->tester();
|
|
$second->execute(['--only' => 'degrees,names']);
|
|
$this->assertStringNotContainsString("#$id ", $second->getDisplay(), 'گام باید idempotent باشد');
|
|
}
|
|
|
|
public function testUnknownStepIsRejected(): void
|
|
{
|
|
$tester = $this->tester();
|
|
$tester->execute(['--only' => 'nonsense']);
|
|
|
|
$this->assertSame(2, $tester->getStatusCode(), 'گام ناشناخته باید INVALID برگرداند');
|
|
$this->assertStringContainsString('گام ناشناخته', $tester->getDisplay());
|
|
}
|
|
}
|