artisan('ifnex:preflight', $args); } /** * تنظیم configهای Production-hardened به‌جز DB_CONNECTION. * برای تست‌هایی که می‌خواهیم preflight PASS شود، expected_db_connection * را روی sqlite می‌گذاریم تا با محیط تست هم‌خوانی داشته باشد. */ private function hardenedConfig(array $overrides = []): void { config(array_merge([ 'app.env' => 'production', 'app.debug' => false, 'app.key' => 'base64:'.str_repeat('A', 43), 'ifnex.api_key' => 'real-rotated-key-1234567890', 'ifnex.bridge_api_key' => 'real-rotated-bridge-key', 'ifnex.zarinpal.sandbox' => false, 'ifnex.zarinpal.merchant_id' => '00000000-0000-0000-0000-000000000000', 'ifnex.cors_allowed_origins' => ['https://ifnex.ir'], // ← database.default را هرگز mutate نکنیم؛ اگه Need be، expected را تغییر می‌دهیم. 'ifnex.preflight.expected_db_connection' => 'sqlite', ], $overrides)); } // ═══════════════════ FAIL scenarios ═══════════════════ #[Test] public function it_fails_when_app_env_is_not_production() { // محیط تست testing است → باید fail شود $this->runPreflight(['--skip-db' => true]) ->assertFailed(); } #[Test] public function it_fails_when_debug_is_true_in_production() { $this->hardenedConfig([ 'app.debug' => true, ]); $this->runPreflight(['--skip-db' => true]) ->assertFailed(); } #[Test] public function it_fails_when_api_keys_are_default() { $this->hardenedConfig([ 'ifnex.api_key' => 'change-me', 'ifnex.bridge_api_key' => 'change-this-secret-key', ]); $this->runPreflight(['--skip-db' => true]) ->assertFailed(); } #[Test] public function it_fails_when_zarinpal_sandbox_is_true_in_production() { $this->hardenedConfig([ 'ifnex.zarinpal.sandbox' => true, ]); $this->runPreflight(['--skip-db' => true]) ->assertFailed(); } #[Test] public function it_fails_when_db_connection_is_not_expected() { // تست می‌گوید: اگر expected=mysql ولی actual=sqlite، باید fail شود. // در محیط تست، database.default = sqlite است. $this->hardenedConfig([ 'ifnex.preflight.expected_db_connection' => 'mysql', // انتظار mysql ولی واقعیت sqlite ]); $this->runPreflight(['--skip-db' => true]) ->assertFailed(); } #[Test] public function it_fails_when_cors_has_localhost() { $this->hardenedConfig([ 'ifnex.cors_allowed_origins' => ['https://ifnex.ir', 'http://localhost'], ]); $this->runPreflight(['--skip-db' => true]) ->assertFailed(); } #[Test] public function it_fails_when_currencies_table_is_empty() { $this->hardenedConfig(); // جدول currencies خالی است (RefreshDatabase آن را خالی ایجاد کرده) $this->runPreflight() ->assertFailed(); } // ═══════════════════ PASS scenarios ═══════════════════ #[Test] public function it_passes_when_everything_is_hardened_and_currencies_seeded() { $this->hardenedConfig(); // ایجاد ۵ ارز پایه (با اتصال sqlite که محیط تست پیش‌فرض است) foreach (['USD', 'AED', 'EUR', 'CNY', 'IRR'] as $code) { Currency::create([ 'code' => $code, 'name' => $code, 'symbol' => $code, 'rate_to_rial' => $code === 'IRR' ? 1 : 100000, 'is_active' => true, ]); } $this->runPreflight() ->assertSuccessful(); } #[Test] public function it_can_skip_db_check_with_option() { $this->hardenedConfig(); // با --skip-db نباید currencies را چک کند $this->runPreflight(['--skip-db' => true]) ->assertSuccessful(); } }