diff --git a/src/app/api/gigs/route.test.ts b/src/app/api/gigs/route.test.ts index ca705ff7..3b5bd0e8 100644 --- a/src/app/api/gigs/route.test.ts +++ b/src/app/api/gigs/route.test.ts @@ -144,6 +144,39 @@ describe("GET /api/gigs", () => { expect(json.pagination.totalPages).toBe(3); }); + it("truncates fractional page and limit values before querying", async () => { + const chain = chainResult({ data: null, error: null }); + chain.select = vi.fn().mockReturnValue(chain); + chain.range = vi.fn().mockResolvedValue({ data: [], error: null, count: 30 }); + + mockFrom.mockReturnValue(chain); + + const res = await GET(makeGetRequest({ page: "2.9", limit: "5.9" })); + const json = await res.json(); + + expect(res.status).toBe(200); + expect(chain.range).toHaveBeenCalledWith(5, 9); + expect(json.pagination.page).toBe(2); + expect(json.pagination.limit).toBe(5); + expect(json.pagination.totalPages).toBe(6); + }); + + it("defaults invalid and non-positive pagination values before querying", async () => { + const chain = chainResult({ data: null, error: null }); + chain.select = vi.fn().mockReturnValue(chain); + chain.range = vi.fn().mockResolvedValue({ data: [], error: null, count: 0 }); + + mockFrom.mockReturnValue(chain); + + const res = await GET(makeGetRequest({ page: "-10", limit: "abc" })); + const json = await res.json(); + + expect(res.status).toBe(200); + expect(chain.range).toHaveBeenCalledWith(0, 19); + expect(json.pagination.page).toBe(1); + expect(json.pagination.limit).toBe(20); + }); + it("filters by listing_type when provided", async () => { const chain = chainResult({ data: null, error: null }); chain.select = vi.fn().mockReturnValue(chain); diff --git a/src/app/api/gigs/route.ts b/src/app/api/gigs/route.ts index 5d7172d0..cd218842 100644 --- a/src/app/api/gigs/route.ts +++ b/src/app/api/gigs/route.ts @@ -10,6 +10,16 @@ import { logActivity } from "@/lib/activity"; const MAX_GIG_PAGE = 100_000; const MAX_GIG_LIMIT = 50; +function parsePositiveIntegerParam( + value: string | null, + defaultValue: number, + max: number +) { + const parsed = Number(value && value.trim() !== "" ? value : defaultValue); + const finiteValue = Number.isFinite(parsed) ? parsed : defaultValue; + return Math.min(Math.max(1, Math.trunc(finiteValue)), max); +} + // GET /api/gigs - List gigs (public) export async function GET(request: NextRequest) { try { @@ -27,8 +37,8 @@ export async function GET(request: NextRequest) { account_type: searchParams.get("account_type") || undefined, listing_type: searchParams.get("listing_type") || undefined, sort: searchParams.get("sort") || "newest", - page: Math.min(Number(searchParams.get("page")) || 1, MAX_GIG_PAGE), - limit: Math.min(Number(searchParams.get("limit")) || 20, MAX_GIG_LIMIT), + page: parsePositiveIntegerParam(searchParams.get("page"), 1, MAX_GIG_PAGE), + limit: parsePositiveIntegerParam(searchParams.get("limit"), 20, MAX_GIG_LIMIT), }); if (!filters.success) {