feat: enhance purge commands with environment handling and testing improvements
This commit is contained in:
@@ -280,6 +280,9 @@ php bin/console app:doctors:purge-unclaimed --all-sources # هر منبعِ غ
|
|||||||
| `--all-sources` | هر پزشک claimنشدهٔ غیرِ `manual` |
|
| `--all-sources` | هر پزشک claimنشدهٔ غیرِ `manual` |
|
||||||
| `--i-know-this-is-prod` | لازم برای اجرا روی `APP_ENV=prod` |
|
| `--i-know-this-is-prod` | لازم برای اجرا روی `APP_ENV=prod` |
|
||||||
|
|
||||||
|
> محیط از kernel خوانده میشود (`%kernel.environment%`)، نه `$_ENV['APP_ENV']` — تا اگر
|
||||||
|
> APP_ENV از راهی غیر از dotenv ست شده باشد گارد دور زده نشود.
|
||||||
|
|
||||||
> حذف FK-safe است (همان ۱۶ جدول وابستهٔ `app:doctors:purge`). اگر نوبتی به پزشک
|
> حذف FK-safe است (همان ۱۶ جدول وابستهٔ `app:doctors:purge`). اگر نوبتی به پزشک
|
||||||
> claimنشده وصل باشد در dry-run هشدار میدهد چون آن نوبت هم حذف میشود. کاربر جانشین
|
> claimنشده وصل باشد در dry-run هشدار میدهد چون آن نوبت هم حذف میشود. کاربر جانشین
|
||||||
> فقط وقتی حذف میشود که غیرفعال (`status=0`) و با موبایلِ `imp_` باشد — تا کاربر واقعی
|
> فقط وقتی حذف میشود که غیرفعال (`status=0`) و با موبایلِ `imp_` باشد — تا کاربر واقعی
|
||||||
@@ -302,6 +305,8 @@ php bin/console app:doctors:purge --force # حذف واقعی + کاربرا
|
|||||||
> `dev`/`test` اجرا میشود**؛ روی هر `APP_ENV` دیگری (از جمله `prod`) بدون هیچ راه
|
> `dev`/`test` اجرا میشود**؛ روی هر `APP_ENV` دیگری (از جمله `prod`) بدون هیچ راه
|
||||||
> فراری با خطا متوقف میشود. سوییچ `--i-know-this-is-prod` اینجا **وجود ندارد** —
|
> فراری با خطا متوقف میشود. سوییچ `--i-know-this-is-prod` اینجا **وجود ندارد** —
|
||||||
> تنها کامند این خانواده است که هیچ راهی برای اجرا روی prod ندارد.
|
> تنها کامند این خانواده است که هیچ راهی برای اجرا روی prod ندارد.
|
||||||
|
> محیط از `%kernel.environment%` میآید، پس با دستکاری `$_ENV` هم دور نمیخورد.
|
||||||
|
> (تستها: `PurgeDoctorsCommandTest`، `PurgeUnclaimedDoctorsCommandTest::testRefusesOnProdWithoutExplicitSwitch`)
|
||||||
|
|
||||||
### خلاصهٔ تفاوت دو کامند حذف
|
### خلاصهٔ تفاوت دو کامند حذف
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ use Symfony\Component\Console\Input\InputInterface;
|
|||||||
use Symfony\Component\Console\Input\InputOption;
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
|
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* پاکسازی کامل همهٔ پزشکان و دادههای وابسته — برای ساختِ یک دیتابیس تمیزِ تست.
|
* پاکسازی کامل همهٔ پزشکان و دادههای وابسته — برای ساختِ یک دیتابیس تمیزِ تست.
|
||||||
@@ -34,8 +35,11 @@ class PurgeDoctorsCommand extends Command
|
|||||||
'holidays', 'comments', 'rates', 'appointments', 'doctors',
|
'holidays', 'comments', 'rates', 'appointments', 'doctors',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function __construct(private readonly Connection $conn)
|
public function __construct(
|
||||||
{
|
private readonly Connection $conn,
|
||||||
|
#[Autowire('%kernel.environment%')]
|
||||||
|
private readonly string $env,
|
||||||
|
) {
|
||||||
parent::__construct();
|
parent::__construct();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,10 +54,10 @@ class PurgeDoctorsCommand extends Command
|
|||||||
$force = (bool) $input->getOption('force');
|
$force = (bool) $input->getOption('force');
|
||||||
|
|
||||||
// فقط و فقط dev/test. این کامند همهٔ پزشکان + نوبت/نظر/پرداخت را پاک میکند و
|
// فقط و فقط dev/test. این کامند همهٔ پزشکان + نوبت/نظر/پرداخت را پاک میکند و
|
||||||
// هیچ راه فراری برای prod ندارد.
|
// هیچ راه فراری برای prod ندارد. محیط از kernel میآید (نه $_ENV) تا اگر APP_ENV
|
||||||
$env = $_ENV['APP_ENV'] ?? 'dev';
|
// از راهی غیر از dotenv ست شده باشد، به اشتباه به dev fallback نشود.
|
||||||
if (!in_array($env, ['dev', 'test'], true)) {
|
if (!in_array($this->env, ['dev', 'test'], true)) {
|
||||||
$io->error(sprintf('این کامند فقط در محیط dev اجرا میشود (APP_ENV فعلی: %s).', $env));
|
$io->error(sprintf('این کامند فقط در محیط dev اجرا میشود (APP_ENV فعلی: %s).', $this->env));
|
||||||
return Command::FAILURE;
|
return Command::FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ use Symfony\Component\Console\Input\InputInterface;
|
|||||||
use Symfony\Component\Console\Input\InputOption;
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
|
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* حذف پزشکانِ ایمپورتشدهٔ خزنده که هنوز مالکیتشان گرفته نشده (`owner_status='unclaimed'`).
|
* حذف پزشکانِ ایمپورتشدهٔ خزنده که هنوز مالکیتشان گرفته نشده (`owner_status='unclaimed'`).
|
||||||
@@ -35,8 +36,11 @@ class PurgeUnclaimedDoctorsCommand extends Command
|
|||||||
'holidays', 'comments', 'rates', 'appointments',
|
'holidays', 'comments', 'rates', 'appointments',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function __construct(private readonly Connection $conn)
|
public function __construct(
|
||||||
{
|
private readonly Connection $conn,
|
||||||
|
#[Autowire('%kernel.environment%')]
|
||||||
|
private readonly string $env,
|
||||||
|
) {
|
||||||
parent::__construct();
|
parent::__construct();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,7 +58,9 @@ class PurgeUnclaimedDoctorsCommand extends Command
|
|||||||
$io = new SymfonyStyle($input, $output);
|
$io = new SymfonyStyle($input, $output);
|
||||||
$force = (bool) $input->getOption('force');
|
$force = (bool) $input->getOption('force');
|
||||||
|
|
||||||
if (($_ENV['APP_ENV'] ?? 'dev') === 'prod' && !$input->getOption('i-know-this-is-prod')) {
|
// محیط از kernel میآید (نه $_ENV) تا گاردِ prod با ستشدن APP_ENV از راهی
|
||||||
|
// غیر از dotenv دور زده نشود.
|
||||||
|
if ($this->env === 'prod' && !$input->getOption('i-know-this-is-prod')) {
|
||||||
$io->error('روی prod بدون --i-know-this-is-prod اجرا نمیشود.');
|
$io->error('روی prod بدون --i-know-this-is-prod اجرا نمیشود.');
|
||||||
return Command::FAILURE;
|
return Command::FAILURE;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
namespace App\Tests\Doctor;
|
namespace App\Tests\Doctor;
|
||||||
|
|
||||||
|
use App\Doctor\Command\PurgeDoctorsCommand;
|
||||||
|
use Doctrine\DBAL\Connection;
|
||||||
use Symfony\Bundle\FrameworkBundle\Console\Application;
|
use Symfony\Bundle\FrameworkBundle\Console\Application;
|
||||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||||
use Symfony\Component\Console\Command\Command;
|
use Symfony\Component\Console\Command\Command;
|
||||||
@@ -30,26 +32,19 @@ class PurgeDoctorsCommandTest extends KernelTestCase
|
|||||||
$this->assertStringContainsString('dry-run', $tester->getDisplay());
|
$this->assertStringContainsString('dry-run', $tester->getDisplay());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* محیط از kernel تزریق میشود، پس گارد با ساختِ مستقیم کامند در محیط prod تست
|
||||||
|
* میشود — نه با دستکاری $_ENV که دیگر خوانده نمیشود.
|
||||||
|
*/
|
||||||
public function testRefusesOnProdWithNoEscapeHatch(): void
|
public function testRefusesOnProdWithNoEscapeHatch(): void
|
||||||
{
|
{
|
||||||
self::bootKernel();
|
self::bootKernel();
|
||||||
$original = $_ENV['APP_ENV'] ?? null;
|
$command = new PurgeDoctorsCommand(static::getContainer()->get(Connection::class), 'prod');
|
||||||
$_ENV['APP_ENV'] = 'prod';
|
$tester = new CommandTester($command);
|
||||||
|
|
||||||
try {
|
$tester->execute(['--force' => true]);
|
||||||
$tester = new CommandTester(
|
|
||||||
(new Application(self::$kernel))->find('app:doctors:purge')
|
|
||||||
);
|
|
||||||
$tester->execute(['--force' => true]);
|
|
||||||
|
|
||||||
$this->assertSame(Command::FAILURE, $tester->getStatusCode());
|
$this->assertSame(Command::FAILURE, $tester->getStatusCode());
|
||||||
$this->assertStringContainsString('فقط در محیط dev', $tester->getDisplay());
|
$this->assertStringContainsString('فقط در محیط dev', $tester->getDisplay());
|
||||||
} finally {
|
|
||||||
if ($original === null) {
|
|
||||||
unset($_ENV['APP_ENV']);
|
|
||||||
} else {
|
|
||||||
$_ENV['APP_ENV'] = $original;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,10 +3,12 @@
|
|||||||
namespace App\Tests\Doctor;
|
namespace App\Tests\Doctor;
|
||||||
|
|
||||||
use App\Auth\Entity\User;
|
use App\Auth\Entity\User;
|
||||||
|
use App\Doctor\Command\PurgeUnclaimedDoctorsCommand;
|
||||||
use App\Doctor\Entity\Doctor;
|
use App\Doctor\Entity\Doctor;
|
||||||
use Doctrine\DBAL\Connection;
|
use Doctrine\DBAL\Connection;
|
||||||
use Symfony\Bundle\FrameworkBundle\Console\Application;
|
use Symfony\Bundle\FrameworkBundle\Console\Application;
|
||||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||||
|
use Symfony\Component\Console\Command\Command;
|
||||||
use Symfony\Component\Console\Tester\CommandTester;
|
use Symfony\Component\Console\Tester\CommandTester;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -78,6 +80,39 @@ class PurgeUnclaimedDoctorsCommandTest extends KernelTestCase
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** برخلاف app:doctors:purge، این کامند روی prod قابل اجراست — ولی فقط با سوییچ صریح. */
|
||||||
|
private function prodTester(): CommandTester
|
||||||
|
{
|
||||||
|
self::bootKernel();
|
||||||
|
|
||||||
|
return new CommandTester(
|
||||||
|
new PurgeUnclaimedDoctorsCommand(static::getContainer()->get(Connection::class), 'prod')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRefusesOnProdWithoutExplicitSwitch(): void
|
||||||
|
{
|
||||||
|
$doctor = $this->makeDoctor('irimc', 'unclaimed');
|
||||||
|
|
||||||
|
$tester = $this->prodTester();
|
||||||
|
$tester->execute(['--force' => true]);
|
||||||
|
|
||||||
|
$this->assertSame(Command::FAILURE, $tester->getStatusCode());
|
||||||
|
$this->assertStringContainsString('i-know-this-is-prod', $tester->getDisplay());
|
||||||
|
$this->assertTrue($this->doctorExists($doctor->getId()), 'گاردِ prod نباید چیزی حذف کند');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRunsOnProdWithExplicitSwitch(): void
|
||||||
|
{
|
||||||
|
$doctor = $this->makeDoctor('irimc', 'unclaimed');
|
||||||
|
|
||||||
|
$tester = $this->prodTester();
|
||||||
|
$tester->execute(['--force' => true, '--i-know-this-is-prod' => true]);
|
||||||
|
|
||||||
|
$tester->assertCommandIsSuccessful();
|
||||||
|
$this->assertFalse($this->doctorExists($doctor->getId()), 'با سوییچ صریح باید روی prod حذف کند');
|
||||||
|
}
|
||||||
|
|
||||||
public function testClaimedAndOtherSourceDoctorsSurvive(): void
|
public function testClaimedAndOtherSourceDoctorsSurvive(): void
|
||||||
{
|
{
|
||||||
$claimed = $this->makeDoctor('irimc', 'claimed', userStatus: 1);
|
$claimed = $this->makeDoctor('irimc', 'claimed', userStatus: 1);
|
||||||
|
|||||||
Reference in New Issue
Block a user