2026-07-02 10:54:00 +08:00

154 lines
6.4 KiB
JavaScript

import { afterEach, expect, test, vi } from "vitest";
import { setAccessToken } from "@/shared/api/request";
import {
approveFinanceApplication,
approveFinanceWithdrawalApplication,
createFinanceApplication,
fetchFinanceApplicationOptions,
filterOperationsByPermissions,
listFinanceApplications,
listFinanceRechargeDetails,
listMyFinanceApplications,
listFinanceWithdrawalApplications,
rejectFinanceApplication,
rejectFinanceWithdrawalApplication
} from "./api.js";
import { FINANCE_OPERATIONS } from "./constants.js";
afterEach(() => {
setAccessToken("");
vi.unstubAllGlobals();
});
test("finance application APIs use generated endpoint paths", async () => {
vi.stubGlobal(
"fetch",
vi.fn(async (url) => {
if (String(url).includes("/admin/apps")) {
return new Response(JSON.stringify({ code: 0, data: { items: [{ appCode: "lalu", appName: "lalu" }], total: 1 } }));
}
if (String(url).includes("/applications?")) {
return new Response(JSON.stringify({ code: 0, data: { items: [], page: 1, pageSize: 50, total: 0 } }));
}
if (String(url).includes("/applications/mine?")) {
return new Response(JSON.stringify({ code: 0, data: { items: [], page: 1, pageSize: 50, total: 0 } }));
}
if (String(url).includes("/withdrawal-applications?")) {
return new Response(JSON.stringify({ code: 0, data: { items: [], page: 1, pageSize: 50, total: 0 } }));
}
return new Response(JSON.stringify({ code: 0, data: { id: 12 } }));
})
);
const options = await fetchFinanceApplicationOptions();
await listFinanceApplications({ page: 1, page_size: 50, status: "pending" });
await listMyFinanceApplications({ page: 1, page_size: 50 });
await listFinanceWithdrawalApplications({ page: 1, page_size: 50 });
await createFinanceApplication({
appCode: "lalu",
coinAmount: 100,
operation: "coin_seller_coin_credit",
rechargeAmount: 10,
targetUserId: "10001"
});
await approveFinanceApplication(12, { auditRemark: "ok" });
await rejectFinanceApplication(13, { auditRemark: "bad" });
await approveFinanceWithdrawalApplication(14, { auditImageUrl: "https://cdn.example.com/pay.png", auditRemark: "ok" });
await rejectFinanceWithdrawalApplication(15, { auditRemark: "bad" });
const calls = vi.mocked(fetch).mock.calls;
expect(options.apps).toEqual([{ appCode: "lalu", appName: "lalu" }]);
expect(options.operations.map((item) => item.label)).toEqual([
"增加用户金币",
"减少用户金币",
"用户钱包扣减",
"用户钱包增加",
"增加币商金币",
"减少币商金币"
]);
expect(String(calls[0][0])).toContain("/api/v1/admin/apps");
expect(calls[0][1]?.method).toBe("GET");
expect(String(calls[1][0])).toContain("/api/v1/admin/finance/applications?");
expect(String(calls[1][0])).toContain("status=pending");
expect(calls[1][1]?.method).toBe("GET");
expect(String(calls[2][0])).toContain("/api/v1/admin/finance/applications/mine?");
expect(calls[2][1]?.method).toBe("GET");
expect(String(calls[3][0])).toContain("/api/v1/admin/finance/withdrawal-applications?");
expect(calls[3][1]?.method).toBe("GET");
expect(String(calls[4][0])).toContain("/api/v1/admin/finance/applications");
expect(calls[4][1]?.method).toBe("POST");
expect(JSON.parse(String(calls[4][1]?.body))).toMatchObject({ appCode: "lalu", operation: "coin_seller_coin_credit" });
expect(String(calls[5][0])).toContain("/api/v1/admin/finance/applications/12/approve");
expect(calls[5][1]?.method).toBe("POST");
expect(JSON.parse(String(calls[5][1]?.body))).toEqual({ auditRemark: "ok" });
expect(String(calls[6][0])).toContain("/api/v1/admin/finance/applications/13/reject");
expect(calls[6][1]?.method).toBe("POST");
expect(JSON.parse(String(calls[6][1]?.body))).toEqual({ auditRemark: "bad" });
expect(String(calls[7][0])).toContain("/api/v1/admin/finance/withdrawal-applications/14/approve");
expect(calls[7][1]?.method).toBe("POST");
expect(JSON.parse(String(calls[7][1]?.body))).toEqual({ auditImageUrl: "https://cdn.example.com/pay.png", auditRemark: "ok" });
expect(String(calls[8][0])).toContain("/api/v1/admin/finance/withdrawal-applications/15/reject");
expect(calls[8][1]?.method).toBe("POST");
expect(JSON.parse(String(calls[8][1]?.body))).toEqual({ auditRemark: "bad" });
});
test("filters operation options by backend-configured permissions", () => {
const allowed = filterOperationsByPermissions(FINANCE_OPERATIONS, [
"finance-operation:user-coin-credit",
"finance-operation:coin-seller-coin-debit"
]);
expect(allowed.map((item) => item.value)).toEqual(["user_coin_credit", "coin_seller_coin_debit"]);
});
test("finance recharge details query every app with app header and merge by recharge time", async () => {
vi.stubGlobal(
"fetch",
vi.fn(async (url, init) => {
const appCode = init?.headers?.["X-App-Code"];
return new Response(
JSON.stringify({
code: 0,
data: {
items: [
{
appCode,
coinAmount: appCode === "aslan" ? 2000 : 1000,
createdAtMs: appCode === "aslan" ? 1760003600000 : 1760000000000,
exchangeCoinAmount: appCode === "aslan" ? 2000 : 1000,
exchangeUsdMinorAmount: appCode === "aslan" ? 200 : 100,
rechargeType: "google_play_recharge",
status: "succeeded",
transactionId: `${appCode}-tx`,
usdMinorAmount: appCode === "aslan" ? 200 : 100,
userId: "10001"
}
],
page: 1,
pageSize: 2,
total: 1
}
})
);
})
);
const page = await listFinanceRechargeDetails(
{ page: 1, page_size: 2, start_at_ms: 1760000000000, status: "succeeded" },
[
{ appCode: "lalu", appName: "lalu" },
{ appCode: "aslan", appName: "aslan" }
]
);
const calls = vi.mocked(fetch).mock.calls;
expect(page.total).toBe(2);
expect(page.items.map((item) => item.transactionId)).toEqual(["aslan-tx", "lalu-tx"]);
expect(calls).toHaveLength(2);
expect(calls.map(([, init]) => init?.headers?.["X-App-Code"])).toEqual(["lalu", "aslan"]);
expect(String(calls[0][0])).toContain("/api/v1/admin/payment/recharge-bills?");
expect(String(calls[0][0])).toContain("page_size=2");
expect(String(calls[0][0])).toContain("status=succeeded");
expect(String(calls[0][0])).toContain("start_at_ms=1760000000000");
});