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']); });