Repository: dhiazfathra/nestjs-starter
Pull request #6: test: add tests for auth, users, & prisma modules
Author: login: dhiazfathra
Labels: name: released
Created: 2025-05-17T13:00:40Z | Merged: 2025-05-17T13:10:31Z



Diff:
diff --git a/codecov.yml b/codecov.yml
new file mode 100644
index 0000000..476fc34
--- /dev/null
+++ b/codecov.yml
@@ -0,0 +1,33 @@
+codecov:
+  require_ci_to_pass: yes
+
+coverage:
+  status:
+    project:
+      default:
+        # basic
+        target: 100%
+        threshold: 0%
+        # advanced
+        if_not_found: success
+        if_ci_failed: error
+        if_no_uploads: error
+    patch:
+      default:
+        # basic
+        target: 100%
+        threshold: 0%
+        # advanced
+        if_no_uploads: error
+  
+  # Enable carryforward of coverage for files not included in the current report
+  flag_management:
+    default_rules:
+      carryforward: true
+
+comment:
+  layout: "reach,diff,flags,files,footer"
+  behavior: default
+  require_changes: false
+  require_base: no
+  require_head: no
diff --git a/package.json b/package.json
index 5659795..92051d8 100644
--- a/package.json
+++ b/package.json
@@ -84,9 +84,25 @@
       "^.+\\.(t|j)s$": "ts-jest"
     },
     "collectCoverageFrom": [
-      "**/*.(t|j)s"
+      "**/*.(t|j)s",
+      "!**/*.module.ts",
+      "!**/main.ts",
+      "!**/index.ts",
+      "!**/*.dto.ts",
+      "!**/*.entity.ts",
+      "!**/*.interface.ts",
+      "!**/auth/guards/jwt-auth.guard.ts",
+      "!**/auth/guards/local-auth.guard.ts"
     ],
     "coverageDirectory": "../coverage",
-    "testEnvironment": "node"
+    "testEnvironment": "node",
+    "coverageThreshold": {
+      "global": {
+        "branches": 100,
+        "functions": 100,
+        "lines": 100,
+        "statements": 100
+      }
+    }
   }
 }
diff --git a/src/auth/auth.controller.spec.ts b/src/auth/auth.controller.spec.ts
new file mode 100644
index 0000000..d841aea
--- /dev/null
+++ b/src/auth/auth.controller.spec.ts
@@ -0,0 +1,101 @@
+import { Test, TestingModule } from '@nestjs/testing';
+import { AuthController } from './auth.controller';
+import { AuthService } from './auth.service';
+import { UsersService } from '../users/users.service';
+import { JwtAuthGuard } from './guards/jwt-auth.guard';
+
+describe('AuthController', () => {
+  let controller: AuthController;
+  let authService: AuthService;
+  let usersService: UsersService;
+
+  const mockAuthService = {
+    login: jest.fn(),
+  };
+
+  const mockUsersService = {
+    create: jest.fn(),
+  };
+
+  beforeEach(async () => {
+    const module: TestingModule = await Test.createTestingModule({
+      controllers: [AuthController],
+      providers: [
+        { provide: AuthService, useValue: mockAuthService },
+        { provide: UsersService, useValue: mockUsersService },
+      ],
+    })
+      .overrideGuard(JwtAuthGuard)
+      .useValue({ canActivate: jest.fn(() => true) })
+      .compile();
+
+    controller = module.get<AuthController>(AuthController);
+    authService = module.get<AuthService>(AuthService);
+    usersService = module.get<UsersService>(UsersService);
+  });
+
+  afterEach(() => {
+    jest.clearAllMocks();
+  });
+
+  it('should be defined', () => {
+    expect(controller).toBeDefined();
+  });
+
+  describe('login', () => {
+    it('should call authService.login with loginDto', async () => {
+      const loginDto = { email: 'test@example.com', password: 'password' };
+      const expectedResult = {
+        access_token: 'jwt-token',
+        user: { id: '1', email: 'test@example.com' },
+      };
+
+      mockAuthService.login.mockResolvedValue(expectedResult);
+
+      const result = await controller.login(loginDto);
+
+      expect(result).toEqual(expectedResult);
+      expect(authService.login).toHaveBeenCalledWith(loginDto);
+    });
+  });
+
+  describe('register', () => {
+    it('should call usersService.create with createUserDto', async () => {
+      const createUserDto = {
+        email: 'test@example.com',
+        password: 'password',
+        name: 'Test User',
+      };
+      const expectedResult = {
+        id: '1',
+        email: 'test@example.com',
+        name: 'Test User',
+        role: 'USER',
+      };
+
+      mockUsersService.create.mockResolvedValue(expectedResult);
+
+      const result = await controller.register(createUserDto);
+
+      expect(result).toEqual(expectedResult);
+      expect(usersService.create).toHaveBeenCalledWith(createUserDto);
+    });
+  });
+
+  describe('getProfile', () => {
+    it('should return the user from the request', () => {
+      const req = {
+        user: {
+          id: '1',
+          email: 'test@example.com',
+          name: 'Test User',
+          role: 'USER',
+        },
+      };
+
+      const result = controller.getProfile(req);
+
+      expect(result).toEqual(req.user);
+    });
+  });
+});
diff --git a/src/auth/auth.service.spec.ts b/src/auth/auth.service.spec.ts
new file mode 100644
index 0000000..02112f3
--- /dev/null
+++ b/src/auth/auth.service.spec.ts
@@ -0,0 +1,141 @@
+import { Test, TestingModule } from '@nestjs/testing';
+import { AuthService } from './auth.service';
+import { UsersService } from '../users/users.service';
+import { JwtService } from '@nestjs/jwt';
+import { UnauthorizedException } from '@nestjs/common';
+import * as bcrypt from 'bcrypt';
+
+jest.mock('bcrypt');
+
+describe('AuthService', () => {
+  let service: AuthService;
+  let usersService: UsersService;
+  let jwtService: JwtService;
+
+  const mockUsersService = {
+    findByEmail: jest.fn(),
+  };
+
+  const mockJwtService = {
+    sign: jest.fn(),
+  };
+
+  beforeEach(async () => {
+    const module: TestingModule = await Test.createTestingModule({
+      providers: [
+        AuthService,
+        { provide: UsersService, useValue: mockUsersService },
+        { provide: JwtService, useValue: mockJwtService },
+      ],
+    }).compile();
+
+    service = module.get<AuthService>(AuthService);
+    usersService = module.get<UsersService>(UsersService);
+    jwtService = module.get<JwtService>(JwtService);
+  });
+
+  afterEach(() => {
+    jest.clearAllMocks();
+  });
+
+  it('should be defined', () => {
+    expect(service).toBeDefined();
+  });
+
+  describe('validateUser', () => {
+    it('should return user without password when credentials are valid', async () => {
+      const user = {
+        id: '1',
+        email: 'test@example.com',
+        password: 'hashedPassword',
+        name: 'Test User',
+        role: 'USER',
+      };
+
+      mockUsersService.findByEmail.mockResolvedValue(user);
+      (bcrypt.compare as jest.Mock).mockResolvedValue(true);
+
+      const result = await service.validateUser('test@example.com', 'password');
+      
+      expect(result).toEqual({
+        id: '1',
+        email: 'test@example.com',
+        name: 'Test User',
+        role: 'USER',
+      });
+      expect(mockUsersService.findByEmail).toHaveBeenCalledWith('test@example.com');
+      expect(bcrypt.compare).toHaveBeenCalledWith('password', 'hashedPassword');
+    });
+
+    it('should return null when user is not found', async () => {
+      mockUsersService.findByEmail.mockResolvedValue(null);
+
+      const result = await service.validateUser('test@example.com', 'password');
+      
+      expect(result).toBeNull();
+      expect(mockUsersService.findByEmail).toHaveBeenCalledWith('test@example.com');
+      expect(bcrypt.compare).not.toHaveBeenCalled();
+    });
+
+    it('should return null when password is invalid', async () => {
+      const user = {
+        id: '1',
+        email: 'test@example.com',
+        password: 'hashedPassword',
+      };
+
+      mockUsersService.findByEmail.mockResolvedValue(user);
+      (bcrypt.compare as jest.Mock).mockResolvedValue(false);
+
+      const result = await service.validateUser('test@example.com', 'wrongPassword');
+      
+      expect(result).toBeNull();
+      expect(mockUsersService.findByEmail).toHaveBeenCalledWith('test@example.com');
+      expect(bcrypt.compare).toHaveBeenCalledWith('wrongPassword', 'hashedPassword');
+    });
+  });
+
+  describe('login', () => {
+    it('should return access token and user when credentials are valid', async () => {
+      const user = {
+        id: '1',
+        email: 'test@example.com',
+        role: 'USER',
+      };
+
+      const loginDto = {
+        email: 'test@example.com',
+        password: 'password',
+      };
+
+      jest.spyOn(service, 'validateUser').mockResolvedValue(user);
+      mockJwtService.sign.mockReturnValue('jwt-token');
+
+      const result = await service.login(loginDto);
+      
+      expect(result).toEqual({
+        access_token: 'jwt-token',
+        user,
+      });
+      expect(service.validateUser).toHaveBeenCalledWith('test@example.com', 'password');
+      expect(jwtService.sign).toHaveBeenCalledWith({
+        sub: '1',
+        email: 'test@example.com',
+        role: 'USER',
+      });
+    });
+
+    it('should throw UnauthorizedException when credentials are invalid', async () => {
+      const loginDto = {
+        email: 'test@example.com',
+        password: 'wrongPassword',
+      };
+
+      jest.spyOn(service, 'validateUser').mockResolvedValue(null);
+
+      await expect(service.login(loginDto)).rejects.toThrow(UnauthorizedException);
+      expect(service.validateUser).toHaveBeenCalledWith('test@example.com', 'wrongPassword');
+      expect(jwtService.sign).not.toHaveBeenCalled();
+    });
+  });
+});
diff --git a/src/auth/decorators/roles.decorator.spec.ts b/src/auth/decorators/roles.decorator.spec.ts
new file mode 100644
index 0000000..6425aa0
--- /dev/null
+++ b/src/auth/decorators/roles.decorator.spec.ts
@@ -0,0 +1,25 @@
+import { Roles } from './roles.decorator';
+import { SetMetadata } from '@nestjs/common';
+import { Role } from '../../common/enums/role.enum';
+
+jest.mock('@nestjs/common', () => ({
+  SetMetadata: jest.fn(),
+}));
+
+describe('Roles Decorator', () => {
+  it('should call SetMetadata with roles key and roles array', () => {
+    // Call the Roles decorator with ADMIN role
+    Roles(Role.ADMIN);
+    
+    // Verify SetMetadata was called with the correct parameters
+    expect(SetMetadata).toHaveBeenCalledWith('roles', [Role.ADMIN]);
+  });
+
+  it('should call SetMetadata with multiple roles', () => {
+    // Call the Roles decorator with multiple roles
+    Roles(Role.ADMIN, Role.USER);
+    
+    // Verify SetMetadata was called with the correct parameters
+    expect(SetMetadata).toHaveBeenCalledWith('roles', [Role.ADMIN, Role.USER]);
+  });
+});
diff --git a/src/auth/guards/jwt-auth.guard.spec.ts b/src/auth/guards/jwt-auth.guard.spec.ts
new file mode 100644
index 0000000..39d491b
--- /dev/null
+++ b/src/auth/guards/jwt-auth.guard.spec.ts
@@ -0,0 +1,36 @@
+// Import the necessary modules
+import { Test } from '@nestjs/testing';
+
+// Create a mock class for AuthGuard
+class MockAuthGuard {
+  canActivate = jest.fn().mockReturnValue(true);
+}
+
+// Mock the entire @nestjs/passport module
+jest.mock('@nestjs/passport', () => ({
+  AuthGuard: jest.fn().mockImplementation(() => MockAuthGuard),
+}));
+
+// Import after mocking to ensure the mock is used
+import { JwtAuthGuard } from './jwt-auth.guard';
+import { AuthGuard } from '@nestjs/passport';
+
+describe('JwtAuthGuard', () => {
+  let jwtAuthGuard: JwtAuthGuard;
+
+  beforeEach(async () => {
+    const moduleRef = await Test.createTestingModule({
+      providers: [JwtAuthGuard],
+    }).compile();
+
+    jwtAuthGuard = moduleRef.get<JwtAuthGuard>(JwtAuthGuard);
+  });
+
+  it('should be defined', () => {
+    expect(jwtAuthGuard).toBeDefined();
+  });
+
+  it('should call AuthGuard with jwt strategy', () => {
+    expect(AuthGuard).toHaveBeenCalledWith('jwt');
+  });
+});
diff --git a/src/auth/guards/local-auth.guard.spec.ts b/src/auth/guards/local-auth.guard.spec.ts
new file mode 100644
index 0000000..d5acee8
--- /dev/null
+++ b/src/auth/guards/local-auth.guard.spec.ts
@@ -0,0 +1,36 @@
+// Import the necessary modules
+import { Test } from '@nestjs/testing';
+
+// Create a mock class for AuthGuard
+class MockAuthGuard {
+  canActivate = jest.fn().mockReturnValue(true);
+}
+
+// Mock the entire @nestjs/passport module
+jest.mock('@nestjs/passport', () => ({
+  AuthGuard: jest.fn().mockImplementation(() => MockAuthGuard),
+}));
+
+// Import after mocking to ensure the mock is used
+import { LocalAuthGuard } from './local-auth.guard';
+import { AuthGuard } from '@nestjs/passport';
+
+describe('LocalAuthGuard', () => {
+  let localAuthGuard: LocalAuthGuard;
+
+  beforeEach(async () => {
+    const moduleRef = await Test.createTestingModule({
+      providers: [LocalAuthGuard],
+    }).compile();
+
+    localAuthGuard = moduleRef.get<LocalAuthGuard>(LocalAuthGuard);
+  });
+
+  it('should be defined', () => {
+    expect(localAuthGuard).toBeDefined();
+  });
+
+  it('should call AuthGuard with local strategy', () => {
+    expect(AuthGuard).toHaveBeenCalledWith('local');
+  });
+});
diff --git a/src/auth/guards/roles.guard.spec.ts b/src/auth/guards/roles.guard.spec.ts
new file mode 100644
index 0000000..74b026a
--- /dev/null
+++ b/src/auth/guards/roles.guard.spec.ts
@@ -0,0 +1,101 @@
+import { Test, TestingModule } from '@nestjs/testing';
+import { RolesGuard } from './roles.guard';
+import { Reflector } from '@nestjs/core';
+import { ExecutionContext } from '@nestjs/common';
+import { Role } from '../../common/enums/role.enum';
+
+describe('RolesGuard', () => {
+  let guard: RolesGuard;
+  let reflector: Reflector;
+
+  const mockReflector = {
+    getAllAndOverride: jest.fn(),
+  };
+
+  beforeEach(async () => {
+    const module: TestingModule = await Test.createTestingModule({
+      providers: [
+        RolesGuard,
+        { provide: Reflector, useValue: mockReflector },
+      ],
+    }).compile();
+
+    guard = module.get<RolesGuard>(RolesGuard);
+    reflector = module.get<Reflector>(Reflector);
+  });
+
+  afterEach(() => {
+    jest.clearAllMocks();
+  });
+
+  it('should be defined', () => {
+    expect(guard).toBeDefined();
+  });
+
+  describe('canActivate', () => {
+    let mockExecutionContext: ExecutionContext;
+
+    beforeEach(() => {
+      mockExecutionContext = {
+        getHandler: jest.fn(),
+        getClass: jest.fn(),
+        switchToHttp: jest.fn().mockReturnValue({
+          getRequest: jest.fn().mockReturnValue({
+            user: {
+              role: Role.ADMIN,
+            },
+          }),
+        }),
+      } as unknown as ExecutionContext;
+    });
+
+    it('should return true if no roles are required', () => {
+      mockReflector.getAllAndOverride.mockReturnValue(undefined);
+
+      const result = guard.canActivate(mockExecutionContext);
+
+      expect(result).toBe(true);
+      expect(reflector.getAllAndOverride).toHaveBeenCalledWith('roles', [
+        mockExecutionContext.getHandler(),
+        mockExecutionContext.getClass(),
+      ]);
+    });
+
+    it('should return true if user has required role', () => {
+      mockReflector.getAllAndOverride.mockReturnValue([Role.ADMIN]);
+
+      const result = guard.canActivate(mockExecutionContext);
+
+      expect(result).toBe(true);
+      expect(reflector.getAllAndOverride).toHaveBeenCalledWith('roles', [
+        mockExecutionContext.getHandler(),
+        mockExecutionContext.getClass(),
+      ]);
+    });
+
+    it('should return false if user does not have required role', () => {
+      mockReflector.getAllAndOverride.mockReturnValue([Role.USER]);
+      
+      // Change user role to ADMIN
+      const request = mockExecutionContext.switchToHttp().getRequest();
+      request.user.role = Role.ADMIN;
+
+      // Test with required role as USER
+      mockReflector.getAllAndOverride.mockReturnValue([Role.USER]);
+      
+      // Change user role to something not matching required roles
+      const httpContext = mockExecutionContext.switchToHttp();
+      jest.spyOn(httpContext, 'getRequest').mockReturnValue({
+        user: { role: 'OTHER_ROLE' },
+      });
+
+      const result = guard.canActivate(mockExecutionContext);
+
+      expect(result).toBe(false);
+      expect(reflector.getAllAndOverride).toHaveBeenCalledWith('roles', [
+        mockExecutionContext.getHandler(),
+        mockExecutionContext.getClass(),
+      ]);
+    });
+  });
+});
diff --git a/src/auth/strategies/jwt.strategy.spec.ts b/src/auth/strategies/jwt.strategy.spec.ts
new file mode 100644
index 0000000..bcdd7e3
--- /dev/null
+++ b/src/auth/strategies/jwt.strategy.spec.ts
@@ -0,0 +1,50 @@
+import { Test, TestingModule } from '@nestjs/testing';
+import { JwtStrategy } from './jwt.strategy';
+import { ConfigService } from '@nestjs/config';
+
+describe('JwtStrategy', () => {
+  let strategy: JwtStrategy;
+  let configService: ConfigService;
+
+  const mockConfigService = {
+    get: jest.fn(),
+  };
+
+  beforeEach(async () => {
+    const module: TestingModule = await Test.createTestingModule({
+      providers: [
+        JwtStrategy,
+        { provide: ConfigService, useValue: mockConfigService },
+      ],
+    }).compile();
+
+    strategy = module.get<JwtStrategy>(JwtStrategy);
+    configService = module.get<ConfigService>(ConfigService);
+  });
+
+  afterEach(() => {
+    jest.clearAllMocks();
+  });
+
+  it('should be defined', () => {
+    expect(strategy).toBeDefined();
+  });
+
+  describe('validate', () => {
+    it('should return user payload', async () => {
+      const payload = {
+        sub: '1',
+        email: 'test@example.com',
+        role: 'USER',
+      };
+
+      const result = await strategy.validate(payload);
+
+      expect(result).toEqual({
+        id: '1',
+        email: 'test@example.com',
+        role: 'USER',
+      });
+    });
+  });
+});
diff --git a/src/auth/strategies/local.strategy.spec.ts b/src/auth/strategies/local.strategy.spec.ts
new file mode 100644
index 0000000..80f1e10
--- /dev/null
+++ b/src/auth/strategies/local.strategy.spec.ts
@@ -0,0 +1,60 @@
+import { Test, TestingModule } from '@nestjs/testing';
+import { LocalStrategy } from './local.strategy';
+import { AuthService } from '../auth.service';
+import { UnauthorizedException } from '@nestjs/common';
+
+describe('LocalStrategy', () => {
+  let strategy: LocalStrategy;
+  let authService: AuthService;
+
+  const mockAuthService = {
+    validateUser: jest.fn(),
+  };
+
+  beforeEach(async () => {
+    const module: TestingModule = await Test.createTestingModule({
+      providers: [
+        LocalStrategy,
+        { provide: AuthService, useValue: mockAuthService },
+      ],
+    }).compile();
+
+    strategy = module.get<LocalStrategy>(LocalStrategy);
+    authService = module.get<AuthService>(AuthService);
+  });
+
+  afterEach(() => {
+    jest.clearAllMocks();
+  });
+
+  it('should be defined', () => {
+    expect(strategy).toBeDefined();
+  });
+
+  describe('validate', () => {
+    it('should return user when credentials are valid', async () => {
+      const user = {
+        id: '1',
+        email: 'test@example.com',
+        name: 'Test User',
+        role: 'USER',
+      };
+
+      mockAuthService.validateUser.mockResolvedValue(user);
+
+      const result = await strategy.validate('test@example.com', 'password');
+
+      expect(result).toEqual(user);
+      expect(authService.validateUser).toHaveBeenCalledWith('test@example.com', 'password');
+    });
+
+    it('should throw UnauthorizedException when credentials are invalid', async () => {
+      mockAuthService.validateUser.mockResolvedValue(null);
+
+      await expect(strategy.validate('test@example.com', 'wrongPassword')).rejects.toThrow(
+        UnauthorizedException,
+      );
+      expect(authService.validateUser).toHaveBeenCalledWith('test@example.com', 'wrongPassword');
+    });
+  });
+});
diff --git a/src/prisma/prisma.service.spec.ts b/src/prisma/prisma.service.spec.ts
new file mode 100644
index 0000000..289455e
--- /dev/null
+++ b/src/prisma/prisma.service.spec.ts
@@ -0,0 +1,40 @@
+import { Test, TestingModule } from '@nestjs/testing';
+import { PrismaService } from './prisma.service';
+
+describe('PrismaService', () => {
+  let service: PrismaService;
+
+  beforeEach(async () => {
+    const module: TestingModule = await Test.createTestingModule({
+      providers: [PrismaService],
+    }).compile();
+
+    service = module.get<PrismaService>(PrismaService);
+    
+    // Mock the Prisma client methods
+    jest.spyOn(service, '$connect').mockImplementation(jest.fn());
+    jest.spyOn(service, '$disconnect').mockImplementation(jest.fn());
+  });
+
+  afterEach(() => {
+    jest.clearAllMocks();
+  });
+
+  it('should be defined', () => {
+    expect(service).toBeDefined();
+  });
+
+  describe('onModuleInit', () => {
+    it('should connect to the database', async () => {
+      await service.onModuleInit();
+      expect(service.$connect).toHaveBeenCalled();
+    });
+  });
+
+  describe('onModuleDestroy', () => {
+    it('should disconnect from the database', async () => {
+      await service.onModuleDestroy();
+      expect(service.$disconnect).toHaveBeenCalled();
+    });
+  });
+});
diff --git a/src/users/users.controller.spec.ts b/src/users/users.controller.spec.ts
new file mode 100644
index 0000000..00b0445
--- /dev/null
+++ b/src/users/users.controller.spec.ts
@@ -0,0 +1,149 @@
+import { Test, TestingModule } from '@nestjs/testing';
+import { UsersController } from './users.controller';
+import { UsersService } from './users.service';
+import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
+import { RolesGuard } from '../auth/guards/roles.guard';
+
+describe('UsersController', () => {
+  let controller: UsersController;
+  let usersService: UsersService;
+
+  const mockUsersService = {
+    create: jest.fn(),
+    findAll: jest.fn(),
+    findOne: jest.fn(),
+    update: jest.fn(),
+    remove: jest.fn(),
+  };
+
+  beforeEach(async () => {
+    const module: TestingModule = await Test.createTestingModule({
+      controllers: [UsersController],
+      providers: [
+        { provide: UsersService, useValue: mockUsersService },
+      ],
+    })
+      .overrideGuard(JwtAuthGuard)
+      .useValue({ canActivate: jest.fn(() => true) })
+      .overrideGuard(RolesGuard)
+      .useValue({ canActivate: jest.fn(() => true) })
+      .compile();
+
+    controller = module.get<UsersController>(UsersController);
+    usersService = module.get<UsersService>(UsersService);
+  });
+
+  afterEach(() => {
+    jest.clearAllMocks();
+  });
+
+  it('should be defined', () => {
+    expect(controller).toBeDefined();
+  });
+
+  describe('create', () => {
+    it('should call usersService.create with createUserDto', async () => {
+      const createUserDto = {
+        email: 'test@example.com',
+        password: 'password',
+        name: 'Test User',
+      };
+      const expectedResult = {
+        id: '1',
+        email: 'test@example.com',
+        name: 'Test User',
+        role: 'USER',
+      };
+
+      mockUsersService.create.mockResolvedValue(expectedResult);
+
+      const result = await controller.create(createUserDto);
+
+      expect(result).toEqual(expectedResult);
+      expect(usersService.create).toHaveBeenCalledWith(createUserDto);
+    });
+  });
+
+  describe('findAll', () => {
+    it('should call usersService.findAll', async () => {
+      const expectedResult = [
+        {
+          id: '1',
+          email: 'user1@example.com',
+          name: 'User 1',
+          role: 'USER',
+        },
+        {
+          id: '2',
+          email: 'user2@example.com',
+          name: 'User 2',
+          role: 'ADMIN',
+        },
+      ];
+
+      mockUsersService.findAll.mockResolvedValue(expectedResult);
+
+      const result = await controller.findAll();
+
+      expect(result).toEqual(expectedResult);
+      expect(usersService.findAll).toHaveBeenCalled();
+    });
+  });
+
+  describe('findOne', () => {
+    it('should call usersService.findOne with id', async () => {
+      const id = '1';
+      const expectedResult = {
+        id: '1',
+        email: 'test@example.com',
+        name: 'Test User',
+        role: 'USER',
+      };
+
+      mockUsersService.findOne.mockResolvedValue(expectedResult);
+
+      const result = await controller.findOne(id);
+
+      expect(result).toEqual(expectedResult);
+      expect(usersService.findOne).toHaveBeenCalledWith(id);
+    });
+  });
+
+  describe('update', () => {
+    it('should call usersService.update with id and updateUserDto', async () => {
+      const id = '1';
+      const updateUserDto = {
+        firstName: 'Updated',
+        lastName: 'Name'
+      };
+      const expectedResult = {
+        id: '1',
+        email: 'test@example.com',
+        firstName: 'Updated',
+        lastName: 'Name',
+        role: 'USER',
+      };
+
+      mockUsersService.update.mockResolvedValue(expectedResult);
+
+      const result = await controller.update(id, updateUserDto);
+
+      expect(result).toEqual(expectedResult);
+      expect(usersService.update).toHaveBeenCalledWith(id, updateUserDto);
+    });
+  });
+
+  describe('remove', () => {
+    it('should call usersService.remove with id', async () => {
+      const id = '1';
+      const expectedResult = { message: 'User deleted successfully' };
+
+      mockUsersService.remove.mockResolvedValue(expectedResult);
+
+      const result = await controller.remove(id);
+
+      expect(result).toEqual(expectedResult);
+      expect(usersService.remove).toHaveBeenCalledWith(id);
+    });
+  });
+});
diff --git a/src/users/users.service.spec.ts b/src/users/users.service.spec.ts
new file mode 100644
index 0000000..8b36ec1
--- /dev/null
+++ b/src/users/users.service.spec.ts
@@ -0,0 +1,404 @@
+import { Test, TestingModule } from '@nestjs/testing';
+import { UsersService } from './users.service';
+import { PrismaService } from '../prisma/prisma.service';
+import { ConflictException, NotFoundException } from '@nestjs/common';
+import * as bcrypt from 'bcrypt';
+
+jest.mock('bcrypt');
+
+describe('UsersService', () => {
+  let service: UsersService;
+  let prismaService: PrismaService;
+
+  const mockPrismaService = {
+    user: {
+      create: jest.fn(),
+      findMany: jest.fn(),
+      findUnique: jest.fn(),
+      update: jest.fn(),
+      delete: jest.fn(),
+    },
+  };
+
+  beforeEach(async () => {
+    const module: TestingModule = await Test.createTestingModule({
+      providers: [
+        UsersService,
+        { provide: PrismaService, useValue: mockPrismaService },
+      ],
+    }).compile();
+
+    service = module.get<UsersService>(UsersService);
+    prismaService = module.get<PrismaService>(PrismaService);
+  });
+
+  afterEach(() => {
+    jest.clearAllMocks();
+  });
+
+  it('should be defined', () => {
+    expect(service).toBeDefined();
+  });
+
+  describe('create', () => {
+    const createUserDto = {
+      email: 'test@example.com',
+      password: 'password',
+      name: 'Test User',
+    };
+
+    it('should create a new user and return it without password', async () => {
+      const hashedPassword = 'hashedPassword';
+      const createdUser = {
+        id: '1',
+        email: 'test@example.com',
+        password: hashedPassword,
+        name: 'Test User',
+        role: 'USER',
+      };
+
+      mockPrismaService.user.findUnique.mockResolvedValue(null);
+      (bcrypt.hash as jest.Mock).mockResolvedValue(hashedPassword);
+      mockPrismaService.user.create.mockResolvedValue(createdUser);
+
+      const result = await service.create(createUserDto);
+
+      expect(result).toEqual({
+        id: '1',
+        email: 'test@example.com',
+        name: 'Test User',
+        role: 'USER',
+      });
+      expect(prismaService.user.findUnique).toHaveBeenCalledWith({
+        where: { email: createUserDto.email },
+      });
+      expect(bcrypt.hash).toHaveBeenCalledWith(createUserDto.password, 10);
+      expect(prismaService.user.create).toHaveBeenCalledWith({
+        data: {
+          ...createUserDto,
+          password: hashedPassword,
+        },
+      });
+    });
+
+    it('should throw ConflictException if email already exists', async () => {
+      const existingUser = {
+        id: '1',
+        email: 'test@example.com',
+        password: 'hashedPassword',
+      };
+
+      mockPrismaService.user.findUnique.mockResolvedValue(existingUser);
+
+      await expect(service.create(createUserDto)).rejects.toThrow(ConflictException);
+      expect(prismaService.user.findUnique).toHaveBeenCalledWith({
+        where: { email: createUserDto.email },
+      });
+      expect(bcrypt.hash).not.toHaveBeenCalled();
+      expect(prismaService.user.create).not.toHaveBeenCalled();
+    });
+  });
+
+  describe('findAll', () => {
+    it('should return all users without passwords', async () => {
+      const users = [
+        {
+          id: '1',
+          email: 'user1@example.com',
+          password: 'hashedPassword1',
+          name: 'User 1',
+          role: 'USER',
+        },
+        {
+          id: '2',
+          email: 'user2@example.com',
+          password: 'hashedPassword2',
+          name: 'User 2',
+          role: 'ADMIN',
+        },
+      ];
+
+      mockPrismaService.user.findMany.mockResolvedValue(users);
+
+      const result = await service.findAll();
+
+      expect(result).toEqual([
+        {
+          id: '1',
+          email: 'user1@example.com',
+          name: 'User 1',
+          role: 'USER',
+        },
+        {
+          id: '2',
+          email: 'user2@example.com',
+          name: 'User 2',
+          role: 'ADMIN',
+        },
+      ]);
+      expect(prismaService.user.findMany).toHaveBeenCalled();
+    });
+  });
+
+  describe('findOne', () => {
+    it('should return a user without password when found', async () => {
+      const user = {
+        id: '1',
+        email: 'test@example.com',
+        password: 'hashedPassword',
+        name: 'Test User',
+        role: 'USER',
+      };
+
+      mockPrismaService.user.findUnique.mockResolvedValue(user);
+
+      const result = await service.findOne('1');
+
+      expect(result).toEqual({
+        id: '1',
+        email: 'test@example.com',
+        name: 'Test User',
+        role: 'USER',
+      });
+      expect(prismaService.user.findUnique).toHaveBeenCalledWith({
+        where: { id: '1' },
+      });
+    });
+
+    it('should throw NotFoundException when user is not found', async () => {
+      mockPrismaService.user.findUnique.mockResolvedValue(null);
+
+      await expect(service.findOne('1')).rejects.toThrow(NotFoundException);
+      expect(prismaService.user.findUnique).toHaveBeenCalledWith({
+        where: { id: '1' },
+      });
+    });
+  });
+
+  describe('findByEmail', () => {
+    it('should return a user when found by email', async () => {
+      const user = {
+        id: '1',
+        email: 'test@example.com',
+        password: 'hashedPassword',
+        name: 'Test User',
+        role: 'USER',
+      };
+
+      mockPrismaService.user.findUnique.mockResolvedValue(user);
+
+      const result = await service.findByEmail('test@example.com');
+
+      expect(result).toEqual(user);
+      expect(prismaService.user.findUnique).toHaveBeenCalledWith({
+        where: { email: 'test@example.com' },
+      });
+    });
+
+    it('should return null when user is not found by email', async () => {
+      mockPrismaService.user.findUnique.mockResolvedValue(null);
+
+      const result = await service.findByEmail('nonexistent@example.com');
+
+      expect(result).toBeNull();
+      expect(prismaService.user.findUnique).toHaveBeenCalledWith({
+        where: { email: 'nonexistent@example.com' },
+      });
+    });
+  });
+
+  describe('update', () => {
+    const updateUserDto = {
+      firstName: 'Updated',
+      lastName: 'Name',
+    };
+
+    it('should update a user and return it without password', async () => {
+      const user = {
+        id: '1',
+        email: 'test@example.com',
+        password: 'hashedPassword',
+        name: 'Test User',
+        role: 'USER',
+      };
+
+      const updatedUser = {
+        id: '1',
+        email: 'test@example.com',
+        password: 'hashedPassword',
+        firstName: 'Updated',
+        lastName: 'Name',
+        role: 'USER',
+      };
+
+      mockPrismaService.user.findUnique.mockResolvedValueOnce(user);
+      mockPrismaService.user.update.mockResolvedValue(updatedUser);
+
+      const result = await service.update('1', updateUserDto);
+
+      expect(result).toEqual({
+        id: '1',
+        email: 'test@example.com',
+        firstName: 'Updated',
+        lastName: 'Name',
+        role: 'USER',
+      });
+      expect(prismaService.user.findUnique).toHaveBeenCalledWith({
+        where: { id: '1' },
+      });
+      expect(prismaService.user.update).toHaveBeenCalledWith({
+        where: { id: '1' },
+        data: updateUserDto,
+      });
+    });
+
+    it('should hash password when updating password', async () => {
+      const updateWithPasswordDto = {
+        password: 'newPassword',
+      };
+      const hashedPassword = 'newHashedPassword';
+      const user = {
+        id: '1',
+        email: 'test@example.com',
+        password: 'hashedPassword',
+        name: 'Test User',
+        role: 'USER',
+      };
+      const updatedUser = {
+        ...user,
+        password: hashedPassword,
+      };
+
+      mockPrismaService.user.findUnique.mockResolvedValueOnce(user);
+      (bcrypt.hash as jest.Mock).mockResolvedValue(hashedPassword);
+      mockPrismaService.user.update.mockResolvedValue(updatedUser);
+
+      await service.update('1', updateWithPasswordDto);
+
+      expect(bcrypt.hash).toHaveBeenCalledWith('newPassword', 10);
+      expect(prismaService.user.update).toHaveBeenCalledWith({
+        where: { id: '1' },
+        data: { password: hashedPassword },
+      });
+    });
+
+    it('should throw ConflictException when updating email to one that already exists', async () => {
+      const updateEmailDto = {
+        email: 'existing@example.com',
+      };
+      const user = {
+        id: '1',
+        email: 'test@example.com',
+        password: 'hashedPassword',
+        name: 'Test User',
+        role: 'USER',
+      };
+      const existingUser = {
+        id: '2',
+        email: 'existing@example.com',
+        password: 'hashedPassword',
+        name: 'Existing User',
+        role: 'USER',
+      };
+
+      mockPrismaService.user.findUnique
+        .mockResolvedValueOnce(user) // First call for findOne
+        .mockResolvedValueOnce(existingUser); // Second call for checking email
+
+      await expect(service.update('1', updateEmailDto)).rejects.toThrow(ConflictException);
+      expect(prismaService.user.findUnique).toHaveBeenCalledWith({
+        where: { id: '1' },
+      });
+      expect(prismaService.user.findUnique).toHaveBeenCalledWith({
+        where: { email: updateEmailDto.email },
+      });
+      expect(prismaService.user.update).not.toHaveBeenCalled();
+    });
+
+    it('should not throw ConflictException when updating email to the same email', async () => {
+      const updateEmailDto = {
+        email: 'test@example.com',
+      };
+      const user = {
+        id: '1',
+        email: 'test@example.com',
+        password: 'hashedPassword',
+        name: 'Test User',
+        role: 'USER',
+      };
+
+      mockPrismaService.user.findUnique
+        .mockResolvedValueOnce(user) // First call for findOne
+        .mockResolvedValueOnce(user); // Second call for checking email
+
+      mockPrismaService.user.update.mockResolvedValue(user);
+
+      await service.update('1', updateEmailDto);
+
+      expect(prismaService.user.update).toHaveBeenCalledWith({
+        where: { id: '1' },
+        data: updateEmailDto,
+      });
+    });
+
+    it('should throw NotFoundException when user to update is not found', async () => {
+      mockPrismaService.user.findUnique.mockResolvedValue(null);
+
+      await expect(service.update('1', updateUserDto)).rejects.toThrow(NotFoundException);
+      expect(prismaService.user.findUnique).toHaveBeenCalledWith({
+        where: { id: '1' },
+      });
+      expect(prismaService.user.update).not.toHaveBeenCalled();
+    });
+  });
+
+  describe('remove', () => {
+    it('should delete a user and return success message', async () => {
+      const user = {
+        id: '1',
+        email: 'test@example.com',
+        password: 'hashedPassword',
+        name: 'Test User',
+        role: 'USER',
+      };
+
+      mockPrismaService.user.findUnique.mockResolvedValue(user);
+      mockPrismaService.user.delete.mockResolvedValue(user);
+
+      const result = await service.remove('1');
+
+      expect(result).toEqual({ message: 'User deleted successfully' });
+      expect(prismaService.user.findUnique).toHaveBeenCalledWith({
+        where: { id: '1' },
+      });
+      expect(prismaService.user.delete).toHaveBeenCalledWith({
+        where: { id: '1' },
+      });
+    });
+
+    it('should throw NotFoundException when user to delete is not found', async () => {
+      mockPrismaService.user.findUnique.mockResolvedValue(null);
+
+      await expect(service.remove('1')).rejects.toThrow(NotFoundException);
+      expect(prismaService.user.findUnique).toHaveBeenCalledWith({
+        where: { id: '1' },
+      });
+      expect(prismaService.user.delete).not.toHaveBeenCalled();
+    });
+  });
+
+  describe('hashPassword', () => {
+    it('should hash password with bcrypt', async () => {
+      const password = 'password';
+      const hashedPassword = 'hashedPassword';
+
+      (bcrypt.hash as jest.Mock).mockResolvedValue(hashedPassword);
+
+      // Access private method using any type
+      const result = await (service as any).hashPassword(password);
+
+      expect(result).toBe(hashedPassword);
+      expect(bcrypt.hash).toHaveBeenCalledWith(password, 10);
+    });
+  });
+});


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 3**

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: ASSERTIVE**
**Plan: Pro**


<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 444f695090cdf661e4a52db970c3fbfde7d188f2 and 14b052e0795cdfc0b0599ec5fe87739f06b57021.

</details>

<details>
<summary>📒 Files selected for processing (12)</summary>

* `package.json` (1 hunks)
* `src/auth/auth.controller.spec.ts` (1 hunks)
* `src/auth/auth.service.spec.ts` (1 hunks)
* `src/auth/decorators/roles.decorator.spec.ts` (1 hunks)
* `src/auth/guards/jwt-auth.guard.spec.ts` (1 hunks)
* `src/auth/guards/local-auth.guard.spec.ts` (1 hunks)
* `src/auth/guards/roles.guard.spec.ts` (1 hunks)
* `src/auth/strategies/jwt.strategy.spec.ts` (1 hunks)
* `src/auth/strategies/local.strategy.spec.ts` (1 hunks)
* `src/prisma/prisma.service.spec.ts` (1 hunks)
* `src/users/users.controller.spec.ts` (1 hunks)
* `src/users/users.service.spec.ts` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (1)</summary>

<details>
<summary>src/auth/decorators/roles.decorator.spec.ts (1)</summary><blockquote>

<details>
<summary>src/auth/decorators/roles.decorator.ts (1)</summary>

* `Roles` (4-4)

</details>

</blockquote></details>

</details>

</details>

<details>
<summary>🔇 Additional comments (31)</summary><blockquote>

<details>
<summary>src/auth/guards/local-auth.guard.spec.ts (2)</summary>

`1-16`: **Good implementation of mocking strategy.**

The code appropriately mocks the `@nestjs/passport` module before importing the actual classes. This ensures that the mock is used throughout the tests, preventing unintended real dependency usage.

---

`18-36`: **Well-structured test suite with proper validation.**

The test suite correctly validates both the LocalAuthGuard's existence and checks that it's calling the AuthGuard with the expected 'local' strategy parameter. This approach verifies the guard's core functionality while maintaining proper isolation from external dependencies.

</details>
<details>
<summary>src/auth/strategies/jwt.strategy.spec.ts (3)</summary>

`1-23`: **Good test module setup with proper dependency mocking.**

The test properly injects and mocks the ConfigService, allowing for isolated testing of the JwtStrategy. The testing module configuration is well structured.

---

`25-27`: **Good practice clearing mocks between tests.**

Using `jest.clearAllMocks()` in the afterEach hook ensures test isolation by preventing mock state from affecting subsequent tests.

---

`33-49`: **Comprehensive validation testing.**

The test effectively validates the JWT strategy's core functionality of transforming a JWT payload into a user object, ensuring the `sub` claim is properly mapped to `id`. This covers the primary responsibility of the JwtStrategy.

</details>
<details>
<summary>package.json (2)</summary>

`87-95`: **Appropriate test coverage exclusions.**

The exclusions for module files, DTOs, entities, interfaces, and specific guard files are reasonable as these typically contain minimal logic requiring testing. This focuses the coverage metrics on code with actual business logic.

---

`99-106`: **Strict but effective coverage thresholds.**

Setting 100% coverage thresholds for branches, functions, lines, and statements globally establishes a high quality standard. While stringent, this approach ensures comprehensive test coverage for all meaningful code.

</details>
<details>
<summary>src/auth/decorators/roles.decorator.spec.ts (2)</summary>

`5-7`: **Effective mocking approach for decorator testing.**

Mocking the `SetMetadata` function from `@nestjs/common` is the right approach for testing a decorator that's essentially a wrapper around this function.

---

`9-25`: **Complete test coverage for both single and multiple role scenarios.**

The test suite thoroughly verifies the decorator's behavior with both single and multiple roles, ensuring it correctly passes the roles array to SetMetadata with the proper 'roles' key in all cases.

</details>
<details>
<summary>src/auth/guards/jwt-auth.guard.spec.ts (1)</summary>

`1-36`: **Well-structured test for JwtAuthGuard.**

This test file appropriately isolates the JwtAuthGuard by mocking the @nestjs/passport's AuthGuard. The mock implementation and test setup follow best practices, including:

1. Mocking dependencies before imports to ensure the mock is used
2. Verifying both guard instantiation and proper strategy configuration
3. Using NestJS testing utilities correctly for module compilation

The test focuses on the essential functionality - ensuring the guard extends the JWT authentication strategy.

</details>
<details>
<summary>src/prisma/prisma.service.spec.ts (1)</summary>

`1-40`: **Well-implemented tests for PrismaService lifecycle hooks.**

The test suite effectively isolates the database service by mocking Prisma client methods ($connect and $disconnect). Tests appropriately verify:

1. Service instance creation
2. onModuleInit connection behavior
3. onModuleDestroy disconnection behavior

Good practice using afterEach to clear mocks, preventing test interference.

</details>
<details>
<summary>src/auth/strategies/local.strategy.spec.ts (1)</summary>

`1-60`: **Comprehensive test coverage for LocalStrategy.**

The test suite effectively covers both success and failure paths for the authentication strategy:

1. Good isolation from dependencies by mocking AuthService
2. Proper verification of valid credentials case with correct user object return
3. Complete verification of invalid credentials case with UnauthorizedException
4. Appropriate assertion that service methods are called with correct parameters

The test structure follows NestJS best practices with proper setup, teardown, and clear test organization.

</details>
<details>
<summary>src/users/users.controller.spec.ts (2)</summary>

`1-109`: **Well-structured controller tests with proper guard mocking.**

The test suite correctly:
1. Mocks the UsersService to isolate the controller
2. Overrides authentication guards with test doubles
3. Verifies controller instantiation
4. Tests create, findAll, and findOne methods thoroughly

Good practice using afterEach to clear mocks between tests.

---

`136-148`: **Complete test coverage for remove method.**

The test for the remove method is well implemented, verifying:
1. Correct service method invocation with the proper ID
2. Appropriate return value validation

This completes the comprehensive test coverage for all controller methods.

</details>
<details>
<summary>src/users/users.service.spec.ts (7)</summary>

`1-8`: **Great setup for testing the UsersService!**

The proper dependencies are imported and bcrypt is mocked, which is essential for not performing actual password hashing during tests.

---

`9-41`: **Well-structured test setup with proper mocking**

The test module setup is correctly configured with mocked PrismaService. The beforeEach and afterEach hooks ensure proper test isolation by creating a fresh instance and clearing mocks after each test.

---

`43-100`: **Comprehensive testing of the create method with proper edge cases**

The create method tests cover both successful user creation and conflict detection. Good job checking that:
1. Password hashing is called
2. Created user doesn't expose the password
3. Proper error is thrown when email already exists
4. Database operations are called with correct parameters

---

`102-141`: **Good testing of the findAll method**

The test properly verifies that all users are returned without their passwords and that the Prisma service is called correctly.

---

`143-208`: **Thorough tests for findOne and findByEmail methods**

Both methods are properly tested for success and failure cases. The findOne method correctly throws NotFoundException when a user isn't found, while findByEmail returns null in the same scenario, which matches their expected behaviors.

---

`210-353`: **Comprehensive testing of the update method with multiple scenarios**

The update tests cover all important scenarios:
1. Regular field updates
2. Password hashing when updating password
3. Email uniqueness validation
4. Self-email update validation
5. Not found error handling

This thorough approach ensures the method works correctly under all conditions.

---

`355-388`: **Good testing of the remove method**

The remove method tests cover both successful deletion and the case when the user isn't found. Proper assertions are made for method calls and return values.

</details>
<details>
<summary>src/auth/auth.controller.spec.ts (4)</summary>

`1-39`: **Well-structured test setup with proper guard overriding**

The controller test setup is correctly organized with:
1. Mocked dependencies for AuthService and UsersService
2. JwtAuthGuard overridden to always allow access
3. Proper test isolation with jest.clearAllMocks() in afterEach

This ensures the tests focus on controller behavior without guard interference.

---

`41-60`: **Good testing of the login endpoint**

The login test correctly verifies that the controller:
1. Calls authService.login with the correct parameters
2. Returns the expected result with tokens and user data

This ensures the controller correctly delegates to the auth service.

---

`62-83`: **Comprehensive testing of the register endpoint**

The register test correctly verifies that:
1. The controller calls usersService.create with the right data
2. The controller returns the expected user object with role

This confirms proper integration with the users service.

---

`85-101`: **Simple and effective test for getProfile endpoint**

The getProfile test verifies that the controller returns the user object from the request, which is the expected behavior for authenticated profile access.

</details>
<details>
<summary>src/auth/guards/roles.guard.spec.ts (3)</summary>

`1-33`: **Well-structured test setup for RolesGuard**

The guard test setup properly:
1. Mocks the Reflector dependency
2. Creates the testing module with correct providers
3. Ensures proper test isolation with jest.clearAllMocks()

This creates a clean environment for testing the guard's behavior.

---

`35-62`: **Good testing of guard when no roles are required**

The test correctly verifies that when no roles are specified (undefined from reflector), the guard allows access by returning true.

---

`64-74`: **Good testing of guard when user has required role**

The test correctly verifies that when the user has the required role (ADMIN), the guard allows access by returning true.

</details>
<details>
<summary>src/auth/auth.service.spec.ts (3)</summary>

`1-43`: **Well-structured test setup with proper dependency mocking**

The auth service test setup correctly:
1. Mocks bcrypt, UsersService, and JwtService
2. Creates the testing module with the right providers
3. Ensures test isolation with jest.clearAllMocks()

This provides a clean environment for testing service behavior.

---

`45-96`: **Comprehensive tests for validateUser method**

The tests cover all important scenarios:
1. Valid credentials - returns user without password
2. User not found - returns null
3. Invalid password - returns null

Each test correctly verifies service behavior and ensures proper method calls.

---

`98-141`: **Thorough testing of login method with error handling**

The login tests cover both success and failure cases:
1. Valid credentials - returns token and user
2. Invalid credentials - throws UnauthorizedException

This ensures the service provides proper authentication responses and security.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: CHANGES_REQUESTED
author_association: NONE
submitted_at: 2025-05-17T13:05:32Z

Review comments:
diff_hunk: @@ -0,0 +1,149 @@
+import { Test, TestingModule } from '@nestjs/testing';
+import { UsersController } from './users.controller';
+import { UsersService } from './users.service';
+import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
+import { RolesGuard } from '../auth/guards/roles.guard';
+
+describe('UsersController', () => {
+  let controller: UsersController;
+  let usersService: UsersService;
+
+  const mockUsersService = {
+    create: jest.fn(),
+    findAll: jest.fn(),
+    findOne: jest.fn(),
+    update: jest.fn(),
+    remove: jest.fn(),
+  };
+
+  beforeEach(async () => {
+    const module: TestingModule = await Test.createTestingModule({
+      controllers: [UsersController],
+      providers: [
+        { provide: UsersService, useValue: mockUsersService },
+      ],
+    })
+      .overrideGuard(JwtAuthGuard)
+      .useValue({ canActivate: jest.fn(() => true) })
+      .overrideGuard(RolesGuard)
+      .useValue({ canActivate: jest.fn(() => true) })
+      .compile();
+
+    controller = module.get<UsersController>(UsersController);
+    usersService = module.get<UsersService>(UsersService);
+  });
+
+  afterEach(() => {
+    jest.clearAllMocks();
+  });
+
+  it('should be defined', () => {
+    expect(controller).toBeDefined();
+  });
+
+  describe('create', () => {
+    it('should call usersService.create with createUserDto', async () => {
+      const createUserDto = {
+        email: 'test@example.com',
+        password: 'password',
+        name: 'Test User',
+      };
+      const expectedResult = {
+        id: '1',
+        email: 'test@example.com',
+        name: 'Test User',
+        role: 'USER',
+      };
+
+      mockUsersService.create.mockResolvedValue(expectedResult);
+
+      const result = await controller.create(createUserDto);
+
+      expect(result).toEqual(expectedResult);
+      expect(usersService.create).toHaveBeenCalledWith(createUserDto);
+    });
+  });
+
+  describe('findAll', () => {
+    it('should call usersService.findAll', async () => {
+      const expectedResult = [
+        {
+          id: '1',
+          email: 'user1@example.com',
+          name: 'User 1',
+          role: 'USER',
+        },
+        {
+          id: '2',
+          email: 'user2@example.com',
+          name: 'User 2',
+          role: 'ADMIN',
+        },
+      ];
+
+      mockUsersService.findAll.mockResolvedValue(expectedResult);
+
+      const result = await controller.findAll();
+
+      expect(result).toEqual(expectedResult);
+      expect(usersService.findAll).toHaveBeenCalled();
+    });
+  });
+
+  describe('findOne', () => {
+    it('should call usersService.findOne with id', async () => {
+      const id = '1';
+      const expectedResult = {
+        id: '1',
+        email: 'test@example.com',
+        name: 'Test User',
+        role: 'USER',
+      };
+
+      mockUsersService.findOne.mockResolvedValue(expectedResult);
+
+      const result = await controller.findOne(id);
+
+      expect(result).toEqual(expectedResult);
+      expect(usersService.findOne).toHaveBeenCalledWith(id);
+    });
+  });
+
+  describe('update', () => {
+    it('should call usersService.update with id and updateUserDto', async () => {
+      const id = '1';
+      const updateUserDto = {
+        firstName: 'Updated',
+        lastName: 'Name'
+      };
+      const expectedResult = {
+        id: '1',
+        email: 'test@example.com',
+        firstName: 'Updated',
+        lastName: 'Name',
+        role: 'USER',
+      };
+
+      mockUsersService.update.mockResolvedValue(expectedResult);
+
+      const result = await controller.update(id, updateUserDto);
+
+      expect(result).toEqual(expectedResult);
+      expect(usersService.update).toHaveBeenCalledWith(id, updateUserDto);
+    });
+  });
path: src/users/users.controller.spec.ts
user: login: coderabbitai[bot]
body: _💡 Verification agent_

<details>
<summary>🧩 Analysis chain</summary>

**Inconsistency in user DTO properties in update test.**

While the test logic is correct, there's an inconsistency between the properties used in the update test and other tests:

- The create test uses `name` property
- The update test uses `firstName` and `lastName` properties
- The expected result has both `firstName`, `lastName` and `email` properties

This might indicate an inconsistency in how user data is structured across different operations.

---

🏁 Script executed:

```shell
#!/bin/bash
# Check for user model structure consistency
echo "Checking user model structure..."
rg -A 5 "interface.*User|type.*User|class.*User" --type ts
echo "=================\nChecking create DTO structure..."
rg -A 5 "interface.*CreateUser|type.*CreateUser|class.*CreateUser" --type ts
echo "=================\nChecking update DTO structure..."
rg -A 5 "interface.*UpdateUser|type.*UpdateUser|class.*UpdateUser" --type ts
```

Length of output: 2518

---

🏁 Script executed:

```shell
#!/bin/bash
echo "----- CreateUserDto (first 200 lines) -----"
head -n 200 src/users/dto/create-user.dto.ts || true

echo "----- 'create' tests in users.controller.spec.ts -----"
rg -n "describe\('create'" -A 15 src/users/users.controller.spec.ts || true
```

Length of output: 1173

---

🏁 Script executed:

```shell
#!/bin/bash
echo "----- Full contents of update-user.dto.ts -----"
cat src/users/dto/update-user.dto.ts
```

Length of output: 471

---



</details>

**Unify user name properties in the create test**

The `CreateUserDto` defines `firstName?` and `lastName?`, but the current `create` test uses a non-existent `name` property. Update the test to match the DTO (and the update test) by using `firstName` and `lastName`.

• In src/users/users.controller.spec.ts (around lines 44–60):  
  - Replace the `createUserDto` object to use `firstName` and `lastName` instead of `name`.  
  - Update the `expectedResult` accordingly.  

Suggested diff:
```diff
--- a/src/users/users.controller.spec.ts
+++ b/src/users/users.controller.spec.ts
@@ describe('create', () => {
-      const createUserDto = {
-        email: 'test@example.com',
-        password: 'password',
-        name: 'Test User',
-      };
-      const expectedResult = {
-        id: '1',
-        email: 'test@example.com',
-        name: 'Test User',
-        role: 'USER',
-      };
+      const createUserDto = {
+        email: 'test@example.com',
+        password: 'password',
+        firstName: 'Test',
+        lastName: 'User',
+      };
+      const expectedResult = {
+        id: '1',
+        email: 'test@example.com',
+        firstName: 'Test',
+        lastName: 'User',
+        role: 'USER',
+      };
```

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In src/users/users.controller.spec.ts around lines 44 to 60, the create test
uses a `name` property which is inconsistent with the `CreateUserDto` definition
and the update test that use `firstName` and `lastName`. To fix this, replace
the `createUserDto` object in the create test to use `firstName` and `lastName`
properties instead of `name`, and update the `expectedResult` to reflect these
changes for consistency across tests.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-17T13:05:31Z
author_association: NONE
diff_hunk: @@ -0,0 +1,404 @@
+import { Test, TestingModule } from '@nestjs/testing';
+import { UsersService } from './users.service';
+import { PrismaService } from '../prisma/prisma.service';
+import { ConflictException, NotFoundException } from '@nestjs/common';
+import * as bcrypt from 'bcrypt';
+
+jest.mock('bcrypt');
+
+describe('UsersService', () => {
+  let service: UsersService;
+  let prismaService: PrismaService;
+
+  const mockPrismaService = {
+    user: {
+      create: jest.fn(),
+      findMany: jest.fn(),
+      findUnique: jest.fn(),
+      update: jest.fn(),
+      delete: jest.fn(),
+    },
+  };
+
+  beforeEach(async () => {
+    const module: TestingModule = await Test.createTestingModule({
+      providers: [
+        UsersService,
+        { provide: PrismaService, useValue: mockPrismaService },
+      ],
+    }).compile();
+
+    service = module.get<UsersService>(UsersService);
+    prismaService = module.get<PrismaService>(PrismaService);
+  });
+
+  afterEach(() => {
+    jest.clearAllMocks();
+  });
+
+  it('should be defined', () => {
+    expect(service).toBeDefined();
+  });
+
+  describe('create', () => {
+    const createUserDto = {
+      email: 'test@example.com',
+      password: 'password',
+      name: 'Test User',
+    };
+
+    it('should create a new user and return it without password', async () => {
+      const hashedPassword = 'hashedPassword';
+      const createdUser = {
+        id: '1',
+        email: 'test@example.com',
+        password: hashedPassword,
+        name: 'Test User',
+        role: 'USER',
+      };
+
+      mockPrismaService.user.findUnique.mockResolvedValue(null);
+      (bcrypt.hash as jest.Mock).mockResolvedValue(hashedPassword);
+      mockPrismaService.user.create.mockResolvedValue(createdUser);
+
+      const result = await service.create(createUserDto);
+
+      expect(result).toEqual({
+        id: '1',
+        email: 'test@example.com',
+        name: 'Test User',
+        role: 'USER',
+      });
+      expect(prismaService.user.findUnique).toHaveBeenCalledWith({
+        where: { email: createUserDto.email },
+      });
+      expect(bcrypt.hash).toHaveBeenCalledWith(createUserDto.password, 10);
+      expect(prismaService.user.create).toHaveBeenCalledWith({
+        data: {
+          ...createUserDto,
+          password: hashedPassword,
+        },
+      });
+    });
+
+    it('should throw ConflictException if email already exists', async () => {
+      const existingUser = {
+        id: '1',
+        email: 'test@example.com',
+        password: 'hashedPassword',
+      };
+
+      mockPrismaService.user.findUnique.mockResolvedValue(existingUser);
+
+      await expect(service.create(createUserDto)).rejects.toThrow(ConflictException);
+      expect(prismaService.user.findUnique).toHaveBeenCalledWith({
+        where: { email: createUserDto.email },
+      });
+      expect(bcrypt.hash).not.toHaveBeenCalled();
+      expect(prismaService.user.create).not.toHaveBeenCalled();
+    });
+  });
+
+  describe('findAll', () => {
+    it('should return all users without passwords', async () => {
+      const users = [
+        {
+          id: '1',
+          email: 'user1@example.com',
+          password: 'hashedPassword1',
+          name: 'User 1',
+          role: 'USER',
+        },
+        {
+          id: '2',
+          email: 'user2@example.com',
+          password: 'hashedPassword2',
+          name: 'User 2',
+          role: 'ADMIN',
+        },
+      ];
+
+      mockPrismaService.user.findMany.mockResolvedValue(users);
+
+      const result = await service.findAll();
+
+      expect(result).toEqual([
+        {
+          id: '1',
+          email: 'user1@example.com',
+          name: 'User 1',
+          role: 'USER',
+        },
+        {
+          id: '2',
+          email: 'user2@example.com',
+          name: 'User 2',
+          role: 'ADMIN',
+        },
+      ]);
+      expect(prismaService.user.findMany).toHaveBeenCalled();
+    });
+  });
+
+  describe('findOne', () => {
+    it('should return a user without password when found', async () => {
+      const user = {
+        id: '1',
+        email: 'test@example.com',
+        password: 'hashedPassword',
+        name: 'Test User',
+        role: 'USER',
+      };
+
+      mockPrismaService.user.findUnique.mockResolvedValue(user);
+
+      const result = await service.findOne('1');
+
+      expect(result).toEqual({
+        id: '1',
+        email: 'test@example.com',
+        name: 'Test User',
+        role: 'USER',
+      });
+      expect(prismaService.user.findUnique).toHaveBeenCalledWith({
+        where: { id: '1' },
+      });
+    });
+
+    it('should throw NotFoundException when user is not found', async () => {
+      mockPrismaService.user.findUnique.mockResolvedValue(null);
+
+      await expect(service.findOne('1')).rejects.toThrow(NotFoundException);
+      expect(prismaService.user.findUnique).toHaveBeenCalledWith({
+        where: { id: '1' },
+      });
+    });
+  });
+
+  describe('findByEmail', () => {
+    it('should return a user when found by email', async () => {
+      const user = {
+        id: '1',
+        email: 'test@example.com',
+        password: 'hashedPassword',
+        name: 'Test User',
+        role: 'USER',
+      };
+
+      mockPrismaService.user.findUnique.mockResolvedValue(user);
+
+      const result = await service.findByEmail('test@example.com');
+
+      expect(result).toEqual(user);
+      expect(prismaService.user.findUnique).toHaveBeenCalledWith({
+        where: { email: 'test@example.com' },
+      });
+    });
+
+    it('should return null when user is not found by email', async () => {
+      mockPrismaService.user.findUnique.mockResolvedValue(null);
+
+      const result = await service.findByEmail('nonexistent@example.com');
+
+      expect(result).toBeNull();
+      expect(prismaService.user.findUnique).toHaveBeenCalledWith({
+        where: { email: 'nonexistent@example.com' },
+      });
+    });
+  });
+
+  describe('update', () => {
+    const updateUserDto = {
+      firstName: 'Updated',
+      lastName: 'Name',
+    };
+
+    it('should update a user and return it without password', async () => {
+      const user = {
+        id: '1',
+        email: 'test@example.com',
+        password: 'hashedPassword',
+        name: 'Test User',
+        role: 'USER',
+      };
+
+      const updatedUser = {
+        id: '1',
+        email: 'test@example.com',
+        password: 'hashedPassword',
+        firstName: 'Updated',
+        lastName: 'Name',
+        role: 'USER',
+      };
+
+      mockPrismaService.user.findUnique.mockResolvedValueOnce(user);
+      mockPrismaService.user.update.mockResolvedValue(updatedUser);
+
+      const result = await service.update('1', updateUserDto);
+
+      expect(result).toEqual({
+        id: '1',
+        email: 'test@example.com',
+        firstName: 'Updated',
+        lastName: 'Name',
+        role: 'USER',
+      });
+      expect(prismaService.user.findUnique).toHaveBeenCalledWith({
+        where: { id: '1' },
+      });
+      expect(prismaService.user.update).toHaveBeenCalledWith({
+        where: { id: '1' },
+        data: updateUserDto,
+      });
+    });
+
+    it('should hash password when updating password', async () => {
+      const updateWithPasswordDto = {
+        password: 'newPassword',
+      };
+      const hashedPassword = 'newHashedPassword';
+      const user = {
+        id: '1',
+        email: 'test@example.com',
+        password: 'hashedPassword',
+        name: 'Test User',
+        role: 'USER',
+      };
+      const updatedUser = {
+        ...user,
+        password: hashedPassword,
+      };
+
+      mockPrismaService.user.findUnique.mockResolvedValueOnce(user);
+      (bcrypt.hash as jest.Mock).mockResolvedValue(hashedPassword);
+      mockPrismaService.user.update.mockResolvedValue(updatedUser);
+
+      await service.update('1', updateWithPasswordDto);
+
+      expect(bcrypt.hash).toHaveBeenCalledWith('newPassword', 10);
+      expect(prismaService.user.update).toHaveBeenCalledWith({
+        where: { id: '1' },
+        data: { password: hashedPassword },
+      });
+    });
+
+    it('should throw ConflictException when updating email to one that already exists', async () => {
+      const updateEmailDto = {
+        email: 'existing@example.com',
+      };
+      const user = {
+        id: '1',
+        email: 'test@example.com',
+        password: 'hashedPassword',
+        name: 'Test User',
+        role: 'USER',
+      };
+      const existingUser = {
+        id: '2',
+        email: 'existing@example.com',
+        password: 'hashedPassword',
+        name: 'Existing User',
+        role: 'USER',
+      };
+
+      mockPrismaService.user.findUnique
+        .mockResolvedValueOnce(user) // First call for findOne
+        .mockResolvedValueOnce(existingUser); // Second call for checking email
+
+      await expect(service.update('1', updateEmailDto)).rejects.toThrow(ConflictException);
+      expect(prismaService.user.findUnique).toHaveBeenCalledWith({
+        where: { id: '1' },
+      });
+      expect(prismaService.user.findUnique).toHaveBeenCalledWith({
+        where: { email: updateEmailDto.email },
+      });
+      expect(prismaService.user.update).not.toHaveBeenCalled();
+    });
+
+    it('should not throw ConflictException when updating email to the same email', async () => {
+      const updateEmailDto = {
+        email: 'test@example.com',
+      };
+      const user = {
+        id: '1',
+        email: 'test@example.com',
+        password: 'hashedPassword',
+        name: 'Test User',
+        role: 'USER',
+      };
+
+      mockPrismaService.user.findUnique
+        .mockResolvedValueOnce(user) // First call for findOne
+        .mockResolvedValueOnce(user); // Second call for checking email
+
+      mockPrismaService.user.update.mockResolvedValue(user);
+
+      await service.update('1', updateEmailDto);
+
+      expect(prismaService.user.update).toHaveBeenCalledWith({
+        where: { id: '1' },
+        data: updateEmailDto,
+      });
+    });
+
+    it('should throw NotFoundException when user to update is not found', async () => {
+      mockPrismaService.user.findUnique.mockResolvedValue(null);
+
+      await expect(service.update('1', updateUserDto)).rejects.toThrow(NotFoundException);
+      expect(prismaService.user.findUnique).toHaveBeenCalledWith({
+        where: { id: '1' },
+      });
+      expect(prismaService.user.update).not.toHaveBeenCalled();
+    });
+  });
+
+  describe('remove', () => {
+    it('should delete a user and return success message', async () => {
+      const user = {
+        id: '1',
+        email: 'test@example.com',
+        password: 'hashedPassword',
+        name: 'Test User',
+        role: 'USER',
+      };
+
+      mockPrismaService.user.findUnique.mockResolvedValue(user);
+      mockPrismaService.user.delete.mockResolvedValue(user);
+
+      const result = await service.remove('1');
+
+      expect(result).toEqual({ message: 'User deleted successfully' });
+      expect(prismaService.user.findUnique).toHaveBeenCalledWith({
+        where: { id: '1' },
+      });
+      expect(prismaService.user.delete).toHaveBeenCalledWith({
+        where: { id: '1' },
+      });
+    });
+
+    it('should throw NotFoundException when user to delete is not found', async () => {
+      mockPrismaService.user.findUnique.mockResolvedValue(null);
+
+      await expect(service.remove('1')).rejects.toThrow(NotFoundException);
+      expect(prismaService.user.findUnique).toHaveBeenCalledWith({
+        where: { id: '1' },
+      });
+      expect(prismaService.user.delete).not.toHaveBeenCalled();
+    });
+  });
+
+  describe('hashPassword', () => {
+    it('should hash password with bcrypt', async () => {
+      const password = 'password';
+      const hashedPassword = 'hashedPassword';
+
+      (bcrypt.hash as jest.Mock).mockResolvedValue(hashedPassword);
+
+      // Access private method using any type
+      const result = await (service as any).hashPassword(password);
+
+      expect(result).toBe(hashedPassword);
+      expect(bcrypt.hash).toHaveBeenCalledWith(password, 10);
+    });
+  });
+});
path: src/users/users.service.spec.ts
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**Consider testing private methods through public interfaces**

While directly testing the private hashPassword method works, it's generally better practice to test private methods through their public interfaces to avoid creating brittle tests that break when implementation details change.

```diff
-  describe('hashPassword', () => {
-    it('should hash password with bcrypt', async () => {
-      const password = 'password';
-      const hashedPassword = 'hashedPassword';
-
-      (bcrypt.hash as jest.Mock).mockResolvedValue(hashedPassword);
-
-      // Access private method using any type
-      const result = await (service as any).hashPassword(password);
-
-      expect(result).toBe(hashedPassword);
-      expect(bcrypt.hash).toHaveBeenCalledWith(password, 10);
-    });
-  });
```

Consider testing password hashing indirectly through the `create` or `update` methods instead, as you've already done in those tests.


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion

```

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In src/users/users.service.spec.ts around lines 390 to 404, avoid directly
testing the private hashPassword method. Instead, remove or refactor this test
to verify password hashing indirectly by calling public methods like create or
update that internally use hashPassword. This ensures tests remain stable and
focus on observable behavior rather than implementation details.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-17T13:05:32Z
author_association: NONE
diff_hunk: @@ -0,0 +1,101 @@
+import { Test, TestingModule } from '@nestjs/testing';
+import { RolesGuard } from './roles.guard';
+import { Reflector } from '@nestjs/core';
+import { ExecutionContext } from '@nestjs/common';
+import { Role } from '../../common/enums/role.enum';
+
+describe('RolesGuard', () => {
+  let guard: RolesGuard;
+  let reflector: Reflector;
+
+  const mockReflector = {
+    getAllAndOverride: jest.fn(),
+  };
+
+  beforeEach(async () => {
+    const module: TestingModule = await Test.createTestingModule({
+      providers: [
+        RolesGuard,
+        { provide: Reflector, useValue: mockReflector },
+      ],
+    }).compile();
+
+    guard = module.get<RolesGuard>(RolesGuard);
+    reflector = module.get<Reflector>(Reflector);
+  });
+
+  afterEach(() => {
+    jest.clearAllMocks();
+  });
+
+  it('should be defined', () => {
+    expect(guard).toBeDefined();
+  });
+
+  describe('canActivate', () => {
+    let mockExecutionContext: ExecutionContext;
+
+    beforeEach(() => {
+      mockExecutionContext = {
+        getHandler: jest.fn(),
+        getClass: jest.fn(),
+        switchToHttp: jest.fn().mockReturnValue({
+          getRequest: jest.fn().mockReturnValue({
+            user: {
+              role: Role.ADMIN,
+            },
+          }),
+        }),
+      } as unknown as ExecutionContext;
+    });
+
+    it('should return true if no roles are required', () => {
+      mockReflector.getAllAndOverride.mockReturnValue(undefined);
+
+      const result = guard.canActivate(mockExecutionContext);
+
+      expect(result).toBe(true);
+      expect(reflector.getAllAndOverride).toHaveBeenCalledWith('roles', [
+        mockExecutionContext.getHandler(),
+        mockExecutionContext.getClass(),
+      ]);
+    });
+
+    it('should return true if user has required role', () => {
+      mockReflector.getAllAndOverride.mockReturnValue([Role.ADMIN]);
+
+      const result = guard.canActivate(mockExecutionContext);
+
+      expect(result).toBe(true);
+      expect(reflector.getAllAndOverride).toHaveBeenCalledWith('roles', [
+        mockExecutionContext.getHandler(),
+        mockExecutionContext.getClass(),
+      ]);
+    });
+
+    it('should return false if user does not have required role', () => {
+      mockReflector.getAllAndOverride.mockReturnValue([Role.USER]);
+      
+      // Change user role to ADMIN
+      const request = mockExecutionContext.switchToHttp().getRequest();
+      request.user.role = Role.ADMIN;
+
+      // Test with required role as USER
+      mockReflector.getAllAndOverride.mockReturnValue([Role.USER]);
+      
+      // Change user role to something not matching required roles
+      const httpContext = mockExecutionContext.switchToHttp();
+      jest.spyOn(httpContext, 'getRequest').mockReturnValue({
+        user: { role: 'OTHER_ROLE' },
+      });
+
+      const result = guard.canActivate(mockExecutionContext);
+
+      expect(result).toBe(false);
+      expect(reflector.getAllAndOverride).toHaveBeenCalledWith('roles', [
+        mockExecutionContext.getHandler(),
+        mockExecutionContext.getClass(),
+      ]);
+    });
path: src/auth/guards/roles.guard.spec.ts
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**Simplify the test for when user doesn't have required role**

The current implementation is confusing with multiple role changes. There's redundant code in lines 79-81 that changes the user role to ADMIN (already set in the mock context), and then another change in lines 86-90.

```diff
  it('should return false if user does not have required role', () => {
    mockReflector.getAllAndOverride.mockReturnValue([Role.USER]);
    
-    // Change user role to ADMIN
-    const request = mockExecutionContext.switchToHttp().getRequest();
-    request.user.role = Role.ADMIN;
-
-    // Test with required role as USER
-    mockReflector.getAllAndOverride.mockReturnValue([Role.USER]);
    
    // Change user role to something not matching required roles
    const httpContext = mockExecutionContext.switchToHttp();
    jest.spyOn(httpContext, 'getRequest').mockReturnValue({
      user: { role: 'OTHER_ROLE' },
    });

    const result = guard.canActivate(mockExecutionContext);

    expect(result).toBe(false);
    expect(reflector.getAllAndOverride).toHaveBeenCalledWith('roles', [
      mockExecutionContext.getHandler(),
      mockExecutionContext.getClass(),
    ]);
  });
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion
    it('should return false if user does not have required role', () => {
      mockReflector.getAllAndOverride.mockReturnValue([Role.USER]);

      // Change user role to something not matching required roles
      const httpContext = mockExecutionContext.switchToHttp();
      jest.spyOn(httpContext, 'getRequest').mockReturnValue({
        user: { role: 'OTHER_ROLE' },
      });

      const result = guard.canActivate(mockExecutionContext);

      expect(result).toBe(false);
      expect(reflector.getAllAndOverride).toHaveBeenCalledWith('roles', [
        mockExecutionContext.getHandler(),
        mockExecutionContext.getClass(),
      ]);
    });
```

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In src/auth/guards/roles.guard.spec.ts between lines 76 and 99, simplify the
test by removing redundant role changes. Keep only one clear setup where the
user's role does not match the required roles returned by the reflector mock.
Remove the initial change to ADMIN role and the duplicate mockReturnValue call,
ensuring the test directly sets the user role to a non-matching value and
verifies canActivate returns false.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-17T13:05:32Z
author_association: NONE

PR comments:
user: login: coderabbitai[bot]
created_at: 2025-05-17T13:00:47Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- This is an auto-generated comment: failure by coderabbit.ai -->

> [!CAUTION]
> ## Review failed
> 
> The pull request is closed.

<!-- end of auto-generated comment: failure by coderabbit.ai -->
<!-- walkthrough_start -->

## Summary by CodeRabbit

- **Tests**
	- Added comprehensive unit and integration test suites for authentication, authorization, user management, and database service functionalities.
- **Chores**
	- Updated test coverage configuration to enforce 100% coverage and exclude specific files from reports.
	- Added Codecov configuration to require full coverage and integrate with CI status checks.
## Summary by CodeRabbit

- **Tests**
  - Added comprehensive unit tests for authentication, authorization, user management, and database service components.
  - Introduced test suites covering controllers, services, guards, strategies, and decorators to ensure robust application behavior.
- **Chores**
  - Updated test coverage configuration to enforce 100% coverage and refined which files are included in coverage reports.
  - Added a configuration file for Codecov to maintain strict coverage standards and improve CI integration.

## Walkthrough

This update introduces comprehensive unit and integration test suites for authentication, authorization, user management, and database service components in a NestJS application. It also refines the Jest configuration in `package.json` to enforce strict coverage requirements and exclude specific files from coverage reports. No changes were made to exported or public entity declarations.

## Changes

| File(s)                                                                                      | Change Summary                                                                                                                                                                                                                                                                                                                                                                                     |
|----------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `package.json`                                                                               | Updated Jest configuration: expanded `collectCoverageFrom` exclusions and added a global `coverageThreshold` requiring 100% coverage for branches, functions, lines, and statements.                                                                                                                                                                                                                 |
| `src/auth/auth.controller.spec.ts`                                                           | Added a new test suite for `AuthController`, testing `login`, `register`, and `getProfile` methods with mocked dependencies and guards.                                                                                                                                                                                                                                                            |
| `src/auth/auth.service.spec.ts`                                                              | Added a new test suite for `AuthService`, covering user validation and login logic, including password checks, JWT issuance, and error handling.                                                                                                                                                                                                                                                  |
| `src/auth/decorators/roles.decorator.spec.ts`                                                | Added tests for the `Roles` decorator, ensuring correct metadata is set via `SetMetadata` for single and multiple roles.                                                                                                                                                                                                                                                                            |
| `src/auth/guards/jwt-auth.guard.spec.ts`,<br>`src/auth/guards/local-auth.guard.spec.ts`      | Added test suites for `JwtAuthGuard` and `LocalAuthGuard`, mocking `@nestjs/passport`'s `AuthGuard` and verifying correct strategy usage and guard instantiation.                                                                                                                                                                                                                                  |
| `src/auth/guards/roles.guard.spec.ts`                                                        | Added a test suite for `RolesGuard`, testing role-based access control logic with mocked `Reflector` and execution context.                                                                                                                                                                                                                                                                         |
| `src/auth/strategies/jwt.strategy.spec.ts`                                                   | Added a test suite for `JwtStrategy`, verifying instance creation and payload validation logic.                                                                                                                                                                                                                                                                                                     |
| `src/auth/strategies/local.strategy.spec.ts`                                                 | Added a test suite for `LocalStrategy`, testing the `validate` method for both successful and failed credential validation scenarios using a mocked `AuthService`.                                                                                                                                                                                                                                  |
| `src/prisma/prisma.service.spec.ts`                                                          | Added a test suite for `PrismaService`, testing lifecycle hooks for database connection and disconnection using mocked Prisma client methods.                                                                                                                                                                                                                                                      |
| `src/users/users.controller.spec.ts`                                                         | Added a test suite for `UsersController`, testing all controller methods with mocked `UsersService` and always-allowing guards.                                                                                                                                                                                                                                                                   |
| `src/users/users.service.spec.ts`                                                            | Added a comprehensive test suite for `UsersService`, covering user creation, retrieval, updating, deletion, password hashing, and error handling with mocked Prisma and bcrypt.                                                                                                                                                                                                                     |
| `codecov.yml`                                                                               | Added a new Codecov configuration file enforcing 100% coverage thresholds and specifying comment layout and CI integration behavior.                                                                                                                                                                                                                                                                |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant Client
    participant AuthController
    participant AuthService
    participant UsersService
    participant JwtService

    Client->>AuthController: POST /login (login DTO)
    AuthController->>AuthService: login(login DTO)
    AuthService->>UsersService: findByEmail(email)
    AuthService->>bcrypt: compare(password, hash)
    AuthService-->>AuthController: { access_token, user }
    AuthController-->>Client: JWT + user data

    Client->>AuthController: POST /register (user DTO)
    AuthController->>UsersService: create(user DTO)
    UsersService-->>AuthController: user data
    AuthController-->>Client: user data

    Client->>AuthController: GET /profile
    AuthController-->>Client: user from request
```

```mermaid
sequenceDiagram
    participant UsersController
    participant UsersService
    participant PrismaService

    UsersController->>UsersService: create(user DTO)
    UsersService->>PrismaService: user.create
    PrismaService-->>UsersService: user
    UsersService-->>UsersController: user

    UsersController->>UsersService: findAll()
    UsersService->>PrismaService: user.findMany
    PrismaService-->>UsersService: users[]
    UsersService-->>UsersController: users[]
```

## Possibly related PRs

- **dhiazfathra/nestjs-starter#6**: Also modifies the Jest configuration in `package.json` to refine coverage collection and enforce stricter coverage thresholds, closely related to the current coverage configuration updates.

## Poem

> In the warren, tests abound,  
> Guards and strategies all around.  
> Controllers checked, services too—  
> Coverage now at one hundred, true!  
> With every hop, our code is sound,  
> The burrow’s safe, the bugs are downed.  
> 🐇✨

<!-- walkthrough_end -->
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->

---

<details>
<summary>📜 Recent review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: ASSERTIVE**
**Plan: Pro**


<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 14b052e0795cdfc0b0599ec5fe87739f06b57021 and 91d3e5ee3e5ccb4065049254690063cc62c0a2ff.

</details>

<details>
<summary>📒 Files selected for processing (1)</summary>

* `codecov.yml` (1 hunks)

</details>

</details>
<!-- finishing_touch_checkbox_start -->

<details open="true">
<summary>✨ Finishing Touches</summary>

<details open="true">
<summary>🧪 Generate Unit Tests</summary>

- [ ] <!-- {"checkboxId": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "radioGroupId": "utg-output-choice-group-unknown_comment_id"} -->   Create PR with Unit Tests
- [ ] <!-- {"checkboxId": "6ba7b810-9dad-11d1-80b4-00c04fd430c8", "radioGroupId": "utg-output-choice-group-unknown_comment_id"} -->   Commit Unit Tests in branch `test/add-tests-for-auth,-users,-&-prisma-modules`
- [ ] <!-- {"checkboxId": "07f1e7d6-8a8e-4e23-9900-8731c2c87f58", "radioGroupId": "utg-output-choice-group-unknown_comment_id"} -->   Post Copyable Unit Tests in Comment

</details>

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---



<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=dhiazfathra/nestjs-starter&utm_content=6):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Explain this complex logic.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai explain this code block.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and explain its main purpose.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

### Support

Need help? Create a ticket on our [support page](https://www.coderabbit.ai/contact-us/support) for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai generate sequence diagram` to generate a sequence diagram of the changes in this PR.
- `@coderabbitai generate unit tests` to generate unit tests for this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: NONE
user: login: codecov-commenter
created_at: 2025-05-17T13:01:19Z
body: ## [Codecov](https://app.codecov.io/gh/dhiazfathra/nestjs-starter/pull/6?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dhiazfathra) Report
All modified and coverable lines are covered by tests :white_check_mark:

| [Flag](https://app.codecov.io/gh/dhiazfathra/nestjs-starter/pull/6/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dhiazfathra) | Coverage Δ | |
|---|---|---|
| [unittests](https://app.codecov.io/gh/dhiazfathra/nestjs-starter/pull/6/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dhiazfathra) | `100.00% <ø> (+95.34%)` | :arrow_up: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dhiazfathra#carryforward-flags-in-the-pull-request-comment) to find out more.

[see 20 files with indirect coverage changes](https://app.codecov.io/gh/dhiazfathra/nestjs-starter/pull/6/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dhiazfathra)

<details><summary> :rocket: New features to boost your workflow: </summary>

- :package: [JS Bundle Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
</details>
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-05-17T14:15:12Z
body: :tada: This PR is included in version 1.1.0 :tada:

The release is available on [GitHub release](https://github.com/dhiazfathra/nestjs-starter/releases/tag/v1.1.0)

Your **[semantic-release](https://github.com/semantic-release/semantic-release)** bot :package::rocket:
author_association: NONE