Files
chatroom/tests/Feature/AchievementServiceTest.php
T

286 lines
9.7 KiB
PHP

<?php
/**
* 文件功能:用户成就系统功能测试。
*
* 覆盖固定成就扫描、重复扫描幂等性与本人可见通知。
*/
namespace Tests\Feature;
use App\Enums\CurrencySource;
use App\Jobs\SaveMessageJob;
use App\Models\DailySignIn;
use App\Models\Message;
use App\Models\Room;
use App\Models\User;
use App\Models\UserAchievement;
use App\Models\UserCurrencyLog;
use App\Services\AchievementService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Queue;
use Illuminate\Support\Facades\Redis;
use Tests\TestCase;
/**
* 类功能:验证成就扫描服务和 Artisan 命令的核心行为。
*/
class AchievementServiceTest extends TestCase
{
use RefreshDatabase;
/**
* 每个测试前清空 Redis 房间状态。
*/
protected function setUp(): void
{
parent::setUp();
$this->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('初来乍到');
}
}