You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
96 lines
3.0 KiB
96 lines
3.0 KiB
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<typeof import("@/lib/rpa/env")>();
|
|
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();
|
|
});
|
|
});
|