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"); });