This commit is contained in:
2026-10-22 14:37:59 +08:00
parent de96e47d74
commit 760bf8daa1
49 changed files with 2450 additions and 117 deletions
@@ -4,6 +4,7 @@ import fs from 'node:fs'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import { run, createPartnerWarehouse, retirePartnerWarehouse } from './phase4-overseas-business.mjs'
import { verifyUsTruckFulfillment } from './us-truck-fulfillment.mjs'
test('fixed partner warehouse is reused and never retired', async () => {
const calls = []
@@ -21,6 +22,32 @@ test('fixed partner warehouse is reused and never retired', async () => {
const here = path.dirname(fileURLToPath(import.meta.url))
const source = fs.readFileSync(path.join(here, 'phase4-overseas-business.mjs'), 'utf8')
for (const fault of ['network', 'wrong customer', 'wrong pallet count', 'replay drift']) {
test(`truck fulfillment stops and retains original order on ${fault}`, async () => {
const context = fixture(), original = context.request.bind(context)
let receipts = 0
context.request = async (method, path, ...args) => {
if (method === 'POST' && path.includes('/receive-pallets?')) {
receipts += 1
if (fault === 'network') return { http: 0 }
const result = structuredClone(await original(method, path, ...args))
if (fault === 'wrong customer') result.body.data.summary.customerId = 'other'
if (fault === 'wrong pallet count') result.body.data.pallets.push({ ...result.body.data.pallets[0] })
if (fault === 'replay drift' && receipts === 2) result.body.data.summary.version += 1
return result
}
return original(method, path, ...args)
}
await assert.rejects(run(context))
const proof = context.createdResources.phase4OverseasBusiness.truck
assert.equal(proof.fulfillment.status, 'FAILED')
assert.equal(proof.retained, true)
assert.equal(receipts, fault === 'replay drift' ? 2 : 1)
assert.equal(context.calls.some(c => c.path.includes('/allocate-pallets?') || c.path.includes('/TK1/status?')), false)
await assert.rejects(verifyUsTruckFulfillment(context, proof), /本轮未执行/)
})
}
test('fixed customer beyond first page is found without creating or retiring a partner warehouse', async () => {
const context = fixture(), request = context.request.bind(context), pages = []
context.options = { fixedCustomerId: '10001' }
@@ -63,7 +90,7 @@ function fixture(options = {}) {
}
function purchaseCustomer() { return { order: state.purchase, fees: [], progress: [], boxes: [], shipments: [], exceptionSummaries: [] } }
function purchasePlatform() { return { customerView: purchaseCustomer(), internalRemark: state.purchase?.remark ?? null, reviewHistory: state.purchaseHistories } }
function truckDetail() { return { summary: state.truck, remark: state.truck?.remark ?? null, histories: state.truckHistories } }
function truckDetail() { return structuredClone({ summary: state.truck, pallets: state.truckPallets || [], remark: state.truck?.remark ?? null, histories: state.truckHistories }) }
function looseboardDetail() { return { summary: state.looseboard, remark: state.looseboard?.remark ?? null, histories: state.looseboardHistories } }
const context = {
@@ -122,7 +149,7 @@ function fixture(options = {}) {
const hash = JSON.stringify(body)
if (state.truck) return hash === state.truckHash ? response(truckDetail()) : response(null, 'A0409')
state.truckHash = hash
state.truck = { id: '40001', orderNo: 'TK1', customerId: '10001', status: 'PENDING_REVIEW', version: 0, ...body }
state.truck = { id: '40001', orderNo: 'TK1', customerId: '10001', receivedPalletCount: 0, allocatedPalletCount: 0, status: 'PENDING_REVIEW', version: 0, ...body }
state.truckHistories.push({ fromStatus: null, toStatus: 'PENDING_REVIEW', remark: marker })
return response(truckDetail())
}
@@ -144,6 +171,32 @@ function fixture(options = {}) {
state.truckReviews.set(body.idempotencyKey, { hash, result })
return response(result)
}
const stages = { 'receive-pallets': ['WAITING_INBOUND', 'WAITING_CONSOLIDATION'],
'allocate-pallets': ['WAITING_CONSOLIDATION', 'ALLOCATED'], 'prepare-dispatch': ['ALLOCATED', 'PENDING_DISPATCH'],
dispatch: ['PENDING_DISPATCH', 'DISPATCHED'], 'start-transit': ['DISPATCHED', 'IN_TRANSIT'], complete: ['IN_TRANSIT', 'COMPLETED'] }
const action = requestPath.match(/\/us-truck-orders\/TK1\/([^?]+)\?/)?.[1]
if (method === 'POST' && stages[action]) {
const existing = state.truckStatuses.get(body.idempotencyKey)
if (existing) return response(truckDetail())
const [from, to] = stages[action]
assert.equal(state.truck.status, from)
assert.equal(body.version, state.truck.version)
if (action === 'receive-pallets') {
assert.deepEqual(body.palletPackageCounts, [2])
assert.equal(body.actualPalletCount, 1)
state.truckPallets = [{ id: '701', palletNo: 'TK1-P001', packageCount: 2, status: 'RECEIVED' }]
state.truck.receivedPalletCount = 1
}
if (action === 'allocate-pallets') {
assert.deepEqual(body.palletIds, ['701'])
state.truckPallets[0].status = 'ALLOCATED'
state.truck.allocatedPalletCount = 1
}
state.truck.status = to; state.truck.version += 1
state.truckHistories.push({ fromStatus: from, toStatus: to, idempotencyKey: body.idempotencyKey })
state.truckStatuses.set(body.idempotencyKey, true)
return response(truckDetail())
}
if (requestPath.includes('/us-truck-orders/TK1/status?') && method === 'PATCH') {
assert.equal(body.status, 'CANCELLED')
assert.equal(typeof body.idempotencyKey === 'string' && body.idempotencyKey.length > 0, true)
@@ -272,7 +325,7 @@ test('procurement creation review boundaries and transport cancellation paths pa
assert.notEqual(procurementReviews[3].body.idempotencyKey, procurementReviews[0].body.idempotencyKey)
const truckCalls = context.calls.filter((call) => call.path.includes('/us-truck-orders'))
assert.equal(truckCalls.filter((call) => call.method === 'POST').length, 7)
assert.equal(truckCalls.filter((call) => call.method === 'POST').length, 19)
assert.equal(truckCalls.filter((call) => call.method === 'GET' && call.path.includes('page=1')).length, 1)
const truckReviews = truckCalls.filter((call) => call.method === 'POST' && call.path.includes('/review?'))
assert.equal(truckReviews.length, 4)
@@ -281,7 +334,7 @@ test('procurement creation review boundaries and transport cancellation paths pa
assert.equal(new Set(truckReviews.slice(0, 3).map((call) => call.body.idempotencyKey)).size, 1)
assert.notEqual(truckReviews[3].body.idempotencyKey, truckReviews[0].body.idempotencyKey)
const truckStatuses = truckCalls.filter((call) => call.method === 'PATCH' && call.path.includes('/status?'))
assert.equal(truckStatuses.length, 1)
assert.equal(truckStatuses.length, 0)
assert.ok(truckStatuses.every((call) => call.body.status === 'CANCELLED'
&& typeof call.body.idempotencyKey === 'string' && call.body.idempotencyKey.length > 0))
@@ -290,20 +343,21 @@ test('procurement creation review boundaries and transport cancellation paths pa
assert.equal(looseboardCalls.filter((call) => call.method === 'GET' && call.path.includes('page=1')).length, 1)
assert.equal(looseboardCalls.filter((call) => call.method === 'PUT').length, 2)
const looseboardStatuses = looseboardCalls.filter((call) => call.method === 'PATCH' && call.path.includes('/status?'))
assert.equal(looseboardStatuses.length, 5)
assert.equal(looseboardStatuses.length, 4)
assert.ok(looseboardStatuses.every((call) => typeof call.body.idempotencyKey === 'string'
&& call.body.idempotencyKey.length > 0))
assert.equal(new Set(looseboardStatuses.slice(0, 3).map((call) => call.body.idempotencyKey)).size, 1)
assert.equal(new Set(looseboardStatuses.slice(3).map((call) => call.body.idempotencyKey)).size, 2)
assert.equal(new Set(looseboardStatuses.slice(3).map((call) => call.body.idempotencyKey)).size, 1)
assert.ok(looseboardStatuses.slice(3).every((call) => call.body.idempotencyKey !== looseboardStatuses[0].body.idempotencyKey))
const registry = context.createdResources.phase4OverseasBusiness
assert.equal(registry.purchase.finalStatus, 'REJECTED')
assert.equal(registry.purchase.retained, false)
assert.equal(registry.truck.finalStatus, 'CANCELLED')
assert.equal(registry.truck.retained, false)
assert.equal(registry.looseboard.finalStatus, 'CANCELLED')
assert.equal(registry.looseboard.retained, false)
assert.equal(registry.truck.finalStatus, 'COMPLETED')
assert.equal(registry.truck.retained, true)
assert.equal(registry.truck.fulfillment.status, 'HTTP_VERIFIED')
assert.equal(registry.looseboard.finalStatus, 'PENDING_ACCEPTANCE')
assert.equal(registry.looseboard.retained, true)
assert.equal(registry.partnerWarehouse.finalStatus, 'INACTIVE')
assert.equal(registry.partnerWarehouse.retained, false)
})