flushChatRoomRedisState(); } /** * 测试命令可以按现有日志解锁聊天、签到与游戏成就。 */ public function test_scan_command_unlocks_achievements_from_existing_logs(): void { $room = Room::create(['room_name' => 'ach']); $user = User::factory()->create([ 'username' => 'achiever', 'room_id' => $room->id, 'jjb' => 700000, 'bank_jjb' => 300000, ]); for ($i = 0; $i < 100; $i++) { Message::query()->create([ 'room_id' => $room->id, 'from_user' => $user->username, 'to_user' => '大家', 'content' => '成就聊天', 'is_secret' => false, 'font_color' => '', 'action' => '', 'message_type' => 'text', 'retention_type' => Message::RETENTION_USER_CHAT, 'sent_at' => now()->subMinutes($i + 1), ]); } DailySignIn::query()->create([ 'user_id' => $user->id, 'room_id' => $room->id, 'sign_in_date' => today(), 'streak_days' => 7, 'gold_reward' => 10, 'exp_reward' => 20, 'charm_reward' => 0, ]); UserCurrencyLog::query()->create([ 'user_id' => $user->id, 'username' => $user->username, 'currency' => 'gold', 'amount' => 1000, 'balance_after' => 1000, 'source' => CurrencySource::GAME_REWARD->value, 'remark' => '猜谜奖励', 'room_id' => $room->id, ]); UserCurrencyLog::query()->create([ 'user_id' => $user->id, 'username' => $user->username, 'currency' => 'gold', 'amount' => -1000, 'balance_after' => 0, 'source' => CurrencySource::BACCARAT_BET->value, 'remark' => '百家乐下注', 'room_id' => $room->id, ]); $this->artisan('achievements:scan', ['--user' => $user->username]) ->assertSuccessful(); $this->assertDatabaseHas('user_achievements', [ 'user_id' => $user->id, 'achievement_key' => 'chat_first_message', ]); $this->assertDatabaseHas('user_achievements', [ 'user_id' => $user->id, 'achievement_key' => 'chat_100_messages', ]); $this->assertDatabaseHas('user_achievements', [ 'user_id' => $user->id, 'achievement_key' => 'signin_7_streak', ]); $this->assertDatabaseHas('user_achievements', [ 'user_id' => $user->id, 'achievement_key' => 'game_riddle_win', ]); $this->assertDatabaseHas('user_achievements', [ 'user_id' => $user->id, 'achievement_key' => 'game_win_1000', ]); $this->assertDatabaseHas('user_achievements', [ 'user_id' => $user->id, 'achievement_key' => 'game_loss_1000', ]); $this->assertDatabaseHas('user_achievements', [ 'user_id' => $user->id, 'achievement_key' => 'growth_assets_1000000', ]); $this->assertDatabaseHas('user_achievement_progress', [ 'user_id' => $user->id, 'achievement_key' => 'chat_100_messages', 'progress_value' => 100, 'threshold_value' => 100, ]); $this->assertNotNull(UserAchievement::query() ->where('user_id', $user->id) ->where('achievement_key', 'chat_100_messages') ->value('achieved_at')); } /** * 测试重复扫描不会重复创建同一个用户成就。 */ public function test_scan_command_is_idempotent_for_same_achievement(): void { $room = Room::create(['room_name' => 'idem']); $user = User::factory()->create(['username' => 'idem_user', 'room_id' => $room->id]); Message::query()->create([ 'room_id' => $room->id, 'from_user' => $user->username, 'to_user' => '大家', 'content' => '第一条', 'is_secret' => false, 'font_color' => '', 'action' => '', 'message_type' => 'text', 'retention_type' => Message::RETENTION_USER_CHAT, 'sent_at' => now(), ]); $this->artisan('achievements:scan', ['--user' => $user->id])->assertSuccessful(); $this->artisan('achievements:scan', ['--user' => $user->id])->assertSuccessful(); $this->assertSame(1, UserAchievement::query() ->where('user_id', $user->id) ->where('achievement_key', 'chat_first_message') ->count()); } /** * 测试成就解锁通知只发给本人并带悄悄话标记。 */ public function test_unlock_notification_is_private_to_the_user(): void { Queue::fake([SaveMessageJob::class]); $room = Room::create(['room_name' => 'notice']); $user = User::factory()->create(['username' => 'notice_user', 'room_id' => $room->id]); Message::query()->create([ 'room_id' => $room->id, 'from_user' => $user->username, 'to_user' => '大家', 'content' => '第一条', 'is_secret' => false, 'font_color' => '', 'action' => '', 'message_type' => 'text', 'retention_type' => Message::RETENTION_USER_CHAT, 'sent_at' => now(), ]); app(AchievementService::class)->scanUser($user, notify: true); $messages = collect(Redis::lrange("room:{$room->id}:messages", 0, -1)) ->map(fn (string $item): array => json_decode($item, true)); $notice = $messages->first(fn (array $item): bool => ($item['action'] ?? '') === 'achievement_unlocked'); $this->assertNotNull($notice); $this->assertSame($user->username, $notice['to_user'] ?? null); $this->assertTrue((bool) ($notice['is_secret'] ?? false)); $this->assertSame(Message::RETENTION_SYSTEM_NOTICE, $notice['retention_type'] ?? null); Queue::assertPushed(SaveMessageJob::class); } /** * 测试打开我的成就页面时会静默补算已达标成就。 */ public function test_achievement_page_silently_unlocks_reached_achievements(): void { $room = Room::create(['room_name' => 'page']); $user = User::factory()->create(['username' => 'page_user', 'room_id' => $room->id]); Message::query()->create([ 'room_id' => $room->id, 'from_user' => $user->username, 'to_user' => '大家', 'content' => '页面触发补算', 'is_secret' => false, 'font_color' => '', 'action' => '', 'message_type' => 'text', 'retention_type' => Message::RETENTION_USER_CHAT, 'sent_at' => now(), ]); $this->assertDatabaseMissing('user_achievements', [ 'user_id' => $user->id, 'achievement_key' => 'chat_first_message', ]); $this->actingAs($user) ->get(route('achievements.index')) ->assertOk() ->assertSee('已解锁 1 /', false); $this->assertDatabaseHas('user_achievements', [ 'user_id' => $user->id, 'achievement_key' => 'chat_first_message', ]); $this->assertNotNull(UserAchievement::query() ->where('user_id', $user->id) ->where('achievement_key', 'chat_first_message') ->value('achieved_at')); } /** * 测试我的成就页面可以按已完成和未达成筛选。 */ public function test_achievement_page_can_filter_by_unlocked_and_locked_tabs(): void { $room = Room::create(['room_name' => 'tabs']); $user = User::factory()->create(['username' => 'tab_user', 'room_id' => $room->id]); Message::query()->create([ 'room_id' => $room->id, 'from_user' => $user->username, 'to_user' => '大家', 'content' => '筛选测试', 'is_secret' => false, 'font_color' => '', 'action' => '', 'message_type' => 'text', 'retention_type' => Message::RETENTION_USER_CHAT, 'sent_at' => now(), ]); $this->actingAs($user) ->get(route('achievements.index', ['status' => 'unlocked'])) ->assertOk() ->assertSee('已完成') ->assertSee('初来乍到') ->assertDontSee('百句达人'); $this->actingAs($user) ->get(route('achievements.index', ['status' => 'locked'])) ->assertOk() ->assertSee('未达成') ->assertSee('百句达人') ->assertDontSee('初来乍到'); } }