Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 41 additions & 1 deletion api/__tests__/handlers.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import type { VercelRequest, VercelResponse } from "@vercel/node";

vi.mock("../_lib/middleware.js", async () => {
const actual = await vi.importActual<typeof import("../_lib/middleware.js")>(
"../_lib/middleware.js"
);
return {
...actual,
checkRateLimit: vi.fn(
async (...args: Parameters<typeof actual.checkRateLimit>) =>
actual.checkRateLimit(...args)
),
};
});

// Stub the workspace builders/readers — these tests exercise the HTTP handler
// contract (method guards, field validation, status codes, payload shape), not
// the Soroban transaction building, which is unit-tested in the helpers package.
Expand Down Expand Up @@ -96,7 +109,10 @@ import vaultsHandler from "../v1/vaults/index";
import positionsHandler from "../v1/positions/[publicKey]";
import keeperHandler from "../v1/keepers/accrue";
import rebalanceHandler from "../v1/keepers/rebalance";
import { resetRateLimitForTesting } from "../_lib/middleware.js";
import {
checkRateLimit,
resetRateLimitForTesting,
} from "../_lib/middleware.js";
import {
buildDepositTx,
runBlendAccrualKeeper,
Expand Down Expand Up @@ -146,6 +162,30 @@ beforeEach(() => {
});

describe("POST /api/v1/tx/deposit", () => {
it("returns 503 when the upstream rate limiter fails", async () => {
vi.mocked(checkRateLimit).mockRejectedValueOnce(
new Error("Upstash timeout")
);

const res = makeRes();
await depositHandler(
fakeReq({
method: "POST",
body: {
walletAddress: PUBKEY,
vaultId: "blend-usdc-fixed",
amount: "10",
},
}),
res
);

expect(res.statusCode).toBe(503);
expect(res.body).toEqual({
error: "Rate limiter unavailable; refusing to run",
});
});

it("rejects non-POST methods with 405", async () => {
const res = makeRes();
await depositHandler(fakeReq({ method: "GET", body: {} }), res);
Expand Down
9 changes: 8 additions & 1 deletion api/v1/positions/[publicKey].ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,14 @@ import { applyCors, checkRateLimit } from "../../_lib/middleware.js";

export default async function handler(req: VercelRequest, res: VercelResponse) {
if (applyCors(req, res)) return;
if (!(await checkRateLimit(req, res))) return;
try {
if (!(await checkRateLimit(req, res))) return;
} catch (err) {
console.error("[positions] rate limit check failed:", err);
return res
.status(503)
.json({ error: "Rate limiter unavailable; refusing to run" });
}
const { publicKey } = req.query as { publicKey: string };

const result = await handleGetPositions(publicKey);
Expand Down
9 changes: 8 additions & 1 deletion api/v1/tx/add-trustline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,14 @@ import { applyCors, checkRateLimit } from "../../_lib/middleware.js";

export default async function handler(req: VercelRequest, res: VercelResponse) {
if (applyCors(req, res)) return;
if (!(await checkRateLimit(req, res, { strict: true }))) return;
try {
if (!(await checkRateLimit(req, res, { strict: true }))) return;
} catch (err) {
console.error("[tx/add-trustline] rate limit check failed:", err);
return res
.status(503)
.json({ error: "Rate limiter unavailable; refusing to run" });
}
if (req.method !== "POST")
return res.status(405).json({ error: "Method not allowed" });

Expand Down
9 changes: 8 additions & 1 deletion api/v1/tx/deposit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,14 @@ import { applyCors, checkRateLimit } from "../../_lib/middleware.js";

export default async function handler(req: VercelRequest, res: VercelResponse) {
if (applyCors(req, res)) return;
if (!(await checkRateLimit(req, res, { strict: true }))) return;
try {
if (!(await checkRateLimit(req, res, { strict: true }))) return;
} catch (err) {
console.error("[tx/deposit] rate limit check failed:", err);
return res
.status(503)
.json({ error: "Rate limiter unavailable; refusing to run" });
}
if (req.method !== "POST")
return res.status(405).json({ error: "Method not allowed" });

Expand Down
9 changes: 8 additions & 1 deletion api/v1/tx/submit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,14 @@ import { applyCors, checkRateLimit } from "../../_lib/middleware.js";

export default async function handler(req: VercelRequest, res: VercelResponse) {
if (applyCors(req, res)) return;
if (!(await checkRateLimit(req, res, { strict: true }))) return;
try {
if (!(await checkRateLimit(req, res, { strict: true }))) return;
} catch (err) {
console.error("[tx/submit] rate limit check failed:", err);
return res
.status(503)
.json({ error: "Rate limiter unavailable; refusing to run" });
}
if (req.method !== "POST")
return res.status(405).json({ error: "Method not allowed" });

Expand Down
9 changes: 8 additions & 1 deletion api/v1/tx/withdraw.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,14 @@ import { applyCors, checkRateLimit } from "../../_lib/middleware.js";

export default async function handler(req: VercelRequest, res: VercelResponse) {
if (applyCors(req, res)) return;
if (!(await checkRateLimit(req, res, { strict: true }))) return;
try {
if (!(await checkRateLimit(req, res, { strict: true }))) return;
} catch (err) {
console.error("[tx/withdraw] rate limit check failed:", err);
return res
.status(503)
.json({ error: "Rate limiter unavailable; refusing to run" });
}
if (req.method !== "POST")
return res.status(405).json({ error: "Method not allowed" });

Expand Down
9 changes: 8 additions & 1 deletion api/v1/vaults/[vaultId].ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,14 @@ const KNOWN_VAULT_IDS = new Set(

export default async function handler(req: VercelRequest, res: VercelResponse) {
if (applyCors(req, res)) return;
if (!(await checkRateLimit(req, res))) return;
try {
if (!(await checkRateLimit(req, res))) return;
} catch (err) {
console.error("[vaults] rate limit check failed:", err);
return res
.status(503)
.json({ error: "Rate limiter unavailable; refusing to run" });
}
const raw = req.query["vaultId"];
const vaultId = typeof raw === "string" ? raw : undefined;
if (!vaultId) return res.status(400).json({ error: "vaultId is required" });
Expand Down
9 changes: 8 additions & 1 deletion api/v1/vaults/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,14 @@ const TESTNET_CACHE_CONTROL = "no-store";

export default async function handler(req: VercelRequest, res: VercelResponse) {
if (applyCors(req, res)) return;
if (!(await checkRateLimit(req, res))) return;
try {
if (!(await checkRateLimit(req, res))) return;
} catch (err) {
console.error("[vaults] rate limit check failed:", err);
return res
.status(503)
.json({ error: "Rate limiter unavailable; refusing to run" });
}

const result = await handleGetVaults();
if (result.error) {
Expand Down
Loading