Files
ln-bi/src/server/routes/energy/routes.test.ts
T
kfluousandHiFox Agent 6c91a6694a
ci/woodpecker/push/woodpecker Pipeline was successful
fix(energy): deliver prototype-aligned hydrogen board and acceptance fixes
Co-authored-by: HiFox Agent <agents-noreply@hifox.com>
2026-09-03 22:27:23 +08:00

942 lines
26 KiB
TypeScript

import assert from "node:assert/strict";
import { createHash } from "node:crypto";
import test from "node:test";
import { Hono } from "hono";
import app from "./index.js";
import {
registerElectricMonthlyRoute,
registerElectricOverviewRoute,
type ElectricDependencies,
} from "./electric.js";
import {
registerEtcOverviewRoute,
type EtcOverviewDependencies,
} from "./etc.js";
import {
registerHydrogenDailyRoute,
type HydrogenDailyDependencies,
} from "./hydrogen-daily.js";
import {
registerHydrogenOverviewRoute,
type HydrogenOverviewDependencies,
} from "./hydrogen-overview.js";
import {
registerHydrogenSettlementRoute,
type HydrogenSettlementDependencies,
} from "./hydrogen-settlement.js";
import {
registerHydrogenStationBoardRoute,
type HydrogenStationBoardDependencies,
} from "./hydrogen-station-board.js";
import {
registerHydrogenBiV2Routes,
type HydrogenBiV2Dependencies,
} from "./hydrogen-bi-v2.js";
interface QueryCall {
sql: string;
params: unknown;
}
interface CacheCall {
key: string;
force: boolean | undefined;
}
function createQueryMock(resultSets: unknown[][], calls: QueryCall[]) {
return async (sql: string, params?: unknown) => {
calls.push({ sql, params });
return [resultSets.shift() ?? [], []];
};
}
function createCachedMock(calls: CacheCall[]) {
return async <T>(
key: string,
loader: () => Promise<T>,
opts: { force?: boolean } = {},
): Promise<T> => {
calls.push({ key, force: opts.force });
return loader();
};
}
function sqlHash(sql: string): string {
return createHash("sha256").update(sql).digest("hex").slice(0, 12);
}
test("能源路由按既有顺序注册并增加只读单站看板接口", () => {
assert.deepEqual(
app.routes
.filter((route) => route.method === "GET")
.map((route) => route.path),
[
"/hydrogen/overview",
"/hydrogen/overview-detail",
"/hydrogen/daily",
"/hydrogen/daily-detail",
"/hydrogen/settlement",
"/hydrogen/station-board",
"/h2/v2/meta",
"/h2/v2/overview",
"/h2/v2/daily",
"/h2/v2/daily-tree",
"/h2/v2/drill",
"/electric/overview",
"/electric/monthly",
"/etc/overview",
],
);
});
test("氢能 BI v2 使用独立真实账本合同,并且不让前端拼接旧接口", async () => {
const calls: QueryCall[] = [];
const v2App = new Hono();
registerHydrogenBiV2Routes(v2App, {
hydrogenPool: {
query: createQueryMock(
[
[{ value: 2026, startDate: "2026-01-01", endDate: "2026-08-18" }],
[{ id: 7, name: "测试站", province: "广东省", city: "佛山市" }],
[{ ledgerAt: "2026-08-18 10:00:00" }],
],
calls,
),
},
} as unknown as HydrogenBiV2Dependencies);
const response = await v2App.request("/h2/v2/meta");
assert.equal(response.status, 200);
assert.deepEqual(await response.json(), {
years: [{ value: 2026, startDate: "2026-01-01", endDate: "2026-08-18" }],
stations: [{ id: "7", name: "测试站", province: "广东省", city: "佛山市" }],
watermark: { ledgerAt: "2026-08-18 10:00:00", paymentAt: null },
});
assert.equal(calls.length, 3);
assert.match(calls[0].sql, /FROM hydrogen_fuel_ledger/);
assert.match(calls[1].sql, /FROM new_hydrogen_site/);
assert.match(calls[1].sql, /EXISTS\s*\([\s\S]*amount_kg[\s\S]*> 0/);
});
test("氢能 BI v2 下钻在日期、站点、客户、车辆及区域上下文中复用同一过滤口径", async () => {
const calls: QueryCall[] = [];
const v2App = new Hono();
registerHydrogenBiV2Routes(v2App, {
hydrogenPool: {
query: createQueryMock(
[
[{ id: "123" }],
[{ recordCount: 2, kg: 30, cost: 900, revenue: 1000 }],
[
{
id: "2026-08-18",
name: "2026-08-18",
province: null,
city: null,
recordCount: 2,
stationCount: 1,
customerCount: 1,
kg: 30,
cost: 900,
revenue: 1000,
lingniuKg: 0,
externalKg: 30,
},
],
],
calls,
),
},
} as unknown as HydrogenBiV2Dependencies);
const response = await v2App.request(
"/h2/v2/drill?date=2026-08-18&stationId=0&customerId=0&customerName=%E6%9C%AA%E5%85%B3%E8%81%94%E5%AE%A2%E6%88%B7&plateNo=%E6%97%A0%E8%BD%A6%E7%89%8C&region=%E5%98%89%E5%85%B4%E5%B8%82&regionGranularity=city&vehicleScope=external&verifyScope=verified&groupBy=date&pageSize=20",
);
assert.equal(response.status, 200);
const payload = await response.json();
assert.equal(payload.groupBy, "date");
assert.equal(payload.filters.date, "2026-08-18");
assert.equal(payload.filters.stationId, "0");
assert.equal(payload.filters.customerId, 0);
assert.equal(payload.filters.customerName, "未关联客户");
assert.equal(payload.filters.plateNo, "无车牌");
assert.equal(payload.filters.region, "嘉兴市");
assert.equal(payload.groups[0].id, "2026-08-18");
assert.equal(payload.summary.kg, 30);
assert.equal(calls.length, 3);
assert.deepEqual(calls[0].params, ["嘉兴市"]);
assert.match(calls[0].sql, /FROM new_hydrogen_site rs/);
const commonParams = [
"2026-08-18",
"2026-08-18",
"0",
0,
"未关联客户",
"无车牌",
"123",
];
assert.deepEqual(calls[1].params, commonParams);
assert.deepEqual((calls[2].params as unknown[]).slice(0, -2), commonParams);
for (const call of calls.slice(1)) {
assert.match(call.sql, /COALESCE\(b\.station_id, 0\) = \?/);
assert.match(
call.sql,
/COALESCE\(b\.system_customer_id, b\.customer_id, 0\) = \?/,
);
assert.match(call.sql, /COALESCE\(NULLIF\(b\.license_plate/);
assert.match(call.sql, /b\.station_id IN \(\?\)/);
assert.match(call.sql, /b\.vehicle_id IS NULL/);
assert.match(call.sql, /verify_status/);
}
assert.match(
calls[2].sql,
/GROUP BY DATE_FORMAT\(b\.refuel_time, '%Y-%m-%d'\)/,
);
assert.match(calls[2].sql, /ORDER BY id DESC/);
});
test("氢能 BI v2 利润下钻只保留客户承担订单,并与对客总价使用同一订单集合", async () => {
const calls: QueryCall[] = [];
const v2App = new Hono();
registerHydrogenBiV2Routes(v2App, {
hydrogenPool: {
query: createQueryMock(
[
[{ recordCount: 1, kg: 10, cost: 300, revenue: 360 }],
[
{
id: "7",
name: "客户承担站",
province: "浙江省",
city: "嘉兴市",
recordCount: 1,
stationCount: 1,
customerCount: 1,
kg: 10,
cost: 300,
revenue: 360,
lingniuKg: 10,
externalKg: 0,
},
],
[
{
id: 1,
time: "2026-08-18 09:00:00",
orderNo: "H2-1",
stationId: 7,
stationName: "客户承担站",
customerId: 8,
customerName: "客户A",
plateNo: "浙A1",
source: "api",
verifyStatus: "VERIFIED",
vehicleId: 1,
kg: 10,
unitPrice: 30,
cost: 300,
revenue: 360,
},
],
],
calls,
),
},
} as unknown as HydrogenBiV2Dependencies);
const response = await v2App.request(
"/h2/v2/drill?year=2026&vehicleScope=all&verifyScope=all&groupBy=station&amountScope=customer",
);
assert.equal(response.status, 200);
const payload = await response.json();
assert.equal(payload.amountScope, "customer");
assert.equal(payload.summary.revenue - payload.summary.cost, 60);
assert.equal(calls.length, 2);
for (const call of calls) {
assert.match(
call.sql,
/b\.settlement_type = 1/,
);
}
});
test("氢能 BI v2 保留原型允许选择的 2023 历史年份,不回退到当前年", async () => {
const calls: QueryCall[] = [];
const v2App = new Hono();
registerHydrogenBiV2Routes(v2App, {
hydrogenPool: {
query: createQueryMock(
[
[
{
ledgerAt: null,
totalKg: 0,
totalCost: 0,
totalRevenue: 0,
customerCost: 0,
monthKg: 0,
monthCost: 0,
todayKg: 0,
todayCost: 0,
recordCount: 0,
stationCount: 0,
},
],
[],
[],
[],
],
calls,
),
},
} as unknown as HydrogenBiV2Dependencies);
const response = await v2App.request(
"/h2/v2/overview?year=2023&vehicleScope=all&verifyScope=all",
);
assert.equal(response.status, 200);
const payload = await response.json();
assert.deepEqual(payload.range, {
startDate: "2023-01-01",
endDate: "2023-12-31",
});
assert.equal(payload.filters.startDate, "2023-01-01");
assert.equal(payload.filters.endDate, "2023-12-31");
assert.equal(payload.kpis.totalKg, 0);
assert.equal(calls.length, 4);
});
test("氢能 BI v2 客户账单按 settlement_type 映射客户、我司和其他承担", async () => {
const calls: QueryCall[] = [];
const v2App = new Hono();
registerHydrogenBiV2Routes(v2App, {
hydrogenPool: {
query: createQueryMock(
[
[
{
ledgerAt: "2026-08-19 10:00:00",
totalKg: 60,
totalCost: 1800,
totalRevenue: 720,
customerBearingKg: 10,
companyBearingKg: 30,
otherBearingKg: 20,
customerRevenue: 360,
customerCost: 600,
companyCost: 900,
otherCost: 300,
monthKg: 60,
monthCost: 1800,
todayKg: 0,
todayCost: 0,
recordCount: 3,
stationCount: 1,
},
],
[
{
month: "2026-01",
totalKg: 60,
lingniuKg: 30,
externalKg: 30,
cost: 1800,
revenue: 720,
customerRevenue: 360,
customerCost: 600,
},
],
[],
[
{
id: 1,
name: "混合承担客户",
kg: 30,
customerBearingKg: 10,
companyBearingKg: 20,
otherBearingKg: 0,
cost: 900,
revenue: 300,
customerRevenue: 360,
customerCost: 600,
companyCost: 300,
otherCost: 0,
recordCount: 2,
},
{
id: 2,
name: "仅我司承担客户",
kg: 30,
customerBearingKg: 0,
companyBearingKg: 10,
otherBearingKg: 20,
cost: 900,
revenue: 0,
customerRevenue: 0,
customerCost: 0,
companyCost: 300,
otherCost: 600,
recordCount: 1,
},
],
],
calls,
),
},
} as unknown as HydrogenBiV2Dependencies);
const response = await v2App.request(
"/h2/v2/overview?year=2026&vehicleScope=all&verifyScope=all",
);
assert.equal(response.status, 200);
const payload = await response.json();
assert.equal(payload.kpis.customerBearingKg, 10);
assert.equal(payload.kpis.companyBearingKg, 30);
assert.equal(payload.kpis.otherBearingKg, 20);
assert.equal(
payload.kpis.totalKg,
payload.kpis.customerBearingKg +
payload.kpis.companyBearingKg +
payload.kpis.otherBearingKg,
);
assert.equal(payload.kpis.customerGrossProfit, -240);
assert.equal(payload.monthly[0].customerRevenue, 360);
assert.equal(payload.monthly[0].customerCost, 600);
assert.equal(payload.monthly[0].customerGrossProfit, -240);
assert.deepEqual(payload.customers.map((row: { name: string; bearer: string }) => ({
name: row.name,
bearer: row.bearer,
})), [
{ name: "混合承担客户", bearer: "both" },
{ name: "仅我司承担客户", bearer: "other" },
]);
assert.match(calls[3].sql, /AS customerBearingKg/);
assert.match(calls[3].sql, /AS companyBearingKg/);
assert.match(calls[3].sql, /AS otherBearingKg/);
assert.match(calls[2].sql, /AS customerRevenue/);
assert.match(calls[2].sql, /AS customerCost/);
assert.match(calls[2].sql, /AS companyCost/);
assert.match(calls[2].sql, /AS otherCost/);
assert.match(calls[3].sql, /AS customerRevenue/);
assert.match(calls[3].sql, /AS customerCost/);
assert.match(calls[3].sql, /AS companyCost/);
assert.match(calls[3].sql, /AS otherCost/);
assert.equal(payload.customers[0].customerRevenue, 360);
assert.equal(payload.customers[0].customerCost, 600);
assert.match(calls[3].sql, /b\.settlement_type = 1/);
assert.match(calls[3].sql, /b\.settlement_type = 2/);
assert.match(calls[3].sql, /b\.settlement_type NOT IN \(1, 2\)/);
});
test("氢能 BI v2 月度上下文换算为完整自然月并沿用至明细", async () => {
const calls: QueryCall[] = [];
const v2App = new Hono();
registerHydrogenBiV2Routes(v2App, {
hydrogenPool: {
query: createQueryMock(
[[{ recordCount: 0, kg: 0, cost: 0, revenue: 0 }], []],
calls,
),
},
} as unknown as HydrogenBiV2Dependencies);
const response = await v2App.request(
"/h2/v2/drill?month=2026-02&vehicleScope=all&verifyScope=all&groupBy=record",
);
assert.equal(response.status, 200);
const payload = await response.json();
assert.deepEqual(payload.filters.startDate, "2026-02-01");
assert.deepEqual(payload.filters.endDate, "2026-02-28");
assert.equal(payload.filters.month, "2026-02");
assert.equal(calls.length, 2);
assert.deepEqual(calls[0].params, ["2026-02-01", "2026-02-28"]);
assert.deepEqual((calls[1].params as unknown[]).slice(0, -2), [
"2026-02-01",
"2026-02-28",
]);
});
test("单站看板仅保留有加氢记录的站点并合并区间现结流水", async () => {
const calls: QueryCall[] = [];
const cacheCalls: CacheCall[] = [];
const stationBoardApp = new Hono();
registerHydrogenStationBoardRoute(stationBoardApp, {
hydrogenPool: {
query: createQueryMock(
[
[
{
id: 7,
name: "测试加氢站",
province: "广东省",
city: "佛山市",
kg: 100,
fee: 3500,
recordCount: 2,
latestLedgerTime: "2026-08-17 10:00:00",
},
{
id: 8,
name: "零业务站",
province: "广东省",
city: "广州市",
kg: 0,
fee: 0,
recordCount: 0,
latestLedgerTime: null,
},
],
[
{
stationId: 7,
amount: 5000,
paymentCount: 1,
latestPaymentDate: "2026-08-17",
},
],
[
{ date: "2026-08-16", kg: 40, fee: 1400, recordCount: 1 },
{ date: "2026-08-17", kg: 60, fee: 2100, recordCount: 1 },
],
[
{ stationId: 7, date: "2026-08-16", kg: 40 },
{ stationId: 7, date: "2026-08-17", kg: 60 },
],
[{ date: "2026-08-17", amount: 5000, paymentCount: 1 }],
],
calls,
),
},
cached: createCachedMock(cacheCalls),
} as unknown as HydrogenStationBoardDependencies);
const response = await stationBoardApp.request(
"/hydrogen/station-board?startDate=2026-08-16&endDate=2026-08-17&force=1",
);
assert.equal(response.status, 200);
const payload = await response.json();
assert.deepEqual(payload.summary, {
stationCount: 1,
activeStationCount: 1,
totalKg: 100,
totalFee: 3500,
recordCount: 2,
paymentAmount: 5000,
paymentCount: 1,
latestLedgerTime: "2026-08-17 10:00:00",
daily: [
{
date: "2026-08-16",
kg: 40,
fee: 1400,
recordCount: 1,
paymentAmount: 0,
paymentCount: 0,
},
{
date: "2026-08-17",
kg: 60,
fee: 2100,
recordCount: 1,
paymentAmount: 5000,
paymentCount: 1,
},
],
});
assert.equal(payload.stations.length, 1);
assert.equal(payload.stations[0].name, "测试加氢站");
assert.deepEqual(payload.stations[0].dailyKg, [
{ date: "2026-08-16", kg: 40 },
{ date: "2026-08-17", kg: 60 },
]);
assert.deepEqual(cacheCalls, [
{
key: "hydrogen/station-board?start=2026-08-16&end=2026-08-17",
force: true,
},
]);
assert.equal(calls.length, 5);
assert.match(calls[0].sql, /FROM hydrogen_station s/);
assert.match(calls[1].sql, /FROM hydrogen_station_payment/);
assert.match(calls[2].sql, /SUM\(b\.amount_kg\)/);
assert.match(calls[3].sql, /GROUP BY b\.station_id/);
assert.match(calls[4].sql, /GROUP BY DATE_FORMAT\(payment_date/);
});
test("站日现结台账仅读取付款流水,并按最近有数据的 15 天聚合", async () => {
const calls: QueryCall[] = [];
const cacheCalls: CacheCall[] = [];
const app = new Hono();
registerHydrogenSettlementRoute(app, {
hydrogenPool: {
query: createQueryMock(
[
[{ latestPaymentDate: "2026-05-26" }],
[
{
date: "2026-05-26",
stationId: 44,
stationName: "联新加氢站",
amount: 30000,
paymentCount: 1,
matchMode: "exact",
},
{
date: "2026-05-25",
stationId: null,
stationName: "佛山中石化",
amount: 20000,
paymentCount: 2,
matchMode: "group",
},
],
],
calls,
),
},
cached: createCachedMock(cacheCalls),
} as unknown as HydrogenSettlementDependencies);
const response = await app.request(
"/hydrogen/settlement?range=latest&force=1",
);
assert.equal(response.status, 200);
assert.deepEqual(await response.json(), {
range: { start: "2026-05-12", end: "2026-05-26", mode: "latest" },
summary: {
amount: 50000,
paymentCount: 3,
stationDayCount: 2,
stationCount: 2,
latestPaymentDate: "2026-05-26",
},
rows: [
{
date: "2026-05-26",
stationId: 44,
stationName: "联新加氢站",
amount: 30000,
paymentCount: 1,
matchMode: "exact",
},
{
date: "2026-05-25",
stationId: null,
stationName: "佛山中石化",
amount: 20000,
paymentCount: 2,
matchMode: "group",
},
],
});
assert.deepEqual(cacheCalls, [
{ key: "hydrogen/settlement?start=2026-05-12&end=2026-05-26", force: true },
]);
assert.equal(calls.length, 2);
assert.match(calls[0].sql, /MAX\(payment_date\)/);
assert.match(calls[1].sql, /FROM hydrogen_station_payment p/);
assert.match(calls[1].sql, /LIMIT 500/);
assert.deepEqual(calls[1].params, ["2026-05-12", "2026-05-26"]);
});
test("四个处理器继续使用原缓存语义、数据库、SQL 与参数", async () => {
const hydrogenCalls: QueryCall[] = [];
const electricCalls: QueryCall[] = [];
const cacheCalls: CacheCall[] = [];
const cached = createCachedMock(cacheCalls);
const hydrogenQuery = createQueryMock(
[
[{ y: 2025, latestLedgerTime: "2025-01-02 10:30:00" }],
[{}],
[],
[],
[],
[],
[
{
name: "混合承担客户",
payer: "mixed",
kg: 10,
cost: 350,
revenue: 360,
},
],
[
{
d: "2025-01-02",
stationId: 7,
stationName: "测试站",
kg: 10,
fee: 350,
pricePerKg: 35,
},
],
],
hydrogenCalls,
);
const electricQuery = createQueryMock(
[
[
{
totalKwh: 20,
totalFee: 30,
monthKwh: 15,
monthFee: 25,
todayKwh: 10,
todayFee: 12,
},
],
[],
[{ date: "2025-01-02", kwh: 10, fee: 12 }],
[{ kwh: 5 }],
[{ date: "2025-01-02", kwh: 10, fee: 12 }],
],
electricCalls,
);
const hydrogenOverviewApp = new Hono();
registerHydrogenOverviewRoute(hydrogenOverviewApp, {
hydrogenPool: { query: hydrogenQuery },
cached,
} as unknown as HydrogenOverviewDependencies);
const overviewResponse = await hydrogenOverviewApp.request(
"/hydrogen/overview?year=2025&force=1",
);
assert.equal(overviewResponse.status, 200);
const overviewPayload = (await overviewResponse.json()) as {
customers: { payer: string; name: string }[];
};
assert.deepEqual(overviewPayload.customers, [
{
name: "混合承担客户",
payer: "mixed",
kg: 10,
cost: 350,
revenue: 360,
},
]);
const hydrogenDailyApp = new Hono();
registerHydrogenDailyRoute(hydrogenDailyApp, {
hydrogenPool: { query: hydrogenQuery },
cached,
} as unknown as HydrogenDailyDependencies);
const dailyResponse = await hydrogenDailyApp.request(
"/hydrogen/daily?startDate=2025-01-01&endDate=2025-01-02&customer=external&force=1",
);
assert.equal(dailyResponse.status, 200);
assert.deepEqual(await dailyResponse.json(), [
{
date: "2025-01-02",
totalKg: 10,
totalFee: 350,
chainPct: 0,
customerType: "external",
stations: [
{
id: 7,
name: "测试站",
pricePerKg: 35,
kg: 10,
fee: 350,
chainPct: 0,
},
],
},
{
date: "2025-01-01",
totalKg: 0,
totalFee: 0,
chainPct: 0,
customerType: "external",
stations: [],
},
]);
const electricApp = new Hono();
const electricDependencies = {
pool: { query: electricQuery },
cached,
} as unknown as ElectricDependencies;
registerElectricOverviewRoute(electricApp, electricDependencies);
registerElectricMonthlyRoute(electricApp, electricDependencies);
const electricOverviewResponse = await electricApp.request(
"/electric/overview?force=1",
);
assert.equal(electricOverviewResponse.status, 200);
const electricMonthlyResponse = await electricApp.request(
"/electric/monthly?startDate=2025-01-01&endDate=2025-01-02&customer=external&force=1",
);
assert.equal(electricMonthlyResponse.status, 200);
assert.deepEqual(cacheCalls, [
{ key: "hydrogen/overview?year=2025", force: true },
{
key: "hydrogen/daily?start=2025-01-01&end=2025-01-02&customer=external",
force: true,
},
{ key: "electric/overview", force: true },
{
key: "electric/monthly?customer=external&start=2025-01-01&end=2025-01-02",
force: true,
},
]);
// 指纹来自拆分前的 13 条 SQL,连空白符变化也会使测试失败。
assert.deepEqual(
[...hydrogenCalls, ...electricCalls].map((call) => sqlHash(call.sql)),
[
"515acf4dbb2a",
"de16af670e96",
"1367eddb3b7f",
"f125d4dde376",
"c547b8e1318e",
"0e3dfd8f4b81",
"f1f69f275993",
"688cfdb7a2bf",
"03fd669f8077",
"6e4031b10926",
"4fc77463294c",
"1886d54939d8",
"09fc714ec4b2",
],
);
assert.deepEqual(
hydrogenCalls.map((call) => call.params),
[
["2024-01-01"],
[
2025,
2025,
2025,
2025,
2025,
2025,
2025,
2025,
0,
0,
0,
0,
0,
0,
0,
0,
"2024-01-01",
],
["2024-01-01", 2025],
["2024-01-01", 2025],
["2024-01-01", 2025],
["2024-01-01", 2025],
["2024-01-01", 2025],
["2025-01-01", "2025-01-02"],
],
);
assert.match(hydrogenCalls[7].sql, /b\.vehicle_id IS NULL/);
assert.deepEqual(
electricCalls.map((call) => call.params),
[undefined, undefined, undefined, undefined, ["2025-01-01", "2025-01-02"]],
);
});
test("氢能总览按车辆归属和单站筛选,并隔离缓存结果", async () => {
const calls: QueryCall[] = [];
const cacheCalls: CacheCall[] = [];
const app = new Hono();
registerHydrogenOverviewRoute(app, {
hydrogenPool: {
query: createQueryMock(
[
[{ y: 2026, latestLedgerTime: "2026-08-17 21:50:09" }],
[{ yearKg: 12, latestLedgerTime: "2026-08-17 21:50:09" }],
[],
[],
[],
[],
[],
],
calls,
),
},
cached: createCachedMock(cacheCalls),
} as unknown as HydrogenOverviewDependencies);
const response = await app.request(
"/hydrogen/overview?year=2026&stationId=7&vehicleScope=lingniu",
);
const payload = await response.json();
assert.equal(response.status, 200);
assert.equal(payload.latestLedgerTime, "2026-08-17 21:50:09");
assert.deepEqual(cacheCalls, [
{
key: "hydrogen/overview?year=2026&station=7&vehicle=lingniu",
force: false,
},
]);
assert.deepEqual(payload.filter, {
stationId: 7,
vehicleScope: "lingniu",
verifyScope: "all",
});
// 年份候选项是全局元数据;其余查询都必须带上单站筛选参数。
assert.ok(
calls
.slice(1)
.every(
(call) =>
Array.isArray(call.params) && (call.params as unknown[]).includes(7),
),
);
assert.match(calls[1].sql, /station_id = \? AND vehicle_id IS NOT NULL/);
assert.match(
calls[2].sql,
/b\.station_id = \? AND b\.vehicle_id IS NOT NULL/,
);
});
test("ETC 总览对空台账返回明确空状态", async () => {
const calls: QueryCall[] = [];
const cacheCalls: CacheCall[] = [];
const app = new Hono();
registerEtcOverviewRoute(app, {
pool: {
query: createQueryMock(
[
[
{
tollRecordCount: 0,
vehicleCount: 0,
totalAmount: null,
latestTollTime: null,
billCount: 0,
receivableAmount: null,
paidAmount: null,
},
],
],
calls,
),
},
cached: createCachedMock(cacheCalls),
} as unknown as EtcOverviewDependencies);
const response = await app.request("/etc/overview?force=1");
assert.equal(response.status, 200);
assert.deepEqual(await response.json(), {
tollRecordCount: 0,
vehicleCount: 0,
totalAmount: 0,
latestTollTime: null,
billCount: 0,
receivableAmount: 0,
paidAmount: 0,
hasData: false,
});
assert.deepEqual(cacheCalls, [{ key: "etc/overview", force: true }]);
assert.match(calls[0].sql, /FROM etc_toll_record/);
assert.match(calls[0].sql, /FROM energy_etc_bill/);
});