Files

198 lines
9.3 KiB
JavaScript
Raw Permalink Normal View History

2026-08-08 10:11:04 +08:00
import { readFile } from 'node:fs/promises'
import { dirname, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
const SCENARIO = 'mojia-measurement'
const DEFAULT_IMAGE = resolve(
dirname(fileURLToPath(import.meta.url)), '..', '..', 'apps', 'pda-uniapp', 'static', 'c7.png')
function success(response) {
return response?.http === 200 && String(response?.body?.code) === '200'
}
function data(response) {
return response?.body?.data ?? null
}
function record(run, name, response, passed, evidence = '') {
return run.addStep(SCENARIO, name, response, passed, evidence)
}
function responseOf(http, body, raw = null) {
return {
method: 'POST',
path: null,
http,
body,
raw,
requestId: body?.requestId ?? null,
traceId: body?.traceId ?? null,
}
}
async function multipartRequest(run, path, fields, imagePath) {
const form = new FormData()
form.set('data', JSON.stringify(fields))
form.set('image', new Blob([await readFile(imagePath)], { type: 'image/png' }), 'mojia-e2e.png')
try {
const response = await fetch(`${run.options.gateway}${path}`, {
method: 'POST',
headers: { Accept: 'application/json', Authorization: `Bearer ${run.token}` },
body: form,
signal: AbortSignal.timeout(30_000),
})
const text = await response.text()
let body = null
try { body = text ? JSON.parse(text) : null } catch { /* Keep raw response for the report. */ }
return {
method: 'POST', path, http: response.status, body,
raw: body === null ? text.slice(0, 500) : null,
requestId: body?.requestId ?? response.headers.get('x-request-id'),
traceId: body?.traceId ?? response.headers.get('x-trace-id'),
}
} catch (error) {
return responseOf(0, null, error instanceof Error ? error.message : String(error))
}
}
function requireData(run, name, response, evidence = '') {
record(run, name, response, success(response) && data(response) !== null, evidence)
if (!success(response) || data(response) === null) {
throw new Error(`${name} failed: code=${response?.body?.code}, message=${response?.body?.message}`)
}
return data(response)
}
/**
* Run the internal platform simulation for the MoJia carton measurement flow.
* The final external MoJia key/signature contract is intentionally not defined here.
*
* @param {object} run Existing AcceptanceRun instance from admin-test-env-acceptance.mjs.
* @param {object} [options]
* @param {string|number} [options.tenantId] Customer scope for selecting the test inbound.
* @param {string|number} [options.inboundId] Explicit WAIT_MEASURE inbound, preferred for CI.
* @param {string} [options.imagePath] PNG/JPG evidence image path.
*/
export async function runMojiaMeasurementScenario(run, options = {}) {
const tenantId = options.tenantId ?? process.env.MOJIA_TEST_TENANT_ID
const imagePath = options.imagePath ?? process.env.MOJIA_TEST_IMAGE ?? DEFAULT_IMAGE
let inboundId = options.inboundId ?? process.env.MOJIA_TEST_INBOUND_ID
if (!tenantId && !inboundId) {
const response = responseOf(0, { code: 'DEPLOYMENT_BLOCKED', message: '缺少墨家测试 tenantId 或 inboundId' })
record(run, '墨家测量测试数据配置', response, false, '请传入 MOJIA_TEST_TENANT_ID 或 MOJIA_TEST_INBOUND_ID')
return false
}
try {
if (!inboundId) {
const response = await run.request(
'GET', '/api/platform/inbounds?processStatus=WAIT_MEASURE&page=1&size=200')
const rows = requireData(run, '查找墨家 WAIT_MEASURE 入库单', response)
const candidate = rows.records?.find((row) => String(row.tenantId) === String(tenantId)
&& row.totalPackageCount == null)
if (!candidate) {
record(run, '墨家测量测试数据配置', response, false,
`tenantId=${tenantId} 下没有未冻结总箱数的 WAIT_MEASURE 入库单`)
return false
}
inboundId = String(candidate.id)
}
const detailPath = `/api/platform/inbounds/${inboundId}?tenantId=${tenantId}`
const detailResponse = await run.request('GET', detailPath)
const detail = requireData(run, '查询墨家测试入库单', detailResponse, `inboundId=${inboundId}`)
const summary = detail.summary
if (summary.processStatus !== 'WAIT_MEASURE' || summary.totalPackageCount != null) {
record(run, '墨家测试入库单状态校验', detailResponse, false,
`processStatus=${summary.processStatus}, totalPackageCount=${summary.totalPackageCount}`)
return false
}
const resolvedTenantId = String(summary.tenantId ?? tenantId)
const printPath = `/api/platform/inbounds/${inboundId}/carton-label-pdf?tenantId=${resolvedTenantId}`
const printResponse = await run.requestBinary('POST', printPath)
record(run, '墨家测试生成稳定箱唛 PDF', printResponse,
printResponse.http === 200 && printResponse.bytes?.length > 0
&& printResponse.contentType?.toLowerCase().includes('application/pdf'),
`inboundId=${inboundId}, bytes=${printResponse.bytes?.length ?? 0}`)
const afterPrintResponse = await run.request('GET', detailPath)
const afterPrint = requireData(run, '回查墨家稳定箱号总数', afterPrintResponse)
const printedTotal = Number(afterPrint.summary.totalPackageCount)
if (!Number.isInteger(printedTotal) || printedTotal < 1) {
throw new Error('打印后没有冻结原始总箱数')
}
const boxCode = `${afterPrint.summary.inboundCode}#${printedTotal}-1`
const scanPath = `/api/platform/v1/first-leg/inbounds/${inboundId}/measurement/carton-scans?tenantId=${resolvedTenantId}`
const firstFields = {
2026-08-18 15:43:58 +08:00
boxCode, length: 59.1, width: 40.2, height: 30.3,
2026-08-08 10:11:04 +08:00
machineNo: 'MJ-ADMIN-E2E-01', measuredAt: '2026-08-08T10:00:00',
}
const firstResponse = await multipartRequest(run, scanPath, firstFields, imagePath)
const first = requireData(run, '墨家单箱首次测量上传', firstResponse, `boxCode=${boxCode}`)
const firstPassed = first.created === true && first.confirmedPackageCount === 1
&& String(first.boxCode) === boxCode
record(run, '墨家单箱首次记录语义', firstResponse, firstPassed,
`itemId=${first.itemId}, measurementId=${first.measurementId}`)
if (!firstPassed) throw new Error('首次扫描没有创建单箱记录或默认箱数不是 1')
const countPath = `/api/platform/v1/first-leg/inbounds/${inboundId}/measurement/items/${first.itemId}/confirmed-package-count?tenantId=${resolvedTenantId}`
const countResponse = await run.request('PUT', countPath, {
measurementVersion: first.measurementVersion,
confirmedPackageCount: printedTotal,
})
const counted = requireData(run, '人工确认墨家单箱代表箱数', countResponse,
`confirmedPackageCount=${printedTotal}`)
if (counted.confirmedPackageCount !== printedTotal) {
throw new Error('人工确认箱数回显不一致')
}
const rescanResponse = await multipartRequest(run, scanPath, {
2026-08-18 15:43:58 +08:00
boxCode, length: 59.5, width: 41.2, height: 31.3,
2026-08-08 10:11:04 +08:00
machineNo: 'MJ-ADMIN-E2E-02', measuredAt: '2026-08-08T10:01:00',
}, imagePath)
const rescan = requireData(run, '墨家同箱复测覆盖', rescanResponse)
const rescanPassed = rescan.created === false
&& String(rescan.itemId) === String(first.itemId)
&& rescan.confirmedPackageCount === printedTotal
&& Number(rescan.lengthCm) === 59.5
2026-08-08 10:11:04 +08:00
record(run, '墨家同箱复测保留人工箱数', rescanResponse, rescanPassed,
`itemId=${rescan.itemId}, confirmedPackageCount=${rescan.confirmedPackageCount}`)
if (!rescanPassed) throw new Error('同箱复测没有覆盖设备值或覆盖人工箱数')
const submitPath = `/api/platform/v1/first-leg/inbounds/${inboundId}/measurement/submit?tenantId=${resolvedTenantId}`
const submitResponse = await run.request('POST', submitPath, {
measurementId: rescan.measurementId,
version: rescan.measurementVersion,
})
const submitted = requireData(run, '提交墨家测量审核', submitResponse)
const submittedPassed = submitted.processStatus === 'MEASURE_PENDING'
&& submitted.measureReviewStatus === 'PENDING'
record(run, '墨家测量进入待审核', submitResponse, submittedPassed,
`processStatus=${submitted.processStatus}, reviewStatus=${submitted.measureReviewStatus}`)
if (!submittedPassed) throw new Error('墨家测量提交后状态不正确')
const blockedResponse = await multipartRequest(run, scanPath, {
2026-08-18 15:43:58 +08:00
boxCode, length: 99.1, width: 99.2, height: 99.3,
2026-08-08 10:11:04 +08:00
machineNo: 'MJ-ADMIN-E2E-BLOCKED', measuredAt: '2026-08-08T10:02:00',
}, imagePath)
const blockedPassed = blockedResponse.body?.code === 'A0409'
&& blockedResponse.body?.message?.includes('测量已提交')
record(run, '提交后拒绝墨家设备覆盖', blockedResponse, blockedPassed,
`code=${blockedResponse.body?.code}, message=${blockedResponse.body?.message ?? ''}`)
if (!blockedPassed) throw new Error('提交后没有拒绝墨家设备覆盖')
run.context ??= {}
run.context.mojiaMeasurement = {
tenantId: resolvedTenantId, inboundId: String(inboundId), boxCode,
measurementId: String(rescan.measurementId), itemId: String(rescan.itemId),
}
return true
} catch (error) {
const response = responseOf(0, null, error instanceof Error ? error.message : String(error))
record(run, '墨家测量场景执行', response, false, response.raw)
return false
}
}