53 lines
2.5 KiB
TypeScript
53 lines
2.5 KiB
TypeScript
import { createHash } from "node:crypto";
|
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
const limit = vi.fn();
|
|
const where = vi.fn(() => ({ limit }));
|
|
const from = vi.fn(() => ({ where }));
|
|
const select = vi.fn(() => ({ from }));
|
|
const get = vi.fn();
|
|
const set = vi.fn();
|
|
const redis = { get, set };
|
|
|
|
vi.mock("server-only", () => ({}));
|
|
vi.mock("@/db", () => ({ getDb: () => ({ select }) }));
|
|
vi.mock("@/lib/redis/client", () => ({ getRedisClient: async () => redis }));
|
|
|
|
const { authorizeMobileSlip, createMobileSlipLink } = await import("./mobile-slip");
|
|
const checkout = { id: "checkout-1", userId: "customer-1", amountBaht: 150 };
|
|
|
|
describe("commission mobile slip links", () => {
|
|
beforeEach(() => { vi.clearAllMocks(); set.mockResolvedValue("OK"); });
|
|
|
|
it("requires checkout ownership before issuing a link", async () => {
|
|
limit.mockResolvedValueOnce([]);
|
|
await expect(createMobileSlipLink("checkout-1", "wrong-user")).rejects.toMatchObject({ status: 404 });
|
|
expect(set).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("issues a ten-minute link and rejects an expired or replaced token", async () => {
|
|
limit.mockResolvedValueOnce([{ id: checkout.id }]).mockResolvedValueOnce([]);
|
|
const { token, digest } = await createMobileSlipLink(checkout.id, checkout.userId);
|
|
expect(token).toMatch(/^[A-Za-z0-9_-]{43}$/);
|
|
expect(digest).toBe(createHash("sha256").update(token).digest("hex"));
|
|
expect(set).toHaveBeenCalledWith(expect.stringContaining(":token:"),
|
|
JSON.stringify({ checkoutId: checkout.id, userId: checkout.userId }), "EX", 600);
|
|
expect(set).toHaveBeenCalledWith(expect.stringContaining(":active:"), digest, "EX", 600);
|
|
|
|
get.mockResolvedValueOnce(null);
|
|
await expect(authorizeMobileSlip(token)).rejects.toMatchObject({ status: 404 });
|
|
get.mockResolvedValueOnce(JSON.stringify({ checkoutId: checkout.id, userId: checkout.userId }))
|
|
.mockResolvedValueOnce("another-digest");
|
|
await expect(authorizeMobileSlip(token)).rejects.toMatchObject({ status: 404 });
|
|
});
|
|
|
|
it("accepts the active token only for its stored checkout", async () => {
|
|
const token = "x".repeat(43);
|
|
const digest = createHash("sha256").update(token).digest("hex");
|
|
get.mockResolvedValueOnce(JSON.stringify({ checkoutId: checkout.id, userId: checkout.userId }))
|
|
.mockResolvedValueOnce(digest);
|
|
limit.mockResolvedValueOnce([checkout]).mockResolvedValueOnce([]);
|
|
expect(await authorizeMobileSlip(token)).toEqual({ checkout, ticketId: null, digest });
|
|
});
|
|
});
|