Files
order_site/apps/frontend/tests/consignment-match-log-reasons.test.ts
T

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'), '原因列必须使用中文映射')
})