import { afterAll, beforeEach, expect, mock, test } from "bun:test"; import { normalizeAsin, searchProductOffers } from "./searxng.ts"; const originalFetch = globalThis.fetch; beforeEach(() => { globalThis.fetch = originalFetch; }); afterAll(() => { globalThis.fetch = originalFetch; }); test("normalizeAsin uppercases and validates ASINs", () => { expect(normalizeAsin(" b07sn9bhvv ")).toBe("B07SN9BHVV"); expect(normalizeAsin("0306406152")).toBe("0306406152"); expect(() => normalizeAsin("not-an-asin")).toThrow("Invalid ASIN"); }); test("searchProductOffers derives ASIN search behavior for ASIN-only queries", async () => { const fetchMock = mock(async (input: string | URL | Request) => { const url = input instanceof URL ? input : new URL(String(input)); expect(url.pathname).toBe("/search"); expect(url.searchParams.get("format")).toBe("json"); expect(url.searchParams.get("q")).toBe("B07SN9BHVV price sale offer buy online"); return Response.json({ results: [ { title: "Amazon listing B07SN9BHVV", url: "https://www.amazon.com/dp/B07SN9BHVV", content: "Official marketplace listing.", engines: ["duckduckgo"], }, { title: "Romand palette offer", url: "https://example-shop.com/item", content: "Buy product ASIN B07SN9BHVV. Offer price: $12.99 today.", engines: ["brave"], }, ], }); }); const results = await searchProductOffers("B07SN9BHVV", { provider: "searxng", baseUrl: "https://searxng.test/", fetchImpl: fetchMock as unknown as typeof fetch, maxResults: 10, }); expect(results).toHaveLength(2); expect(results[0]?.domain).toBe("example-shop.com"); expect(results[0]?.matchedAsin).toBe("B07SN9BHVV"); expect(results[0]?.detectedPrice).toBe(12.99); expect(results[0]?.detectedPriceCurrency).toBe("USD"); expect(results[0]?.detectedPriceLabel).toBe("offer price"); expect(results[0]?.detectedPriceText).toBe("$12.99"); expect(results[0]?.engines).toEqual(["brave"]); expect(fetchMock).toHaveBeenCalledTimes(1); }); test("searchProductOffers falls back to HTML when JSON is unavailable", async () => { const html = `

Supplier offer B07SN9BHVV

Wholesale product sale price: USD 9.50 with ASIN B07SN9BHVV.

duckduckgo
`; const fetchMock = mock(async (input: string | URL | Request) => { const url = input instanceof URL ? input : new URL(String(input)); if (url.searchParams.get("format") === "json") { return new Response("forbidden", { status: 403 }); } return new Response(html, { status: 200, headers: { "content-type": "text/html" }, }); }); const results = await searchProductOffers("B07SN9BHVV", { provider: "searxng", baseUrl: "https://searxng.test/", fetchImpl: fetchMock as unknown as typeof fetch, }); expect(results).toHaveLength(1); expect(results[0]?.title).toBe("Supplier offer B07SN9BHVV"); expect(results[0]?.domain).toBe("supplier.example"); expect(results[0]?.detectedPrice).toBe(9.5); expect(results[0]?.detectedPriceLabel).toBe("sale price"); expect(results[0]?.detectedPriceText).toBe("USD 9.50"); expect(results[0]?.matchedAsin).toBe("B07SN9BHVV"); expect(results[0]?.engines).toEqual(["duckduckgo"]); expect(fetchMock).toHaveBeenCalledTimes(2); }); test("searchProductOffers detects common selling and sale price formats", async () => { const fetchMock = mock(async () => Response.json({ results: [ { title: "Supplier page", url: "https://supplier.example/item", content: "Selling price is €18.75 and list price is $24.00.", }, { title: "Backup page", url: "https://backup.example/item", content: "Available now for 22.10 USD.", }, ], }), ); const results = await searchProductOffers("romand palette price", { provider: "searxng", baseUrl: "https://searxng.test/", fetchImpl: fetchMock as unknown as typeof fetch, maxResults: 2, }); expect(results[0]?.detectedPrice).toBe(18.75); expect(results[0]?.detectedPriceCurrency).toBe("EUR"); expect(results[0]?.detectedPriceLabel).toBe("selling price"); expect(results[1]?.detectedPrice).toBe(22.1); expect(results[1]?.detectedPriceCurrency).toBe("USD"); }); test("searchProductOffers filters unrelated priced results for ASIN-only queries", async () => { const fetchMock = mock(async () => Response.json({ results: [ { title: "Unrelated deal", url: "https://deals.example/phones", content: "This price is $449 but it is for another product.", }, { title: "Amazon listing B07SN9BHVV", url: "https://www.amazon.in/dp/B07SN9BHVV", content: "1 offer from ₹550.00 · Buying options.", }, ], }), ); const results = await searchProductOffers("B07SN9BHVV", { provider: "searxng", baseUrl: "https://searxng.test/", fetchImpl: fetchMock as unknown as typeof fetch, }); expect(results).toHaveLength(1); expect(results[0]?.matchedAsin).toBe("B07SN9BHVV"); expect(results[0]?.detectedPrice).toBe(550); expect(results[0]?.detectedPriceCurrency).toBe("INR"); expect(results[0]?.detectedPriceText).toBe("₹550.00"); }); test("searchProductOffers keeps arbitrary query strings generic", async () => { const fetchMock = mock(async (input: string | URL | Request) => { const url = input instanceof URL ? input : new URL(String(input)); expect(url.searchParams.get("q")).toBe("romand dry mango tulip price"); return Response.json({ results: [ { title: "Generic result", url: "https://shop.example/romand", content: "Sale price: $14.25", }, ], }); }); const results = await searchProductOffers("romand dry mango tulip price", { provider: "searxng", baseUrl: "https://searxng.test/", fetchImpl: fetchMock as unknown as typeof fetch, }); expect(results).toHaveLength(1); expect(results[0]?.asin).toBeUndefined(); expect(results[0]?.detectedPrice).toBe(14.25); }); test("searchProductOffers sends configured categories", async () => { const fetchMock = mock(async (input: string | URL | Request) => { const url = input instanceof URL ? input : new URL(String(input)); expect(url.searchParams.get("categories")).toBe("shopping"); return Response.json({ results: [ { title: "Shopping result", url: "https://shop.example/item", content: "Offer price: $10.00", }, ], }); }); const results = await searchProductOffers("romand price", { provider: "searxng", baseUrl: "https://searxng.test/", categories: "shopping", fetchImpl: fetchMock as unknown as typeof fetch, }); expect(results[0]?.detectedPrice).toBe(10); }); test("searchProductOffers sends configured SearXNG engines", async () => { const fetchMock = mock(async (input: string | URL | Request) => { const url = input instanceof URL ? input : new URL(String(input)); expect(url.searchParams.get("engines")).toBe("google"); expect(url.searchParams.get("q")).toBe("!go romand price"); return Response.json({ results: [ { title: "Google-backed result", url: "https://shop.example/item", content: "Offer price: $11.00", engine: "google", }, ], }); }); const results = await searchProductOffers("romand price", { provider: "searxng", baseUrl: "https://searxng.test/", engines: "google", fetchImpl: fetchMock as unknown as typeof fetch, }); expect(results[0]?.detectedPrice).toBe(11); expect(results[0]?.engines).toEqual(["google"]); }); test("searchProductOffers uses Google Custom Search API and pagemap offer prices", async () => { const fetchMock = mock(async (input: string | URL | Request) => { const url = input instanceof URL ? input : new URL(String(input)); expect(url.hostname).toBe("googleapis.test"); expect(url.searchParams.get("key")).toBe("test-key"); expect(url.searchParams.get("cx")).toBe("test-cx"); expect(url.searchParams.get("num")).toBe("5"); expect(url.searchParams.get("q")).toBe("romand dry mango tulip"); return Response.json({ items: [ { title: "Romand Dry Mango Tulip", link: "https://store.example/romand", snippet: "Buy from Store Example.", pagemap: { offer: [{ price: "12.50", pricecurrency: "USD" }], }, }, ], }); }); const results = await searchProductOffers("romand dry mango tulip", { provider: "google-custom-search", baseUrl: "https://googleapis.test/customsearch/v1", googleApiKey: "test-key", googleCx: "test-cx", maxResults: 5, fetchImpl: fetchMock as unknown as typeof fetch, }); expect(results).toHaveLength(1); expect(results[0]?.title).toContain("Romand Dry Mango Tulip"); expect(results[0]?.domain).toBe("store.example"); expect(results[0]?.detectedPrice).toBe(12.5); expect(results[0]?.detectedPriceLabel).toBe("offer price"); expect(results[0]?.engines).toEqual(["google custom search"]); }); test("searchProductOffers defaults to SerpApi Google Shopping results", async () => { const fetchMock = mock(async (input: string | URL | Request) => { const url = input instanceof URL ? input : new URL(String(input)); expect(url.hostname).toBe("serpapi.test"); expect(url.searchParams.get("engine")).toBe("google_shopping"); expect(url.searchParams.get("q")).toBe("dry mango tulip price"); expect(url.searchParams.get("api_key")).toBe("serpapi-key"); expect(url.searchParams.get("gl")).toBe("us"); expect(url.searchParams.get("hl")).toBe("en"); return Response.json({ shopping_results: [ { position: 1, title: "Romand Better Than Eyes Dry Mango Tulip", source: "K-Beauty Store", link: "https://store.example/products/romand", price: "$13.40", extracted_price: 13.4, delivery: "$4.99 delivery", rating: 4.7, reviews: 128, }, ], }); }); const results = await searchProductOffers("dry mango tulip price", { baseUrl: "https://serpapi.test/search.json", serpapiApiKey: "serpapi-key", fetchImpl: fetchMock as unknown as typeof fetch, }); expect(results).toHaveLength(1); expect(results[0]?.domain).toBe("store.example"); expect(results[0]?.detectedPrice).toBe(13.4); expect(results[0]?.detectedPriceText).toBe("$13.40"); expect(results[0]?.engines).toEqual(["serpapi google shopping"]); }); test("searchProductOffers applies result limits and handles empty results", async () => { const fetchMock = mock(async () => Response.json({ results: [ { title: "One", url: "https://one.example", content: "No price" }, { title: "Two", url: "https://two.example", content: "$20.00" }, ], }), ); const limited = await searchProductOffers("romand palette", { provider: "searxng", baseUrl: "https://searxng.test/", fetchImpl: fetchMock as unknown as typeof fetch, maxResults: 1, }); expect(limited).toHaveLength(1); expect(limited[0]?.domain).toBe("two.example"); const emptyFetch = mock(async () => Response.json({ results: [] })); const empty = await searchProductOffers("missing product", { provider: "searxng", baseUrl: "https://searxng.test/", fetchImpl: emptyFetch as unknown as typeof fetch, }); expect(empty).toEqual([]); });