diff --git a/packages/core/src/scrapers/ebay.ts b/packages/core/src/scrapers/ebay.ts index 0d0afe0..924d266 100644 --- a/packages/core/src/scrapers/ebay.ts +++ b/packages/core/src/scrapers/ebay.ts @@ -274,7 +274,7 @@ function parseEbayListings( ); // Filter to only elements that actually contain prices (not labels) - const actualPrices: HTMLElement[] = []; + const actualPrices: Element[] = []; for (const el of allPriceElements) { const text = el.textContent?.trim(); if (text && EBAY_PRICE_TEXT_RE.test(text) && text.length < 50) { @@ -301,11 +301,10 @@ function parseEbayListings( if (nonStrikethroughPrices.length > 0) { // Use the first non-strikethrough price (sale price) - priceElement = nonStrikethroughPrices[0]; + priceElement = nonStrikethroughPrices[0] ?? null; } else { // Fallback: use the last price (likely the most current) - const lastPrice = actualPrices[actualPrices.length - 1]; - priceElement = lastPrice; + priceElement = actualPrices[actualPrices.length - 1] ?? null; } } } diff --git a/packages/core/src/scrapers/facebook.ts b/packages/core/src/scrapers/facebook.ts index e3832b3..54412d6 100644 --- a/packages/core/src/scrapers/facebook.ts +++ b/packages/core/src/scrapers/facebook.ts @@ -86,7 +86,7 @@ interface FacebookMarketplaceItem { __typename: "GroupCommerceProductItem"; // Listing content - marketplace_listing_title: string; + marketplace_listing_title?: string; redacted_description?: { text: string; }; @@ -99,7 +99,7 @@ interface FacebookMarketplaceItem { listing_price?: { amount: string; currency: string; - amount_with_offset: string; + amount_with_offset?: string; }; // Location @@ -127,9 +127,9 @@ interface FacebookMarketplaceItem { // Seller information marketplace_listing_seller?: { - __typename: "User"; - id: string; - name: string; + __typename?: "User"; + id?: string; + name?: string; profile_picture?: { uri: string; }; @@ -1321,6 +1321,14 @@ export async function fetchFacebookItem( 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); if (classification.unavailable && !itemData) { diff --git a/packages/core/src/utils/http.ts b/packages/core/src/utils/http.ts index 3bd6e1e..b2b4796 100644 --- a/packages/core/src/utils/http.ts +++ b/packages/core/src/utils/http.ts @@ -1,56 +1,53 @@ /** Custom error class for HTTP-related failures */ export class HttpError extends Error { + override name = "HttpError"; constructor( message: string, public readonly statusCode: number, public readonly url?: string, ) { super(message); - this.name = "HttpError"; } } /** Error class for network failures (timeouts, connection issues) */ export class NetworkError extends Error { + override name = "NetworkError"; constructor( message: string, public readonly url: string, - public readonly cause?: Error, + public override readonly cause?: Error, ) { super(message); - this.name = "NetworkError"; } } /** Error class for parsing failures */ export class ParseError extends Error { + override name = "ParseError"; constructor( message: string, public readonly data?: unknown, ) { super(message); - this.name = "ParseError"; } } /** Error class for rate limiting */ export class RateLimitError extends Error { + override name = "RateLimitError"; constructor( message: string, public readonly url: string, public readonly resetTime?: number, ) { super(message); - this.name = "RateLimitError"; } } /** Error class for validation failures */ export class ValidationError extends Error { - constructor(message: string) { - super(message); - this.name = "ValidationError"; - } + override name = "ValidationError"; } /** Type guard to check if a value is a record (object) */ diff --git a/packages/core/src/utils/unstable.ts b/packages/core/src/utils/unstable.ts index bffb606..d628ef5 100644 --- a/packages/core/src/utils/unstable.ts +++ b/packages/core/src/utils/unstable.ts @@ -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 { const middleIndex = Math.floor(values.length / 2); 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( +export function classifyUnstableListings( listings: T[], ): UnstableListingBuckets { const validPrices = listings - .map((listing) => listing.listingPrice.cents) - .filter((price) => Number.isFinite(price) && price > 0) + .map((listing) => listing.listingPrice?.cents) + .filter( + (price): price is number => Number.isFinite(price) && (price ?? 0) > 0, + ) .sort((left, right) => left - right); if (validPrices.length < 2) { @@ -32,9 +40,13 @@ export function classifyUnstableListings( }; 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); continue; } diff --git a/packages/core/test/ebay-core.test.ts b/packages/core/test/ebay-core.test.ts index df4cd2a..cc6cb05 100644 --- a/packages/core/test/ebay-core.test.ts +++ b/packages/core/test/ebay-core.test.ts @@ -34,7 +34,7 @@ describe("eBay Scraper Cookie Handling", () => { ok: true, text: () => Promise.resolve(""), }), - ) as typeof fetch; + ) as unknown as typeof fetch; }); afterEach(() => { @@ -51,7 +51,13 @@ describe("eBay Scraper Cookie Handling", () => { expect(global.fetch).toHaveBeenCalledTimes(1); - const [, init] = (global.fetch as ReturnType).mock.calls[0]; + const firstFetchCall = (global.fetch as unknown as ReturnType) + .mock.calls[0]; + if (!firstFetchCall) { + throw new Error("Expected fetch to be called"); + } + + const [, init] = firstFetchCall; const headers = (init as RequestInit).headers as Record; expect(headers.Cookie).toBeUndefined(); @@ -75,7 +81,7 @@ describe("eBay Scraper Cookie Handling", () => { `), }), - ) as typeof fetch; + ) as unknown as typeof fetch; const results = await fetchEbayItems("laptop", 1000); @@ -100,7 +106,7 @@ describe("eBay Scraper Cookie Handling", () => { `), }), - ) as typeof fetch; + ) as unknown as typeof fetch; const results = await fetchEbayItems("laptop", 1000); @@ -130,7 +136,7 @@ describe("eBay Scraper Cookie Handling", () => { `), }), - ) as typeof fetch; + ) as unknown as typeof fetch; const results = await fetchEbayItems("laptop", 1000); @@ -167,7 +173,7 @@ describe("eBay Scraper Cookie Handling", () => { `), }), - ) as typeof fetch; + ) as unknown as typeof fetch; const results = await fetchEbayItems("laptop", 1000); @@ -199,7 +205,7 @@ describe("eBay Scraper Cookie Handling", () => { `), }), - ) as typeof fetch; + ) as unknown as typeof fetch; const results = await fetchEbayItems("laptop", 1000); @@ -225,7 +231,7 @@ describe("eBay Scraper Cookie Handling", () => { `), }), - ) as typeof fetch; + ) as unknown as typeof fetch; const results = await fetchEbayItems("laptop", 1000); @@ -254,7 +260,7 @@ describe("eBay Scraper Cookie Handling", () => { `), }), - ) as typeof fetch; + ) as unknown as typeof fetch; const results = await fetchEbayItems("laptop", 1000); @@ -283,7 +289,7 @@ describe("eBay Scraper Cookie Handling", () => { `), }), - ) as typeof fetch; + ) as unknown as typeof fetch; const results = await fetchEbayItems("laptop", 1000); @@ -317,7 +323,7 @@ describe("eBay Scraper Cookie Handling", () => { `), }), - ) as typeof fetch; + ) as unknown as typeof fetch; const results = await fetchEbayItems("bundle", 1000, { keywords: ["bundle"], @@ -357,7 +363,7 @@ describe("eBay Scraper Cookie Handling", () => { `), }), - ) as typeof fetch; + ) as unknown as typeof fetch; const results = await fetchEbayItems("laptop", 1000); @@ -389,7 +395,7 @@ describe("eBay Scraper Cookie Handling", () => { `), }), - ) as typeof fetch; + ) as unknown as typeof fetch; const results = await fetchEbayItems("laptop", 1000); @@ -421,7 +427,7 @@ describe("eBay Scraper Cookie Handling", () => { `), }), - ) as typeof fetch; + ) as unknown as typeof fetch; const results = await fetchEbayItems("laptop", 1000); @@ -451,7 +457,7 @@ describe("eBay Scraper Cookie Handling", () => { `), }), - ) as typeof fetch; + ) as unknown as typeof fetch; const results = await fetchEbayItems("bike", 1000); @@ -478,7 +484,7 @@ describe("eBay Scraper Cookie Handling", () => { `), }), - ) as typeof fetch; + ) as unknown as typeof fetch; const results = await fetchEbayItems("microphone", 1000, { keywords: ["microphone"], @@ -510,7 +516,7 @@ describe("eBay Scraper Cookie Handling", () => { `), }), - ) as typeof fetch; + ) as unknown as typeof fetch; const results = await fetchEbayItems("laptop", 1000, { minPrice: 0, @@ -550,7 +556,7 @@ describe("eBay Scraper Cookie Handling", () => { `), }), - ) as typeof fetch; + ) as unknown as typeof fetch; const results = await fetchEbayItems( "laptop", @@ -595,7 +601,7 @@ describe("eBay Scraper Cookie Handling", () => { `), }), - ) as typeof fetch; + ) as unknown as typeof fetch; const results = await fetchEbayItems("laptop", 1000, { maxItems: 2 }); @@ -633,7 +639,7 @@ describe("eBay Scraper Cookie Handling", () => { `), }), - ) as typeof fetch; + ) as unknown as typeof fetch; const results = await fetchEbayItems( "laptop", diff --git a/packages/core/test/facebook-core.test.ts b/packages/core/test/facebook-core.test.ts index a262166..4dd4fea 100644 --- a/packages/core/test/facebook-core.test.ts +++ b/packages/core/test/facebook-core.test.ts @@ -52,7 +52,7 @@ describe("Facebook Marketplace Scraper Core Tests", () => { beforeEach(() => { global.fetch = mock(() => { throw new Error("fetch should be mocked in individual tests"); - }); + }) as unknown as typeof fetch; }); afterEach(() => { @@ -93,8 +93,8 @@ describe("Facebook Marketplace Scraper Core Tests", () => { const cookieString = "c_user=123%2B456; xs=abc%3Ddef"; const result = parseFacebookCookieString(cookieString); - expect(result[0].value).toBe("123+456"); - expect(result[1].value).toBe("abc=def"); + expect(result[0]?.value).toBe("123+456"); + expect(result[1]?.value).toBe("abc=def"); }); test("should filter out malformed cookies", () => { @@ -115,10 +115,10 @@ describe("Facebook Marketplace Scraper Core Tests", () => { const result = parseFacebookCookieString(cookieString); expect(result).toHaveLength(2); - expect(result[0].name).toBe("c_user"); - expect(result[0].value).toBe("123"); - expect(result[1].name).toBe("xs"); - expect(result[1].value).toBe("abc"); + expect(result[0]?.name).toBe("c_user"); + expect(result[0]?.value).toBe("123"); + expect(result[1]?.name).toBe("xs"); + expect(result[1]?.value).toBe("abc"); }); test("should load Facebook cookies from FACEBOOK_COOKIE env var", async () => { @@ -190,7 +190,7 @@ describe("Facebook Marketplace Scraper Core Tests", () => { get: () => null, }, }), - ); + ) as unknown as typeof fetch; try { const result = await fetchFacebookItem("123"); @@ -214,7 +214,7 @@ describe("Facebook Marketplace Scraper Core Tests", () => { get: () => null, }, }), - ); + ) as unknown as typeof fetch; const result = await fetchFacebookItem("nonexistent"); expect(result).toBeNull(); @@ -274,7 +274,7 @@ describe("Facebook Marketplace Scraper Core Tests", () => { get: () => null, }, }); - }); + }) as unknown as typeof fetch; const _result = await fetchFacebookItem("123"); expect(attempts).toBe(2); @@ -297,7 +297,7 @@ describe("Facebook Marketplace Scraper Core Tests", () => { }, text: () => Promise.resolve("Rate limited"), }); - }); + }) as unknown as typeof fetch; const result = await fetchFacebookItem("429-loop"); @@ -346,7 +346,7 @@ describe("Facebook Marketplace Scraper Core Tests", () => { get: () => null, }, }), - ); + ) as unknown as typeof fetch; const result = await fetchFacebookItem("456"); expect(result?.listingStatus).toBe("SOLD"); @@ -388,7 +388,7 @@ describe("Facebook Marketplace Scraper Core Tests", () => { get: () => null, }, }), - ); + ) as unknown as typeof fetch; const result = await fetchFacebookItem("457"); @@ -435,7 +435,7 @@ describe("Facebook Marketplace Scraper Core Tests", () => { get: () => null, }, }), - ); + ) as unknown as typeof fetch; const result = await fetchFacebookItem("458"); @@ -493,7 +493,7 @@ describe("Facebook Marketplace Scraper Core Tests", () => { get: () => null, }, }), - ); + ) as unknown as typeof fetch; const result = await fetchFacebookItem("789"); expect(result).not.toBeNull(); @@ -512,7 +512,7 @@ describe("Facebook Marketplace Scraper Core Tests", () => { get: () => null, }, }), - ); + ) as unknown as typeof fetch; const result = await fetchFacebookItem("error"); expect(result).toBeNull(); @@ -573,7 +573,7 @@ describe("Facebook Marketplace Scraper Core Tests", () => { get: () => null, }, }), - ); + ) as unknown as typeof fetch; const results = await fetchFacebookItems("chair", 1, "toronto", 25); @@ -618,7 +618,7 @@ describe("Facebook Marketplace Scraper Core Tests", () => { get: () => null, }, }), - ); + ) as unknown as typeof fetch; const results = await fetchFacebookItems("chair", 1, "toronto", 25); @@ -682,7 +682,7 @@ describe("Facebook Marketplace Scraper Core Tests", () => { get: () => null, }, }), - ); + ) as unknown as typeof fetch; const results = await fetchFacebookItems("chair", 1, "toronto", 25); @@ -762,7 +762,7 @@ describe("Facebook Marketplace Scraper Core Tests", () => { get: () => null, }, }), - ); + ) as unknown as typeof fetch; const results = await fetchFacebookItems("chair", 1, "toronto", 25, { hideUnstableResults: true, @@ -845,7 +845,7 @@ describe("Facebook Marketplace Scraper Core Tests", () => { get: () => null, }, }), - ); + ) as unknown as typeof fetch; const results = await fetchFacebookItems("chair", 1, "toronto", 2, { hideUnstableResults: true, @@ -1132,7 +1132,7 @@ describe("Facebook Marketplace Scraper Core Tests", () => { const result = extractFacebookMarketplaceData(html); expect(result).not.toBeNull(); expect(result).toHaveLength(2); - expect(result?.[0].node.listing.marketplace_listing_title).toBe( + expect(result?.[0]?.node.listing.marketplace_listing_title).toBe( "Item 1", ); }); @@ -1153,11 +1153,11 @@ describe("Facebook Marketplace Scraper Core Tests", () => { const result = extractFacebookMarketplaceData(html); expect(result).not.toBeNull(); expect(result).toHaveLength(1); - expect(result?.[0].node.listing.id).toBe("987654321"); - expect(result?.[0].node.listing.marketplace_listing_title).toBe( + expect(result?.[0]?.node.listing.id).toBe("987654321"); + expect(result?.[0]?.node.listing.marketplace_listing_title).toBe( "Vintage Bike", ); - expect(result?.[0].node.listing.listing_price).toEqual({ + expect(result?.[0]?.node.listing.listing_price).toEqual({ amount: "120.00", formatted_amount: "CA$120", currency: "CAD", @@ -1385,7 +1385,7 @@ describe("Facebook Marketplace Scraper Core Tests", () => { const ads = extractFacebookMarketplaceData(html); 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", () => { @@ -1443,7 +1443,7 @@ describe("Facebook Marketplace Scraper Core Tests", () => { const ads = extractFacebookMarketplaceData(html); 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", () => { @@ -1668,11 +1668,11 @@ describe("Facebook Marketplace Scraper Core Tests", () => { const results = parseFacebookAds(ads); expect(results).toHaveLength(2); - expect(results[0].title).toBe("Ad 1"); - expect(results[0].listingPrice?.cents).toBe(5000); - expect(results[0].address).toBe("Toronto"); - expect(results[1].title).toBe("Ad 2"); - expect(results[1].address).toBe("Ottawa"); + expect(results[0]?.title).toBe("Ad 1"); + expect(results[0]?.listingPrice?.cents).toBe(5000); + expect(results[0]?.address).toBe("Toronto"); + expect(results[1]?.title).toBe("Ad 2"); + expect(results[1]?.address).toBe("Ottawa"); }); test("should filter out ads without price", () => { @@ -1704,7 +1704,7 @@ describe("Facebook Marketplace Scraper Core Tests", () => { const results = parseFacebookAds(ads); expect(results).toHaveLength(1); - expect(results[0].title).toBe("With Price"); + expect(results[0]?.title).toBe("With Price"); }); test("should handle malformed ads gracefully", () => { @@ -1731,12 +1731,14 @@ describe("Facebook Marketplace Scraper Core Tests", () => { node: { // Missing listing }, - } as { node: { listing?: unknown } }, + } as unknown as { node: { listing?: unknown } }, ]; - const results = parseFacebookAds(ads); + const results = parseFacebookAds( + ads as unknown as Parameters[0], + ); expect(results).toHaveLength(1); - expect(results[0].title).toBe("Valid Ad"); + expect(results[0]?.title).toBe("Valid Ad"); expect(warnMock).toHaveBeenCalledTimes(1); console.warn = originalWarn; diff --git a/packages/core/test/facebook-integration.test.ts b/packages/core/test/facebook-integration.test.ts index a66ba19..dad5c7b 100644 --- a/packages/core/test/facebook-integration.test.ts +++ b/packages/core/test/facebook-integration.test.ts @@ -15,7 +15,7 @@ describe("Facebook Marketplace Scraper Integration Tests", () => { process.env.FACEBOOK_COOKIE = facebookCookie; global.fetch = mock(() => { throw new Error("fetch should be mocked in individual tests"); - }); + }) as unknown as typeof fetch; }); afterEach(() => { @@ -69,11 +69,11 @@ describe("Facebook Marketplace Scraper Integration Tests", () => { get: () => null, }, }), - ); + ) as unknown as typeof fetch; const results = await fetchFacebookItems("iPhone", 1, "toronto", 25); 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 () => { @@ -135,11 +135,11 @@ describe("Facebook Marketplace Scraper Integration Tests", () => { get: () => null, }, }), - ); + ) as unknown as typeof fetch; const results = await fetchFacebookItems("test", 1, "toronto", 25); 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 () => { @@ -190,7 +190,7 @@ describe("Facebook Marketplace Scraper Integration Tests", () => { get: () => null, }, }), - ); + ) as unknown as typeof fetch; const results = await fetchFacebookItems("test", 1, "toronto", 5); expect(results).toHaveLength(5); @@ -231,7 +231,7 @@ describe("Facebook Marketplace Scraper Integration Tests", () => { get: () => null, }, }), - ); + ) as unknown as typeof fetch; const results = await fetchFacebookItems( "nonexistent query", @@ -252,7 +252,7 @@ describe("Facebook Marketplace Scraper Integration Tests", () => { get: () => null, }, }), - ); + ) as unknown as typeof fetch; const results = await fetchFacebookItems("test", 1, "toronto", 25); expect(results).toEqual([]); @@ -281,7 +281,7 @@ describe("Facebook Marketplace Scraper Integration Tests", () => { get: () => null, }, }), - ); + ) as unknown as typeof fetch; const results = await fetchFacebookItems("lamp", 1, "toronto", 25); expect(results).toEqual([]); @@ -322,14 +322,16 @@ describe("Facebook Marketplace Scraper Integration Tests", () => { get: () => null, }, }), - ); + ) as unknown as typeof fetch; const results = await fetchFacebookItems("lamp", 1, "toronto", 25); expect(results).toEqual([]); }); 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( fetchFacebookItems("test", 1, "toronto", 25), @@ -400,7 +402,7 @@ describe("Facebook Marketplace Scraper Integration Tests", () => { get: () => null, }, }); - }); + }) as unknown as typeof fetch; const results = await fetchFacebookItems("test", 1, "toronto", 25); expect(attempts).toBe(2); @@ -473,13 +475,13 @@ describe("Facebook Marketplace Scraper Integration Tests", () => { get: () => null, }, }), - ); + ) as unknown as typeof fetch; const results = await fetchFacebookItems("cars", 1, "toronto", 25); expect(results).toHaveLength(2); // 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[1].title).toBe("iPhone 13"); + expect(results[0]?.title).toBe("2006 Honda Civic"); + expect(results[1]?.title).toBe("iPhone 13"); }); }); @@ -542,7 +544,7 @@ describe("Facebook Marketplace Scraper Integration Tests", () => { get: () => null, }, }), - ); + ) as unknown as typeof fetch; const results = await fetchFacebookItems( "nintendo switch", @@ -551,8 +553,8 @@ describe("Facebook Marketplace Scraper Integration Tests", () => { 25, ); expect(results).toHaveLength(1); - expect(results[0].title).toBe("Nintendo Switch"); - expect(results[0].categoryId).toBe("479353692612078"); + expect(results[0]?.title).toBe("Nintendo Switch"); + expect(results[0]?.categoryId).toBe("479353692612078"); }); test("should handle home goods/furniture listings", async () => { @@ -613,12 +615,12 @@ describe("Facebook Marketplace Scraper Integration Tests", () => { get: () => null, }, }), - ); + ) as unknown as typeof fetch; const results = await fetchFacebookItems("table", 1, "toronto", 25); expect(results).toHaveLength(1); - expect(results[0].title).toBe("Dining Table"); - expect(results[0].categoryId).toBe("1569171756675761"); + expect(results[0]?.title).toBe("Dining Table"); + expect(results[0]?.categoryId).toBe("1569171756675761"); }); }); @@ -635,7 +637,7 @@ describe("Facebook Marketplace Scraper Integration Tests", () => { get: () => null, }, }), - ); + ) as unknown as typeof fetch; const results = await fetchFacebookItems("test", 1, "toronto", 25); expect(results).toEqual([]); @@ -651,7 +653,7 @@ describe("Facebook Marketplace Scraper Integration Tests", () => { get: () => null, }, }), - ); + ) as unknown as typeof fetch; const results = await fetchFacebookItems("test", 1, "toronto", 25); expect(results).toEqual([]); @@ -667,7 +669,7 @@ describe("Facebook Marketplace Scraper Integration Tests", () => { get: () => null, }, }), - ); + ) as unknown as typeof fetch; const results = await fetchFacebookItems("test", 1, "toronto", 25); expect(results).toEqual([]); @@ -708,7 +710,7 @@ describe("Facebook Marketplace Scraper Integration Tests", () => { get: () => null, }, }), - ); + ) as unknown as typeof fetch; const result = await fetchFacebookItem("123"); expect(result).toBeNull(); diff --git a/packages/core/test/kijiji-core.test.ts b/packages/core/test/kijiji-core.test.ts index 90c5f57..72e2488 100644 --- a/packages/core/test/kijiji-core.test.ts +++ b/packages/core/test/kijiji-core.test.ts @@ -49,7 +49,7 @@ const originalFetch = global.fetch; beforeEach(() => { global.fetch = mock(() => { throw new Error("fetch should be mocked in individual tests"); - }); + }) as unknown as typeof fetch; }); afterEach(() => { @@ -310,7 +310,7 @@ describe("fetchKijijiItems", () => { } throw new Error(`Unexpected URL: ${url}`); - }) as typeof fetch; + }) as unknown as typeof fetch; const results = await fetchKijijiItems( "phone", @@ -418,7 +418,7 @@ describe("fetchKijijiItems", () => { } throw new Error(`Unexpected URL: ${url}`); - }) as typeof fetch; + }) as unknown as typeof fetch; const results = await fetchKijijiItems( "phone", @@ -515,7 +515,7 @@ describe("fetchKijijiItems", () => { } throw new Error(`Unexpected URL: ${url}`); - }) as typeof fetch; + }) as unknown as typeof fetch; const results = await fetchKijijiItems( "phone", @@ -628,7 +628,7 @@ describe("fetchKijijiItems", () => { } throw new Error(`Unexpected URL: ${url}`); - }) as typeof fetch; + }) as unknown as typeof fetch; const results = await fetchKijijiItems( "phone", @@ -771,7 +771,7 @@ describe("fetchKijijiItems", () => { } throw new Error(`Unexpected URL: ${url}`); - }) as typeof fetch; + }) as unknown as typeof fetch; const results = await fetchKijijiItems( "phone", @@ -872,7 +872,7 @@ describe("fetchKijijiItems", () => { } throw new Error(`Unexpected URL: ${url}`); - }) as typeof fetch; + }) as unknown as typeof fetch; await parseDetailedListing(html, "https://www.kijiji.ca", { includeClientSideData: true, @@ -981,7 +981,7 @@ describe("fetchKijijiItems", () => { } throw new Error(`Unexpected URL: ${url}`); - }) as typeof fetch; + }) as unknown as typeof fetch; const results = await fetchKijijiItems( "phone", diff --git a/packages/core/test/kijiji-integration.test.ts b/packages/core/test/kijiji-integration.test.ts index 9195d0b..f638233 100644 --- a/packages/core/test/kijiji-integration.test.ts +++ b/packages/core/test/kijiji-integration.test.ts @@ -13,7 +13,7 @@ describe("HTML Parsing Integration", () => { // Mock fetch for all tests global.fetch = mock(() => { throw new Error("fetch should be mocked in individual tests"); - }); + }) as unknown as typeof fetch; }); afterEach(() => { @@ -111,7 +111,7 @@ describe("HTML Parsing Integration", () => { `; 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", ); }); @@ -146,7 +146,7 @@ describe("HTML Parsing Integration", () => { const results = parseSearch(mockHtml, "https://www.kijiji.ca"); 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", () => { diff --git a/packages/mcp-server/test/protocol.test.ts b/packages/mcp-server/test/protocol.test.ts index e6ba98c..a137e9f 100644 --- a/packages/mcp-server/test/protocol.test.ts +++ b/packages/mcp-server/test/protocol.test.ts @@ -8,7 +8,7 @@ describe("MCP protocol cookie inputs", () => { beforeEach(() => { global.fetch = mock(() => Promise.resolve(new Response(JSON.stringify([]), { status: 200 })), - ) as typeof fetch; + ) as unknown as typeof fetch; }); afterEach(() => { @@ -48,7 +48,7 @@ describe("MCP protocol cookie inputs", () => { }), ); - const calledUrl = (global.fetch as ReturnType).mock + const calledUrl = (global.fetch as unknown as ReturnType).mock .calls[0]?.[0]; expect(String(calledUrl)).toContain("/facebook?q=laptop"); expect(String(calledUrl)).not.toContain("cookies="); @@ -59,7 +59,7 @@ describe("MCP protocol unstableFilter", () => { beforeEach(() => { global.fetch = mock(() => Promise.resolve(new Response(JSON.stringify([]), { status: 200 })), - ) as typeof fetch; + ) as unknown as typeof fetch; }); afterEach(() => { @@ -103,7 +103,7 @@ describe("MCP protocol unstableFilter", () => { }), ); - const calledUrl = (global.fetch as ReturnType).mock + const calledUrl = (global.fetch as unknown as ReturnType).mock .calls[0]?.[0]; expect(String(calledUrl)).toContain("unstableFilter=true"); }); @@ -127,7 +127,7 @@ describe("MCP protocol unstableFilter", () => { }), ); - const calledUrl = (global.fetch as ReturnType).mock + const calledUrl = (global.fetch as unknown as ReturnType).mock .calls[0]?.[0]; expect(String(calledUrl)).toContain("unstableFilter=true"); }); @@ -151,7 +151,7 @@ describe("MCP protocol unstableFilter", () => { }), ); - const calledUrl = (global.fetch as ReturnType).mock + const calledUrl = (global.fetch as unknown as ReturnType).mock .calls[0]?.[0]; expect(String(calledUrl)).toContain("unstableFilter=true"); });