import test from 'node:test' import assert from 'node:assert/strict' import { readFile } from 'node:fs/promises' const read = (path) => readFile(new URL(path, import.meta.url), 'utf8') test('overseas PDA is an independent Vue 3 app with separate packaging identity', async () => { const [manifest, pages, pkg, vite, styles] = await Promise.all([ read('../src/manifest.json'), read('../src/pages.json'), read('../package.json'), read('../vite.config.js'), read('../src/styles/pda.scss') ]) assert.match(manifest, /"name"\s*:\s*"柏悦物流\s*PDA\s*海外版"/) assert.match(manifest, /"packagename"\s*:\s*"com\.baiyuelogistics\.pda\.overseas"/) assert.match(manifest, /"vueVersion"\s*:\s*"3"/) assert.doesNotMatch(pages, /pages\/picking|pages\/tasks/) assert.match(pkg, /"build:app"\s*:\s*"uni build -p app"/) assert.match(vite, /API_PROXY_TARGET/) assert.doesNotMatch(vite, /OVERSEAS_API_PROXY_TARGET|gateway-overseas/) assert.match(vite, /\/api/) assert.match(vite, /removeHeader\('origin'\)/) assert.match(vite, /removeHeader\('referer'\)/) assert.match(vite, /__VUE_I18N_FULL_INSTALL__:\s*true/) assert.match(vite, /__VUE_I18N_LEGACY_API__:\s*false/) assert.match(vite, /__VUE_I18N_PROD_DEVTOOLS__:\s*false/) assert.match(vite, /__INTLIFY_PROD_DEVTOOLS__:\s*false/) assert.match(styles, /home-operation-grid[^}]*grid-template-columns:\s*repeat\(3/) assert.match(styles, /home-operation[^}]*width:\s*100%/) assert.doesNotMatch(styles, /home-operation-primary[^}]*grid-column:\s*1\s*\/\s*-1/) }) test('overseas workbench contains confirmed warehouse and phase-five outbound domains without delivery', async () => { const [data, session, runtime] = await Promise.all([ read('../src/data/overseas-workbench.js'), read('../src/stores/session.js'), read('../src/config/runtime.js') ]) for (const label of ['入仓作业', '拆柜收货', '退货入库', '合箱', '出库履约', '拣货', '出库', '库内作业', '换标作业', '现场查询']) assert.match(data, new RegExp(label)) for (const excluded of ['集装箱拣货', '装柜取证', '海外派送', '派送单', '客户回访']) assert.doesNotMatch(data, new RegExp(excluded)) assert.match(session, /overseas-pda-auth-session/) assert.match(runtime, /VITE_API_BASE_URL/) }) test('login has no committed demo credentials and inbound operation implements the locked container flow', async () => { const [login, auth, runtime, operation, inbound, adapter, overseas] = await Promise.all([ read('../src/pages/login/login.vue'), read('../src/api/auth.js'), read('../src/config/runtime.js'), read('../src/pages/operation/operation.vue'), read('../src/api/inbound.js'), read('../src/api/receiving-adapter.js'), read('../src/api/overseas.js') ]) assert.match(login, /柏悦物流海外版/) assert.doesNotMatch(login, /ChangeMe|platform_admin/) assert.match(login, /DEV_LOGIN\.username|DEV_LOGIN\.password/) assert.match(auth, /authRequest\('\/auth\/login', \{ loginId, password \}\)/) assert.doesNotMatch(auth, /authRequest\('\/auth\/login', \{ username, password \}\)/) assert.match(runtime, /runtimeEnvironment\.DEV|VITE_PDA_DEV_USERNAME|VITE_PDA_DEV_PASSWORD/) assert.match(runtime, /'\/gateway-api'/) assert.match(runtime, /#ifdef H5[\s\S]*?VITE_H5_API_BASE_URL/) assert.match(runtime, /#ifdef H5[\s\S]*?VITE_H5_API_BASE_URL/) assert.match(operation, /startScanBroadcast/) assert.match(operation, /palletWorkflowActive = Boolean\(this\.activeInbound \|\| this\.resolvedPallet\)/) assert.match(operation, /queuedTarget: scan\.target/) for (const action of ['prepareReceivingContainer', 'getCurrentReceiving', 'resolveCurrentCarton', 'resolveCurrentPallet', 'receiveCurrentBulk', 'receiveCurrentCarton', 'receiveCurrentWholePallet', 'completeCurrentReceiving']) assert.match(operation, new RegExp(action)) for (const path of ['/candidates', '/prepare', '/unload-photos/files', '/sign', '/cartons/resolve', '/pallets/resolve', '/receipts/bulk', '/receipts/carton', '/receipts/pallet', '/complete']) assert.match(inbound, new RegExp(path.replaceAll('/', '\\/'))) assert.match(inbound, /current\('\/pallets\/resolve'\)/) assert.doesNotMatch(inbound, /platform\/v1\/warehouse-admin/, 'PDA只能调用current/pallets/resolve,不能访问Admin仓储资源接口') assert.doesNotMatch(operation, /resolveCurrentSku|resolvedSku|skuInput|selectedInboundCode|chooseInbound|class="inbound-tabs"/) assert.doesNotMatch(operation, /InboundList|柜内入仓码/) assert.doesNotMatch(inbound, /skus\/resolve/) assert.doesNotMatch(operation, /\.split\([^\n]*['"]#['"]/, '前端必须原样提交完整箱码,禁止按 # 猜测入仓码或箱序号') assert.match(operation, /const payload = isFba \? \{ inboundCode: this\.activeInbound\.inboundCode, palletCode: this\.resolvedPallet\.palletCode, packageCount: Number\(this\.packageCount\), expectedPalletVersion: this\.resolvedPallet\.version \}/) assert.doesNotMatch(operation, /skuCode/) assert.match(operation, /resolveCurrentCarton\(cartonMark\)[\s\S]*value\.operationMode !== 'DROPSHIP_PALLET'/) assert.match(operation, /name="carton"[\s\S]*@submit="resolveCarton"/) assert.match(operation, /sourcePalletNo: value\.sourcePalletNo \|\| cartonMark[\s\S]*receiveCurrentWholePallet/) assert.match(operation, /clearReceiptContext\(\)[\s\S]*activeScanTarget = 'carton'[\s\S]*scheduleScanDrain/) assert.match(operation, /name="pallet"[\s\S]*@activate="activeTarget = 'pallet'"/) assert.match(operation, /receiving\.activeScanTarget = 'pallet'/, '普通FBA或中转解析后直接进入托盘扫码以减少操作') assert.match(operation, /\[Overseas PDA scan\] dispatch/) assert.match(operation, /\[Overseas PDA cargo\] failed[\s\S]*requestId[\s\S]*traceId/) for (const role of ['LICENSE_PLATE', 'CONTAINER_SEAL', 'DOOR_OPEN']) assert.match(adapter, new RegExp(role)) for (const forbidden of ['customerId', 'planNo', 'allocationId', 'warehouseId', 'locationId']) assert.doesNotMatch(inbound, new RegExp(forbidden)) assert.match(operation, /isReceivingConflict|recoverCurrentAfterConflict/) assert.match(operation, /isNoCurrentReceiving\(recoveryError\)[\s\S]*柜状态已变化,列表已刷新/) assert.match(operation, /onShow\(\)[\s\S]*this\.phase === 'candidates'[\s\S]*this\.bootstrap\(\)/) assert.match(operation, /candidateLoading[\s\S]*candidateError/) assert.match(operation, /candidateError\.requestId[\s\S]*candidateError\.traceId/) assert.match(operation, /@tap="loadCandidates"[^>]*>重新加载/) assert.doesNotMatch(operation, /扫描柜号|查询柜号<\/button>|v-model="containerInput"/) assert.match(operation, /phase === 'candidates'[^\n]*请直接点击待卸货集装箱进入详情/) assert.match(operation, /async loadCandidates\(feedback = true, refreshWarehouse = true\)[\s\S]*if \(this\.candidateLoading\) return[\s\S]*this\.candidateError = error/) assert.match(operation, /async prepareContainer\(\)[\s\S]*this\.busy\) return/) assert.match(operation, /prepareReceivingContainer[\s\S]*releaseIdempotencyKey\(key\.signature\)/) assert.match(overseas, /replaceTokens|refreshOverseasToken/) assert.match(overseas, /requestId/) }) test('warehouse operations use dedicated real pages and contain no prototype task data', async () => { const [operation, home, workbench, pages] = await Promise.all([ read('../src/pages/operation/operation.vue'), read('../src/pages/home/home.vue'), read('../src/data/overseas-workbench.js'), read('../src/pages.json') ]) assert.doesNotMatch(operation, /prototypeScan|prototypeInput/) assert.doesNotMatch(workbench, /PROTOTYPES|getOperationPrototype|模拟成功/) for (const key of ['move', 'relabelReceive', 'inventoryQuery', 'locationQuery', 'palletQuery']) { assert.match(workbench, new RegExp(`key: '${key}'[^\\n]*available: true[^\\n]*route:`)) } assert.doesNotMatch(workbench, /key: 'stocktake'/) for (const page of ['pages/stocktake/stocktake', 'pages/move/move', 'pages/relabel/relabel', 'pages/query/query']) assert.match(pages, new RegExp(page)) assert.match(home, /operation\.route \|\|/) }) test('receiving phases use a compiled container summary with cabinet and aggregate facts', async () => { const [operation, summary] = await Promise.all([ read('../src/pages/operation/operation.vue'), read('../src/components/ReceivingSessionSummary.vue') ]) assert.match(operation, /import ReceivingSessionSummary/) assert.equal((operation.match(/ { const operation = await read('../src/pages/operation/operation.vue') assert.match(operation, /class="container-card container-card-compact"[\s\S]*container-card-disabled[\s\S]*@tap="openContainer\(item\)"/) assert.match(operation, /class="container-detail-link"/) assert.doesNotMatch(operation, /