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

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();
});
});