Files
baiyue-firstleg/scripts/fleet-quotes-test-env.mjs

131 lines
8.3 KiB
JavaScript

import { open, mkdir, readFile } from 'node:fs/promises'
import { resolve, dirname } from 'node:path'
import { fileURLToPath, pathToFileURL } from 'node:url'
import { parseEnv } from 'node:util'
import { createHash } from 'node:crypto'
import { AcceptanceRun } from './admin-test-env-acceptance.mjs'
import { runQuoteSession } from './admin-test-env-modules/fleet-quotes-operations.mjs'
export const READY_COMMIT = 'a6fd0ba3'
export const MARKER = 'FLEET-QUOTE-20261003'
const repository = resolve(dirname(fileURLToPath(import.meta.url)), '..')
const outputDirectory = resolve(repository, 'reports/fleet-quote-live-20261003')
const sensitive = /password|token|authorization|cookie|secret|loginId|username/i
export function requireTestProfile(args, execArgs) {
if (args.length !== 1 || args[0] !== `--ready-commit=${READY_COMMIT}`) throw new Error('Explicit current ready-commit required; no resume or replay mode')
const files = []
for (let i = 0; i < execArgs.length; i++) {
if (execArgs[i].startsWith('--env-file=')) files.push(execArgs[i].slice(11))
else if (execArgs[i] === '--env-file') files.push(execArgs[++i])
else throw new Error('Only the explicit TEST env-file runtime option is allowed')
}
if (files.length !== 1 || !files[0] || !resolve(files[0]).replaceAll('\\', '/').endsWith('/.codex/env/chongchong-global/test-acceptance.env')) throw new Error('Explicit authorized TEST profile required')
return resolve(files[0])
}
/** Append-only, fsync-before-send evidence. A fixed exclusive claim prohibits automatic reruns even after a crash. */
export async function installQuoteJournal(context, directory, { persist, secrets = [] } = {}) {
let sequence = 0, halted = false
const knownSecrets = new Set(secrets.filter(Boolean))
function discover(value) {
if (!value || typeof value !== 'object') return
for (const [key, child] of Object.entries(value)) {
if (sensitive.test(key) && typeof child === 'string' && child) knownSecrets.add(child)
else if (child && typeof child === 'object') discover(child)
}
}
function clean(value) {
discover(value)
if (typeof value === 'string') {
let result = value
for (const secret of knownSecrets) result = result.split(secret).join('[REDACTED]')
return result
}
if (Array.isArray(value)) return value.map(clean)
if (value && typeof value === 'object') return Object.fromEntries(Object.entries(value).map(([key, child]) => [key, sensitive.test(key) ? '[REDACTED]' : clean(child)]))
return value
}
const save = persist || (async (name, value) => {
const file = await open(resolve(directory, name), 'wx', 0o600)
try { await file.writeFile(`${JSON.stringify(value, null, 2)}\n`, 'utf8'); await file.sync() } finally { await file.close() }
})
async function checkpoint(kind, value) {
const name = `${String(++sequence).padStart(5, '0')}-${kind}.json`
await save(name, clean({ marker: MARKER, at: new Date().toISOString(), ...value }))
return name
}
await checkpoint('claim', { expectedCommit: READY_COMMIT, scope: 'QUOTES_ONLY', status: 'CLAIMED', rerun: 'FORBIDDEN; reconcile original samples manually' })
async function bodyEvidence(body) {
if (!(body instanceof FormData)) return body === undefined ? { encoding: 'absent' } : { encoding: 'json', value: structuredClone(body) }
const parts = []
for (const [name, value] of body.entries()) {
if (typeof value === 'string') parts.push({ name, value })
else {
const bytes = Buffer.from(await value.arrayBuffer())
parts.push({ name, filename: value.name, contentType: value.type, size: bytes.length, sha256: createHash('sha256').update(bytes).digest('hex'), base64: bytes.toString('base64') })
}
}
return { encoding: 'multipart', parts }
}
for (const name of ['request', 'requestWithBody', 'requestBinary']) {
const original = context[name].bind(context)
context[name] = async (method, path, ...rest) => {
method = String(method).toUpperCase()
const logout = method === 'POST' && path === '/api/auth/logout'
if (halted && !logout) throw new Error('Unknown request outcome: halted; original sample retained, no retry or cleanup')
const body = name === 'requestBinary' ? undefined : rest[0]
discover(body)
const request = { method, path, transport: name, body: await bodyEvidence(body), headers: name === 'requestBinary' ? {} : rest[2] || {}, idempotencyKey: body?.idempotencyKey ?? null }
let sample
try { sample = await checkpoint('before', request) } catch (error) { halted = true; context.quoteRequestOutcomeUnknown = true; throw error }
let response
try { response = await original(method, path, ...rest) }
catch { response = { http: 0, body: null, requestId: null, traceId: null } }
discover(response?.body)
const code = String(response?.body?.code ?? '')
const write = !['GET', 'HEAD'].includes(method)
const known = response?.http === 200 && (code === '200' || /^A04\d+$/.test(code)) || response?.http >= 400 && response?.http < 500 && response?.http !== 408
const unknown = write && !known
if (unknown) { halted = true; context.quoteRequestOutcomeUnknown = true }
try {
await checkpoint('after', { sample, method, path, outcome: unknown ? 'UNKNOWN_STOP' : 'RECEIVED', http: response?.http, code, requestId: response?.requestId, traceId: response?.traceId,
// Only write responses belong to our isolated fixture. Do not persist unrelated directory records.
body: write ? response?.body : undefined, createdResources: context.createdResources, steps: context.steps })
} catch (error) { halted = true; context.quoteRequestOutcomeUnknown = true; throw error }
if (unknown) throw new Error('Unknown write outcome; reconcile persisted original request before further business actions')
return response
}
}
const executeModule = context.executeModule.bind(context)
context.executeModule = async (name, handler) => {
await checkpoint('module-start', { module: name, createdResources: context.createdResources })
try { return await executeModule(name, handler) }
finally { await checkpoint('module-end', { module: name, createdResources: context.createdResources, steps: context.steps, modules: context.modules }) }
}
return { checkpoint, unknown: () => halted }
}
export async function execute(args = process.argv.slice(2), execArgs = process.execArgv) {
const file = requireTestProfile(args, execArgs)
if (!repository.replaceAll('\\', '/').endsWith('/chongchong-global/baiyue-firstleg')) throw new Error('Only registered TEST workspace is allowed')
const env = parseEnv(await readFile(file, 'utf8'))
if (env.CHONGCHONG_ENV !== 'test' || !env.CHONGCHONG_ADMIN_USERNAME || !env.CHONGCHONG_ADMIN_PASSWORD) throw new Error('TEST credentials missing')
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('Invalid unified TEST API base')
await mkdir(outputDirectory, { recursive: true })
const context = new AcceptanceRun({ environment: 'test', gateway: base.origin, username: env.CHONGCHONG_ADMIN_USERNAME, password: env.CHONGCHONG_ADMIN_PASSWORD, quiet: true, modules: [] })
const journal = await installQuoteJournal(context, outputDirectory, { secrets: [env.CHONGCHONG_ADMIN_USERNAME, env.CHONGCHONG_ADMIN_PASSWORD] })
let report
try { report = await runQuoteSession(context, { marker: MARKER }) }
catch { report = { success: false, fatal: 'Session interrupted; inspect durable request checkpoints', operationsStatus: 'NOT_TESTED' } }
finally { context.token = ''; context.refreshToken = '' }
const success = Boolean(report.success) && !journal.unknown()
await journal.checkpoint('final', { ...report, success, outcome: journal.unknown() ? 'UNKNOWN_STOP' : success ? 'VERIFIED' : 'FAILED', createdResources: context.createdResources })
return { outputDirectory, success, realExecution: true }
}
if (process.argv[1] && import.meta.url === pathToFileURL(resolve(process.argv[1])).href) {
execute().then(result => { console.log(JSON.stringify(result)); process.exitCode = result.success ? 0 : 1 })
.catch(() => { console.error('报价验收未启动或已中止;核对TEST配置、就绪标记和持久检查点,禁止自动重跑。'); process.exitCode = 1 })
}