app = new Application(dirname(__DIR__, 2)); $this->app->instance('config', new Repository(['app' => ['locale' => 'zh']])); $this->app->instance('log', new \Psr\Log\NullLogger()); $translator = new \Illuminate\Translation\Translator(new \Illuminate\Translation\ArrayLoader(), 'zh'); $this->app->instance('translator', $translator); $this->app->instance('validator', new \Illuminate\Validation\Factory($translator, $this->app)); $this->app->instance(\Illuminate\Contracts\Routing\ResponseFactory::class, new \Tests\Support\JsonResponseFactory()); Facade::setFacadeApplication($this->app); $db = new Manager($this->app); $db->addConnection(['driver' => 'sqlite', 'database' => ':memory:', 'prefix' => 'bot_']); $db->setAsGlobal(); $db->bootEloquent(); $this->app->instance('db', $db->getDatabaseManager()); $this->app->bind('db.schema', fn () => $db->schema()); foreach ([ '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', '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', 'promotion_activities' => 'id INTEGER PRIMARY KEY,type INTEGER', 'promotion_rewards' => 'id INTEGER PRIMARY KEY,activity_id INTEGER,credited_log_id INTEGER', 'recharge_bonus_rewards' => 'id INTEGER PRIMARY KEY,credited_log_id INTEGER', ] as $table => $columns) DB::statement('CREATE TABLE bot_' . $table . ' (' . $columns . ')'); DB::table('users')->insert(['id' => 1, 'member_id' => 'm1', 'account' => 'alice', 'from' => 1]); 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]) { 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']); } DB::table('promotion_activities')->insert([['id'=>9,'type'=>9],['id'=>7,'type'=>7]]); DB::table('promotion_rewards')->insert([['id'=>1,'activity_id'=>9,'credited_log_id'=>3],['id'=>2,'activity_id'=>7,'credited_log_id'=>4]]); Request::macro('validate', function (array $rules) { return app('validator')->make($this->all(), $rules)->validate(); }); } protected function tearDown(): void { DB::disconnect(); Facade::clearResolvedInstances(); parent::tearDown(); } private function records(array $filters = []): array { $this->app->instance('request', Request::create('/admin/balance/log', 'GET', $filters)); $body = (new Balance())->log()->getData(true); $this->assertSame(0, $body['code'], $body['msg']); return $body['data']; } public function test_bonus_includes_available_and_legacy_rewards_without_counting_transfers(): void { $data=$this->records(['is_fancai'=>1,'type'=>2,'limit'=>2]); $this->assertSame(5,$data['total']); $this->assertSame('1176.00',$data['total_amount']); $this->assertCount(2,$data['data']); $this->assertSame([8,4],array_column($data['data'],'id')); $this->assertSame('净亏邀请',$data['data'][1]['change_type']); $this->assertArrayNotHasKey('activity_types', $data); foreach (['reward_source', 'activity_type', 'activity_id'] as $field) $this->assertArrayNotHasKey($field, $data['data'][1]); $this->assertSame('1176.00',$this->records(['is_fancai'=>1,'page'=>2,'limit'=>2])['total_amount']); } public function test_activity_and_member_filters_apply_to_counts_and_sum(): void { $data=$this->records(['is_fancai'=>1,'change_type'=>'转盘','account'=>'lic']); $this->assertSame(1,$data['total']);$this->assertSame('666.00',$data['total_amount']); $this->assertSame('转盘',$data['data'][0]['change_type']); $this->assertSame(0,$this->records(['is_fancai'=>1,'account'=>'bob'])['total']); $this->assertSame(2,$this->records(['is_fancai'=>1,'change_type'=>'余额宝利息'])['total']); } public function test_regular_balance_type_filter_remains_effective(): void { $this->assertSame(2,$this->records(['type'=>2])['total']); $this->assertSame(8,$this->records()['total']); $ordinary = $this->records(['change_type'=>'优惠活动']); $this->assertSame(2, $ordinary['total']); $this->assertSame(['优惠活动'], array_values(array_unique(array_column($ordinary['data'], 'change_type')))); } private function log(int $id, string $type, string $amount = '10'): void { DB::table('balance_logs')->insert(['id'=>$id, 'member_id'=>'m1', 'type'=>1, 'change_type'=>$type, 'amount'=>$amount, 'before_balance'=>'100.00', 'after_balance'=>bcadd('100', $amount, 2), 'related_id'=>1, 'created_at'=>'2026-11-05 12:00:00']); } public function test_each_dropdown_value_filters_exactly_the_type_returned_in_rows(): void { DB::table('promotion_activities')->insert([['id'=>8,'type'=>8],['id'=>10,'type'=>10]]); foreach ([80=>8, 100=>10] as $log => $activity) { $this->log($log, '优惠活动'); DB::table('promotion_rewards')->insert(['activity_id'=>$activity,'credited_log_id'=>$log]); } $this->log(101, '优惠活动', '6'); DB::table('recharge_bonus_rewards')->insert(['credited_log_id'=>101]); $this->log(102, '优惠活动', '4'); // Same related_id as a promotion reward; it is not that reward's log. $this->log(103, '老用户回归'); $this->log(104, '注册赠送'); $this->log(105, '充值返现'); // Unclaimed/coupon rewards and zero-valued channel rewards have no credited log. DB::table('promotion_rewards')->insert(['activity_id'=>9,'credited_log_id'=>null]); DB::table('recharge_bonus_rewards')->insert(['credited_log_id'=>null]); $options = $this->records(['is_fancai'=>1])['change_types']; $this->assertContains('转盘', $options); $this->assertContains('净亏邀请', $options); $this->assertContains('首充奖励', $options); $this->assertContains('邀请升级奖励', $options); foreach ($options as $type) { $filtered = $this->records(['is_fancai'=>1, 'change_type'=>$type, 'limit'=>100]); $this->assertGreaterThan(0, $filtered['total'], $type); $this->assertSame([$type], array_values(array_unique(array_column($filtered['data'], 'change_type')))); } $generic = $this->records(['is_fancai'=>1, 'change_type'=>'优惠活动']); $this->assertSame([102], array_column($generic['data'], 'id')); $this->assertSame('4.00', $generic['total_amount']); $channel = $this->records(['is_fancai'=>1, 'change_type'=>'充值渠道赠送']); $this->assertSame([101], array_column($channel['data'], 'id')); $this->assertSame('6.00', $channel['total_amount']); $this->assertSame('优惠活动', DB::table('balance_logs')->where('id', 3)->value('change_type')); } public function test_source_filter_pagination_totals_and_money_fields_remain_consistent(): void { $this->log(200, '优惠活动', '20'); DB::table('promotion_rewards')->insert(['activity_id'=>9,'credited_log_id'=>200]); foreach ([1=>200, 2=>3] as $page=>$id) { $data = $this->records(['is_fancai'=>1, 'change_type'=>'转盘', 'limit'=>1, 'page'=>$page]); $this->assertSame(2, $data['total']); $this->assertSame('686.00', $data['total_amount']); $this->assertSame([$id], array_column($data['data'], 'id')); } $row = $this->records(['is_fancai'=>1, 'change_type'=>'转盘', 'limit'=>1])['data'][0]; $this->assertSame('100.00', $row['before_balance']); $this->assertSame('20', $row['amount']); $this->assertSame('120.00', $row['after_balance']); } public function test_legacy_schema_keeps_generic_rewards_readable_and_specific_filters_empty(): void { DB::statement('DROP TABLE bot_promotion_rewards'); DB::statement('DROP TABLE bot_promotion_activities'); DB::statement('DROP TABLE bot_recharge_bonus_rewards'); $generic = $this->records(['is_fancai'=>1, 'change_type'=>'优惠活动']); $this->assertSame(2, $generic['total']); $this->assertSame('1166.00', $generic['total_amount']); foreach (['转盘', '净亏邀请', '充值渠道赠送'] as $type) { $this->assertSame(0, $this->records(['is_fancai'=>1, 'change_type'=>$type])['total']); } } }