import { afterEach, expect, test, vi } from "vitest"; import { setAccessToken } from "@/shared/api/request"; import { approveFinanceWithdrawalApplication, createFinanceCoinSellerRechargeOrder, fetchFinanceOptions, fetchFinanceSession, getFinanceCoinSellerRechargeExchangeRate, grantFinanceCoinSellerRechargeOrder, listFinanceCoinSellerRechargeOrders, listFinanceRechargeDetails, listFinanceWithdrawalApplications, rejectFinanceWithdrawalApplication, quoteFinanceCoinSellerRechargeOrder, replaceFinanceCoinSellerRechargeExchangeRate, verifyFinanceCoinSellerRechargeReceipt, verifyFinanceCoinSellerRechargeOrder } from "./api.js"; afterEach(() => { setAccessToken(""); vi.unstubAllGlobals(); }); test("finance session no longer treats removed application permissions as withdrawal access", async () => { vi.stubGlobal( "fetch", vi.fn(async () => new Response( JSON.stringify({ code: 0, data: { permissions: ["finance-application:audit"], user: { account: "legacy-finance" } } }) ) ) ); const session = await fetchFinanceSession(); expect(session.canAccessFinance).toBe(false); expect(session.canAuditWithdrawalApplications).toBe(false); expect(session.canViewWithdrawalApplications).toBe(false); expect(session).not.toHaveProperty("canAuditApplication"); expect(session).not.toHaveProperty("canCreateApplication"); }); test("finance options and withdrawal 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", logoUrl: "https://media.example.com/lalu.png" }], total: 1 } })); } 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 fetchFinanceOptions(); await listFinanceWithdrawalApplications({ page: 1, page_size: 50 }); 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", logoUrl: "https://media.example.com/lalu.png" } ]); 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/withdrawal-applications?"); expect(calls[1][1]?.method).toBe("GET"); expect(String(calls[2][0])).toContain("/api/v1/admin/finance/withdrawal-applications/14/approve"); expect(calls[2][1]?.method).toBe("POST"); expect(JSON.parse(String(calls[2][1]?.body))).toEqual({ auditImageUrl: "https://cdn.example.com/pay.png", auditRemark: "ok" }); expect(String(calls[3][0])).toContain("/api/v1/admin/finance/withdrawal-applications/15/reject"); expect(calls[3][1]?.method).toBe("POST"); expect(JSON.parse(String(calls[3][1]?.body))).toEqual({ auditRemark: "bad" }); }); test("finance coin seller recharge order APIs use generated endpoint paths", async () => { vi.stubGlobal( "fetch", vi.fn(async (url) => { if (String(url).includes("/coin-seller-recharges?")) { return new Response(JSON.stringify({ code: 0, data: { items: [], page: 1, pageSize: 50, total: 0 } })); } return new Response(JSON.stringify({ code: 0, data: { id: "order-1", targetUserId: "10001" } })); }) ); await listFinanceCoinSellerRechargeOrders({ page: 1, page_size: 50, provider_code: "mifapay" }); await createFinanceCoinSellerRechargeOrder({ appCode: "lalu", externalOrderNo: "MIFA-001", providerAmountMinor: 125050, providerCode: "mifapay", providerCurrencyCode: "PHP", targetUserId: "10001", usdAmount: 10 }); await verifyFinanceCoinSellerRechargeReceipt({ appCode: "lalu", externalOrderNo: "MIFA-001", providerAmountMinor: 125050, providerCode: "mifapay", providerCurrencyCode: "PHP", usdAmount: 10 }); await verifyFinanceCoinSellerRechargeOrder("order-1"); await grantFinanceCoinSellerRechargeOrder("order-1"); const calls = vi.mocked(fetch).mock.calls; expect(String(calls[0][0])).toContain("/api/v1/admin/finance/orders/coin-seller-recharges?"); expect(String(calls[0][0])).toContain("provider_code=mifapay"); expect(calls[0][1]?.method).toBe("GET"); expect(String(calls[1][0])).toContain("/api/v1/admin/finance/orders/coin-seller-recharges"); expect(calls[1][1]?.method).toBe("POST"); expect(JSON.parse(String(calls[1][1]?.body))).toEqual({ appCode: "lalu", externalOrderNo: "MIFA-001", providerAmountMinor: 125050, providerCode: "mifapay", providerCurrencyCode: "PHP", targetUserId: "10001", usdAmount: 10 }); expect(String(calls[2][0])).toContain("/api/v1/admin/finance/orders/coin-seller-recharges/receipt-verifications"); expect(calls[2][1]?.method).toBe("POST"); expect(JSON.parse(String(calls[2][1]?.body))).toEqual({ appCode: "lalu", externalOrderNo: "MIFA-001", providerAmountMinor: 125050, providerCode: "mifapay", providerCurrencyCode: "PHP", usdAmount: 10 }); expect(String(calls[3][0])).toContain("/api/v1/admin/finance/orders/coin-seller-recharges/order-1/verify"); expect(calls[3][1]?.method).toBe("POST"); expect(String(calls[4][0])).toContain("/api/v1/admin/finance/orders/coin-seller-recharges/order-1/grant"); expect(calls[4][1]?.method).toBe("POST"); }); test("finance coin seller quote and exchange rate APIs use generated endpoint paths", async () => { vi.stubGlobal( "fetch", vi.fn(async () => new Response(JSON.stringify({ code: 0, data: { appCode: "lalu", tiers: [], whitelist: [] } }))) ); await quoteFinanceCoinSellerRechargeOrder({ appCode: "lalu", targetUserId: "123456", usdAmount: 150 }); await getFinanceCoinSellerRechargeExchangeRate("lalu"); await replaceFinanceCoinSellerRechargeExchangeRate("lalu", { tiers: [{ minUsdAmount: 100, maxUsdAmount: 200, coinsPerUsd: 80000 }], whitelist: [{ userId: "123456", coinsPerUsd: 100000 }] }); const calls = vi.mocked(fetch).mock.calls; expect(String(calls[0][0])).toContain("/api/v1/admin/finance/orders/coin-seller-recharges/quotes"); expect(calls[0][1]?.method).toBe("POST"); expect(JSON.parse(String(calls[0][1]?.body))).toEqual({ appCode: "lalu", targetUserId: "123456", usdAmount: 150 }); expect(String(calls[1][0])).toContain("/api/v1/admin/finance/coin-seller-recharge-exchange-rates/lalu"); expect(calls[1][1]?.method).toBe("GET"); expect(String(calls[2][0])).toContain("/api/v1/admin/finance/coin-seller-recharge-exchange-rates/lalu"); expect(calls[2][1]?.method).toBe("PUT"); }); 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"); });