import { describe, expect, it, vi, beforeEach } from "vitest"; vi.mock("@/modules/address/mothership-candidates", () => ({ resolveMothershipCandidates: vi.fn(), })); vi.mock("@/modules/host/public-session", () => ({ saveHostPublicSession: vi.fn(), loadHostPublicSession: vi.fn(), deleteHostPublicSession: vi.fn(), })); vi.mock("@/modules/quote/orchestrator", () => ({ submitQuote: vi.fn(), })); vi.mock("@/modules/host/wait-for-quote", () => ({ waitForQuoteDetail: vi.fn(), HostQuoteWaitError: class HostQuoteWaitError extends Error { code = "QUOTE_FAILED"; detail = undefined; }, })); vi.mock("@/lib/prisma", () => ({ prisma: { quoteRecord: { findFirst: vi.fn(), }, }, })); import { resolveMothershipCandidates } from "@/modules/address/mothership-candidates"; import { deleteHostPublicSession, loadHostPublicSession, saveHostPublicSession, } from "@/modules/host/public-session"; import { hostPublicResolveCandidates, hostPublicSubmitAndWait, } from "@/modules/host/public-orchestrator"; import { waitForQuoteDetail } from "@/modules/host/wait-for-quote"; import { submitQuote } from "@/modules/quote/orchestrator"; import { prisma } from "@/lib/prisma"; const pickupCandidate = { option_id: "pickup-opt-1", display_label: "1234 Warehouse St, Los Angeles, CA, USA", formatted_address: "1234 Warehouse St, Los Angeles, CA, USA", street: "1234 Warehouse St", city: "Los Angeles", state: "CA", zip: "90001", selectable: true, }; const deliveryCandidate = { option_id: "delivery-opt-1", display_label: "5678 Distribution Dr, Dallas, TX, USA", formatted_address: "5678 Distribution Dr, Dallas, TX, USA", street: "5678 Distribution Dr", city: "Dallas", state: "TX", zip: "75201", selectable: true, }; describe("hostPublicResolveCandidates", () => { beforeEach(() => { vi.clearAllMocks(); vi.mocked(saveHostPublicSession).mockResolvedValue("session-uuid"); }); it("returns host_session_id and candidates", async () => { vi.mocked(resolveMothershipCandidates).mockResolvedValue({ pickup_candidates: [pickupCandidate], delivery_candidates: [deliveryCandidate], requires_selection: false, quote_session_id: "quote-session-1", }); const result = await hostPublicResolveCandidates("CUST_002", { pickup_address: { street: "1234 Warehouse Blvd", city: "Los Angeles", state: "CA", zip: "90001", }, delivery_address: { street: "5678 Distribution Dr", city: "Dallas", state: "TX", zip: "75201", }, cargo: { weight: { value: 500, unit: "kg" }, dimensions: { length: 120, width: 100, height: 150, unit: "cm" }, pallet_count: 2, cargo_type: "general_freight", }, }); expect(result.host_session_id).toBe("session-uuid"); expect(result.pickup_candidates).toHaveLength(1); expect(saveHostPublicSession).toHaveBeenCalledWith( expect.objectContaining({ customer_id: "CUST_002" }), ); }); }); describe("hostPublicSubmitAndWait", () => { beforeEach(() => { vi.clearAllMocks(); vi.mocked(loadHostPublicSession).mockResolvedValue({ customer_id: "CUST_001", cargo: { weight: { value: 500, unit: "kg" }, dimensions: { length: 120, width: 100, height: 150, unit: "cm" }, pallet_count: 2, cargo_type: "general_freight", }, pickup_draft: { street: "1234 Warehouse Blvd", city: "Los Angeles", state: "CA", zip: "90001", }, delivery_draft: { street: "5678 Distribution Dr", city: "Dallas", state: "TX", zip: "75201", }, quote_session_id: "quote-session-1", pickup_candidates: [pickupCandidate], delivery_candidates: [deliveryCandidate], created_at: new Date().toISOString(), }); vi.mocked(submitQuote).mockResolvedValue({ quote_id: "QTE_test_001", status: "processing", }); vi.mocked(waitForQuoteDetail).mockResolvedValue({ quote_id: "QTE_test_001", request_id: "req-1", status: "done", currency: "USD", quotes: [{ final_total: 337.06 }], }); vi.mocked(prisma.quoteRecord.findFirst).mockResolvedValue({ quoteId: "QTE_test_001", requestId: "req-1", status: "done", sourceType: "rpa", isRealtime: true, currency: "USD", confidenceScore: 0.95, quotesJson: [{ final_total: 337.06 }], validUntil: new Date("2026-06-30T10:00:00.000Z"), createdAt: new Date("2026-06-30T09:55:00.000Z"), palletCount: 2, cargoType: "general_freight", errorCode: null, errorMessage: null, isDeleted: false, } as never); }); it("submits quote and waits for final detail", async () => { const detail = await hostPublicSubmitAndWait({ host_session_id: "session-uuid", pickup_candidate: pickupCandidate, delivery_candidate: deliveryCandidate, }); expect(submitQuote).toHaveBeenCalledOnce(); expect(waitForQuoteDetail).toHaveBeenCalledWith( "QTE_test_001", "CUST_001", ); expect(deleteHostPublicSession).toHaveBeenCalledWith("session-uuid"); expect(detail.status).toBe("done"); }); it("rejects candidate not in session", async () => { await expect( hostPublicSubmitAndWait({ host_session_id: "session-uuid", pickup_candidate: { ...pickupCandidate, option_id: "unknown" }, delivery_candidate: deliveryCandidate, }), ).rejects.toMatchObject({ code: "ADDRESS_SESSION_MISMATCH" }); }); it("submitQuote 已 done 时直接查一次 DB,不走 waitForQuoteDetail", async () => { vi.mocked(submitQuote).mockResolvedValue({ quote_id: "QTE_test_001", status: "done", source_type: "rpa", is_realtime: true, }); const detail = await hostPublicSubmitAndWait({ host_session_id: "session-uuid", pickup_candidate: pickupCandidate, delivery_candidate: deliveryCandidate, }); expect(prisma.quoteRecord.findFirst).toHaveBeenCalledWith({ where: { quoteId: "QTE_test_001", customerId: "CUST_001", isDeleted: false, }, }); expect(waitForQuoteDetail).not.toHaveBeenCalled(); expect(detail.status).toBe("done"); }); });