147 lines
5.9 KiB
JavaScript
147 lines
5.9 KiB
JavaScript
import { afterEach, expect, test, vi } from "vitest";
|
|
import { buildOpsUrl, fetchLuckyGiftDraws, fetchOpsDashboard, OPS_API_PREFIX, saveLuckyGiftConfig } from "./api.js";
|
|
|
|
afterEach(() => {
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
function jsonResponse(data) {
|
|
return new Response(JSON.stringify({ code: 0, data }), {
|
|
headers: { "Content-Type": "application/json" },
|
|
status: 200
|
|
});
|
|
}
|
|
|
|
test("builds ops api urls under the dedicated prefix", () => {
|
|
const url = buildOpsUrl("/apps", { app_code: "lalu", empty: "", status: "active" });
|
|
|
|
expect(OPS_API_PREFIX).toBe("/api/v1/admin/ops-center");
|
|
expect(url).toBe("http://localhost:3000/api/v1/admin/ops-center/apps?app_code=lalu&status=active");
|
|
});
|
|
|
|
test("loads every pool config per app through the plural configs endpoint", async () => {
|
|
const calls = [];
|
|
vi.spyOn(window, "fetch").mockImplementation(async (url) => {
|
|
const text = String(url);
|
|
calls.push(text);
|
|
if (text.endsWith("/apps")) {
|
|
return jsonResponse({ items: [{ app_code: "lalu", status: "active" }] });
|
|
}
|
|
if (text.includes("/lucky-gifts/configs?")) {
|
|
// lalu 在 default 之外还有 lucky/super_lucky 两个已发布奖池,复数接口必须全部带回。
|
|
return jsonResponse([
|
|
{ app_code: "lalu", enabled: true, pool_id: "lucky", rule_version: 3, target_rtp_percent: 95 },
|
|
{ app_code: "lalu", enabled: false, pool_id: "super_lucky", rule_version: 2, target_rtp_percent: 92 }
|
|
]);
|
|
}
|
|
if (text.includes("/lucky-gifts/pools?")) {
|
|
return jsonResponse({
|
|
items: [
|
|
{ app_code: "lalu", available_balance: 37950017, balance: 38559017, materialized: true, pool_id: "lucky", reserve_floor: 609000 },
|
|
{ app_code: "lalu", available_balance: 361500, balance: 382500, materialized: false, pool_id: "default", reserve_floor: 21000 }
|
|
]
|
|
});
|
|
}
|
|
if (text.includes("/lucky-gifts/summary?")) {
|
|
return jsonResponse({ granted_draws: 9, pending_draws: 1, total_draws: 10, total_reward_coins: 900, total_spent_coins: 1000 });
|
|
}
|
|
return jsonResponse({ items: [] });
|
|
});
|
|
|
|
const data = await fetchOpsDashboard();
|
|
|
|
expect(calls).toEqual([
|
|
"http://localhost:3000/api/v1/admin/ops-center/apps",
|
|
"http://localhost:3000/api/v1/admin/ops-center/lucky-gifts/configs?app_code=lalu",
|
|
"http://localhost:3000/api/v1/admin/ops-center/lucky-gifts/pools?app_code=lalu",
|
|
"http://localhost:3000/api/v1/admin/ops-center/lucky-gifts/summary?app_code=lalu"
|
|
]);
|
|
expect(data.luckyGifts).toEqual([
|
|
expect.objectContaining({ app_code: "lalu", is_default: false, pool_id: "lucky", rule_version: 3 }),
|
|
expect.objectContaining({ app_code: "lalu", is_default: false, pool_id: "super_lucky", rule_version: 2 })
|
|
]);
|
|
expect(data.summary).toMatchObject({
|
|
activeApps: 1,
|
|
configuredPools: 2,
|
|
enabledPools: 1,
|
|
poolBalanceTotal: 38941517,
|
|
totalDraws: 10
|
|
});
|
|
});
|
|
|
|
test("falls back to the server default draft when an app has no published config", async () => {
|
|
const calls = [];
|
|
vi.spyOn(window, "fetch").mockImplementation(async (url) => {
|
|
const text = String(url);
|
|
calls.push(text);
|
|
if (text.endsWith("/apps")) {
|
|
return jsonResponse({ items: [{ app_code: "yumi", status: "active" }] });
|
|
}
|
|
if (text.includes("/lucky-gifts/configs?")) {
|
|
return jsonResponse([]);
|
|
}
|
|
if (text.includes("/lucky-gifts/config?")) {
|
|
return jsonResponse({ app_code: "yumi", enabled: false, pool_id: "default", rule_version: 0, target_rtp_percent: 95 });
|
|
}
|
|
return jsonResponse({ items: [] });
|
|
});
|
|
|
|
const data = await fetchOpsDashboard();
|
|
|
|
expect(calls).toContain("http://localhost:3000/api/v1/admin/ops-center/lucky-gifts/config?app_code=yumi");
|
|
expect(data.luckyGifts).toEqual([
|
|
expect.objectContaining({ app_code: "yumi", is_default: true, pool_id: "default", rule_version: 0 })
|
|
]);
|
|
});
|
|
|
|
test("fetches draws with numeric ids as user_id and other ids as external_user_id", async () => {
|
|
const calls = [];
|
|
vi.spyOn(window, "fetch").mockImplementation(async (url) => {
|
|
calls.push(String(url));
|
|
return jsonResponse({ items: [{ draw_id: "draw-1" }], page: 1, pageSize: 20, total: 1 });
|
|
});
|
|
|
|
const numericResult = await fetchLuckyGiftDraws({ appCode: "lalu", poolId: "lucky", userId: "10001" });
|
|
await fetchLuckyGiftDraws({ appCode: "lalu", userId: "ext-abc" });
|
|
|
|
const numericParams = new URL(calls[0]).searchParams;
|
|
expect(numericParams.get("app_code")).toBe("lalu");
|
|
expect(numericParams.get("pool_id")).toBe("lucky");
|
|
expect(numericParams.get("user_id")).toBe("10001");
|
|
expect(numericParams.get("external_user_id")).toBeNull();
|
|
const externalParams = new URL(calls[1]).searchParams;
|
|
expect(externalParams.get("external_user_id")).toBe("ext-abc");
|
|
expect(externalParams.get("user_id")).toBeNull();
|
|
expect(numericResult).toMatchObject({ items: [{ draw_id: "draw-1" }], page: 1, total: 1 });
|
|
});
|
|
|
|
test("saves lucky gift config through admin ops route", async () => {
|
|
const calls = [];
|
|
vi.spyOn(window, "fetch").mockImplementation(async (url, init) => {
|
|
calls.push({ body: init.body, method: init.method, url: String(url) });
|
|
return jsonResponse({ app_code: "aslan", pool_id: "default" });
|
|
});
|
|
|
|
await saveLuckyGiftConfig({
|
|
app_code: "aslan",
|
|
pool_id: "default",
|
|
enabled: true,
|
|
target_rtp_percent: 95,
|
|
pool_rate_percent: 96,
|
|
settlement_window_wager: 1_000_000,
|
|
control_band_percent: 1,
|
|
gift_price_reference: 100,
|
|
stages: [{ stage: "novice", tiers: [{ multiplier: 1, probabilityPercent: 100, enabled: true }] }]
|
|
});
|
|
|
|
expect(calls[0].method).toBe("PUT");
|
|
expect(calls[0].url).toBe("http://localhost:3000/api/v1/admin/ops-center/lucky-gifts/config?app_code=aslan&pool_id=default");
|
|
expect(JSON.parse(calls[0].body)).toMatchObject({
|
|
app_code: "aslan",
|
|
enabled: true,
|
|
pool_id: "default",
|
|
stages: [{ stage: "novice", tiers: [{ probability_percent: 100 }] }],
|
|
target_rtp_percent: 95
|
|
});
|
|
});
|