fix: align marketplace price filter parsing

This commit is contained in:
2026-04-23 11:14:57 -04:00
parent 10c2856bf6
commit 0f77155c8d
4 changed files with 48 additions and 7 deletions

View File

@@ -38,7 +38,7 @@ export interface EbayListingDetails {
address?: string | null; address?: string | null;
} }
const EBAY_PRICE_TEXT_RE = /^(?:\s*(?:CA|C)\s*\$|\s*[$£¥])/u; const EBAY_PRICE_TEXT_RE = /^(?:\s*(?:CA|C|US)\s*\$|\s*[$£¥])/u;
function canonicalizeEbayItemUrl(url: string): string { function canonicalizeEbayItemUrl(url: string): string {
try { try {

View File

@@ -218,6 +218,10 @@ function normalizeLookupKey(value: string): string {
return value.toLowerCase().replace(/[\s-]+/g, "-"); return value.toLowerCase().replace(/[\s-]+/g, "-");
} }
function centsToKijijiPriceParam(cents: number): number {
return Math.floor(cents / 100);
}
/** /**
* Resolve location ID from name or return numeric ID * Resolve location ID from name or return numeric ID
*/ */
@@ -293,9 +297,13 @@ export function buildSearchUrl(
: "relevancyDesc"; : "relevancyDesc";
const sortOrder = options.sortOrder === "asc" ? "ASC" : "DESC"; const sortOrder = options.sortOrder === "asc" ? "ASC" : "DESC";
const priceMinParam = const priceMinParam =
typeof options.priceMin === "number" ? `&priceMin=${options.priceMin}` : ""; typeof options.priceMin === "number"
? `&priceMin=${centsToKijijiPriceParam(options.priceMin)}`
: "";
const priceMaxParam = const priceMaxParam =
typeof options.priceMax === "number" ? `&priceMax=${options.priceMax}` : ""; typeof options.priceMax === "number"
? `&priceMax=${centsToKijijiPriceParam(options.priceMax)}`
: "";
const pageParam = const pageParam =
options.page && options.page > 1 ? `&page=${options.page}` : ""; options.page && options.page > 1 ? `&page=${options.page}` : "";

View File

@@ -360,6 +360,39 @@ describe("eBay Scraper Cookie Handling", () => {
]); ]);
}); });
test("prefers discounted US dollar prices over original prices", 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">
<s>US $150.00</s>
<span>US $100.00</span>
</span>
</li>
</body></html>
`),
}),
) as typeof fetch;
const results = await fetchEbayItems("laptop", 1000);
expect(results).toEqual([
expect.objectContaining({
listingPrice: expect.objectContaining({
amountFormatted: "US $100.00",
cents: 10000,
currency: "USD",
}),
}),
]);
});
test("accepts higher fallback prices without price classes", async () => { test("accepts higher fallback prices without price classes", async () => {
global.fetch = mock(() => global.fetch = mock(() =>
Promise.resolve({ Promise.resolve({

View File

@@ -163,8 +163,8 @@ describe("URL Construction", () => {
priceMax: 10000, priceMax: 10000,
}); });
expect(url).toContain("priceMin=8000"); expect(url).toContain("priceMin=80");
expect(url).toContain("priceMax=10000"); expect(url).toContain("priceMax=100");
}); });
test("should handle string location/category inputs", () => { test("should handle string location/category inputs", () => {
@@ -570,7 +570,7 @@ describe("fetchKijijiItems", () => {
global.fetch = mock((input: string | URL | Request) => { global.fetch = mock((input: string | URL | Request) => {
const url = typeof input === "string" ? input : input.toString(); const url = typeof input === "string" ? input : input.toString();
if (url.includes("/k0c0l1700272") && url.includes("priceMin=8000")) { if (url.includes("/k0c0l1700272") && url.includes("priceMin=80")) {
return Promise.resolve({ return Promise.resolve({
ok: true, ok: true,
text: () => Promise.resolve(searchHtml), text: () => Promise.resolve(searchHtml),
@@ -672,7 +672,7 @@ describe("fetchKijijiItems", () => {
global.fetch = mock((input: string | URL | Request) => { global.fetch = mock((input: string | URL | Request) => {
const url = typeof input === "string" ? input : input.toString(); const url = typeof input === "string" ? input : input.toString();
if (url.includes("/k0c0l1700272") && url.includes("priceMin=8000") && url.includes("priceMax=15000")) { if (url.includes("/k0c0l1700272") && url.includes("priceMin=80") && url.includes("priceMax=150")) {
return Promise.resolve({ return Promise.resolve({
ok: true, ok: true,
text: () => Promise.resolve(searchHtml), text: () => Promise.resolve(searchHtml),