setLaravel($this->app); $tester = new CommandTester($command); $tester->execute($options); return $tester; } private function configs(): array { return DB::table('payment_providers')->orderBy('code')->pluck('config', 'code')->all(); } public function test_default_preview_lists_only_field_names_and_never_writes_or_discloses_values(): void { $before = $this->configs(); $result = $this->command(); $this->assertSame(0, $result->getStatusCode()); $output = $result->getDisplay(); $this->assertStringContainsString('未写入数据库', $output); $this->assertStringContainsString('mch_id', $output); $this->assertStringContainsString('config:cache', $output); foreach (['sanjin', 'qianbao', 'jd', 'no', 'zimu'] as $code) $this->assertStringContainsString($code, $output); foreach (['env-jd-mch', 'env-jd-secret-1234', 'https://env.example.com/pay'] as $value) $this->assertStringNotContainsString($value, $output); $this->assertSame($before, $this->configs()); $this->assertSame(0, DB::table('operation_audits')->count()); } public function test_selected_company_import_preserves_exact_active_credentials_and_pending_callbacks(): void { config(['app.jd_pay_key' => ' fixture-key-with-spaces ', 'app.jd_pay_recharge_user_ids' => ' 7, 8 ']); $before = PaymentProviderConfig::snapshot('jd'); DB::table('payment_orders')->insert(['type' => 1, 'channel' => 'JDpay', 'status' => 1]); $callback = ['userCode' => JdPayService::getMerchantId(), 'orderCode' => 'pending-order', 'amount' => '100.00', 'status' => '3']; $callback['sign'] = JdPayService::signature([$callback['orderCode'], $callback['amount'], $callback['userCode'], $callback['status']]); $result = $this->command(['--company' => ['jd'], '--write' => true]); $this->assertSame(0, $result->getStatusCode()); $this->assertSame($before, PaymentProviderConfig::snapshot('jd')); $this->assertTrue(JdPayService::verifyPayNotify($callback)); $this->assertSame(' fixture-key-with-spaces ', JdPayService::getSecret()); $raw = $this->configs(); $this->assertNotNull($raw['jd']); foreach (['no', 'sanjin', 'qianbao', 'zimu'] as $code) $this->assertNull($raw[$code]); $this->assertStringNotContainsString('fixture-key-with-spaces', $raw['jd']); $this->assertStringNotContainsString('fixture-key-with-spaces', $result->getDisplay()); $audit = DB::table('operation_audits')->first(); $this->assertSame('import_env', $audit->action); $this->assertSame('console:payment:config-import-env', $audit->operator_name); $changes = json_decode($audit->changes, true, 512, JSON_THROW_ON_ERROR); $this->assertSame('******', $changes['after']['key']); } public function test_existing_admin_values_including_empty_allowlist_win_and_repeated_import_is_idempotent(): void { (new PaymentProviderConfig())->save('jd', ['mch_id' => 'admin-merchant', 'key' => 'admin-secret', 'recharge_user_ids' => '']); config(['app.jd_pay_recharge_user_ids' => '99']); $before = PaymentProviderConfig::snapshot('jd'); $result = $this->command(['--company' => ['jd'], '--write' => true]); $this->assertSame(0, $result->getStatusCode()); $this->assertSame($before, PaymentProviderConfig::snapshot('jd')); $this->assertSame('admin-merchant', JdPayService::getMerchantId()); $this->assertSame('admin-secret', JdPayService::getSecret()); $this->assertSame('', PaymentProviderConfig::get('jd', 'recharge_user_ids')); $raw = $this->configs(); $audits = DB::table('operation_audits')->count(); $this->assertSame(0, $this->command(['--company' => ['jd', 'jd'], '--write' => true])->getStatusCode()); $this->assertSame($raw, $this->configs()); $this->assertSame($audits, DB::table('operation_audits')->count()); } public function test_bulk_import_copies_only_nonempty_defaults_and_changes_no_effective_values(): void { $before = []; foreach (array_keys($this->configs()) as $code) $before[$code] = PaymentProviderConfig::snapshot($code); $this->assertSame(0, $this->command(['--write' => true])->getStatusCode()); foreach ($before as $code => $snapshot) $this->assertSame($snapshot, PaymentProviderConfig::snapshot($code)); $fields = array_column((new PaymentProviderConfig())->show('jd')['fields'], null, 'field'); $this->assertSame('admin', $fields['mch_id']['source']); $this->assertSame('admin', $fields['key']['source']); $this->assertSame('empty', $fields['pay_query_gateway']['source']); $this->assertNull($this->configs()['zimu']); $this->assertSame('7,8', PaymentProviderConfig::get('no', 'recharge_user_ids')); } public function test_invalid_company_is_rejected_without_writing(): void { $before = $this->configs(); $result = $this->command(['--company' => ['unknown'], '--write' => true]); $this->assertSame(1, $result->getStatusCode()); $this->assertStringContainsString('代码无效', $result->getDisplay()); $this->assertSame($before, $this->configs()); $this->assertSame(0, DB::table('operation_audits')->count()); } public function test_unreadable_later_company_rolls_back_the_whole_batch(): void { (new PaymentProviderConfig())->save('jd', ['mch_id' => 'admin-merchant', 'key' => 'admin-secret']); $before = $this->configs(); $audits = DB::table('operation_audits')->count(); $this->app->instance('encrypter', new Encrypter(str_repeat('x', 32), 'AES-256-CBC')); Facade::clearResolvedInstance('encrypter'); $result = $this->command(['--write' => true]); $this->assertSame(1, $result->getStatusCode()); $this->assertSame($before, $this->configs()); $this->assertSame($audits, DB::table('operation_audits')->count()); $this->assertStringNotContainsString('MAC', $result->getDisplay()); $this->assertStringNotContainsString('admin-secret', $result->getDisplay()); } public function test_missing_schema_returns_a_controlled_failure(): void { DB::statement('DROP TABLE bot_payment_providers'); $result = $this->command(['--write' => true]); $this->assertSame(1, $result->getStatusCode()); $this->assertStringContainsString('导入失败', $result->getDisplay()); $this->assertStringNotContainsString('SQLSTATE', $result->getDisplay()); } public function test_audit_failure_rolls_back_imported_secrets(): void { $before = $this->configs(); DB::statement('DROP TABLE bot_operation_audits'); $result = $this->command(['--company' => ['jd'], '--write' => true]); $this->assertSame(1, $result->getStatusCode()); $this->assertSame($before, $this->configs()); $this->assertStringNotContainsString('env-jd-secret-1234', $result->getDisplay()); } }