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:
251
apps/api/src/users/users.service.spec.ts
Normal file
251
apps/api/src/users/users.service.spec.ts
Normal 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();
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user