'use strict'; const assert = require('node:assert/strict'); const { execFileSync } = require('node:child_process'); const fs = require('node:fs'); const path = require('node:path'); const test = require('node:test'); const root = path.resolve(__dirname, '..', '..'); const read = (file) => fs.readFileSync(path.join(root, file), 'utf8'); test('site security schema registers the 网站安全 page without 人机验证 fields', () => { const schema = read('src/Admin/Schema/SchemaExtensionsSecurity.php'); const registry = read('src/Admin/Schema/SchemaExtensions.php'); const permissions = read('src/Admin/Schema/SchemaPermissions.php'); const clientMirror = read('assets/admin/src/schema/sections/extensions/security.js'); const clientIndex = read('assets/admin/src/schema/sections/extensions/index.js'); const nav = read('assets/admin/src/constants/sections/extensions.js'); assert.match(registry, /'security'\s+=> SchemaExtensionsSecurity::build\(\)/); assert.match(schema, /'key'\s+=> 'security'/); assert.match(schema, /'title'\s+=> '网站安全'/); for (const key of [ 'security_brush_post_10min', 'security_brush_post_day', 'security_brush_comment_10min', 'security_brush_comment_day', 'security_brush_upload_10min', 'security_brush_upload_day', 'security_brush_register_10min', 'security_brush_register_day', 'security_brush_order_10min', 'security_brush_order_day', 'security_captcha_send_cooldown', 'security_captcha_to_hour', 'security_captcha_to_day', 'security_captcha_ip_10min', 'security_captcha_ip_day', 'security_error_frequency_limit', 'security_disabled_pingback', 'security_disabled_xmlrpc', 'security_user_disable_admin', 'close_attachment_page', ]) { assert.match(schema, new RegExp(`'${key}'`), `missing schema field ${key}`); assert.match(clientMirror, new RegExp(`${key}`), `missing client mirror field ${key}`); } assert.doesNotMatch(schema + clientMirror, /verification_|geetest|tcaptcha|user_verification_type/); assert.doesNotMatch(permissions, /close_attachment_page/); assert.match(clientIndex, /import \{ securitySchema \} from '\.\/security\.js'/); assert.match(clientIndex, /security: securitySchema/); assert.match(nav, /key: 'extensions-security'/); assert.match(nav, /route: '\/extensions\/security'/); assert.match(nav, /section: 'security'/); }); test('security settings are consumed at runtime and initialized on boot', () => { const runtime = read('src/Settings/ExtensionRuntime.php'); const bootstrap = read('functions.php'); assert.match(runtime, /public static function get_security_settings\(\): array/); assert.match(bootstrap, /\\AKSRT\\Extensions\\SecurityHardening::get_instance\(\)->init\(\)/); }); test('hardening switches wire the documented WordPress hooks', () => { const hardening = read('src/Extensions/SecurityHardening.php'); assert.match(hardening, /add_filter\('xmlrpc_enabled', '__return_false'\)/); assert.match(hardening, /add_filter\('xmlrpc_methods', '__return_empty_array'\)/); assert.match(hardening, /add_action\('pre_ping', \[\$this, 'disable_self_pingback'\]\)/); assert.match(hardening, /add_action\('admin_init', \[\$this, 'restrict_admin_access'\]\)/); assert.match(hardening, /wp_doing_ajax\(\) \|\| wp_doing_cron\(\)/); assert.match(hardening, /current_user_can\('manage_options'\)/); assert.match(hardening, /brush_allow\(string \$scope, string \$subject, int \$cap_10, int \$cap_day\)/); assert.match(hardening, /error_frequency_wait\(\): int/); assert.match(hardening, /error_frequency_record\(\): void/); assert.match(hardening, /error_frequency_clear\(\): void/); }); test('verification-code sends are funneled through the configurable send limiter', () => { const sms = read('src/Sms/SMS.php'); const sendLimit = read('src/Extensions/SendLimit.php'); const ajax = read('inc/modules/user-parts/account-ajax.php'); const controller = read('src/Admin/UserControllerAuthPhoneOperations.php'); assert.match(sms, /SendLimit::check\(\$phone\)/); assert.match(sms, /SendLimit::record\(\$phone\)/); assert.doesNotMatch(sms, /RATE_LIMIT|DAILY_LIMIT|aksrt_sms_rate_|aksrt_sms_daily_/); assert.match(sendLimit, /security_captcha_send_cooldown/); assert.match(sendLimit, /security_captcha_to_hour/); assert.match(sendLimit, /security_captcha_to_day/); assert.match(sendLimit, /security_captcha_ip_10min/); assert.match(sendLimit, /security_captcha_ip_day/); assert.match(ajax, /SendLimit::check\(\$target\)/); assert.match(ajax, /SendLimit::record\(\$target\)/); assert.match(controller, /SendLimit::check\(\$target\)/); assert.match(controller, /SendLimit::record\(\$target\)/); }); test('content brush limits are wired into post, comment, upload, register, and order flows', () => { const frontSubmit = read('src/Core/FrontSubmitSave.php'); const moderation = read('src/Auth/Moderation.php'); const upload = read('src/Attachment/UploadLimits.php'); const registerEmail = read('src/Auth/ServicePart1a.php'); const registerPhone = read('src/Auth/ServicePart1b.php'); const order = read('src/Payment/OrderRepositoryCore.php'); assert.match(frontSubmit, /brush_allow\('post', \$brush_subject, \(int\) \$security\['security_brush_post_10min'\], \(int\) \$security\['security_brush_post_day'\]\)/); assert.match(moderation, /brush_allow\(\s*'comment'/); assert.match(upload, /add_filter\('wp_handle_upload_prefilter', \[\$this, 'brush_limit_upload'\], 90\)/); assert.match(upload, /brush_allow\('upload', \$subject, \(int\) \$security\['security_brush_upload_10min'\], \(int\) \$security\['security_brush_upload_day'\]\)/); assert.match(registerEmail, /brush_allow\('register'/); assert.match(registerPhone, /brush_allow\('register'/); assert.match(order, /brush_allow\('order', \$brush_subject, \(int\) \$security\['security_brush_order_10min'\], \(int\) \$security\['security_brush_order_day'\]\)/); }); test('phone masking is shared by the account UI and adapts to international numbers', () => { const helper = read('inc/helpers/aksrt-phone.php'); const bootstrap = read('inc/bootstrap.php'); const template = read('inc/helpers/user-center-renderers/settings-account.php'); assert.match(bootstrap, /inc\/helpers\/aksrt-phone\.php/); assert.match(helper, /function aksrt_phone_mask_parts\(string \$phone\): \?array/); assert.match(helper, /function aksrt_phone_mask_display\(string \$phone\): string/); assert.match(helper, /min\(11, max\(4, \$length - 7\)\)/); assert.doesNotMatch(template, /substr_replace\(\$account_phone, '\*\*\*\*', 3, 4\)/); assert.match(template, /aksrt_phone_mask_parts\(\$account_phone\)/); assert.match(template, /aksrt_phone_mask_display\(\$account_phone\)/); }); test('site security runtime contract harness passes', () => { const harness = path.join(root, 'tests', 'widget-system', 'site-security-harness.php'); const output = execFileSync('php', [harness], { encoding: 'utf8' }); assert.match(output, /Site security harness passed/); });