/** * Domain Service Tests: PasswordHashingService * * Tests for password hashing and verification business logic */ import { describe, it, expect, beforeEach } from 'vitest'; import { PasswordHashingService } from './PasswordHashingService'; describe('PasswordHashingService', () => { let service: PasswordHashingService; beforeEach(() => { service = new PasswordHashingService(); }); describe('hash', () => { it('should hash a plain text password', async () => { const plainPassword = 'mySecurePassword123'; const hash = await service.hash(plainPassword); expect(hash).toBeDefined(); expect(typeof hash).toBe('string'); expect(hash.length).toBeGreaterThan(0); // Hash should not be the same as the plain password expect(hash).not.toBe(plainPassword); }); it('should produce different hashes for the same password (with salt)', async () => { const plainPassword = 'mySecurePassword123'; const hash1 = await service.hash(plainPassword); const hash2 = await service.hash(plainPassword); // Due to salting, hashes should be different expect(hash1).not.toBe(hash2); }); it('should handle empty string password', async () => { const hash = await service.hash(''); expect(hash).toBeDefined(); expect(typeof hash).toBe('string'); }); it('should handle special characters in password', async () => { const specialPassword = 'P@ssw0rd!#$%^&*()_+-=[]{}|;:,.<>?'; const hash = await service.hash(specialPassword); expect(hash).toBeDefined(); expect(typeof hash).toBe('string'); }); it('should handle unicode characters in password', async () => { const unicodePassword = 'Pässwörd!🔒'; const hash = await service.hash(unicodePassword); expect(hash).toBeDefined(); expect(typeof hash).toBe('string'); }); it('should handle very long passwords', async () => { const longPassword = 'a'.repeat(1000); const hash = await service.hash(longPassword); expect(hash).toBeDefined(); expect(typeof hash).toBe('string'); }); it('should handle whitespace-only password', async () => { const whitespacePassword = ' '; const hash = await service.hash(whitespacePassword); expect(hash).toBeDefined(); expect(typeof hash).toBe('string'); }); }); describe('verify', () => { it('should verify correct password against hash', async () => { const plainPassword = 'mySecurePassword123'; const hash = await service.hash(plainPassword); const isValid = await service.verify(plainPassword, hash); expect(isValid).toBe(true); }); it('should reject incorrect password', async () => { const plainPassword = 'mySecurePassword123'; const hash = await service.hash(plainPassword); const isValid = await service.verify('wrongPassword', hash); expect(isValid).toBe(false); }); it('should reject empty password against hash', async () => { const plainPassword = 'mySecurePassword123'; const hash = await service.hash(plainPassword); const isValid = await service.verify('', hash); expect(isValid).toBe(false); }); it('should handle verification with special characters', async () => { const specialPassword = 'P@ssw0rd!#$%^&*()_+-=[]{}|;:,.<>?'; const hash = await service.hash(specialPassword); const isValid = await service.verify(specialPassword, hash); expect(isValid).toBe(true); }); it('should handle verification with unicode characters', async () => { const unicodePassword = 'Pässwörd!🔒'; const hash = await service.hash(unicodePassword); const isValid = await service.verify(unicodePassword, hash); expect(isValid).toBe(true); }); it('should handle verification with very long passwords', async () => { const longPassword = 'a'.repeat(1000); const hash = await service.hash(longPassword); const isValid = await service.verify(longPassword, hash); expect(isValid).toBe(true); }); it('should handle verification with whitespace-only password', async () => { const whitespacePassword = ' '; const hash = await service.hash(whitespacePassword); const isValid = await service.verify(whitespacePassword, hash); expect(isValid).toBe(true); }); it('should reject verification with null hash', async () => { // bcrypt throws an error when hash is null, which is expected behavior await expect(service.verify('password', null as any)).rejects.toThrow(); }); it('should reject verification with empty hash', async () => { const isValid = await service.verify('password', ''); expect(isValid).toBe(false); }); it('should reject verification with invalid hash format', async () => { const isValid = await service.verify('password', 'invalid-hash-format'); expect(isValid).toBe(false); }); }); describe('Hash Consistency', () => { it('should consistently verify the same password-hash pair', async () => { const plainPassword = 'testPassword123'; const hash = await service.hash(plainPassword); // Verify multiple times const result1 = await service.verify(plainPassword, hash); const result2 = await service.verify(plainPassword, hash); const result3 = await service.verify(plainPassword, hash); expect(result1).toBe(true); expect(result2).toBe(true); expect(result3).toBe(true); }); it('should consistently reject wrong password', async () => { const plainPassword = 'testPassword123'; const wrongPassword = 'wrongPassword'; const hash = await service.hash(plainPassword); // Verify multiple times with wrong password const result1 = await service.verify(wrongPassword, hash); const result2 = await service.verify(wrongPassword, hash); const result3 = await service.verify(wrongPassword, hash); expect(result1).toBe(false); expect(result2).toBe(false); expect(result3).toBe(false); }); }); describe('Security Properties', () => { it('should not leak information about the original password from hash', async () => { const password1 = 'password123'; const password2 = 'password456'; const hash1 = await service.hash(password1); const hash2 = await service.hash(password2); // Hashes should be different expect(hash1).not.toBe(hash2); // Neither hash should contain the original password expect(hash1).not.toContain(password1); expect(hash2).not.toContain(password2); }); it('should handle case sensitivity correctly', async () => { const password1 = 'Password'; const password2 = 'password'; const hash1 = await service.hash(password1); const hash2 = await service.hash(password2); // Should be treated as different passwords const isValid1 = await service.verify(password1, hash1); const isValid2 = await service.verify(password2, hash2); const isCrossValid1 = await service.verify(password1, hash2); const isCrossValid2 = await service.verify(password2, hash1); expect(isValid1).toBe(true); expect(isValid2).toBe(true); expect(isCrossValid1).toBe(false); expect(isCrossValid2).toBe(false); }); }); });