103 lines
7.7 KiB
JavaScript
103 lines
7.7 KiB
JavaScript
import { readFile, mkdir, writeFile } from 'node:fs/promises'
|
|
import { parseEnv } from 'node:util'
|
|
import { resolve, dirname } from 'node:path'
|
|
import { pathToFileURL, fileURLToPath } from 'node:url'
|
|
import { AcceptanceRun } from './admin-test-env-acceptance.mjs'
|
|
import { run as verifyCompanyCost } from './admin-test-env-modules/fleet-company-cost-acceptance.mjs'
|
|
|
|
export const MARKER = 'FLEET-COST-20261003'
|
|
// Production source anchor, not a deployment-health assertion. The coordinator must independently
|
|
// verify the deployed commit (including any descendant) and service readiness before passing this exact anchor.
|
|
// This gate intentionally does not infer ancestry, compare deployed source, or perform network checks.
|
|
export const READY_COMMIT = '8700fc34238bc33339351c73c15f3af8de8ce517'
|
|
export function requireReady(args, execArgs) {
|
|
if (!args.includes(`--ready-commit=${READY_COMMIT}`)) throw new Error('等待总控确认目标服务就绪后,才可显式传入本轮ready-commit')
|
|
return requireTestProfile(execArgs)
|
|
}
|
|
export function requireTestProfile(execArgs) {
|
|
const joined = execArgs.find(value => value.startsWith('--env-file='))
|
|
const index = execArgs.indexOf('--env-file')
|
|
const file = joined?.slice('--env-file='.length) || (index >= 0 ? execArgs[index + 1] : '')
|
|
if (!file || !resolve(file).replaceAll('\\', '/').endsWith('/.codex/env/chongchong-global/test-acceptance.env'))
|
|
throw new Error('须在脚本前显式指定授权目录的test-acceptance.env,禁止复用其他环境或任务会话文件')
|
|
return resolve(file)
|
|
}
|
|
|
|
/** Explicit single-task runner. It does not expand other acceptance modules or reuse their resources/sessions. */
|
|
export async function execute(args = process.argv.slice(2), execArgs = process.execArgv) {
|
|
const preflight = args.includes('--preflight')
|
|
const envFile = preflight ? requireTestProfile(execArgs) : requireReady(args, execArgs)
|
|
const env = parseEnv(await readFile(envFile, 'utf8'))
|
|
if (env.CHONGCHONG_ENV !== 'test' || !env.CHONGCHONG_ADMIN_USERNAME || !env.CHONGCHONG_ADMIN_PASSWORD) throw new Error('TEST身份配置缺失')
|
|
const base = new URL(env.CHONGCHONG_ADMIN_API_BASE_URL)
|
|
if (!['http:', 'https:'].includes(base.protocol) || base.pathname.replace(/\/$/, '') !== '/api' || base.username || base.password || base.search || base.hash)
|
|
throw new Error('TEST统一API基础地址格式与当前验收合同不符')
|
|
const repository = resolve(dirname(fileURLToPath(import.meta.url)), '..')
|
|
if (!repository.replaceAll('\\', '/').endsWith('/chongchong-global/baiyue-firstleg')) throw new Error('公司成本验收只允许在登记的TEST项目运行')
|
|
const output = resolve(repository, `reports/fleet-cost-live-20261003/${preflight ? `preflight-${new Date().toISOString().replace(/\D/g, '').slice(0,17)}` : 'api-report'}.json`)
|
|
await mkdir(dirname(output), { recursive: true })
|
|
const context = new AcceptanceRun({ gateway: base.origin, quiet: true, modules: [] })
|
|
context.marker = MARKER
|
|
const report = { marker: MARKER, mode: preflight ? 'READ_ONLY_PREFLIGHT' : 'BUSINESS_ACCEPTANCE', expectedCommit: READY_COMMIT, status: 'PREPARING', startedAt: new Date().toISOString(), session: { login: 'NOT_STARTED', logout: 'NOT_STARTED' }, createdResources: context.createdResources, steps: context.steps }
|
|
let refresh = '', failure
|
|
const secrets = [env.CHONGCHONG_ADMIN_PASSWORD]
|
|
async function checkpoint(first = false) {
|
|
const value = JSON.stringify(report, null, 2)
|
|
if (secrets.some(secret => secret && secret.length >= 8 && value.includes(secret))) throw new Error('报告敏感信息检查失败,停止输出')
|
|
await writeFile(output, `${value}\n`, { encoding: 'utf8', flag: first ? 'wx' : 'w' })
|
|
}
|
|
await checkpoint(true) // Refuse automatic repeat runs over any prior evidence.
|
|
context.checkpoint = checkpoint
|
|
try {
|
|
const response = await context.request('POST', '/api/auth/login', { loginId: env.CHONGCHONG_ADMIN_USERNAME, password: env.CHONGCHONG_ADMIN_PASSWORD }, '')
|
|
report.session.login = response?.http === 200 && String(response?.body?.code) === '200' ? 'SUCCESS' : 'FAILED'
|
|
report.session.loginDiagnostic = { http: response?.http, code: response?.body?.code, requestId: response?.requestId, traceId: response?.traceId }
|
|
if (report.session.login !== 'SUCCESS' || !response.body.data?.accessToken || !response.body.data?.refreshToken) throw new Error('独立TEST登录未成功,未执行业务写入')
|
|
context.token = response.body.data.accessToken; refresh = response.body.data.refreshToken
|
|
secrets.push(context.token, refresh)
|
|
report.status = 'RUNNING'; await checkpoint()
|
|
if (preflight) {
|
|
const checks = [
|
|
['permissions', '/api/platform/v1/iam/authorization/permissions'],
|
|
['suppliers', '/api/platform/v1/suppliers?page=1&size=1&status=ACTIVE'],
|
|
['exchange', `/api/platform/v1/last-leg/master-data/exchange-rates/${new Date().toISOString().slice(0,7)}`],
|
|
]
|
|
report.preflight = {}
|
|
for (const [name, path] of checks) {
|
|
const checked = await context.request('GET', path)
|
|
report.preflight[name] = { http: checked?.http, code: checked?.body?.code, requestId: checked?.requestId, traceId: checked?.traceId }
|
|
if (checked?.http !== 200 || String(checked?.body?.code) !== '200') throw new Error(`只读预检未通过:${name}`)
|
|
if (name === 'permissions') {
|
|
const required = ['platform:supplier:create', 'platform:supplier:list', 'platform:overseas-master-data:vehicle:list',
|
|
'platform:overseas-master-data:vehicle:create', 'platform:overseas-master-data:vehicle:status',
|
|
'platform:overseas-master-data:vehicle:record-create', 'platform:overseas-master-data:vehicle:record-edit',
|
|
'platform:overseas-master-data:vehicle:record-finance-submit', 'platform:finance:customer-arap:list',
|
|
...['list','approve','void','pay','upload'].map(action => `platform:finance:vehicle-cost:${action}`)]
|
|
if (!Array.isArray(checked.body.data)) throw new Error('权限快照格式不匹配')
|
|
report.preflight.missingPermissions = required.filter(code => !checked.body.data.includes(code))
|
|
if (report.preflight.missingPermissions.length) throw new Error('公司成本验收所需权限缺失')
|
|
}
|
|
if (name === 'exchange') report.preflight.exchange.active = checked.body.data?.status === 'ACTIVE'
|
|
await checkpoint()
|
|
}
|
|
} else await verifyCompanyCost(context)
|
|
report.status = 'VERIFIED'
|
|
} catch (error) { failure = error; report.status = 'FAILED'; report.error = error instanceof Error ? error.message : String(error) }
|
|
finally {
|
|
if (refresh) {
|
|
const response = await context.request('POST', '/api/auth/logout', { refreshToken: refresh })
|
|
report.session.logout = response?.http === 200 && String(response?.body?.code) === '200' ? 'SUCCESS' : 'FAILED'
|
|
report.session.logoutDiagnostic = { http: response?.http, code: response?.body?.code, requestId: response?.requestId, traceId: response?.traceId }
|
|
if (report.session.logout !== 'SUCCESS') { report.status = 'FAILED'; failure ??= new Error('本任务独立TEST会话退出未确认') }
|
|
}
|
|
context.token = ''; context.refreshToken = ''; refresh = ''
|
|
report.finishedAt = new Date().toISOString()
|
|
await checkpoint()
|
|
}
|
|
return { output, status: report.status, failed: Boolean(failure), error: failure ? '验收失败,按报告中的原请求核对;未自动重新造数' : null }
|
|
}
|
|
if (process.argv[1] && import.meta.url === pathToFileURL(resolve(process.argv[1])).href) {
|
|
execute().then(result => { console.log(JSON.stringify(result)); process.exitCode = result.failed ? 1 : 0 })
|
|
.catch(() => { console.error('公司成本验收未启动或报告保存失败;请核对就绪标记、TEST配置及既有报告,未输出凭据'); process.exitCode = 1 })
|
|
}
|