126 lines
4.3 KiB
TypeScript
126 lines
4.3 KiB
TypeScript
import assert from 'node:assert/strict'
|
|
import { readFileSync } from 'node:fs'
|
|
import { fileURLToPath } from 'node:url'
|
|
import test from 'node:test'
|
|
|
|
import ts from 'typescript'
|
|
|
|
import {
|
|
CONSIGNMENT_MATCH_REASON_LABELS,
|
|
resolveConsignmentMatchReasonLabel,
|
|
} from '../src/components/admin/consignment-match-log-labels.ts'
|
|
|
|
const ENGINE_PATH = fileURLToPath(
|
|
new URL(
|
|
'../../backend/src/services/worker-platform/consignment-match-engine-service.ts',
|
|
import.meta.url,
|
|
),
|
|
)
|
|
const MODAL_PATH = fileURLToPath(
|
|
new URL('../src/components/admin/ConsignmentMatchLogsModal.tsx', import.meta.url),
|
|
)
|
|
|
|
function collectStringLiterals(expr: ts.Expression, result: Set<string>) {
|
|
if (ts.isStringLiteralLike(expr)) {
|
|
result.add(expr.text)
|
|
return
|
|
}
|
|
if (ts.isParenthesizedExpression(expr)) {
|
|
collectStringLiterals(expr.expression, result)
|
|
return
|
|
}
|
|
if (ts.isConditionalExpression(expr)) {
|
|
// 只取分支结果,不把条件里的字符串当成原因码。
|
|
collectStringLiterals(expr.whenTrue, result)
|
|
collectStringLiterals(expr.whenFalse, result)
|
|
return
|
|
}
|
|
if (
|
|
ts.isBinaryExpression(expr) &&
|
|
[
|
|
ts.SyntaxKind.BarBarToken,
|
|
ts.SyntaxKind.QuestionQuestionToken,
|
|
ts.SyntaxKind.PlusToken,
|
|
].includes(expr.operatorToken.kind)
|
|
) {
|
|
collectStringLiterals(expr.left, result)
|
|
collectStringLiterals(expr.right, result)
|
|
return
|
|
}
|
|
}
|
|
|
|
/** 匹配日志的 reasonCode 只有两种来源:固定流程原因 + createHttpError 的 errorCode。 */
|
|
function collectEngineReasonCodes() {
|
|
const source = readFileSync(ENGINE_PATH, 'utf8')
|
|
const sourceFile = ts.createSourceFile(ENGINE_PATH, source, ts.ScriptTarget.Latest, true)
|
|
const result = new Set<string>()
|
|
|
|
function visit(node: ts.Node) {
|
|
if (ts.isCallExpression(node)) {
|
|
const callee = node.expression.getText(sourceFile).split('.').pop() || ''
|
|
if (callee === 'recordConsignmentMatchLog') {
|
|
const objectArg = node.arguments.find((arg): arg is ts.ObjectLiteralExpression =>
|
|
ts.isObjectLiteralExpression(arg),
|
|
)
|
|
for (const property of objectArg?.properties || []) {
|
|
if (
|
|
ts.isPropertyAssignment(property) &&
|
|
property.name.getText(sourceFile) === 'reasonCode'
|
|
) {
|
|
collectStringLiterals(property.initializer, result)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (ts.isPropertyAssignment(node) && node.name.getText(sourceFile) === 'errorCode') {
|
|
collectStringLiterals(node.initializer, result)
|
|
}
|
|
ts.forEachChild(node, visit)
|
|
}
|
|
|
|
visit(sourceFile)
|
|
return result
|
|
}
|
|
|
|
test('寄卖匹配日志:后端写入的每个原因码都必须有中文映射', () => {
|
|
const reasonCodes = collectEngineReasonCodes()
|
|
assert.ok(reasonCodes.size > 0, '没有扫描到任何原因码,测试逻辑可能失效')
|
|
|
|
const missing = [...reasonCodes].filter((code) => !CONSIGNMENT_MATCH_REASON_LABELS[code]).sort()
|
|
|
|
assert.deepEqual(
|
|
missing,
|
|
[],
|
|
`以下原因码缺少中文映射,会在匹配日志里直接显示英文:${missing.join(', ')}`,
|
|
)
|
|
})
|
|
|
|
test('寄卖匹配日志:原因列优先显示中文,未知码兜底原文', () => {
|
|
assert.equal(resolveConsignmentMatchReasonLabel('manual_matched', ''), '手动匹配成功')
|
|
assert.equal(resolveConsignmentMatchReasonLabel('manual_matched', undefined), '手动匹配成功')
|
|
assert.equal(
|
|
resolveConsignmentMatchReasonLabel(
|
|
'consignment_spec_mismatch',
|
|
'所选库存与订单规格不一致:INV1',
|
|
),
|
|
'所选库存与订单规格不一致:INV1',
|
|
)
|
|
assert.equal(resolveConsignmentMatchReasonLabel('future_unknown_code', ''), 'future_unknown_code')
|
|
assert.equal(resolveConsignmentMatchReasonLabel('', ''), '-')
|
|
})
|
|
|
|
test('寄卖匹配日志弹窗:工单右侧新增寄卖单号列', () => {
|
|
const modalSource = readFileSync(MODAL_PATH, 'utf8')
|
|
|
|
assert.ok(modalSource.includes("title: '寄卖单号'"), '必须新增「寄卖单号」列')
|
|
assert.ok(
|
|
modalSource.includes("dataIndex: 'consignmentNos'"),
|
|
'「寄卖单号」列必须读取后端返回的 consignmentNos',
|
|
)
|
|
assert.ok(
|
|
modalSource.indexOf("title: '寄卖单号'") > modalSource.indexOf("title: '工单'"),
|
|
'「寄卖单号」列必须在「工单」列右侧',
|
|
)
|
|
assert.ok(modalSource.includes('resolveConsignmentMatchReasonLabel'), '原因列必须使用中文映射')
|
|
})
|