instance('config', new Repository(['app' => ['timezone' => 'Asia/Shanghai'], 'admin_realtime' => ['enabled' => false]])); Facade::setFacadeApplication($app); $db = new Manager($app); $db->addConnection(['driver' => 'sqlite', 'database' => ':memory:', 'prefix' => 'bot_']); $db->setAsGlobal(); $db->bootEloquent(); $app->instance('db', $db->getDatabaseManager()); $db->schema()->create('payment_orders', function (Blueprint $t) { $t->id(); $t->string('member_id'); $t->integer('type'); $t->integer('status'); $t->string('state')->nullable(); $t->text('callback_data')->nullable(); $t->decimal('amount', 24, 10); $t->timestamps(); }); $old = date('Y-m-d H:i:s', strtotime('-2 hours')); $fresh = date('Y-m-d H:i:s', strtotime('-5 minutes')); foreach ([ [1, 1, $old], // 1 → 超时 [1, 1, $fresh], // 2 未到时限,仍处理中 [1, 2, $old], // 3 已成功不动 [3, 1, $old], // 4 人工充值不受影响 ] as [$type, $status, $at]) { DB::table('payment_orders')->insert(['member_id' => 'm1', 'type' => $type, 'status' => $status, 'amount' => 10, 'created_at' => $at, 'updated_at' => $at]); } } protected function tearDown(): void { DB::disconnect(); Facade::clearResolvedInstances(); parent::tearDown(); } public function test_command_marks_only_overdue_online_pay_orders_as_timeout(): void { (new PaymentPayTimeout())->handle(); $this->assertSame([1 => 7, 2 => 1, 3 => 2, 4 => 1], DB::table('payment_orders')->orderBy('id')->pluck('status', 'id')->map(fn($s) => (int)$s)->all()); } public function test_late_callback_is_still_applied_to_timeout_order(): void { (new PaymentPayTimeout())->handle(); $apply = new \ReflectionMethod(PaymentOrderService::class, 'applyPayCallback'); $processed = $apply->invoke(null, PaymentOrder::find(1), '10', '2', '3', '2', []); $this->assertTrue($processed); $this->assertSame(PaymentOrderService::STATUS_FAIL, (int)DB::table('payment_orders')->where('id', 1)->value('status')); } }