From 5c36156ac54ee002e8e9dc8855ad4e3f5a253cd0 Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 3 Jul 2026 23:26:40 +0800 Subject: [PATCH] test: extend remote SMS smoke to phone binding --- scripts/deploy/README.md | 6 ++++ scripts/remote-sms-login-smoke-test.js | 22 +++++++++++-- scripts/remote-sms-login-smoke.js | 44 ++++++++++++++++++++++++++ 3 files changed, 69 insertions(+), 3 deletions(-) diff --git a/scripts/deploy/README.md b/scripts/deploy/README.md index da012604..8e3bf5b6 100644 --- a/scripts/deploy/README.md +++ b/scripts/deploy/README.md @@ -339,6 +339,12 @@ SMS_SMOKE_API_BASE_URL=https://api.tjszsb.com SMS_SMOKE_TENANT_ID=00000000-0000- npm run perf:api:local ``` +如果要同时验证手机号绑定也走 PNVS provider verification,准备一个未绑定测试手机号后执行: + +```bash +SMS_SMOKE_API_BASE_URL=https://api.tjszsb.com SMS_SMOKE_TENANT_ID=00000000-0000-0000-0000-000000000001 SMS_SMOKE_PHONE=replace-with-login-phone SMS_SMOKE_BIND_PHONE=replace-with-bind-phone SMS_SMOKE_ORIGIN=https://admin.tjszsb.com npm run smoke:sms-login:remote +``` + 压测必须在目标云服务器、目标数据库参数、目标对象存储和目标 Nginx 配置下重新计算,本地 Windows 压测数据只能作为开发参考。 ## 关键安全要求 diff --git a/scripts/remote-sms-login-smoke-test.js b/scripts/remote-sms-login-smoke-test.js index d7e28cb9..cec9c516 100644 --- a/scripts/remote-sms-login-smoke-test.js +++ b/scripts/remote-sms-login-smoke-test.js @@ -4,6 +4,7 @@ import { runRemoteSmsLoginSmoke } from './remote-sms-login-smoke.js'; const tenantId = '00000000-0000-0000-0000-000000000001'; const phone = '13800138000'; +const bindPhone = '13900139000'; function json(res, status, payload) { res.writeHead(status, { 'content-type': 'application/json' }); @@ -31,10 +32,11 @@ const server = http.createServer(async (req, res) => { if (url.pathname === '/api/auth/sms/send' && req.method === 'POST') { const body = await bodyJson(req); - assert.equal(body.phone, phone); - assert.equal(body.purpose, 'login'); + assert.ok([phone, bindPhone].includes(body.phone)); + assert.equal(body.purpose, body.phone === bindPhone ? 'bind_phone' : 'login'); + if (body.phone === bindPhone) assert.equal(req.headers.authorization, 'Bearer session-token'); json(res, 200, { - item: { id: 'sms-id', phone, purpose: 'login', provider: 'aliyun-pnvs', status: 'sent' }, + item: { id: 'sms-id', phone: body.phone, purpose: body.purpose, provider: 'aliyun-pnvs', status: 'sent' }, expireIn: 300, cooldown: 60, }); @@ -58,6 +60,16 @@ const server = http.createServer(async (req, res) => { return; } + if (url.pathname === '/api/auth/phone/bind' && req.method === 'POST') { + assert.equal(req.headers.authorization, 'Bearer session-token'); + const body = await bodyJson(req); + assert.equal(body.phone, bindPhone); + assert.equal(body.code, '654321'); + assert.equal(body.purpose, 'bind_phone'); + json(res, 200, { ok: true, user: { id: 'user-id', phone: bindPhone }, phoneChanged: true, revokedOtherSessions: 0 }); + return; + } + json(res, 404, { code: 'NOT_FOUND', path: url.pathname }); }); @@ -73,6 +85,8 @@ try { origin: 'https://admin.tjszsb.com', purpose: 'login', code: '123456', + bindPhone, + bindCode: '654321', timeoutMs: 5000, skipSend: false, skipMe: false, @@ -81,9 +95,11 @@ try { ); assert.equal(result.sessionToken, 'session-token'); + assert.equal(result.boundPhone, bindPhone); assert.equal(seen.some(item => item.path === '/api/auth/sms/send' && item.tenantId === tenantId), true); assert.equal(seen.some(item => item.path === '/api/auth/sms/verify' && item.origin === 'https://admin.tjszsb.com'), true); assert.equal(seen.some(item => item.path === '/api/auth/me'), true); + assert.equal(seen.some(item => item.path === '/api/auth/phone/bind'), true); console.log('[PASS] remote SMS login smoke script'); } finally { await new Promise(resolve => server.close(resolve)); diff --git a/scripts/remote-sms-login-smoke.js b/scripts/remote-sms-login-smoke.js index 08c0a5e8..66470900 100644 --- a/scripts/remote-sms-login-smoke.js +++ b/scripts/remote-sms-login-smoke.js @@ -31,9 +31,11 @@ function buildConfig(env = process.env) { const apiBaseUrl = envString(env, 'SMS_SMOKE_API_BASE_URL', envString(env, 'API_BASE', '')); const tenantId = envString(env, 'SMS_SMOKE_TENANT_ID', envString(env, 'TENANT_ID', '')); const phone = envString(env, 'SMS_SMOKE_PHONE'); + const bindPhone = envString(env, 'SMS_SMOKE_BIND_PHONE'); const origin = envString(env, 'SMS_SMOKE_ORIGIN', 'https://admin.tjszsb.com'); const purpose = envString(env, 'SMS_SMOKE_PURPOSE', 'login'); const code = envString(env, 'SMS_SMOKE_CODE'); + const bindCode = envString(env, 'SMS_SMOKE_BIND_CODE'); const missing = []; if (!apiBaseUrl) missing.push('SMS_SMOKE_API_BASE_URL'); @@ -47,9 +49,11 @@ function buildConfig(env = process.env) { apiBaseUrl: normalizeBaseUrl(apiBaseUrl), tenantId, phone, + bindPhone, origin, purpose, code, + bindCode, timeoutMs: envNumber(env, 'SMS_SMOKE_TIMEOUT_MS', DEFAULT_TIMEOUT_MS), skipSend: ['1', 'true', 'yes', 'on'].includes(envString(env, 'SMS_SMOKE_SKIP_SEND').toLowerCase()), skipMe: ['1', 'true', 'yes', 'on'].includes(envString(env, 'SMS_SMOKE_SKIP_ME').toLowerCase()), @@ -105,6 +109,16 @@ async function promptCode(config) { } } +async function promptBindCode(config) { + if (config.bindCode) return config.bindCode; + const readline = createInterface({ input, output }); + try { + return (await readline.question('Enter received bind_phone SMS verification code: ')).trim(); + } finally { + readline.close(); + } +} + async function runRemoteSmsLoginSmoke(inputConfig, options = {}) { const config = inputConfig?.apiBaseUrl ? inputConfig : buildConfig(options.env || process.env); if (!options.quiet) { @@ -145,9 +159,37 @@ async function runRemoteSmsLoginSmoke(inputConfig, options = {}) { if (!options.quiet) console.log(`PASS auth.me user=${me.payload?.user?.id || me.payload?.item?.userId || 'unknown'}`); } + let boundPhone = null; + if (config.bindPhone) { + const sendBind = await requestJson(config, '/api/auth/sms/send', { + method: 'POST', + token, + body: { phone: config.bindPhone, purpose: 'bind_phone' }, + }); + assertOk('sms.bind.send', sendBind); + if (!options.quiet) { + console.log(`PASS sms.bind.send provider=${sendBind.payload?.item?.provider || 'unknown'} phone=${maskPhone(config.bindPhone)}`); + } + + const bindCode = await promptBindCode(config); + if (!bindCode) throw new Error('Missing bind_phone SMS verification code'); + + const bind = await requestJson(config, '/api/auth/phone/bind', { + method: 'POST', + token, + body: { phone: config.bindPhone, code: bindCode, purpose: 'bind_phone' }, + }); + assertOk('auth.phone.bind', bind); + boundPhone = bind.payload?.user?.phone || config.bindPhone; + if (!options.quiet) { + console.log(`PASS auth.phone.bind phone=${maskPhone(boundPhone)} phoneChanged=${bind.payload?.phoneChanged ?? '-'}`); + } + } + return { sessionToken: token, user: verify.payload?.user || verify.payload?.item || null, + boundPhone, }; } @@ -167,6 +209,8 @@ Required example: Optional: SMS_SMOKE_ORIGIN=https://admin.tjszsb.com SMS_SMOKE_CODE= + SMS_SMOKE_BIND_PHONE= + SMS_SMOKE_BIND_CODE= SMS_SMOKE_SKIP_SEND=true `); }