Files
ln-bi/src/server/routes/mileage/monitoring-cache.test.ts
T
shishengliang e2f932232f
ci/woodpecker/push/woodpecker Pipeline was successful
feat(mileage): 接入区间汇总并复用查询结果
2026-10-29 09:11:21 +08:00

83 lines
6.5 KiB
TypeScript

import assert from 'node:assert/strict';
import test from 'node:test';
import { Hono } from 'hono';
import pool from '../../db.js';
import type { AuthUser } from '../../auth/types.js';
import type { MonitoringResponse } from './types.js';
import monitoring from './monitoring.js';
import recent from './vehicle-recent.js';
const originalConfig = { url: process.env.ONEOS_MILEAGE_API_BASE_URL, key: process.env.ONEOS_MILEAGE_API_KEY };
test.before(() => { process.env.ONEOS_MILEAGE_API_BASE_URL = 'https://oneos.test'; process.env.ONEOS_MILEAGE_API_KEY = 'test-only'; });
test.after(() => {
if (originalConfig.url === undefined) delete process.env.ONEOS_MILEAGE_API_BASE_URL; else process.env.ONEOS_MILEAGE_API_BASE_URL = originalConfig.url;
if (originalConfig.key === undefined) delete process.env.ONEOS_MILEAGE_API_KEY; else process.env.ONEOS_MILEAGE_API_KEY = originalConfig.key;
});
test('summary integration preserves scope, valid partial mileage, trend nulls, reuse, details and refresh', async t => {
const requests: {url:string;body:any}[]=[];
let reassigned=false;
let failure=0;
t.mock.method(globalThis,'fetch',async(url:string,options:RequestInit)=>{
const body=JSON.parse(options.body as string); requests.push({url,body});
if(url.endsWith('/summary/query')){
if(failure)return Response.json({code:'UPSTREAM_ERROR',traceId:'trace'}, {status:failure,headers:{'Retry-After':'12'}});
const selected=body.plateNumbers || ['TEST0','TEST1','TEST2'];
const values=[0,30,20];
return Response.json({code:'SUCCESS',startDate:body.startDate,endDate:body.endDate,timezone:'Asia/Shanghai',vehicleCount:selected.length,
data:{vehicles:selected.map((plate:string)=>({plateNumber:plate,vin:'VIN',periodMileageKm:values[Number(plate.slice(-1))],
endDateTotalMileageKm:plate==='TEST0'?null:100,endDateDataTime:null,endDateSourceProtocol:null,
validDays:1,missingDays:plate==='TEST2'?0:1,anomalyDays:plate==='TEST2'?1:0,
status:plate==='TEST2'?'DATA_ANOMALY':'PARTIAL_DATA',qualityReasons:[plate==='TEST2'?'BASELINE_MISSING':'CARRIED_FORWARD'],sourceProtocols:['MQTT'],updatedAt:null})),
dailyTrend:[{date:body.startDate,mileageKm:selected.reduce((sum:number,p:string)=>sum+values[Number(p.slice(-1))],0),validVehicleCount:selected.length,missingVehicleCount:0,anomalyVehicleCount:0},
{date:body.endDate,mileageKm:null,validVehicleCount:0,missingVehicleCount:selected.filter((p:string)=>p!=='TEST2').length,anomalyVehicleCount:selected.includes('TEST2')?1:0}]},
snapshotId:'snapshot',generatedAt:'2026-10-27T00:00:00Z',expiresAt:'2099-01-01T00:00:00Z',nextCursor:null});
}
if(url.endsWith('/range/query'))return Response.json({code:'SUCCESS',data:(body.plateNumbers||[]).flatMap((plate:string)=>[
{plateNumber:plate,date:body.startDate,dailyMileageKm:20,totalMileageKm:100,status:'NORMAL',sourceProtocol:'MQTT'},
{plateNumber:plate,date:body.endDate,dailyMileageKm:0,totalMileageKm:100,status:'NORMAL',dataQuality:'CARRIED_FORWARD',sourceProtocol:'MQTT'},
]),snapshotId:'detail',nextCursor:null});
return Response.json({code:'SUCCESS',data:[]});
});
t.mock.method(pool,'execute',async(sql:string)=>{
if(!sql.includes('FROM vehicle_info'))return [[],[]];
return [[0,1,2,3].map(i=>({plate:`TEST${i}`,vin:'VIN',customer:'客户',department:i===1?'B':'A',manager:null,
manager_id:i===0&&reassigned?'new-owner':`${i}`,rent_status:'租赁',entity:null,project:null,inventory_location:null,brand_label:null})),[]];
});
let user:AuthUser={userId:'0',userName:'测试',loginName:'test',depCode:'',depName:'A',roles:[],permissionLevel:'full'};
const app=new Hono<{Variables:{user:AuthUser}}>();
app.use('*',async(c,next)=>{c.set('user',user);await next();});
app.route('/monitoring',monitoring);app.route('/vehicle',recent);
const base='/monitoring?startDate=2026-10-10&endDate=2026-10-11&sourcePriority=instrument';
const read=async(extra='')=>{const response=await app.request(base+extra);assert.equal(response.status,200);return await response.json() as MonitoringResponse;};
const first=await read('&limit=1');
assert.equal(first.vehicles[0].plate,'TEST1');assert.equal(first.stats.totalToday,50);
assert.equal(first.rangeDailyTotals?.[1].totalKm,null);
assert.equal(requests.filter(r=>r.url.endsWith('/summary/query')).length,1);
assert.equal(requests.filter(r=>r.url.endsWith('/range/query')).length,0);
const second=await read('&page=2&limit=1&includeDailyTrend=0');assert.equal(second.vehicles[0].periodMileageKm,20);
const sorted=await read('&sortOrder=asc&includeDailyTrend=0');assert.equal(sorted.vehicles[0].periodMileageKm,0);
const exported=await read('&limit=9999&includeDailyTrend=0');
assert.equal(exported.vehicles.length,4);assert.equal(exported.vehicles[3].periodMileageKm,null);
assert.equal(exported.queriedAt,first.queriedAt);assert.ok(!exported.vehicles[0].dailyMileage);
assert.equal(requests.filter(r=>r.url.endsWith('/summary/query')).length,1);
user={...user,permissionLevel:'department'};
const department=await read();assert.equal(department.stats.totalToday,20);
assert.equal(department.rangeDailyTotals?.[0].totalKm,20);
assert.deepEqual(requests.filter(r=>r.url.endsWith('/summary/query')).at(-1)!.body.plateNumbers,['TEST0','TEST2']);
user={...user,permissionLevel:'personal'};
const personal=await read();assert.equal(personal.rangeDailyTotals?.[0].totalKm,0);
assert.equal(personal.rangeDailyTotals?.[1].totalKm,null);
const count=requests.length;reassigned=true;
const empty=await read();assert.equal(empty.total,0);assert.ok(empty.rangeDailyTotals?.every(d=>d.totalKm===null));assert.equal(requests.length,count);
const denied=await app.request('/vehicle/TEST0/recent?start=2026-10-10&end=2026-10-11');assert.equal(denied.status,403);
reassigned=false;user={...user,permissionLevel:'full'};
const detail=await read('&plate=TEST2&dailyDetails=1&includeDailyTrend=0');
assert.deepEqual(detail.vehicles[0].dailyMileage,{'2026-10-10':20,'2026-10-11':null});
assert.deepEqual(requests.at(-1)!.body.plateNumbers,['TEST2']);
const before=requests.filter(r=>r.url.endsWith('/summary/query')).length;
await read('&refresh=1');assert.equal(requests.filter(r=>r.url.endsWith('/summary/query')).length,before+1);
failure=403;const fail=await app.request(base+'&refresh=1');assert.equal(fail.status,403);assert.equal((await fail.json()).code,'UPSTREAM_ERROR');
failure=429;const limited=await app.request(base);assert.equal(limited.status,429);assert.equal(limited.headers.get('Retry-After'),'12');
});