fix: use explicit conditional calls and validate negative params

This commit is contained in:
2026-04-27 10:46:06 -04:00
parent a1af5d2630
commit 02b3f805b2
4 changed files with 131 additions and 28 deletions

View File

@@ -21,7 +21,7 @@ export async function ebayRoute(req: Request): Promise<Response> {
const minPriceParam = reqUrl.searchParams.get("minPrice"); const minPriceParam = reqUrl.searchParams.get("minPrice");
const minPrice = minPriceParam ? parseInt(minPriceParam, 10) : undefined; const minPrice = minPriceParam ? parseInt(minPriceParam, 10) : undefined;
if (minPriceParam && Number.isNaN(minPrice)) { if (minPriceParam && (Number.isNaN(minPrice) || minPrice < 0)) {
return Response.json( return Response.json(
{ message: "Invalid minPrice parameter" }, { message: "Invalid minPrice parameter" },
{ status: 400 }, { status: 400 },
@@ -29,7 +29,7 @@ export async function ebayRoute(req: Request): Promise<Response> {
} }
const maxPriceParam = reqUrl.searchParams.get("maxPrice"); const maxPriceParam = reqUrl.searchParams.get("maxPrice");
const maxPrice = maxPriceParam ? parseInt(maxPriceParam, 10) : undefined; const maxPrice = maxPriceParam ? parseInt(maxPriceParam, 10) : undefined;
if (maxPriceParam && Number.isNaN(maxPrice)) { if (maxPriceParam && (Number.isNaN(maxPrice) || maxPrice < 0)) {
return Response.json( return Response.json(
{ message: "Invalid maxPrice parameter" }, { message: "Invalid maxPrice parameter" },
{ status: 400 }, { status: 400 },
@@ -49,7 +49,7 @@ export async function ebayRoute(req: Request): Promise<Response> {
const maxItemsParam = reqUrl.searchParams.get("maxItems"); const maxItemsParam = reqUrl.searchParams.get("maxItems");
const maxItems = maxItemsParam ? parseInt(maxItemsParam, 10) : undefined; const maxItems = maxItemsParam ? parseInt(maxItemsParam, 10) : undefined;
if (maxItemsParam && Number.isNaN(maxItems)) { if (maxItemsParam && (Number.isNaN(maxItems) || maxItems < 0)) {
return Response.json( return Response.json(
{ message: "Invalid maxItems parameter" }, { message: "Invalid maxItems parameter" },
{ status: 400 }, { status: 400 },
@@ -66,12 +66,11 @@ export async function ebayRoute(req: Request): Promise<Response> {
buyItNowOnly, buyItNowOnly,
canadaOnly, canadaOnly,
}; };
const items = await fetchEbayItems( const items = hideUnstableResults
SEARCH_QUERY, ? await fetchEbayItems(SEARCH_QUERY, 1, opts, {
1, hideUnstableResults: true,
opts, })
...(hideUnstableResults ? [{ hideUnstableResults: true }] : []), : await fetchEbayItems(SEARCH_QUERY, 1, opts);
);
const isEmpty = hideUnstableResults const isEmpty = hideUnstableResults
? items.results.length === 0 && items.unstableResults.length === 0 ? items.results.length === 0 && items.unstableResults.length === 0

View File

@@ -20,7 +20,7 @@ export async function facebookRoute(req: Request): Promise<Response> {
const LOCATION = reqUrl.searchParams.get("location") || "toronto"; const LOCATION = reqUrl.searchParams.get("location") || "toronto";
const maxItemsParam = reqUrl.searchParams.get("maxItems"); const maxItemsParam = reqUrl.searchParams.get("maxItems");
const maxItems = maxItemsParam ? parseInt(maxItemsParam, 10) : 25; const maxItems = maxItemsParam ? parseInt(maxItemsParam, 10) : 25;
if (maxItemsParam && Number.isNaN(maxItems)) { if (maxItemsParam && (Number.isNaN(maxItems) || maxItems < 0)) {
return Response.json( return Response.json(
{ message: "Invalid maxItems parameter" }, { message: "Invalid maxItems parameter" },
{ status: 400 }, { status: 400 },
@@ -30,13 +30,11 @@ export async function facebookRoute(req: Request): Promise<Response> {
reqUrl.searchParams.get("unstableFilter") === "true"; reqUrl.searchParams.get("unstableFilter") === "true";
try { try {
const items = await fetchFacebookItems( const items = hideUnstableResults
SEARCH_QUERY, ? await fetchFacebookItems(SEARCH_QUERY, 1, LOCATION, maxItems, {
1, hideUnstableResults: true,
LOCATION, })
maxItems, : await fetchFacebookItems(SEARCH_QUERY, 1, LOCATION, maxItems);
...(hideUnstableResults ? [{ hideUnstableResults: true }] : []),
);
const isEmpty = hideUnstableResults const isEmpty = hideUnstableResults
? items.results.length === 0 && items.unstableResults.length === 0 ? items.results.length === 0 && items.unstableResults.length === 0

View File

@@ -19,7 +19,7 @@ export async function kijijiRoute(req: Request): Promise<Response> {
const maxPagesParam = reqUrl.searchParams.get("maxPages"); const maxPagesParam = reqUrl.searchParams.get("maxPages");
const maxPages = maxPagesParam ? parseInt(maxPagesParam, 10) : 5; const maxPages = maxPagesParam ? parseInt(maxPagesParam, 10) : 5;
if (maxPagesParam && Number.isNaN(maxPages)) { if (maxPagesParam && (Number.isNaN(maxPages) || maxPages < 0)) {
return Response.json( return Response.json(
{ message: "Invalid maxPages parameter" }, { message: "Invalid maxPages parameter" },
{ status: 400 }, { status: 400 },
@@ -27,7 +27,7 @@ export async function kijijiRoute(req: Request): Promise<Response> {
} }
const priceMinParam = reqUrl.searchParams.get("priceMin"); const priceMinParam = reqUrl.searchParams.get("priceMin");
const priceMin = priceMinParam ? parseInt(priceMinParam, 10) : undefined; const priceMin = priceMinParam ? parseInt(priceMinParam, 10) : undefined;
if (priceMinParam && Number.isNaN(priceMin)) { if (priceMinParam && (Number.isNaN(priceMin) || priceMin < 0)) {
return Response.json( return Response.json(
{ message: "Invalid priceMin parameter" }, { message: "Invalid priceMin parameter" },
{ status: 400 }, { status: 400 },
@@ -35,7 +35,7 @@ export async function kijijiRoute(req: Request): Promise<Response> {
} }
const priceMaxParam = reqUrl.searchParams.get("priceMax"); const priceMaxParam = reqUrl.searchParams.get("priceMax");
const priceMax = priceMaxParam ? parseInt(priceMaxParam, 10) : undefined; const priceMax = priceMaxParam ? parseInt(priceMaxParam, 10) : undefined;
if (priceMaxParam && Number.isNaN(priceMax)) { if (priceMaxParam && (Number.isNaN(priceMax) || priceMax < 0)) {
return Response.json( return Response.json(
{ message: "Invalid priceMax parameter" }, { message: "Invalid priceMax parameter" },
{ status: 400 }, { status: 400 },
@@ -65,13 +65,21 @@ export async function kijijiRoute(req: Request): Promise<Response> {
}; };
try { try {
const items = await fetchKijijiItems( 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, SEARCH_QUERY,
4, // 4 requests per second for faster scraping 4, // 4 requests per second for faster scraping
"https://www.kijiji.ca", "https://www.kijiji.ca",
searchOptions, searchOptions,
{}, {},
...(hideUnstableResults ? [{ hideUnstableResults: true }] : []),
); );
const isEmpty = hideUnstableResults const isEmpty = hideUnstableResults

View File

@@ -540,4 +540,102 @@ describe("API routes", () => {
const body = await response.json(); const body = await response.json();
expect(body.message).toBe("Invalid priceMax parameter"); 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");
});
}); });