This commit is contained in:
2026-09-15 14:18:18 +08:00
parent 6a2075fe4b
commit a474ca4d93
35 changed files with 751 additions and 228 deletions
@@ -3,7 +3,78 @@ import assert from 'node:assert/strict'
import { readFileSync } from 'node:fs'
import { normalizeModuleOrder } from '../admin-test-env-acceptance.mjs'
import { sameOutboundTask } from './dropship-pallet-matrix.mjs'
import { probePartialPick } from './dropship-outbound-edge-cases.mjs'
import { receivingProgressMatches, outboundCandidateGuards, probePartialPick } from './dropship-outbound-edge-cases.mjs'
test('receiving verifier follows three independent inbound codes through interleaved progress', () => {
const expected = [{ inboundCode: 'FBA', total: 5, received: 0 },
{ inboundCode: 'DROPSHIP', total: 4, received: 0 }, { inboundCode: 'OVERSEAS', total: 3, received: 0 }]
for (const [index, count] of [[0, 2], [1, 1], [2, 3], [0, 3], [1, 3]]) {
expected[index].received += count
const snapshot = { totalPackages: 12, receivedPackages: expected.reduce((sum, x) => sum + x.received, 0),
pendingPackages: expected.reduce((sum, x) => sum + x.total - x.received, 0),
inbounds: expected.map(x => ({ inboundCode: x.inboundCode, totalPackages: x.total,
receivedPackages: x.received, pendingPackages: x.total - x.received })) }
assert.equal(receivingProgressMatches(snapshot, expected), true)
const stale = structuredClone(snapshot)
stale.inbounds[index].pendingPackages += 1
assert.equal(receivingProgressMatches(stale, expected), false)
const swapped = structuredClone(snapshot)
swapped.inbounds[0].receivedPackages += 1
swapped.inbounds[1].receivedPackages -= 1
assert.equal(receivingProgressMatches(swapped, expected), false, 'correct container total cannot conceal wrong ownership')
const duplicate = structuredClone(snapshot)
duplicate.inbounds[2] = duplicate.inbounds[0]
assert.equal(receivingProgressMatches(duplicate, expected), false)
}
assert.equal(receivingProgressMatches({ inbounds: [] }, []), false)
})
test('receiving verifier does not interpret absent or malformed progress as zero', () => {
const expected = [{ inboundCode: 'IN', total: 4, received: 0 }]
const good = { totalPackages: 4, receivedPackages: 0, pendingPackages: 4,
inbounds: [{ inboundCode: 'IN', totalPackages: 4, receivedPackages: 0, pendingPackages: 4 }] }
for (const value of [null, undefined, '', false, -1, 0.5]) {
const snapshot = structuredClone(good)
snapshot.inbounds[0].receivedPackages = value
assert.equal(receivingProgressMatches(snapshot, expected), false)
}
assert.equal(receivingProgressMatches(good, expected), true)
})
function candidateProbe(transform = rows => rows) {
const rows = ['P1', 'P2'].map((palletNo, index) => ({ palletNo, inventoryPositionId: index + 1,
inboundCode: 'IN-1', skuCode: 'SKU', availablePackages: 2, customerId: 'C1', overseasWarehouseId: 'W1' }))
const paths = []
return { paths, edge: { label: 'MULTI', customerId: 'C1', warehouseId: 'W1',
group: [{ label: 'MULTI', source: { inboundCode: 'IN-1' },
task: { pallets: [{ palletNo: 'P1' }, { palletNo: 'P2' }] }, expectedCartonMarks: ['a', 'b', 'c', 'd'] }],
check: (name, _response, passed) => assert.ok(passed, name),
call: async (_name, method, path) => {
assert.equal(method, 'GET'); paths.push(path)
return transform(structuredClone(rows), path)
},
} }
}
test('candidate probe sends the actual platform query without customer and checks both pallets', async () => {
const { edge, paths } = candidateProbe()
await outboundCandidateGuards(edge)
assert.equal(new URL(paths[0], 'https://example.invalid').searchParams.has('customerId'), false)
assert.equal(new URL(paths[1], 'https://example.invalid').searchParams.get('customerId'), 'C1')
})
for (const [name, transform] of [
['missing second pallet', rows => rows.slice(0, 1)],
['wrong warehouse', rows => rows.map(row => ({ ...row, overseasWarehouseId: 'W2' }))],
['wrong customer', rows => rows.map(row => ({ ...row, customerId: 'C2' }))],
['bad request response', () => ({ code: 'A0400', message: 'Bad request' })],
['candidate identity drift after filtering', (rows, path) => path.includes('customerId=')
? rows.map(row => ({ ...row, inventoryPositionId: row.inventoryPositionId + 10 })) : rows],
]) {
test(`candidate probe fails closed on ${name}`, async () => {
await assert.rejects(outboundCandidateGuards(candidateProbe(transform).edge))
})
}
test('browser fixture is explicit and stops before overseas receipt or outbound', () => {
assert.deepEqual(normalizeModuleOrder(['dropship-browser-fixture']), ['merchant-qualification-fixture', 'dropship-browser-fixture'])
@@ -47,6 +118,21 @@ test('outbound replay preserves business identity and database millisecond times
const source = readFileSync(new URL('./dropship-pallet-matrix.mjs', import.meta.url), 'utf8')
const edges = readFileSync(new URL('./dropship-outbound-edge-cases.mjs', import.meta.url), 'utf8')
test('matrix runs candidate probes before selecting inventory or creating outbound', () => {
const probe = source.indexOf('await outboundCandidateGuards(')
assert.ok(probe > 0)
assert.ok(probe < source.indexOf("const all = await call('读取专用出库可用库存'"))
})
test('each remaining pallet tests normal completion rejection and zero drift before receiving', () => {
const loop = source.slice(source.indexOf('for (const [index, sourcePalletNo] of sourceNos.entries())'),
source.indexOf("check(`${label}整柜实收总箱数守恒`"))
assert.match(loop, /allowDifference: false/)
assert.match(loop, /premature.body\?\.code === 'A0409'/)
assert.match(loop, /JSON.stringify\(unchanged\) === JSON.stringify\(before\)/)
assert.ok(loop.indexOf('JSON.stringify(unchanged)') < loop.indexOf("const received = await call"))
})
test('outbound edge probes preserve failures and require zero drift before continuing', () => {
assert.match(edges, /deploymentBlocker: true/)
assert.match(edges, /四箱任务先拣一箱成功/)