import { beforeEach, describe, expect, it, vi } from "vitest"; import type { FlockRunQuoteResult } from "@/workers/rpa/flock/types"; const directMock = vi.fn<() => Promise>(); const visualMock = vi.fn<() => Promise>(); const customerLoginMock = vi.fn(); const hasCredMock = vi.fn(); vi.mock("@/lib/flock/direct-quote", () => ({ fetchFlockQuotesViaDirect: () => directMock(), })); vi.mock("@/workers/rpa/flock/visual-chain", () => ({ runFlockVisualChain: () => visualMock(), })); vi.mock("@/workers/rpa/flock/worker-session", () => ({ runFlockQuoteWithWorkerSession: () => visualMock(), shouldRotateFlockSession: () => false, })); vi.mock("@/lib/flock/exclusive-lock", () => ({ withFlockExclusiveLock: async ( _owner: string, fn: () => Promise, ) => fn(), })); vi.mock("@/modules/customer/provider-credentials", () => ({ getCustomerProviderLogin: (...args: unknown[]) => customerLoginMock(...args), hasCustomerProviderCredential: (...args: unknown[]) => hasCredMock(...args), })); describe("runFlockQuoteRpa customer login forces DOM", () => { beforeEach(() => { directMock.mockReset(); visualMock.mockReset(); customerLoginMock.mockReset(); hasCredMock.mockReset(); hasCredMock.mockResolvedValue(false); process.env.RPA_MOCK_MODE = "false"; process.env.FLOCK_QUOTE_MODE = "direct_then_dom"; delete process.env.FLOCK_LOGIN_EMAIL; delete process.env.FLOCK_LOGIN_PASSWORD; process.env.FLOCK_WORKER_REUSE_SESSION = "false"; }); const input = { pickupZip: "94102", deliveryZip: "90212", pickupDate: "07/20/2026", palletCount: 4, totalWeightLb: 2800, lengthIn: 48, widthIn: 40, heightIn: 48, }; it("有客户账密时跳过 Direct,走 DOM", async () => { customerLoginMock.mockResolvedValue({ email: "cust@example.com", password: "secret", }); visualMock.mockResolvedValue({ ok: true, quotes: [{ tier: "standard", totalUsd: 100 } as FlockRunQuoteResult["quotes"][0]], }); const { runFlockQuoteRpa } = await import("@/workers/rpa/flock/run-quote"); const result = await runFlockQuoteRpa(input, { customerId: "CUST_001" }); expect(result.ok).toBe(true); expect(directMock).not.toHaveBeenCalled(); expect(visualMock).toHaveBeenCalled(); expect(customerLoginMock).toHaveBeenCalledWith("CUST_001", "flock"); }); it("库中已配账密但解密失败时不回落 Direct", async () => { customerLoginMock.mockResolvedValue(null); hasCredMock.mockResolvedValue(true); const { runFlockQuoteRpa } = await import("@/workers/rpa/flock/run-quote"); const result = await runFlockQuoteRpa(input, { customerId: "CUST_001" }); expect(result.ok).toBe(false); expect(result.errorMessage).toMatch(/解密失败|禁止改走 Direct/i); expect(directMock).not.toHaveBeenCalled(); expect(visualMock).not.toHaveBeenCalled(); }); it("无客户账密时走 Direct", async () => { customerLoginMock.mockResolvedValue(null); hasCredMock.mockResolvedValue(false); directMock.mockResolvedValue({ ok: true, quotes: [{ tier: "standard", totalUsd: 200 } as FlockRunQuoteResult["quotes"][0]], }); const { runFlockQuoteRpa } = await import("@/workers/rpa/flock/run-quote"); const result = await runFlockQuoteRpa(input, { customerId: "CUST_001" }); expect(result.ok).toBe(true); expect(directMock).toHaveBeenCalled(); }); });