import assert from 'node:assert/strict' import { readFileSync } from 'node:fs' import test from 'node:test' import { fileURLToPath } from 'node:url' import { normalizeRiskObservationDashboard } from '../src/services/riskObservations.js' const dashboardComponent = readFileSync( fileURLToPath(new URL('../src/components/dashboard/RiskObservationDashboard.vue', import.meta.url)), 'utf8' ) const overviewViewModel = readFileSync( fileURLToPath(new URL('../src/composables/useOverviewView.js', import.meta.url)), 'utf8' ) const overviewTemplate = readFileSync( fileURLToPath(new URL('../src/views/OverviewView.vue', import.meta.url)), 'utf8' ) test('risk dashboard normalizes amount, distributions, and ranking fields', () => { const dashboard = normalizeRiskObservationDashboard({ total_observations: 5, risk_clue_count: 2, feedback_sample_count: 3, total_amount: 12800, department_distribution: { 风控部: 3 }, expense_type_distribution: { travel: 2 }, risk_type_distribution: { duplicate_invoice: 2 }, supplier_distribution: { 上海差旅供应商: 1 }, employee_grade_distribution: { P6: 2 }, top_departments: [{ name: '风控部', count: 3, amount: 8800 }], top_employees: [{ name: '风险员工', count: 2, amount: 6200 }], top_suppliers: [{ name: '上海差旅供应商', count: 1, amount: 1200 }], top_expense_types: [{ name: 'travel', count: 2, amount: 4600 }], top_rules: [{ name: 'policy.duplicate_invoice', count: 2, amount: 3000 }] }) assert.equal(dashboard.totalAmount, 12800) assert.equal(dashboard.riskClueCount, 2) assert.equal(dashboard.feedbackSampleCount, 3) assert.equal(dashboard.departmentDistribution['风控部'], 3) assert.equal(dashboard.expenseTypeDistribution.travel, 2) assert.equal(dashboard.riskTypeDistribution.duplicate_invoice, 2) assert.equal(dashboard.supplierDistribution['上海差旅供应商'], 1) assert.equal(dashboard.employeeGradeDistribution.P6, 2) assert.equal(dashboard.topDepartments[0].amount, 8800) assert.equal(dashboard.topRules[0].name, 'policy.duplicate_invoice') }) test('risk dashboard renders overview amount and multi-dimension panels', () => { assert.match(overviewViewModel, /label: '新增风险数'/) assert.match(overviewViewModel, /label: '涉及金额'/) assert.match(overviewViewModel, /label: '已确认风险'/) assert.match(overviewViewModel, /label: '误报数量'/) assert.match(dashboardComponent, /业务维度分布/) assert.match(dashboardComponent, /异常排行/) assert.match(dashboardComponent, /待复核线索/) assert.match(dashboardComponent, /反馈样本/) assert.doesNotMatch(dashboardComponent, /候选规则/) assert.match(dashboardComponent, /departmentDistribution/) assert.match(dashboardComponent, /expenseTypeDistribution/) assert.match(dashboardComponent, /supplierDistribution/) assert.match(dashboardComponent, /employeeGradeDistribution/) assert.match(dashboardComponent, /topDepartments/) assert.match(dashboardComponent, /topEmployees/) assert.match(dashboardComponent, /topSuppliers/) assert.match(dashboardComponent, /topRules/) }) test('risk dashboard wires window filter to trend, ranking, and cards data source', () => { assert.match(overviewViewModel, /const activeRiskWindowDays = ref\(30\)/) assert.match(overviewViewModel, /windowDays: activeRiskWindowDays\.value/) assert.match(overviewViewModel, /watch\(activeRiskWindowDays/) assert.match(overviewViewModel, /setRiskWindowDays/) assert.match(overviewTemplate, /:window-options="riskWindowOptions"/) assert.match(overviewTemplate, /:active-window-days="activeRiskWindowDays"/) assert.match(overviewTemplate, /@update:window-days="setRiskWindowDays"/) assert.match(dashboardComponent, /EnterpriseSelect/) assert.match(dashboardComponent, /aria-label="风险看板时间窗口"/) assert.match(dashboardComponent, /emit\('update:windowDays', \$event\)/) assert.match(dashboardComponent, /RiskDailyTrendChart/) assert.match(dashboardComponent, /rankingGroups/) }) test('risk dashboard shows loading overlay and realtime refresh status', () => { assert.match(dashboardComponent, /risk-dashboard-loading-overlay/) assert.match(dashboardComponent, /loadingLabel/) assert.match(dashboardComponent, /lastUpdatedLabel/) assert.match(dashboardComponent, /lastUpdatedAt/) assert.match(overviewViewModel, /riskDashboardLastUpdatedAt/) assert.match(overviewViewModel, /startRiskDashboardRealtimeRefresh/) assert.match(overviewViewModel, /setInterval/) assert.match(overviewViewModel, /document\.visibilityState === 'hidden'/) assert.match(overviewViewModel, /riskDashboardRequestSeq/) assert.match(overviewTemplate, /:last-updated-at="riskDashboardLastUpdatedAt"/) })