test(firstleg): harden measurement demo package

This commit is contained in:
2026-09-01 12:03:21 +08:00
parent 6eaf484fcd
commit 9e32efdd90
6 changed files with 208 additions and 32 deletions
@@ -3,6 +3,11 @@ import { readFileSync } from 'node:fs'
import test from 'node:test'
const source = readFileSync(new URL('inbound-measurement.mjs', import.meta.url), 'utf8')
const read = (relativeUrl) => readFileSync(new URL(relativeUrl, import.meta.url), 'utf8')
const labelTests = read('../../firstleg/src/test/java/com/baiyue/firstleg/service/InboundCartonLabelServiceTest.java')
const measurementTests = read('../../firstleg/src/test/java/com/baiyue/firstleg/service/InboundMeasurementServiceTest.java')
const cartonTests = read('../../firstleg/src/test/java/com/baiyue/firstleg/service/CartonMeasurementServiceTest.java')
const reviewTests = read('../../firstleg/src/test/java/com/baiyue/firstleg/service/MeasurementReviewServiceTest.java')
test('measurement acceptance scans the stable box code frozen by first print', () => {
assert.match(source, /const boxCode = `\$\{inboundCode\}#\$\{originalPackageCount\}-1`/)
@@ -32,3 +37,37 @@ test('measurement fixture derives pallet resources from confirmed image count',
test('volume ceiling removes binary floating point noise before scaling', () => {
assert.match(source, /Number\.EPSILON \* Math\.max\(1, Math\.abs\(value\)\) \* 4/)
})
test('demo package freezes repeated label printing and Mojia dimension replacement', () => {
assert.match(labelTests, /repeatedAndPartialPrintsReadFrozenMappingWithoutReallocation/)
assert.match(labelTests, /changedCargoKeepsTheFirstFrozenBoxCodes/)
assert.match(cartonTests, /repeatedScanReplacesDeviceValuesButPreservesManualCountAndOldImageIsDeleted/)
assert.match(source, /增量 fixture 形成动态真实独立货物测量明细/)
})
test('demo package covers OCR failure, manual confirmation, aggregation and stale versions', () => {
assert.match(source, /OCR无有效重量时转人工确认/)
assert.match(source, /称重旧版本确认被拒绝且不覆盖已确认结果/)
assert.match(source, /图片张数决定托数且净重汇总已落地/)
assert.match(measurementTests, /ocrFailureCreatesManualWeightThatCanBeAdjustedAndConfirmed/)
assert.match(measurementTests, /confirmingFifthPalletSeparatelyAggregatesGrossAndCargoNetWeight/)
assert.match(measurementTests, /confirmingWithStaleVersionDoesNotTreatRequestAsIdempotent/)
})
test('demo package keeps pallet pages bounded and historical snapshots read-only', () => {
assert.match(source, /weight-pallet-options[^\n]+page=1&size=20&keyword=/)
assert.match(source, /records\.length <= 20/)
assert.match(measurementTests, /detailUsesStoredPalletSnapshotAfterCandidateBecomesUnavailable/)
})
test('approval freezes measurement and requires quantity weight and volume conservation', () => {
assert.match(source, /审核进入WAIT_SPLIT后箱码尺寸与图片快照不可再覆盖/)
assert.match(measurementTests, /approvedMeasurementCannotBeResubmittedOrMutateFrozenTotals/)
assert.match(reviewTests, /shouldApproveMultipleCargoLinesOnlyWhenAllFrozenTotalsMatch/)
assert.match(reviewTests, /shouldRejectApprovalWhenVolumeSnapshotDoesNotConserve/)
assert.match(source, /actualPackageCount[^\n]+originalPackageCount/)
assert.match(source, /actualGrossWeightKg[^\n]+weightEvidence\.grossWeightKg/)
assert.match(source, /actualNetWeightKg[^\n]+weightEvidence\.netWeightKg/)
assert.match(source, /actualVolumeCbm[^\n]+totalVolume/)
assert.match(source, /volumeWeightKg[^\n]+volumeWeight/)
})
@@ -84,7 +84,7 @@ async function waitForCompletedMeasurement(context, measurementPath, attempts =
return { response, measurement, attempts }
}
async function provisionWarehouseResources(context, { inboundId, warehouseId, palletCount }) {
async function provisionWarehouseResources(context, { inboundId, tenantId, warehouseId, palletCount }) {
if (!warehouseId || !Number.isInteger(palletCount) || palletCount < 1) return null
const resources = []
@@ -92,6 +92,20 @@ async function provisionWarehouseResources(context, { inboundId, warehouseId, pa
if (!candidates) return null
const palletIds = randomlyReusePalletIds(candidates, palletCount,
context.context?.waitSplitVariant?.randomValue)
const candidateKeyword = candidates.find((item) => item.id === palletIds[0])?.code || ''
const candidateResponse = await context.request('GET',
`/api/platform/v1/first-leg/inbounds/${inboundId}/weight-pallet-options?${query(tenantId)}&page=1&size=20&keyword=${encodeURIComponent(candidateKeyword)}`)
const candidatePage = context.apiSuccess(MODULE, '按关键词分页查询当前入库仓称重托盘候选', candidateResponse,
`warehouseId=${warehouseId}, keyword=${candidateKeyword}`, OPTIONS)
const pageControlled = Array.isArray(candidatePage?.records) && candidatePage.records.length <= 20
&& Number(candidatePage?.page) === 1 && Number(candidatePage?.size) === 20
&& Number(candidatePage?.total) >= candidatePage.records.length
&& candidatePage.records.some((item) => asId(item?.palletId) === palletIds[0])
context.assert(MODULE, '托盘候选保持服务端关键词分页且不返回无界数组', candidateResponse,
pageControlled,
`records=${candidatePage?.records?.length ?? 'missing'}, page=${candidatePage?.page ?? 'missing'}, size=${candidatePage?.size ?? 'missing'}, total=${candidatePage?.total ?? 'missing'}`,
OPTIONS)
if (!pageControlled) return null
for (let index = 1; index <= palletCount; index += 1) {
const palletId = palletIds[index - 1]
context.assert(MODULE, `随机复用原有称重托盘 ${index}/${palletCount}`, null,
@@ -134,6 +148,10 @@ async function createConfirmedWeightEvidence(context, {
const created = context.apiSuccess(MODULE, `登记称重图片与托盘快照 ${index + 1}/${palletIds.length}`,
createResponse, `fileId=${fileId}, palletId=${palletId}`, OPTIONS)
if (!created?.id) return null
context.assert(MODULE, `OCR无有效重量时转人工确认 ${index + 1}/${palletIds.length}`, createResponse,
created.recognitionStatus === 'NEEDS_MANUAL' && created.ocrWeightValue == null,
`recognitionStatus=${created.recognitionStatus || 'missing'}, ocrWeightValue=${created.ocrWeightValue ?? 'null'}`,
OPTIONS)
const grossWeight = Number(grossWeightPerPalletKg) + index * 5
const confirmResponse = await context.request('POST',
@@ -148,6 +166,16 @@ async function createConfirmedWeightEvidence(context, {
if (confirmed?.recognitionStatus !== 'CONFIRMED' || !Number.isFinite(netWeightKg) || netWeightKg <= 0) {
return null
}
const staleConfirmResponse = await context.request('POST',
`/api/platform/v1/first-leg/inbounds/${inboundId}/weight-results/${created.id}/confirm?${query(tenantId)}`, {
version: Number(created.version), adjustedWeightValue: grossWeight + 1,
adjustedWeightUnit: 'KG', adjustmentReason: `${context.marker} 旧版本冲突验证`,
})
context.assert(MODULE, `称重旧版本确认被拒绝且不覆盖已确认结果 ${index + 1}/${palletIds.length}`,
staleConfirmResponse,
staleConfirmResponse.http === 200 && String(staleConfirmResponse.body?.code) === 'A0409',
`HTTP=${staleConfirmResponse.http}, code=${staleConfirmResponse.body?.code ?? 'missing'}, requestId=${staleConfirmResponse.requestId || 'missing'}`,
OPTIONS)
confirmedResults.push({ id: asId(created.id), palletId, netWeightKg, grossWeightKg })
}
@@ -161,7 +189,9 @@ async function createConfirmedWeightEvidence(context, {
const netWeightKg = confirmedResults.reduce((sum, result) => sum + result.netWeightKg, 0)
const persisted = confirmedRows.length === palletIds.length
&& confirmedResults.every((expected) => confirmedRows.some((row) =>
asId(row?.id) === expected.id && asId(row?.palletId) === expected.palletId))
asId(row?.id) === expected.id && asId(row?.palletId) === expected.palletId
&& String(row?.palletCode || '').trim() && String(row?.palletType || '').trim()
&& Number.isFinite(Number(row?.palletTareWeightKg))))
&& closeTo(workspace?.actualGrossWeightKg, grossWeightKg, 0.001)
&& closeTo(workspace?.actualNetWeightKg, netWeightKg, 0.001)
context.assert(MODULE, '图片张数决定托数且净重汇总已落地', workspaceResponse, persisted,
@@ -533,7 +563,7 @@ export async function run(context) {
const palletCount = Math.max(1, Number(context.context?.waitSplitVariant?.palletCount || 1))
const warehouseResourcesReady = await provisionWarehouseResources(context, {
inboundId, warehouseId: asId(summary?.warehouseId), palletCount,
inboundId, tenantId, warehouseId: asId(summary?.warehouseId), palletCount,
})
context.assert(MODULE, '测量提交前按称重图片数准备真实库位', draftResponse,
Boolean(warehouseResourcesReady),
@@ -606,6 +636,15 @@ export async function run(context) {
&& closeTo(finalMeasurement?.volumeWeightKg, volumeWeight),
`processStatus=${finalMeasurement?.processStatus ?? 'missing'}, measurementStatus=${finalMeasurement?.measurementStatus ?? 'missing'}, packages=${finalMeasurement?.actualPackageCount ?? 'missing'}, pallets=${weightEvidence.count}, grossWeight=${finalMeasurement?.actualGrossWeightKg ?? 'missing'}, netWeight=${finalMeasurement?.actualNetWeightKg ?? 'missing'}`, OPTIONS)
const approvedScanResponse = await multipartRequest(context, scanPath, {
boxCode, ...replacement, machineNo: 'MJ-ADMIN-E2E-AFTER-APPROVAL',
measuredAt: new Date().toISOString().slice(0, 19),
})
context.assert(MODULE, '审核进入WAIT_SPLIT后箱码尺寸与图片快照不可再覆盖', approvedScanResponse,
approvedScanResponse.http === 200 && String(approvedScanResponse.body?.code) === 'A0409',
`HTTP=${approvedScanResponse.http}, code=${approvedScanResponse.body?.code ?? 'missing'}, requestId=${approvedScanResponse.requestId || 'missing'}`,
OPTIONS)
context.createdResources.inboundMeasurement = {
marker: context.marker, tenantId, inboundId, measurementId: asId(draft.measurementId),
boxCode, finalStatus: finalMeasurement?.measurementStatus || null,