148 lines
5.5 KiB
TypeScript
148 lines
5.5 KiB
TypeScript
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("成员计费数据格式错误:响应必须是对象。");
|
||
});
|
||
});
|