加固房间准入与消息广播边界

This commit is contained in:
2026-04-19 14:42:52 +08:00
parent 5ce83a769d
commit ba6406ed68
6 changed files with 304 additions and 14 deletions
+152 -5
View File
@@ -1,12 +1,22 @@
<?php
/**
* 文件功能:聊天室控制器功能测试
*
* 覆盖进房、发言、图片消息、权限限制与广播范围等关键聊天流程。
*/
namespace Tests\Feature;
use App\Events\MessageSent;
use App\Models\Room;
use App\Models\User;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Http\Request;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Broadcast;
use Illuminate\Support\Facades\Redis;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\URL;
@@ -14,8 +24,7 @@ use Symfony\Component\HttpKernel\Exception\HttpException;
use Tests\TestCase;
/**
* 聊天室控制器功能测试
* 覆盖进房、发言、图片消息与退房等关键聊天流程。
* 类功能:验证聊天室核心控制器的关键行为。
*/
class ChatControllerTest extends TestCase
{
@@ -33,7 +42,7 @@ class ChatControllerTest extends TestCase
/**
* 测试用户可以正常进入聊天室页面。
*/
public function test_can_view_room()
public function test_can_view_room(): void
{
$room = Room::create(['room_name' => 'testroom']);
$user = User::factory()->create();
@@ -47,6 +56,70 @@ class ChatControllerTest extends TestCase
$this->assertEquals(1, Redis::hexists("room:{$room->id}:users", $user->username));
}
/**
* 测试关闭房间会拒绝普通用户直接进入。
*/
public function test_cannot_view_closed_room_without_bypass_permission(): void
{
$room = Room::create([
'room_name' => 'closed',
'door_open' => false,
]);
$user = User::factory()->create([
'user_level' => 1,
]);
$response = $this->actingAs($user)->get(route('chat.room', $room->id));
$response->assertForbidden();
$this->assertSame(0, Redis::hexists("room:{$room->id}:users", $user->username));
}
/**
* 测试等级不足的用户不能进入受限房间。
*/
public function test_cannot_view_room_when_level_is_below_permit_level(): void
{
$room = Room::create([
'room_name' => 'viprm',
'permit_level' => 10,
'door_open' => true,
]);
$user = User::factory()->create([
'user_level' => 5,
]);
$response = $this->actingAs($user)->get(route('chat.room', $room->id));
$response->assertForbidden();
$this->assertSame(0, Redis::hexists("room:{$room->id}:users", $user->username));
}
/**
* 测试只有已成功进入房间的用户才可通过 Presence 频道鉴权。
*/
public function test_room_presence_channel_requires_room_access_and_join_state(): void
{
$room = Room::create([
'room_name' => 'guard',
'door_open' => true,
]);
$user = User::factory()->create();
$channelCallback = Broadcast::driver()->getChannels()->get('room.{roomId}');
$this->assertIsCallable($channelCallback);
$this->assertFalse($channelCallback($user, (string) $room->id));
$this->actingAs($user)->get(route('chat.room', $room->id));
$authorizedPayload = $channelCallback($user, (string) $room->id);
$this->assertIsArray($authorizedPayload);
$this->assertSame($user->id, $authorizedPayload['id'] ?? null);
Redis::del("room:{$room->id}:users");
$this->assertFalse($channelCallback($user, (string) $room->id));
}
/**
* 测试主干默认聊天室页面不会渲染虚拟形象挂载点和配置。
*/
@@ -87,7 +160,7 @@ class ChatControllerTest extends TestCase
/**
* 测试用户可以发送普通文本消息。
*/
public function test_can_send_message()
public function test_can_send_message(): void
{
$room = Room::create(['room_name' => 'test_send']);
$user = User::factory()->create();
@@ -100,7 +173,7 @@ class ChatControllerTest extends TestCase
'content' => '测试消息',
'is_secret' => false,
'font_color' => '#000000',
'action' => 'say',
'action' => '微笑',
]);
$response->assertStatus(200);
@@ -121,6 +194,80 @@ class ChatControllerTest extends TestCase
$this->assertTrue($found, 'Message not found in Redis');
}
/**
* 测试发送接口会拦截不在白名单内的危险动作值。
*/
public function test_send_message_rejects_invalid_action_value(): void
{
$room = Room::create(['room_name' => 'badact']);
$user = User::factory()->create();
$this->actingAs($user)->get(route('chat.room', $room->id));
$response = $this->actingAs($user)->postJson(route('chat.send', $room->id), [
'to_user' => '大家',
'content' => '危险动作测试',
'is_secret' => false,
'font_color' => '#000000',
'action' => '\"></span><img src=x onerror=alert(1)>',
]);
$response->assertStatus(422);
$response->assertJsonValidationErrors('action');
}
/**
* 测试定向消息仅广播到发送方与接收方私有频道。
*/
public function test_targeted_message_event_uses_private_user_channels(): void
{
$sender = User::factory()->create(['username' => 'sender-user']);
$receiver = User::factory()->create(['username' => 'receiver-user']);
$event = new MessageSent(1, [
'room_id' => 1,
'from_user' => $sender->username,
'to_user' => $receiver->username,
'content' => '只给你看',
'is_secret' => true,
'font_color' => '#000000',
'action' => '',
'sent_at' => now()->toDateTimeString(),
]);
$channels = $event->broadcastOn();
$this->assertCount(2, $channels);
$this->assertContainsOnlyInstancesOf(PrivateChannel::class, $channels);
$this->assertSame([
'private-user.'.$sender->id,
'private-user.'.$receiver->id,
], array_map(fn (PrivateChannel $channel) => $channel->name, $channels));
}
/**
* 测试公共消息仍广播到房间 Presence 频道。
*/
public function test_public_message_event_still_uses_room_presence_channel(): void
{
$event = new MessageSent(3, [
'room_id' => 3,
'from_user' => 'tester',
'to_user' => '大家',
'content' => '公开消息',
'is_secret' => false,
'font_color' => '#000000',
'action' => '',
'sent_at' => now()->toDateTimeString(),
]);
$channels = $event->broadcastOn();
$this->assertCount(1, $channels);
$this->assertInstanceOf(PresenceChannel::class, $channels[0]);
$this->assertSame('presence-room.3', $channels[0]->name);
}
/**
* 测试文本内容为字符串 0 时仍可正常发送。
*/