Files
huangjingfen/pro_v3.5.1/tests/hjf/PointsCalculationTest.php
apple 78de918c37 Initial commit: queue workspace
Made-with: Cursor
2026-03-21 02:55:24 +08:00

233 lines
7.7 KiB
PHP
Raw Permalink 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
declare(strict_types=1);
namespace tests\hjf;
use PHPUnit\Framework\TestCase;
/**
* 积分计算单元测试
*
* 覆盖点:
* 1. 级差计算:直推 / 伞下 / 多层级差公式正确性
* 2. 每日释放精度bcmath 计算 frozen × rate / 1000
* 3. 边界值frozen_points=0 不释放rate=0 不释放
* 4. 伞下业绩分离:云店级别下级业绩不计入上级
*
* Class PointsCalculationTest
* @package tests\hjf
*/
class PointsCalculationTest extends TestCase
{
// -----------------------------------------------------------------------
// 每日释放精度bcmath
// -----------------------------------------------------------------------
/**
* @test
* 每日释放1,000,000 frozen × 4‰ = 4,000无浮点误差
*/
public function testDailyReleaseCalculationPrecision(): void
{
$frozen = '1000000';
$rate = '4';
// Formula: FLOOR(frozen × rate / 1000)
$releaseAmount = (int)bcdiv(bcmul($frozen, $rate), '1000');
$this->assertEquals(4000, $releaseAmount, '每日释放精度测试1000000×4‰=4000');
}
/**
* @test
* 每日释放:小数点精度 —— 999 × 4‰ = 3FLOOR不是3.996
*/
public function testDailyReleaseFloorRounding(): void
{
$frozen = '999';
$rate = '4';
$releaseAmount = (int)bcdiv(bcmul($frozen, $rate), '1000');
$this->assertEquals(3, $releaseAmount, '每日释放应使用 FLOOR 取整999×4‰=3');
}
/**
* @test
* frozen_points = 0 时不释放
*/
public function testDailyReleaseZeroFrozenPoints(): void
{
$frozen = '0';
$rate = '4';
$releaseAmount = (int)bcdiv(bcmul($frozen, $rate), '1000');
$this->assertEquals(0, $releaseAmount, 'frozen_points=0 时释放量应为 0');
}
/**
* @test
* rate = 0 时不释放(防止除零配置错误)
*/
public function testDailyReleaseZeroRate(): void
{
$frozen = '100000';
$rate = '0';
$releaseAmount = (int)bcdiv(bcmul($frozen, $rate), '1000');
$this->assertEquals(0, $releaseAmount, 'rate=0 时释放量应为 0');
}
/**
* @test
* 浮点陷阱验证PHP 原生浮点 vs bcmath 结果对比
*/
public function testBcmathVsNativeFloat(): void
{
$frozen = 1234567;
$rate = 4;
// Native float (may have precision errors)
$nativeResult = (int)($frozen * $rate / 1000);
// bcmath (exact)
$bcResult = (int)bcdiv(bcmul((string)$frozen, (string)$rate), '1000');
// Both should equal 4938 for this input
$expected = (int)floor($frozen * $rate / 1000);
$this->assertEquals($expected, $bcResult, 'bcmath 结果与预期一致');
$this->assertEquals($bcResult, $nativeResult, '此用例下两者结果应相同(验证无浮点偏差)');
}
// -----------------------------------------------------------------------
// 级差计算
// -----------------------------------------------------------------------
/**
* @test
* 直推奖励level=1 (创客) 应获得 500 积分
*/
public function testDirectRewardForLevel1(): void
{
// Default config: level1 direct = 500
$defaults = [0 => 0, 1 => 500, 2 => 800, 3 => 1000, 4 => 1300];
$level = 1;
$reward = $defaults[$level];
$this->assertEquals(500, $reward, '创客直推奖励默认应为 500');
}
/**
* @test
* 级差计算:上级 level=2(云店,800) — 下级已获得 level=1(创客,500) = 差额 300
*/
public function testUmbrellaRewardCascadeLevel2(): void
{
$directDefaults = [0 => 0, 1 => 500, 2 => 800, 3 => 1000, 4 => 1300];
$umbrellaDefaults = [0 => 0, 1 => 0, 2 => 300, 3 => 200, 4 => 300];
$upperLevel = 2; // 云店
$lowerReward = $directDefaults[1]; // 下级(创客)已获得的直推奖励 500
$umbrellaReward = $umbrellaDefaults[$upperLevel]; // 云店伞下奖励 300
$actual = max(0, $umbrellaReward - $lowerReward);
// 级差300 - 500 = -200 → max(0, -200) = 0
$this->assertEquals(0, $actual, '级差为负数时实发为 0云店伞下300 < 创客直推500');
}
/**
* @test
* 级差计算:上级 level=3(服务商, umbrella=200) — 下级已获得 umbrella(云店=300) → max(0, 200-300) = 0
*/
public function testUmbrellaRewardCascadeLevel3(): void
{
$umbrellaDefaults = [0 => 0, 1 => 0, 2 => 300, 3 => 200, 4 => 300];
$upperLevel = 3;
$lowerReward = $umbrellaDefaults[2]; // 下级云店获得的伞下奖励 300
$actual = max(0, $umbrellaDefaults[$upperLevel] - $lowerReward);
$this->assertEquals(0, $actual, '服务商伞下200 < 云店伞下300级差为0');
}
/**
* @test
* 级差计算:上级 level=4(分公司, umbrella=300) — 下级已获得 umbrella(服务商=200) = 100
*/
public function testUmbrellaRewardCascadeLevel4(): void
{
$umbrellaDefaults = [0 => 0, 1 => 0, 2 => 300, 3 => 200, 4 => 300];
$upperLevel = 4;
$lowerReward = $umbrellaDefaults[3]; // 服务商已获得 200
$actual = max(0, $umbrellaDefaults[$upperLevel] - $lowerReward);
$this->assertEquals(100, $actual, '分公司伞下300 - 服务商伞下200 = 级差100');
}
/**
* @test
* 传递参数验证propagateReward 向上传递的是"应得额"而非"实发额"
*
* 场景level3 实发 0因级差为负但向上传递的 lowerReward 仍为其"应得额"200
*/
public function testCascadePropagatesExpectedNotActual(): void
{
$umbrellaDefaults = [0 => 0, 1 => 0, 2 => 300, 3 => 200, 4 => 300];
// Level3 应得 = 200, 下级已得 300 → 实发 = max(0, 200-300) = 0
$level3Expected = $umbrellaDefaults[3]; // 200
$level3Actual = max(0, $level3Expected - $umbrellaDefaults[2]); // max(0, 200-300) = 0
// 但向上传递时使用 level3Expected200而非 level3Actual0
$level4Expected = $umbrellaDefaults[4]; // 300
$level4Actual = max(0, $level4Expected - $level3Expected); // max(0, 300-200) = 100
$this->assertEquals(0, $level3Actual, 'Level3 实发为 0级差为负');
$this->assertEquals(100, $level4Actual, 'Level4 基于 level3 应得额200计算实发 100');
}
// -----------------------------------------------------------------------
// 每日释放批量处理
// -----------------------------------------------------------------------
/**
* @test
* 批量释放:多用户同时处理,每人独立计算(统计结果正确)
*/
public function testBatchReleaseAggregation(): void
{
$rate = 4;
$users = [
['uid' => 1, 'frozen_points' => 10000],
['uid' => 2, 'frozen_points' => 5000],
['uid' => 3, 'frozen_points' => 250], // 250×4/1000 = 1FLOOR
['uid' => 4, 'frozen_points' => 0], // skip
];
$totalReleased = 0;
$processed = 0;
foreach ($users as $user) {
$frozen = $user['frozen_points'];
$releaseAmount = (int)bcdiv(bcmul((string)$frozen, (string)$rate), '1000');
if ($releaseAmount <= 0) {
continue;
}
$totalReleased += $releaseAmount;
$processed++;
}
$this->assertEquals(3, $processed, '应处理3个用户frozen>0且释放额>0');
$this->assertEquals(61, $totalReleased, '总释放积分应为 40+20+1=61');
}
}