Files
webref/backend/tests/auth/test_security.py
Danilo Reyes 948fe591dc
All checks were successful
CI/CD Pipeline / VM Test - security (push) Successful in 7s
CI/CD Pipeline / Backend Linting (push) Successful in 4s
CI/CD Pipeline / VM Test - backend-integration (push) Successful in 11s
CI/CD Pipeline / VM Test - full-stack (push) Successful in 8s
CI/CD Pipeline / VM Test - performance (push) Successful in 8s
CI/CD Pipeline / Nix Flake Check (push) Successful in 38s
CI/CD Pipeline / CI Summary (push) Successful in 0s
CI/CD Pipeline / Frontend Linting (push) Successful in 17s
phase 13
2025-11-02 14:48:03 -06:00

235 lines
7.9 KiB
Python

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