import test from 'node:test' 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 { receivingProgressMatches, outboundCandidateGuards, probePartialPick } from './dropship-outbound-edge-cases.mjs' test('loading confirmation assigns the authenticated PDA operator from the target warehouse', () => { const source = readFileSync(new URL('./dropship-pallet-matrix.mjs', import.meta.url), 'utf8') assert.match(source, /domestic-pda-operators\?warehouseId=\$\{summary\.warehouseId\}/) assert.match(source, /selectSessionPdaOperator\(operators, context\.pdaToken\)/) assert.match(source, /assigneeId: String\(operator\.userId\)/) }) 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']) const source = readFileSync(new URL('dropship-pallet-matrix.mjs', import.meta.url), 'utf8') const branch = source.slice(source.indexOf('if (stopBeforeOverseasPda)'), source.indexOf("await verify(await prepare('SINGLE-MULTI'")) assert.match(branch, /prepare\('BROWSER-MULTI', 2\)/) assert.match(branch, /convertOverseas\(\[record\]\)/) assert.match(branch, /READY_FOR_BROWSER_RECEIVING'[\s\S]*return/) assert.doesNotMatch(branch, /receiveOverseas|outboundOverseas|createPicker/) }) test('partial-pick rejection stays failed; a changed snapshot aborts further picking', async () => { const failures = [] const snapshot = { pallets: [{ pickedPackageCount: 0 }] } const edge = { label: 'SINGLE-SINGLE', task: { outboundNo: 'OD', taskNo: 'PDA' }, pick: { packages: 4, cartonMarks: ['A', 'B', 'C', 'D'], idempotencyKey: 'pick' }, context: { assert: (_module, name, _response, passed) => { if (!passed) failures.push(name) }, request: async () => ({ body: { code: 'A0409' } }) }, check: (_name, _response, passed) => assert.ok(passed), call: async () => structuredClone(snapshot), } assert.equal(await probePartialPick(edge), 0) assert.equal(failures.length, 1) assert.match(failures[0], /四箱任务先拣一箱成功/) let calls = 0 edge.call = async () => ({ pallets: [{ pickedPackageCount: calls++ }] }) await assert.rejects(probePartialPick(edge)) }) test('outbound replay preserves business identity and database millisecond timestamps', () => { const task = { id: '1', taskNo: 'PDA1', outboundNo: 'OD1', status: 'PENDING', pushedAt: '2026-09-14T20:24:45.920278604' } assert.equal(sameOutboundTask(task, { ...task, pushedAt: '2026-09-14T20:24:45.92' }), true) assert.equal(sameOutboundTask({ ...task, pushedAt: '2026-09-14T20:24:45.9999' }, { ...task, pushedAt: '2026-09-14T20:24:46' }), true) for (const key of ['id', 'taskNo', 'outboundNo', 'status', 'pushedAt']) { assert.equal(sameOutboundTask(task, { ...task, [key]: 'changed' }), false) } }) 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, /四箱任务先拣一箱成功/) assert.match(edges, /分批拣货失败后无库存明细变化/) assert.match(edges, /JSON.stringify\(before\) === JSON.stringify\(after\)/) assert.match(source, /await probePartialPick/) assert.match(source, /pick.packages -= count/) assert.match(source, /pick.cartonMarks = pick.cartonMarks.slice\(count\)/) }) test('outbound creation probes cover concurrent idempotency, invalid cartons and cancellation', () => { assert.match(edges, /Promise.all\(\[create\(\), create\(\)\]\)/) for (const scenario of ['超量创建', '错误SKU创建', '不存在箱唛创建', '重复箱唛创建', '箱数与箱唛不符创建', '出库创建同键异载荷返回A0409', '未拣齐禁止完成出库', '部分已拣禁止取消']) { assert.ok(edges.includes(scenario)) } assert.match(edges, /取消后逐票SKU可用箱数恢复/) assert.match(source, /await outboundCreationGuards\(edge\)/) assert.match(source, /await outboundTaskGuards\(\{ ...edge, task \}\)/) }) test('two inbound orders explicitly share a SKU without losing their separate identities', () => { assert.match(source, /label.startsWith\('MULTI-MERGE-'\)/) assert.match(source, /skuCodes = \[`SKU-SHARED-\$\{marker\}`/) assert.match(source, /row.inboundCode === candidate.inboundCode/) }) test('dedicated outbound uses exact source cartons and verifies completion and stock deduction', () => { assert.match(source, /await outboundOverseas\(group, picker.token\)/) assert.match(source, /one-click-dropship\?customerId=/) assert.match(source, /position.inboundCode === candidate.inboundCode/) assert.match(source, /cartonMarks: inputs\[index\].cartonMarks/) assert.match(source, /outbound.outbound\?\.status === 'OUTBOUNDED'/) assert.match(source, /!remaining.some/) }) test('dropship matrix has a real customer prerequisite without generic dispatch fixtures', () => { const modules = normalizeModuleOrder(['dropship-pallet-matrix']) assert.ok(modules.includes('merchant-qualification-fixture')) assert.ok(modules.includes('dropship-pallet-matrix')) assert.ok(!modules.includes('phase5-overseas-fulfillment')) }) test('physical-carton chain covers mixed SKU cartons without multiplying carton totals', () => { const modules = normalizeModuleOrder(['dropship-physical-carton-chain']) assert.deepEqual(modules, ['merchant-qualification-fixture', 'dropship-physical-carton-chain']) assert.match(source, /prepare\('PHYSICAL-20X2', 2[\s\S]*totalPackageCount: 20, skuCount: 2/) assert.match(source, /prepare\('PHYSICAL-5X3', 1[\s\S]*totalPackageCount: 5, skuCount: 3/) assert.match(source, /prepare\('PHYSICAL-7X2-MERGED', 2, \{ mergeToFirstPallet: true \}/) assert.match(source, /carton\.skuLines\.length === \(record\.expectedSkuCount \|\| 1\)/) assert.match(source, /packagesByPhysicalUnit/) assert.match(source, /new Set\(actual\.map\(\(item\) => item\.skuCode\)/) assert.match(source, /PHYSICAL_CARTON_OUTBOUND_COMPLETED/) }) test('all four physical pallet shapes are explicitly produced', () => { assert.match(source, /prepare\('SINGLE-MULTI', 2\)/) assert.match(source, /prepare\('SINGLE-SINGLE', 1\)/) assert.match(source, /prepare\('SINGLE-MERGE', 2, \{ mergeToFirstPallet: true \}\)/) assert.match(source, /prepare\('MULTI-MERGE-A', 1, \{ deferSeal: true \}\)/) assert.match(source, /prepare\('MULTI-MERGE-B', 1, \{ targetPalletNo \}\)/) assert.match(source, /await verify\(first, 1\)/) assert.match(source, /await verify\(second, 1\)/) }) test('allocation checks both physical summaries and uncollapsed per-inbound quantities', () => { assert.match(source, /first-leg\/loading-plans\/\$\{plan.id\}/) assert.match(source, /const actual = detail.items.filter/) assert.match(source, /plan.items\?\.length === pallets.length/) assert.match(source, /packagesByPhysicalUnit\.values\(\)[\s\S]*?record.expectedCartonMarks.length/) assert.match(source, /await conserve\('手工加入后'\)/) assert.match(source, /await conserve\('智能推荐后'\)/) assert.match(source, /fullFlowVerified: false/) }) test('each scenario reaches physical loading through released booking and asynchronous completion', () => { assert.match(source, /装柜前保存柜级预计到仓时间/) assert.match(source, /capacityCbm: 68, maxWeightKg: 19500, estimatedWarehouseArrivalAt/) assert.ok(source.indexOf('装柜前保存柜级预计到仓时间') < source.indexOf('读取自动生成装柜计划')) assert.match(source, /ocean-booking-link/) assert.match(source, /readiness\?\.canConfirm === true/) assert.match(source, /action: 'PICK_PALLET'/) assert.match(source, /inventorySnapshotToken: inventory.snapshotToken/) for (const type of ['PHOTO_STEP_1', 'PHOTO_STEP_2', 'PHOTO_STEP_3', 'PHOTO_STEP_4', 'SEAL']) { assert.ok(source.includes(`'${type}'`)) } assert.match(source, /await waitForCompletion\(context/) assert.match(source, /finalTask\?\.taskStatus === 'COMPLETED'/) assert.match(source, /inbound.summary\?\.processStatus === 'LOADED'/) }) test('shipping uses the current group and requires real departure and pickup readiness', () => { assert.match(source, /for \(const group of groups\) await progressShipping\(group\)/) assert.match(source, /containerBookingId: String\(record.loading.cabinet.id\)/) assert.match(source, /\['customs', completeCustoms\]/) assert.match(source, /\['departure', confirmDomesticDeparture\]/) assert.match(source, /\['pickup-ready', runUntilPickupReady\]/) assert.match(source, /confirmedStatus === 'WAITING_FOR_LOADING'/) assert.match(source, /phase4CarrierPickupReady\?\.ready === true/) }) test('overseas plans preserve per-inbound ownership and shared physical identity', () => { assert.match(source, /inbound-plans\/convert-container/) assert.match(source, /matching.length === 1/) assert.match(source, /JSON.stringify\(expectedNos\) === JSON.stringify\(actualNos\)/) assert.match(source, /byPallet.get\(pallet.palletNo\) === identity/) assert.match(source, /JSON.stringify\(replay\) === JSON.stringify\(plans\)/) }) test('overseas receipt uses scoped PDA identity and scans each physical pallet once', () => { assert.match(source, /await createPicker\(context/) assert.match(source, /await disablePicker\(context/) assert.match(source, /const sourceNos = \[\.\.\.new Set\(group.flatMap/) assert.match(source, /current\/receipts\/pallet/) assert.match(source, /duplicate.result === 'ALREADY_RECEIVED'/) assert.match(source, /Number\(inbound.receivedPackages\) === item.expectedCartonMarks.length/) assert.match(source, /completed.status === 'COMPLETED'/) assert.match(source, /fullFlowVerified: false/) assert.match(source, /stage = 'PHYSICAL_CARTON_OUTBOUND_COMPLETED'[\s\S]*?fullFlowVerified = true/) })