Files
pump-game-ops/tools/gateway-api-v2-check.cjs
T
subochev a4ac702907 test(gateway): живая приёмка API v2 шлюза smart-updown — 26/26
Скрипт проверяет на живом стенде (BASE=https://smart-updown.binom.pw/api):
/state с canBet/canBetReason/betPercent/balance/openBet/lastBet + status,
POST /bet без суммы (30% от баланса, кламп), отказы 200 {accepted:false,
reasonCode, retryAfterSeconds}, авто-закрытие ставки по экспирации,
переход openBet -> lastBet. Запуск: BASE=... node tools/gateway-api-v2-check.cjs
2026-09-17 00:11:56 +03:00

180 lines
8.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* ЖИВАЯ приёмка API v2 шлюза smart-updown (ставка без суммы, 30% от баланса).
*
* BASE=https://smart-updown.binom.pw/api node gateway-api-v2-check.cjs
*
* Проверяет:
* 1. GET /state — все СТАРЫЕ поля на месте (обратная совместимость)
* 2. GET /state — новые поля: bettor, betPercent, canBet, canBetReasonCode,
* canBetReason, betAmountUnits, balance, openBet, lastBet
* 3. /state — openBet и lastBet взаимоисключающие; у ставки есть status
* 4. /state — можно ли ставить (canBet) и что баланс читается
* 5. POST /bet {side:"up"} — БЕЗ суммы: accepted=true, есть betId и txSig
* 6. POST /bet — retryAfterSeconds это ЧИСЛО (дельта), 0 < r <= expiry
* 7. GET /state сразу после — openBet не null, status="open"
* 8. POST /bet повторно — accepted=false, reasonCode=OPEN_BET, retryAfterSeconds > 0
* 9. POST /bet {side:"кривой"} — 400
* 10. ждать экспирацию ~20 с — /state: openBet=null, lastBet.status="closed"
*/
const BASE = process.env.BASE || "https://smart-updown.binom.pw/api";
const results = [];
function check(name, ok, detail) {
results.push({ name, ok, detail });
console.log(`${ok ? "OK " : "FAIL"} | ${name}${detail ? " — " + detail : ""}`);
}
const j = async (path, init) => {
const r = await fetch(BASE + path, init);
let body = null;
try { body = await r.json(); } catch { /* не JSON */ }
return { status: r.status, body };
};
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
// Поля, которые читает тестовый фронт и которые НЕЛЬЗЯ терять.
const LEGACY_STATE = [
"counter", "nextBetId", "minAmount", "maxAmount", "minAmountHuman",
"maxAmountHuman", "multiplierBps", "expirySeconds", "paused", "admin",
"relayer", "vaultBalance", "vaultBalanceHuman",
];
const NEW_STATE = [
"bettor", "betPercent", "betAmountUnits", "betAmountHuman",
"canBet", "canBetReasonCode", "canBetReason", "balance",
"openBet", "lastBet",
];
(async () => {
console.log(`BASE=${BASE}\n`);
// --- 1-2. /state: старые и новые поля ---
const st = await j("/state");
const b = st.body || {};
const missingLegacy = LEGACY_STATE.filter((k) => !(k in b));
check("GET /state → 200", st.status === 200, `status=${st.status}`);
check("нет потери старых полей", missingLegacy.length === 0,
missingLegacy.length ? "нет: " + missingLegacy.join(",") : `${LEGACY_STATE.length} полей на месте`);
const missingNew = NEW_STATE.filter((k) => !(k in b));
check("новые поля присутствуют", missingNew.length === 0,
missingNew.length ? "нет: " + missingNew.join(",") : `${NEW_STATE.length} полей на месте`);
console.log(` bettor=${b.bettor} betPercent=${b.betPercent}`);
console.log(` canBet=${b.canBet} code=${b.canBetReasonCode} reason=${b.canBetReason}`);
console.log(` balance=${JSON.stringify(b.balance)}`);
console.log(` betAmountUnits=${b.betAmountUnits} (${b.betAmountHuman})`);
// --- 3. взаимоисключение openBet / lastBet + поле status ---
const openBet = b.openBet || null;
const lastBet = b.lastBet || null;
check("openBet и lastBet не оба заполнены", !(openBet && lastBet),
`openBet=${openBet ? openBet.id : "null"} lastBet=${lastBet ? lastBet.id : "null"}`);
const shown = openBet || lastBet;
if (shown) {
check("у ставки есть поле status (open|closed)",
shown.status === "open" || shown.status === "closed", `status=${shown.status}`);
check("у ставки есть side/amountUnits/entryPrice",
!!shown.side && !!shown.amountUnits && shown.entryPrice !== undefined,
`side=${shown.side} amount=${shown.amountUnits} entry=${shown.entryPrice}`);
} else {
console.log(" (ставок ещё нет — пропускаем проверки формы)");
}
// --- 4. можно ли ставить ---
if (b.canBet !== true) {
check("canBet=true (готовы ставить)", false, `code=${b.canBetReasonCode} reason=${b.canBetReason}`);
console.log("\nдальше невозможно: шлюз отказывает. Причина выше.");
process.exit(1);
}
check("canBet=true (готовы ставить)", true, `размер=${b.betAmountUnits}`);
// --- 5. ставка БЕЗ суммы ---
const bet1 = await j("/bet", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ side: "up" }),
});
const r1 = bet1.body || {};
console.log(` ответ /bet: ${JSON.stringify(r1).slice(0, 300)}`);
check("POST /bet {side} → 200", bet1.status === 200, `status=${bet1.status}`);
check("ставка принята (accepted=true)", r1.accepted === true,
`accepted=${r1.accepted} code=${r1.reasonCode} reason=${r1.reason}`);
const betId = r1.betId || r1.id;
check("есть betId (п.6)", !!betId, `betId=${betId}`);
check("есть txSig (п.6)", typeof r1.txSig === "string" && r1.txSig.length > 20,
`txSig=${r1.txSig ? r1.txSig.slice(0, 24) + "…" : r1.txSig}`);
// --- 6. retryAfterSeconds — ДЕЛЬТА ---
const retry = r1.retryAfterSeconds;
const expiry = Number(b.expirySeconds || 15);
check("retryAfterSeconds — число-дельта (п.5)",
typeof retry === "number" && retry > 0 && retry <= expiry,
`retryAfterSeconds=${retry} (expiry=${expiry})`);
// --- 7. /state сразу после: открытая ставка ---
await sleep(1500);
const st2 = (await j("/state")).body || {};
const ob = st2.openBet;
check("openBet заполнен после ставки (п.7)", !!ob, `openBet=${ob ? ob.id : "null"}`);
if (ob) {
check("openBet.status = \"open\"", ob.status === "open", `status=${ob.status}`);
check("openBet.id совпадает с ответом /bet", String(ob.id) === String(betId),
`openBet.id=${ob.id} betId=${betId}`);
check("openBet.retryAfterSeconds > 0",
typeof ob.retryAfterSeconds === "number" && ob.retryAfterSeconds >= 0,
`retryAfterSeconds=${ob.retryAfterSeconds}`);
}
// --- 8. вторая ставка при открытой: отказ OPEN_BET ---
const bet2 = await j("/bet", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ side: "down" }),
});
const r2 = bet2.body || {};
check("вторая ставка отклонена (accepted=false)", r2.accepted === false,
`accepted=${r2.accepted}`);
check("reasonCode=OPEN_BET", r2.reasonCode === "OPEN_BET", `code=${r2.reasonCode}`);
check("есть reason по-русски (п.2)",
typeof r2.reason === "string" && r2.reason.length > 5, `reason=${r2.reason}`);
check("retryAfterSeconds > 0 при отказе по открытой (п.5)",
typeof r2.retryAfterSeconds === "number" && r2.retryAfterSeconds > 0,
`retryAfterSeconds=${r2.retryAfterSeconds}`);
// --- 9. кривой side ---
const bet3 = await j("/bet", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ side: "вбок" }),
});
check("кривой side → 400", bet3.status === 400, `status=${bet3.status}`);
// --- 10. дождаться авто-закрытия ---
console.log("\nждём авто-закрытие (~22 с)…");
await sleep(22000);
const st3 = (await j("/state")).body || {};
check("после экспирации openBet = null", st3.openBet === null,
`openBet=${st3.openBet ? st3.openBet.id : "null"}`);
const lb = st3.lastBet;
check("lastBet заполнен после закрытия (п.7)", !!lb, `lastBet=${lb ? lb.id : "null"}`);
if (lb) {
check("lastBet.status = \"closed\"", lb.status === "closed", `status=${lb.status}`);
check("lastBet.result заполнен", !!lb.result,
`result=${lb.result} statusCode=${lb.statusCode}`);
}
check("canBet снова true после расчёта", st3.canBet === true,
`canBet=${st3.canBet} code=${st3.canBetReasonCode}`);
// --- итог ---
const failed = results.filter((r) => !r.ok);
console.log(`\n=== ИТОГ: ${results.length - failed.length}/${results.length} пройдено ===`);
if (failed.length) {
console.log("ПРОВАЛЫ:");
for (const f of failed) console.log(` - ${f.name} :: ${f.detail || ""}`);
process.exit(1);
}
console.log("все проверки пройдены");
})().catch((e) => {
console.error("исключение:", e);
process.exit(2);
});