Files
huangjingfen/pro_v3.5.1_副本/tests/hjf/QueueEngineTest.php

196 lines
6.7 KiB
PHP
Raw Normal View History

feat(fsgx): 完成全部24项开发任务 Phase1-7 Phase1 后端核心: - 新增 fsgx_v1.sql 迁移脚本(is_queue_goods/frozen_points/available_points/no_assess) - SystemConfigServices 返佣设置扩展(周期人数/分档比例/范围/时机) - StoreOrderCreateServices 周期循环佣金计算 - StoreOrderTakeServices 佣金发放后同步冻结积分 - StoreProductServices/StoreProduct 保存 is_queue_goods Phase2 后端接口: - GET /api/hjf/brokerage/progress 佣金周期进度 - GET /api/hjf/assets/overview 资产总览 - HjfPointsServices 每日 frozen_points 0.4‰ 释放定时任务 - PUT /adminapi/hjf/member/{uid}/no_assess 不考核接口 - GET /adminapi/hjf/points/release_log 积分日志接口 Phase3 前端清理: - hjfCustom.js 路由精简(仅保留 points/log) - hjfQueue.js/hjfMember.js API 清理/重定向至 CRMEB 原生接口 - pages.json 公排→推荐佣金/佣金记录/佣金规则 Phase4-5 前端改造: - queue/status.vue 推荐佣金进度页整体重写 - 商品详情/订单确认/支付结果页文案与逻辑改造 - 个人中心/资产页/引导页/规则页文案改造 - HjfQueueProgress/HjfRefundNotice/HjfAssetCard 组件改造 - 推广中心嵌入佣金进度摘要 - hjfMockData.js 全量更新(公排字段→佣金字段) Phase6 Admin 增强: - 用户列表新增 frozen_points/available_points 列及不考核操作按钮 - hjfPoints.js USE_MOCK=false 对接真实积分日志接口 Phase7 配置文档: - docs/fsgx-phase7-config-checklist.md 后台配置与全链路验收清单 Made-with: Cursor
2026-03-23 22:32:19 +08:00
<?php
declare(strict_types=1);
namespace tests\hjf;
use PHPUnit\Framework\TestCase;
/**
* 公排引擎单元测试桩
*
* 覆盖点:
* 1. 入队逻辑:正常入队写库,返回 queue_no
* 2. 退款触发pending 单数 >= triggerMultiple 时触发退款 Job
* 3. 分布式锁:并发入队时只有一个请求能获得锁
* 4. 幂等性:同一订单不能重复入队
*
* Class QueueEngineTest
* @package tests\hjf
*/
class QueueEngineTest extends TestCase
{
// -----------------------------------------------------------------------
// 入队逻辑
// -----------------------------------------------------------------------
/**
* @test
* 正常入队should write a new record to queue_pool and return queue_no
*/
public function testEnqueueCreatesRecord(): void
{
// Arrange
$uid = 1001;
$orderId = 'ORDER_20240101_001';
$amount = 3600.00;
// Mock QueuePoolDao: expects one save() call
$daoMock = $this->createMock(\app\dao\hjf\QueuePoolDao::class);
$daoMock->expects($this->once())
->method('save')
->willReturn(true);
$daoMock->method('nextQueueNo')->willReturn(42);
// Mock CacheService (Redis lock): SET NX succeeds
$cacheMock = $this->createMock(\crmeb\services\CacheService::class);
$cacheMock->method('handler')->willReturnSelf();
$cacheMock->method('set')->willReturn(true); // lock acquired
// Act — 实际测试时替换为 DI 注入
// $service = new QueuePoolServices($daoMock, $cacheMock);
// $result = $service->enqueue($uid, $orderId, $amount);
// Assert
// $this->assertArrayHasKey('queue_no', $result);
// $this->assertEquals(42, $result['queue_no']);
// Stub assertion (placeholder until DI wiring is ready)
$this->assertTrue(true, '入队正常流程桩测试通过');
}
/**
* @test
* 重复入队same order_id should throw or return error
*/
public function testEnqueueDuplicateOrderIdIsRejected(): void
{
// Mock QueuePoolDao: getOne returns existing record
$daoMock = $this->createMock(\app\dao\hjf\QueuePoolDao::class);
$daoMock->method('getOne')
->willReturn(['id' => 99, 'order_id' => 'ORDER_20240101_001']);
// Assert that duplicate is rejected (exception or false return)
// $this->expectException(\RuntimeException::class);
// $service->enqueue(1001, 'ORDER_20240101_001', 3600);
$this->assertTrue(true, '幂等性保护桩测试通过');
}
// -----------------------------------------------------------------------
// 退款触发
// -----------------------------------------------------------------------
/**
* @test
* 触发阈值:当 pending >= triggerMultiple(4),应该派发 QueueRefundJob
*/
public function testRefundTriggeredWhenThresholdReached(): void
{
$daoMock = $this->createMock(\app\dao\hjf\QueuePoolDao::class);
$daoMock->method('countPending')->willReturn(4); // 4 >= 4, should trigger
$daoMock->method('getEarliestPending')->willReturn([
'id' => 1,
'uid' => 1001,
'amount' => '3600.00',
]);
// Verify Job dispatch would be called
// In real test: assert QueueRefundJob::dispatch() called once
$this->assertTrue(true, '退款触发桩测试通过pending=4, multiple=4');
}
/**
* @test
* 未达阈值:当 pending < triggerMultiple不触发退款
*/
public function testNoRefundWhenBelowThreshold(): void
{
$daoMock = $this->createMock(\app\dao\hjf\QueuePoolDao::class);
$daoMock->method('countPending')->willReturn(3); // 3 < 4, should NOT trigger
// Verify Job dispatch would NOT be called
$this->assertTrue(true, '未触发退款桩测试通过pending=3, multiple=4');
}
// -----------------------------------------------------------------------
// 分布式锁
// -----------------------------------------------------------------------
/**
* @test
* 并发锁:第一个请求获得锁后,第二个并发请求应被拒绝
*/
public function testDistributedLockPreventsConcurrentEnqueue(): void
{
// Redis SET NX 模拟:第一次返回 true获得锁第二次返回 false锁已占用
$responses = [true, false];
$callCount = 0;
$cacheMock = $this->getMockBuilder(\stdClass::class)
->addMethods(['set', 'del'])
->getMock();
$cacheMock->method('set')->willReturnCallback(
function () use (&$responses, &$callCount) {
return $responses[$callCount++] ?? false;
}
);
// First request: lock acquired → proceed
$lock1 = $responses[0]; // true
$this->assertTrue($lock1, '第一个请求应获得分布式锁');
// Second request: lock not acquired → reject
$lock2 = $responses[1]; // false
$this->assertFalse($lock2, '第二个并发请求应被分布式锁拒绝');
}
// -----------------------------------------------------------------------
// 退款 Job 执行
// -----------------------------------------------------------------------
/**
* @test
* 退款 Jobstatus 已是 refunded 时不重复处理(幂等)
*/
public function testRefundJobIsIdempotent(): void
{
$daoMock = $this->createMock(\app\dao\hjf\QueuePoolDao::class);
// Record already refunded
$daoMock->method('get')->willReturn([
'id' => 1,
'status' => 'refunded',
]);
// doJob should return true without doing any DB writes
// Assert no userDao->bcInc() called
$this->assertTrue(true, '退款 Job 幂等性桩测试通过');
}
/**
* @test
* 退款 Job正常执行 markRefunded + balance increment
*/
public function testRefundJobExecutesSuccessfully(): void
{
$daoMock = $this->createMock(\app\dao\hjf\QueuePoolDao::class);
$userMock = $this->createMock(\app\dao\user\UserDao::class);
$daoMock->method('get')->willReturn([
'id' => 1,
'uid' => 1001,
'amount' => '3600.00',
'status' => 'pending',
]);
$daoMock->expects($this->once())->method('markRefunded')->willReturn(true);
$userMock->expects($this->once())->method('bcInc')->willReturn(true);
// Real execution: $job->doJob(1, 1001, 3600.00, 'BATCH_001')
// Assert both mock methods were called
$this->assertTrue(true, '退款 Job 执行流程桩测试通过');
}
}