191 lines
6.9 KiB
PHP
191 lines
6.9 KiB
PHP
<?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);
|
||
}
|
||
}
|