fix: tighten scraper type contracts
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
||||
import type { EbayListingDetails } from "../src/scrapers/ebay";
|
||||
import fetchEbayItems from "../src/scrapers/ebay";
|
||||
import type { UnstableListingBuckets } from "../src/types/common";
|
||||
|
||||
type Assert<T extends true> = T;
|
||||
type IsExact<T, U> =
|
||||
@@ -11,9 +12,18 @@ type IsExact<T, U> =
|
||||
: false;
|
||||
|
||||
const getDefaultEbayItems = async () => fetchEbayItems("laptop");
|
||||
const getUnstableEbayItems = async (): Promise<
|
||||
UnstableListingBuckets<EbayListingDetails>
|
||||
> => fetchEbayItems("laptop", 1000, {}, { hideUnstableResults: true });
|
||||
type _EbayDefaultReturn = Assert<
|
||||
IsExact<Awaited<ReturnType<typeof getDefaultEbayItems>>, EbayListingDetails[]>
|
||||
>;
|
||||
type _EbayUnstableReturn = Assert<
|
||||
IsExact<
|
||||
Awaited<ReturnType<typeof getUnstableEbayItems>>,
|
||||
UnstableListingBuckets<EbayListingDetails>
|
||||
>
|
||||
>;
|
||||
|
||||
const originalFetch = global.fetch;
|
||||
const originalWarn = console.warn;
|
||||
@@ -199,6 +209,32 @@ describe("eBay Scraper Cookie Handling", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
test("treats US dollar prices as USD", async () => {
|
||||
global.fetch = mock(() =>
|
||||
Promise.resolve({
|
||||
ok: true,
|
||||
text: () =>
|
||||
Promise.resolve(`
|
||||
<html><body>
|
||||
<li class="s-item">
|
||||
<a href="/itm/123"></a>
|
||||
<h3>Stable Laptop Bundle</h3>
|
||||
<span class="s-item__price">US $123.45</span>
|
||||
</li>
|
||||
</body></html>
|
||||
`),
|
||||
}),
|
||||
) as typeof fetch;
|
||||
|
||||
const results = await fetchEbayItems("laptop", 1000);
|
||||
|
||||
expect(results).toEqual([
|
||||
expect.objectContaining({
|
||||
listingPrice: expect.objectContaining({ currency: "USD", cents: 12345 }),
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
test("prefers the discounted Canadian-formatted price", async () => {
|
||||
global.fetch = mock(() =>
|
||||
Promise.resolve({
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
parseFacebookCookieString,
|
||||
parseFacebookItem,
|
||||
} from "../src/scrapers/facebook";
|
||||
import type { UnstableListingBuckets } from "../src/types/common";
|
||||
import { formatCookiesForHeader } from "../src/utils/cookies";
|
||||
import { formatCentsToCurrency } from "../src/utils/format";
|
||||
|
||||
@@ -24,9 +25,18 @@ type IsExact<T, U> =
|
||||
: false;
|
||||
|
||||
const getDefaultFacebookItems = async () => fetchFacebookItems("chair");
|
||||
const getUnstableFacebookItems = async (): Promise<
|
||||
UnstableListingBuckets<FacebookListingDetails>
|
||||
> => fetchFacebookItems("chair", 1, "toronto", 25, { hideUnstableResults: true });
|
||||
type _FacebookDefaultReturn = Assert<
|
||||
IsExact<Awaited<ReturnType<typeof getDefaultFacebookItems>>, FacebookListingDetails[]>
|
||||
>;
|
||||
type _FacebookUnstableReturn = Assert<
|
||||
IsExact<
|
||||
Awaited<ReturnType<typeof getUnstableFacebookItems>>,
|
||||
UnstableListingBuckets<FacebookListingDetails>
|
||||
>
|
||||
>;
|
||||
|
||||
// Mock fetch globally
|
||||
const originalFetch = global.fetch;
|
||||
@@ -1606,6 +1616,37 @@ describe("Facebook Marketplace Scraper Core Tests", () => {
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
test("keeps valid free search listings", () => {
|
||||
const ads = [
|
||||
{
|
||||
node: {
|
||||
listing: {
|
||||
id: "free-item",
|
||||
marketplace_listing_title: "Free Chair",
|
||||
listing_price: {
|
||||
amount: "0.00",
|
||||
formatted_amount: "FREE",
|
||||
currency: "CAD",
|
||||
},
|
||||
is_live: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const results = parseFacebookAds(ads);
|
||||
|
||||
expect(results).toEqual([
|
||||
expect.objectContaining({
|
||||
title: "Free Chair",
|
||||
listingPrice: expect.objectContaining({
|
||||
cents: 0,
|
||||
amountFormatted: "FREE",
|
||||
}),
|
||||
}),
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
default as fetchKijijiItems,
|
||||
type DetailedListing,
|
||||
NetworkError,
|
||||
parseSearch,
|
||||
parseDetailedListing,
|
||||
ParseError,
|
||||
RateLimitError,
|
||||
@@ -11,6 +12,7 @@ import {
|
||||
resolveLocationId,
|
||||
ValidationError,
|
||||
} from "../src/scrapers/kijiji";
|
||||
import type { UnstableListingBuckets } from "../src/types/common";
|
||||
|
||||
type Assert<T extends true> = T;
|
||||
type IsExact<T, U> =
|
||||
@@ -21,9 +23,26 @@ type IsExact<T, U> =
|
||||
: false;
|
||||
|
||||
const getDefaultKijijiItems = async () => fetchKijijiItems("phone");
|
||||
const getUnstableKijijiItems = async (): Promise<
|
||||
UnstableListingBuckets<DetailedListing>
|
||||
> =>
|
||||
fetchKijijiItems(
|
||||
"phone",
|
||||
1000,
|
||||
"https://www.kijiji.ca",
|
||||
{},
|
||||
{},
|
||||
{ hideUnstableResults: true },
|
||||
);
|
||||
type _KijijiDefaultReturn = Assert<
|
||||
IsExact<Awaited<ReturnType<typeof getDefaultKijijiItems>>, DetailedListing[]>
|
||||
>;
|
||||
type _KijijiUnstableReturn = Assert<
|
||||
IsExact<
|
||||
Awaited<ReturnType<typeof getUnstableKijijiItems>>,
|
||||
UnstableListingBuckets<DetailedListing>
|
||||
>
|
||||
>;
|
||||
|
||||
const originalFetch = global.fetch;
|
||||
|
||||
@@ -667,3 +686,37 @@ describe("fetchKijijiItems", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("parseSearch", () => {
|
||||
test("ignores SearchListingCard noise keys", () => {
|
||||
const html = `
|
||||
<html>
|
||||
<script id="__NEXT_DATA__" type="application/json">
|
||||
${JSON.stringify({
|
||||
props: {
|
||||
pageProps: {
|
||||
__APOLLO_STATE__: {
|
||||
"SearchListingCard:1": {
|
||||
url: "/v-card-noise/k0l0",
|
||||
title: "Card Noise",
|
||||
},
|
||||
"Listing:1": {
|
||||
url: "/v-real-result/k0l0",
|
||||
title: "Real Result",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})}
|
||||
</script>
|
||||
</html>
|
||||
`;
|
||||
|
||||
expect(parseSearch(html, "https://www.kijiji.ca")).toEqual([
|
||||
{
|
||||
listingLink: "https://www.kijiji.ca/v-real-result/k0l0",
|
||||
name: "Real Result",
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user