import { beforeEach, describe, expect, it, vi } from "vitest"; import { axelSearchRowToCandidate, resolveAxelMothershipCandidates, resolveAxelMothershipSuggest, } from "@/lib/axel/candidates"; import { AxelHttpClient } from "@/lib/axel/client"; describe("axelSearchRowToCandidate", () => { it("display_label 与 axel description 一致,option_id 保留原始 placeId", () => { const row = { placeId: "ChIJ4cLf10jGwoARBQWdZHer2OQ", description: "123 Main Street, Los Angeles, CA, USA", mainText: "123 Main Street", secondaryText: "Los Angeles, CA, USA", }; const candidate = axelSearchRowToCandidate(row, { street: "123 Main St", city: "Los Angeles", state: "CA", zip: "90001", }); expect(candidate?.option_id).toBe("ChIJ4cLf10jGwoARBQWdZHer2OQ"); expect(candidate?.display_label).toBe( "123 Main Street, Los Angeles, CA, USA", ); expect(candidate?.street).toContain("123"); }); }); describe("resolveAxelMothershipCandidates", () => { beforeEach(() => { vi.restoreAllMocks(); vi.stubEnv("AXEL_CANDIDATE_VERIFY_TOP_K", "3"); }); function makeSearchRow(suffix: string) { return { placeId: `ChIJ-${suffix}`, description: `${suffix} Main Street, Dallas, TX, USA`, mainText: `${suffix} Main Street`, secondaryText: "Dallas, TX, USA", }; } it("仅对前 3 条候选 fetchPlace 校验,其余默认可选", async () => { const fetchPlace = vi.fn().mockResolvedValue({ placeId: "ok" }); const searchLocation = vi .fn() .mockResolvedValueOnce([ makeSearchRow("1"), makeSearchRow("2"), makeSearchRow("3"), makeSearchRow("4"), makeSearchRow("5"), ]) .mockResolvedValueOnce([makeSearchRow("d1")]); vi.spyOn(AxelHttpClient, "create").mockResolvedValue({ searchLocation, fetchPlace, } as unknown as AxelHttpClient); const result = await resolveAxelMothershipCandidates({ pickup: { street: "1 Main St", city: "Dallas", state: "TX", zip: "75201", }, delivery: { street: "2 Oak Ave", city: "Los Angeles", state: "CA", zip: "90001", }, }); expect(result.pickup_candidates).toHaveLength(5); expect(result.delivery_candidates).toHaveLength(1); expect(fetchPlace).toHaveBeenCalledTimes(4); expect(result.pickup_candidates[0]?.selectable).toBe(true); expect(result.pickup_candidates[3]?.selectable).toBe(true); expect(result.pickup_candidates[4]?.selectable).toBe(true); }); it("前 K 条均失败时,未校验候选仍可使 assert 通过", async () => { const fetchPlace = vi .fn() .mockRejectedValueOnce(new Error("place 404")) .mockRejectedValueOnce(new Error("place 404")) .mockRejectedValueOnce(new Error("place 404")) .mockResolvedValueOnce({ placeId: "ChIJ-d1" }); const searchLocation = vi .fn() .mockResolvedValueOnce([ makeSearchRow("bad1"), makeSearchRow("bad2"), makeSearchRow("bad3"), makeSearchRow("good-tail"), ]) .mockResolvedValueOnce([makeSearchRow("d1")]); vi.spyOn(AxelHttpClient, "create").mockResolvedValue({ searchLocation, fetchPlace, } as unknown as AxelHttpClient); const result = await resolveAxelMothershipCandidates({ pickup: { street: "99 Main St", city: "Dallas", state: "TX", zip: "75201", }, delivery: { street: "1 Oak", city: "LA", state: "CA", zip: "90001", }, }); expect(result.pickup_candidates[0]?.selectable).toBe(false); expect(result.pickup_candidates[3]?.selectable).toBe(true); expect(fetchPlace).toHaveBeenCalledTimes(4); }); }); describe("resolveAxelMothershipSuggest", () => { beforeEach(() => { vi.restoreAllMocks(); vi.stubEnv("AXEL_CANDIDATE_VERIFY_TOP_K", "3"); }); it("仅映射 Axel googlePlacesResults,无 placeId 丢弃,并对 TopK fetchPlace", async () => { const fetchPlace = vi.fn().mockResolvedValue({ placeId: "ok" }); const searchLocation = vi.fn().mockResolvedValue([ { placeId: "ChIJ-a", description: "100 A St, LA, CA, USA", mainText: "100 A St", secondaryText: "LA, CA, USA", }, { placeId: "", description: "本系统不应出现的假地址", mainText: "fake", secondaryText: "Nowhere", }, ]); vi.spyOn(AxelHttpClient, "create").mockResolvedValue({ searchLocation, fetchPlace, } as unknown as AxelHttpClient); const list = await resolveAxelMothershipSuggest("100 A St"); expect(searchLocation).toHaveBeenCalledWith("100 A St"); expect(list).toHaveLength(1); expect(list[0]?.option_id).toBe("ChIJ-a"); expect(fetchPlace).toHaveBeenCalledTimes(1); }); });