优化
This commit is contained in:
@@ -9,10 +9,10 @@ const OPTIONS = { scenario: MODULE, deploymentBlocker: true }
|
||||
const clone = value => structuredClone(value)
|
||||
const id = value => String(value ?? '')
|
||||
export const extensionMatrix = Object.freeze([
|
||||
{ scenario: 'dual-destination', scope: 'Two fresh customers, two distinct real FBA destinations, one main/trip; distinct fee inputs and isolated execution documents', readiness: 'READY' },
|
||||
{ scenario: 'dual-destination', scope: 'Two fresh customers, two distinct real FBA destinations, one main/trip; automatic isolated execution documents and actual fee facts', readiness: 'READY' },
|
||||
{ scenario: 'reviews', scope: 'Both rejection/resubmission rounds, zero-pick return and cargo/resource changes, stale/version/idempotency conflicts', readiness: 'READY' },
|
||||
{ scenario: 'relabel', scope: 'Fresh three-box customer receipt, confirmed zero-scan cancellation, two applications sharing one pallet and native rearrangement', readiness: 'READY_FORMAL_THREE_BOX_RECEIPT' },
|
||||
{ scenario: 'competition', scope: 'Two-request Admin/PDA replay race, occupied resources and unarranged departure gates; first-execution race remains untested', readiness: 'READY_BOUNDED_REPLAY' },
|
||||
{ scenario: 'competition', scope: 'Two-request Admin/PDA replay race, occupied resources gates; first-execution race remains untested', readiness: 'READY_BOUNDED_REPLAY' },
|
||||
{ scenario: 'bol', scope: 'Fresh same-destination cargo, empty/same/different formal appointment allocations and actual PDF conservation', readiness: 'READY_FORMAL_APPOINTMENTS' },
|
||||
])
|
||||
export function assertDualDestinationFixture(fixture) {
|
||||
@@ -30,24 +30,6 @@ export function feeCents(value) {
|
||||
const cents = Number(whole) * 100 + Number(fraction.padEnd(2, '0'))
|
||||
assert(Number.isSafeInteger(cents)); return cents
|
||||
}
|
||||
export function applyIndependentFees(body, groups, members, marker) {
|
||||
assert.equal(groups?.length, 2); assert.equal(body.groups?.length, 2)
|
||||
assert.equal(new Set(groups.map(row => row.groupKey)).size, 2)
|
||||
assert.equal(new Set(groups.map(row => id(row.customerId))).size, 2)
|
||||
const centsByCustomer = new Map(members.map((member, index) => [member.customerId, index === 0 ? 3125 : 4775]))
|
||||
const expected = {}
|
||||
for (const group of body.groups) {
|
||||
const matches = groups.filter(row => row.groupKey === group.groupKey)
|
||||
assert.equal(matches.length, 1)
|
||||
const customerId = id(matches[0].customerId), cents = centsByCustomer.get(customerId)
|
||||
assert(cents && group.fees?.length > 0, 'Explicit current-customer fee services required')
|
||||
assert(!expected[customerId], 'Duplicated customer fee group')
|
||||
for (const fee of group.fees) { fee.amount = cents / 100; fee.feeName = `${marker} TEST合成客户独立费用` }
|
||||
expected[customerId] = group.fees.map(fee => ({ feeType: fee.feeType, currency: fee.currency, cents: feeCents(fee.amount) }))
|
||||
}
|
||||
return expected
|
||||
}
|
||||
|
||||
/** Public master-data reads only; no fixture writes until both independent US FBA masters are verified. */
|
||||
export async function discoverSecondDestination(context, primaryId) {
|
||||
const get = async path => {
|
||||
@@ -88,17 +70,13 @@ export async function verifyDualDestination(context, fixture, options) {
|
||||
}
|
||||
const proof = context.createdResources.dispatchWorkflowExtensions = { scenario: options.bol || 'dual-destination', marker: context.marker,
|
||||
dispatchNo: fixture.dispatchNo, status: 'CHECKING', syntheticTestMaterials: true, physicalMeasurementVerified: false,
|
||||
hardwareVerified: false, midTransactionRollback: 'NOT_TESTED_NO_CONTROLLED_INJECTION', expectedFees: {}, isolation: [] }
|
||||
hardwareVerified: false, midTransactionRollback: 'NOT_TESTED_NO_CONTROLLED_INJECTION', isolation: [] }
|
||||
const original = context.request
|
||||
let arrangement, deferredFailure, raceDone = false, firstRaceDone = false, unarrangedDone = false
|
||||
let deferredFailure, raceDone = false, firstRaceDone = false
|
||||
const mainPath = `${ROOT}/dispatches/${encodeURIComponent(fixture.dispatchNo)}`
|
||||
const pdaPath = `/api/pda/v1/last-leg/dispatches/${encodeURIComponent(fixture.dispatchNo)}`
|
||||
context.request = async function (method, path, body, ...rest) {
|
||||
if (deferredFailure) throw deferredFailure
|
||||
if (method === 'POST' && path === `${ROOT}/dispatches/${encodeURIComponent(fixture.dispatchNo)}/transfer-delivery`) {
|
||||
proof.expectedFees = applyIndependentFees(body, arrangement?.groups, fixture.members, context.marker)
|
||||
proof.arrangeRequest = clone(body); await context.checkpoint()
|
||||
}
|
||||
let response
|
||||
if (options.firstCompetition && !firstRaceDone && method === 'POST' && path === `${pdaPath}/loading-complete`
|
||||
&& fixture.tripProof.loadingCommand?.body?.idempotencyKey === body?.idempotencyKey) {
|
||||
@@ -107,21 +85,14 @@ export async function verifyDualDestination(context, fixture, options) {
|
||||
response = outcome.pdaResponse
|
||||
if (outcome.error) deferredFailure = outcome.error
|
||||
} else response = await original.call(this, method, path, body, ...rest)
|
||||
if (method === 'GET' && path === `${ROOT}/dispatches/${encodeURIComponent(fixture.dispatchNo)}/delivery-arrangement`
|
||||
&& response.http === 200 && String(response.body?.code) === '200') arrangement = clone(response.body.data)
|
||||
// 写请求已收到确定回执后,只做独立扩展;后续核验失败延至下一读请求抛出,不能把已知成功伪报成未知写。
|
||||
if (!deferredFailure && response.http === 200 && String(response.body?.code) === '200') {
|
||||
try {
|
||||
if (options.competition && method === 'POST' && path === `${pdaPath}/loading-complete` && !raceDone
|
||||
&& response.body.data?.replayed !== true && response.body.data?.stage === 'AFTER_LOADING') {
|
||||
&& response.body.data?.replayed !== true && response.body.data?.stage === 'DELIVERING') {
|
||||
raceDone = true
|
||||
await verifyLoadingReplayRace(context, fixture, options, original.bind(this), body, response.body.data)
|
||||
}
|
||||
if (options.competition && method === 'GET' && path === `${mainPath}/delivery-arrangement` && !unarrangedDone
|
||||
&& response.body.data?.stage === 'AFTER_LOADING' && response.body.data?.arrangement == null) {
|
||||
unarrangedDone = true
|
||||
await verifyUnarrangedDeparture(context, fixture, options, original.bind(this), response.body.data)
|
||||
}
|
||||
if (method === 'PUT' && body?.idempotencyKey === `${context.marker}-CROSS-SIGN-1`) {
|
||||
const fence = proof.completionReadFence = { status: 'READING', attempts: [], writeReceiptPreserved: true }
|
||||
for (let attempt = 1; attempt <= 60; attempt++) {
|
||||
@@ -154,11 +125,9 @@ export async function verifyDualDestination(context, fixture, options) {
|
||||
assert.equal(id(delivery.order.customerId), member.customerId)
|
||||
assert.equal(delivery.order.destinationWarehouseCode, member.destination.warehouseCode)
|
||||
assert.equal(Number(delivery.order.packageCount), 1); assert.equal(Number(delivery.order.palletCount), 1)
|
||||
const expected = proof.expectedFees[member.customerId]
|
||||
assert(expected?.length > 0); assert.equal(delivery.fees?.length, expected.length)
|
||||
assert(Array.isArray(delivery.fees))
|
||||
for (const fee of delivery.fees) {
|
||||
assert(fee.id && !feeIds.has(id(fee.id))); feeIds.add(id(fee.id))
|
||||
assert(expected.some(row => row.feeType === fee.feeType && row.currency === fee.currency && row.cents === feeCents(fee.amount)))
|
||||
assert.equal(fee.feeDirection, 'RECEIVABLE'); assert.equal(fee.sourceStatus, 'ACTIVE')
|
||||
}
|
||||
const other = fixture.members[1 - index]
|
||||
@@ -170,7 +139,6 @@ export async function verifyDualDestination(context, fixture, options) {
|
||||
proof.isolation.push({ customerId: member.customerId, wrongCustomerId: other.customerId, deliveryNo: delivery.order.deliveryNo,
|
||||
http: response.http, code: response.body.code, requestId: response.requestId, traceId: response.traceId })
|
||||
}
|
||||
assert.notDeepEqual(proof.expectedFees[fixture.members[0].customerId], proof.expectedFees[fixture.members[1].customerId])
|
||||
proof.status = 'VERIFIED'; await context.checkpoint(); return verified
|
||||
}
|
||||
|
||||
@@ -238,11 +206,11 @@ export async function executeFirstLoadingRace(context, fixture, options, send, b
|
||||
assert.equal(winnerIndexes.length, 1)
|
||||
const winnerIndex = winnerIndexes[0], winner = responses[winnerIndex], loser = responses[1 - winnerIndex]
|
||||
assert.equal(String(loser.body?.code), 'A0409')
|
||||
assert.equal(winner.body.data.stage, 'AFTER_LOADING')
|
||||
assert.equal(winner.body.data.stage, 'DELIVERING')
|
||||
assert.equal(winner.body.data.result?.confirmedLoad?.manifestHash, body.manifestHash)
|
||||
assert.equal(BigInt(winner.body.data.recordVersion), BigInt(body.recordVersion) + 1n)
|
||||
assert(BigInt(winner.body.data.recordVersion) > BigInt(body.recordVersion))
|
||||
race.after = await competitionSnapshot(context, fixture, options, send)
|
||||
assert.equal(race.after.main.stage, 'AFTER_LOADING')
|
||||
assert.equal(race.after.main.stage, 'DELIVERING')
|
||||
assert.equal(id(race.after.main.recordVersion), id(winner.body.data.recordVersion))
|
||||
assert.deepEqual(race.after.stock, race.before.stock)
|
||||
assert.equal(Number(race.after.main.counts.packageCount), 2)
|
||||
@@ -259,20 +227,6 @@ export async function executeFirstLoadingRace(context, fixture, options, send, b
|
||||
}
|
||||
}
|
||||
|
||||
async function verifyUnarrangedDeparture(context, fixture, options, send, arrangement) {
|
||||
const proof = context.createdResources.dispatchWorkflowCompetition
|
||||
const state = proof.unarrangedDeparture = { status: 'INTENT', before: await competitionSnapshot(context, fixture, options, send),
|
||||
body: { recordVersion: arrangement.recordVersion, idempotencyKey: `${context.marker}-EXT-NO-ARRANGEMENT-DEPART`, remark: 'TEST装车后未安排派送必须拒绝' } }
|
||||
assert.equal(state.before.main.stage, 'AFTER_LOADING'); assert.equal(state.before.main.documents?.deliveries?.length, 0)
|
||||
await context.checkpoint()
|
||||
const response = await send('POST', `/api/pda/v1/last-leg/dispatches/${encodeURIComponent(fixture.dispatchNo)}/depart`, state.body, fixture.pdaToken)
|
||||
state.response = receipt(response); await context.checkpoint()
|
||||
assert.equal(String(response.body?.code), 'A0409'); assert.match(response.body?.message || '', /派送|安排/)
|
||||
state.after = await competitionSnapshot(context, fixture, options, send); assert.deepEqual(state.after, state.before)
|
||||
context.assert(MODULE, 'Loaded but unarranged departure is rejected without inventory or execution mutation', response, true, '', OPTIONS)
|
||||
state.status = 'VERIFIED'; await context.checkpoint()
|
||||
}
|
||||
|
||||
export async function verifyCompetitionExtension(context, fixture, options) {
|
||||
const proof = context.createdResources.dispatchWorkflowCompetition = { marker: context.marker, dispatchNo: fixture.dispatchNo,
|
||||
status: 'CHECKING', firstExecutionRace: 'NOT_TESTED', arbitraryPrivilegeMatrix: 'NOT_TESTED_NO_PERMISSION_MUTATION' }
|
||||
@@ -291,7 +245,7 @@ export async function verifyCompetitionExtension(context, fixture, options) {
|
||||
context.assert(MODULE, 'Occupied vehicle/driver reject a second native main without changing the existing trip or inventory', response, true, '', OPTIONS)
|
||||
proof.resourceConflict.status = 'VERIFIED'
|
||||
const verified = await verifyDualDestination(context, fixture, { ...options, competition: true })
|
||||
assert.equal(proof.loadingReplayRace?.status, 'VERIFIED'); assert.equal(proof.unarrangedDeparture?.status, 'VERIFIED')
|
||||
assert.equal(proof.loadingReplayRace?.status, 'VERIFIED')
|
||||
if (options.firstCompetition) assert.equal(proof.firstLoadingRace?.status, 'VERIFIED')
|
||||
proof.status = options.firstCompetition ? 'VERIFIED_FIRST_EXECUTION_AND_REPLAY' : 'VERIFIED_BOUNDED_SCOPE'
|
||||
await context.checkpoint(); return verified
|
||||
@@ -784,10 +738,11 @@ export async function reconcileReviewTerminal(context, fixture, { readInventory
|
||||
assert.equal(outbound.outbound.dispatchNo, fixture.dispatchNo); assert.equal(outbound.outbound.status, 'OUTBOUNDED')
|
||||
assert.equal(delivery.order.dispatchNo, fixture.dispatchNo); assert.equal(delivery.order.status, 'DELIVERED')
|
||||
assert.equal(id(delivery.order.customerId), member.customerId); assert.equal(delivery.order.destinationWarehouseCode, member.destination.warehouseCode)
|
||||
const expected = extension.expectedFees[member.customerId]; assert.equal(delivery.fees.length, expected.length)
|
||||
assert(Array.isArray(delivery.fees))
|
||||
const originalFees=old.snapshots.BEFORE_SIGN_1.members[index].delivery.fees
|
||||
assert(Array.isArray(originalFees)); assert.deepEqual(delivery.fees,originalFees)
|
||||
for (const fee of delivery.fees) {
|
||||
assert(!allFeeIds.has(id(fee.id))); allFeeIds.add(id(fee.id))
|
||||
assert(expected.some(row => row.feeType === fee.feeType && row.currency === fee.currency && row.cents === feeCents(fee.amount)))
|
||||
assert(fee.id && !allFeeIds.has(id(fee.id))); allFeeIds.add(id(fee.id))
|
||||
}
|
||||
const stock = await readInventory({ marker: context.marker, customerId: member.customerId, warehouseId: fixture.warehouseId })
|
||||
assert.deepEqual(stock, old.snapshots.BEFORE_SIGN_1.members[index].stock, 'Terminal completion must not change already shipped inventory')
|
||||
|
||||
Reference in New Issue
Block a user