#!/usr/bin/env node /** * 为已授权的非生产环境尽力生成 IT测试/义乌仓 WAIT_SPLIT 数据。 * * 每票随机 1-300 箱(可通过参数缩小),失败会记录后继续;不会自动清理已写入数据。 * 测量前置只复用 TP-001/YW-TP-002,不创建托盘;库位由正式仓储接口分配, * 普通位不足时由服务端落公共位 A0,脚本不创建库位。 * 账号、密码和 Gateway 只从 admin-test-env-acceptance.mjs 支持的环境变量读取。 */ import { randomInt } from 'node:crypto' import { appendFile, mkdir, writeFile } from 'node:fs/promises' import { resolve } from 'node:path' import process from 'node:process' import { AcceptanceRun, parseArguments, validateExecutionOptions } from './admin-test-env-acceptance.mjs' function argumentsMap(argv) { const values = new Map() for (let index = 0; index < argv.length; index += 1) { const argument = argv[index] if (!argument.startsWith('--')) throw new Error(`无法识别的参数: ${argument}`) const name = argument.slice(2) const value = argv[index + 1] if (!value || value.startsWith('--')) throw new Error(`参数 --${name} 缺少值`) values.set(name, value) index += 1 } return values } function positiveInteger(value, fallback, name) { const parsed = Number(value ?? fallback) if (!Number.isInteger(parsed) || parsed < 1) throw new Error(`${name}必须是正整数`) return parsed } function increment(record, key) { const normalized = String(key || 'UNKNOWN') record[normalized] = Number(record[normalized] || 0) + 1 } async function main() { const values = argumentsMap(process.argv.slice(2)) const count = positiveInteger(values.get('count'), 8000, 'count') const batchSize = positiveInteger(values.get('batch-size'), 100, 'batch-size') const startIndex = positiveInteger(values.get('start-index'), 1, 'start-index') const boxMin = positiveInteger(values.get('box-min'), 1, 'box-min') const boxMax = positiveInteger(values.get('box-max'), 300, 'box-max') if (boxMax < boxMin || boxMax > 300) throw new Error('箱数范围必须满足1 <= box-min <= box-max <= 300') const customerName = values.get('customer-name') || 'IT测试' const originWarehouseName = values.get('origin-warehouse-name') || '义乌仓' const workerId = values.get('worker-id') || 'worker-1' const timestamp = new Date().toISOString().replace(/\D/g, '').slice(0, 14) const outputDirectory = resolve(values.get('output-dir') || `reports/it-test-wait-split-8000-${timestamp}`) const journalPath = resolve(outputDirectory, 'results.jsonl') const summaryPath = resolve(outputDirectory, 'summary.json') const failureDirectory = resolve(outputDirectory, 'failures') await mkdir(failureDirectory, { recursive: true }) const summary = { workerId, customerName, originWarehouseName, requested: count, startIndex, batchSize, boxMin, boxMax, attempted: 0, succeeded: 0, failed: 0, totalPackages: 0, totalPallets: 0, minPackages: null, maxPackages: null, channels: {}, destinationTypes: {}, processId: process.pid, startedAt: new Date().toISOString(), updatedAt: new Date().toISOString(), outputDirectory, } await writeFile(summaryPath, `${JSON.stringify(summary, null, 2)}\n`, 'utf8') console.log(`OUTPUT_DIR=${outputDirectory}`) for (let offset = 0; offset < count; offset += 1) { const sequence = startIndex + offset const packageCount = randomInt(boxMin, boxMax + 1) const randomValue = randomInt(1, 2_147_483_647) const batchNo = Math.floor(offset / batchSize) + 1 const planned = { event: 'planned', sequence, batchNo, packageCount, randomValue, plannedAt: new Date().toISOString() } await appendFile(journalPath, `${JSON.stringify(planned)}\n`, 'utf8') let report = null let thrown = null try { const options = validateExecutionOptions(parseArguments([ '--modules', 'orders,inbound-measurement', '--customer-name', customerName, '--origin-warehouse-name', originWarehouseName, '--order-profile-mode', 'randomized', '--route-mode', 'random', '--wait-split-count', '1', '--wait-split-package-count', String(packageCount), '--wait-split-sequence', String(sequence), '--wait-split-random-value', String(randomValue), '--quiet', 'true', ])) options.markerNamespace = workerId report = await new AcceptanceRun(options).run() } catch (error) { thrown = error instanceof Error ? error.message : String(error) } const fixture = report?.createdResources?.waitSplitFixtures?.find?.( (item) => Number(item.sequence) === sequence) const completed = Boolean(fixture?.processStatus === 'WAIT_SPLIT' && fixture?.reviewStatus === 'APPROVED' && Number(fixture?.totalPackageCount) === packageCount && Number(fixture?.palletCount) === Math.ceil(packageCount / 30) && fixture?.receivingWarehouseName === originWarehouseName) const result = { event: 'result', sequence, batchNo, packageCount, completed, orderId: fixture?.orderId || null, orderNo: fixture?.orderNo || null, inboundId: fixture?.inboundId || null, inboundCode: fixture?.inboundCode || null, palletCount: fixture?.palletCount || null, channelCode: fixture?.channelCode || null, channelName: fixture?.channelName || null, destinationType: fixture?.destinationType || null, destinationWarehouseCode: fixture?.destinationWarehouseCode || null, reportSuccess: report?.success ?? false, assertionFailures: report?.summary?.failed ?? null, deploymentBlockers: report?.deploymentBlockers || [], error: thrown, finishedAt: new Date().toISOString(), } await appendFile(journalPath, `${JSON.stringify(result)}\n`, 'utf8') summary.attempted += 1 if (completed) { summary.succeeded += 1 summary.totalPackages += packageCount summary.totalPallets += Number(fixture.palletCount) summary.minPackages = summary.minPackages === null ? packageCount : Math.min(summary.minPackages, packageCount) summary.maxPackages = summary.maxPackages === null ? packageCount : Math.max(summary.maxPackages, packageCount) increment(summary.channels, fixture.channelCode) increment(summary.destinationTypes, fixture.destinationType) } else { summary.failed += 1 if (report) { await writeFile(resolve(failureDirectory, `sequence-${String(sequence).padStart(5, '0')}.json`), `${JSON.stringify(report, null, 2)}\n`, 'utf8') } } summary.updatedAt = new Date().toISOString() summary.averagePackages = summary.succeeded > 0 ? Number((summary.totalPackages / summary.succeeded).toFixed(3)) : null summary.averagePallets = summary.succeeded > 0 ? Number((summary.totalPallets / summary.succeeded).toFixed(3)) : null if (summary.attempted % batchSize === 0 || !completed || summary.attempted === count) { await writeFile(summaryPath, `${JSON.stringify(summary, null, 2)}\n`, 'utf8') console.log(`PROGRESS attempted=${summary.attempted}/${count} succeeded=${summary.succeeded} failed=${summary.failed} batch=${batchNo}`) } } summary.finishedAt = new Date().toISOString() await writeFile(summaryPath, `${JSON.stringify(summary, null, 2)}\n`, 'utf8') console.log(`RESULT attempted=${summary.attempted} succeeded=${summary.succeeded} failed=${summary.failed}`) } main().catch((error) => { console.error(error instanceof Error ? error.message : String(error)) process.exitCode = 1 })