fix(core): handle partial listing data
This commit is contained in:
@@ -274,7 +274,7 @@ function parseEbayListings(
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Filter to only elements that actually contain prices (not labels)
|
// Filter to only elements that actually contain prices (not labels)
|
||||||
const actualPrices: HTMLElement[] = [];
|
const actualPrices: Element[] = [];
|
||||||
for (const el of allPriceElements) {
|
for (const el of allPriceElements) {
|
||||||
const text = el.textContent?.trim();
|
const text = el.textContent?.trim();
|
||||||
if (text && EBAY_PRICE_TEXT_RE.test(text) && text.length < 50) {
|
if (text && EBAY_PRICE_TEXT_RE.test(text) && text.length < 50) {
|
||||||
@@ -301,11 +301,10 @@ function parseEbayListings(
|
|||||||
|
|
||||||
if (nonStrikethroughPrices.length > 0) {
|
if (nonStrikethroughPrices.length > 0) {
|
||||||
// Use the first non-strikethrough price (sale price)
|
// Use the first non-strikethrough price (sale price)
|
||||||
priceElement = nonStrikethroughPrices[0];
|
priceElement = nonStrikethroughPrices[0] ?? null;
|
||||||
} else {
|
} else {
|
||||||
// Fallback: use the last price (likely the most current)
|
// Fallback: use the last price (likely the most current)
|
||||||
const lastPrice = actualPrices[actualPrices.length - 1];
|
priceElement = actualPrices[actualPrices.length - 1] ?? null;
|
||||||
priceElement = lastPrice;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ interface FacebookMarketplaceItem {
|
|||||||
__typename: "GroupCommerceProductItem";
|
__typename: "GroupCommerceProductItem";
|
||||||
|
|
||||||
// Listing content
|
// Listing content
|
||||||
marketplace_listing_title: string;
|
marketplace_listing_title?: string;
|
||||||
redacted_description?: {
|
redacted_description?: {
|
||||||
text: string;
|
text: string;
|
||||||
};
|
};
|
||||||
@@ -99,7 +99,7 @@ interface FacebookMarketplaceItem {
|
|||||||
listing_price?: {
|
listing_price?: {
|
||||||
amount: string;
|
amount: string;
|
||||||
currency: string;
|
currency: string;
|
||||||
amount_with_offset: string;
|
amount_with_offset?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Location
|
// Location
|
||||||
@@ -127,9 +127,9 @@ interface FacebookMarketplaceItem {
|
|||||||
|
|
||||||
// Seller information
|
// Seller information
|
||||||
marketplace_listing_seller?: {
|
marketplace_listing_seller?: {
|
||||||
__typename: "User";
|
__typename?: "User";
|
||||||
id: string;
|
id?: string;
|
||||||
name: string;
|
name?: string;
|
||||||
profile_picture?: {
|
profile_picture?: {
|
||||||
uri: string;
|
uri: string;
|
||||||
};
|
};
|
||||||
@@ -1321,6 +1321,14 @@ export async function fetchFacebookItem(
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (itemResponseUrl.includes("unavailable_product=1")) {
|
||||||
|
logExtractionMetrics(false, itemId);
|
||||||
|
console.warn(
|
||||||
|
`Item ${itemId} appears to be sold or removed from marketplace.`,
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const itemData = extractFacebookItemData(itemHtml);
|
const itemData = extractFacebookItemData(itemHtml);
|
||||||
|
|
||||||
if (classification.unavailable && !itemData) {
|
if (classification.unavailable && !itemData) {
|
||||||
|
|||||||
@@ -1,56 +1,53 @@
|
|||||||
/** Custom error class for HTTP-related failures */
|
/** Custom error class for HTTP-related failures */
|
||||||
export class HttpError extends Error {
|
export class HttpError extends Error {
|
||||||
|
override name = "HttpError";
|
||||||
constructor(
|
constructor(
|
||||||
message: string,
|
message: string,
|
||||||
public readonly statusCode: number,
|
public readonly statusCode: number,
|
||||||
public readonly url?: string,
|
public readonly url?: string,
|
||||||
) {
|
) {
|
||||||
super(message);
|
super(message);
|
||||||
this.name = "HttpError";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Error class for network failures (timeouts, connection issues) */
|
/** Error class for network failures (timeouts, connection issues) */
|
||||||
export class NetworkError extends Error {
|
export class NetworkError extends Error {
|
||||||
|
override name = "NetworkError";
|
||||||
constructor(
|
constructor(
|
||||||
message: string,
|
message: string,
|
||||||
public readonly url: string,
|
public readonly url: string,
|
||||||
public readonly cause?: Error,
|
public override readonly cause?: Error,
|
||||||
) {
|
) {
|
||||||
super(message);
|
super(message);
|
||||||
this.name = "NetworkError";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Error class for parsing failures */
|
/** Error class for parsing failures */
|
||||||
export class ParseError extends Error {
|
export class ParseError extends Error {
|
||||||
|
override name = "ParseError";
|
||||||
constructor(
|
constructor(
|
||||||
message: string,
|
message: string,
|
||||||
public readonly data?: unknown,
|
public readonly data?: unknown,
|
||||||
) {
|
) {
|
||||||
super(message);
|
super(message);
|
||||||
this.name = "ParseError";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Error class for rate limiting */
|
/** Error class for rate limiting */
|
||||||
export class RateLimitError extends Error {
|
export class RateLimitError extends Error {
|
||||||
|
override name = "RateLimitError";
|
||||||
constructor(
|
constructor(
|
||||||
message: string,
|
message: string,
|
||||||
public readonly url: string,
|
public readonly url: string,
|
||||||
public readonly resetTime?: number,
|
public readonly resetTime?: number,
|
||||||
) {
|
) {
|
||||||
super(message);
|
super(message);
|
||||||
this.name = "RateLimitError";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Error class for validation failures */
|
/** Error class for validation failures */
|
||||||
export class ValidationError extends Error {
|
export class ValidationError extends Error {
|
||||||
constructor(message: string) {
|
override name = "ValidationError";
|
||||||
super(message);
|
|
||||||
this.name = "ValidationError";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Type guard to check if a value is a record (object) */
|
/** Type guard to check if a value is a record (object) */
|
||||||
|
|||||||
@@ -1,21 +1,29 @@
|
|||||||
import type { ListingDetails, UnstableListingBuckets } from "../types/common";
|
import type { UnstableListingBuckets } from "../types/common";
|
||||||
|
|
||||||
|
interface HasListingPrice {
|
||||||
|
listingPrice?: { cents?: number } | null;
|
||||||
|
}
|
||||||
|
|
||||||
function getMedian(values: number[]): number {
|
function getMedian(values: number[]): number {
|
||||||
const middleIndex = Math.floor(values.length / 2);
|
const middleIndex = Math.floor(values.length / 2);
|
||||||
|
|
||||||
if (values.length % 2 === 0) {
|
if (values.length % 2 === 0) {
|
||||||
return (values[middleIndex - 1] + values[middleIndex]) / 2;
|
const left = values[middleIndex - 1] ?? 0;
|
||||||
|
const right = values[middleIndex] ?? 0;
|
||||||
|
return (left + right) / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
return values[middleIndex];
|
return values[middleIndex] ?? 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function classifyUnstableListings<T extends ListingDetails>(
|
export function classifyUnstableListings<T extends HasListingPrice>(
|
||||||
listings: T[],
|
listings: T[],
|
||||||
): UnstableListingBuckets<T> {
|
): UnstableListingBuckets<T> {
|
||||||
const validPrices = listings
|
const validPrices = listings
|
||||||
.map((listing) => listing.listingPrice.cents)
|
.map((listing) => listing.listingPrice?.cents)
|
||||||
.filter((price) => Number.isFinite(price) && price > 0)
|
.filter(
|
||||||
|
(price): price is number => Number.isFinite(price) && (price ?? 0) > 0,
|
||||||
|
)
|
||||||
.sort((left, right) => left - right);
|
.sort((left, right) => left - right);
|
||||||
|
|
||||||
if (validPrices.length < 2) {
|
if (validPrices.length < 2) {
|
||||||
@@ -32,9 +40,13 @@ export function classifyUnstableListings<T extends ListingDetails>(
|
|||||||
};
|
};
|
||||||
|
|
||||||
for (const listing of listings) {
|
for (const listing of listings) {
|
||||||
const price = listing.listingPrice.cents;
|
const price = listing.listingPrice?.cents;
|
||||||
|
|
||||||
if (Number.isFinite(price) && price > 0 && price < threshold) {
|
if (
|
||||||
|
Number.isFinite(price) &&
|
||||||
|
(price ?? 0) > 0 &&
|
||||||
|
(price ?? 0) < threshold
|
||||||
|
) {
|
||||||
buckets.unstableResults.push(listing);
|
buckets.unstableResults.push(listing);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ describe("eBay Scraper Cookie Handling", () => {
|
|||||||
ok: true,
|
ok: true,
|
||||||
text: () => Promise.resolve("<html><body></body></html>"),
|
text: () => Promise.resolve("<html><body></body></html>"),
|
||||||
}),
|
}),
|
||||||
) as typeof fetch;
|
) as unknown as typeof fetch;
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@@ -51,7 +51,13 @@ describe("eBay Scraper Cookie Handling", () => {
|
|||||||
|
|
||||||
expect(global.fetch).toHaveBeenCalledTimes(1);
|
expect(global.fetch).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
const [, init] = (global.fetch as ReturnType<typeof mock>).mock.calls[0];
|
const firstFetchCall = (global.fetch as unknown as ReturnType<typeof mock>)
|
||||||
|
.mock.calls[0];
|
||||||
|
if (!firstFetchCall) {
|
||||||
|
throw new Error("Expected fetch to be called");
|
||||||
|
}
|
||||||
|
|
||||||
|
const [, init] = firstFetchCall;
|
||||||
const headers = (init as RequestInit).headers as Record<string, string>;
|
const headers = (init as RequestInit).headers as Record<string, string>;
|
||||||
|
|
||||||
expect(headers.Cookie).toBeUndefined();
|
expect(headers.Cookie).toBeUndefined();
|
||||||
@@ -75,7 +81,7 @@ describe("eBay Scraper Cookie Handling", () => {
|
|||||||
</body></html>
|
</body></html>
|
||||||
`),
|
`),
|
||||||
}),
|
}),
|
||||||
) as typeof fetch;
|
) as unknown as typeof fetch;
|
||||||
|
|
||||||
const results = await fetchEbayItems("laptop", 1000);
|
const results = await fetchEbayItems("laptop", 1000);
|
||||||
|
|
||||||
@@ -100,7 +106,7 @@ describe("eBay Scraper Cookie Handling", () => {
|
|||||||
</body></html>
|
</body></html>
|
||||||
`),
|
`),
|
||||||
}),
|
}),
|
||||||
) as typeof fetch;
|
) as unknown as typeof fetch;
|
||||||
|
|
||||||
const results = await fetchEbayItems("laptop", 1000);
|
const results = await fetchEbayItems("laptop", 1000);
|
||||||
|
|
||||||
@@ -130,7 +136,7 @@ describe("eBay Scraper Cookie Handling", () => {
|
|||||||
</body></html>
|
</body></html>
|
||||||
`),
|
`),
|
||||||
}),
|
}),
|
||||||
) as typeof fetch;
|
) as unknown as typeof fetch;
|
||||||
|
|
||||||
const results = await fetchEbayItems("laptop", 1000);
|
const results = await fetchEbayItems("laptop", 1000);
|
||||||
|
|
||||||
@@ -167,7 +173,7 @@ describe("eBay Scraper Cookie Handling", () => {
|
|||||||
</body></html>
|
</body></html>
|
||||||
`),
|
`),
|
||||||
}),
|
}),
|
||||||
) as typeof fetch;
|
) as unknown as typeof fetch;
|
||||||
|
|
||||||
const results = await fetchEbayItems("laptop", 1000);
|
const results = await fetchEbayItems("laptop", 1000);
|
||||||
|
|
||||||
@@ -199,7 +205,7 @@ describe("eBay Scraper Cookie Handling", () => {
|
|||||||
</body></html>
|
</body></html>
|
||||||
`),
|
`),
|
||||||
}),
|
}),
|
||||||
) as typeof fetch;
|
) as unknown as typeof fetch;
|
||||||
|
|
||||||
const results = await fetchEbayItems("laptop", 1000);
|
const results = await fetchEbayItems("laptop", 1000);
|
||||||
|
|
||||||
@@ -225,7 +231,7 @@ describe("eBay Scraper Cookie Handling", () => {
|
|||||||
</body></html>
|
</body></html>
|
||||||
`),
|
`),
|
||||||
}),
|
}),
|
||||||
) as typeof fetch;
|
) as unknown as typeof fetch;
|
||||||
|
|
||||||
const results = await fetchEbayItems("laptop", 1000);
|
const results = await fetchEbayItems("laptop", 1000);
|
||||||
|
|
||||||
@@ -254,7 +260,7 @@ describe("eBay Scraper Cookie Handling", () => {
|
|||||||
</body></html>
|
</body></html>
|
||||||
`),
|
`),
|
||||||
}),
|
}),
|
||||||
) as typeof fetch;
|
) as unknown as typeof fetch;
|
||||||
|
|
||||||
const results = await fetchEbayItems("laptop", 1000);
|
const results = await fetchEbayItems("laptop", 1000);
|
||||||
|
|
||||||
@@ -283,7 +289,7 @@ describe("eBay Scraper Cookie Handling", () => {
|
|||||||
</body></html>
|
</body></html>
|
||||||
`),
|
`),
|
||||||
}),
|
}),
|
||||||
) as typeof fetch;
|
) as unknown as typeof fetch;
|
||||||
|
|
||||||
const results = await fetchEbayItems("laptop", 1000);
|
const results = await fetchEbayItems("laptop", 1000);
|
||||||
|
|
||||||
@@ -317,7 +323,7 @@ describe("eBay Scraper Cookie Handling", () => {
|
|||||||
</body></html>
|
</body></html>
|
||||||
`),
|
`),
|
||||||
}),
|
}),
|
||||||
) as typeof fetch;
|
) as unknown as typeof fetch;
|
||||||
|
|
||||||
const results = await fetchEbayItems("bundle", 1000, {
|
const results = await fetchEbayItems("bundle", 1000, {
|
||||||
keywords: ["bundle"],
|
keywords: ["bundle"],
|
||||||
@@ -357,7 +363,7 @@ describe("eBay Scraper Cookie Handling", () => {
|
|||||||
</body></html>
|
</body></html>
|
||||||
`),
|
`),
|
||||||
}),
|
}),
|
||||||
) as typeof fetch;
|
) as unknown as typeof fetch;
|
||||||
|
|
||||||
const results = await fetchEbayItems("laptop", 1000);
|
const results = await fetchEbayItems("laptop", 1000);
|
||||||
|
|
||||||
@@ -389,7 +395,7 @@ describe("eBay Scraper Cookie Handling", () => {
|
|||||||
</body></html>
|
</body></html>
|
||||||
`),
|
`),
|
||||||
}),
|
}),
|
||||||
) as typeof fetch;
|
) as unknown as typeof fetch;
|
||||||
|
|
||||||
const results = await fetchEbayItems("laptop", 1000);
|
const results = await fetchEbayItems("laptop", 1000);
|
||||||
|
|
||||||
@@ -421,7 +427,7 @@ describe("eBay Scraper Cookie Handling", () => {
|
|||||||
</body></html>
|
</body></html>
|
||||||
`),
|
`),
|
||||||
}),
|
}),
|
||||||
) as typeof fetch;
|
) as unknown as typeof fetch;
|
||||||
|
|
||||||
const results = await fetchEbayItems("laptop", 1000);
|
const results = await fetchEbayItems("laptop", 1000);
|
||||||
|
|
||||||
@@ -451,7 +457,7 @@ describe("eBay Scraper Cookie Handling", () => {
|
|||||||
</body></html>
|
</body></html>
|
||||||
`),
|
`),
|
||||||
}),
|
}),
|
||||||
) as typeof fetch;
|
) as unknown as typeof fetch;
|
||||||
|
|
||||||
const results = await fetchEbayItems("bike", 1000);
|
const results = await fetchEbayItems("bike", 1000);
|
||||||
|
|
||||||
@@ -478,7 +484,7 @@ describe("eBay Scraper Cookie Handling", () => {
|
|||||||
</body></html>
|
</body></html>
|
||||||
`),
|
`),
|
||||||
}),
|
}),
|
||||||
) as typeof fetch;
|
) as unknown as typeof fetch;
|
||||||
|
|
||||||
const results = await fetchEbayItems("microphone", 1000, {
|
const results = await fetchEbayItems("microphone", 1000, {
|
||||||
keywords: ["microphone"],
|
keywords: ["microphone"],
|
||||||
@@ -510,7 +516,7 @@ describe("eBay Scraper Cookie Handling", () => {
|
|||||||
</body></html>
|
</body></html>
|
||||||
`),
|
`),
|
||||||
}),
|
}),
|
||||||
) as typeof fetch;
|
) as unknown as typeof fetch;
|
||||||
|
|
||||||
const results = await fetchEbayItems("laptop", 1000, {
|
const results = await fetchEbayItems("laptop", 1000, {
|
||||||
minPrice: 0,
|
minPrice: 0,
|
||||||
@@ -550,7 +556,7 @@ describe("eBay Scraper Cookie Handling", () => {
|
|||||||
</body></html>
|
</body></html>
|
||||||
`),
|
`),
|
||||||
}),
|
}),
|
||||||
) as typeof fetch;
|
) as unknown as typeof fetch;
|
||||||
|
|
||||||
const results = await fetchEbayItems(
|
const results = await fetchEbayItems(
|
||||||
"laptop",
|
"laptop",
|
||||||
@@ -595,7 +601,7 @@ describe("eBay Scraper Cookie Handling", () => {
|
|||||||
</body></html>
|
</body></html>
|
||||||
`),
|
`),
|
||||||
}),
|
}),
|
||||||
) as typeof fetch;
|
) as unknown as typeof fetch;
|
||||||
|
|
||||||
const results = await fetchEbayItems("laptop", 1000, { maxItems: 2 });
|
const results = await fetchEbayItems("laptop", 1000, { maxItems: 2 });
|
||||||
|
|
||||||
@@ -633,7 +639,7 @@ describe("eBay Scraper Cookie Handling", () => {
|
|||||||
</body></html>
|
</body></html>
|
||||||
`),
|
`),
|
||||||
}),
|
}),
|
||||||
) as typeof fetch;
|
) as unknown as typeof fetch;
|
||||||
|
|
||||||
const results = await fetchEbayItems(
|
const results = await fetchEbayItems(
|
||||||
"laptop",
|
"laptop",
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ describe("Facebook Marketplace Scraper Core Tests", () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
global.fetch = mock(() => {
|
global.fetch = mock(() => {
|
||||||
throw new Error("fetch should be mocked in individual tests");
|
throw new Error("fetch should be mocked in individual tests");
|
||||||
});
|
}) as unknown as typeof fetch;
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@@ -93,8 +93,8 @@ describe("Facebook Marketplace Scraper Core Tests", () => {
|
|||||||
const cookieString = "c_user=123%2B456; xs=abc%3Ddef";
|
const cookieString = "c_user=123%2B456; xs=abc%3Ddef";
|
||||||
const result = parseFacebookCookieString(cookieString);
|
const result = parseFacebookCookieString(cookieString);
|
||||||
|
|
||||||
expect(result[0].value).toBe("123+456");
|
expect(result[0]?.value).toBe("123+456");
|
||||||
expect(result[1].value).toBe("abc=def");
|
expect(result[1]?.value).toBe("abc=def");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should filter out malformed cookies", () => {
|
test("should filter out malformed cookies", () => {
|
||||||
@@ -115,10 +115,10 @@ describe("Facebook Marketplace Scraper Core Tests", () => {
|
|||||||
const result = parseFacebookCookieString(cookieString);
|
const result = parseFacebookCookieString(cookieString);
|
||||||
|
|
||||||
expect(result).toHaveLength(2);
|
expect(result).toHaveLength(2);
|
||||||
expect(result[0].name).toBe("c_user");
|
expect(result[0]?.name).toBe("c_user");
|
||||||
expect(result[0].value).toBe("123");
|
expect(result[0]?.value).toBe("123");
|
||||||
expect(result[1].name).toBe("xs");
|
expect(result[1]?.name).toBe("xs");
|
||||||
expect(result[1].value).toBe("abc");
|
expect(result[1]?.value).toBe("abc");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should load Facebook cookies from FACEBOOK_COOKIE env var", async () => {
|
test("should load Facebook cookies from FACEBOOK_COOKIE env var", async () => {
|
||||||
@@ -190,7 +190,7 @@ describe("Facebook Marketplace Scraper Core Tests", () => {
|
|||||||
get: () => null,
|
get: () => null,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
);
|
) as unknown as typeof fetch;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await fetchFacebookItem("123");
|
const result = await fetchFacebookItem("123");
|
||||||
@@ -214,7 +214,7 @@ describe("Facebook Marketplace Scraper Core Tests", () => {
|
|||||||
get: () => null,
|
get: () => null,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
);
|
) as unknown as typeof fetch;
|
||||||
|
|
||||||
const result = await fetchFacebookItem("nonexistent");
|
const result = await fetchFacebookItem("nonexistent");
|
||||||
expect(result).toBeNull();
|
expect(result).toBeNull();
|
||||||
@@ -274,7 +274,7 @@ describe("Facebook Marketplace Scraper Core Tests", () => {
|
|||||||
get: () => null,
|
get: () => null,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
}) as unknown as typeof fetch;
|
||||||
|
|
||||||
const _result = await fetchFacebookItem("123");
|
const _result = await fetchFacebookItem("123");
|
||||||
expect(attempts).toBe(2);
|
expect(attempts).toBe(2);
|
||||||
@@ -297,7 +297,7 @@ describe("Facebook Marketplace Scraper Core Tests", () => {
|
|||||||
},
|
},
|
||||||
text: () => Promise.resolve("Rate limited"),
|
text: () => Promise.resolve("Rate limited"),
|
||||||
});
|
});
|
||||||
});
|
}) as unknown as typeof fetch;
|
||||||
|
|
||||||
const result = await fetchFacebookItem("429-loop");
|
const result = await fetchFacebookItem("429-loop");
|
||||||
|
|
||||||
@@ -346,7 +346,7 @@ describe("Facebook Marketplace Scraper Core Tests", () => {
|
|||||||
get: () => null,
|
get: () => null,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
);
|
) as unknown as typeof fetch;
|
||||||
|
|
||||||
const result = await fetchFacebookItem("456");
|
const result = await fetchFacebookItem("456");
|
||||||
expect(result?.listingStatus).toBe("SOLD");
|
expect(result?.listingStatus).toBe("SOLD");
|
||||||
@@ -388,7 +388,7 @@ describe("Facebook Marketplace Scraper Core Tests", () => {
|
|||||||
get: () => null,
|
get: () => null,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
);
|
) as unknown as typeof fetch;
|
||||||
|
|
||||||
const result = await fetchFacebookItem("457");
|
const result = await fetchFacebookItem("457");
|
||||||
|
|
||||||
@@ -435,7 +435,7 @@ describe("Facebook Marketplace Scraper Core Tests", () => {
|
|||||||
get: () => null,
|
get: () => null,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
);
|
) as unknown as typeof fetch;
|
||||||
|
|
||||||
const result = await fetchFacebookItem("458");
|
const result = await fetchFacebookItem("458");
|
||||||
|
|
||||||
@@ -493,7 +493,7 @@ describe("Facebook Marketplace Scraper Core Tests", () => {
|
|||||||
get: () => null,
|
get: () => null,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
);
|
) as unknown as typeof fetch;
|
||||||
|
|
||||||
const result = await fetchFacebookItem("789");
|
const result = await fetchFacebookItem("789");
|
||||||
expect(result).not.toBeNull();
|
expect(result).not.toBeNull();
|
||||||
@@ -512,7 +512,7 @@ describe("Facebook Marketplace Scraper Core Tests", () => {
|
|||||||
get: () => null,
|
get: () => null,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
);
|
) as unknown as typeof fetch;
|
||||||
|
|
||||||
const result = await fetchFacebookItem("error");
|
const result = await fetchFacebookItem("error");
|
||||||
expect(result).toBeNull();
|
expect(result).toBeNull();
|
||||||
@@ -573,7 +573,7 @@ describe("Facebook Marketplace Scraper Core Tests", () => {
|
|||||||
get: () => null,
|
get: () => null,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
);
|
) as unknown as typeof fetch;
|
||||||
|
|
||||||
const results = await fetchFacebookItems("chair", 1, "toronto", 25);
|
const results = await fetchFacebookItems("chair", 1, "toronto", 25);
|
||||||
|
|
||||||
@@ -618,7 +618,7 @@ describe("Facebook Marketplace Scraper Core Tests", () => {
|
|||||||
get: () => null,
|
get: () => null,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
);
|
) as unknown as typeof fetch;
|
||||||
|
|
||||||
const results = await fetchFacebookItems("chair", 1, "toronto", 25);
|
const results = await fetchFacebookItems("chair", 1, "toronto", 25);
|
||||||
|
|
||||||
@@ -682,7 +682,7 @@ describe("Facebook Marketplace Scraper Core Tests", () => {
|
|||||||
get: () => null,
|
get: () => null,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
);
|
) as unknown as typeof fetch;
|
||||||
|
|
||||||
const results = await fetchFacebookItems("chair", 1, "toronto", 25);
|
const results = await fetchFacebookItems("chair", 1, "toronto", 25);
|
||||||
|
|
||||||
@@ -762,7 +762,7 @@ describe("Facebook Marketplace Scraper Core Tests", () => {
|
|||||||
get: () => null,
|
get: () => null,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
);
|
) as unknown as typeof fetch;
|
||||||
|
|
||||||
const results = await fetchFacebookItems("chair", 1, "toronto", 25, {
|
const results = await fetchFacebookItems("chair", 1, "toronto", 25, {
|
||||||
hideUnstableResults: true,
|
hideUnstableResults: true,
|
||||||
@@ -845,7 +845,7 @@ describe("Facebook Marketplace Scraper Core Tests", () => {
|
|||||||
get: () => null,
|
get: () => null,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
);
|
) as unknown as typeof fetch;
|
||||||
|
|
||||||
const results = await fetchFacebookItems("chair", 1, "toronto", 2, {
|
const results = await fetchFacebookItems("chair", 1, "toronto", 2, {
|
||||||
hideUnstableResults: true,
|
hideUnstableResults: true,
|
||||||
@@ -1132,7 +1132,7 @@ describe("Facebook Marketplace Scraper Core Tests", () => {
|
|||||||
const result = extractFacebookMarketplaceData(html);
|
const result = extractFacebookMarketplaceData(html);
|
||||||
expect(result).not.toBeNull();
|
expect(result).not.toBeNull();
|
||||||
expect(result).toHaveLength(2);
|
expect(result).toHaveLength(2);
|
||||||
expect(result?.[0].node.listing.marketplace_listing_title).toBe(
|
expect(result?.[0]?.node.listing.marketplace_listing_title).toBe(
|
||||||
"Item 1",
|
"Item 1",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -1153,11 +1153,11 @@ describe("Facebook Marketplace Scraper Core Tests", () => {
|
|||||||
const result = extractFacebookMarketplaceData(html);
|
const result = extractFacebookMarketplaceData(html);
|
||||||
expect(result).not.toBeNull();
|
expect(result).not.toBeNull();
|
||||||
expect(result).toHaveLength(1);
|
expect(result).toHaveLength(1);
|
||||||
expect(result?.[0].node.listing.id).toBe("987654321");
|
expect(result?.[0]?.node.listing.id).toBe("987654321");
|
||||||
expect(result?.[0].node.listing.marketplace_listing_title).toBe(
|
expect(result?.[0]?.node.listing.marketplace_listing_title).toBe(
|
||||||
"Vintage Bike",
|
"Vintage Bike",
|
||||||
);
|
);
|
||||||
expect(result?.[0].node.listing.listing_price).toEqual({
|
expect(result?.[0]?.node.listing.listing_price).toEqual({
|
||||||
amount: "120.00",
|
amount: "120.00",
|
||||||
formatted_amount: "CA$120",
|
formatted_amount: "CA$120",
|
||||||
currency: "CAD",
|
currency: "CAD",
|
||||||
@@ -1385,7 +1385,7 @@ describe("Facebook Marketplace Scraper Core Tests", () => {
|
|||||||
|
|
||||||
const ads = extractFacebookMarketplaceData(html);
|
const ads = extractFacebookMarketplaceData(html);
|
||||||
expect(ads).toHaveLength(1);
|
expect(ads).toHaveLength(1);
|
||||||
expect(ads?.[0].node.listing.marketplace_listing_title).toBe("Bike");
|
expect(ads?.[0]?.node.listing.marketplace_listing_title).toBe("Bike");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("prefers the strongest marketplace edge set when multiple edges arrays exist", () => {
|
test("prefers the strongest marketplace edge set when multiple edges arrays exist", () => {
|
||||||
@@ -1443,7 +1443,7 @@ describe("Facebook Marketplace Scraper Core Tests", () => {
|
|||||||
|
|
||||||
const ads = extractFacebookMarketplaceData(html);
|
const ads = extractFacebookMarketplaceData(html);
|
||||||
expect(ads).toHaveLength(1);
|
expect(ads).toHaveLength(1);
|
||||||
expect(ads?.[0].node.listing.id).toBe("right-1");
|
expect(ads?.[0]?.node.listing.id).toBe("right-1");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("rejects mixed edge arrays that contain non-listing entries", () => {
|
test("rejects mixed edge arrays that contain non-listing entries", () => {
|
||||||
@@ -1668,11 +1668,11 @@ describe("Facebook Marketplace Scraper Core Tests", () => {
|
|||||||
|
|
||||||
const results = parseFacebookAds(ads);
|
const results = parseFacebookAds(ads);
|
||||||
expect(results).toHaveLength(2);
|
expect(results).toHaveLength(2);
|
||||||
expect(results[0].title).toBe("Ad 1");
|
expect(results[0]?.title).toBe("Ad 1");
|
||||||
expect(results[0].listingPrice?.cents).toBe(5000);
|
expect(results[0]?.listingPrice?.cents).toBe(5000);
|
||||||
expect(results[0].address).toBe("Toronto");
|
expect(results[0]?.address).toBe("Toronto");
|
||||||
expect(results[1].title).toBe("Ad 2");
|
expect(results[1]?.title).toBe("Ad 2");
|
||||||
expect(results[1].address).toBe("Ottawa");
|
expect(results[1]?.address).toBe("Ottawa");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should filter out ads without price", () => {
|
test("should filter out ads without price", () => {
|
||||||
@@ -1704,7 +1704,7 @@ describe("Facebook Marketplace Scraper Core Tests", () => {
|
|||||||
|
|
||||||
const results = parseFacebookAds(ads);
|
const results = parseFacebookAds(ads);
|
||||||
expect(results).toHaveLength(1);
|
expect(results).toHaveLength(1);
|
||||||
expect(results[0].title).toBe("With Price");
|
expect(results[0]?.title).toBe("With Price");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should handle malformed ads gracefully", () => {
|
test("should handle malformed ads gracefully", () => {
|
||||||
@@ -1731,12 +1731,14 @@ describe("Facebook Marketplace Scraper Core Tests", () => {
|
|||||||
node: {
|
node: {
|
||||||
// Missing listing
|
// Missing listing
|
||||||
},
|
},
|
||||||
} as { node: { listing?: unknown } },
|
} as unknown as { node: { listing?: unknown } },
|
||||||
];
|
];
|
||||||
|
|
||||||
const results = parseFacebookAds(ads);
|
const results = parseFacebookAds(
|
||||||
|
ads as unknown as Parameters<typeof parseFacebookAds>[0],
|
||||||
|
);
|
||||||
expect(results).toHaveLength(1);
|
expect(results).toHaveLength(1);
|
||||||
expect(results[0].title).toBe("Valid Ad");
|
expect(results[0]?.title).toBe("Valid Ad");
|
||||||
expect(warnMock).toHaveBeenCalledTimes(1);
|
expect(warnMock).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
console.warn = originalWarn;
|
console.warn = originalWarn;
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ describe("Facebook Marketplace Scraper Integration Tests", () => {
|
|||||||
process.env.FACEBOOK_COOKIE = facebookCookie;
|
process.env.FACEBOOK_COOKIE = facebookCookie;
|
||||||
global.fetch = mock(() => {
|
global.fetch = mock(() => {
|
||||||
throw new Error("fetch should be mocked in individual tests");
|
throw new Error("fetch should be mocked in individual tests");
|
||||||
});
|
}) as unknown as typeof fetch;
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@@ -69,11 +69,11 @@ describe("Facebook Marketplace Scraper Integration Tests", () => {
|
|||||||
get: () => null,
|
get: () => null,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
);
|
) as unknown as typeof fetch;
|
||||||
|
|
||||||
const results = await fetchFacebookItems("iPhone", 1, "toronto", 25);
|
const results = await fetchFacebookItems("iPhone", 1, "toronto", 25);
|
||||||
expect(results).toHaveLength(1);
|
expect(results).toHaveLength(1);
|
||||||
expect(results[0].title).toBe("iPhone 13");
|
expect(results[0]?.title).toBe("iPhone 13");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should filter out items without price", async () => {
|
test("should filter out items without price", async () => {
|
||||||
@@ -135,11 +135,11 @@ describe("Facebook Marketplace Scraper Integration Tests", () => {
|
|||||||
get: () => null,
|
get: () => null,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
);
|
) as unknown as typeof fetch;
|
||||||
|
|
||||||
const results = await fetchFacebookItems("test", 1, "toronto", 25);
|
const results = await fetchFacebookItems("test", 1, "toronto", 25);
|
||||||
expect(results).toHaveLength(1);
|
expect(results).toHaveLength(1);
|
||||||
expect(results[0].title).toBe("With Price");
|
expect(results[0]?.title).toBe("With Price");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should respect MAX_ITEMS parameter", async () => {
|
test("should respect MAX_ITEMS parameter", async () => {
|
||||||
@@ -190,7 +190,7 @@ describe("Facebook Marketplace Scraper Integration Tests", () => {
|
|||||||
get: () => null,
|
get: () => null,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
);
|
) as unknown as typeof fetch;
|
||||||
|
|
||||||
const results = await fetchFacebookItems("test", 1, "toronto", 5);
|
const results = await fetchFacebookItems("test", 1, "toronto", 5);
|
||||||
expect(results).toHaveLength(5);
|
expect(results).toHaveLength(5);
|
||||||
@@ -231,7 +231,7 @@ describe("Facebook Marketplace Scraper Integration Tests", () => {
|
|||||||
get: () => null,
|
get: () => null,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
);
|
) as unknown as typeof fetch;
|
||||||
|
|
||||||
const results = await fetchFacebookItems(
|
const results = await fetchFacebookItems(
|
||||||
"nonexistent query",
|
"nonexistent query",
|
||||||
@@ -252,7 +252,7 @@ describe("Facebook Marketplace Scraper Integration Tests", () => {
|
|||||||
get: () => null,
|
get: () => null,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
);
|
) as unknown as typeof fetch;
|
||||||
|
|
||||||
const results = await fetchFacebookItems("test", 1, "toronto", 25);
|
const results = await fetchFacebookItems("test", 1, "toronto", 25);
|
||||||
expect(results).toEqual([]);
|
expect(results).toEqual([]);
|
||||||
@@ -281,7 +281,7 @@ describe("Facebook Marketplace Scraper Integration Tests", () => {
|
|||||||
get: () => null,
|
get: () => null,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
);
|
) as unknown as typeof fetch;
|
||||||
|
|
||||||
const results = await fetchFacebookItems("lamp", 1, "toronto", 25);
|
const results = await fetchFacebookItems("lamp", 1, "toronto", 25);
|
||||||
expect(results).toEqual([]);
|
expect(results).toEqual([]);
|
||||||
@@ -322,14 +322,16 @@ describe("Facebook Marketplace Scraper Integration Tests", () => {
|
|||||||
get: () => null,
|
get: () => null,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
);
|
) as unknown as typeof fetch;
|
||||||
|
|
||||||
const results = await fetchFacebookItems("lamp", 1, "toronto", 25);
|
const results = await fetchFacebookItems("lamp", 1, "toronto", 25);
|
||||||
expect(results).toEqual([]);
|
expect(results).toEqual([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should handle network errors", async () => {
|
test("should handle network errors", async () => {
|
||||||
global.fetch = mock(() => Promise.reject(new Error("Network error")));
|
global.fetch = mock(() =>
|
||||||
|
Promise.reject(new Error("Network error")),
|
||||||
|
) as unknown as typeof fetch;
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
fetchFacebookItems("test", 1, "toronto", 25),
|
fetchFacebookItems("test", 1, "toronto", 25),
|
||||||
@@ -400,7 +402,7 @@ describe("Facebook Marketplace Scraper Integration Tests", () => {
|
|||||||
get: () => null,
|
get: () => null,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
}) as unknown as typeof fetch;
|
||||||
|
|
||||||
const results = await fetchFacebookItems("test", 1, "toronto", 25);
|
const results = await fetchFacebookItems("test", 1, "toronto", 25);
|
||||||
expect(attempts).toBe(2);
|
expect(attempts).toBe(2);
|
||||||
@@ -473,13 +475,13 @@ describe("Facebook Marketplace Scraper Integration Tests", () => {
|
|||||||
get: () => null,
|
get: () => null,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
);
|
) as unknown as typeof fetch;
|
||||||
|
|
||||||
const results = await fetchFacebookItems("cars", 1, "toronto", 25);
|
const results = await fetchFacebookItems("cars", 1, "toronto", 25);
|
||||||
expect(results).toHaveLength(2);
|
expect(results).toHaveLength(2);
|
||||||
// Both should be classified as "item" type in search results (vehicle detection is for item details)
|
// Both should be classified as "item" type in search results (vehicle detection is for item details)
|
||||||
expect(results[0].title).toBe("2006 Honda Civic");
|
expect(results[0]?.title).toBe("2006 Honda Civic");
|
||||||
expect(results[1].title).toBe("iPhone 13");
|
expect(results[1]?.title).toBe("iPhone 13");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -542,7 +544,7 @@ describe("Facebook Marketplace Scraper Integration Tests", () => {
|
|||||||
get: () => null,
|
get: () => null,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
);
|
) as unknown as typeof fetch;
|
||||||
|
|
||||||
const results = await fetchFacebookItems(
|
const results = await fetchFacebookItems(
|
||||||
"nintendo switch",
|
"nintendo switch",
|
||||||
@@ -551,8 +553,8 @@ describe("Facebook Marketplace Scraper Integration Tests", () => {
|
|||||||
25,
|
25,
|
||||||
);
|
);
|
||||||
expect(results).toHaveLength(1);
|
expect(results).toHaveLength(1);
|
||||||
expect(results[0].title).toBe("Nintendo Switch");
|
expect(results[0]?.title).toBe("Nintendo Switch");
|
||||||
expect(results[0].categoryId).toBe("479353692612078");
|
expect(results[0]?.categoryId).toBe("479353692612078");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should handle home goods/furniture listings", async () => {
|
test("should handle home goods/furniture listings", async () => {
|
||||||
@@ -613,12 +615,12 @@ describe("Facebook Marketplace Scraper Integration Tests", () => {
|
|||||||
get: () => null,
|
get: () => null,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
);
|
) as unknown as typeof fetch;
|
||||||
|
|
||||||
const results = await fetchFacebookItems("table", 1, "toronto", 25);
|
const results = await fetchFacebookItems("table", 1, "toronto", 25);
|
||||||
expect(results).toHaveLength(1);
|
expect(results).toHaveLength(1);
|
||||||
expect(results[0].title).toBe("Dining Table");
|
expect(results[0]?.title).toBe("Dining Table");
|
||||||
expect(results[0].categoryId).toBe("1569171756675761");
|
expect(results[0]?.categoryId).toBe("1569171756675761");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -635,7 +637,7 @@ describe("Facebook Marketplace Scraper Integration Tests", () => {
|
|||||||
get: () => null,
|
get: () => null,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
);
|
) as unknown as typeof fetch;
|
||||||
|
|
||||||
const results = await fetchFacebookItems("test", 1, "toronto", 25);
|
const results = await fetchFacebookItems("test", 1, "toronto", 25);
|
||||||
expect(results).toEqual([]);
|
expect(results).toEqual([]);
|
||||||
@@ -651,7 +653,7 @@ describe("Facebook Marketplace Scraper Integration Tests", () => {
|
|||||||
get: () => null,
|
get: () => null,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
);
|
) as unknown as typeof fetch;
|
||||||
|
|
||||||
const results = await fetchFacebookItems("test", 1, "toronto", 25);
|
const results = await fetchFacebookItems("test", 1, "toronto", 25);
|
||||||
expect(results).toEqual([]);
|
expect(results).toEqual([]);
|
||||||
@@ -667,7 +669,7 @@ describe("Facebook Marketplace Scraper Integration Tests", () => {
|
|||||||
get: () => null,
|
get: () => null,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
);
|
) as unknown as typeof fetch;
|
||||||
|
|
||||||
const results = await fetchFacebookItems("test", 1, "toronto", 25);
|
const results = await fetchFacebookItems("test", 1, "toronto", 25);
|
||||||
expect(results).toEqual([]);
|
expect(results).toEqual([]);
|
||||||
@@ -708,7 +710,7 @@ describe("Facebook Marketplace Scraper Integration Tests", () => {
|
|||||||
get: () => null,
|
get: () => null,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
);
|
) as unknown as typeof fetch;
|
||||||
|
|
||||||
const result = await fetchFacebookItem("123");
|
const result = await fetchFacebookItem("123");
|
||||||
expect(result).toBeNull();
|
expect(result).toBeNull();
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ const originalFetch = global.fetch;
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
global.fetch = mock(() => {
|
global.fetch = mock(() => {
|
||||||
throw new Error("fetch should be mocked in individual tests");
|
throw new Error("fetch should be mocked in individual tests");
|
||||||
});
|
}) as unknown as typeof fetch;
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@@ -310,7 +310,7 @@ describe("fetchKijijiItems", () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
throw new Error(`Unexpected URL: ${url}`);
|
throw new Error(`Unexpected URL: ${url}`);
|
||||||
}) as typeof fetch;
|
}) as unknown as typeof fetch;
|
||||||
|
|
||||||
const results = await fetchKijijiItems(
|
const results = await fetchKijijiItems(
|
||||||
"phone",
|
"phone",
|
||||||
@@ -418,7 +418,7 @@ describe("fetchKijijiItems", () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
throw new Error(`Unexpected URL: ${url}`);
|
throw new Error(`Unexpected URL: ${url}`);
|
||||||
}) as typeof fetch;
|
}) as unknown as typeof fetch;
|
||||||
|
|
||||||
const results = await fetchKijijiItems(
|
const results = await fetchKijijiItems(
|
||||||
"phone",
|
"phone",
|
||||||
@@ -515,7 +515,7 @@ describe("fetchKijijiItems", () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
throw new Error(`Unexpected URL: ${url}`);
|
throw new Error(`Unexpected URL: ${url}`);
|
||||||
}) as typeof fetch;
|
}) as unknown as typeof fetch;
|
||||||
|
|
||||||
const results = await fetchKijijiItems(
|
const results = await fetchKijijiItems(
|
||||||
"phone",
|
"phone",
|
||||||
@@ -628,7 +628,7 @@ describe("fetchKijijiItems", () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
throw new Error(`Unexpected URL: ${url}`);
|
throw new Error(`Unexpected URL: ${url}`);
|
||||||
}) as typeof fetch;
|
}) as unknown as typeof fetch;
|
||||||
|
|
||||||
const results = await fetchKijijiItems(
|
const results = await fetchKijijiItems(
|
||||||
"phone",
|
"phone",
|
||||||
@@ -771,7 +771,7 @@ describe("fetchKijijiItems", () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
throw new Error(`Unexpected URL: ${url}`);
|
throw new Error(`Unexpected URL: ${url}`);
|
||||||
}) as typeof fetch;
|
}) as unknown as typeof fetch;
|
||||||
|
|
||||||
const results = await fetchKijijiItems(
|
const results = await fetchKijijiItems(
|
||||||
"phone",
|
"phone",
|
||||||
@@ -872,7 +872,7 @@ describe("fetchKijijiItems", () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
throw new Error(`Unexpected URL: ${url}`);
|
throw new Error(`Unexpected URL: ${url}`);
|
||||||
}) as typeof fetch;
|
}) as unknown as typeof fetch;
|
||||||
|
|
||||||
await parseDetailedListing(html, "https://www.kijiji.ca", {
|
await parseDetailedListing(html, "https://www.kijiji.ca", {
|
||||||
includeClientSideData: true,
|
includeClientSideData: true,
|
||||||
@@ -981,7 +981,7 @@ describe("fetchKijijiItems", () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
throw new Error(`Unexpected URL: ${url}`);
|
throw new Error(`Unexpected URL: ${url}`);
|
||||||
}) as typeof fetch;
|
}) as unknown as typeof fetch;
|
||||||
|
|
||||||
const results = await fetchKijijiItems(
|
const results = await fetchKijijiItems(
|
||||||
"phone",
|
"phone",
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ describe("HTML Parsing Integration", () => {
|
|||||||
// Mock fetch for all tests
|
// Mock fetch for all tests
|
||||||
global.fetch = mock(() => {
|
global.fetch = mock(() => {
|
||||||
throw new Error("fetch should be mocked in individual tests");
|
throw new Error("fetch should be mocked in individual tests");
|
||||||
});
|
}) as unknown as typeof fetch;
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@@ -111,7 +111,7 @@ describe("HTML Parsing Integration", () => {
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const results = parseSearch(mockHtml, "https://www.kijiji.ca");
|
const results = parseSearch(mockHtml, "https://www.kijiji.ca");
|
||||||
expect(results[0].listingLink).toBe(
|
expect(results[0]?.listingLink).toBe(
|
||||||
"https://www.kijiji.ca/v-iphone/k0l0",
|
"https://www.kijiji.ca/v-iphone/k0l0",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -146,7 +146,7 @@ describe("HTML Parsing Integration", () => {
|
|||||||
|
|
||||||
const results = parseSearch(mockHtml, "https://www.kijiji.ca");
|
const results = parseSearch(mockHtml, "https://www.kijiji.ca");
|
||||||
expect(results).toHaveLength(1);
|
expect(results).toHaveLength(1);
|
||||||
expect(results[0].name).toBe("iPhone 13 Pro");
|
expect(results[0]?.name).toBe("iPhone 13 Pro");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should return empty array for invalid HTML", () => {
|
test("should return empty array for invalid HTML", () => {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ describe("MCP protocol cookie inputs", () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
global.fetch = mock(() =>
|
global.fetch = mock(() =>
|
||||||
Promise.resolve(new Response(JSON.stringify([]), { status: 200 })),
|
Promise.resolve(new Response(JSON.stringify([]), { status: 200 })),
|
||||||
) as typeof fetch;
|
) as unknown as typeof fetch;
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@@ -48,7 +48,7 @@ describe("MCP protocol cookie inputs", () => {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const calledUrl = (global.fetch as ReturnType<typeof mock>).mock
|
const calledUrl = (global.fetch as unknown as ReturnType<typeof mock>).mock
|
||||||
.calls[0]?.[0];
|
.calls[0]?.[0];
|
||||||
expect(String(calledUrl)).toContain("/facebook?q=laptop");
|
expect(String(calledUrl)).toContain("/facebook?q=laptop");
|
||||||
expect(String(calledUrl)).not.toContain("cookies=");
|
expect(String(calledUrl)).not.toContain("cookies=");
|
||||||
@@ -59,7 +59,7 @@ describe("MCP protocol unstableFilter", () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
global.fetch = mock(() =>
|
global.fetch = mock(() =>
|
||||||
Promise.resolve(new Response(JSON.stringify([]), { status: 200 })),
|
Promise.resolve(new Response(JSON.stringify([]), { status: 200 })),
|
||||||
) as typeof fetch;
|
) as unknown as typeof fetch;
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@@ -103,7 +103,7 @@ describe("MCP protocol unstableFilter", () => {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const calledUrl = (global.fetch as ReturnType<typeof mock>).mock
|
const calledUrl = (global.fetch as unknown as ReturnType<typeof mock>).mock
|
||||||
.calls[0]?.[0];
|
.calls[0]?.[0];
|
||||||
expect(String(calledUrl)).toContain("unstableFilter=true");
|
expect(String(calledUrl)).toContain("unstableFilter=true");
|
||||||
});
|
});
|
||||||
@@ -127,7 +127,7 @@ describe("MCP protocol unstableFilter", () => {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const calledUrl = (global.fetch as ReturnType<typeof mock>).mock
|
const calledUrl = (global.fetch as unknown as ReturnType<typeof mock>).mock
|
||||||
.calls[0]?.[0];
|
.calls[0]?.[0];
|
||||||
expect(String(calledUrl)).toContain("unstableFilter=true");
|
expect(String(calledUrl)).toContain("unstableFilter=true");
|
||||||
});
|
});
|
||||||
@@ -151,7 +151,7 @@ describe("MCP protocol unstableFilter", () => {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const calledUrl = (global.fetch as ReturnType<typeof mock>).mock
|
const calledUrl = (global.fetch as unknown as ReturnType<typeof mock>).mock
|
||||||
.calls[0]?.[0];
|
.calls[0]?.[0];
|
||||||
expect(String(calledUrl)).toContain("unstableFilter=true");
|
expect(String(calledUrl)).toContain("unstableFilter=true");
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user