chore: biome lint and formatting

Signed-off-by: Dmytro Stanchiev <git@dmytros.dev>
This commit is contained in:
2026-04-28 19:21:16 -04:00
parent 49e90d45f8
commit 957e0f137b
10 changed files with 465 additions and 337 deletions

View File

@@ -136,7 +136,9 @@ describe("eBay Scraper Cookie Handling", () => {
expect(results).toHaveLength(1);
expect(results[0]).toEqual(
expect.objectContaining({ url: "https://www.ebay.ca/itm/123?_trkparms=foo" }),
expect.objectContaining({
url: "https://www.ebay.ca/itm/123?_trkparms=foo",
}),
);
});
@@ -229,7 +231,10 @@ describe("eBay Scraper Cookie Handling", () => {
expect(results).toEqual([
expect.objectContaining({
listingPrice: expect.objectContaining({ currency: "USD", cents: 12345 }),
listingPrice: expect.objectContaining({
currency: "USD",
cents: 12345,
}),
}),
]);
});
@@ -255,7 +260,10 @@ describe("eBay Scraper Cookie Handling", () => {
expect(results).toEqual([
expect.objectContaining({
listingPrice: expect.objectContaining({ currency: "USD", cents: 12345 }),
listingPrice: expect.objectContaining({
currency: "USD",
cents: 12345,
}),
}),
]);
});
@@ -281,7 +289,10 @@ describe("eBay Scraper Cookie Handling", () => {
expect(results).toEqual([
expect.objectContaining({
listingPrice: expect.objectContaining({ currency: "GBP", cents: 12345 }),
listingPrice: expect.objectContaining({
currency: "GBP",
cents: 12345,
}),
}),
]);
});
@@ -314,10 +325,16 @@ describe("eBay Scraper Cookie Handling", () => {
expect(results).toEqual([
expect.objectContaining({
listingPrice: expect.objectContaining({ currency: "EUR", cents: 12345 }),
listingPrice: expect.objectContaining({
currency: "EUR",
cents: 12345,
}),
}),
expect.objectContaining({
listingPrice: expect.objectContaining({ currency: "JPY", cents: 12300 }),
listingPrice: expect.objectContaining({
currency: "JPY",
cents: 12300,
}),
}),
]);
});

View File

@@ -2,13 +2,13 @@ import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
import cliProgress from "cli-progress";
import {
classifyFacebookResponse,
type FacebookListingDetails,
ensureFacebookCookies,
extractFacebookBootstrapCandidates,
extractFacebookItemData,
extractFacebookMarketplaceData,
default as fetchFacebookItems,
type FacebookListingDetails,
fetchFacebookItem,
default as fetchFacebookItems,
parseFacebookAds,
parseFacebookCookieString,
parseFacebookItem,
@@ -30,9 +30,13 @@ type IsExact<T, U> =
const getDefaultFacebookItems = async () => fetchFacebookItems("chair");
const getUnstableFacebookItems = async (): Promise<
UnstableListingBuckets<FacebookListingDetails>
> => fetchFacebookItems("chair", 1, "toronto", 25, { hideUnstableResults: true });
> =>
fetchFacebookItems("chair", 1, "toronto", 25, { hideUnstableResults: true });
type _FacebookDefaultReturn = Assert<
IsExact<Awaited<ReturnType<typeof getDefaultFacebookItems>>, FacebookListingDetails[]>
IsExact<
Awaited<ReturnType<typeof getDefaultFacebookItems>>,
FacebookListingDetails[]
>
>;
type _FacebookUnstableReturn = Assert<
IsExact<
@@ -533,30 +537,32 @@ describe("Facebook Marketplace Scraper Core Tests", () => {
});
test("returns an array by default", async () => {
const mockSearchHtml = `<html><body><script>"XCometMarketplaceSearchController"</script><script>${JSON.stringify({
payload: {
resultGroups: [
{
edges: [
{
node: {
listing: {
id: "1",
marketplace_listing_title: "Stable Chair Listing",
listing_price: {
amount: "120.00",
formatted_amount: "CA$120",
currency: "CAD",
const mockSearchHtml = `<html><body><script>"XCometMarketplaceSearchController"</script><script>${JSON.stringify(
{
payload: {
resultGroups: [
{
edges: [
{
node: {
listing: {
id: "1",
marketplace_listing_title: "Stable Chair Listing",
listing_price: {
amount: "120.00",
formatted_amount: "CA$120",
currency: "CAD",
},
is_live: true,
},
is_live: true,
},
},
},
],
},
],
],
},
],
},
},
})}</script></body></html>`;
)}</script></body></html>`;
global.fetch = mock(() =>
Promise.resolve({
@@ -576,30 +582,32 @@ describe("Facebook Marketplace Scraper Core Tests", () => {
});
test("preserves free listings through the public fetch entrypoint", async () => {
const mockSearchHtml = `<html><body><script>"XCometMarketplaceSearchController"</script><script>${JSON.stringify({
payload: {
resultGroups: [
{
edges: [
{
node: {
listing: {
id: "free-1",
marketplace_listing_title: "Free Chair",
listing_price: {
amount: "0.00",
formatted_amount: "FREE",
currency: "CAD",
const mockSearchHtml = `<html><body><script>"XCometMarketplaceSearchController"</script><script>${JSON.stringify(
{
payload: {
resultGroups: [
{
edges: [
{
node: {
listing: {
id: "free-1",
marketplace_listing_title: "Free Chair",
listing_price: {
amount: "0.00",
formatted_amount: "FREE",
currency: "CAD",
},
is_live: true,
},
is_live: true,
},
},
},
],
},
],
],
},
],
},
},
})}</script></body></html>`;
)}</script></body></html>`;
global.fetch = mock(() =>
Promise.resolve({
@@ -626,30 +634,32 @@ describe("Facebook Marketplace Scraper Core Tests", () => {
});
test("does not start a progress bar when stdout is not a TTY", async () => {
const mockSearchHtml = `<html><body><script>"XCometMarketplaceSearchController"</script><script>${JSON.stringify({
payload: {
resultGroups: [
{
edges: [
{
node: {
listing: {
id: "1",
marketplace_listing_title: "Chair Listing",
listing_price: {
amount: "120.00",
formatted_amount: "CA$120",
currency: "CAD",
const mockSearchHtml = `<html><body><script>"XCometMarketplaceSearchController"</script><script>${JSON.stringify(
{
payload: {
resultGroups: [
{
edges: [
{
node: {
listing: {
id: "1",
marketplace_listing_title: "Chair Listing",
listing_price: {
amount: "120.00",
formatted_amount: "CA$120",
currency: "CAD",
},
is_live: true,
},
is_live: true,
},
},
},
],
},
],
],
},
],
},
},
})}</script></body></html>`;
)}</script></body></html>`;
process.stdout.isTTY = false;
const startSpy = mock(() => {});
@@ -688,58 +698,60 @@ describe("Facebook Marketplace Scraper Core Tests", () => {
});
test("returns results and unstableResults when unstable mode is enabled", async () => {
const mockSearchHtml = `<html><body><script>"XCometMarketplaceSearchController"</script><script>${JSON.stringify({
payload: {
resultGroups: [
{
edges: [
{
node: {
listing: {
id: "1",
marketplace_listing_title: "Stable Chair Listing",
listing_price: {
amount: "100.00",
formatted_amount: "CA$100",
currency: "CAD",
const mockSearchHtml = `<html><body><script>"XCometMarketplaceSearchController"</script><script>${JSON.stringify(
{
payload: {
resultGroups: [
{
edges: [
{
node: {
listing: {
id: "1",
marketplace_listing_title: "Stable Chair Listing",
listing_price: {
amount: "100.00",
formatted_amount: "CA$100",
currency: "CAD",
},
is_live: true,
},
is_live: true,
},
},
},
{
node: {
listing: {
id: "2",
marketplace_listing_title: "Another Stable Chair",
listing_price: {
amount: "110.00",
formatted_amount: "CA$110",
currency: "CAD",
{
node: {
listing: {
id: "2",
marketplace_listing_title: "Another Stable Chair",
listing_price: {
amount: "110.00",
formatted_amount: "CA$110",
currency: "CAD",
},
is_live: true,
},
is_live: true,
},
},
},
{
node: {
listing: {
id: "3",
marketplace_listing_title: "Suspiciously Cheap Chair",
listing_price: {
amount: "70.00",
formatted_amount: "CA$70",
currency: "CAD",
{
node: {
listing: {
id: "3",
marketplace_listing_title: "Suspiciously Cheap Chair",
listing_price: {
amount: "70.00",
formatted_amount: "CA$70",
currency: "CAD",
},
is_live: true,
},
is_live: true,
},
},
},
],
},
],
],
},
],
},
},
})}</script></body></html>`;
)}</script></body></html>`;
global.fetch = mock(() =>
Promise.resolve({
@@ -768,58 +780,61 @@ describe("Facebook Marketplace Scraper Core Tests", () => {
});
test("unstable mode classifies before the final MAX_ITEMS limit", async () => {
const mockSearchHtml = `<html><body><script>"XCometMarketplaceSearchController"</script><script>${JSON.stringify({
payload: {
resultGroups: [
{
edges: [
{
node: {
listing: {
id: "1",
marketplace_listing_title: "Boundary Stable Chair",
listing_price: {
amount: "100.00",
formatted_amount: "CA$100",
currency: "CAD",
const mockSearchHtml = `<html><body><script>"XCometMarketplaceSearchController"</script><script>${JSON.stringify(
{
payload: {
resultGroups: [
{
edges: [
{
node: {
listing: {
id: "1",
marketplace_listing_title: "Boundary Stable Chair",
listing_price: {
amount: "100.00",
formatted_amount: "CA$100",
currency: "CAD",
},
is_live: true,
},
is_live: true,
},
},
},
{
node: {
listing: {
id: "2",
marketplace_listing_title: "Second Boundary Stable Chair",
listing_price: {
amount: "110.00",
formatted_amount: "CA$110",
currency: "CAD",
{
node: {
listing: {
id: "2",
marketplace_listing_title:
"Second Boundary Stable Chair",
listing_price: {
amount: "110.00",
formatted_amount: "CA$110",
currency: "CAD",
},
is_live: true,
},
is_live: true,
},
},
},
{
node: {
listing: {
id: "3",
marketplace_listing_title: "Past Boundary Cheap Chair",
listing_price: {
amount: "70.00",
formatted_amount: "CA$70",
currency: "CAD",
{
node: {
listing: {
id: "3",
marketplace_listing_title: "Past Boundary Cheap Chair",
listing_price: {
amount: "70.00",
formatted_amount: "CA$70",
currency: "CAD",
},
is_live: true,
},
is_live: true,
},
},
},
],
},
],
],
},
],
},
},
})}</script></body></html>`;
)}</script></body></html>`;
global.fetch = mock(() =>
Promise.resolve({
@@ -869,7 +884,10 @@ describe("Facebook Marketplace Scraper Core Tests", () => {
},
redacted_description: { text: "Solid wood chair" },
location_text: { text: "Toronto, ON" },
marketplace_listing_seller: { id: "seller-1", name: "Alex" },
marketplace_listing_seller: {
id: "seller-1",
name: "Alex",
},
condition: "USED",
is_live: true,
},

View File

@@ -1,5 +1,7 @@
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
import fetchFacebookItems, { fetchFacebookItem } from "../src/scrapers/facebook";
import fetchFacebookItems, {
fetchFacebookItem,
} from "../src/scrapers/facebook";
// Mock fetch globally
const originalFetch = global.fetch;
@@ -27,35 +29,37 @@ describe("Facebook Marketplace Scraper Integration Tests", () => {
describe("Main Search Function", () => {
test("should successfully fetch search results", async () => {
const mockSearchHtml = `<html><body><script>"XCometMarketplaceSearchController"</script><script>${JSON.stringify({
payload: {
resultGroups: [
{
edges: [
{
node: {
listing: {
id: "1",
marketplace_listing_title: "iPhone 13",
listing_price: {
amount: "500.00",
formatted_amount: "CA$500",
currency: "CAD",
},
location: {
reverse_geocode: {
city_page: { display_name: "Toronto" },
const mockSearchHtml = `<html><body><script>"XCometMarketplaceSearchController"</script><script>${JSON.stringify(
{
payload: {
resultGroups: [
{
edges: [
{
node: {
listing: {
id: "1",
marketplace_listing_title: "iPhone 13",
listing_price: {
amount: "500.00",
formatted_amount: "CA$500",
currency: "CAD",
},
location: {
reverse_geocode: {
city_page: { display_name: "Toronto" },
},
},
is_live: true,
},
is_live: true,
},
},
},
],
},
],
],
},
],
},
},
})}</script></body></html>`;
)}</script></body></html>`;
global.fetch = mock(() =>
Promise.resolve({

View File

@@ -1,12 +1,12 @@
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
import {
buildSearchUrl,
default as fetchKijijiItems,
type DetailedListing,
default as fetchKijijiItems,
NetworkError,
parseSearch,
parseDetailedListing,
ParseError,
parseDetailedListing,
parseSearch,
RateLimitError,
resolveCategoryId,
resolveLocationId,
@@ -282,7 +282,8 @@ describe("fetchKijijiItems", () => {
if (url.endsWith("/v-low/k0l0")) {
return Promise.resolve({
ok: true,
text: () => Promise.resolve(listingHtml("Low Listing", 7000, "v-low/k0l0")),
text: () =>
Promise.resolve(listingHtml("Low Listing", 7000, "v-low/k0l0")),
headers: { get: () => null },
url,
});
@@ -291,7 +292,8 @@ describe("fetchKijijiItems", () => {
if (url.endsWith("/v-mid/k0l0")) {
return Promise.resolve({
ok: true,
text: () => Promise.resolve(listingHtml("Mid Listing", 9000, "v-mid/k0l0")),
text: () =>
Promise.resolve(listingHtml("Mid Listing", 9000, "v-mid/k0l0")),
headers: { get: () => null },
url,
});
@@ -300,7 +302,8 @@ describe("fetchKijijiItems", () => {
if (url.endsWith("/v-high/k0l0")) {
return Promise.resolve({
ok: true,
text: () => Promise.resolve(listingHtml("High Listing", 12000, "v-high/k0l0")),
text: () =>
Promise.resolve(listingHtml("High Listing", 12000, "v-high/k0l0")),
headers: { get: () => null },
url,
});
@@ -534,9 +537,18 @@ describe("fetchKijijiItems", () => {
props: {
pageProps: {
__APOLLO_STATE__: {
"Listing:1": { url: "/v-stable-one/k0l0", title: "Stable Listing One" },
"Listing:2": { url: "/v-stable-two/k0l0", title: "Stable Listing Two" },
"Listing:3": { url: "/v-unstable/k0l0", title: "Unstable Listing" },
"Listing:1": {
url: "/v-stable-one/k0l0",
title: "Stable Listing One",
},
"Listing:2": {
url: "/v-stable-two/k0l0",
title: "Stable Listing Two",
},
"Listing:3": {
url: "/v-unstable/k0l0",
title: "Unstable Listing",
},
},
},
},
@@ -582,7 +594,10 @@ describe("fetchKijijiItems", () => {
if (url.endsWith("/v-stable-one/k0l0")) {
return Promise.resolve({
ok: true,
text: () => Promise.resolve(listingHtml("Stable Listing One", 10000, "v-stable-one/k0l0")),
text: () =>
Promise.resolve(
listingHtml("Stable Listing One", 10000, "v-stable-one/k0l0"),
),
headers: { get: () => null },
url,
});
@@ -591,7 +606,10 @@ describe("fetchKijijiItems", () => {
if (url.endsWith("/v-stable-two/k0l0")) {
return Promise.resolve({
ok: true,
text: () => Promise.resolve(listingHtml("Stable Listing Two", 11000, "v-stable-two/k0l0")),
text: () =>
Promise.resolve(
listingHtml("Stable Listing Two", 11000, "v-stable-two/k0l0"),
),
headers: { get: () => null },
url,
});
@@ -600,7 +618,10 @@ describe("fetchKijijiItems", () => {
if (url.endsWith("/v-unstable/k0l0")) {
return Promise.resolve({
ok: true,
text: () => Promise.resolve(listingHtml("Unstable Listing", 7000, "v-unstable/k0l0")),
text: () =>
Promise.resolve(
listingHtml("Unstable Listing", 7000, "v-unstable/k0l0"),
),
headers: { get: () => null },
url,
});
@@ -635,10 +656,22 @@ describe("fetchKijijiItems", () => {
props: {
pageProps: {
__APOLLO_STATE__: {
"Listing:1": { url: "/v-stable-one/k0l0", title: "Stable Listing One" },
"Listing:2": { url: "/v-stable-two/k0l0", title: "Stable Listing Two" },
"Listing:3": { url: "/v-out-of-range-high/k0l0", title: "Out Of Range High" },
"Listing:4": { url: "/v-out-of-range-low/k0l0", title: "Out Of Range Low" },
"Listing:1": {
url: "/v-stable-one/k0l0",
title: "Stable Listing One",
},
"Listing:2": {
url: "/v-stable-two/k0l0",
title: "Stable Listing Two",
},
"Listing:3": {
url: "/v-out-of-range-high/k0l0",
title: "Out Of Range High",
},
"Listing:4": {
url: "/v-out-of-range-low/k0l0",
title: "Out Of Range Low",
},
},
},
},
@@ -672,7 +705,11 @@ describe("fetchKijijiItems", () => {
global.fetch = mock((input: string | URL | Request) => {
const url = typeof input === "string" ? input : input.toString();
if (url.includes("/k0c0l1700272") && url.includes("priceMin=80") && url.includes("priceMax=150")) {
if (
url.includes("/k0c0l1700272") &&
url.includes("priceMin=80") &&
url.includes("priceMax=150")
) {
return Promise.resolve({
ok: true,
text: () => Promise.resolve(searchHtml),
@@ -684,7 +721,10 @@ describe("fetchKijijiItems", () => {
if (url.endsWith("/v-stable-one/k0l0")) {
return Promise.resolve({
ok: true,
text: () => Promise.resolve(listingHtml("Stable Listing One", 10000, "v-stable-one/k0l0")),
text: () =>
Promise.resolve(
listingHtml("Stable Listing One", 10000, "v-stable-one/k0l0"),
),
headers: { get: () => null },
url,
});
@@ -693,7 +733,10 @@ describe("fetchKijijiItems", () => {
if (url.endsWith("/v-stable-two/k0l0")) {
return Promise.resolve({
ok: true,
text: () => Promise.resolve(listingHtml("Stable Listing Two", 11000, "v-stable-two/k0l0")),
text: () =>
Promise.resolve(
listingHtml("Stable Listing Two", 11000, "v-stable-two/k0l0"),
),
headers: { get: () => null },
url,
});
@@ -702,7 +745,14 @@ describe("fetchKijijiItems", () => {
if (url.endsWith("/v-out-of-range-high/k0l0")) {
return Promise.resolve({
ok: true,
text: () => Promise.resolve(listingHtml("Out Of Range High", 20000, "v-out-of-range-high/k0l0")),
text: () =>
Promise.resolve(
listingHtml(
"Out Of Range High",
20000,
"v-out-of-range-high/k0l0",
),
),
headers: { get: () => null },
url,
});
@@ -711,7 +761,10 @@ describe("fetchKijijiItems", () => {
if (url.endsWith("/v-out-of-range-low/k0l0")) {
return Promise.resolve({
ok: true,
text: () => Promise.resolve(listingHtml("Out Of Range Low", 7000, "v-out-of-range-low/k0l0")),
text: () =>
Promise.resolve(
listingHtml("Out Of Range Low", 7000, "v-out-of-range-low/k0l0"),
),
headers: { get: () => null },
url,
});

View File

@@ -31,8 +31,13 @@ describe("classifyUnstableListings", () => {
const buckets = classifyUnstableListings(listings);
expect(buckets.results.map((listing) => listing.id)).toEqual(["stable-1", "stable-2"]);
expect(buckets.unstableResults.map((listing) => listing.id)).toEqual(["unstable"]);
expect(buckets.results.map((listing) => listing.id)).toEqual([
"stable-1",
"stable-2",
]);
expect(buckets.unstableResults.map((listing) => listing.id)).toEqual([
"unstable",
]);
});
test("uses the midpoint median for even-sized priced inputs", () => {
@@ -45,8 +50,14 @@ describe("classifyUnstableListings", () => {
const buckets = classifyUnstableListings(listings);
expect(buckets.results.map((listing) => listing.id)).toEqual(["mid-low", "mid-high", "high"]);
expect(buckets.unstableResults.map((listing) => listing.id)).toEqual(["low"]);
expect(buckets.results.map((listing) => listing.id)).toEqual([
"mid-low",
"mid-high",
"high",
]);
expect(buckets.unstableResults.map((listing) => listing.id)).toEqual([
"low",
]);
});
test("keeps non-positive prices in results and excludes them from the median input", () => {
@@ -66,7 +77,9 @@ describe("classifyUnstableListings", () => {
"stable-1",
"stable-2",
]);
expect(buckets.unstableResults.map((listing) => listing.id)).toEqual(["unstable"]);
expect(buckets.unstableResults.map((listing) => listing.id)).toEqual([
"unstable",
]);
});
test("returns all listings in results when fewer than two valid prices are present", () => {
@@ -78,7 +91,11 @@ describe("classifyUnstableListings", () => {
const buckets = classifyUnstableListings(listings);
expect(buckets.results.map((listing) => listing.id)).toEqual(["zero", "negative", "only-valid"]);
expect(buckets.results.map((listing) => listing.id)).toEqual([
"zero",
"negative",
"only-valid",
]);
expect(buckets.unstableResults).toEqual([]);
});
});