2026-07-14 14:07:17 +08:00

227 lines
8.7 KiB
JavaScript

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