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.
109 lines
2.9 KiB
109 lines
2.9 KiB
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
import {
|
|
checkIdempotency,
|
|
saveIdempotency,
|
|
} from "@/modules/quote/idempotency";
|
|
|
|
vi.mock("@/modules/cache/redis-cache", () => ({
|
|
getL1: vi.fn(),
|
|
setL1: vi.fn(),
|
|
}));
|
|
|
|
vi.mock("@/lib/prisma", () => ({
|
|
prisma: {
|
|
idempotencyRecord: {
|
|
findUnique: vi.fn(),
|
|
create: vi.fn(),
|
|
},
|
|
quoteRecord: {
|
|
findUnique: vi.fn(),
|
|
},
|
|
},
|
|
}));
|
|
|
|
import { getL1, setL1 } from "@/modules/cache/redis-cache";
|
|
import { prisma } from "@/lib/prisma";
|
|
|
|
const mockedGetL1 = vi.mocked(getL1);
|
|
const mockedSetL1 = vi.mocked(setL1);
|
|
const mockedFindUnique = vi.mocked(prisma.idempotencyRecord.findUnique);
|
|
const mockedCreate = vi.mocked(prisma.idempotencyRecord.create);
|
|
const mockedQuoteFind = vi.mocked(prisma.quoteRecord.findUnique);
|
|
|
|
describe("checkIdempotency", () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
it("L1 命中 → 返回 quote_id", async () => {
|
|
mockedGetL1.mockResolvedValue({
|
|
quoteId: "QTE_20260616_0001",
|
|
response: { quote_id: "QTE_20260616_0001", status: "done" },
|
|
});
|
|
|
|
const result = await checkIdempotency("550e8400-e29b-41d4-a716-446655440000");
|
|
expect(result.hit).toBe(true);
|
|
if (result.hit) {
|
|
expect(result.quoteId).toBe("QTE_20260616_0001");
|
|
}
|
|
});
|
|
|
|
it("L1 未命中、DB 命中 → 回填 L1", async () => {
|
|
mockedGetL1.mockResolvedValue(null);
|
|
mockedFindUnique.mockResolvedValue({
|
|
quoteId: "QTE_20260616_0002",
|
|
} as never);
|
|
mockedQuoteFind.mockResolvedValue({
|
|
quoteId: "QTE_20260616_0002",
|
|
status: "done",
|
|
} as never);
|
|
|
|
const result = await checkIdempotency("550e8400-e29b-41d4-a716-446655440000");
|
|
expect(result.hit).toBe(true);
|
|
expect(mockedSetL1).toHaveBeenCalled();
|
|
});
|
|
|
|
it("均未命中 → hit=false", async () => {
|
|
mockedGetL1.mockResolvedValue(null);
|
|
mockedFindUnique.mockResolvedValue(null);
|
|
|
|
const result = await checkIdempotency("550e8400-e29b-41d4-a716-446655440000");
|
|
expect(result.hit).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("saveIdempotency", () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
mockedCreate.mockResolvedValue({} as never);
|
|
mockedSetL1.mockResolvedValue(true);
|
|
});
|
|
|
|
it("写入 DB + Redis", async () => {
|
|
await saveIdempotency(
|
|
"550e8400-e29b-41d4-a716-446655440000",
|
|
"CUST_001",
|
|
"QTE_20260616_0001",
|
|
{ quote_id: "QTE_20260616_0001", status: "done" },
|
|
);
|
|
expect(mockedCreate).toHaveBeenCalled();
|
|
expect(mockedSetL1).toHaveBeenCalled();
|
|
});
|
|
|
|
it("P2002 冲突 → 静默返回", async () => {
|
|
mockedCreate.mockRejectedValue({ code: "P2002" });
|
|
mockedFindUnique.mockResolvedValue({
|
|
quoteId: "QTE_20260616_0001",
|
|
} as never);
|
|
|
|
await expect(
|
|
saveIdempotency(
|
|
"550e8400-e29b-41d4-a716-446655440000",
|
|
"CUST_001",
|
|
"QTE_20260616_0001",
|
|
{},
|
|
),
|
|
).resolves.toBeUndefined();
|
|
});
|
|
});
|