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