AdminBonusLogTest.php 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. <?php
  2. namespace Tests\Integration;
  3. use App\Http\Controllers\admin\Balance;
  4. use Illuminate\Config\Repository;
  5. use Illuminate\Database\Capsule\Manager;
  6. use Illuminate\Foundation\Application;
  7. use Illuminate\Http\Request;
  8. use Illuminate\Support\Facades\DB;
  9. use Illuminate\Support\Facades\Facade;
  10. use PHPUnit\Framework\TestCase;
  11. class AdminBonusLogTest extends TestCase
  12. {
  13. private Application $app;
  14. protected function setUp(): void
  15. {
  16. Facade::clearResolvedInstances();
  17. $this->app = new Application(dirname(__DIR__, 2));
  18. $this->app->instance('config', new Repository(['app' => ['locale' => 'zh']]));
  19. $this->app->instance('log', new \Psr\Log\NullLogger());
  20. $translator = new \Illuminate\Translation\Translator(new \Illuminate\Translation\ArrayLoader(), 'zh');
  21. $this->app->instance('translator', $translator);
  22. $this->app->instance('validator', new \Illuminate\Validation\Factory($translator, $this->app));
  23. $this->app->instance(\Illuminate\Contracts\Routing\ResponseFactory::class, new \Tests\Support\JsonResponseFactory());
  24. Facade::setFacadeApplication($this->app);
  25. $db = new Manager($this->app);
  26. $db->addConnection(['driver' => 'sqlite', 'database' => ':memory:', 'prefix' => 'bot_']);
  27. $db->setAsGlobal(); $db->bootEloquent(); $this->app->instance('db', $db->getDatabaseManager());
  28. $this->app->bind('db.schema', fn () => $db->schema());
  29. foreach ([
  30. 'users' => 'id INTEGER PRIMARY KEY,member_id TEXT,account TEXT,username TEXT,first_name TEXT,admin_note TEXT,status INTEGER,phone TEXT,visitor_id TEXT,register_ip TEXT,"from" INTEGER',
  31. 'balance_logs' => 'id INTEGER PRIMARY KEY,member_id TEXT,type INTEGER,change_type TEXT,amount TEXT,before_balance TEXT,after_balance TEXT,related_id INTEGER,remark TEXT,frozen_status INTEGER,created_at TEXT,updated_at TEXT',
  32. 'promotion_activities' => 'id INTEGER PRIMARY KEY,type INTEGER',
  33. 'promotion_rewards' => 'id INTEGER PRIMARY KEY,activity_id INTEGER,credited_log_id INTEGER',
  34. 'recharge_bonus_rewards' => 'id INTEGER PRIMARY KEY,credited_log_id INTEGER',
  35. ] as $table => $columns) DB::statement('CREATE TABLE bot_' . $table . ' (' . $columns . ')');
  36. DB::table('users')->insert(['id' => 1, 'member_id' => 'm1', 'account' => 'alice', 'from' => 1]);
  37. foreach ([[1,2,'余额宝利息','2'],[2,1,'余额宝利息','3'],[3,1,'优惠活动','666'],[4,1,'优惠活动','500'],[5,1,'流水解冻','2'],[6,2,'流水解冻','-2'],[7,1,'充值','100'],[8,1,'即充即送','5']] as [$id,$type,$name,$amount]) {
  38. DB::table('balance_logs')->insert(['id'=>$id,'member_id'=>'m1','type'=>$type,'change_type'=>$name,'amount'=>$amount,'created_at'=>'2026-11-05 12:00:00']);
  39. }
  40. DB::table('promotion_activities')->insert([['id'=>9,'type'=>9],['id'=>7,'type'=>7]]);
  41. DB::table('promotion_rewards')->insert([['id'=>1,'activity_id'=>9,'credited_log_id'=>3],['id'=>2,'activity_id'=>7,'credited_log_id'=>4]]);
  42. Request::macro('validate', function (array $rules) { return app('validator')->make($this->all(), $rules)->validate(); });
  43. }
  44. protected function tearDown(): void
  45. {
  46. DB::disconnect(); Facade::clearResolvedInstances(); parent::tearDown();
  47. }
  48. private function records(array $filters = []): array
  49. {
  50. $this->app->instance('request', Request::create('/admin/balance/log', 'GET', $filters));
  51. $body = (new Balance())->log()->getData(true);
  52. $this->assertSame(0, $body['code'], $body['msg']);
  53. return $body['data'];
  54. }
  55. public function test_bonus_includes_available_and_legacy_rewards_without_counting_transfers(): void
  56. {
  57. $data=$this->records(['is_fancai'=>1,'type'=>2,'limit'=>2]);
  58. $this->assertSame(5,$data['total']);
  59. $this->assertSame('1176.00',$data['total_amount']);
  60. $this->assertCount(2,$data['data']);
  61. $this->assertSame([8,4],array_column($data['data'],'id'));
  62. $this->assertSame('净亏邀请',$data['data'][1]['change_type']);
  63. $this->assertArrayNotHasKey('activity_types', $data);
  64. foreach (['reward_source', 'activity_type', 'activity_id'] as $field) $this->assertArrayNotHasKey($field, $data['data'][1]);
  65. $this->assertSame('1176.00',$this->records(['is_fancai'=>1,'page'=>2,'limit'=>2])['total_amount']);
  66. }
  67. public function test_activity_and_member_filters_apply_to_counts_and_sum(): void
  68. {
  69. $data=$this->records(['is_fancai'=>1,'change_type'=>'转盘','account'=>'lic']);
  70. $this->assertSame(1,$data['total']);$this->assertSame('666.00',$data['total_amount']);
  71. $this->assertSame('转盘',$data['data'][0]['change_type']);
  72. $this->assertSame(0,$this->records(['is_fancai'=>1,'account'=>'bob'])['total']);
  73. $this->assertSame(2,$this->records(['is_fancai'=>1,'change_type'=>'余额宝利息'])['total']);
  74. }
  75. public function test_regular_balance_type_filter_remains_effective(): void
  76. {
  77. $this->assertSame(2,$this->records(['type'=>2])['total']);
  78. $this->assertSame(8,$this->records()['total']);
  79. $ordinary = $this->records(['change_type'=>'优惠活动']);
  80. $this->assertSame(2, $ordinary['total']);
  81. $this->assertSame(['优惠活动'], array_values(array_unique(array_column($ordinary['data'], 'change_type'))));
  82. }
  83. private function log(int $id, string $type, string $amount = '10'): void
  84. {
  85. DB::table('balance_logs')->insert(['id'=>$id, 'member_id'=>'m1', 'type'=>1, 'change_type'=>$type,
  86. 'amount'=>$amount, 'before_balance'=>'100.00', 'after_balance'=>bcadd('100', $amount, 2),
  87. 'related_id'=>1, 'created_at'=>'2026-11-05 12:00:00']);
  88. }
  89. public function test_each_dropdown_value_filters_exactly_the_type_returned_in_rows(): void
  90. {
  91. DB::table('promotion_activities')->insert([['id'=>8,'type'=>8],['id'=>10,'type'=>10]]);
  92. foreach ([80=>8, 100=>10] as $log => $activity) {
  93. $this->log($log, '优惠活动');
  94. DB::table('promotion_rewards')->insert(['activity_id'=>$activity,'credited_log_id'=>$log]);
  95. }
  96. $this->log(101, '优惠活动', '6');
  97. DB::table('recharge_bonus_rewards')->insert(['credited_log_id'=>101]);
  98. $this->log(102, '优惠活动', '4'); // Same related_id as a promotion reward; it is not that reward's log.
  99. $this->log(103, '老用户回归');
  100. $this->log(104, '注册赠送');
  101. $this->log(105, '充值返现');
  102. // Unclaimed/coupon rewards and zero-valued channel rewards have no credited log.
  103. DB::table('promotion_rewards')->insert(['activity_id'=>9,'credited_log_id'=>null]);
  104. DB::table('recharge_bonus_rewards')->insert(['credited_log_id'=>null]);
  105. $options = $this->records(['is_fancai'=>1])['change_types'];
  106. $this->assertContains('转盘', $options);
  107. $this->assertContains('净亏邀请', $options);
  108. $this->assertContains('首充奖励', $options);
  109. $this->assertContains('邀请升级奖励', $options);
  110. foreach ($options as $type) {
  111. $filtered = $this->records(['is_fancai'=>1, 'change_type'=>$type, 'limit'=>100]);
  112. $this->assertGreaterThan(0, $filtered['total'], $type);
  113. $this->assertSame([$type], array_values(array_unique(array_column($filtered['data'], 'change_type'))));
  114. }
  115. $generic = $this->records(['is_fancai'=>1, 'change_type'=>'优惠活动']);
  116. $this->assertSame([102], array_column($generic['data'], 'id'));
  117. $this->assertSame('4.00', $generic['total_amount']);
  118. $channel = $this->records(['is_fancai'=>1, 'change_type'=>'充值渠道赠送']);
  119. $this->assertSame([101], array_column($channel['data'], 'id'));
  120. $this->assertSame('6.00', $channel['total_amount']);
  121. $this->assertSame('优惠活动', DB::table('balance_logs')->where('id', 3)->value('change_type'));
  122. }
  123. public function test_source_filter_pagination_totals_and_money_fields_remain_consistent(): void
  124. {
  125. $this->log(200, '优惠活动', '20');
  126. DB::table('promotion_rewards')->insert(['activity_id'=>9,'credited_log_id'=>200]);
  127. foreach ([1=>200, 2=>3] as $page=>$id) {
  128. $data = $this->records(['is_fancai'=>1, 'change_type'=>'转盘', 'limit'=>1, 'page'=>$page]);
  129. $this->assertSame(2, $data['total']);
  130. $this->assertSame('686.00', $data['total_amount']);
  131. $this->assertSame([$id], array_column($data['data'], 'id'));
  132. }
  133. $row = $this->records(['is_fancai'=>1, 'change_type'=>'转盘', 'limit'=>1])['data'][0];
  134. $this->assertSame('100.00', $row['before_balance']);
  135. $this->assertSame('20', $row['amount']);
  136. $this->assertSame('120.00', $row['after_balance']);
  137. }
  138. public function test_legacy_schema_keeps_generic_rewards_readable_and_specific_filters_empty(): void
  139. {
  140. DB::statement('DROP TABLE bot_promotion_rewards');
  141. DB::statement('DROP TABLE bot_promotion_activities');
  142. DB::statement('DROP TABLE bot_recharge_bonus_rewards');
  143. $generic = $this->records(['is_fancai'=>1, 'change_type'=>'优惠活动']);
  144. $this->assertSame(2, $generic['total']);
  145. $this->assertSame('1166.00', $generic['total_amount']);
  146. foreach (['转盘', '净亏邀请', '充值渠道赠送'] as $type) {
  147. $this->assertSame(0, $this->records(['is_fancai'=>1, 'change_type'=>$type])['total']);
  148. }
  149. }
  150. }