2026-10-20 15:48:38 +08:00
|
|
|
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'
|
2026-10-20 16:45:04 +08:00
|
|
|
import { discoverSecondDestination, verifyDualDestination, verifyReviewExtension, verifyCompetitionExtension, verifyFirstCompetitionExtension, verifyRelabelExtension, reconcileRelabelShared, prepareBolAppointments, verifyBolExtension, reconcileReviewTerminal, extensionMatrix } from './admin-test-env-modules/dispatch-workflow-extension-acceptance.mjs'
|
2026-10-20 15:48:38 +08:00
|
|
|
|
|
|
|
|
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')
|
2026-10-20 16:45:04 +08:00
|
|
|
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
|
2026-10-20 15:48:38 +08:00
|
|
|
|| !/^[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')
|
2026-10-20 16:45:04 +08:00
|
|
|
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)
|
2026-10-20 15:48:38 +08:00
|
|
|
&& (!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-')
|
2026-10-20 16:45:04 +08:00
|
|
|
const relabelResume = config.scenario === 'relabel-shared'
|
2026-10-20 15:48:38 +08:00
|
|
|
if (bol) execFileSync(config.pdfPython, ['-c', 'import pypdf'], { stdio: 'pipe', timeout: 10000 })
|
|
|
|
|
let prior
|
2026-10-20 16:45:04 +08:00
|
|
|
if (config.scenario === 'review-terminal' || relabelResume || bol && config.resumeReport) {
|
2026-10-20 15:48:38 +08:00
|
|
|
const sourcePath = resolve(ROOT, config.resumeReport)
|
2026-10-20 16:45:04 +08:00
|
|
|
const prefix = bol ? 'fleet-cross-DISPATCH20261020EXTBOL' : relabelResume ? 'fleet-cross-DISPATCH20261020EXTRELABEL' : 'fleet-cross-DISPATCH20261020EXTREV'
|
2026-10-20 15:48:38 +08:00
|
|
|
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)
|
2026-10-20 16:45:04 +08:00
|
|
|
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) {
|
2026-10-20 15:48:38 +08:00
|
|
|
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',
|
2026-10-20 16:45:04 +08:00
|
|
|
'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']
|
2026-10-20 15:48:38 +08:00
|
|
|
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]
|
2026-10-20 16:45:04 +08:00
|
|
|
context.createdResources[bol ? 'explicitBolCheckpoint' : relabelResume ? 'explicitRelabelCheckpoint' : 'explicitReviewTerminal'] = { sourceReport: config.resumeReport, sourceReportSha256: config.resumeReportSha256,
|
2026-10-20 15:48:38 +08:00
|
|
|
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()
|
2026-10-20 16:45:04 +08:00
|
|
|
if (relabelResume) return reconcileRelabelShared(...args, prior)
|
2026-10-20 15:48:38 +08:00
|
|
|
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
|
|
|
|
|
} })
|
|
|
|
|
}
|
2026-10-20 16:45:04 +08:00
|
|
|
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)
|
2026-10-20 15:48:38 +08:00
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
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,
|
2026-10-20 16:45:04 +08:00
|
|
|
relabelStatus: result.createdResources?.dispatchWorkflowRelabel?.status,
|
2026-10-20 15:48:38 +08:00
|
|
|
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
|
|
|
|
|
}
|