This commit is contained in:
2026-10-08 17:07:57 +08:00
parent 16d2d9111a
commit c1ddb658bd
111 changed files with 2914 additions and 941 deletions
+35 -15
View File
@@ -3,8 +3,9 @@ import { execFileSync } from 'node:child_process'
import { dirname, resolve, join } from 'node:path'
import { fileURLToPath, pathToFileURL } from 'node:url'
import { createHash } from 'node:crypto'
import { AcceptanceRun, parseArguments, validateExecutionOptions, buildAcceptanceSummary } from './admin-test-env-acceptance.mjs'
import { AcceptanceRun, parseArguments, validateExecutionOptions, buildAcceptanceSummary, createFleetPoolGrayOptions } from './admin-test-env-acceptance.mjs'
import { createTestEvidenceReaders } from './fba-core-regression-evidence.mjs'
import { createGrayEvidenceReaders } from './gray-regression-evidence.mjs'
import { redactEvidence } from './fba-core-regression-test-env.mjs'
import { resolvePickerAccess, createPicker, disablePicker } from './admin-test-env-modules/phase4-overseas-pda.mjs'
import { verifyFleetCrossCustomer } from './admin-test-env-modules/fleet-cross-customer-acceptance.mjs'
@@ -38,20 +39,36 @@ export function crossCustomerPlan(config) {
limits: 'Prerequisite rejection is not mid-transaction rollback. No deployment, configuration change, SQL writes or automatic rerun. Retain business samples; disable temporary PDA in finally.' }
}
export async function runCrossCustomer(config, { env = process.env, outputRoot = ROOT, readers, prepare, verify = verifyFleetCrossCustomer,
finish = finishFleetTrip, contextFactory = options => new AcceptanceRun(options), pickerAccess = resolvePickerAccess, pickerCreate = createPicker, pickerDisable = disablePicker } = {}) {
const plan = crossCustomerPlan(config)
export async function runCrossCustomer(config, dependencies = {}) {
return runCrossCustomerCore(config, dependencies, false)
}
/** Not a CLI mode: called only by the explicit GRAY pool entry with its verified pool scenario. */
export async function runFleetPoolGrayCrossCustomer(config, dependencies = {}) {
if (Object.keys(dependencies).some(key => !['env', 'outputRoot', 'verify', 'contextFactory'].includes(key)))
throw Error('Gray pool cannot override evidence readers or fixture lifecycle')
if (config.execute && (typeof dependencies.verify !== 'function' || typeof dependencies.contextFactory !== 'function'))
throw Error('Gray pool requires its dedicated scenario and journaled context')
return runCrossCustomerCore(config, dependencies, true)
}
async function runCrossCustomerCore(config, { env = process.env, outputRoot = ROOT, readers, prepare, verify = verifyFleetCrossCustomer,
finish = finishFleetTrip, contextFactory = options => new AcceptanceRun(options), pickerAccess = resolvePickerAccess, pickerCreate = createPicker, pickerDisable = disablePicker } = {}, gray = false) {
const environment = gray ? 'GRAY' : 'TEST', reportName = `${gray ? 'fleet-pool-gray' : 'fleet-cross'}-${config.runId}`
const plan = { ...crossCustomerPlan(config), environment, output: `reports/${reportName}`,
...(gray ? { scope: 'FLEET_CAPACITY_POOL_GRAY', stages: ['current pool fixture and full pool HTTP matrix', 'trip completion and separate resource release'] } : {}) }
if (!config.execute) return plan
if (config.environment !== 'TEST' || env.CHONGCHONG_ENV !== 'test' || !/^[A-Za-z0-9][A-Za-z0-9_-]{0,47}$/.test(config.runId)
if (config.environment !== environment || !(gray ? ['gray', 'prod'].includes(env.CHONGCHONG_ENV) : env.CHONGCHONG_ENV === 'test') || !/^[A-Za-z0-9][A-Za-z0-9_-]{0,47}$/.test(config.runId)
|| config.runId === 'plan' || !/^[1-9][0-9]{0,19}$/.test(config.warehouseId) || !/^[1-9][0-9]{0,19}$/.test(config.destinationWarehouseId)
|| !env.CHONGCHONG_ADMIN_USERNAME || !env.CHONGCHONG_ADMIN_PASSWORD) throw Error('Explicit TEST configuration required')
|| !env.CHONGCHONG_ADMIN_USERNAME || !env.CHONGCHONG_ADMIN_PASSWORD) throw Error(`Explicit ${environment} configuration required`)
const base = new URL(env.CHONGCHONG_ADMIN_API_BASE_URL)
if (!['http:', 'https:'].includes(base.protocol) || base.username || base.password || base.search || base.hash || !['/api', '/api/'].includes(base.pathname)) throw Error('Invalid TEST gateway')
readers ||= createTestEvidenceReaders(env)
if (!(gray ? base.protocol === 'https:' : ['http:', 'https:'].includes(base.protocol)) || base.username || base.password || base.search || base.hash || !['/api', '/api/'].includes(base.pathname)) throw Error(`Invalid ${environment} gateway`)
if (gray) readers = createGrayEvidenceReaders(env)
else readers ||= createTestEvidenceReaders(env)
// Load the reviewed producer before creating any resources.
prepare ||= (await import('./admin-test-env-modules/fleet-cross-customer-fixture.mjs')).createFleetCrossCustomerFixture
if (typeof prepare !== 'function' || typeof readers.readFleetInventory !== 'function') throw Error('Fixture producer and inventory reader required')
const reports = resolve(outputRoot, 'reports'), output = join(reports, `fleet-cross-${config.runId}`)
const reports = resolve(outputRoot, 'reports'), output = join(reports, reportName)
mkdirSync(reports, { recursive: true })
const lockPath = join(reports, '.fleet-acceptance.lock'), lock = openSync(lockPath, 'wx')
let context, picker, createdPicker, initialized = false, report, evidenceIndex = 0, requestIndex = 0
@@ -61,14 +78,15 @@ export async function runCrossCustomer(config, { env = process.env, outputRoot =
try {
writeFileSync(lock, JSON.stringify({ pid: process.pid, runId: config.runId }))
mkdirSync(output); initialized = true; mkdirSync(join(output, 'evidence')); mkdirSync(join(output, 'requests'))
const sources = ['scripts/fleet-cross-customer-test-env.mjs', 'scripts/fleet-inventory-evidence.mjs', 'scripts/fba-core-regression-evidence.mjs',
const sources = ['scripts/fleet-cross-customer-test-env.mjs', 'scripts/fleet-inventory-evidence.mjs', gray ? 'scripts/gray-regression-evidence.mjs' : 'scripts/fba-core-regression-evidence.mjs',
'scripts/admin-test-env-modules/fleet-cross-customer-fixture.mjs', 'scripts/admin-test-env-modules/fleet-cross-customer-acceptance.mjs',
'scripts/admin-test-env-modules/fleet-trip-fulfillment.mjs'].map(path => ({ path, sha256: createHash('sha256').update(readFileSync(join(ROOT, path))).digest('hex') }))
save('manifest.json', { ...plan, sourceCommit: execFileSync('git', ['rev-parse', 'HEAD'], { cwd: ROOT, encoding: 'utf8' }).trim(), sources })
save('manifest.json', { ...plan, runtimeProfile: env.CHONGCHONG_ENV, sourceCommit: execFileSync('git', ['rev-parse', 'HEAD'], { cwd: ROOT, encoding: 'utf8' }).trim(), sources })
await readers.preflight()
const options = validateExecutionOptions({ ...parseArguments(['--modules', 'merchant-qualification-fixture', '--quiet', 'true']),
environment: 'test', gateway: base.origin, username: env.CHONGCHONG_ADMIN_USERNAME, password: env.CHONGCHONG_ADMIN_PASSWORD,
markerNamespace: createHash('sha256').update(config.runId).digest('hex').slice(0, 12) })
const transport = { gateway: base.origin, username: env.CHONGCHONG_ADMIN_USERNAME, password: env.CHONGCHONG_ADMIN_PASSWORD,
output, markerNamespace: createHash('sha256').update(`${environment}:${config.runId}`).digest('hex').slice(0, 12) }
const options = gray ? createFleetPoolGrayOptions(transport) : validateExecutionOptions({
...parseArguments(['--modules', 'merchant-qualification-fixture', '--quiet', 'true']), ...transport, environment: 'test' })
context = contextFactory(options)
for (const method of ['request', 'requestWithBody']) {
const original = context[method]
@@ -92,7 +110,7 @@ export async function runCrossCustomer(config, { env = process.env, outputRoot =
}
}
const login = await context.request('POST', '/api/auth/login', { loginId: options.username, password: options.password }, '')
if (!ok(login) || !login.body.data?.accessToken) throw Error('TEST administrator login failed')
if (!ok(login) || !login.body.data?.accessToken) throw Error(`${environment} administrator login failed`)
context.token = login.body.data.accessToken; context.refreshToken = login.body.data.refreshToken
const warehouses = await context.request('GET', '/api/platform/v1/last-leg/fleet/drivers/warehouse-options?region=OVERSEAS')
if (!ok(warehouses) || !warehouses.body.data?.some(row => String(row.id) === config.warehouseId)) throw Error('Requested warehouse not available')
@@ -137,6 +155,8 @@ export async function runCrossCustomer(config, { env = process.env, outputRoot =
report.marker = context.marker; report.createdResources = context.createdResources; report.steps = context.steps
}
report ||= { success: false }
report.environment = environment; report.runtimeProfile = env.CHONGCHONG_ENV
if (gray) report.scope = 'FLEET_CAPACITY_POOL_GRAY'
report.requestCount = requestIndex; report.inventorySnapshots = evidenceIndex; report.goalComplete = false
try { if (initialized) save('report.json', report) } finally { closeSync(lock); unlinkSync(lockPath) }
}