This commit is contained in:
2026-08-25 16:40:48 +08:00
parent 1f6f1a0677
commit 87c5e18ca6
93 changed files with 1154 additions and 527 deletions
+48 -1
View File
@@ -350,7 +350,9 @@ export class AcceptanceRun {
this.coverageGaps = []
this.context = {}
this.token = ''
this.refreshToken = ''
this.overseasToken = ''
this.overseasExpiresAt = 0
this.fatal = null
}
@@ -553,6 +555,30 @@ export class AcceptanceRun {
this.modules.push({ name, startedAt, finishedAt: new Date().toISOString(), success: fatal === null && steps.every((s) => s.passed), passed: steps.filter((s) => s.passed).length, failed: steps.filter((s) => !s.passed).length, fatal })
}
async refreshSession(reason) {
const response = await this.request('POST', '/api/auth/refresh',
{ refreshToken: this.refreshToken }, '')
const next = this.apiSuccess('authentication', '国内 refresh 签发新双区域会话', response,
`reason=${reason}`, { scenario: 'dual-token-refresh', deploymentBlocker: true })
const valid = Boolean(next?.accessToken) && Boolean(next?.refreshToken)
&& Boolean(next?.overseasAccessToken) && Number(next?.overseasExpiresIn) > 0
this.assert('authentication', '刷新响应完整后原子替换国内与海外Token', response, valid,
`accessTokenPresent=${Boolean(next?.accessToken)}, refreshTokenPresent=${Boolean(next?.refreshToken)}, overseasTokenPresent=${Boolean(next?.overseasAccessToken)}, overseasExpiresIn=${next?.overseasExpiresIn ?? 'missing'}`,
{ scenario: 'dual-token-refresh', deploymentBlocker: true })
if (!valid) return false
const replacement = {
token: next.accessToken,
refreshToken: next.refreshToken,
overseasToken: next.overseasAccessToken,
overseasExpiresAt: Date.now() + Number(next.overseasExpiresIn) * 1000,
}
this.token = replacement.token
this.refreshToken = replacement.refreshToken
this.overseasToken = replacement.overseasToken
this.overseasExpiresAt = replacement.overseasExpiresAt
return true
}
async findAvailablePhone(module = 'customers') {
const seed = BigInt(Date.now() % 10_000_000_000)
for (let attempt = 0; attempt < 20; attempt += 1) {
@@ -1159,7 +1185,10 @@ export class AcceptanceRun {
if (!login?.accessToken) this.fatal = '平台管理员登录失败,未执行业务模块'
else {
this.token = login.accessToken
this.refreshToken = login.refreshToken || ''
this.overseasToken = login.overseasAccessToken || ''
this.overseasExpiresAt = Number(login.overseasExpiresIn) > 0
? Date.now() + Number(login.overseasExpiresIn) * 1000 : 0
const requiresOverseas = this.options.modules.some((module) => module.startsWith('phase4-overseas-'))
if (requiresOverseas) {
this.assert('authentication', '登录响应包含有效海外访问令牌', loginResponse,
@@ -1170,6 +1199,10 @@ export class AcceptanceRun {
const handlers = { customers: () => this.runCustomers() }
for (let moduleIndex = 0; moduleIndex < this.options.modules.length; moduleIndex += 1) {
const module = this.options.modules[moduleIndex]
if (module.startsWith('phase4-overseas-')
&& this.overseasExpiresAt > 0 && Date.now() + 120_000 >= this.overseasExpiresAt) {
await this.refreshSession(`海外模块 ${module} 执行前Token临近到期`)
}
const fullFinanceChain = ['finance-full-fixtures', 'finance-reconciliation-full', 'finance-settlement-full'].every((name) => this.options.modules.includes(name))
if (module === 'finance' && fullFinanceChain) {
this.modules.push({ name: module, startedAt: new Date().toISOString(), finishedAt: new Date().toISOString(), success: true, passed: 0, failed: 0, fatal: null, skipped: 'finance-full-chain owns the finance source locks' })
@@ -1233,6 +1266,19 @@ export class AcceptanceRun {
} finally {
if (restoreFixture) restoreFixture()
}
const laterOverseasModule = this.options.modules.slice(moduleIndex + 1)
.some((name) => name.startsWith('phase4-overseas-'))
if (module === 'system' && laterOverseasModule) {
const refreshed = await this.refreshSession('system模块权限与角色变更完成')
if (refreshed) {
const probe = await this.requestOverseas('GET',
'/api/platform/v1/last-leg/orders?page=1&size=1')
this.assert('authentication', 'system后新admin海外Token只读探针可用', probe,
probe.http === 200 && String(probe.body?.code) === '200',
`HTTP=${probe.http}, code=${probe.body?.code ?? 'missing'}, requestId=${probe.requestId || 'missing'}, traceId=${probe.traceId || 'missing'}`,
{ scenario: 'dual-token-refresh', deploymentBlocker: true })
}
}
}
}
const reportSurface = JSON.stringify({
@@ -1240,7 +1286,8 @@ export class AcceptanceRun {
deploymentBlockers: this.deploymentBlockers, coverageGaps: this.coverageGaps,
createdResources: this.createdResources,
})
const forbiddenValues = [this.options.password, this.token, this.overseasToken].filter((value) => typeof value === 'string' && value.length >= 8)
const forbiddenValues = [this.options.password, this.token, this.refreshToken, this.overseasToken]
.filter((value) => typeof value === 'string' && value.length >= 8)
const sensitivePatterns = [
/"(?:password|newPassword|temporaryPassword|accessToken|refreshToken|authorization)"\s*:/i,
/\bBearer\s+[A-Za-z0-9._~-]{16,}/i,