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
74 changes: 74 additions & 0 deletions src/app/api/bounties/route.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { describe, it, expect, vi, beforeEach } from "vitest";

vi.mock("@/lib/supabase/server", () => ({
createClient: vi.fn(),
}));

import { GET } from "./route";
import { createClient } from "@/lib/supabase/server";

function makeReq(url: string) {
return { nextUrl: new URL(url) } as any;
}

function chain(result: { data: any; error?: any }) {
return {
select: vi.fn().mockReturnThis(),
eq: vi.fn().mockReturnThis(),
order: vi.fn().mockReturnThis(),
range: vi.fn().mockResolvedValue({ data: result.data, error: result.error ?? null }),
};
}

describe("GET /api/bounties", () => {
beforeEach(() => vi.clearAllMocks());

it("falls back to default limit when limit is non-positive", async () => {
const bountyChain = chain({ data: [] });
(createClient as any).mockResolvedValue({
from: vi.fn(() => bountyChain),
});

const res = await GET(makeReq("http://localhost/api/bounties?limit=0&page=1"));

expect(res.status).toBe(200);
expect(bountyChain.range).toHaveBeenCalledWith(0, 49);
});

it("falls back to page 1 when page is invalid or non-positive", async () => {
const bountyChain = chain({ data: [] });
(createClient as any).mockResolvedValue({
from: vi.fn(() => bountyChain),
});

const res = await GET(makeReq("http://localhost/api/bounties?limit=50&page=-2"));

expect(res.status).toBe(200);
expect(bountyChain.range).toHaveBeenCalledWith(0, 49);
});

it("caps limit at 100 and computes offset using page", async () => {
const bountyChain = chain({ data: [] });
(createClient as any).mockResolvedValue({
from: vi.fn(() => bountyChain),
});

const res = await GET(makeReq("http://localhost/api/bounties?limit=101&page=2"));

expect(res.status).toBe(200);
expect(bountyChain.range).toHaveBeenCalledWith(100, 199);
});

it("falls back to defaults when limit/page are non-numeric", async () => {
const bountyChain = chain({ data: [] });
(createClient as any).mockResolvedValue({
from: vi.fn(() => bountyChain),
});

const res = await GET(makeReq("http://localhost/api/bounties?limit=abc&page=def"));

expect(res.status).toBe(200);
expect(bountyChain.range).toHaveBeenCalledWith(0, 49);
});
});

11 changes: 9 additions & 2 deletions src/app/api/bounties/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,15 @@ export async function GET(request: NextRequest) {
try {
const params = request.nextUrl.searchParams;
const status = params.get("status") || "open";
const limit = Math.min(Number(params.get("limit") || 50), 100);
const page = Math.max(Number(params.get("page") || 1), 1);
const defaultLimit = 50;

const limitRaw = Number(params.get("limit"));
const limitCandidate =
Number.isFinite(limitRaw) && limitRaw > 0 ? Math.floor(limitRaw) : defaultLimit;
const limit = Math.min(limitCandidate, 100);

const pageRaw = Number(params.get("page"));
const page = Number.isFinite(pageRaw) && pageRaw > 0 ? Math.floor(pageRaw) : 1;
const offset = (page - 1) * limit;

const supabase = await createClient();
Expand Down
Loading