Files
X-Financial/web/tests/risk-visibility.test.mjs
caoxiaozhu 87da5df91b feat: 风险可见性控制与差旅详情页交互优化
- 新增风险可见性工具函数与风险日趋势图表组件
- 优化差旅请求详情页费用模型与视图交互
- 完善顶部导航栏样式与应用壳路由逻辑
- 补充风险可见性、风险看板与差旅详情测试覆盖
2026-06-03 22:15:45 +08:00

193 lines
5.3 KiB
JavaScript

import assert from 'node:assert/strict'
import test from 'node:test'
import { buildDetailAlerts } from '../src/utils/detailAlerts.js'
import {
canViewRiskForContext,
filterRiskCardsForVisibility,
resolveRiskActionability,
resolveRiskDomain,
resolveRiskVisibilityScope
} from '../src/utils/riskVisibility.js'
const submitter = {
id: 'EMP-001',
employeeId: 'EMP-001',
name: '张三',
roleCodes: []
}
const budgetManager = {
id: 'EMP-P8',
name: '预算管理员',
grade: 'P8',
departmentName: '研发部',
roleCodes: ['budget_monitor']
}
const financeUser = {
id: 'FIN-001',
name: '财务',
roleCodes: ['finance']
}
const applicationRequest = {
id: 'AP-202606010001',
claimId: 'application-claim-id',
documentTypeCode: 'application',
approvalKey: 'in_progress',
node: '预算管理者审批',
employeeId: 'EMP-001',
departmentName: '研发部',
typeCode: 'travel_application',
typeLabel: '差旅费用申请',
reason: '北京出差申请',
location: '北京',
occurredDisplay: '2026-06-01 至 2026-06-03',
amountValue: 10000,
riskFlags: [
{
source: 'budget_control',
severity: 'high',
label: '预算可用余额不足',
message: '当前部门预算余额不足。',
business_stage: 'expense_application',
risk_domain: 'budget',
visibility_scope: 'budget_manager',
actionability: 'budget_governance'
}
],
expenseItems: []
}
test('application submitter cannot see budget governance alerts in detail topbar', () => {
const alerts = buildDetailAlerts(applicationRequest, { currentUser: submitter })
assert.deepEqual(alerts.map((item) => item.label), ['SLA 催单次数 0'])
})
test('budget manager can see application budget governance alerts', () => {
const alerts = buildDetailAlerts(applicationRequest, { currentUser: budgetManager })
assert.equal(alerts[0].label, '高风险 1 项')
assert.equal(alerts[0].tone, 'danger')
})
test('reimbursement submitter sees only fixable claim risks', () => {
const request = {
id: 'RE-202606010001',
claimId: 'reimbursement-claim-id',
documentTypeCode: 'claim',
approvalKey: 'draft',
node: '待提交',
employeeId: 'EMP-001',
typeCode: 'travel',
expenseItems: []
}
const cards = [
{
id: 'ticket-date',
businessStage: 'reimbursement',
tone: 'high',
risk: '票据日期早于申请行程。',
risk_domain: 'trip',
visibility_scope: 'submitter',
actionability: 'fixable_by_submitter'
},
{
id: 'budget-detail',
businessStage: 'reimbursement',
tone: 'medium',
risk: '预算占用率超过 90%。',
risk_domain: 'budget',
visibility_scope: 'budget_manager',
actionability: 'budget_governance'
}
]
const visibleCards = filterRiskCardsForVisibility(cards, { request, currentUser: submitter })
assert.deepEqual(visibleCards.map((card) => card.id), ['ticket-date'])
})
test('reimbursement detail still shows submitter-fixable attachment risks when viewer identity is incomplete', () => {
const request = {
id: 'RE-20260603083825-876B85XW',
claimId: '2ad80b25-b153-407e-91be-ed2651045fb1',
documentTypeCode: 'claim',
approvalKey: 'draft',
node: 'pending-submit',
employeeId: 'EMP-CLAIM-OWNER',
typeCode: 'travel',
expenseItems: []
}
const currentUserWithoutEmployeeMatch = {
id: 'FRONTEND-AUTH-SNAPSHOT',
employeeId: '',
name: '',
roleCodes: []
}
const cards = [
{
id: 'hotel-limit-risk',
source: 'attachment_analysis',
businessStage: 'reimbursement',
tone: 'high',
risk: 'hotel limit exceeded',
risk_domain: 'invoice',
visibility_scope: 'submitter',
actionability: 'fixable_by_submitter'
}
]
const visibleCards = filterRiskCardsForVisibility(cards, {
request,
currentUser: currentUserWithoutEmployeeMatch
})
assert.deepEqual(visibleCards.map((card) => card.id), ['hotel-limit-risk'])
})
test('finance can see reimbursement compliance risks but not budget governance detail', () => {
const request = {
id: 'RE-202606010002',
documentTypeCode: 'claim',
approvalKey: 'in_progress',
node: '财务审批',
employeeId: 'EMP-001',
typeCode: 'travel',
expenseItems: []
}
assert.equal(
canViewRiskForContext(
{
businessStage: 'reimbursement',
risk: '发票抬头与报销主体不一致。',
risk_domain: 'invoice',
actionability: 'fixable_by_submitter'
},
{ request, currentUser: financeUser }
),
true
)
assert.equal(
canViewRiskForContext(
{
businessStage: 'reimbursement',
risk: '预算余额不足。',
risk_domain: 'budget',
actionability: 'budget_governance'
},
{ request, currentUser: financeUser }
),
false
)
})
test('legacy risk text falls back to semantic visibility defaults', () => {
const legacyFlag = {
source: 'submission_review',
severity: 'high',
message: '住宿发票城市与行程城市不一致。',
business_stage: 'reimbursement'
}
assert.equal(resolveRiskDomain(legacyFlag), 'trip')
assert.equal(resolveRiskActionability(legacyFlag, { businessStage: 'reimbursement' }), 'fixable_by_submitter')
assert.equal(resolveRiskVisibilityScope(legacyFlag, { businessStage: 'reimbursement' }), 'submitter')
})