198 lines
9.4 KiB
JavaScript
198 lines
9.4 KiB
JavaScript
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 = {
|
|
boxCode, length: 59.1, width: 40.2, height: 30.3, weight: 12.5,
|
|
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, {
|
|
boxCode, length: 59.5, width: 41.2, height: 31.3, weight: 13.5,
|
|
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
|
|
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, {
|
|
boxCode, length: 99.1, width: 99.2, height: 99.3, weight: 99.4,
|
|
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
|
|
}
|
|
}
|