import assert from 'node:assert/strict' import fs from 'node:fs' import path from 'node:path' import test from 'node:test' import { fileURLToPath } from 'node:url' import { expandOverseasCartonCandidates, resolveCartonFixtures, run, uniqueContainerProducer } from './phase4-overseas-pda.mjs' test('receiving selects one current booking and supplies mandatory booking identity', () => { assert.match(source, /id\(item.sourceContainerBookingId\) === preferredBookingId/) assert.match(source, /matchingCandidates.length === 1/) assert.match(source, /if \(!longId\(sourceContainerBookingId\)\)/) assert.match(source, /sourceContainerBookingId=\$\{encodeURIComponent\(sourceContainerBookingId\)\}/) assert.equal((source.match(/\/prepare\$\{bookingQuery\}/g) || []).length, 3) }) test('one physical container is received once even when matrix roles overlap', () => { const fba = { containerNo: 'TEST-C1', fulfillmentType: 'STANDARD' } const overseas = { containerNo: 'TEST-C2' } assert.deepEqual([null, fba, { ...fba }, overseas].filter(uniqueContainerProducer), [fba, overseas]) assert.doesNotMatch(source, /dropship \|\| matrix/) }) const here = path.dirname(fileURLToPath(import.meta.url)) const source = fs.readFileSync(path.join(here, 'phase4-overseas-pda.mjs'), 'utf8') test('dropship original counted cartons must resolve exactly before any receiving or inventory checks', async () => { const inbounds = [{ inboundCode: 'REAL-INBOUND', destinationType: 'OVERSEAS', totalPackages: 2 }] const producer = { fulfillmentType: 'ONE_CLICK_DROPSHIP', inboundCode: 'REAL-INBOUND', destinationType: 'OVERSEAS', sealedCartonMarks: ['REAL-INBOUND#2-2', 'REAL-INBOUND#2-1'] } for (const fault of [null, 'missing-carton', 'wrong-carton', 'wrong-inbound', 'wrong-destination']) { const calls = []; const checks = [] const context = { async requestOverseas(method, requestPath, body) { calls.push({ method, requestPath, body }) const data = { cartonMark: body.cartonMark, inboundCode: 'REAL-INBOUND', destinationType: 'OVERSEAS' } if (fault === 'wrong-carton') data.cartonMark = 'ANOTHER-CARTON' if (fault === 'wrong-inbound') data.inboundCode = 'ANOTHER-INBOUND' if (fault === 'wrong-destination') data.destinationType = 'FBA' return { http: 200, body: { code: fault === 'missing-carton' ? 'A0404' : '200', message: 'Carton mark was not found', data }, requestId: 'request', traceId: 'trace' } }, assert(module, name, response, passed) { checks.push({ name, passed }) }, addStep(module, name, response, passed) { checks.push({ name, passed }) }, addCoverageGap() {}, } const result = await resolveCartonFixtures(context, 'token', inbounds, ['OLD-CANDIDATE#20-1'], producer) assert.ok(calls.every(call => call.method === 'POST' && call.requestPath.endsWith('/current/cartons/resolve'))) assert.ok(calls.every(call => producer.sealedCartonMarks.includes(call.body.cartonMark))) if (fault) { assert.equal(result, null, fault) assert.ok(checks.some(check => !check.passed), fault) assert.ok(!checks.some(check => check.name.includes('排除未实装箱码候选')), fault) } else { assert.deepEqual(result.map(item => item.cartonMark), producer.sealedCartonMarks) assert.equal(calls.length, 2) assert.ok(checks.every(check => check.passed)) } } for (const sealedCartonMarks of [undefined, [], ['REAL-INBOUND#2-1'], ['REAL-INBOUND#2-1', 'REAL-INBOUND#2-1']]) { const checks = [] const result = await resolveCartonFixtures({ assert(m,n,r,passed) { checks.push(passed) }, async requestOverseas() { assert.fail('incomplete physical fixture must not issue HTTP') } }, 'token', inbounds, producer.sealedCartonMarks, { ...producer, sealedCartonMarks }) assert.equal(result, null) assert.deepEqual(checks, [false]) } }) test('current loading event binds each pallet carton group to an explicit source detail', () => { const model = fs.readFileSync(path.join(here, '../../common-event-contract/src/main/java/com/baiyue/common/event/FirstLegLoadingPlanCompletedV2.java'), 'utf8') const producer = fs.readFileSync(path.join(here, '../../firstleg/src/main/java/com/baiyue/firstleg/service/LoadingCompletedOutboxService.java'), 'utf8') assert.match(model, /record SkuPackageSnapshot\(String sourceInboundDetailId, String skuCode/) assert.match(model, /sourceInboundDetailId == null \|\| sourceInboundDetailId\.isBlank\(\)/) assert.match(producer, /SkuPackageSnapshot\(detail\.getSourceInboundDetailId\(\)\.toString\(\), detail\.getSkuCode\(\)/) assert.match(producer, /groupingBy\(PalletRow::getSourceInboundDetailId/) assert.match(producer, /map\(PalletRow::getActualSkuQuantity\)/) assert.match(producer, /cartonQuantities\(detailRows\.getValue\(\), marks\)/) const measurement = fs.readFileSync(path.join(here, 'inbound-measurement.mjs'), 'utf8') const loading = fs.readFileSync(path.join(here, 'phase3-loading.mjs'), 'utf8') const finalize = fs.readFileSync(path.join(here, 'phase3-loading-finalize.mjs'), 'utf8') assert.match(measurement, /sealedCartonMarks\.push\(resolved\.boxCode\)/) assert.match(loading, /phase3Loading\.cartonMarks = marks/) assert.match(finalize, /sealedCartonMarks: Array\.isArray\(fixture\.sealedCartonMarks\)/) const entry = fs.readFileSync(path.join(here, '../admin-test-env-acceptance.mjs'), 'utf8') assert.match(entry, /sealedCartonMarks: Array\.isArray\(fixture\.sealedCartonMarks\)/) assert.match(entry, /runPhase4ProducerVariant\('OVERSEAS-PRIMARY', 'OVERSEAS', 'ONE_CLICK_DROPSHIP', true\)/) assert.match(source, /resolveCartonFixtures\(context, token, current\.data\.inbounds, producerCartonMarks, preferredProducer\)/) assert.ok(source.indexOf('const cartonFixtures = await resolveCartonFixtures') < source.indexOf('const completed = await pda')) }) test('PDA module is injected, token-scoped, traceable, and contains no direct network or secret source', () => { assert.match(source, /export async function run\(context\)/) assert.match(source, /requestOverseas\(/) assert.match(source, /requestOverseasMultipart\(/) assert.match(source, /requestId=/) assert.match(source, /traceId=/) assert.doesNotMatch(source, /https?:\/\//) assert.doesNotMatch(source, /fetch\(|process\.env/) assert.doesNotMatch(source, /planNo|allocationId/) }) test('PDA module covers the container-number receiving contract and excludes outbound domains', () => { for (const token of ['/candidates', '/prepare', '/current/unload-photos/files', '/current/sign', '/current/cartons/resolve', '/current/pallets/resolve', '/current/receipts/bulk', '/current/receipts/carton', '/current/receipts/pallet', '/current/complete']) assert.ok(source.includes(token), token) for (const token of ['/inventory/resolve', '/locations/resolve', '/pallets/resolve', '/operations?page=1&size=50']) { assert.ok(source.includes(token), token) } for (const role of ['LICENSE_PLATE', 'CONTAINER_SEAL', 'DOOR_OPEN']) assert.ok(source.includes(role), role) for (const forbidden of ['/outbound', '/dispatch', '/delivery', '/internal/']) assert.equal(source.includes(forbidden), false, forbidden) assert.match(source, /同键同载荷重放/) assert.match(source, /inbound-plans\/container-preview/) assert.match(source, /orders\/container-arrival-times\/query/) assert.match(source, /container-summaries\/\$\{encodeURIComponent\(bookingId\)\}\/expected-arrival/) assert.match(source, /estimatedWarehouseArrivalAt, expectedVersion: Number\(arrivalTime\.version\)/) assert.match(source, /snapshotVersion: Number\(preview\.snapshotVersion\)/) assert.match(source, /snapshotHash: String\(preview\.snapshotHash\)/) assert.match(source, /rememberGeneratedPlans\(context, plans\)/) assert.match(source, /resolveGeneratedPallet\(context, inbound\)/) assert.match(source, /同键异载荷冲突/) assert.match(source, /跨柜切换锁返回409/) assert.match(source, /item\.operable === true && item\.allPlansReady === true/) assert.match(source, /第二仓库操作员抢占同柜返回409/) assert.match(source, /OVERSEAS_WAREHOUSE_PDA_OPERATOR/) assert.match(source, /lastleg:pda:operate/) assert.match(source, /安全投影已收敛/) assert.match(source, /projectionReady = success\(projectionResponse\) \|\| notFound\(projectionResponse\)/) assert.match(source, /setTimeout\(resolve, 500\)/) assert.match(source, /重复累计不产生超收/) assert.match(source, /重复箱新键被拒绝/) assert.match(source, /key\(context, `fba-bulk-\$\{index\}`\)/) assert.match(source, /inbounds\.reduce\(\(total, inbound\) => total \+ Number\(inbound\.totalPackages \|\| 0\), 0\)/) assert.match(source, /resolvedInboundCodes\.includes\(scannedInboundCode\)/) assert.match(source, /duplicate\.data\?\.result === 'ALREADY_RECEIVED'/) assert.match(source, /if \(packageCount <= 0\) return true/) assert.match(source, /key\(context, `overseas-carton-\$\{index\}`\)/) assert.match(source, /模式硬拒绝/) assert.match(source, /current返回404正常空态/) assert.match(source, /安全扫描返回A0400/) assert.match(source, /warehouse-admin\?tenantId=0&.*capabilityCode=RECEIVING&warehouseType=OVERSEAS&status=ACTIVE/) assert.match(source, /193000000000200003.*193000000000200004/) assert.doesNotMatch(source, /requestOverseas\('POST', '\/api\/platform\/v1\/warehouse-admin'/) assert.doesNotMatch(source, /requestOverseas\('GET', '\/api\/platform\/v1\/warehouse-admin'/) assert.match(source, /receiving-containers\/current\/pallets\/resolve/) assert.match(source, /overseasWarehouseId, password/) assert.match(source, /inbound\?\.overseasWarehouseId/) assert.doesNotMatch(source, /const warehouseId = id\(inbound\?\.destinationWarehouseId\)/) assert.match(source, /禁止在PDA收货时临时新建脱离计划的托盘/) assert.match(source, /rememberGeneratedPlans\(context, plans\)/) assert.match(source, /resolveGeneratedPallet\(context, inbound\)/) assert.match(source, /generatedPalletsByInbound/) assert.match(source, /standardFbaProducers = \(matrix\?\.byDestinationType\?\.FBA \|\| \[\]\)/) assert.match(source, /item\?\.fulfillmentType !== 'ONE_CLICK_DROPSHIP'/) assert.match(source, /\.slice\(0, 2\)/) assert.match(source, /const producers = \[dropship, \.\.\.standardFbaProducers,/) assert.doesNotMatch(source, /创建当前柜实收托盘|创建当前柜实收库位|async function createPallet/) assert.match(source, /retained as acceptance evidence/) assert.match(source, /preferredProducer\?\.cartonMarks\?\.length/) assert.match(source, /const cartonCandidates =/) assert.doesNotMatch(source, /current\/skus\/resolve/) assert.doesNotMatch(source, /skuCode:/) assert.match(source, /FBA箱码与OVERSEAS真实manifest箱码必须HTTP 200\/code 200并返回当前柜唯一入仓码/) assert.match(source, /排除未实装箱码候选/) assert.match(source, /const matches = inbounds\.filter/) assert.match(source, /!Array\.isArray\(resolved\?\.inboundCode\)/) assert.match(source, /matches\.length === 1/) assert.match(source, /destinations\.size === 1/) assert.doesNotMatch(source, /\.split\([^\n]*['"]#['"]/) assert.match(source, /`\$\{item\.inboundCode\}#PDA-CONTRACT-\$\{index \+ 1\}`/) for (const negative of ['无#箱码被服务端拒绝', '空#前缀箱码被服务端拒绝', '未命中入仓码前缀被服务端拒绝', '其他柜箱码前缀被当前锁定柜拒绝', 'OVERSEAS缺少真实manifest行时不得按标准箱码推导']) { assert.ok(source.includes(negative), negative) } assert.doesNotMatch(source, /FBA标准箱码序号超范围被拒绝/) assert.match(source, /FBA与OVERSEAS箱码resolve均为纯查询且零状态写入/) assert.match(source, /receivingSnapshot\(afterResolve\.data\) === beforeResolve/) assert.match(source, /const fixturesConserved = overseasFixtures\.length === expectedPending/) assert.match(source, /OVERSEAS实装箱唛与待收箱数守恒/) assert.match(source, /for \(const \[index, fixture\] of overseasFixtures\.entries\(\)\)/) assert.doesNotMatch(source, /for \(const \[index, cartonMark\] of cartonMarks\.entries\(\)\)/) assert.match(source, /last-leg\/orders\?page=1&size=200&containerNo=/) assert.match(source, /phase4OverseasPda\.orderId = id\(order\.id\)/) assert.match(source, /phase4OverseasPda\.customerId = id\(order\.customerId\)/) assert.match(source, /现场库存查询只返回Token绑定仓且包含库存位置/) assert.match(source, /作业历史包含本轮柜号收货动作且分页稳定/) assert.match(source, /awaitProducerConsumption\(context, allProducers\)/) assert.match(source, /last-leg\/orders\?page=1&size=200&containerNo=/) assert.match(source, /跨区广播消费观察/) assert.match(source, /跨区广播消费观察 \$\{containerNo\}`,[\s\S]*?response,[\s\S]*?true,/) assert.match(source, /OBSERVATION_OPTIONS = \{ scenario: SCENARIO, deploymentBlocker: false \}/) assert.doesNotMatch(source, /blockerGap\(context, '跨区消费生产矩阵'/) assert.match(source, /awaitPickupReadyCandidates\(context, primary\.token, producers, allProducers\)/) assert.match(source, /missingPickupReady=/) assert.match(source, /item\?\.shippingReady === false/) assert.match(source, /!expectedSet\.has\(id\(item\?\.containerNo\)\)/) assert.match(source, /unexpectedAwaiting=/) assert.match(source, /candidatesByContainer\.get\(containerNo\)\?\.operable === true/) assert.match(source, /candidatesByContainer\.get\(containerNo\)\?\.operable !== true/) assert.match(source, /missingPickupReady\.length === 0 && unexpectedAwaiting\.length === 0/) assert.doesNotMatch(source, /shippingReady == null|shippingReady !== true/) assert.match(source, /nonBlank\(item\?\.resourceKey\)/) assert.match(source, /item\?\.shippingReady !== false/) assert.match(source, /unexpectedHistorical=/) assert.doesNotMatch(source, /awaitProducerCandidates/) }) test('OVERSEAS carton discovery probes the frozen sequence and lets the server exclude skipped cartons', () => { assert.deepEqual(expandOverseasCartonCandidates([ { inboundCode: 'NYEXX2609020042', destinationType: 'OVERSEAS' }, { inboundCode: 'FBA2609020001', destinationType: 'FBA' }, ], ['NYEXX2609020042#8-5', 'NYEXX2609020042#8-2', 'NYEXX2609020042#8-1']), [ 'NYEXX2609020042#8-1', 'NYEXX2609020042#8-2', 'NYEXX2609020042#8-3', 'NYEXX2609020042#8-4', 'NYEXX2609020042#8-5', 'NYEXX2609020042#8-6', 'NYEXX2609020042#8-7', 'NYEXX2609020042#8-8', ]) assert.deepEqual(expandOverseasCartonCandidates([ { inboundCode: 'NYEXX2609020042', destinationType: 'OVERSEAS' }, ], ['NYEXX2609020042#2001-1']), []) }) test('real-device legacy FBA carton rejection is retained as regression evidence', () => { const evidence = { http: 200, code: 'A0409', message: 'FBA destination must use bulk count', requestId: '5e925a3d-8184-41e1-b0d2-f4e1aae86639', traceId: '62fdf603b65fd7f43ec452bb8a6ca185', } assert.deepEqual(evidence, { http: 200, code: 'A0409', message: 'FBA destination must use bulk count', requestId: '5e925a3d-8184-41e1-b0d2-f4e1aae86639', traceId: '62fdf603b65fd7f43ec452bb8a6ca185', }) assert.match(source, /a0409Message\(response, \/\^FBA destination must use bulk count\$\//) assert.match(source, /a0409Message\(response, \/\^OVERSEAS destination must use carton-by-carton scanning; bulk count is forbidden\$\//) }) test('real-device deployed FBA resolve A0404 is retained until server-side inbound-prefix matching lands', () => { const evidence = { http: 200, code: 'A0404', message: 'Carton mark was not found', requestId: 'a716e2c2-dc10-4d07-8014-7438afb63090', traceId: '6ab8fcfcd812056ad6570d3b1652157b', databaseFact: 'current FBA has no ll_inbound_plan_carton manifest', } assert.equal(evidence.code, 'A0404') assert.equal(evidence.message, 'Carton mark was not found') assert.match(evidence.databaseFact, /no ll_inbound_plan_carton manifest/) assert.match(source, /a0404Message\(response, \/\^Carton mark was not found\$\//) }) test('PDA receiving lifecycle is ordered from candidate claim through photos, mode-specific receipts, pallet and complete', () => { const orderedTokens = [ "'查询当前客户待收柜号'", "'选择柜号并建立WAIT_SIGN会话'", 'uploadPhotos(context, token)', "'绑定车牌封签开柜三角色照片并签收'", 'resolveGeneratedPallet(context, inbound)', "`解析${inbound.destinationType}实物托盘`", 'receiveFba(context, token', 'receiveOverseas(context, token', "'回查柜内逐入仓码实收进度'", "'完成当前柜并写入库存与卸柜事件'", "'完成后current返回404正常空态'", ] let cursor = -1 for (const token of orderedTokens) { const next = source.indexOf(token, cursor + 1) assert.ok(next > cursor, `${orderedTokens[Math.max(0, orderedTokens.indexOf(token) - 1)]} -> ${token}`) cursor = next } for (const token of [ '/current/receipts/bulk', '/current/receipts/carton', "'FBA模式硬拒绝逐箱收货'", "'OVERSEAS模式硬拒绝批量收货'", 'PHOTO_ROLES.every', "finalStatus === 'COMPLETED'", 'completedContainers.push', 'completedContainerFacts.push', 'expectedPalletCost', 'physicalPallets', 'unitPrice', ]) assert.ok(source.includes(token), token) }) test('missing OVERSEAS_WAREHOUSE_PDA_OPERATOR overseas authority is a deployment blocker', async () => { const calls = []; const gaps = []; const steps = [] const context = { marker: 'PDA-CONTRACT', context: {}, createdResources: {}, async request(method, requestPath) { calls.push({ method, requestPath }) return { method, path: requestPath, http: 200, body: { code: '200', data: [] }, requestId: 'request-contract-1', traceId: 'trace-contract-0001' } }, async requestOverseas(...args) { calls.push(args); throw new Error('must not request without credentials') }, async requestOverseasMultipart(...args) { calls.push(args); throw new Error('must not request without credentials') }, apiSuccess(module, name, response) { return response.body.data }, addStep(...args) { steps.push(args) }, assert(module, name, response, passed, evidence, options) { steps.push({ module, name, passed, evidence, options }) }, addCoverageGap(...args) { gaps.push(args) }, } await run(context) assert.ok(calls.some((call) => call.requestPath === '/api/platform/v1/iam/roles')) assert.ok(calls.some((call) => call.requestPath === '/api/platform/v1/iam/permissions')) assert.ok(gaps.some((gap) => gap[2] === '海外PDA仓库操作员权限')) assert.ok(steps.some((step) => step?.passed === false && step?.options?.deploymentBlocker)) }) test('empty candidate response cannot become a zero-business-request pass', async () => { const calls = []; const gaps = []; const steps = [] const response = (data, requestPath, method = 'GET') => ({ method, path: requestPath, http: 200, body: { code: '200', data }, requestId: 'req-contract', traceId: 'trace-contract' }) let userSequence = 0 const roleId = '4000005'; const permissionId = '5107015' const context = { marker: 'PDA-CONTRACT', overseasToken: '', createdResources: {}, context: {}, async findAvailablePhone() { userSequence += 1; return `1750000000${userSequence}` }, async request(method, requestPath, body) { calls.push({ channel: 'domestic', method, requestPath }) if (method === 'GET' && requestPath.endsWith('/roles')) return response([ { id: roleId, roleCode: 'OVERSEAS_WAREHOUSE_PDA_OPERATOR', status: 'ACTIVE', permissionIds: [permissionId] }, ], requestPath, method) if (method === 'GET' && requestPath.endsWith('/permissions')) return response([ { id: permissionId, permissionCode: 'lastleg:pda:operate' }, ], requestPath, method) if (method === 'GET' && requestPath.startsWith('/api/platform/v1/warehouse-admin?')) return response({ records: [{ id: '193000000000200003', warehouseName: '洛杉矶仓', warehouseType: 'OVERSEAS', status: 'ACTIVE' }], page: 1, size: 200, total: 1, pages: 1, }, requestPath, method) if (method === 'POST' && requestPath.endsWith('/users')) return response({ membershipId: String(9000 + userSequence), username: body.username, tenantId: '0', roleIds: [roleId], overseasWarehouseId: String(body.overseasWarehouseId), status: 'ACTIVE', }, requestPath, method) if (method === 'POST' && requestPath === '/api/auth/login') return response({ overseasAccessToken: `pda-token-${userSequence}` }, requestPath, method) if (method === 'PUT' && /\/users\/\d+\/status$/.test(requestPath)) return response({ status: 'DISABLED' }, requestPath, method) throw new Error(`unexpected domestic request ${method} ${requestPath}`) }, async requestOverseas(method, requestPath, body, headers, token) { calls.push({ channel: 'overseas', method, requestPath }) if (requestPath.endsWith('/current')) return { method, path: requestPath, http: 200, body: { code: 'A0404', message: 'not found' }, requestId: 'req-current', traceId: 'trace-current' } return response([], requestPath, method) }, apiSuccess(module, name, result) { return result.body.data }, addStep(module, name, result, passed, evidence, options) { steps.push({ module, name, result, passed, evidence, options }) }, assert(module, name, result, passed, evidence, options) { steps.push({ module, name, result, passed, evidence, options }) }, addCoverageGap(...args) { gaps.push(args) }, } await run(context) assert.ok(calls.some((call) => call.requestPath.endsWith('/candidates'))) assert.ok(steps.some((step) => step.name.includes('正常空态') && step.passed)) assert.ok(steps.some((step) => step.name.includes('PDA柜号候选覆盖前置') && !step.passed && step.options.deploymentBlocker)) assert.equal(gaps.length, 1) assert.equal(context.createdResources.phase4OverseasPda.finalStatus, null) assert.equal(context.createdResources.phase4OverseasPda.pickerMembershipIds.length, 2) assert.equal(calls.filter((call) => /\/users\/\d+\/status$/.test(call.requestPath)).length, 2) })