- rename the users.php submenu to 举报&禁封申诉 and the page heading to 用户禁封申诉及用户举报管理 - merge report tickets and ban appeals into one Zibll-style queue: type filter buttons (全部/禁封申诉/用户举报), status subsubsub (全部/待处理/已批准/已拒绝), search box, sortable widefat table with the same column set (状态/类型/提交用户/详情/提交时间/更新时间), inline process forms and 20-per-page pagination - report process form shows the reported target, restriction status and an enforcement action select; appeal form offers approve/reject radios with a message to the user - move restriction enforcement into ReportCenter::apply_action so REST and the native page share one implementation; add get_report_row() - drop the old three-tab rendering (reports/appeals tabs) and fix all notice/mail/SPA links to the merged page
159 lines
8.9 KiB
JavaScript
159 lines
8.9 KiB
JavaScript
const test = require('node:test')
|
|
const assert = require('node:assert/strict')
|
|
const fs = require('node:fs')
|
|
const path = require('node:path')
|
|
|
|
const root = path.resolve(__dirname, '..', '..')
|
|
const read = (file) => fs.readFileSync(path.join(root, file), 'utf8')
|
|
|
|
const userController = read('src/Admin/UserController.php')
|
|
const reportOps = read('src/Admin/UserControllerReportOperations.php')
|
|
const restrictionOps = read('src/Admin/UserControllerRestrictionOperations.php')
|
|
const restriction = read('src/User/Restriction.php')
|
|
const reportCenter = read('src/Messages/ReportCenter.php')
|
|
const usersMenus = read('src/Admin/Native/UsersMenus.php')
|
|
const mailNotifications = read('src/Settings/MailNotifications.php')
|
|
const home = read('src/User/Home.php')
|
|
const commentItem = read('inc/helpers/comments/item.php')
|
|
const usersBan = read('assets/admin/src/pages/users/UsersBan.vue')
|
|
const functions = read('functions.php')
|
|
const uninstall = read('uninstall.php')
|
|
|
|
test('report REST surface is canonical: submit + users/reports only', () => {
|
|
assert.match(userController, /'\/report\/submit'/)
|
|
assert.match(userController, /'\/users\/reports'/)
|
|
assert.match(userController, /'\/users\/reports\/\(\?P<id>\\d\+\)'/)
|
|
assert.match(userController, /'\/users\/reports\/\(\?P<id>\\d\+\)\/process'/)
|
|
for (const dead of ["'/report/list'", "'/report/update-status'", "'/report/counts'"]) {
|
|
assert.ok(!userController.includes(dead), `alias route should be removed: ${dead}`)
|
|
}
|
|
assert.ok(!reportOps.includes('public function update_report_status'), 'dead update_report_status method')
|
|
assert.ok(!reportOps.includes('public function get_report_counts'), 'dead get_report_counts method')
|
|
})
|
|
|
|
test('report center has no legacy ajax handlers or option migration', () => {
|
|
assert.ok(!reportCenter.includes('ajax_update_status'))
|
|
assert.ok(!reportCenter.includes('ajax_list'))
|
|
assert.ok(!reportCenter.includes('ajax_counts'))
|
|
assert.ok(!reportCenter.includes('migrate_from_option'))
|
|
assert.ok(!reportCenter.includes('maybe_migrate'))
|
|
assert.ok(!reportCenter.includes("get_option('aksrt_reports'"))
|
|
assert.ok(!functions.includes('ReportCenter::get_instance()->init()'), 'ReportCenter init() is gone')
|
|
assert.match(reportCenter, /public function has_pending_report/)
|
|
assert.match(reportOps, /has_pending_report\(\$reporter_id, \$target_type, \$target_id\)/)
|
|
})
|
|
|
|
test('report processing delegates actions to the shared ReportCenter entry', () => {
|
|
assert.match(reportOps, /ReportCenter::get_instance\(\)->apply_action\(\$target_type, \$target_id, \$action_taken, \$reason\)/)
|
|
assert.match(reportCenter, /public function apply_action\(/)
|
|
assert.match(reportCenter, /->restrict\(\s*\$target_id,\s*\$action_taken,/)
|
|
assert.match(reportCenter, /lift_user_restriction\(\$target_id\)/)
|
|
assert.match(reportCenter, /in_array\(\$action_taken, \\AKSRT\\User\\Restriction::TYPES, true\)/)
|
|
assert.ok(!reportCenter.includes('aksrt_user_ban_state'), 'dual-track ban state meta must not be written or read')
|
|
for (const deadAction of ["'value' => 'warned'", "'value' => 'muted'", "'value' => 'restricted'", "'value' => 'banned'"]) {
|
|
assert.ok(!reportOps.includes(deadAction), `action value must map to Restriction types: ${deadAction}`)
|
|
}
|
|
for (const action of ["'value' => 'warn'", "'value' => 'mute'", "'value' => 'suspend'", "'value' => 'ban'", "'value' => 'clear'"]) {
|
|
assert.ok(reportOps.includes(action), `missing action option: ${action}`)
|
|
}
|
|
assert.match(reportOps, /get_user_restriction_state/)
|
|
assert.match(reportCenter, /public function get_report_row\(/)
|
|
assert.match(usersMenus, /\$svc->apply_action\(/)
|
|
})
|
|
|
|
test('restriction class supports lifting and kicks sessions on ban', () => {
|
|
assert.match(restriction, /public function lift_user_restriction\(int \$user_id/)
|
|
assert.match(restriction, /public function lift_user_restriction\(int \$user_id, array \$types = \[self::TYPE_BAN, self::TYPE_SUSPEND, self::TYPE_MUTE\]\): int/)
|
|
assert.match(restriction, /WP_Session_Tokens::get_instance\(\$user_id\)->destroy_all\(\)/)
|
|
assert.ok(!restriction.includes('sync_user_restriction_meta'), 'write-only meta sync is dead code')
|
|
assert.ok(!restriction.includes('_aksrt_is_banned'))
|
|
})
|
|
|
|
test('ban admin REST endpoints stay in sync with the SPA client', () => {
|
|
assert.match(userController, /'\/users\/\(\?P<user_id>\\d\+\)\/restrictions'/)
|
|
assert.match(userController, /'\/users\/\(\?P<user_id>\\d\+\)\/restrict'/)
|
|
assert.match(usersBan, /\/users\/\$\{addForm\.user_id\}\/restrict`/)
|
|
assert.ok(!usersBan.includes('/restrictions`, {'), 'SPA must not POST to the unregistered /restrictions endpoint')
|
|
assert.match(usersBan, /expires_at: durationToExpiresAt\(addForm\.duration\)/)
|
|
for (const t of ["value: 'warn'", "value: 'mute'", "value: 'suspend'", "value: 'ban'"]) {
|
|
assert.ok(usersBan.includes(t), `SPA restriction type missing: ${t}`)
|
|
}
|
|
for (const dead of ["value: 'muted'", "value: 'restricted'", "value: 'banned'"]) {
|
|
assert.ok(!usersBan.includes(dead), `invalid SPA restriction type: ${dead}`)
|
|
}
|
|
})
|
|
|
|
test('ban appeal approval actually lifts restrictions', () => {
|
|
assert.match(usersMenus, /lift_user_restriction\(\$user_id\)/)
|
|
assert.ok(!usersMenus.includes("SET status = 'lifted'"), 'restriction table has no status column')
|
|
assert.ok(!usersMenus.includes('clear_user_bans'))
|
|
assert.ok(!usersMenus.includes('lift_ban'))
|
|
})
|
|
|
|
test('profile governance notice reads from the restriction table', () => {
|
|
assert.match(home, /->is_banned\(\$user_id\)/)
|
|
assert.match(home, /\\AKSRT\\User\\Restriction::get_instance\(\)/)
|
|
assert.ok(!home.includes('aksrt_user_ban_state'))
|
|
})
|
|
|
|
test('admin report notification links to the real page', () => {
|
|
assert.match(mailNotifications, /users\.php\?page=aksrt_user_ban'/)
|
|
assert.ok(!mailNotifications.includes('page=aksrt_reports&'), 'dead admin page link')
|
|
assert.ok(!mailNotifications.includes('tab=reports'), 'merged ticket page has no reports tab')
|
|
})
|
|
|
|
test('native ban page mirrors the reference theme layout', () => {
|
|
const tickets = read('src/Admin/Native/UsersMenusTickets.php')
|
|
|
|
// 菜单与页面标题对齐参考主题
|
|
assert.ok(usersMenus.includes(`__('举报&禁封申诉', 'aksrt'),\n __('举报&禁封申诉', 'aksrt')`), 'submenu title should be 举报&禁封申诉')
|
|
assert.ok(usersMenus.includes('用户禁封申诉及用户举报管理'), 'page heading should match the reference theme')
|
|
|
|
// 类型筛选按钮组 + 状态 subsubsub + 搜索
|
|
for (const label of [`__('禁封申诉', 'aksrt')`, `__('用户举报', 'aksrt')`, `__('待处理', 'aksrt')`, `__('已批准', 'aksrt')`, `__('已拒绝', 'aksrt')`, `__('搜索记录', 'aksrt')`]) {
|
|
assert.ok(tickets.includes(label), `missing reference-theme label: ${label}`)
|
|
}
|
|
|
|
// 表头列与参考主题一致:状态/类型/提交用户/详情/提交时间/更新时间(带排序)
|
|
for (const head of ['状态', '类型', '提交用户', '详情', '提交时间', '更新时间']) {
|
|
assert.ok(tickets.includes(`__('${head}', 'aksrt')`), `missing table head: ${head}`)
|
|
}
|
|
assert.match(tickets, /orderby-but/)
|
|
assert.match(tickets, /dashicons-arrow-up/)
|
|
|
|
// 行内处理表单对齐参考主题
|
|
for (const label of [`__('立即处理', 'aksrt')`, `__('申诉通过(解封该用户)', 'aksrt')`, `__('申诉拒绝(保持原禁封状态)', 'aksrt')`, `__('此处内容会反馈给举报人', 'aksrt')`, `__('确认提交', 'aksrt')`, `__('用户状态正常', 'aksrt')`, `__('暂无申请记录', 'aksrt')`]) {
|
|
assert.ok(tickets.includes(label), `missing process form label: ${label}`)
|
|
}
|
|
|
|
// 分页(每页 20 条,与参考主题一致)
|
|
assert.match(tickets, /TICKETS_PER_PAGE = 20/)
|
|
assert.match(usersMenus, /private function pagination\(int \$pages, int \$page_num\): void/)
|
|
assert.match(usersMenus, /paginate_links\(/)
|
|
|
|
// 旧的三 tab 结构已收敛
|
|
assert.ok(!usersMenus.includes('tab=reports'), 'old reports tab is gone')
|
|
assert.ok(!usersMenus.includes('tab=appeals'), 'old appeals tab is gone')
|
|
assert.ok(!usersMenus.includes('render_reports_tab'))
|
|
assert.ok(!usersMenus.includes('render_appeals_tab'))
|
|
assert.match(usersMenus, /use UsersMenusTickets;/)
|
|
})
|
|
|
|
test('comments expose a report entry for other users when reporting is enabled', () => {
|
|
assert.match(commentItem, /data-aksrt-report=\\?'\{"target_type":"comment","target_id":/)
|
|
assert.match(commentItem, /function aksrt_get_comment_report_html/)
|
|
assert.match(commentItem, /report'\]\['enabled'\]/)
|
|
assert.match(commentItem, /\$author_id === \$reporter_id/)
|
|
})
|
|
|
|
test('uninstall cleans up every ban/report meta key written by the theme', () => {
|
|
for (const key of ['_aksrt_active_restrictions', '_aksrt_is_banned', 'aksrt_user_ban_state']) {
|
|
assert.ok(uninstall.includes(`'${key}'`), `missing uninstall cleanup: ${key}`)
|
|
}
|
|
})
|
|
|
|
test('restriction REST operations pass expiry through to the service', () => {
|
|
assert.match(restrictionOps, /\$params\['expires_at'\] \?\? ''/)
|
|
assert.match(restrictionOps, /Restriction::get_instance\(\)->restrict\(/)
|
|
})
|