286 lines
9.7 KiB
PHP
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('初来乍到');
|
|
}
|
|
}
|