create([ 'username' => $username, 'name' => 'Tester', 'email' => null, 'password' => Hash::make('secret-strong'), 'status' => 0, ]); $role = AdminRole::query()->create([ 'slug' => 'role_'.$username, 'name' => 'Role '.$username, ]); $codes = []; foreach ($permissionSlugs as $slug) { $codes = array_merge($codes, AdminPermissionBridge::menuActionCodesForLegacy($slug)); } $codes = array_values(array_unique($codes)); $ids = DB::table('admin_menu_actions') ->whereIn('permission_code', $codes) ->where('status', 1) ->pluck('id') ->all(); foreach ($ids as $mid) { DB::table('admin_role_menu_actions')->insert([ 'role_id' => $role->id, 'menu_action_id' => (int) $mid, ]); } $siteId = AdminUser::defaultAdminSiteId(); $admin->roles()->sync([ (int) $role->id => [ 'site_id' => $siteId, 'granted_at' => now(), ], ]); return $admin->createToken('test', ['*'], now()->addDay())->plainTextToken; } test('admin user permission apis require rbac permission', function (): void { $token = makeAdminWithPermissions('rbac_viewer', ['prd.report.player']); $this->withHeader('Authorization', 'Bearer '.$token) ->getJson('/api/v1/admin/admin-users') ->assertForbidden() ->assertJsonPath('code', ErrorCode::AdminForbidden->value); }); test('admin can list users and sync direct permissions', function (): void { $token = makeAdminWithPermissions('rbac_manager', ['prd.admin_user.manage']); $target = AdminUser::query()->create([ 'username' => 'target_user', 'name' => 'Target User', 'email' => 'target@example.com', 'password' => Hash::make('secret-strong'), 'status' => 0, ]); $targetRole = AdminRole::query()->create(['slug' => 'target_role', 'name' => 'Target Role']); $drawCodes = AdminPermissionBridge::menuActionCodesForLegacy('prd.draw_result.view'); $drawIds = DB::table('admin_menu_actions') ->whereIn('permission_code', $drawCodes) ->where('status', 1) ->pluck('id') ->all(); foreach ($drawIds as $mid) { DB::table('admin_role_menu_actions')->insert([ 'role_id' => $targetRole->id, 'menu_action_id' => (int) $mid, ]); } $siteId = AdminUser::defaultAdminSiteId(); $target->roles()->sync([ (int) $targetRole->id => [ 'site_id' => $siteId, 'granted_at' => now(), ], ]); $this->withHeader('Authorization', 'Bearer '.$token) ->getJson('/api/v1/admin/admin-user-permission-catalog') ->assertOk() ->assertJsonPath('code', ErrorCode::Success->value) ->assertJsonFragment(['slug' => 'prd.admin_user.manage']); $this->withHeader('Authorization', 'Bearer '.$token) ->getJson('/api/v1/admin/admin-users?keyword=target') ->assertOk() ->assertJsonPath('code', ErrorCode::Success->value) ->assertJsonPath('data.items.0.username', 'target_user') ->assertJsonPath('data.items.0.roles.0', 'target_role'); $this->withHeader('Authorization', 'Bearer '.$token) ->putJson('/api/v1/admin/admin-users/'.$target->id.'/permissions', [ 'permission_slugs' => ['prd.report.player'], ]) ->assertOk() ->assertJsonPath('code', ErrorCode::Success->value) ->assertJsonFragment(['prd.report.player']); expect($target->fresh()->directLegacyPermissionSlugs())->toContain('prd.report.player'); $list = $this->withHeader('Authorization', 'Bearer '.$token) ->getJson('/api/v1/admin/admin-users?keyword=target') ->assertOk() ->json('data.items.0.effective_permissions'); expect($list)->toContain('prd.draw_result.view'); expect($list)->toContain('prd.report.player'); }); test('admin can sync user roles for default site', function (): void { $token = makeAdminWithPermissions('rbac_role_editor', ['prd.admin_user.manage']); $r1 = AdminRole::query()->create(['slug' => 'role_sync_a', 'name' => 'Role A']); $r2 = AdminRole::query()->create(['slug' => 'role_sync_b', 'name' => 'Role B']); $target = AdminUser::query()->create([ 'username' => 'role_target', 'name' => 'Role Target', 'email' => null, 'password' => Hash::make('secret-strong'), 'status' => 0, ]); $this->withHeader('Authorization', 'Bearer '.$token) ->putJson('/api/v1/admin/admin-users/'.$target->id.'/roles', [ 'role_slugs' => ['role_sync_b', 'role_sync_a'], ]) ->assertOk() ->assertJsonPath('code', ErrorCode::Success->value); $slugs = $target->fresh()->adminRoleSlugs(); sort($slugs); expect($slugs)->toBe(['role_sync_a', 'role_sync_b']); }); test('admin can create update and delete users with crud rules', function (): void { $token = makeAdminWithPermissions('crud_actor', ['prd.admin_user.manage']); $crudRole = AdminRole::query()->create(['slug' => 'crud_new_user_role', 'name' => 'Crud Role']); $this->withHeader('Authorization', 'Bearer '.$token) ->postJson('/api/v1/admin/admin-users', [ 'username' => 'NewUser_XX', 'nickname' => '新用户', 'email' => 'newuser@example.com', 'password' => 'secret-long', 'status' => 0, 'role_slugs' => ['crud_new_user_role'], ]) ->assertOk() ->assertJsonPath('code', ErrorCode::Success->value) ->assertJsonPath('data.username', 'newuser_xx') ->assertJsonPath('data.roles.0', 'crud_new_user_role'); $created = AdminUser::query()->where('username', 'newuser_xx')->firstOrFail(); expect($created->adminRoleSlugs())->toContain('crud_new_user_role'); $this->withHeader('Authorization', 'Bearer '.$token) ->postJson('/api/v1/admin/admin-users', [ 'username' => 'newuser_xx', 'nickname' => 'dup', 'email' => null, 'password' => 'secret-long', 'role_slugs' => [$crudRole->slug], ]) ->assertStatus(422) ->assertJsonPath('code', ErrorCode::ValidationFailed->value); $target = AdminUser::query()->where('username', 'newuser_xx')->firstOrFail(); $this->withHeader('Authorization', 'Bearer '.$token) ->putJson('/api/v1/admin/admin-users/'.$target->id, [ 'nickname' => '已改名', 'email' => null, 'password' => 'new-secret-9', ]) ->assertOk() ->assertJsonPath('data.nickname', '已改名'); expect(Hash::check('new-secret-9', $target->fresh()->password))->toBeTrue(); $victim = AdminUser::query()->create([ 'username' => 'to_delete', 'name' => 'Delete Me', 'email' => null, 'password' => Hash::make('secret-strong'), 'status' => 0, ]); $this->withHeader('Authorization', 'Bearer '.$token) ->deleteJson('/api/v1/admin/admin-users/'.$victim->id) ->assertOk() ->assertJsonPath('data.deleted', true); expect(AdminUser::query()->whereKey($victim->id)->exists())->toBeFalse(); }); test('admin user create requires at least one role slug', function (): void { $token = makeAdminWithPermissions('create_need_roles', ['prd.admin_user.manage']); AdminRole::query()->create(['slug' => 'role_for_create_gate', 'name' => 'Gate Role']); $this->withHeader('Authorization', 'Bearer '.$token) ->postJson('/api/v1/admin/admin-users', [ 'username' => 'no_roles_user', 'nickname' => 'NR', 'email' => null, 'password' => 'secret-long', 'role_slugs' => [], ]) ->assertStatus(422) ->assertJsonPath('code', ErrorCode::ValidationFailed->value); }); test('admin cannot delete self', function (): void { $token = makeAdminWithPermissions('self_guard', ['prd.admin_user.manage']); $me = AdminUser::query()->where('username', 'self_guard')->firstOrFail(); $this->withHeader('Authorization', 'Bearer '.$token) ->deleteJson('/api/v1/admin/admin-users/'.$me->id) ->assertStatus(422) ->assertJsonPath('code', ErrorCode::ValidationFailed->value) ->assertJsonPath('msg', '不能删除当前登录账号'); }); test('admin cannot delete the last super admin', function (): void { $token = makeAdminWithPermissions('super_deleter', ['prd.admin_user.manage']); $s1 = AdminUser::query()->create([ 'username' => 'super_one', 'name' => 'S1', 'email' => null, 'password' => Hash::make('secret-strong'), 'status' => 0, ]); grantSuperAdminRole($s1); $this->withHeader('Authorization', 'Bearer '.$token) ->deleteJson('/api/v1/admin/admin-users/'.$s1->id) ->assertStatus(422) ->assertJsonPath('msg', '不能删除最后一个超级管理员'); $s2 = AdminUser::query()->create([ 'username' => 'super_two', 'name' => 'S2', 'email' => null, 'password' => Hash::make('secret-strong'), 'status' => 0, ]); grantSuperAdminRole($s2); $this->withHeader('Authorization', 'Bearer '.$token) ->deleteJson('/api/v1/admin/admin-users/'.$s1->id) ->assertOk(); $this->withHeader('Authorization', 'Bearer '.$token) ->deleteJson('/api/v1/admin/admin-users/'.$s2->id) ->assertStatus(422) ->assertJsonPath('msg', '不能删除最后一个超级管理员'); });