Files
baiyue-firstleg/scripts/admin-test-env-modules/cargo-discard-fulfillment-contract.test.mjs

104 lines
6.3 KiB
JavaScript

import test from 'node:test'
import assert from 'node:assert/strict'
import { verifyCargoDiscardFulfillment } from './cargo-discard-fulfillment.mjs'
function harness(fault = '') {
const calls = [], persisted = []
let discarded = false, confirmed = false, repeats = 0, invalidPhotoRejected = false
const proof = { approvedNo: 'CD202610220001', customerId: '101', warehouseId: '201', inboundCode: 'RK-TEST', skuCode: 'SKU-TEST',
status: 'APPROVAL_HTTP_VERIFIED_CARTON_SQL_PENDING' }
const task = { discardNo: proof.approvedNo, customerId: '101', overseasWarehouseId: '201', palletNo: 'PALLET-TEST',
status: 'PENDING', skus: [{ itemId: '301', skuCode: proof.skuCode, quantityUnit: '箱', plannedPackages: 1 }], cartonMarks: ['BOX-TEST'] }
const context = { marker: 'DISCARD-TEST', context: { merchantQualificationFixture: { tenantId: '101' } },
createdResources: { phase4OverseasClientServices: { marker: 'DISCARD-TEST' } },
assert(_module, name, _response, passed) { assert.ok(passed, name) },
async request(method, path, body, token) {
calls.push({ method, path, body, token })
assert.equal(persisted.at(-1)?.requests.at(-1)?.status, 'SENDING')
let data
if (method === 'GET') data = [{ ...task, customerId: fault === 'wrong customer' ? '999' : '101' }]
else if (path.endsWith('/resolve')) data = task
else if (path.endsWith('/confirm-completion')) { confirmed = true; data = {} }
else if (path.endsWith('/confirm')) {
if (body.photoFileIds.length === 2) {
invalidPhotoRejected = true
return { http: 200, requestId: 'request-test', traceId: fault === 'photo missing trace' ? null : 'trace-test',
body: { code: fault === 'photo accepted' ? '200' : fault === 'photo wrong code' ? 'A0400' : 'A0409', data: null } }
}
if (fault === 'unknown confirm') throw new Error('network unknown')
if (discarded) repeats++
discarded = true
data = { ...task, status: 'COMPLETED', skus: [{ ...task.skus[0], actualPackages: 1 }] }
} else throw new Error(`unexpected ${path}`)
return { http: 200, body: { code: '200', data } }
},
async requestMultipart(method, path, form, token) {
calls.push({ method, path, token })
assert.ok(form.get('file'))
assert.equal(persisted.at(-1)?.requests.at(-1)?.status, 'SENDING')
return { http: 200, body: { code: '200', data: { fileId: '401', palletNo: task.palletNo } } }
},
}
const inputs = { proof, merchantToken: 'client-secret', pdaToken: 'pda-secret',
async checkpoint() {
if (fault === 'disk') throw new Error('disk full')
persisted.push(structuredClone(proof.fulfillment))
},
async snapshot() {
const quantity = discarded ? fault === 'no deduction' ? 4 : fault === 'replay deduction' && repeats ? 2 : 3 : 4
const ledger = discarded ? [{ businessOrderNo: proof.approvedNo, businessType: 'DISCARD', deltaPackages: -1,
id: '501', customerId: proof.customerId, overseasWarehouseId: proof.warehouseId, palletNo: task.palletNo,
beforePackages: 4, afterPackages: 3,
skuCode: proof.skuCode, inboundCode: proof.inboundCode }] : []
if (discarded && fault === 'duplicate ledger') ledger.push({ ...ledger[0] })
if (discarded && fault === 'ledger customer') ledger[0].customerId = '999'
if (discarded && fault === 'ledger warehouse') ledger[0].overseasWarehouseId = '999'
if (discarded && fault === 'ledger pallet') ledger[0].palletNo = 'OTHER-PALLET'
if (discarded && fault === 'ledger identity') delete ledger[0].id
if (discarded && fault === 'ledger arithmetic') ledger[0].afterPackages = 4
return { balances: [{ inboundCode: proof.inboundCode, skuCode: proof.skuCode, totalPackages: quantity, availablePackages: quantity }],
ledger, detail: { summary: { discardNo: proof.approvedNo, customerId: proof.customerId, overseasWarehouseId: proof.warehouseId,
status: confirmed ? 'COMPLETED' : discarded ? 'PDA_COMPLETED' : 'APPROVED', totalPackages: 1,
actualPackages: discarded || invalidPhotoRejected && fault === 'photo drift' ? 1 : null, customerConfirmedAt: confirmed ? '2026-10-22T12:00:00' : null } } }
} }
return { context, inputs, calls, persisted }
}
test('discard executes same application through PDA and customer completion with zero-drift replays', async () => {
const h = harness()
const result = await verifyCargoDiscardFulfillment(h.context, h.inputs)
assert.equal(result.status, 'HTTP_VERIFIED')
assert.equal(h.inputs.proof.status, 'COMPLETED_HTTP_VERIFIED_CARTON_SQL_PENDING')
assert.equal(h.calls.filter(call => call.path.endsWith('/confirm')).length, 4)
assert.equal(h.calls.filter(call => call.path.endsWith('/confirm-completion')).length, 2)
assert.equal(h.calls.filter(call => call.path.endsWith('/photos/files')).length, 1)
assert.ok(!JSON.stringify(h.persisted).includes('client-secret'))
assert.ok(!JSON.stringify(h.persisted).includes('pda-secret'))
await assert.rejects(verifyCargoDiscardFulfillment(h.context, h.inputs), /已批准申请|不自动重跑/)
})
for (const fault of ['wrong customer', 'no deduction', 'duplicate ledger', 'replay deduction', 'unknown confirm', 'disk',
'ledger customer', 'ledger warehouse', 'ledger pallet', 'ledger identity', 'ledger arithmetic',
'photo accepted', 'photo wrong code', 'photo missing trace', 'photo drift']) {
test(`discard stops on ${fault}`, async () => {
const h = harness(fault)
await assert.rejects(verifyCargoDiscardFulfillment(h.context, h.inputs))
assert.equal(h.inputs.proof.fulfillment.status, 'FAILED')
assert.equal(h.calls.some(call => call.path.endsWith('/confirm-completion')), false)
if (fault === 'wrong customer') assert.equal(h.calls.length, 1)
if (fault === 'disk') assert.equal(h.calls.length, 0)
if (fault === 'unknown confirm') {
assert.equal(h.calls.filter(call => call.path.endsWith('/confirm')).length, 2)
assert.equal(h.inputs.proof.fulfillment.requests.at(-1).status, 'UNKNOWN_RESULT')
}
})
}
test('foreign batch or customer fails before HTTP or checkpoint', async () => {
const h = harness()
h.inputs.proof.customerId = '999'
await assert.rejects(verifyCargoDiscardFulfillment(h.context, h.inputs), /本轮已批准申请/)
assert.equal(h.calls.length, 0)
assert.equal(h.persisted.length, 0)
})