import { PasswordService } from '../../../src/Application/Services/PasswordService'; // Mock bcrypt completely jest.mock('bcrypt'); describe('PasswordService', () => { // Mock functions for bcrypt const mockBcryptHash = jest.fn(); const mockBcryptCompare = jest.fn(); beforeEach(() => { jest.clearAllMocks(); // Reset console.error mock to avoid noise in tests jest.spyOn(console, 'error').mockImplementation(() => {}); // Setup bcrypt mocks const bcrypt = require('bcrypt'); bcrypt.hash = mockBcryptHash; bcrypt.compare = mockBcryptCompare; }); afterEach(() => { jest.restoreAllMocks(); }); describe('hashPassword', () => { it('should hash a valid password successfully', async () => { // Arrange const password = 'validPassword123!'; const hashedPassword = '$2b$12$hashed.password.here'; mockBcryptHash.mockResolvedValue(hashedPassword); // Act const result = await PasswordService.hashPassword(password); // Assert expect(result).toBe(hashedPassword); expect(mockBcryptHash).toHaveBeenCalledWith(password, 12); }); it('should throw error for empty password', async () => { // Arrange const password = ''; // Act & Assert await expect(PasswordService.hashPassword(password)).rejects.toThrow('Password must be a non-empty string'); expect(mockBcryptHash).not.toHaveBeenCalled(); }); it('should throw error for non-string password', async () => { // Arrange const password = null as any; // Act & Assert await expect(PasswordService.hashPassword(password)).rejects.toThrow('Password must be a non-empty string'); expect(mockBcryptHash).not.toHaveBeenCalled(); }); it('should handle bcrypt errors and throw generic error', async () => { // Arrange const password = 'validPassword123!'; mockBcryptHash.mockRejectedValue(new Error('Bcrypt error')); // Act & Assert await expect(PasswordService.hashPassword(password)).rejects.toThrow('Failed to hash password'); expect(mockBcryptHash).toHaveBeenCalledWith(password, 12); }); }); describe('verifyPassword', () => { it('should return true for matching password and hash', async () => { // Arrange const password = 'validPassword123!'; const hashedPassword = '$2b$12$hashed.password.here'; mockBcryptCompare.mockResolvedValue(true); // Act const result = await PasswordService.verifyPassword(password, hashedPassword); // Assert expect(result).toBe(true); expect(mockBcryptCompare).toHaveBeenCalledWith(password, hashedPassword); }); it('should return false for non-matching password and hash', async () => { // Arrange const password = 'wrongPassword'; const hashedPassword = '$2b$12$hashed.password.here'; mockBcryptCompare.mockResolvedValue(false); // Act const result = await PasswordService.verifyPassword(password, hashedPassword); // Assert expect(result).toBe(false); expect(mockBcryptCompare).toHaveBeenCalledWith(password, hashedPassword); }); it('should return false for empty password', async () => { // Arrange const password = ''; const hashedPassword = '$2b$12$hashed.password.here'; // Act const result = await PasswordService.verifyPassword(password, hashedPassword); // Assert expect(result).toBe(false); expect(mockBcryptCompare).not.toHaveBeenCalled(); }); it('should return false for empty hashed password', async () => { // Arrange const password = 'validPassword123!'; const hashedPassword = ''; // Act const result = await PasswordService.verifyPassword(password, hashedPassword); // Assert expect(result).toBe(false); expect(mockBcryptCompare).not.toHaveBeenCalled(); }); it('should return false for non-string inputs', async () => { // Arrange const password = null as any; const hashedPassword = undefined as any; // Act const result = await PasswordService.verifyPassword(password, hashedPassword); // Assert expect(result).toBe(false); expect(mockBcryptCompare).not.toHaveBeenCalled(); }); it('should return false when bcrypt throws error', async () => { // Arrange const password = 'validPassword123!'; const hashedPassword = '$2b$12$hashed.password.here'; mockBcryptCompare.mockRejectedValue(new Error('Bcrypt compare error')); // Act const result = await PasswordService.verifyPassword(password, hashedPassword); // Assert expect(result).toBe(false); expect(mockBcryptCompare).toHaveBeenCalledWith(password, hashedPassword); }); }); describe('validatePasswordStrength', () => { it('should return valid for strong password', () => { // Arrange const password = 'StrongPass123!'; // Act const result = PasswordService.validatePasswordStrength(password); // Assert expect(result.isValid).toBe(true); expect(result.errors).toEqual([]); }); it('should return invalid for short password', () => { // Arrange const password = 'Short1!'; // Act const result = PasswordService.validatePasswordStrength(password); // Assert expect(result.isValid).toBe(false); expect(result.errors).toContain('Password must be at least 8 characters long'); }); it('should return invalid for password without uppercase', () => { // Arrange const password = 'lowercase123!'; // Act const result = PasswordService.validatePasswordStrength(password); // Assert expect(result.isValid).toBe(false); expect(result.errors).toContain('Password must contain at least one uppercase letter'); }); it('should return invalid for password without lowercase', () => { // Arrange const password = 'UPPERCASE123!'; // Act const result = PasswordService.validatePasswordStrength(password); // Assert expect(result.isValid).toBe(false); expect(result.errors).toContain('Password must contain at least one lowercase letter'); }); it('should return invalid for password without numbers', () => { // Arrange const password = 'NoNumbers!'; // Act const result = PasswordService.validatePasswordStrength(password); // Assert expect(result.isValid).toBe(false); expect(result.errors).toContain('Password must contain at least one number'); }); it('should return invalid for password without special characters', () => { // Arrange const password = 'NoSpecial123'; // Act const result = PasswordService.validatePasswordStrength(password); // Assert expect(result.isValid).toBe(false); expect(result.errors).toContain('Password must contain at least one special character'); }); it('should return multiple errors for weak password', () => { // Arrange const password = 'weak'; // Act const result = PasswordService.validatePasswordStrength(password); // Assert expect(result.isValid).toBe(false); expect(result.errors).toHaveLength(4); expect(result.errors).toContain('Password must be at least 8 characters long'); expect(result.errors).toContain('Password must contain at least one uppercase letter'); expect(result.errors).toContain('Password must contain at least one number'); expect(result.errors).toContain('Password must contain at least one special character'); }); it('should handle empty password', () => { // Arrange const password = ''; // Act const result = PasswordService.validatePasswordStrength(password); // Assert expect(result.isValid).toBe(false); expect(result.errors).toContain('Password must be provided as a string'); }); it('should handle null password', () => { // Arrange const password = null as any; // Act const result = PasswordService.validatePasswordStrength(password); // Assert expect(result.isValid).toBe(false); expect(result.errors).toContain('Password must be provided as a string'); }); }); });