seed(); $this->post('/admin/login', [ 'email' => 'platform.admin@demo.local', 'password' => 'Platform@123456', ])->assertRedirect('/admin'); } public function test_platform_admin_can_batch_activate_subscriptions_in_filtered_scope(): void { $this->loginAsPlatformAdmin(); $merchant = Merchant::query()->firstOrFail(); $plan = Plan::query()->create([ 'code' => 'batch_activate_plan', 'name' => '批量同步测试套餐', 'billing_cycle' => 'monthly', 'price' => 10, 'list_price' => 10, 'status' => 'active', 'sort' => 10, 'published_at' => now(), ]); // 可同步(命中):已支付 + 已生效 + 未同步 $syncable = PlatformOrder::query()->create([ 'merchant_id' => $merchant->id, 'plan_id' => $plan->id, 'order_no' => 'PO_BATCH_SYNC_0001', 'order_type' => 'new_purchase', 'status' => 'activated', 'payment_status' => 'paid', 'plan_name' => $plan->name, 'billing_cycle' => $plan->billing_cycle, 'period_months' => 1, 'quantity' => 1, 'payable_amount' => 10, 'paid_amount' => 10, 'placed_at' => now()->subMinutes(10), 'paid_at' => now()->subMinutes(9), 'activated_at' => now()->subMinutes(8), ]); // 不可同步(不应命中):未支付 PlatformOrder::query()->create([ 'merchant_id' => $merchant->id, 'plan_id' => $plan->id, 'order_no' => 'PO_BATCH_SYNC_0002', 'order_type' => 'renewal', 'status' => 'activated', 'payment_status' => 'unpaid', 'plan_name' => $plan->name, 'billing_cycle' => $plan->billing_cycle, 'period_months' => 1, 'quantity' => 1, 'payable_amount' => 10, 'paid_amount' => 0, 'placed_at' => now()->subMinutes(5), ]); $this->post('/admin/platform-orders/batch-activate-subscriptions', [ 'scope' => 'filtered', 'syncable_only' => '1', ])->assertRedirect(); $syncable->refresh(); $this->assertNotNull($syncable->site_subscription_id); $this->assertNotEmpty(data_get($syncable->meta, 'subscription_activation.subscription_id')); $this->assertSame('batch_activate_subscription', data_get($syncable->meta, 'audit.0.action')); $this->assertNotEmpty(data_get($syncable->meta, 'audit.0.subscription_id')); $this->assertNotEmpty(data_get($syncable->meta, 'audit.0.filters')); $this->assertNotEmpty(data_get($syncable->meta, 'audit.0.note')); } public function test_platform_admin_batch_activate_records_failure_reason_and_summary(): void { $this->loginAsPlatformAdmin(); $merchant = Merchant::query()->firstOrFail(); $plan = Plan::query()->create([ 'code' => 'batch_activate_fail_plan', 'name' => '批量同步失败治理测试套餐', 'billing_cycle' => 'monthly', 'price' => 10, 'list_price' => 10, 'status' => 'active', 'sort' => 10, 'published_at' => now(), ]); $ok = PlatformOrder::query()->create([ 'merchant_id' => $merchant->id, 'plan_id' => $plan->id, 'order_no' => 'PO_BATCH_FAIL_0001', 'order_type' => 'new_purchase', 'status' => 'activated', 'payment_status' => 'paid', 'plan_name' => $plan->name, 'billing_cycle' => $plan->billing_cycle, 'period_months' => 1, 'quantity' => 1, 'payable_amount' => 10, 'paid_amount' => 10, 'placed_at' => now()->subMinutes(10), 'paid_at' => now()->subMinutes(9), 'activated_at' => now()->subMinutes(8), ]); // 强制失败:构造一条同样“可同步”的订单,然后在测试中用 DI 绑定一个假的 SubscriptionActivationService, // 让它在处理该订单时抛异常,从而验证失败原因落库与 Top 汇总。 $bad = PlatformOrder::query()->create([ 'merchant_id' => $merchant->id, 'plan_id' => $plan->id, 'order_no' => 'PO_BATCH_FAIL_0002', 'order_type' => 'new_purchase', 'status' => 'activated', 'payment_status' => 'paid', 'plan_name' => $plan->name, 'billing_cycle' => $plan->billing_cycle, 'period_months' => 1, 'quantity' => 1, 'payable_amount' => 10, 'paid_amount' => 10, 'placed_at' => now()->subMinutes(7), 'paid_at' => now()->subMinutes(6), 'activated_at' => now()->subMinutes(5), ]); $badId = $bad->id; $this->app->bind(SubscriptionActivationService::class, function () use ($badId) { return new class($badId) extends SubscriptionActivationService { public function __construct(private int $badId) {} public function activateOrder(int $orderId, ?int $adminId = null): SiteSubscription { if ($orderId === $this->badId) { throw new \RuntimeException('模拟失败:订阅同步异常'); } return parent::activateOrder($orderId, $adminId); } }; }); $res = $this->post('/admin/platform-orders/batch-activate-subscriptions', [ 'scope' => 'filtered', 'syncable_only' => '1', 'limit' => 50, ]); $res->assertRedirect()->assertSessionHas('success'); $ok->refresh(); $bad->refresh(); $this->assertNotNull($ok->site_subscription_id); $this->assertNotEmpty(data_get($ok->meta, 'subscription_activation.subscription_id')); $this->assertNotEmpty(data_get($bad->meta, 'subscription_activation_error.message')); $this->assertNotEmpty(data_get($bad->meta, 'subscription_activation_error.at')); // 批量结果摘要应包含失败原因Top $msg = (string) $res->getSession()->get('success'); $this->assertStringContainsString('失败原因Top', $msg); $this->assertStringContainsString('模拟失败:订阅同步异常', $msg); } public function test_platform_admin_batch_activate_respects_limit(): void { $this->loginAsPlatformAdmin(); $merchant = Merchant::query()->firstOrFail(); $plan = Plan::query()->create([ 'code' => 'batch_activate_limit_plan', 'name' => '批量同步限额测试套餐', 'billing_cycle' => 'monthly', 'price' => 10, 'list_price' => 10, 'status' => 'active', 'sort' => 10, 'published_at' => now(), ]); $o1 = PlatformOrder::query()->create([ 'merchant_id' => $merchant->id, 'plan_id' => $plan->id, 'order_no' => 'PO_BATCH_LIMIT_0001', 'order_type' => 'new_purchase', 'status' => 'activated', 'payment_status' => 'paid', 'plan_name' => $plan->name, 'billing_cycle' => $plan->billing_cycle, 'period_months' => 1, 'quantity' => 1, 'payable_amount' => 10, 'paid_amount' => 10, 'placed_at' => now()->subMinutes(10), 'paid_at' => now()->subMinutes(9), 'activated_at' => now()->subMinutes(8), ]); $o2 = PlatformOrder::query()->create([ 'merchant_id' => $merchant->id, 'plan_id' => $plan->id, 'order_no' => 'PO_BATCH_LIMIT_0002', 'order_type' => 'new_purchase', 'status' => 'activated', 'payment_status' => 'paid', 'plan_name' => $plan->name, 'billing_cycle' => $plan->billing_cycle, 'period_months' => 1, 'quantity' => 1, 'payable_amount' => 10, 'paid_amount' => 10, 'placed_at' => now()->subMinutes(7), 'paid_at' => now()->subMinutes(6), 'activated_at' => now()->subMinutes(5), ]); $o3 = PlatformOrder::query()->create([ 'merchant_id' => $merchant->id, 'plan_id' => $plan->id, 'order_no' => 'PO_BATCH_LIMIT_0003', 'order_type' => 'new_purchase', 'status' => 'activated', 'payment_status' => 'paid', 'plan_name' => $plan->name, 'billing_cycle' => $plan->billing_cycle, 'period_months' => 1, 'quantity' => 1, 'payable_amount' => 10, 'paid_amount' => 10, 'placed_at' => now()->subMinutes(4), 'paid_at' => now()->subMinutes(3), 'activated_at' => now()->subMinutes(2), ]); $this->post('/admin/platform-orders/batch-activate-subscriptions', [ 'scope' => 'filtered', 'syncable_only' => '1', 'limit' => 1, ])->assertRedirect(); $o1->refresh(); $o2->refresh(); $o3->refresh(); // 当前实现按 id 倒序优先处理最新订单,因此 limit=1 时应只处理 o3 $this->assertNull($o1->site_subscription_id); $this->assertNull($o2->site_subscription_id); $this->assertNotNull($o3->site_subscription_id); } public function test_platform_admin_batch_activate_requires_syncable_only_filter(): void { $this->loginAsPlatformAdmin(); $merchant = Merchant::query()->firstOrFail(); $plan = Plan::query()->create([ 'code' => 'batch_activate_guard_plan', 'name' => '批量同步防误操作测试套餐', 'billing_cycle' => 'monthly', 'price' => 10, 'list_price' => 10, 'status' => 'active', 'sort' => 10, 'published_at' => now(), ]); $syncable = PlatformOrder::query()->create([ 'merchant_id' => $merchant->id, 'plan_id' => $plan->id, 'order_no' => 'PO_BATCH_GUARD_0001', 'order_type' => 'new_purchase', 'status' => 'activated', 'payment_status' => 'paid', 'plan_name' => $plan->name, 'billing_cycle' => $plan->billing_cycle, 'period_months' => 1, 'quantity' => 1, 'payable_amount' => 10, 'paid_amount' => 10, 'placed_at' => now()->subMinutes(10), 'paid_at' => now()->subMinutes(9), 'activated_at' => now()->subMinutes(8), ]); $this->post('/admin/platform-orders/batch-activate-subscriptions', [ 'scope' => 'filtered', // 故意不传 syncable_only ])->assertRedirect()->assertSessionHas('warning'); $syncable->refresh(); $this->assertNull($syncable->site_subscription_id); } public function test_platform_admin_batch_activate_scope_all_requires_confirm_yes(): void { $this->loginAsPlatformAdmin(); $merchant = Merchant::query()->firstOrFail(); $plan = Plan::query()->create([ 'code' => 'batch_activate_all_guard_plan', 'name' => '批量同步全量二次确认测试套餐', 'billing_cycle' => 'monthly', 'price' => 10, 'list_price' => 10, 'status' => 'active', 'sort' => 10, 'published_at' => now(), ]); $syncable = PlatformOrder::query()->create([ 'merchant_id' => $merchant->id, 'plan_id' => $plan->id, 'order_no' => 'PO_BATCH_ALL_GUARD_0001', 'order_type' => 'new_purchase', 'status' => 'activated', 'payment_status' => 'paid', 'plan_name' => $plan->name, 'billing_cycle' => $plan->billing_cycle, 'period_months' => 1, 'quantity' => 1, 'payable_amount' => 10, 'paid_amount' => 10, 'placed_at' => now()->subMinutes(10), 'paid_at' => now()->subMinutes(9), 'activated_at' => now()->subMinutes(8), ]); // 未确认:不应执行 $this->post('/admin/platform-orders/batch-activate-subscriptions', [ 'scope' => 'all', 'limit' => 50, ])->assertRedirect()->assertSessionHas('warning'); $syncable->refresh(); $this->assertNull($syncable->site_subscription_id); // 确认 YES:应执行 $this->post('/admin/platform-orders/batch-activate-subscriptions', [ 'scope' => 'all', 'confirm' => 'YES', 'limit' => 50, ])->assertRedirect()->assertSessionHas('success'); $syncable->refresh(); $this->assertNotNull($syncable->site_subscription_id); } public function test_guest_cannot_batch_activate_subscriptions(): void { $this->seed(); $this->post('/admin/platform-orders/batch-activate-subscriptions', [ 'scope' => 'filtered', ])->assertRedirect('/admin/login'); } }