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