Skip to content
Closed
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
33 changes: 33 additions & 0 deletions src/app/api/gigs/route.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
Comment on lines +147 to +178

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Missing test for explicit zero input clamping

The two new tests cover fractions and non-positive strings, but neither passes page=0 or limit=0 explicitly. Number("0") = 0 is finite and non-NaN, so the fallback to defaultValue is skipped — the value travels all the way to Math.max(1, Math.trunc(0)), which correctly returns 1. That path isn't exercised by any existing test, leaving a gap in the zero-input contract. A test asserting range(0, 19) when page="0" (and default limit=20) would close it.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!


it("filters by listing_type when provided", async () => {
const chain = chainResult({ data: null, error: null });
chain.select = vi.fn().mockReturnValue(chain);
Expand Down
14 changes: 12 additions & 2 deletions src/app/api/gigs/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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) {
Expand Down
Loading