聊天室管理权限统一为职务权限
This commit is contained in:
@@ -370,6 +370,7 @@ class ChatControllerTest extends TestCase
|
||||
$user = $this->createUserWithPositionPermissions([
|
||||
PositionPermissionRegistry::USER_WARN,
|
||||
PositionPermissionRegistry::USER_MUTE,
|
||||
PositionPermissionRegistry::USER_BAN,
|
||||
]);
|
||||
|
||||
$response = $this->actingAs($user)->get(route('chat.room', $room->id));
|
||||
@@ -377,7 +378,9 @@ class ChatControllerTest extends TestCase
|
||||
$response->assertOk();
|
||||
$response->assertSee('⚠️ 警告', false);
|
||||
$response->assertSee('🔇 禁言', false);
|
||||
$response->assertSee('⛔ 封号', false);
|
||||
$response->assertDontSee('🚫 踢出', false);
|
||||
$response->assertDontSee('🌐 封IP', false);
|
||||
$response->assertDontSee('🧊 冻结', false);
|
||||
}
|
||||
|
||||
@@ -395,6 +398,8 @@ class ChatControllerTest extends TestCase
|
||||
$response->assertDontSee('⚠️ 警告', false);
|
||||
$response->assertDontSee('🚫 踢出', false);
|
||||
$response->assertDontSee('🔇 禁言', false);
|
||||
$response->assertDontSee('⛔ 封号', false);
|
||||
$response->assertDontSee('🌐 封IP', false);
|
||||
$response->assertDontSee('🧊 冻结', false);
|
||||
}
|
||||
|
||||
|
||||
@@ -409,14 +409,14 @@ class AdminCommandControllerTest extends TestCase
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试冻结操作会给目标用户写入带 toast 的私聊提示。
|
||||
* 测试封号操作会给目标用户写入带 toast 的私聊提示。
|
||||
*/
|
||||
public function test_freeze_message_contains_toast_notification_for_receiver(): void
|
||||
public function test_ban_message_contains_toast_notification_for_receiver(): void
|
||||
{
|
||||
Queue::fake();
|
||||
[$admin, $target, $room] = $this->createAdminCommandActors();
|
||||
|
||||
$response = $this->actingAs($admin)->postJson(route('command.freeze'), [
|
||||
$response = $this->actingAs($admin)->postJson(route('command.ban'), [
|
||||
'username' => $target->username,
|
||||
'room_id' => $room->id,
|
||||
'reason' => '严重违规',
|
||||
@@ -424,17 +424,49 @@ class AdminCommandControllerTest extends TestCase
|
||||
|
||||
$response->assertOk()->assertJson([
|
||||
'status' => 'success',
|
||||
'message' => "已冻结 {$target->username} 的账号",
|
||||
'message' => "已封禁 {$target->username} 的账号",
|
||||
]);
|
||||
|
||||
$this->assertSame(-1, (int) $target->fresh()->user_level);
|
||||
|
||||
$privateMessage = $this->findPrivateSystemMessage($room->id, $target->username, '已冻结你的账号');
|
||||
$privateMessage = $this->findPrivateSystemMessage($room->id, $target->username, '已封禁你的账号');
|
||||
|
||||
$this->assertNotNull($privateMessage);
|
||||
$this->assertSame('🧊 账号已冻结', $privateMessage['toast_notification']['title'] ?? null);
|
||||
$this->assertSame('🧊', $privateMessage['toast_notification']['icon'] ?? null);
|
||||
$this->assertSame('#3b82f6', $privateMessage['toast_notification']['color'] ?? null);
|
||||
$this->assertSame('⛔ 账号已封禁', $privateMessage['toast_notification']['title'] ?? null);
|
||||
$this->assertSame('⛔', $privateMessage['toast_notification']['icon'] ?? null);
|
||||
$this->assertSame('#991b1b', $privateMessage['toast_notification']['color'] ?? null);
|
||||
Queue::assertPushed(SaveMessageJob::class, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试封IP操作会给目标用户写入带 toast 的私聊提示。
|
||||
*/
|
||||
public function test_banip_message_contains_toast_notification_for_receiver(): void
|
||||
{
|
||||
Queue::fake();
|
||||
[$admin, $target, $room] = $this->createAdminCommandActors();
|
||||
$target->forceFill(['last_ip' => '192.168.1.100'])->save();
|
||||
|
||||
$response = $this->actingAs($admin)->postJson(route('command.banip'), [
|
||||
'username' => $target->username,
|
||||
'room_id' => $room->id,
|
||||
'reason' => '恶意刷屏',
|
||||
]);
|
||||
|
||||
$response->assertOk()->assertJson([
|
||||
'status' => 'success',
|
||||
'message' => "已封禁 {$target->username} 的 IP 与账号",
|
||||
]);
|
||||
|
||||
$this->assertSame(-1, (int) $target->fresh()->user_level);
|
||||
$this->assertSame(1, Redis::sismember('banned_ips', '192.168.1.100'));
|
||||
|
||||
$privateMessage = $this->findPrivateSystemMessage($room->id, $target->username, '已封禁你的 IP 并冻结账号');
|
||||
|
||||
$this->assertNotNull($privateMessage);
|
||||
$this->assertSame('🌐 IP 已封禁', $privateMessage['toast_notification']['title'] ?? null);
|
||||
$this->assertSame('🌐', $privateMessage['toast_notification']['icon'] ?? null);
|
||||
$this->assertSame('#7c2d12', $privateMessage['toast_notification']['color'] ?? null);
|
||||
Queue::assertPushed(SaveMessageJob::class, 2);
|
||||
}
|
||||
|
||||
@@ -466,6 +498,64 @@ class AdminCommandControllerTest extends TestCase
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试缺少封号权限的职务用户会被拒绝。
|
||||
*/
|
||||
public function test_position_user_without_ban_permission_cannot_ban_target(): void
|
||||
{
|
||||
$admin = $this->createPositionedManager([
|
||||
PositionPermissionRegistry::USER_WARN,
|
||||
]);
|
||||
$target = User::factory()->create([
|
||||
'user_level' => 1,
|
||||
]);
|
||||
$room = Room::create([
|
||||
'room_name' => '无权封号房',
|
||||
]);
|
||||
$this->joinRoom($admin, $room);
|
||||
$this->joinRoom($target, $room);
|
||||
|
||||
$response = $this->actingAs($admin)->postJson(route('command.ban'), [
|
||||
'username' => $target->username,
|
||||
'room_id' => $room->id,
|
||||
'reason' => '测试',
|
||||
]);
|
||||
|
||||
$response->assertStatus(403)->assertJson([
|
||||
'status' => 'error',
|
||||
'message' => '当前职务无权封禁用户',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试缺少封IP权限的职务用户会被拒绝。
|
||||
*/
|
||||
public function test_position_user_without_banip_permission_cannot_ban_target_ip(): void
|
||||
{
|
||||
$admin = $this->createPositionedManager([
|
||||
PositionPermissionRegistry::USER_WARN,
|
||||
]);
|
||||
$target = User::factory()->create([
|
||||
'user_level' => 1,
|
||||
]);
|
||||
$room = Room::create([
|
||||
'room_name' => '无权封IP房',
|
||||
]);
|
||||
$this->joinRoom($admin, $room);
|
||||
$this->joinRoom($target, $room);
|
||||
|
||||
$response = $this->actingAs($admin)->postJson(route('command.banip'), [
|
||||
'username' => $target->username,
|
||||
'room_id' => $room->id,
|
||||
'reason' => '测试',
|
||||
]);
|
||||
|
||||
$response->assertStatus(403)->assertJson([
|
||||
'status' => 'error',
|
||||
'message' => '当前职务无权封禁 IP 用户',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试不能处理部门位阶更高的目标用户。
|
||||
*/
|
||||
@@ -645,7 +735,8 @@ class AdminCommandControllerTest extends TestCase
|
||||
PositionPermissionRegistry::USER_WARN,
|
||||
PositionPermissionRegistry::USER_MUTE,
|
||||
PositionPermissionRegistry::USER_KICK,
|
||||
PositionPermissionRegistry::USER_FREEZE,
|
||||
PositionPermissionRegistry::USER_BAN,
|
||||
PositionPermissionRegistry::USER_BANIP,
|
||||
]);
|
||||
$admin->load('activePosition.position.department');
|
||||
$target = User::factory()->create([
|
||||
|
||||
@@ -47,6 +47,8 @@ class AdminPositionPermissionTest extends TestCase
|
||||
'permissions' => [
|
||||
PositionPermissionRegistry::ROOM_ANNOUNCEMENT,
|
||||
PositionPermissionRegistry::ROOM_PUBLIC_BROADCAST,
|
||||
PositionPermissionRegistry::USER_BAN,
|
||||
PositionPermissionRegistry::USER_BANIP,
|
||||
],
|
||||
]);
|
||||
|
||||
@@ -59,6 +61,8 @@ class AdminPositionPermissionTest extends TestCase
|
||||
$this->assertSame([
|
||||
PositionPermissionRegistry::ROOM_ANNOUNCEMENT,
|
||||
PositionPermissionRegistry::ROOM_PUBLIC_BROADCAST,
|
||||
PositionPermissionRegistry::USER_BAN,
|
||||
PositionPermissionRegistry::USER_BANIP,
|
||||
], $position->permissions);
|
||||
}
|
||||
|
||||
@@ -195,7 +199,9 @@ class AdminPositionPermissionTest extends TestCase
|
||||
$response->assertSee('默认礼包总量');
|
||||
$response->assertSee('默认礼包份数');
|
||||
$response->assertSee('警告用户');
|
||||
$response->assertSee('冻结用户');
|
||||
$response->assertSee('封号用户');
|
||||
$response->assertSee('封IP');
|
||||
$response->assertSee('聊天室管理动作已统一按职务权限控制');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -39,6 +39,13 @@ class AdminSystemControllerTest extends TestCase
|
||||
$response->assertDontSee('wechat_bot_config');
|
||||
$response->assertDontSee('chatbot_max_gold');
|
||||
$response->assertDontSee('levelexp');
|
||||
$response->assertDontSee('level_warn');
|
||||
$response->assertDontSee('level_mute');
|
||||
$response->assertDontSee('level_kick');
|
||||
$response->assertDontSee('level_announcement');
|
||||
$response->assertDontSee('level_ban');
|
||||
$response->assertDontSee('level_banip');
|
||||
$response->assertDontSee('level_freeze');
|
||||
$response->assertSee('maxlevel');
|
||||
$response->assertSee('superlevel');
|
||||
}
|
||||
@@ -55,6 +62,13 @@ class AdminSystemControllerTest extends TestCase
|
||||
'sys_name' => '新版聊天室',
|
||||
'sys_notice' => '新的公共公告',
|
||||
'levelexp' => '20,80,180',
|
||||
'level_warn' => '40',
|
||||
'level_mute' => '50',
|
||||
'level_kick' => '60',
|
||||
'level_announcement' => '65',
|
||||
'level_ban' => '80',
|
||||
'level_banip' => '90',
|
||||
'level_freeze' => '95',
|
||||
'maxlevel' => '88',
|
||||
'superlevel' => '666',
|
||||
'smtp_host' => 'attacker.smtp.example',
|
||||
@@ -79,6 +93,34 @@ class AdminSystemControllerTest extends TestCase
|
||||
'alias' => 'levelexp',
|
||||
'body' => '10,50,150',
|
||||
]);
|
||||
$this->assertDatabaseHas('sysparam', [
|
||||
'alias' => 'level_warn',
|
||||
'body' => '5',
|
||||
]);
|
||||
$this->assertDatabaseHas('sysparam', [
|
||||
'alias' => 'level_mute',
|
||||
'body' => '50',
|
||||
]);
|
||||
$this->assertDatabaseHas('sysparam', [
|
||||
'alias' => 'level_kick',
|
||||
'body' => '60',
|
||||
]);
|
||||
$this->assertDatabaseHas('sysparam', [
|
||||
'alias' => 'level_announcement',
|
||||
'body' => '60',
|
||||
]);
|
||||
$this->assertDatabaseHas('sysparam', [
|
||||
'alias' => 'level_ban',
|
||||
'body' => '80',
|
||||
]);
|
||||
$this->assertDatabaseHas('sysparam', [
|
||||
'alias' => 'level_banip',
|
||||
'body' => '90',
|
||||
]);
|
||||
$this->assertDatabaseHas('sysparam', [
|
||||
'alias' => 'level_freeze',
|
||||
'body' => '14',
|
||||
]);
|
||||
$this->assertDatabaseHas('sysparam', [
|
||||
'alias' => 'maxlevel',
|
||||
'body' => '88',
|
||||
@@ -147,6 +189,13 @@ class AdminSystemControllerTest extends TestCase
|
||||
'sys_name' => '原始聊天室',
|
||||
'sys_notice' => '原始公告',
|
||||
'levelexp' => '10,50,150',
|
||||
'level_warn' => '5',
|
||||
'level_mute' => '50',
|
||||
'level_kick' => '60',
|
||||
'level_announcement' => '60',
|
||||
'level_ban' => '80',
|
||||
'level_banip' => '90',
|
||||
'level_freeze' => '14',
|
||||
'maxlevel' => '99',
|
||||
'superlevel' => '100',
|
||||
'smtp_host' => 'owner.smtp.example',
|
||||
|
||||
@@ -8,11 +8,15 @@
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Enums\CurrencySource;
|
||||
use App\Models\Department;
|
||||
use App\Models\Position;
|
||||
use App\Models\Room;
|
||||
use App\Models\Sysparam;
|
||||
use App\Models\User;
|
||||
use App\Models\UserCurrencyLog;
|
||||
use App\Models\UserPosition;
|
||||
use App\Services\ChatUserPresenceService;
|
||||
use App\Support\PositionPermissionRegistry;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
@@ -39,10 +43,6 @@ class UserControllerTest extends TestCase
|
||||
Redis::flushall();
|
||||
|
||||
Sysparam::updateOrCreate(['alias' => 'superlevel'], ['body' => '100']);
|
||||
Sysparam::updateOrCreate(['alias' => 'level_kick'], ['body' => '15']);
|
||||
Sysparam::updateOrCreate(['alias' => 'level_mute'], ['body' => '15']);
|
||||
Sysparam::updateOrCreate(['alias' => 'level_ban'], ['body' => '15']);
|
||||
Sysparam::updateOrCreate(['alias' => 'level_banip'], ['body' => '15']);
|
||||
Sysparam::updateOrCreate(['alias' => 'smtp_enabled'], ['body' => '1']); // Allow email changing in tests
|
||||
}
|
||||
|
||||
@@ -294,6 +294,51 @@ class UserControllerTest extends TestCase
|
||||
->assertJsonPath('data.bank_jjb_can_reveal', false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试拥有封IP职务权限的用户可以查看名片中的管理员网络信息。
|
||||
*/
|
||||
public function test_position_user_with_banip_permission_can_view_admin_network_info(): void
|
||||
{
|
||||
$viewer = $this->createUserWithPositionPermissions([
|
||||
PositionPermissionRegistry::USER_BANIP,
|
||||
]);
|
||||
$target = User::factory()->create([
|
||||
'username' => 'nettarget',
|
||||
'first_ip' => '10.0.0.1',
|
||||
'previous_ip' => '10.0.0.2',
|
||||
'last_ip' => '10.0.0.3',
|
||||
]);
|
||||
|
||||
$response = $this->actingAs($viewer)->getJson("/user/{$target->username}");
|
||||
|
||||
$response->assertOk()
|
||||
->assertJsonPath('data.first_ip', '10.0.0.1')
|
||||
->assertJsonPath('data.last_ip', '10.0.0.2')
|
||||
->assertJsonPath('data.login_ip', '10.0.0.3');
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试普通用户查看别人名片时不会拿到管理员网络信息字段。
|
||||
*/
|
||||
public function test_user_without_banip_permission_cannot_view_admin_network_info(): void
|
||||
{
|
||||
$viewer = User::factory()->create();
|
||||
$target = User::factory()->create([
|
||||
'username' => 'hidden-net',
|
||||
'first_ip' => '10.0.1.1',
|
||||
'previous_ip' => '10.0.1.2',
|
||||
'last_ip' => '10.0.1.3',
|
||||
]);
|
||||
|
||||
$response = $this->actingAs($viewer)->getJson("/user/{$target->username}");
|
||||
|
||||
$response->assertOk()
|
||||
->assertJsonMissingPath('data.first_ip')
|
||||
->assertJsonMissingPath('data.last_ip')
|
||||
->assertJsonMissingPath('data.login_ip')
|
||||
->assertJsonMissingPath('data.location');
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试不改邮箱时可以正常更新个人资料。
|
||||
*/
|
||||
@@ -510,126 +555,6 @@ class UserControllerTest extends TestCase
|
||||
$this->assertTrue(Hash::check('newpassword123', $user->password));
|
||||
}
|
||||
|
||||
public function test_admin_can_kick_user()
|
||||
{
|
||||
$admin = User::factory()->create(['username' => 'admin', 'user_level' => 20]);
|
||||
$target = User::factory()->create(['username' => 'target', 'user_level' => 1]);
|
||||
$room = Room::create(['id' => 1, 'room_name' => 'Test Room', 'room_owner' => 'someone']);
|
||||
|
||||
$this->actingAs($admin);
|
||||
|
||||
$response = $this->postJson("/user/{$target->username}/kick", [
|
||||
'room_id' => $room->id,
|
||||
]);
|
||||
|
||||
$response->assertStatus(200)
|
||||
->assertJsonPath('status', 'success');
|
||||
}
|
||||
|
||||
public function test_low_level_user_cannot_kick()
|
||||
{
|
||||
$user = User::factory()->create(['username' => 'user', 'user_level' => 1]);
|
||||
$target = User::factory()->create(['username' => 'target', 'user_level' => 1]);
|
||||
$room = Room::create(['id' => 1, 'room_name' => 'Test Room', 'room_owner' => 'someone']);
|
||||
|
||||
$this->actingAs($user);
|
||||
|
||||
$response = $this->postJson("/user/{$target->username}/kick", [
|
||||
'room_id' => $room->id,
|
||||
]);
|
||||
|
||||
$response->assertStatus(403);
|
||||
}
|
||||
|
||||
public function test_room_master_can_kick()
|
||||
{
|
||||
$user = User::factory()->create(['username' => 'user', 'user_level' => 2]);
|
||||
$target = User::factory()->create(['username' => 'target', 'user_level' => 1]);
|
||||
$room = Room::create(['id' => 1, 'room_name' => 'Test Room', 'room_owner' => 'user']); // Master is 'user'
|
||||
|
||||
$this->actingAs($user);
|
||||
|
||||
$response = $this->postJson("/user/{$target->username}/kick", [
|
||||
'room_id' => $room->id,
|
||||
]);
|
||||
|
||||
if ($response->status() !== 200) {
|
||||
dump($response->json());
|
||||
}
|
||||
$response->assertStatus(200);
|
||||
}
|
||||
|
||||
public function test_cannot_kick_higher_level()
|
||||
{
|
||||
$admin = User::factory()->create(['username' => 'admin', 'user_level' => 20]);
|
||||
$superadmin = User::factory()->create(['username' => 'superadmin', 'user_level' => 100]);
|
||||
$room = Room::create(['id' => 1, 'room_name' => 'Test Room', 'room_owner' => 'someone']);
|
||||
|
||||
$this->actingAs($admin);
|
||||
|
||||
$response = $this->postJson("/user/{$superadmin->username}/kick", [
|
||||
'room_id' => $room->id,
|
||||
]);
|
||||
|
||||
$response->assertStatus(403);
|
||||
}
|
||||
|
||||
public function test_admin_can_mute_user()
|
||||
{
|
||||
$admin = User::factory()->create(['username' => 'admin', 'user_level' => 20]);
|
||||
$target = User::factory()->create(['username' => 'target', 'user_level' => 1]);
|
||||
$room = Room::create(['id' => 1, 'room_name' => 'Test Room', 'room_owner' => 'someone']);
|
||||
|
||||
Redis::shouldReceive('setex')->once();
|
||||
|
||||
$this->actingAs($admin);
|
||||
|
||||
$response = $this->postJson("/user/{$target->username}/mute", [
|
||||
'room_id' => $room->id,
|
||||
'duration' => 10,
|
||||
]);
|
||||
|
||||
$response->assertStatus(200);
|
||||
}
|
||||
|
||||
public function test_admin_can_ban_user()
|
||||
{
|
||||
$admin = User::factory()->create(['username' => 'admin', 'user_level' => 20]);
|
||||
$target = User::factory()->create(['username' => 'target', 'user_level' => 1]);
|
||||
$room = Room::create(['id' => 1, 'room_name' => 'Test Room', 'room_owner' => 'someone']);
|
||||
|
||||
$this->actingAs($admin);
|
||||
|
||||
$response = $this->postJson("/user/{$target->username}/ban", [
|
||||
'room_id' => $room->id,
|
||||
]);
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$target->refresh();
|
||||
$this->assertEquals(-1, $target->user_level);
|
||||
}
|
||||
|
||||
public function test_admin_can_ban_ip()
|
||||
{
|
||||
$admin = User::factory()->create(['username' => 'admin', 'user_level' => 20]);
|
||||
$target = User::factory()->create(['username' => 'target', 'user_level' => 1, 'last_ip' => '192.168.1.100']);
|
||||
$room = Room::create(['id' => 1, 'room_name' => 'Test Room', 'room_owner' => 'someone']);
|
||||
|
||||
Redis::shouldReceive('sadd')->with('banned_ips', '192.168.1.100')->once();
|
||||
|
||||
$this->actingAs($admin);
|
||||
|
||||
$response = $this->postJson("/user/{$target->username}/banip", [
|
||||
'room_id' => $room->id,
|
||||
]);
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$target->refresh();
|
||||
$this->assertEquals(-1, $target->user_level);
|
||||
}
|
||||
|
||||
/**
|
||||
* 让指定用户先进入聊天室,满足“仅在线用户可设置状态”的前置条件。
|
||||
*/
|
||||
@@ -646,4 +571,43 @@ class UserControllerTest extends TestCase
|
||||
|
||||
return $room;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建带指定职务权限的测试用户。
|
||||
*
|
||||
* @param list<string> $permissions
|
||||
*/
|
||||
private function createUserWithPositionPermissions(array $permissions): User
|
||||
{
|
||||
$user = User::factory()->create([
|
||||
'user_level' => 88,
|
||||
]);
|
||||
|
||||
$department = Department::create([
|
||||
'name' => '资料权限部'.$user->id,
|
||||
'rank' => 88,
|
||||
'color' => '#4f46e5',
|
||||
'sort_order' => 1,
|
||||
'description' => '资料权限测试部门',
|
||||
]);
|
||||
|
||||
$position = Position::create([
|
||||
'department_id' => $department->id,
|
||||
'name' => '资料权限职务'.$user->id,
|
||||
'icon' => '🛡️',
|
||||
'rank' => 88,
|
||||
'level' => 88,
|
||||
'sort_order' => 1,
|
||||
'permissions' => $permissions,
|
||||
]);
|
||||
|
||||
UserPosition::create([
|
||||
'user_id' => $user->id,
|
||||
'position_id' => $position->id,
|
||||
'appointed_at' => now(),
|
||||
'is_active' => true,
|
||||
]);
|
||||
|
||||
return $user->fresh();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user