Files
NianAIGC/tests/payment-api.test.ts
T
2026-10-12 13:57:23 +08:00

58 lines
3.1 KiB
TypeScript

import { afterEach, describe, expect, it, vi } from "vitest";
import { createPaymentOrder, createPaymentRefund, formatYuan, getPaymentOrder, getPaymentRefund, listAdminPaymentOrders, listPaymentRefunds, parseYuanToFen } from "@/lib/client/payment-api";
afterEach(() => vi.unstubAllGlobals());
describe("recharge amounts", () => {
it("converts decimal yuan to exact integer fen within the server range", () => {
expect(parseYuanToFen("1", 100, 1_000_000)).toBe(100);
expect(parseYuanToFen("1.01", 100, 1_000_000)).toBe(101);
expect(parseYuanToFen(" 10000.00 ", 100, 1_000_000)).toBe(1_000_000);
expect(formatYuan(101)).toBe("1.01");
});
it.each(["0.99", "1.001", "1e2", "-1", "+1", "1,000", "01", "10000.01", "0", "1."])("rejects %s", (value) => {
expect(parseYuanToFen(value, 100, 1_000_000)).toBeNull();
});
});
describe("admin payment requests", () => {
it("lists across organizations or filters by organization and page", async () => {
const fetchMock = vi.fn().mockResolvedValue({ ok: true, json: async () => ({ orders: [], hasMore: false, enabled: true }) });
vi.stubGlobal("fetch", fetchMock);
await listAdminPaymentOrders("org/a", 50);
expect(fetchMock.mock.calls[0][0]).toBe("/api/payments/admin/orders?limit=50&offset=50&organizationId=org%2Fa");
await listAdminPaymentOrders("", 0);
expect(fetchMock.mock.calls[1][0]).toBe("/api/payments/admin/orders?limit=50&offset=0");
});
it("uses admin endpoints and preserves the refund attempt key", async () => {
const fetchMock = vi.fn().mockResolvedValue({ ok: true, json: async () => ({ refund: { id: "r1", status: "pending" }, refunds: [] }) });
vi.stubGlobal("fetch", fetchMock);
await createPaymentRefund("a/b", 250, "客户申请", "same-attempt");
expect(fetchMock.mock.calls[0][0]).toBe("/api/payments/admin/orders/a%2Fb/refunds");
expect(JSON.parse(fetchMock.mock.calls[0][1].body)).toEqual({ amountFen: 250, reason: "客户申请", requestKey: "same-attempt" });
await listPaymentRefunds("a/b");
expect(fetchMock.mock.calls[1][0]).toBe("/api/payments/admin/orders/a%2Fb/refunds");
await getPaymentRefund("r/1");
expect(fetchMock.mock.calls[2][0]).toBe("/api/payments/admin/refunds/r%2F1");
});
});
describe("payment requests", () => {
it("sends integer fen and a stable idempotency key", async () => {
const fetchMock = vi.fn().mockResolvedValue({ ok: true, json: async () => ({ order: { id: "pay_1", status: "pending" } }) });
vi.stubGlobal("fetch", fetchMock);
await createPaymentOrder(10000, "same-attempt");
const [, init] = fetchMock.mock.calls[0];
expect(JSON.parse(init.body)).toEqual({ amountFen: 10000, requestKey: "same-attempt" });
});
it("escapes order IDs and reports server errors", async () => {
const fetchMock = vi.fn().mockResolvedValue({ ok: false, status: 404, json: async () => ({ error: "订单不存在" }) });
vi.stubGlobal("fetch", fetchMock);
await expect(getPaymentOrder("a/b")).rejects.toThrow("订单不存在");
expect(fetchMock.mock.calls[0][0]).toBe("/api/payments/orders/a%2Fb");
});
});