You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

117 lines
3.5 KiB

import { beforeEach, describe, expect, it, vi } from "vitest";
vi.mock("@/lib/prisma", () => ({
prisma: {
quoteRecord: {
findFirst: vi.fn(),
findMany: vi.fn(),
update: vi.fn(),
},
alertLog: { create: vi.fn() },
},
}));
vi.mock("@/modules/cache/redis-cache", () => ({
getL3: vi.fn(),
}));
vi.mock("@/modules/pricing/engine", () => ({
getMarkupPercent: vi.fn().mockResolvedValue(0),
applyMarkupToQuotes: vi.fn((q: unknown[]) => q),
}));
import { prisma } from "@/lib/prisma";
import { getL3 } from "@/modules/cache/redis-cache";
import {
sweepTimedOutQuote,
sweepTimedOutQuotes,
} from "@/workers/scheduler/timeout-sweeper";
const FOUR_TIERS = [
{ service_level: "standard", rate_option: "lowest", raw_freight: 320, surcharges: 0, raw_total: 320, carrier: "M", transit_days: "5", transit_description: "x" },
{ service_level: "standard", rate_option: "fastest", raw_freight: 365, surcharges: 0, raw_total: 365, carrier: "M", transit_days: "3", transit_description: "x" },
{ service_level: "guaranteed", rate_option: "lowest", raw_freight: 380, surcharges: 0, raw_total: 380, carrier: "M", transit_days: "4", transit_description: "x" },
{ service_level: "guaranteed", rate_option: "fastest", raw_freight: 410, surcharges: 0, raw_total: 410, carrier: "M", transit_days: "3", transit_description: "x" },
];
const mockedFindFirst = vi.mocked(prisma.quoteRecord.findFirst);
const mockedUpdate = vi.mocked(prisma.quoteRecord.update);
const mockedGetL3 = vi.mocked(getL3);
const mockedAlertCreate = vi.mocked(prisma.alertLog.create);
beforeEach(() => {
vi.clearAllMocks();
});
describe("sweepTimedOutQuote", () => {
it("无 L3 → failed + QUOTE_TIMEOUT", async () => {
mockedFindFirst.mockResolvedValue({
quoteId: "QTE_001",
cargoHash: "hash1",
customerId: "CUST_001",
createdAt: new Date(Date.now() - 421_000),
} as never);
mockedGetL3.mockResolvedValue(null);
await sweepTimedOutQuote("QTE_001");
expect(mockedUpdate).toHaveBeenCalledWith(
expect.objectContaining({
data: expect.objectContaining({
status: "failed",
errorCode: "QUOTE_TIMEOUT",
}),
}),
);
expect(mockedAlertCreate).toHaveBeenCalled();
});
it("有 L3 → done stale", async () => {
mockedFindFirst.mockResolvedValue({
quoteId: "QTE_002",
cargoHash: "hash2",
customerId: "CUST_001",
createdAt: new Date(Date.now() - 421_000),
} as never);
mockedGetL3.mockResolvedValue({ quotes: FOUR_TIERS });
await sweepTimedOutQuote("QTE_002");
expect(mockedUpdate).toHaveBeenCalledWith(
expect.objectContaining({
data: expect.objectContaining({
status: "done",
sourceType: "stale",
isRealtime: false,
}),
}),
);
});
it("未超时 → 不处理", async () => {
mockedFindFirst.mockResolvedValue({
quoteId: "QTE_003",
cargoHash: "hash3",
customerId: "CUST_001",
createdAt: new Date(Date.now() - 5_000),
} as never);
await sweepTimedOutQuote("QTE_003");
expect(mockedUpdate).not.toHaveBeenCalled();
});
});
describe("sweepTimedOutQuotes", () => {
it("批量扫描返回处理数量", async () => {
vi.mocked(prisma.quoteRecord.findMany).mockResolvedValue([
{ quoteId: "QTE_A" },
{ quoteId: "QTE_B" },
] as never);
mockedFindFirst.mockResolvedValue(null);
const count = await sweepTimedOutQuotes();
expect(count).toBe(2);
});
});