forked from wangziqi/gongxue-base
chore: require PNVS SMS smoke launch evidence
This commit is contained in:
@@ -54,6 +54,19 @@
|
|||||||
"requireAdminTokens": true
|
"requireAdminTokens": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "auth.sms-pnvs-remote-smoke",
|
||||||
|
"status": "pass",
|
||||||
|
"command": "SMS_SMOKE_API_BASE_URL=https://api.example.com SMS_SMOKE_TENANT_ID=00000000-0000-0000-0000-000000000001 SMS_SMOKE_PHONE=replace-with-real-phone SMS_SMOKE_ORIGIN=https://admin.example.com npm run smoke:sms-login:remote -- --json > docs/refactor/launch-artifacts/sms-pnvs-remote-smoke.json",
|
||||||
|
"completedAt": "2026-06-30T10:12:00+08:00",
|
||||||
|
"artifact": "launch-artifacts/sms-pnvs-remote-smoke.json",
|
||||||
|
"summary": {
|
||||||
|
"failed": 0,
|
||||||
|
"provider": "aliyun-pnvs",
|
||||||
|
"loginVerified": true,
|
||||||
|
"authMe": true
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "rls.tenant-isolation",
|
"id": "rls.tenant-isolation",
|
||||||
"status": "pass",
|
"status": "pass",
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ SMS_SMOKE_API_BASE_URL=https://api.tjszsb.com \
|
|||||||
SMS_SMOKE_TENANT_ID=00000000-0000-0000-0000-000000000001 \
|
SMS_SMOKE_TENANT_ID=00000000-0000-0000-0000-000000000001 \
|
||||||
SMS_SMOKE_PHONE=13800138000 \
|
SMS_SMOKE_PHONE=13800138000 \
|
||||||
SMS_SMOKE_ORIGIN=https://admin.tjszsb.com \
|
SMS_SMOKE_ORIGIN=https://admin.tjszsb.com \
|
||||||
npm run smoke:sms-login:remote
|
npm run smoke:sms-login:remote -- --json > docs/refactor/launch-artifacts/sms-pnvs-remote-smoke.json
|
||||||
```
|
```
|
||||||
|
|
||||||
PNVS 只验收手机号登录/换绑验证码链路。催缴、营销、CRM 等非验证码短信不应接 PNVS;平台催缴通知仍按 webhook 类通知渠道验收。
|
PNVS 只验收手机号登录/换绑验证码链路。催缴、营销、CRM 等非验证码短信不应接 PNVS;平台催缴通知仍按 webhook 类通知渠道验收。
|
||||||
|
|||||||
@@ -335,14 +335,14 @@ npm run readiness:production
|
|||||||
```bash
|
```bash
|
||||||
npm run readiness:production:db
|
npm run readiness:production:db
|
||||||
npm run smoke:auth:remote
|
npm run smoke:auth:remote
|
||||||
SMS_SMOKE_API_BASE_URL=https://api.tjszsb.com SMS_SMOKE_TENANT_ID=00000000-0000-0000-0000-000000000001 SMS_SMOKE_PHONE=replace-with-real-phone SMS_SMOKE_ORIGIN=https://admin.tjszsb.com npm run smoke:sms-login:remote
|
SMS_SMOKE_API_BASE_URL=https://api.tjszsb.com SMS_SMOKE_TENANT_ID=00000000-0000-0000-0000-000000000001 SMS_SMOKE_PHONE=replace-with-real-phone SMS_SMOKE_ORIGIN=https://admin.tjszsb.com npm run smoke:sms-login:remote -- --json > docs/refactor/launch-artifacts/sms-pnvs-remote-smoke.json
|
||||||
npm run perf:api:local
|
npm run perf:api:local
|
||||||
```
|
```
|
||||||
|
|
||||||
如果要同时验证手机号绑定也走 PNVS provider verification,准备一个未绑定测试手机号后执行:
|
如果要同时验证手机号绑定也走 PNVS provider verification,准备一个未绑定测试手机号后执行:
|
||||||
|
|
||||||
```bash
|
```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
|
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 -- --json > docs/refactor/launch-artifacts/sms-pnvs-remote-smoke.json
|
||||||
```
|
```
|
||||||
|
|
||||||
压测必须在目标云服务器、目标数据库参数、目标对象存储和目标 Nginx 配置下重新计算,本地 Windows 压测数据只能作为开发参考。
|
压测必须在目标云服务器、目标数据库参数、目标对象存储和目标 Nginx 配置下重新计算,本地 Windows 压测数据只能作为开发参考。
|
||||||
|
|||||||
@@ -119,6 +119,29 @@ assert.ok(
|
|||||||
'missing artifact should be reported as a blocker',
|
'missing artifact should be reported as a blocker',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const missingSmsPnvsSmoke = runGate(tempDir => {
|
||||||
|
const evidence = createEvidence(tempDir);
|
||||||
|
evidence.checks = evidence.checks.filter(item => item.id !== 'auth.sms-pnvs-remote-smoke');
|
||||||
|
return evidence;
|
||||||
|
});
|
||||||
|
assert.notEqual(missingSmsPnvsSmoke.status, 0, 'missing PNVS SMS smoke should fail launch gate');
|
||||||
|
assert.ok(
|
||||||
|
missingSmsPnvsSmoke.payload.checks?.some(item => item.id === 'check.auth.sms-pnvs-remote-smoke' && item.status === 'blocker'),
|
||||||
|
'missing PNVS SMS smoke should be reported as a blocker',
|
||||||
|
);
|
||||||
|
|
||||||
|
const wrongSmsProviderSmoke = runGate(tempDir => {
|
||||||
|
const evidence = createEvidence(tempDir);
|
||||||
|
const item = evidence.checks.find(check => check.id === 'auth.sms-pnvs-remote-smoke');
|
||||||
|
item.summary.provider = 'aliyun';
|
||||||
|
return evidence;
|
||||||
|
});
|
||||||
|
assert.notEqual(wrongSmsProviderSmoke.status, 0, 'non-PNVS SMS smoke should fail launch gate');
|
||||||
|
assert.ok(
|
||||||
|
wrongSmsProviderSmoke.payload.checks?.some(item => item.id === 'check.auth.sms-pnvs-remote-smoke.summary' && item.status === 'blocker'),
|
||||||
|
'non-PNVS SMS smoke should be reported as a blocker',
|
||||||
|
);
|
||||||
|
|
||||||
const wrongMigrationProfile = runGate(tempDir => {
|
const wrongMigrationProfile = runGate(tempDir => {
|
||||||
const evidence = createEvidence(tempDir);
|
const evidence = createEvidence(tempDir);
|
||||||
const item = evidence.checks.find(check => check.id === 'migration.pb-production-dry-run');
|
const item = evidence.checks.find(check => check.id === 'migration.pb-production-dry-run');
|
||||||
|
|||||||
@@ -38,6 +38,17 @@ const gateChecks = [
|
|||||||
commandIncludes: 'smoke:auth:remote',
|
commandIncludes: 'smoke:auth:remote',
|
||||||
summary: { failed: 0, requireAdminTokens: true },
|
summary: { failed: 0, requireAdminTokens: true },
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'auth.sms-pnvs-remote-smoke',
|
||||||
|
label: 'Remote Aliyun PNVS SMS login smoke',
|
||||||
|
commandIncludes: 'smoke:sms-login:remote',
|
||||||
|
summary: {
|
||||||
|
failed: 0,
|
||||||
|
provider: 'aliyun-pnvs',
|
||||||
|
loginVerified: true,
|
||||||
|
authMe: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'rls.tenant-isolation',
|
id: 'rls.tenant-isolation',
|
||||||
label: 'Runtime tenant RLS isolation',
|
label: 'Runtime tenant RLS isolation',
|
||||||
|
|||||||
@@ -96,6 +96,11 @@ try {
|
|||||||
|
|
||||||
assert.equal(result.sessionToken, 'session-token');
|
assert.equal(result.sessionToken, 'session-token');
|
||||||
assert.equal(result.boundPhone, bindPhone);
|
assert.equal(result.boundPhone, bindPhone);
|
||||||
|
assert.equal(result.summary.provider, 'aliyun-pnvs');
|
||||||
|
assert.equal(result.summary.loginVerified, true);
|
||||||
|
assert.equal(result.summary.authMe, true);
|
||||||
|
assert.equal(result.summary.bindProvider, 'aliyun-pnvs');
|
||||||
|
assert.equal(result.summary.bindVerified, true);
|
||||||
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/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/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/me'), true);
|
||||||
|
|||||||
@@ -13,6 +13,13 @@ function envNumber(env, key, fallback) {
|
|||||||
return Number.isFinite(value) && value > 0 ? Math.trunc(value) : fallback;
|
return Number.isFinite(value) && value > 0 ? Math.trunc(value) : fallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function parseArgs(argv) {
|
||||||
|
return {
|
||||||
|
json: argv.includes('--json'),
|
||||||
|
quiet: argv.includes('--quiet'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function normalizeBaseUrl(value) {
|
function normalizeBaseUrl(value) {
|
||||||
return value.replace(/\/+$/, '');
|
return value.replace(/\/+$/, '');
|
||||||
}
|
}
|
||||||
@@ -137,6 +144,7 @@ async function runRemoteSmsLoginSmoke(inputConfig, options = {}) {
|
|||||||
if (!options.quiet) {
|
if (!options.quiet) {
|
||||||
console.log(`PASS sms.send provider=${send.payload?.item?.provider || 'unknown'} expireIn=${send.payload?.expireIn || '-'} cooldown=${send.payload?.cooldown || '-'}`);
|
console.log(`PASS sms.send provider=${send.payload?.item?.provider || 'unknown'} expireIn=${send.payload?.expireIn || '-'} cooldown=${send.payload?.cooldown || '-'}`);
|
||||||
}
|
}
|
||||||
|
config.sendProvider = send.payload?.item?.provider || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
const code = await promptCode(config);
|
const code = await promptCode(config);
|
||||||
@@ -160,6 +168,7 @@ async function runRemoteSmsLoginSmoke(inputConfig, options = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let boundPhone = null;
|
let boundPhone = null;
|
||||||
|
let bindProvider = '';
|
||||||
if (config.bindPhone) {
|
if (config.bindPhone) {
|
||||||
const sendBind = await requestJson(config, '/api/auth/sms/send', {
|
const sendBind = await requestJson(config, '/api/auth/sms/send', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -167,8 +176,9 @@ async function runRemoteSmsLoginSmoke(inputConfig, options = {}) {
|
|||||||
body: { phone: config.bindPhone, purpose: 'bind_phone' },
|
body: { phone: config.bindPhone, purpose: 'bind_phone' },
|
||||||
});
|
});
|
||||||
assertOk('sms.bind.send', sendBind);
|
assertOk('sms.bind.send', sendBind);
|
||||||
|
bindProvider = sendBind.payload?.item?.provider || '';
|
||||||
if (!options.quiet) {
|
if (!options.quiet) {
|
||||||
console.log(`PASS sms.bind.send provider=${sendBind.payload?.item?.provider || 'unknown'} phone=${maskPhone(config.bindPhone)}`);
|
console.log(`PASS sms.bind.send provider=${bindProvider || 'unknown'} phone=${maskPhone(config.bindPhone)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const bindCode = await promptBindCode(config);
|
const bindCode = await promptBindCode(config);
|
||||||
@@ -190,13 +200,30 @@ async function runRemoteSmsLoginSmoke(inputConfig, options = {}) {
|
|||||||
sessionToken: token,
|
sessionToken: token,
|
||||||
user: verify.payload?.user || verify.payload?.item || null,
|
user: verify.payload?.user || verify.payload?.item || null,
|
||||||
boundPhone,
|
boundPhone,
|
||||||
|
summary: {
|
||||||
|
failed: 0,
|
||||||
|
provider: config.sendProvider || 'unknown',
|
||||||
|
loginVerified: true,
|
||||||
|
authMe: !config.skipMe,
|
||||||
|
bindProvider: bindProvider || undefined,
|
||||||
|
bindVerified: Boolean(boundPhone),
|
||||||
|
phoneMasked: maskPhone(config.phone),
|
||||||
|
bindPhoneMasked: config.bindPhone ? maskPhone(config.bindPhone) : undefined,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
|
const options = parseArgs(process.argv.slice(2));
|
||||||
try {
|
try {
|
||||||
await runRemoteSmsLoginSmoke();
|
const result = await runRemoteSmsLoginSmoke(null, { quiet: options.quiet || options.json });
|
||||||
|
if (options.json) console.log(JSON.stringify(result.summary, null, 2));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
if (options.json) {
|
||||||
|
console.log(JSON.stringify({ failed: 1, error: error.message }, null, 2));
|
||||||
|
process.exitCode = 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
console.error(error.message);
|
console.error(error.message);
|
||||||
if (error.response?.payload) console.error(JSON.stringify(error.response.payload, null, 2));
|
if (error.response?.payload) console.error(JSON.stringify(error.response.payload, null, 2));
|
||||||
if (/Missing required remote SMS smoke env/.test(error.message)) {
|
if (/Missing required remote SMS smoke env/.test(error.message)) {
|
||||||
@@ -212,6 +239,7 @@ Optional:
|
|||||||
SMS_SMOKE_BIND_PHONE=<another-phone-to-bind>
|
SMS_SMOKE_BIND_PHONE=<another-phone-to-bind>
|
||||||
SMS_SMOKE_BIND_CODE=<bind-code-you-received>
|
SMS_SMOKE_BIND_CODE=<bind-code-you-received>
|
||||||
SMS_SMOKE_SKIP_SEND=true
|
SMS_SMOKE_SKIP_SEND=true
|
||||||
|
--json
|
||||||
`);
|
`);
|
||||||
}
|
}
|
||||||
process.exitCode = 1;
|
process.exitCode = 1;
|
||||||
|
|||||||
Reference in New Issue
Block a user