|
|
import { describe, expect, it, vi, beforeEach, afterEach } from "vitest";
|
|
|
import type { Page } from "playwright";
|
|
|
import { RPA_SESSION_WIDGET_TIMEOUT_MS } from "@/lib/constants/rpa";
|
|
|
import { MothershipQuotePageAdapter } from "@/workers/rpa/quote-page-adapter";
|
|
|
import { RpaError } from "@/modules/rpa/errors";
|
|
|
import { waitForQuoteWidgetReady } from "@/workers/rpa/address-side";
|
|
|
|
|
|
vi.mock("@/workers/rpa/page-prep", () => ({
|
|
|
stabilizeQuoteLandingPage: vi.fn().mockResolvedValue(undefined),
|
|
|
}));
|
|
|
|
|
|
vi.mock("@/workers/rpa/address-side", () => ({
|
|
|
waitForQuoteWidgetReady: vi.fn().mockResolvedValue(undefined),
|
|
|
ensureQuoteFormVisible: vi.fn().mockResolvedValue(undefined),
|
|
|
}));
|
|
|
|
|
|
function mockPage(overrides: {
|
|
|
url?: string;
|
|
|
goto?: ReturnType<typeof vi.fn>;
|
|
|
pickupVisible?: boolean;
|
|
|
submitVisible?: boolean;
|
|
|
bodyText?: string;
|
|
|
cfCount?: number;
|
|
|
}): Page {
|
|
|
const url = overrides.url ?? "https://quote.example.com/";
|
|
|
const bodyLocator = {
|
|
|
innerText: vi.fn().mockResolvedValue(overrides.bodyText ?? "Quote form"),
|
|
|
};
|
|
|
return {
|
|
|
url: () => url,
|
|
|
goto: overrides.goto ?? vi.fn().mockResolvedValue(undefined),
|
|
|
waitForLoadState: vi.fn().mockResolvedValue(undefined),
|
|
|
locator: vi.fn((selector: string) => {
|
|
|
if (selector === "body") {
|
|
|
return bodyLocator;
|
|
|
}
|
|
|
return {
|
|
|
first: () => ({
|
|
|
isVisible: vi.fn().mockImplementation(async () => {
|
|
|
if (selector.includes("pickup")) {
|
|
|
return overrides.pickupVisible ?? true;
|
|
|
}
|
|
|
if (selector.includes("submit")) {
|
|
|
return overrides.submitVisible ?? true;
|
|
|
}
|
|
|
return false;
|
|
|
}),
|
|
|
innerText: vi.fn().mockResolvedValue(overrides.bodyText ?? "Quote form"),
|
|
|
}),
|
|
|
count: vi.fn().mockResolvedValue(overrides.cfCount ?? 0),
|
|
|
};
|
|
|
}),
|
|
|
} as unknown as Page;
|
|
|
}
|
|
|
|
|
|
describe("MothershipQuotePageAdapter", () => {
|
|
|
const adapter = new MothershipQuotePageAdapter();
|
|
|
const envBackup = { ...process.env };
|
|
|
|
|
|
beforeEach(() => {
|
|
|
process.env.MOTHERSHIP_EMAIL = "";
|
|
|
process.env.MOTHERSHIP_PASSWORD = "";
|
|
|
process.env.RPA_SELECTOR_PICKUP_STREET = '[data-testid="pickup"]';
|
|
|
process.env.RPA_SELECTOR_SUBMIT = 'button[type="submit"]';
|
|
|
});
|
|
|
|
|
|
afterEach(() => {
|
|
|
process.env = { ...envBackup };
|
|
|
});
|
|
|
|
|
|
it("isLoginWithoutCredentials:login 且无凭据", () => {
|
|
|
const page = mockPage({ url: "https://example.com/login" });
|
|
|
expect(adapter.isLoginWithoutCredentials(page)).toBe(true);
|
|
|
});
|
|
|
|
|
|
it("resolveQuoteEntry:首个 URL 通过 preCheck", async () => {
|
|
|
process.env.MOTHERSHIP_QUOTE_URLS =
|
|
|
"https://good.example.com,https://bad.example.com";
|
|
|
process.env.RPA_SELECTOR_PICKUP_SECTION = "text=pickup";
|
|
|
const page = mockPage({
|
|
|
pickupVisible: true,
|
|
|
submitVisible: false,
|
|
|
});
|
|
|
|
|
|
const result = await adapter.resolveQuoteEntry(page);
|
|
|
expect(result.activeUrl).toBe("https://good.example.com");
|
|
|
expect(page.goto).toHaveBeenCalledTimes(1);
|
|
|
});
|
|
|
|
|
|
it("resolveQuoteEntry:全部失败 → QUOTE_ENTRY_UNAVAILABLE", async () => {
|
|
|
process.env.MOTHERSHIP_QUOTE_URLS =
|
|
|
"https://bad1.example.com,https://bad2.example.com";
|
|
|
const page = mockPage({
|
|
|
pickupVisible: false,
|
|
|
submitVisible: false,
|
|
|
});
|
|
|
vi.mocked(page.goto).mockRejectedValue(new Error("net err"));
|
|
|
|
|
|
await expect(adapter.resolveQuoteEntry(page)).rejects.toMatchObject({
|
|
|
code: "QUOTE_ENTRY_UNAVAILABLE",
|
|
|
});
|
|
|
expect(page.goto).toHaveBeenCalled();
|
|
|
});
|
|
|
});
|
|
|
|
|
|
describe("MothershipQuotePageAdapter preCheck", () => {
|
|
|
const adapter = new MothershipQuotePageAdapter();
|
|
|
|
|
|
beforeEach(() => {
|
|
|
process.env.RPA_SELECTOR_PICKUP_STREET = '[data-testid="pickup"]';
|
|
|
process.env.RPA_SELECTOR_SUBMIT = 'button[type="submit"]';
|
|
|
});
|
|
|
|
|
|
it("login 无凭据 → STRUCT_CHANGE", async () => {
|
|
|
process.env.MOTHERSHIP_EMAIL = "";
|
|
|
const page = mockPage({ url: "https://example.com/login" });
|
|
|
await expect(adapter.preCheck(page)).rejects.toMatchObject({
|
|
|
code: "STRUCT_CHANGE",
|
|
|
});
|
|
|
});
|
|
|
|
|
|
it("reuseSession 时使用较短 widget 等待", async () => {
|
|
|
const page = mockPage({ pickupVisible: true });
|
|
|
await adapter.preCheck(page, { reuseSession: true });
|
|
|
expect(waitForQuoteWidgetReady).toHaveBeenCalledWith(
|
|
|
page,
|
|
|
RPA_SESSION_WIDGET_TIMEOUT_MS,
|
|
|
);
|
|
|
});
|
|
|
});
|