From 02b3f805b2af4ff75aaaea3837253dd38cfbaf5c Mon Sep 17 00:00:00 2001 From: Dmytro Stanchiev Date: Mon, 27 Apr 2026 10:46:06 -0400 Subject: [PATCH] fix: use explicit conditional calls and validate negative params --- packages/api-server/src/routes/ebay.ts | 17 ++-- packages/api-server/src/routes/facebook.ts | 14 ++-- packages/api-server/src/routes/kijiji.ts | 30 ++++--- packages/api-server/test/routes.test.ts | 98 ++++++++++++++++++++++ 4 files changed, 131 insertions(+), 28 deletions(-) diff --git a/packages/api-server/src/routes/ebay.ts b/packages/api-server/src/routes/ebay.ts index 48652b7..06ebdca 100644 --- a/packages/api-server/src/routes/ebay.ts +++ b/packages/api-server/src/routes/ebay.ts @@ -21,7 +21,7 @@ export async function ebayRoute(req: Request): Promise { const minPriceParam = reqUrl.searchParams.get("minPrice"); const minPrice = minPriceParam ? parseInt(minPriceParam, 10) : undefined; - if (minPriceParam && Number.isNaN(minPrice)) { + if (minPriceParam && (Number.isNaN(minPrice) || minPrice < 0)) { return Response.json( { message: "Invalid minPrice parameter" }, { status: 400 }, @@ -29,7 +29,7 @@ export async function ebayRoute(req: Request): Promise { } const maxPriceParam = reqUrl.searchParams.get("maxPrice"); const maxPrice = maxPriceParam ? parseInt(maxPriceParam, 10) : undefined; - if (maxPriceParam && Number.isNaN(maxPrice)) { + if (maxPriceParam && (Number.isNaN(maxPrice) || maxPrice < 0)) { return Response.json( { message: "Invalid maxPrice parameter" }, { status: 400 }, @@ -49,7 +49,7 @@ export async function ebayRoute(req: Request): Promise { const maxItemsParam = reqUrl.searchParams.get("maxItems"); const maxItems = maxItemsParam ? parseInt(maxItemsParam, 10) : undefined; - if (maxItemsParam && Number.isNaN(maxItems)) { + if (maxItemsParam && (Number.isNaN(maxItems) || maxItems < 0)) { return Response.json( { message: "Invalid maxItems parameter" }, { status: 400 }, @@ -66,12 +66,11 @@ export async function ebayRoute(req: Request): Promise { buyItNowOnly, canadaOnly, }; - const items = await fetchEbayItems( - SEARCH_QUERY, - 1, - opts, - ...(hideUnstableResults ? [{ hideUnstableResults: true }] : []), - ); + const items = hideUnstableResults + ? await fetchEbayItems(SEARCH_QUERY, 1, opts, { + hideUnstableResults: true, + }) + : await fetchEbayItems(SEARCH_QUERY, 1, opts); const isEmpty = hideUnstableResults ? items.results.length === 0 && items.unstableResults.length === 0 diff --git a/packages/api-server/src/routes/facebook.ts b/packages/api-server/src/routes/facebook.ts index a401cd0..ef5d4a2 100644 --- a/packages/api-server/src/routes/facebook.ts +++ b/packages/api-server/src/routes/facebook.ts @@ -20,7 +20,7 @@ export async function facebookRoute(req: Request): Promise { const LOCATION = reqUrl.searchParams.get("location") || "toronto"; const maxItemsParam = reqUrl.searchParams.get("maxItems"); const maxItems = maxItemsParam ? parseInt(maxItemsParam, 10) : 25; - if (maxItemsParam && Number.isNaN(maxItems)) { + if (maxItemsParam && (Number.isNaN(maxItems) || maxItems < 0)) { return Response.json( { message: "Invalid maxItems parameter" }, { status: 400 }, @@ -30,13 +30,11 @@ export async function facebookRoute(req: Request): Promise { reqUrl.searchParams.get("unstableFilter") === "true"; try { - const items = await fetchFacebookItems( - SEARCH_QUERY, - 1, - LOCATION, - maxItems, - ...(hideUnstableResults ? [{ hideUnstableResults: true }] : []), - ); + const items = hideUnstableResults + ? await fetchFacebookItems(SEARCH_QUERY, 1, LOCATION, maxItems, { + hideUnstableResults: true, + }) + : await fetchFacebookItems(SEARCH_QUERY, 1, LOCATION, maxItems); const isEmpty = hideUnstableResults ? items.results.length === 0 && items.unstableResults.length === 0 diff --git a/packages/api-server/src/routes/kijiji.ts b/packages/api-server/src/routes/kijiji.ts index f6aa972..373ea42 100644 --- a/packages/api-server/src/routes/kijiji.ts +++ b/packages/api-server/src/routes/kijiji.ts @@ -19,7 +19,7 @@ export async function kijijiRoute(req: Request): Promise { const maxPagesParam = reqUrl.searchParams.get("maxPages"); const maxPages = maxPagesParam ? parseInt(maxPagesParam, 10) : 5; - if (maxPagesParam && Number.isNaN(maxPages)) { + if (maxPagesParam && (Number.isNaN(maxPages) || maxPages < 0)) { return Response.json( { message: "Invalid maxPages parameter" }, { status: 400 }, @@ -27,7 +27,7 @@ export async function kijijiRoute(req: Request): Promise { } const priceMinParam = reqUrl.searchParams.get("priceMin"); const priceMin = priceMinParam ? parseInt(priceMinParam, 10) : undefined; - if (priceMinParam && Number.isNaN(priceMin)) { + if (priceMinParam && (Number.isNaN(priceMin) || priceMin < 0)) { return Response.json( { message: "Invalid priceMin parameter" }, { status: 400 }, @@ -35,7 +35,7 @@ export async function kijijiRoute(req: Request): Promise { } const priceMaxParam = reqUrl.searchParams.get("priceMax"); const priceMax = priceMaxParam ? parseInt(priceMaxParam, 10) : undefined; - if (priceMaxParam && Number.isNaN(priceMax)) { + if (priceMaxParam && (Number.isNaN(priceMax) || priceMax < 0)) { return Response.json( { message: "Invalid priceMax parameter" }, { status: 400 }, @@ -65,14 +65,22 @@ export async function kijijiRoute(req: Request): Promise { }; try { - const items = await fetchKijijiItems( - SEARCH_QUERY, - 4, // 4 requests per second for faster scraping - "https://www.kijiji.ca", - searchOptions, - {}, - ...(hideUnstableResults ? [{ hideUnstableResults: true }] : []), - ); + const items = hideUnstableResults + ? await fetchKijijiItems( + SEARCH_QUERY, + 4, // 4 requests per second for faster scraping + "https://www.kijiji.ca", + searchOptions, + {}, + { hideUnstableResults: true }, + ) + : await fetchKijijiItems( + SEARCH_QUERY, + 4, // 4 requests per second for faster scraping + "https://www.kijiji.ca", + searchOptions, + {}, + ); const isEmpty = hideUnstableResults ? items.results.length === 0 && items.unstableResults.length === 0 diff --git a/packages/api-server/test/routes.test.ts b/packages/api-server/test/routes.test.ts index 3ec8d6c..e194448 100644 --- a/packages/api-server/test/routes.test.ts +++ b/packages/api-server/test/routes.test.ts @@ -540,4 +540,102 @@ describe("API routes", () => { const body = await response.json(); expect(body.message).toBe("Invalid priceMax parameter"); }); + + test("facebookRoute returns 400 for negative maxItems", async () => { + const { facebookRoute } = await import("../src/routes/facebook"); + + const response = await facebookRoute( + new Request( + "http://localhost/api/facebook?q=laptop&maxItems=-1", + ), + ); + + expect(response.status).toBe(400); + const body = await response.json(); + expect(body.message).toBe("Invalid maxItems parameter"); + }); + + test("ebayRoute returns 400 for negative maxItems", async () => { + const { ebayRoute } = await import("../src/routes/ebay"); + + const response = await ebayRoute( + new Request( + "http://localhost/api/ebay?q=laptop&maxItems=-1", + ), + ); + + expect(response.status).toBe(400); + const body = await response.json(); + expect(body.message).toBe("Invalid maxItems parameter"); + }); + + test("ebayRoute returns 400 for negative minPrice", async () => { + const { ebayRoute } = await import("../src/routes/ebay"); + + const response = await ebayRoute( + new Request( + "http://localhost/api/ebay?q=laptop&minPrice=-5", + ), + ); + + expect(response.status).toBe(400); + const body = await response.json(); + expect(body.message).toBe("Invalid minPrice parameter"); + }); + + test("ebayRoute returns 400 for negative maxPrice", async () => { + const { ebayRoute } = await import("../src/routes/ebay"); + + const response = await ebayRoute( + new Request( + "http://localhost/api/ebay?q=laptop&maxPrice=-10", + ), + ); + + expect(response.status).toBe(400); + const body = await response.json(); + expect(body.message).toBe("Invalid maxPrice parameter"); + }); + + test("kijijiRoute returns 400 for negative maxPages", async () => { + const { kijijiRoute } = await import("../src/routes/kijiji"); + + const response = await kijijiRoute( + new Request( + "http://localhost/api/kijiji?q=laptop&maxPages=-2", + ), + ); + + expect(response.status).toBe(400); + const body = await response.json(); + expect(body.message).toBe("Invalid maxPages parameter"); + }); + + test("kijijiRoute returns 400 for negative priceMin", async () => { + const { kijijiRoute } = await import("../src/routes/kijiji"); + + const response = await kijijiRoute( + new Request( + "http://localhost/api/kijiji?q=laptop&priceMin=-5", + ), + ); + + expect(response.status).toBe(400); + const body = await response.json(); + expect(body.message).toBe("Invalid priceMin parameter"); + }); + + test("kijijiRoute returns 400 for negative priceMax", async () => { + const { kijijiRoute } = await import("../src/routes/kijiji"); + + const response = await kijijiRoute( + new Request( + "http://localhost/api/kijiji?q=laptop&priceMax=-10", + ), + ); + + expect(response.status).toBe(400); + const body = await response.json(); + expect(body.message).toBe("Invalid priceMax parameter"); + }); });