From a802035ca4f3d63c87f4952b80e244efe2aaa30e Mon Sep 17 00:00:00 2001 From: Dmytro Stanchiev Date: Mon, 27 Apr 2026 09:34:08 -0400 Subject: [PATCH] fix: correct empty-result and maxItems handling in routes --- packages/api-server/src/routes/ebay.ts | 40 ++++--- packages/api-server/src/routes/facebook.ts | 7 +- packages/api-server/src/routes/kijiji.ts | 7 +- packages/api-server/test/routes.test.ts | 133 +++++++++++++++++++-- 4 files changed, 164 insertions(+), 23 deletions(-) diff --git a/packages/api-server/src/routes/ebay.ts b/packages/api-server/src/routes/ebay.ts index a19bcf1..9ab874c 100644 --- a/packages/api-server/src/routes/ebay.ts +++ b/packages/api-server/src/routes/ebay.ts @@ -64,27 +64,39 @@ export async function ebayRoute(req: Request): Promise { canadaOnly, }); - let results; - if (hideUnstableResults) { - results = maxItems - ? { - results: items.results.slice(0, maxItems), - unstableResults: items.unstableResults.slice(0, maxItems), - } - : items; - } else { - results = maxItems ? items.slice(0, maxItems) : items; - } - const isEmpty = hideUnstableResults - ? results.results.length === 0 && results.unstableResults.length === 0 - : !results || results.length === 0; + ? items.results.length === 0 && items.unstableResults.length === 0 + : !items || items.length === 0; if (isEmpty) return Response.json( { message: "Search didn't return any results!" }, { status: 404 }, ); + + let results; + if (hideUnstableResults) { + const limitedResults = + maxItems !== undefined + ? items.results.slice(0, maxItems) + : items.results; + const remainingSlots = + maxItems !== undefined + ? Math.max(0, maxItems - limitedResults.length) + : undefined; + const limitedUnstable = + remainingSlots !== undefined + ? items.unstableResults.slice(0, remainingSlots) + : items.unstableResults; + results = { + results: limitedResults, + unstableResults: limitedUnstable, + }; + } else { + results = + maxItems !== undefined ? items.slice(0, maxItems) : items; + } + return Response.json(results, { status: 200 }); } catch (error) { console.error("eBay scraping error:", error); diff --git a/packages/api-server/src/routes/facebook.ts b/packages/api-server/src/routes/facebook.ts index 6038a27..1236c40 100644 --- a/packages/api-server/src/routes/facebook.ts +++ b/packages/api-server/src/routes/facebook.ts @@ -29,7 +29,12 @@ export async function facebookRoute(req: Request): Promise { hideUnstableResults: true, }) : await fetchFacebookItems(SEARCH_QUERY, 1, LOCATION, maxItems); - if (!items || items.length === 0) + + const isEmpty = hideUnstableResults + ? items.results.length === 0 && items.unstableResults.length === 0 + : !items || items.length === 0; + + if (isEmpty) return Response.json( { message: "Search didn't return any results!" }, { status: 404 }, diff --git a/packages/api-server/src/routes/kijiji.ts b/packages/api-server/src/routes/kijiji.ts index a51302b..014e04a 100644 --- a/packages/api-server/src/routes/kijiji.ts +++ b/packages/api-server/src/routes/kijiji.ts @@ -63,7 +63,12 @@ export async function kijijiRoute(req: Request): Promise { searchOptions, {}, ); - if (!items) + + const isEmpty = hideUnstableResults + ? items.results.length === 0 && items.unstableResults.length === 0 + : !items || items.length === 0; + + if (isEmpty) return Response.json( { message: "Search didn't return any results!" }, { status: 404 }, diff --git a/packages/api-server/test/routes.test.ts b/packages/api-server/test/routes.test.ts index e1e3630..358e45f 100644 --- a/packages/api-server/test/routes.test.ts +++ b/packages/api-server/test/routes.test.ts @@ -64,7 +64,7 @@ describe("API routes", () => { }); }); - test("kijijiRoute ignores cookies query parameter", async () => { + test("kijijiRoute passes cookies query parameter", async () => { const { kijijiRoute } = await import("../src/routes/kijiji"); await kijijiRoute( @@ -95,6 +95,13 @@ describe("API routes", () => { test("facebookRoute forwards unstableFilter=true to core", async () => { const { facebookRoute } = await import("../src/routes/facebook"); + fetchFacebookItems.mockImplementation(() => + Promise.resolve({ + results: [{ title: "item" }], + unstableResults: [], + }), + ); + await facebookRoute( new Request( "http://localhost/api/facebook?q=laptop&location=toronto&maxItems=3&unstableFilter=true", @@ -138,6 +145,13 @@ describe("API routes", () => { test("kijijiRoute forwards unstableFilter=true to core", async () => { const { kijijiRoute } = await import("../src/routes/kijiji"); + fetchKijijiItems.mockImplementation(() => + Promise.resolve({ + results: [{ title: "item" }], + unstableResults: [], + }), + ); + await kijijiRoute( new Request( "http://localhost/api/kijiji?q=laptop&maxPages=5&unstableFilter=true", @@ -286,7 +300,112 @@ describe("API routes", () => { ); }); - test("ebayRoute applies maxItems in unstable mode", async () => { + test("facebookRoute returns bucket shape when unstableFilter is enabled", async () => { + const { facebookRoute } = await import("../src/routes/facebook"); + + fetchFacebookItems.mockImplementation(() => + Promise.resolve({ + results: [{ title: "a" }], + unstableResults: [{ title: "b" }], + }), + ); + + const response = await facebookRoute( + new Request( + "http://localhost/api/facebook?q=laptop&location=toronto&maxItems=3&unstableFilter=true", + ), + ); + + const body = await response.json(); + expect(body.results).toHaveLength(1); + expect(body.unstableResults).toHaveLength(1); + expect(body.results[0].title).toBe("a"); + expect(body.unstableResults[0].title).toBe("b"); + }); + + test("kijijiRoute returns bucket shape when unstableFilter is enabled", async () => { + const { kijijiRoute } = await import("../src/routes/kijiji"); + + fetchKijijiItems.mockImplementation(() => + Promise.resolve({ + results: [{ title: "a" }], + unstableResults: [{ title: "b" }], + }), + ); + + const response = await kijijiRoute( + new Request( + "http://localhost/api/kijiji?q=laptop&maxPages=5&unstableFilter=true", + ), + ); + + const body = await response.json(); + expect(body.results).toHaveLength(1); + expect(body.unstableResults).toHaveLength(1); + expect(body.results[0].title).toBe("a"); + expect(body.unstableResults[0].title).toBe("b"); + }); + + test("facebookRoute returns 404 when unstable results are empty", async () => { + const { facebookRoute } = await import("../src/routes/facebook"); + + fetchFacebookItems.mockImplementation(() => + Promise.resolve({ + results: [], + unstableResults: [], + }), + ); + + const response = await facebookRoute( + new Request( + "http://localhost/api/facebook?q=laptop&location=toronto&maxItems=3&unstableFilter=true", + ), + ); + + expect(response.status).toBe(404); + const body = await response.json(); + expect(body.message).toBe("Search didn't return any results!"); + }); + + test("kijijiRoute returns 404 when unstable results are empty", async () => { + const { kijijiRoute } = await import("../src/routes/kijiji"); + + fetchKijijiItems.mockImplementation(() => + Promise.resolve({ + results: [], + unstableResults: [], + }), + ); + + const response = await kijijiRoute( + new Request( + "http://localhost/api/kijiji?q=laptop&maxPages=5&unstableFilter=true", + ), + ); + + expect(response.status).toBe(404); + const body = await response.json(); + expect(body.message).toBe("Search didn't return any results!"); + }); + + test("ebayRoute respects maxItems=0 in default mode", async () => { + const { ebayRoute } = await import("../src/routes/ebay"); + + fetchEbayItems.mockImplementation(() => + Promise.resolve([{ title: "a" }, { title: "b" }]), + ); + + const response = await ebayRoute( + new Request( + "http://localhost/api/ebay?q=laptop&maxItems=0", + ), + ); + + const body = await response.json(); + expect(body).toHaveLength(0); + }); + + test("ebayRoute limits total items with maxItems in unstable mode", async () => { const { ebayRoute } = await import("../src/routes/ebay"); fetchEbayItems.mockImplementation(() => @@ -298,16 +417,16 @@ describe("API routes", () => { const response = await ebayRoute( new Request( - "http://localhost/api/ebay?q=laptop&unstableFilter=true&maxItems=2", + "http://localhost/api/ebay?q=laptop&unstableFilter=true&maxItems=4", ), ); const body = await response.json(); - expect(body.results).toHaveLength(2); - expect(body.unstableResults).toHaveLength(2); + const total = body.results.length + body.unstableResults.length; + expect(total).toBe(4); + expect(body.results).toHaveLength(3); + expect(body.unstableResults).toHaveLength(1); expect(body.results[0].title).toBe("a"); - expect(body.results[1].title).toBe("b"); expect(body.unstableResults[0].title).toBe("d"); - expect(body.unstableResults[1].title).toBe("e"); }); });