Files
saasshop/tests/Feature/AdminPlatformOrderBatchActivateSubscriptionsTest.php

369 lines
13 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' => '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');
// 队列化后:请求内不会立即跑完,不应强行断言订单已同步/失败原因已落库。
// 这里仅锁定“已提交到队列”的口径;具体成功/失败应由队列 worker 执行。
$msg = (string) $res->getSession()->get('success');
$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');
}
}