- remove unused AuthReader::invitation_mode and ReferralAttribution helpers (get_or_create_code / validate_ref); contract test now asserts the canonical invit_code_s reader in CreditsRuntime - drop legacy aksrt_invite table from uninstall cleanup list - track referral code creation time (aksrt_referral_code_created) and surface it in the referral ops REST list - formalize ops list headers (归属用户/推广码/启用状态/推广用户数/ 有效付费用户数/创建时间) and right-align count columns
242 lines
13 KiB
JavaScript
242 lines
13 KiB
JavaScript
const assert = require('node:assert/strict');
|
|
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('modal and sign page use one renderer and one state machine', () => {
|
|
const modal = read('templates/parts/modal-auth.php');
|
|
const page = read('templates/pages/page-sign.php');
|
|
const shared = read('templates/parts/auth-content.php');
|
|
const scripts = read('src/Core/ScriptsPart1c.php');
|
|
const runtime = read('assets/js/runtime/main.js') + read('assets/js/runtime/main.min.js');
|
|
const loader = read('assets/js/runtime/loader.js');
|
|
|
|
assert.match(modal, /templates\/parts\/auth-content\.php/);
|
|
assert.match(page, /templates\/parts\/auth-content\.php/);
|
|
assert.match(shared, /data-auth-panel="signin"/);
|
|
assert.match(shared, /data-auth-panel="signup"/);
|
|
assert.match(shared, /data-auth-panel="resetpassword"/);
|
|
assert.match(shared, /data-auth-panel="bind"/);
|
|
assert.match(shared, /data-auth-panel="oauth"/);
|
|
assert.match(scripts, /assets\/js\/auth\.js/);
|
|
assert.match(scripts, /'aksrt-auth',[\s\S]*?assets\/js\/auth\.js[\s\S]*?\['loader_js'\]/);
|
|
assert.doesNotMatch(scripts, /modal-auth\.js|sign-register\.js/);
|
|
assert.doesNotMatch(runtime, /signsubmit-loader|tbquire\(\['sign-register'\]\)|\.on\('click', '\.(?:signin|signup)-loader'/);
|
|
assert.doesNotMatch(loader, /sign-register/);
|
|
});
|
|
|
|
test('static Zibll authentication dimensions and responsive contract are retained', () => {
|
|
const css = read('assets/css/routes/sign.css');
|
|
assert.match(css, /\.sign\s*\{[\s\S]*?max-width:\s*330px/);
|
|
assert.match(css, /#u_sign\.has-sign-img \.modal-dialog\s*\{\s*width:\s*800px/);
|
|
assert.match(css, /#u_sign \.sign-img\s*\{[^}]*inset:\s*0 45% 0 0;[^}]*padding:\s*0/);
|
|
assert.match(css, /#u_sign \.sign-img \+ \.sign\s*\{[^}]*margin-left:\s*55%/);
|
|
assert.match(css, /\.social-login-item\s*\{[\s\S]*?width:\s*32px;[\s\S]*?height:\s*32px/);
|
|
assert.match(css, /\.auth-qrcode\s*\{[\s\S]*?width:\s*180px;[\s\S]*?min-height:\s*180px/);
|
|
assert.match(css, /@media \(max-width:\s*767px\)[\s\S]*?\.sign-img\s*\{\s*display:\s*none/);
|
|
});
|
|
|
|
test('authentication settings use reference defaults and canonical keys', () => {
|
|
const js = read('assets/admin/src/schema/sections/users/auth.js');
|
|
const php = read('src/Admin/Schema/SchemaUsers.php');
|
|
const all = js + '\n' + php;
|
|
|
|
assert.match(all, /user_sign_type['"]?\s*(?:=>|:)\s*['"]modal/);
|
|
assert.match(all, /user_signup_captch['"]?\s*(?:=>|:)\s*false/);
|
|
assert.match(all, /sign_card_position['"]?\s*(?:=>|:)\s*['"]right/);
|
|
assert.match(all, /sign_show_logo['"]?\s*(?:=>|:)\s*false/);
|
|
assert.match(all, /sign_modal_show_logo['"]?\s*(?:=>|:)\s*false/);
|
|
assert.match(all, /invit_code_s/);
|
|
assert.match(all, /only_nopas/);
|
|
assert.match(all, /user_signup_no_repas[\s\S]*?dependsOn[\s\S]*?user_signup_captch/);
|
|
assert.doesNotMatch(all, /user_close_login|user_close_signup|nopas_first|password_first|nopas_only|referral_registration_enabled|referral_register_field/);
|
|
});
|
|
|
|
test('authentication galleries save attachment IDs and are consumed by both surfaces', () => {
|
|
const field = read('assets/admin/src/components/form/AksField.vue');
|
|
const gallery = read('assets/admin/src/components/form/AksGalleryField.vue');
|
|
const provider = read('src/Admin/SchemaProvider.php');
|
|
const reader = read('src/Settings/AuthReader.php');
|
|
const modal = read('templates/parts/modal-auth.php');
|
|
const page = read('templates/pages/page-sign.php');
|
|
const css = read('assets/css/routes/sign.css');
|
|
|
|
assert.match(field, /<AksGalleryField[\s\S]*?schema\.type === 'gallery'/);
|
|
assert.match(gallery, /multiple:\s*true/);
|
|
assert.match(gallery, /Number\.parseInt\(attachment\.id/);
|
|
assert.match(provider, /'gallery'\s*=>\s*'attachment_ids'/);
|
|
assert.match(provider, /case 'attachment_ids':[\s\S]*?array_map\('absint'/);
|
|
assert.match(reader, /auth_gallery_ids\('sign_modal_background'\)/);
|
|
assert.match(reader, /auth_gallery_ids\('sign_page_background'\)/);
|
|
assert.match(modal, /wp_get_attachment_image\(\$image_id, 'full'/);
|
|
assert.match(modal, /<div class="sign-img"><\?php echo \$image_html/);
|
|
assert.match(page, /wp_get_attachment_image_url\(\$background_id, 'full'\)/);
|
|
assert.match(page, /<div class="sign-page-background" style="background-image:url/);
|
|
assert.match(css, /\.sign-img img\s*\{[\s\S]*?object-fit:\s*cover/);
|
|
assert.doesNotMatch(modal + page, /is_string\(\$images\)|explode\(',', \$images\)/);
|
|
});
|
|
|
|
test('only the shared authentication REST routes and /sign/ renderer are registered', () => {
|
|
const controller = read('src/Admin/UserController.php');
|
|
const router = read('src/Auth/Router.php');
|
|
const urls = read('inc/modules/user-parts/urls.php');
|
|
|
|
['/auth/code', '/auth/login', '/auth/register', '/auth/reset-password'].forEach((route) => {
|
|
assert.match(controller, new RegExp(route.replace('/', '\\/')));
|
|
});
|
|
assert.doesNotMatch(controller, /auth\/(?:modal-|phone-|send-sms-code|bind-phone)/);
|
|
assert.match(router, /'sign'\s*=>\s*'index\.php\?aksrt_sign=1'/);
|
|
assert.doesNotMatch(router, /'login'\s*=>|'register'\s*=>|'reset-password'\s*=>|'verify-email'\s*=>|'bind-account'\s*=>/);
|
|
assert.match(router, /aksrt_bypass/);
|
|
assert.match(router, /\$action === 'logout'[\s\S]*?aksrt_get_logout_url/);
|
|
assert.doesNotMatch(router, /is_user_logged_in\(\) && current_user_can\('manage_options'\)|wp_doing_ajax\(\)/);
|
|
assert.match(urls, /home_url\('\/sign\/'\)/);
|
|
});
|
|
|
|
test('legacy authentication templates and scripts are removed', () => {
|
|
[
|
|
'assets/js/modal-auth.js',
|
|
'assets/js/sign-register.js',
|
|
'assets/js/runtime/sign-register.js',
|
|
'assets/js/runtime/sign-register.min.js',
|
|
'assets/css/routes/auth.css',
|
|
'assets/css/frontend/enhancements/modal-motion.css',
|
|
'templates/pages/page-login.php',
|
|
'templates/pages/page-register.php',
|
|
'templates/pages/page-reset-password.php',
|
|
'templates/pages/page-bind-account.php',
|
|
'templates/pages/page-verify-email.php',
|
|
].forEach((file) => assert.equal(fs.existsSync(path.join(root, file)), false, file));
|
|
});
|
|
|
|
test('unified handlers enforce nonce, captcha, rate limits, configured channels, and generic errors', () => {
|
|
const handlers = read('src/Admin/UserControllerAuthPhoneOperations.php');
|
|
const service = read('src/Auth/Service.php');
|
|
const serviceA = read('src/Auth/ServicePart1a.php');
|
|
const serviceB = read('src/Auth/ServicePart1b.php');
|
|
|
|
assert.match(handlers, /auth_verify_rest_nonce/);
|
|
assert.match(handlers, /private function auth_require_captcha/);
|
|
assert.match(handlers, /get_error_code\(\)/);
|
|
assert.match(handlers, /get_error_message\(\)/);
|
|
assert.match(handlers, /auth_rate_limited/);
|
|
assert.match(handlers, /auth_channel_allowed/);
|
|
['auth_code', 'auth_login', 'auth_register', 'auth_reset_password'].forEach((method) => {
|
|
const start = handlers.indexOf(`public function ${method}(`);
|
|
const end = handlers.indexOf('\n\tpublic function ', start + 1);
|
|
const body = handlers.slice(start, end === -1 ? handlers.length : end);
|
|
assert.match(body, /auth_require_captcha/, method);
|
|
});
|
|
assert.match(handlers, /passwordless_login_type/);
|
|
assert.match(handlers, /signup_verification_type/);
|
|
assert.match(handlers, /reset_password_verification_type/);
|
|
assert.match(handlers, /账号或密码错误/);
|
|
assert.match(handlers, /账号或验证码错误/);
|
|
assert.match(service, /signin_phone_enabled/);
|
|
assert.doesNotMatch(service + serviceA + serviceB, /aksrt_require_captcha/);
|
|
assert.doesNotMatch(service + serviceA + serviceB, /_auth_code_verified|_defer_login/);
|
|
assert.doesNotMatch(service, /captcha_scene/);
|
|
assert.match(handlers, /public function user_bind_phone[\s\S]*?auth_require_captcha\('bind_phone'/);
|
|
assert.doesNotMatch(service, /login_by_phone_password|login_by_phone_code/);
|
|
});
|
|
|
|
test('OAuth bind and reset flows stay inside the shared page and four auth endpoints', () => {
|
|
const shared = read('templates/parts/auth-content.php');
|
|
const page = read('templates/pages/page-sign.php');
|
|
const handlers = read('src/Admin/UserControllerAuthPhoneOperations.php');
|
|
const serviceA = read('src/Auth/ServicePart1a.php');
|
|
const serviceB = read('src/Auth/ServicePart1b.php');
|
|
|
|
assert.match(page, /'obk'\s*=>\s*\$obk/);
|
|
assert.match(shared, /data-auth-panel="bind"[\s\S]*?data-auth-form="login"/);
|
|
assert.match(shared, /name="obk"/);
|
|
assert.match(handlers, /finalize_oauth_binding/);
|
|
assert.match(serviceA, /finalize_oauth_binding/);
|
|
assert.doesNotMatch(serviceA + serviceB, /bind_oauth_account|get_reset_context|request_password_reset|reset_password_with_token/);
|
|
});
|
|
|
|
test('shared renderer contracts every configured authentication state', () => {
|
|
const shared = read('templates/parts/auth-content.php');
|
|
const modal = read('templates/parts/modal-auth.php');
|
|
const client = read('assets/js/auth.js');
|
|
|
|
assert.match(shared, /data-auth-password-fields/);
|
|
assert.match(shared, /data-auth-code-fields/);
|
|
assert.match(shared, /data-login-mode/);
|
|
['login', 'register', 'resetpassword'].forEach((scene) => {
|
|
assert.match(shared, new RegExp(`data-send-code="${scene}"`));
|
|
});
|
|
assert.match(shared, /\$omit_confirm/);
|
|
assert.match(shared, /\$ref_mode !== 'hidden'/);
|
|
assert.match(shared, /\$ref_mode === 'required'/);
|
|
assert.match(shared, /sign-agreement-implicit/);
|
|
assert.match(shared, /name="agree_terms"[\s\S]*?required/);
|
|
assert.match(shared, /oauth-button-lg/);
|
|
assert.match(shared, /data-auth-qrcode/);
|
|
assert.match(modal, /data-auto=/);
|
|
assert.match(client, /aksrtAuthAutoUntil/);
|
|
assert.match(client, /data-password-show/);
|
|
assert.match(client, /pollQr/);
|
|
assert.match(client, /AbortController/);
|
|
assert.match(client, /refreshInFlight|inFlight/);
|
|
assert.match(client, /visibilitychange/);
|
|
assert.match(client, /Math\.pow\(2/);
|
|
assert.match(client, /QR_TIMEOUT/);
|
|
assert.match(client, /stopQr\(root\)/);
|
|
assert.match(client, /window\.notyf\(escapeHtml\(text\), type \|\| 'info'\)/);
|
|
assert.match(client, /notify\('请先完成人机验证', 'warning'\)/);
|
|
assert.match(client, /form\.noValidate = true/);
|
|
assert.match(client, /form\.checkValidity\(\)[\s\S]*?field\.validationMessage/);
|
|
assert.match(client, /window\.location\.assign\(authPageUrl/);
|
|
assert.doesNotMatch(client + shared, /showMessage|data-auth-message|sign-message/);
|
|
assert.match(read('src/Settings/CreditsRuntime.php'), /invit_code_s/);
|
|
assert.match(read('src/Settings/CreditsRuntime.php'), /\['close', 'open', 'must'\]/);
|
|
});
|
|
|
|
test('OAuth frontend is accessible, redirect-aware, and removes retired providers', () => {
|
|
const shared = read('templates/parts/auth-content.php');
|
|
const modal = read('templates/parts/modal-auth.php');
|
|
const client = read('assets/js/auth.js');
|
|
const userCenter = read('assets/js/routes/user-center.js');
|
|
const bindings = read('inc/helpers/user-center-renderers/settings-bindings.php');
|
|
const signCss = read('assets/css/routes/sign.css');
|
|
const mainCss = read('assets/css/main.css');
|
|
|
|
assert.match(modal, /role="dialog"[^>]*aria-modal="true"[^>]*aria-labelledby="aksrt-auth-modal-title"/);
|
|
assert.match(modal, /id="aksrt-auth-modal-title"/);
|
|
assert.match(client, /focusableModalItems/);
|
|
assert.match(client, /lastModalTrigger/);
|
|
assert.match(client, /restore\.focus\(\)/);
|
|
assert.match(shared, /role="tabpanel"[^>]*data-auth-panel="signin"/);
|
|
assert.match(shared, /role="tabpanel"[^>]*data-auth-panel="oauth"/);
|
|
assert.match(client, /setAttribute\('role', 'tab'\)/);
|
|
assert.match(client, /aria-controls/);
|
|
assert.match(client, /aria-selected/);
|
|
assert.match(shared, /data-password-show[^>]*aria-label=/);
|
|
assert.match(shared, /aria-pressed="false"/);
|
|
assert.match(client, /setAttribute\('aria-label', visible \? '隐藏密码' : '显示密码'\)/);
|
|
assert.match(client, /setAttribute\('aria-pressed', visible \? 'true' : 'false'\)/);
|
|
assert.match(shared, /data-auth-qr-status[^>]*role="status"[^>]*aria-live="polite"/);
|
|
assert.match(shared, /data-provider=/);
|
|
assert.match(shared, /aria-label="<\?php echo esc_attr\(sprintf/);
|
|
assert.match(client, /searchParams\.set\('redirect_to', redirect\)/);
|
|
|
|
assert.match(client, /oauth_action/);
|
|
assert.match(client, /oauth_provider/);
|
|
assert.match(client, /oauth_error/);
|
|
assert.match(client, /oauthErrorMessages/);
|
|
assert.doesNotMatch(client, /notify\([^\n;]*searchParams\.get\('oauth_error'/);
|
|
assert.match(userCenter, /errorMessages\[currentUrl\.searchParams\.get\('oauth_error'\)/);
|
|
assert.match(userCenter, /result\.status === 409/);
|
|
assert.match(userCenter, /d\.message \|\| \(d\.data && d\.data\.message\)/);
|
|
assert.match(bindings, /\$binding\['provider'\] \?\? \$binding\['key'\]/);
|
|
assert.match(bindings, /\$binding\['start_url'\] \?\? \$binding\['url'\]/);
|
|
assert.match(bindings, /data-provider=/);
|
|
|
|
assert.doesNotMatch(signCss + mainCss, /social-login-item[^\n{]*(?:baidu|alipay)|social-login-item\.(?:baidu|alipay)/i);
|
|
assert.match(mainCss, /\.pay-box \.but\.baidu/);
|
|
});
|