fix: preserve billing response shape

This commit is contained in:
2026-08-17 11:28:08 +08:00
parent 600cba023c
commit 2ef3da7af5
7 changed files with 488 additions and 6 deletions

View File

@@ -0,0 +1,147 @@
import { describe, expect, it } from "vitest";
import { parseAdminBillingPayload, parseMemberBillingPayload } from "@/lib/client/billing-api";
const wallet = {
organizationId: "org-1",
balanceFen: 0,
totalRechargedFen: 0,
totalChargedFen: 0,
updatedAt: "2026-08-17T00:00:00.000Z"
};
const ledgerEntry = {
id: "ledger-1",
organizationId: "org-1",
kind: "recharge",
deltaFen: 100,
balanceAfterFen: 100,
description: "充值",
metadata: {},
createdAt: "2026-08-17T00:00:00.000Z"
};
function validAdminPayload() {
return {
billingAccount: {},
organizations: [],
members: [],
ledger: [],
priceRules: []
};
}
function validMemberPayload() {
return {
organization: { id: "org-1", name: "测试组织" },
billingAccount: {},
wallet,
ledger: [],
summary: { rechargeFen: 0, chargedFen: 0, refundedFen: 0, netConsumedFen: 0 },
personal: { rechargeFen: 0, chargedFen: 0, refundedFen: 0, netConsumedFen: 0 }
};
}
describe("billing API payload contract", () => {
it("accepts valid empty collection payloads", () => {
const admin = validAdminPayload();
const member = validMemberPayload();
expect(parseAdminBillingPayload(admin)).toBe(admin);
expect(parseMemberBillingPayload(member)).toBe(member);
});
it.each(["organizations", "members", "ledger", "priceRules"])(
"rejects a missing admin %s collection",
(field) => {
const payload: Record<string, unknown> = validAdminPayload();
delete payload[field];
expect(() => parseAdminBillingPayload(payload))
.toThrow(`超管计费数据格式错误:${field} 必须是数组。`);
}
);
it("rejects a malformed admin price-rule element", () => {
expect(() => parseAdminBillingPayload({ ...validAdminPayload(), priceRules: [null] }))
.toThrow("超管计费数据格式错误priceRules[0] 必须是对象。");
});
it("rejects malformed admin organization elements and wallets", () => {
expect(() => parseAdminBillingPayload({ ...validAdminPayload(), organizations: [null] }))
.toThrow("超管计费数据格式错误organizations[0] 必须是对象。");
expect(() => parseAdminBillingPayload({
...validAdminPayload(),
organizations: [{ id: "org-1", name: "测试组织", status: "active", wallet: { ...wallet, balanceFen: "0" } }]
})).toThrow("超管计费数据格式错误organizations[0].wallet.balanceFen 必须是有限数字。");
});
it("rejects a malformed admin member element", () => {
expect(() => parseAdminBillingPayload({ ...validAdminPayload(), members: [null] }))
.toThrow("超管计费数据格式错误members[0] 必须是对象。");
});
it("accepts a fresh super administrator without organizationId", () => {
const payload = {
...validAdminPayload(),
members: [{ id: "root", displayName: "管理员", phone: "13800000000", role: "super_admin", status: "active" }]
};
expect(parseAdminBillingPayload(payload)).toBe(payload);
});
it("rejects a malformed admin ledger element", () => {
expect(() => parseAdminBillingPayload({ ...validAdminPayload(), ledger: [null] }))
.toThrow("超管计费数据格式错误ledger[0] 必须是对象。");
});
it("rejects malformed member objects used by the renderer", () => {
expect(() => parseMemberBillingPayload({ ...validMemberPayload(), organization: null }))
.toThrow("成员计费数据格式错误organization 必须是对象。");
expect(() => parseMemberBillingPayload({ ...validMemberPayload(), wallet: null }))
.toThrow("成员计费数据格式错误wallet 必须是对象。");
expect(() => parseMemberBillingPayload({ ...validMemberPayload(), summary: null }))
.toThrow("成员计费数据格式错误summary 必须是对象。");
expect(() => parseMemberBillingPayload({ ...validMemberPayload(), personal: null }))
.toThrow("成员计费数据格式错误personal 必须是对象。");
});
it("rejects a malformed member ledger element", () => {
expect(() => parseMemberBillingPayload({ ...validMemberPayload(), ledger: [null] }))
.toThrow("成员计费数据格式错误ledger[0] 必须是对象。");
});
it("accepts fully populated collection elements", () => {
const admin = {
...validAdminPayload(),
organizations: [{ id: "org-1", name: "测试组织", status: "active", wallet }],
members: [{ id: "member-1", displayName: "成员", phone: "13800000000", role: "user", organizationId: "org-1", status: "active" }],
ledger: [ledgerEntry],
priceRules: [{
id: "rule-1",
provider: "evolink",
capability: "image.generate",
unit: "image",
standardUnitPriceFen: 100,
markupMultiplier: 1.2,
enabled: true,
parameterDimensions: [{
key: "quality",
label: "质量",
baselineValue: "standard",
tiers: [{ value: "standard", label: "标准", standardFactor: 1, markupMultiplier: 1.2, enabled: true }]
}]
}]
};
const member = { ...validMemberPayload(), ledger: [ledgerEntry] };
expect(parseAdminBillingPayload(admin)).toBe(admin);
expect(parseMemberBillingPayload(member)).toBe(member);
});
it("rejects non-object responses", () => {
expect(() => parseAdminBillingPayload("unexpected"))
.toThrow("超管计费数据格式错误:响应必须是对象。");
expect(() => parseMemberBillingPayload(null))
.toThrow("成员计费数据格式错误:响应必须是对象。");
});
});