import assert from 'node:assert/strict'; import test from 'node:test'; import { Hono } from 'hono'; import { mapFault } from './model.js'; import { faultDashboard, dashboardAnalysis, filterDashboardRows } from './dashboard.js'; import { createFaultRouter } from './index.js'; import type { AuthUser } from '../../auth/types.js'; const updatedAt='2026-10-13T08:00:00Z'; const q={start:'2026-10-12',end:'2026-10-13'}; const fault=(id:string,extra:Record={})=>mapFault({id,fault_code:`F${id}`,matched_vehicle_id:'v1',plate_number:'沪A001',model_name:'车型A',province_name:'上海市',customer_name:'客户A',business_dept:'部门A',reported_at:'2026-10-13 08:00:00',handling_status:'待处理',fault_level:1,fault_categories:'底盘故障,三电故障',...extra},new Map()); const rows=[fault('1'),fault('2',{handling_status:'已归档',archived_at:'2026-10-13 12:00:00'}),fault('3',{reported_at:'2026-10-01 08:00:00',handling_status:'处理中',deadline_at:'2026-10-12 15:00:00',matched_vehicle_id:'v2'}),fault('4',{matched_vehicle_id:null,plate_number:null,fault_level:null,fault_categories:null,customer_name:null}),fault('5',{reported_at:'2026-10-10 08:00:00',handling_status:'挂起'})]; test('dashboard separates reporting cohort from all-date current workload, preserves unknowns and deduplicates vehicles',()=>{ const d=faultDashboard(rows,q,updatedAt); assert.equal(d.totals.reported,3);assert.equal(d.totals.vehicles,1);assert.equal(d.totals.customers,1); assert.equal(d.totals.open,3);assert.equal(d.totals.waiting+d.totals.processing,d.totals.open);assert.equal(d.totals.overdue,1);assert.equal(d.totals.longestOverdueDays,2); assert.equal(d.totals.previous,1);assert.equal(d.totals.change,200); assert.equal(d.quality.missingLevel,1);assert.equal(d.quality.missingVehicle,1);assert.equal(d.quality.missingCategory,1); assert.equal(d.focus[0].id,'3');assert.equal(d.distributions.region.reduce((n,r)=>n+r.reported,0),3); const m=rows[0].assetCell.model;assert.equal(d.matrix.totals[m],3);assert.equal(d.matrix.cells.find(c=>c.model===m&&c.category==='底盘故障')?.count,2); assert.equal(d.matrix.cells.find(c=>c.model===m&&c.category==='三电故障')?.count,2); }); test('dashboard drill scope and all filters reconcile to visible aggregates without leaking internal fields',()=>{ const d=faultDashboard(rows,q,updatedAt);const group=d.distributions.customer.find(r=>r.label!=='未关联客户')!; const details=dashboardAnalysis(rows,{...q,customer:group.value},updatedAt); assert.equal(details.rows.length,group.reported);assert.ok(details.rows.every(r=>!('vehicleKey'in r)&&!('managerId'in r))); assert.deepEqual(dashboardAnalysis(rows,{...q,scope:'overdue'},updatedAt).rows.map(r=>r.id),['3']); assert.equal(dashboardAnalysis(rows,{...q,scope:'ledger',id:'5'},updatedAt).rows.length,1); assert.equal(filterDashboardRows(rows,{level:'未标注',category:'未标注'}).length,1); assert.equal(dashboardAnalysis(rows,{...q,scope:'cohort',level:'未标注'},updatedAt).rows.length,1); const empty=faultDashboard(rows,{...q,region:'不存在'},updatedAt);assert.equal(empty.totals.reported,0);assert.equal(empty.totals.change,null);assert.equal(empty.totals.longestOverdueDays,0); }); test('dashboard and report endpoints enforce authentication, permissions, dates and shared filters',async()=>{ let loads=0;const router=createFaultRouter(async()=>{loads++;return{rows,updatedAt};});const anonymous=new Hono();anonymous.route('/f',router); assert.equal((await anonymous.request('/f/dashboard')).status,401);assert.equal(loads,0); const user:AuthUser={userId:'test',loginName:'test',userName:'测试',roles:[],permissionLevel:'department',depName:'部门A',depCode:'a'}; const app=new Hono<{Variables:{user:AuthUser}}>();app.use('*',async(c,next)=>{c.set('user',user);await next();});app.route('/f',router); assert.equal((await app.request('/f/dashboard?start=bad')).status,400);assert.equal(loads,0); assert.equal((await app.request('/f/dashboard-analysis?scope=unknown')).status,400);assert.equal(loads,0); const report=await(await app.request('/f/dashboard?start=2026-10-12&end=2026-10-13')).json();assert.equal(report.totals.reported,2);assert.equal(report.options.level.some((o:{value:string})=>o.value==='未标注'),false); const trend=await(await app.request('/f/trend?start=2026-10-12&end=2026-10-13&level=未标注')).json();assert.equal(trend.totals.reported,0); const weekly=await(await app.request('/f/weekly-report?start=2026-10-12&end=2026-10-13&level=未标注')).json();assert.equal(weekly.totals.reported,0); }); test('focus contains only L1 unclosed faults across all dates and keeps totals and full drill consistent', () => { const focusRows = [ fault('waiting', { reported_at: '2026-09-01 08:00:00' }), fault('processing', { handling_status: '处理中', deadline_at: '2026-10-12 15:00:00' }), fault('paused', { handling_status: '挂起', deadline_at: '2026-10-01 08:00:00' }), fault('review', { handling_status: '审核中' }), fault('unknown', { handling_status: '其他状态' }), fault('sixth'), fault('seventh'), fault('closed', { handling_status: '已归档' }), fault('l2-overdue', { fault_level: 2, deadline_at: '2026-10-01 08:00:00' }), fault('l3', { fault_level: 3 }), fault('l4', { fault_level: 4 }), fault('unmarked', { fault_level: null }), ]; const dashboard = faultDashboard(focusRows, q, updatedAt); assert.deepEqual(dashboard.focusTotals, { total: 7, overdue: 1 }); assert.equal(dashboard.focus.length, 5); assert.equal(dashboard.focus[0].id, 'processing'); assert.ok(dashboard.focus.every(row => row.level === 'L1-特急' && row.status !== '已归档')); const allFocus = dashboardAnalysis(focusRows, { ...q, scope: 'focus' }, updatedAt).rows; assert.equal(allFocus.length, dashboard.focusTotals.total); assert.ok(allFocus.some(row => row.id === 'waiting')); assert.ok(allFocus.some(row => row.id === 'paused')); assert.ok(allFocus.some(row => row.id === 'review')); assert.ok(allFocus.every(row => !('vehicleKey' in row) && !('managerId' in row))); assert.deepEqual(dashboardAnalysis(focusRows, { ...q, scope: 'focus', id: 'closed' }, updatedAt).rows, []); const filtered = faultDashboard(focusRows, { ...q, level: 'L2-紧急' }, updatedAt); assert.deepEqual(filtered.focus, []); assert.deepEqual(filtered.focusTotals, { total: 0, overdue: 0 }); assert.deepEqual(dashboardAnalysis(focusRows, { ...q, scope: 'focus', level: 'L2-紧急' }, updatedAt).rows, []); }); test('focus dashboard and full drill enforce permissions and business filters before selecting L1 faults', async () => { const scopedRows = [fault('allowed'), fault('outside', { business_dept: '部门B' }), fault('closed', { handling_status: '已归档' }), fault('lower', { fault_level: 2 })]; const user: AuthUser = { userId: 'test', loginName: 'test', userName: '测试', roles: [], permissionLevel: 'department', depName: '部门A', depCode: 'a' }; const app = new Hono<{ Variables: { user: AuthUser } }>(); app.use('*', async (c, next) => { c.set('user', user); await next(); }); app.route('/f', createFaultRouter(async () => ({ rows: scopedRows, updatedAt }))); const report = await (await app.request('/f/dashboard?start=2026-10-12&end=2026-10-13')).json(); assert.deepEqual(report.focus.map((row: { id: string }) => row.id), ['allowed']); assert.deepEqual(report.focusTotals, { total: 1, overdue: 0 }); const response = await app.request('/f/dashboard-analysis?start=2026-10-12&end=2026-10-13&scope=focus'); assert.equal(response.status, 200); const detail = await response.json(); assert.deepEqual(detail.rows.map((row: { id: string }) => row.id), ['allowed']); const empty = await (await app.request('/f/dashboard-analysis?scope=focus&department=不存在')).json(); assert.deepEqual(empty.rows, []); }); test('report source follows explicit OneOS report method before legacy task linkage', () => { const cases: [Record, 'operations' | 'customer'][] = [ [{ report_method: '运维上报', source_work_order_id: '9007199254740993' }, 'operations'], [{ report_method: '客户上报', source_work_order_id: null }, 'customer'], [{ report_method: ' 客户上报 ', source_work_order_id: '123' }, 'customer'], [{ report_method: null, source_work_order_id: '9007199254740993' }, 'customer'], [{ report_method: '', source_work_order_id: null }, 'operations'], [{ report_method: null, source_work_order_id: null, fault_source: 6 }, 'operations'], ]; for (const [raw, expected] of cases) assert.equal(fault('source', raw).reportMethod, expected); }); test('reported source counts reconcile with cohort, filtered drills, permissions and export labels', async () => { const sourceRows = [ fault('ops-linked', { report_method: '运维上报', source_work_order_id: '123' }), fault('ai-explicit', { report_method: '客户上报', source_work_order_id: null }), fault('ai-legacy', { report_method: null, source_work_order_id: '9007199254740993', handling_status: '已归档' }), fault('ops-legacy', { report_method: null, source_work_order_id: null }), fault('previous-ai', { report_method: '客户上报', reported_at: '2026-10-10 10:00:00' }), fault('hidden-ai', { report_method: '客户上报', business_dept: '部门B' }), ]; const user: AuthUser = { userId: 'test', loginName: 'test', userName: '测试', roles: [], permissionLevel: 'department', depName: '部门A', depCode: 'a' }; const app = new Hono<{ Variables: { user: AuthUser } }>(); app.use('*', async (c, next) => { c.set('user', user); await next(); }); app.route('/f', createFaultRouter(async () => ({ rows: sourceRows, updatedAt }))); const query = new URLSearchParams(q); const report = await (await app.request(`/f/dashboard?${query}`)).json(); assert.equal(report.totals.reported, 4); assert.equal(report.totals.operationsReported, 2); assert.equal(report.totals.customerReported, 2); assert.equal(report.totals.operationsReported + report.totals.customerReported, report.totals.reported); assert.equal(report.options.reportMethod.find((o: { value: string }) => o.value === 'customer').label, 'AI提报'); assert.equal(report.options.reportMethod.find((o: { value: string }) => o.value === 'operations').label, '运维提报'); for (const [method, expected] of [['operations', ['ops-linked', 'ops-legacy']], ['customer', ['ai-explicit', 'ai-legacy']]] as const) { const response = await app.request(`/f/dashboard-analysis?${query}&scope=cohort&reportMethod=${method}`); assert.equal(response.status, 200); const detail = await response.json(); assert.deepEqual(detail.rows.map((row: { id: string }) => row.id).sort(), [...expected].sort()); const filtered = await (await app.request(`/f/dashboard?${query}&reportMethod=${method}`)).json(); assert.equal(filtered.totals.reported, expected.length); assert.equal(filtered.totals.operationsReported + filtered.totals.customerReported, filtered.totals.reported); } const empty = await (await app.request(`/f/dashboard?${query}®ion=不存在`)).json(); assert.equal(empty.totals.operationsReported, 0); assert.equal(empty.totals.customerReported, 0); });