revert: restore v1.1.14 (37a9f303)
ci/woodpecker/push/woodpecker Pipeline failed

This commit is contained in:
shishengliang
2026-09-16 09:29:30 +08:00
parent c49a02e3d9
commit 6ea1b3d5dd
210 changed files with 4448 additions and 36179 deletions
+95 -741
View File
@@ -1,26 +1,21 @@
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 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";
} from './electric.js';
import {
registerEtcOverviewRoute,
type EtcOverviewDependencies,
} from "./etc.js";
registerHydrogenDailyRoute,
type HydrogenDailyDependencies,
} from './hydrogen-daily.js';
import {
registerHydrogenStationBoardRoute,
type HydrogenStationBoardDependencies,
} from "./hydrogen-station-board.js";
import {
registerHydrogenBiV2Routes,
type HydrogenBiV2Dependencies,
} from "./hydrogen-bi-v2.js";
import { HYDROGEN_VERIFY_START_DATE } from "./constants.js";
registerHydrogenOverviewRoute,
type HydrogenOverviewDependencies,
} from './hydrogen-overview.js';
interface QueryCall {
sql: string;
@@ -32,40 +27,6 @@ interface CacheCall {
force: boolean | undefined;
}
test("待核对的汇总和下钻统一从核对上线期初起统计,不影响其他核对范围", async () => {
// 期初日期以字面量锁定:它同时被前端文案引用,改动必须是有意的。
assert.equal(HYDROGEN_VERIFY_START_DATE, "2026-01-01");
const unverifiedOnlyClauses = [
// 期初之前的订单不算待核对。
`b.refuel_time >= '${HYDROGEN_VERIFY_START_DATE}'`,
// 无车牌、或未关联羚牛车辆(vehicle_id 为 NULL/0)的订单没有核对对象。
"NULLIF(TRIM(b.license_plate), '') IS NOT NULL",
"COALESCE(b.vehicle_id, 0) <> 0",
];
for (const scope of ["unverified", "all", "verified"]) {
for (const endpoint of ["overview", "drill"]) {
const calls: QueryCall[] = [];
const app = new Hono();
registerHydrogenBiV2Routes(app, {
hydrogenPool: { query: createQueryMock([], calls) },
} as unknown as HydrogenBiV2Dependencies);
const response = await app.request(`/h2/v2/${endpoint}?year=2026&verifyScope=${scope}`);
assert.equal(response.status, 200);
const ledgerQueries = calls.filter(({ sql }) => sql.includes("FROM hydrogen_fuel_ledger b"));
assert.ok(ledgerQueries.length > 0);
for (const { sql } of ledgerQueries) {
for (const clause of unverifiedOnlyClauses) {
assert.equal(
sql.includes(clause),
scope === "unverified",
`${endpoint}/${scope} 对待核对限定条件「${clause}」的处理不符合预期`,
);
}
}
}
}
});
function createQueryMock(resultSets: unknown[][], calls: QueryCall[]) {
return async (sql: string, params?: unknown) => {
calls.push({ sql, params });
@@ -85,647 +46,65 @@ function createCachedMock(calls: CacheCall[]) {
}
function sqlHash(sql: string): string {
return createHash("sha256").update(sql).digest("hex").slice(0, 12);
return createHash('sha256').update(sql).digest('hex').slice(0, 12);
}
test("能源路由按既有顺序注册并增加只读单站看板接口", () => {
test('能源路由继续按原顺序注册四个 GET 接口', () => {
assert.deepEqual(
app.routes
.filter((route) => route.method === "GET")
.map((route) => route.path),
app.routes.filter(route => route.method === 'GET').map(route => route.path),
[
"/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",
'/hydrogen/overview',
'/hydrogen/daily',
'/electric/overview',
'/electric/monthly',
],
);
});
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/);
// 所有真实下钻共用此站点选项池:历史加氢量为正,而非仅当前区间活跃。
assert.match(calls[1].sql, /b\.station_id = s\.id/);
assert.match(calls[1].sql, /external_recharge_manual/);
assert.doesNotMatch(calls[1].sql, /refuel_time\s*[<>]/);
});
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&pageSize=1&page=2",
);
assert.equal(response.status, 200);
const payload = await response.json();
assert.equal(payload.amountScope, "customer");
assert.equal(payload.page.hasMore, true);
assert.deepEqual((calls[1].params as unknown[]).slice(-2), [1, 1]);
assert.equal(payload.summary.revenue - payload.summary.cost, 60);
assert.equal(calls.length, 2);
assert.match(calls[1].sql, /ORDER BY kg DESC, id ASC, name ASC LIMIT \? OFFSET \?/);
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 app = new Hono();
registerHydrogenStationBoardRoute(app, {
hydrogenPool: { query: createQueryMock([
[
{ id: 341, name: "佛山南海羚牛加氢站", kg: 0, fee: 0, recordCount: 0 },
{ id: 342, name: "广州交投东鹏大道加氢站", kg: 0, fee: 0, recordCount: 0 },
{ id: 343, name: "其他零业务站", kg: 0, fee: 0, recordCount: 0 },
], [], [], [], [],
], []) },
cached: createCachedMock([]),
} as unknown as HydrogenStationBoardDependencies);
const response = await app.request("/hydrogen/station-board?startDate=2026-08-16&endDate=2026-08-17");
assert.equal(response.status, 200);
const payload = await response.json();
assert.deepEqual(payload.stations.map((station: { id: number }) => station.id), [341, 342]);
assert.equal(payload.summary.totalKg, 0);
assert.equal(payload.summary.activeStationCount, 0);
assert.ok(payload.stations.every((station: { dailyKg: { kg: number }[] }) => station.dailyKg.every(row => row.kg === 0)));
});
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("单站外部客户按唯一标准化车牌聚合,进账保持租户客户级口径", async () => {
const calls: QueryCall[] = [];
const stationBoardApp = new Hono();
registerHydrogenStationBoardRoute(stationBoardApp, {
hydrogenPool: {
query: createQueryMock(
[
[{ id: 341, name: "佛山南海羚牛加氢站", province: "广东省", city: "佛山市", kg: 10, fee: 300, recordCount: 1 }],
[], [], [], [],
[], [], [],
[
{
month: "2026-09",
customerName: "外部客户 A",
kg: null,
fee: null,
recordCount: 1,
},
],
[
{
id: "auto:9007199254740993",
date: "2026-09-05",
customerName: "外部客户 A",
amount: 200,
payMethod: "wechat",
source: "spot_daily_auto",
sourceRecordCount: 2,
updatedAt: "2026-09-05 23:59:00",
},
{
id: "manual:1",
date: "2026-09-04",
customerName: "外部客户 B",
amount: 0,
payMethod: "corporate",
source: "external_recharge_manual",
sourceRecordCount: 1,
updatedAt: null,
},
],
],
calls,
),
},
cached: createCachedMock([]),
} as unknown as HydrogenStationBoardDependencies);
const response = await stationBoardApp.request(
"/hydrogen/station-board?stationId=341&startDate=2026-09-01&endDate=2026-09-05",
);
assert.equal(response.status, 200);
const payload = await response.json();
assert.deepEqual(payload.selected.externalCustomerMonths, [{
month: "2026-09", customerName: "外部客户 A", kg: 0, fee: 0, recordCount: 1,
}]);
assert.deepEqual(payload.selected.externalReceipts, {
scope: "customer",
reason: "当前租户全部外部客户进账,不按站点归属,不计入本单站经营收益或现结 KPI。",
rows: [
{
id: "auto:9007199254740993", date: "2026-09-05", customerName: "外部客户 A", amount: 200,
payMethod: "wechat", source: "spot_daily_auto", sourceRecordCount: 2, updatedAt: "2026-09-05 23:59:00",
},
{
id: "manual:1", date: "2026-09-04", customerName: "外部客户 B", amount: 0,
payMethod: "corporate", source: "external_recharge_manual", sourceRecordCount: 1, updatedAt: null,
},
],
});
assert.equal(calls.length, 10);
for (const index of [0, 2, 3, 5, 7, 8, 9]) {
assert.match(calls[index].sql, /tenant_id\s*=\s*\?/);
assert.ok((calls[index].params as unknown[]).includes("000000"));
}
for (const index of [0, 2, 3, 5, 7, 8]) {
assert.match(calls[index].sql, /record_source, ''\) <> \?/);
assert.ok((calls[index].params as unknown[]).includes("external_recharge_manual"));
}
assert.match(calls[8].sql, /customerCount = 1/);
// 自营站客户月度保留全部加氢事实:映射仅做补充,不过滤未匹配或内部车辆。
assert.match(calls[7].sql, /LEFT JOIN external_vehicle_map/);
assert.match(calls[7].sql, /COALESCE\(m.customerName/);
assert.doesNotMatch(calls[7].sql.split('WHERE b.del_flag')[1], /vehicle_id IS NULL/);
assert.match(calls[7].sql, /GROUP BY[\s\S]*customerName/);
assert.match(calls[8].sql, /CONVERT\(UPPER\(REPLACE/);
assert.match(calls[8].sql, /b\.vehicle_id IS NULL OR b\.vehicle_id = 0/);
assert.match(calls[9].sql, /UNION ALL/);
assert.match(calls[9].sql, /fee_total AS amount/);
assert.match(calls[9].sql, /summary_date >= \?/);
assert.match(calls[9].sql, /refuel_time < DATE_ADD\(\?, INTERVAL 1 DAY\)/);
});
test("电能处理器继续使用原缓存语义、数据库、SQL 与参数", async () => {
test('四个处理器继续使用原缓存语义、数据库、SQL 与参数', async () => {
const hydrogenCalls: QueryCall[] = [];
const electricCalls: QueryCall[] = [];
const cacheCalls: CacheCall[] = [];
const cached = createCachedMock(cacheCalls);
const electricQuery = createQueryMock(
const hydrogenQuery = createQueryMock([
[{ y: 2025 }],
[{}],
[],
[],
[],
[],
[],
[
[
{
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 }],
{ d: '2025-01-02', stationId: 7, stationName: '测试站', kg: 10, pricePerKg: 35 },
],
electricCalls,
], 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 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);
const electricApp = new Hono();
const electricDependencies = {
@@ -734,80 +113,55 @@ test("电能处理器继续使用原缓存语义、数据库、SQL 与参数", a
} as unknown as ElectricDependencies;
registerElectricOverviewRoute(electricApp, electricDependencies);
registerElectricMonthlyRoute(electricApp, electricDependencies);
const electricOverviewResponse = await electricApp.request(
"/electric/overview?force=1",
);
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",
'/electric/monthly?startDate=2025-01-01&endDate=2025-01-02&customer=external&force=1',
);
assert.equal(electricMonthlyResponse.status, 200);
assert.deepEqual(cacheCalls, [
{ key: "electric/overview", force: true },
{
key: "electric/monthly?customer=external&start=2025-01-01&end=2025-01-02",
force: true,
},
{ 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(
electricCalls.map((call) => sqlHash(call.sql)),
[...hydrogenCalls, ...electricCalls].map(call => sqlHash(call.sql)),
[
"03fd669f8077",
"6e4031b10926",
"4fc77463294c",
"1886d54939d8",
"09fc714ec4b2",
'95f6d99b61c9',
'63a63c5f78bb',
'1367eddb3b7f',
'83a52a7f9336',
'f28fa4a3cb08',
'dac9e86fc527',
'a71da8f05765',
'b562668859ce',
'226fd8f950a1',
'6e4031b10926',
'4fc77463294c',
'1886d54939d8',
'09fc714ec4b2',
],
);
assert.deepEqual(
electricCalls.map((call) => call.params),
[undefined, undefined, undefined, undefined, ["2025-01-01", "2025-01-02"]],
);
});
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/);
assert.deepEqual(hydrogenCalls.map(call => call.params), [
['2024-01-01'],
[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.deepEqual(electricCalls.map(call => call.params), [
undefined,
undefined,
undefined,
undefined,
['2025-01-01', '2025-01-02'],
]);
});