test: add comprehensive unit tests for all API services and guards

Add 11 new test files covering roles guard, brand-access guard, users,
brands, plans, payments, vehicles, categories, parts, referrals, and
admin services. Total test count increases from ~52 to 164, all passing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Sase Dev
2026-02-12 04:24:55 +00:00
parent ffa9781eb9
commit 8fb7bbbaca
11 changed files with 2270 additions and 0 deletions

View File

@@ -0,0 +1,251 @@
import { describe, it, expect, vi, beforeEach } from "vitest";
import { BadRequestException, NotFoundException } from "@nestjs/common";
import { UsersService } from "./users.service";
vi.mock("better-auth/crypto", () => ({
hashPassword: vi.fn().mockResolvedValue("hashed-new-password"),
verifyPassword: vi.fn(),
}));
import { verifyPassword } from "better-auth/crypto";
function createMockDb(overrides: Record<string, unknown> = {}) {
function chainable(terminalValue: unknown) {
const chain: Record<string, unknown> = {};
const methods = [
"select", "from", "where", "orderBy", "limit", "offset",
"innerJoin", "insert", "values", "update", "set", "delete",
"returning", "onConflictDoNothing",
];
for (const m of methods) chain[m] = vi.fn().mockReturnValue(chain);
chain.limit = vi.fn().mockReturnValue(terminalValue);
chain.returning = vi.fn().mockReturnValue(terminalValue);
chain.offset = vi.fn().mockReturnValue(terminalValue);
return chain;
}
return {
select: vi.fn().mockImplementation(() => chainable(overrides._selectRows ?? [])),
insert: vi.fn().mockImplementation(() => chainable(overrides._insertRows ?? [])),
update: vi.fn().mockImplementation(() => chainable(overrides._updateRows ?? [])),
delete: vi.fn().mockImplementation(() => chainable(overrides._deleteRows ?? [])),
...overrides,
};
}
describe("UsersService", () => {
beforeEach(() => {
vi.clearAllMocks();
});
describe("findById", () => {
it("should return user when found", async () => {
const user = { id: "u1", name: "Ali", email: "ali@test.com" };
const db = createMockDb({ _selectRows: [user] });
const service = new UsersService(db as any);
const result = await service.findById("u1");
expect(result).toEqual(user);
});
it("should throw NotFoundException when not found", async () => {
const db = createMockDb({ _selectRows: [] });
const service = new UsersService(db as any);
await expect(service.findById("nonexistent")).rejects.toThrow(NotFoundException);
});
});
describe("findByEmail", () => {
it("should return user when found", async () => {
const user = { id: "u1", email: "ali@test.com" };
const db = createMockDb({ _selectRows: [user] });
const service = new UsersService(db as any);
const result = await service.findByEmail("ali@test.com");
expect(result).toEqual(user);
});
it("should return null when not found", async () => {
const db = createMockDb({ _selectRows: [] });
const service = new UsersService(db as any);
const result = await service.findByEmail("nobody@test.com");
expect(result).toBeNull();
});
});
describe("updateProfile", () => {
it("should return updated user on success", async () => {
const updated = { id: "u1", name: "Updated" };
const db = createMockDb({ _updateRows: [updated] });
const service = new UsersService(db as any);
const result = await service.updateProfile("u1", { name: "Updated" });
expect(result).toEqual(updated);
});
it("should throw NotFoundException when user not found", async () => {
const db = createMockDb({ _updateRows: [] });
const service = new UsersService(db as any);
await expect(service.updateProfile("nonexistent", { name: "X" })).rejects.toThrow(NotFoundException);
});
});
describe("getConnections", () => {
it("should return provider map with google true", async () => {
// getConnections: select({providerId}).from(accounts).where(...) — where is terminal
const db = {
select: vi.fn().mockReturnValue({
from: vi.fn().mockReturnThis(),
where: vi.fn().mockReturnValue([{ providerId: "google" }]),
}),
};
const service = new UsersService(db as any);
const result = await service.getConnections("u1");
expect(result).toEqual({ google: true });
});
it("should return provider map with google false when no google account", async () => {
const db = {
select: vi.fn().mockReturnValue({
from: vi.fn().mockReturnThis(),
where: vi.fn().mockReturnValue([]),
}),
};
const service = new UsersService(db as any);
const result = await service.getConnections("u1");
expect(result).toEqual({ google: false });
});
});
describe("unlinkConnection", () => {
it("should return success when connection deleted", async () => {
const db = createMockDb({ _deleteRows: [{ id: "acc-1" }] });
const service = new UsersService(db as any);
const result = await service.unlinkConnection("u1", "google");
expect(result).toEqual({ success: true });
});
it("should throw NotFoundException when connection not found", async () => {
const db = createMockDb({ _deleteRows: [] });
const service = new UsersService(db as any);
await expect(service.unlinkConnection("u1", "google")).rejects.toThrow(NotFoundException);
});
});
describe("changePassword", () => {
it("should return success when password changed", async () => {
vi.mocked(verifyPassword).mockResolvedValue(true);
const db = {
select: vi.fn().mockReturnValue({
from: vi.fn().mockReturnThis(),
where: vi.fn().mockReturnThis(),
limit: vi.fn().mockReturnValue([{ id: "acc-1", password: "old-hash", providerId: "credential" }]),
}),
update: vi.fn().mockReturnValue({
set: vi.fn().mockReturnThis(),
where: vi.fn().mockResolvedValue(undefined),
}),
};
const service = new UsersService(db as any);
const result = await service.changePassword("u1", "oldpass", "newpassword");
expect(result).toEqual({ success: true });
});
it("should throw BadRequestException when no credential account", async () => {
const db = {
select: vi.fn().mockReturnValue({
from: vi.fn().mockReturnThis(),
where: vi.fn().mockReturnThis(),
limit: vi.fn().mockReturnValue([]),
}),
};
const service = new UsersService(db as any);
await expect(service.changePassword("u1", "old", "newpassword")).rejects.toThrow(BadRequestException);
});
it("should throw BadRequestException when current password is wrong", async () => {
vi.mocked(verifyPassword).mockResolvedValue(false);
const db = {
select: vi.fn().mockReturnValue({
from: vi.fn().mockReturnThis(),
where: vi.fn().mockReturnThis(),
limit: vi.fn().mockReturnValue([{ id: "acc-1", password: "hash", providerId: "credential" }]),
}),
};
const service = new UsersService(db as any);
await expect(service.changePassword("u1", "wrongpass", "newpassword")).rejects.toThrow(BadRequestException);
});
it("should throw BadRequestException when new password is too short", async () => {
vi.mocked(verifyPassword).mockResolvedValue(true);
const db = {
select: vi.fn().mockReturnValue({
from: vi.fn().mockReturnThis(),
where: vi.fn().mockReturnThis(),
limit: vi.fn().mockReturnValue([{ id: "acc-1", password: "hash", providerId: "credential" }]),
}),
};
const service = new UsersService(db as any);
await expect(service.changePassword("u1", "oldpass", "short")).rejects.toThrow(BadRequestException);
});
});
describe("deleteAccount", () => {
it("should return success when account deleted", async () => {
const db = createMockDb({ _deleteRows: [{ id: "u1" }] });
const service = new UsersService(db as any);
const result = await service.deleteAccount("u1");
expect(result).toEqual({ success: true });
});
it("should throw NotFoundException when user not found", async () => {
const db = createMockDb({ _deleteRows: [] });
const service = new UsersService(db as any);
await expect(service.deleteAccount("nonexistent")).rejects.toThrow(NotFoundException);
});
});
describe("findAll", () => {
it("should return paginated items and total", async () => {
const items = [{ id: "u1" }, { id: "u2" }];
// findAll: Promise.all([
// db.select().from(users).limit(limit).offset(offset).orderBy(createdAt),
// db.select({count}).from(users),
// ])
let callCount = 0;
const db = {
select: vi.fn().mockImplementation(() => {
callCount++;
const captured = callCount;
const chain: Record<string, any> = {};
chain.from = vi.fn().mockImplementation(() => {
if (captured === 2) return [{ id: "c" }]; // from is terminal for count query
return chain;
});
chain.limit = vi.fn().mockReturnValue(chain);
chain.offset = vi.fn().mockReturnValue(chain);
chain.orderBy = vi.fn().mockReturnValue(items);
return chain;
}),
};
const service = new UsersService(db as any);
const result = await service.findAll(1, 20);
expect(result.items).toEqual(items);
expect(result.total).toBeDefined();
});
});
});