feat: turbostarter boilerplate

Production-ready Next.js boilerplate with:
- Runtime env validation (fail-fast on missing vars)
- Feature-gated config (S3, Stripe, email, OAuth)
- Docker + Coolify deployment pipeline
- PostgreSQL + pgvector, MinIO S3, Better Auth
- TypeScript strict mode (no ignoreBuildErrors)
- i18n (en/es), AI modules, billing, monitoring

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Alejandro Gutiérrez
2026-02-02 17:29:12 +00:00
commit 3527e732d4
1618 changed files with 338230 additions and 0 deletions

View File

@@ -0,0 +1,183 @@
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();
});
});
});