docs(security): SEC-2/SEC-3/SEC-8 закрыты, SEC-4 усилен (updown-relayer:6)
- SEC-3 закрыт: ручка POST /close удалена, ставки закрывает сам шлюз (404) - SEC-8 закрыт: авто-закрытие + одна открытая ставка на адрес (409) - SEC-2 закрыт по факту: планировщик settle.ts работает, проверено живьём - SEC-4 усилен: пара UP+DOWN больше не набирается (вторя ставка -> 409) - SEC-5 частично: счётчик больше не накрутить массово, но /bet без авторизации - tools/autoclose-check.cjs: сквозной живой тест (9/9 пройдено)
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
/**
|
||||
* ЖИВАЯ проверка итерации: авто-закрытие + одна ставка на адрес + WS-события.
|
||||
* Запуск на хосте ассистента (Node 22, глобальный fetch + WebSocket).
|
||||
*
|
||||
* node autoclose-check.cjs
|
||||
*
|
||||
* Проверяет по шагам:
|
||||
* 1. POST /wallet — новый кошелёк
|
||||
* 2. GET /faucet/{addr} — пополнить
|
||||
* 3. WS /api/ws — подключиться и слушать события
|
||||
* 4. POST /bet — 1-я ставка: ждём 200 + событие bet_open
|
||||
* 5. POST /bet — 2-я ставка: ждём 409 (одна открытая на адрес)
|
||||
* 6. ждать экспирацию ~15 с — ждём событие bet_closed (авто-закрытие)
|
||||
* 7. POST /close — должен быть 404 (ручку убрали)
|
||||
*/
|
||||
const BASE = process.env.BASE || "https://testfront.binom.pw";
|
||||
const WS_URL = process.env.WS_URL || "wss://testfront.binom.pw/api/ws";
|
||||
|
||||
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));
|
||||
|
||||
(async () => {
|
||||
// --- 3. WS заранее: события редкие, слушаем всё время ---
|
||||
const events = [];
|
||||
let ws;
|
||||
try {
|
||||
ws = new WebSocket(WS_URL);
|
||||
ws.onmessage = (m) => {
|
||||
try {
|
||||
const d = JSON.parse(m.data);
|
||||
if (d.type !== "price") { events.push(d); console.log(" WS<-", JSON.stringify(d)); }
|
||||
} catch { /* ignore */ }
|
||||
};
|
||||
await new Promise((res, rej) => {
|
||||
ws.onopen = res;
|
||||
ws.onerror = (e) => rej(new Error("ws error " + (e.message || "")));
|
||||
setTimeout(() => rej(new Error("ws timeout")), 10000);
|
||||
});
|
||||
check("WS подключён", true, WS_URL);
|
||||
} catch (e) {
|
||||
check("WS подключён", false, e.message);
|
||||
}
|
||||
|
||||
// --- 1. кошелёк ---
|
||||
const w = await j("/wallet", { method: "POST" });
|
||||
const addr = w.body && (w.body.address || w.body.pubkey || w.body.bettor);
|
||||
check("POST /wallet → 200 + адрес", w.status === 200 && !!addr, `status=${w.status} addr=${addr}`);
|
||||
if (!addr) { console.log("\nдальше невозможно: нет адреса"); process.exit(1); }
|
||||
|
||||
// --- 2. faucet ---
|
||||
let f = await j(`/faucet/${addr}`);
|
||||
if (f.status !== 200) { f = await j(`/faucet/${addr}`, { method: "POST" }); }
|
||||
check("GET /faucet → пополнен", f.status === 200, `status=${f.status} ${JSON.stringify(f.body).slice(0,120)}`);
|
||||
await sleep(2500);
|
||||
|
||||
const bal = await j(`/balance/${addr}`);
|
||||
check("GET /balance → 200", bal.status === 200, JSON.stringify(bal.body).slice(0, 160));
|
||||
|
||||
// --- 4. первая ставка ---
|
||||
const bet1 = await j("/bet", {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json" },
|
||||
body: JSON.stringify({ address: addr, side: "UP", amountUnits: 100000000 }),
|
||||
});
|
||||
check("POST /bet (1-я) → 200", bet1.status === 200, `status=${bet1.status} ${JSON.stringify(bet1.body).slice(0,180)}`);
|
||||
const betId = bet1.body && (bet1.body.id ?? bet1.body.betId ?? bet1.body.bet_id);
|
||||
console.log(` id ставки = ${betId}`);
|
||||
|
||||
// --- 5. вторая ставка — должна быть отклонена ---
|
||||
const bet2 = await j("/bet", {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json" },
|
||||
body: JSON.stringify({ address: addr, side: "DOWN", amountUnits: 100000000 }),
|
||||
});
|
||||
const blocked = bet2.status === 409 || bet2.status === 400;
|
||||
check("POST /bet (2-я, пока 1-я открыта) → отклонена", blocked,
|
||||
`status=${bet2.status} ${JSON.stringify(bet2.body).slice(0,180)}`);
|
||||
|
||||
// --- 6. авто-закрытие: ждём событие ---
|
||||
const t0 = Date.now();
|
||||
let closed = null;
|
||||
while (Date.now() - t0 < 45000) {
|
||||
closed = events.find((e) => e.type === "bet_closed" && String(e.id) === String(betId));
|
||||
if (closed) break;
|
||||
await sleep(1000);
|
||||
}
|
||||
const secs = ((Date.now() - t0) / 1000).toFixed(0);
|
||||
check("WS: событие bet_closed (авто-закрытие)", !!closed, closed ? `через ${secs} с: ${JSON.stringify(closed)}` : `не пришло за 45 с`);
|
||||
|
||||
const opened = events.find((e) => e.type === "bet_open" && String(e.id) === String(betId));
|
||||
check("WS: событие bet_open", !!opened, opened ? JSON.stringify(opened) : "не пришло");
|
||||
|
||||
// --- 7. ручка /close убрана ---
|
||||
const cl = await j("/close", {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json" },
|
||||
body: JSON.stringify({ id: betId }),
|
||||
});
|
||||
check("POST /close → 404 (ручку убрали)", cl.status === 404, `status=${cl.status}`);
|
||||
|
||||
// --- итог ---
|
||||
const bad = results.filter((r) => !r.ok);
|
||||
console.log(`\n=== ИТОГ: ${results.length - bad.length}/${results.length} пройдено ===`);
|
||||
if (bad.length) console.log("провалы: " + bad.map((b) => b.name).join("; "));
|
||||
if (ws) try { ws.close(); } catch { /* ignore */ }
|
||||
process.exit(bad.length ? 1 : 0);
|
||||
})().catch((e) => { console.error("ERR", e); process.exit(2); });
|
||||
Reference in New Issue
Block a user