feat(dispatch): unify whole vehicle scheduling, source ownership and staged execution
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
import test from 'node:test'
|
||||
import { relabelOutboundMarksMatch, inventoryQuantitySnapshot, inventorySnapshot, dispatchInventorySources, inventorySourcesPresent, dispatchStockConservation, verifyDepartureReadiness, verifyPartialOrderCompletion, verifyFulfillmentRootStatus,
|
||||
createDeliveryReturn, completeReturnedException, returnedExceptionFactsMatch, verifyCustomerSignedDocuments, createSignedDocumentIsolationCustomer,
|
||||
dispatchDestinationConservation } from './phase5-overseas-fulfillment.mjs'
|
||||
dispatchDestinationConservation, pickAll, arrangeDispatchDelivery, fulfillmentEvidenceContext } from './phase5-overseas-fulfillment.mjs'
|
||||
import assert from 'node:assert/strict'
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { readFile, mkdtemp, rm } from 'node:fs/promises'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { parseArguments } from '../admin-test-env-acceptance.mjs'
|
||||
|
||||
@@ -27,27 +29,26 @@ test('一箱实收从四箱派送退回仅保持RETURN_PENDING,全部实收才
|
||||
}
|
||||
})
|
||||
|
||||
test('route equals actual item destinations and frozen selected candidates, then conserves split deliveries',()=>{
|
||||
const fixture={planPalletIds:['10','11','12'],destinationWarehouseCodes:['ONT8','LAX9']}
|
||||
const detail={route:'ONT8,LAX9',items:[{planPalletId:'10',fbaWarehouseCode:'ONT8'},
|
||||
{planPalletId:'11',destinationWarehouseCode:'LAX9'},{planPalletId:'12',fbaWarehouseCode:'ONT8'}]}
|
||||
const deliveries=[{destinationWarehouseCode:'LAX9'},{destinationWarehouseCode:'ONT8'}]
|
||||
assert.equal(dispatchDestinationConservation(detail,fixture),true)
|
||||
assert.equal(dispatchDestinationConservation(detail,fixture,deliveries),true)
|
||||
for(const mutate of [
|
||||
value=>value.detail.route='ONT8',value=>value.detail.route='ONT8,LAX9,OTHER',
|
||||
value=>value.detail.route='ONT8,ONT8,LAX9',value=>value.detail.items[1].destinationWarehouseCode='OTHER',
|
||||
value=>value.detail.items[0].destinationWarehouseCode='REASSIGNED',
|
||||
value=>value.detail.items[1].planPalletId='99',value=>value.detail.items[1].planPalletId='10',
|
||||
value=>value.fixture.destinationWarehouseCodes.push('UNSELECTED'),value=>value.deliveries.pop(),
|
||||
value=>value.deliveries.push({destinationWarehouseCode:'OTHER'}),
|
||||
value=>value.deliveries.push({destinationWarehouseCode:'ONT8'}),
|
||||
]) {
|
||||
const value=structuredClone({detail,fixture,deliveries});mutate(value)
|
||||
assert.equal(dispatchDestinationConservation(value.detail,value.fixture,value.deliveries),false)
|
||||
test('native route conserves source keys and destinations while same destination may contain multiple customers', () => {
|
||||
const fixture = { sourceKeys: ['PLAN:10', 'PLAN:11', 'PLAN:12'], destinationWarehouseCodes: ['ONT8', 'LAX9'] }
|
||||
const detail = { dispatchInfo: { route: 'ONT8,LAX9' }, orderDetails: [
|
||||
{ sourceKey: 'PLAN:10', customerId: '100', destinationFbaWarehouse: 'ONT8' },
|
||||
{ sourceKey: 'PLAN:11', customerId: '200', destinationFbaWarehouse: 'LAX9' },
|
||||
{ sourceKey: 'PLAN:12', customerId: '200', destinationFbaWarehouse: 'ONT8' }] }
|
||||
const deliveries = [{ customerId: '100', destination: { key: 'FBA:ONT8', warehouseCode: 'ONT8' } },
|
||||
{ customerId: '200', destination: { key: 'FBA:ONT8', warehouseCode: 'ONT8' } },
|
||||
{ customerId: '200', destination: { key: 'FBA:LAX9', warehouseCode: 'LAX9' } }]
|
||||
assert.equal(dispatchDestinationConservation(detail, fixture), true)
|
||||
assert.equal(dispatchDestinationConservation(detail, fixture, deliveries), true)
|
||||
for (const mutate of [value => { value.detail.dispatchInfo.route = 'ONT8' },
|
||||
value => { value.detail.dispatchInfo.route = 'ONT8,ONT8,LAX9' },
|
||||
value => { value.detail.orderDetails[1].destinationFbaWarehouse = 'OTHER' },
|
||||
value => { value.detail.orderDetails[1].sourceKey = 'PLAN:99' },
|
||||
value => { value.fixture.destinationWarehouseCodes.push('UNSELECTED') },
|
||||
value => { value.deliveries.pop() }, value => { value.deliveries.push(value.deliveries[0]) }]) {
|
||||
const value = structuredClone({ detail, fixture, deliveries }); mutate(value)
|
||||
assert.equal(dispatchDestinationConservation(value.detail, value.fixture, value.deliveries), false)
|
||||
}
|
||||
assert.match(source,/dispatchDestinationConservation\(beforeEdit.data,source.dispatch\)/)
|
||||
assert.match(source,/dispatchDestinationConservation\(beforeEdit.data,source.dispatch,deliveries\)/)
|
||||
})
|
||||
|
||||
test('phase-five fulfillment is a registered default HTTP module after the real phase-four dispatch fixture', () => {
|
||||
@@ -61,40 +62,38 @@ test('phase-five fulfillment is a registered default HTTP module after the real
|
||||
}
|
||||
})
|
||||
|
||||
test('phase-five module covers dispatch edit, transfer, PDA pick/depart and delivery state', () => {
|
||||
for (const token of [
|
||||
"const DISPATCH_BASE = '/api/platform/v1/last-leg/dispatches'",
|
||||
"const PDA_DISPATCH_BASE = '/api/pda/v1/last-leg/dispatches'",
|
||||
'query(MERCHANT_DELIVERY_BASE, { dispatchNo })', '/items/${encodeURIComponent(id(item.dispatchItemId))}/pick',
|
||||
'/complete`', '/depart`', '/outbound-orders/${encodeURIComponent(outboundNo)}',
|
||||
'/delivery-orders/${encodeURIComponent(currentNo)}/status',
|
||||
]) assert.ok(source.includes(token), token)
|
||||
assert.match(source, /WAIT_OUTBOUND/)
|
||||
assert.match(source, /WAIT_DELIVERY/)
|
||||
assert.match(source, /READY_TO_DEPART/)
|
||||
assert.match(source, /OUTBOUNDED/)
|
||||
assert.match(source, /DELIVERING/)
|
||||
assert.match(source, /expectedVersion: deliveryVersion/)
|
||||
assert.match(source, /transferDeliveries\(transferred\.data\)/)
|
||||
assert.match(source, /new Set\(destinationKeys\)\.size === deliveries\.length/)
|
||||
assert.match(source, /deliveries\.reduce\(\(sum, value\) => sum \+ Number\(value\?\.packageCount \|\| 0\), 0\)/)
|
||||
test('native fulfillment uses real public DTOs and scans before loading, arranging and departing', () => {
|
||||
for (const token of ["const DISPATCH_BASE = '/api/platform/v1/last-leg/dispatches'",
|
||||
"const PDA_DISPATCH_BASE = '/api/pda/v1/last-leg/dispatches'", '/transfer-delivery', '/delivery-arrangement',
|
||||
'cargoHash: value.cargoHash', 'loadingCompletionHash: value.loadingCompletionHash',
|
||||
'recordVersion: version(detail.data.recordVersion)', 'positionId: id',
|
||||
'departed.data.result?.shippedPackages', 'outboundRef.outboundNo']) {
|
||||
if (token === 'positionId: id') assert.match(source, /positionId: source.position \? id\(source.position.positionId\)/)
|
||||
else assert.ok(source.includes(token), token)
|
||||
}
|
||||
assert.doesNotMatch(source, /READY_TO_DEPART|PENDING_DEPARTURE|DISPATCH-COMPLETE|\/items\/.*['"]DELETE['"]/)
|
||||
assert.match(source, /data.status === 'ACTIVE' && data.stage === 'AFTER_LOADING'/)
|
||||
assert.match(source, /departed.data.stage === 'DELIVERING'/)
|
||||
const run = source.slice(source.indexOf('export async function run'))
|
||||
assert.ok(run.indexOf('prepareFleetTrip(') < run.indexOf('await pickAll('))
|
||||
assert.ok(run.indexOf('await pickAll(') < run.indexOf('await confirmFleetLoading('))
|
||||
assert.ok(run.indexOf('await confirmFleetLoading(') < run.indexOf('await arrangeDispatchDelivery('))
|
||||
assert.ok(run.indexOf('await arrangeDispatchDelivery(') < run.indexOf('await verifyDepartureReadiness('))
|
||||
assert.ok(run.indexOf('全部客户正常签收汇总整车DELIVERED') < run.indexOf('await finishFleetTrip('))
|
||||
assert.ok(run.indexOf('await finishFleetTrip(') < run.indexOf('const returnRequest'))
|
||||
})
|
||||
|
||||
test('phase-five module proves the client delivery create, list and nested detail contract', () => {
|
||||
assert.match(source, /const MERCHANT_DELIVERY_BASE = '\/api\/merchant\/v1\/last-leg\/delivery-orders'/)
|
||||
test('client reads its own native-arranged deliveries and full nested document contract', () => {
|
||||
assert.match(source, /loginMerchant\(context, customerId\)/)
|
||||
assert.match(source, /context\.options\?\.merchantUsername/)
|
||||
assert.match(source, /\/api\/merchant\/v1\/iam\/tenant\/profile/)
|
||||
assert.match(source, /id\(profile\?\.id\) === customerId/)
|
||||
assert.match(source, /客户创建派送并原子转出库单/)
|
||||
assert.match(source, /deliveryMethod: 'TRUCK_PALLET'/)
|
||||
assert.match(source, /fees: \[\{ feeType: 'DELIVERY_FEE', feeName: '派送费', direction: 'RECEIVABLE',\s*currency: 'USD', amount: 10/)
|
||||
assert.match(source, /context.options\?.merchantUsername/)
|
||||
assert.match(source, /id\(profile\?.id\) === customerId/)
|
||||
assert.match(source, /客户分页回查自身派送单/)
|
||||
assert.match(source, /客户读取自身派送详情/)
|
||||
assert.match(source, /merchantDetail\.data\?\.order\?\.deliveryNo/)
|
||||
assert.match(source, /merchantDetail\.data\?\.skus/)
|
||||
assert.match(source, /merchantDetail\.data\?\.fees/)
|
||||
assert.match(source, /merchantDetail\.data\?\.operations/)
|
||||
assert.match(source, /merchantDetail.data\?.order\?.deliveryNo/)
|
||||
assert.match(source, /merchantDetail.data\?.skus/)
|
||||
assert.match(source, /merchantDetail.data\?.fees/)
|
||||
assert.match(source, /merchantDetail.data\?.operations/)
|
||||
assert.doesNotMatch(source, /merchant\(context, merchantToken, 'POST'.*创建派送/)
|
||||
})
|
||||
|
||||
test('phase-five module proves receipt-save completion, cross-region convergence, customer visit, exception and return', () => {
|
||||
@@ -134,10 +133,9 @@ test('phase-five module uses marker-owned fixtures and always disables its tempo
|
||||
})
|
||||
|
||||
test('phase-five writes carry idempotency and record deployment blockers instead of passing empty fixtures', () => {
|
||||
assert.match(source, /idempotencyKey: key\(context, 'TRANSFER'\)/)
|
||||
assert.match(source, /idempotencyKey: key\(context, 'TRANSFER', dispatchNo\)/)
|
||||
assert.match(source, /idempotencyKey: key\(context, 'DISPATCH-PICK'/)
|
||||
assert.match(source, /idempotencyKey: key\(context, 'DISPATCH-COMPLETE'\)/)
|
||||
assert.match(source, /idempotencyKey: key\(context, 'DISPATCH-DEPART'\)/)
|
||||
assert.match(source, /idempotencyKey: key\(context, 'DISPATCH-DEPART', dispatchNo\)/)
|
||||
assert.match(source, /addCoverageGap\(MODULE, SCENARIO/)
|
||||
assert.match(source, /deploymentBlocker: true/)
|
||||
})
|
||||
@@ -153,9 +151,10 @@ test('relabel marks are checked on the authoritative outbound DTO without weaken
|
||||
assert.match(source, /relabel\?\.marker !== context\.marker/)
|
||||
})
|
||||
|
||||
test('manual exception retries only a transport failure with the identical business body', () => {
|
||||
assert.match(source, /manualException\.response\.http === 0/)
|
||||
assert.match(source, /人工异常连接失败后使用同载荷重试/)
|
||||
test('unknown writes preserve the original command and stop instead of transport retry', () => {
|
||||
assert.match(source, /entry.status = 'UNKNOWN'/)
|
||||
assert.match(source, /存在未知写结果/)
|
||||
assert.doesNotMatch(source, /人工异常连接失败后使用同载荷重试/)
|
||||
assert.match(source, /manualExceptionBody/)
|
||||
})
|
||||
|
||||
@@ -497,34 +496,28 @@ test('inventory evidence captures zero drift independently for every balance buc
|
||||
}
|
||||
})
|
||||
|
||||
test('departure gate requires the PDA task COMPLETED, server canDepart, and independent matching dispatch READY_TO_DEPART', async () => {
|
||||
for (const [taskStatus, canDepart, dispatchStatus, dispatchNo, expected] of [
|
||||
['COMPLETED', true, 'READY_TO_DEPART', 'DS-1', true],
|
||||
['READY_TO_DEPART', true, 'READY_TO_DEPART', 'DS-1', false],
|
||||
['PICKING', true, 'READY_TO_DEPART', 'DS-1', false],
|
||||
['COMPLETED', false, 'READY_TO_DEPART', 'DS-1', false],
|
||||
['COMPLETED', true, 'PENDING_DEPARTURE', 'DS-1', false],
|
||||
['COMPLETED', true, 'DEPARTED', 'DS-1', false],
|
||||
['COMPLETED', true, 'READY_TO_DEPART', 'DS-OTHER', false],
|
||||
]) {
|
||||
const context = contextFor((method, path) => path.startsWith('/api/pda/')
|
||||
? ok({ dispatchNo: 'DS-1', status: taskStatus, canDepart }) : ok({ dispatchNo, status: dispatchStatus }))
|
||||
assert.equal(Boolean(await verifyDepartureReadiness(context, 'PDA', 'DS-1')), expected)
|
||||
test('departure requires independent matching native stage, shared version, reconciled picks and both action gates', async () => {
|
||||
const valid = { dispatchNo: 'DS-1', status: 'ACTIVE', stage: 'AFTER_LOADING', recordVersion: '9007199254740993',
|
||||
pickingReconciled: true, actions: { DEPART: { allowed: true } } }
|
||||
for (const change of [null, { stage: 'LOADING' }, { stage: 'DELIVERING' }, { status: 'CANCELLED' },
|
||||
{ dispatchNo: 'OTHER' }, { recordVersion: '9007199254740994' }, { pickingReconciled: false },
|
||||
{ actions: { DEPART: { allowed: false } } }]) {
|
||||
const context = contextFor((method, path) => ok(path.startsWith('/api/pda/') ? valid : { ...valid, ...change }))
|
||||
assert.equal(Boolean(await verifyDepartureReadiness(context, 'PDA', 'DS-1')), change == null)
|
||||
assert.equal(context.calls.length, 2)
|
||||
assert.equal(context.calls.some(call => call.method !== 'GET'), false)
|
||||
assert.equal(context.assertions.some(row => !row.passed), !expected)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
test('G05 freezes actual PDA balance IDs and measures raw sources independently from grouped representatives without inbound filters', async () => {
|
||||
const task = { dispatchNo: 'DS-1', warehouseId: 'WH-1', items: [
|
||||
{ positions: [{ id: '11', balanceId: '1' }, { id: '12', balanceId: '2' }, { id: '11', balanceId: '1' }] }] }
|
||||
{ positions: [{ positionId: '11', balanceId: '1' }, { positionId: '12', balanceId: '2' }, { positionId: '11', balanceId: '1' }] }] }
|
||||
assert.deepEqual(dispatchInventorySources(task, 'DS-1', 'WH-1'), { sourceBalanceIds: ['1', '2'], sourcePositionIds: ['11', '12'],
|
||||
sourcePositionBalances: [{ positionId: '11', balanceId: '1' }, { positionId: '12', balanceId: '2' }] })
|
||||
assert.equal(dispatchInventorySources(task, 'OTHER', 'WH-1'), null)
|
||||
assert.equal(dispatchInventorySources(task, 'DS-1', 'OTHER'), null)
|
||||
assert.equal(dispatchInventorySources({ ...task, items: [{ positions: [{ id: '11' }] }] }, 'DS-1', 'WH-1'), null)
|
||||
assert.equal(dispatchInventorySources({ ...task, items: [{ positions: [{ positionId: '11' }] }] }, 'DS-1', 'WH-1'), null)
|
||||
let shipped = false
|
||||
const source = balanceId => ({ id: balanceId, customerId: '100', overseasWarehouseId: 'WH-1',
|
||||
destinationWarehouseCode: 'FBA', skuCode: 'MIXED', totalPackages: balanceId === '1' ? 3 : 6,
|
||||
@@ -761,3 +754,129 @@ test('all fragments completed still requires raw aggregate history and matching
|
||||
? ok({ summary: { orderId: '1002', mainStatus: 'COMPLETED' } }) : originalRequest(...args)
|
||||
assert.equal((await verifyFulfillmentRootStatus(foreignProjection.context, foreignProjection.args)).passed, false)
|
||||
})
|
||||
|
||||
|
||||
test('native PDA scans use exact current versions including zero, immutable replay and stale-version rejection without implicit loading', async () => {
|
||||
const values = ['0', '9007199254740993', '9007199254740994']
|
||||
let currentVersion = values[0], picked = 0, firstBody, firstResult
|
||||
const context = contextFor((method, path, body) => {
|
||||
if (method === 'GET') return ok({ dispatchNo: 'DS-1', status: 'ACTIVE', stage: 'LOADING', recordVersion: currentVersion,
|
||||
pickingReconciled: picked === 2, counts: { packageCount: 2, pickedPackageCount: picked },
|
||||
items: [{ dispatchItemId: '9007199254740995', customerId: '100', scheduledPackages: 2, pickedPackages: picked,
|
||||
positions: [{ positionId: '9007199254740996', balanceId: '9007199254740997', availablePackages: 1 },
|
||||
{ positionId: '9007199254740998', balanceId: '9007199254740999', availablePackages: 1 }] }] })
|
||||
assert.match(path, /items\/9007199254740995\/pick$/)
|
||||
assert.equal(Object.hasOwn(body, 'expectedVersion'), false)
|
||||
assert.equal(typeof body.positionId, 'string')
|
||||
if (body.idempotencyKey.endsWith('-STALE')) {
|
||||
assert.equal(body.recordVersion, '0'); return { http: 200, body: { code: 'A0409' } }
|
||||
}
|
||||
if (firstBody?.idempotencyKey === body.idempotencyKey) {
|
||||
assert.deepEqual(body, firstBody); return ok({ ...firstResult, replayed: true })
|
||||
}
|
||||
assert.equal(body.recordVersion, currentVersion)
|
||||
picked += 1; currentVersion = values[picked]
|
||||
firstBody = structuredClone(body)
|
||||
firstResult = { dispatchNo: 'DS-1', status: 'ACTIVE', stage: 'LOADING', recordVersion: currentVersion,
|
||||
result: { dispatchItemId: '9007199254740995', pickedPackages: picked }, counts: { pickedPackageCount: picked } }
|
||||
return ok(firstResult)
|
||||
})
|
||||
assert.equal(await pickAll(context, 'PDA-IN-MEMORY', 'DS-1'), true)
|
||||
assert.equal(picked, 2)
|
||||
assert.equal(context.calls.some(call => /loading-complete|\/complete|\/depart/.test(call.path)), false)
|
||||
assert.deepEqual(context.assertions.filter(row => !row.passed), [])
|
||||
})
|
||||
|
||||
function nativeArrangementPreview(recordVersion = '0') {
|
||||
return { dispatchNo: 'DS-1', recordVersion, status: 'ACTIVE', stage: 'AFTER_LOADING', cargoHash: 'CARGO',
|
||||
loadingCompletionHash: 'LOAD', arrange: { allowed: true }, groups: ['100', '200'].map(customerId => ({
|
||||
groupKey: `GROUP-${customerId}`, customerId, destination: { key: 'FBA:ONT8', warehouseCode: 'ONT8' },
|
||||
groupCargoHash: `GROUP-HASH-${customerId}`, externalFreightHash: 'FREIGHT', feeCoverageCargoHash: 'COVERAGE',
|
||||
requiredServices: ['DELIVERY_FEE'], feeSources: [], cargo: [{ customerId, packages: 1 }], blockReasons: [] })) }
|
||||
}
|
||||
|
||||
test('native arrangement carries frozen group and cost hashes, preserves all customer outbounds, and replays the exact original command', async () => {
|
||||
for (const recordVersion of ['0', '9007199254740993']) {
|
||||
const preview = nativeArrangementPreview(recordVersion)
|
||||
let original, result
|
||||
const context = contextFor((method, path, body) => {
|
||||
if (method === 'GET') { assert.match(path, /delivery-arrangement$/); return ok(preview) }
|
||||
assert.match(path, /transfer-delivery$/)
|
||||
if (original) { assert.deepEqual(body, original); return ok({ ...result, replayed: true }) }
|
||||
original = structuredClone(body)
|
||||
assert.equal(body.recordVersion, recordVersion)
|
||||
assert.equal(body.cargoHash, 'CARGO'); assert.equal(body.loadingCompletionHash, 'LOAD')
|
||||
assert.deepEqual(body.groups.map(row => row.groupKey), ['GROUP-100', 'GROUP-200'])
|
||||
assert.ok(body.groups.every(row => row.externalFreightHash === 'FREIGHT' && row.feeCoverageCargoHash === 'COVERAGE'))
|
||||
result = { dispatchNo: 'DS-1', status: 'ACTIVE', stage: 'AFTER_LOADING', recordVersion: '9007199254740994',
|
||||
result: { dispatchNo: 'DS-1', cargoHash: 'CARGO', outbounds: ['100', '200'].map(customerId => ({
|
||||
customerId, outboundNo: `OB-${customerId}`, status: 'WAIT_OUTBOUND', packageCount: 1 })),
|
||||
deliveries: ['100', '200'].map(customerId => ({ customerId, outboundNo: `OB-${customerId}`,
|
||||
deliveryNo: `DL-${customerId}`, destination: { key: 'FBA:ONT8', warehouseCode: 'ONT8' }, status: 'WAIT_DELIVERY', packageCount: 1 })) } }
|
||||
return ok(result)
|
||||
})
|
||||
const arranged = await arrangeDispatchDelivery(context, 'DS-1')
|
||||
assert.deepEqual(arranged.outbounds.map(row => row.outboundNo), ['OB-100', 'OB-200'])
|
||||
assert.equal(context.calls.filter(call => call.method === 'POST').length, 2)
|
||||
assert.deepEqual(context.assertions.filter(row => !row.passed), [])
|
||||
}
|
||||
})
|
||||
|
||||
test('arrangement cannot run before loading or with missing hashes, groups or server gate', async () => {
|
||||
for (const mutate of [value => { value.stage = 'LOADING' }, value => { value.status = 'CANCELLED' },
|
||||
value => { value.cargoHash = '' }, value => { value.loadingCompletionHash = '' },
|
||||
value => { value.groups = [] }, value => { value.arrange.allowed = false },
|
||||
value => { value.groups[0].blockReasons = ['MISSING_FEE'] }]) {
|
||||
const preview = nativeArrangementPreview(); mutate(preview)
|
||||
const context = contextFor((method) => { assert.equal(method, 'GET'); return ok(preview) })
|
||||
assert.equal(await arrangeDispatchDelivery(context, 'DS-1'), null)
|
||||
assert.ok(context.assertions.some(row => !row.passed))
|
||||
}
|
||||
})
|
||||
|
||||
for (const [name, outcome] of [
|
||||
['transport', { http: 0, body: { code: '' } }],
|
||||
['infrastructure', { http: 503, body: { code: 'S0503' } }],
|
||||
['malformed success', { http: 200, body: { code: '200', data: null } }],
|
||||
['missing business response', { http: 200, body: {} }],
|
||||
]) test(`write intent persists and ${name} stops commands and checkpoint restarts`, async () => {
|
||||
const dir = await mkdtemp(join(tmpdir(), 'phase5-contract-'))
|
||||
try {
|
||||
const context = contextFor(async (method, path, body) => {
|
||||
const saved = JSON.parse(await readFile(context.createdResources.phase5OverseasFulfillment.checkpointPath, 'utf8'))
|
||||
assert.equal(saved.requests.at(-1).status, 'SENDING')
|
||||
assert.equal(saved.requests.at(-1).body.recordVersion, '0')
|
||||
assert.equal(saved.requests.at(-1).body.idempotencyKey, body.idempotencyKey)
|
||||
assert.equal(JSON.stringify(saved).includes('SECRET-CREDENTIAL'), false)
|
||||
return { ...outcome, requestId: 'REQ-UNKNOWN', traceId: 'TRACE-UNKNOWN' }
|
||||
})
|
||||
context.options.output = join(dir, 'report.json')
|
||||
context.createdResources.phase5OverseasFulfillment = {}
|
||||
const scoped = await fulfillmentEvidenceContext(context)
|
||||
const body = { recordVersion: '0', idempotencyKey: 'ORIGINAL-COMMAND', password: 'SECRET-CREDENTIAL' }
|
||||
await assert.rejects(scoped.request('POST', '/api/platform/v1/last-leg/dispatches/DS-1/depart', body), /结果未知/)
|
||||
await assert.rejects(scoped.request('POST', '/api/platform/v1/last-leg/dispatches/DS-1/depart', { ...body, idempotencyKey: 'NEW-KEY' }), /未知写结果/)
|
||||
assert.equal(context.calls.length, 1)
|
||||
const saved = JSON.parse(await readFile(context.createdResources.phase5OverseasFulfillment.checkpointPath, 'utf8'))
|
||||
assert.equal(saved.status, 'UNKNOWN'); assert.equal(saved.requests[0].status, 'UNKNOWN')
|
||||
assert.equal(saved.requests[0].requestId, 'REQ-UNKNOWN')
|
||||
await assert.rejects(fulfillmentEvidenceContext(context), /已存在本轮履约检查点/)
|
||||
} finally { await rm(dir, { recursive: true, force: true }) }
|
||||
})
|
||||
|
||||
|
||||
test('default normal completion returns before exception writes; explicit legacy return stays after its original receipt guards and before closure', () => {
|
||||
const run = source.slice(source.indexOf('export async function run'))
|
||||
const start = run.indexOf('if (context.context?.phase5TailVerification?.runReturnScenario !== true)')
|
||||
const exception = run.indexOf('const manualExceptionBody')
|
||||
assert.ok(start > run.indexOf("savedVisit.data?.visit?.status !== 'COMPLETED'"))
|
||||
const normalBranch = run.slice(start, exception)
|
||||
assert.ok(normalBranch.includes("finalStatus = 'DELIVERED_WITH_COMPLETED_VISIT'"))
|
||||
assert.match(normalBranch, /await finishFleetTrip/)
|
||||
assert.match(normalBranch, /return\s*}/)
|
||||
assert.doesNotMatch(normalBranch, /createDeliveryReturn|completeReturnedException|exception-orders|mark-return/)
|
||||
const explicitBranch = run.slice(exception)
|
||||
assert.ok(explicitBranch.indexOf('const returnReceipt = await completeReturnedException') < explicitBranch.indexOf('await finishFleetTrip'))
|
||||
assert.match(source, /const sourceBalanceIds = owned.inventoryProof\?.sourceBalanceIds/)
|
||||
assert.match(source, /原returnSource位置必须存在于正式排车冻结真实余额中/)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user