import { describe, expect, it, vi, beforeEach } from "vitest"; import { detectDeviation } from "@/modules/alert/deviation-detector"; const prismaMock = vi.hoisted(() => ({ quoteCacheMeta: { findUnique: vi.fn(), create: vi.fn(), update: vi.fn(), }, alertLog: { create: vi.fn(), }, })); vi.mock("@/lib/prisma", () => ({ prisma: prismaMock })); const FOUR_TIERS = [ { service_level: "standard" as const, rate_option: "lowest" as const, carrier: "M", transit_days: "5", transit_description: "x", raw_freight: 320, surcharges: 0, raw_total: 320, }, { service_level: "standard" as const, rate_option: "fastest" as const, carrier: "M", transit_days: "3", transit_description: "x", raw_freight: 365, surcharges: 0, raw_total: 365, }, { service_level: "guaranteed" as const, rate_option: "lowest" as const, carrier: "M", transit_days: "4", transit_description: "x", raw_freight: 380, surcharges: 0, raw_total: 380, }, { service_level: "guaranteed" as const, rate_option: "fastest" as const, carrier: "M", transit_days: "3", transit_description: "x", raw_freight: 410, surcharges: 0, raw_total: 410, }, ]; describe("TC-401 cache vs RPA 一致性(task-113)", () => { beforeEach(() => { vi.clearAllMocks(); }); it("L2 raw_total 与 RPA 写入一致;偏差≥5% 触发 PRICE_DEVIATION", async () => { const l2RawTotal = 320; prismaMock.quoteCacheMeta.findUnique.mockResolvedValue({ cargoHash: "hash_cache", rawTotalStandard: l2RawTotal, }); prismaMock.quoteCacheMeta.update.mockResolvedValue({}); prismaMock.alertLog.create.mockResolvedValue({}); const newRpaQuotes = FOUR_TIERS.map((t) => ({ ...t })); newRpaQuotes[0].raw_total = 340; newRpaQuotes[0].raw_freight = 340; await detectDeviation("hash_cache", newRpaQuotes, "QTE_CACHE"); expect(prismaMock.quoteCacheMeta.findUnique).toHaveBeenCalledWith({ where: { cargoHash: "hash_cache" }, }); expect(prismaMock.alertLog.create).toHaveBeenCalledWith( expect.objectContaining({ data: expect.objectContaining({ alertType: "PRICE_DEVIATION" }), }), ); }); it("L2 与 RPA 一致(偏差<5%)→ 不触发预警", async () => { prismaMock.quoteCacheMeta.findUnique.mockResolvedValue({ cargoHash: "hash_ok", rawTotalStandard: 320, }); prismaMock.quoteCacheMeta.update.mockResolvedValue({}); await detectDeviation("hash_ok", FOUR_TIERS, "QTE_OK"); expect(prismaMock.alertLog.create).not.toHaveBeenCalled(); }); });