Files
whyrating/packages/api/tests/credits/update-credits.test.ts
2026-02-04 01:55:00 +01:00

184 lines
6.1 KiB
TypeScript

import { describe, it, expect, vi, beforeEach } from "vitest";
// Mock the database module before importing the mutation
vi.mock("@turbostarter/db/server", () => ({
db: {
transaction: vi.fn(),
},
}));
vi.mock("@turbostarter/shared/utils", () => ({
generateId: vi.fn(() => "test-transaction-id"),
HttpException: class HttpException extends Error {
constructor(public statusCode: number, public body?: { code: string; message?: string }) {
super(body?.message ?? "HttpException");
}
},
}));
import { db } from "@turbostarter/db/server";
import { updateCustomerCredits } from "../../src/modules/admin/customers/mutations";
describe("updateCustomerCredits", () => {
beforeEach(() => {
vi.clearAllMocks();
});
describe("action: add", () => {
it("should add credits to customer balance", async () => {
const mockCustomer = { id: "cust-1", credits: 100 };
const mockTx = {
select: vi.fn().mockReturnThis(),
from: vi.fn().mockReturnThis(),
where: vi.fn().mockResolvedValue([mockCustomer]),
update: vi.fn().mockReturnThis(),
set: vi.fn().mockReturnThis(),
insert: vi.fn().mockReturnThis(),
values: vi.fn().mockResolvedValue(undefined),
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument, @typescript-eslint/unbound-method
vi.mocked(db.transaction).mockImplementation((callback) => callback(mockTx as any));
const result = await updateCustomerCredits(
"cust-1",
{ action: "add", amount: 50 },
"admin-1"
);
expect(result).toEqual({
previousBalance: 100,
newBalance: 150,
action: "add",
amount: 50,
});
expect(mockTx.insert).toHaveBeenCalled();
});
});
describe("action: deduct", () => {
it("should deduct credits from customer balance", async () => {
const mockCustomer = { id: "cust-1", credits: 100 };
const mockTx = {
select: vi.fn().mockReturnThis(),
from: vi.fn().mockReturnThis(),
where: vi.fn().mockResolvedValue([mockCustomer]),
update: vi.fn().mockReturnThis(),
set: vi.fn().mockReturnThis(),
insert: vi.fn().mockReturnThis(),
values: vi.fn().mockResolvedValue(undefined),
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument, @typescript-eslint/unbound-method
vi.mocked(db.transaction).mockImplementation((callback) => callback(mockTx as any));
const result = await updateCustomerCredits(
"cust-1",
{ action: "deduct", amount: 30 },
"admin-1"
);
expect(result).toEqual({
previousBalance: 100,
newBalance: 70,
action: "deduct",
amount: 30,
});
});
it("should throw error when deducting more than available", async () => {
const mockCustomer = { id: "cust-1", credits: 20 };
const mockTx = {
select: vi.fn().mockReturnThis(),
from: vi.fn().mockReturnThis(),
where: vi.fn().mockResolvedValue([mockCustomer]),
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument, @typescript-eslint/unbound-method
vi.mocked(db.transaction).mockImplementation((callback) => callback(mockTx as any));
await expect(
updateCustomerCredits("cust-1", { action: "deduct", amount: 50 }, "admin-1")
).rejects.toThrow();
});
});
describe("action: set", () => {
it("should set credits to exact amount (increase)", async () => {
const mockCustomer = { id: "cust-1", credits: 100 };
const mockTx = {
select: vi.fn().mockReturnThis(),
from: vi.fn().mockReturnThis(),
where: vi.fn().mockResolvedValue([mockCustomer]),
update: vi.fn().mockReturnThis(),
set: vi.fn().mockReturnThis(),
insert: vi.fn().mockReturnThis(),
values: vi.fn().mockResolvedValue(undefined),
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument, @typescript-eslint/unbound-method
vi.mocked(db.transaction).mockImplementation((callback) => callback(mockTx as any));
const result = await updateCustomerCredits(
"cust-1",
{ action: "set", amount: 200 },
"admin-1"
);
expect(result).toEqual({
previousBalance: 100,
newBalance: 200,
action: "set",
amount: 200,
});
});
it("should set credits to exact amount (decrease)", async () => {
const mockCustomer = { id: "cust-1", credits: 100 };
const mockTx = {
select: vi.fn().mockReturnThis(),
from: vi.fn().mockReturnThis(),
where: vi.fn().mockResolvedValue([mockCustomer]),
update: vi.fn().mockReturnThis(),
set: vi.fn().mockReturnThis(),
insert: vi.fn().mockReturnThis(),
values: vi.fn().mockResolvedValue(undefined),
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument, @typescript-eslint/unbound-method
vi.mocked(db.transaction).mockImplementation((callback) => callback(mockTx as any));
const result = await updateCustomerCredits(
"cust-1",
{ action: "set", amount: 50 },
"admin-1"
);
expect(result).toEqual({
previousBalance: 100,
newBalance: 50,
action: "set",
amount: 50,
});
});
});
describe("error cases", () => {
it("should throw when customer not found", async () => {
const mockTx = {
select: vi.fn().mockReturnThis(),
from: vi.fn().mockReturnThis(),
where: vi.fn().mockResolvedValue([]),
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument, @typescript-eslint/unbound-method
vi.mocked(db.transaction).mockImplementation((callback) => callback(mockTx as any));
await expect(
updateCustomerCredits("nonexistent", { action: "add", amount: 100 }, "admin-1")
).rejects.toThrow();
});
});
});