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
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);
|
|
});
|
|
});
|