105 lines
3.5 KiB
Python
105 lines
3.5 KiB
Python
"""
|
|
Unit tests for password security utilities.
|
|
|
|
Tests the bcrypt-based password hashing and verification functions
|
|
including edge cases and error handling.
|
|
"""
|
|
|
|
import pytest
|
|
from app.utils.security import hash_password, verify_password
|
|
|
|
|
|
def test_i_can_hash_password():
|
|
"""Test that a password is correctly hashed and different from original."""
|
|
password = "my_secure_password"
|
|
hashed = hash_password(password)
|
|
|
|
# Hash should be different from original password
|
|
assert hashed != password
|
|
|
|
# Hash should be a non-empty string
|
|
assert isinstance(hashed, str)
|
|
assert len(hashed) > 0
|
|
|
|
# Hash should start with bcrypt identifier
|
|
assert hashed.startswith("$2b$")
|
|
|
|
|
|
def test_same_password_generates_different_hashes():
|
|
"""Test that the salt generates different hashes for the same password."""
|
|
password = "identical_password"
|
|
|
|
hash1 = hash_password(password)
|
|
hash2 = hash_password(password)
|
|
|
|
# Same password should generate different hashes due to salt
|
|
assert hash1 != hash2
|
|
|
|
# But both should be valid bcrypt hashes
|
|
assert hash1.startswith("$2b$")
|
|
assert hash2.startswith("$2b$")
|
|
|
|
|
|
def test_i_can_verify_correct_password():
|
|
"""Test that a correct password is validated against its hash."""
|
|
password = "correct_password"
|
|
hashed = hash_password(password)
|
|
|
|
# Correct password should verify successfully
|
|
assert verify_password(password, hashed) is True
|
|
|
|
|
|
def test_i_cannot_verify_incorrect_password():
|
|
"""Test that an incorrect password is rejected."""
|
|
password = "correct_password"
|
|
wrong_password = "wrong_password"
|
|
hashed = hash_password(password)
|
|
|
|
# Wrong password should fail verification
|
|
assert verify_password(wrong_password, hashed) is False
|
|
|
|
|
|
def test_i_cannot_hash_empty_password():
|
|
"""Test that empty passwords are rejected during hashing."""
|
|
# Empty string should raise ValueError
|
|
with pytest.raises(ValueError, match="Password cannot be empty or None"):
|
|
hash_password("")
|
|
|
|
# None should raise ValueError
|
|
with pytest.raises(ValueError, match="Password cannot be empty or None"):
|
|
hash_password(None)
|
|
|
|
|
|
def test_i_cannot_verify_with_malformed_hash():
|
|
"""Test that malformed hashes are rejected during verification."""
|
|
password = "test_password"
|
|
malformed_hash = "not_a_valid_bcrypt_hash"
|
|
|
|
# Malformed hash should raise RuntimeError
|
|
with pytest.raises(RuntimeError, match="Invalid hash format"):
|
|
verify_password(password, malformed_hash)
|
|
|
|
|
|
def test_i_cannot_verify_with_none_values():
|
|
"""Test that None values are rejected during verification."""
|
|
password = "test_password"
|
|
hashed = hash_password(password)
|
|
|
|
# None password should raise ValueError
|
|
with pytest.raises(ValueError, match="Password and hashed_password cannot be empty or None"):
|
|
verify_password(None, hashed)
|
|
|
|
# None hash should raise ValueError
|
|
with pytest.raises(ValueError, match="Password and hashed_password cannot be empty or None"):
|
|
verify_password(password, None)
|
|
|
|
# Both None should raise ValueError
|
|
with pytest.raises(ValueError, match="Password and hashed_password cannot be empty or None"):
|
|
verify_password(None, None)
|
|
|
|
# Empty strings should also raise ValueError
|
|
with pytest.raises(ValueError, match="Password and hashed_password cannot be empty or None"):
|
|
verify_password("", hashed)
|
|
|
|
with pytest.raises(ValueError, match="Password and hashed_password cannot be empty or None"):
|
|
verify_password(password, "") |