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

177 lines
5.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import assert from 'node:assert/strict'
import { readFileSync } from 'node:fs'
import { fileURLToPath } from 'node:url'
import test from 'node:test'
import {
computeConsignmentMatchScore,
describeConsignmentMatch,
resolveMatchCoverage,
} from '../src/utils/consignment-match-coverage.ts'
test('匹配打分:与仓库列表同口径(精确 100 / 高配 85 / 未知 50 / 冲突 0)', () => {
assert.equal(computeConsignmentMatchScore({ matchType: 'exact', matchDetails: [] }), 100)
assert.equal(computeConsignmentMatchScore({ matchType: 'partial', matchDetails: [] }), 85)
assert.equal(computeConsignmentMatchScore({ matchType: 'unknown', matchDetails: [] }), 50)
assert.equal(computeConsignmentMatchScore({ matchType: 'conflict', matchDetails: [] }), 0)
assert.equal(
computeConsignmentMatchScore({
matchType: 'partial',
matchDetails: [
{
key: 'grade',
label: '等级',
orderValue: 'A',
inventoryValue: 'S',
status: 'match',
},
{
key: 'series',
label: '系列',
orderValue: 'A',
inventoryValue: 'S',
status: 'partial',
},
],
}),
93,
)
})
test('匹配说明:精确/非精确/冲突都必须有可读文案', () => {
assert.equal(describeConsignmentMatch({ matchType: 'exact' }), '规格完全一致')
assert.equal(
describeConsignmentMatch({ matchType: 'partial', matchReasons: [] }),
'规格高于要求(非精确)',
)
assert.equal(
describeConsignmentMatch({
matchType: 'conflict',
matchReasons: ['系统不一致', '区服不一致'],
}),
'不匹配:系统不一致;区服不一致',
)
})
test('拼单覆盖:1+1 不够 3 份,2+1 才允许提交', () => {
const short = resolveMatchCoverage({
sharingEnabled: true,
totalQuantity: 3,
activeQuantity: 0,
selected: [
{ inventoryId: 1, remainingQuantity: 1 },
{ inventoryId: 2, remainingQuantity: 1 },
],
})
assert.equal(short.requiredQuantity, 3)
assert.equal(short.selectedQuantity, 2)
assert.equal(short.sufficient, false)
assert.equal(short.gap, 1)
const enough = resolveMatchCoverage({
sharingEnabled: true,
totalQuantity: 3,
activeQuantity: 0,
selected: [
{ inventoryId: 1, remainingQuantity: 2 },
{ inventoryId: 2, remainingQuantity: 1 },
],
})
assert.equal(enough.selectedQuantity, 3)
assert.equal(enough.sufficient, true)
assert.equal(enough.gap, 0)
})
test('拼单覆盖:已匹配 1 份时只需再选 2 份', () => {
const result = resolveMatchCoverage({
sharingEnabled: true,
totalQuantity: 3,
activeQuantity: 1,
selected: [{ inventoryId: 1, remainingQuantity: 2 }],
})
assert.equal(result.requiredQuantity, 2)
assert.equal(result.sufficient, true)
})
test('整单覆盖:必须一条库存满足全部数量', () => {
const short = resolveMatchCoverage({
sharingEnabled: false,
totalQuantity: 3,
activeQuantity: 0,
selected: [{ inventoryId: 1, remainingQuantity: 2 }],
})
assert.equal(short.requiredQuantity, 3)
assert.equal(short.selectedQuantity, 2)
assert.equal(short.sufficient, false)
assert.equal(short.gap, 1)
const enough = resolveMatchCoverage({
sharingEnabled: false,
totalQuantity: 3,
activeQuantity: 0,
selected: [{ inventoryId: 1, remainingQuantity: 3 }],
})
assert.equal(enough.sufficient, true)
})
test('工单页:内部库存匹配只对待分配/待抢单显示,不再包含内部匹配中', () => {
const source = readFileSync(
fileURLToPath(new URL('../src/pages/admin/panels/WorkOrdersPanel.tsx', import.meta.url)),
'utf8',
)
assert.ok(
source.includes("['unassigned', 'open'].includes(String(row.status || ''))"),
'内部库存匹配入口必须只覆盖待分配/待抢单',
)
assert.ok(!source.includes("'internal_matching'"), '状态筛选与入口都不应再包含内部匹配中')
})
test('匹配弹窗:接口错误必须展示,不能伪装成没有库存', () => {
const source = readFileSync(
fileURLToPath(
new URL('../src/components/admin/ConsignmentWorkOrderMatchModal.tsx', import.meta.url),
),
'utf8',
)
assert.ok(source.includes('candidatesQuery.isError'), '必须处理候选查询错误')
assert.ok(source.includes('读取可匹配寄卖库存失败'), '必须展示错误标题')
assert.ok(source.includes('重试'), '必须提供重试入口')
assert.ok(
source.includes(
'!candidatesQuery.isLoading && !candidatesQuery.isError && options.length === 0',
),
'空状态必须排除错误场景',
)
})
test('工单页:手动匹配寄卖成功后必须按真实查询键刷新列表与库存', () => {
const source = readFileSync(
fileURLToPath(new URL('../src/pages/admin/panels/WorkOrdersPanel.tsx', import.meta.url)),
'utf8',
)
const modalIndex = source.indexOf('<ConsignmentWorkOrderMatchModal')
assert.ok(modalIndex > 0, '工单页必须挂载手动匹配寄卖弹窗')
const matchedIndex = source.indexOf('onMatched={() => {', modalIndex)
const matchedBlock = source.slice(matchedIndex, source.indexOf('}}', matchedIndex) + 2)
assert.ok(matchedBlock.includes('refreshAll()'), '匹配成功必须调用 refreshAll() 重取列表')
assert.ok(
!matchedBlock.includes("'admin-work-orders'") &&
!matchedBlock.includes("'admin-work-orders-stats'"),
'不得 invalidate 不存在的 admin-work-orders / admin-work-orders-stats 查询键',
)
const refreshIndex = source.indexOf('async function refreshAll()')
const refreshBlock = source.slice(
refreshIndex,
source.indexOf('async function runAction', refreshIndex),
)
for (const key of [
'admin-worker-platform-orders',
'admin-worker-platform-summary',
'admin-consignment-inventories',
'admin-consignment-stats',
]) {
assert.ok(refreshBlock.includes(key), `refreshAll() 必须刷新 ${key}`)
}
})