Files
saasshop/tests/Feature/AdminPlatformOrderBatchActivateSubscriptionsTest.php

378 lines
14 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace Tests\Feature;
use App\Models\Merchant;
use App\Models\Plan;
use App\Models\PlatformOrder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use App\Models\SiteSubscription;
use App\Support\SubscriptionActivationService;
use Tests\TestCase;
class AdminPlatformOrderBatchActivateSubscriptionsTest extends TestCase
{
use RefreshDatabase;
protected function loginAsPlatformAdmin(): void
{
$this->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' => 'renewal',
'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' => 'renewal',
'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' => 'renewal',
'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' => 'renewal',
'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' => 'renewal',
'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');
}
}