Files
chatroom/tests/Feature/AutoSaveExpCommandTest.php
T

191 lines
6.9 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* 文件功能:自动存点命令功能测试
*
* 覆盖定时自动存点私信内容,确保用户身份信息展示完整。
*/
namespace Tests\Feature;
use App\Events\MessageSent;
use App\Models\Department;
use App\Models\Position;
use App\Models\Room;
use App\Models\Sysparam;
use App\Models\User;
use App\Models\UserPosition;
use App\Models\VipLevel;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Redis;
use Tests\TestCase;
/**
* 类功能:验证自动存点命令推送给用户的状态通知内容。
*/
class AutoSaveExpCommandTest extends TestCase
{
use RefreshDatabase;
/**
* 每个测试前清空 Redis 与配置缓存,避免在线状态和系统参数串扰。
*/
protected function setUp(): void
{
parent::setUp();
Redis::flushall();
Cache::flush();
}
/**
* 测试自动存点私信会展示部门、职务与会员信息。
*/
public function test_auto_save_notice_includes_department_position_and_vip_identity(): void
{
Event::fake([MessageSent::class]);
Sysparam::updateOrCreate(['alias' => 'exp_per_heartbeat'], ['body' => '0']);
Sysparam::updateOrCreate(['alias' => 'jjb_per_heartbeat'], ['body' => '0']);
Sysparam::updateOrCreate(['alias' => 'superlevel'], ['body' => '100']);
Cache::flush();
$room = Room::create(['room_name' => 'asave']);
$vipLevel = VipLevel::factory()->create([
'name' => '至尊会员',
'icon' => '👑',
]);
$user = User::factory()->create([
'username' => 'autosave-user',
'user_level' => 100,
'exp_num' => 168762,
'jjb' => 1100017,
'vip_level_id' => $vipLevel->id,
'hy_time' => now()->addDay(),
]);
$department = Department::create([
'name' => '办公厅',
'rank' => 100,
'color' => '#1d4ed8',
'sort_order' => 1,
]);
$position = Position::create([
'department_id' => $department->id,
'name' => '厅长',
'icon' => '🏛️',
'rank' => 100,
'level' => 100,
'sort_order' => 1,
]);
UserPosition::create([
'user_id' => $user->id,
'position_id' => $position->id,
'appointed_at' => now(),
'is_active' => true,
]);
Redis::hset("room:{$room->id}:users", $user->username, json_encode(['username' => $user->username], JSON_UNESCAPED_UNICODE));
Redis::setex("room:{$room->id}:alive:{$user->username}", 90, 1);
$this->artisan('chatroom:auto-save-exp')->assertSuccessful();
$messages = collect(Redis::lrange("room:{$room->id}:messages", 0, -1))
->map(fn (string $message): array => json_decode($message, true));
$notice = $messages->first(fn (array $message): bool => ($message['to_user'] ?? '') === $user->username);
$this->assertIsArray($notice);
$this->assertStringContainsString('部门 办公厅 · 职务 🏛️ 厅长 · 会员 👑 至尊会员', $notice['content']);
$this->assertStringContainsString('LV.100 · 经验 168762 · 金币 1100017 · 已满级 ✓', $notice['content']);
}
/**
* 测试自动存点命令会清理掉线不活跃的用户(懒清理)并不发放奖励。
*/
public function test_auto_save_cleans_up_inactive_zombie_users(): void
{
Sysparam::updateOrCreate(['alias' => 'exp_per_heartbeat'], ['body' => '5']);
Cache::flush();
$room = Room::create(['room_name' => 'zmb']);
$user = User::factory()->create([
'username' => 'zombie-user',
'exp_num' => 10,
]);
// 只把用户加入哈希表,但不设置 alive 活跃键
Redis::hset("room:{$room->id}:users", $user->username, json_encode(['username' => $user->username], JSON_UNESCAPED_UNICODE));
$this->artisan('chatroom:auto-save-exp')->assertSuccessful();
// 验证用户已经被从房间在线哈希表中懒清理剔除
$this->assertFalse((bool) Redis::hexists("room:{$room->id}:users", $user->username));
// 验证用户的经验没有增加(仍然是 10)
$user->refresh();
$this->assertEquals(10, $user->exp_num);
}
/**
* 测试自动存点命令会全局去重,防止玩家在多房间同时挂机时重复吃挂机奖励。
*/
public function test_auto_save_deduplicates_multiple_rooms_rewards(): void
{
Sysparam::updateOrCreate(['alias' => 'exp_per_heartbeat'], ['body' => '5']);
Cache::flush();
$room1 = Room::create(['room_name' => 'r1']);
$room2 = Room::create(['room_name' => 'r2']);
$user = User::factory()->create([
'username' => 'multi-room-user',
'exp_num' => 10,
]);
// 玩家同时在两个房间在线,且两边都有心跳活跃标记
Redis::hset("room:{$room1->id}:users", $user->username, json_encode(['username' => $user->username], JSON_UNESCAPED_UNICODE));
Redis::setex("room:{$room1->id}:alive:{$user->username}", 90, 1);
Redis::hset("room:{$room2->id}:users", $user->username, json_encode(['username' => $user->username], JSON_UNESCAPED_UNICODE));
Redis::setex("room:{$room2->id}:alive:{$user->username}", 90, 1);
$this->artisan('chatroom:auto-save-exp')->assertSuccessful();
// 验证用户只被加了一次经验(10 + 5 = 15,而不是 10 + 5 + 5 = 20
$user->refresh();
$this->assertEquals(15, $user->exp_num);
}
/**
* 测试自动存点命令会通过 Redis 冷却锁,防止在短时间内并发重复调度执行。
*/
public function test_auto_save_cooldown_prevents_overlapping_process(): void
{
Sysparam::updateOrCreate(['alias' => 'exp_per_heartbeat'], ['body' => '5']);
Cache::flush();
$room = Room::create(['room_name' => 'cd']);
$user = User::factory()->create([
'username' => 'cooldown-user',
'exp_num' => 10,
]);
Redis::hset("room:{$room->id}:users", $user->username, json_encode(['username' => $user->username], JSON_UNESCAPED_UNICODE));
Redis::setex("room:{$room->id}:alive:{$user->username}", 90, 1);
// 第一次执行自动存点
$this->artisan('chatroom:auto-save-exp')->assertSuccessful();
// 验证第一次加了经验 (10 + 5 = 15)
$user->refresh();
$this->assertEquals(15, $user->exp_num);
// 紧接着在冷却时间内(200秒内)模拟第二次并发执行
$this->artisan('chatroom:auto-save-exp')->assertSuccessful();
// 验证因为有 Redis 冷却锁,第二次执行被拦截,经验仍为 15,并未再次增加
$user->refresh();
$this->assertEquals(15, $user->exp_num);
}
}