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