"use strict"; const test = require("node:test"); const assert = require("node:assert/strict"); const { createCrawlerClient } = require("../src/crawlerClient"); function createJsonResponse(body, status = 200) { return { ok: status >= 200 && status < 300, status, async json() { return body; }, async text() { return JSON.stringify(body); }, }; } function createTextResponse(text, status = 500) { return { ok: false, status, async json() { throw new Error("json not available"); }, async text() { return text; }, }; } test("single endpoint mode still works", async () => { const crawler = createCrawlerClient({ endpoint: "https://single-endpoint.example", maxAttempts: 1, fetch: async () => createJsonResponse({ currency: "KRW", offers: [{ provider: "single", price: 123456 }], }), }); const offers = await crawler.getQuotes({ watchId: "watch-1", searchParams: { segments: [{ from: "ICN", to: "MAD" }] }, }); assert.equal(offers.length, 1); assert.equal(offers[0].provider, "single"); assert.equal(offers[0].price, 123456); }); test("endpoint mode retries transient failures and then succeeds", async () => { let calls = 0; const crawler = createCrawlerClient({ endpoint: "https://retry-endpoint.example", maxAttempts: 3, retryBaseDelayMs: 1, retryMaxDelayMs: 1, fetch: async () => { calls += 1; if (calls < 3) { return createTextResponse("upstream failed", 503); } return createJsonResponse({ currency: "KRW", offers: [{ provider: "retry", price: 777000 }], }); }, }); const offers = await crawler.getQuotes({ watchId: "watch-retry", searchParams: { segments: [{ from: "ICN", to: "MAD" }] }, }); assert.equal(calls, 3); assert.equal(offers.length, 1); assert.equal(offers[0].provider, "retry"); }); test("priorityFallback tries next provider when primary fails", async () => { const calls = []; const crawler = createCrawlerClient({ endpoint: null, providers: ["skyscanner", "naver"], providerEndpoints: { skyscanner: "https://skyscanner.example", naver: "https://naver.example", }, routingStrategy: "priorityFallback", maxAttempts: 1, fetch: async (url) => { calls.push(url); if (url.includes("skyscanner")) { return createTextResponse("upstream failed", 503); } return createJsonResponse({ currency: "KRW", offers: [{ price: 980000 }], }); }, }); const offers = await crawler.getQuotes({ watchId: "watch-2", searchParams: { segments: [{ from: "ICN", to: "MAD" }] }, }); assert.equal(calls.length, 2); assert.equal(offers.length, 1); assert.equal(offers[0].provider, "naver"); assert.equal(offers[0].price, 980000); }); test("primaryOnly does not fallback to the next provider", async () => { const calls = []; const crawler = createCrawlerClient({ endpoint: null, providers: ["skyscanner", "naver"], providerEndpoints: { skyscanner: "https://skyscanner.example", naver: "https://naver.example", }, routingStrategy: "primaryOnly", maxAttempts: 1, fetch: async (url) => { calls.push(url); if (url.includes("skyscanner")) { return createTextResponse("timeout", 504); } return createJsonResponse({ currency: "KRW", offers: [{ price: 970000 }], }); }, }); await assert.rejects(async () => crawler.getQuotes({ watchId: "watch-3", searchParams: {} }), { message: /Primary provider failed/, }); assert.equal(calls.length, 1); }); test("parallelRace returns first successful provider result", async () => { const crawler = createCrawlerClient({ endpoint: null, providers: ["skyscanner", "naver"], providerEndpoints: { skyscanner: "https://skyscanner.example", naver: "https://naver.example", }, routingStrategy: "parallelRace", maxAttempts: 1, fetch: async (url) => new Promise((resolve) => { if (url.includes("naver")) { setTimeout(() => { resolve( createJsonResponse({ currency: "KRW", offers: [{ price: 930000 }], }) ); }, 5); return; } setTimeout(() => { resolve( createJsonResponse({ currency: "KRW", offers: [{ price: 950000 }], }) ); }, 40); }), }); const offers = await crawler.getQuotes({ watchId: "watch-4", searchParams: { segments: [{ from: "ICN", to: "MAD" }] }, }); assert.equal(offers.length, 1); assert.equal(offers[0].provider, "naver"); assert.equal(offers[0].price, 930000); }); test("throws when multi-source providers are configured without endpoints", () => { assert.throws( () => createCrawlerClient({ endpoint: null, providers: ["unconfigured-provider"], providerEndpoints: {}, }), /Missing endpoint for provider\(s\): unconfigured-provider/ ); });