"""Unit tests for password hashing and validation.""" from app.auth.security import hash_password, validate_password_strength, verify_password class TestPasswordHashing: """Test password hashing functionality.""" def test_hash_password_returns_string(self): """Test that hash_password returns a non-empty string.""" password = "TestPassword123" hashed = hash_password(password) assert isinstance(hashed, str) assert len(hashed) > 0 assert hashed != password def test_hash_password_generates_unique_hashes(self): """Test that same password generates different hashes (bcrypt salt).""" password = "TestPassword123" hash1 = hash_password(password) hash2 = hash_password(password) assert hash1 != hash2 # Different salts def test_hash_password_with_special_characters(self): """Test hashing passwords with special characters.""" password = "P@ssw0rd!#$%" hashed = hash_password(password) assert isinstance(hashed, str) assert len(hashed) > 0 def test_hash_password_with_unicode(self): """Test hashing passwords with unicode characters.""" password = "Pässwörd123" hashed = hash_password(password) assert isinstance(hashed, str) assert len(hashed) > 0 class TestPasswordVerification: """Test password verification functionality.""" def test_verify_password_correct_password(self): """Test that correct password verifies successfully.""" password = "TestPassword123" hashed = hash_password(password) assert verify_password(password, hashed) is True def test_verify_password_incorrect_password(self): """Test that incorrect password fails verification.""" password = "TestPassword123" hashed = hash_password(password) assert verify_password("WrongPassword123", hashed) is False def test_verify_password_case_sensitive(self): """Test that password verification is case-sensitive.""" password = "TestPassword123" hashed = hash_password(password) assert verify_password("testpassword123", hashed) is False assert verify_password("TESTPASSWORD123", hashed) is False def test_verify_password_empty_string(self): """Test that empty password fails verification.""" password = "TestPassword123" hashed = hash_password(password) assert verify_password("", hashed) is False def test_verify_password_with_special_characters(self): """Test verification of passwords with special characters.""" password = "P@ssw0rd!#$%" hashed = hash_password(password) assert verify_password(password, hashed) is True assert verify_password("P@ssw0rd!#$", hashed) is False # Missing last char def test_verify_password_invalid_hash_format(self): """Test that invalid hash format returns False.""" password = "TestPassword123" assert verify_password(password, "invalid_hash") is False assert verify_password(password, "") is False class TestPasswordStrengthValidation: """Test password strength validation.""" def test_validate_password_valid_password(self): """Test that valid passwords pass validation.""" valid_passwords = [ "Password123", "Abcdef123", "SecureP@ss1", "MyP4ssword", ] for password in valid_passwords: is_valid, error = validate_password_strength(password) assert is_valid is True, f"Password '{password}' should be valid" assert error == "" def test_validate_password_too_short(self): """Test that passwords shorter than 8 characters fail.""" short_passwords = [ "Pass1", "Abc123", "Short1A", ] for password in short_passwords: is_valid, error = validate_password_strength(password) assert is_valid is False assert "at least 8 characters" in error def test_validate_password_no_uppercase(self): """Test that passwords without uppercase letters fail.""" passwords = [ "password123", "mypassword1", "lowercase8", ] for password in passwords: is_valid, error = validate_password_strength(password) assert is_valid is False assert "uppercase letter" in error def test_validate_password_no_lowercase(self): """Test that passwords without lowercase letters fail.""" passwords = [ "PASSWORD123", "MYPASSWORD1", "UPPERCASE8", ] for password in passwords: is_valid, error = validate_password_strength(password) assert is_valid is False assert "lowercase letter" in error def test_validate_password_no_number(self): """Test that passwords without numbers fail.""" passwords = [ "Password", "MyPassword", "NoNumbers", ] for password in passwords: is_valid, error = validate_password_strength(password) assert is_valid is False assert "one number" in error def test_validate_password_edge_cases(self): """Test password validation edge cases.""" # Exactly 8 characters, all requirements met is_valid, error = validate_password_strength("Abcdef12") assert is_valid is True assert error == "" # Very long password is_valid, error = validate_password_strength("A" * 100 + "a1") assert is_valid is True # Empty password is_valid, error = validate_password_strength("") assert is_valid is False def test_validate_password_with_special_chars(self): """Test that special characters don't interfere with validation.""" passwords_with_special = [ "P@ssw0rd!", "MyP@ss123", "Test#Pass1", ] for password in passwords_with_special: is_valid, error = validate_password_strength(password) assert is_valid is True, f"Password '{password}' should be valid" assert error == "" class TestPasswordSecurityProperties: """Test security properties of password handling.""" def test_hashed_password_not_reversible(self): """Test that hashed passwords cannot be easily reversed.""" password = "TestPassword123" hashed = hash_password(password) # Hash should not contain original password assert password not in hashed assert password.lower() not in hashed.lower() def test_different_passwords_different_hashes(self): """Test that different passwords produce different hashes.""" password1 = "TestPassword123" password2 = "TestPassword124" # Only last char different hash1 = hash_password(password1) hash2 = hash_password(password2) assert hash1 != hash2 def test_hashed_password_length_consistent(self): """Test that bcrypt hashes have consistent length.""" passwords = ["Short1A", "MediumPassword123", "VeryLongPasswordWithLotsOfCharacters123"] hashes = [hash_password(p) for p in passwords] # All bcrypt hashes should be 60 characters for hashed in hashes: assert len(hashed) == 60 def test_verify_handles_timing_attack_resistant(self): """Test that verification doesn't leak timing information (bcrypt property).""" # This is more of a documentation test - bcrypt is designed to be timing-attack resistant password = "TestPassword123" hashed = hash_password(password) # Both should take roughly the same time (bcrypt property) verify_password("WrongPassword123", hashed) verify_password(password, hashed) # No actual timing measurement here, just documenting the property assert True