create([ 'username' => 'AI小班长', 'exp_num' => 10, 'jjb' => 20, ]); Sysparam::updateOrCreate(['alias' => 'chatbot_enabled'], ['body' => '0']); $this->artisan('chatroom:ai-heartbeat')->assertSuccessful(); $aiUser->refresh(); $this->assertSame(10, (int) $aiUser->exp_num); $this->assertSame(20, (int) $aiUser->jjb); } /** * AI 开启时,基础心跳仍会发放配置中的经验和金币。 */ public function test_ai_heartbeat_grants_base_exp_and_gold(): void { Event::fake([MessageSent::class]); $aiUser = User::factory()->create([ 'username' => 'AI小班长', 'user_level' => 1, 'exp_num' => 10, 'jjb' => 20, ]); $this->markAlreadySigned($aiUser); $this->enableHeartbeat([ 'exp_per_heartbeat' => '5', 'jjb_per_heartbeat' => '7', 'auto_event_chance' => '0', ]); $this->artisan('chatroom:ai-heartbeat')->assertSuccessful(); $aiUser->refresh(); $this->assertSame(15, (int) $aiUser->exp_num); $this->assertSame(27, (int) $aiUser->jjb); } /** * 今日已签到时,命令不应再进入签到事务,避免每分钟重复做重活。 */ public function test_ai_heartbeat_skips_sign_in_claim_when_already_signed_today(): void { $aiUser = User::factory()->create([ 'username' => 'AI小班长', 'exp_num' => 0, 'jjb' => 0, ]); $this->markAlreadySigned($aiUser); $this->enableHeartbeat([ 'exp_per_heartbeat' => '0', 'jjb_per_heartbeat' => '0', 'auto_event_chance' => '0', ]); $this->mock(SignInService::class, function (MockInterface $mock): void { $mock->shouldReceive('claim')->never(); }); $this->artisan('chatroom:ai-heartbeat')->assertSuccessful(); } /** * 钓鱼开启且命中概率时,命令只派发延迟收竿任务,不在当前心跳里等待结果。 */ public function test_ai_heartbeat_dispatches_delayed_fishing_job(): void { Bus::fake(); Event::fake([MessageSent::class]); $aiUser = User::factory()->create([ 'username' => 'AI小班长', 'exp_num' => 0, 'jjb' => 100, ]); $this->markAlreadySigned($aiUser); $this->enableHeartbeat([ 'exp_per_heartbeat' => '0', 'jjb_per_heartbeat' => '0', 'auto_event_chance' => '0', 'chatbot_fishing_enabled' => '1', 'chatbot_fishing_chance' => '100', ]); GameConfig::query()->create([ 'game_key' => 'fishing', 'name' => '钓鱼', 'icon' => '🎣', 'enabled' => true, 'params' => [ 'fishing_cost' => 5, 'fishing_wait_min' => 8, 'fishing_wait_max' => 8, ], ]); $this->artisan('chatroom:ai-heartbeat')->assertSuccessful(); Bus::assertDispatched(AiFishingJob::class); $aiUser->refresh(); $this->assertSame(95, (int) $aiUser->jjb); } /** * 批量写入本轮心跳需要的系统参数。 * * @param array $overrides 覆盖默认值的配置 */ private function enableHeartbeat(array $overrides = []): void { $defaults = [ 'chatbot_enabled' => '1', 'exp_per_heartbeat' => '0', 'jjb_per_heartbeat' => '0', 'superlevel' => '100', 'auto_event_chance' => '0', 'chatbot_fishing_enabled' => '0', 'chatbot_fishing_chance' => '0', 'fishing_cost' => '5', 'fishing_wait_min' => '8', 'fishing_wait_max' => '15', ]; foreach (array_merge($defaults, $overrides) as $alias => $body) { Sysparam::query()->updateOrCreate(['alias' => $alias], ['body' => $body]); } Cache::flush(); } /** * 创建今日已签到记录,让测试聚焦心跳命令本身。 */ private function markAlreadySigned(User $user): void { DailySignIn::query()->create([ 'user_id' => $user->id, 'room_id' => 1, 'sign_in_date' => today()->toDateString(), 'streak_days' => 1, 'gold_reward' => 0, 'exp_reward' => 0, 'charm_reward' => 0, ]); } }