| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340 |
- <?php
- namespace Tests\Unit;
- use Tests\TestCase;
- /**
- * 测试召回礼包(gift_id=305)核心逻辑:tier 计算、分布比例、天数计算
- *
- * @see PayRechargeController::getRecallGiftTiers()
- * @see PayRechargeController::vipInactiveGiftSevenDays()
- * @see PayRechargeController::claimVipInactiveGiftDayReward()
- */
- class RecallGiftLogicTest extends TestCase
- {
- /**
- * 可用档位:9, 19, 29, 39, 49, 59, 79, 99
- * 初档 70%,中档 85%,高档 100%
- */
- /** @test */
- public function it_selects_first_three_tiers_when_avg_below_minimum()
- {
- // avgRecharge < 9 → 初档=9, 中档=19, 高档=29
- $tiers = $this->getRecallGiftTiers(5);
- $this->assertCount(3, $tiers);
- $this->assertEquals(9, $tiers[0]['amount']);
- $this->assertEquals(19, $tiers[1]['amount']);
- $this->assertEquals(29, $tiers[2]['amount']);
- $this->assertEquals(70, $tiers[0]['bonus_percent']);
- $this->assertEquals(85, $tiers[1]['bonus_percent']);
- $this->assertEquals(100, $tiers[2]['bonus_percent']);
- }
- /** @test */
- public function it_selects_tiers_based_on_average_recharge()
- {
- // avgRecharge = 15,初档应为第一个 >= 15 的档位 = 19
- $tiers = $this->getRecallGiftTiers(15);
- $this->assertCount(3, $tiers);
- $this->assertEquals(19, $tiers[0]['amount']);
- $this->assertEquals(29, $tiers[1]['amount']);
- $this->assertEquals(39, $tiers[2]['amount']);
- }
- /** @test */
- public function it_selects_last_three_tiers_when_avg_above_maximum()
- {
- // avgRecharge = 100 > 99,取最后三位:59, 79, 99
- $tiers = $this->getRecallGiftTiers(100);
- $this->assertCount(3, $tiers);
- $this->assertEquals(59, $tiers[0]['amount']);
- $this->assertEquals(79, $tiers[1]['amount']);
- $this->assertEquals(99, $tiers[2]['amount']);
- $this->assertEquals(70, $tiers[0]['bonus_percent']); // 初档
- $this->assertEquals(85, $tiers[1]['bonus_percent']); // 中档
- $this->assertEquals(100, $tiers[2]['bonus_percent']); // 高档
- }
- /** @test */
- public function it_handles_exact_boundary_values()
- {
- // avgRecharge = 9,恰好等于初档
- $tiers = $this->getRecallGiftTiers(9);
- $this->assertEquals(9, $tiers[0]['amount']);
- // avgRecharge = 29,初档应为 29
- $tiers = $this->getRecallGiftTiers(29);
- $this->assertEquals(29, $tiers[0]['amount']);
- $this->assertEquals(39, $tiers[1]['amount']);
- $this->assertEquals(49, $tiers[2]['amount']);
- }
- /** @test */
- public function it_handles_avg_between_tiers()
- {
- // avgRecharge = 25,第一个 >= 25 的是 29
- $tiers = $this->getRecallGiftTiers(25);
- $this->assertEquals(29, $tiers[0]['amount']);
- $this->assertEquals(39, $tiers[1]['amount']);
- $this->assertEquals(49, $tiers[2]['amount']);
- }
- /** @test */
- public function distribution_sums_to_100_percent()
- {
- $distribution = [7, 9, 11, 13, 14, 21, 25];
- $this->assertEquals(100, array_sum($distribution));
- }
- /** @test */
- public function daily_amounts_match_distribution()
- {
- // 如果总奖励 = $10.00,按比例分布
- $totalBonus = 10.00;
- $distribution = [7, 9, 11, 13, 14, 21, 25];
- $dayAmounts = [];
- for ($i = 0; $i < 7; $i++) {
- $dayAmounts[] = round($totalBonus * $distribution[$i] / 100, 2);
- }
- $this->assertEquals(0.70, $dayAmounts[0]); // Day 1: 7%
- $this->assertEquals(0.90, $dayAmounts[1]); // Day 2: 9%
- $this->assertEquals(1.10, $dayAmounts[2]); // Day 3: 11%
- $this->assertEquals(1.30, $dayAmounts[3]); // Day 4: 13%
- $this->assertEquals(1.40, $dayAmounts[4]); // Day 5: 14%
- $this->assertEquals(2.10, $dayAmounts[5]); // Day 6: 21%
- $this->assertEquals(2.50, $dayAmounts[6]); // Day 7: 25%
- // 总和应等于 $10.00(允许浮点误差)
- $this->assertEquals(10.00, round(array_sum($dayAmounts), 2));
- }
- /** @test */
- public function daily_amounts_match_distribution_realistic_bonus()
- {
- // payAmt=19, bonusPercent=70% → totalBonus = 19 * 0.70 = 13.30
- $payAmt = 19.00;
- $bonusPercent = 70;
- $totalBonus = round($payAmt * $bonusPercent / 100, 2);
- $this->assertEquals(13.30, $totalBonus);
- $distribution = [7, 9, 11, 13, 14, 21, 25];
- $dayAmounts = [];
- for ($i = 0; $i < 7; $i++) {
- $dayAmounts[] = round($totalBonus * $distribution[$i] / 100, 2);
- }
- $this->assertEquals(0.93, $dayAmounts[0]); // 13.30 * 7% = 0.931
- $this->assertEquals(1.20, $dayAmounts[1]); // 13.30 * 9% = 1.197
- $this->assertEquals(1.46, $dayAmounts[2]); // 13.30 * 11% = 1.463
- $this->assertEquals(1.73, $dayAmounts[3]); // 13.30 * 13% = 1.729
- $this->assertEquals(1.86, $dayAmounts[4]); // 13.30 * 14% = 1.862
- $this->assertEquals(2.79, $dayAmounts[5]); // 13.30 * 21% = 2.793
- $this->assertEquals(3.33, $dayAmounts[6]); // 13.30 * 25% = 3.325
- }
- /** @test */
- public function inactive_days_calculation_is_correct()
- {
- // 模拟:最后一次充值在 7 天前
- $lastDate = date('Y-m-d', strtotime('-7 days'));
- $today = date('Y-m-d');
- $diffDays = (strtotime($today) - strtotime($lastDate)) / 86400;
- $inactiveDays = max(0, (int)$diffDays - 1);
- // 7天前充值 → diffDays=7 → inactiveDays=6
- // 但需要连续7天未充值,所以 lastPay 应是8天前
- $this->assertEquals(6, $inactiveDays);
- // 8天前充值 → diffDays=8 → inactiveDays=7(满足条件)
- $lastDate = date('Y-m-d', strtotime('-8 days'));
- $diffDays = (strtotime($today) - strtotime($lastDate)) / 86400;
- $inactiveDays = max(0, (int)$diffDays - 1);
- $this->assertEquals(7, $inactiveDays);
- }
- /** @test */
- public function day_offset_from_recharge_to_signin()
- {
- // 如果充值在 2026-05-14
- $createdAt = '2026-05-14';
-
- // 签到第1天 = 充值次日 = 2026-05-15
- $day1Target = date('Ymd', strtotime($createdAt . ' +1 days'));
- $this->assertEquals('20260515', $day1Target);
-
- // 签到第7天 = 充值 + 7天 = 2026-05-21
- $day7Target = date('Ymd', strtotime($createdAt . ' +7 days'));
- $this->assertEquals('20260521', $day7Target);
- }
- /** @test */
- public function days_passed_calculation()
- {
- // 充值当天
- $createdAt = '2026-05-14';
- $today = '2026-05-14';
- $daysPassed = floor((strtotime($today) - strtotime($createdAt)) / 86400);
- $this->assertEquals(0, $daysPassed); // 还不能签到
- // 充值次日
- $today = '2026-05-15';
- $daysPassed = floor((strtotime($today) - strtotime($createdAt)) / 86400);
- $this->assertEquals(1, $daysPassed); // 可以签到第1天
- // 充值7天后
- $today = '2026-05-21';
- $daysPassed = floor((strtotime($today) - strtotime($createdAt)) / 86400);
- $this->assertEquals(7, $daysPassed); // 可以签到第7天
- // 充值8天后(过期)
- $today = '2026-05-22';
- $daysPassed = floor((strtotime($today) - strtotime($createdAt)) / 86400);
- $this->assertEquals(8, $daysPassed); // 超过7天,过期
- }
- /** @test */
- public function claim_day_mapping()
- {
- // 按实际签到次数确定天数,不再依赖自然日
- // 已签到0次 → day=1 (第1次签到) → dayIndex=0 → 7%
- $claimedDaysCount = 0;
- $day = $claimedDaysCount + 1;
- $dayIndex = $day - 1; // 0
- $distribution = [7, 9, 11, 13, 14, 21, 25];
- $this->assertEquals(0, $dayIndex);
- $this->assertEquals(7, $distribution[$dayIndex]);
- // 已签到1次 → day=2 (第2次签到) → dayIndex=1 → 9%
- $claimedDaysCount = 1;
- $day = $claimedDaysCount + 1;
- $dayIndex = $day - 1; // 1
- $this->assertEquals(1, $dayIndex);
- $this->assertEquals(9, $distribution[$dayIndex]);
- // 已签到3次 → day=4 (第4次签到) → dayIndex=3 → 13%
- $claimedDaysCount = 3;
- $day = $claimedDaysCount + 1;
- $dayIndex = $day - 1; // 3
- $this->assertEquals(3, $dayIndex);
- $this->assertEquals(13, $distribution[$dayIndex]);
- // 已签到6次 → day=7 (第7次签到) → dayIndex=6 → 25%
- $claimedDaysCount = 6;
- $day = $claimedDaysCount + 1;
- $dayIndex = $day - 1; // 6
- $this->assertEquals(6, $dayIndex);
- $this->assertEquals(25, $distribution[$dayIndex]);
- }
- /** @test */
- public function claimed_count_determines_next_day()
- {
- // 签到次数直接影响下一次能签第几天
- // 已签到0次,下次签第1天
- $claimedMask = 0; // 0b0000000
- $claimedCount = 0;
- for ($i = 0; $i < 7; $i++) {
- if ($claimedMask & (1 << $i)) {
- $claimedCount++;
- }
- }
- $nextDay = $claimedCount + 1;
- $this->assertEquals(1, $nextDay);
- // 已签到2次(第1、2天),下次签第3天
- $claimedMask = (1 << 0) | (1 << 1); // 0b0000011 = 3
- $claimedCount = 0;
- for ($i = 0; $i < 7; $i++) {
- if ($claimedMask & (1 << $i)) {
- $claimedCount++;
- }
- }
- $nextDay = $claimedCount + 1;
- $this->assertEquals(3, $nextDay);
- $this->assertEquals(2, $claimedCount);
- // 已签到7次,所有签完
- $claimedMask = (1 << 7) - 1; // 0b1111111 = 127
- $claimedCount = 0;
- for ($i = 0; $i < 7; $i++) {
- if ($claimedMask & (1 << $i)) {
- $claimedCount++;
- }
- }
- $nextDay = $claimedCount + 1;
- $this->assertEquals(7, $claimedCount);
- $this->assertEquals(8, $nextDay); // > 7,不能再签
- }
- /** @test */
- public function only_one_claim_per_day()
- {
- // 如果今天已经签过,不能再次签到
- $lastClaimDate = '2026-05-15';
- $todayDate = '2026-05-15';
- $this->assertTrue($lastClaimDate === $todayDate); // 今天已签
- $lastClaimDate = '2026-05-14';
- $todayDate = '2026-05-15';
- $this->assertFalse($lastClaimDate === $todayDate); // 今天未签
- }
- /** @test */
- public function claimed_bits_match_seven_days_view()
- {
- // sevenDays 用 bit0 表示 day1,bit1 表示 day2 ...
- // claim 中用 dayIndex = day-1,也映射到 bit0=day1, bit1=day2
- // 模拟已领取 day1, day2, day3
- $claimedMask = (1 << 0) | (1 << 1) | (1 << 2); // 0b0000111 = 7
- $this->assertTrue((bool)($claimedMask & (1 << 0))); // day1 已领
- $this->assertTrue((bool)($claimedMask & (1 << 1))); // day2 已领
- $this->assertTrue((bool)($claimedMask & (1 << 2))); // day3 已领
- $this->assertFalse((bool)($claimedMask & (1 << 3))); // day4 未领
- $this->assertFalse((bool)($claimedMask & (1 << 6))); // day7 未领
- // 所有 7 天都已领完
- $allClaimedMask = (1 << 7) - 1; // 0b1111111 = 127
- $this->assertEquals(127, $allClaimedMask);
- $isAllClaimed = ($claimedMask & $allClaimedMask) == $allClaimedMask;
- $this->assertFalse($isAllClaimed); // 只领了3天
- $fullMask = 0b1111111;
- $this->assertTrue(($fullMask & $allClaimedMask) == $allClaimedMask);
- }
- /**
- * 复制控制器中的 getRecallGiftTiers 逻辑用于纯逻辑测试
- */
- private function getRecallGiftTiers($avgRecharge)
- {
- $amounts = [9, 19, 29, 39, 49, 59, 79, 99];
- $bonusPercents = [70, 85, 100];
- $index = count($amounts);
- foreach ($amounts as $i => $amt) {
- if ($avgRecharge <= $amt) {
- $index = $i;
- break;
- }
- }
- if ($index > count($amounts) - 3) {
- $index = count($amounts) - 3;
- }
- $tiers = [];
- for ($i = 0; $i < 3; $i++) {
- $tiers[] = [
- 'amount' => $amounts[$index + $i],
- 'bonus_percent' => $bonusPercents[$i],
- ];
- }
- return $tiers;
- }
- }
|