Files
baiyue-firstleg/scripts/dispatch-workflow-extensions-test-env.mjs
2026-10-20 16:45:04 +08:00

193 lines
17 KiB
JavaScript

import { readFileSync, existsSync, writeFileSync, mkdirSync } from 'node:fs'
import { createHash } from 'node:crypto'
import { execFileSync } from 'node:child_process'
import { dirname, resolve, join } from 'node:path'
import { fileURLToPath, pathToFileURL } from 'node:url'
import { runCrossCustomer } from './fleet-cross-customer-test-env.mjs'
import { createFleetCrossCustomerFixture } from './admin-test-env-modules/fleet-cross-customer-fixture.mjs'
import { finishFleetTrip } from './admin-test-env-modules/fleet-trip-fulfillment.mjs'
import { AcceptanceRun } from './admin-test-env-acceptance.mjs'
import { fixtureContract } from './admin-test-env-modules/fleet-cross-customer-acceptance.mjs'
import { discoverSecondDestination, verifyDualDestination, verifyReviewExtension, verifyCompetitionExtension, verifyFirstCompetitionExtension, verifyRelabelExtension, reconcileRelabelShared, prepareBolAppointments, verifyBolExtension, reconcileReviewTerminal, extensionMatrix } from './admin-test-env-modules/dispatch-workflow-extension-acceptance.mjs'
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..')
const hash = bytes => createHash('sha256').update(bytes).digest('hex')
export function parseExtensionArguments(args) {
const config = { execute: false, environment: 'TEST', runId: 'plan', scenario: 'dual-destination', warehouseId: '', destinationWarehouseId: '', pdaEnvFile: '', syntheticPalletSpaces: null, resumeReport: '', resumeReportSha256: '', pdfPython: '' }
const seen = new Set()
for (let index = 0; index < args.length; index++) {
const name = args[index]
if (['--plan', '--execute'].includes(name)) {
if (seen.has('mode')) throw Error('Duplicate mode'); seen.add('mode'); config.execute = name === '--execute'; continue
}
const field = { '--environment': 'environment', '--run-id': 'runId', '--scenario': 'scenario', '--warehouse-id': 'warehouseId',
'--destination-warehouse-id': 'destinationWarehouseId', '--pda-env-file': 'pdaEnvFile', '--synthetic-pallet-spaces': 'syntheticPalletSpaces',
'--resume-report': 'resumeReport', '--resume-report-sha256': 'resumeReportSha256', '--pdf-python': 'pdfPython' }[name]
if (!field || seen.has(name) || !args[index + 1] || args[index + 1].startsWith('--')) throw Error('Unsupported/duplicate argument')
seen.add(name); config[field] = args[++index]
}
if (config.environment !== 'TEST' || !/^[A-Za-z0-9][A-Za-z0-9_-]{0,47}$/.test(config.runId)) throw Error('Explicit TEST/fresh run ID required')
if (config.execute && (!['dual-destination', 'reviews', 'competition', 'first-competition', 'relabel', 'relabel-shared', 'review-terminal', 'bol-empty', 'bol-same', 'bol-different'].includes(config.scenario) || config.runId === 'plan' || !config.pdaEnvFile
|| !/^[1-9]\d*$/.test(config.warehouseId) || !/^[1-9]\d*$/.test(config.destinationWarehouseId) || config.syntheticPalletSpaces !== '1'))
throw Error('Ready scenario, explicit warehouse/destination/PDA config and synthetic pallet-spaces=1 required')
if (['review-terminal', 'relabel-shared'].includes(config.scenario) && (!config.resumeReport || !/^[0-9a-f]{64}$/.test(config.resumeReportSha256))) throw Error('Explicit frozen owned report required')
if (!['review-terminal', 'relabel-shared'].includes(config.scenario) && (config.resumeReport || config.resumeReportSha256)
&& (!config.scenario.startsWith('bol-') || !config.resumeReport || !/^[0-9a-f]{64}$/.test(config.resumeReportSha256)))
throw Error('Resume options require an explicit frozen owned checkpoint')
if (config.execute && config.scenario.startsWith('bol-') && !config.pdfPython) throw Error('Explicit PDF reader runtime required')
return config
}
export async function runDispatchExtensions(config, env = process.env) {
if (!config.execute) return { execute: false, environment: 'TEST', matrix: extensionMatrix,
limits: 'No deploy, SQL writes, permission edits, deletion, old manual main mutation, blind retries or uncontrolled fault injection.' }
if (env.CHONGCHONG_ENV !== 'test') throw Error('Only the explicitly loaded TEST configuration is accepted')
const bol = config.scenario.startsWith('bol-')
const relabelResume = config.scenario === 'relabel-shared'
if (bol) execFileSync(config.pdfPython, ['-c', 'import pypdf'], { stdio: 'pipe', timeout: 10000 })
let prior
if (config.scenario === 'review-terminal' || relabelResume || bol && config.resumeReport) {
const sourcePath = resolve(ROOT, config.resumeReport)
const prefix = bol ? 'fleet-cross-DISPATCH20261020EXTBOL' : relabelResume ? 'fleet-cross-DISPATCH20261020EXTRELABEL' : 'fleet-cross-DISPATCH20261020EXTREV'
if (!sourcePath.startsWith(join(ROOT, 'reports', prefix))) throw Error('Only this extension-owned frozen report can be reconciled')
const bytes = readFileSync(sourcePath)
if (hash(bytes) !== config.resumeReportSha256) throw Error('Original review report changed')
prior = JSON.parse(bytes)
if (relabelResume) {
const proof = prior.createdResources?.dispatchWorkflowRelabel
if (prior.success !== false || prior.error !== "Expected values to be strictly equal:\n\n'S0500' !== '200'\n"
|| proof?.cancel?.status !== 'VERIFIED' || proof.sharedDispatches?.length !== 0 || proof.scenarios?.length !== 3
|| !proof.scenarios.every(row => row.relabelVerified === true) || prior.createdResources?.fleetWriteBoundary
|| prior.createdResources?.fleetTrips?.[0]?.closure?.status !== 'CARGO_COMPLETED_AND_RESOURCES_RELEASED'
|| prior.createdResources?.fleetCrossCustomer?.status !== 'VERIFIED') throw Error('Not the bounded owned relabel candidate read checkpoint')
} else if (bol) {
if (prior.success !== false || prior.error !== 'Expected values to be strictly equal:\n\n0 !== 1\n'
|| prior.createdResources?.dispatchWorkflowBol?.status !== 'CHECKING' || prior.createdResources.dispatchWorkflowBol.scenario !== config.scenario
|| prior.createdResources?.fleetCrossCustomerFixture?.status !== 'READY' || prior.createdResources?.fleetCrossCustomer
|| prior.createdResources?.fleetWriteBoundary || prior.createdResources?.fleetTrips?.[0]?.closure)
throw Error('Not the bounded PDF long-pallet-code assertion checkpoint')
} else if (prior.success !== false || prior.error !== 'Native main/items/PDA retain both true customers and physical pallets'
|| prior.createdResources?.dispatchWorkflowReviews?.status !== 'VERIFIED' || prior.createdResources?.fleetWriteBoundary
|| prior.createdResources?.fleetTrips?.[0]?.closure) throw Error('Not the bounded known terminal-read race')
for (let number = 1; number <= prior.requestCount; number++)
if (JSON.parse(readFileSync(join(dirname(sourcePath), 'requests', `${number}.json`))).stage !== 'RESPONSE') throw Error('Prior request outcome uncertain; terminal mode forbidden')
}
const reportDirectory = join(ROOT, 'reports', `fleet-cross-${config.runId}`)
const intentPath = join(ROOT, 'reports', `dispatch-workflow-${config.runId}.intent.json`)
if (existsSync(reportDirectory) || existsSync(intentPath)) throw Error('Previous attempt exists; retain original requests and reconcile, never re-enter')
const pda = Object.fromEntries(readFileSync(config.pdaEnvFile, 'utf8').replace(/^\uFEFF/, '').split(/\r?\n/)
.filter(line => /^[A-Z][A-Z0-9_]*=/.test(line)).map(line => { const separator = line.indexOf('=');
return [line.slice(0, separator), line.slice(separator + 1).trim().replace(/^["']|["']$/g, '')] }))
if (!pda.PDA_USERNAME || !pda.PDA_PASSWORD) throw Error('Missing authorized existing overseas PDA credentials')
const files = ['scripts/dispatch-workflow-extensions-test-env.mjs', 'scripts/admin-test-env-modules/dispatch-workflow-extension-acceptance.mjs',
'scripts/admin-test-env-modules/fleet-cross-customer-fixture.mjs', 'scripts/fleet-cross-customer-test-env.mjs',
'scripts/admin-test-env-acceptance.mjs', 'scripts/admin-test-env-modules/fleet-cross-customer-acceptance.mjs', 'scripts/admin-test-env-modules/fleet-trip-fulfillment.mjs',
'scripts/admin-test-env-modules/merchant-qualification-fixture.mjs', 'scripts/overseas-relabel-real-acceptance.mjs']
const seal = Object.fromEntries(files.map(path => [path, hash(readFileSync(join(ROOT, path)))]))
const stable = () => { for (const [path, expected] of Object.entries(seal)) if (hash(readFileSync(join(ROOT, path))) !== expected) throw Error(`Source drift: ${path}`) }
mkdirSync(join(ROOT, 'reports'), { recursive: true })
writeFileSync(intentPath, JSON.stringify({ runId: config.runId, scenario: config.scenario, environment: 'TEST', seal,
createdAt: new Date().toISOString(), syntheticTestMaterials: true, physicalMeasurementVerified: false, hardwareVerified: false,
originalManualMain: 'READ_ONLY_NOT_USED', resumeReportSha256: prior ? config.resumeReportSha256 : null,
midTransactionRollback: 'NOT_TESTED_NO_CONTROLLED_INJECTION' }, null, 2), { flag: 'wx' })
const result = await runCrossCustomer(config, {
env: { ...env, DISPATCH_EXISTING_PDA_PASSWORD: pda.PDA_PASSWORD },
contextFactory: options => { const context = new AcceptanceRun(options); if (prior) context.marker = prior.marker; return context },
pickerCreate: async context => {
context.createdResources.extensionIdentity = { mode: 'EXISTING_TEST_PDA_OWNED_NEW_SESSION', accountMutation: false }
const response = await context.request('POST', '/api/auth/login', { loginId: pda.PDA_USERNAME, password: pda.PDA_PASSWORD }, '')
if (response.http !== 200 || String(response.body?.code) !== '200' || !response.body.data?.accessToken || !response.body.data.refreshToken) throw Error('Existing PDA login failed')
return { membershipId: null, password: pda.PDA_PASSWORD, token: response.body.data.accessToken, refreshToken: response.body.data.refreshToken }
},
pickerDisable: async () => { throw Error('Existing identity must never be disabled') },
prepare: async (context, options) => {
stable(); context.createdResources.extensionSourceSeal = seal
if (prior) {
const tripProof = structuredClone(prior.createdResources.fleetTrips[0])
context.createdResources.fleetTrips = [tripProof]
context.createdResources[bol ? 'explicitBolCheckpoint' : relabelResume ? 'explicitRelabelCheckpoint' : 'explicitReviewTerminal'] = { sourceReport: config.resumeReport, sourceReportSha256: config.resumeReportSha256,
originalFailurePreserved: true, fulfillmentReplay: false, fixtureCreationReplay: false }
if (bol) {
context.createdResources.dispatchWorkflowBolAppointments = structuredClone(prior.createdResources.dispatchWorkflowBolAppointments)
context.createdResources.fleetCrossCustomerFixture = structuredClone(prior.createdResources.fleetCrossCustomerFixture)
return { marker: prior.marker, stage: fixtureContract.stage, dispatchNo: tripProof.dispatchNo, tripNo: tripProof.tripNo,
warehouseId: config.warehouseId, pdaToken: options.pdaToken, tripProof,
members: structuredClone(prior.createdResources.fleetCrossCustomerFixture.members),
loadingMeasurements: structuredClone(prior.createdResources.extensionSyntheticLoadingInput) }
}
const members = prior.createdResources.fleetCrossCustomerFixture.members.map(member => {
const match = prior.createdResources.fleetCrossCustomer.members.find(row => row.customerId === member.customerId)
if (!match) throw Error('Original receipt/member attribution missing')
return { ...structuredClone(member), ...structuredClone(match) }
})
return { marker: prior.marker, stage: fixtureContract.stage, dispatchNo: tripProof.dispatchNo, tripNo: tripProof.tripNo,
warehouseId: config.warehouseId, pdaToken: options.pdaToken, tripProof, members }
}
if (bol) {
const master = await context.request('GET', `/api/platform/v1/warehouse-admin/${encodeURIComponent(config.destinationWarehouseId)}?tenantId=0`)
const timezone = master.body?.data?.warehouse?.timezone
if (master.http !== 200 || String(master.body?.code) !== '200' || !/^[A-Za-z_]+\/[A-Za-z_]+(?:\/[A-Za-z_]+)?$/.test(timezone || ''))
throw Error('BOL appointment fixtures require an existing FBA destination with an actual regional timezone before any fixture write')
}
const secondId = bol ? config.destinationWarehouseId : await discoverSecondDestination(context, config.destinationWarehouseId)
const fixture = await createFleetCrossCustomerFixture(context, { ...options, destinationWarehouseIds: [config.destinationWarehouseId, secondId],
...(bol ? { shipmentReferences: ['A', 'B'].map(suffix => `TEST-FBA-${context.marker.slice(-12)}-${suffix}`),
prepareBatchAllocations: members => prepareBolAppointments(context, members, config.scenario, config.warehouseId) } : {}) })
fixture.loadingMeasurements = { environment: 'TEST', marker: context.marker, dispatchNo: fixture.dispatchNo, syntheticTestMaterials: true,
physicalMeasurementVerified: false, hardwareVerified: false, pallets: fixture.members.map(member => ({ warehousePalletId: member.palletId,
palletSpaces: 1, evidence: [{ field: 'palletSpaces', value: 1, actual: true, sourceType: 'TEST_SYNTHETIC_LOADING_INPUT',
sourceBusinessNo: config.runId, observedAt: new Date().toISOString() }] })) }
context.createdResources.extensionSyntheticLoadingInput = fixture.loadingMeasurements
await context.checkpoint(); stable(); return fixture
},
verify: async (...args) => {
stable()
if (relabelResume) return reconcileRelabelShared(...args, prior)
if (prior && !bol) return reconcileReviewTerminal(...args, prior)
if (bol) {
const [context, fixture, options] = args
return verifyBolExtension(context, fixture, { ...options, bol: config.scenario, inspectBol: async () => {
const evidence = join(reportDirectory, 'evidence'), pdfPath = join(evidence, 'BOL.pdf'), journal = join(evidence, 'bol-http.json')
const path = `/api/platform/v1/last-leg/dispatches/${encodeURIComponent(fixture.dispatchNo)}/prints/bol`
const record = { method: 'GET', path, stage: 'INTENT', numberOfBinaryRequests: 1 }
writeFileSync(journal, JSON.stringify(record, null, 2), { flag: 'wx' }); await context.checkpoint()
const response = await context.requestBinary('GET', path)
Object.assign(record, { stage: 'RESPONSE', http: response.http, contentType: response.contentType,
requestId: response.requestId, traceId: response.traceId, bytes: response.bytes.length, sha256: hash(response.bytes) })
writeFileSync(journal, JSON.stringify(record, null, 2))
if (response.http !== 200 || !response.contentType.includes('application/pdf') || Buffer.from(response.bytes).subarray(0, 5).toString() !== '%PDF-')
throw Error('Actual BOL endpoint did not return a PDF; preserve response metadata')
writeFileSync(pdfPath, response.bytes, { flag: 'wx' })
const result = JSON.parse(execFileSync(config.pdfPython, ['-c',
'import json,sys; from pypdf import PdfReader; r=PdfReader(sys.argv[1]); print(json.dumps({"pages":[p.extract_text() for p in r.pages]}))', pdfPath],
{ encoding: 'utf8', timeout: 30000 }))
writeFileSync(join(evidence, 'bol-pages.json'), JSON.stringify(result, null, 2), { flag: 'wx' })
context.createdResources.dispatchWorkflowBol.pdf = { path: 'evidence/BOL.pdf', ...record, pageCount: result.pages.length }
await context.checkpoint(); return result.pages
} })
}
return (config.scenario === 'reviews' ? verifyReviewExtension : config.scenario === 'competition' ? verifyCompetitionExtension
: config.scenario === 'first-competition' ? verifyFirstCompetitionExtension
: config.scenario === 'relabel' ? verifyRelabelExtension : verifyDualDestination)(...args)
},
finish: async (...args) => {
stable()
if (relabelResume) {
if (args[2]?.closure?.status !== 'CARGO_COMPLETED_AND_RESOURCES_RELEASED') throw Error('Original normal trip closure missing')
return args[2].closure
}
return finishFleetTrip(...args)
},
})
return { success: result.success, error: result.error, runId: config.runId, requestCount: result.requestCount, summary: result.summary,
sessionsClosed: result.sessionsClosed, scenario: config.scenario, extensionStatus: result.createdResources?.dispatchWorkflowExtensions?.status,
reviewStatus: result.createdResources?.dispatchWorkflowReviews?.status,
competitionStatus: result.createdResources?.dispatchWorkflowCompetition?.status,
bolStatus: result.createdResources?.dispatchWorkflowBol?.status,
relabelStatus: result.createdResources?.dispatchWorkflowRelabel?.status,
report: join(reportDirectory, 'report.json'), syntheticTestMaterials: true, hardwareVerified: false, physicalMeasurementVerified: false }
}
if (process.argv[1] && import.meta.url === pathToFileURL(resolve(process.argv[1])).href) {
const result = await runDispatchExtensions(parseExtensionArguments(process.argv.slice(2)))
console.log(JSON.stringify(result)); if (result.success === false) process.exitCode = 1
}