import { describe, expect, it, vi, beforeEach } from "vitest"; import type { QuoteItem, QuoteRequest } from "@/modules/providers/quote-provider"; import { RpaError } from "@/modules/rpa/errors"; import type { RPAContext } from "@/workers/rpa/kernel/context"; const directMock = vi.fn(); const widgetMock = vi.fn(); vi.mock("@/workers/rpa/quote-capture/axel-quote-direct", () => ({ scrapeQuotesViaDirectAxel: (...args: unknown[]) => directMock(...args), })); vi.mock("@/workers/rpa/quote-capture/quote-schema-validator", () => ({ validateQuoteSchema: (items: QuoteItem[]) => items, })); vi.mock("@/lib/rpa/env", async (importOriginal) => { const actual = await importOriginal(); return { ...actual, isWidgetQuoteFallbackEnabled: vi.fn(() => true), }; }); vi.mock("@/workers/rpa/kernel/steps/quote-step", () => ({ scrapeQuotesStep: (...args: unknown[]) => widgetMock(...args), })); import { isWidgetQuoteFallbackEnabled } from "@/lib/rpa/env"; import { captureQuotesDirectFirst } from "@/workers/rpa/quote-capture/quote-strategy"; function sampleItems(): QuoteItem[] { return [ { serviceLevel: "standard", rateOption: "lowest", carrier: "Test", transitDays: "3-5", transitDescription: "3-5 business days", rawFreight: 100, surcharges: 10, rawTotal: 110, }, ]; } function makeCtx(): RPAContext { return { page: {} as RPAContext["page"], state: { axelPlaceLocations: { pickup: { placeId: "ChIJ-pickup", description: "pickup" }, delivery: { placeId: "ChIJ-delivery", description: "delivery" }, }, }, } as RPAContext; } const req = {} as QuoteRequest; describe("captureQuotesDirectFirst", () => { beforeEach(() => { directMock.mockReset(); widgetMock.mockReset(); vi.mocked(isWidgetQuoteFallbackEnabled).mockReturnValue(true); }); it("Direct 成功时不调用 Widget", async () => { directMock.mockResolvedValue(sampleItems()); const result = await captureQuotesDirectFirst(makeCtx(), req); expect(result.source).toBe("direct"); expect(widgetMock).not.toHaveBeenCalled(); }); it("Direct 失败且允许 fallback 时才调用 Widget", async () => { directMock.mockRejectedValue(new RpaError("RPA_DATA_INVALID", "direct fail")); widgetMock.mockResolvedValue(sampleItems()); const result = await captureQuotesDirectFirst(makeCtx(), req); expect(result.source).toBe("widget"); expect(widgetMock).toHaveBeenCalledWith( expect.anything(), expect.objectContaining({ widgetFallbackAfterDirectFailure: true }), ); }); it("关闭 Widget fallback 时 Direct 失败直接抛错", async () => { vi.mocked(isWidgetQuoteFallbackEnabled).mockReturnValue(false); directMock.mockRejectedValue(new RpaError("RPA_DATA_INVALID", "direct fail")); await expect(captureQuotesDirectFirst(makeCtx(), req)).rejects.toThrow( /direct fail/, ); expect(widgetMock).not.toHaveBeenCalled(); }); });